diff --git a/src/lib/api/api-client.js b/src/lib/api/api-client.js index 514c5c5..9643bc2 100644 --- a/src/lib/api/api-client.js +++ b/src/lib/api/api-client.js @@ -76,6 +76,7 @@ export async function searchWithPath(endpoint, searchQuery) { } export async function create(endpoint, formData) { + console.log(cleanEmptyStrings(formData)); try { const res = await fetch(`${API.BASE_URL}${endpoint}`, { method: 'POST', diff --git a/src/lib/components/composable/use-form.svelte.js b/src/lib/components/composable/use-form.svelte.js index 6cb1a64..5ca01e9 100644 --- a/src/lib/components/composable/use-form.svelte.js +++ b/src/lib/components/composable/use-form.svelte.js @@ -11,15 +11,13 @@ export function useForm({schema, initialForm, defaultErrors = {}, mode = 'create state.isSaving.current = true try { - // const payload = { ...state.form }; const payload = customPayload || { ...state.form }; let result; - // const { ProvinceID, CityID, ...rest } = state.form; - // const payload = customPayload || rest; - // const result = currentMode === 'edit' ? await editEndpoint(payload, idKey) : await saveEndpoint(payload); + if (currentMode === 'edit') { const id = payload[idKey]; - result = await editEndpoint(payload, id); + const { [idKey]: _, ...body } = payload; + result = await editEndpoint(body, id); } else { result = await saveEndpoint(payload); } diff --git a/src/lib/components/dictionary/account/api/account-api.js b/src/lib/components/dictionary/account/api/account-api.js index 93ab42b..2986020 100644 --- a/src/lib/components/dictionary/account/api/account-api.js +++ b/src/lib/components/dictionary/account/api/account-api.js @@ -10,6 +10,7 @@ export async function getAccount(searchQuery) { } export async function createAccount(newAccountForm) { + console.log(newAccountForm); return await create(API.ACCOUNT, newAccountForm) } diff --git a/src/lib/components/dictionary/account/config/account-form-config.js b/src/lib/components/dictionary/account/config/account-form-config.js index 543f5af..d121e27 100644 --- a/src/lib/components/dictionary/account/config/account-form-config.js +++ b/src/lib/components/dictionary/account/config/account-form-config.js @@ -5,6 +5,10 @@ import { z } from "zod"; export const accountSchema = z.object({ Initial: z.string().min(1, "Required"), AccountName: z.string().min(1, "Required"), + EmailAddress1: z.string().trim().optional().refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),"Invalid email format"), + EmailAddress2: z.string().trim().optional().refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),"Invalid email format"), + ZIP: z.string().regex(/^$|^[0-9]+$/, "Can only contain numbers"), + Phone: z.string().max(14, "Max 14 chars").regex(/^$|^[0-9]+$/, "Can only contain numbers"), }); export const accountInitialForm = { @@ -12,13 +16,13 @@ export const accountInitialForm = { ParentAccount: '', AccountName: '', Initial: '', + Street_1: '', + Street_2: '', + Street_3: '', Country: '', Province: '', City: '', ZIP: '', - Street_1: '', - Street_2: '', - Street_3: '', EmailAddress1: '', EmailAddress2: '', Phone: '', @@ -28,6 +32,10 @@ export const accountInitialForm = { export const accountDefaultErrors = { Initial: "Required", AccountName: "Required", + EmailAddress1: null, + EmailAddress2: null, + ZIP: null, + Phone: null, }; export const accountFormFields = [ @@ -162,6 +170,7 @@ export const accountFormFields = [ label: "Phone", required: false, type: "text", + validateOn: ["input"] }, ] }, diff --git a/src/lib/components/dictionary/contact/config/contact-config.js b/src/lib/components/dictionary/contact/config/contact-config.js index 3325e7d..274e307 100644 --- a/src/lib/components/dictionary/contact/config/contact-config.js +++ b/src/lib/components/dictionary/contact/config/contact-config.js @@ -13,7 +13,6 @@ export const searchFields = [ export const detailSections = [ { - title: "", class: "grid grid-cols-1 md:grid-cols-2 gap-4", groups: [ { @@ -35,7 +34,8 @@ export const detailSections = [ { key: "MobilePhone1", label: "Mobile Phone 1" }, { key: "MobilePhone2", label: "Mobile Phone 2" }, ] - } + }, + ] }, { @@ -45,6 +45,12 @@ export const detailSections = [ { key: "SubSpecialty", label: "Sub Specialty" }, ] }, + { + class: "grid grid-cols-2 gap-4 items-center", + fields: [ + { key: "Details", label: "Details", fullWidth: true }, + ] + } ]; export function contactActions(masterDetail) { diff --git a/src/lib/components/dictionary/contact/config/contact-form-config.js b/src/lib/components/dictionary/contact/config/contact-form-config.js index 2cd8d64..1a5b265 100644 --- a/src/lib/components/dictionary/contact/config/contact-form-config.js +++ b/src/lib/components/dictionary/contact/config/contact-form-config.js @@ -232,7 +232,13 @@ export const contactDetailFormFields = [ type: "row", columns: [ { key: "JobTitle", label: "Job Title", required: false, type: "text" }, - { key: "ContactEmail", label: "Email", required: false, type: "text" }, + { + key: "ContactEmail", + label: "Email", + required: false, + type: "text", + validateOn: ["input"], + }, ] } ] @@ -256,6 +262,7 @@ export function buildContactPayload({ let payload = { ...mainForm, Details: tempDetailContact.map((item) => ({ + ContactDetID: item.ContactDetID, SiteID: item.SiteID, ContactCode: item.ContactCode, ContactEmail: item.ContactEmail, diff --git a/src/lib/components/dictionary/contact/page/edit-page.svelte b/src/lib/components/dictionary/contact/page/edit-page.svelte index f972fe2..7d355af 100644 --- a/src/lib/components/dictionary/contact/page/edit-page.svelte +++ b/src/lib/components/dictionary/contact/page/edit-page.svelte @@ -11,12 +11,15 @@ import * as Card from "$lib/components/ui/card/index.js"; import { Badge } from "$lib/components/ui/badge/index.js"; import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte"; - import { contactDetailInitialForm, contactDetailFormFields } from "$lib/components/dictionary/contact/config/contact-form-config"; + import { contactDetailSchema, contactDetailInitialForm, contactDetailDefaultErrors, contactDetailFormFields, buildContactPayload } from "$lib/components/dictionary/contact/config/contact-form-config"; import { Button } from "$lib/components/ui/button/index.js"; import { useForm } from "$lib/components/composable/use-form.svelte"; import XIcon from "@lucide/svelte/icons/x"; import Edit2Icon from "@lucide/svelte/icons/edit-2"; - import { getChangedFields } from "$lib/utils/getChangedFields"; + // import { getChangedFields } from "$lib/utils/getChangedFields"; + import * as Table from '$lib/components/ui/table/index.js'; + import PencilIcon from "@lucide/svelte/icons/pencil"; + import Trash2Icon from "@lucide/svelte/icons/trash-2"; let props = $props(); @@ -24,33 +27,24 @@ const { formState } = masterDetail; - const detailFormState = useForm({ - schema: null, + const contactDetailFormState = useForm({ + schema: contactDetailSchema, initialForm: contactDetailInitialForm, - defaultErrors: {}, - mode: 'create', - modeOpt: 'default', - saveEndpoint: null, - editEndpoint: null, + defaultErrors: contactDetailDefaultErrors, }); - let showDetailForm = $state(false); - - let editingDetailIndex = $state(null); - - let isEditingDetail = $derived(editingDetailIndex !== null); - const helpers = useDictionaryForm(formState); let showConfirm = $state(false); let editingId = $state(null); let idCounter = $state(0); - let tempMap = $state([]); + let tempDetailContact = $state([]); + let deletedDetailIds = $state([]); function getLabel(fieldKey, value) { - if (!detailFormState.selectOptions?.[fieldKey]) return value; - const option = detailFormState.selectOptions[fieldKey].find(opt => opt.value === value); + if (!contactDetailFormState.selectOptions?.[fieldKey]) return value; + const option = contactDetailFormState.selectOptions[fieldKey].find(opt => opt.value === value); return option?.label || value || "-"; } @@ -80,7 +74,7 @@ group.rows.forEach(row => { row.columns.forEach(col => { if (col.type === "select" && col.optionsEndpoint) { - detailFormState.fetchOptions(col, detailFormState.form); + contactDetailFormState.fetchOptions(col, contactDetailFormState.form); } }); }); @@ -88,49 +82,116 @@ }); }); - let editedDetails = $state(masterDetail.selectedItem?.data?.Details || []); + // 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; + + // console.log('ITEM:', item); + // console.log('ORIG:', orig); + // console.log('KEYS current:', Object.keys(item)); + // console.log('KEYS original:', Object.keys(orig)); + + const changed = Object.keys(item).some( + key => item[key] !== orig[key] + ); + + if (changed) updated.push(item); + } + + return updated; + } + + function getChangedFields(original, current) { + const changed = {}; + for (const key in current) { + if (JSON.stringify(current[key]) !== JSON.stringify(original[key])) { + changed[key] = current[key]; + } + } + return changed; + } async function handleEdit() { - const originalDetails = masterDetail.selectedItem?.data?.Details || []; + 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 + })) + } + }; - 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); + 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 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)); + // // *** + // const changedFields = getChangedFields(originalPayload, currentPayload); + // console.log('Changed Fields:', JSON.stringify(changedFields)); - // if (Object.keys(changedFields).length === 0) { - // toast('No changes detected'); - // return; - // } + // // if (Object.keys(changedFields).length === 0) { + // // toast('No changes detected'); + // // return; + // // } - // const payload = { - // ContactID: formState.form.ContactID, - // ...changedFields - // }; + // // const payload = { + // // ContactID: formState.form.ContactID, + // // ...changedFields + // // }; - // console.log('Custom Payload for Edit:', payload); - // *** + // // console.log('Custom Payload for Edit:', payload); + // // *** - // const result = await formState.save(masterDetail.mode, 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); - // } + // // 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({ @@ -142,71 +203,114 @@ const secondaryActions = []; - const actionsDetail = [ - { - Icon: PlusIcon, - label: 'Add Contact Detail', - onClick: () => addDetail(), - }, - ]; - - function addDetail() { - editingDetailIndex = null; // Mode create baru - detailFormState.reset(); // Reset form ke initialForm - detailFormState.setForm({ ...contactDetailInitialForm }); // Set form kosong - showDetailForm = true; + function snapshotForm() { + return untrack(() => { + const f = contactDetailFormState.form; + return { + SiteID: f.SiteID ?? "", + ContactCode: f.ContactCode ?? "", + ContactEmail: f.ContactEmail ?? "", + Department: f.Department ?? "", + OccupationID: f.OccupationID ?? "", + JobTitle: f.JobTitle ?? "", + }; + }); } - async function saveDetail() { - // Ambil current form dari detailFormState.form - const newDetail = { ...detailFormState.form }; - - if (isEditingDetail) { - // Mode edit: update detail yang ada - masterDetail.selectedItem.data.Details[editingDetailIndex] = newDetail; - toast('Contact Detail Updated!'); - } else { - // Mode create: tambah detail baru - if (!masterDetail.selectedItem.data.Details) { - masterDetail.selectedItem.data.Details = []; - } - masterDetail.selectedItem.data.Details.push(newDetail); - toast('Contact Detail Added!'); + function resetContactDetailForm() { + contactDetailFormState.reset(); + editingId = null; + } + + function handleInsertDetail() { + const row = { + id: ++idCounter, + ContactDetID: null, + ...snapshotForm() + }; + + tempDetailContact = [...tempDetailContact, row]; + + resetContactDetailForm(); + } + + async function handleEditDetail(row) { + editingId = row.id; + + untrack(() => { + const f = contactDetailFormState.form; + + f.SiteID = row.SiteID; + f.ContactCode = row.ContactCode; + f.ContactEmail = row.ContactEmail; + f.Department = row.Department; + f.OccupationID = row.OccupationID; + f.JobTitle = row.JobTitle; + }); + } + + // function handleUpdateDetail() { + // tempDetailContact = tempDetailContact.map((row) => + // row.id === editingId ? + // { + // ...row, + // ...snapshotForm() + // } : row + // ); + // resetContactDetailForm(); + // } + + function handleUpdateDetail() { + const updated = snapshotForm(); + + tempDetailContact = tempDetailContact.map((row) => + row.id === editingId + ? + { + ...row, + ...updated, + ContactDetID: row.ContactDetID ?? null + } : row + ); + + resetContactDetailForm(); + } + + function handleCancelEditDetail() { + resetContactDetailForm(); + } +$inspect(deletedDetailIds) + function handleRemoveDetail(id) { + const row = tempDetailContact.find(r => r.id === id); + if (row?.ContactDetID) { + deletedDetailIds.push(row.ContactDetID); } - - // Reset form dan tutup form - detailFormState.reset(); - detailFormState.setForm({ ...contactDetailInitialForm }); - editingDetailIndex = null; - showDetailForm = false; - } - - function editDetail(index) { - const detailToEdit = masterDetail.selectedItem.data.Details[index]; - editingDetailIndex = index; // Set mode edit dengan index - detailFormState.setForm({ ...detailToEdit }); // Load data ke form - showDetailForm = true; - } - - function cancelDetail() { - detailFormState.reset(); - detailFormState.setForm({ ...contactDetailInitialForm }); - editingDetailIndex = null; - showDetailForm = false; - } - - function removeDetail(index) { - masterDetail.selectedItem.data.Details = masterDetail.selectedItem.data.Details.filter((_, i) => i !== index); - toast('Contact Detail Removed!'); - - // Jika sedang mengedit detail yang dihapus, reset form - if (editingDetailIndex === index) { - cancelDetail(); - } else if (editingDetailIndex !== null && editingDetailIndex > index) { - // Adjust index jika mengedit detail setelah yang dihapus - editingDetailIndex--; + tempDetailContact = tempDetailContact.filter((row) => row.id !== id); + if (editingId === id) { + resetContactDetailForm(); } } + + $effect(() => { + const mainForm = formState.form; + if (mainForm.Details && Array.isArray(mainForm.Details)) { + tempDetailContact = mainForm.Details.map((row, index) => ({ + id: row.id ?? index + 1, + ...row, + })); + } + }) + + $effect(() => { + const maxId = tempDetailContact.reduce((max, row) => { + const rowId = typeof row.id === 'number' ? row.id : 0; + return rowId > max ? rowId : max; + }, 0); + + if (maxId > idCounter) { + idCounter = maxId; + } + }); @@ -215,96 +319,82 @@ formFields={formFields} mode="edit" /> + -
- + -
- {#if showDetailForm} - - - - - - - - - +
+ {#if editingId !== null} + + + {:else} + {/if} - {#each masterDetail.selectedItem?.data?.Details as contactdetail, index} - - -
-
- - {contactdetail.ContactCode || "null"} - - - {contactdetail.ContactEmail || "null"} - -
-
- - {getLabel('SiteID', contactdetail.SiteID)} - -
-
+
+ +
+ + + + + Site + Code + Department + Occupation + Job Title + Email + + + + + {#if tempDetailContact.length === 0} + + + No data. Fill the form above and click Insert. + + + {:else} + {#each tempDetailContact as row (row.id)} + + {row.SiteID} + {row.ContactCode} + {row.Department} + {row.OccupationID} + {row.JobTitle} + {row.ContactEmail} + +
+ -
-
-
-
- -
-
-

- Department -

-

- {getLabel('Department', contactdetail.Department)} -

-
-
-

- Job Title -

-

- {contactdetail.JobTitle || "-"} -

-
-
-

- Occupation -

-

- - {getLabel('OccupationID', contactdetail.OccupationID)} -

-
-
-
-
- {/each} -
+ + + {/each} + {/if} + +
diff --git a/src/lib/components/dictionary/contact/page/view-page.svelte b/src/lib/components/dictionary/contact/page/view-page.svelte index 08df044..188f10d 100644 --- a/src/lib/components/dictionary/contact/page/view-page.svelte +++ b/src/lib/components/dictionary/contact/page/view-page.svelte @@ -8,6 +8,7 @@ import PlusIcon from "@lucide/svelte/icons/plus"; import * as Card from "$lib/components/ui/card/index.js"; import { Badge } from "$lib/components/ui/badge/index.js"; + import * as Table from "$lib/components/ui/table/index.js"; let props = $props(); @@ -41,6 +42,46 @@ } +{#snippet DetailsTable({ value, label })} +
+
+ {label} +
+
+ {#if value && Array.isArray(value) && value.length > 0} +
+ + + + Site + Code + Department + Occupation + Job Title + Email + + + + {#each value as row, i} + + {row.SiteID} + {row.ContactCode} + {row.Department} + {row.OccupationID} + {row.JobTitle} + {row.ContactEmail} + + {/each} + + +
+ {:else} + - + {/if} +
+
+{/snippet} + {#snippet Fieldset({ value, label, isUTCDate = false })}
@@ -62,7 +103,7 @@ title={masterDetail.selectedItem.data.NameFirst} {actions} /> -
+
{#each detailSections as section}
{#if section.groups} @@ -84,11 +125,26 @@ {:else}
{#each section.fields as field} - {@render Fieldset({ - label: field.label, - value: getFieldValue(field), - isUTCDate: field.isUTCDate - })} + {#if field.fullWidth} +
+ {#if field.key === "Details"} + {@render DetailsTable({ label: field.label, value: getFieldValue(field) })} + {:else} + {@render Fieldset({ label: field.label, value: getFieldValue(field), isUTCDate: field.isUTCDate })} + {/if} +
+ {:else if field.key === "Details"} + {@render DetailsTable({ + label: field.label, + value: getFieldValue(field), + })} + {:else} + {@render Fieldset({ + label: field.label, + value: getFieldValue(field), + isUTCDate: field.isUTCDate + })} + {/if} {/each}
{/if} @@ -96,62 +152,6 @@ {/each}
- -
- -
- {#each masterDetail.selectedItem?.data?.Details as contactdetail} - - -
-
- - {contactdetail.ContactCode} - - - {contactdetail.ContactEmail} - -
- - Site {contactdetail.SiteID} - -
-
- -
-
-

- Department -

-

- {contactdetail.Department || "-"} -

-
-
-

- Job Title -

-

- {contactdetail.JobTitle || "-"} -

-
-
-

- Occupation -

-

- {contactdetail.OccupationID || "-"} -

-
-
-
-
- {/each} -
-
{:else} {/if} \ No newline at end of file diff --git a/src/lib/components/dictionary/container/api/container-api.js b/src/lib/components/dictionary/container/api/container-api.js index 3bddca3..dc2b779 100644 --- a/src/lib/components/dictionary/container/api/container-api.js +++ b/src/lib/components/dictionary/container/api/container-api.js @@ -13,6 +13,6 @@ export async function createContainer(newContainerForm) { return await create(API.CONTAINER, newContainerForm) } -export async function editContainer(editContainerForm) { - return await update(API.CONTAINER, editContainerForm) +export async function editContainer(editContainerForm, id) { + return await update(API.CONTAINER, editContainerForm, id) } \ No newline at end of file diff --git a/src/lib/components/dictionary/container/config/container-config.js b/src/lib/components/dictionary/container/config/container-config.js index 4a7854c..e89e757 100644 --- a/src/lib/components/dictionary/container/config/container-config.js +++ b/src/lib/components/dictionary/container/config/container-config.js @@ -28,9 +28,9 @@ export const detailSections = [ { class: "grid grid-cols-2 gap-4 items-center", fields: [ - { key: "ConClass", label: "Container Class" }, + { key: "ConClassLabel", label: "Container Class" }, { key: "Color", label: "Color" }, - { key: "Additive", label: "Additive" }, + { key: "AdditiveLabel", label: "Additive" }, ] }, ]; diff --git a/src/lib/components/dictionary/container/page/edit-page.svelte b/src/lib/components/dictionary/container/page/edit-page.svelte index f4629db..f9b31e8 100644 --- a/src/lib/components/dictionary/container/page/edit-page.svelte +++ b/src/lib/components/dictionary/container/page/edit-page.svelte @@ -6,6 +6,7 @@ import { untrack } from "svelte"; import { API } from "$lib/config/api"; import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte"; + import { getChangedFields } from "$lib/utils/getChangedFields"; let props = $props(); @@ -38,7 +39,24 @@ }); async function handleEdit() { - const result = await formState.save(masterDetail.mode); + const currentPayload = formState.form; + const originalPayload = masterDetail.formSnapshot; + + const changedFields = getChangedFields(originalPayload, currentPayload); + + if (Object.keys(changedFields).length === 0) { + toast('No changes detected'); + return; + } + + const payload = { + ConDefID: formState.form.ConDefID, + ...changedFields + }; + + console.log('Payload:', payload); + + const result = await formState.save(masterDetail.mode, payload); if (result.status === 'success') { console.log('Container updated successfully'); @@ -46,6 +64,8 @@ masterDetail.exitForm(true); } else { console.error('Failed to update container:', result.message); + const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to update container'; + toast.error(errorMessages) } } diff --git a/src/lib/components/dictionary/location/api/location-api.js b/src/lib/components/dictionary/location/api/location-api.js index 426f081..720fa88 100644 --- a/src/lib/components/dictionary/location/api/location-api.js +++ b/src/lib/components/dictionary/location/api/location-api.js @@ -13,6 +13,6 @@ export async function createLocation(newLocationForm) { return await create(API.LOCATION, newLocationForm) } -export async function editLocation(editLocationForm) { - return await update(API.LOCATION, editLocationForm) +export async function editLocation(editLocationForm, id) { + return await update(API.LOCATION, editLocationForm, id) } \ No newline at end of file diff --git a/src/lib/components/dictionary/location/config/location-config.js b/src/lib/components/dictionary/location/config/location-config.js index 5034baf..41aebae 100644 --- a/src/lib/components/dictionary/location/config/location-config.js +++ b/src/lib/components/dictionary/location/config/location-config.js @@ -43,7 +43,8 @@ export const detailSections = [ fields: [ { key: "Province", label: "Province" }, { key: "City", label: "City" }, - { key: "PostCode", label: "ZIP" }, + { key: "PostCode", label: "Post Code" }, + { key: "Email", label: "Email" }, ] }, { @@ -51,8 +52,10 @@ export const detailSections = [ fields: [ { key: "Street1", label: "Street 1" }, { key: "Street2", label: "Street 2" }, + { key: "Phone", label: "Phone" }, + { key: "Mobile", label: "Mobile" }, ] - } + }, ] }, { diff --git a/src/lib/components/dictionary/location/config/location-form-config.js b/src/lib/components/dictionary/location/config/location-form-config.js index 437e54c..af9f8ed 100644 --- a/src/lib/components/dictionary/location/config/location-form-config.js +++ b/src/lib/components/dictionary/location/config/location-form-config.js @@ -7,24 +7,28 @@ export const locationSchema = z.object({ LocFull: z.string().min(1, "Required"), Email: z.string().trim().optional().refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),"Invalid email format"), Phone: z.string().max(14, "Max 14 chars").regex(/^$|^[0-9]+$/, "Can only contain numbers"), - ZIP: z.string().regex(/^$|^[0-9]+$/, "Can only contain numbers"), + Mobile: z.string().max(14, "Max 14 chars").regex(/^$|^[0-9]+$/, "Can only contain numbers"), + PostCode: z.string().regex(/^$|^[0-9]+$/, "Can only contain numbers"), }); export const locationInitialForm = { LocationID: '', - LocCode: '', - LocType: '', - LocFull: '', SiteID: '', + LocCode: '', + Parent: '', + LocFull: '', + Description: '', + LocType: '', Street1: '', Street2: '', - Phone: '', - Email: '', - City: '', Province: '', - ZIP: '', + City: '', + PostCode: '', GeoLocationSystem: '', GeoLocationData: '', + Phone: '', + Mobile: '', + Email: '', }; export const locationDefaultErrors = { @@ -32,7 +36,8 @@ export const locationDefaultErrors = { LocFull: "Required", Email: null, Phone: null, - ZIP: null, + Mobile: null, + PostCode: null, }; export const locationFormFields = [ @@ -52,17 +57,26 @@ export const locationFormFields = [ labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`, }, { - key: "LocCode", - label: "Location Code", - required: true, - type: "text", - validateOn: ["input"] + key: "Parent", + label: "Parent", + required: false, + type: "select", + optionsEndpoint: `${API.BASE_URL}${API.SITE}`, + valueKey: "SiteID", + labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`, }, ] }, { type: "row", columns: [ + { + key: "LocCode", + label: "Location Code", + required: true, + type: "text", + validateOn: ["input"] + }, { key: "LocType", label: "Location Type", @@ -70,13 +84,25 @@ export const locationFormFields = [ type: "select", optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/location_type`, }, + + ] + }, + { + type: "row", + columns: [ { key: "LocFull", label: "Location Name", required: true, type: "text", validateOn: ["input"] - } + }, + { + key: "Description", + label: "Description", + required: false, + type: "text", + }, ] }, ] @@ -126,8 +152,8 @@ export const locationFormFields = [ type: "row", columns: [ { - key: "ZIP", - label: "ZIP", + key: "PostCode", + label: "Post Code", required: false, type: "text", validateOn: ["input"], @@ -150,13 +176,20 @@ export const locationFormFields = [ type: "text", validateOn: ["input"] }, + { + key: "Mobile", + label: "Mobile", + required: false, + type: "text", + validateOn: ["input"] + }, { key: "Email", label: "Email", required: false, type: "text", validateOn: ["input"] - } + }, ] }, ] diff --git a/src/lib/components/dictionary/location/page/edit-page.svelte b/src/lib/components/dictionary/location/page/edit-page.svelte index ae396ff..a104484 100644 --- a/src/lib/components/dictionary/location/page/edit-page.svelte +++ b/src/lib/components/dictionary/location/page/edit-page.svelte @@ -6,6 +6,7 @@ import { untrack } from "svelte"; import { API } from "$lib/config/api"; import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte"; + import { getChangedFields } from "$lib/utils/getChangedFields"; let props = $props(); @@ -50,7 +51,24 @@ }); async function handleEdit() { - const result = await formState.save(masterDetail.mode); + const currentPayload = formState.form; + const originalPayload = masterDetail.formSnapshot; + + const changedFields = getChangedFields(originalPayload, currentPayload); + + if (Object.keys(changedFields).length === 0) { + toast('No changes detected'); + return; + } + + const payload = { + LocationID: formState.form.LocationID, + ...changedFields + }; + + console.log('Payload:', payload); + + const result = await formState.save(masterDetail.mode, payload); if (result.status === 'success') { console.log('Location updated successfully'); @@ -58,6 +76,8 @@ masterDetail.exitForm(true); } else { console.error('Failed to update location:', result.message); + const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to update location'; + toast.error(errorMessages) } } diff --git a/src/lib/components/dictionary/occupation/api/occupation-api.js b/src/lib/components/dictionary/occupation/api/occupation-api.js index 2294013..d2855da 100644 --- a/src/lib/components/dictionary/occupation/api/occupation-api.js +++ b/src/lib/components/dictionary/occupation/api/occupation-api.js @@ -13,6 +13,6 @@ export async function createOccupation(newOccupationForm) { return await create(API.OCCUPATION, newOccupationForm) } -export async function editOccupation(editOccupationForm) { - return await update(API.OCCUPATION, editOccupationForm) +export async function editOccupation(editOccupationForm, id) { + return await update(API.OCCUPATION, editOccupationForm, id) } \ No newline at end of file diff --git a/src/lib/components/dictionary/occupation/page/edit-page.svelte b/src/lib/components/dictionary/occupation/page/edit-page.svelte index 2e9859e..1d93300 100644 --- a/src/lib/components/dictionary/occupation/page/edit-page.svelte +++ b/src/lib/components/dictionary/occupation/page/edit-page.svelte @@ -6,6 +6,7 @@ import { untrack } from "svelte"; import { API } from "$lib/config/api"; import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte"; + import { getChangedFields } from "$lib/utils/getChangedFields"; let props = $props(); @@ -38,7 +39,24 @@ }); async function handleEdit() { - const result = await formState.save(masterDetail.mode); + const currentPayload = formState.form; + const originalPayload = masterDetail.formSnapshot; + + const changedFields = getChangedFields(originalPayload, currentPayload); + + if (Object.keys(changedFields).length === 0) { + toast('No changes detected'); + return; + } + + const payload = { + OccupationID: formState.form.OccupationID, + ...changedFields + }; + + console.log('Payload:', payload); + + const result = await formState.save(masterDetail.mode, payload); if (result.status === 'success') { console.log('Occupation updated successfully'); @@ -46,6 +64,8 @@ masterDetail.exitForm(true); } else { console.error('Failed to update occupation:', result.message); + const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to update occupation'; + toast.error(errorMessages) } } diff --git a/src/lib/components/dictionary/testmap/config/testmap-config.js b/src/lib/components/dictionary/testmap/config/testmap-config.js index 6031de3..b5fb9d5 100644 --- a/src/lib/components/dictionary/testmap/config/testmap-config.js +++ b/src/lib/components/dictionary/testmap/config/testmap-config.js @@ -30,7 +30,7 @@ export const detailSections = [ { key: "ClientTypeLabel", label: "Client Type" }, { key: "HostID", label: "Host ID" }, { key: "ClientID", label: "Client ID" }, - { key: "details", label: "Details", fullWidth: true }, + { key: "Details", label: "Details", fullWidth: true }, ] }, ]; diff --git a/src/lib/config/api.js b/src/lib/config/api.js index 1dce259..79591cd 100644 --- a/src/lib/config/api.js +++ b/src/lib/config/api.js @@ -16,7 +16,7 @@ export const API = { PATVISIT: '/api/patvisit', VISITLIST: '/api/patvisit/patient/', COUNTER: '/api/counter', - CONTAINER: '/api/specimen/containerdef', + CONTAINER: '/api/specimen/container', PROVINCE: '/api/areageo/provinces', CITY: '/api/areageo/cities', ACCOUNT: '/api/organization/account', diff --git a/src/routes/dictionary/account/+page.svelte b/src/routes/dictionary/account/+page.svelte index 3dc4aac..d510569 100644 --- a/src/routes/dictionary/account/+page.svelte +++ b/src/routes/dictionary/account/+page.svelte @@ -20,6 +20,7 @@ modeOpt: 'cascade', saveEndpoint: createAccount, editEndpoint: editAccount, + idKey: 'AccountID', } }); diff --git a/src/routes/dictionary/container/+page.svelte b/src/routes/dictionary/container/+page.svelte index 2edb635..2b5792d 100644 --- a/src/routes/dictionary/container/+page.svelte +++ b/src/routes/dictionary/container/+page.svelte @@ -20,6 +20,7 @@ modeOpt: 'default', saveEndpoint: createContainer, editEndpoint: editContainer, + idKey: 'ConDefID', } }); diff --git a/src/routes/dictionary/location/+page.svelte b/src/routes/dictionary/location/+page.svelte index 5987f50..226ab1d 100644 --- a/src/routes/dictionary/location/+page.svelte +++ b/src/routes/dictionary/location/+page.svelte @@ -20,6 +20,7 @@ modeOpt: 'cascade', saveEndpoint: createLocation, editEndpoint: editLocation, + idKey: 'LocationID', } }); diff --git a/src/routes/dictionary/occupation/+page.svelte b/src/routes/dictionary/occupation/+page.svelte index 55e90f3..5abef47 100644 --- a/src/routes/dictionary/occupation/+page.svelte +++ b/src/routes/dictionary/occupation/+page.svelte @@ -20,6 +20,7 @@ modeOpt: 'default', saveEndpoint: createOccupation, editEndpoint: editOccupation, + idKey: 'OccupationID', } });