change patient list crud

This commit is contained in:
faiztyanirh 2026-04-01 16:45:34 +07:00
parent 3f482fdecc
commit 09c2fc5b33
10 changed files with 246 additions and 416 deletions

View File

@ -9,10 +9,13 @@
import DictionaryFormRenderer from '$lib/components/reusable/form/dictionary-form-renderer.svelte';
let props = $props();
const operators = ['+', '-', '*', '/', '^', '(', ')'];
const logicalop = ['IF', 'THEN', 'ELSE', 'END', 'AND', 'OR', 'NOT', 'MIN', 'MAX', 'RESULT'];
$inspect(props.calFormState.errors)
const mathop = ['+', '-', '*', '/', '%','**', '(', ')'];
const comparisonop = ['=', '!=', '<', '>', '<=', '>='];
const logicalop = ['AND', 'OR', 'NOT'];
const conditionalop = ['?:', '??'];
const booleanop = ['TRUE', 'FALSE', 'NULL'];
const availableVar =['{ RESULT }', '{ GENDER }', '{ AGE }'];
let tokens = $state([]);
let cursorIndex = $state(0);
@ -30,6 +33,32 @@
syncFormState();
}
function insertTernary() {
// insert '?' di cursor sekarang
const questionToken = { id: uid(), type: 'conditional', value: '?' };
const colonToken = { id: uid(), type: 'conditional', value: ':' };
// insert keduanya, dengan 1 slot kosong di antara (cursor di antara ? dan :)
tokens = [
...tokens.slice(0, cursorIndex),
questionToken,
colonToken,
...tokens.slice(cursorIndex)
];
// cursor setelah '?' supaya user langsung isi nilai true-nya
cursorIndex += 1;
syncFormState();
}
function insertNullCoalescing() {
insertToken('conditional', '??');
}
function addConditional(op) {
if (op === '?:') insertTernary();
else if (op === '??') insertNullCoalescing();
}
function addValue(val) {
insertToken('test', val);
}
@ -102,6 +131,7 @@
function unselectAll(key) {
props.calFormState.form[key] = [];
props.calFormState.validateField?.(key, [], false);
props.calFormState.errors.FormulaCode = null;
}
$inspect(props.calFormState.selectOptions)
</script>
@ -110,9 +140,12 @@
<DictionaryFormRenderer
formState={props.calFormState}
formFields={props.testCalFormFields}
operators={operators}
logicalop={logicalop}
mathop={mathop}
comparisonop={comparisonop}
conditionalop={conditionalop}
logicalop={logicalop}
booleanop={booleanop}
availableVar={availableVar}
tokens={tokens}
cursorIndex={cursorIndex}
onUnselectAll={unselectAll}
@ -120,6 +153,7 @@
onAddOperator={addOperator}
onAddValue={addValue}
onAddLiteral={onLiteralConfirm}
onAddConditional={addConditional}
onMoveCursorLeft={moveCursorLeft}
onMoveCursorRight={moveCursorRight}
onDeleteChar={deleteChar}

View File

@ -15,7 +15,7 @@ export async function createPatient(newContactForm) {
return await create(API.PATIENTS, newContactForm)
}
export async function editPatient(editContactForm) {
export async function editPatient(editContactForm, id) {
// console.log(JSON.stringify(editContactForm));
return await update(API.PATIENTS, editContactForm)
return await update(API.PATIENTS, editContactForm, id)
}

View File

@ -82,19 +82,19 @@ export const detailSections = [
{ key: "TimeOfDeath", label: "Death Date", isUTCDate: true },
]
},
{
class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
fields: [
{ key: "", label: "Patient Visit ID" },
{ key: "", label: "Insurance" },
{ key: "", label: "Visit Class" },
{ key: "", label: "Service Class" },
{ key: "", label: "Location" },
{ key: "", label: "Doctor" },
{ key: "", label: "Admission Date", isUTCDate: true },
{ key: "", label: "Discharge Date", isUTCDate: true },
]
},
// {
// class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
// fields: [
// { key: "", label: "Patient Visit ID" },
// { key: "", label: "Insurance" },
// { key: "", label: "Visit Class" },
// { key: "", label: "Service Class" },
// { key: "", label: "Location" },
// { key: "", label: "Doctor" },
// { key: "", label: "Admission Date", isUTCDate: true },
// { key: "", label: "Discharge Date", isUTCDate: true },
// ]
// },
{
class: "grid grid-cols-1 sm:grid-cols-2 gap-3",
fields: [

View File

@ -18,6 +18,7 @@ export const patientSchema = z.object({
});
export const patientInitialForm = {
InternalPID: "",
PatientID: "",
AlternatePID: "",
PatIdt_IdentifierType: "",

View File

@ -37,6 +37,7 @@
async function handleSave() {
const payload = buildPatientPayload(formState.form);
console.log(payload);
const result = await formState.save(masterDetail.mode, payload);
if (result.status === 'success') {
@ -44,6 +45,8 @@
masterDetail?.exitForm(true);
} else {
console.error('Failed to save patient');
const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to save patient';
toast.error(errorMessages)
}
}

View File

@ -62,8 +62,6 @@
});
});
$inspect(masterDetail?.selectedItem?.patient)
// $inspect(formState.form)
async function handleEdit() {
const payload = buildPatientPayload(formState.form);
const result = await formState.save(masterDetail.mode, payload);
@ -74,6 +72,8 @@ $inspect(masterDetail?.selectedItem?.patient)
masterDetail.exitForm(true);
} else {
console.error('Failed to update patient:', result.message);
const errorMessages = result.messages ? Object.values(result.messages).join('\n') : 'Failed to update patient';
toast.error(errorMessages)
}
}

