diff --git a/src/lib/components/composable/use-search.svelte.js b/src/lib/components/composable/use-search.svelte.js
index 5a402d9..f733c69 100644
--- a/src/lib/components/composable/use-search.svelte.js
+++ b/src/lib/components/composable/use-search.svelte.js
@@ -17,7 +17,7 @@ export function useSearch(searchFields, searchApiFunction) {
return query;
}
- async function handleSearch() {
+ async function handleSearch(onDone) {
isLoading = true;
try {
searchData = await searchApiFunction(searchQuery);
@@ -25,6 +25,7 @@ export function useSearch(searchFields, searchApiFunction) {
console.error('Search failed:', error);
} finally {
isLoading = false;
+ onDone?.();
}
}
diff --git a/src/lib/components/dictionary/contact/config/contact-config.js b/src/lib/components/dictionary/contact/config/contact-config.js
index 274e307..1b07521 100644
--- a/src/lib/components/dictionary/contact/config/contact-config.js
+++ b/src/lib/components/dictionary/contact/config/contact-config.js
@@ -2,6 +2,7 @@ import PlusIcon from "@lucide/svelte/icons/plus";
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";
export const searchFields = [
{
@@ -53,8 +54,13 @@ export const detailSections = [
}
];
-export function contactActions(masterDetail) {
+export function contactActions(masterDetail, handlers) {
return [
+ {
+ Icon: RefreshIcon,
+ label: 'Refresh Data',
+ onClick: handlers.refresh,
+ },
{
Icon: PlusIcon,
label: 'Add Contact',
diff --git a/src/lib/components/dictionary/contact/page/create-page.svelte b/src/lib/components/dictionary/contact/page/create-page.svelte
index 3a63272..db0742b 100644
--- a/src/lib/components/dictionary/contact/page/create-page.svelte
+++ b/src/lib/components/dictionary/contact/page/create-page.svelte
@@ -113,10 +113,16 @@
tempDetailContact,
});
console.log(payload)
- const result = await formState.save(masterDetail.mode);
+ const result = await formState.save(masterDetail.mode, payload);
- toast('Contact Created!');
- masterDetail?.exitForm(true);
+ if (result.status === 'success') {
+ toast('Contact Created!');
+ masterDetail?.exitForm(true);
+ } else {
+ console.error('Failed to save contact');
+ const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to save contact';
+ toast.error(errorMessages)
+ }
}
const primaryAction = $derived({
diff --git a/src/lib/components/dictionary/contact/page/edit-page copy.svelte b/src/lib/components/dictionary/contact/page/edit-page copy.svelte
deleted file mode 100644
index 16c3a1e..0000000
--- a/src/lib/components/dictionary/contact/page/edit-page copy.svelte
+++ /dev/null
@@ -1,309 +0,0 @@
-
-
-
-
-
-
-
-
- {#if showDetailForm}
-
-
-
-
-
-
-
-
-
- {/if}
- {#each masterDetail.selectedItem?.data?.Details as contactdetail, index}
-
-
-
-
-
- {contactdetail.ContactCode || "null"}
-
-
- {contactdetail.ContactEmail || "null"}
-
-
-
-
- {getLabel('SiteID', contactdetail.SiteID)}
-
-
-
-
-
-
-
-
-
-
-
-
- Department
-
-
- {getLabel('Department', contactdetail.Department)}
-
-
-
-
- Job Title
-
-
- {contactdetail.JobTitle || "-"}
-
-
-
-
- Occupation
-
-
-
- {getLabel('OccupationID', contactdetail.OccupationID)}
-
-
-
-
-
- {/each}
-
-
-
-
-
\ No newline at end of file
diff --git a/src/lib/components/dictionary/contact/page/edit-page.svelte b/src/lib/components/dictionary/contact/page/edit-page.svelte
index 8e825ee..ddd1d25 100644
--- a/src/lib/components/dictionary/contact/page/edit-page.svelte
+++ b/src/lib/components/dictionary/contact/page/edit-page.svelte
@@ -21,6 +21,11 @@
const { formState } = masterDetail;
+ let editingId = $state(null);
+ let idCounter = $state(0);
+ let tempDetailContact = $state([]);
+ let deletedDetailIds = $state([]);
+
const contactDetailFormState = useForm({
schema: contactDetailSchema,
initialForm: contactDetailInitialForm,
@@ -31,158 +36,7 @@
let showConfirm = $state(false);
- let editingId = $state(null);
- let idCounter = $state(0);
- let tempDetailContact = $state([]);
- let deletedDetailIds = $state([]);
-
- function getLabel(fieldKey, value) {
- if (!contactDetailFormState.selectOptions?.[fieldKey]) return value;
- const option = contactDetailFormState.selectOptions[fieldKey].find(opt => opt.value === value);
- return option?.label || value || "-";
- }
-
- $effect(() => {
- untrack(() => {
- formFields.forEach(group => {
- group.rows.forEach(row => {
- row.columns.forEach(col => {
- if (col.type === "group") {
- col.columns.forEach(child => {
- if (child.type === "select" && child.optionsEndpoint) {
- formState.fetchOptions(child, formState.form);
- }
- });
- } else if ((col.type === "select") && col.optionsEndpoint) {
- formState.fetchOptions(col, formState.form);
- }
- });
- });
- });
- });
- });
-
- $effect(() => {
- untrack(() => {
- contactDetailFormFields.forEach(group => {
- group.rows.forEach(row => {
- row.columns.forEach(col => {
- if (col.type === "select" && col.optionsEndpoint) {
- contactDetailFormState.fetchOptions(col, contactDetailFormState.form);
- }
- });
- });
- });
- });
- });
-
- // let editedDetails = $state(masterDetail.selectedItem?.data?.Details || []);
-
- function diffDetails(current, original) {
- const originalMap = new Map(
- original.map(item => [item.ContactDetID, item])
- );
-
- const updated = [];
-
- for (const item of current) {
- const orig = originalMap.get(item.ContactDetID);
- if (!orig) continue;
-
- const changed = Object.keys(item).some(
- key => item[key] !== orig[key]
- );
-
- if (changed) updated.push(item);
- }
-
- return updated;
- }
-
- async function handleEdit() {
- const currentPayload = buildContactPayload({
- mainForm: formState.form,
- tempDetailContact
- });
- const originalPayload = buildContactPayload({
- mainForm: masterDetail.formSnapshot,
- tempDetailContact: masterDetail.formSnapshot.Details
- });
- const updatedDetails = diffDetails(
- currentPayload.Details,
- originalPayload.Details
- );
- const finalPayload = {
- ...getChangedFields(originalPayload, currentPayload),
- Details: {
- created: tempDetailContact.filter(r => !r.ContactDetID),
- updated: updatedDetails,
- deleted: deletedDetailIds.map(id => ({
- ContactDetID: id
- }))
- }
- };
-
- console.log(finalPayload);
- // console.log('Original Payload:', JSON.stringify(originalPayload));
- // console.log('Current Payload:', JSON.stringify(currentPayload));
- // console.log('Changed Fields:', getChangedFields(originalPayload, currentPayload));
- // console.log(originalPayload.Details);
- // console.log(currentPayload.Details);
- // console.log('Diff:', diffDetails(currentPayload.Details, originalPayload.Details));
-
- // const originalDetails = masterDetail.selectedItem?.data?.Details || [];
-
- // const currentPayload = $state.snapshot({ ...formState.form, Details: editedDetails });
- // const originalPayload = $state.snapshot({ ...masterDetail.formSnapshot, Details: originalDetails });
- // console.log('Current Payload:', editedDetails);
- // console.log('Original Payload:', originalDetails);
-
- // // const customPayload = {
- // // ...formState.form,
- // // Details: masterDetail.selectedItem?.data?.Details || []
- // // };
- // // console.log('Custom Payload for Edit:', JSON.stringify(customPayload));
- // // const result = await formState.save(masterDetail.mode, customPayload);
-
- // // ***
- // const changedFields = getChangedFields(originalPayload, currentPayload);
- // console.log('Changed Fields:', JSON.stringify(changedFields));
-
- // // if (Object.keys(changedFields).length === 0) {
- // // toast('No changes detected');
- // // return;
- // // }
-
- // // const payload = {
- // // ContactID: formState.form.ContactID,
- // // ...changedFields
- // // };
-
- // // console.log('Custom Payload for Edit:', payload);
- // // ***
-
- // // const result = await formState.save(masterDetail.mode, payload);
-
- // // if (result.status === 'success') {
- // // console.log('Contact updated successfully');
- // // toast('Contact Updated!');
- // // masterDetail.exitForm(true);
- // // } else {
- // // console.error('Failed to update contact:', result.message);
- // // }
- }
-
- const primaryAction = $derived({
- label: 'Edit',
- onClick: handleEdit,
- disabled: helpers.hasErrors || formState.isSaving.current,
- loading: formState.isSaving.current
- });
-
- const secondaryActions = [];
-
- function snapshotForm() {
+ function snapshotForm() {
return untrack(() => {
const f = contactDetailFormState.form;
return {
@@ -228,17 +82,6 @@
});
}
- // function handleUpdateDetail() {
- // tempDetailContact = tempDetailContact.map((row) =>
- // row.id === editingId ?
- // {
- // ...row,
- // ...snapshotForm()
- // } : row
- // );
- // resetContactDetailForm();
- // }
-
function handleUpdateDetail() {
const updated = snapshotForm();
@@ -270,6 +113,144 @@
}
}
+ function getLabel(fieldKey, value) {
+ if (!contactDetailFormState.selectOptions?.[fieldKey]) return value;
+ const option = contactDetailFormState.selectOptions[fieldKey].find(opt => opt.value === value);
+ return option?.label || value || "-";
+ }
+
+ $effect(() => {
+ untrack(() => {
+ formFields.forEach(group => {
+ group.rows.forEach(row => {
+ row.columns.forEach(col => {
+ if ((col.type === "select") && col.optionsEndpoint) {
+ formState.fetchOptions(col, formState.form);
+ }
+ });
+ });
+ });
+ });
+ });
+
+ $effect(() => {
+ untrack(() => {
+ contactDetailFormFields.forEach(group => {
+ group.rows.forEach(row => {
+ row.columns.forEach(col => {
+ if ((col.type === "select") && col.optionsEndpoint) {
+ contactDetailFormState.fetchOptions(col, contactDetailFormState.form);
+ }
+ });
+ });
+ });
+ });
+ });
+
+ // function diffDetails(current, original) {
+ // const originalMap = new Map(
+ // original.map(item => [item.ContactDetID, item])
+ // );
+
+ // const updated = [];
+
+ // for (const item of current) {
+ // const orig = originalMap.get(item.ContactDetID);
+ // if (!orig) continue;
+
+ // const changed = Object.keys(item).some(
+ // key => item[key] !== orig[key]
+ // );
+
+ // if (changed) updated.push(item);
+ // }
+
+ // return updated;
+ // }
+
+ function diffDetails(currentRows, originalRows) {
+ const originalMap = new Map(
+ originalRows
+ .filter(item => item.ContactDetID)
+ .map(item => [item.ContactDetID, item])
+ );
+
+ const updated = [];
+ const detailKeys = ['SiteID', 'ContactCode', 'ContactEmail', 'OccupationID', 'JobTitle', 'Department'];
+
+ for (const item of currentRows) {
+ if (!item.ContactDetID) continue;
+
+ const orig = originalMap.get(item.ContactDetID);
+ if (!orig) continue;
+
+ const changed = detailKeys.some(
+ key => item[key] !== orig[key]
+ );
+
+ if (changed) updated.push(item);
+ }
+
+ return updated;
+ }
+
+ async function handleEdit() {
+ const currentPayload = buildContactPayload({
+ mainForm: formState.form,
+ tempDetailContact
+ });
+ const originalPayload = buildContactPayload({
+ mainForm: masterDetail.formSnapshot,
+ tempDetailContact: masterDetail.formSnapshot.Details ?? []
+ });
+ const originalRows = masterDetail.formSnapshot.Details ?? [];
+ const updatedDetails = diffDetails(tempDetailContact, originalRows);
+
+ const changedFields = getChangedFields(originalPayload, currentPayload);
+ const hasMainChanges = Object.keys(changedFields).length > 0;
+ const hasDetailChanges = updatedDetails.length > 0 || tempDetailContact.some(r => !r.ContactDetID) || deletedDetailIds.length > 0;
+
+ if (!hasMainChanges && !hasDetailChanges) {
+ toast('No changes detected');
+ return;
+ }
+
+ const finalPayload = {
+ ContactID: formState.form.ContactID,
+ ...changedFields,
+ ...(hasDetailChanges && {
+ Details: {
+ created: tempDetailContact.filter(r => !r.ContactDetID),
+ edited: updatedDetails,
+ deleted: deletedDetailIds
+ }
+ })
+ };
+
+ console.log(finalPayload);
+
+ const result = await formState.save(masterDetail.mode, finalPayload);
+
+ if (result.status === 'success') {
+ console.log('Contact updated successfully');
+ toast('Contact Updated!');
+ masterDetail.exitForm(true);
+ } else {
+ console.error('Failed to update contact:', result.message);
+ const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to update contact';
+ toast.error(errorMessages)
+ }
+ }
+
+ const primaryAction = $derived({
+ label: 'Edit',
+ onClick: handleEdit,
+ disabled: helpers.hasErrors || formState.isSaving.current,
+ loading: formState.isSaving.current
+ });
+
+ const secondaryActions = [];
+
$effect(() => {
const mainForm = formState.form;
if (mainForm.Details && Array.isArray(mainForm.Details)) {
diff --git a/src/lib/components/dictionary/contact/page/master-page.svelte b/src/lib/components/dictionary/contact/page/master-page.svelte
index 8f064bd..eff5826 100644
--- a/src/lib/components/dictionary/contact/page/master-page.svelte
+++ b/src/lib/components/dictionary/contact/page/master-page.svelte
@@ -8,12 +8,15 @@
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
import UserXIcon from "@lucide/svelte/icons/user-x";
+ import MoveLeftIcon from "@lucide/svelte/icons/move-left";
let props = $props();
const search = useSearch(searchFields, getContacts);
- const initialForm = props.masterDetail.formState.form;
- const actions = contactActions(props.masterDetail, initialForm)
+ const handlers = {
+ refresh: () => {search.handleSearch()},
+ };
+ const actions = contactActions(props.masterDetail, handlers)
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
let activeRowId = $state(null);
@@ -38,10 +41,13 @@
>
{#if props.masterDetail.isFormMode}
-
- {#each "CONTACT".split("") as c}
- {c}
- {/each}
+
+
+
+ {#each "CONTACT".split("") as c}
+ {c}
+ {/each}
+
{/if}
diff --git a/src/lib/components/dictionary/location/config/location-config.js b/src/lib/components/dictionary/location/config/location-config.js
index c6883f2..b604612 100644
--- a/src/lib/components/dictionary/location/config/location-config.js
+++ b/src/lib/components/dictionary/location/config/location-config.js
@@ -2,6 +2,7 @@ import PlusIcon from "@lucide/svelte/icons/plus";
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";
export const searchFields = [
{
@@ -67,8 +68,13 @@ export const detailSections = [
},
];
-export function locationActions(masterDetail) {
+export function locationActions(masterDetail, handlers) {
return [
+ {
+ Icon: RefreshIcon,
+ label: 'Refresh Data',
+ onClick: handlers.refresh,
+ },
{
Icon: PlusIcon,
label: 'Add Location',
diff --git a/src/lib/components/dictionary/location/page/master-page.svelte b/src/lib/components/dictionary/location/page/master-page.svelte
index d9213b9..8de7230 100644
--- a/src/lib/components/dictionary/location/page/master-page.svelte
+++ b/src/lib/components/dictionary/location/page/master-page.svelte
@@ -8,12 +8,15 @@
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
import MapPinXIcon from "@lucide/svelte/icons/map-pin-x";
+ import MoveLeftIcon from "@lucide/svelte/icons/move-left";
let props = $props();
const search = useSearch(searchFields, getLocations);
- const initialForm = props.masterDetail.formState.form;
- const actions = locationActions(props.masterDetail, initialForm)
+ const handlers = {
+ refresh: () => {search.handleSearch()},
+ };
+ const actions = locationActions(props.masterDetail, handlers)
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
let activeRowId = $state(null);
@@ -38,10 +41,13 @@
>
{#if props.masterDetail.isFormMode}
-
- {#each "LOCATION".split("") as c}
- {c}
- {/each}
+
+
+
+ {#each "LOCATION".split("") as c}
+ {c}
+ {/each}
+
{/if}
diff --git a/src/lib/components/dictionary/occupation/config/occupation-config.js b/src/lib/components/dictionary/occupation/config/occupation-config.js
index 738871e..769c437 100644
--- a/src/lib/components/dictionary/occupation/config/occupation-config.js
+++ b/src/lib/components/dictionary/occupation/config/occupation-config.js
@@ -1,6 +1,7 @@
import PlusIcon from "@lucide/svelte/icons/plus";
import Settings2Icon from "@lucide/svelte/icons/settings-2";
import PencilIcon from "@lucide/svelte/icons/pencil";
+import RefreshIcon from "@lucide/svelte/icons/refresh-cw";
export const searchFields = [
{
@@ -37,8 +38,13 @@ export const detailSections = [
},
];
-export function occupationActions(masterDetail) {
+export function occupationActions(masterDetail, handlers) {
return [
+ {
+ Icon: RefreshIcon,
+ label: 'Refresh Data',
+ onClick: handlers.refresh,
+ },
{
Icon: PlusIcon,
label: 'Add Location',
diff --git a/src/lib/components/dictionary/occupation/page/master-page.svelte b/src/lib/components/dictionary/occupation/page/master-page.svelte
index 83727aa..92e362d 100644
--- a/src/lib/components/dictionary/occupation/page/master-page.svelte
+++ b/src/lib/components/dictionary/occupation/page/master-page.svelte
@@ -8,20 +8,23 @@
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
import FolderXIcon from "@lucide/svelte/icons/folder-x";
+ import MoveLeftIcon from "@lucide/svelte/icons/move-left";
let props = $props();
const search = useSearch(searchFields, getOccupations);
- const initialForm = props.masterDetail.formState.form;
- const actions = occupationActions(props.masterDetail, initialForm)
+ const handlers = {
+ refresh: () => {search.handleSearch()},
+ };
+ const actions = occupationActions(props.masterDetail, handlers)
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
let activeRowId = $state(null);
-{#snippet searchParamSnippet()}
+{#snippet searchParamSnippet(close)}
search.handleSearch(close)} onReset={search.handleReset} isLoading={search.isLoading}
selectOptions={search.selectOptions} loadingOptions={search.loadingOptions} fetchOptions={search.fetchOptions}
/>
{/snippet}
@@ -38,10 +41,13 @@
>
{#if props.masterDetail.isFormMode}
-
- {#each "OCCUPATION".split("") as c}
- {c}
- {/each}
+
+
+
+ {#each "OCCUPATION".split("") as c}
+ {c}
+ {/each}
+
{/if}
diff --git a/src/lib/components/dictionary/occupation/page/view-page.svelte b/src/lib/components/dictionary/occupation/page/view-page.svelte
index 7ca1cf1..1b4f386 100644
--- a/src/lib/components/dictionary/occupation/page/view-page.svelte
+++ b/src/lib/components/dictionary/occupation/page/view-page.svelte
@@ -4,6 +4,7 @@
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
import FolderXIcon from "@lucide/svelte/icons/folder-x";
+ import { Spinner } from "$lib/components/ui/spinner/index.js";
let props = $props();
@@ -46,7 +47,11 @@
{/snippet}
-{#if masterDetail.selectedItem}
+{#if masterDetail.isLoadingDetail}
+
+
+
+{:else if masterDetail.selectedItem}
diff --git a/src/lib/components/patient/list/page/view-page.svelte b/src/lib/components/patient/list/page/view-page.svelte
index b5d93aa..0b499bc 100644
--- a/src/lib/components/patient/list/page/view-page.svelte
+++ b/src/lib/components/patient/list/page/view-page.svelte
@@ -42,8 +42,14 @@
.filter(val => val && val.trim() !== "")
.join(" / ");
}
+ let value = field.parentKey ? patient[field.parentKey]?.[field.key] : patient[field.key];
- return field.parentKey ? patient[field.parentKey]?.[field.key] : patient[field.key];
+ if (field.valueMap && value != null) {
+ value = field.valueMap[value] ?? value;
+ }
+
+ // return field.parentKey ? patient[field.parentKey]?.[field.key] : patient[field.key];
+ return value;
}
diff --git a/src/lib/components/topbar/topbar-action.svelte b/src/lib/components/topbar/topbar-action.svelte
index 37bee19..a2a4b87 100644
--- a/src/lib/components/topbar/topbar-action.svelte
+++ b/src/lib/components/topbar/topbar-action.svelte
@@ -7,6 +7,8 @@
let props = $props();
const { Icon } = props;
+
+ let open = $state(false);
@@ -14,7 +16,7 @@
{#snippet child({ props: tooltipProps })}
{#if props.popoverContent}
-
+
{#snippet child({ props: popoverProps })}
{:else}
diff --git a/src/routes/dictionary/contact/+page.svelte b/src/routes/dictionary/contact/+page.svelte
index ab99793..13d0b26 100644
--- a/src/routes/dictionary/contact/+page.svelte
+++ b/src/routes/dictionary/contact/+page.svelte
@@ -20,6 +20,7 @@
modeOpt: 'default',
saveEndpoint: createContact,
editEndpoint: editContact,
+ idKey: 'ContactID',
}
});