- ui/checkbox.tsx und form/checkbox-field.tsx: ganze Zeile, Kachel, Aufgabe oder Sheet-Zeile ist der Knopf (Base UI), Haken bzw. Strich für teilweise; role="switch" mit Daumen und Haken. ToggleChip und SwitchRow entfernt. - 14 Kacheln, 11 native Checkboxen, 3 Schalter-Zeilen, 6 Base-UI-Stellen und die Heute-Aufgaben umgestellt; Werte ans Backend unverändert. - Enter in einer Checkbox schickt kein Formular ab und schaltet nicht (Leertaste schaltet); Nachbarn mit mindestens 4 px Abstand; gesperrte Felder mit sichtbarem Grund; gesperrte Kacheln ganz gedimmt. - Baukasten-Wächter: keine eigenen Checkboxen/Schalter, auch bedingte role-Werte und setAttribute; Zeilenlisten mit Abstand. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
144 lines
4.9 KiB
TypeScript
144 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { Layers } from "lucide-react";
|
|
import type { InspectionFormValues } from "@/app/(app)/inspections/new/inspection-form-values";
|
|
import { FEED_CONDITION_LABELS, ratingToCondition } from "@/lib/colonies/condition";
|
|
import { COLONY_CONDITION_LABELS } from "@/lib/colonies/labels";
|
|
import type { Colony, ColonyCondition } from "@/lib/colonies/types";
|
|
import { CheckboxField } from "@/components/form/checkbox-field";
|
|
import { COLONY_TAKEOVER_FIELDS, type ColonyTakeoverField } from "@/lib/inspections/types";
|
|
|
|
export interface ColonyTakeoverSectionProps {
|
|
/** What the colony card holds now; null when it is not known (nothing loaded yet). */
|
|
colony: Pick<Colony, "occupiedFrames" | "broodStatus" | "feedStatus"> | null;
|
|
/** What this inspection recorded. */
|
|
values: Pick<InspectionFormValues, "colonyStrength" | "broodPattern" | "feedStatus">;
|
|
selected: ColonyTakeoverField[];
|
|
onChange: (fields: ColonyTakeoverField[]) => void;
|
|
/** Without colonies:update the section is not offered at all. */
|
|
canUpdateColony: boolean;
|
|
}
|
|
|
|
interface TakeoverRow {
|
|
field: ColonyTakeoverField;
|
|
label: string;
|
|
}
|
|
|
|
const NOT_RECORDED = "noch nicht erfasst";
|
|
const UNKNOWN = "bisher unbekannt";
|
|
|
|
function conditionText(
|
|
condition: ColonyCondition | null | undefined,
|
|
labels: Record<ColonyCondition, string>,
|
|
): string {
|
|
if (condition === undefined) return UNKNOWN;
|
|
return condition === null ? NOT_RECORDED : labels[condition];
|
|
}
|
|
|
|
function rowText(name: string, before: string, after: string): string {
|
|
return before === after ? `${name}: ${after} (unverändert)` : `${name}: ${before} → ${after}`;
|
|
}
|
|
|
|
/** One row per colony-card value this inspection has an answer for. */
|
|
function takeoverRows({ colony, values }: Pick<ColonyTakeoverSectionProps, "colony" | "values">): TakeoverRow[] {
|
|
const rows: TakeoverRow[] = [];
|
|
for (const field of COLONY_TAKEOVER_FIELDS) {
|
|
if (field === "occupiedFrames" && values.colonyStrength.trim() !== "") {
|
|
const before =
|
|
colony === null
|
|
? UNKNOWN
|
|
: colony.occupiedFrames === null
|
|
? NOT_RECORDED
|
|
: String(colony.occupiedFrames);
|
|
rows.push({
|
|
field,
|
|
label: rowText("Besetzte Waben", before, String(Number(values.colonyStrength))),
|
|
});
|
|
}
|
|
const brood = ratingToCondition(values.broodPattern);
|
|
if (field === "broodStatus" && brood !== null) {
|
|
rows.push({
|
|
field,
|
|
label: rowText(
|
|
"Brut",
|
|
conditionText(colony?.broodStatus, COLONY_CONDITION_LABELS),
|
|
COLONY_CONDITION_LABELS[brood],
|
|
),
|
|
});
|
|
}
|
|
if (field === "feedStatus" && values.feedStatus !== null) {
|
|
rows.push({
|
|
field,
|
|
label: rowText(
|
|
"Futter",
|
|
conditionText(colony?.feedStatus, FEED_CONDITION_LABELS),
|
|
FEED_CONDITION_LABELS[values.feedStatus],
|
|
),
|
|
});
|
|
}
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
function countText(count: number): string {
|
|
if (count === 0) return "Es wird nichts auf die Volkskarte übernommen.";
|
|
if (count === 1) return "1 Wert wird auf die Volkskarte übernommen.";
|
|
return `${count} Werte werden auf die Volkskarte übernommen.`;
|
|
}
|
|
|
|
/**
|
|
* „Volkskarte“ above the save button: which of today's answers also go onto
|
|
* the colony card, each as „bisher → neu“ with a box. Ticked are exactly the
|
|
* fields filled in this inspection; nothing is taken over that was not asked.
|
|
*/
|
|
export function ColonyTakeoverSection({
|
|
colony,
|
|
values,
|
|
selected,
|
|
onChange,
|
|
canUpdateColony,
|
|
}: ColonyTakeoverSectionProps) {
|
|
if (!canUpdateColony) {
|
|
return null;
|
|
}
|
|
const rows = takeoverRows({ colony, values });
|
|
|
|
function toggle(field: ColonyTakeoverField, checked: boolean) {
|
|
onChange(
|
|
COLONY_TAKEOVER_FIELDS.filter((candidate) =>
|
|
candidate === field ? checked : selected.includes(candidate),
|
|
),
|
|
);
|
|
}
|
|
|
|
const takenCount = rows.filter((row) => selected.includes(row.field)).length;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-2">
|
|
{rows.length > 0 ? (
|
|
<fieldset className="flex flex-col gap-2 rounded-xl bg-muted/50 p-3">
|
|
<legend className="flex items-center gap-2 px-1 text-sm font-semibold">
|
|
<Layers className="size-4" aria-hidden />
|
|
Volkskarte
|
|
</legend>
|
|
<p className="text-sm text-muted-foreground">
|
|
Diese Angaben auch auf der Volkskarte eintragen:
|
|
</p>
|
|
{rows.map((row) => (
|
|
<CheckboxField
|
|
key={row.field}
|
|
label={row.label}
|
|
checked={selected.includes(row.field)}
|
|
onCheckedChange={(checked) => toggle(row.field, checked)}
|
|
/>
|
|
))}
|
|
</fieldset>
|
|
) : null}
|
|
{/* Mounted even while empty, or its first message is not announced. */}
|
|
<p role="status" aria-live="polite" className="text-sm text-muted-foreground empty:hidden">
|
|
{rows.length > 0 ? countText(takenCount) : ""}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|