mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-27 11:25:53 +07:00
continue contact edit & edit function
This commit is contained in:
parent
ec14173256
commit
6afc0067e2
@ -76,6 +76,7 @@ export async function searchWithPath(endpoint, searchQuery) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function create(endpoint, formData) {
|
export async function create(endpoint, formData) {
|
||||||
|
console.log(cleanEmptyStrings(formData));
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API.BASE_URL}${endpoint}`, {
|
const res = await fetch(`${API.BASE_URL}${endpoint}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@ -11,15 +11,13 @@ export function useForm({schema, initialForm, defaultErrors = {}, mode = 'create
|
|||||||
state.isSaving.current = true
|
state.isSaving.current = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// const payload = { ...state.form };
|
|
||||||
const payload = customPayload || { ...state.form };
|
const payload = customPayload || { ...state.form };
|
||||||
let result;
|
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') {
|
if (currentMode === 'edit') {
|
||||||
const id = payload[idKey];
|
const id = payload[idKey];
|
||||||
result = await editEndpoint(payload, id);
|
const { [idKey]: _, ...body } = payload;
|
||||||
|
result = await editEndpoint(body, id);
|
||||||
} else {
|
} else {
|
||||||
result = await saveEndpoint(payload);
|
result = await saveEndpoint(payload);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export async function getAccount(searchQuery) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createAccount(newAccountForm) {
|
export async function createAccount(newAccountForm) {
|
||||||
|
console.log(newAccountForm);
|
||||||
return await create(API.ACCOUNT, newAccountForm)
|
return await create(API.ACCOUNT, newAccountForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,10 @@ import { z } from "zod";
|
|||||||
export const accountSchema = z.object({
|
export const accountSchema = z.object({
|
||||||
Initial: z.string().min(1, "Required"),
|
Initial: z.string().min(1, "Required"),
|
||||||
AccountName: 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 = {
|
export const accountInitialForm = {
|
||||||
@ -12,13 +16,13 @@ export const accountInitialForm = {
|
|||||||
ParentAccount: '',
|
ParentAccount: '',
|
||||||
AccountName: '',
|
AccountName: '',
|
||||||
Initial: '',
|
Initial: '',
|
||||||
|
Street_1: '',
|
||||||
|
Street_2: '',
|
||||||
|
Street_3: '',
|
||||||
Country: '',
|
Country: '',
|
||||||
Province: '',
|
Province: '',
|
||||||
City: '',
|
City: '',
|
||||||
ZIP: '',
|
ZIP: '',
|
||||||
Street_1: '',
|
|
||||||
Street_2: '',
|
|
||||||
Street_3: '',
|
|
||||||
EmailAddress1: '',
|
EmailAddress1: '',
|
||||||
EmailAddress2: '',
|
EmailAddress2: '',
|
||||||
Phone: '',
|
Phone: '',
|
||||||
@ -28,6 +32,10 @@ export const accountInitialForm = {
|
|||||||
export const accountDefaultErrors = {
|
export const accountDefaultErrors = {
|
||||||
Initial: "Required",
|
Initial: "Required",
|
||||||
AccountName: "Required",
|
AccountName: "Required",
|
||||||
|
EmailAddress1: null,
|
||||||
|
EmailAddress2: null,
|
||||||
|
ZIP: null,
|
||||||
|
Phone: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accountFormFields = [
|
export const accountFormFields = [
|
||||||
@ -162,6 +170,7 @@ export const accountFormFields = [
|
|||||||
label: "Phone",
|
label: "Phone",
|
||||||
required: false,
|
required: false,
|
||||||
type: "text",
|
type: "text",
|
||||||
|
validateOn: ["input"]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,7 +13,6 @@ export const searchFields = [
|
|||||||
|
|
||||||
export const detailSections = [
|
export const detailSections = [
|
||||||
{
|
{
|
||||||
title: "",
|
|
||||||
class: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
class: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
@ -35,7 +34,8 @@ export const detailSections = [
|
|||||||
{ key: "MobilePhone1", label: "Mobile Phone 1" },
|
{ key: "MobilePhone1", label: "Mobile Phone 1" },
|
||||||
{ key: "MobilePhone2", label: "Mobile Phone 2" },
|
{ key: "MobilePhone2", label: "Mobile Phone 2" },
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -45,6 +45,12 @@ export const detailSections = [
|
|||||||
{ key: "SubSpecialty", label: "Sub Specialty" },
|
{ 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) {
|
export function contactActions(masterDetail) {
|
||||||
|
|||||||
@ -232,7 +232,13 @@ export const contactDetailFormFields = [
|
|||||||
type: "row",
|
type: "row",
|
||||||
columns: [
|
columns: [
|
||||||
{ key: "JobTitle", label: "Job Title", required: false, type: "text" },
|
{ 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 = {
|
let payload = {
|
||||||
...mainForm,
|
...mainForm,
|
||||||
Details: tempDetailContact.map((item) => ({
|
Details: tempDetailContact.map((item) => ({
|
||||||
|
ContactDetID: item.ContactDetID,
|
||||||
SiteID: item.SiteID,
|
SiteID: item.SiteID,
|
||||||
ContactCode: item.ContactCode,
|
ContactCode: item.ContactCode,
|
||||||
ContactEmail: item.ContactEmail,
|
ContactEmail: item.ContactEmail,
|
||||||
|
|||||||
@ -11,12 +11,15 @@
|
|||||||
import * as Card from "$lib/components/ui/card/index.js";
|
import * as Card from "$lib/components/ui/card/index.js";
|
||||||
import { Badge } from "$lib/components/ui/badge/index.js";
|
import { Badge } from "$lib/components/ui/badge/index.js";
|
||||||
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
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 { Button } from "$lib/components/ui/button/index.js";
|
||||||
import { useForm } from "$lib/components/composable/use-form.svelte";
|
import { useForm } from "$lib/components/composable/use-form.svelte";
|
||||||
import XIcon from "@lucide/svelte/icons/x";
|
import XIcon from "@lucide/svelte/icons/x";
|
||||||
import Edit2Icon from "@lucide/svelte/icons/edit-2";
|
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();
|
let props = $props();
|
||||||
|
|
||||||
@ -24,33 +27,24 @@
|
|||||||
|
|
||||||
const { formState } = masterDetail;
|
const { formState } = masterDetail;
|
||||||
|
|
||||||
const detailFormState = useForm({
|
const contactDetailFormState = useForm({
|
||||||
schema: null,
|
schema: contactDetailSchema,
|
||||||
initialForm: contactDetailInitialForm,
|
initialForm: contactDetailInitialForm,
|
||||||
defaultErrors: {},
|
defaultErrors: contactDetailDefaultErrors,
|
||||||
mode: 'create',
|
|
||||||
modeOpt: 'default',
|
|
||||||
saveEndpoint: null,
|
|
||||||
editEndpoint: null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let showDetailForm = $state(false);
|
|
||||||
|
|
||||||
let editingDetailIndex = $state(null);
|
|
||||||
|
|
||||||
let isEditingDetail = $derived(editingDetailIndex !== null);
|
|
||||||
|
|
||||||
const helpers = useDictionaryForm(formState);
|
const helpers = useDictionaryForm(formState);
|
||||||
|
|
||||||
let showConfirm = $state(false);
|
let showConfirm = $state(false);
|
||||||
|
|
||||||
let editingId = $state(null);
|
let editingId = $state(null);
|
||||||
let idCounter = $state(0);
|
let idCounter = $state(0);
|
||||||
let tempMap = $state([]);
|
let tempDetailContact = $state([]);
|
||||||
|
let deletedDetailIds = $state([]);
|
||||||
|
|
||||||
function getLabel(fieldKey, value) {
|
function getLabel(fieldKey, value) {
|
||||||
if (!detailFormState.selectOptions?.[fieldKey]) return value;
|
if (!contactDetailFormState.selectOptions?.[fieldKey]) return value;
|
||||||
const option = detailFormState.selectOptions[fieldKey].find(opt => opt.value === value);
|
const option = contactDetailFormState.selectOptions[fieldKey].find(opt => opt.value === value);
|
||||||
return option?.label || value || "-";
|
return option?.label || value || "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +74,7 @@
|
|||||||
group.rows.forEach(row => {
|
group.rows.forEach(row => {
|
||||||
row.columns.forEach(col => {
|
row.columns.forEach(col => {
|
||||||
if (col.type === "select" && col.optionsEndpoint) {
|
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() {
|
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 });
|
console.log(finalPayload);
|
||||||
const originalPayload = $state.snapshot({ ...masterDetail.formSnapshot, Details: originalDetails });
|
// console.log('Original Payload:', JSON.stringify(originalPayload));
|
||||||
console.log('Current Payload:', editedDetails);
|
// console.log('Current Payload:', JSON.stringify(currentPayload));
|
||||||
console.log('Original Payload:', originalDetails);
|
// 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 = {
|
// // const customPayload = {
|
||||||
// ...formState.form,
|
// // ...formState.form,
|
||||||
// Details: masterDetail.selectedItem?.data?.Details || []
|
// // Details: masterDetail.selectedItem?.data?.Details || []
|
||||||
// };
|
// // };
|
||||||
// console.log('Custom Payload for Edit:', JSON.stringify(customPayload));
|
// // console.log('Custom Payload for Edit:', JSON.stringify(customPayload));
|
||||||
// const result = await formState.save(masterDetail.mode, customPayload);
|
// // const result = await formState.save(masterDetail.mode, customPayload);
|
||||||
|
|
||||||
// ***
|
// // ***
|
||||||
const changedFields = getChangedFields(originalPayload, currentPayload);
|
// const changedFields = getChangedFields(originalPayload, currentPayload);
|
||||||
console.log('Changed Fields:', JSON.stringify(changedFields));
|
// console.log('Changed Fields:', JSON.stringify(changedFields));
|
||||||
|
|
||||||
// if (Object.keys(changedFields).length === 0) {
|
// // if (Object.keys(changedFields).length === 0) {
|
||||||
// toast('No changes detected');
|
// // toast('No changes detected');
|
||||||
// return;
|
// // return;
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
// const payload = {
|
// // const payload = {
|
||||||
// ContactID: formState.form.ContactID,
|
// // ContactID: formState.form.ContactID,
|
||||||
// ...changedFields
|
// // ...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') {
|
// // if (result.status === 'success') {
|
||||||
// console.log('Contact updated successfully');
|
// // console.log('Contact updated successfully');
|
||||||
// toast('Contact Updated!');
|
// // toast('Contact Updated!');
|
||||||
// masterDetail.exitForm(true);
|
// // masterDetail.exitForm(true);
|
||||||
// } else {
|
// // } else {
|
||||||
// console.error('Failed to update contact:', result.message);
|
// // console.error('Failed to update contact:', result.message);
|
||||||
// }
|
// // }
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryAction = $derived({
|
const primaryAction = $derived({
|
||||||
@ -142,71 +203,114 @@
|
|||||||
|
|
||||||
const secondaryActions = [];
|
const secondaryActions = [];
|
||||||
|
|
||||||
const actionsDetail = [
|
function snapshotForm() {
|
||||||
{
|
return untrack(() => {
|
||||||
Icon: PlusIcon,
|
const f = contactDetailFormState.form;
|
||||||
label: 'Add Contact Detail',
|
return {
|
||||||
onClick: () => addDetail(),
|
SiteID: f.SiteID ?? "",
|
||||||
},
|
ContactCode: f.ContactCode ?? "",
|
||||||
];
|
ContactEmail: f.ContactEmail ?? "",
|
||||||
|
Department: f.Department ?? "",
|
||||||
function addDetail() {
|
OccupationID: f.OccupationID ?? "",
|
||||||
editingDetailIndex = null; // Mode create baru
|
JobTitle: f.JobTitle ?? "",
|
||||||
detailFormState.reset(); // Reset form ke initialForm
|
};
|
||||||
detailFormState.setForm({ ...contactDetailInitialForm }); // Set form kosong
|
});
|
||||||
showDetailForm = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveDetail() {
|
function resetContactDetailForm() {
|
||||||
// Ambil current form dari detailFormState.form
|
contactDetailFormState.reset();
|
||||||
const newDetail = { ...detailFormState.form };
|
editingId = null;
|
||||||
|
}
|
||||||
if (isEditingDetail) {
|
|
||||||
// Mode edit: update detail yang ada
|
function handleInsertDetail() {
|
||||||
masterDetail.selectedItem.data.Details[editingDetailIndex] = newDetail;
|
const row = {
|
||||||
toast('Contact Detail Updated!');
|
id: ++idCounter,
|
||||||
} else {
|
ContactDetID: null,
|
||||||
// Mode create: tambah detail baru
|
...snapshotForm()
|
||||||
if (!masterDetail.selectedItem.data.Details) {
|
};
|
||||||
masterDetail.selectedItem.data.Details = [];
|
|
||||||
}
|
tempDetailContact = [...tempDetailContact, row];
|
||||||
masterDetail.selectedItem.data.Details.push(newDetail);
|
|
||||||
toast('Contact Detail Added!');
|
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);
|
||||||
}
|
}
|
||||||
|
tempDetailContact = tempDetailContact.filter((row) => row.id !== id);
|
||||||
// Reset form dan tutup form
|
if (editingId === id) {
|
||||||
detailFormState.reset();
|
resetContactDetailForm();
|
||||||
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--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPageContainer title="Edit Contact" {primaryAction} {secondaryActions}>
|
<FormPageContainer title="Edit Contact" {primaryAction} {secondaryActions}>
|
||||||
@ -215,96 +319,82 @@
|
|||||||
formFields={formFields}
|
formFields={formFields}
|
||||||
mode="edit"
|
mode="edit"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator class="my-4"/>
|
<Separator class="my-4"/>
|
||||||
<div class="flex flex-col px-2 py-1 gap-2 h-fit w-full">
|
|
||||||
<TopbarWrapper
|
<div>
|
||||||
title="Contact Detail"
|
<DictionaryFormRenderer
|
||||||
actions={actionsDetail}
|
formState={contactDetailFormState}
|
||||||
|
formFields={contactDetailFormFields}
|
||||||
|
mode="create"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex gap-2 mt-1 ms-2">
|
||||||
{#if showDetailForm}
|
{#if editingId !== null}
|
||||||
<Card.Root class="w-full gap-2 2xl:gap-4 py-2 2xl:py-4">
|
<Button size="sm" class="cursor-pointer" onclick={handleUpdateDetail}>Update</Button>
|
||||||
<Card.Content class="space-y-3">
|
<Button size="sm" variant="outline" class="cursor-pointer" onclick={handleCancelEditDetail}>
|
||||||
<DictionaryFormRenderer
|
Cancel
|
||||||
formState={detailFormState}
|
</Button>
|
||||||
formFields={contactDetailFormFields}
|
{:else}
|
||||||
/>
|
<Button size="sm" class="cursor-pointer" onclick={handleInsertDetail}>Insert</Button>
|
||||||
</Card.Content>
|
|
||||||
<Card.Footer class="flex justify-end flex-end gap-2">
|
|
||||||
<Button size="sm" onclick={saveDetail}>
|
|
||||||
{isEditingDetail ? 'Update Detail' : 'Save Detail'}
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="outline" onclick={cancelDetail}>Cancel</Button>
|
|
||||||
</Card.Footer>
|
|
||||||
</Card.Root>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#each masterDetail.selectedItem?.data?.Details as contactdetail, index}
|
</div>
|
||||||
<Card.Root class="w-full gap-2 2xl:gap-4 py-2 2xl:py-4">
|
</div>
|
||||||
<Card.Header>
|
|
||||||
<div class="flex items-start justify-between">
|
<div class="mt-4">
|
||||||
<div class="space-y-1">
|
<Separator />
|
||||||
<Card.Title class="text-sm font-medium">
|
<Table.Root>
|
||||||
{contactdetail.ContactCode || "null"}
|
<Table.Header>
|
||||||
</Card.Title>
|
<Table.Row class="hover:bg-transparent">
|
||||||
<Card.Description class="text-sm font-medium">
|
<Table.Head>Site</Table.Head>
|
||||||
{contactdetail.ContactEmail || "null"}
|
<Table.Head>Code</Table.Head>
|
||||||
</Card.Description>
|
<Table.Head>Department</Table.Head>
|
||||||
</div>
|
<Table.Head>Occupation</Table.Head>
|
||||||
<div class="flex items-center gap-4">
|
<Table.Head>Job Title</Table.Head>
|
||||||
<Badge variant="outline" class="text-xs">
|
<Table.Head>Email</Table.Head>
|
||||||
{getLabel('SiteID', contactdetail.SiteID)}
|
<Table.Head class="w-[80px]"></Table.Head>
|
||||||
</Badge>
|
</Table.Row>
|
||||||
<div>
|
</Table.Header>
|
||||||
<Button
|
<Table.Body>
|
||||||
size="icon"
|
{#if tempDetailContact.length === 0}
|
||||||
variant="ghost" class="size-7"
|
<Table.Row>
|
||||||
onclick={() => editDetail(index)}
|
<Table.Cell colspan={9} class="text-center text-muted-foreground py-6">
|
||||||
|
No data. Fill the form above and click Insert.
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{:else}
|
||||||
|
{#each tempDetailContact as row (row.id)}
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>{row.SiteID}</Table.Cell>
|
||||||
|
<Table.Cell>{row.ContactCode}</Table.Cell>
|
||||||
|
<Table.Cell>{row.Department}</Table.Cell>
|
||||||
|
<Table.Cell>{row.OccupationID}</Table.Cell>
|
||||||
|
<Table.Cell>{row.JobTitle}</Table.Cell>
|
||||||
|
<Table.Cell>{row.ContactEmail}</Table.Cell>
|
||||||
|
<Table.Cell class="w-[80px]">
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
class="h-7 w-7 cursor-pointer"
|
||||||
|
onclick={() => handleEditDetail(row)}
|
||||||
>
|
>
|
||||||
<Edit2Icon class="h-4 w-4" />
|
<PencilIcon class="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost" class="size-7"
|
variant="ghost"
|
||||||
onclick={() => removeDetail(index)}
|
class="h-7 w-7 cursor-pointer"
|
||||||
|
onclick={() => handleRemoveDetail(row.id)}
|
||||||
>
|
>
|
||||||
<XIcon class="h-4 w-4" />
|
<Trash2Icon class="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Table.Cell>
|
||||||
</div>
|
</Table.Row>
|
||||||
</Card.Header>
|
{/each}
|
||||||
<Card.Content class="space-y-3">
|
{/if}
|
||||||
<div class="grid grid-cols-3 gap-3">
|
</Table.Body>
|
||||||
<div class="space-y-1">
|
</Table.Root>
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
||||||
Department
|
|
||||||
</p>
|
|
||||||
<p class="text-sm font-medium">
|
|
||||||
{getLabel('Department', contactdetail.Department)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1">
|
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
||||||
Job Title
|
|
||||||
</p>
|
|
||||||
<p class="text-sm font-medium">
|
|
||||||
{contactdetail.JobTitle || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1">
|
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
||||||
Occupation
|
|
||||||
</p>
|
|
||||||
<p class="text-sm font-medium">
|
|
||||||
<!-- {contactdetail.OccupationID || "-"} -->
|
|
||||||
{getLabel('OccupationID', contactdetail.OccupationID)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card.Content>
|
|
||||||
</Card.Root>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</FormPageContainer>
|
</FormPageContainer>
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
import PlusIcon from "@lucide/svelte/icons/plus";
|
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||||
import * as Card from "$lib/components/ui/card/index.js";
|
import * as Card from "$lib/components/ui/card/index.js";
|
||||||
import { Badge } from "$lib/components/ui/badge/index.js";
|
import { Badge } from "$lib/components/ui/badge/index.js";
|
||||||
|
import * as Table from "$lib/components/ui/table/index.js";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -41,6 +42,46 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#snippet DetailsTable({ value, label })}
|
||||||
|
<div class="space-y-1.5 w-full">
|
||||||
|
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||||
|
{label}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{#if value && Array.isArray(value) && value.length > 0}
|
||||||
|
<div class="border rounded-md">
|
||||||
|
<Table.Root>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Head>Site</Table.Head>
|
||||||
|
<Table.Head>Code</Table.Head>
|
||||||
|
<Table.Head>Department</Table.Head>
|
||||||
|
<Table.Head>Occupation</Table.Head>
|
||||||
|
<Table.Head>Job Title</Table.Head>
|
||||||
|
<Table.Head>Email</Table.Head>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body>
|
||||||
|
{#each value as row, i}
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>{row.SiteID}</Table.Cell>
|
||||||
|
<Table.Cell>{row.ContactCode}</Table.Cell>
|
||||||
|
<Table.Cell>{row.Department}</Table.Cell>
|
||||||
|
<Table.Cell>{row.OccupationID}</Table.Cell>
|
||||||
|
<Table.Cell>{row.JobTitle}</Table.Cell>
|
||||||
|
<Table.Cell>{row.ContactEmail}</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<span class="text-sm font-medium">-</span>
|
||||||
|
{/if}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
{#snippet Fieldset({ value, label, isUTCDate = false })}
|
{#snippet Fieldset({ value, label, isUTCDate = false })}
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
<dt class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||||
@ -62,7 +103,7 @@
|
|||||||
title={masterDetail.selectedItem.data.NameFirst}
|
title={masterDetail.selectedItem.data.NameFirst}
|
||||||
{actions}
|
{actions}
|
||||||
/>
|
/>
|
||||||
<div class="flex-1 min-h-0 overflow-y-auto space-y-4">
|
<div class="flex-1 min-h-0 overflow-y-auto">
|
||||||
{#each detailSections as section}
|
{#each detailSections as section}
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
{#if section.groups}
|
{#if section.groups}
|
||||||
@ -84,11 +125,26 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class={section.class}>
|
<div class={section.class}>
|
||||||
{#each section.fields as field}
|
{#each section.fields as field}
|
||||||
{@render Fieldset({
|
{#if field.fullWidth}
|
||||||
label: field.label,
|
<div class="col-span-2">
|
||||||
value: getFieldValue(field),
|
{#if field.key === "Details"}
|
||||||
isUTCDate: field.isUTCDate
|
{@render DetailsTable({ label: field.label, value: getFieldValue(field) })}
|
||||||
})}
|
{:else}
|
||||||
|
{@render Fieldset({ label: field.label, value: getFieldValue(field), isUTCDate: field.isUTCDate })}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{: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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -96,62 +152,6 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
|
||||||
<div class="flex flex-col px-2 py-1 gap-2 h-full w-full">
|
|
||||||
<TopbarWrapper
|
|
||||||
title="Contact Detail"
|
|
||||||
actions={actionsDetail}
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
{#each masterDetail.selectedItem?.data?.Details as contactdetail}
|
|
||||||
<Card.Root class="w-full gap-2 2xl:gap-4 py-2 2xl:py-4">
|
|
||||||
<Card.Header>
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div class="space-y-1">
|
|
||||||
<Card.Title class="text-sm font-medium">
|
|
||||||
{contactdetail.ContactCode}
|
|
||||||
</Card.Title>
|
|
||||||
<Card.Description class="text-sm font-medium">
|
|
||||||
{contactdetail.ContactEmail}
|
|
||||||
</Card.Description>
|
|
||||||
</div>
|
|
||||||
<Badge variant="secondary" class="text-xs">
|
|
||||||
Site {contactdetail.SiteID}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</Card.Header>
|
|
||||||
<Card.Content class="space-y-3">
|
|
||||||
<div class="grid grid-cols-3 gap-3">
|
|
||||||
<div class="space-y-1">
|
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
||||||
Department
|
|
||||||
</p>
|
|
||||||
<p class="text-sm font-medium">
|
|
||||||
{contactdetail.Department || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1">
|
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
||||||
Job Title
|
|
||||||
</p>
|
|
||||||
<p class="text-sm font-medium">
|
|
||||||
{contactdetail.JobTitle || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1">
|
|
||||||
<p class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
||||||
Occupation
|
|
||||||
</p>
|
|
||||||
<p class="text-sm font-medium">
|
|
||||||
{contactdetail.OccupationID || "-"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card.Content>
|
|
||||||
</Card.Root>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<ReusableEmpty icon={UserXIcon} desc="Select a contact to see details"/>
|
<ReusableEmpty icon={UserXIcon} desc="Select a contact to see details"/>
|
||||||
{/if}
|
{/if}
|
||||||
@ -13,6 +13,6 @@ export async function createContainer(newContainerForm) {
|
|||||||
return await create(API.CONTAINER, newContainerForm)
|
return await create(API.CONTAINER, newContainerForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editContainer(editContainerForm) {
|
export async function editContainer(editContainerForm, id) {
|
||||||
return await update(API.CONTAINER, editContainerForm)
|
return await update(API.CONTAINER, editContainerForm, id)
|
||||||
}
|
}
|
||||||
@ -28,9 +28,9 @@ export const detailSections = [
|
|||||||
{
|
{
|
||||||
class: "grid grid-cols-2 gap-4 items-center",
|
class: "grid grid-cols-2 gap-4 items-center",
|
||||||
fields: [
|
fields: [
|
||||||
{ key: "ConClass", label: "Container Class" },
|
{ key: "ConClassLabel", label: "Container Class" },
|
||||||
{ key: "Color", label: "Color" },
|
{ key: "Color", label: "Color" },
|
||||||
{ key: "Additive", label: "Additive" },
|
{ key: "AdditiveLabel", label: "Additive" },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { API } from "$lib/config/api";
|
import { API } from "$lib/config/api";
|
||||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||||
|
import { getChangedFields } from "$lib/utils/getChangedFields";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -38,7 +39,24 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handleEdit() {
|
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') {
|
if (result.status === 'success') {
|
||||||
console.log('Container updated successfully');
|
console.log('Container updated successfully');
|
||||||
@ -46,6 +64,8 @@
|
|||||||
masterDetail.exitForm(true);
|
masterDetail.exitForm(true);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to update container:', result.message);
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,6 @@ export async function createLocation(newLocationForm) {
|
|||||||
return await create(API.LOCATION, newLocationForm)
|
return await create(API.LOCATION, newLocationForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editLocation(editLocationForm) {
|
export async function editLocation(editLocationForm, id) {
|
||||||
return await update(API.LOCATION, editLocationForm)
|
return await update(API.LOCATION, editLocationForm, id)
|
||||||
}
|
}
|
||||||
@ -43,7 +43,8 @@ export const detailSections = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ key: "Province", label: "Province" },
|
{ key: "Province", label: "Province" },
|
||||||
{ key: "City", label: "City" },
|
{ 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: [
|
fields: [
|
||||||
{ key: "Street1", label: "Street 1" },
|
{ key: "Street1", label: "Street 1" },
|
||||||
{ key: "Street2", label: "Street 2" },
|
{ key: "Street2", label: "Street 2" },
|
||||||
|
{ key: "Phone", label: "Phone" },
|
||||||
|
{ key: "Mobile", label: "Mobile" },
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,24 +7,28 @@ export const locationSchema = z.object({
|
|||||||
LocFull: z.string().min(1, "Required"),
|
LocFull: z.string().min(1, "Required"),
|
||||||
Email: z.string().trim().optional().refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),"Invalid email format"),
|
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"),
|
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 = {
|
export const locationInitialForm = {
|
||||||
LocationID: '',
|
LocationID: '',
|
||||||
LocCode: '',
|
|
||||||
LocType: '',
|
|
||||||
LocFull: '',
|
|
||||||
SiteID: '',
|
SiteID: '',
|
||||||
|
LocCode: '',
|
||||||
|
Parent: '',
|
||||||
|
LocFull: '',
|
||||||
|
Description: '',
|
||||||
|
LocType: '',
|
||||||
Street1: '',
|
Street1: '',
|
||||||
Street2: '',
|
Street2: '',
|
||||||
Phone: '',
|
|
||||||
Email: '',
|
|
||||||
City: '',
|
|
||||||
Province: '',
|
Province: '',
|
||||||
ZIP: '',
|
City: '',
|
||||||
|
PostCode: '',
|
||||||
GeoLocationSystem: '',
|
GeoLocationSystem: '',
|
||||||
GeoLocationData: '',
|
GeoLocationData: '',
|
||||||
|
Phone: '',
|
||||||
|
Mobile: '',
|
||||||
|
Email: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const locationDefaultErrors = {
|
export const locationDefaultErrors = {
|
||||||
@ -32,7 +36,8 @@ export const locationDefaultErrors = {
|
|||||||
LocFull: "Required",
|
LocFull: "Required",
|
||||||
Email: null,
|
Email: null,
|
||||||
Phone: null,
|
Phone: null,
|
||||||
ZIP: null,
|
Mobile: null,
|
||||||
|
PostCode: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const locationFormFields = [
|
export const locationFormFields = [
|
||||||
@ -52,17 +57,26 @@ export const locationFormFields = [
|
|||||||
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
|
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "LocCode",
|
key: "Parent",
|
||||||
label: "Location Code",
|
label: "Parent",
|
||||||
required: true,
|
required: false,
|
||||||
type: "text",
|
type: "select",
|
||||||
validateOn: ["input"]
|
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
|
||||||
|
valueKey: "SiteID",
|
||||||
|
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "row",
|
type: "row",
|
||||||
columns: [
|
columns: [
|
||||||
|
{
|
||||||
|
key: "LocCode",
|
||||||
|
label: "Location Code",
|
||||||
|
required: true,
|
||||||
|
type: "text",
|
||||||
|
validateOn: ["input"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "LocType",
|
key: "LocType",
|
||||||
label: "Location Type",
|
label: "Location Type",
|
||||||
@ -70,13 +84,25 @@ export const locationFormFields = [
|
|||||||
type: "select",
|
type: "select",
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/location_type`,
|
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/location_type`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
{
|
{
|
||||||
key: "LocFull",
|
key: "LocFull",
|
||||||
label: "Location Name",
|
label: "Location Name",
|
||||||
required: true,
|
required: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
validateOn: ["input"]
|
validateOn: ["input"]
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
key: "Description",
|
||||||
|
label: "Description",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -126,8 +152,8 @@ export const locationFormFields = [
|
|||||||
type: "row",
|
type: "row",
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
key: "ZIP",
|
key: "PostCode",
|
||||||
label: "ZIP",
|
label: "Post Code",
|
||||||
required: false,
|
required: false,
|
||||||
type: "text",
|
type: "text",
|
||||||
validateOn: ["input"],
|
validateOn: ["input"],
|
||||||
@ -150,13 +176,20 @@ export const locationFormFields = [
|
|||||||
type: "text",
|
type: "text",
|
||||||
validateOn: ["input"]
|
validateOn: ["input"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "Mobile",
|
||||||
|
label: "Mobile",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
validateOn: ["input"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "Email",
|
key: "Email",
|
||||||
label: "Email",
|
label: "Email",
|
||||||
required: false,
|
required: false,
|
||||||
type: "text",
|
type: "text",
|
||||||
validateOn: ["input"]
|
validateOn: ["input"]
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { API } from "$lib/config/api";
|
import { API } from "$lib/config/api";
|
||||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||||
|
import { getChangedFields } from "$lib/utils/getChangedFields";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -50,7 +51,24 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handleEdit() {
|
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') {
|
if (result.status === 'success') {
|
||||||
console.log('Location updated successfully');
|
console.log('Location updated successfully');
|
||||||
@ -58,6 +76,8 @@
|
|||||||
masterDetail.exitForm(true);
|
masterDetail.exitForm(true);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to update location:', result.message);
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,6 @@ export async function createOccupation(newOccupationForm) {
|
|||||||
return await create(API.OCCUPATION, newOccupationForm)
|
return await create(API.OCCUPATION, newOccupationForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editOccupation(editOccupationForm) {
|
export async function editOccupation(editOccupationForm, id) {
|
||||||
return await update(API.OCCUPATION, editOccupationForm)
|
return await update(API.OCCUPATION, editOccupationForm, id)
|
||||||
}
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import { API } from "$lib/config/api";
|
import { API } from "$lib/config/api";
|
||||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||||
|
import { getChangedFields } from "$lib/utils/getChangedFields";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -38,7 +39,24 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handleEdit() {
|
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') {
|
if (result.status === 'success') {
|
||||||
console.log('Occupation updated successfully');
|
console.log('Occupation updated successfully');
|
||||||
@ -46,6 +64,8 @@
|
|||||||
masterDetail.exitForm(true);
|
masterDetail.exitForm(true);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to update occupation:', result.message);
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export const detailSections = [
|
|||||||
{ key: "ClientTypeLabel", label: "Client Type" },
|
{ key: "ClientTypeLabel", label: "Client Type" },
|
||||||
{ key: "HostID", label: "Host ID" },
|
{ key: "HostID", label: "Host ID" },
|
||||||
{ key: "ClientID", label: "Client ID" },
|
{ key: "ClientID", label: "Client ID" },
|
||||||
{ key: "details", label: "Details", fullWidth: true },
|
{ key: "Details", label: "Details", fullWidth: true },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const API = {
|
|||||||
PATVISIT: '/api/patvisit',
|
PATVISIT: '/api/patvisit',
|
||||||
VISITLIST: '/api/patvisit/patient/',
|
VISITLIST: '/api/patvisit/patient/',
|
||||||
COUNTER: '/api/counter',
|
COUNTER: '/api/counter',
|
||||||
CONTAINER: '/api/specimen/containerdef',
|
CONTAINER: '/api/specimen/container',
|
||||||
PROVINCE: '/api/areageo/provinces',
|
PROVINCE: '/api/areageo/provinces',
|
||||||
CITY: '/api/areageo/cities',
|
CITY: '/api/areageo/cities',
|
||||||
ACCOUNT: '/api/organization/account',
|
ACCOUNT: '/api/organization/account',
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
modeOpt: 'cascade',
|
modeOpt: 'cascade',
|
||||||
saveEndpoint: createAccount,
|
saveEndpoint: createAccount,
|
||||||
editEndpoint: editAccount,
|
editEndpoint: editAccount,
|
||||||
|
idKey: 'AccountID',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
modeOpt: 'default',
|
modeOpt: 'default',
|
||||||
saveEndpoint: createContainer,
|
saveEndpoint: createContainer,
|
||||||
editEndpoint: editContainer,
|
editEndpoint: editContainer,
|
||||||
|
idKey: 'ConDefID',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
modeOpt: 'cascade',
|
modeOpt: 'cascade',
|
||||||
saveEndpoint: createLocation,
|
saveEndpoint: createLocation,
|
||||||
editEndpoint: editLocation,
|
editEndpoint: editLocation,
|
||||||
|
idKey: 'LocationID',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
modeOpt: 'default',
|
modeOpt: 'default',
|
||||||
saveEndpoint: createOccupation,
|
saveEndpoint: createOccupation,
|
||||||
editEndpoint: editOccupation,
|
editEndpoint: editOccupation,
|
||||||
|
idKey: 'OccupationID',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user