Skrytí osobních údajů #1
Nezobrazovat detail logu anonymnímu uživateli #2
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useContestStore } from "@/stores/contestStore";
|
||||
import { useLanguageStore } from "@/stores/languageStore";
|
||||
import { useUserStore } from "@/stores/userStore";
|
||||
import LogQsoTable from "@/components/LogQsoTable";
|
||||
|
||||
type LogDetailData = {
|
||||
@@ -190,6 +191,8 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
const { t } = useTranslation("common");
|
||||
const locale = useLanguageStore((s) => s.locale) || navigator.language || "cs-CZ";
|
||||
const setSelectedRound = useContestStore((s) => s.setSelectedRound);
|
||||
const user = useUserStore((s) => s.user);
|
||||
const isAdmin = !!user?.is_admin;
|
||||
|
||||
const [detail, setDetail] = useState<LogDetailData | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -209,8 +212,9 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const res = await axios.get<LogDetailData>(`/api/logs/${logId}`, {
|
||||
params: { include_qsos: 0 },
|
||||
const endpoint = isAdmin ? `/api/logs/${logId}` : `/api/logs/${logId}/public`;
|
||||
const res = await axios.get<LogDetailData>(endpoint, {
|
||||
params: isAdmin ? { include_qsos: 0 } : undefined,
|
||||
headers: { Accept: "application/json" },
|
||||
withCredentials: true,
|
||||
});
|
||||
@@ -230,9 +234,13 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
logs_deadline: res.data.round.logs_deadline ?? null,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
} catch (e: any) {
|
||||
if (!active) return;
|
||||
setError(t("unable_to_load_log") ?? "Nepodařilo se načíst log.");
|
||||
const fallback = isAdmin
|
||||
? t("unable_to_load_log") ?? "Nepodařilo se načíst log."
|
||||
: "Detail logu bude dostupný po zveřejnění finálních výsledků.";
|
||||
const message = e?.response?.data?.message || fallback;
|
||||
setError(message);
|
||||
} finally {
|
||||
if (active) setLoading(false);
|
||||
}
|
||||
@@ -240,10 +248,10 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [logId, t, setSelectedRound]);
|
||||
}, [logId, t, setSelectedRound, isAdmin]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!detail?.id) return;
|
||||
if (!detail?.id || !isAdmin) return;
|
||||
let active = true;
|
||||
|
||||
(async () => {
|
||||
@@ -319,10 +327,13 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [detail?.id]);
|
||||
}, [detail?.id, isAdmin]);
|
||||
|
||||
const title = (() => {
|
||||
const pcall = detail?.pcall ?? "";
|
||||
if (!isAdmin) {
|
||||
return pcall || (t("log") ?? "Log");
|
||||
}
|
||||
const rcall = detail?.rcall ?? "";
|
||||
if (pcall && rcall && pcall !== rcall) {
|
||||
return `${pcall}-${rcall}`;
|
||||
@@ -368,134 +379,190 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
{loading && <div>{t("loading") ?? "Načítám..."}</div>}
|
||||
{detail && !loading && (
|
||||
<div className="space-y-4 text-sm">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_pcall_hint") ?? "Call used during contest"}>
|
||||
PCall:
|
||||
</span>
|
||||
<span>
|
||||
{detail.pcall || "—"}
|
||||
{detail.pclub ? ` (${detail.pclub})` : ""}
|
||||
</span>
|
||||
</div>
|
||||
{detail.pband && (
|
||||
{isAdmin ? (
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_pband_hint") ?? "Band"}>PBand:</span>
|
||||
<span>{detail.pband}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.psect && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_psect_hint") ?? "Section / category"}>PSect:</span>
|
||||
<span>{detail.psect}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.padr1 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_padr1_hint") ?? "Address line 1 (QTH)"}>PAdr1:</span>
|
||||
<span>{detail.padr1}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.padr2 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_padr2_hint") ?? "Address line 2 (QTH)"}>PAdr2:</span>
|
||||
<span>{detail.padr2}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.mope1 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_mope1_hint") ?? "Multi operator line 1"}>MOpe1:</span>
|
||||
<span>{detail.mope1}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.mope2 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_mope2_hint") ?? "Multi operator line 2"}>MOpe2:</span>
|
||||
<span>{detail.mope2}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rcall_hint") ?? "Responsible operator callsign"}>RCall:</span>
|
||||
<span>{detail.rcall || "—"}</span>
|
||||
</div>
|
||||
{detail.radr1 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_radr1_hint") ?? "Address line 1 of responsible operator"}>RAdr1:</span>
|
||||
<span>{detail.radr1}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.radr2 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_radr2_hint") ?? "Address line 2 of responsible operator"}>RAdr2:</span>
|
||||
<span>{detail.radr2}</span>
|
||||
</div>
|
||||
)}
|
||||
{(detail.rpoco || detail.rcity) && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rpoco_rcity_hint") ?? "Postal code / city of responsible operator"}>RPoCo/RCity:</span>
|
||||
<span className="font-semibold" title={t("edi_pcall_hint") ?? "Call used during contest"}>
|
||||
PCall:
|
||||
</span>
|
||||
<span>
|
||||
{detail.rpoco ?? ""} {detail.rcity ?? ""}
|
||||
{detail.pcall || "—"}
|
||||
{detail.pclub ? ` (${detail.pclub})` : ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.rcoun && (
|
||||
{detail.pband && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_pband_hint") ?? "Band"}>PBand:</span>
|
||||
<span>{detail.pband}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.psect && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_psect_hint") ?? "Section / category"}>PSect:</span>
|
||||
<span>{detail.psect}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.padr1 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_padr1_hint") ?? "Address line 1 (QTH)"}>PAdr1:</span>
|
||||
<span>{detail.padr1}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.padr2 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_padr2_hint") ?? "Address line 2 (QTH)"}>PAdr2:</span>
|
||||
<span>{detail.padr2}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.mope1 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_mope1_hint") ?? "Multi operator line 1"}>MOpe1:</span>
|
||||
<span>{detail.mope1}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.mope2 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_mope2_hint") ?? "Multi operator line 2"}>MOpe2:</span>
|
||||
<span>{detail.mope2}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rcoun_hint") ?? "Country of responsible operator"}>RCoun:</span>
|
||||
<span>{detail.rcoun}</span>
|
||||
<span className="font-semibold" title={t("edi_rcall_hint") ?? "Responsible operator callsign"}>RCall:</span>
|
||||
<span>{detail.rcall || "—"}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.rphon && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rphon_hint") ?? "Phone of responsible operator"}>RPhon:</span>
|
||||
<span>{detail.rphon}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.rhbbs && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rhbbs_hint") ?? "Home BBS of responsible operator"}>RHBBS:</span>
|
||||
<span>{detail.rhbbs}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{detail.radr1 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_radr1_hint") ?? "Address line 1 of responsible operator"}>RAdr1:</span>
|
||||
<span>{detail.radr1}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.radr2 && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_radr2_hint") ?? "Address line 2 of responsible operator"}>RAdr2:</span>
|
||||
<span>{detail.radr2}</span>
|
||||
</div>
|
||||
)}
|
||||
{(detail.rpoco || detail.rcity) && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rpoco_rcity_hint") ?? "Postal code / city of responsible operator"}>RPoCo/RCity:</span>
|
||||
<span>
|
||||
{detail.rpoco ?? ""} {detail.rcity ?? ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.rcoun && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rcoun_hint") ?? "Country of responsible operator"}>RCoun:</span>
|
||||
<span>{detail.rcoun}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.rphon && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rphon_hint") ?? "Phone of responsible operator"}>RPhon:</span>
|
||||
<span>{detail.rphon}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.rhbbs && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_rhbbs_hint") ?? "Home BBS of responsible operator"}>RHBBS:</span>
|
||||
<span>{detail.rhbbs}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{detail.stxeq && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_stxeq_hint") ?? "TX equipment"}>STXEq:</span>
|
||||
<span>{detail.stxeq}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.srxeq && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_srxeq_hint") ?? "RX equipment"}>SRXEq:</span>
|
||||
<span>{detail.srxeq}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.power_watt && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_spowe_hint") ?? "TX power [W]"}>SPowe:</span>
|
||||
<span>{detail.power_watt}</span>
|
||||
<div className="space-y-2">
|
||||
{detail.stxeq && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_stxeq_hint") ?? "TX equipment"}>STXEq:</span>
|
||||
<span>{detail.stxeq}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.srxeq && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_srxeq_hint") ?? "RX equipment"}>SRXEq:</span>
|
||||
<span>{detail.srxeq}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.power_watt && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_spowe_hint") ?? "TX power [W]"}>SPowe:</span>
|
||||
<span>{detail.power_watt}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.sante && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_sante_hint") ?? "Antenna"}>SAntenna:</span>
|
||||
<span>{detail.sante}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.santh && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_santh_hint") ?? "Antenna height [m] / ASL [m]"}>SAntH:</span>
|
||||
<span>{detail.santh}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{detail.sante && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_sante_hint") ?? "Antenna"}>SAntenna:</span>
|
||||
<span>{detail.sante}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.santh && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_santh_hint") ?? "Antenna height [m] / ASL [m]"}>SAntH:</span>
|
||||
<span>{detail.santh}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_pcall_hint") ?? "Call used during contest"}>
|
||||
PCall:
|
||||
</span>
|
||||
<span>{detail.pcall || "—"}</span>
|
||||
</div>
|
||||
{detail.pband && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_pband_hint") ?? "Band"}>PBand:</span>
|
||||
<span>{detail.pband}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.psect && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_psect_hint") ?? "Section / category"}>PSect:</span>
|
||||
<span>{detail.psect}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.power_watt && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_spowe_hint") ?? "TX power [W]"}>SPowe:</span>
|
||||
<span>{detail.power_watt}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{detail.stxeq && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_stxeq_hint") ?? "TX equipment"}>STXEq:</span>
|
||||
<span>{detail.stxeq}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.srxeq && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_srxeq_hint") ?? "RX equipment"}>SRXEq:</span>
|
||||
<span>{detail.srxeq}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.sante && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_sante_hint") ?? "Antenna"}>SAntenna:</span>
|
||||
<span>{detail.sante}</span>
|
||||
</div>
|
||||
)}
|
||||
{detail.santh && (
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold" title={t("edi_santh_hint") ?? "Antenna height [m] / ASL [m]"}>SAntH:</span>
|
||||
<span>{detail.santh}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Divider />
|
||||
<div className="space-y-3">
|
||||
<div className="grid gap-4 md:grid-cols-2 text-sm">
|
||||
<div className={isAdmin ? "grid gap-4 md:grid-cols-2 text-sm" : "text-sm"}>
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold">Deklarované výsledky</h4>
|
||||
<div className="flex gap-2">
|
||||
@@ -515,43 +582,45 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
<span>{detail.claimed_dxcc ?? "—"}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold">Zkontrolované výsledky</h4>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Počet QSO:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.valid_qso_count ?? "—"}
|
||||
</span>
|
||||
{isAdmin && (
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-semibold">Zkontrolované výsledky</h4>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Počet QSO:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.valid_qso_count ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Body:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.official_score ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Unikátních WWL:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.multiplier_count ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Penalizace:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.penalty_score ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
{officialError && (
|
||||
<div className="text-xs text-red-600">{officialError}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Body:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.official_score ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Unikátních WWL:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.multiplier_count ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="font-semibold">Penalizace:</span>
|
||||
<span>
|
||||
{officialLoading ? "…" : officialResult?.penalty_score ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
{officialError && (
|
||||
<div className="text-xs text-red-600">{officialError}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{detail.remarks_eval && (
|
||||
{isAdmin && detail.remarks_eval && (
|
||||
<div className="text-sm text-red-600">
|
||||
{renderRemarksEval(detail.remarks_eval)}
|
||||
</div>
|
||||
)}
|
||||
{detail.raw_header && (
|
||||
{isAdmin && detail.raw_header && (
|
||||
<Accordion>
|
||||
<AccordionItem
|
||||
key="raw"
|
||||
@@ -570,7 +639,7 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{(logOverrideReason || Object.keys(qsoOverrides).length > 0) && (
|
||||
{isAdmin && (logOverrideReason || Object.keys(qsoOverrides).length > 0) && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<span className="text-md font-semibold">Zásahy rozhodčího</span>
|
||||
@@ -598,24 +667,26 @@ export default function LogDetail({ logId }: LogDetailProps) {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<span className="text-md font-semibold">QSO</span>
|
||||
</CardHeader>
|
||||
<Divider />
|
||||
<CardBody>
|
||||
<LogQsoTable
|
||||
key={`${detail?.id ?? "log"}-${qsoTableRows.length}-${Object.keys(qsoOverrides).length}-${Object.keys(officialQsoResults).length}`}
|
||||
qsos={qsoTableRows}
|
||||
locale={locale}
|
||||
formatDateTime={formatDateTime}
|
||||
officialQsoResults={officialQsoResults}
|
||||
qsoOverrides={qsoOverrides}
|
||||
emptyLabel={t("logs_empty") ?? "Žádné QSO záznamy."}
|
||||
callsign={title}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{isAdmin && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<span className="text-md font-semibold">QSO</span>
|
||||
</CardHeader>
|
||||
<Divider />
|
||||
<CardBody>
|
||||
<LogQsoTable
|
||||
key={`${detail?.id ?? "log"}-${qsoTableRows.length}-${Object.keys(qsoOverrides).length}-${Object.keys(officialQsoResults).length}`}
|
||||
qsos={qsoTableRows}
|
||||
locale={locale}
|
||||
formatDateTime={formatDateTime}
|
||||
officialQsoResults={officialQsoResults}
|
||||
qsoOverrides={qsoOverrides}
|
||||
emptyLabel={t("logs_empty") ?? "Žádné QSO záznamy."}
|
||||
callsign={title}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user