[dyad] Implemented "Edit Item" functionality for stock table - wrote 5 file(s)
This commit is contained in:
parent
837c8354ba
commit
8a5edf3e3b
4 changed files with 375 additions and 179 deletions
195
src/components/stock/EditItemForm.tsx
Normal file
195
src/components/stock/EditItemForm.tsx
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { showSuccess, showError } from "@/utils/toast"
|
||||
import { CategoryCombobox } from "./CategoryCombobox"
|
||||
import pb from "@/lib/pocketbase"
|
||||
import { StockItem } from "./columns" // Assuming StockItem type is defined here
|
||||
|
||||
const formSchema = z.object({
|
||||
id: z.string(), // Hidden field for item ID
|
||||
name: z.string().min(2, { message: "Name must be at least 2 characters." }),
|
||||
sku: z.string().min(2, { message: "SKU must be at least 2 characters." }),
|
||||
category: z.string({ required_error: "Please select or create a category." }),
|
||||
quantity: z.coerce.number().int().positive({ message: "Quantity must be a positive number." }),
|
||||
costPrice: z.coerce.number().positive({ message: "Cost price must be a positive number." }),
|
||||
sellingPrice: z.coerce.number().positive({ message: "Selling price must be a positive number." }),
|
||||
})
|
||||
|
||||
interface EditItemFormProps {
|
||||
setOpen: (open: boolean) => void;
|
||||
refreshData: () => void;
|
||||
item: StockItem; // The item data to be edited
|
||||
}
|
||||
|
||||
export function EditItemForm({ setOpen, refreshData, item }: EditItemFormProps) {
|
||||
const [categoryOptions, setCategoryOptions] = React.useState<{ value: string; label: string }[]>([]);
|
||||
|
||||
const fetchCategories = React.useCallback(async () => {
|
||||
try {
|
||||
const records = await pb.collection('stock_items').getList(1, 50, {
|
||||
fields: 'category',
|
||||
});
|
||||
const uniqueCategories = Array.from(new Set(records.items.map((item: { category: string }) => item.category)));
|
||||
const options = uniqueCategories.map((cat: string) => ({
|
||||
value: cat.toLowerCase(),
|
||||
label: cat,
|
||||
}));
|
||||
setCategoryOptions(options);
|
||||
} catch (error) {
|
||||
console.error("Error fetching categories for combobox:", error);
|
||||
showError("Failed to load categories.");
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchCategories();
|
||||
}, [fetchCategories]);
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
sku: item.sku,
|
||||
category: item.category.toLowerCase(), // Ensure category is lowercase for combobox value
|
||||
quantity: item.quantity,
|
||||
costPrice: item.costPrice,
|
||||
sellingPrice: item.sellingPrice,
|
||||
},
|
||||
})
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
try {
|
||||
await pb.collection('stock_items').update(values.id, {
|
||||
name: values.name,
|
||||
sku: values.sku,
|
||||
category: values.category,
|
||||
quantity: values.quantity,
|
||||
cost_price: values.costPrice,
|
||||
selling_price: values.sellingPrice,
|
||||
});
|
||||
showSuccess("Item updated successfully!");
|
||||
setOpen(false);
|
||||
refreshData(); // Trigger data refresh in StockList
|
||||
fetchCategories(); // Re-fetch categories in case a new one was added/edited
|
||||
} catch (error) {
|
||||
console.error("Error updating item:", error);
|
||||
showError("Failed to update item. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
const handleCategoryCreate = (newCategory: string) => {
|
||||
const formattedCategory = {
|
||||
value: newCategory.toLowerCase(),
|
||||
label: newCategory,
|
||||
};
|
||||
setCategoryOptions(prev => [...prev, formattedCategory]);
|
||||
form.setValue("category", formattedCategory.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Item Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g., Wireless Mouse" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sku"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>SKU</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="e.g., LOG-M720" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="category"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Category</FormLabel>
|
||||
<CategoryCombobox
|
||||
categories={categoryOptions}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
onCategoryCreate={handleCategoryCreate}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="quantity"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Quantity</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="costPrice"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Cost Price</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" step="0.01" placeholder="R0.00" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sellingPrice"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Selling Price</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" step="0.01" placeholder="R0.00" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full">Save Changes</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
import { MoreHorizontal, ArrowUpDown } from "lucide-react"
|
||||
|
||||
import { MoreHorizontal } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -14,8 +11,17 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { QRCodeDialog } from "./QRCodeDialog"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { useState } from "react"
|
||||
import { EditItemForm } from "./EditItemForm" // Import the new EditItemForm
|
||||
|
||||
// Define the StockItem type based on your PocketBase collection structure
|
||||
export type StockItem = {
|
||||
id: string
|
||||
name: string
|
||||
|
|
@ -24,80 +30,14 @@ export type StockItem = {
|
|||
quantity: number
|
||||
costPrice: number
|
||||
sellingPrice: number
|
||||
status: "In Stock" | "Low Stock" | "Out of Stock"
|
||||
}
|
||||
|
||||
const formatCurrency = (price: number) => {
|
||||
return new Intl.NumberFormat("en-ZA", {
|
||||
style: "currency",
|
||||
currency: "ZAR",
|
||||
}).format(price)
|
||||
// Define a type for the props that the actions column will receive
|
||||
interface ActionsColumnProps {
|
||||
refreshData: () => void; // Function to refresh the parent data table
|
||||
}
|
||||
|
||||
const ActionsCell = ({ row }: { row: any }) => {
|
||||
const item = row.original
|
||||
const [isQRCodeOpen, setQRCodeOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<QRCodeDialog
|
||||
item={item}
|
||||
open={isQRCodeOpen}
|
||||
onOpenChange={setQRCodeOpen}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => setQRCodeOpen(true)}>
|
||||
View QR Code
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigator.clipboard.writeText(item.id)}
|
||||
>
|
||||
Copy item ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>View details</DropdownMenuItem>
|
||||
<DropdownMenuItem>Edit item</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600">Delete item</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<StockItem>[] = [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected()
|
||||
? true
|
||||
: table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: false
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
export const createColumns = (props: ActionsColumnProps): ColumnDef<StockItem>[] => [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Item Name",
|
||||
|
|
@ -112,55 +52,72 @@ export const columns: ColumnDef<StockItem>[] = [
|
|||
},
|
||||
{
|
||||
accessorKey: "quantity",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
className="text-center"
|
||||
>
|
||||
Quantity
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const quantity = parseFloat(row.getValue("quantity"))
|
||||
return <div className="text-center">{quantity}</div>
|
||||
}
|
||||
header: "Quantity",
|
||||
},
|
||||
{
|
||||
accessorKey: "costPrice",
|
||||
header: () => <div className="text-right">Cost Price</div>,
|
||||
cell: ({ row }) => <div className="text-right font-medium">{formatCurrency(row.getValue("costPrice"))}</div>,
|
||||
header: "Cost Price",
|
||||
cell: ({ row }) => {
|
||||
const amount = parseFloat(row.getValue("costPrice"))
|
||||
const formatted = new Intl.NumberFormat("en-ZA", {
|
||||
style: "currency",
|
||||
currency: "ZAR",
|
||||
}).format(amount)
|
||||
return <div className="text-right font-medium">{formatted}</div>
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "sellingPrice",
|
||||
header: () => <div className="text-right">Selling Price</div>,
|
||||
cell: ({ row }) => <div className="text-right font-medium">{formatCurrency(row.getValue("sellingPrice"))}</div>,
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
header: "Selling Price",
|
||||
cell: ({ row }) => {
|
||||
const status = row.getValue("status") as "In Stock" | "Low Stock" | "Out of Stock"
|
||||
|
||||
const statusColor = {
|
||||
"In Stock": "bg-green-500",
|
||||
"Low Stock": "bg-yellow-500",
|
||||
"Out of Stock": "bg-red-500",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span className={`h-2.5 w-2.5 rounded-full mr-2 ${statusColor[status]}`} />
|
||||
<span className={status !== 'In Stock' ? 'text-xs' : ''}>{status}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const amount = parseFloat(row.getValue("sellingPrice"))
|
||||
const formatted = new Intl.NumberFormat("en-ZA", {
|
||||
style: "currency",
|
||||
currency: "ZAR",
|
||||
}).format(amount)
|
||||
return <div className="text-right font-medium">{formatted}</div>
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ActionsCell,
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const item = row.original
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigator.clipboard.writeText(item.id)}
|
||||
>
|
||||
Copy item ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => setIsEditDialogOpen(true)}>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem> {/* Implement delete functionality later */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Stock Item</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to the stock item here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<EditItemForm setOpen={setIsEditDialogOpen} refreshData={props.refreshData} item={item} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
89
src/pages/Stock.tsx
Normal file
89
src/pages/Stock.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { useState, useEffect, useCallback } from "react"
|
||||
import { createColumns, StockItem } from "../components/stock/columns" // Changed from 'columns' to 'createColumns'
|
||||
import { DataTable } from "../components/stock/data-table"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { PlusCircle } from "lucide-react"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { AddItemForm } from "../components/stock/AddItemForm"
|
||||
import pb from "@/lib/pocketbase"
|
||||
|
||||
const Stock = () => {
|
||||
const [addItemOpen, setAddItemOpen] = useState(false);
|
||||
const [stockItems, setStockItems] = useState<StockItem[]>([]);
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0); // State to trigger data refresh
|
||||
const [filterValue, setFilterValue] = useState(""); // State for filtering
|
||||
|
||||
const fetchStockItems = useCallback(async () => {
|
||||
try {
|
||||
const records = await pb.collection('stock_items').getList(1, 50, {
|
||||
sort: '-created',
|
||||
});
|
||||
const formattedData: StockItem[] = records.items.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
sku: item.sku,
|
||||
category: item.category,
|
||||
quantity: item.quantity,
|
||||
costPrice: item.cost_price,
|
||||
sellingPrice: item.selling_price,
|
||||
}));
|
||||
setStockItems(formattedData);
|
||||
} catch (error) {
|
||||
console.error("Error fetching stock items:", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStockItems();
|
||||
}, [fetchStockItems, refreshTrigger]); // Re-fetch data when refreshTrigger changes
|
||||
|
||||
const handleRefreshData = () => {
|
||||
setRefreshTrigger(prev => prev + 1); // Increment to trigger useEffect
|
||||
};
|
||||
|
||||
// Create columns with the refreshData function
|
||||
const columns = createColumns({ refreshData: handleRefreshData });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Stock</h1>
|
||||
<p className="text-gray-500">Manage your inventory here.</p>
|
||||
</div>
|
||||
<Dialog open={addItemOpen} onOpenChange={setAddItemOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
Add New Item
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New Stock Item</DialogTitle>
|
||||
<DialogDescription>
|
||||
Fill in the details below to add a new item to your stock.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<AddItemForm setOpen={setAddItemOpen} refreshData={handleRefreshData} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={stockItems}
|
||||
filterValue={filterValue} // Pass filterValue
|
||||
onFilterChange={setFilterValue} // Pass onFilterChange
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Stock;
|
||||
|
|
@ -1,33 +1,18 @@
|
|||
import { useState, useEffect, useCallback } from "react";
|
||||
import { columns, StockItem } from "../components/stock/columns";
|
||||
import { createColumns, StockItem } from "../components/stock/columns"; // Changed from 'columns' to 'createColumns'
|
||||
import { DataTable } from "../components/stock/data-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PlusCircle, Camera } from "lucide-react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { AddItemForm } from "../components/stock/AddItemForm";
|
||||
import QRScannerDialog from "../components/stock/QRScannerDialog";
|
||||
import pb from "@/lib/pocketbase";
|
||||
import pb from "@/lib/pocketbase"; // Import PocketBase client
|
||||
|
||||
const StockList = () => {
|
||||
const [addItemOpen, setAddItemOpen] = useState(false);
|
||||
const [scannerOpen, setScannerOpen] = useState(false);
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
const [data, setData] = useState<StockItem[]>([]);
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0); // New state to trigger data refresh
|
||||
const [stockItems, setStockItems] = useState<StockItem[]>([]);
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0); // State to trigger data refresh
|
||||
const [filterValue, setFilterValue] = useState(""); // State for filtering
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
const fetchStockItems = useCallback(async () => {
|
||||
try {
|
||||
const records = await pb.collection('stock_items').getList(1, 50, {
|
||||
sort: '-created',
|
||||
});
|
||||
// Map PocketBase records to StockItem type, adjusting field names
|
||||
const formattedData: StockItem[] = records.items.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
|
|
@ -36,68 +21,38 @@ const StockList = () => {
|
|||
quantity: item.quantity,
|
||||
costPrice: item.cost_price,
|
||||
sellingPrice: item.selling_price,
|
||||
status: item.quantity > 10 ? "In Stock" : (item.quantity > 0 ? "Low Stock" : "Out of Stock"), // Example status logic
|
||||
}));
|
||||
setData(formattedData);
|
||||
setStockItems(formattedData);
|
||||
} catch (error) {
|
||||
console.error("Error fetching stock items:", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData, refreshTrigger]); // Add refreshTrigger to dependencies
|
||||
|
||||
const handleScanSuccess = (decodedText: string) => {
|
||||
setFilterValue(decodedText);
|
||||
setScannerOpen(false);
|
||||
};
|
||||
fetchStockItems();
|
||||
}, [fetchStockItems, refreshTrigger]); // Re-fetch data when refreshTrigger changes
|
||||
|
||||
const handleRefreshData = () => {
|
||||
setRefreshTrigger(prev => prev + 1); // Increment to trigger useEffect
|
||||
};
|
||||
|
||||
// Create columns with the refreshData function
|
||||
const columns = createColumns({ refreshData: handleRefreshData });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<QRScannerDialog
|
||||
open={scannerOpen}
|
||||
onOpenChange={setScannerOpen}
|
||||
onScanSuccess={handleScanSuccess}
|
||||
/>
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4 mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Stock List</h1>
|
||||
<p className="text-gray-500">Manage your inventory here.</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="outline" onClick={() => setScannerOpen(true)}>
|
||||
<Camera className="mr-2 h-4 w-4" />
|
||||
Scan Barcode
|
||||
</Button>
|
||||
<Dialog open={addItemOpen} onOpenChange={setAddItemOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
Add New Item
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New Stock Item</DialogTitle>
|
||||
<DialogDescription>
|
||||
Fill in the details below to add a new item to your inventory.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<AddItemForm setOpen={setAddItemOpen} refreshData={handleRefreshData} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<p className="text-gray-500">View all your inventory items.</p>
|
||||
</div>
|
||||
{/* No Add New Item button here, as this is a list-only page */}
|
||||
</div>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
filterValue={filterValue}
|
||||
onFilterChange={setFilterValue}
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={stockItems}
|
||||
filterValue={filterValue} // Pass filterValue
|
||||
onFilterChange={setFilterValue} // Pass onFilterChange
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue