import { useResponsive } from "./use-responsive.svelte.js"; import { useForm } from "./use-form.svelte.js"; import { tick } from "svelte"; export function useMasterDetail(options = {}) { const { onSelect = null, formConfig = null, } = options; let selectedItem = $state(null); let mode = $state("view"); let isLoadingDetail = $state(false); let formSnapshot = $state(null); let extraSnapshots = $state({}); let extraRegistry = $state.raw([]); let showExitConfirm = $state(false); const formState = useForm(formConfig); const { isMobile } = useResponsive(); const isFormMode = $derived(mode === "create" || mode === "edit"); const showMaster = $derived(!isMobile || (mode === "view" && !selectedItem)); const showDetail = $derived(!isMobile || selectedItem || isFormMode); const layout = $derived({ masterWidth: isMobile ? "w-full" : isFormMode ? "w-[3%]" : "w-[35%]", detailWidth: isMobile ? "w-full" : isFormMode ? "w-[97%]" : "w-[65%]", }); // const isDirty = $derived( // JSON.stringify(formState.form) !== JSON.stringify(formSnapshot) // ); // const isDirty = $derived( // JSON.stringify(formState.form) !== JSON.stringify(formSnapshot) || // (extraRegistry.length > 0 && extraRegistry.some(({ key, get }) => // JSON.stringify(get()) !== JSON.stringify(extraSnapshots[key]) // )) // ); const isDirty = $derived.by(() => { if (JSON.stringify(formState.form) !== JSON.stringify(formSnapshot)) { return true; } return extraRegistry.some((item) => { const current = item.get(); const snapshot = extraSnapshots[item.key]; return JSON.stringify(current) !== JSON.stringify(snapshot); }); }); async function select(item) { mode = "view"; if (onSelect) { isLoadingDetail = true; try { const detailData = await onSelect(item); selectedItem = detailData; } catch (error) { console.error("Failed to fetch detail:", error); selectedItem = null; } finally { isLoadingDetail = false; } } else { selectedItem = item; } } async function enterCreate(initialData = null) { mode = "create"; selectedItem = null; formState.reset(); for (const item of extraRegistry) { item.reset?.(); } if (initialData) { formState.setForm(initialData); } await tick(); formSnapshot = $state.snapshot(formState.form); // extraSnapshots = extraRegistry.length > 0 ? Object.fromEntries( // extraRegistry.map(({ key, get }) => [key, $state.snapshot(get())]) // ) : {}; extraSnapshots = extraRegistry.length > 0 ? Object.fromEntries(extraRegistry.map(item => [item.key, $state.snapshot(item.get())])) : {}; } async function enterEdit(param) { if (!selectedItem) return; mode = "edit"; const raw = param ? selectedItem[param] : selectedItem; if (!raw) return; const base = $state.snapshot(raw); const entity = formConfig.mapToForm ? formConfig.mapToForm(base) : base; formState.reset(); formState.setForm(entity); Object.keys(formState.errors).forEach(key => { formState.errors[key] = null; }); formSnapshot = $state.snapshot(formState.form); await tick(); if (extraRegistry.length > 0) { extraSnapshots = Object.fromEntries( // extraRegistry.map(({ key, get }) => [key, $state.snapshot(get())]) extraRegistry.map((item) => { const key = item.key; const get = item.get; return [key, $state.snapshot(get())]; }) ); } } function exitForm(force = false) { if (!force && isDirty) { showExitConfirm = true; return; } // Direct exit mode = "view"; selectedItem = null; formSnapshot = null; } function confirmExit() { mode = "view"; selectedItem = null; formSnapshot = null; extraSnapshots = {}; } function backToList() { selectedItem = null; mode = "view"; formSnapshot = null; extraSnapshots = {} } function saveForm() { formSnapshot = { ...form }; } function registerExtraState({ key, get, reset }) { const exists = extraRegistry.findIndex(e => e.key === key); if (exists !== -1) { extraRegistry[exists] = { key, get, reset }; } else { extraRegistry = [...extraRegistry, { key, get, reset }] } } return { get selectedItem() { return selectedItem; }, get mode() { return mode; }, get isFormMode() { return isFormMode; }, get isMobile() { return isMobile; }, get showMaster() { return showMaster; }, get showDetail() { return showDetail; }, get layout() { return layout; }, get formSnapshot() { return formSnapshot; }, // get form() { // return form; // }, get isDirty() { return isDirty; }, get isLoadingDetail() { return isLoadingDetail; }, get formState() { return formState; }, get showExitConfirm() { return showExitConfirm; }, set showExitConfirm(value) { showExitConfirm = value; }, select, enterCreate, enterEdit, exitForm, confirmExit, backToList, saveForm, registerExtraState, }; }