@@ -2,7 +2,9 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/
22import { Badge } from '@/components/ui/badge'
33import { Button } from '@/components/ui/button'
44import { Dialog , DialogContent , DialogHeader , DialogTitle } from '@/components/ui/dialog'
5+ import { DatePicker } from '@/components/common/date-picker'
56import { Form , FormControl , FormDescription , FormField , FormItem , FormLabel , FormMessage } from '@/components/ui/form'
7+ import { Input } from '@/components/ui/input'
68import { LoaderButton } from '@/components/ui/loader-button'
79import { Select , SelectContent , SelectItem , SelectTrigger , SelectValue } from '@/components/ui/select'
810import { Switch } from '@/components/ui/switch'
@@ -29,6 +31,8 @@ interface AdvanceSearchModalProps {
2931export default function AdvanceSearchModal ( { isDialogOpen, onOpenChange, form, onSubmit, isSudo, isApplying = false } : AdvanceSearchModalProps ) {
3032 const dir = useDirDetection ( )
3133 const { t } = useTranslation ( )
34+ const noDataLimitOnly = form . watch ( 'no_data_limit' )
35+ const noExpireOnly = form . watch ( 'no_expire' )
3236
3337 const { data : groupsData } = useGetGroupsSimple ( { all : true } )
3438
@@ -167,6 +171,164 @@ export default function AdvanceSearchModal({ isDialogOpen, onOpenChange, form, o
167171 } }
168172 />
169173
174+ < div className = "grid gap-4 sm:grid-cols-2" >
175+ < FormField
176+ control = { form . control }
177+ name = "data_limit_min"
178+ render = { ( { field } ) => (
179+ < FormItem className = "w-full" >
180+ < FormLabel > { t ( 'advanceSearch.dataLimitMin' , { defaultValue : 'Minimum data limit (GB)' } ) } </ FormLabel >
181+ < FormDescription > { t ( 'advanceSearch.dataLimitDescription' , { defaultValue : 'Filter users by data-limit range in gigabytes.' } ) } </ FormDescription >
182+ < FormControl >
183+ < Input
184+ type = "number"
185+ min = "0"
186+ step = "any"
187+ inputMode = "decimal"
188+ placeholder = { t ( 'advanceSearch.dataLimitMinPlaceholder' , { defaultValue : 'e.g. 10' } ) }
189+ value = { field . value ?? '' }
190+ disabled = { isApplying || noDataLimitOnly }
191+ onChange = { event => {
192+ const rawValue = event . target . value
193+ field . onChange ( rawValue === '' ? undefined : Number ( rawValue ) )
194+ } }
195+ />
196+ </ FormControl >
197+ < FormMessage />
198+ </ FormItem >
199+ ) }
200+ />
201+
202+ < FormField
203+ control = { form . control }
204+ name = "data_limit_max"
205+ render = { ( { field } ) => (
206+ < FormItem className = "w-full" >
207+ < FormLabel > { t ( 'advanceSearch.dataLimitMax' , { defaultValue : 'Maximum data limit (GB)' } ) } </ FormLabel >
208+ < FormDescription > { t ( 'advanceSearch.dataLimitDescription' , { defaultValue : 'Filter users by data-limit range in gigabytes.' } ) } </ FormDescription >
209+ < FormControl >
210+ < Input
211+ type = "number"
212+ min = "0"
213+ step = "any"
214+ inputMode = "decimal"
215+ placeholder = { t ( 'advanceSearch.dataLimitMaxPlaceholder' , { defaultValue : 'e.g. 100' } ) }
216+ value = { field . value ?? '' }
217+ disabled = { isApplying || noDataLimitOnly }
218+ onChange = { event => {
219+ const rawValue = event . target . value
220+ field . onChange ( rawValue === '' ? undefined : Number ( rawValue ) )
221+ } }
222+ />
223+ </ FormControl >
224+ < FormMessage />
225+ </ FormItem >
226+ ) }
227+ />
228+ </ div >
229+
230+ < FormField
231+ control = { form . control }
232+ name = "no_data_limit"
233+ render = { ( { field } ) => (
234+ < FormItem className = "flex w-full items-start justify-between gap-4 rounded-md border p-4" >
235+ < div className = "space-y-1" >
236+ < FormLabel > { t ( 'advanceSearch.noDataLimit' , { defaultValue : 'Only users with no data limit' } ) } </ FormLabel >
237+ < FormDescription > { t ( 'advanceSearch.noDataLimitDescription' , { defaultValue : 'Shows users whose data limit is unlimited.' } ) } </ FormDescription >
238+ </ div >
239+ < FormControl >
240+ < Switch
241+ checked = { field . value }
242+ disabled = { isApplying }
243+ onCheckedChange = { checked => {
244+ field . onChange ( checked )
245+ if ( checked ) {
246+ form . setValue ( 'data_limit_min' , undefined , { shouldDirty : true } )
247+ form . setValue ( 'data_limit_max' , undefined , { shouldDirty : true } )
248+ }
249+ } }
250+ />
251+ </ FormControl >
252+ < FormMessage />
253+ </ FormItem >
254+ ) }
255+ />
256+
257+ < div className = "grid gap-4 sm:grid-cols-2" >
258+ < FormField
259+ control = { form . control }
260+ name = "expire_after"
261+ render = { ( { field } ) => (
262+ < FormItem className = "w-full" >
263+ < FormControl >
264+ < div className = { cn ( ( isApplying || noExpireOnly ) && 'pointer-events-none opacity-60' ) } >
265+ < DatePicker
266+ mode = "single"
267+ date = { field . value }
268+ onDateChange = { field . onChange }
269+ label = { t ( 'advanceSearch.expireAfter' , { defaultValue : 'Expire after' } ) }
270+ placeholder = { t ( 'advanceSearch.expireAfterPlaceholder' , { defaultValue : 'Select start date' } ) }
271+ minDate = { new Date ( '1900-01-01' ) }
272+ className = "[&_label]:text-sm"
273+ />
274+ </ div >
275+ </ FormControl >
276+ < FormMessage />
277+ </ FormItem >
278+ ) }
279+ />
280+
281+ < FormField
282+ control = { form . control }
283+ name = "expire_before"
284+ render = { ( { field } ) => (
285+ < FormItem className = "w-full" >
286+ < FormControl >
287+ < div className = { cn ( ( isApplying || noExpireOnly ) && 'pointer-events-none opacity-60' ) } >
288+ < DatePicker
289+ mode = "single"
290+ date = { field . value }
291+ onDateChange = { field . onChange }
292+ label = { t ( 'advanceSearch.expireBefore' , { defaultValue : 'Expire before' } ) }
293+ placeholder = { t ( 'advanceSearch.expireBeforePlaceholder' , { defaultValue : 'Select end date' } ) }
294+ minDate = { new Date ( '1900-01-01' ) }
295+ className = "[&_label]:text-sm"
296+ />
297+ </ div >
298+ </ FormControl >
299+ < FormMessage />
300+ </ FormItem >
301+ ) }
302+ />
303+ </ div >
304+
305+ < FormField
306+ control = { form . control }
307+ name = "no_expire"
308+ render = { ( { field } ) => (
309+ < FormItem className = "flex w-full items-start justify-between gap-4 rounded-md border p-4" >
310+ < div className = "space-y-1" >
311+ < FormLabel > { t ( 'advanceSearch.noExpire' , { defaultValue : 'Only users with no expire date' } ) } </ FormLabel >
312+ < FormDescription > { t ( 'advanceSearch.noExpireDescription' , { defaultValue : 'Shows users whose account has no expire date.' } ) } </ FormDescription >
313+ </ div >
314+ < FormControl >
315+ < Switch
316+ checked = { field . value }
317+ disabled = { isApplying }
318+ onCheckedChange = { checked => {
319+ field . onChange ( checked )
320+ if ( checked ) {
321+ form . setValue ( 'expire_after' , undefined , { shouldDirty : true } )
322+ form . setValue ( 'expire_before' , undefined , { shouldDirty : true } )
323+ }
324+ } }
325+ />
326+ </ FormControl >
327+ < FormMessage />
328+ </ FormItem >
329+ ) }
330+ />
331+
170332 < FormField
171333 control = { form . control }
172334 name = "group"
0 commit comments