"use client"; import { useId, useMemo, useRef, useState } from "react"; import { Save } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { FormApiError } from "@/components/ui/form-api-error"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { DiveFormFieldSwitches, type DiveFormFieldsView, } from "@/components/dives/dive-form-field-switches"; import { DiveFormPresetList } from "@/components/dives/dive-form-preset-list"; import { DiveFormPresetSaveAs } from "@/components/dives/dive-form-preset-save-as"; import { canonicalHiddenFields, hiddenFieldsEqual, type DiveFormFieldKey, } from "@/lib/dive-form-fields"; import { currentDiveFormPreset, type DiveFormPreset, } from "@/lib/api/dive-form-presets"; import type { DiveFormPresets } from "@/hooks/useDiveFormPresets"; import type { DiveFormVisibility } from "@/hooks/useDiveFormVisibility"; interface Draft { hidden: readonly DiveFormFieldKey[]; /** The draft's keys whose switch the diver turned off, in the order of `hidden`. */ turnedOff: readonly DiveFormFieldKey[]; } interface DiveFormFieldsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; visibility: DiveFormVisibility; /** Held by the Fields menu and handed down, so opening this refetches nothing. */ presets: DiveFormPresets; } /** * The saved preset the fields on screen are on, or `""` for none - the built-in "All" * included, which nothing can be saved over. The same answer the Fields trigger gives, * so the two can never disagree about what is current. * * Recomputed on every render, deliberately. Freezing it at the moment the * dialog opened is the behaviour "Save as" needs, and it gets that from being * *mounted* then rather than from anything here holding a value still - see the * `key` at the call site. */ function matchingPresetName( presets: readonly DiveFormPreset[] | null, hidden: readonly DiveFormFieldKey[], pickedUuid: string | null, ): string { const current = currentDiveFormPreset(presets, hidden, pickedUuid); return current.kind === "saved" ? current.preset.name : ""; } /** * Configure: the fields the dive form asks for, and the presets they can be saved as. * * **Two tabs, Fields first and default**, because that is what a diver opens this for. * Presets is the housekeeping tab - renaming, deleting, restoring the three seeded * defaults - and nothing on it changes what the form shows. * * **Saving lives with the fields, not with the presets.** "Save as" sits at the foot of * the Fields tab, where the thing being saved is on screen above it, and its Save * button shows in the footer only while that tab is; a preset row is the wrong place * to write the current form into, which is what the per-row "Update with current * fields" button was. * * **The switches edit a draft.** Nothing reaches the form until Apply or Save preset; * Cancel, the X and Escape drop it, as they do in every other dialog. So there is * nothing to undo, and no hide has emptied a value it would have to bring back. The switches * preview what applying the draft will do, revealed fields included: the draft keeps * which switches were turned off, and those are the only fields `setHidden` takes off * screen when they hold a value. Preset writes on the Presets tab go to the server at * once and are not part of the draft. * * **A dialog rather than a panel on the card.** Nothing in it is a form control of the * dive. Base UI portals the content to `document.body`, so the DOM has no nested `