mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-22 09:35:34 +07:00
continue fixing testmap
add disable button change search param fix quick filter not working fix host/client name in viewpage change text button Edit to Save initial multi form formSnapshot
This commit is contained in:
parent
1502fbd140
commit
4ed048d734
166
src/lib/components/composable/use-master-detail.svelte copy.js
Normal file
166
src/lib/components/composable/use-master-detail.svelte copy.js
Normal file
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
@ -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',
|
||||
|
||||
@ -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; },
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<FormPageContainer title="Create Test Map" {primaryAction} {secondaryActions} {actions}>
|
||||
|
||||
@ -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; },
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<FormPageContainer title="Edit Test Map" {primaryAction} {secondaryActions}>
|
||||
|
||||
@ -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 @@
|
||||
</div>
|
||||
{:else}
|
||||
<ReusableEmpty icon={MapIcon} desc="Select a test map to see details"/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<ReusableAlertDialog
|
||||
bind:open={showDisableConfirm}
|
||||
title="Disable Test Map?"
|
||||
description="Are you sure you want to disable this test map?"
|
||||
confirmText="Disable"
|
||||
onConfirm={() => {
|
||||
console.log('disabled!');
|
||||
}}
|
||||
/>
|
||||
@ -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}`
|
||||
},
|
||||
];
|
||||
@ -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);
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<FormPageContainer title="Create Admission" {primaryAction} {secondaryActions} {actions}>
|
||||
<FormPageContainer title="Create Admission for {formState.form?.PatientID}" {primaryAction} {secondaryActions} {actions}>
|
||||
<PatientFormRenderer
|
||||
{formState}
|
||||
formFields={formFields}
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
function confirmDiscard() {
|
||||
masterDetail.exitForm(true);
|
||||
}
|
||||
$inspect(formState.form)
|
||||
|
||||
$effect(() => {
|
||||
// const backendData = masterDetail?.selectedItem.data;
|
||||
@ -80,7 +81,7 @@
|
||||
const secondaryActions = [];
|
||||
</script>
|
||||
|
||||
<FormPageContainer title="Edit Admission" {primaryAction} {secondaryActions}>
|
||||
<FormPageContainer title="Edit Admission for {formState.form.PVID}" {primaryAction} {secondaryActions}>
|
||||
<PatientFormRenderer
|
||||
{formState}
|
||||
formFields={formFields}
|
||||
|
||||
@ -62,7 +62,6 @@
|
||||
</script>
|
||||
<div class="h-full flex flex-col relative w-full">
|
||||
{#if props.searchable ?? true}
|
||||
<!-- <div class="flex items-center absolute top-[-2rem]"> -->
|
||||
<div class="flex items-center absolute" style="top: -{props.offset ?? 2}rem">
|
||||
<Input
|
||||
placeholder="Filter all columns..."
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user