diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 92a9a24..4935f32 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -104,8 +104,8 @@ jobs: php artisan optimize:clear && php artisan optimize && php artisan view:cache - " - docker exec koori-erp-laravel chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache + " + docker exec koori-erp-laravel chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache # --- 2. 正式環境部署 (erp.koori.tw:2224) --- deploy-production: diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 6147253..e979731 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -15,14 +15,18 @@ class UserController extends Controller /** * Display a listing of the resource. */ - public function index() + public function index(Request $request) { + $perPage = $request->input('per_page', 10); + $users = User::with('roles') ->orderBy('id') - ->paginate(10); // 分頁 + ->paginate($perPage) + ->withQueryString(); return Inertia::render('Admin/User/Index', [ - 'users' => $users + 'users' => $users, + 'filters' => $request->only(['per_page']), ]); } diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index 18b87af..006a588 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -43,11 +43,12 @@ class PurchaseOrderController extends Controller $query->orderBy($sortField, $sortDirection); } - $orders = $query->paginate(15)->withQueryString(); + $perPage = $request->input('per_page', 10); + $orders = $query->paginate($perPage)->withQueryString(); return Inertia::render('PurchaseOrder/Index', [ 'orders' => $orders, - 'filters' => $request->only(['search', 'status', 'warehouse_id', 'sort_field', 'sort_direction']), + 'filters' => $request->only(['search', 'status', 'warehouse_id', 'sort_field', 'sort_direction', 'per_page']), 'warehouses' => Warehouse::all(['id', 'name']), ]); } diff --git a/app/Http/Controllers/VendorController.php b/app/Http/Controllers/VendorController.php index f7487aa..6919205 100644 --- a/app/Http/Controllers/VendorController.php +++ b/app/Http/Controllers/VendorController.php @@ -36,13 +36,15 @@ class VendorController extends Controller $sortDirection = 'desc'; } + $perPage = $request->input('per_page', 10); + $vendors = $query->orderBy($sortField, $sortDirection) - ->paginate(10) + ->paginate($perPage) ->withQueryString(); return \Inertia\Inertia::render('Vendor/Index', [ 'vendors' => $vendors, - 'filters' => $request->only(['search', 'sort_field', 'sort_direction']), + 'filters' => $request->only(['search', 'sort_field', 'sort_direction', 'per_page']), ]); } diff --git a/resources/js/Pages/Admin/User/Index.tsx b/resources/js/Pages/Admin/User/Index.tsx index c538c1a..0370713 100644 --- a/resources/js/Pages/Admin/User/Index.tsx +++ b/resources/js/Pages/Admin/User/Index.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { Head, Link, router } from '@inertiajs/react'; import { Users, Plus, Pencil, Trash2, Mail, Shield } from 'lucide-react'; @@ -13,6 +14,9 @@ import { import { format } from 'date-fns'; import { toast } from 'sonner'; import { Can } from '@/Components/Permission/Can'; +import { cn } from "@/lib/utils"; +import Pagination from "@/Components/shared/Pagination"; +import { SearchableSelect } from "@/Components/ui/searchable-select"; interface Role { id: number; @@ -28,27 +32,26 @@ interface User { roles: Role[]; } -interface Pagination { - current_page: number; - last_page: number; - per_page: number; - total: number; - from: number; - links: { - url: string | null; - label: string; - active: boolean; - }[]; +interface PaginationLinks { + url: string | null; + label: string; + active: boolean; } interface Props { users: { data: User[]; - meta?: Pagination; // Standard Laravel Pagination resource structure, but if simple paginate() it's direct properties - } & Pagination; // paginate() returns object with data and meta properties mixed + from: number; + links: PaginationLinks[]; + }; + filters: { + per_page?: string; + }; } -export default function UserIndex({ users }: Props) { +export default function UserIndex({ users, filters }: Props) { + const [perPage, setPerPage] = useState(filters.per_page || "10"); + const handleDelete = (id: number, name: string) => { if (confirm(`確定要刪除使用者「${name}」嗎?此操作無法復原。`)) { router.delete(route('users.destroy', id), { @@ -58,6 +61,15 @@ export default function UserIndex({ users }: Props) { } }; + const handlePerPageChange = (value: string) => { + setPerPage(value); + router.get( + route('users.index'), + { per_page: value }, + { preserveState: false, replace: true, preserveScroll: true } + ); + }; + const translateRoleName = (name: string) => { const map: Record = { 'super-admin': '超級管理員', @@ -183,50 +195,29 @@ export default function UserIndex({ users }: Props) { ))} + - {/* Pagination - Simple implementation */} - {users.links && users.links.length > 3 && ( -
-
- {/* Mobile pagination */} -
-
-
-

- 顯示第 {users.current_page} 頁 -