View File

@ -45,15 +45,19 @@
onAddLiteral,
onAddNewline,
onSetCursor,
operators,
mathop,
logicalop,
conditionalop,
comparisonop,
booleanop,
availableVar,
onMoveCursorLeft,
onMoveCursorRight,
onDeleteChar,
onClearExpression,
onAddOperator,
onAddValue,
onAddConditional,
} = $props();
@ -147,7 +151,7 @@
{/if}
</div>
<div class="relative flex flex-col items-center w-full">
<div class="relative flex flex-col items-start w-full">
{#if type === 'text'}
<Input
type="text"
@ -512,13 +516,10 @@
</InputGroup.Root>
</div>
{:else if type === 'formulabuilder'}
<div class="flex flex-col gap-8 w-full">
<div class="flex gap-1 w-full">
<Button type="button" variant="outline" size="icon" onclick={onMoveCursorLeft}>
<MoveLeftIcon class="w-4 h-4" />
</Button>
<div class="flex flex-col gap-6 w-full">
<div class="flex gap-2 w-full">
<div
class="relative flex-1 min-h-[2rem] rounded-md border bg-background px-3 py-2 font-mono text-sm cursor-text focus-within:ring-1 focus-within:ring-ring"
class="relative flex-1 min-h-[3rem] rounded-md border bg-background px-3 py-2 font-mono text-sm cursor-text focus-within:ring-1 focus-within:ring-ring overflow-y-auto max-h-32"
role="textbox"
tabindex="0"
onkeydown={(e) => {
@ -546,7 +547,7 @@
{/if}
<button
type="button"
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold border transition-colors"
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold border bg-secondary hover:bg-secondary/80 transition-colors"
onclick={() => onSetCursor(idx + 1)}
>
{tok.value}
@ -561,190 +562,190 @@
{/each}
{/if}
</div>
<Button type="button" variant="outline" size="icon" onclick={onMoveCursorRight}>
<MoveRightIcon class="w-4 h-4" />
</Button>
<Button type="button" variant="outline" size="icon" onclick={onDeleteChar}>
<DeleteIcon class="w-4 h-4" />
</Button>
<Button type="button" variant="outline" size="icon" onclick={onClearExpression}>
<BrushCleaningIcon class="w-4 h-4" />
</Button>
<Button type="button" variant="outline" size="icon" onclick={onAddNewline} title="New line">
<CornerDownLeftIcon class="w-4 h-4" />
</Button>
<div class="grid grid-cols-2 gap-1">
<Button variant="outline" size="icon" onclick={onMoveCursorLeft}><MoveLeftIcon class="w-4 h-4" /></Button>
<Button variant="outline" size="icon" onclick={onMoveCursorRight}><MoveRightIcon class="w-4 h-4" /></Button>
<Button variant="outline" size="icon" onclick={onDeleteChar} class="text-destructive"><DeleteIcon class="w-4 h-4" /></Button>
<Button variant="outline" size="icon" onclick={onAddNewline} title="New line"><CornerDownLeftIcon class="w-4 h-4" /></Button>
<Button variant="outline" size="icon" onclick={onClearExpression} class="col-span-2 w-full"><BrushCleaningIcon class="w-4 h-4" /></Button>
</div>
</div>
<div class="flex flex-col gap-4">
{#if formState.form.FormulaInput?.length > 0}
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Selected Tests</Label>
<div class="flex flex-wrap gap-2">
{#each formState.form.FormulaInput as item (item)}
<Button
type="button"
variant="outline"
class="h-auto w-auto p-2"
onclick={() => onAddValue?.(item.value)}
>
{item.value}
</Button>
{/each}
</div>
</div>
{#if formState.form.FormulaInput?.length}
<div>
<p class="text-xs font-semibold mb-1">Selected Tests</p>
<div class="flex flex-wrap gap-2">
{#each formState.form.FormulaInput as item}
<Button size="sm" variant="secondary" onclick={() => onAddValue?.(item.value)}>
{item.value}
</Button>
{/each}
</div>
{/if}
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Custom Values</Label>
<div class="flex flex-wrap gap-2">
<Popover.Root bind:open={stringPopoverOpen}>
<Popover.Trigger>
{#snippet child({ props: triggerProps })}
<Button
{...triggerProps}
type="button"
variant="outline"
class="h-auto w-auto p-2"
>
"abc"
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-64" side="bottom" align="start">
<div>
<div class="flex flex-col gap-3">
<p class="text-sm font-semibold">Enter Text Value</p>
<Input
type="text"
placeholder='e.g. F, POS, NEG'
bind:value={stringLiteralInput}
onkeydown={(e) => {
if (e.key === 'Enter' && stringLiteralInput.trim()) {
onAddLiteral(`"${stringLiteralInput.trim()}"`);
stringLiteralInput = '';
}
}}
/>
<div class="flex justify-end gap-2">
<Button
type="button"
size="sm"
disabled={!stringLiteralInput.trim()}
onclick={() => {
onAddLiteral(`"${stringLiteralInput.trim()}"`);
stringLiteralInput = '';
}}
>
Add
</Button>
</div>
</div>
</div>
</Popover.Content>
</Popover.Root>
<Popover.Root bind:open={numberPopoverOpen}>
<Popover.Trigger>
{#snippet child({ props: triggerProps })}
<Button
{...triggerProps}
type="button"
variant="outline"
class="h-auto w-auto p-2"
>
123
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-64" side="bottom" align="start">
<div>
<div class="flex flex-col gap-3">
<p class="text-sm font-semibold">Enter Number Value</p>
<Input
type="number"
placeholder='e.g. 142'
bind:value={numberLiteralInput}
</div>
{/if}
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 w-full">
<div>
<p class="text-xs font-semibold mb-1">Math Operator</p>
<div class="flex flex-wrap gap-1">
{#each mathop as op}
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
{op}
</Button>
{/each}
</div>
</div>
<div>
<p class="text-xs font-semibold mb-1">Comparison Operator</p>
<div class="flex flex-wrap gap-1">
{#each comparisonop as op}
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
{op}
</Button>
{/each}
</div>
</div>
<div>
<p class="text-xs font-semibold mb-1">Logical Operator</p>
<div class="flex flex-wrap gap-1">
{#each logicalop as op}
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
{op}
</Button>
{/each}
</div>
</div>
<div>
<p class="text-xs font-semibold mb-1">Conditional Operator</p>
<div class="flex flex-wrap gap-1">
{#each conditionalop as op}
<Button size="sm" variant="outline" onclick={() => onAddConditional(op)}>
{op}
</Button>
{/each}
</div>
</div>
<div>
<p class="text-xs font-semibold mb-1">Boolean Operator</p>
<div class="flex flex-wrap gap-1">
{#each booleanop as op}
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
{op}
</Button>
{/each}
</div>
</div>
<div>
<p class="text-xs font-semibold mb-1">Variables</p>
<div class="flex flex-wrap gap-1">
{#each availableVar as op}
<Button size="sm" variant="outline" onclick={() => onAddOperator(op)}>
{op}
</Button>
{/each}
</div>
</div>
<div>
<p class="text-xs font-semibold mb-1">Custom Values</p>
<div class="flex flex-wrap gap-1">
<Popover.Root bind:open={stringPopoverOpen}>
<Popover.Trigger>
{#snippet child({ props: triggerProps })}
<Button
{...triggerProps}
size="sm"
variant="outline"
>
"abc"
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-64" side="bottom" align="start">
<div>
<div class="flex flex-col gap-3">
<p class="text-sm font-semibold">Enter Text Value</p>
<Input
type="text"
placeholder='e.g. F, POS, NEG'
bind:value={stringLiteralInput}
onkeydown={(e) => {
if (e.key === 'Enter' && numberLiteralInput != null && !isNaN(numberLiteralInput)) {
if (e.key === 'Enter' && stringLiteralInput.trim()) {
onAddLiteral(`"${stringLiteralInput.trim()}"`);
stringLiteralInput = '';
}
}}
/>
<div class="flex justify-end gap-2">
<Button
type="button"
size="sm"
disabled={!stringLiteralInput.trim()}
onclick={() => {
onAddLiteral(`"${stringLiteralInput.trim()}"`);
stringLiteralInput = '';
}}
>
Add
</Button>
</div>
</div>
</div>
</Popover.Content>
</Popover.Root>
<Popover.Root bind:open={numberPopoverOpen}>
<Popover.Trigger>
{#snippet child({ props: triggerProps })}
<Button
{...triggerProps}
size="sm"
variant="outline"
>
123
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-64" side="bottom" align="start">
<div>
<div class="flex flex-col gap-3">
<p class="text-sm font-semibold">Enter Number Value</p>
<Input
type="number"
placeholder='e.g. 142'
bind:value={numberLiteralInput}
onkeydown={(e) => {
if (e.key === 'Enter' && numberLiteralInput != null && !isNaN(numberLiteralInput)) {
onAddLiteral(String(numberLiteralInput));
numberLiteralInput = null;
}
}}
/>
<div class="flex justify-end gap-2">
<Button
type="button"
size="sm"
disabled={numberLiteralInput == null || isNaN(numberLiteralInput)}
onclick={() => {
onAddLiteral(String(numberLiteralInput));
numberLiteralInput = null;
}
numberPopoverOpen = false;
}}
/>
<div class="flex justify-end gap-2">
<Button
type="button"
size="sm"
disabled={numberLiteralInput == null || isNaN(numberLiteralInput)}
onclick={() => {
onAddLiteral(String(numberLiteralInput));
numberLiteralInput = null;
numberPopoverOpen = false;
}}
>
Add
</Button>
</div>
</div>
>
Add
</Button>
</div>
</Popover.Content>
</Popover.Root>
</div>
</div>
<div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Logical Operators</Label>
<div class="flex flex-wrap gap-2">
{#each logicalop as op}
<Button
type="button"
variant="outline"
class="h-auto w-auto p-2"
onclick={() => onAddOperator?.(op)}
>
{op}
</Button>
{/each}
</div>
</div>
<div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Comparison Operators</Label>
<div class="flex flex-wrap gap-2">
{#each comparisonop as op}
<Button
type="button"
variant="outline"
size="icon"
onclick={() => onAddOperator?.(op)}
>
{op}
</Button>
{/each}
</div>
</div>
<div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Math Operators</Label>
<div class="flex flex-wrap gap-2">
{#each operators as op}
<Button
type="button"
variant="outline"
size="icon"
onclick={() => onAddOperator?.(op)}
>
{op}
</Button>
{/each}
</div>
</div>
</div>
</Popover.Content>
</Popover.Root>
</div>
</div>
</div>
{#if tokens.length > 0}
<div class="flex flex-col gap-2">
<Label class="uppercase tracking-wide">Preview</Label>
<div class="border-2 border-dashed border-muted-foreground/30 rounded-lg">
<pre class="font-mono text-sm bg-muted/50 p-2 rounded">{expressionString}</pre>
<div class="flex flex-col gap-2 border-t pt-4">
<div class="flex items-center justify-between">
<Label class="uppercase tracking-widest text-xs font-semibold">Expression Preview</Label>
</div>
<div class="bg-primary p-2 rounded-md border shadow-inner">
<code class="font-mono text-green-300 dark:text-green-600 text-sm">{expressionString}</code>
</div>
</div>
{/if}

View File

@ -1,210 +0,0 @@
<script>
import { getCoreRowModel, getPaginationRowModel, getFilteredRowModel } from "@tanstack/table-core";
import {
createSvelteTable,
FlexRender,
} from "$lib/components/ui/data-table/index.js";
import * as Table from "$lib/components/ui/table/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import * as Select from "$lib/components/ui/select/index.js";
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
import ChevronsRightIcon from "@lucide/svelte/icons/chevrons-right";
import ChevronsLeftIcon from "@lucide/svelte/icons/chevrons-left";
import { Input } from "$lib/components/ui/input/index.js";
let props = $props();
let pagination = $state({ pageIndex: 0, pageSize: 10 });
let columnFilters = $state([]);
let globalFilter = $state("");
let activeRowId = $state(null);
let table = createSvelteTable({
get data() {
return props.data;
},
get columns() {
return props.columns;
},
state: {
get pagination() {
return pagination;
},
get columnFilters() {
return columnFilters;
},
get globalFilter() {
return globalFilter;
},
},
onPaginationChange: (updater) => {
if (typeof updater === "function") {
pagination = updater(pagination);
} else {
pagination = updater;
}
},
onColumnFiltersChange: (updater) => {
if (typeof updater === "function") {
columnFilters = updater(columnFilters);
} else {
columnFilters = updater;
}
},
onGlobalFilterChange: (value) => {
globalFilter = value;
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});
</script>
<!-- ✅ Updated with scrollable body -->
<div class="h-full flex flex-col relative w-full">
{#if props.searchable ?? true}
<div class="flex items-center absolute top-[-2rem]">
<Input
placeholder="Filter all columns..."
value={globalFilter}
oninput={(e) => {
globalFilter = e.currentTarget.value;
}}
class="h-7 w-64 text-xs px-2"
/>
</div>
{/if}
<!-- ✅ Container with flex column -->
<div class="rounded-md border h-full flex flex-col w-full overflow-hidden">
<!-- ✅ Wrapper untuk table dengan overflow -->
<div class="flex-1 overflow-auto">
<Table.Root>
<!-- ✅ Sticky Header -->
<Table.Header class="sticky top-0 bg-background z-10">
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<Table.Row>
{#each headerGroup.headers as header (header.id)}
<Table.Head colspan={header.colSpan}>
{#if !header.isPlaceholder}
<FlexRender
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if}
</Table.Head>
{/each}
</Table.Row>
{/each}
</Table.Header>
<!-- ✅ Scrollable Body -->
<Table.Body>
{#each table.getRowModel().rows as row (row.id)}
<Table.Row
data-state={row.getIsSelected() && "selected"}
onclick={() => props.handleRowClick(row.original)}
class="cursor-pointer"
>
{#each row.getVisibleCells() as cell, i (cell.id)}
<Table.Cell class={`${i === 0 ? "border-l-4" : ""} ${i === 0 && activeRowId == row.original[props.rowIdKey] ? "border-primary" : "border-transparent"}`}>
<FlexRender
content={cell.column.columnDef.cell}
context={cell.getContext()}
/>
</Table.Cell>
{/each}
</Table.Row>
{:else}
<Table.Row>
<Table.Cell colspan={props.columns.length} class="h-24 text-center">
No results.
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
<!-- ✅ Sticky Pagination Footer -->
<div class="flex items-center justify-between p-2 border-t bg-background">
<div class="flex items-center space-x-2">
<p class="text-sm font-medium">Rows</p>
<Select.Root
allowDeselect={false}
type="single"
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<Select.Trigger class="h-7 w-[70px]">
{String(table.getState().pagination.pageSize)}
</Select.Trigger>
<Select.Content side="top">
{#each [5, 10, 15, 20, 25] as pageSize (pageSize)}
<Select.Item value={`${pageSize}`}>
{pageSize}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="flex items-center gap-2">
<div class="flex w-[100px] items-center justify-center text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of
{table.getPageCount()}
</div>
<div class="flex items-center space-x-2">
<Button
variant="outline"
class="hidden size-7 p-0 lg:flex"
onclick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span class="sr-only">Go to first page</span>
<ChevronsLeftIcon />
</Button>
<Button
variant="outline"
class="size-7 p-0"
onclick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span class="sr-only">Go to previous page</span>
<ChevronLeftIcon />
</Button>
<Button
variant="outline"
class="size-7 p-0"
onclick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span class="sr-only">Go to next page</span>
<ChevronRightIcon />
</Button>
<Button
variant="outline"
class="hidden size-7 p-0 lg:flex"
onclick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span class="sr-only">Go to last page</span>
<ChevronsRightIcon />
</Button>
</div>
</div>
</div>
</div>
</div>
<style>
/* ✅ Ensure sticky header works properly */
:global(.sticky) {
position: sticky;
top: 0;
z-index: 10;
}
</style>

View File

@ -15,7 +15,7 @@
let props = $props();
let pagination = $state({ pageIndex: 0, pageSize: 10 });
let pagination = $state({ pageIndex: 0, pageSize: 12 });
let columnFilters = $state([]);
let globalFilter = $state("");
let activeRowId = $state(null);
@ -131,7 +131,7 @@
{String(table.getState().pagination.pageSize)}
</Select.Trigger>
<Select.Content side="top">
{#each [5, 10, 15, 20, 25] as pageSize (pageSize)}
{#each [12, 24, 36, 48] as pageSize (pageSize)}
<Select.Item value={`${pageSize}`}>
{pageSize}
</Select.Item>

View File

@ -20,6 +20,7 @@
modeOpt: 'cascade',
saveEndpoint: createPatient,
editEndpoint: editPatient,
idKey: 'InternalPID',
mapToForm: (data) => ({
...data,
PatIdt_IdentifierType: data.PatIdt?.IdentifierType ?? "",