From ee844564dec075e720c8e1d627ce71cd30743d89 Mon Sep 17 00:00:00 2001 From: rouggy Date: Tue, 14 Jul 2026 20:02:52 +0200 Subject: [PATCH] fix: changed colors on qsl info tab --- frontend/src/components/QSOEditModal.tsx | 32 +++++-- internal/award/bench_test.go | 107 +++++++++++++++++++++++ 2 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 internal/award/bench_test.go diff --git a/frontend/src/components/QSOEditModal.tsx b/frontend/src/components/QSOEditModal.tsx index be4eca2..f6726d6 100644 --- a/frontend/src/components/QSOEditModal.tsx +++ b/frontend/src/components/QSOEditModal.tsx @@ -80,11 +80,23 @@ function StatusCell({ value }: { value?: string }) { if (v === '') { return ; } + // One colour per state, and each colour means something: + // Yes green — confirmed, the thing you wanted + // Requested blue — in flight, waiting on the other end. Not a problem. + // Modified orange — uploaded, then the QSO changed: it needs re-uploading. + // This is the ONLY state asking for action, so it gets the + // only alarming colour. + // No neutral — nothing done yet. Every freshly logged QSO is "No" on + // every row; painting that orange (as it used to be, in the + // same orange as Requested) made the table shout about a + // non-problem and told you nothing apart. + // Ignore dashed — deliberately excluded, on purpose. const label = v === 'Y' ? t('qedit.qslYes') : v === 'R' ? t('qedit.qslRequested') : v === 'I' ? t('qedit.qslIgnore') : v === 'M' ? t('qedit.statusModified') : t('qedit.qslNo'); - const cls = v === 'Y' ? 'bg-success text-success-foreground' - : v === 'R' ? 'bg-warning text-warning-foreground' - : v === 'I' ? 'bg-muted-foreground text-background' - : 'bg-warning text-warning-foreground'; + const cls = v === 'Y' ? 'bg-success text-success-foreground border border-success' + : v === 'R' ? 'bg-info-muted text-info-muted-foreground border border-info-border' + : v === 'M' ? 'bg-warning text-warning-foreground border border-warning' + : v === 'I' ? 'bg-muted text-muted-foreground border border-dashed border-border italic' + : 'bg-muted text-muted-foreground border border-border'; return {label}; } @@ -571,9 +583,13 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b

- {/* Right: live status grid for every channel */} -
- + {/* Right: live status grid for every channel. + Sized by its content, not pinned to a width: a fixed 288px box + left the label column too narrow, so "QSL (paper)" wrapped onto + two lines and padded out the whole row. There is spare width to + the right — spend it on the label. */} +
+
@@ -584,7 +600,7 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [], b {CONFIRMATIONS.map((c) => ( - + diff --git a/internal/award/bench_test.go b/internal/award/bench_test.go new file mode 100644 index 0000000..92d509b --- /dev/null +++ b/internal/award/bench_test.go @@ -0,0 +1,107 @@ +package award + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "hamlog/internal/qso" +) + +// A synthetic log with the shape a real one has: a spread of bands, modes, +// entities, and the free-text fields the harder awards actually scan. +func benchLog(n int) []qso.QSO { + bands := []string{"160m", "80m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m"} + modes := []string{"SSB", "CW", "FT8", "FT4", "RTTY"} + entities := []int{291, 248, 227, 230, 339, 5, 108, 110, 6, 281} + towns := []string{"SERIATE (BG)", "MILANO (MI)", "ROMA (RM)", "TORINO (TO)", "NAPOLI (NA)"} + out := make([]qso.QSO, n) + base := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) + for i := range out { + e := entities[i%len(entities)] + out[i] = qso.QSO{ + ID: int64(i + 1), + Callsign: fmt.Sprintf("I%dABC", i%10), + Band: bands[i%len(bands)], + Mode: modes[i%len(modes)], + DXCC: &e, + QSODate: base.Add(time.Duration(i) * time.Hour), + QTH: towns[i%len(towns)], + Address: "Via Roma 12, 24068 " + towns[i%len(towns)] + " Italy", + Grid: "JN45AA", + State: "CA", + LOTWRcvd: "Y", + } + } + return out +} + +// WAIP is the worst realistic shape: a predefined list, an exact-match primary +// that always produces a doomed candidate, then a fallback chain that tokenises a +// free-text field. If anything is slow, it is this. +func benchWAIP() (Def, []RefMeta) { + d := Def{ + Code: "WAIP", Valid: true, Type: TypeReference, + Field: "address", MatchBy: "code", ExactMatch: true, + OrRules: []OrRule{ + {Field: "qth", MatchBy: "description"}, + {Field: "qth", MatchBy: "code"}, + {Field: "address", MatchBy: "code"}, + }, + DXCCFilter: []int{248}, + Confirm: []string{"lotw", "qsl"}, + } + codes := []string{"AG", "AL", "AN", "AO", "AP", "AQ", "AR", "AT", "AV", "BA", "BG", "BI", "BL", "BN", "BO", "BR", "BS", "BT", "BZ", "CA", "CB", "CE", "CH", "CL", "CN", "CO", "CR", "CS", "CT", "CZ", "EN", "FC", "FE", "FG", "FI", "FM", "FR", "GE", "GO", "GR", "IM", "IS", "KR", "LC", "LE", "LI", "LO", "LT", "LU", "MB", "MC", "ME", "MI", "MN", "MO", "MS", "MT", "NA", "NO", "NU", "OR", "PA", "PC", "PD", "PE", "PG", "PI", "PN", "PO", "PR", "PT", "PU", "PV", "PZ", "RA", "RC", "RE", "RG", "RI", "RM", "RN", "RO", "SA", "SI", "SO", "SP", "SR", "SS", "SU", "SV", "TA", "TE", "TN", "TO", "TP", "TR", "TS", "TV", "UD", "VA", "VB", "VC", "VE", "VI", "VR", "VT", "VV"} + metas := make([]RefMeta, 0, len(codes)) + for _, c := range codes { + metas = append(metas, RefMeta{Code: c, Name: "Province " + c, Valid: true}) + } + return d, metas +} + +// One award, whole log — what GetAward(code) does when the Awards panel opens. +func BenchmarkComputeOneAward(b *testing.B) { + d, metas := benchWAIP() + for _, n := range []int{10_000, 50_000, 100_000} { + log := benchLog(n) + b.Run(fmt.Sprintf("WAIP/%dqso", n), func(b *testing.B) { + for i := 0; i < b.N; i++ { + Compute([]Def{d}, log, map[string][]RefMeta{"WAIP": metas}, nil) + } + }) + } +} + +// Every shipped award at once — GetAwards(), the heaviest thing the app can ask. +func BenchmarkComputeWholeCatalog(b *testing.B) { + defs := Defaults() + metas := map[string][]RefMeta{} + wd, wm := benchWAIP() + defs = append(defs, wd) + metas["WAIP"] = wm + for _, c := range Catalog() { + if raw, ok := CatalogRefs(c.Def.Code); ok { + var refs []struct { + Code string `json:"code"` + Name string `json:"name"` + Valid bool `json:"valid"` + } + if json.Unmarshal(raw, &refs) == nil { + m := make([]RefMeta, 0, len(refs)) + for _, r := range refs { + m = append(m, RefMeta{Code: r.Code, Name: r.Name, Valid: r.Valid}) + } + metas[c.Def.Code] = m + } + } + } + for _, n := range []int{10_000, 100_000} { + log := benchLog(n) + b.Run(fmt.Sprintf("%dawards/%dqso", len(defs), n), func(b *testing.B) { + for i := 0; i < b.N; i++ { + Compute(defs, log, metas, nil) + } + }) + } +}
{t('qedit.thType')}
{CONF_LABEL_KEYS[c.key] ? t(CONF_LABEL_KEYS[c.key]) : c.label}{CONF_LABEL_KEYS[c.key] ? t(CONF_LABEL_KEYS[c.key]) : c.label} {c.rcvd ? : }