-
-
- -
-
-
- )} + {/* 分頁元件 - 統一樣式 */} +
+
+ 每頁顯示 + + +
+
); } -// Helper for conditional class names if not imported -function cn(...classes: (string | undefined | null | false)[]) { - return classes.filter(Boolean).join(' '); -} diff --git a/resources/js/Pages/PurchaseOrder/Index.tsx b/resources/js/Pages/PurchaseOrder/Index.tsx index b295e4a..ca88047 100644 --- a/resources/js/Pages/PurchaseOrder/Index.tsx +++ b/resources/js/Pages/PurchaseOrder/Index.tsx @@ -3,7 +3,7 @@ */ import { useState, useCallback } from "react"; -import { Plus, Search, X, ShoppingCart } from 'lucide-react'; +import { Plus, ShoppingCart } from 'lucide-react'; import { Button } from "@/Components/ui/button"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import { Head, router } from "@inertiajs/react"; @@ -15,6 +15,7 @@ import { debounce } from "lodash"; import Pagination from "@/Components/shared/Pagination"; import { getBreadcrumbs } from "@/utils/breadcrumb"; import { Can } from "@/Components/Permission/Can"; +import { SearchableSelect } from "@/Components/ui/searchable-select"; interface Props { orders: { @@ -30,6 +31,7 @@ interface Props { warehouse_id?: string; sort_field?: string; sort_direction?: string; + per_page?: string; }; warehouses: { id: number; name: string }[]; } @@ -38,6 +40,7 @@ export default function PurchaseOrderIndex({ orders, filters, warehouses }: Prop const [searchQuery, setSearchQuery] = useState(filters.search || ""); const [statusFilter, setStatusFilter] = useState(filters.status || "all"); const [requesterFilter, setRequesterFilter] = useState(filters.warehouse_id || "all"); + const [perPage, setPerPage] = useState(filters.per_page || "10"); const [dateRange, setDateRange] = useState(null); const handleFilterChange = (newFilters: any) => { @@ -87,6 +90,18 @@ export default function PurchaseOrderIndex({ orders, filters, warehouses }: Prop router.get("/purchase-orders/create"); }; + const handlePerPageChange = (value: string) => { + setPerPage(value); + router.get("/purchase-orders", { + ...filters, + per_page: value, + page: 1, + }, { + preserveState: false, + replace: true, + }); + }; + return ( @@ -134,7 +149,24 @@ export default function PurchaseOrderIndex({ orders, filters, warehouses }: Prop orders={orders.data} /> -
+ {/* 分頁元件 - 統一樣式 */} +
+
+ 每頁顯示 + + +
diff --git a/resources/js/Pages/Vendor/Index.tsx b/resources/js/Pages/Vendor/Index.tsx index 83fb241..4bdca1a 100644 --- a/resources/js/Pages/Vendor/Index.tsx +++ b/resources/js/Pages/Vendor/Index.tsx @@ -9,6 +9,8 @@ import { Head, router } from "@inertiajs/react"; import { debounce } from "lodash"; import { getBreadcrumbs } from "@/utils/breadcrumb"; import { Can } from "@/Components/Permission/Can"; +import Pagination from "@/Components/shared/Pagination"; +import { SearchableSelect } from "@/Components/ui/searchable-select"; export interface Vendor { id: number; @@ -37,6 +39,7 @@ interface PageProps { search?: string; sort_field?: string; sort_direction?: string; + per_page?: string; }; } @@ -44,6 +47,7 @@ export default function VendorManagement({ vendors, filters }: PageProps) { const [searchTerm, setSearchTerm] = useState(filters.search || ""); const [sortField, setSortField] = useState(filters.sort_field || null); const [sortDirection, setSortDirection] = useState<"asc" | "desc" | null>(filters.sort_direction as "asc" | "desc" || null); + const [perPage, setPerPage] = useState(filters.per_page || "10"); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingVendor, setEditingVendor] = useState(null); @@ -52,6 +56,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"); }, [filters]); // Debounced Search @@ -125,6 +130,15 @@ export default function VendorManagement({ vendors, filters }: PageProps) { router.delete(route('vendors.destroy', id)); }; + const handlePerPageChange = (value: string) => { + setPerPage(value); + router.get( + route("vendors.index"), + { search: searchTerm, sort_field: sortField, sort_direction: sortDirection, per_page: value }, + { preserveState: false, replace: true, preserveScroll: true } + ); + }; + return ( @@ -186,6 +200,27 @@ export default function VendorManagement({ vendors, filters }: PageProps) { onOpenChange={setIsDialogOpen} vendor={editingVendor} /> + + {/* 分頁元件 - 統一樣式 */} +
+
+ 每頁顯示 + + +
+ +
);