Files
OpsLog/frontend/src/components/CallHistoryPanel.tsx
T

122 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Star } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { useI18n } from '@/lib/i18n';
import type { WorkedBeforeView } from '@/types';
type WorkedBefore = WorkedBeforeView;
interface Props {
wb: WorkedBefore | null;
busy: boolean;
currentCall: string;
}
function fmtDate(s: any): string {
if (!s) return '';
const d = new Date(s);
if (isNaN(d.getTime())) return '';
const p = (n: number) => String(n).padStart(2, '0');
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}`;
}
function fmtDateTime(s: any): string {
if (!s) return '';
const d = new Date(s);
if (isNaN(d.getTime())) return '';
const p = (n: number) => String(n).padStart(2, '0');
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`;
}
export function CallHistoryPanel({ wb, busy, currentCall }: Props) {
const { t } = useI18n();
const hasCall = currentCall.trim() !== '';
const count = wb?.count ?? 0;
const entries = wb?.entries ?? [];
return (
<aside className="flex flex-col bg-card border border-border rounded-lg shadow-sm min-h-0 overflow-hidden">
<header className="flex items-center justify-between px-3 py-2 border-b border-border bg-muted/40">
<div className="flex items-baseline gap-2">
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
{t('chp.workedBefore')}
</span>
{hasCall && (
<span className="font-mono text-sm font-bold text-primary tracking-wider">
{currentCall.toUpperCase()}
</span>
)}
</div>
{hasCall && count > 0 && (
<Badge variant="accent" className="font-mono text-[11px] tracking-wider">
{count}×
</Badge>
)}
</header>
{!hasCall ? (
<div className="flex-1 flex flex-col items-center justify-center gap-2 p-6 text-center text-xs text-muted-foreground">
{t('chp.typeCallsign')}
</div>
) : busy && count === 0 ? (
<div className="flex-1 flex items-center justify-center text-xs text-muted-foreground italic">
{t('chp.checking')}
</div>
) : count === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center gap-2 p-6 text-center text-xs text-muted-foreground">
<Star className="size-6 text-primary fill-current" />
<div className="text-lg font-bold text-primary tracking-wider">{t('chp.new')}</div>
<div>{t('chp.noPriorQso')}</div>
</div>
) : (
<>
<div className="px-3 py-1.5 text-[11px] text-muted-foreground bg-muted/40 border-b border-border">
{t('chp.first')}: <strong className="text-foreground font-semibold">{fmtDate(wb?.first)}</strong> ·{' '}
{t('chp.last')}: <strong className="text-foreground font-semibold">{fmtDate(wb?.last)}</strong>
</div>
<div className="flex-1 overflow-y-auto">
<table className="w-full text-[11px] border-collapse">
<thead>
<tr>
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">{t('chp.dateUtc')}</th>
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">{t('chp.band')}</th>
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">{t('chp.mode')}</th>
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">RST tx</th>
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">RST rx</th>
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">QSL</th>
</tr>
</thead>
<tbody>
{entries.map((e) => (
<tr key={e.id} className="hover:bg-stone-50">
<td className="px-2 py-1 font-mono border-b border-border/40 whitespace-nowrap">{fmtDateTime(e.qso_date)}</td>
<td className="px-2 py-1 border-b border-border/40 whitespace-nowrap">
<span className="inline-block px-1.5 py-0.5 rounded font-mono text-[10px] font-semibold bg-accent text-accent-foreground">{e.band}</span>
</td>
<td className="px-2 py-1 border-b border-border/40 whitespace-nowrap">
<span className="inline-block px-1.5 py-0.5 rounded font-mono text-[10px] font-semibold bg-emerald-100 text-emerald-700">{e.mode}</span>
</td>
<td className="px-2 py-1 font-mono border-b border-border/40 whitespace-nowrap">{e.rst_sent ?? ''}</td>
<td className="px-2 py-1 font-mono border-b border-border/40 whitespace-nowrap">{e.rst_rcvd ?? ''}</td>
<td className="px-2 py-1 border-b border-border/40 whitespace-nowrap text-muted-foreground">
{e.lotw_rcvd === 'Y' && (
<span className="inline-block w-[14px] h-[14px] rounded text-center leading-[14px] text-[9px] font-bold text-white bg-blue-600 mr-0.5" title={t('chp.lotwRcvd')}>L</span>
)}
{e.qsl_rcvd === 'Y' && (
<span className="inline-block w-[14px] h-[14px] rounded text-center leading-[14px] text-[9px] font-bold text-white bg-emerald-600 mr-0.5" title={t('chp.bureauRcvd')}>B</span>
)}
</td>
</tr>
))}
</tbody>
</table>
{count > entries.length && (
<div className="text-center py-1.5 text-[11px] italic text-muted-foreground">
{t('chp.olderQsos', { n: count - entries.length })}
</div>
)}
</div>
</>
)}
</aside>
);
}