import { useMemo, useState, useEffect } from "react"; import { Link, useNavigate, useSearchParams } from "react-router-dom"; import { ArrowLeft, Download, Edit3, Grid, Image, Layout, Plus, Settings, Table, Trash2, } from "react-feather"; import { putJson, request } from "../lib/api"; import { BUILTIN_PAGE_TEMPLATES, createCustomTemplateId, mergePageTemplates, } from "../lib/pageTemplates"; import { ensureSections, flattenSections } from "../lib/sections"; import { buildSessionQuery, getSessionId, setStoredSessionId } from "../lib/session"; import type { JobsheetSection, PageTemplateDefinition, Session } from "../types/session"; import { InfoMenu } from "../components/InfoMenu"; import { PageFooter } from "../components/PageFooter"; import { PageHeader } from "../components/PageHeader"; import { PageShell } from "../components/PageShell"; type Variant = "full" | "photos"; type PhotoLayout = "auto" | "two-column" | "stacked"; const DEFAULT_VARIANT: Variant = "full"; const DEFAULT_PHOTO_LAYOUT: PhotoLayout = "auto"; export default function LayoutTemplatesPage() { const [searchParams] = useSearchParams(); const sessionId = getSessionId(searchParams.toString()); const sessionQuery = buildSessionQuery(sessionId); const navigate = useNavigate(); const [session, setSession] = useState(null); const [sections, setSections] = useState([]); const [templates, setTemplates] = useState( BUILTIN_PAGE_TEMPLATES, ); const [customTemplates, setCustomTemplates] = useState( [], ); const [newName, setNewName] = useState(""); const [newDescription, setNewDescription] = useState(""); const [newVariant, setNewVariant] = useState(DEFAULT_VARIANT); const [newPhotoLayout, setNewPhotoLayout] = useState(DEFAULT_PHOTO_LAYOUT); const [newBlank, setNewBlank] = useState(false); const [status, setStatus] = useState(""); const [isSaving, setIsSaving] = useState(false); useEffect(() => { if (!sessionId) { setStatus("No active session found."); return; } setStoredSessionId(sessionId); async function load() { try { const [data, sectionResp] = await Promise.all([ request(`/sessions/${sessionId}`), request<{ sections: JobsheetSection[] }>(`/sessions/${sessionId}/sections`), ]); setSession(data); setSections(ensureSections(sectionResp.sections)); const merged = mergePageTemplates(data.page_templates); setTemplates(merged); setCustomTemplates(merged.filter((template) => template.source === "custom")); } catch (error) { const message = error instanceof Error ? error.message : "Failed to load templates."; setStatus(message); } } load(); }, [sessionId]); const flatPages = useMemo( () => flattenSections(ensureSections(sections)), [sections], ); const saveTemplates = async (nextTemplates: PageTemplateDefinition[]) => { if (!sessionId) return; setIsSaving(true); setStatus("Saving templates..."); try { const payload = nextTemplates.map((template) => ({ id: template.id, name: template.name, description: template.description ?? "", blank: Boolean(template.blank), variant: (template.variant ?? DEFAULT_VARIANT) as Variant, photo_layout: (template.photo_layout ?? DEFAULT_PHOTO_LAYOUT) as PhotoLayout, })); const response = await putJson<{ page_templates: PageTemplateDefinition[] }>( `/sessions/${sessionId}/page-templates`, { page_templates: payload }, ); const merged = mergePageTemplates(response.page_templates); setTemplates(merged); setCustomTemplates(merged.filter((template) => template.source === "custom")); setSession((prev) => prev ? { ...prev, page_templates: response.page_templates ?? payload } : prev, ); setStatus("Templates saved."); } catch (error) { const message = error instanceof Error ? error.message : "Failed to save templates."; setStatus(message); } finally { setIsSaving(false); } }; const addTemplate = async () => { const name = newName.trim(); if (!name) { setStatus("Template name is required."); return; } const nextTemplate: PageTemplateDefinition = { id: createCustomTemplateId(), name, description: newDescription.trim(), blank: newBlank, variant: newVariant, photo_layout: newPhotoLayout, source: "custom", }; await saveTemplates([...customTemplates, nextTemplate]); setNewName(""); setNewDescription(""); setNewVariant(DEFAULT_VARIANT); setNewPhotoLayout(DEFAULT_PHOTO_LAYOUT); setNewBlank(false); }; const updateCustomTemplate = ( templateId: string, patch: Partial, ) => { setCustomTemplates((prev) => prev.map((template) => template.id === templateId ? { ...template, ...patch } : template, ), ); }; const removeCustomTemplate = async (templateId: string) => { const next = customTemplates.filter((template) => template.id !== templateId); await saveTemplates(next); }; const persistCustomTemplateEdits = async () => { await saveTemplates(customTemplates); }; return ( Back } />