"use client" import * as React from "react" import { ArrowDownCircleIcon, ArrowUpCircleIcon, ListIcon, RotateCcwIcon } from "lucide-react" import { getProducts, getStockMovements } from "@/lib/api/inventory" import type { ReturnCategory, StockMovement } from "@/lib/models/inventory" import { formatCurrency } from "@/lib/format" import { useFetch } from "@/lib/use-fetch" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { DataTable, type DataTableColumn } from "@/components/hexa/data-table" import { InfoBanner } from "@/components/hexa/info-banner" import { PageSkeleton } from "@/components/hexa/page-skeleton" const TYPE_OPTIONS: { label: string; value: "all" | StockMovement["type"] }[] = [ { label: "All types", value: "all" }, { label: "Stock in (GRN)", value: "GRN" }, { label: "Stock out (Sale)", value: "Sale" }, { label: "Returns / write-offs", value: "Return" }, ] const CATEGORY_OPTIONS: { label: string; value: "all" | ReturnCategory }[] = [ { label: "All categories", value: "all" }, { label: "Expired", value: "Expired" }, { label: "Damaged", value: "Damaged" }, { label: "Lost", value: "Lost" }, { label: "Sent back to supplier", value: "SupplierReturn" }, { label: "Handed back by customer", value: "CustomerReturn" }, { label: "Other", value: "Other" }, ] function categoryLabel(category: ReturnCategory) { return CATEGORY_OPTIONS.find((o) => o.value === category)?.label ?? category } function typeLabel(type: StockMovement["type"]) { return TYPE_OPTIONS.find((o) => o.value === type)?.label ?? type } function formatDate(iso: string) { return new Date(iso).toLocaleString(undefined, { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", }) } function typeIcon(type: StockMovement["type"]) { if (type === "GRN") return if (type === "Sale") return return } function InventoryStockTrackingTab() { const movementsFetch = useFetch(() => getStockMovements()) const movements = movementsFetch.data ?? [] const productsFetch = useFetch(() => getProducts(true)) const products = productsFetch.data ?? [] const [typeFilter, setTypeFilter] = React.useState<"all" | StockMovement["type"]>("all") const [productFilter, setProductFilter] = React.useState("all") const [categoryFilter, setCategoryFilter] = React.useState<"all" | ReturnCategory>("all") const [dateFrom, setDateFrom] = React.useState("") const [dateTo, setDateTo] = React.useState("") const hasActiveFilters = typeFilter !== "all" || productFilter !== "all" || categoryFilter !== "all" || !!dateFrom || !!dateTo function resetFilters() { setTypeFilter("all") setProductFilter("all") setCategoryFilter("all") setDateFrom("") setDateTo("") } // Shows every stock movement that ever happened by default -- all filters are opt-in. const filtered = movements.filter((m) => { if (typeFilter !== "all" && m.type !== typeFilter) return false if (productFilter !== "all" && m.productId !== productFilter) return false if (categoryFilter !== "all" && m.category !== categoryFilter) return false if (dateFrom && new Date(m.date) < new Date(dateFrom)) return false if (dateTo && new Date(m.date) > new Date(`${dateTo}T23:59:59`)) return false return true }) const productOptions = [{ label: "All products", value: "all" }, ...products.map((p) => ({ label: p.name, value: p.productId }))] const columns: DataTableColumn[] = [ { key: "date", header: "Date", cell: (row) => formatDate(row.date) }, { key: "type", header: "Type", cell: (row) => (
{typeIcon(row.type)} {typeLabel(row.type)}
), }, { key: "product", header: "Product", cell: (row) => row.productName }, { key: "reference", header: "Reference", cell: (row) => row.reference }, { key: "category", header: "Category", cell: (row) => (row.category ? {categoryLabel(row.category)} : "—"), }, { key: "quantity", header: "Quantity", cell: (row) => ( = 0 ? "text-emerald-600 dark:text-emerald-400" : "text-destructive"}> {row.quantityChange >= 0 ? "+" : ""} {row.quantityChange} ), }, { key: "amount", header: "Amount", cell: (row) => formatCurrency(row.amount) }, ] if (movementsFetch.loading) { return } return (

Stock Tracking

Every stock movement that ever happened — GRNs in, sales out, and returns — in one ledger. Shows everything by default; use the filters to narrow it down.

setDateFrom(e.target.value)} />
setDateTo(e.target.value)} />
{hasActiveFilters && ( )}
`${row.type}-${row.productId}-${row.reference}-${row.date}-${row.amount}`} emptyMessage={hasActiveFilters ? "No movements match these filters." : "No stock movements recorded yet."} renderMobileCard={(row) => (
{typeIcon(row.type)}

{row.productName}

{typeLabel(row.type)} · {row.reference} · {formatDate(row.date)}

= 0 ? "text-emerald-600 dark:text-emerald-400" : "text-destructive"}> {row.quantityChange >= 0 ? "+" : ""} {row.quantityChange} {formatCurrency(row.amount)}
)} /> Read-only — this tab is a combined view of the GRN, Sales and Returns tabs. To record a new movement, use the relevant tab directly.
) } export { InventoryStockTrackingTab }