ForFarm/frontend/app/(sidebar)/inventory/page.tsx
2025-04-03 20:01:44 +07:00

363 lines
11 KiB
TypeScript

"use client";
import { useState, useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getPaginationRowModel,
flexRender,
SortingState,
PaginationState,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { TriangleAlertIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
} from "@/components/ui/pagination";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Search } from "lucide-react";
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
import { fetchHarvestUnits } from "@/api/harvest";
import { Badge } from "@/components/ui/badge";
import {
fetchInventoryItems,
fetchInventoryStatus,
fetchInventoryCategory,
} from "@/api/inventory";
import { AddInventoryItem } from "./add-inventory-item";
import {
EditInventoryItem,
EditInventoryItemProps,
} from "./edit-inventory-item";
import { DeleteInventoryItem } from "./delete-inventory-item";
export default function InventoryPage() {
const [sorting, setSorting] = useState<SortingState>([]);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
//////////////////////////////
// query the necessary data for edit and etc.
const {
data: inventoryItems = [],
isLoading: isItemLoading,
isError: isItemError,
} = useQuery({
queryKey: ["inventoryItems"],
queryFn: fetchInventoryItems,
staleTime: 60 * 1000,
});
// console.table(inventoryItems);
// console.log(inventoryItems);
const {
data: inventoryStatus = [],
isLoading: isLoadingStatus,
isError: isErrorStatus,
} = useQuery({
queryKey: ["inventoryStatus"],
queryFn: fetchInventoryStatus,
staleTime: 60 * 1000,
});
// console.log(inventoryStatus);
const {
data: inventoryCategory = [],
isLoading: isLoadingCategory,
isError: isErrorCategory,
} = useQuery({
queryKey: ["inventoryCategory"],
queryFn: fetchInventoryCategory,
staleTime: 60 * 1000,
});
const {
data: harvestUnits = [],
isLoading: isLoadingHarvestUnits,
isError: isErrorHarvestUnits,
} = useQuery({
queryKey: ["harvestUnits"],
queryFn: fetchHarvestUnits,
staleTime: 60 * 1000,
});
//////////////////////////////
// console.table(inventoryItems);
// console.table(inventoryStatus);
// console.table(harvestUnits);
const [searchTerm, setSearchTerm] = useState("");
const filteredItems = useMemo(() => {
return inventoryItems
.map((item) => ({
...item,
status: item.status.name,
category: item.category.name,
categoryId: item.categoryId,
unit: item.unit.name,
unitId: item.unitId,
statusId: item.statusId,
fetchedInventoryStatus: inventoryStatus,
fetchedInventoryCategory: inventoryCategory,
fetchedHarvestUnits: harvestUnits,
lastUpdated: new Date(item.updatedAt).toLocaleString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: true,
}),
}))
.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [inventoryItems, searchTerm]);
// prepare columns for table
const columns = [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "category", header: "Category" },
{ accessorKey: "quantity", header: "Quantity" },
{ accessorKey: "unit", header: "Unit" },
{ accessorKey: "lastUpdated", header: "Last Updated" },
{
accessorKey: "status",
header: "Status",
cell: (info: { getValue: () => string }) => {
const status = info.getValue();
let statusClass = "";
if (status === "In Stock") {
statusClass = "bg-green-500 hover:bg-green-600 text-white";
} else if (status === "Low Stock") {
statusClass = "bg-yellow-300 hover:bg-yellow-400";
} else if (status === "Out of Stock") {
statusClass = "bg-red-500 hover:bg-red-600 text-white";
} else if (status === "Expired") {
statusClass = "bg-gray-500 hover:bg-gray-600 text-white";
} else if (status === "Reserved") {
statusClass = "bg-blue-500 hover:bg-blue-600 text-white";
}
return (
<Badge className={`py-1 px-2 rounded-md ${statusClass}`}>
{status}
</Badge>
);
},
},
{
accessorKey: "edit",
header: "Edit",
cell: ({ row }: { row: { original: EditInventoryItemProps } }) => (
<EditInventoryItem
{...row.original}
fetchedInventoryStatus={inventoryStatus}
fetchedInventoryCategory={inventoryCategory}
/>
),
enableSorting: false,
},
{
accessorKey: "delete",
header: "Delete",
cell: ({ row }: { row: { original: EditInventoryItemProps } }) => (
<DeleteInventoryItem id={row.original.id} />
),
enableSorting: false,
},
];
const table = useReactTable({
data: filteredItems,
columns,
state: { sorting, pagination },
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
onPaginationChange: setPagination,
});
const loadingStates = [
isItemLoading,
isLoadingStatus,
isLoadingCategory,
isLoadingHarvestUnits,
];
const errorStates = [
isItemError,
isErrorStatus,
isErrorCategory,
isErrorHarvestUnits,
];
const isLoading = loadingStates.some((loading) => loading);
const isError = errorStates.some((error) => error);
if (isLoading)
return (
<div className="flex min-h-screen items-center justify-center">
Loading...
</div>
);
if (isError)
return (
<div className="flex min-h-screen items-center justify-center">
Error loading inventory data.
</div>
);
if (inventoryItems.length === 0) {
return (
<div className="flex flex-col items-center justify-center h-[50vh]">
<Alert variant="destructive" className="w-full max-w-md text-center">
<div className="flex flex-col items-center">
<TriangleAlertIcon className="h-6 w-6 text-red-500 mb-2" />
<AlertTitle>No Inventory Data</AlertTitle>
<AlertDescription>
<div>
You currently have no inventory items. Add a new item to get
started!
</div>
<div className="mt-5">
<AddInventoryItem
inventoryCategory={inventoryCategory}
inventoryStatus={inventoryStatus}
harvestUnits={harvestUnits}
/>
</div>
</AlertDescription>
</div>
</Alert>
</div>
);
}
return (
<div className="flex min-h-screen bg-background">
<div className="flex-1 flex flex-col">
<main className="flex-1 p-6">
<h1 className="text-2xl font-bold tracking-tight mb-6">Inventory</h1>
<div className="flex flex-col sm:flex-row gap-4 mb-6">
<Search className="mt-1" />
<Input
type="search"
placeholder="Search by name..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<AddInventoryItem
inventoryCategory={inventoryCategory}
inventoryStatus={inventoryStatus}
harvestUnits={harvestUnits}
/>
</div>
<div className="border rounded-md">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
onClick={header.column.getToggleSortingHandler()}
className="cursor-pointer "
>
<div className="flex items-center">
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.column.getCanSort() &&
!header.column.columnDef.enableSorting && (
<span className="ml-2">
{header.column.getIsSorted() === "desc" ? (
<FaSortDown />
) : header.column.getIsSorted() === "asc" ? (
<FaSortUp />
) : (
<FaSort />
)}
</span>
)}
</div>
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id} className="even:bg-gray-800">
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
<Pagination className="mt-5">
<PaginationContent className="space-x-5">
<PaginationItem>
<Button
className="flex w-24"
onClick={() =>
setPagination((prev) => ({
...prev,
pageIndex: Math.max(0, prev.pageIndex - 1),
}))
}
disabled={pagination.pageIndex === 0}
>
Previous
</Button>
</PaginationItem>
<PaginationItem>
<Button
className="flex w-24"
onClick={() =>
setPagination((prev) => ({
...prev,
pageIndex: prev.pageIndex + 1,
}))
}
disabled={
pagination.pageIndex >=
Math.ceil(filteredItems.length / pagination.pageSize) - 1
}
>
Next
</Button>
</PaginationItem>
</PaginationContent>
</Pagination>
</main>
</div>
</div>
);
}