250 lines
9.5 KiB
TypeScript
250 lines
9.5 KiB
TypeScript
import { memo, useEffect, useState } from "react";
|
|
import { Button, Select, SelectItem, Textarea, type Selection } from "@heroui/react";
|
|
import { useTranslation } from "react-i18next";
|
|
import type { LogItem, LogOverride, OverrideForm } from "@/components/RoundEvaluationOverrides.types";
|
|
|
|
type RoundEvaluationOverrideRowProps = {
|
|
log: LogItem;
|
|
form?: OverrideForm;
|
|
override?: LogOverride;
|
|
bands: { id: number; name: string }[];
|
|
categories: { id: number; name: string }[];
|
|
powerCategories: { id: number; name: string }[];
|
|
onFieldChange: (logId: number, field: keyof OverrideForm, value: string) => void;
|
|
onReasonCommit: (logId: number, reason: string) => void;
|
|
onSave: (logId: number, reason?: string) => void;
|
|
onOpenDetail: (logId: number) => void;
|
|
};
|
|
|
|
const getFirstSelection = (keys: Selection) => {
|
|
if (keys === "all") return "";
|
|
const [first] = Array.from(keys);
|
|
return typeof first === "string" || typeof first === "number" ? String(first) : "";
|
|
};
|
|
|
|
const highlightSelectClassNames = (isHighlighted: boolean) =>
|
|
isHighlighted
|
|
? {
|
|
trigger: "border-warning-400 bg-warning-50",
|
|
}
|
|
: undefined;
|
|
|
|
const RoundEvaluationOverrideRow = memo(function RoundEvaluationOverrideRow({
|
|
log,
|
|
form,
|
|
override,
|
|
bands,
|
|
categories,
|
|
powerCategories,
|
|
onFieldChange,
|
|
onReasonCommit,
|
|
onSave,
|
|
onOpenDetail,
|
|
}: RoundEvaluationOverrideRowProps) {
|
|
const { t } = useTranslation("common");
|
|
const highlightStatus = !!override?.forced_log_status && override.forced_log_status !== "AUTO";
|
|
const highlightBand = override?.forced_band_id !== null && override?.forced_band_id !== undefined;
|
|
const highlightCategory =
|
|
override?.forced_category_id !== null && override?.forced_category_id !== undefined;
|
|
const highlightPower =
|
|
override?.forced_power_category_id !== null && override?.forced_power_category_id !== undefined;
|
|
const highlightSixhr =
|
|
override?.forced_sixhr_category !== null && override?.forced_sixhr_category !== undefined;
|
|
const [localReason, setLocalReason] = useState(form?.reason ?? override?.reason ?? "");
|
|
|
|
useEffect(() => {
|
|
setLocalReason(form?.reason ?? override?.reason ?? "");
|
|
}, [form?.reason, override?.reason]);
|
|
|
|
return (
|
|
<div className="rounded border border-divider px-2 py-1 text-xs">
|
|
<div className="flex flex-wrap items-center gap-2 mb-2 text-sm">
|
|
<span className="font-semibold">{log.pcall ?? "—"}</span>
|
|
<span>PBAND: {log.pband ?? "—"}</span>
|
|
<span>PSECT: {log.psect ?? "—"}</span>
|
|
<span>SPowe: {log.power_watt ?? "—"}</span>
|
|
<span>PWWLo: {log.pwwlo ?? "—"}</span>
|
|
<Button
|
|
type="button"
|
|
size="sm"
|
|
variant="light"
|
|
className="h-7 min-h-[28px] text-xs px-2"
|
|
onPress={() => onOpenDetail(log.id)}
|
|
>
|
|
{t("override_detail") ?? "Detail"}
|
|
</Button>
|
|
</div>
|
|
{override?.reason && (
|
|
<div className="mb-2 text-foreground-500 text-sm">
|
|
{t("override_reason_prefix") ?? "Důvod"}: {override.reason}
|
|
</div>
|
|
)}
|
|
<div className="grid gap-1 md:grid-cols-7">
|
|
<Select
|
|
label={t("override_status_label") ?? "Status"}
|
|
aria-label="Status"
|
|
size="sm"
|
|
variant="bordered"
|
|
classNames={{
|
|
...highlightSelectClassNames(highlightStatus),
|
|
trigger: "h-8 min-h-[32px]",
|
|
}}
|
|
selectedKeys={new Set([form?.status ?? "AUTO"])}
|
|
onSelectionChange={(keys) =>
|
|
onFieldChange(log.id, "status", getFirstSelection(keys) || "AUTO")
|
|
}
|
|
selectionMode="single"
|
|
disallowEmptySelection={true}
|
|
>
|
|
{[
|
|
{ value: "AUTO", label: t("override_status_auto") ?? "AUTO" },
|
|
{ value: "IGNORED", label: t("override_status_ignored") ?? "IGNORED" },
|
|
{ value: "CHECK", label: t("override_status_check") ?? "CHECK" },
|
|
{ value: "OK", label: t("override_status_ok") ?? "OK" },
|
|
{ value: "DQ", label: t("override_status_dq") ?? "DQ" },
|
|
].map((opt) => (
|
|
<SelectItem key={opt.value} textValue={opt.label}>
|
|
{opt.label}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
<Select
|
|
label={t("override_band_label") ?? "Band"}
|
|
aria-label="Band"
|
|
size="sm"
|
|
variant="bordered"
|
|
classNames={{
|
|
...highlightSelectClassNames(highlightBand),
|
|
trigger: "h-8 min-h-[32px]",
|
|
}}
|
|
selectedKeys={new Set([form?.bandId ? form.bandId : "auto"])}
|
|
onSelectionChange={(keys) => {
|
|
const value = getFirstSelection(keys);
|
|
onFieldChange(log.id, "bandId", value === "auto" ? "" : value);
|
|
}}
|
|
selectionMode="single"
|
|
disallowEmptySelection={true}
|
|
>
|
|
<SelectItem key="auto" textValue={t("override_auto") ?? "AUTO"}>
|
|
{t("override_auto") ?? "AUTO"}
|
|
</SelectItem>
|
|
{bands.map((band) => (
|
|
<SelectItem key={String(band.id)} textValue={band.name}>
|
|
{band.name}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
<Select
|
|
label={t("override_category_label") ?? "Kategorie"}
|
|
aria-label="Kategorie"
|
|
size="sm"
|
|
variant="bordered"
|
|
classNames={{
|
|
...highlightSelectClassNames(highlightCategory),
|
|
trigger: "h-8 min-h-[32px]",
|
|
}}
|
|
selectedKeys={new Set([form?.categoryId ? form.categoryId : "auto"])}
|
|
onSelectionChange={(keys) => {
|
|
const value = getFirstSelection(keys);
|
|
onFieldChange(log.id, "categoryId", value === "auto" ? "" : value);
|
|
}}
|
|
selectionMode="single"
|
|
disallowEmptySelection={true}
|
|
>
|
|
<SelectItem key="auto" textValue={t("override_auto") ?? "AUTO"}>
|
|
{t("override_auto") ?? "AUTO"}
|
|
</SelectItem>
|
|
{categories.map((cat) => (
|
|
<SelectItem key={String(cat.id)} textValue={cat.name}>
|
|
{cat.name}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
<Select
|
|
aria-label="Výkon"
|
|
size="sm"
|
|
label={t("override_power_label") ?? "Výkon"}
|
|
variant="bordered"
|
|
classNames={{
|
|
...highlightSelectClassNames(highlightPower),
|
|
trigger: "h-8 min-h-[32px]",
|
|
}}
|
|
selectedKeys={new Set([form?.powerCategoryId ? form.powerCategoryId : "auto"])}
|
|
onSelectionChange={(keys) => {
|
|
const value = getFirstSelection(keys);
|
|
onFieldChange(log.id, "powerCategoryId", value === "auto" ? "" : value);
|
|
}}
|
|
selectionMode="single"
|
|
disallowEmptySelection={true}
|
|
>
|
|
<SelectItem key="auto" textValue={t("override_auto") ?? "AUTO"}>
|
|
{t("override_auto") ?? "AUTO"}
|
|
</SelectItem>
|
|
{powerCategories.map((cat) => (
|
|
<SelectItem key={String(cat.id)} textValue={cat.name}>
|
|
{cat.name}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
<Select
|
|
aria-label="6H"
|
|
size="sm"
|
|
label={t("override_sixhr_label") ?? "6H"}
|
|
variant="bordered"
|
|
classNames={{
|
|
...highlightSelectClassNames(highlightSixhr),
|
|
trigger: "h-8 min-h-[32px]",
|
|
}}
|
|
selectedKeys={new Set([form?.sixhrCategory ? form.sixhrCategory : "auto"])}
|
|
onSelectionChange={(keys) => {
|
|
const value = getFirstSelection(keys);
|
|
onFieldChange(log.id, "sixhrCategory", value === "auto" ? "" : value);
|
|
}}
|
|
selectionMode="single"
|
|
disallowEmptySelection={true}
|
|
>
|
|
<SelectItem key="auto" textValue={t("override_auto") ?? "AUTO"}>
|
|
{t("override_auto") ?? "AUTO"}
|
|
</SelectItem>
|
|
<SelectItem key="1" textValue={t("yes") ?? "Ano"}>
|
|
{t("yes") ?? "Ano"}
|
|
</SelectItem>
|
|
<SelectItem key="0" textValue={t("no") ?? "Ne"}>
|
|
{t("no") ?? "Ne"}
|
|
</SelectItem>
|
|
</Select>
|
|
<Textarea
|
|
label={t("override_reason_label") ?? "Důvod změny"}
|
|
size="sm"
|
|
variant="bordered"
|
|
minRows={1}
|
|
value={localReason}
|
|
onChange={(e) => setLocalReason(e.target.value)}
|
|
onBlur={() => onReasonCommit(log.id, localReason)}
|
|
classNames={{
|
|
inputWrapper: "h-8 min-h-[32px] py-0",
|
|
input: "h-5 text-xs",
|
|
}}
|
|
/>
|
|
<div className="flex items-center gap-3">
|
|
<Button
|
|
type="button"
|
|
size="sm"
|
|
color="primary"
|
|
isDisabled={form?.saving}
|
|
onPress={() => onSave(log.id, localReason)}
|
|
>
|
|
{form?.saving
|
|
? t("override_saving") ?? "Ukládám…"
|
|
: t("override_save") ?? "Uložit"}
|
|
</Button>
|
|
{form?.error && <span className="text-red-600">{form.error}</span>}
|
|
{form?.success && <span className="text-green-600">{form.success}</span>}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
export default RoundEvaluationOverrideRow;
|