fix: bugs
This commit is contained in:
@@ -7765,16 +7765,31 @@ type ConfirmationItem struct {
|
||||
}
|
||||
|
||||
// GetSlotStats returns the worked/confirmed slot + DXCC tallies for the QSL
|
||||
// Manager. Slots are counted by mode CLASS (Phone/CW/Digital); the raw-mode
|
||||
// granularity (RTTY ≠ FT8) is kept only for the cluster/matrix "new slot" flag.
|
||||
// Manager. It counts over the SAME data path as the DXCC award, using the
|
||||
// award's confirmation test (award.Confirmed with the DXCC sources lotw+qsl),
|
||||
// so the numbers agree with the DXCC award matrix. A slot is a distinct
|
||||
// DXCC × band (mode-agnostic) — i.e. the award's WORKED/CONFIRMED "Grand"
|
||||
// totals, NOT split per emission.
|
||||
func (a *App) GetSlotStats() qso.SlotStats {
|
||||
if a.qso == nil {
|
||||
return qso.SlotStats{}
|
||||
}
|
||||
st, err := a.qso.GetSlotStats(a.ctx)
|
||||
// Delegate to the exact same matrix the DXCC award draws: the "WORKED"/
|
||||
// "CONFIRMED" (ALL-emission) rows give DXCC entities (Total) and slots =
|
||||
// entity × band (GrandTotal). Guaranteed to equal the award's Grand column.
|
||||
res, err := a.GetAwardStats("DXCC")
|
||||
if err != nil {
|
||||
return qso.SlotStats{}
|
||||
}
|
||||
var st qso.SlotStats
|
||||
for _, r := range res.Rows {
|
||||
switch r.Label {
|
||||
case "WORKED":
|
||||
st.SlotsWorked, st.DXCCWorked = r.GrandTotal, r.Total
|
||||
case "CONFIRMED":
|
||||
st.SlotsConfirmed, st.DXCCConfirmed = r.GrandTotal, r.Total
|
||||
}
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
|
||||
@@ -210,8 +210,8 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
||||
|
||||
// Worked/confirmed tallies. Slots are counted by mode CLASS (PH/CW/DIGI) — the
|
||||
// raw-mode granularity (RTTY ≠ FT8) stays only in the cluster/matrix new-slot flag.
|
||||
const [stats, setStats] = useState<{ slots_worked: number; slots_confirmed: number; dxcc_worked: number; dxcc_confirmed: number }>(
|
||||
{ slots_worked: 0, slots_confirmed: 0, dxcc_worked: 0, dxcc_confirmed: 0 });
|
||||
const [stats, setStats] = useState<any>(
|
||||
{ slots_worked: 0, slots_confirmed: 0, dxcc_worked: 0, dxcc_confirmed: 0, ph_worked: 0, ph_confirmed: 0, cw_worked: 0, cw_confirmed: 0, dig_worked: 0, dig_confirmed: 0 });
|
||||
const refreshCounts = useCallback(() => { GetSlotStats().then((c: any) => setStats(c)).catch(() => {}); }, []);
|
||||
useEffect(() => { refreshCounts(); }, [refreshCounts]);
|
||||
|
||||
@@ -363,7 +363,8 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
||||
</>
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
{/* Worked / confirmed tallies. Slots by mode class (PH/CW/DIGI). */}
|
||||
{/* Worked / confirmed tallies. Slots by mode class (PH/CW/DIGI), with the
|
||||
per-class breakdown so the totals are checkable. */}
|
||||
<div className="self-center flex items-center gap-3 text-[11px] font-mono" title={t('qslm.countsTip')}>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-muted-foreground uppercase text-[9px] tracking-wider">{t('qslm.slots')}</span>
|
||||
|
||||
@@ -3664,6 +3664,12 @@ export namespace qso {
|
||||
slots_confirmed: number;
|
||||
dxcc_worked: number;
|
||||
dxcc_confirmed: number;
|
||||
ph_worked: number;
|
||||
ph_confirmed: number;
|
||||
cw_worked: number;
|
||||
cw_confirmed: number;
|
||||
dig_worked: number;
|
||||
dig_confirmed: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SlotStats(source);
|
||||
@@ -3675,6 +3681,12 @@ export namespace qso {
|
||||
this.slots_confirmed = source["slots_confirmed"];
|
||||
this.dxcc_worked = source["dxcc_worked"];
|
||||
this.dxcc_confirmed = source["dxcc_confirmed"];
|
||||
this.ph_worked = source["ph_worked"];
|
||||
this.ph_confirmed = source["ph_confirmed"];
|
||||
this.cw_worked = source["cw_worked"];
|
||||
this.cw_confirmed = source["cw_confirmed"];
|
||||
this.dig_worked = source["dig_worked"];
|
||||
this.dig_confirmed = source["dig_confirmed"];
|
||||
}
|
||||
}
|
||||
export class Stats {
|
||||
|
||||
+28
-8
@@ -1993,6 +1993,13 @@ type SlotStats struct {
|
||||
SlotsConfirmed int `json:"slots_confirmed"` // distinct DXCC × band × class, confirmed
|
||||
DXCCWorked int `json:"dxcc_worked"` // distinct DXCC entities worked
|
||||
DXCCConfirmed int `json:"dxcc_confirmed"` // distinct DXCC entities confirmed
|
||||
// Per-class slot breakdown (Phone / CW / Digital) so the numbers are checkable.
|
||||
PHWorked int `json:"ph_worked"`
|
||||
PHConfirmed int `json:"ph_confirmed"`
|
||||
CWWorked int `json:"cw_worked"`
|
||||
CWConfirmed int `json:"cw_confirmed"`
|
||||
DIGWorked int `json:"dig_worked"`
|
||||
DIGConfirmed int `json:"dig_confirmed"`
|
||||
}
|
||||
|
||||
// GetSlotStats computes the worked/confirmed slot and DXCC tallies in one pass.
|
||||
@@ -2008,6 +2015,9 @@ func (r *Repo) GetSlotStats(ctx context.Context) (SlotStats, error) {
|
||||
defer rows.Close()
|
||||
dxccW, slotW := map[int]bool{}, map[string]bool{}
|
||||
dxccC, slotC := map[int]bool{}, map[string]bool{}
|
||||
// Per-class distinct slots (worked "w" / confirmed "c").
|
||||
clsW := map[string]map[string]bool{"PH": {}, "CW": {}, "DIG": {}}
|
||||
clsC := map[string]map[string]bool{"PH": {}, "CW": {}, "DIG": {}}
|
||||
for rows.Next() {
|
||||
var dxcc, conf int
|
||||
var band, mode string
|
||||
@@ -2017,19 +2027,29 @@ func (r *Repo) GetSlotStats(ctx context.Context) (SlotStats, error) {
|
||||
if dxcc == 0 {
|
||||
continue
|
||||
}
|
||||
key := SlotClassKey(dxcc, band, mode)
|
||||
dxccW[dxcc] = true
|
||||
if band != "" {
|
||||
slotW[key] = true
|
||||
}
|
||||
if conf == 1 {
|
||||
dxccC[dxcc] = true
|
||||
if band != "" {
|
||||
slotC[key] = true
|
||||
}
|
||||
}
|
||||
if band == "" {
|
||||
continue // no band → counts for DXCC but not for a slot
|
||||
}
|
||||
key := SlotClassKey(dxcc, band, mode)
|
||||
cl := modeClass(mode)
|
||||
slotW[key] = true
|
||||
clsW[cl][key] = true
|
||||
if conf == 1 {
|
||||
slotC[key] = true
|
||||
clsC[cl][key] = true
|
||||
}
|
||||
}
|
||||
return SlotStats{SlotsWorked: len(slotW), SlotsConfirmed: len(slotC), DXCCWorked: len(dxccW), DXCCConfirmed: len(dxccC)}, rows.Err()
|
||||
return SlotStats{
|
||||
SlotsWorked: len(slotW), SlotsConfirmed: len(slotC),
|
||||
DXCCWorked: len(dxccW), DXCCConfirmed: len(dxccC),
|
||||
PHWorked: len(clsW["PH"]), PHConfirmed: len(clsC["PH"]),
|
||||
CWWorked: len(clsW["CW"]), CWConfirmed: len(clsC["CW"]),
|
||||
DIGWorked: len(clsW["DIG"]), DIGConfirmed: len(clsC["DIG"]),
|
||||
}, rows.Err()
|
||||
}
|
||||
|
||||
// confirmedCols whitelists the received-status columns ConfirmedSlots may
|
||||
|
||||
Reference in New Issue
Block a user