mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-24 18:18:07 +07:00
229 lines
8.2 KiB
Svelte
229 lines
8.2 KiB
Svelte
<script>
|
|
import DictionaryFormRenderer from "$lib/components/reusable/form/dictionary-form-renderer.svelte";
|
|
import { Separator } from "$lib/components/ui/separator/index.js";
|
|
import * as Table from "$lib/components/ui/table/index.js";
|
|
import { Button } from "$lib/components/ui/button/index.js";
|
|
import { Badge } from "$lib/components/ui/badge/index.js";
|
|
import { buildAgeText } from "$lib/utils/ageUtils";
|
|
import PencilIcon from "@lucide/svelte/icons/pencil";
|
|
import Trash2Icon from "@lucide/svelte/icons/trash-2";
|
|
import { untrack } from "svelte";
|
|
|
|
let props = $props();
|
|
|
|
let tempTxt = $state([]);
|
|
let editingId = $state(null);
|
|
let idCounter = $state(0);
|
|
|
|
let joinFields = $state({
|
|
AgeStart: { DD: "", MM: "", YY: "" },
|
|
AgeEnd: { DD: "", MM: "", YY: "" },
|
|
});
|
|
|
|
function snapshotForm() {
|
|
const f = props.refTxtState.form;
|
|
return {
|
|
SpcType: f.SpcType ?? "",
|
|
Sex: f.Sex ?? "",
|
|
AgeStart: f.AgeStart ?? "",
|
|
AgeEnd: f.AgeEnd ?? "",
|
|
TxtRefType: f.TxtRefType ?? "",
|
|
RefTxt: f.RefTxt ?? "",
|
|
Flag: f.Flag ?? "",
|
|
Notes: f.Notes ?? "",
|
|
};
|
|
}
|
|
|
|
function resetForm() {
|
|
props.refTxtState.reset?.();
|
|
joinFields = {
|
|
AgeStart: { DD: "", MM: "", YY: "" },
|
|
AgeEnd: { DD: "", MM: "", YY: "" },
|
|
};
|
|
editingId = null;
|
|
}
|
|
|
|
function handleInsert() {
|
|
const row = { id: ++idCounter, ...snapshotForm() };
|
|
tempTxt = [...tempTxt, row];
|
|
resetForm();
|
|
}
|
|
|
|
function handleEdit(row) {
|
|
editingId = row.id;
|
|
|
|
const f = props.refTxtState.form;
|
|
f.SpcType = row.SpcType;
|
|
f.Sex = row.Sex;
|
|
f.AgeStart = row.AgeStart;
|
|
f.AgeEnd = row.AgeEnd;
|
|
f.TxtRefType = row.TxtRefType;
|
|
f.RefTxt = row.RefTxt;
|
|
f.Flag = row.Flag;
|
|
f.Notes = row.Notes;
|
|
|
|
for (const key of ["AgeStart", "AgeEnd"]) {
|
|
const val = row[key] ?? "";
|
|
const match = val.match(/(\d+)Y\s*(\d+)M\s*(\d+)D/);
|
|
joinFields[key] = match
|
|
? { YY: match[1], MM: match[2], DD: match[3] }
|
|
: { DD: "", MM: "", YY: "" };
|
|
}
|
|
}
|
|
|
|
function handleUpdate() {
|
|
tempTxt = tempTxt.map((row) =>
|
|
row.id === editingId ? { id: row.id, ...snapshotForm() } : row
|
|
);
|
|
resetForm();
|
|
}
|
|
|
|
function handleCancelEdit() {
|
|
resetForm();
|
|
}
|
|
|
|
function handleRemove(id) {
|
|
tempTxt = tempTxt.filter((row) => row.id !== id);
|
|
if (editingId === id) resetForm();
|
|
}
|
|
|
|
function getLabel(fieldKey, value) {
|
|
const opts = props.refTxtState.selectOptions[fieldKey] ?? [];
|
|
const found = opts.find((o) => o.value == value);
|
|
return found ? found.label : value;
|
|
}
|
|
|
|
function getCode(fieldKey, value) {
|
|
const opts = props.refTxtState.selectOptions[fieldKey] ?? [];
|
|
const found = opts.find((o) => o.value == value);
|
|
return found ? found.code : value;
|
|
}
|
|
|
|
const txtRefTypeBadge = (type) => ({ TEXT: "TX", VSET: "VS" }[type] ?? null);;
|
|
|
|
$effect(() => {
|
|
if (props.refType) {
|
|
props.refTxtState.form.TxtRefType = props.refType;
|
|
}
|
|
});
|
|
|
|
$effect(() => {
|
|
for (const key of ["AgeStart", "AgeEnd"]) {
|
|
props.refTxtState.form[key] =
|
|
buildAgeText(joinFields[key]);
|
|
}
|
|
});
|
|
|
|
$effect(() => {
|
|
const allColumns = props.refTxtFormFields.flatMap(
|
|
(section) => section.rows.flatMap(
|
|
(row) => row.columns ?? []
|
|
)
|
|
);
|
|
|
|
untrack(() => {
|
|
for (const col of allColumns) {
|
|
if (!col.optionsEndpoint) continue;
|
|
|
|
props.refTxtState.fetchOptions?.(
|
|
{
|
|
key: col.key,
|
|
optionsEndpoint: col.optionsEndpoint,
|
|
valueKey: col.valueKey,
|
|
labelKey: col.labelKey,
|
|
},
|
|
props.refTxtState.form
|
|
);
|
|
}
|
|
})
|
|
});
|
|
</script>
|
|
|
|
<div class="flex flex-col gap-4 w-full">
|
|
<div>
|
|
<DictionaryFormRenderer
|
|
formState={props.refTxtState}
|
|
formFields={props.refTxtFormFields}
|
|
bind:joinFields
|
|
/>
|
|
<div class="flex gap-2 mt-1 ms-2">
|
|
{#if editingId !== null}
|
|
<Button size="sm" class="cursor-pointer" onclick={handleUpdate}>
|
|
Update
|
|
</Button>
|
|
<Button size="sm" variant="outline" class="cursor-pointer" onclick={handleCancelEdit}>
|
|
Cancel
|
|
</Button>
|
|
{:else}
|
|
<Button size="sm" class="cursor-pointer" onclick={handleInsert}>
|
|
Insert
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div>
|
|
<Table.Root>
|
|
<Table.Header>
|
|
<Table.Row class="hover:bg-transparent">
|
|
<Table.Head>Specimen Type</Table.Head>
|
|
<Table.Head>Sex</Table.Head>
|
|
<Table.Head>Age Range</Table.Head>
|
|
<Table.Head>Text/ValueSet</Table.Head>
|
|
<Table.Head>Flag</Table.Head>
|
|
<Table.Head>Notes</Table.Head>
|
|
<Table.Head class="w-[80px]"></Table.Head>
|
|
</Table.Row>
|
|
</Table.Header>
|
|
<Table.Body>
|
|
{#if tempTxt.length === 0}
|
|
<Table.Row>
|
|
<Table.Cell colspan={9} class="text-center text-muted-foreground py-6">
|
|
No data. Fill the form above and click Insert.
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
{:else}
|
|
{#each tempTxt as row (row.id)}
|
|
<Table.Row
|
|
class="cursor-pointer hover:bg-muted/50"
|
|
>
|
|
<Table.Cell>{row.SpcType ? getLabel("SpcType", row.SpcType) : "-"}</Table.Cell>
|
|
<Table.Cell class="font-medium">{row.Sex ? getLabel("Sex", row.Sex) : "-"}</Table.Cell>
|
|
<Table.Cell>{row.AgeStart} – {row.AgeEnd}</Table.Cell>
|
|
<Table.Cell class="font-medium whitespace-pre-line relative pr-16">
|
|
{row.RefTxt ? row.RefTxt : "-"}
|
|
<span class="absolute top-2 right-2">
|
|
<Badge variant="outline" class="border-dashed border-primary border-2">{txtRefTypeBadge(row.TxtRefType)}</Badge>
|
|
</span>
|
|
</Table.Cell>
|
|
<Table.Cell class="font-medium">{row.Flag ? row.Flag : "-"}</Table.Cell>
|
|
<Table.Cell class="font-medium">{row.Notes ? row.Notes : "-"}</Table.Cell>
|
|
<Table.Cell>
|
|
<div class="flex gap-1">
|
|
<Button
|
|
size="icon"
|
|
variant="ghost"
|
|
class="h-7 w-7 cursor-pointer"
|
|
onclick={() => handleEdit(row)}
|
|
>
|
|
<PencilIcon class="h-3.5 w-3.5" />
|
|
</Button>
|
|
<Button
|
|
size="icon"
|
|
variant="ghost"
|
|
class="h-7 w-7 cursor-pointer"
|
|
onclick={() => handleRemove(row.id)}
|
|
>
|
|
<Trash2Icon class="h-3.5 w-3.5" />
|
|
</Button>
|
|
</div>
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
{/each}
|
|
{/if}
|
|
</Table.Body>
|
|
</Table.Root>
|
|
</div>
|
|
</div> |