From 4784ede1a6ec7b3f6f87315bc47a51dbce47ea6c Mon Sep 17 00:00:00 2001 From: faiztyanirh Date: Thu, 12 Feb 2026 17:02:40 +0700 Subject: [PATCH] bug fixing patient list & admission --- src/lib/api/api-client.js | 28 +++---- .../composable/use-form-state.svelte.js | 14 +++- .../composable/use-master-detail.svelte.js | 38 +++++++-- .../composable/use-patient-form.svelte.js | 13 ++- .../admission/config/admission-form-config.js | 2 +- .../admission/config/admission-payload.js | 25 ++++-- .../admission/modal/search-param-modal.svelte | 32 +++---- .../patient/admission/page/create-page.svelte | 2 +- .../patient/admission/page/edit-page.svelte | 28 ++++--- .../patient/admission/page/view-page.svelte | 2 +- .../patient/list/config/patient-config.js | 3 +- .../list/config/patient-form-config.js | 8 +- .../patient/list/page/edit-page.svelte | 83 +++++++++++-------- .../patient/list/page/view-page.svelte | 2 +- .../reusable/patient-form-renderer.svelte | 10 +-- src/routes/patient/admission/+page.svelte | 7 +- src/routes/patient/list/+page.svelte | 20 ++++- 17 files changed, 200 insertions(+), 117 deletions(-) diff --git a/src/lib/api/api-client.js b/src/lib/api/api-client.js index 94e4798..eceef27 100644 --- a/src/lib/api/api-client.js +++ b/src/lib/api/api-client.js @@ -114,19 +114,19 @@ export async function create(endpoint, formData) { export async function update(endpoint, formData) { console.log(cleanEmptyStrings(formData)); - // try { - // const res = await fetch(`${API.BASE_URL}${endpoint}`, { - // method: 'PATCH', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify(cleanEmptyStrings(formData)) - // }); + try { + const res = await fetch(`${API.BASE_URL}${endpoint}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(cleanEmptyStrings(formData)) + }); - // const data = await res.json(); - // return data; - // } catch (err) { - // console.error('Update Error:', err.message); - // return { success: false, message: err.message || 'Network error' }; - // } + const data = await res.json(); + return data; + } catch (err) { + console.error('Update Error:', err.message); + return { success: false, message: err.message || 'Network error' }; + } } \ No newline at end of file diff --git a/src/lib/components/composable/use-form-state.svelte.js b/src/lib/components/composable/use-form-state.svelte.js index a2b145e..a7511e5 100644 --- a/src/lib/components/composable/use-form-state.svelte.js +++ b/src/lib/components/composable/use-form-state.svelte.js @@ -2,13 +2,23 @@ export function useFormState(initial) { const form = $state(structuredClone(initial)) const isSaving = $state({ current: false }); + // function resetForm() { + // Object.assign(form, structuredClone(initial)); + // } + function resetForm() { + Object.keys(form).forEach(key => delete form[key]); Object.assign(form, structuredClone(initial)); } + // function setForm(data) { + // const snapshotData = $state.snapshot(data); + // Object.assign(form, JSON.parse(JSON.stringify(snapshotData))); + // } + function setForm(data) { - const snapshotData = $state.snapshot(data); - Object.assign(form, JSON.parse(JSON.stringify(snapshotData))); + // Object.keys(form).forEach(key => delete form[key]); + Object.assign(form, structuredClone(data)); } return { isSaving, form, resetForm, setForm } diff --git a/src/lib/components/composable/use-master-detail.svelte.js b/src/lib/components/composable/use-master-detail.svelte.js index 7915834..61ee763 100644 --- a/src/lib/components/composable/use-master-detail.svelte.js +++ b/src/lib/components/composable/use-master-detail.svelte.js @@ -7,6 +7,7 @@ export function useMasterDetail(options = {}) { let selectedItem = $state(null); let mode = $state("view"); let isLoadingDetail = $state(false); + let formSnapshot = $state(null); const formState = useForm(formConfig); @@ -23,6 +24,10 @@ export function useMasterDetail(options = {}) { 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"; @@ -49,32 +54,46 @@ export function useMasterDetail(options = {}) { formState.reset(); if (initialData) { - formState.setForm({ - ...formState.form, - ...initialData - }); + formState.setForm(initialData); } } - function enterEdit(mapToForm = null) { + function enterEdit(param) { if (!selectedItem) return; mode = "edit"; - const data = mapToForm - ? mapToForm(selectedItem) + const raw = param + ? selectedItem[param] : selectedItem; + if (!raw) return; + + const base = $state.snapshot(raw); + + const entity = formConfig.mapToForm + ? formConfig.mapToForm(base) + : base; + formState.reset(); - Object.assign(formState.form, data); + formState.setForm(entity); Object.keys(formState.errors).forEach(key => { formState.errors[key] = null; }); + + formSnapshot = $state.snapshot(formState.form); } function exitForm() { + if (isDirty) { + const ok = confirm('You have unsaved changes. Discard them?'); + if (!ok) return; + } + mode = "view"; selectedItem = null; + + formSnapshot = null; } function backToList() { @@ -108,6 +127,9 @@ export function useMasterDetail(options = {}) { get layout() { return layout; }, + get formSnapshot() { + return formSnapshot; + }, // get form() { // return form; // }, diff --git a/src/lib/components/composable/use-patient-form.svelte.js b/src/lib/components/composable/use-patient-form.svelte.js index 9c7c35b..152ab20 100644 --- a/src/lib/components/composable/use-patient-form.svelte.js +++ b/src/lib/components/composable/use-patient-form.svelte.js @@ -5,7 +5,14 @@ export function usePatientForm(formState, patientSchema) { let uploadErrors = $state({}); let isChecking = $state({}); - async function validateFieldAsync(field) { + async function validateFieldAsync(field, mode = 'create', originalValue = null) { + + if (mode === 'edit' && formState.form[field] === originalValue) { + formState.errors[field] = null; + isChecking[field] = false; + return; + } + isChecking[field] = true; try { @@ -33,8 +40,8 @@ export function usePatientForm(formState, patientSchema) { } function validateIdentifier() { - const identifierType = formState.form.PatIdt.IdentifierType; - const identifierValue = formState.form.PatIdt.Identifier; + const identifierType = formState.form.PatIdt?.IdentifierType; + const identifierValue = formState.form.PatIdt?.Identifier; if (!identifierType || !identifierValue) { formState.errors['PatIdt.Identifier'] = null; return; diff --git a/src/lib/components/patient/admission/config/admission-form-config.js b/src/lib/components/patient/admission/config/admission-form-config.js index 09ee352..4f39a76 100644 --- a/src/lib/components/patient/admission/config/admission-form-config.js +++ b/src/lib/components/patient/admission/config/admission-form-config.js @@ -133,7 +133,7 @@ export const admissionFormFields = [ }, { key: "isDischarge", - label: "Discharge Status", + label: "Admission Status", required: false, type: "toggle", defaultValue: false, diff --git a/src/lib/components/patient/admission/config/admission-payload.js b/src/lib/components/patient/admission/config/admission-payload.js index 7bbfb0e..92f9285 100644 --- a/src/lib/components/patient/admission/config/admission-payload.js +++ b/src/lib/components/patient/admission/config/admission-payload.js @@ -67,12 +67,26 @@ export function buildEditAdmissionPayload(form, diffs = []) { const PatVisitADT = diffs.map(diff => { // Determine ADTCode based on discharge status let ADTCode; - if (diff.isDischarge) { - ADTCode = "A03"; // Discharge - } else if (diff.originalisDischarge) { - ADTCode = "A13"; // Cancel discharge + // if (diff.isDischarge) { + // ADTCode = "A03"; // Discharge + // } else if (diff.originalisDischarge) { + // ADTCode = "A13"; // Cancel discharge + // } else { + // ADTCode = diff.code; // Normal update + // } + + if (diff.isDischarge && !diff.originalisDischarge) { + // NEW discharge (false → true) + ADTCode = "A03"; + } else if (!diff.isDischarge && diff.originalisDischarge) { + // CANCEL discharge (true → false) + ADTCode = "A13"; + } else if (diff.isDischarge && diff.originalisDischarge) { + // ALREADY discharged, other field changed + ADTCode = "A03"; // Keep discharge } else { - ADTCode = diff.code; // Normal update + // Normal update (not related to discharge) + ADTCode = diff.code; } return { @@ -96,7 +110,6 @@ export function buildEditPayloadWithDiff(originalData, formData) { // Compare & get diffs const diffs = compareAdmissionData(cleanedOriginal, cleanedForm); - console.log(formData); // Build payload const payload = buildEditAdmissionPayload(cleanedForm, diffs); 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 cff58ca..3781e36 100644 --- a/src/lib/components/patient/admission/modal/search-param-modal.svelte +++ b/src/lib/components/patient/admission/modal/search-param-modal.svelte @@ -45,8 +45,8 @@
-
-
+
+
{#each props.searchFields as field} {#if field.type === "text"}
@@ -87,9 +87,9 @@
-
+
{#if props.search.searchData && props.search.searchData.length > 0} -
+
@@ -123,18 +123,18 @@
-
- - - -
+
+
+ + +
{:else}
diff --git a/src/lib/components/patient/admission/page/create-page.svelte b/src/lib/components/patient/admission/page/create-page.svelte index e92ac9e..d6f463d 100644 --- a/src/lib/components/patient/admission/page/create-page.svelte +++ b/src/lib/components/patient/admission/page/create-page.svelte @@ -24,7 +24,7 @@ async function handleSave() { const payload = buildPayload(formState.form); - // const result = await formState.save(masterDetail.mode, payload); + const result = await formState.save(masterDetail.mode, payload); console.log(payload); toast('Visit Created!'); diff --git a/src/lib/components/patient/admission/page/edit-page.svelte b/src/lib/components/patient/admission/page/edit-page.svelte index 611deb5..aada9bc 100644 --- a/src/lib/components/patient/admission/page/edit-page.svelte +++ b/src/lib/components/patient/admission/page/edit-page.svelte @@ -13,39 +13,41 @@ const { formState } = masterDetail; $effect(() => { - const backendData = masterDetail?.selectedItem.data; - if (!backendData) return; + // const backendData = masterDetail?.selectedItem.data; + // if (!backendData) return; untrack(() => { - const formData = { - ...backendData, - }; + // const formData = { + // ...backendData, + // }; - formState.setForm(formData); + // formState.setForm(formData); formFields.forEach(group => { group.rows.forEach(row => { row.columns.forEach(col => { if (col.type === "select" && col.optionsEndpoint) { - formState.fetchOptions(col, formData); + formState.fetchOptions(col, formState.form); } }); }); }); }) }) - - $inspect(formState.form) +// $inspect(masterDetail.selectedItem?.data) +// $inspect(formState.form) async function handleEdit() { // const payload = buildPayload(formState.form); - const payload = buildEditPayloadWithDiff( + const customPayload = buildEditPayloadWithDiff( masterDetail.selectedItem?.data, formState.form ); + + const result = await masterDetail.formState.save(masterDetail.mode, customPayload); - console.log(payload); - // toast('Visit Updated!'); - // masterDetail?.exitForm(); + console.log(customPayload); + toast('Visit Updated!'); + masterDetail?.exitForm(); // const result = await formState.save(); // if (result.status === 'success') { diff --git a/src/lib/components/patient/admission/page/view-page.svelte b/src/lib/components/patient/admission/page/view-page.svelte index 202031f..7debd4d 100644 --- a/src/lib/components/patient/admission/page/view-page.svelte +++ b/src/lib/components/patient/admission/page/view-page.svelte @@ -11,7 +11,7 @@ let visit = $derived(masterDetail?.selectedItem?.data); const handlers = { - editPatient: () => masterDetail.enterEdit(), + editPatient: () => masterDetail.enterEdit("data"), }; const actions = viewActions(handlers); diff --git a/src/lib/components/patient/list/config/patient-config.js b/src/lib/components/patient/list/config/patient-config.js index 9a12b19..f02ec6c 100644 --- a/src/lib/components/patient/list/config/patient-config.js +++ b/src/lib/components/patient/list/config/patient-config.js @@ -109,7 +109,7 @@ export function patientActions(masterDetail, patientInitialForm) { { Icon: PlusIcon, label: 'Add Patient', - onClick: () => masterDetail.enterCreate(patientInitialForm), + onClick: () => masterDetail.enterCreate(), }, { Icon: Settings2Icon, @@ -139,7 +139,6 @@ export function viewActions(handlers){ Icon: PencilIcon, label: 'Edit Patient', onClick: handlers.editPatient, - }, ] } \ No newline at end of file diff --git a/src/lib/components/patient/list/config/patient-form-config.js b/src/lib/components/patient/list/config/patient-form-config.js index 58827e6..c64a845 100644 --- a/src/lib/components/patient/list/config/patient-form-config.js +++ b/src/lib/components/patient/list/config/patient-form-config.js @@ -203,7 +203,7 @@ export const patientFormFields = [ columns: [ { key: "Street_1", label: "Street 1", required: false, type: "text" }, { - key: "Province", + key: "ProvinceID", label: "Province", required: false, type: "select", @@ -216,13 +216,13 @@ export const patientFormFields = [ columns: [ { key: "Street_2", label: "Street 2", required: false, type: "text" }, { - key: "City", + key: "CityID", label: "City", required: false, type: "select", optionsEndpoint: `${API.BASE_URL}${API.CITY}`, - dependsOn: "Province", // ← field yang jadi parent - endpointParamKey: "Parent" // ← query param name + dependsOn: "ProvinceID", + endpointParamKey: "Parent" } ] }, diff --git a/src/lib/components/patient/list/page/edit-page.svelte b/src/lib/components/patient/list/page/edit-page.svelte index 66cd170..4ae73aa 100644 --- a/src/lib/components/patient/list/page/edit-page.svelte +++ b/src/lib/components/patient/list/page/edit-page.svelte @@ -8,36 +8,45 @@ let props = $props(); - const { masterDetail, formFields, formActions, schema, initialForm, defaultError } = props.context; + // const { masterDetail, formFields, formActions, schema, initialForm, defaultError } = props.context; + const { masterDetail, formFields, formActions, schema, initialForm } = props.context; const { formState } = masterDetail; const helpers = usePatientForm(formState, schema); $effect(() => { - const backendData = masterDetail?.selectedItem?.patient; - if (!backendData) return; + // const backendData = masterDetail?.selectedItem?.patient; + // if (!backendData) return; untrack(() => { - const formData = { - ...backendData, + // const formData = { + // ...backendData, - PatIdt: backendData.PatIdt ?? initialForm.PatIdt, - LinkTo: backendData.LinkTo ?? [], - Custodian: backendData.Custodian ?? initialForm.Custodian, + // PatIdt: backendData.PatIdt ?? initialForm.PatIdt, + // LinkTo: backendData.LinkTo ?? [], + // Custodian: backendData.Custodian ?? initialForm.Custodian, - Sex: backendData.SexKey || backendData.Sex, - Religion: backendData.ReligionKey || backendData.Religion, - MaritalStatus: backendData.MaritalStatusKey || backendData.MaritalStatus, - Ethnic: backendData.EthnicKey || backendData.Ethnic, - Race: backendData.RaceKey || backendData.Race, - Country: backendData.CountryKey || backendData.Country, - DeathIndicator: backendData.DeathIndicatorKey || backendData.DeathIndicator, - Province: backendData.ProvinceID || backendData.Province, - City: backendData.CityID || backendData.City, - }; + // Sex: backendData.SexKey || backendData.Sex, + // Religion: backendData.ReligionKey || backendData.Religion, + // MaritalStatus: backendData.MaritalStatusKey || backendData.MaritalStatus, + // Ethnic: backendData.EthnicKey || backendData.Ethnic, + // Race: backendData.RaceKey || backendData.Race, + // Country: backendData.CountryKey || backendData.Country, + // DeathIndicator: backendData.DeathIndicatorKey || backendData.DeathIndicator, + // Province: backendData.ProvinceID || backendData.Province, + // City: backendData.CityID || backendData.City, + // }; - formState.setForm(formData); + // formState.setForm(formData); + + // Ensure PatIdt is always an object to prevent binding errors + // if (!formState.form.PatIdt) { + // formState.form.PatIdt = { + // IdentifierType: "", + // Identifier: "" + // }; + // } formFields.forEach(group => { group.rows.forEach(row => { @@ -45,40 +54,44 @@ if (col.type === "group") { col.columns.forEach(child => { if (child.type === "select" && child.optionsEndpoint) { - formState.fetchOptions(child, formData); + formState.fetchOptions(child, formState.form); } }); } else if ((col.type === "select" || col.type === "identity") && col.optionsEndpoint) { - formState.fetchOptions(col, formData); + formState.fetchOptions(col, formState.form); } }); }); }); - if (formData.Province && formData.City) { + // if (formState.form.ProvinceID && formState.form.CityID) { + if (formState.form.Province) { formState.fetchOptions( { key: "City", optionsEndpoint: `${API.BASE_URL}${API.CITY}`, - dependsOn: "Province", + dependsOn: "ProvinceID", endpointParamKey: "Parent" }, - formData + formState.form ); } }); }); -$inspect(formState.form) + +// $inspect(masterDetail?.selectedItem?.patient) +// $inspect(formState.form) async function handleEdit() { - const result = await formState.save(masterDetail.mode); + console.log(formState.form); + // const result = await formState.save(masterDetail.mode); - if (result.status === 'success') { - console.log('Patient updated successfully'); - toast('Patient Updated!'); - masterDetail.exitForm(); - } else { - console.error('Failed to update patient:', result.message); - } + // if (result.status === 'success') { + // console.log('Patient updated successfully'); + // toast('Patient Updated!'); + // masterDetail.exitForm(); + // } else { + // console.error('Failed to update patient:', result.message); + // } } const primaryAction = $derived({ @@ -100,6 +113,8 @@ $inspect(formState.form) isChecking={helpers.isChecking} linkToDisplay={helpers.linkToDisplay} validateIdentifier={helpers.validateIdentifier} + validateFieldAsync={helpers.validateFieldAsync} + originalData={masterDetail.formSnapshot} mode="edit" /> - \ No newline at end of file + diff --git a/src/lib/components/patient/list/page/view-page.svelte b/src/lib/components/patient/list/page/view-page.svelte index e8a84af..b5d93aa 100644 --- a/src/lib/components/patient/list/page/view-page.svelte +++ b/src/lib/components/patient/list/page/view-page.svelte @@ -15,7 +15,7 @@ orderLab: () => console.log('order lab'), medicalRecord: () => console.log('medical record'), auditPatient: () => console.log('audit patient'), - editPatient: () => masterDetail.enterEdit(), + editPatient: () => masterDetail.enterEdit("patient"), }; const actions = viewActions(handlers); diff --git a/src/lib/components/patient/reusable/patient-form-renderer.svelte b/src/lib/components/patient/reusable/patient-form-renderer.svelte index 4a2e7bc..46e780b 100644 --- a/src/lib/components/patient/reusable/patient-form-renderer.svelte +++ b/src/lib/components/patient/reusable/patient-form-renderer.svelte @@ -23,6 +23,7 @@ linkToDisplay, validateIdentifier, validateFieldAsync, + originalData = null, mode = 'create' } = $props(); @@ -99,7 +100,7 @@ }} onblur={() => { if (validateOn?.includes("blur")) { - validateFieldAsync(key); + validateFieldAsync(key, mode, originalData?.[key]); } }} /> @@ -220,7 +221,7 @@ {:else if type === "identity"} - {@const selectedLabel = formState.selectOptions[key]?.find(opt => opt.value === formState.form.PatIdt.IdentifierType)?.label || "Choose"} + {@const selectedLabel = formState.selectOptions[key]?.find(opt => opt.value === formState.form.PatIdt?.IdentifierType)?.label || "Choose"}
{ @@ -253,7 +254,7 @@ {/if} - +
{:else if type === "custodian"}
@@ -289,9 +290,6 @@ variant="outline" class="w-full transition-all data-[state=on]:text-primary" bind:pressed={formState.form.isDischarge} - onPressedChange={(pressed) => { - formState.form.ADTCode = pressed ? "A03" : ""; - }} > {#if formState.form.isDischarge} diff --git a/src/routes/patient/admission/+page.svelte b/src/routes/patient/admission/+page.svelte index 6742077..fd87fe9 100644 --- a/src/routes/patient/admission/+page.svelte +++ b/src/routes/patient/admission/+page.svelte @@ -10,7 +10,12 @@ const masterDetail = useMasterDetail({ onSelect: async (row) => { - return await getVisit(row.PVID); + const response = await getVisit(row.PVID); + if (response?.data) { + response.data.isDischarge = response.data.ADTCode === "A03"; + } + + return response }, formConfig: { schema: admissionSchema, diff --git a/src/routes/patient/list/+page.svelte b/src/routes/patient/list/+page.svelte index d3870d8..bf03f6a 100644 --- a/src/routes/patient/list/+page.svelte +++ b/src/routes/patient/list/+page.svelte @@ -20,6 +20,18 @@ modeOpt: 'cascade', saveEndpoint: createPatient, editEndpoint: editPatient, + mapToForm: (data) => ({ + ...data, + PatIdt: { + IdentifierType: data.PatIdt?.IdentifierType ?? "", + Identifier: data.PatIdt?.Identifier ?? "" + }, + LinkTo: Array.isArray(data.LinkTo) ? data.LinkTo : [], + Custodian: data.Custodian ?? { + InternalPID: "", + PatientID: "" + }, + }) } }); @@ -29,10 +41,10 @@ formActions: getPatientFormActions, schema: patientSchema, initialForm: patientInitialForm, - defaultErrors: { - create: patientDefaultErrors, - edit: {} - } + // defaultErrors: { + // create: patientDefaultErrors, + // edit: {} + // } }