Messenger Online / Offline mekanizması
This commit is contained in:
parent
af726c530a
commit
771ea95e55
11 changed files with 137 additions and 52 deletions
|
|
@ -18,6 +18,7 @@ public class MessengerContactDto
|
||||||
public string FullName { get; set; } = string.Empty;
|
public string FullName { get; set; } = string.Empty;
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
|
public bool IsOnline { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MessengerAttachmentDto
|
public class MessengerAttachmentDto
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ public class OrgChartUserDto
|
||||||
public string FullName { get; set; }
|
public string FullName { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; }
|
||||||
|
public string PhoneNumber { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OrgChartNodeDto
|
public class OrgChartNodeDto
|
||||||
|
|
@ -21,5 +22,8 @@ public class OrgChartNodeDto
|
||||||
public Guid? DepartmentId { get; set; }
|
public Guid? DepartmentId { get; set; }
|
||||||
public string DepartmentName { get; set; }
|
public string DepartmentName { get; set; }
|
||||||
|
|
||||||
|
public Guid? JobPositionId { get; set; }
|
||||||
|
public string JobPositionName { get; set; }
|
||||||
|
|
||||||
public List<OrgChartUserDto> Users { get; set; } = [];
|
public List<OrgChartUserDto> Users { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ public class MessengerAppService : ApplicationService
|
||||||
private readonly IRepository<IdentityUser, Guid> _userRepository;
|
private readonly IRepository<IdentityUser, Guid> _userRepository;
|
||||||
private readonly IRepository<MessengerConversation, Guid> _conversationRepository;
|
private readonly IRepository<MessengerConversation, Guid> _conversationRepository;
|
||||||
private readonly IRepository<MessengerConversationMessage, Guid> _messageRepository;
|
private readonly IRepository<MessengerConversationMessage, Guid> _messageRepository;
|
||||||
|
private readonly IIdentitySessionRepository _identitySessionRepository;
|
||||||
private readonly BlobManager _blobManager;
|
private readonly BlobManager _blobManager;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
|
@ -33,12 +34,14 @@ public class MessengerAppService : ApplicationService
|
||||||
IRepository<IdentityUser, Guid> userRepository,
|
IRepository<IdentityUser, Guid> userRepository,
|
||||||
IRepository<MessengerConversation, Guid> conversationRepository,
|
IRepository<MessengerConversation, Guid> conversationRepository,
|
||||||
IRepository<MessengerConversationMessage, Guid> messageRepository,
|
IRepository<MessengerConversationMessage, Guid> messageRepository,
|
||||||
|
IIdentitySessionRepository identitySessionRepository,
|
||||||
BlobManager blobManager,
|
BlobManager blobManager,
|
||||||
IConfiguration configuration)
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_conversationRepository = conversationRepository;
|
_conversationRepository = conversationRepository;
|
||||||
_messageRepository = messageRepository;
|
_messageRepository = messageRepository;
|
||||||
|
_identitySessionRepository = identitySessionRepository;
|
||||||
_blobManager = blobManager;
|
_blobManager = blobManager;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +74,8 @@ public class MessengerAppService : ApplicationService
|
||||||
.Take(100)
|
.Take(100)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var onlineUserIds = await GetOnlineUserIdsAsync(users.Select(user => user.Id));
|
||||||
|
|
||||||
return users.Select(user =>
|
return users.Select(user =>
|
||||||
{
|
{
|
||||||
var fullName = $"{user.Name} {user.Surname}".Trim();
|
var fullName = $"{user.Name} {user.Surname}".Trim();
|
||||||
|
|
@ -83,7 +88,8 @@ public class MessengerAppService : ApplicationService
|
||||||
Surname = user.Surname,
|
Surname = user.Surname,
|
||||||
FullName = fullName.IsNullOrWhiteSpace() ? user.UserName : fullName,
|
FullName = fullName.IsNullOrWhiteSpace() ? user.UserName : fullName,
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
IsActive = user.IsActive
|
IsActive = user.IsActive,
|
||||||
|
IsOnline = onlineUserIds.Contains(user.Id)
|
||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
@ -238,6 +244,12 @@ public class MessengerAppService : ApplicationService
|
||||||
throw new UserFriendlyException("Bu tenant icinde gecersiz alici var.");
|
throw new UserFriendlyException("Bu tenant icinde gecersiz alici var.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onlineRecipientIds = await GetOnlineUserIdsAsync(recipientIds);
|
||||||
|
if (onlineRecipientIds.Count != recipientIds.Count)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Sadece online kullanicilara mesaj gonderebilirsiniz.");
|
||||||
|
}
|
||||||
|
|
||||||
MessengerConversation? conversation = null;
|
MessengerConversation? conversation = null;
|
||||||
if (input.ConversationId.HasValue)
|
if (input.ConversationId.HasValue)
|
||||||
{
|
{
|
||||||
|
|
@ -406,6 +418,25 @@ public class MessengerAppService : ApplicationService
|
||||||
user.IsActive);
|
user.IsActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<HashSet<Guid>> GetOnlineUserIdsAsync(IEnumerable<Guid> userIds)
|
||||||
|
{
|
||||||
|
var normalizedUserIds = userIds
|
||||||
|
.Where(id => id != Guid.Empty)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (normalizedUserIds.Count == 0)
|
||||||
|
{
|
||||||
|
return new HashSet<Guid>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessions = await _identitySessionRepository.GetListAsync();
|
||||||
|
return sessions
|
||||||
|
.Where(session => normalizedUserIds.Contains(session.UserId))
|
||||||
|
.Select(session => session.UserId)
|
||||||
|
.ToHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<MessengerConversation?> FindConversationByParticipantKeyAsync(string participantKey)
|
private async Task<MessengerConversation?> FindConversationByParticipantKeyAsync(string participantKey)
|
||||||
{
|
{
|
||||||
var query = await _conversationRepository.GetQueryableAsync();
|
var query = await _conversationRepository.GetQueryableAsync();
|
||||||
|
|
|
||||||
|
|
@ -66,23 +66,31 @@ public class OrgChartAppService : PlatformAppService
|
||||||
topPositionByDepartment[department.Id] = selectedTop;
|
topPositionByDepartment[department.Id] = selectedTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodes = departments.Select(d => new OrgChartNodeDto
|
var nodes = departments.Select(d =>
|
||||||
{
|
{
|
||||||
Id = d.Id,
|
topPositionByDepartment.TryGetValue(d.Id, out var topPosition);
|
||||||
Name = d.Name,
|
|
||||||
ParentId = d.ParentId,
|
return new OrgChartNodeDto
|
||||||
Users = users
|
{
|
||||||
.Where(u =>
|
Id = d.Id,
|
||||||
topPositionByDepartment.TryGetValue(d.Id, out var topPosition) &&
|
Name = d.Name,
|
||||||
u.GetJobPositionId() == topPosition.Id)
|
ParentId = d.ParentId,
|
||||||
.Select(u => new OrgChartUserDto
|
JobPositionId = topPosition?.Id,
|
||||||
{
|
JobPositionName = topPosition?.Name,
|
||||||
Id = u.Id,
|
Users = users
|
||||||
FullName = u.GetFullName(),
|
.Where(u =>
|
||||||
Email = u.Email,
|
topPosition != null &&
|
||||||
UserName = u.UserName,
|
u.GetJobPositionId() == topPosition.Id)
|
||||||
})
|
.Select(u => new OrgChartUserDto
|
||||||
.ToList(),
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
FullName = u.GetFullName(),
|
||||||
|
Email = u.Email,
|
||||||
|
UserName = u.UserName,
|
||||||
|
PhoneNumber = u.PhoneNumber
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
|
|
@ -104,6 +112,8 @@ public class OrgChartAppService : PlatformAppService
|
||||||
ParentId = jp.ParentId,
|
ParentId = jp.ParentId,
|
||||||
DepartmentId = jp.DepartmentId,
|
DepartmentId = jp.DepartmentId,
|
||||||
DepartmentName = deptDict.TryGetValue(jp.DepartmentId, out var name) ? name : null,
|
DepartmentName = deptDict.TryGetValue(jp.DepartmentId, out var name) ? name : null,
|
||||||
|
JobPositionId = jp.Id,
|
||||||
|
JobPositionName = jp.Name,
|
||||||
Users = users
|
Users = users
|
||||||
.Where(u => u.GetJobPositionId() == jp.Id)
|
.Where(u => u.GetJobPositionId() == jp.Id)
|
||||||
.Select(u => new OrgChartUserDto
|
.Select(u => new OrgChartUserDto
|
||||||
|
|
@ -112,6 +122,7 @@ public class OrgChartAppService : PlatformAppService
|
||||||
FullName = u.GetFullName(),
|
FullName = u.GetFullName(),
|
||||||
Email = u.Email,
|
Email = u.Email,
|
||||||
UserName = u.UserName,
|
UserName = u.UserName,
|
||||||
|
PhoneNumber = u.PhoneNumber
|
||||||
})
|
})
|
||||||
.ToList(),
|
.ToList(),
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
|
||||||
|
|
@ -1439,12 +1439,6 @@
|
||||||
"DepartmentName": "Yönetim",
|
"DepartmentName": "Yönetim",
|
||||||
"Name": "Genel Müdür"
|
"Name": "Genel Müdür"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"Id": "a2b3c4d5-e6f7-4890-ab12-cd34ef56ab78",
|
|
||||||
"DepartmentName": "Yönetim",
|
|
||||||
"Name": "Genel Müdür Yardımcısı",
|
|
||||||
"ParentName": "Genel Müdür"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Id": "b7c8d9e0-f1a2-4b3c-8d9e-0f1a2b3c4d5e",
|
"Id": "b7c8d9e0-f1a2-4b3c-8d9e-0f1a2b3c4d5e",
|
||||||
"DepartmentName": "Muhasebe",
|
"DepartmentName": "Muhasebe",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import {
|
||||||
import { IoCheckmarkDone, IoChatbubbleEllipsesOutline, IoSend } from 'react-icons/io5'
|
import { IoCheckmarkDone, IoChatbubbleEllipsesOutline, IoSend } from 'react-icons/io5'
|
||||||
|
|
||||||
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024
|
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024
|
||||||
|
const CONTACT_REFRESH_INTERVAL = 30000
|
||||||
|
|
||||||
const formatFileSize = (size: number) => {
|
const formatFileSize = (size: number) => {
|
||||||
if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`
|
if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`
|
||||||
|
|
@ -99,9 +100,25 @@ const MessengerWidget = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
getMessengerContacts(filter)
|
let active = true
|
||||||
.then((response) => setContacts(response.data))
|
|
||||||
.catch(() => setContacts([]))
|
const loadContacts = () => {
|
||||||
|
getMessengerContacts(filter)
|
||||||
|
.then((response) => {
|
||||||
|
if (active) setContacts(response.data)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (active) setContacts([])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadContacts()
|
||||||
|
const timer = window.setInterval(loadContacts, CONTACT_REFRESH_INTERVAL)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false
|
||||||
|
window.clearInterval(timer)
|
||||||
|
}
|
||||||
}, [filter, open])
|
}, [filter, open])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -135,6 +152,9 @@ const MessengerWidget = () => {
|
||||||
[contacts, selectedIds],
|
[contacts, selectedIds],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const canSendToSelectedContacts =
|
||||||
|
selectedContacts.length > 0 && selectedContacts.every((contact) => contact.isOnline)
|
||||||
|
|
||||||
const visibleMessages = useMemo(() => {
|
const visibleMessages = useMemo(() => {
|
||||||
if (selectedIds.length === 0) return []
|
if (selectedIds.length === 0) return []
|
||||||
|
|
||||||
|
|
@ -146,6 +166,14 @@ const MessengerWidget = () => {
|
||||||
}, [auth.user.id, messages, selectedIds])
|
}, [auth.user.id, messages, selectedIds])
|
||||||
|
|
||||||
const toggleContact = (id: string) => {
|
const toggleContact = (id: string) => {
|
||||||
|
const contact = contacts.find((item) => item.id === id)
|
||||||
|
if (!contact?.isOnline) {
|
||||||
|
toast.push(<Notification title="Offline kullanıcılara mesaj gönderilemez" type="warning" />, {
|
||||||
|
placement: 'top-end',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setUnreadByContact((current) => {
|
setUnreadByContact((current) => {
|
||||||
if (!current[id]) return current
|
if (!current[id]) return current
|
||||||
|
|
||||||
|
|
@ -210,6 +238,12 @@ const MessengerWidget = () => {
|
||||||
const sendMessage = async () => {
|
const sendMessage = async () => {
|
||||||
const trimmedText = text.trim()
|
const trimmedText = text.trim()
|
||||||
if (selectedIds.length === 0 || (!trimmedText && attachments.length === 0)) return
|
if (selectedIds.length === 0 || (!trimmedText && attachments.length === 0)) return
|
||||||
|
if (!canSendToSelectedContacts) {
|
||||||
|
toast.push(<Notification title="Sadece online kullanıcılara mesaj gönderebilirsiniz" type="warning" />, {
|
||||||
|
placement: 'top-end',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await messengerSignalR.sendMessage({
|
await messengerSignalR.sendMessage({
|
||||||
|
|
@ -311,16 +345,31 @@ const MessengerWidget = () => {
|
||||||
key={contact.id}
|
key={contact.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleContact(contact.id)}
|
onClick={() => toggleContact(contact.id)}
|
||||||
|
disabled={!contact.isOnline}
|
||||||
className={`mb-1 flex w-full items-center gap-3 rounded-md px-2 py-2 text-left transition ${
|
className={`mb-1 flex w-full items-center gap-3 rounded-md px-2 py-2 text-left transition ${
|
||||||
active
|
active
|
||||||
? 'bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-100'
|
? 'bg-emerald-50 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-100'
|
||||||
: 'hover:bg-white dark:hover:bg-gray-800'
|
: contact.isOnline
|
||||||
|
? 'hover:bg-white dark:hover:bg-gray-800'
|
||||||
|
: 'cursor-not-allowed opacity-60'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Avatar size={36} shape="circle" src={AVATAR_URL(contact.id, contact.tenantId || tenantId)} />
|
<Avatar size={36} shape="circle" src={AVATAR_URL(contact.id, contact.tenantId || tenantId)} />
|
||||||
<span className="min-w-0 flex-1">
|
<span className="min-w-0 flex-1">
|
||||||
<span className="block truncate text-sm font-medium">{contact.fullName}</span>
|
<span className="block truncate text-sm font-medium">{contact.fullName}</span>
|
||||||
<span className="block truncate text-xs text-gray-500">{contact.email || contact.userName}</span>
|
<span className="block truncate text-xs text-gray-500">{contact.email || contact.userName}</span>
|
||||||
|
<span
|
||||||
|
className={`mt-1 inline-flex items-center gap-1 text-xs font-medium ${
|
||||||
|
contact.isOnline ? 'text-emerald-600' : 'text-red-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`h-2 w-2 rounded-full ${
|
||||||
|
contact.isOnline ? 'bg-emerald-500' : 'bg-red-500'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
{contact.isOnline ? 'Online' : 'Offline'}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{contactUnread > 0 && (
|
{contactUnread > 0 && (
|
||||||
<span className="shrink-0 rounded-full bg-red-500 px-2 py-0.5 text-xs font-semibold text-white">
|
<span className="shrink-0 rounded-full bg-red-500 px-2 py-0.5 text-xs font-semibold text-white">
|
||||||
|
|
@ -370,9 +419,13 @@ const MessengerWidget = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="truncate text-xs text-gray-500">
|
<div className="truncate text-xs text-gray-500">
|
||||||
{selectedContacts.length === 1
|
{selectedContacts.length === 1
|
||||||
? selectedContacts[0].email || selectedContacts[0].userName
|
? `${selectedContacts[0].isOnline ? 'Online' : 'Offline'} - ${
|
||||||
|
selectedContacts[0].email || selectedContacts[0].userName
|
||||||
|
}`
|
||||||
: selectedContacts.length > 1
|
: selectedContacts.length > 1
|
||||||
? 'Toplu mesaj'
|
? canSendToSelectedContacts
|
||||||
|
? 'Toplu mesaj'
|
||||||
|
: 'Offline kullanici secili'
|
||||||
: 'Mesaj göndermek için kişi seçin'}
|
: 'Mesaj göndermek için kişi seçin'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -518,6 +571,7 @@ const MessengerWidget = () => {
|
||||||
loading={uploading}
|
loading={uploading}
|
||||||
icon={<FaPaperclip />}
|
icon={<FaPaperclip />}
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
|
disabled={!canSendToSelectedContacts}
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -537,6 +591,7 @@ const MessengerWidget = () => {
|
||||||
placeholder="Mesaj yazın"
|
placeholder="Mesaj yazın"
|
||||||
rows={1}
|
rows={1}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
disabled={!canSendToSelectedContacts}
|
||||||
className="min-h-[36px] flex-1 resize-none border-0 bg-transparent px-2 py-2 text-sm text-gray-900 outline-none placeholder:text-gray-400 focus:ring-0 dark:text-gray-100"
|
className="min-h-[36px] flex-1 resize-none border-0 bg-transparent px-2 py-2 text-sm text-gray-900 outline-none placeholder:text-gray-400 focus:ring-0 dark:text-gray-100"
|
||||||
onChange={(event) => setText(event.target.value)}
|
onChange={(event) => setText(event.target.value)}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
|
|
@ -552,7 +607,7 @@ const MessengerWidget = () => {
|
||||||
variant="solid"
|
variant="solid"
|
||||||
icon={<IoSend />}
|
icon={<IoSend />}
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
disabled={!text.trim() && attachments.length === 0}
|
disabled={!canSendToSelectedContacts || (!text.trim() && attachments.length === 0)}
|
||||||
onClick={sendMessage}
|
onClick={sendMessage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export interface MessengerContactDto {
|
||||||
fullName: string
|
fullName: string
|
||||||
email?: string
|
email?: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
isOnline: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessengerAttachmentDto {
|
export interface MessengerAttachmentDto {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export interface OrgChartUserDto {
|
||||||
fullName: string
|
fullName: string
|
||||||
email: string
|
email: string
|
||||||
userName: string
|
userName: string
|
||||||
|
phoneNumber?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrgChartNodeDto {
|
export interface OrgChartNodeDto {
|
||||||
|
|
@ -13,6 +14,8 @@ export interface OrgChartNodeDto {
|
||||||
parentId?: string
|
parentId?: string
|
||||||
departmentId?: string
|
departmentId?: string
|
||||||
departmentName?: string
|
departmentName?: string
|
||||||
|
jobPositionId?: string
|
||||||
|
jobPositionName?: string
|
||||||
users: OrgChartUserDto[]
|
users: OrgChartUserDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -342,12 +342,12 @@ function buildEdges(tree: TreeNode[]): EdgeLink[] {
|
||||||
function UserAvatar({
|
function UserAvatar({
|
||||||
user,
|
user,
|
||||||
tenantId,
|
tenantId,
|
||||||
title,
|
jobPosition,
|
||||||
department,
|
department,
|
||||||
}: {
|
}: {
|
||||||
user: OrgChartUserDto
|
user: OrgChartUserDto
|
||||||
tenantId?: string
|
tenantId?: string
|
||||||
title: string
|
jobPosition?: string
|
||||||
department?: string
|
department?: string
|
||||||
}) {
|
}) {
|
||||||
const [showUserCard, setShowUserCard] = useState(false)
|
const [showUserCard, setShowUserCard] = useState(false)
|
||||||
|
|
@ -381,8 +381,9 @@ function UserAvatar({
|
||||||
user={{
|
user={{
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.fullName || user.userName,
|
name: user.fullName || user.userName,
|
||||||
title,
|
title: jobPosition || '',
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
phoneNumber: user.phoneNumber,
|
||||||
department,
|
department,
|
||||||
tenantId: tenantId ?? '',
|
tenantId: tenantId ?? '',
|
||||||
}}
|
}}
|
||||||
|
|
@ -518,16 +519,7 @@ function OrgChartNode({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Department label for job positions */}
|
|
||||||
{mode === 'jobPosition' && node.departmentName && (
|
|
||||||
<div className="px-3 pt-2">
|
|
||||||
<span
|
|
||||||
className={`inline-block text-xs px-2 py-0.5 rounded-full font-medium ${badgeBg}`}
|
|
||||||
>
|
|
||||||
{node.departmentName}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Users section */}
|
{/* Users section */}
|
||||||
{showUsers && (
|
{showUsers && (
|
||||||
|
|
@ -539,7 +531,7 @@ function OrgChartNode({
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
user={user}
|
user={user}
|
||||||
tenantId={tenantId}
|
tenantId={tenantId}
|
||||||
title={mode === 'jobPosition' ? node.name : node.departmentName || node.name}
|
jobPosition={node.jobPositionName || (mode === 'jobPosition' ? node.name : undefined)}
|
||||||
department={mode === 'jobPosition' ? node.departmentName : node.name}
|
department={mode === 'jobPosition' ? node.departmentName : node.name}
|
||||||
/>
|
/>
|
||||||
<span data-user-name="" className="text-xs text-slate-600 truncate">
|
<span data-user-name="" className="text-xs text-slate-600 truncate">
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,7 @@ const PostItem: React.FC<PostItemProps> = ({ post, onLike, onComment, onDelete,
|
||||||
id: comment.user?.id || '',
|
id: comment.user?.id || '',
|
||||||
name: comment.user?.fullName || '',
|
name: comment.user?.fullName || '',
|
||||||
title: comment.user?.jobPositions?.[0]?.name || '',
|
title: comment.user?.jobPositions?.[0]?.name || '',
|
||||||
|
phoneNumber: comment.user.phoneNumber,
|
||||||
tenantId: comment.user?.tenantId || '',
|
tenantId: comment.user?.tenantId || '',
|
||||||
}}
|
}}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ interface UserProfileCardProps {
|
||||||
email?: string
|
email?: string
|
||||||
phoneNumber?: string
|
phoneNumber?: string
|
||||||
department?: string
|
department?: string
|
||||||
location?: string
|
|
||||||
tenantId: string
|
tenantId: string
|
||||||
}
|
}
|
||||||
position?: 'top' | 'bottom'
|
position?: 'top' | 'bottom'
|
||||||
|
|
@ -76,13 +75,6 @@ const UserProfileCard: React.FC<UserProfileCardProps> = ({ user, position = 'bot
|
||||||
<span className="text-gray-700 dark:text-gray-300">{user.department}</span>
|
<span className="text-gray-700 dark:text-gray-300">{user.department}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{user.location && (
|
|
||||||
<div className="flex items-center gap-2 text-sm">
|
|
||||||
<FaMapMarkerAlt className="w-4 h-4 text-gray-400 flex-shrink-0" />
|
|
||||||
<span className="text-gray-700 dark:text-gray-300">{user.location}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Arrow indicator */}
|
{/* Arrow indicator */}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue