mirror of
https://github.com/faiztyanirh/clqms-shadcn-v1.git
synced 2026-04-27 03:16:33 +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 searchQuery = $state(initializeSearchQuery(searchFields));
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let searchData = $state([]);
|
let searchData = $state([]);
|
||||||
|
let selectOptions = $state({});
|
||||||
|
let loadingOptions = $state({});
|
||||||
|
|
||||||
function initializeSearchQuery(fields) {
|
function initializeSearchQuery(fields) {
|
||||||
const query = {};
|
const query = {};
|
||||||
@ -30,12 +32,38 @@ export function useSearch(searchFields, searchApiFunction) {
|
|||||||
searchQuery = initializeSearchQuery(searchFields);
|
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 {
|
return {
|
||||||
get searchQuery() { return searchQuery; },
|
get searchQuery() { return searchQuery; },
|
||||||
set searchQuery(value) { searchQuery = value; },
|
set searchQuery(value) { searchQuery = value; },
|
||||||
get searchData() { return searchData; },
|
get searchData() { return searchData; },
|
||||||
get isLoading() { return isLoading; },
|
get isLoading() { return isLoading; },
|
||||||
|
get loadingOptions() { return loadingOptions; },
|
||||||
|
get selectOptions() { return selectOptions; },
|
||||||
handleSearch,
|
handleSearch,
|
||||||
handleReset
|
handleReset,
|
||||||
|
fetchOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -16,7 +16,8 @@ export const searchFields = [
|
|||||||
{
|
{
|
||||||
key: "LocType",
|
key: "LocType",
|
||||||
label: "Location Type",
|
label: "Location Type",
|
||||||
type: "text"
|
type: "select",
|
||||||
|
optionsEndpoint: `https://clqms01-api.services-summit.my.id/api/valueset/location_type`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet searchParamSnippet()}
|
{#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}
|
{/snippet}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -66,7 +66,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</Collapsible.Root>
|
</Collapsible.Root>
|
||||||
{:else}
|
{: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>
|
<Sidebar.MenuItem>
|
||||||
{#snippet trigger(props)}
|
{#snippet trigger(props)}
|
||||||
<Popover.Trigger {...props}>
|
<Popover.Trigger {...props}>
|
||||||
@ -89,8 +89,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
{#each item.submenus || [] as submenu}
|
{#each item.submenus || [] as submenu}
|
||||||
<a href={submenu.url}
|
<a href={`${item.url}${submenu.url}`}
|
||||||
onclick={() => openPopovers[index] = false}
|
onclick={() => openPopovers[item.url] = false}
|
||||||
class="flex items-center rounded-md px-2 py-1.5 text-sm hover:bg-accent"
|
class="flex items-center rounded-md px-2 py-1.5 text-sm hover:bg-accent"
|
||||||
class:bg-accent={$page.url.pathname === submenu.url}
|
class:bg-accent={$page.url.pathname === submenu.url}
|
||||||
>
|
>
|
||||||
@ -99,7 +99,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<a href={item.url}
|
<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="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}
|
class:bg-accent={$page.url.pathname === item.url}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -7,10 +7,20 @@
|
|||||||
import * as Select from "$lib/components/ui/select/index.js";
|
import * as Select from "$lib/components/ui/select/index.js";
|
||||||
|
|
||||||
let props = $props();
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="space-y-2">
|
<div class="w-full space-y-2">
|
||||||
{#each props.searchFields as field}
|
{#each props.searchFields as field}
|
||||||
{#if field.type === "text"}
|
{#if field.type === "text"}
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
@ -22,20 +32,81 @@
|
|||||||
<ReusableCalendar title={field.label} bind:value={props.searchQuery[field.key]}/>
|
<ReusableCalendar title={field.label} bind:value={props.searchQuery[field.key]}/>
|
||||||
</div>
|
</div>
|
||||||
{:else if field.type === "select"}
|
{:else if field.type === "select"}
|
||||||
<div class="space-y-2">
|
<div class="w-full space-y-2">
|
||||||
<Label for={field.key}>{field.label}</Label>
|
<Label for={field.key}>{field.label}</Label>
|
||||||
<Select.Root bind:value={props.searchQuery[field.key]}>
|
<Select.Root type="single" bind:value={props.searchQuery[field.key]}
|
||||||
<Select.Trigger id={field.key}>
|
onOpenChange={() => {
|
||||||
<Select.Value placeholder={field.placeholder} />
|
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.Trigger>
|
||||||
<Select.Content>
|
<Select.Content class="w-full">
|
||||||
{#each field.options as opt}
|
<Select.Item value="">- None -</Select.Item>
|
||||||
|
{#each props.selectOptions[field.key] as opt}
|
||||||
<Select.Item value={opt.value}>
|
<Select.Item value={opt.value}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
{/each}
|
{/each}
|
||||||
</Select.Content>
|
</Select.Content>
|
||||||
</Select.Root>
|
</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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user