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
|
// GetSlotStats returns the worked/confirmed slot + DXCC tallies for the QSL
|
||||||
// Manager. Slots are counted by mode CLASS (Phone/CW/Digital); the raw-mode
|
// Manager. It counts over the SAME data path as the DXCC award, using the
|
||||||
// granularity (RTTY ≠ FT8) is kept only for the cluster/matrix "new slot" flag.
|
// 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 {
|
func (a *App) GetSlotStats() qso.SlotStats {
|
||||||
if a.qso == nil {
|
if a.qso == nil {
|
||||||
return qso.SlotStats{}
|
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 {
|
if err != nil {
|
||||||
return qso.SlotStats{}
|
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
|
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
|
// 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.
|
// 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 }>(
|
const [stats, setStats] = useState<any>(
|
||||||
{ slots_worked: 0, slots_confirmed: 0, dxcc_worked: 0, dxcc_confirmed: 0 });
|
{ 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(() => {}); }, []);
|
const refreshCounts = useCallback(() => { GetSlotStats().then((c: any) => setStats(c)).catch(() => {}); }, []);
|
||||||
useEffect(() => { refreshCounts(); }, [refreshCounts]);
|
useEffect(() => { refreshCounts(); }, [refreshCounts]);
|
||||||
|
|
||||||
@@ -363,7 +363,8 @@ export function QSLManagerPanel({ onEditQSO }: { onEditQSO?: (id: number) => voi
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1" />
|
<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')}>
|
<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="flex items-center gap-1">
|
||||||
<span className="text-muted-foreground uppercase text-[9px] tracking-wider">{t('qslm.slots')}</span>
|
<span className="text-muted-foreground uppercase text-[9px] tracking-wider">{t('qslm.slots')}</span>
|
||||||
|
|||||||
@@ -3664,6 +3664,12 @@ export namespace qso {
|
|||||||
slots_confirmed: number;
|
slots_confirmed: number;
|
||||||
dxcc_worked: number;
|
dxcc_worked: number;
|
||||||
dxcc_confirmed: 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 = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new SlotStats(source);
|
return new SlotStats(source);
|
||||||
@@ -3675,6 +3681,12 @@ export namespace qso {
|
|||||||
this.slots_confirmed = source["slots_confirmed"];
|
this.slots_confirmed = source["slots_confirmed"];
|
||||||
this.dxcc_worked = source["dxcc_worked"];
|
this.dxcc_worked = source["dxcc_worked"];
|
||||||
this.dxcc_confirmed = source["dxcc_confirmed"];
|
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 {
|
export class Stats {
|
||||||
|
|||||||
+28
-8
@@ -1993,6 +1993,13 @@ type SlotStats struct {
|
|||||||
SlotsConfirmed int `json:"slots_confirmed"` // distinct DXCC × band × class, confirmed
|
SlotsConfirmed int `json:"slots_confirmed"` // distinct DXCC × band × class, confirmed
|
||||||
DXCCWorked int `json:"dxcc_worked"` // distinct DXCC entities worked
|
DXCCWorked int `json:"dxcc_worked"` // distinct DXCC entities worked
|
||||||
DXCCConfirmed int `json:"dxcc_confirmed"` // distinct DXCC entities confirmed
|
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.
|
// 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()
|
defer rows.Close()
|
||||||
dxccW, slotW := map[int]bool{}, map[string]bool{}
|
dxccW, slotW := map[int]bool{}, map[string]bool{}
|
||||||
dxccC, slotC := 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() {
|
for rows.Next() {
|
||||||
var dxcc, conf int
|
var dxcc, conf int
|
||||||
var band, mode string
|
var band, mode string
|
||||||
@@ -2017,19 +2027,29 @@ func (r *Repo) GetSlotStats(ctx context.Context) (SlotStats, error) {
|
|||||||
if dxcc == 0 {
|
if dxcc == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := SlotClassKey(dxcc, band, mode)
|
|
||||||
dxccW[dxcc] = true
|
dxccW[dxcc] = true
|
||||||
if band != "" {
|
|
||||||
slotW[key] = true
|
|
||||||
}
|
|
||||||
if conf == 1 {
|
if conf == 1 {
|
||||||
dxccC[dxcc] = true
|
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
|
// confirmedCols whitelists the received-status columns ConfirmedSlots may
|
||||||
|
|||||||
Reference in New Issue
Block a user