nav-next/components/tools/sp-inventory-management/inventory-management.tsx
mei 6440d171b9 feat: 添加新的工具类别和工具项
- 新增了三个新的工具项:仓库管理器、仓库管理器(特供版)和检查单
- 添加 README.md
2025-01-27 08:34:05 +08:00

1095 lines
42 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, useEffect } from 'react'
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Plus, Minus, Package, Trash2, Search, BarChart, Download, Upload, Edit2, Save, Warehouse, TrendingUp, Users, ShoppingCart } from 'lucide-react'
import { motion } from "framer-motion"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { BarChart as RechartsBarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import { format } from 'date-fns'
interface InventoryItem {
id: number;
name: string;
quantity: number;
warehouse: string;
unitPrice: number;
supplier: string;
}
interface Supplier {
id: number;
name: string;
}
interface Product {
id: number;
name: string;
}
interface OutboundRecord {
id: number;
productId: number;
productName: string;
quantity: number;
recipient: string;
date: string;
warehouse: string;
supplier: string;
}
const fadeIn = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.5 }
}
export default function InventoryManagement() {
const [inventory, setInventory] = useState<InventoryItem[]>([])
const [newItemName, setNewItemName] = useState("")
const [newItemQuantity, setNewItemQuantity] = useState("")
const [newItemWarehouse, setNewItemWarehouse] = useState("")
const [newItemUnitPrice, setNewItemUnitPrice] = useState("")
const [newItemSupplier, setNewItemSupplier] = useState("")
const [searchTerm, setSearchTerm] = useState("")
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const [itemToDelete, setItemToDelete] = useState<number | null>(null)
const [editingItemId, setEditingItemId] = useState<number | null>(null)
const [editingItemName, setEditingItemName] = useState("")
const [warehouses, setWarehouses] = useState<string[]>([])
const [activeTab, setActiveTab] = useState("inventory")
const [suppliers, setSuppliers] = useState<Supplier[]>([])
const [products, setProducts] = useState<Product[]>([])
const [outboundRecords, setOutboundRecords] = useState<OutboundRecord[]>([])
const [sortConfig, setSortConfig] = useState<{ key: keyof InventoryItem | 'totalValue'; direction: 'ascending' | 'descending' } | null>(null)
// Load data from localStorage on component mount
useEffect(() => {
const savedInventory = localStorage.getItem('inventory')
const savedWarehouses = localStorage.getItem('warehouses')
const savedSuppliers = localStorage.getItem('suppliers')
const savedProducts = localStorage.getItem('products')
const savedOutboundRecords = localStorage.getItem('outboundRecords')
if (savedInventory) setInventory(JSON.parse(savedInventory))
if (savedWarehouses) setWarehouses(JSON.parse(savedWarehouses))
if (savedSuppliers) setSuppliers(JSON.parse(savedSuppliers))
if (savedProducts) setProducts(JSON.parse(savedProducts))
if (savedOutboundRecords) setOutboundRecords(JSON.parse(savedOutboundRecords))
}, [])
// Save data to localStorage when it changes
useEffect(() => {
localStorage.setItem('inventory', JSON.stringify(inventory))
localStorage.setItem('warehouses', JSON.stringify(warehouses))
localStorage.setItem('suppliers', JSON.stringify(suppliers))
localStorage.setItem('products', JSON.stringify(products))
localStorage.setItem('outboundRecords', JSON.stringify(outboundRecords))
}, [inventory, warehouses, suppliers, products, outboundRecords])
const addItem = () => {
if (newItemName && newItemQuantity && newItemWarehouse && newItemUnitPrice && newItemSupplier) {
const newItem: InventoryItem = {
id: Date.now(),
name: newItemName,
quantity: parseInt(newItemQuantity),
warehouse: newItemWarehouse,
unitPrice: parseFloat(newItemUnitPrice),
supplier: newItemSupplier,
}
setInventory([...inventory, newItem])
setNewItemName("")
setNewItemQuantity("")
setNewItemWarehouse("")
setNewItemUnitPrice("")
setNewItemSupplier("")
}
}
const updateQuantity = (id: number, change: number) => {
setInventory(inventory.map(item => {
if (item.id === id) {
const newQuantity = Math.max(0, item.quantity + change)
return { ...item, quantity: newQuantity }
}
return item
}))
}
const confirmDelete = (id: number) => {
setItemToDelete(id)
setDeleteConfirmOpen(true)
}
const deleteItem = () => {
if (itemToDelete !== null) {
setInventory(inventory.filter(item => item.id !== itemToDelete))
setDeleteConfirmOpen(false)
setItemToDelete(null)
}
}
const startEditing = (id: number, name: string) => {
setEditingItemId(id)
setEditingItemName(name)
}
const saveEdit = () => {
if (editingItemId !== null) {
setInventory(inventory.map(item => {
if (item.id === editingItemId) {
return { ...item, name: editingItemName }
}
return item
}))
setEditingItemId(null)
setEditingItemName("")
}
}
const filteredInventory = inventory.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.warehouse.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.supplier.toLowerCase().includes(searchTerm.toLowerCase())
)
const sortedInventory = sortConfig !== null
? [...filteredInventory].sort((a, b) => {
if (sortConfig.key === 'totalValue') {
const aTotal = a.quantity * a.unitPrice;
const bTotal = b.quantity * b.unitPrice;
return sortConfig.direction === 'ascending' ? aTotal - bTotal : bTotal - aTotal;
}
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
})
: filteredInventory;
const requestSort = (key: keyof InventoryItem | 'totalValue') => {
let direction: 'ascending' | 'descending' = 'ascending';
if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') {
direction = 'descending';
}
setSortConfig({ key, direction });
}
const exportData = () => {
const dataStr = JSON.stringify({
inventory,
warehouses,
suppliers,
products,
outboundRecords
})
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr)
const exportFileDefaultName = 'inventory_backup.json'
const linkElement = document.createElement('a')
linkElement.setAttribute('href', dataUri)
linkElement.setAttribute('download', exportFileDefaultName)
linkElement.click()
}
const importData = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
try {
const importedData = JSON.parse(e.target?.result as string)
setInventory(importedData.inventory)
setWarehouses(importedData.warehouses)
setSuppliers(importedData.suppliers)
setProducts(importedData.products)
setOutboundRecords(importedData.outboundRecords)
} catch (error) {
console.error('Error parsing imported data:', error)
alert('导入失败,请确保文件格式正确。')
}
}
reader.readAsText(file)
}
}
const getInventoryChartData = () => {
return inventory.map(item => ({
name: item.name,
quantity: item.quantity,
totalValue: item.quantity * item.unitPrice
}))
}
const addOutboundRecord = (productId: number, productName: string, quantity: number, recipient: string, warehouse: string, supplier: string) => {
const newRecord: OutboundRecord = {
id: Date.now(),
productId,
productName,
quantity,
recipient,
date: new Date().toISOString(),
warehouse,
supplier
}
setOutboundRecords(prevRecords => [...prevRecords, newRecord])
// 更新库存
setInventory(prevInventory =>
prevInventory.map(item =>
item.id === productId
? { ...item, quantity: Math.max(0, item.quantity - quantity) }
: item
)
)
}
const updateWarehouseName = (oldName: string, newName: string) => {
setInventory(prevInventory =>
prevInventory.map(item =>
item.warehouse === oldName ? { ...item, warehouse: newName } : item
)
)
setOutboundRecords(prevRecords =>
prevRecords.map(record =>
record.warehouse === oldName ? { ...record, warehouse: newName } : record
)
)
}
const updateSupplierName = (oldName: string, newName: string) => {
setInventory(prevInventory =>
prevInventory.map(item =>
item.supplier === oldName ? { ...item, supplier: newName } : item
)
)
setOutboundRecords(prevRecords =>
prevRecords.map(record =>
record.supplier === oldName ? { ...record, supplier: newName } : record
)
)
}
const updateProductName = (oldName: string, newName: string) => {
setInventory(prevInventory =>
prevInventory.map(item =>
item.name === oldName ? { ...item, name: newName } : item
)
)
setOutboundRecords(prevRecords =>
prevRecords.map(record =>
record.productName === oldName ? { ...record, productName: newName } : record
)
)
}
return (
<div className="container mx-auto p-4 space-y-8">
<header className="text-center bg-gradient-to-r from-blue-500 to-purple-600 text-white py-8 rounded-lg shadow-lg">
<h1 className="text-4xl font-bold mb-2"></h1>
<p className="text-xl"></p>
</header>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-6">
<TabsTrigger value="inventory"></TabsTrigger>
<TabsTrigger value="analytics"></TabsTrigger>
<TabsTrigger value="warehouses"></TabsTrigger>
<TabsTrigger value="suppliers"></TabsTrigger>
<TabsTrigger value="products"></TabsTrigger>
<TabsTrigger value="outbound"></TabsTrigger>
</TabsList>
<TabsContent value="inventory">
<div className="grid gap-8 md:grid-cols-2">
<motion.div {...fadeIn}>
<Card className="h-full">
<CardHeader>
<CardTitle className="flex items-center">
<Package className="mr-2" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4">
<div>
<Label htmlFor="itemName"></Label>
<Select onValueChange={setNewItemName} value={newItemName}>
<SelectTrigger>
<SelectValue placeholder="选择商品" />
</SelectTrigger>
<SelectContent>
{products.map((product) => (
<SelectItem key={product.id} value={product.name}>
{product.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="itemQuantity"></Label>
<Input
id="itemQuantity"
type="number"
value={newItemQuantity}
onChange={(e) => setNewItemQuantity(e.target.value)}
placeholder="输入数量"
/>
</div>
<div>
<Label htmlFor="itemWarehouse"></Label>
<Select onValueChange={setNewItemWarehouse} value={newItemWarehouse}>
<SelectTrigger>
<SelectValue placeholder="选择仓库" />
</SelectTrigger>
<SelectContent>
{warehouses.map((warehouse) => (
<SelectItem key={warehouse} value={warehouse}>
{warehouse}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="itemUnitPrice"></Label>
<Input
id="itemUnitPrice"
type="number"
value={newItemUnitPrice}
onChange={(e) => setNewItemUnitPrice(e.target.value)}
placeholder="输入单价"
/>
</div>
<div>
<Label htmlFor="itemSupplier"></Label>
<Select onValueChange={setNewItemSupplier} value={newItemSupplier}>
<SelectTrigger>
<SelectValue placeholder="选择供应商" />
</SelectTrigger>
<SelectContent>
{suppliers.map((supplier) => (
<SelectItem key={supplier.id} value={supplier.name}>
{supplier.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Button onClick={addItem} className="w-full">
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
</motion.div>
<motion.div {...fadeIn} transition={{ delay: 0.2 }}>
<Card className="h-full">
<CardHeader>
<CardTitle className="flex items-center">
<BarChart className="mr-2" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="mb-4 space-y-4">
<div>
<Label htmlFor="search"></Label>
<div className="flex">
<Input
id="search"
type="text"
placeholder="输入商品名称、仓库或供应商"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="mr-2"
/>
<Button variant="outline">
<Search className="h-4 w-4" />
</Button>
</div>
</div>
<div className="flex space-x-2">
<Button variant="outline" onClick={exportData}>
<Download className="mr-2 h-4 w-4" />
</Button>
<div>
<Input
type="file"
id="importData"
className="hidden"
onChange={importData}
accept=".json"
/>
<Button variant="outline" onClick={() => document.getElementById('importData')?.click()}>
<Upload className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
</div>
<div className="rounded-md border overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead onClick={() => requestSort('name')} className="cursor-pointer"> {sortConfig?.key === 'name' && (sortConfig.direction === 'ascending' ? '↑' : '↓')}</TableHead>
<TableHead onClick={() => requestSort('quantity')} className="cursor-pointer"> {sortConfig?.key === 'quantity' && (sortConfig.direction === 'ascending' ? '↑' : '↓')}</TableHead>
<TableHead onClick={() => requestSort('warehouse')} className="cursor-pointer"> {sortConfig?.key === 'warehouse' && (sortConfig.direction === 'ascending' ? '↑' : '↓')}</TableHead>
<TableHead onClick={() => requestSort('unitPrice')} className="cursor-pointer"> {sortConfig?.key === 'unitPrice' && (sortConfig.direction === 'ascending' ? '↑' : '↓')}</TableHead>
<TableHead onClick={() => requestSort('supplier')} className="cursor-pointer"> {sortConfig?.key === 'supplier' && (sortConfig.direction === 'ascending' ? '↑' : '↓')}</TableHead>
<TableHead onClick={() => requestSort('totalValue')} className="cursor-pointer"> {sortConfig?.key === 'totalValue' && (sortConfig.direction === 'ascending' ? '↑' : '↓')}</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedInventory.map((item) => (
<TableRow key={item.id}>
<TableCell>
{editingItemId === item.id ?
<Input
value={editingItemName}
onChange={(e) => setEditingItemName(e.target.value)}
className="w-full"
/> : item.name
}
</TableCell>
<TableCell>{item.quantity}</TableCell>
<TableCell>{item.warehouse}</TableCell>
<TableCell>{item.unitPrice.toFixed(2)}</TableCell>
<TableCell>{item.supplier}</TableCell>
<TableCell>{(item.quantity * item.unitPrice).toFixed(2)}</TableCell>
<TableCell>
<div className="flex space-x-2">
<Button size="sm" variant="outline" onClick={() => updateQuantity(item.id, 1)}>
<Plus className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" onClick={() => updateQuantity(item.id, -1)}>
<Minus className="h-4 w-4" />
</Button>
{editingItemId === item.id ? (
<Button size="sm" variant="outline" onClick={saveEdit}>
<Save className="h-4 w-4" />
</Button>
) : (
<Button size="sm" variant="outline" onClick={() => startEditing(item.id, item.name)}>
<Edit2 className="h-4 w-4" />
</Button>
)}
<Button size="sm" variant="outline" onClick={() => confirmDelete(item.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</motion.div>
</div>
</TabsContent>
<TabsContent value="analytics">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={400}>
<RechartsBarChart data={getInventoryChartData()}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis yAxisId="left" orientation="left" stroke="#8884d8" />
<YAxis yAxisId="right" orientation="right" stroke="#82ca9d" />
<Tooltip />
<Legend />
<Bar yAxisId="left" dataKey="quantity" fill="#8884d8" name="库存数量" />
<Bar yAxisId="right" dataKey="totalValue" fill="#82ca9d" name="总价值" />
</RechartsBarChart>
</ResponsiveContainer>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="warehouses">
<WarehouseManagement warehouses={warehouses} setWarehouses={setWarehouses} updateWarehouseName={updateWarehouseName} />
</TabsContent>
<TabsContent value="suppliers">
<SupplierManagement suppliers={suppliers} setSuppliers={setSuppliers} updateSupplierName={updateSupplierName} />
</TabsContent>
<TabsContent value="products">
<ProductManagement products={products} setProducts={setProducts} updateProductName={updateProductName} />
</TabsContent>
<TabsContent value="outbound">
<OutboundManagement
inventory={inventory}
setInventory={setInventory}
outboundRecords={outboundRecords}
addOutboundRecord={addOutboundRecord}
products={products}
/>
</TabsContent>
</Tabs>
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={deleteItem}></AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)
}
function WarehouseManagement({
warehouses,
setWarehouses,
updateWarehouseName
}: {
warehouses: string[],
setWarehouses: React.Dispatch<React.SetStateAction<string[]>>,
updateWarehouseName: (oldName: string, newName: string) => void
}) {
const [newWarehouse, setNewWarehouse] = useState("")
const [editingWarehouse, setEditingWarehouse] = useState<string | null>(null)
const [editedWarehouseName, setEditedWarehouseName] = useState("")
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const [warehouseToDelete, setWarehouseToDelete] = useState<string | null>(null)
const addWarehouse = () => {
if (newWarehouse && !warehouses.includes(newWarehouse)) {
setWarehouses([...warehouses, newWarehouse])
setNewWarehouse("")
}
}
const startEditing = (warehouse: string) => {
setEditingWarehouse(warehouse)
setEditedWarehouseName(warehouse)
}
const saveEdit = () => {
if (editingWarehouse && editedWarehouseName) {
setWarehouses(warehouses.map(w => w === editingWarehouse ? editedWarehouseName : w))
updateWarehouseName(editingWarehouse, editedWarehouseName)
setEditingWarehouse(null)
setEditedWarehouseName("")
}
}
const confirmDelete = (warehouse: string) => {
setWarehouseToDelete(warehouse)
setDeleteConfirmOpen(true)
}
const deleteWarehouse = () => {
if (warehouseToDelete) {
setWarehouses(warehouses.filter(w => w !== warehouseToDelete))
setDeleteConfirmOpen(false)
setWarehouseToDelete(null)
}
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Warehouse className="mr-2" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex space-x-2">
<Input
value={newWarehouse}
onChange={(e) => setNewWarehouse(e.target.value)}
placeholder="输入新仓库名称"
/>
<Button onClick={addWarehouse}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{warehouses.map((warehouse) => (
<TableRow key={warehouse}>
<TableCell>
{editingWarehouse === warehouse ? (
<Input
value={editedWarehouseName}
onChange={(e) => setEditedWarehouseName(e.target.value)}
className="w-full"
/>
) : (
warehouse
)}
</TableCell>
<TableCell>
<div className="flex space-x-2">
{editingWarehouse === warehouse ? (
<Button size="sm" variant="outline" onClick={saveEdit}>
<Save className="h-4 w-4" />
</Button>
) : (
<Button size="sm" variant="outline" onClick={() => startEditing(warehouse)}>
<Edit2 className="h-4 w-4" />
</Button>
)}
<Button size="sm" variant="outline" onClick={() => confirmDelete(warehouse)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</CardContent>
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
&quot;{warehouseToDelete}&quot;
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={deleteWarehouse}></AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Card>
)
}
function SupplierManagement({
suppliers,
setSuppliers,
updateSupplierName
}: {
suppliers: Supplier[],
setSuppliers: React.Dispatch<React.SetStateAction<Supplier[]>>,
updateSupplierName: (oldName: string, newName: string) => void
}) {
const [newSupplier, setNewSupplier] = useState("")
const [editingSupplier, setEditingSupplier] = useState<number | null>(null)
const [editedSupplierName, setEditedSupplierName] = useState("")
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const [supplierToDelete, setSupplierToDelete] = useState<number | null>(null)
const addSupplier = () => {
if (newSupplier && !suppliers.some(s => s.name === newSupplier)) {
setSuppliers([...suppliers, { id: Date.now(), name: newSupplier }])
setNewSupplier("")
}
}
const startEditing = (supplier: Supplier) => {
setEditingSupplier(supplier.id)
setEditedSupplierName(supplier.name)
}
const saveEdit = () => {
if (editingSupplier !== null && editedSupplierName) {
const oldName = suppliers.find(s => s.id === editingSupplier)?.name
setSuppliers(suppliers.map(s => s.id === editingSupplier ? { ...s, name: editedSupplierName } : s))
if (oldName) {
updateSupplierName(oldName, editedSupplierName)
}
setEditingSupplier(null)
setEditedSupplierName("")
}
}
const confirmDelete = (id: number) => {
setSupplierToDelete(id)
setDeleteConfirmOpen(true)
}
const deleteSupplier = () => {
if (supplierToDelete !== null) {
setSuppliers(suppliers.filter(s => s.id !== supplierToDelete))
setDeleteConfirmOpen(false)
setSupplierToDelete(null)
}
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Users className="mr-2" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex space-x-2">
<Input
value={newSupplier}
onChange={(e) => setNewSupplier(e.target.value)}
placeholder="输入新供应商名称"
/>
<Button onClick={addSupplier}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{suppliers.map((supplier) => (
<TableRow key={supplier.id}>
<TableCell>
{editingSupplier === supplier.id ? (
<Input
value={editedSupplierName}
onChange={(e) => setEditedSupplierName(e.target.value)}
className="w-full"
/>
) : (
supplier.name
)}
</TableCell>
<TableCell>
<div className="flex space-x-2">
{editingSupplier === supplier.id ? (
<Button size="sm" variant="outline" onClick={saveEdit}>
<Save className="h-4 w-4" />
</Button>
) : (
<Button size="sm" variant="outline" onClick={() => startEditing(supplier)}>
<Edit2 className="h-4 w-4" />
</Button>
)}
<Button size="sm" variant="outline" onClick={() => confirmDelete(supplier.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</CardContent>
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={deleteSupplier}></AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Card>
)
}
function ProductManagement({
products,
setProducts,
updateProductName
}: {
products: Product[],
setProducts: React.Dispatch<React.SetStateAction<Product[]>>,
updateProductName: (oldName: string, newName: string) => void
}) {
const [newProduct, setNewProduct] = useState("")
const [editingProduct, setEditingProduct] = useState<number | null>(null)
const [editedProductName, setEditedProductName] = useState("")
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const [productToDelete, setProductToDelete] = useState<number | null>(null)
const addProduct = () => {
if (newProduct && !products.some(p => p.name === newProduct)) {
setProducts([...products, { id: Date.now(), name: newProduct }])
setNewProduct("")
}
}
const startEditing = (product: Product) => {
setEditingProduct(product.id)
setEditedProductName(product.name)
}
const saveEdit = () => {
if (editingProduct !== null && editedProductName) {
const oldName = products.find(p => p.id === editingProduct)?.name
setProducts(products.map(p => p.id === editingProduct ? { ...p, name: editedProductName } : p))
if (oldName) {
updateProductName(oldName, editedProductName)
}
setEditingProduct(null)
setEditedProductName("")
}
}
const confirmDelete = (id: number) => {
setProductToDelete(id)
setDeleteConfirmOpen(true)
}
const deleteProduct = () => {
if (productToDelete !== null) {
setProducts(products.filter(p => p.id !== productToDelete))
setDeleteConfirmOpen(false)
setProductToDelete(null)
}
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Package className="mr-2" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex space-x-2">
<Input
value={newProduct}
onChange={(e) => setNewProduct(e.target.value)}
placeholder="输入新商品名称"
/>
<Button onClick={addProduct}>
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{products.map((product) => (
<TableRow key={product.id}>
<TableCell>
{editingProduct === product.id ? (
<Input
value={editedProductName}
onChange={(e) => setEditedProductName(e.target.value)}
className="w-full"
/>
) : (
product.name
)}
</TableCell>
<TableCell>
<div className="flex space-x-2">
{editingProduct === product.id ? (
<Button size="sm" variant="outline" onClick={saveEdit}>
<Save className="h-4 w-4" />
</Button>
) : (
<Button size="sm" variant="outline" onClick={() => startEditing(product)}>
<Edit2 className="h-4 w-4" />
</Button>
)}
<Button size="sm" variant="outline" onClick={() => confirmDelete(product.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</CardContent>
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={deleteProduct}></AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Card>
)
}
function OutboundManagement({
inventory,
outboundRecords,
addOutboundRecord
}: {
inventory: InventoryItem[],
setInventory: React.Dispatch<React.SetStateAction<InventoryItem[]>>,
outboundRecords: OutboundRecord[],
addOutboundRecord: (productId: number, productName: string, quantity: number, recipient: string, warehouse: string, supplier: string) => void,
products: Product[]
}) {
const [selectedProduct, setSelectedProduct] = useState("")
const [outboundQuantity, setOutboundQuantity] = useState("")
const [recipient, setRecipient] = useState("")
const handleOutbound = () => {
const product = inventory.find(item => item.name === selectedProduct)
if (product && outboundQuantity && recipient) {
const quantity = parseInt(outboundQuantity)
if (quantity > product.quantity) {
alert('出库数量不能大于库存数量!')
return
}
addOutboundRecord(product.id, product.name, quantity, recipient, product.warehouse, product.supplier)
// 清空输入框
setSelectedProduct("")
setOutboundQuantity("")
setRecipient("")
}
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<ShoppingCart className="mr-2" />
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<Label htmlFor="product"></Label>
<Select onValueChange={setSelectedProduct} value={selectedProduct}>
<SelectTrigger>
<SelectValue placeholder="选择商品" />
</SelectTrigger>
<SelectContent>
{inventory.map((item) => (
<SelectItem key={item.id} value={item.name}>
{item.name} (: {item.quantity})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="quantity"></Label>
<Input
id="quantity"
type="number"
value={outboundQuantity}
onChange={(e) => setOutboundQuantity(e.target.value)}
placeholder="输入数量"
/>
</div>
<div>
<Label htmlFor="recipient"></Label>
<Input
id="recipient"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
placeholder="输入接收方"
/>
</div>
<div className="flex items-end">
<Button onClick={handleOutbound} className="w-full">
<TrendingUp className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{outboundRecords.map((record) => (
<TableRow key={record.id}>
<TableCell>{record.productName}</TableCell>
<TableCell>{record.quantity}</TableCell>
<TableCell>{record.recipient}</TableCell>
<TableCell>{record.warehouse}</TableCell>
<TableCell>{record.supplier}</TableCell>
<TableCell>{format(new Date(record.date), 'yyyy-MM-dd HH:mm:ss')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</CardContent>
</Card>
)
}