mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-22 09:35:34 +07:00
bug fix + add feature
fix salah url ketika sidebar collapsed menambahkan type select pada reusable searchparam
This commit is contained in:
parent
b03c0565f1
commit
4f61832f19
@ -2,6 +2,8 @@ export function useSearch(searchFields, searchApiFunction) {
|
||||
let searchQuery = $state(initializeSearchQuery(searchFields));
|
||||
let isLoading = $state(false);
|
||||
let searchData = $state([]);
|
||||
let selectOptions = $state({});
|
||||
let loadingOptions = $state({});
|
||||
|
||||
function initializeSearchQuery(fields) {
|
||||
const query = {};
|
||||
@ -30,12 +32,38 @@ export function useSearch(searchFields, searchApiFunction) {
|
||||
searchQuery = initializeSearchQuery(searchFields);
|
||||
}
|
||||
|
||||
async function fetchOptions(field) {
|
||||
if (!field.optionsEndpoint) return;
|
||||
|
||||
if (selectOptions[field.key]?.length) return;
|
||||
|
||||
loadingOptions[field.key] = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(field.optionsEndpoint);
|
||||
const json = await response.json();
|
||||
|
||||
selectOptions[field.key] = json.data.map(item => ({
|
||||
value: item[field.valueKey ?? 'value'],
|
||||
label: item[field.labelKey ?? 'label'],
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error("Failed fetching options:", err);
|
||||
selectOptions[field.key] = [];
|
||||
} finally {
|
||||
loadingOptions[field.key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
get searchQuery() { return searchQuery; },
|
||||
set searchQuery(value) { searchQuery = value; },
|
||||
get searchData() { return searchData; },
|
||||
get isLoading() { return isLoading; },
|
||||
get loadingOptions() { return loadingOptions; },
|
||||
get selectOptions() { return selectOptions; },
|
||||
handleSearch,
|
||||
handleReset
|
||||
handleReset,
|
||||
fetchOptions,
|
||||
};
|
||||
}
|
||||
@ -16,7 +16,8 @@ export const searchFields = [
|
||||
{
|
||||
key: "LocType",
|
||||
label: "Location Type",
|
||||
type: "text"
|
||||
type: "select",
|
||||
optionsEndpoint: `https://clqms01-api.services-summit.my.id/api/valueset/location_type`,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -20,7 +20,10 @@
|
||||
</script>
|
||||
|
||||
{#snippet searchParamSnippet()}
|
||||
<ReusableSearchParam {searchFields} bind:searchQuery={search.searchQuery} onSearch={search.handleSearch} onReset={search.handleReset} isLoading={search.isLoading}/>
|
||||
<ReusableSearchParam {searchFields}
|
||||
bind:searchQuery={search.searchQuery} onSearch={search.handleSearch} onReset={search.handleReset} isLoading={search.isLoading}
|
||||
selectOptions={search.selectOptions} loadingOptions={search.loadingOptions} fetchOptions={search.fetchOptions}
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
<div
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
{/snippet}
|
||||
</Collapsible.Root>
|
||||
{:else}
|
||||
<Popover.Root open={openPopovers[index]} onOpenChange={(open) => openPopovers[index] = open}>
|
||||
<Popover.Root open={openPopovers[item.url]} onOpenChange={(open) => openPopovers[item.url] = open}>
|
||||
<Sidebar.MenuItem>
|
||||
{#snippet trigger(props)}
|
||||
<Popover.Trigger {...props}>
|
||||
@ -89,8 +89,8 @@
|
||||
</div>
|
||||
<Separator />
|
||||
{#each item.submenus || [] as submenu}
|
||||
<a href={submenu.url}
|
||||
onclick={() => openPopovers[index] = false}
|
||||
<a href={`${item.url}${submenu.url}`}
|
||||
onclick={() => openPopovers[item.url] = false}
|
||||
class="flex items-center rounded-md px-2 py-1.5 text-sm hover:bg-accent"
|
||||
class:bg-accent={$page.url.pathname === submenu.url}
|
||||
>
|
||||
@ -99,7 +99,7 @@
|
||||
{/each}
|
||||
{:else}
|
||||
<a href={item.url}
|
||||
onclick={() => openPopovers[index] = false}
|
||||
onclick={() => openPopovers[item.url] = false}
|
||||
class="flex items-center rounded-md px-2 py-1.5 text-sm font-semibold hover:bg-accent"
|
||||
class:bg-accent={$page.url.pathname === item.url}
|
||||
>
|
||||
|
||||
@ -7,10 +7,20 @@
|
||||
import * as Select from "$lib/components/ui/select/index.js";
|
||||
|
||||
let props = $props();
|
||||
|
||||
let loadedFields = $state(new Set());
|
||||
|
||||
function handleOpenSelect(field) {
|
||||
if (loadedFields.has(field.key)) return;
|
||||
|
||||
loadedFields.add(field.key);
|
||||
props.fetchOptions(field);
|
||||
}
|
||||
$inspect(props.searchQuery)
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="space-y-2">
|
||||
<div class="w-full space-y-2">
|
||||
{#each props.searchFields as field}
|
||||
{#if field.type === "text"}
|
||||
<div class="space-y-2">
|
||||
@ -22,20 +32,81 @@
|
||||
<ReusableCalendar title={field.label} bind:value={props.searchQuery[field.key]}/>
|
||||
</div>
|
||||
{:else if field.type === "select"}
|
||||
<div class="space-y-2">
|
||||
<div class="w-full space-y-2">
|
||||
<Label for={field.key}>{field.label}</Label>
|
||||
<Select.Root bind:value={props.searchQuery[field.key]}>
|
||||
<Select.Trigger id={field.key}>
|
||||
<Select.Value placeholder={field.placeholder} />
|
||||
<Select.Root type="single" bind:value={props.searchQuery[field.key]}
|
||||
onOpenChange={() => {
|
||||
props.fetchOptions(field)
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id={field.key} class="w-full flex justify-between">
|
||||
{
|
||||
props.selectOptions[field.key]?.find(
|
||||
opt => opt.value === props.searchQuery[field.key]
|
||||
)?.label ?? "Choose"
|
||||
}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each field.options as opt}
|
||||
<Select.Content class="w-full">
|
||||
<Select.Item value="">- None -</Select.Item>
|
||||
{#each props.selectOptions[field.key] as opt}
|
||||
<Select.Item value={opt.value}>
|
||||
{opt.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<!-- === -->
|
||||
<!-- <Select.Root type="single" bind:value={props.searchQuery[field.key]}
|
||||
onOpenChange={(open) => {
|
||||
if (open && optionsEndpoint) {
|
||||
formState.fetchOptions(
|
||||
{ key, optionsEndpoint, dependsOn, endpointParamKey, valueKey, labelKey },
|
||||
formState.form
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select.Trigger class="w-full truncate">
|
||||
{selectedLabel}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#if formState.loadingOptions[key]}
|
||||
<Select.Item disabled value="loading">Loading...</Select.Item>
|
||||
{:else}
|
||||
{#if !required}
|
||||
<Select.Item value="">- None -</Select.Item>
|
||||
{/if}
|
||||
{#each selectOptions as option}
|
||||
<Select.Item value={option.value}>
|
||||
{option.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root> -->
|
||||
<!-- === -->
|
||||
<!-- <Select.Root bind:value={props.searchQuery[field.key]}>
|
||||
<Select.Trigger id={field.key}>
|
||||
<Select.Value placeholder={`Select ${field.label}`} />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#if props.loadingOptions[field.key]}
|
||||
<Select.Item value="" disabled>
|
||||
Loading...
|
||||
</Select.Item>
|
||||
{:else}
|
||||
<Select.Item value="">
|
||||
All {field.label}
|
||||
</Select.Item>
|
||||
|
||||
{#each props.selectOptions[field.key] || [] as option}
|
||||
<Select.Item value={option.value}>
|
||||
{option.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root> -->
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user