deat: Added statistics on your log

This commit is contained in:
2026-07-13 01:29:54 +02:00
parent b59c6856bd
commit eb9e2db41a
10 changed files with 1695 additions and 0 deletions
+30
View File
@@ -67,6 +67,7 @@ import { IcomPanel } from '@/components/IcomPanel';
import { AntGeniusPanel, type AGStatus } from '@/components/AntGeniusPanel';
import { FilterBuilder, type QueryFilter } from '@/components/FilterBuilder';
import { AwardsPanel } from '@/components/AwardsPanel';
import { StatsPanel } from '@/components/StatsPanel';
import { RecentQSOsGrid } from '@/components/RecentQSOsGrid';
import { ShutdownProgress } from '@/components/ShutdownProgress';
import { ClusterGrid } from '@/components/ClusterGrid';
@@ -616,6 +617,12 @@ export default function App() {
setQslTabOpen(false);
setActiveTab((t) => (t === 'qsl' ? 'recent' : t));
}
// Statistics is likewise a closable tab, opened from Tools → Statistics.
const [statsTabOpen, setStatsTabOpen] = useState(false);
function closeStatsTab() {
setStatsTabOpen(false);
setActiveTab((t) => (t === 'stats' ? 'recent' : t));
}
// Recent QSOs row cap, persisted. With AG Grid's virtual scroller
// huge logs render OK once loaded, but a 25k+ logbook still takes a
// couple of seconds to round-trip from SQLite at launch. Defaulting
@@ -2455,6 +2462,7 @@ export default function App() {
]},
{ name: 'tools', label: t('menu.tools'), items: [
{ type: 'item', label: t('tools.qslManager'), action: 'tools.qslmanager' },
{ type: 'item', label: t('stats.tab'), action: 'tools.stats' },
{ type: 'item', label: t('tools.qslDesigner'), action: 'tools.qsldesigner' },
{ type: 'separator' },
{ type: 'item', label: (wkEnabled ? '✓ ' : '') + t('tools.winkeyer'), action: 'tools.winkeyer' },
@@ -2490,6 +2498,7 @@ export default function App() {
case 'edit.bulkedit': openBulkEdit(selectedIds); break;
case 'edit.prefs': setShowSettings(true); break;
case 'tools.qslmanager': setQslTabOpen(true); setActiveTab('qsl'); break;
case 'tools.stats': setStatsTabOpen(true); setActiveTab('stats'); break;
case 'tools.qsldesigner': setQslDesignerOpen(true); break;
case 'tools.winkeyer': wkSetEnabled(!wkEnabled); break;
case 'tools.dvk': setDvkEnabled((v) => !v); break;
@@ -4083,6 +4092,21 @@ export default function App() {
)}
{catState.backend === 'flex' && <TabsTrigger value="flex">FlexRadio</TabsTrigger>}
{catState.backend === 'icom' && <TabsTrigger value="icom">Icom</TabsTrigger>}
{statsTabOpen && (
<TabsTrigger value="stats" className="gap-1.5">
{t('stats.tab')}
<span
role="button"
aria-label="Close Statistics"
title="Close"
className="inline-flex items-center justify-center size-4 rounded hover:bg-foreground/10 text-muted-foreground hover:text-foreground"
onPointerDown={(e) => { e.stopPropagation(); }}
onClick={(e) => { e.stopPropagation(); closeStatsTab(); }}
>
<X className="size-3" />
</span>
</TabsTrigger>
)}
{qslTabOpen && (
<TabsTrigger value="qsl" className="gap-1.5">
QSL Manager
@@ -4377,6 +4401,12 @@ export default function App() {
<AwardsPanel onEditQSO={openEdit} onAwardsChanged={() => setAwardsVersion((v) => v + 1)} />
</TabsContent>
{statsTabOpen && (
<TabsContent value="stats" className="mt-0 flex flex-col min-h-0 flex-1 data-[state=inactive]:hidden">
<StatsPanel />
</TabsContent>
)}
{contestTabEnabled && (
<TabsContent value="contest" className="flex-1 min-h-0 p-0">
<ContestPanel session={contest} onChange={updateContest} />