import { useState } from 'react' import { createFileRoute, useNavigate } from '@tanstack/react-router' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { productListOptions, productMutations, productKeys, categoryAllOptions } from '@/api/inventory' import { ProductForm } from '@/components/inventory/product-form' import { usePagination } from '@/hooks/use-pagination' import { DataTable, type Column } from '@/components/shared/data-table' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' import { Search, Plus } from 'lucide-react' import { toast } from 'sonner' import { useAuthStore } from '@/stores/auth.store' import type { Product } from '@/types/inventory' export const Route = createFileRoute('/_authenticated/inventory/')({ validateSearch: (search: Record) => ({ page: Number(search.page) || 1, limit: Number(search.limit) || 25, q: (search.q as string) || undefined, sort: (search.sort as string) || undefined, order: (search.order as 'asc' | 'desc') || 'asc', categoryId: (search.categoryId as string) || undefined, isActive: (search.isActive as string) || undefined, type: (search.type as string) || undefined, lowStock: (search.lowStock as string) || undefined, }), component: InventoryPage, }) function qtyBadge(qty: number, reorderPoint: number | null) { if (qty === 0) return {qty} if (reorderPoint !== null && qty <= reorderPoint) return {qty} Low return {qty} } function InventoryPage() { const navigate = useNavigate() const queryClient = useQueryClient() const hasPermission = useAuthStore((s) => s.hasPermission) const { params, setPage, setSearch, setSort } = usePagination() const [searchInput, setSearchInput] = useState(params.q ?? '') const [createOpen, setCreateOpen] = useState(false) const search = Route.useSearch() const [categoryFilter, setCategoryFilter] = useState(search.categoryId ?? '') const [activeFilter, setActiveFilter] = useState(search.isActive ?? '') const [typeFilter, setTypeFilter] = useState(search.type ?? '') const [lowStockFilter, setLowStockFilter] = useState(search.lowStock === 'true') const { data: categoriesData } = useQuery(categoryAllOptions()) const categories = categoriesData?.data ?? [] const categoryMap = new Map(categories.map((c) => [c.id, c.name])) const queryParams: Record = { ...params } if (categoryFilter) queryParams.categoryId = categoryFilter if (activeFilter) queryParams.isActive = activeFilter === 'true' if (typeFilter === 'serialized') queryParams.isSerialized = true if (typeFilter === 'rental') queryParams.isRental = true if (typeFilter === 'repair') queryParams.isDualUseRepair = true if (lowStockFilter) queryParams.lowStock = true const { data, isLoading } = useQuery(productListOptions(queryParams)) const createMutation = useMutation({ mutationFn: productMutations.create, onSuccess: (product) => { queryClient.invalidateQueries({ queryKey: productKeys.all }) toast.success('Product created') setCreateOpen(false) navigate({ to: '/inventory/$productId', params: { productId: product.id }, search: {} as any }) }, onError: (err) => toast.error(err.message), }) function handleSearchSubmit(e: React.FormEvent) { e.preventDefault() setSearch(searchInput) } function handleCategoryChange(v: string) { setCategoryFilter(v === 'all' ? '' : v) navigate({ to: '/inventory', search: { ...search, categoryId: v === 'all' ? undefined : v, page: 1 } as any }) } function handleActiveChange(v: string) { setActiveFilter(v === 'all' ? '' : v) navigate({ to: '/inventory', search: { ...search, isActive: v === 'all' ? undefined : v, page: 1 } as any }) } function handleTypeChange(v: string) { setTypeFilter(v === 'all' ? '' : v) navigate({ to: '/inventory', search: { ...search, type: v === 'all' ? undefined : v, page: 1 } as any }) } function handleLowStockChange(v: string) { const on = v === 'true' setLowStockFilter(on) navigate({ to: '/inventory', search: { ...search, lowStock: on ? 'true' : undefined, page: 1 } as any }) } const columns: Column[] = [ { key: 'name', header: 'Name', sortable: true, render: (p) => {p.name}, }, { key: 'sku', header: 'SKU', sortable: true, render: (p) => p.sku ? {p.sku} : , }, { key: 'brand', header: 'Brand', sortable: true, render: (p) => p.brand ?? , }, { key: 'category', header: 'Category', render: (p) => p.categoryId ? (categoryMap.get(p.categoryId) ?? ) : , }, { key: 'price', header: 'Price', sortable: true, render: (p) => p.price ? `$${Number(p.price).toFixed(2)}` : , }, { key: 'qty_on_hand', header: 'Qty', sortable: true, render: (p) => qtyBadge(p.qtyOnHand, p.qtyReorderPoint), }, { key: 'flags', header: 'Type', render: (p) => (
{p.isSerialized && Serial} {p.isRental && Rental} {p.isDualUseRepair && Repair}
), }, { key: 'is_active', header: 'Status', sortable: true, render: (p) => p.isActive ? Active : Inactive, }, ] return (

Products

{hasPermission('inventory.edit') && ( New Product )}
setSearchInput(e.target.value)} className="pl-9 w-64" />
navigate({ to: '/inventory/$productId', params: { productId: p.id }, search: {} as any })} />
) }