fix ubah url parameter dari parent jadi ProvinceID
fix tampilan input bisa fullWidth atau tidak dari form config
This commit is contained in:
faiztyanirh 2026-02-16 13:08:26 +07:00
parent 8f9e9ddffd
commit 671a360c4c
11 changed files with 270 additions and 19 deletions

View File

@ -29,6 +29,7 @@ const optionsMode = {
cascade: async (field, selectOptions, loadingOptions, form, lastFetched) => {
const parentValue = field.dependsOn ? form?.[field.dependsOn] : null;
// console.log(parentValue);
// If has dependency and parent changed, or not fetched yet
if (field.dependsOn) {

View 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 getAccounts(searchQuery) {
return await searchWithParams(API.ACCOUNT, searchQuery)
}
export async function getAccount(searchQuery) {
return await getById(API.ACCOUNT, searchQuery)
}
export async function createAccount(newAccountForm) {
return await create(API.ACCOUNT, newAccountForm)
}
export async function editAccount(editAccountForm) {
return await update(API.ACCOUNT, editAccountForm)
}

View File

@ -38,7 +38,13 @@ export const containerFormFields = [
optionsEndpoint: `${API.BASE_URL}${API.SITE}`,
valueKey: "SiteID",
labelKey: (item) => `${item.SiteCode} - ${item.SiteName}`,
fullWidth: false,
},
]
},
{
type: "row",
columns: [
{
key: "ConCode",
label: "Container Code",
@ -46,18 +52,18 @@ export const containerFormFields = [
type: "text",
validateOn: ["input"]
},
{
key: "ConName",
label: "Container Name",
required: true,
type: "text",
validateOn: ["input"]
},
]
},
{
type: "row",
columns: [
{
key: "ConName",
label: "Container Name",
required: true,
type: "text",
validateOn: ["input"]
},
{
key: "ConDesc",
label: "Description",
@ -99,6 +105,7 @@ export const containerFormFields = [
required: false,
type: "select",
optionsEndpoint: `${API.BASE_URL}${API.VALUESET}/additive`,
fullWidth: false
},
]
}

View File

@ -112,7 +112,7 @@ export const locationFormFields = [
type: "select",
optionsEndpoint: `${API.BASE_URL}${API.CITY}`,
dependsOn: "Province",
endpointParamKey: "Parent"
endpointParamKey: "ProvinceID"
},
{
key: "Street2",
@ -130,7 +130,8 @@ export const locationFormFields = [
label: "ZIP",
required: false,
type: "text",
validateOn: ["input"]
validateOn: ["input"],
fullWidth: false
},
]
},

View File

@ -44,10 +44,10 @@ export const patientInitialForm = {
Citizenship: "",
Street_1: "",
City: "",
CityID: "",
// CityID: "",
Street_2: "",
Province: "",
ProvinceID: "",
// ProvinceID: "",
Street_3: "",
ZIP: "",
Country: "",
@ -205,7 +205,7 @@ export const patientFormFields = [
columns: [
{ key: "Street_1", label: "Street 1", required: false, type: "text" },
{
key: "ProvinceID",
key: "Province",
label: "Province",
required: false,
type: "select",
@ -218,13 +218,13 @@ export const patientFormFields = [
columns: [
{ key: "Street_2", label: "Street 2", required: false, type: "text" },
{
key: "CityID",
key: "City",
label: "City",
required: false,
type: "select",
optionsEndpoint: `${API.BASE_URL}${API.CITY}`,
dependsOn: "ProvinceID",
endpointParamKey: "Parent"
dependsOn: "Province",
endpointParamKey: "ProvinceID"
}
]
},

View File

@ -63,6 +63,7 @@
disabled: formState.isSaving.current
}
];
// $inspect(formState.selectOptions)
</script>
<FormPageContainer title="Create Patient" {primaryAction} {secondaryActions} {actions}>

View File

@ -84,8 +84,8 @@
{
key: "City",
optionsEndpoint: `${API.BASE_URL}${API.CITY}`,
dependsOn: "ProvinceID",
endpointParamKey: "Parent"
dependsOn: "Province",
endpointParamKey: "ProvinceID"
},
formState.form
);

View File

@ -204,19 +204,31 @@
{/if}
{#each group.rows as row}
<div
<!-- <div
class="grid grid-cols-1 space-y-2 gap-6 md:gap-4"
class:md:grid-cols-1={row.columns.length === 1}
class:md:grid-cols-2={row.columns.length === 2}
class:md:grid-cols-3={row.columns.length === 3}
> -->
<div
class="grid grid-cols-1 space-y-2 gap-6 md:gap-4"
class:md:grid-cols-1={row.columns.length === 1 && row.columns[0].fullWidth !== false}
class:md:grid-cols-2={row.columns.length === 2 || (row.columns.length === 1 && row.columns[0].fullWidth === false)}
class:md:grid-cols-3={row.columns.length === 3}
>
{#each row.columns as col}
{#if col.type === "group"}
<div
<!-- <div
class="grid grid-cols-1 gap-6 md:gap-2"
class:md:grid-cols-1={col.columns.length === 1}
class:md:grid-cols-2={col.columns.length === 2}
class:md:grid-cols-3={col.columns.length === 3}
> -->
<div
class="grid grid-cols-1 gap-6 md:gap-2"
class:md:grid-cols-1={col.columns.length === 1 && col.columns[0].fullWidth !== false}
class:md:grid-cols-2={col.columns.length === 2 || (col.columns.length === 1 && col.columns[0].fullWidth === false)}
class:md:grid-cols-3={col.columns.length === 3}
>
{#each col.columns as child}
{@render Fieldset(child)}

View File

@ -0,0 +1,210 @@
<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

@ -0,0 +1 @@
acc