Scaffold Gishen Admin: React/Vite shadcn console with role modules, EN/AM i18n, mocks, and backend spec. Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
5.3 KiB
TypeScript
134 lines
5.3 KiB
TypeScript
import { useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { toast } from 'sonner'
|
|
import { PageShell } from '@/layouts/PageShell'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
import { branches, stockRows } from '@/mocks/data'
|
|
|
|
export function StockPage() {
|
|
const { t } = useTranslation()
|
|
const [entry, setEntry] = useState('')
|
|
const [rows, setRows] = useState(stockRows)
|
|
|
|
const duplicates = entry.length > 2
|
|
? rows.filter((r) => r.name.toLowerCase().includes(entry.toLowerCase()) || r.sku.toLowerCase().includes(entry.toLowerCase()))
|
|
: []
|
|
|
|
const resolve = (sku: string) => {
|
|
setRows((prev) => prev.map((r) => (r.sku === sku ? { ...r, qty: r.erpQty } : r)))
|
|
toast.success(`Discrepancy resolved for ${sku}`)
|
|
}
|
|
|
|
return (
|
|
<PageShell title={t('stock.title')} subtitle={t('stock.subtitle')}>
|
|
<Tabs defaultValue="inventory">
|
|
<TabsList>
|
|
<TabsTrigger value="inventory">Inventory</TabsTrigger>
|
|
<TabsTrigger value="erp">ERP sync</TabsTrigger>
|
|
<TabsTrigger value="entry">AI-assisted entry</TabsTrigger>
|
|
<TabsTrigger value="transfers">Transfers</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="inventory">
|
|
<Card>
|
|
<CardContent className="p-4">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>SKU</TableHead>
|
|
<TableHead>Name</TableHead>
|
|
<TableHead>Branch</TableHead>
|
|
<TableHead>Qty</TableHead>
|
|
<TableHead>Batch</TableHead>
|
|
<TableHead>Expiry</TableHead>
|
|
<TableHead>Alert</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rows.map((r) => (
|
|
<TableRow key={`${r.sku}-${r.branchId}`}>
|
|
<TableCell className="font-medium">{r.sku}</TableCell>
|
|
<TableCell>{r.name}</TableCell>
|
|
<TableCell>{branches.find((b) => b.id === r.branchId)?.name}</TableCell>
|
|
<TableCell>{r.qty}</TableCell>
|
|
<TableCell>{r.batch}</TableCell>
|
|
<TableCell>{r.expiry}</TableCell>
|
|
<TableCell>
|
|
{r.qty < 20 ? <Badge variant="warning">low stock</Badge> : <Badge variant="success">ok</Badge>}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
<TabsContent value="erp">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Platform vs ERP discrepancies</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3 p-4 pt-0">
|
|
{rows
|
|
.filter((r) => r.qty !== r.erpQty)
|
|
.map((r) => (
|
|
<div key={r.sku} className="flex items-center justify-between rounded-xl border p-3">
|
|
<div>
|
|
<p className="font-medium">{r.sku}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Platform {r.qty} · ERP {r.erpQty}
|
|
</p>
|
|
</div>
|
|
<Button size="sm" onClick={() => resolve(r.sku)}>
|
|
Resolve to ERP
|
|
</Button>
|
|
</div>
|
|
))}
|
|
{rows.every((r) => r.qty === r.erpQty) && (
|
|
<p className="text-sm text-muted-foreground">No open discrepancies</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
<TabsContent value="entry">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Duplicate detection while typing</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<Input
|
|
placeholder="Name, barcode, or SKU…"
|
|
value={entry}
|
|
onChange={(e) => setEntry(e.target.value)}
|
|
/>
|
|
{duplicates.map((d) => (
|
|
<div key={d.sku} className="rounded-xl border border-orange/30 bg-orange/5 p-3 text-sm">
|
|
Possible match: <strong>{d.name}</strong> ({d.sku}) — merge requires human approval
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
<TabsContent value="transfers">
|
|
<Card>
|
|
<CardContent className="space-y-3 p-5">
|
|
<p className="text-sm text-muted-foreground">
|
|
Stock Manager owns inter-branch transfers. Suggest move of Vitamin C from Bole → CMC (fast mover).
|
|
</p>
|
|
<Button
|
|
onClick={() => toast.success('Transfer draft created (mock)')}
|
|
>
|
|
Create transfer suggestion
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</PageShell>
|
|
)
|
|
}
|