1+ <script setup lang="ts">
2+ import { Input } from ' @/components/ui/input' ;
3+ import Select from ' @/components/Select.vue' ;
4+ import { Button } from ' @/components/ui/button' ;
5+ import { Search , X } from ' lucide-vue-next' ;
6+ import { ref , watch , computed } from ' vue' ;
7+ import type { FilterConfig } from ' @/types' ;
8+
9+ interface Props {
10+ searchPlaceholder? : string ;
11+ searchValue? : string ;
12+ filters? : FilterConfig [];
13+ showReset? : boolean ;
14+ }
15+
16+ const props = withDefaults (defineProps <Props >(), {
17+ searchPlaceholder: ' Search...' ,
18+ searchValue: ' ' ,
19+ filters : () => [],
20+ showReset: true ,
21+ });
22+
23+ const emit = defineEmits <{
24+ ' update:search' : [value : string ];
25+ ' update:filter' : [key : string , value : string ];
26+ ' reset' : [];
27+ }>();
28+
29+ // Local search state for immediate UI updates
30+ const localSearch = ref (props .searchValue );
31+
32+ // Local filter values to track selections
33+ const localFilterValues = ref <Record <string , string >>({});
34+
35+ // Initialize local filter values from props
36+ props .filters .forEach (filter => {
37+ if (filter .value ) {
38+ localFilterValues .value [filter .key ] = filter .value ;
39+ }
40+ });
41+
42+ // Watch for external changes (like reset)
43+ watch (() => props .searchValue , (newValue ) => {
44+ localSearch .value = newValue ;
45+ });
46+
47+ // Watch for filter value changes from props
48+ watch (() => props .filters , (newFilters ) => {
49+ newFilters .forEach (filter => {
50+ if (filter .value ) {
51+ localFilterValues .value [filter .key ] = filter .value ;
52+ } else {
53+ delete localFilterValues .value [filter .key ];
54+ }
55+ });
56+ }, { deep: true });
57+
58+ // Check if any filters are active
59+ const hasActiveFilters = computed (() => {
60+ // Check if search has value
61+ if (localSearch .value && localSearch .value .trim () !== ' ' ) {
62+ return true ;
63+ }
64+
65+ // Check if any filter dropdown has a value
66+ return Object .keys (localFilterValues .value ).length > 0 ;
67+ });
68+
69+ const handleSearchInput = (event : Event ) => {
70+ const target = event .target as HTMLInputElement ;
71+ localSearch .value = target .value ;
72+ emit (' update:search' , target .value );
73+ }
74+
75+ const handleFilterChange = (key : string , value : string ) => {
76+ if (value ) {
77+ localFilterValues .value [key ] = value ;
78+ } else {
79+ delete localFilterValues .value [key ];
80+ }
81+ emit (' update:filter' , key , value );
82+ }
83+
84+ const handleReset = () => {
85+ localSearch .value = ' ' ;
86+ localFilterValues .value = {};
87+ emit (' reset' );
88+ }
89+ </script >
90+
91+ <template >
92+ <div class =" flex flex-col gap-4 mb-4" >
93+ <div class =" flex flex-col md:flex-row gap-4" >
94+ <!-- Search Input -->
95+ <div class =" flex-1" >
96+ <div class =" relative" >
97+ <Search :size =" 18" class =" absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
98+ <Input v-model =" localSearch" @input =" handleSearchInput" :placeholder =" searchPlaceholder"
99+ class =" pl-10" />
100+ </div >
101+ </div >
102+
103+ <!-- Filter Dropdowns -->
104+ <template v-for =" filter in filters " :key =" filter .key " >
105+ <div class =" w-full md:w-48" >
106+ <Select v-model =" localFilterValues[filter.key]" :options =" filter.options"
107+ :placeholder =" filter.placeholder || `Filter by ${filter.label}`"
108+ @update:modelValue =" (value: string) => handleFilterChange(filter.key, value)" />
109+ </div >
110+ </template >
111+
112+ <!-- Reset Button (only show if filters are active) -->
113+ <Button v-if =" showReset && hasActiveFilters" variant =" outline" @click =" handleReset"
114+ class =" w-full md:w-auto" >
115+ <X :size =" 16" class =" mr-2" />
116+ Reset
117+ </Button >
118+ </div >
119+ </div >
120+ </template >
0 commit comments