mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-28 17:52:31 +07:00
add feature and bug fix
patient list : menambahkan client validation patientid boleh '.' dan '-' fix isDeadLabel karena backend gamau ngeluarin valuenya contact : tambahkan guard pada handlesave fix create contact gak nampilin detail di payload fix edit contact updated ga nampil occupation : testing spinner untuk indikasi rowclick testing close popover setelah save selesai
This commit is contained in:
parent
ffb57539e8
commit
239147f7ec
@ -17,7 +17,7 @@ export function useSearch(searchFields, searchApiFunction) {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSearch() {
|
async function handleSearch(onDone) {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
try {
|
try {
|
||||||
searchData = await searchApiFunction(searchQuery);
|
searchData = await searchApiFunction(searchQuery);
|
||||||
@ -25,6 +25,7 @@ export function useSearch(searchFields, searchApiFunction) {
|
|||||||
console.error('Search failed:', error);
|
console.error('Search failed:', error);
|
||||||
} finally {
|
} finally {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
onDone?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import PlusIcon from "@lucide/svelte/icons/plus";
|
|||||||
import Settings2Icon from "@lucide/svelte/icons/settings-2";
|
import Settings2Icon from "@lucide/svelte/icons/settings-2";
|
||||||
import PencilIcon from "@lucide/svelte/icons/pencil";
|
import PencilIcon from "@lucide/svelte/icons/pencil";
|
||||||
import { API } from "$lib/config/api";
|
import { API } from "$lib/config/api";
|
||||||
|
import RefreshIcon from "@lucide/svelte/icons/refresh-cw";
|
||||||
|
|
||||||
export const searchFields = [
|
export const searchFields = [
|
||||||
{
|
{
|
||||||
@ -53,8 +54,13 @@ export const detailSections = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export function contactActions(masterDetail) {
|
export function contactActions(masterDetail, handlers) {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
Icon: RefreshIcon,
|
||||||
|
label: 'Refresh Data',
|
||||||
|
onClick: handlers.refresh,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Icon: PlusIcon,
|
Icon: PlusIcon,
|
||||||
label: 'Add Contact',
|
label: 'Add Contact',
|
||||||
|
|||||||
@ -113,10 +113,16 @@
|
|||||||
tempDetailContact,
|
tempDetailContact,
|
||||||
});
|
});
|
||||||
console.log(payload)
|
console.log(payload)
|
||||||
const result = await formState.save(masterDetail.mode);
|
const result = await formState.save(masterDetail.mode, payload);
|
||||||
|
|
||||||
toast('Contact Created!');
|
if (result.status === 'success') {
|
||||||
masterDetail?.exitForm(true);
|
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({
|
const primaryAction = $derived({
|
||||||
|
|||||||
@ -1,309 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { useDictionaryForm } from "$lib/components/composable/use-dictionary-form.svelte";
|
|
||||||
import FormPageContainer from "$lib/components/reusable/form/form-page-container.svelte";
|
|
||||||
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
|
||||||
import { toast } from "svelte-sonner";
|
|
||||||
import { untrack } from "svelte";
|
|
||||||
import { API } from "$lib/config/api";
|
|
||||||
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
|
||||||
import { Separator } from "$lib/components/ui/separator/index.js";
|
|
||||||
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 TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
|
||||||
import { contactDetailInitialForm, contactDetailFormFields } 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";
|
|
||||||
|
|
||||||
let props = $props();
|
|
||||||
|
|
||||||
const { masterDetail, formFields, formActions, schema, initialForm } = props.context;
|
|
||||||
|
|
||||||
const { formState } = masterDetail;
|
|
||||||
|
|
||||||
const detailFormState = useForm({
|
|
||||||
schema: null,
|
|
||||||
initialForm: contactDetailInitialForm,
|
|
||||||
defaultErrors: {},
|
|
||||||
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);
|
|
||||||
|
|
||||||
let showConfirm = $state(false);
|
|
||||||
|
|
||||||
function getLabel(fieldKey, value) {
|
|
||||||
if (!detailFormState.selectOptions?.[fieldKey]) return value;
|
|
||||||
const option = detailFormState.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) {
|
|
||||||
detailFormState.fetchOptions(col, detailFormState.form);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
let editedDetails = $state(masterDetail.selectedItem?.data?.Details || []);
|
|
||||||
|
|
||||||
async function handleEdit() {
|
|
||||||
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 = [];
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FormPageContainer title="Edit Contact" {primaryAction} {secondaryActions}>
|
|
||||||
<DictionaryFormRenderer
|
|
||||||
{formState}
|
|
||||||
formFields={formFields}
|
|
||||||
mode="edit"
|
|
||||||
/>
|
|
||||||
<Separator class="my-4"/>
|
|
||||||
<div class="flex flex-col px-2 py-1 gap-2 h-fit w-full">
|
|
||||||
<TopbarWrapper
|
|
||||||
title="Contact Detail"
|
|
||||||
actions={actionsDetail}
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
{#if showDetailForm}
|
|
||||||
<Card.Root class="w-full gap-2 2xl:gap-4 py-2 2xl:py-4">
|
|
||||||
<Card.Content class="space-y-3">
|
|
||||||
<DictionaryFormRenderer
|
|
||||||
formState={detailFormState}
|
|
||||||
formFields={contactDetailFormFields}
|
|
||||||
/>
|
|
||||||
</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}
|
|
||||||
{#each masterDetail.selectedItem?.data?.Details as contactdetail, index}
|
|
||||||
<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 || "null"}
|
|
||||||
</Card.Title>
|
|
||||||
<Card.Description class="text-sm font-medium">
|
|
||||||
{contactdetail.ContactEmail || "null"}
|
|
||||||
</Card.Description>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<Badge variant="outline" class="text-xs">
|
|
||||||
{getLabel('SiteID', contactdetail.SiteID)}
|
|
||||||
</Badge>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
variant="ghost" class="size-7"
|
|
||||||
onclick={() => editDetail(index)}
|
|
||||||
>
|
|
||||||
<Edit2Icon class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
variant="ghost" class="size-7"
|
|
||||||
onclick={() => removeDetail(index)}
|
|
||||||
>
|
|
||||||
<XIcon class="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</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">
|
|
||||||
{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>
|
|
||||||
</FormPageContainer>
|
|
||||||
|
|
||||||
<ReusableAlertDialog
|
|
||||||
bind:open={masterDetail.showExitConfirm}
|
|
||||||
onConfirm={masterDetail.confirmExit}
|
|
||||||
/>
|
|
||||||
@ -21,6 +21,11 @@
|
|||||||
|
|
||||||
const { formState } = masterDetail;
|
const { formState } = masterDetail;
|
||||||
|
|
||||||
|
let editingId = $state(null);
|
||||||
|
let idCounter = $state(0);
|
||||||
|
let tempDetailContact = $state([]);
|
||||||
|
let deletedDetailIds = $state([]);
|
||||||
|
|
||||||
const contactDetailFormState = useForm({
|
const contactDetailFormState = useForm({
|
||||||
schema: contactDetailSchema,
|
schema: contactDetailSchema,
|
||||||
initialForm: contactDetailInitialForm,
|
initialForm: contactDetailInitialForm,
|
||||||
@ -31,158 +36,7 @@
|
|||||||
|
|
||||||
let showConfirm = $state(false);
|
let showConfirm = $state(false);
|
||||||
|
|
||||||
let editingId = $state(null);
|
function snapshotForm() {
|
||||||
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() {
|
|
||||||
return untrack(() => {
|
return untrack(() => {
|
||||||
const f = contactDetailFormState.form;
|
const f = contactDetailFormState.form;
|
||||||
return {
|
return {
|
||||||
@ -228,17 +82,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// function handleUpdateDetail() {
|
|
||||||
// tempDetailContact = tempDetailContact.map((row) =>
|
|
||||||
// row.id === editingId ?
|
|
||||||
// {
|
|
||||||
// ...row,
|
|
||||||
// ...snapshotForm()
|
|
||||||
// } : row
|
|
||||||
// );
|
|
||||||
// resetContactDetailForm();
|
|
||||||
// }
|
|
||||||
|
|
||||||
function handleUpdateDetail() {
|
function handleUpdateDetail() {
|
||||||
const updated = snapshotForm();
|
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(() => {
|
$effect(() => {
|
||||||
const mainForm = formState.form;
|
const mainForm = formState.form;
|
||||||
if (mainForm.Details && Array.isArray(mainForm.Details)) {
|
if (mainForm.Details && Array.isArray(mainForm.Details)) {
|
||||||
|
|||||||
@ -8,12 +8,15 @@
|
|||||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||||
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
|
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
|
||||||
import UserXIcon from "@lucide/svelte/icons/user-x";
|
import UserXIcon from "@lucide/svelte/icons/user-x";
|
||||||
|
import MoveLeftIcon from "@lucide/svelte/icons/move-left";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
const search = useSearch(searchFields, getContacts);
|
const search = useSearch(searchFields, getContacts);
|
||||||
const initialForm = props.masterDetail.formState.form;
|
const handlers = {
|
||||||
const actions = contactActions(props.masterDetail, initialForm)
|
refresh: () => {search.handleSearch()},
|
||||||
|
};
|
||||||
|
const actions = contactActions(props.masterDetail, handlers)
|
||||||
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
|
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
|
||||||
|
|
||||||
let activeRowId = $state(null);
|
let activeRowId = $state(null);
|
||||||
@ -38,10 +41,13 @@
|
|||||||
>
|
>
|
||||||
<div class={`flex w-full ${props.masterDetail.isFormMode ? "flex-col justify-center h-full items-center" : "flex-col justify-start h-full"}`} >
|
<div class={`flex w-full ${props.masterDetail.isFormMode ? "flex-col justify-center h-full items-center" : "flex-col justify-start h-full"}`} >
|
||||||
{#if props.masterDetail.isFormMode}
|
{#if props.masterDetail.isFormMode}
|
||||||
<span class="flex flex-col items-center justify-center gap-4 tracking-widest font-semibold select-none">
|
<span class="flex flex-col items-center justify-start gap-4 tracking-widest font-semibold select-none h-full">
|
||||||
{#each "CONTACT".split("") as c}
|
<MoveLeftIcon />
|
||||||
<span class="leading-none">{c}</span>
|
<div class="flex flex-col items-center justify-center flex-grow gap-4">
|
||||||
{/each}
|
{#each "CONTACT".split("") as c}
|
||||||
|
<span class="leading-none">{c}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import PlusIcon from "@lucide/svelte/icons/plus";
|
|||||||
import Settings2Icon from "@lucide/svelte/icons/settings-2";
|
import Settings2Icon from "@lucide/svelte/icons/settings-2";
|
||||||
import PencilIcon from "@lucide/svelte/icons/pencil";
|
import PencilIcon from "@lucide/svelte/icons/pencil";
|
||||||
import { API } from "$lib/config/api";
|
import { API } from "$lib/config/api";
|
||||||
|
import RefreshIcon from "@lucide/svelte/icons/refresh-cw";
|
||||||
|
|
||||||
export const searchFields = [
|
export const searchFields = [
|
||||||
{
|
{
|
||||||
@ -67,8 +68,13 @@ export const detailSections = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function locationActions(masterDetail) {
|
export function locationActions(masterDetail, handlers) {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
Icon: RefreshIcon,
|
||||||
|
label: 'Refresh Data',
|
||||||
|
onClick: handlers.refresh,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Icon: PlusIcon,
|
Icon: PlusIcon,
|
||||||
label: 'Add Location',
|
label: 'Add Location',
|
||||||
|
|||||||
@ -8,12 +8,15 @@
|
|||||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||||
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
|
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
|
||||||
import MapPinXIcon from "@lucide/svelte/icons/map-pin-x";
|
import MapPinXIcon from "@lucide/svelte/icons/map-pin-x";
|
||||||
|
import MoveLeftIcon from "@lucide/svelte/icons/move-left";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
const search = useSearch(searchFields, getLocations);
|
const search = useSearch(searchFields, getLocations);
|
||||||
const initialForm = props.masterDetail.formState.form;
|
const handlers = {
|
||||||
const actions = locationActions(props.masterDetail, initialForm)
|
refresh: () => {search.handleSearch()},
|
||||||
|
};
|
||||||
|
const actions = locationActions(props.masterDetail, handlers)
|
||||||
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
|
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
|
||||||
|
|
||||||
let activeRowId = $state(null);
|
let activeRowId = $state(null);
|
||||||
@ -38,10 +41,13 @@
|
|||||||
>
|
>
|
||||||
<div class={`flex w-full ${props.masterDetail.isFormMode ? "flex-col justify-center h-full items-center" : "flex-col justify-start h-full"}`} >
|
<div class={`flex w-full ${props.masterDetail.isFormMode ? "flex-col justify-center h-full items-center" : "flex-col justify-start h-full"}`} >
|
||||||
{#if props.masterDetail.isFormMode}
|
{#if props.masterDetail.isFormMode}
|
||||||
<span class="flex flex-col items-center justify-center gap-4 tracking-widest font-semibold select-none">
|
<span class="flex flex-col items-center justify-start gap-4 tracking-widest font-semibold select-none h-full">
|
||||||
{#each "LOCATION".split("") as c}
|
<MoveLeftIcon />
|
||||||
<span class="leading-none">{c}</span>
|
<div class="flex flex-col items-center justify-center flex-grow gap-4">
|
||||||
{/each}
|
{#each "LOCATION".split("") as c}
|
||||||
|
<span class="leading-none">{c}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import PlusIcon from "@lucide/svelte/icons/plus";
|
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||||
import Settings2Icon from "@lucide/svelte/icons/settings-2";
|
import Settings2Icon from "@lucide/svelte/icons/settings-2";
|
||||||
import PencilIcon from "@lucide/svelte/icons/pencil";
|
import PencilIcon from "@lucide/svelte/icons/pencil";
|
||||||
|
import RefreshIcon from "@lucide/svelte/icons/refresh-cw";
|
||||||
|
|
||||||
export const searchFields = [
|
export const searchFields = [
|
||||||
{
|
{
|
||||||
@ -37,8 +38,13 @@ export const detailSections = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function occupationActions(masterDetail) {
|
export function occupationActions(masterDetail, handlers) {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
Icon: RefreshIcon,
|
||||||
|
label: 'Refresh Data',
|
||||||
|
onClick: handlers.refresh,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Icon: PlusIcon,
|
Icon: PlusIcon,
|
||||||
label: 'Add Location',
|
label: 'Add Location',
|
||||||
|
|||||||
@ -8,20 +8,23 @@
|
|||||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||||
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
|
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
|
||||||
import FolderXIcon from "@lucide/svelte/icons/folder-x";
|
import FolderXIcon from "@lucide/svelte/icons/folder-x";
|
||||||
|
import MoveLeftIcon from "@lucide/svelte/icons/move-left";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
const search = useSearch(searchFields, getOccupations);
|
const search = useSearch(searchFields, getOccupations);
|
||||||
const initialForm = props.masterDetail.formState.form;
|
const handlers = {
|
||||||
const actions = occupationActions(props.masterDetail, initialForm)
|
refresh: () => {search.handleSearch()},
|
||||||
|
};
|
||||||
|
const actions = occupationActions(props.masterDetail, handlers)
|
||||||
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
|
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
|
||||||
|
|
||||||
let activeRowId = $state(null);
|
let activeRowId = $state(null);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet searchParamSnippet()}
|
{#snippet searchParamSnippet(close)}
|
||||||
<ReusableSearchParam {searchFields}
|
<ReusableSearchParam {searchFields}
|
||||||
bind:searchQuery={search.searchQuery} onSearch={search.handleSearch} onReset={search.handleReset} isLoading={search.isLoading}
|
bind:searchQuery={search.searchQuery} onSearch={() => search.handleSearch(close)} onReset={search.handleReset} isLoading={search.isLoading}
|
||||||
selectOptions={search.selectOptions} loadingOptions={search.loadingOptions} fetchOptions={search.fetchOptions}
|
selectOptions={search.selectOptions} loadingOptions={search.loadingOptions} fetchOptions={search.fetchOptions}
|
||||||
/>
|
/>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@ -38,10 +41,13 @@
|
|||||||
>
|
>
|
||||||
<div class={`flex w-full ${props.masterDetail.isFormMode ? "flex-col justify-center h-full items-center" : "flex-col justify-start h-full"}`} >
|
<div class={`flex w-full ${props.masterDetail.isFormMode ? "flex-col justify-center h-full items-center" : "flex-col justify-start h-full"}`} >
|
||||||
{#if props.masterDetail.isFormMode}
|
{#if props.masterDetail.isFormMode}
|
||||||
<span class="flex flex-col items-center justify-center gap-4 tracking-widest font-semibold select-none">
|
<span class="flex flex-col items-center justify-start gap-4 tracking-widest font-semibold select-none h-full">
|
||||||
{#each "OCCUPATION".split("") as c}
|
<MoveLeftIcon />
|
||||||
<span class="leading-none">{c}</span>
|
<div class="flex flex-col items-center justify-center flex-grow gap-4">
|
||||||
{/each}
|
{#each "OCCUPATION".split("") as c}
|
||||||
|
<span class="leading-none">{c}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
||||||
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||||
import FolderXIcon from "@lucide/svelte/icons/folder-x";
|
import FolderXIcon from "@lucide/svelte/icons/folder-x";
|
||||||
|
import { Spinner } from "$lib/components/ui/spinner/index.js";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -46,7 +47,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#if masterDetail.selectedItem}
|
{#if masterDetail.isLoadingDetail}
|
||||||
|
<div class="h-full w-full flex items-center justify-center">
|
||||||
|
<Spinner class="size-6" />
|
||||||
|
</div>
|
||||||
|
{:else if masterDetail.selectedItem}
|
||||||
<div class="flex flex-col px-2 py-1 gap-2 h-full w-full">
|
<div class="flex flex-col px-2 py-1 gap-2 h-full w-full">
|
||||||
<TopbarWrapper
|
<TopbarWrapper
|
||||||
title={masterDetail.selectedItem.data.OccText}
|
title={masterDetail.selectedItem.data.OccText}
|
||||||
|
|||||||
@ -77,7 +77,7 @@ export const detailSections = [
|
|||||||
{ parentKey: "Custodian", key: "PatientID", label: "Custodian ID" },
|
{ parentKey: "Custodian", key: "PatientID", label: "Custodian ID" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ key: "isDeadLabel", label: "Deceased" },
|
{ key: "isDead", label: "Deceased", valueMap: { 0: "N", 1: "Y" } },
|
||||||
{ key: "CreateDate", label: "Create Date", isUTCDate: true },
|
{ key: "CreateDate", label: "Create Date", isUTCDate: true },
|
||||||
{ key: "DelDate", label: "Delete Date" },
|
{ key: "DelDate", label: "Delete Date" },
|
||||||
{ key: "TimeOfDeath", label: "Time of Death", isUTCDate: true },
|
{ key: "TimeOfDeath", label: "Time of Death", isUTCDate: true },
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import EraserIcon from "@lucide/svelte/icons/eraser";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const patientSchema = z.object({
|
export const patientSchema = z.object({
|
||||||
PatientID: z.string().min(1, "Required"),
|
PatientID: z.string().min(1, "Required").regex(/^[A-Za-z0-9.-]+$/, "Only letters, numbers, '-' and '.' are allowed"),
|
||||||
Sex: z.string().min(1, "Required"),
|
Sex: z.string().min(1, "Required"),
|
||||||
NameFirst: z.string().min(1, "Required"),
|
NameFirst: z.string().min(1, "Required"),
|
||||||
Birthdate: z.string().min(1, "Required").refine(
|
Birthdate: z.string().min(1, "Required").refine(
|
||||||
@ -56,7 +56,7 @@ export const patientInitialForm = {
|
|||||||
Phone: "",
|
Phone: "",
|
||||||
EmailAddress2: "",
|
EmailAddress2: "",
|
||||||
MobilePhone: "",
|
MobilePhone: "",
|
||||||
isDead: "",
|
isDead: 0,
|
||||||
TimeOfDeath: "",
|
TimeOfDeath: "",
|
||||||
LinkTo: [],
|
LinkTo: [],
|
||||||
PatCom: "",
|
PatCom: "",
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
showConfirm = true;
|
showConfirm = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// $inspect(formState.form)
|
|
||||||
function confirmDiscard() {
|
function confirmDiscard() {
|
||||||
masterDetail.exitForm(true);
|
masterDetail.exitForm(true);
|
||||||
}
|
}
|
||||||
@ -68,7 +68,6 @@
|
|||||||
disabled: formState.isSaving.current
|
disabled: formState.isSaving.current
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
$inspect(formState.errors)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormPageContainer title="Create Patient" {primaryAction} {secondaryActions} {actions}>
|
<FormPageContainer title="Create Patient" {primaryAction} {secondaryActions} {actions}>
|
||||||
|
|||||||
@ -42,8 +42,14 @@
|
|||||||
.filter(val => val && val.trim() !== "")
|
.filter(val => val && val.trim() !== "")
|
||||||
.join(" / ");
|
.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;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
const { Icon } = props;
|
const { Icon } = props;
|
||||||
|
|
||||||
|
let open = $state(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tooltip.Provider delayDuration={100}>
|
<Tooltip.Provider delayDuration={100}>
|
||||||
@ -14,7 +16,7 @@
|
|||||||
<Tooltip.Trigger>
|
<Tooltip.Trigger>
|
||||||
{#snippet child({ props: tooltipProps })}
|
{#snippet child({ props: tooltipProps })}
|
||||||
{#if props.popoverContent}
|
{#if props.popoverContent}
|
||||||
<Popover.Root>
|
<Popover.Root bind:open>
|
||||||
<Popover.Trigger>
|
<Popover.Trigger>
|
||||||
{#snippet child({ props: popoverProps })}
|
{#snippet child({ props: popoverProps })}
|
||||||
<Button
|
<Button
|
||||||
@ -28,7 +30,8 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</Popover.Trigger>
|
</Popover.Trigger>
|
||||||
<Popover.Content collisionPadding={props.collisionPadding ?? 0} class={props.popoverWidth ?? "w-72"}>
|
<Popover.Content collisionPadding={props.collisionPadding ?? 0} class={props.popoverWidth ?? "w-72"}>
|
||||||
{@render props.popoverContent()}
|
<!-- {@render props.popoverContent()} -->
|
||||||
|
{@render props.popoverContent(() => open = false)}
|
||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
modeOpt: 'default',
|
modeOpt: 'default',
|
||||||
saveEndpoint: createContact,
|
saveEndpoint: createContact,
|
||||||
editEndpoint: editContact,
|
editEndpoint: editContact,
|
||||||
|
idKey: 'ContactID',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user