From 7cf640b2f4ae8608b04d21709e215fca80f30261 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Mon, 9 Feb 2026 17:16:00 +0800 Subject: [PATCH] feat(sales): replace import page with dialog UI and support template download --- .../Controllers/SalesImportController.php | 4 +- .../Exports/SalesImportTemplateExport.php | 37 +++++ app/Modules/Sales/Routes/web.php | 2 +- .../js/Components/Sales/SalesImportDialog.tsx | 139 ++++++++++++++++++ resources/js/Pages/Sales/Import/Create.tsx | 90 ------------ resources/js/Pages/Sales/Import/Index.tsx | 20 ++- 6 files changed, 193 insertions(+), 99 deletions(-) create mode 100644 app/Modules/Sales/Exports/SalesImportTemplateExport.php create mode 100644 resources/js/Components/Sales/SalesImportDialog.tsx delete mode 100644 resources/js/Pages/Sales/Import/Create.tsx diff --git a/app/Modules/Sales/Controllers/SalesImportController.php b/app/Modules/Sales/Controllers/SalesImportController.php index 7c61aab..181a050 100644 --- a/app/Modules/Sales/Controllers/SalesImportController.php +++ b/app/Modules/Sales/Controllers/SalesImportController.php @@ -30,9 +30,9 @@ class SalesImportController extends Controller ]); } - public function create() + public function template() { - return Inertia::render('Sales/Import/Create'); + return Excel::download(new \App\Modules\Sales\Exports\SalesImportTemplateExport, 'sales_import_template.xlsx'); } public function store(Request $request) diff --git a/app/Modules/Sales/Exports/SalesImportTemplateExport.php b/app/Modules/Sales/Exports/SalesImportTemplateExport.php new file mode 100644 index 0000000..b5f3606 --- /dev/null +++ b/app/Modules/Sales/Exports/SalesImportTemplateExport.php @@ -0,0 +1,37 @@ + ['font' => ['bold' => true]], + ]; + } +} diff --git a/app/Modules/Sales/Routes/web.php b/app/Modules/Sales/Routes/web.php index 02e08c1..17f4ce4 100644 --- a/app/Modules/Sales/Routes/web.php +++ b/app/Modules/Sales/Routes/web.php @@ -10,7 +10,7 @@ Route::middleware(['auth', 'verified'])->prefix('sales')->name('sales-imports.') }); Route::post('/imports', [SalesImportController::class, 'store'])->middleware('permission:sales_imports.create')->name('store'); - Route::get('/imports/create', [SalesImportController::class, 'create'])->middleware('permission:sales_imports.create')->name('create'); + Route::get('/imports/template', [SalesImportController::class, 'template'])->middleware('permission:sales_imports.create')->name('template'); Route::post('/imports/{import}/confirm', [SalesImportController::class, 'confirm'])->middleware('permission:sales_imports.confirm')->name('confirm'); Route::delete('/imports/{import}', [SalesImportController::class, 'destroy'])->middleware('permission:sales_imports.delete')->name('destroy'); diff --git a/resources/js/Components/Sales/SalesImportDialog.tsx b/resources/js/Components/Sales/SalesImportDialog.tsx new file mode 100644 index 0000000..8def14d --- /dev/null +++ b/resources/js/Components/Sales/SalesImportDialog.tsx @@ -0,0 +1,139 @@ +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/Components/ui/dialog"; +import { Button } from "@/Components/ui/button"; +import { Input } from "@/Components/ui/input"; +import { Label } from "@/Components/ui/label"; +import { Upload, AlertCircle, Info, FileSpreadsheet } from "lucide-react"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/Components/ui/accordion"; +import { useForm } from "@inertiajs/react"; +import { Alert, AlertDescription } from "@/Components/ui/alert"; +import React from "react"; + +interface SalesImportDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export default function SalesImportDialog({ open, onOpenChange }: SalesImportDialogProps) { + const { data, setData, post, processing, errors, reset, clearErrors } = useForm<{ + file: File | null; + }>({ + file: null, + }); + + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setData("file", e.target.files[0]); + clearErrors("file"); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + post(route("sales-imports.store"), { + onSuccess: () => { + reset(); + onOpenChange(false); + }, + }); + }; + + return ( + + + + 匯入銷售單 + + 請上傳統一格式的銷售單 Excel 檔案。系統將自動解析內容。 + + + +
+ {/* 下載範本區域 */} +
+ +
+ 下載標準範本以確保資料格式正確。請勿修改欄位名稱。 +
+ +
+ + {/* 上傳檔案區域 */} +
+ + +
+ +
+ {errors.file && ( + + + + {errors.file} + + + )} +

支援格式:.xlsx, .xls, .csv

+
+ + {/* 匯入說明 - 使用 Accordion 收合 */} + + + +
+ + 匯入與欄位說明 +
+
+ +
+
    +
  • 檔案格式:請使用統一的 Excel 格式(商品銷貨單)。
  • +
  • 自動解析:系統將自動解析機台編號、商品代號與交易時間。
  • +
  • 後續動作:匯入後請至「待確認」清單檢查內容,確認無誤後再執行扣庫。
  • +
+
+
+
+
+ + + + + +
+
+
+ ); +} diff --git a/resources/js/Pages/Sales/Import/Create.tsx b/resources/js/Pages/Sales/Import/Create.tsx deleted file mode 100644 index 4127f81..0000000 --- a/resources/js/Pages/Sales/Import/Create.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; -import { Head, useForm, Link } from '@inertiajs/react'; -import { Button } from '@/Components/ui/button'; -import { Input } from '@/Components/ui/input'; -import { Label } from '@/Components/ui/label'; -import { Upload, ArrowLeft, FileSpreadsheet } from 'lucide-react'; -import React from 'react'; - -export default function SalesImportCreate() { - const { data, setData, post, processing, errors } = useForm({ - file: null as File | null, - }); - - const submit = (e: React.FormEvent) => { - e.preventDefault(); - post(route('sales-imports.store')); - }; - - return ( - - - -
-
- - - -

- - 新增銷售匯入 -

-
- -
-
-
- -
- setData('file', e.target.files ? e.target.files[0] : null)} - className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" - /> - -
-

- {data.file ? data.file.name : '點擊或拖曳檔案至此'} -

-

支援 .xlsx, .xls, .csv

-
-
- {errors.file &&

{errors.file}

} -
- -
- -
-
-
- -
-

匯入說明

-
    -
  • 請使用統一的 Excel 格式(商品銷貨單)。
  • -
  • 系統將自動解析機台編號、商品代號與交易時間。
  • -
  • 匯入後請至「待確認」清單檢查內容,確認無誤後再執行扣庫。
  • -
-
-
-
- ); -} diff --git a/resources/js/Pages/Sales/Import/Index.tsx b/resources/js/Pages/Sales/Import/Index.tsx index fe2ffaa..2b3861e 100644 --- a/resources/js/Pages/Sales/Import/Index.tsx +++ b/resources/js/Pages/Sales/Import/Index.tsx @@ -28,6 +28,7 @@ import Pagination from "@/Components/shared/Pagination"; import { SearchableSelect } from "@/Components/ui/searchable-select"; import { router } from "@inertiajs/react"; import { usePermission } from "@/hooks/usePermission"; +import SalesImportDialog from "@/Components/Sales/SalesImportDialog"; interface ImportBatch { id: number; @@ -54,6 +55,7 @@ interface Props { export default function SalesImportIndex({ batches, filters = {} }: Props) { const { can } = usePermission(); const [perPage, setPerPage] = useState(filters?.per_page?.toString() || "10"); + const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); useEffect(() => { if (filters?.per_page) { @@ -91,15 +93,21 @@ export default function SalesImportIndex({ batches, filters = {} }: Props) {

{can('sales_imports.create') && ( - - - + )} + +