mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-28 15:45:55 +07:00
initial dictionary work
This commit is contained in:
parent
fccbb5a87f
commit
b03c0565f1
@ -53,7 +53,7 @@
|
|||||||
dictionary: [
|
dictionary: [
|
||||||
{
|
{
|
||||||
title: "Admission",
|
title: "Admission",
|
||||||
url: "/admission",
|
url: "/dictionary",
|
||||||
icon: BookOpenIcon,
|
icon: BookOpenIcon,
|
||||||
submenus: [
|
submenus: [
|
||||||
{
|
{
|
||||||
@ -125,14 +125,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Test",
|
title: "Test",
|
||||||
url: "#",
|
url: "/testdef",
|
||||||
icon: BookOpenIcon,
|
icon: BookOpenIcon,
|
||||||
submenus: [
|
|
||||||
{
|
|
||||||
title: "Test Site",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export function useFormState(initial) {
|
export function useFormState(initial) {
|
||||||
const form = $state(structuredClone(initial))
|
const form = $state(structuredClone(initial))
|
||||||
const isSaving = $state({ current: false });
|
const isSaving = $state({ current: false });
|
||||||
console.log(form);
|
|
||||||
// function resetForm() {
|
// function resetForm() {
|
||||||
// Object.assign(form, structuredClone(initial));
|
// Object.assign(form, structuredClone(initial));
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -29,9 +29,7 @@ export function useMasterDetail(options = {}) {
|
|||||||
const isDirty = $derived(
|
const isDirty = $derived(
|
||||||
JSON.stringify(formState.form) !== JSON.stringify(formSnapshot)
|
JSON.stringify(formState.form) !== JSON.stringify(formSnapshot)
|
||||||
);
|
);
|
||||||
|
// $inspect(formState.form)
|
||||||
$inspect(formState.form)
|
|
||||||
|
|
||||||
async function select(item) {
|
async function select(item) {
|
||||||
mode = "view";
|
mode = "view";
|
||||||
|
|
||||||
|
|||||||
18
src/lib/components/dictionary/location/api/location-api.js
Normal file
18
src/lib/components/dictionary/location/api/location-api.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { API } from '$lib/config/api.js';
|
||||||
|
import { getById, searchWithParams, create, update } from '$lib/api/api-client';
|
||||||
|
|
||||||
|
export async function getLocations(searchQuery) {
|
||||||
|
return await searchWithParams(API.LOCATION, searchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLocation(searchQuery) {
|
||||||
|
return await getById(API.LOCATION, searchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createLocation(newLocationForm) {
|
||||||
|
return await create(API.LOCATION, newLocationForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editLocation(editLocationForm) {
|
||||||
|
return await update(API.LOCATION, editLocationForm)
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||||
|
import Settings2Icon from "@lucide/svelte/icons/settings-2";
|
||||||
|
import PencilIcon from "@lucide/svelte/icons/pencil";
|
||||||
|
|
||||||
|
export const searchFields = [
|
||||||
|
{
|
||||||
|
key: "LocCode",
|
||||||
|
label: "Location Code",
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "LocName",
|
||||||
|
label: "Location Name",
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "LocType",
|
||||||
|
label: "Location Type",
|
||||||
|
type: "text"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const detailSections = [
|
||||||
|
];
|
||||||
|
|
||||||
|
export function locationActions(masterDetail) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
Icon: PlusIcon,
|
||||||
|
label: 'Add Location',
|
||||||
|
onClick: () => masterDetail.enterCreate(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Icon: Settings2Icon,
|
||||||
|
label: 'Search Parameters',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function viewActions(handlers){
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
Icon: PencilIcon,
|
||||||
|
label: 'Edit Location',
|
||||||
|
onClick: handlers.editLocation,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,165 @@
|
|||||||
|
import { API } from "$lib/config/api";
|
||||||
|
import EraserIcon from "@lucide/svelte/icons/eraser";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const locationSchema = z.object({});
|
||||||
|
|
||||||
|
export const locationInitialForm = {
|
||||||
|
LocationID: '',
|
||||||
|
LocCode: '',
|
||||||
|
LocType: '',
|
||||||
|
LocFull: '',
|
||||||
|
SiteID: '',
|
||||||
|
Street1: '',
|
||||||
|
Street2: '',
|
||||||
|
Phone: '',
|
||||||
|
Email: '',
|
||||||
|
City: '',
|
||||||
|
Province: '',
|
||||||
|
ZIP: '',
|
||||||
|
GeoLocationSystem: '',
|
||||||
|
GeoLocationData: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationDefaultErrors = {
|
||||||
|
LocCode: "Required",
|
||||||
|
LocName: "Required",
|
||||||
|
LocType: "Required",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationFormFields = [
|
||||||
|
{
|
||||||
|
title: "Basic Information",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "SiteID",
|
||||||
|
label: "Site ID",
|
||||||
|
required: true,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "LocCode",
|
||||||
|
label: "Location Code",
|
||||||
|
required: true,
|
||||||
|
type: "text",
|
||||||
|
validateOn: ["input"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "LocType",
|
||||||
|
label: "Location Type",
|
||||||
|
required: true,
|
||||||
|
type: "text",
|
||||||
|
validateOn: ["input"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "LocFull",
|
||||||
|
label: "Location Name",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Address Detail",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "Province",
|
||||||
|
label: "Province",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.PROVINCE}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "City",
|
||||||
|
label: "City",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.CITY}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "ZIP",
|
||||||
|
label: "ZIP",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Street1",
|
||||||
|
label: "Street 1",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Street2",
|
||||||
|
label: "Street 2",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Contact Information",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "Phone",
|
||||||
|
label: "Phone",
|
||||||
|
required: false,
|
||||||
|
type: "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Email",
|
||||||
|
label: "Email",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Geographic Data",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "GeoLocationSystem",
|
||||||
|
label: "Geo Location System",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "GeoLocationData",
|
||||||
|
label: "Geo Location Data",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getLocationFormActions(handlers) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
Icon: EraserIcon,
|
||||||
|
label: 'Clear Form',
|
||||||
|
onClick: handlers.clearForm,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
<script>
|
||||||
|
import { locationColumns } from "$lib/components/dictionary/location/table/location-columns";
|
||||||
|
import { getLocations, getLocation } from "$lib/components/dictionary/location/api/location-api";
|
||||||
|
import { useSearch } from "$lib/components/composable/use-search.svelte";
|
||||||
|
import { searchFields, locationActions } from "$lib/components/dictionary/location/config/location-config";
|
||||||
|
import TopbarWrapper from "$lib/components/topbar/topbar-wrapper.svelte";
|
||||||
|
import ReusableSearchParam from "$lib/components/reusable/reusable-search-param.svelte";
|
||||||
|
import ReusableEmpty from "$lib/components/reusable/reusable-empty.svelte";
|
||||||
|
import ReusableDataTable from "$lib/components/reusable/reusable-data-table.svelte";
|
||||||
|
import MapPinXIcon from "@lucide/svelte/icons/map-pin-x";
|
||||||
|
|
||||||
|
let props = $props();
|
||||||
|
|
||||||
|
const search = useSearch(searchFields, getLocations);
|
||||||
|
const initialForm = props.masterDetail.formState.form;
|
||||||
|
const actions = locationActions(props.masterDetail, initialForm)
|
||||||
|
actions.find(a => a.label === 'Search Parameters').popoverContent = searchParamSnippet;
|
||||||
|
|
||||||
|
let activeRowId = $state(null);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#snippet searchParamSnippet()}
|
||||||
|
<ReusableSearchParam {searchFields} bind:searchQuery={search.searchQuery} onSearch={search.handleSearch} onReset={search.handleReset} isLoading={search.isLoading}/>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
||||||
|
onkeydown={(e) => e.key === 'Enter' && props.masterDetail.isFormMode && props.masterDetail.exitForm()}
|
||||||
|
class={`
|
||||||
|
${props.masterDetail.isMobile ? "w-full" : props.masterDetail.isFormMode ? "w-[3%] cursor-pointer" : "w-[35%]"}
|
||||||
|
transition-all duration-300 flex flex-col items-center p-2 h-full overflow-y-auto
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<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}
|
||||||
|
<span class="flex flex-col items-center justify-center gap-4 tracking-widest font-semibold select-none">
|
||||||
|
{#each "LOCATION".split("") as c}
|
||||||
|
<span class="leading-none">{c}</span>
|
||||||
|
{/each}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !props.masterDetail.isFormMode}
|
||||||
|
<div role="button" tabindex="0" class="flex flex-1 flex-col" onclick={(e) => e.stopPropagation()} onkeydown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<TopbarWrapper {actions}/>
|
||||||
|
<div class="flex-1 w-full h-full">
|
||||||
|
{#if search.searchData.length > 0}
|
||||||
|
<ReusableDataTable data={search.searchData} columns={columns} handleRowClick={props.masterDetail.select} {activeRowId} rowIdKey="InternalPID"/>
|
||||||
|
{:else}
|
||||||
|
<div class="flex h-full">
|
||||||
|
<ReusableEmpty icon={MapPinXIcon} desc="Try searching from search parameters"/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
vw
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
export const locationColumns = [
|
||||||
|
{
|
||||||
|
accessorKey: "LocCode",
|
||||||
|
header: "Location Code",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "LocFull",
|
||||||
|
header: "Location Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "LocType",
|
||||||
|
header: "Location Type",
|
||||||
|
},
|
||||||
|
];
|
||||||
18
src/lib/components/dictionary/testdef/api/testdef-api.js
Normal file
18
src/lib/components/dictionary/testdef/api/testdef-api.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { API } from '$lib/config/api.js';
|
||||||
|
import { getById, searchWithParams, create, update } from '$lib/api/api-client';
|
||||||
|
|
||||||
|
export async function searchParam(searchQuery) {
|
||||||
|
return await searchWithParams(API.TEST, searchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTest(searchQuery) {
|
||||||
|
return await getById(API.TEST, searchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTest(newTestForm) {
|
||||||
|
return await create(API.TEST, newTestForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editTest(editTestForm) {
|
||||||
|
return await update(API.TEST, editTestForm)
|
||||||
|
}
|
||||||
@ -0,0 +1,211 @@
|
|||||||
|
import { API } from "$lib/config/api";
|
||||||
|
import EraserIcon from "@lucide/svelte/icons/eraser";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { cleanEmptyStrings } from "$lib/utils/cleanEmptyStrings";
|
||||||
|
|
||||||
|
export const testSchema = z.object({});
|
||||||
|
|
||||||
|
export const admissionInitialForm = {
|
||||||
|
InternalPVID: "",
|
||||||
|
InternalPID: "",
|
||||||
|
PVID: "",
|
||||||
|
EpisodeID: "",
|
||||||
|
DiagCode: "",
|
||||||
|
Diagnosis: "",
|
||||||
|
ADTCode: "",
|
||||||
|
LocationID: "",
|
||||||
|
AttDoc: "",
|
||||||
|
RefDoc: "",
|
||||||
|
AdmDoc: "",
|
||||||
|
CnsDoc: "",
|
||||||
|
isDischarge: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export const admissionDefaultErrors = {};
|
||||||
|
|
||||||
|
export const admissionFormFields = [
|
||||||
|
{
|
||||||
|
title: "Visit Information",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
// {
|
||||||
|
// key: "PVID",
|
||||||
|
// label: "Visit ID",
|
||||||
|
// required: false,
|
||||||
|
// type: "text",
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
key: "EpisodeID",
|
||||||
|
label: "Episode ID",
|
||||||
|
required: false,
|
||||||
|
type: "text",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Medical Team",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "AttDoc",
|
||||||
|
label: "Attended Doctor",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.CONTACT}`,
|
||||||
|
valueKey: "ContactID",
|
||||||
|
labelKey: (item) => `${item.Initial} - ${item.NameFirst} ${item.NameLast}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "RefDoc",
|
||||||
|
label: "Reference Doctor",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.CONTACT}`,
|
||||||
|
valueKey: "ContactID",
|
||||||
|
labelKey: (item) => `${item.Initial} - ${item.NameFirst} ${item.NameLast}`,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "AdmDoc",
|
||||||
|
label: "Admitted Doctor",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.CONTACT}`,
|
||||||
|
valueKey: "ContactID",
|
||||||
|
labelKey: (item) => `${item.Initial} - ${item.NameFirst} ${item.NameLast}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "CnsDoc",
|
||||||
|
label: "Consulte Doctor",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.CONTACT}`,
|
||||||
|
valueKey: "ContactID",
|
||||||
|
labelKey: (item) => `${item.Initial} - ${item.NameFirst} ${item.NameLast}`,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Visit Classification",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "LocationID",
|
||||||
|
label: "Location",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
optionsEndpoint: `${API.BASE_URL}${API.LOCATION}`,
|
||||||
|
valueKey: "LocationID",
|
||||||
|
labelKey: (item) => `${item.LocCode} - ${item.LocFull}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "VisitClass",
|
||||||
|
label: "Visit Class",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
// optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/visit_classes`,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "ServiceClass",
|
||||||
|
label: "Service Class",
|
||||||
|
required: false,
|
||||||
|
type: "select",
|
||||||
|
// optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/service_classes`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "isDischarge",
|
||||||
|
label: "Admission Status",
|
||||||
|
required: false,
|
||||||
|
type: "toggle",
|
||||||
|
defaultValue: false,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Clinical Information",
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
key: "Diagnosis",
|
||||||
|
label: "Diagnosis",
|
||||||
|
required: false,
|
||||||
|
type: "textarea",
|
||||||
|
rows: 4,
|
||||||
|
maxLength: 1000,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getAdmissionFormActions(handlers) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
Icon: EraserIcon,
|
||||||
|
label: 'Clear Form',
|
||||||
|
onClick: handlers.clearForm,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const admissionTemplate = {
|
||||||
|
PVID: 'PVID',
|
||||||
|
InternalPID: 'InternalPID',
|
||||||
|
EpisodeID: 'EpisodeID',
|
||||||
|
PatDiag: {
|
||||||
|
DiagCode: 'DiagCode',
|
||||||
|
Diagnosis: 'Diagnosis',
|
||||||
|
},
|
||||||
|
PatVisitADT: {
|
||||||
|
ADTCode: () => 'A04',
|
||||||
|
LocationID: 'LocationID',
|
||||||
|
AttDoc: 'AttDoc',
|
||||||
|
RefDoc: 'RefDoc',
|
||||||
|
AdmDoc: 'AdmDoc',
|
||||||
|
CnsDoc: 'CnsDoc',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function buildPayload(form, schema = admissionTemplate) {
|
||||||
|
const payload = {};
|
||||||
|
|
||||||
|
for (const [key, config] of Object.entries(schema)) {
|
||||||
|
if (typeof config === 'string') {
|
||||||
|
// Kirim nilai dari form, atau null jika tidak ada (agar key tetap ada)
|
||||||
|
payload[key] = form[config] ?? null;
|
||||||
|
}
|
||||||
|
else if (typeof config === 'function') {
|
||||||
|
payload[key] = config(form);
|
||||||
|
}
|
||||||
|
else if (typeof config === 'object' && config !== null) {
|
||||||
|
// Rekursif tanpa pengecekan panjang keys, agar objek nested selalu dibuat
|
||||||
|
payload[key] = buildPayload(form, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanEmptyStrings(payload);
|
||||||
|
}
|
||||||
@ -117,7 +117,7 @@ export const admissionFormFields = [
|
|||||||
label: "Visit Class",
|
label: "Visit Class",
|
||||||
required: false,
|
required: false,
|
||||||
type: "select",
|
type: "select",
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/visit_classes`,
|
// optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/visit_classes`,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -129,7 +129,7 @@ export const admissionFormFields = [
|
|||||||
label: "Service Class",
|
label: "Service Class",
|
||||||
required: false,
|
required: false,
|
||||||
type: "select",
|
type: "select",
|
||||||
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/service_classes`,
|
// optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/service_classes`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "isDischarge",
|
key: "isDischarge",
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
import PatientFormRenderer from "$lib/components/patient/reusable/patient-form-renderer.svelte";
|
import PatientFormRenderer from "$lib/components/patient/reusable/patient-form-renderer.svelte";
|
||||||
import { buildPayload } from "$lib/components/patient/admission/config/admission-form-config";
|
import { buildPayload } from "$lib/components/patient/admission/config/admission-form-config";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
|
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -21,6 +22,19 @@
|
|||||||
|
|
||||||
const actions = formActions(handlers);
|
const actions = formActions(handlers);
|
||||||
|
|
||||||
|
let showConfirm = $state(false);
|
||||||
|
|
||||||
|
function handleExit() {
|
||||||
|
const ok = masterDetail.exitForm();
|
||||||
|
if (!ok) {
|
||||||
|
showConfirm = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDiscard() {
|
||||||
|
masterDetail.exitForm(true);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
const payload = buildPayload(formState.form);
|
const payload = buildPayload(formState.form);
|
||||||
|
|
||||||
@ -28,7 +42,7 @@
|
|||||||
|
|
||||||
console.log(payload);
|
console.log(payload);
|
||||||
toast('Visit Created!');
|
toast('Visit Created!');
|
||||||
masterDetail?.exitForm();
|
masterDetail?.exitForm(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryAction = $derived({
|
const primaryAction = $derived({
|
||||||
@ -56,4 +70,9 @@
|
|||||||
formFields={formFields}
|
formFields={formFields}
|
||||||
mode="create"
|
mode="create"
|
||||||
/>
|
/>
|
||||||
</FormPageContainer>
|
</FormPageContainer>
|
||||||
|
|
||||||
|
<ReusableAlertDialog
|
||||||
|
bind:open={masterDetail.showExitConfirm}
|
||||||
|
onConfirm={masterDetail.confirmExit}
|
||||||
|
/>
|
||||||
@ -5,6 +5,7 @@
|
|||||||
import { buildPayload } from "$lib/components/patient/admission/config/admission-form-config";
|
import { buildPayload } from "$lib/components/patient/admission/config/admission-form-config";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
import { buildEditPayloadWithDiff } from "$lib/components/patient/admission/config/admission-payload";
|
import { buildEditPayloadWithDiff } from "$lib/components/patient/admission/config/admission-payload";
|
||||||
|
import ReusableAlertDialog from "$lib/components/reusable/reusable-alert-dialog.svelte";
|
||||||
|
|
||||||
let props = $props();
|
let props = $props();
|
||||||
|
|
||||||
@ -12,6 +13,19 @@
|
|||||||
|
|
||||||
const { formState } = masterDetail;
|
const { formState } = masterDetail;
|
||||||
|
|
||||||
|
let showConfirm = $state(false);
|
||||||
|
|
||||||
|
function handleExit() {
|
||||||
|
const ok = masterDetail.exitForm();
|
||||||
|
if (!ok) {
|
||||||
|
showConfirm = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDiscard() {
|
||||||
|
masterDetail.exitForm(true);
|
||||||
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// const backendData = masterDetail?.selectedItem.data;
|
// const backendData = masterDetail?.selectedItem.data;
|
||||||
// if (!backendData) return;
|
// if (!backendData) return;
|
||||||
@ -46,8 +60,8 @@
|
|||||||
const result = await masterDetail.formState.save(masterDetail.mode, customPayload);
|
const result = await masterDetail.formState.save(masterDetail.mode, customPayload);
|
||||||
|
|
||||||
console.log(customPayload);
|
console.log(customPayload);
|
||||||
toast('Visit Updated!');
|
// toast('Visit Updated!');
|
||||||
masterDetail?.exitForm();
|
// masterDetail?.exitForm();
|
||||||
// const result = await formState.save();
|
// const result = await formState.save();
|
||||||
|
|
||||||
// if (result.status === 'success') {
|
// if (result.status === 'success') {
|
||||||
@ -72,4 +86,9 @@
|
|||||||
formFields={formFields}
|
formFields={formFields}
|
||||||
mode="edit"
|
mode="edit"
|
||||||
/>
|
/>
|
||||||
</FormPageContainer>
|
</FormPageContainer>
|
||||||
|
|
||||||
|
<ReusableAlertDialog
|
||||||
|
bind:open={masterDetail.showExitConfirm}
|
||||||
|
onConfirm={masterDetail.confirmExit}
|
||||||
|
/>
|
||||||
@ -10,7 +10,7 @@ export const patientSchema = z.object({
|
|||||||
(date) => new Date(date) <= new Date(),
|
(date) => new Date(date) <= new Date(),
|
||||||
"Cannot exceed today's date"
|
"Cannot exceed today's date"
|
||||||
),
|
),
|
||||||
EmailAddress1: z.string().min(1, "Required").email("Invalid email format"),
|
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"),
|
EmailAddress2: 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"),
|
||||||
MobilePhone: z.string().max(14, "Max 14 chars").regex(/^$|^[0-9]+$/, "Can only contain numbers"),
|
MobilePhone: z.string().max(14, "Max 14 chars").regex(/^$|^[0-9]+$/, "Can only contain numbers"),
|
||||||
@ -67,7 +67,7 @@ export const patientDefaultErrors = {
|
|||||||
NameFirst: "Required",
|
NameFirst: "Required",
|
||||||
Sex: "Required",
|
Sex: "Required",
|
||||||
Birthdate: "Required",
|
Birthdate: "Required",
|
||||||
EmailAddress1: "Required",
|
EmailAddress1: null,
|
||||||
EmailAddress2: null,
|
EmailAddress2: null,
|
||||||
'PatIdt.Identifier': null,
|
'PatIdt.Identifier': null,
|
||||||
Phone: null,
|
Phone: null,
|
||||||
@ -255,7 +255,7 @@ export const patientFormFields = [
|
|||||||
{
|
{
|
||||||
type: "row",
|
type: "row",
|
||||||
columns: [
|
columns: [
|
||||||
{ key: "EmailAddress1", label: "Email Address 1", required: true, type: "email", validateOn: ["input", "blur"] },
|
{ key: "EmailAddress1", label: "Email Address 1", required: false, type: "email", validateOn: ["input", "blur"] },
|
||||||
{ key: "Phone", label: "Phone", required: false, type: "text", validateOn: ["input"] },
|
{ key: "Phone", label: "Phone", required: false, type: "text", validateOn: ["input"] },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -96,16 +96,16 @@
|
|||||||
// $inspect(masterDetail?.selectedItem?.patient)
|
// $inspect(masterDetail?.selectedItem?.patient)
|
||||||
// $inspect(formState.form)
|
// $inspect(formState.form)
|
||||||
async function handleEdit() {
|
async function handleEdit() {
|
||||||
console.log(formState.form);
|
// console.log(formState.form);
|
||||||
// const result = await formState.save(masterDetail.mode);
|
const result = await formState.save(masterDetail.mode);
|
||||||
|
|
||||||
// if (result.status === 'success') {
|
if (result.status === 'success') {
|
||||||
// console.log('Patient updated successfully');
|
console.log('Patient updated successfully');
|
||||||
// toast('Patient Updated!');
|
toast('Patient Updated!');
|
||||||
// masterDetail.exitForm();
|
masterDetail.exitForm(true);
|
||||||
// } else {
|
} else {
|
||||||
// console.error('Failed to update patient:', result.message);
|
console.error('Failed to update patient:', result.message);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryAction = $derived({
|
const primaryAction = $derived({
|
||||||
|
|||||||
@ -25,5 +25,4 @@ export const API = {
|
|||||||
DEPARTMENT: '/api/organization/department',
|
DEPARTMENT: '/api/organization/department',
|
||||||
WORKSTATION: '/api/organization/workstation',
|
WORKSTATION: '/api/organization/workstation',
|
||||||
TEST: '/api/tests',
|
TEST: '/api/tests',
|
||||||
TESTSITE: '/api/test/testdefsite',
|
|
||||||
};
|
};
|
||||||
|
|||||||
51
src/routes/dictionary/location/+page.svelte
Normal file
51
src/routes/dictionary/location/+page.svelte
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script>
|
||||||
|
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||||
|
import { useMasterDetail } from "$lib/components/composable/use-master-detail.svelte";
|
||||||
|
import { getLocation, createLocation, editLocation } from "$lib/components/dictionary/location/api/location-api";
|
||||||
|
import MasterPage from "$lib/components/dictionary/location/page/master-page.svelte";
|
||||||
|
import ViewPage from "$lib/components/dictionary/location/page/view-page.svelte";
|
||||||
|
import CreatePage from "$lib/components/dictionary/location/page/create-page.svelte";
|
||||||
|
import EditPage from "$lib/components/dictionary/location/page/edit-page.svelte";
|
||||||
|
import { locationSchema, locationInitialForm, locationDefaultErrors, locationFormFields, getLocationFormActions } from "$lib/components/dictionary/location/config/location-form-config";
|
||||||
|
|
||||||
|
const masterDetail = useMasterDetail({
|
||||||
|
onSelect: async (row) => {
|
||||||
|
return await getLocation(row.LocationID);
|
||||||
|
},
|
||||||
|
formConfig: {
|
||||||
|
schema: locationSchema,
|
||||||
|
initialForm: locationInitialForm,
|
||||||
|
defaultErrors: locationDefaultErrors,
|
||||||
|
mode: 'create',
|
||||||
|
modeOpt: 'cascade',
|
||||||
|
saveEndpoint: createLocation,
|
||||||
|
editEndpoint: editLocation,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageContext = {
|
||||||
|
masterDetail,
|
||||||
|
formFields: locationFormFields,
|
||||||
|
formActions: getLocationFormActions,
|
||||||
|
schema: locationSchema,
|
||||||
|
initialForm: locationInitialForm,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex w-full h-full overflow-hidden">
|
||||||
|
{#if masterDetail.showMaster}
|
||||||
|
<MasterPage {masterDetail} />
|
||||||
|
{/if}
|
||||||
|
<Separator orientation="vertical"/>
|
||||||
|
{#if masterDetail.showDetail}
|
||||||
|
<main class={`${masterDetail.isMobile ? 'w-full' : masterDetail.isFormMode ? 'w-[97%] flex flex-col items-start' : 'w-[65%]'} h-full overflow-y-auto flex flex-col items-center transition-all duration-300`}>
|
||||||
|
{#if masterDetail.mode === "view"}
|
||||||
|
<ViewPage context={pageContext}/>
|
||||||
|
{:else if masterDetail.mode === "create"}
|
||||||
|
<CreatePage context={pageContext}/>
|
||||||
|
{:else if masterDetail.mode === "edit"}
|
||||||
|
<EditPage context={pageContext}/>
|
||||||
|
{/if}
|
||||||
|
</main>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
60
src/routes/dictionary/testdef/+page.svelte
Normal file
60
src/routes/dictionary/testdef/+page.svelte
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<script>
|
||||||
|
import { Separator } from "$lib/components/ui/separator/index.js";
|
||||||
|
import { useMasterDetail } from "$lib/components/composable/use-master-detail.svelte";
|
||||||
|
import { getTest, createTest, editTest } from "$lib/components/dictionary/testdef/api/testdef-api";
|
||||||
|
import MasterPage from "$lib/components/dictionary/testdef/page/master-page.svelte";
|
||||||
|
import ViewPage from "$lib/components/dictionary/testdef/page/view-page.svelte";
|
||||||
|
import CreatePage from "$lib/components/dictionary/testdef/page/create-page.svelte";
|
||||||
|
import EditPage from "$lib/components/dictionary/testdef/page/edit-page.svelte";
|
||||||
|
import { admissionSchema, admissionInitialForm, admissionDefaultErrors, admissionFormFields, getAdmissionFormActions, buildPayload } from "$lib/components/dictionary/testdef/config/testdef-form-config";
|
||||||
|
|
||||||
|
const masterDetail = useMasterDetail({
|
||||||
|
onSelect: async (row) => {
|
||||||
|
const response = await getVisit(row.PVID);
|
||||||
|
if (response?.data) {
|
||||||
|
response.data.isDischarge = response.data.ADTCode === "A03";
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
formConfig: {
|
||||||
|
schema: admissionSchema,
|
||||||
|
initialForm: admissionInitialForm,
|
||||||
|
defaultErrors: admissionDefaultErrors,
|
||||||
|
mode: 'create',
|
||||||
|
modeOpt: 'default',
|
||||||
|
saveEndpoint: createAdmission,
|
||||||
|
editEndpoint: editAdmission,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageContext = {
|
||||||
|
masterDetail,
|
||||||
|
formFields: admissionFormFields,
|
||||||
|
formActions: getAdmissionFormActions,
|
||||||
|
schema: admissionSchema,
|
||||||
|
initialForm: admissionInitialForm,
|
||||||
|
defaultErrors: {
|
||||||
|
create: admissionDefaultErrors,
|
||||||
|
edit: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex w-full h-full overflow-hidden">
|
||||||
|
{#if masterDetail.showMaster}
|
||||||
|
<MasterPage {masterDetail} />
|
||||||
|
{/if}
|
||||||
|
<Separator orientation="vertical"/>
|
||||||
|
{#if masterDetail.showDetail}
|
||||||
|
<main class={`${masterDetail.isMobile ? 'w-full' : masterDetail.isFormMode ? 'w-[97%] flex flex-col items-start' : 'w-[65%]'} h-full overflow-y-auto flex flex-col items-center transition-all duration-300`}>
|
||||||
|
{#if masterDetail.mode === "view"}
|
||||||
|
<ViewPage context={pageContext}/>
|
||||||
|
{:else if masterDetail.mode === "create"}
|
||||||
|
<CreatePage context={pageContext}/>
|
||||||
|
{:else if masterDetail.mode === "edit"}
|
||||||
|
<EditPage context={pageContext}/>
|
||||||
|
{/if}
|
||||||
|
</main>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
Loading…
x
Reference in New Issue
Block a user