feat: initial JaniChat messenger — PWA, WebSocket, admin panel

This commit is contained in:
Ai
2026-05-22 11:06:43 +03:00
commit 1cabe9d04f
46 changed files with 3570 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
import { formatDistanceToNow } from 'date-fns';
import { ru } from 'date-fns/locale';
import Avatar from './Avatar';
import { Chat } from '../types';
interface Props {
chat: Chat;
active: boolean;
online?: boolean;
onClick: () => void;
}
export default function ChatListItem({ chat, active, online, onClick }: Props) {
const time = chat.lastMessageAt
? formatDistanceToNow(new Date(chat.lastMessageAt), { addSuffix: false, locale: ru })
: '';
const icon = chat.type === 'channel' ? '📢' : chat.type === 'group' ? '👥' : null;
return (
<button
onClick={onClick}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-left transition-colors ${
active ? 'bg-blue-50' : 'hover:bg-gray-50'
}`}
>
<Avatar
name={chat.title}
color={chat.avatarColor}
online={chat.type === 'private' ? online : undefined}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<span className="font-medium text-gray-900 text-sm truncate">
{icon && <span className="mr-1 text-xs">{icon}</span>}
{chat.title}
</span>
{time && <span className="text-xs text-gray-400 flex-shrink-0 ml-1">{time}</span>}
</div>
<div className="flex items-center justify-between mt-0.5">
<p className="text-xs text-gray-500 truncate">
{chat.lastMessage || (chat.type === 'channel' ? 'Канал' : 'Нет сообщений')}
</p>
{chat.unreadCount > 0 && (
<span className="bg-blue-500 text-white text-xs rounded-full min-w-[18px] h-[18px] flex items-center justify-center px-1 flex-shrink-0 ml-1">
{chat.unreadCount > 99 ? '99+' : chat.unreadCount}
</span>
)}
</div>
</div>
</button>
);
}