1'use client'
2
3import * as React from 'react'
4import { ChevronDown } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import { Checkbox } from '@/components/ui/checkbox'
8import { Input } from '@/components/ui/input'
9import { Label } from '@/components/ui/label'
10import {
11 Popover,
12 PopoverContent,
13 PopoverTrigger,
14} from '@/components/ui/popover'
15import {
16 Tabs,
17 TabsContent,
18 TabsList,
19 TabsTrigger,
20} from '@/components/ui/tabs'
21
22type Filters = {
23 category: string[]
24 availableDate: string
25 instant: boolean
26 experience: string[]
27}
28
29const DEFAULT_FILTERS: Filters = {
30 category: [],
31 availableDate: '',
32 instant: false,
33 experience: [],
34}
35
36export function FilterWithTabs() {
37 const [filters, setFilters] = React.useState<Filters>(DEFAULT_FILTERS)
38 const [isOpen, setIsOpen] = React.useState(false)
39
40 const toggleArrayValue = (
41 key: 'category' | 'experience',
42 value: string,
43 ) => {
44 setFilters((prev) => ({
45 ...prev,
46 [key]: prev[key].includes(value)
47 ? prev[key].filter((v) => v !== value)
48 : [...prev[key], value],
49 }))
50 }
51
52 const handleApply = () => {
53 setIsOpen(false)
54 }
55
56 const handleReset = () => {
57 setFilters(DEFAULT_FILTERS)
58 }
59 return (
60 <Popover open={isOpen} onOpenChange={setIsOpen}>
61 <PopoverTrigger asChild>
62 <Button className="gap-2">
63 Filter products
64 <ChevronDown className="h-4 w-4" />
65 </Button>
66 </PopoverTrigger>
67
68 <PopoverContent className="w-80 rounded-lg bg-slate-800 p-4">
69 <Tabs defaultValue="category">
70 <TabsList className="mb-2 grid grid-cols-3">
71 <TabsTrigger value="category">Category</TabsTrigger>
72 <TabsTrigger value="availability">
73 Availability
74 </TabsTrigger>
75 <TabsTrigger value="experience">Experience</TabsTrigger>
76 </TabsList>
77
78 <TabsContent value="category">
79 <div className="space-y-2">
80 <Label className="text-sm">Choose Type</Label>
81
82 <div className="grid grid-cols-2 gap-2">
83 {[
84 'Electronics',
85 'Fashion',
86 'Home',
87 'Beauty',
88 ].map((item) => (
89 <Button
90 key={item}
91 variant={
92 filters.category.includes(item)
93 ? 'primary'
94 : 'secondary'
95 }
96 className="justify-start"
97 onClick={() =>
98 toggleArrayValue('category', item)
99 }
100 >
101 {item}
102 </Button>
103 ))}
104 </div>
105 </div>
106 </TabsContent>
107
108 <TabsContent value="availability" className="space-y-4">
109 <div className="space-y-2">
110 <Label>Available On</Label>
111 <Input
112 type="date"
113 placeholder="Select date"
114 value={filters.availableDate}
115 onChange={(e) =>
116 setFilters((prev) => ({
117 ...prev,
118 availableDate: e.target.value,
119 }))
120 }
121 />
122 </div>
123
124 <div className="flex items-center gap-2">
125 <Checkbox
126 id="instant"
127 checked={filters.instant}
128 onCheckedChange={(v) =>
129 setFilters((prev) => ({
130 ...prev,
131 instant: Boolean(v),
132 }))
133 }
134 />
135 <Label htmlFor="instant">Instant access only</Label>
136 </div>
137 </TabsContent>
138
139 <TabsContent value="experience">
140 <div className="space-y-2">
141 <Label className="text-sm">Experience Level</Label>
142
143 <div className="space-y-2">
144 {[
145 'Beginner Friendly',
146 'Intermediate',
147 'Expert',
148 ].map((level) => (
149 <div
150 key={level}
151 className="flex items-center gap-2"
152 >
153 <Checkbox
154 checked={filters.experience.includes(
155 level,
156 )}
157 onCheckedChange={() =>
158 toggleArrayValue(
159 'experience',
160 level,
161 )
162 }
163 />
164 <Label htmlFor={level}>{level}</Label>
165 </div>
166 ))}
167 </div>
168 </div>
169 </TabsContent>
170 </Tabs>
171 <div className="border-border mt-3 flex justify-end gap-2 border-t pt-2">
172 <Button variant="ghost" onClick={handleReset}>
173 Reset
174 </Button>
175 <Button onClick={handleApply}>Apply</Button>
176 </div>
177 </PopoverContent>
178 </Popover>
179 )
180}
1811'use client'
2
3import * as React from 'react'
4import { ChevronDown } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import { Checkbox } from '@/components/ui/checkbox'
8import { Input } from '@/components/ui/input'
9import { Label } from '@/components/ui/label'
10import {
11 Popover,
12 PopoverContent,
13 PopoverTrigger,
14} from '@/components/ui/popover'
15import {
16 Tabs,
17 TabsContent,
18 TabsList,
19 TabsTrigger,
20} from '@/components/ui/tabs'
21
22type Filters = {
23 category: string[]
24 availableDate: string
25 instant: boolean
26 experience: string[]
27}
28
29const DEFAULT_FILTERS: Filters = {
30 category: [],
31 availableDate: '',
32 instant: false,
33 experience: [],
34}
35
36export function FilterWithTabs() {
37 const [filters, setFilters] = React.useState<Filters>(DEFAULT_FILTERS)
38 const [isOpen, setIsOpen] = React.useState(false)
39
40 const toggleArrayValue = (
41 key: 'category' | 'experience',
42 value: string,
43 ) => {
44 setFilters((prev) => ({
45 ...prev,
46 [key]: prev[key].includes(value)
47 ? prev[key].filter((v) => v !== value)
48 : [...prev[key], value],
49 }))
50 }
51
52 const handleApply = () => {
53 setIsOpen(false)
54 }
55
56 const handleReset = () => {
57 setFilters(DEFAULT_FILTERS)
58 }
59 return (
60 <Popover open={isOpen} onOpenChange={setIsOpen}>
61 <PopoverTrigger asChild>
62 <Button className="gap-2">
63 Filter products
64 <ChevronDown className="h-4 w-4" />
65 </Button>
66 </PopoverTrigger>
67
68 <PopoverContent className="w-80 rounded-lg bg-slate-800 p-4">
69 <Tabs defaultValue="category">
70 <TabsList className="mb-2 grid grid-cols-3">
71 <TabsTrigger value="category">Category</TabsTrigger>
72 <TabsTrigger value="availability">
73 Availability
74 </TabsTrigger>
75 <TabsTrigger value="experience">Experience</TabsTrigger>
76 </TabsList>
77
78 <TabsContent value="category">
79 <div className="space-y-2">
80 <Label className="text-sm">Choose Type</Label>
81
82 <div className="grid grid-cols-2 gap-2">
83 {[
84 'Electronics',
85 'Fashion',
86 'Home',
87 'Beauty',
88 ].map((item) => (
89 <Button
90 key={item}
91 variant={
92 filters.category.includes(item)
93 ? 'primary'
94 : 'secondary'
95 }
96 className="justify-start"
97 onClick={() =>
98 toggleArrayValue('category', item)
99 }
100 >
101 {item}
102 </Button>
103 ))}
104 </div>
105 </div>
106 </TabsContent>
107
108 <TabsContent value="availability" className="space-y-4">
109 <div className="space-y-2">
110 <Label>Available On</Label>
111 <Input
112 type="date"
113 placeholder="Select date"
114 value={filters.availableDate}
115 onChange={(e) =>
116 setFilters((prev) => ({
117 ...prev,
118 availableDate: e.target.value,
119 }))
120 }
121 />
122 </div>
123
124 <div className="flex items-center gap-2">
125 <Checkbox
126 id="instant"
127 checked={filters.instant}
128 onCheckedChange={(v) =>
129 setFilters((prev) => ({
130 ...prev,
131 instant: Boolean(v),
132 }))
133 }
134 />
135 <Label htmlFor="instant">Instant access only</Label>
136 </div>
137 </TabsContent>
138
139 <TabsContent value="experience">
140 <div className="space-y-2">
141 <Label className="text-sm">Experience Level</Label>
142
143 <div className="space-y-2">
144 {[
145 'Beginner Friendly',
146 'Intermediate',
147 'Expert',
148 ].map((level) => (
149 <div
150 key={level}
151 className="flex items-center gap-2"
152 >
153 <Checkbox
154 checked={filters.experience.includes(
155 level,
156 )}
157 onCheckedChange={() =>
158 toggleArrayValue(
159 'experience',
160 level,
161 )
162 }
163 />
164 <Label htmlFor={level}>{level}</Label>
165 </div>
166 ))}
167 </div>
168 </div>
169 </TabsContent>
170 </Tabs>
171 <div className="border-border mt-3 flex justify-end gap-2 border-t pt-2">
172 <Button variant="ghost" onClick={handleReset}>
173 Reset
174 </Button>
175 <Button onClick={handleApply}>Apply</Button>
176 </div>
177 </PopoverContent>
178 </Popover>
179 )
180}
1811'use client'
2
3import * as React from 'react'
4import { Search } from 'lucide-react'
5
6import {
7 Accordion,
8 AccordionContent,
9 AccordionItem,
10 AccordionTrigger,
11} from '@/components/ui/accordion'
12import { Badge } from '@/components/ui/badge'
13import { Button } from '@/components/ui/button'
14import { Checkbox } from '@/components/ui/checkbox'
15import { Input } from '@/components/ui/input'
16import { Label } from '@/components/ui/label'
17import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
18import { Slider } from '@/components/ui/slider'
19
20type Filters = {
21 keyword: string
22 status: string
23 range: number[]
24 options: string[]
25}
26
27const DEFAULT_FILTERS: Filters = {
28 keyword: '',
29 status: '',
30 range: [20, 80],
31 options: [],
32}
33
34export function Filter02() {
35 const [filters, setFilters] = React.useState<Filters>(DEFAULT_FILTERS)
36
37 const toggleOption = (value: string) => {
38 setFilters((prev) => ({
39 ...prev,
40 options: prev.options.includes(value)
41 ? prev.options.filter((v) => v !== value)
42 : [...prev.options, value],
43 }))
44 }
45
46 const handleApply = () => {
47 localStorage.setItem('filters_v3', JSON.stringify(filters))
48 }
49
50 const handleReset = () => {
51 setFilters(DEFAULT_FILTERS)
52 localStorage.removeItem('filters_v3')
53 }
54
55 return (
56 <div className="w-full p-4">
57 <div className="mb-3 flex items-center justify-between">
58 <Badge>Filter Product</Badge>
59 </div>
60
61 <div className="relative mb-3">
62 <Input
63 placeholder="Search by name"
64 className="pl-9"
65 iconLeft={<Search className="h-4 w-4 text-slate-400" />}
66 value={filters.keyword}
67 onChange={(e) =>
68 setFilters((prev) => ({
69 ...prev,
70 keyword: e.target.value,
71 }))
72 }
73 />
74 </div>
75
76 <Accordion
77 type="single"
78 collapsible
79 defaultValue="status"
80 className="space-y-1"
81 >
82 <AccordionItem value="status">
83 <AccordionTrigger>Status</AccordionTrigger>
84 <AccordionContent>
85 <RadioGroup
86 value={filters.status}
87 onValueChange={(value) =>
88 setFilters((prev) => ({
89 ...prev,
90 status: value,
91 }))
92 }
93 className="space-y-1"
94 >
95 {['New', 'Used', 'Refurbished'].map((item) => (
96 <div
97 key={item}
98 className="flex items-center gap-2"
99 >
100 <RadioGroupItem value={item} />
101 <Label>{item}</Label>
102 </div>
103 ))}
104 </RadioGroup>
105 </AccordionContent>
106 </AccordionItem>
107
108 <AccordionItem value="range">
109 <AccordionTrigger>Range</AccordionTrigger>
110 <AccordionContent className="space-y-3">
111 <Slider
112 value={filters.range}
113 onValueChange={(value) =>
114 setFilters((prev) => ({
115 ...prev,
116 range: value,
117 }))
118 }
119 min={0}
120 max={100}
121 step={1}
122 />
123 <div className="text-sm text-slate-400">
124 {filters.range[0]} – {filters.range[1]}
125 </div>
126 </AccordionContent>
127 </AccordionItem>
128
129 <AccordionItem value="options">
130 <AccordionTrigger>Options</AccordionTrigger>
131 <AccordionContent className="grid grid-cols-3 gap-2">
132 {[
133 'Free delivery',
134 'Premium support',
135 'Extended warranty',
136 'Instant activation',
137 'Auto renewal',
138 ].map((option) => (
139 <div
140 key={option}
141 className="flex items-center gap-2"
142 >
143 <Checkbox
144 checked={filters.options.includes(option)}
145 onCheckedChange={() => toggleOption(option)}
146 />
147 <Label>{option}</Label>
148 </div>
149 ))}
150 </AccordionContent>
151 </AccordionItem>
152 </Accordion>
153
154 <div className="flex justify-end gap-2 pt-3">
155 <Button variant="ghost" onClick={handleReset}>
156 Reset
157 </Button>
158 <Button onClick={handleApply}>Apply</Button>
159 </div>
160 </div>
161 )
162}
1631'use client'
2
3import * as React from 'react'
4import { Search } from 'lucide-react'
5
6import {
7 Accordion,
8 AccordionContent,
9 AccordionItem,
10 AccordionTrigger,
11} from '@/components/ui/accordion'
12import { Badge } from '@/components/ui/badge'
13import { Button } from '@/components/ui/button'
14import { Checkbox } from '@/components/ui/checkbox'
15import { Input } from '@/components/ui/input'
16import { Label } from '@/components/ui/label'
17import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
18import { Slider } from '@/components/ui/slider'
19
20type Filters = {
21 keyword: string
22 status: string
23 range: number[]
24 options: string[]
25}
26
27const DEFAULT_FILTERS: Filters = {
28 keyword: '',
29 status: '',
30 range: [20, 80],
31 options: [],
32}
33
34export function Filter02() {
35 const [filters, setFilters] = React.useState<Filters>(DEFAULT_FILTERS)
36
37 const toggleOption = (value: string) => {
38 setFilters((prev) => ({
39 ...prev,
40 options: prev.options.includes(value)
41 ? prev.options.filter((v) => v !== value)
42 : [...prev.options, value],
43 }))
44 }
45
46 const handleApply = () => {
47 localStorage.setItem('filters_v3', JSON.stringify(filters))
48 }
49
50 const handleReset = () => {
51 setFilters(DEFAULT_FILTERS)
52 localStorage.removeItem('filters_v3')
53 }
54
55 return (
56 <div className="w-full p-4">
57 <div className="mb-3 flex items-center justify-between">
58 <Badge>Filter Product</Badge>
59 </div>
60
61 <div className="relative mb-3">
62 <Input
63 placeholder="Search by name"
64 className="pl-9"
65 iconLeft={<Search className="h-4 w-4 text-slate-400" />}
66 value={filters.keyword}
67 onChange={(e) =>
68 setFilters((prev) => ({
69 ...prev,
70 keyword: e.target.value,
71 }))
72 }
73 />
74 </div>
75
76 <Accordion
77 type="single"
78 collapsible
79 defaultValue="status"
80 className="space-y-1"
81 >
82 <AccordionItem value="status">
83 <AccordionTrigger>Status</AccordionTrigger>
84 <AccordionContent>
85 <RadioGroup
86 value={filters.status}
87 onValueChange={(value) =>
88 setFilters((prev) => ({
89 ...prev,
90 status: value,
91 }))
92 }
93 className="space-y-1"
94 >
95 {['New', 'Used', 'Refurbished'].map((item) => (
96 <div
97 key={item}
98 className="flex items-center gap-2"
99 >
100 <RadioGroupItem value={item} />
101 <Label>{item}</Label>
102 </div>
103 ))}
104 </RadioGroup>
105 </AccordionContent>
106 </AccordionItem>
107
108 <AccordionItem value="range">
109 <AccordionTrigger>Range</AccordionTrigger>
110 <AccordionContent className="space-y-3">
111 <Slider
112 value={filters.range}
113 onValueChange={(value) =>
114 setFilters((prev) => ({
115 ...prev,
116 range: value,
117 }))
118 }
119 min={0}
120 max={100}
121 step={1}
122 />
123 <div className="text-sm text-slate-400">
124 {filters.range[0]} – {filters.range[1]}
125 </div>
126 </AccordionContent>
127 </AccordionItem>
128
129 <AccordionItem value="options">
130 <AccordionTrigger>Options</AccordionTrigger>
131 <AccordionContent className="grid grid-cols-3 gap-2">
132 {[
133 'Free delivery',
134 'Premium support',
135 'Extended warranty',
136 'Instant activation',
137 'Auto renewal',
138 ].map((option) => (
139 <div
140 key={option}
141 className="flex items-center gap-2"
142 >
143 <Checkbox
144 checked={filters.options.includes(option)}
145 onCheckedChange={() => toggleOption(option)}
146 />
147 <Label>{option}</Label>
148 </div>
149 ))}
150 </AccordionContent>
151 </AccordionItem>
152 </Accordion>
153
154 <div className="flex justify-end gap-2 pt-3">
155 <Button variant="ghost" onClick={handleReset}>
156 Reset
157 </Button>
158 <Button onClick={handleApply}>Apply</Button>
159 </div>
160 </div>
161 )
162}
1631'use client'
2
3import * as React from 'react'
4import { ChevronDown } from 'lucide-react'
5
6import { cn } from '@/lib/utils'
7import { Button } from '@/components/ui/button'
8import { Label } from '@/components/ui/label'
9import {
10 Popover,
11 PopoverContent,
12 PopoverTrigger,
13} from '@/components/ui/popover'
14import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
15
16type PriorityKey = 'all' | 'critical' | 'high' | 'medium' | 'low'
17
18const PRIORITIES: {
19 key: PriorityKey
20 label: string
21 count: number
22 color: string
23}[] = [
24 { key: 'all', label: 'All Priorities', count: 128, color: 'bg-gray' },
25 {
26 key: 'critical',
27 label: 'Critical',
28 count: 14,
29 color: 'bg-danger',
30 },
31 { key: 'high', label: 'High', count: 36, color: 'bg-warning' },
32 { key: 'medium', label: 'Medium', count: 52, color: 'bg-blue' },
33 { key: 'low', label: 'Low', count: 26, color: 'bg-success' },
34]
35
36const PRIORITY_COLOR_MAP: Record<PriorityKey, string> = {
37 all: 'bg-gray',
38 critical: 'bg-danger',
39 high: 'bg-warning',
40 medium: 'bg-blue',
41 low: 'bg-success',
42}
43
44export function StatusFilterRadio() {
45 const [open, setOpen] = React.useState(false)
46 const [value, setValue] = React.useState<PriorityKey>('all')
47 const [appliedValue, setAppliedValue] = React.useState<PriorityKey>('all')
48
49 const handleApply = () => {
50 setAppliedValue(value)
51 setOpen(false)
52 }
53
54 const handleReset = () => {
55 setAppliedValue('all')
56 setValue('all')
57 }
58
59 return (
60 <Popover open={open} onOpenChange={setOpen}>
61 <PopoverTrigger asChild>
62 <Button className="gap-2" variant="outline">
63 <div className="relative ml-auto flex h-2.5 w-2.5">
64 <span
65 className={cn(
66 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75',
67 PRIORITY_COLOR_MAP[appliedValue],
68 )}
69 ></span>
70 <span
71 className={cn(
72 'relative inline-flex h-2.5 w-2.5 rounded-full',
73 PRIORITY_COLOR_MAP[appliedValue],
74 )}
75 ></span>
76 </div>
77 Filter by priority
78 <ChevronDown className="h-4 w-4" />
79 </Button>
80 </PopoverTrigger>
81
82 <PopoverContent className="rounded-lg">
83 <RadioGroup
84 value={value}
85 onValueChange={(v) => setValue(v as PriorityKey)}
86 >
87 {PRIORITIES.map((priority) => (
88 <div
89 key={priority.key}
90 className="flex items-center justify-between rounded-md hover:bg-slate-700"
91 >
92 <div className="flex items-center gap-2">
93 <RadioGroupItem
94 value={priority.key}
95 id={priority.key}
96 />
97 <span
98 className={`h-2.5 w-2.5 rounded-full ${priority.color}`}
99 />
100 <Label
101 htmlFor={priority.key}
102 className="cursor-pointer text-sm"
103 >
104 {priority.label}
105 </Label>
106 </div>
107 <span className="text-xs text-slate-300">
108 {priority.count}
109 </span>
110 </div>
111 ))}
112 </RadioGroup>
113
114 <div className="border-border mt-3 flex justify-end gap-2 border-t pt-2">
115 <Button variant="ghost" onClick={handleReset}>
116 Reset
117 </Button>
118 <Button onClick={handleApply}>Apply</Button>
119 </div>
120 </PopoverContent>
121 </Popover>
122 )
123}
1241'use client'
2
3import * as React from 'react'
4import { ChevronDown } from 'lucide-react'
5
6import { cn } from '@/lib/utils'
7import { Button } from '@/components/ui/button'
8import { Label } from '@/components/ui/label'
9import {
10 Popover,
11 PopoverContent,
12 PopoverTrigger,
13} from '@/components/ui/popover'
14import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
15
16type PriorityKey = 'all' | 'critical' | 'high' | 'medium' | 'low'
17
18const PRIORITIES: {
19 key: PriorityKey
20 label: string
21 count: number
22 color: string
23}[] = [
24 { key: 'all', label: 'All Priorities', count: 128, color: 'bg-gray' },
25 {
26 key: 'critical',
27 label: 'Critical',
28 count: 14,
29 color: 'bg-danger',
30 },
31 { key: 'high', label: 'High', count: 36, color: 'bg-warning' },
32 { key: 'medium', label: 'Medium', count: 52, color: 'bg-blue' },
33 { key: 'low', label: 'Low', count: 26, color: 'bg-success' },
34]
35
36const PRIORITY_COLOR_MAP: Record<PriorityKey, string> = {
37 all: 'bg-gray',
38 critical: 'bg-danger',
39 high: 'bg-warning',
40 medium: 'bg-blue',
41 low: 'bg-success',
42}
43
44export function StatusFilterRadio() {
45 const [open, setOpen] = React.useState(false)
46 const [value, setValue] = React.useState<PriorityKey>('all')
47 const [appliedValue, setAppliedValue] = React.useState<PriorityKey>('all')
48
49 const handleApply = () => {
50 setAppliedValue(value)
51 setOpen(false)
52 }
53
54 const handleReset = () => {
55 setAppliedValue('all')
56 setValue('all')
57 }
58
59 return (
60 <Popover open={open} onOpenChange={setOpen}>
61 <PopoverTrigger asChild>
62 <Button className="gap-2" variant="outline">
63 <div className="relative ml-auto flex h-2.5 w-2.5">
64 <span
65 className={cn(
66 'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75',
67 PRIORITY_COLOR_MAP[appliedValue],
68 )}
69 ></span>
70 <span
71 className={cn(
72 'relative inline-flex h-2.5 w-2.5 rounded-full',
73 PRIORITY_COLOR_MAP[appliedValue],
74 )}
75 ></span>
76 </div>
77 Filter by priority
78 <ChevronDown className="h-4 w-4" />
79 </Button>
80 </PopoverTrigger>
81
82 <PopoverContent className="rounded-lg">
83 <RadioGroup
84 value={value}
85 onValueChange={(v) => setValue(v as PriorityKey)}
86 >
87 {PRIORITIES.map((priority) => (
88 <div
89 key={priority.key}
90 className="flex items-center justify-between rounded-md hover:bg-slate-700"
91 >
92 <div className="flex items-center gap-2">
93 <RadioGroupItem
94 value={priority.key}
95 id={priority.key}
96 />
97 <span
98 className={`h-2.5 w-2.5 rounded-full ${priority.color}`}
99 />
100 <Label
101 htmlFor={priority.key}
102 className="cursor-pointer text-sm"
103 >
104 {priority.label}
105 </Label>
106 </div>
107 <span className="text-xs text-slate-300">
108 {priority.count}
109 </span>
110 </div>
111 ))}
112 </RadioGroup>
113
114 <div className="border-border mt-3 flex justify-end gap-2 border-t pt-2">
115 <Button variant="ghost" onClick={handleReset}>
116 Reset
117 </Button>
118 <Button onClick={handleApply}>Apply</Button>
119 </div>
120 </PopoverContent>
121 </Popover>
122 )
123}
1241'use client'
2
3import * as React from 'react'
4
5import { Button } from '@/components/ui/button'
6import { ButtonGroup } from '@/components/ui/button-group'
7import { Checkbox } from '@/components/ui/checkbox'
8import { Input } from '@/components/ui/input'
9import { Label } from '@/components/ui/label'
10import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
11
12type FilterTab = 'range' | 'category'
13
14type RangeKey =
15 | '100k+'
16 | '10k-100k'
17 | '1k-10k'
18 | '101-1k'
19 | '11-100'
20 | '1-10'
21 | 'custom'
22
23type CategoryKey =
24 | 'electronics'
25 | 'fashion'
26 | 'home'
27 | 'beauty'
28 | 'sports'
29 | 'books'
30
31type Filters = {
32 range: RangeKey | null
33 from: string
34 to: string
35 category: CategoryKey[]
36}
37
38const DEFAULT_FILTERS: Filters = {
39 range: null,
40 from: '',
41 to: '',
42 category: [],
43}
44
45const RANGES: { key: RangeKey; label: string }[] = [
46 { key: '100k+', label: '100,001+' },
47 { key: '10k-100k', label: '10,001 – 100,000' },
48 { key: '1k-10k', label: '1,001 – 10,000' },
49 { key: '101-1k', label: '101 – 1,000' },
50 { key: '11-100', label: '11 – 100' },
51 { key: '1-10', label: '1 – 10' },
52]
53
54const CATEGORIES: { key: CategoryKey; label: string }[] = [
55 { key: 'electronics', label: 'Electronics' },
56 { key: 'fashion', label: 'Fashion' },
57 { key: 'home', label: 'Home & Living' },
58 { key: 'beauty', label: 'Beauty' },
59 { key: 'sports', label: 'Sports & Fitness' },
60 { key: 'books', label: 'Books' },
61]
62
63export function FilterPanel() {
64 const [activeTab, setActiveTab] = React.useState<FilterTab>('range')
65
66 const [filters, setFilters] = React.useState<Filters>(DEFAULT_FILTERS)
67 const [draft, setDraft] = React.useState<Filters>(DEFAULT_FILTERS)
68
69 const toggleCategory = (key: CategoryKey) => {
70 setDraft((prev) => ({
71 ...prev,
72 category: prev.category.includes(key)
73 ? prev.category.filter((k) => k !== key)
74 : [...prev.category, key],
75 }))
76 }
77
78 const handleApply = () => {
79 setFilters(draft)
80 }
81
82 const handleReset = () => {
83 setDraft(DEFAULT_FILTERS)
84 setFilters(DEFAULT_FILTERS)
85 }
86
87 const handleCustomFocus = () => {
88 setDraft((prev) => ({ ...prev, range: 'custom' }))
89 }
90
91 return (
92 <div className="w-full space-y-4 rounded-lg">
93 <ButtonGroup>
94 {(['range', 'category'] as FilterTab[]).map((tab) => (
95 <Button
96 key={tab}
97 variant={activeTab === tab ? 'primary' : 'outline'}
98 onClick={() => setActiveTab(tab)}
99 >
100 {tab === 'range' ? 'Range' : 'Category'}
101 </Button>
102 ))}
103 </ButtonGroup>
104
105 {activeTab === 'range' && (
106 <div>
107 <RadioGroup
108 value={draft.range ?? undefined}
109 onValueChange={(v) =>
110 setDraft((prev) => ({
111 ...prev,
112 range: v as RangeKey,
113 from: '',
114 to: '',
115 }))
116 }
117 >
118 {RANGES.map((range) => (
119 <Label
120 key={range.key}
121 className="flex cursor-pointer items-center gap-2 rounded-md hover:bg-slate-800"
122 >
123 <RadioGroupItem value={range.key} />
124 {range.label}
125 </Label>
126 ))}
127 </RadioGroup>
128
129 <div className="pt-2">
130 <Label className="text-xs text-slate-400">
131 Custom range
132 </Label>
133 <div className="mt-2 flex gap-2">
134 <Input
135 type="number"
136 placeholder="From"
137 value={draft.from}
138 onFocus={handleCustomFocus}
139 onChange={(e) =>
140 setDraft((prev) => ({
141 ...prev,
142 from: e.target.value,
143 }))
144 }
145 />
146 <Input
147 type="number"
148 placeholder="To"
149 value={draft.to}
150 onFocus={handleCustomFocus}
151 onChange={(e) =>
152 setDraft((prev) => ({
153 ...prev,
154 to: e.target.value,
155 }))
156 }
157 />
158 </div>
159 </div>
160 </div>
161 )}
162
163 {activeTab === 'category' && (
164 <div className="space-y-1">
165 {CATEGORIES.map((cat) => (
166 <Label
167 key={cat.key}
168 className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 hover:bg-slate-800"
169 >
170 <Checkbox
171 checked={draft.category.includes(cat.key)}
172 onCheckedChange={() => toggleCategory(cat.key)}
173 />
174 {cat.label}
175 </Label>
176 ))}
177 </div>
178 )}
179
180 <div className="border-border mt-3 flex justify-end gap-2 border-t pt-2">
181 <Button variant="ghost" onClick={handleReset}>
182 Reset
183 </Button>
184 <Button onClick={handleApply}>Apply</Button>
185 </div>
186 </div>
187 )
188}
1891'use client'
2
3import * as React from 'react'
4
5import { Button } from '@/components/ui/button'
6import { ButtonGroup } from '@/components/ui/button-group'
7import { Checkbox } from '@/components/ui/checkbox'
8import { Input } from '@/components/ui/input'
9import { Label } from '@/components/ui/label'
10import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
11
12type FilterTab = 'range' | 'category'
13
14type RangeKey =
15 | '100k+'
16 | '10k-100k'
17 | '1k-10k'
18 | '101-1k'
19 | '11-100'
20 | '1-10'
21 | 'custom'
22
23type CategoryKey =
24 | 'electronics'
25 | 'fashion'
26 | 'home'
27 | 'beauty'
28 | 'sports'
29 | 'books'
30
31type Filters = {
32 range: RangeKey | null
33 from: string
34 to: string
35 category: CategoryKey[]
36}
37
38const DEFAULT_FILTERS: Filters = {
39 range: null,
40 from: '',
41 to: '',
42 category: [],
43}
44
45const RANGES: { key: RangeKey; label: string }[] = [
46 { key: '100k+', label: '100,001+' },
47 { key: '10k-100k', label: '10,001 – 100,000' },
48 { key: '1k-10k', label: '1,001 – 10,000' },
49 { key: '101-1k', label: '101 – 1,000' },
50 { key: '11-100', label: '11 – 100' },
51 { key: '1-10', label: '1 – 10' },
52]
53
54const CATEGORIES: { key: CategoryKey; label: string }[] = [
55 { key: 'electronics', label: 'Electronics' },
56 { key: 'fashion', label: 'Fashion' },
57 { key: 'home', label: 'Home & Living' },
58 { key: 'beauty', label: 'Beauty' },
59 { key: 'sports', label: 'Sports & Fitness' },
60 { key: 'books', label: 'Books' },
61]
62
63export function FilterPanel() {
64 const [activeTab, setActiveTab] = React.useState<FilterTab>('range')
65
66 const [filters, setFilters] = React.useState<Filters>(DEFAULT_FILTERS)
67 const [draft, setDraft] = React.useState<Filters>(DEFAULT_FILTERS)
68
69 const toggleCategory = (key: CategoryKey) => {
70 setDraft((prev) => ({
71 ...prev,
72 category: prev.category.includes(key)
73 ? prev.category.filter((k) => k !== key)
74 : [...prev.category, key],
75 }))
76 }
77
78 const handleApply = () => {
79 setFilters(draft)
80 }
81
82 const handleReset = () => {
83 setDraft(DEFAULT_FILTERS)
84 setFilters(DEFAULT_FILTERS)
85 }
86
87 const handleCustomFocus = () => {
88 setDraft((prev) => ({ ...prev, range: 'custom' }))
89 }
90
91 return (
92 <div className="w-full space-y-4 rounded-lg">
93 <ButtonGroup>
94 {(['range', 'category'] as FilterTab[]).map((tab) => (
95 <Button
96 key={tab}
97 variant={activeTab === tab ? 'primary' : 'outline'}
98 onClick={() => setActiveTab(tab)}
99 >
100 {tab === 'range' ? 'Range' : 'Category'}
101 </Button>
102 ))}
103 </ButtonGroup>
104
105 {activeTab === 'range' && (
106 <div>
107 <RadioGroup
108 value={draft.range ?? undefined}
109 onValueChange={(v) =>
110 setDraft((prev) => ({
111 ...prev,
112 range: v as RangeKey,
113 from: '',
114 to: '',
115 }))
116 }
117 >
118 {RANGES.map((range) => (
119 <Label
120 key={range.key}
121 className="flex cursor-pointer items-center gap-2 rounded-md hover:bg-slate-800"
122 >
123 <RadioGroupItem value={range.key} />
124 {range.label}
125 </Label>
126 ))}
127 </RadioGroup>
128
129 <div className="pt-2">
130 <Label className="text-xs text-slate-400">
131 Custom range
132 </Label>
133 <div className="mt-2 flex gap-2">
134 <Input
135 type="number"
136 placeholder="From"
137 value={draft.from}
138 onFocus={handleCustomFocus}
139 onChange={(e) =>
140 setDraft((prev) => ({
141 ...prev,
142 from: e.target.value,
143 }))
144 }
145 />
146 <Input
147 type="number"
148 placeholder="To"
149 value={draft.to}
150 onFocus={handleCustomFocus}
151 onChange={(e) =>
152 setDraft((prev) => ({
153 ...prev,
154 to: e.target.value,
155 }))
156 }
157 />
158 </div>
159 </div>
160 </div>
161 )}
162
163 {activeTab === 'category' && (
164 <div className="space-y-1">
165 {CATEGORIES.map((cat) => (
166 <Label
167 key={cat.key}
168 className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 hover:bg-slate-800"
169 >
170 <Checkbox
171 checked={draft.category.includes(cat.key)}
172 onCheckedChange={() => toggleCategory(cat.key)}
173 />
174 {cat.label}
175 </Label>
176 ))}
177 </div>
178 )}
179
180 <div className="border-border mt-3 flex justify-end gap-2 border-t pt-2">
181 <Button variant="ghost" onClick={handleReset}>
182 Reset
183 </Button>
184 <Button onClick={handleApply}>Apply</Button>
185 </div>
186 </div>
187 )
188}
1891'use client'
2
3import * as React from 'react'
4import { Check, ChevronDown, Trash2, X } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import {
8 DropdownMenu,
9 DropdownMenuContent,
10 DropdownMenuItem,
11 DropdownMenuTrigger,
12} from '@/components/ui/dropdown-menu'
13import { Input } from '@/components/ui/input'
14import {
15 Popover,
16 PopoverContent,
17 PopoverTrigger,
18} from '@/components/ui/popover'
19
20type FilterRow = {
21 scope: string
22 criteria: string
23 match: string
24 value: string
25}
26
27const DEFAULT_ROWS: FilterRow[] = [
28 {
29 scope: 'Location',
30 criteria: 'Germany',
31 match: 'Is',
32 value: '',
33 },
34 {
35 scope: 'Traffic Source',
36 criteria: 'Referring site',
37 match: 'Exactly matches',
38 value: '',
39 },
40]
41
42const SCOPE_OPTIONS = ['Location', 'Traffic Source', 'Device']
43const CRITERIA_OPTIONS = ['Germany', 'Canada', 'Referring site', 'Direct visit']
44const MATCH_OPTIONS = ['Is', 'Is not', 'Exactly matches']
45
46export function FacetFilter() {
47 const [rows, setRows] = React.useState<FilterRow[]>(DEFAULT_ROWS)
48 const [open, setOpen] = React.useState(false)
49
50 const updateRow = (index: number, key: keyof FilterRow, value: string) => {
51 setRows((prev) =>
52 prev.map((row, i) =>
53 i === index ? { ...row, [key]: value } : row,
54 ),
55 )
56 }
57
58 const addRow = () => {
59 setRows((prev) => [
60 ...prev,
61 { scope: '', criteria: '', match: '', value: '' },
62 ])
63 }
64
65 const removeRow = (index: number) => {
66 setRows((prev) => prev.filter((_, i) => i !== index))
67 }
68
69 const clearAll = () => setRows([])
70
71 return (
72 <Popover open={open} onOpenChange={setOpen}>
73 <PopoverTrigger asChild>
74 <Button className="gap-2">
75 Filter results
76 <ChevronDown className="h-4 w-4" />
77 </Button>
78 </PopoverTrigger>
79
80 <PopoverContent className="w-full space-y-2 rounded-lg">
81 <div className="space-y-3">
82 {rows.map((row, index) => (
83 <div
84 key={index}
85 className="grid grid-cols-[160px_200px_160px_1fr_40px] gap-2"
86 >
87 <FilterDropdown
88 label={row.scope || 'Select scope'}
89 options={SCOPE_OPTIONS}
90 value={row.scope}
91 onSelect={(v) => updateRow(index, 'scope', v)}
92 />
93
94 <FilterDropdown
95 label={row.criteria || 'Select criteria'}
96 options={CRITERIA_OPTIONS}
97 value={row.criteria}
98 onSelect={(v) =>
99 updateRow(index, 'criteria', v)
100 }
101 />
102
103 <FilterDropdown
104 label={row.match || 'Match type'}
105 options={MATCH_OPTIONS}
106 value={row.match}
107 onSelect={(v) => updateRow(index, 'match', v)}
108 />
109
110 <Input
111 placeholder="Enter value"
112 value={row.value}
113 onChange={(e) =>
114 updateRow(index, 'value', e.target.value)
115 }
116 />
117
118 <Button
119 onClick={() => removeRow(index)}
120 variant="ghost"
121 className="hover:text-danger"
122 >
123 <Trash2 size={16} />
124 </Button>
125 </div>
126 ))}
127
128 <Button onClick={addRow}>+ Add filter</Button>
129 </div>
130
131 <div className="border-border flex items-center justify-end gap-2 border-t pt-2">
132 <Button onClick={clearAll} variant="outline">
133 <X className="h-4 w-4" />
134 Clear all
135 </Button>
136 <Button onClick={() => setOpen(false)}>Apply</Button>
137 </div>
138 </PopoverContent>
139 </Popover>
140 )
141}
142
143function FilterDropdown({
144 label,
145 options,
146 value,
147 onSelect,
148}: {
149 label: string
150 options: string[]
151 value: string
152 onSelect: (v: string) => void
153}) {
154 return (
155 <DropdownMenu modal={false}>
156 <DropdownMenuTrigger asChild>
157 <Button variant="secondary" className="w-full justify-between">
158 {label}
159 <ChevronDown className="h-4 w-4 opacity-60" />
160 </Button>
161 </DropdownMenuTrigger>
162
163 <DropdownMenuContent className="w-[200px]">
164 {options.map((option) => (
165 <DropdownMenuItem
166 key={option}
167 onClick={() => onSelect(option)}
168 className="flex items-center justify-between"
169 >
170 {option}
171 {value === option && (
172 <Check className="h-4 w-4 text-blue-500" />
173 )}
174 </DropdownMenuItem>
175 ))}
176 </DropdownMenuContent>
177 </DropdownMenu>
178 )
179}
1801'use client'
2
3import * as React from 'react'
4import { Check, ChevronDown, Trash2, X } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import {
8 DropdownMenu,
9 DropdownMenuContent,
10 DropdownMenuItem,
11 DropdownMenuTrigger,
12} from '@/components/ui/dropdown-menu'
13import { Input } from '@/components/ui/input'
14import {
15 Popover,
16 PopoverContent,
17 PopoverTrigger,
18} from '@/components/ui/popover'
19
20type FilterRow = {
21 scope: string
22 criteria: string
23 match: string
24 value: string
25}
26
27const DEFAULT_ROWS: FilterRow[] = [
28 {
29 scope: 'Location',
30 criteria: 'Germany',
31 match: 'Is',
32 value: '',
33 },
34 {
35 scope: 'Traffic Source',
36 criteria: 'Referring site',
37 match: 'Exactly matches',
38 value: '',
39 },
40]
41
42const SCOPE_OPTIONS = ['Location', 'Traffic Source', 'Device']
43const CRITERIA_OPTIONS = ['Germany', 'Canada', 'Referring site', 'Direct visit']
44const MATCH_OPTIONS = ['Is', 'Is not', 'Exactly matches']
45
46export function FacetFilter() {
47 const [rows, setRows] = React.useState<FilterRow[]>(DEFAULT_ROWS)
48 const [open, setOpen] = React.useState(false)
49
50 const updateRow = (index: number, key: keyof FilterRow, value: string) => {
51 setRows((prev) =>
52 prev.map((row, i) =>
53 i === index ? { ...row, [key]: value } : row,
54 ),
55 )
56 }
57
58 const addRow = () => {
59 setRows((prev) => [
60 ...prev,
61 { scope: '', criteria: '', match: '', value: '' },
62 ])
63 }
64
65 const removeRow = (index: number) => {
66 setRows((prev) => prev.filter((_, i) => i !== index))
67 }
68
69 const clearAll = () => setRows([])
70
71 return (
72 <Popover open={open} onOpenChange={setOpen}>
73 <PopoverTrigger asChild>
74 <Button className="gap-2">
75 Filter results
76 <ChevronDown className="h-4 w-4" />
77 </Button>
78 </PopoverTrigger>
79
80 <PopoverContent className="w-full space-y-2 rounded-lg">
81 <div className="space-y-3">
82 {rows.map((row, index) => (
83 <div
84 key={index}
85 className="grid grid-cols-[160px_200px_160px_1fr_40px] gap-2"
86 >
87 <FilterDropdown
88 label={row.scope || 'Select scope'}
89 options={SCOPE_OPTIONS}
90 value={row.scope}
91 onSelect={(v) => updateRow(index, 'scope', v)}
92 />
93
94 <FilterDropdown
95 label={row.criteria || 'Select criteria'}
96 options={CRITERIA_OPTIONS}
97 value={row.criteria}
98 onSelect={(v) =>
99 updateRow(index, 'criteria', v)
100 }
101 />
102
103 <FilterDropdown
104 label={row.match || 'Match type'}
105 options={MATCH_OPTIONS}
106 value={row.match}
107 onSelect={(v) => updateRow(index, 'match', v)}
108 />
109
110 <Input
111 placeholder="Enter value"
112 value={row.value}
113 onChange={(e) =>
114 updateRow(index, 'value', e.target.value)
115 }
116 />
117
118 <Button
119 onClick={() => removeRow(index)}
120 variant="ghost"
121 className="hover:text-danger"
122 >
123 <Trash2 size={16} />
124 </Button>
125 </div>
126 ))}
127
128 <Button onClick={addRow}>+ Add filter</Button>
129 </div>
130
131 <div className="border-border flex items-center justify-end gap-2 border-t pt-2">
132 <Button onClick={clearAll} variant="outline">
133 <X className="h-4 w-4" />
134 Clear all
135 </Button>
136 <Button onClick={() => setOpen(false)}>Apply</Button>
137 </div>
138 </PopoverContent>
139 </Popover>
140 )
141}
142
143function FilterDropdown({
144 label,
145 options,
146 value,
147 onSelect,
148}: {
149 label: string
150 options: string[]
151 value: string
152 onSelect: (v: string) => void
153}) {
154 return (
155 <DropdownMenu modal={false}>
156 <DropdownMenuTrigger asChild>
157 <Button variant="secondary" className="w-full justify-between">
158 {label}
159 <ChevronDown className="h-4 w-4 opacity-60" />
160 </Button>
161 </DropdownMenuTrigger>
162
163 <DropdownMenuContent className="w-[200px]">
164 {options.map((option) => (
165 <DropdownMenuItem
166 key={option}
167 onClick={() => onSelect(option)}
168 className="flex items-center justify-between"
169 >
170 {option}
171 {value === option && (
172 <Check className="h-4 w-4 text-blue-500" />
173 )}
174 </DropdownMenuItem>
175 ))}
176 </DropdownMenuContent>
177 </DropdownMenu>
178 )
179}
1801'use client'
2
3import * as React from 'react'
4import { Check, ChevronDown } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import {
8 DropdownMenu,
9 DropdownMenuContent,
10 DropdownMenuItem,
11 DropdownMenuTrigger,
12} from '@/components/ui/dropdown-menu'
13import { Input } from '@/components/ui/input'
14import { Label } from '@/components/ui/label'
15import {
16 Popover,
17 PopoverContent,
18 PopoverTrigger,
19} from '@/components/ui/popover'
20import { Switch } from '@/components/ui/switch'
21
22const STATUS_OPTIONS = ['Active', 'Paused', 'Archived']
23const PERIOD_OPTIONS = ['Last 7 days', 'Last 30 days', 'Custom range']
24
25export function ActivityFilter() {
26 const [open, setOpen] = React.useState(false)
27
28 const [status, setStatus] = React.useState<string[]>([])
29 const [period, setPeriod] = React.useState('')
30 const [minCount, setMinCount] = React.useState('')
31 const [maxCount, setMaxCount] = React.useState('')
32
33 const toggleStatus = (value: string) => {
34 setStatus((prev) =>
35 prev.includes(value)
36 ? prev.filter((s) => s !== value)
37 : [...prev, value],
38 )
39 }
40
41 const resetAll = () => {
42 setStatus([])
43 setPeriod('')
44 setMinCount('')
45 setMaxCount('')
46 }
47
48 return (
49 <Popover open={open} onOpenChange={setOpen}>
50 <PopoverTrigger asChild>
51 <Button className="gap-2">
52 Filter activity
53 <ChevronDown className="h-4 w-4" />
54 </Button>
55 </PopoverTrigger>
56
57 <PopoverContent className="w-[250px] space-y-4 rounded-lg sm:w-full">
58 <section>
59 <p className="text-sm font-medium">Status</p>
60
61 <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3">
62 {STATUS_OPTIONS.map((s) => {
63 const checked = status.includes(s)
64
65 return (
66 <div
67 key={s}
68 className="flex items-center justify-between rounded-md px-3 py-2"
69 >
70 <Label className="text-sm">{s}</Label>
71
72 <Switch
73 checked={checked}
74 onCheckedChange={() => toggleStatus(s)}
75 />
76 </div>
77 )
78 })}
79 </div>
80 </section>
81
82 <section className="space-y-2">
83 <p className="text-sm font-medium">Time period</p>
84 <DropdownMenu>
85 <DropdownMenuTrigger asChild>
86 <Button
87 variant="secondary"
88 className="w-full justify-between"
89 >
90 {period || 'Select period'}
91 <ChevronDown className="h-4 w-4 opacity-60" />
92 </Button>
93 </DropdownMenuTrigger>
94
95 <DropdownMenuContent className="w-full">
96 {PERIOD_OPTIONS.map((p) => (
97 <DropdownMenuItem
98 key={p}
99 onClick={() => setPeriod(p)}
100 className="flex justify-between"
101 >
102 {p}
103 {period === p && (
104 <Check className="h-4 w-4 text-blue-500" />
105 )}
106 </DropdownMenuItem>
107 ))}
108 </DropdownMenuContent>
109 </DropdownMenu>
110 </section>
111
112 <section className="space-y-2">
113 <p className="text-sm font-medium">Volume</p>
114 <div className="flex gap-2">
115 <Input
116 placeholder="Min"
117 value={minCount}
118 onChange={(e) => setMinCount(e.target.value)}
119 />
120 <Input
121 placeholder="Max"
122 value={maxCount}
123 onChange={(e) => setMaxCount(e.target.value)}
124 />
125 </div>
126 </section>
127
128 <div className="border-border flex items-center justify-end border-t pt-3">
129 <Button variant="ghost" onClick={resetAll}>
130 Reset
131 </Button>
132
133 <Button onClick={() => setOpen(false)}>Apply</Button>
134 </div>
135 </PopoverContent>
136 </Popover>
137 )
138}
1391'use client'
2
3import * as React from 'react'
4import { Check, ChevronDown } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import {
8 DropdownMenu,
9 DropdownMenuContent,
10 DropdownMenuItem,
11 DropdownMenuTrigger,
12} from '@/components/ui/dropdown-menu'
13import { Input } from '@/components/ui/input'
14import { Label } from '@/components/ui/label'
15import {
16 Popover,
17 PopoverContent,
18 PopoverTrigger,
19} from '@/components/ui/popover'
20import { Switch } from '@/components/ui/switch'
21
22const STATUS_OPTIONS = ['Active', 'Paused', 'Archived']
23const PERIOD_OPTIONS = ['Last 7 days', 'Last 30 days', 'Custom range']
24
25export function ActivityFilter() {
26 const [open, setOpen] = React.useState(false)
27
28 const [status, setStatus] = React.useState<string[]>([])
29 const [period, setPeriod] = React.useState('')
30 const [minCount, setMinCount] = React.useState('')
31 const [maxCount, setMaxCount] = React.useState('')
32
33 const toggleStatus = (value: string) => {
34 setStatus((prev) =>
35 prev.includes(value)
36 ? prev.filter((s) => s !== value)
37 : [...prev, value],
38 )
39 }
40
41 const resetAll = () => {
42 setStatus([])
43 setPeriod('')
44 setMinCount('')
45 setMaxCount('')
46 }
47
48 return (
49 <Popover open={open} onOpenChange={setOpen}>
50 <PopoverTrigger asChild>
51 <Button className="gap-2">
52 Filter activity
53 <ChevronDown className="h-4 w-4" />
54 </Button>
55 </PopoverTrigger>
56
57 <PopoverContent className="w-[250px] space-y-4 rounded-lg sm:w-full">
58 <section>
59 <p className="text-sm font-medium">Status</p>
60
61 <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3">
62 {STATUS_OPTIONS.map((s) => {
63 const checked = status.includes(s)
64
65 return (
66 <div
67 key={s}
68 className="flex items-center justify-between rounded-md px-3 py-2"
69 >
70 <Label className="text-sm">{s}</Label>
71
72 <Switch
73 checked={checked}
74 onCheckedChange={() => toggleStatus(s)}
75 />
76 </div>
77 )
78 })}
79 </div>
80 </section>
81
82 <section className="space-y-2">
83 <p className="text-sm font-medium">Time period</p>
84 <DropdownMenu>
85 <DropdownMenuTrigger asChild>
86 <Button
87 variant="secondary"
88 className="w-full justify-between"
89 >
90 {period || 'Select period'}
91 <ChevronDown className="h-4 w-4 opacity-60" />
92 </Button>
93 </DropdownMenuTrigger>
94
95 <DropdownMenuContent className="w-full">
96 {PERIOD_OPTIONS.map((p) => (
97 <DropdownMenuItem
98 key={p}
99 onClick={() => setPeriod(p)}
100 className="flex justify-between"
101 >
102 {p}
103 {period === p && (
104 <Check className="h-4 w-4 text-blue-500" />
105 )}
106 </DropdownMenuItem>
107 ))}
108 </DropdownMenuContent>
109 </DropdownMenu>
110 </section>
111
112 <section className="space-y-2">
113 <p className="text-sm font-medium">Volume</p>
114 <div className="flex gap-2">
115 <Input
116 placeholder="Min"
117 value={minCount}
118 onChange={(e) => setMinCount(e.target.value)}
119 />
120 <Input
121 placeholder="Max"
122 value={maxCount}
123 onChange={(e) => setMaxCount(e.target.value)}
124 />
125 </div>
126 </section>
127
128 <div className="border-border flex items-center justify-end border-t pt-3">
129 <Button variant="ghost" onClick={resetAll}>
130 Reset
131 </Button>
132
133 <Button onClick={() => setOpen(false)}>Apply</Button>
134 </div>
135 </PopoverContent>
136 </Popover>
137 )
138}
139