pota
This commit is contained in:
@@ -3,7 +3,7 @@ import {
|
||||
ArrowDown, ArrowUp, ArrowLeft, ArrowRight, Copy, Plus, Star, StarOff, Trash2,
|
||||
ChevronDown, ChevronRight,
|
||||
User, Database, Radio, Cog, Server, Award, Antenna as AntennaIcon,
|
||||
Compass, Wifi, Construction, UploadCloud,
|
||||
Compass, Wifi, Construction, UploadCloud, Loader2,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
GetLookupSettings, SaveLookupSettings, ClearLookupCache, TestLookupProvider,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
GetDatabaseSettings, PickOpenDatabase, PickSaveDatabase, OpenDatabase, MoveDatabase, ResetDatabaseToDefault, QuitApp, CreateDatabase,
|
||||
GetQSLDefaults, SaveQSLDefaults,
|
||||
GetExternalServices, SaveExternalServices, TestQRZUpload, TestClublogUpload,
|
||||
GetPOTAToken, SavePOTAToken, SyncPOTAHunterLog,
|
||||
TestLoTWUpload, ListTQSLStationLocations,
|
||||
ComputeStationInfo,
|
||||
} from '../../wailsjs/go/main/App';
|
||||
@@ -481,7 +482,12 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
const [stationLocations, setStationLocations] = useState<string[]>([]);
|
||||
// Active tab in the External Services panel — lifted here because
|
||||
// PANELS[selected]() is called as a function, so panels can't hold hooks.
|
||||
const [extSvcTab, setExtSvcTab] = useState<'qrz' | 'clublog' | 'hrdlog' | 'eqsl' | 'hamqth' | 'lotw'>('qrz');
|
||||
const [extSvcTab, setExtSvcTab] = useState<'qrz' | 'clublog' | 'hrdlog' | 'eqsl' | 'hamqth' | 'lotw' | 'pota'>('qrz');
|
||||
// POTA hunter-log sync (stamps pota_ref on local QSOs from your pota.app log).
|
||||
const [potaToken, setPotaToken] = useState('');
|
||||
const [potaBusy, setPotaBusy] = useState(false);
|
||||
const [potaResult, setPotaResult] = useState<{ ok: boolean; msg: string } | null>(null);
|
||||
useEffect(() => { GetPOTAToken().then((t) => setPotaToken(t || '')).catch(() => {}); }, []);
|
||||
|
||||
const [backupCfg, setBackupCfg] = useState<mainModels.BackupSettings>({
|
||||
enabled: false, folder: '', rotation: 5, zip: false,
|
||||
@@ -2123,7 +2129,22 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
{ k: 'eqsl', label: 'EQSL' },
|
||||
{ k: 'hamqth', label: 'HAMQTH' },
|
||||
{ k: 'lotw', label: 'LOTW', ready: true },
|
||||
{ k: 'pota', label: 'POTA', ready: true },
|
||||
];
|
||||
|
||||
async function syncPota() {
|
||||
setPotaBusy(true);
|
||||
setPotaResult(null);
|
||||
try {
|
||||
await SavePOTAToken(potaToken);
|
||||
const r: any = await SyncPOTAHunterLog();
|
||||
setPotaResult({ ok: true, msg: `${r.updated} QSO updated · ${r.already_tagged} already tagged · ${r.unmatched} unmatched (of ${r.fetched} hunter-log entries).` });
|
||||
} catch (e: any) {
|
||||
setPotaResult({ ok: false, msg: String(e?.message ?? e) });
|
||||
} finally {
|
||||
setPotaBusy(false);
|
||||
}
|
||||
}
|
||||
const qrz = extSvc.qrz;
|
||||
const setQrz = (patch: Partial<ExtServiceCfg>) =>
|
||||
setExtSvc((s) => ({ ...s, qrz: { ...s.qrz, ...patch } }));
|
||||
@@ -2437,6 +2458,37 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : extSvcTab === 'pota' ? (
|
||||
<div className="space-y-4 max-w-2xl">
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
Update your QSOs with the park reference from your <strong>pota.app hunter log</strong>.
|
||||
Paste your session token: log in at <span className="font-mono">pota.app</span>, open the browser
|
||||
DevTools → <strong>Network</strong> tab, click any <span className="font-mono">api.pota.app</span> request,
|
||||
and copy the full <strong>Authorization</strong> header value. The token expires after a while — re-copy it if the sync fails.
|
||||
</p>
|
||||
<div className="grid grid-cols-[170px_1fr] gap-3 items-start">
|
||||
<Label className="text-sm pt-2">Session token</Label>
|
||||
<Textarea
|
||||
value={potaToken}
|
||||
onChange={(e) => setPotaToken(e.target.value)}
|
||||
placeholder="eyJ… (Authorization header from pota.app)"
|
||||
className="font-mono text-[11px] h-20"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button onClick={syncPota} disabled={potaBusy || !potaToken.trim()}>
|
||||
{potaBusy ? <><Loader2 className="size-4 mr-1.5 animate-spin" /> Syncing…</> : 'Sync hunter log'}
|
||||
</Button>
|
||||
<span className="text-[11px] text-muted-foreground">Matches by callsign + band within ±5 min. Only fills QSOs without a POTA ref.</span>
|
||||
</div>
|
||||
{potaResult && (
|
||||
<div className={cn('text-xs rounded-md px-3 py-2 border',
|
||||
potaResult.ok ? 'border-emerald-300 bg-emerald-50 text-emerald-800' : 'border-destructive/30 bg-destructive/10 text-destructive')}>
|
||||
{potaResult.msg}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-[11px] text-muted-foreground">After a sync, rescan the POTA award to see the new references counted.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center text-muted-foreground gap-2 py-16">
|
||||
<Construction className="size-10 opacity-30" />
|
||||
|
||||
Vendored
+6
@@ -141,6 +141,8 @@ export function GetLogFilePath():Promise<string>;
|
||||
|
||||
export function GetLookupSettings():Promise<main.LookupSettings>;
|
||||
|
||||
export function GetPOTAToken():Promise<string>;
|
||||
|
||||
export function GetQSLDefaults():Promise<main.QSLDefaults>;
|
||||
|
||||
export function GetQSO(arg1:number):Promise<qso.QSO>;
|
||||
@@ -265,6 +267,8 @@ export function SaveOperatingAntenna(arg1:operating.Antenna):Promise<operating.A
|
||||
|
||||
export function SaveOperatingStation(arg1:operating.Station):Promise<operating.Station>;
|
||||
|
||||
export function SavePOTAToken(arg1:string):Promise<void>;
|
||||
|
||||
export function SaveProfile(arg1:profile.Profile):Promise<profile.Profile>;
|
||||
|
||||
export function SaveQSLDefaults(arg1:main.QSLDefaults):Promise<void>;
|
||||
@@ -301,6 +305,8 @@ export function SetUIPref(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function SwitchCATRig(arg1:number):Promise<void>;
|
||||
|
||||
export function SyncPOTAHunterLog():Promise<main.POTASyncResult>;
|
||||
|
||||
export function TestClublogUpload():Promise<string>;
|
||||
|
||||
export function TestEmail(arg1:string):Promise<void>;
|
||||
|
||||
@@ -254,6 +254,10 @@ export function GetLookupSettings() {
|
||||
return window['go']['main']['App']['GetLookupSettings']();
|
||||
}
|
||||
|
||||
export function GetPOTAToken() {
|
||||
return window['go']['main']['App']['GetPOTAToken']();
|
||||
}
|
||||
|
||||
export function GetQSLDefaults() {
|
||||
return window['go']['main']['App']['GetQSLDefaults']();
|
||||
}
|
||||
@@ -502,6 +506,10 @@ export function SaveOperatingStation(arg1) {
|
||||
return window['go']['main']['App']['SaveOperatingStation'](arg1);
|
||||
}
|
||||
|
||||
export function SavePOTAToken(arg1) {
|
||||
return window['go']['main']['App']['SavePOTAToken'](arg1);
|
||||
}
|
||||
|
||||
export function SaveProfile(arg1) {
|
||||
return window['go']['main']['App']['SaveProfile'](arg1);
|
||||
}
|
||||
@@ -574,6 +582,10 @@ export function SwitchCATRig(arg1) {
|
||||
return window['go']['main']['App']['SwitchCATRig'](arg1);
|
||||
}
|
||||
|
||||
export function SyncPOTAHunterLog() {
|
||||
return window['go']['main']['App']['SyncPOTAHunterLog']();
|
||||
}
|
||||
|
||||
export function TestClublogUpload() {
|
||||
return window['go']['main']['App']['TestClublogUpload']();
|
||||
}
|
||||
|
||||
@@ -973,6 +973,24 @@ export namespace main {
|
||||
}
|
||||
}
|
||||
|
||||
export class POTASyncResult {
|
||||
fetched: number;
|
||||
updated: number;
|
||||
already_tagged: number;
|
||||
unmatched: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new POTASyncResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.fetched = source["fetched"];
|
||||
this.updated = source["updated"];
|
||||
this.already_tagged = source["already_tagged"];
|
||||
this.unmatched = source["unmatched"];
|
||||
}
|
||||
}
|
||||
export class QSLDefaults {
|
||||
qsl_sent: string;
|
||||
qsl_rcvd: string;
|
||||
|
||||
Reference in New Issue
Block a user