diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 76a38d8..2957b50 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -22,7 +22,8 @@ export interface SaveJoResponse { export interface SearchJoResultRequest extends Pageable { code: string; itemName?: string; - + planStart?: string; + planStartTo?: string; } diff --git a/src/app/api/jo/index.ts b/src/app/api/jo/index.ts index 3a1d60b..568783b 100644 --- a/src/app/api/jo/index.ts +++ b/src/app/api/jo/index.ts @@ -25,6 +25,7 @@ export interface JobOrder { pickLines?: JoDetailPickLine[]; status: JoStatus; planStart?: number[]; + planStartTo?: string; planEnd?: number[]; type: string; // TODO pack below into StockInLineInfo diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx index 8449af6..f2d0dad 100644 --- a/src/components/DashboardPage/DashboardPage.tsx +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -58,7 +58,7 @@ const DashboardPage: React.FC = ({ - + diff --git a/src/components/DoSearch/DoSearch.tsx b/src/components/DoSearch/DoSearch.tsx index 0a61b52..520d5a6 100644 --- a/src/components/DoSearch/DoSearch.tsx +++ b/src/components/DoSearch/DoSearch.tsx @@ -393,15 +393,15 @@ if(orderStartDate != ""){ const result = await Swal.fire( { - icon: "info", + icon: "question", title: t("Batch Release"), html: t("Selected Shop(s): ") + extractedIdsCount.toString() + `

`+ t("Selected Item(s): ") + extractedItemsCount.toString() + `

`, showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), - confirmButtonColor: "#638a01", - cancelButtonColor: "#d33" + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438" }); if (result.isConfirmed) { Swal.fire({ diff --git a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx index 4a32e78..4034a8d 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodSearch.tsx @@ -69,7 +69,6 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const fetchReleasedOrderCount = useCallback(async () => { try { const releasedOrders = await fetchReleasedDoPickOrders(); - // Count only orders that have a valid doOrderId const validCount = releasedOrders.filter(order => order.doOrderId).length; setReleasedOrderCount(validCount); } catch (error) { @@ -105,7 +104,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { if(response.success){ Swal.fire({ position: "bottom-end", - icon: "info", + icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 @@ -130,21 +129,30 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { console.log("Found released orders:", releasedOrders); const confirmResult = await Swal.fire({ - title: t("Confirm Print"), - text: t(`Do you want to print ${releasedOrders.length} draft(s)?`), + title: t("Batch Print"), + text: t("Confirm print: (") + releasedOrders.length.toString() + t("piece(s))"), icon: "question", showCancelButton: true, - confirmButtonText: t("Yes, print"), + confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), - confirmButtonColor: "#3085d6", - cancelButtonColor: "#d33" + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438" }); - // If user cancels, exit the function if (!confirmResult.isConfirmed) { return; } + Swal.fire({ + title: t("Printing..."), + text: t("Please wait..."), + allowOutsideClick: false, + allowEscapeKey: false, + didOpen: () => { + Swal.showLoading(); + } + }); + for (const order of releasedOrders) { const { doOrderId, pickOrderId } = order; @@ -170,7 +178,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { Swal.fire({ position: "bottom-end", icon: "success", - text: t(`Printed ${releasedOrders.length} draft(s) successfully.`), + text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 }); @@ -183,6 +191,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const handleDN = useCallback(async () =>{ const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), + icon: "info", input: "number", inputPlaceholder: t("Number of cartons"), inputAttributes:{ @@ -201,6 +210,8 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438", showLoaderOnConfirm: true, allowOutsideClick: () => !Swal.isLoading() }); @@ -233,7 +244,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { if(response.success){ Swal.fire({ position: "bottom-end", - icon: "info", + icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 @@ -250,6 +261,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const handleDNandLabel = useCallback(async () =>{ const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), + icon: "info", input: "number", inputPlaceholder: t("Number of cartons"), inputAttributes:{ @@ -268,6 +280,8 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438", showLoaderOnConfirm: true, allowOutsideClick: () => !Swal.isLoading() }); @@ -310,7 +324,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { if(LabelsResponse.success && DNResponse.success){ Swal.fire({ position: "bottom-end", - icon: "info", + icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 @@ -332,6 +346,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const handleLabel = useCallback(async () =>{ const askNumofCarton = await Swal.fire({ title: t("Enter the number of cartons: "), + icon: "info", input: "number", inputPlaceholder: t("Number of cartons"), inputAttributes:{ @@ -350,6 +365,8 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { showCancelButton: true, confirmButtonText: t("Confirm"), cancelButtonText: t("Cancel"), + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438", showLoaderOnConfirm: true, allowOutsideClick: () => !Swal.isLoading() }); @@ -380,7 +397,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { if(response.success){ Swal.fire({ position: "bottom-end", - icon: "info", + icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 @@ -454,19 +471,40 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { window.dispatchEvent(new CustomEvent('pickOrderAssigned')); } else if (res.code === "USER_BUSY") { console.warn("⚠️ User already has pick orders in progress:", res.message); + Swal.fire({ + icon: "warning", + title: t("Warning"), + text: t("You already have a pick order in progess. Please complete it first before taking next pick order."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); // ✅ Show warning but still refresh to show existing orders - alert(`Warning: ${res.message}`); + //alert(`Warning: ${res.message}`); window.dispatchEvent(new CustomEvent('pickOrderAssigned')); } else if (res.code === "NO_ORDERS") { console.log("ℹ️ No available pick orders for store", storeId); - alert(`Info: ${res.message}`); + Swal.fire({ + icon: "info", + title: t("Info"), + text: t("No available pick order(s) for this floor."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + //alert(`Info: ${res.message}`); } else { console.log("ℹ️ Assignment result:", res.message); alert(`Info: ${res.message}`); } } catch (error) { console.error("❌ Error assigning by store:", error); - alert("Error occurred during assignment"); + Swal.fire({ + icon: "error", + title: t("Error"), + text: t("Error occurred during assignment."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + //alert("Error occurred during assignment"); } finally { setIsAssigning(false); } diff --git a/src/components/JoSearch/JoCreateFormModal.tsx b/src/components/JoSearch/JoCreateFormModal.tsx index 96b6ad9..fe2cae6 100644 --- a/src/components/JoSearch/JoCreateFormModal.tsx +++ b/src/components/JoSearch/JoCreateFormModal.tsx @@ -181,6 +181,7 @@ const JoCreateFormModal: React.FC = ({ render={({ field, fieldState: { error } }) => ( { handleDateTimePickerChange(newValue, field.onChange) diff --git a/src/components/JoSearch/JoSearch.tsx b/src/components/JoSearch/JoSearch.tsx index 44bb8ee..2cb7fd7 100644 --- a/src/components/JoSearch/JoSearch.tsx +++ b/src/components/JoSearch/JoSearch.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; import { Criterion } from "../SearchBox"; import SearchResults, { Column, defaultPagingController } from "../SearchResults/SearchResults"; import { EditNote } from "@mui/icons-material"; -import { arrayToDateString, integerFormatter } from "@/app/utils/formatUtil"; +import { arrayToDateString, arrayToDateTimeString, integerFormatter } from "@/app/utils/formatUtil"; import { orderBy, uniqBy, upperFirst } from "lodash"; import SearchBox from "../SearchBox/SearchBox"; import { useRouter } from "next/navigation"; @@ -31,10 +31,7 @@ interface Props { bomCombo: BomCombo[] } -type SearchQuery = Partial> & { - planStartFrom?: string; - planStartTo?: string; -}; +type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; @@ -49,6 +46,7 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { const [totalCount, setTotalCount] = useState(0) const [isCreateJoModalOpen, setIsCreateJoModalOpen] = useState(false) + // console.log(inputs) const [inventoryData, setInventoryData] = useState([]); const [detailedJos, setDetailedJos] = useState>(new Map()); @@ -138,7 +136,8 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { const searchCriteria: Criterion[] = useMemo(() => [ { label: t("Code"), paramName: "code", type: "text" }, - { label: t("Item Name"), paramName: "itemName", type: "text" }, + { label: t("Item Name"), paramName: "itemName", type: "text" }, + { label: t("Plan Start"), label2: t("Plan Start To"), paramName: "planStart", type: "datetimeRange" }, ], [t]) const columns = useMemo[]>( @@ -194,7 +193,7 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { align: "left", headerAlign: "left", renderCell: (row) => { - return row.planStart ? arrayToDateString(row.planStart, "output") : '-' + return row.planStart ? arrayToDateTimeString(row.planStart) : '-' } }, { @@ -273,6 +272,8 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { const params: SearchJoResultRequest = { code: query.code, itemName: query.itemName, + planStart: query.planStart, + planStartTo: query.planStartTo, pageNum: pagingController.pageNum - 1, pageSize: pagingController.pageSize, } @@ -364,7 +365,9 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { const onSearch = useCallback((query: Record) => { setInputs(() => ({ code: query.code, - itemName: query.itemName + itemName: query.itemName, + planStart: query.planStart, + planStartTo: query.planStartTo })) refetchData(query, "search"); }, []) diff --git a/src/components/JoSearch/JoSearchWrapper.tsx b/src/components/JoSearch/JoSearchWrapper.tsx index b44345c..7972d56 100644 --- a/src/components/JoSearch/JoSearchWrapper.tsx +++ b/src/components/JoSearch/JoSearchWrapper.tsx @@ -11,7 +11,7 @@ interface SubComponents { const JoSearchWrapper: React.FC & SubComponents = async () => { const defaultInputs: SearchJoResultRequest = { code: "", - name: "", + itemName: "", } const [ diff --git a/src/components/Jodetail/completeJobOrderRecord.tsx b/src/components/Jodetail/completeJobOrderRecord.tsx index 3742a0f..b744062 100644 --- a/src/components/Jodetail/completeJobOrderRecord.tsx +++ b/src/components/Jodetail/completeJobOrderRecord.tsx @@ -321,7 +321,7 @@ const CompleteJobOrderRecord: React.FC = ({ filterArgs }) => { if(response.success){ Swal.fire({ position: "bottom-end", - icon: "info", + icon: "success", text: t("Printed Successfully."), showConfirmButton: false, timer: 1500 diff --git a/src/components/PutAwayScan/PutAwayModal.tsx b/src/components/PutAwayScan/PutAwayModal.tsx index c337849..7069170 100644 --- a/src/components/PutAwayScan/PutAwayModal.tsx +++ b/src/components/PutAwayScan/PutAwayModal.tsx @@ -380,7 +380,7 @@ const PutAwayModal: React.FC = ({ open, onClose, warehouse, stockInLineId {warehouseId > 0 ? `${warehouse.find((w) => w.id == warehouseId)?.name}` - : `${warehouse.find((w) => w.id == 1)?.name} (預設)`} + : `${warehouse.find((w) => w.id == 1)?.name} (建議)`} diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index a6a1ff1..233e363 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -15,7 +15,7 @@ import CardActions from "@mui/material/CardActions"; import Button from "@mui/material/Button"; import RestartAlt from "@mui/icons-material/RestartAlt"; import Search from "@mui/icons-material/Search"; -import dayjs from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import "dayjs/locale/zh-hk"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; @@ -29,6 +29,8 @@ import { } from "@mui/material"; import MultiSelect from "@/components/SearchBox/MultiSelect"; import { intersectionWith } from "lodash"; +import { INPUT_DATE_FORMAT, INPUT_TIME_FORMAT, OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dayjsToDateTimeString } from "@/app/utils/formatUtil"; +import { DateTimePicker } from "@mui/x-date-pickers"; interface BaseCriterion { label: string; @@ -86,6 +88,10 @@ interface DateRangeCriterion extends BaseCriterion { type: "dateRange"; } +interface DatetimeRangeCriterion extends BaseCriterion { + type: "datetimeRange"; +} + interface DateCriterion extends BaseCriterion { type: "date"; } @@ -95,6 +101,7 @@ export type Criterion = | SelectCriterion | SelectWithLabelCriterion | DateRangeCriterion + | DatetimeRangeCriterion | DateCriterion | MultiSelectCriterion | AutocompleteCriterion; @@ -135,7 +142,7 @@ function SearchBox({ : "", }; - if (c.type === "dateRange") { + if (c.type === "dateRange" || c.type === "datetimeRange") { tempCriteria = { ...tempCriteria, [c.paramName]: c.defaultValue ?? "", @@ -216,6 +223,24 @@ function SearchBox({ }; }, []); + const makeDatetimeChangeHandler = useCallback((paramName: T) => { + return (value: Dayjs | null) => { + setInputs((i) => ({ + ...i, + [paramName]: value ? dayjsToDateTimeString(value) : null + })); + }; + }, []); + + const makeDatetimeToChangeHandler = useCallback((paramName: T) => { + return (value: Dayjs | null) => { + setInputs((i) => ({ + ...i, + [paramName + "To"]: value ? dayjsToDateTimeString(value) : null + })); + }; + }, []); + const handleReset = () => { setInputs(defaultInputs); onReset?.(); @@ -397,6 +422,7 @@ function SearchBox({ ({ ({ )} + {c.type === "datetimeRange" && ( + + + + + + + {"-"} + + + + + + + )} {c.type === "date" && ( ({ diff --git a/src/i18n/zh/dashboard.json b/src/i18n/zh/dashboard.json index a7893e4..ec1b5e4 100644 --- a/src/i18n/zh/dashboard.json +++ b/src/i18n/zh/dashboard.json @@ -56,5 +56,5 @@ "No": "無", "Responsible Escalation List": "負責的上報列表", "show completed logs": "顯示已完成上報", - "Rows per page": "每頁行數" + "Rows per page": "每頁行數" } diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index e419f50..26776d1 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -271,5 +271,6 @@ "Total (Verified + Bad + Missing) must equal Required quantity": "驗證數量 + 不良數量 + 缺失數量必須等於需求數量", "BOM Status": "材料預備狀況", "Estimated Production Date": "預計生產日期", - "Plan Start": "預計生產日期" + "Plan Start": "預計生產日期", + "Plan Start To": "預計生產日期(至)" } diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index d3d1fdd..c5dd822 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -367,9 +367,17 @@ "Enter missing quantity (required if no bad items)": "請輸入缺少數量(如果沒有不良項目)", "Submit All Scanned": "提交所有已掃描項目", "Submitting...": "提交中...", - "COMPLETED": "已完成" - - - + "COMPLETED": "已完成", + "Confirm print: (": "確認列印全部草稿?(總數量:", + "piece(s))": "份)", + "Printing...": "列印中", + "Please wait...": "請稍後", + "No available pick order(s) for this floor.": "此樓層沒有可用的提料單", + "You already have a pick order in progess. Please complete it first before taking next pick order.": "請先完成目前的提料單,再提取下一張", + "Error occurred during assignment.": "提料單分配錯誤", + "Info": "消息", + "Warning": "警告", + "Error": "錯誤", + "Batch Print": "批量列印" } \ No newline at end of file diff --git a/src/i18n/zh/purchaseOrder.json b/src/i18n/zh/purchaseOrder.json index de1da99..9900803 100644 --- a/src/i18n/zh/purchaseOrder.json +++ b/src/i18n/zh/purchaseOrder.json @@ -97,7 +97,7 @@ "acceptedWeight": "接受重量", "productionDate": "生產日期", "reportQty": "上報數量", - "Default Warehouse": "預設倉庫", + "Default Warehouse": "建議倉位", "Select warehouse": "選擇倉庫", "Putaway Detail": "上架詳情", "Delivery Detail": "來貨詳情",