"use client"; import SearchBox, { Criterion } from "../SearchBox"; import { useCallback, useMemo, useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults"; import { useRouter } from "next/navigation"; import { successDialog } from "../Swal/CustomAlerts"; import useUploadContext from "../UploadProvider/useUploadContext"; import { downloadFile } from "@/app/utils/commonUtil"; import { EquipmentDetailResult } from "@/app/api/settings/equipmentDetail"; import { exportEquipmentQrCode, printEquipmentQrCode } from "@/app/api/settings/equipmentDetail/client"; import { Checkbox, Box, Button, TextField, Stack, Autocomplete, Modal, Card, IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Typography } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import PrintIcon from "@mui/icons-material/Print"; import CloseIcon from "@mui/icons-material/Close"; import { PrinterCombo } from "@/app/api/settings/printer"; import axiosInstance from "@/app/(main)/axios/axiosInstance"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; interface Props { equipmentDetails: EquipmentDetailResult[]; printerCombo: PrinterCombo[]; } type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const QrCodeHandleEquipmentSearch: React.FC = ({ equipmentDetails, printerCombo }) => { const { t } = useTranslation("common"); const [filteredEquipmentDetails, setFilteredEquipmentDetails] = useState([]); const router = useRouter(); const { setIsUploading } = useUploadContext(); const [pagingController, setPagingController] = useState({ pageNum: 1, pageSize: 10, }); const [filterObj, setFilterObj] = useState({}); const [totalCount, setTotalCount] = useState(0); const [checkboxIds, setCheckboxIds] = useState<(string | number)[]>([]); const [selectedEquipmentDetailsMap, setSelectedEquipmentDetailsMap] = useState>(new Map()); const [selectAll, setSelectAll] = useState(false); const [printQty, setPrintQty] = useState(1); const [isSearching, setIsSearching] = useState(false); const [previewOpen, setPreviewOpen] = useState(false); const [previewUrl, setPreviewUrl] = useState(null); const [selectedEquipmentDetailsModalOpen, setSelectedEquipmentDetailsModalOpen] = useState(false); const filteredPrinters = useMemo(() => { return printerCombo.filter((printer) => { return printer.type === "A4"; }); }, [printerCombo]); const [selectedPrinter, setSelectedPrinter] = useState( filteredPrinters.length > 0 ? filteredPrinters[0] : undefined ); useEffect(() => { if (!selectedPrinter || !filteredPrinters.find(p => p.id === selectedPrinter.id)) { setSelectedPrinter(filteredPrinters.length > 0 ? filteredPrinters[0] : undefined); } }, [filteredPrinters, selectedPrinter]); const searchCriteria: Criterion[] = useMemo( () => [ { label: "設備名稱", paramName: "code", type: "text", }, { label: "設備編號", paramName: "equipmentCode", type: "text", }, ], [], ); interface ApiResponse { records: T[]; total: number; } const refetchData = useCallback( async (filterObj: SearchQuery, pageNum: number, pageSize: number) => { const authHeader = axiosInstance.defaults.headers["Authorization"]; if (!authHeader) { setTimeout(() => { refetchData(filterObj, pageNum, pageSize); }, 10); return; } const params = { pageNum: pageNum, pageSize: pageSize, ...filterObj, }; try { const response = await axiosInstance.get>( `${NEXT_PUBLIC_API_URL}/EquipmentDetail/getRecordByPage`, { params }, ); if (response.status == 200) { setFilteredEquipmentDetails(response.data.records); setTotalCount(response.data.total); return response; } else { throw "400"; } } catch (error) { console.error("Error fetching equipment details:", error); throw error; } }, [], ); useEffect(() => { refetchData(filterObj, pagingController.pageNum, pagingController.pageSize); }, [filterObj, pagingController.pageNum, pagingController.pageSize]); useEffect(() => { if (filteredEquipmentDetails.length > 0) { const allCurrentPageSelected = filteredEquipmentDetails.every(ed => checkboxIds.includes(ed.id)); setSelectAll(allCurrentPageSelected); } else { setSelectAll(false); } }, [filteredEquipmentDetails, checkboxIds]); const handleSelectEquipmentDetail = useCallback((equipmentDetailId: string | number, checked: boolean) => { if (checked) { const equipmentDetail = filteredEquipmentDetails.find(ed => ed.id === equipmentDetailId); if (equipmentDetail) { setCheckboxIds(prev => [...prev, equipmentDetailId]); setSelectedEquipmentDetailsMap(prev => { const newMap = new Map(prev); newMap.set(equipmentDetailId, equipmentDetail); return newMap; }); } } else { setCheckboxIds(prev => prev.filter(id => id !== equipmentDetailId)); setSelectedEquipmentDetailsMap(prev => { const newMap = new Map(prev); newMap.delete(equipmentDetailId); return newMap; }); setSelectAll(false); } }, [filteredEquipmentDetails]); const fetchAllMatchingEquipmentDetails = useCallback(async (): Promise => { const authHeader = axiosInstance.defaults.headers["Authorization"]; if (!authHeader) { return []; } if (totalCount === 0) { return []; } const params = { pageNum: 1, pageSize: totalCount > 0 ? totalCount : 10000, ...filterObj, }; try { const response = await axiosInstance.get>( `${NEXT_PUBLIC_API_URL}/EquipmentDetail/getRecordByPage`, { params }, ); if (response.status == 200) { return response.data.records; } return []; } catch (error) { console.error("Error fetching all equipment details:", error); return []; } }, [filterObj, totalCount]); const handleSelectAll = useCallback(async (checked: boolean) => { if (checked) { try { const allEquipmentDetails = await fetchAllMatchingEquipmentDetails(); const allIds = allEquipmentDetails.map(equipmentDetail => equipmentDetail.id); setCheckboxIds(allIds); setSelectedEquipmentDetailsMap(prev => { const newMap = new Map(prev); allEquipmentDetails.forEach(equipmentDetail => { newMap.set(equipmentDetail.id, equipmentDetail); }); return newMap; }); setSelectAll(true); } catch (error) { console.error("Error selecting all equipment:", error); } } else { setCheckboxIds([]); setSelectedEquipmentDetailsMap(new Map()); setSelectAll(false); } }, [fetchAllMatchingEquipmentDetails]); const showPdfPreview = useCallback(async (equipmentDetailIds: (string | number)[]) => { if (equipmentDetailIds.length === 0) { return; } try { setIsUploading(true); const numericIds = equipmentDetailIds.map(id => typeof id === 'string' ? parseInt(id) : id); const response = await exportEquipmentQrCode(numericIds); const blob = new Blob([new Uint8Array(response.blobValue)], { type: "application/pdf" }); const url = URL.createObjectURL(blob); setPreviewUrl(`${url}#toolbar=0`); setPreviewOpen(true); } catch (error) { console.error("Error exporting QR code:", error); } finally { setIsUploading(false); } }, [setIsUploading]); const handleClosePreview = useCallback(() => { setPreviewOpen(false); if (previewUrl) { URL.revokeObjectURL(previewUrl); setPreviewUrl(null); } }, [previewUrl]); const handleDownloadQrCode = useCallback(async (equipmentDetailIds: (string | number)[]) => { if (equipmentDetailIds.length === 0) { return; } try { setIsUploading(true); const numericIds = equipmentDetailIds.map(id => typeof id === 'string' ? parseInt(id) : id); const response = await exportEquipmentQrCode(numericIds); downloadFile(response.blobValue, response.filename); setSelectedEquipmentDetailsModalOpen(false); successDialog("二維碼已下載", t); } catch (error) { console.error("Error exporting QR code:", error); } finally { setIsUploading(false); } }, [setIsUploading, t]); const handlePrint = useCallback(async () => { if (checkboxIds.length === 0 || !selectedPrinter) { return; } try { setIsUploading(true); const numericIds = checkboxIds.map(id => typeof id === 'string' ? parseInt(id) : id); await printEquipmentQrCode({ equipmentDetailIds: numericIds, printerId: selectedPrinter.id, printQty, }); setSelectedEquipmentDetailsModalOpen(false); successDialog("二維碼已列印", t); } catch (error) { console.error("Error printing QR code:", error); } finally { setIsUploading(false); } }, [checkboxIds, printQty, selectedPrinter, setIsUploading, t]); const handleViewSelectedQrCodes = useCallback(() => { if (checkboxIds.length === 0) { return; } setSelectedEquipmentDetailsModalOpen(true); }, [checkboxIds]); const selectedEquipmentDetails = useMemo(() => { return Array.from(selectedEquipmentDetailsMap.values()); }, [selectedEquipmentDetailsMap]); const handleCloseSelectedEquipmentDetailsModal = useCallback(() => { setSelectedEquipmentDetailsModalOpen(false); }, []); const columns = useMemo[]>( () => [ { name: "id", label: "", sx: { width: "50px", minWidth: "50px" }, renderCell: (params) => ( handleSelectEquipmentDetail(params.id, e.target.checked)} onClick={(e) => e.stopPropagation()} /> ), }, { name: "code", label: "設備名稱", align: "left", headerAlign: "left", sx: { width: "150px", minWidth: "150px" }, }, { name: "description", label: "設備描述", align: "left", headerAlign: "left", sx: { width: "200px", minWidth: "200px" }, }, { name: "equipmentCode", label: "設備編號", align: "left", headerAlign: "left", sx: { width: "150px", minWidth: "150px" }, }, ], [t, checkboxIds, handleSelectEquipmentDetail], ); const onReset = useCallback(() => { setFilterObj({}); setPagingController({ pageNum: 1, pageSize: 10 }); }, []); return ( <> { setFilterObj({ ...query, }); setPagingController({ pageNum: 1, pageSize: 10 }); }} onReset={onReset} /> items={filteredEquipmentDetails} columns={columns} pagingController={pagingController} setPagingController={setPagingController} totalCount={totalCount} isAutoPaging={false} /> 已選擇設備 ({selectedEquipmentDetails.length}) 設備名稱 設備描述 設備編號 {selectedEquipmentDetails.length === 0 ? ( 沒有選擇的設備 ) : ( selectedEquipmentDetails.map((equipmentDetail) => ( {equipmentDetail.code || '-'} {equipmentDetail.description || '-'} {equipmentDetail.equipmentCode || '-'} )) )}
options={filteredPrinters} value={selectedPrinter ?? null} onChange={(event, value) => { setSelectedPrinter(value ?? undefined); }} getOptionLabel={(option) => option.name || option.label || option.code || String(option.id)} renderInput={(params) => ( )} /> { const value = parseInt(e.target.value) || 1; setPrintQty(Math.max(1, value)); }} inputProps={{ min: 1 }} sx={{ width: 120 }} />
{previewUrl && (