feat: 統一各模組分頁組件佈局並新增系統設定功能相關檔案
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 1m5s

This commit is contained in:
2026-02-25 16:16:49 +08:00
parent 878b90e2ad
commit e3df090afd
59 changed files with 889 additions and 299 deletions

View File

@@ -307,6 +307,13 @@ export default function AuthenticatedLayout({
route: "/admin/activity-logs",
permission: "system.view_logs",
},
{
id: "system-settings",
label: "系統設定",
icon: <Settings className="h-4 w-4" />,
route: "/admin/settings",
permission: "system.settings.view",
},
{
id: "manual",
label: "操作手冊",

View File

@@ -260,7 +260,7 @@ export default function AccountPayableIndex({ payables, filters, vendors }: any)
</Table>
</div>
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span></span>
@@ -278,7 +278,7 @@ export default function AccountPayableIndex({ payables, filters, vendors }: any)
/>
<span></span>
</div>
<span className="text-sm text-gray-500"> {payables.total} </span>
<span className="text-sm text-gray-500"> {payables.total} </span>
</div>
<Pagination links={payables.links} />
</div>

View File

@@ -365,21 +365,24 @@ export default function AccountingReport({ records, summary, filters }: PageProp
{/* Pagination Footer */}
<div className="mt-6 flex flex-col sm:flex-row items-start sm: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 className="flex items-center 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>
<span className="text-sm text-gray-500"> {records.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={records.links} />

View File

@@ -318,22 +318,25 @@ export default function ActivityLogIndex({ activities, filters, subject_types, u
from={activities.from}
/>
<div className="mt-4 flex flex-col md: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {activities.total} </span>
</div>
<div className="w-full md:w-auto flex justify-center md:justify-end">
<Pagination links={activities.links} />

View File

@@ -0,0 +1,178 @@
import React from "react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, useForm } from "@inertiajs/react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle
} from "@/Components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/Components/ui/tabs";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import { Button } from "@/Components/ui/button";
import {
Coins,
Package,
RefreshCcw,
Monitor,
Save,
Settings
} from "lucide-react";
import { toast } from "sonner";
interface Setting {
key: string;
value: string;
description: string;
}
interface PageProps {
settings: Record<string, Setting[]>;
}
export default function SettingIndex({ settings }: PageProps) {
const { data, setData, post, processing } = useForm({
settings: Object.values(settings).flat().map(s => ({
key: s.key,
value: s.value
}))
});
const handleValueChange = (key: string, value: string) => {
const newSettings = data.settings.map(s =>
s.key === key ? { ...s, value } : s
);
setData('settings', newSettings);
};
const submit = (e: React.FormEvent) => {
e.preventDefault();
post(route('settings.update'), {
onSuccess: () => toast.success("系統設定已更新"),
});
};
const renderSettingRow = (setting: Setting) => {
const currentVal = data.settings.find(s => s.key === setting.key)?.value || '';
return (
<div key={setting.key} className="grid grid-cols-1 md:grid-cols-2 gap-4 items-center py-4 border-b last:border-0 border-gray-100">
<div>
<Label className="text-base font-semibold text-grey-0">{setting.description}</Label>
<p className="text-sm text-gray-500 mt-1">{setting.key}</p>
</div>
<div>
<Input
type="text"
value={currentVal}
onChange={(e) => handleValueChange(setting.key, e.target.value)}
className="max-w-xs"
/>
</div>
</div>
);
};
return (
<AuthenticatedLayout
breadcrumbs={[
{ label: "系統管理", href: "#" },
{ label: "系統設定" }
]}
>
<Head title="系統設定" />
<div className="container mx-auto p-6 max-w-7xl">
<div className="mb-6">
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<Settings className="h-6 w-6 text-primary-main" />
</h1>
<p className="text-gray-500 mt-2">
</p>
</div>
<form onSubmit={submit}>
<Tabs defaultValue="finance" className="space-y-6">
<TabsList className="bg-white border p-1 h-auto gap-2">
<TabsTrigger value="finance" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<Coins className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="inventory" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<Package className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="turnover" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<RefreshCcw className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="display" className="gap-2 py-2 data-[state=active]:button-filled-primary data-[state=active]:text-white">
<Monitor className="h-4 w-4" />
</TabsTrigger>
</TabsList>
<TabsContent value="finance">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.finance?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="inventory">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.inventory?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="turnover">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>調</CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.turnover?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="display">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-1">
{settings.display?.map(renderSettingRow)}
</CardContent>
</Card>
</TabsContent>
<div className="flex justify-end gap-4 mt-6">
<Button
type="submit"
className="button-filled-primary"
disabled={processing}
>
<Save className="h-4 w-4 mr-2" />
{processing ? "儲存中..." : "儲存設定"}
</Button>
</div>
</Tabs>
</form>
</div>
</AuthenticatedLayout>
);
}

View File

@@ -56,6 +56,7 @@ interface Props {
users: {
data: User[];
from: number;
total: number;
links: PaginationLinks[];
};
filters: {
@@ -394,22 +395,25 @@ export default function UserIndex({ users, roles, filters }: Props) {
</div>
{/* 分頁元件 - 統一樣式 */}
<div className="mt-4 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {users.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={users.links} />

View File

@@ -280,9 +280,7 @@ export default function SalesOrderIndex({ orders, filters }: Props) {
/>
<span></span>
</div>
<span className="text-sm text-gray-500">
{orders.total}
</span>
<span className="text-sm text-gray-500"> {orders.total} </span>
</div>
<Pagination links={orders.links} />
</div>

View File

@@ -353,7 +353,7 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
</Table>
</div>
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span></span>
@@ -371,7 +371,7 @@ export default function Index({ docs, warehouses, filters }: { docs: DocsPaginat
/>
<span></span>
</div>
<span className="text-sm text-gray-500"> {docs?.total || 0} </span>
<span className="text-sm text-gray-500"> {docs.total} </span>
</div>
<Pagination links={docs.links} />
</div>

View File

@@ -415,22 +415,25 @@ export default function InventoryAnalysisIndex({ analysisData, kpis, warehouses,
</div>
{/* Pagination Footer */}
<div className="mt-6 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {analysisData.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={analysisData.links} />

View File

@@ -368,7 +368,7 @@ export default function Index({ docs, warehouses, filters }: any) {
</Table>
</div>
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span></span>
@@ -386,7 +386,7 @@ export default function Index({ docs, warehouses, filters }: any) {
/>
<span></span>
</div>
<span className="text-sm text-gray-500"> {docs.total} </span>
<span className="text-sm text-gray-500"> {docs.total} </span>
</div>
<Pagination links={docs.links} />
</div>

View File

@@ -45,7 +45,7 @@ export default function GoodsReceiptIndex({ receipts, filters, warehouses }: Pro
const [warehouseId, setWarehouseId] = useState(filters.warehouse_id || 'all');
const [dateStart, setDateStart] = useState(filters.date_start || '');
const [dateEnd, setDateEnd] = useState(filters.date_end || '');
const [perPage, setPerPage] = useState(filters.per_page || '10');
const [perPage, setPerPage] = useState(filters.per_page || receipts.per_page?.toString() || '10');
const [dateRangeType, setDateRangeType] = useState('custom');
// Advanced Filter Toggle
@@ -58,7 +58,7 @@ export default function GoodsReceiptIndex({ receipts, filters, warehouses }: Pro
setWarehouseId(filters.warehouse_id || 'all');
setDateStart(filters.date_start || '');
setDateEnd(filters.date_end || '');
setPerPage(filters.per_page || '10');
setPerPage(filters.per_page || receipts.per_page?.toString() || '10');
}, [filters]);
const handleFilter = () => {
@@ -285,22 +285,25 @@ export default function GoodsReceiptIndex({ receipts, filters, warehouses }: Pro
<GoodsReceiptTable receipts={receipts.data} />
{/* 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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {receipts.total} </span>
</div>
<Pagination links={receipts.links} />
</div>

View File

@@ -581,22 +581,25 @@ export default function InventoryReportIndex({ reportData, summary, warehouses,
</div>
{/* Pagination Footer */}
<div className="mt-6 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {reportData.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={reportData.links} />

View File

@@ -123,7 +123,7 @@ export default function StockQueryIndex({
}: Props) {
const [search, setSearch] = useState(filters.search || "");
const [perPage, setPerPage] = useState<string>(
filters.per_page || "10"
filters.per_page || inventories.per_page?.toString() || "10"
);
// 執行篩選

View File

@@ -386,7 +386,7 @@ export default function Index({ warehouses, orders, filters }: any) {
</Table>
</div>
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span></span>
@@ -404,7 +404,7 @@ export default function Index({ warehouses, orders, filters }: any) {
/>
<span></span>
</div>
<span className="text-sm text-gray-500"> {orders.total} </span>
<span className="text-sm text-gray-500"> {orders.total} </span>
</div>
<Pagination links={orders.links} />
</div>

View File

@@ -51,6 +51,9 @@ interface PageProps {
data: Product[];
links: any[]; // Todo: pagination types
from: number;
per_page: number;
current_page: number;
total: number;
};
categories: Category[];
units: Unit[];
@@ -67,7 +70,7 @@ export default function ProductManagement({ products, categories, units, filters
const { branding } = usePage<GlobalPageProps>().props;
const [searchTerm, setSearchTerm] = useState(filters.search || "");
const [typeFilter, setTypeFilter] = useState<string>(filters.category_id || "all");
const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
const [perPage, setPerPage] = useState<string>(filters.per_page || products.per_page?.toString() || "10");
const [sortField, setSortField] = useState<string | null>(filters.sort_field || null);
const [sortDirection, setSortDirection] = useState<"asc" | "desc" | null>(filters.sort_direction as "asc" | "desc" || null);
const [isCategoryDialogOpen, setIsCategoryDialogOpen] = useState(false);
@@ -78,12 +81,10 @@ export default function ProductManagement({ products, categories, units, filters
useEffect(() => {
setSearchTerm(filters.search || "");
setTypeFilter(filters.category_id || "all");
setSearchTerm(filters.search || "");
setTypeFilter(filters.category_id || "all");
setPerPage(filters.per_page || "10");
setPerPage(filters.per_page || products.per_page?.toString() || "10");
setSortField(filters.sort_field || null);
setSortDirection(filters.sort_direction as "asc" | "desc" || null);
}, [filters]);
}, [filters, products.per_page]);
const handleSort = (field: string) => {
let newField: string | null = field;
@@ -270,22 +271,25 @@ export default function ProductManagement({ products, categories, units, filters
/>
{/* 分頁元件 */}
<div className="mt-4 flex flex-col sm:flex-row items-start sm: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-[90px] h-8"
showSearch={false}
/>
<span></span>
<div className="mt-6 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div className="flex items-center 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-[90px] h-8"
showSearch={false}
/>
<span></span>
</div>
<span className="text-sm text-gray-500"> {products.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={products.links} />

View File

@@ -43,6 +43,7 @@ interface Props {
data: ProductionOrder[];
links: any[];
total: number;
per_page: number;
from: number;
to: number;
};
@@ -66,12 +67,12 @@ const statusOptions = [
export default function ProductionIndex({ productionOrders, filters }: Props) {
const [search, setSearch] = useState(filters.search || "");
const [status, setStatus] = useState<string>(filters.status || "all");
const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
const [perPage, setPerPage] = useState<string>(filters.per_page || productionOrders.per_page?.toString() || "10");
useEffect(() => {
setSearch(filters.search || "");
setStatus(filters.status || "all");
setPerPage(filters.per_page || "10");
setPerPage(filters.per_page || productionOrders.per_page?.toString() || "10");
}, [filters]);
const handleFilter = () => {
@@ -302,22 +303,25 @@ export default function ProductionIndex({ productionOrders, filters }: Props) {
</div>
{/* 分頁 */}
<div className="mt-4 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {productionOrders.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={productionOrders.links} />

View File

@@ -306,22 +306,25 @@ export default function RecipeIndex({ recipes, filters }: Props) {
</div>
{/* 分頁 */}
<div className="mt-4 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {recipes.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={recipes.links} />

View File

@@ -30,6 +30,7 @@ interface Props {
data: PurchaseOrder[];
links: any[];
total: number;
per_page: number;
from: number;
to: number;
};
@@ -53,7 +54,7 @@ export default function PurchaseOrderIndex({ orders, filters, warehouses }: Prop
const [warehouseId, setWarehouseId] = useState<string>(filters.warehouse_id || "all");
const [dateStart, setDateStart] = useState(filters.date_start || "");
const [dateEnd, setDateEnd] = useState(filters.date_end || "");
const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
const [perPage, setPerPage] = useState<string>(filters.per_page || orders.per_page?.toString() || "10");
const [dateRangeType, setDateRangeType] = useState('custom');
// Advanced Filter Toggle
@@ -66,7 +67,7 @@ export default function PurchaseOrderIndex({ orders, filters, warehouses }: Prop
setWarehouseId(filters.warehouse_id || "all");
setDateStart(filters.date_start || "");
setDateEnd(filters.date_end || "");
setPerPage(filters.per_page || "10");
setPerPage(filters.per_page || orders.per_page?.toString() || "10");
}, [filters]);
const handleFilter = () => {
@@ -295,22 +296,25 @@ export default function PurchaseOrderIndex({ orders, filters, warehouses }: Prop
/>
{/* 分頁元件 - 統一樣式 */}
<div className="mt-4 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {orders.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={orders.links} />

View File

@@ -14,6 +14,7 @@ import { getBreadcrumbs } from "@/utils/breadcrumb";
import { Can } from "@/Components/Permission/Can";
import { Input } from "@/Components/ui/input";
import { Label } from "@/Components/ui/label";
import { SearchableSelect } from "@/Components/ui/searchable-select";
import {
Select,
SelectContent,
@@ -34,17 +35,20 @@ interface Props {
filters: {
search?: string;
status?: string;
per_page?: string;
};
}
export default function PurchaseReturnIndex({ purchaseReturns, filters }: Props) {
const [search, setSearch] = useState(filters.search || "");
const [status, setStatus] = useState<string>(filters.status || "all");
const [perPage, setPerPage] = useState<string>(filters.per_page || "15");
// 同步 URL 參數
useEffect(() => {
setSearch(filters.search || "");
setStatus(filters.status || "all");
setPerPage(filters.per_page || "15");
}, [filters]);
const handleFilter = () => {
@@ -53,6 +57,7 @@ export default function PurchaseReturnIndex({ purchaseReturns, filters }: Props)
{
search,
status: status === 'all' ? undefined : status,
per_page: perPage,
},
{ preserveState: true, replace: true }
);
@@ -61,9 +66,19 @@ export default function PurchaseReturnIndex({ purchaseReturns, filters }: Props)
const handleReset = () => {
setSearch("");
setStatus("all");
setPerPage("15");
router.get(route('purchase-returns.index'));
};
const handlePerPageChange = (value: string) => {
setPerPage(value);
router.get(
route('purchase-returns.index'),
{ ...filters, per_page: value, page: 1 },
{ preserveState: false, replace: true, preserveScroll: true }
);
};
const handleNavigateToCreate = () => {
router.get(route('purchase-returns.create'));
};
@@ -163,8 +178,29 @@ export default function PurchaseReturnIndex({ purchaseReturns, filters }: Props)
/>
{/* 分頁元件 */}
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between sm:justify-end gap-4 w-full">
<Pagination links={purchaseReturns.links} />
<div className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {purchaseReturns.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={purchaseReturns.links} />
</div>
</div>
</div>
</AuthenticatedLayout>

View File

@@ -47,6 +47,7 @@ interface Props {
batches: {
data: ImportBatch[];
links: any[]; // Pagination links
total: number;
};
filters?: {
per_page?: string;
@@ -250,22 +251,25 @@ export default function SalesImportIndex({ batches, filters = {} }: Props) {
</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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {batches.total} </span>
</div>
<Pagination links={batches.links} />
</div>

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Plus, Package, Search, RotateCcw, ChevronDown, ChevronUp } from 'lucide-react';
import { useState } from "react";
import { Plus, Package, Search, RotateCcw } from 'lucide-react';
import { Button } from "@/Components/ui/button";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, router, Link } from "@inertiajs/react";
@@ -30,7 +30,7 @@ interface Props {
warehouses: { id: number; name: string }[];
}
export default function ShippingOrderIndex({ orders, filters, warehouses }: Props) {
export default function ShippingOrderIndex({ orders, filters }: Props) {
const [search, setSearch] = useState(filters.search || "");
const [status, setStatus] = useState<string>(filters.status || "all");
@@ -198,7 +198,10 @@ export default function ShippingOrderIndex({ orders, filters, warehouses }: Prop
</Table>
</div>
<div className="mt-4 flex justify-end">
<div className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<span className="text-sm text-gray-500"> {(orders as any).total} </span>
</div>
<Pagination links={orders.links} />
</div>
</div>

View File

@@ -367,7 +367,7 @@ export default function Index({
</div>
{/* 分頁 */}
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span></span>
@@ -385,7 +385,7 @@ export default function Index({
/>
<span></span>
</div>
<span className="text-sm text-gray-500"> {requisitions.total} </span>
<span className="text-sm text-gray-500"> {requisitions.total} </span>
</div>
<Pagination links={requisitions.links} />
</div>

View File

@@ -464,22 +464,25 @@ export default function UtilityFeeIndex({ fees, availableCategories, filters }:
</div>
<div className="mt-4 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {fees.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={fees.links} />

View File

@@ -34,6 +34,8 @@ interface PageProps {
data: Vendor[];
links: any[];
meta: any;
total: number;
per_page: number;
};
filters: {
search?: string;
@@ -47,7 +49,7 @@ export default function VendorManagement({ vendors, filters }: PageProps) {
const [searchTerm, setSearchTerm] = useState(filters.search || "");
const [sortField, setSortField] = useState<string | null>(filters.sort_field || null);
const [sortDirection, setSortDirection] = useState<"asc" | "desc" | null>(filters.sort_direction as "asc" | "desc" || null);
const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
const [perPage, setPerPage] = useState<string>(filters.per_page || vendors.per_page?.toString() || "10");
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingVendor, setEditingVendor] = useState<Vendor | null>(null);
@@ -56,7 +58,7 @@ export default function VendorManagement({ vendors, filters }: PageProps) {
setSearchTerm(filters.search || "");
setSortField(filters.sort_field || null);
setSortDirection(filters.sort_direction as "asc" | "desc" || null);
setPerPage(filters.per_page || "10");
setPerPage(filters.per_page || vendors.per_page?.toString() || "10");
}, [filters]);
// Debounced Search
@@ -202,22 +204,25 @@ export default function VendorManagement({ vendors, filters }: PageProps) {
/>
{/* 分頁元件 - 統一樣式 */}
<div className="mt-4 flex flex-col sm:flex-row items-start sm: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 className="mt-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center 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>
<span className="text-sm text-gray-500"> {vendors.total} </span>
</div>
<div className="w-full sm:w-auto flex justify-center sm:justify-end">
<Pagination links={vendors.links} />

View File

@@ -300,7 +300,7 @@ export default function WarehouseIndex({ warehouses, totals, transitWarehouses,
/>
<span></span>
</div>
<span className="text-sm text-gray-500"> {warehouses.total} </span>
<span className="text-sm text-gray-500"> {warehouses.total} </span>
</div>
<Pagination links={warehouses.links} />
</div>