diff --git a/src/lib/components/composable/use-master-detail.svelte copy.js b/src/lib/components/composable/use-master-detail.svelte copy.js new file mode 100644 index 0000000..4c8235d --- /dev/null +++ b/src/lib/components/composable/use-master-detail.svelte copy.js @@ -0,0 +1,166 @@ +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 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) + ); + + 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(); + + if (initialData) { + formState.setForm(initialData); + } + await tick(); + formSnapshot = $state.snapshot(formState.form); + } + + 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); + } + + 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; + } + + function backToList() { + selectedItem = null; + mode = "view"; + } + + function saveForm() { + formSnapshot = { ...form }; + } + + 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, + }; +} \ No newline at end of file diff --git a/src/lib/components/composable/use-master-detail.svelte.js b/src/lib/components/composable/use-master-detail.svelte.js index d043226..46139ee 100644 --- a/src/lib/components/composable/use-master-detail.svelte.js +++ b/src/lib/components/composable/use-master-detail.svelte.js @@ -3,12 +3,14 @@ import { useForm } from "./use-form.svelte.js"; import { tick } from "svelte"; export function useMasterDetail(options = {}) { - const { onSelect = null, formConfig = null, } = options; + const { onSelect = null, formConfig = null, extraState = [], } = 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); @@ -26,10 +28,30 @@ export function useMasterDetail(options = {}) { detailWidth: isMobile ? "w-full" : isFormMode ? "w-[97%]" : "w-[65%]", }); - const isDirty = $derived( - JSON.stringify(formState.form) !== JSON.stringify(formSnapshot) - ); -// $inspect(formState.form) + // 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); + }); + }); + +$inspect(isDirty) async function select(item) { mode = "view"; @@ -52,17 +74,27 @@ export function useMasterDetail(options = {}) { async function enterCreate(initialData = null) { mode = "create"; selectedItem = null; - formState.reset(); + for (const { reset } of extraState) { + 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())]) + // ) : {}; + + const extraSnapshots = extraRegistry.length > 0 + ? Object.fromEntries(extraRegistry.map(item => [item.key, $state.snapshot(item.get())])) + : {}; } - function enterEdit(param) { + async function enterEdit(param) { if (!selectedItem) return; mode = "edit"; @@ -86,6 +118,20 @@ export function useMasterDetail(options = {}) { }); 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) { @@ -104,17 +150,29 @@ export function useMasterDetail(options = {}) { 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; @@ -143,9 +201,9 @@ export function useMasterDetail(options = {}) { // get form() { // return form; // }, - // get isDirty() { - // return isDirty; - // }, + get isDirty() { + return isDirty; + }, get isLoadingDetail() { return isLoadingDetail; }, @@ -162,5 +220,6 @@ export function useMasterDetail(options = {}) { confirmExit, backToList, saveForm, + registerExtraState, }; } \ No newline at end of file diff --git a/src/lib/components/dictionary/testmap/config/testmap-config.js b/src/lib/components/dictionary/testmap/config/testmap-config.js index e57e33c..321a7eb 100644 --- a/src/lib/components/dictionary/testmap/config/testmap-config.js +++ b/src/lib/components/dictionary/testmap/config/testmap-config.js @@ -3,24 +3,21 @@ import Settings2Icon from "@lucide/svelte/icons/settings-2"; import PencilIcon from "@lucide/svelte/icons/pencil"; import { API } from "$lib/config/api"; import RefreshIcon from "@lucide/svelte/icons/refresh-cw"; +import LockKeyholeIcon from "@lucide/svelte/icons/lock-keyhole"; export const searchFields = [ { - key: "TestSiteCode", - label: "Test Code", - type: "text", + key: "HostID", + label: "Host", + type: "select", + optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`, }, { - key: "TestSiteName", - label: "Test Name", - type: "text", + key: "ClientID", + label: "Client", + type: "select", + optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/entity_type`, }, - { - key: "TestMapType", - label: "TestMap Type", - type: "text", - }, - ]; export const detailSections = [ @@ -29,8 +26,8 @@ export const detailSections = [ fields: [ { key: "HostTypeLabel", label: "Host Type" }, { key: "ClientTypeLabel", label: "Client Type" }, - { key: "HostID", label: "Host ID" }, - { key: "ClientID", label: "Client ID" }, + { key: "HostName", label: "Host ID" }, + { key: "ClientName", label: "Client ID" }, { key: "details", label: "Details", fullWidth: true }, ] }, @@ -57,6 +54,11 @@ export function testMapActions(masterDetail, handlers) { export function viewActions(handlers){ return [ + { + Icon: LockKeyholeIcon, + label: 'Disable Test Map', + onClick: handlers.disableTestMap, + }, { Icon: PencilIcon, label: 'Edit TestMap', diff --git a/src/lib/components/dictionary/testmap/page/create-page.svelte b/src/lib/components/dictionary/testmap/page/create-page.svelte index 132d9f4..dd01fd9 100644 --- a/src/lib/components/dictionary/testmap/page/create-page.svelte +++ b/src/lib/components/dictionary/testmap/page/create-page.svelte @@ -13,6 +13,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import { untrack } from "svelte"; import { API } from '$lib/config/api'; + import { onMount } from "svelte"; let props = $props(); @@ -241,6 +242,15 @@ })) })) }) + + onMount(() => { + masterDetail.registerExtraState({ + key: 'tempMap', + get: () => tempMap, + reset: () => { tempMap = []; idCounter = 0; editingId = null; }, + }); + }); + diff --git a/src/lib/components/dictionary/testmap/page/edit-page.svelte b/src/lib/components/dictionary/testmap/page/edit-page.svelte index b6f3553..11e4b32 100644 --- a/src/lib/components/dictionary/testmap/page/edit-page.svelte +++ b/src/lib/components/dictionary/testmap/page/edit-page.svelte @@ -14,6 +14,7 @@ import { untrack } from "svelte"; import { API } from "$lib/config/api"; import { getChangedFields } from "$lib/utils/getChangedFields"; + import { onMount } from "svelte"; let props = $props(); @@ -215,7 +216,7 @@ } const primaryAction = $derived({ - label: 'Edit', + label: 'Save', onClick: handleEdit, disabled: helpers.hasErrors || formState.isSaving.current, loading: formState.isSaving.current @@ -351,6 +352,14 @@ idCounter = maxId; } }); + + onMount(() => { + masterDetail.registerExtraState({ + key: 'tempMap', + get: () => tempMap, + reset: () => { tempMap = []; idCounter = 0; editingId = null; }, + }); + }); diff --git a/src/lib/components/dictionary/testmap/page/view-page.svelte b/src/lib/components/dictionary/testmap/page/view-page.svelte index 6e9cd1b..c07163d 100644 --- a/src/lib/components/dictionary/testmap/page/view-page.svelte +++ b/src/lib/components/dictionary/testmap/page/view-page.svelte @@ -6,14 +6,17 @@ import MapIcon from "@lucide/svelte/icons/map"; import * as Table from "$lib/components/ui/table/index.js"; import { Spinner } from "$lib/components/ui/spinner/index.js"; + import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte"; let props = $props(); const { masterDetail, formFields, formActions, schema } = props.context; let testMap = $derived(masterDetail?.selectedItem?.data); + let showDisableConfirm = $state(false); const handlers = { + disableTestMap: () => { showDisableConfirm = true; }, editTestMap: () => masterDetail.enterEdit("data"), }; @@ -129,4 +132,14 @@ {:else} -{/if} \ No newline at end of file +{/if} + + { + console.log('disabled!'); + }} +/> \ No newline at end of file diff --git a/src/lib/components/dictionary/testmap/table/testmap-columns.js b/src/lib/components/dictionary/testmap/table/testmap-columns.js index 46673b1..dfded90 100644 --- a/src/lib/components/dictionary/testmap/table/testmap-columns.js +++ b/src/lib/components/dictionary/testmap/table/testmap-columns.js @@ -1,14 +1,14 @@ export const testMapColumns = [ { - accessorKey: "HostType", + id: "Host", + accessorFn: (row) => `${row.HostType} ${row.HostName}`, header: "Host", + cell: ({ row }) => `${row.original.HostType} - ${row.original.HostName}` }, { - accessorKey: "ClientType", + id: "Client", + accessorFn: (row) => `${row.ClientType} ${row.ClientName}`, header: "Client", - }, - { - accessorKey: "TestMapType", - header: "Type", + cell: ({ row }) => `${row.original.ClientType} - ${row.original.ClientName}` }, ]; \ No newline at end of file diff --git a/src/lib/components/patient/admission/modal/search-param-modal.svelte b/src/lib/components/patient/admission/modal/search-param-modal.svelte index 1534ec0..163d203 100644 --- a/src/lib/components/patient/admission/modal/search-param-modal.svelte +++ b/src/lib/components/patient/admission/modal/search-param-modal.svelte @@ -19,21 +19,13 @@ let activeRowId = $state(null); - // let isPatientEmpty = $derived(!selectedPatient); let isPatientEmpty = $derived(!tempSelectedPatient); - // function handleCheckboxChange(patient) { - // selectedPatient = patient; - // } - function handleCheckboxChange(patient) { tempSelectedPatient = patient; } function handleButtonClick() { - // if (selectedPatient) { - // props.onConfirm(selectedPatient); - // } if (tempSelectedPatient) { selectedPatient = tempSelectedPatient; props.onConfirm(tempSelectedPatient); diff --git a/src/lib/components/patient/admission/page/create-page.svelte b/src/lib/components/patient/admission/page/create-page.svelte index b4e5949..eb06c67 100644 --- a/src/lib/components/patient/admission/page/create-page.svelte +++ b/src/lib/components/patient/admission/page/create-page.svelte @@ -53,7 +53,7 @@ }); - + { // const backendData = masterDetail?.selectedItem.data; @@ -80,7 +81,7 @@ const secondaryActions = []; - +
{#if props.searchable ?? true} -