filters , order inspections , inventory items , credit notes

This commit is contained in:
2026-04-08 14:38:16 +03:00
parent 90c84a0bda
commit bb02b77be2
33 changed files with 2552 additions and 145 deletions
@@ -0,0 +1,93 @@
"use client"
import React from "react"
import type { FieldValues, UseFormReturn } from "react-hook-form"
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetFooter,
} from "@/shared/components/ui/sheet"
import { Button } from "@/shared/components/ui/button"
import { Badge } from "@/shared/components/ui/badge"
import { Rhform } from "@/shared/components/form"
import { ScrollArea } from "@/shared/components/ui/scroll-area"
import { Filter, FilterIcon, RotateCcwIcon } from "lucide-react"
type FilterDrawerProps<TFormValues extends FieldValues> = {
form: UseFormReturn<TFormValues>
isOpen: boolean
onOpenChange: (open: boolean) => void
onSubmit: (values: TFormValues) => void
onReset: () => void
activeFilterCount: number
title?: string
children: React.ReactNode
}
export function FilterDrawer<TFormValues extends FieldValues>({
form,
isOpen,
onOpenChange,
onSubmit,
onReset,
activeFilterCount,
title = "Filters",
children,
}: FilterDrawerProps<TFormValues>) {
return (
<Sheet open={isOpen} onOpenChange={onOpenChange}>
<SheetContent side="left" className="flex flex-col sm:max-w-md">
<SheetHeader>
<SheetTitle className="text-lg font-bold">
<FilterIcon className="mr-2 inline h-5 w-5" />
{title}</SheetTitle>
</SheetHeader>
<Rhform form={form} onSubmit={onSubmit} className="flex min-h-0 flex-1 flex-col">
<ScrollArea className="min-h-0 flex-1 px-4">
<div className="space-y-4 pb-4">
{children}
</div>
</ScrollArea>
<SheetFooter className="border-t">
<div className="flex w-full gap-2">
<Button type="button" variant="outline" className="flex-1" onClick={onReset}>
<RotateCcwIcon className="mr-2 h-4 w-4" />
Reset
</Button>
<Button type="submit" className="flex-1">
Apply Filters
{activeFilterCount > 0 && (
<Badge variant="secondary" className="ml-2 h-5 min-w-5 rounded-full px-1.5 text-xs">
{activeFilterCount}
</Badge>
)}
</Button>
</div>
</SheetFooter>
</Rhform>
</SheetContent>
</Sheet>
)
}
export function FilterTrigger({
onClick,
activeFilterCount,
}: {
onClick: () => void
activeFilterCount: number
}) {
return (
<Button variant="outline" size="sm" onClick={onClick} className="gap-2">
<FilterIcon className="h-4 w-4" />
Filters
{activeFilterCount > 0 && (
<Badge variant="secondary" className="h-5 min-w-5 rounded-full px-1.5 text-xs">
{activeFilterCount}
</Badge>
)}
</Button>
)
}
@@ -0,0 +1,102 @@
"use client"
import { useState, useMemo, useCallback } from "react"
import { useForm, type DefaultValues, type FieldValues, type UseFormReturn } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { useQueryStates, useQueryState, parseAsInteger } from "nuqs"
import type { ZodType } from "zod"
export type UseFilterParamsOptions<TFormValues extends FieldValues> = {
schema: ZodType<TFormValues, any, any>
defaultValues: DefaultValues<TFormValues>
paramsParsers: Record<string, any>
mapParamsToFormValues: (params: Record<string, any>) => Partial<TFormValues>
mapFormValuesToParams: (values: TFormValues) => Record<string, any>
}
export type UseFilterParamsReturn<TFormValues extends FieldValues> = {
form: UseFormReturn<TFormValues>
isOpen: boolean
open: () => void
close: () => void
onSubmit: (values: TFormValues) => void
reset: () => void
appliedParams: Record<string, unknown>
activeFilterCount: number
}
export function useFilterParams<TFormValues extends FieldValues>({
schema,
defaultValues,
paramsParsers,
mapParamsToFormValues,
mapFormValuesToParams,
}: UseFilterParamsOptions<TFormValues>): UseFilterParamsReturn<TFormValues> {
const [params, setParams] = useQueryStates(paramsParsers)
const [, setPage] = useQueryState("page", parseAsInteger)
const [isOpen, setIsOpen] = useState(false)
const form = useForm<TFormValues>({
resolver: zodResolver(schema) as any,
defaultValues,
})
const open = useCallback(() => {
const formValues = mapParamsToFormValues(params)
form.reset({ ...defaultValues, ...formValues } as any)
setIsOpen(true)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params])
const close = useCallback(() => {
setIsOpen(false)
}, [])
const onSubmit = useCallback((values: TFormValues) => {
const mapped = mapFormValuesToParams(values)
const fullParams: Record<string, any> = {}
for (const key of Object.keys(paramsParsers)) {
fullParams[key] = mapped[key] ?? null
}
setParams(fullParams)
setPage(1)
setIsOpen(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mapFormValuesToParams, paramsParsers, setParams, setPage])
const reset = useCallback(() => {
const nullParams: Record<string, null> = {}
for (const key of Object.keys(paramsParsers)) {
nullParams[key] = null
}
setParams(nullParams)
setPage(1)
form.reset(defaultValues)
setIsOpen(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [paramsParsers, setParams, setPage])
const appliedParams = useMemo(() => {
const result: Record<string, unknown> = {}
for (const [key, value] of Object.entries(params)) {
if (value === null || value === undefined || value === "") continue
if (Array.isArray(value) && value.length === 0) continue
if (typeof value === "boolean" && value === false) continue
result[key] = value
}
return result
}, [params])
const activeFilterCount = Object.keys(appliedParams).length
return {
form,
isOpen,
open,
close,
onSubmit,
reset,
appliedParams,
activeFilterCount,
}
}