import { useEffect, useRef, useState } from 'react'; import { MessageSquare, Send, Users, X } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; export type ChatMsg = { id: number; operator: string; station: string; message: string; created_at: string }; export type ChatPresence = { operator: string; station: string; ago_secs: number }; function hhmm(iso: string): string { const d = new Date(iso); if (isNaN(d.getTime())) return ''; const p = (n: number) => String(n).padStart(2, '0'); return `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`; } // ChatPanel — presentational multi-op chat panel, docked in the aside row next // to the rotor / WinKeyer / DVK panels. All data + state lives in App.tsx. export function ChatPanel({ msgs, online, myCall, onSend, onClose }: { msgs: ChatMsg[]; online: ChatPresence[]; myCall?: string; onSend: (text: string) => void; onClose: () => void; }) { const { t } = useI18n(); const [text, setText] = useState(''); const listRef = useRef(null); const me = (myCall || '').toUpperCase(); useEffect(() => { const el = listRef.current; if (el) el.scrollTop = el.scrollHeight; }, [msgs]); function send() { const t = text.trim(); if (!t) return; setText(''); onSend(t); } return (
{t('chatp.chat')} {/* Online count — hover to see who's connected. */}
{online.length} {online.length > 0 && (
{t('chatp.online')}
{online.map((o) => (
{o.operator}{o.station && o.station.toUpperCase() !== o.operator.toUpperCase() ? · {o.station} : null}
))}
)}
{msgs.length === 0 ? (
{t('chatp.noMessages')}
) : msgs.map((m) => { const mine = m.operator.toUpperCase() === me; return (
{!mine && {m.operator}} {m.message}
{hhmm(m.created_at)}
); })}
setText(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); send(); } }} placeholder={t('chatp.messagePh')} maxLength={1000} className="flex-1 h-8 rounded-md border border-border bg-background px-2 text-xs outline-none focus:border-primary" />
); }