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" />
|
||||
|
||||
Reference in New Issue
Block a user