store/resources/js/components/data-table/DataTableToolbar.vue

147 lines
4.9 KiB
Vue

<script setup lang="ts">
import { ListFilter, Search } from '@lucide/vue';
import { computed } from 'vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type { DataTableFilterDef } from '@/types/data-table';
const props = defineProps<{
filterDefs?: DataTableFilterDef[];
filterValues?: Record<string, string>;
searchPlaceholder?: string;
}>();
const search = defineModel<string>('search', { default: '' });
const emit = defineEmits<{
'filter-change': [key: string, value: string];
'filters-reset': [];
}>();
const activeFilterCount = computed(() => {
if (!props.filterDefs?.length || !props.filterValues) {
return 0;
}
return props.filterDefs.filter((filter) => {
const value = props.filterValues?.[filter.key];
return value !== undefined && value !== '';
}).length;
});
function filterValue(key: string): string {
return props.filterValues?.[key] ?? '';
}
function onFilterChange(key: string, value: string): void {
emit('filter-change', key, value === 'all' ? '' : value);
}
function resetFilters(): void {
emit('filters-reset');
}
</script>
<template>
<div class="flex items-center justify-between gap-4">
<div class="relative w-full max-w-sm">
<Search class="text-muted-foreground absolute top-1/2 left-3 size-4 -translate-y-1/2" />
<Input
v-model="search"
type="search"
:placeholder="searchPlaceholder ?? 'Cari...'"
class="pl-9"
/>
</div>
<Popover v-if="filterDefs?.length">
<PopoverTrigger as-child>
<Button variant="outline" class="shrink-0">
<ListFilter class="size-4" />
Filter
<Badge
v-if="activeFilterCount > 0"
variant="secondary"
class="ml-1 size-5 rounded-full px-1 text-xs"
>
{{ activeFilterCount }}
</Badge>
</Button>
</PopoverTrigger>
<PopoverContent align="end" class="w-80 space-y-4 p-4">
<div class="space-y-1">
<h4 class="text-sm font-medium">
Filter
</h4>
<p class="text-muted-foreground text-xs">
Sesuaikan filter untuk mempersempit data.
</p>
</div>
<div
v-for="filter in filterDefs"
:key="filter.key"
class="space-y-2"
>
<Label :for="`filter-${filter.key}`">{{ filter.label }}</Label>
<Input
v-if="filter.type === 'text'"
:id="`filter-${filter.key}`"
:model-value="filterValue(filter.key)"
:placeholder="filter.placeholder ?? `Filter ${filter.label.toLowerCase()}`"
@update:model-value="onFilterChange(filter.key, String($event ?? ''))"
/>
<Select
v-else
:model-value="filterValue(filter.key) || 'all'"
@update:model-value="onFilterChange(filter.key, String($event ?? ''))"
>
<SelectTrigger :id="`filter-${filter.key}`" class="w-full">
<SelectValue :placeholder="filter.placeholder ?? 'Semua'" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
Semua
</SelectItem>
<SelectItem
v-for="option in filter.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectItem>
</SelectContent>
</Select>
</div>
<Button
v-if="activeFilterCount > 0"
variant="ghost"
size="sm"
class="w-full"
@click="resetFilters"
>
Reset Filter
</Button>
</PopoverContent>
</Popover>
</div>
</template>