Files
star-erp/resources/js/Pages/Sales/Import/Index.tsx
sky121113 b6fe9ad9f3
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 1m8s
feat: 實作銷售單匯入管理、貨道扣庫優化及 UI 細節調整
2026-02-09 14:36:47 +08:00

204 lines
10 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.
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link } from '@inertiajs/react';
import { Button } from '@/Components/ui/button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/Components/ui/table';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/Components/ui/alert-dialog";
import { Badge } from "@/Components/ui/badge";
import { Plus, FileUp, Eye, Trash2 } from 'lucide-react';
import { useState, useEffect } from "react";
import { format } from 'date-fns';
import Pagination from "@/Components/shared/Pagination";
import { SearchableSelect } from "@/Components/ui/searchable-select";
import { router } from "@inertiajs/react";
interface ImportBatch {
id: number;
import_date: string;
status: 'pending' | 'confirmed';
total_quantity: number;
total_amount: number;
importer?: {
name: string;
};
created_at: string;
}
interface Props {
batches: {
data: ImportBatch[];
links: any[]; // Pagination links
};
filters?: { // Add filters prop if not present, though we main need per_page state
per_page?: string;
}
}
export default function SalesImportIndex({ batches, filters = {} }: Props) {
const [perPage, setPerPage] = useState(filters?.per_page?.toString() || "10");
useEffect(() => {
if (filters?.per_page) {
setPerPage(filters.per_page.toString());
}
}, [filters?.per_page]);
const handlePerPageChange = (value: string) => {
setPerPage(value);
router.get(
route("sales-imports.index"),
{ per_page: value },
{ preserveState: true, preserveScroll: true, replace: true }
);
};
return (
<AuthenticatedLayout
breadcrumbs={[
{ label: '銷售管理', href: '#' },
{ label: '銷售單匯入', href: route('sales-imports.index'), isPage: true },
]}
>
<Head title="銷售單匯入管理" />
<div className="container mx-auto p-6 max-w-7xl">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
<FileUp className="h-6 w-6 text-primary-main" />
</h1>
<p className="text-gray-500 mt-1">
</p>
</div>
<Link href={route('sales-imports.create')}>
<Button className="button-filled-primary gap-2">
<Plus className="h-4 w-4" />
</Button>
</Link>
</div>
<div className="bg-white rounded-lg border shadow-sm overflow-hidden">
<Table>
<TableHeader className="bg-gray-50">
<TableRow>
<TableHead className="w-[100px]">ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-center w-[120px]"></TableHead>
<TableHead className="text-right w-[150px]"></TableHead>
<TableHead className="text-center w-[100px]"></TableHead>
<TableHead className="text-center w-[120px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{batches.data.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8 text-gray-500">
</TableCell>
</TableRow>
) : (
batches.data.map((batch) => (
<TableRow key={batch.id} className="hover:bg-gray-50/50">
<TableCell className="font-medium">#{batch.id}</TableCell>
<TableCell>
{format(new Date(batch.created_at), 'yyyy/MM/dd HH:mm')}
</TableCell>
<TableCell>{batch.importer?.name || '--'}</TableCell>
<TableCell className="text-center font-bold text-gray-900">
{Math.floor(batch.total_quantity || 0).toLocaleString()}
</TableCell>
<TableCell className="text-right font-bold text-primary-main">
NT$ {Number(batch.total_amount || 0).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })}
</TableCell>
<TableCell className="text-center">
<Badge variant={batch.status === 'confirmed' ? 'default' : 'secondary'}>
{batch.status === 'confirmed' ? '已確認' : '待確認'}
</Badge>
</TableCell>
<TableCell>
<div className="flex justify-center gap-2">
<Link href={route('sales-imports.show', batch.id)}>
<Button variant="outline" size="sm" className="button-outlined-primary" title="查看詳情">
<Eye className="h-4 w-4" />
</Button>
</Link>
{batch.status === 'pending' && (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline" size="sm" className="button-outlined-error" title="刪除">
<Trash2 className="h-4 w-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
#{batch.id}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
className="bg-red-600 hover:bg-red-700"
onClick={() => router.delete(route('sales-imports.destroy', batch.id), { preserveScroll: true })}
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span></span>
<SearchableSelect
value={perPage}
onValueChange={handlePerPageChange}
options={[
{ label: "10", value: "10" },
{ label: "20", value: "20" },
{ label: "50", value: "50" },
{ label: "100", value: "100" },
]}
className="w-[100px] h-8"
showSearch={false}
/>
<span></span>
</div>
<Pagination links={batches.links} />
</div>
</div>
</AuthenticatedLayout>
);
}