|
|
@@ -1,5 +1,5 @@ |
|
|
"use client"; |
|
|
"use client"; |
|
|
import React, { useCallback, useEffect, useState } from "react"; |
|
|
|
|
|
|
|
|
import React, { useCallback, useEffect, useMemo, useState } from "react"; |
|
|
import { |
|
|
import { |
|
|
Box, |
|
|
Box, |
|
|
Button, |
|
|
Button, |
|
|
@@ -12,6 +12,17 @@ import { |
|
|
CircularProgress, |
|
|
CircularProgress, |
|
|
TablePagination, |
|
|
TablePagination, |
|
|
Grid, |
|
|
Grid, |
|
|
|
|
|
FormControl, |
|
|
|
|
|
InputLabel, |
|
|
|
|
|
Select, |
|
|
|
|
|
MenuItem, |
|
|
|
|
|
Checkbox, |
|
|
|
|
|
ListItemText, |
|
|
|
|
|
SelectChangeEvent, |
|
|
|
|
|
Dialog, |
|
|
|
|
|
DialogTitle, |
|
|
|
|
|
DialogContent, |
|
|
|
|
|
DialogActions, |
|
|
} from "@mui/material"; |
|
|
} from "@mui/material"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { fetchItemForPutAway } from "@/app/api/stockIn/actions"; |
|
|
import { fetchItemForPutAway } from "@/app/api/stockIn/actions"; |
|
|
@@ -20,15 +31,16 @@ import { useSession } from "next-auth/react"; |
|
|
import { SessionWithTokens } from "@/config/authConfig"; |
|
|
import { SessionWithTokens } from "@/config/authConfig"; |
|
|
import dayjs from "dayjs"; |
|
|
import dayjs from "dayjs"; |
|
|
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; |
|
|
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; |
|
|
|
|
|
import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
import { |
|
|
fetchAllJoborderProductProcessInfo, |
|
|
|
|
|
AllJoborderProductProcessInfoResponse, |
|
|
AllJoborderProductProcessInfoResponse, |
|
|
updateJo, |
|
|
updateJo, |
|
|
fetchProductProcessesByJobOrderId, |
|
|
fetchProductProcessesByJobOrderId, |
|
|
completeProductProcessLine, |
|
|
completeProductProcessLine, |
|
|
assignJobOrderPickOrder |
|
|
|
|
|
|
|
|
assignJobOrderPickOrder, |
|
|
|
|
|
fetchJoborderProductProcessesPage |
|
|
} from "@/app/api/jo/actions"; |
|
|
} from "@/app/api/jo/actions"; |
|
|
import { StockInLineInput } from "@/app/api/stockIn"; |
|
|
import { StockInLineInput } from "@/app/api/stockIn"; |
|
|
import { PrinterCombo } from "@/app/api/settings/printer"; |
|
|
import { PrinterCombo } from "@/app/api/settings/printer"; |
|
|
@@ -37,12 +49,14 @@ interface ProductProcessListProps { |
|
|
onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void; |
|
|
onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void; |
|
|
onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void; |
|
|
onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void; |
|
|
printerCombo: PrinterCombo[]; |
|
|
printerCombo: PrinterCombo[]; |
|
|
|
|
|
qcReady: boolean; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type SearchParam = "date" | "itemCode" | "jobOrderCode" | "processType"; |
|
|
|
|
|
|
|
|
const PER_PAGE = 6; |
|
|
|
|
|
|
|
|
const PAGE_SIZE = 50; |
|
|
|
|
|
|
|
|
const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo ,onSelectMatchingStock}) => { |
|
|
|
|
|
|
|
|
const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo ,onSelectMatchingStock, qcReady}) => { |
|
|
const { t } = useTranslation( ["common", "production","purchaseOrder","dashboard"]); |
|
|
const { t } = useTranslation( ["common", "production","purchaseOrder","dashboard"]); |
|
|
const { data: session } = useSession() as { data: SessionWithTokens | null }; |
|
|
const { data: session } = useSession() as { data: SessionWithTokens | null }; |
|
|
const sessionToken = session as SessionWithTokens | null; |
|
|
const sessionToken = session as SessionWithTokens | null; |
|
|
@@ -55,6 +69,58 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
type ProcessFilter = "all" | "drink" | "other"; |
|
|
type ProcessFilter = "all" | "drink" | "other"; |
|
|
const [filter, setFilter] = useState<ProcessFilter>("all"); |
|
|
const [filter, setFilter] = useState<ProcessFilter>("all"); |
|
|
const [suggestedLocationCode, setSuggestedLocationCode] = useState<string | null>(null); |
|
|
const [suggestedLocationCode, setSuggestedLocationCode] = useState<string | null>(null); |
|
|
|
|
|
|
|
|
|
|
|
const [appliedSearch, setAppliedSearch] = useState<{ |
|
|
|
|
|
date: string; |
|
|
|
|
|
itemCode: string | null; |
|
|
|
|
|
jobOrderCode: string | null; |
|
|
|
|
|
}>(() => ({ |
|
|
|
|
|
date: dayjs().format("YYYY-MM-DD"), |
|
|
|
|
|
itemCode: null, |
|
|
|
|
|
jobOrderCode: null, |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
const [totalJobOrders, setTotalJobOrders] = useState(0); |
|
|
|
|
|
const [selectedItemCodes, setSelectedItemCodes] = useState<string[]>([]); |
|
|
|
|
|
|
|
|
|
|
|
// Generic confirm dialog for actions (update job order / etc.) |
|
|
|
|
|
const [confirmOpen, setConfirmOpen] = useState(false); |
|
|
|
|
|
const [confirmMessage, setConfirmMessage] = useState(""); |
|
|
|
|
|
const [confirmLoading, setConfirmLoading] = useState(false); |
|
|
|
|
|
const [pendingConfirmAction, setPendingConfirmAction] = useState<null | (() => Promise<void>)>(null); |
|
|
|
|
|
|
|
|
|
|
|
// QC 的业务判定:同一个 jobOrder 下,所有 productProcess 的所有 lines 都必须是 Completed/Pass |
|
|
|
|
|
// 才允许打开 QcStockInModal(避免仅某个 productProcess 完成就提前出现 view stockin)。 |
|
|
|
|
|
const jobOrderQcReadyById = useMemo(() => { |
|
|
|
|
|
const lineDone = (status: unknown) => { |
|
|
|
|
|
const s = String(status ?? "").trim().toLowerCase(); |
|
|
|
|
|
return s === "completed" || s === "pass"; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const byJobOrder = new Map<number, AllJoborderProductProcessInfoResponse[]>(); |
|
|
|
|
|
for (const p of processes) { |
|
|
|
|
|
if (p.jobOrderId == null) continue; |
|
|
|
|
|
const arr = byJobOrder.get(p.jobOrderId) ?? []; |
|
|
|
|
|
arr.push(p); |
|
|
|
|
|
byJobOrder.set(p.jobOrderId, arr); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const result = new Map<number, boolean>(); |
|
|
|
|
|
byJobOrder.forEach((jobOrderProcesses, jobOrderId) => { |
|
|
|
|
|
const hasStockInLine = jobOrderProcesses.some((p) => p.stockInLineId != null); |
|
|
|
|
|
const allLinesDone = |
|
|
|
|
|
jobOrderProcesses.length > 0 && |
|
|
|
|
|
jobOrderProcesses.every((p) => { |
|
|
|
|
|
const lines = p.lines ?? []; |
|
|
|
|
|
// 没有 lines 的情况认为未完成,避免误放行 |
|
|
|
|
|
return lines.length > 0 && lines.every((l) => lineDone(l.status)); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
result.set(jobOrderId, hasStockInLine && allLinesDone); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
}, [processes]); |
|
|
const handleAssignPickOrder = useCallback(async (pickOrderId: number, jobOrderId?: number, productProcessId?: number) => { |
|
|
const handleAssignPickOrder = useCallback(async (pickOrderId: number, jobOrderId?: number, productProcessId?: number) => { |
|
|
if (!currentUserId) { |
|
|
if (!currentUserId) { |
|
|
alert(t("Unable to get user ID")); |
|
|
alert(t("Unable to get user ID")); |
|
|
@@ -106,22 +172,55 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
setOpenModal(true); |
|
|
setOpenModal(true); |
|
|
}, [t]); |
|
|
}, [t]); |
|
|
|
|
|
|
|
|
|
|
|
const handleApplySearch = useCallback((inputs: Record<SearchParam | `${SearchParam}To`, string>) => { |
|
|
|
|
|
const selectedProcessType = (inputs.processType || "all") as ProcessFilter; |
|
|
|
|
|
setFilter(selectedProcessType); |
|
|
|
|
|
setAppliedSearch({ |
|
|
|
|
|
date: inputs.date || dayjs().format("YYYY-MM-DD"), |
|
|
|
|
|
itemCode: inputs.itemCode?.trim() ? inputs.itemCode.trim() : null, |
|
|
|
|
|
jobOrderCode: inputs.jobOrderCode?.trim() ? inputs.jobOrderCode.trim() : null, |
|
|
|
|
|
}); |
|
|
|
|
|
setSelectedItemCodes([]); |
|
|
|
|
|
setPage(0); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const handleResetSearch = useCallback(() => { |
|
|
|
|
|
setFilter("all"); |
|
|
|
|
|
setAppliedSearch({ |
|
|
|
|
|
date: dayjs().format("YYYY-MM-DD"), |
|
|
|
|
|
itemCode: null, |
|
|
|
|
|
jobOrderCode: null, |
|
|
|
|
|
}); |
|
|
|
|
|
setSelectedItemCodes([]); |
|
|
|
|
|
setPage(0); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
const fetchProcesses = useCallback(async () => { |
|
|
const fetchProcesses = useCallback(async () => { |
|
|
setLoading(true); |
|
|
setLoading(true); |
|
|
try { |
|
|
try { |
|
|
const isDrinkParam = |
|
|
const isDrinkParam = |
|
|
filter === "all" ? undefined : filter === "drink" ? true : false; |
|
|
filter === "all" ? undefined : filter === "drink" ? true : false; |
|
|
|
|
|
|
|
|
const data = await fetchAllJoborderProductProcessInfo(isDrinkParam); |
|
|
|
|
|
setProcesses(data || []); |
|
|
|
|
|
setPage(0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const data = await fetchJoborderProductProcessesPage({ |
|
|
|
|
|
date: appliedSearch.date, |
|
|
|
|
|
itemCode: appliedSearch.itemCode, |
|
|
|
|
|
jobOrderCode: appliedSearch.jobOrderCode, |
|
|
|
|
|
qcReady, |
|
|
|
|
|
isDrink: isDrinkParam, |
|
|
|
|
|
page, |
|
|
|
|
|
size: PAGE_SIZE, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
setProcesses(data?.content || []); |
|
|
|
|
|
setTotalJobOrders(data?.totalJobOrders || 0); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
console.error(e); |
|
|
console.error(e); |
|
|
setProcesses([]); |
|
|
setProcesses([]); |
|
|
|
|
|
setTotalJobOrders(0); |
|
|
} finally { |
|
|
} finally { |
|
|
setLoading(false); |
|
|
setLoading(false); |
|
|
} |
|
|
} |
|
|
}, [filter]); |
|
|
|
|
|
|
|
|
}, [filter, appliedSearch, qcReady, page]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
fetchProcesses(); |
|
|
fetchProcesses(); |
|
|
@@ -161,6 +260,29 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
setLoading(false); |
|
|
setLoading(false); |
|
|
} |
|
|
} |
|
|
}, [t, fetchProcesses]); |
|
|
}, [t, fetchProcesses]); |
|
|
|
|
|
|
|
|
|
|
|
const openConfirm = useCallback((message: string, action: () => Promise<void>) => { |
|
|
|
|
|
setConfirmMessage(message); |
|
|
|
|
|
setPendingConfirmAction(() => action); |
|
|
|
|
|
setConfirmOpen(true); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const closeConfirm = useCallback(() => { |
|
|
|
|
|
setConfirmOpen(false); |
|
|
|
|
|
setPendingConfirmAction(null); |
|
|
|
|
|
setConfirmMessage(""); |
|
|
|
|
|
setConfirmLoading(false); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const onConfirm = useCallback(async () => { |
|
|
|
|
|
if (!pendingConfirmAction) return; |
|
|
|
|
|
setConfirmLoading(true); |
|
|
|
|
|
try { |
|
|
|
|
|
await pendingConfirmAction(); |
|
|
|
|
|
} finally { |
|
|
|
|
|
closeConfirm(); |
|
|
|
|
|
} |
|
|
|
|
|
}, [pendingConfirmAction, closeConfirm]); |
|
|
const closeNewModal = useCallback(() => { |
|
|
const closeNewModal = useCallback(() => { |
|
|
// const response = updateJo({ id: 1, status: "storing" }); |
|
|
// const response = updateJo({ id: 1, status: "storing" }); |
|
|
setOpenModal(false); // Close the modal first |
|
|
setOpenModal(false); // Close the modal first |
|
|
@@ -169,8 +291,56 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
// }, 300); // Add a delay to avoid immediate re-trigger of useEffect |
|
|
// }, 300); // Add a delay to avoid immediate re-trigger of useEffect |
|
|
}, [fetchProcesses]); |
|
|
}, [fetchProcesses]); |
|
|
|
|
|
|
|
|
const startIdx = page * PER_PAGE; |
|
|
|
|
|
const paged = processes.slice(startIdx, startIdx + PER_PAGE); |
|
|
|
|
|
|
|
|
const searchedItemOptions = useMemo( |
|
|
|
|
|
() => |
|
|
|
|
|
Array.from( |
|
|
|
|
|
new Map( |
|
|
|
|
|
processes |
|
|
|
|
|
.filter((p) => !!p.itemCode) |
|
|
|
|
|
.map((p) => [p.itemCode, { itemCode: p.itemCode, itemName: p.itemName }]), |
|
|
|
|
|
).values(), |
|
|
|
|
|
), |
|
|
|
|
|
[processes], |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const paged = useMemo(() => { |
|
|
|
|
|
if (selectedItemCodes.length === 0) return processes; |
|
|
|
|
|
return processes.filter((p) => selectedItemCodes.includes(p.itemCode)); |
|
|
|
|
|
}, [processes, selectedItemCodes]); |
|
|
|
|
|
|
|
|
|
|
|
const searchCriteria: Criterion<SearchParam>[] = useMemo( |
|
|
|
|
|
() => [ |
|
|
|
|
|
{ |
|
|
|
|
|
type: "date", |
|
|
|
|
|
label: "Production date", |
|
|
|
|
|
paramName: "date", |
|
|
|
|
|
preFilledValue: dayjs().format("YYYY-MM-DD"), |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
type: "text", |
|
|
|
|
|
label: "Item Code", |
|
|
|
|
|
paramName: "itemCode", |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
type: "text", |
|
|
|
|
|
label: "Job Order Code", |
|
|
|
|
|
paramName: "jobOrderCode", |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
type: "select", |
|
|
|
|
|
label: "Type", |
|
|
|
|
|
paramName: "processType", |
|
|
|
|
|
options: ["all", "drink", "other"], |
|
|
|
|
|
preFilledValue: "all", |
|
|
|
|
|
}, |
|
|
|
|
|
], |
|
|
|
|
|
[], |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const handleSelectedItemCodesChange = useCallback((e: SelectChangeEvent<string[]>) => { |
|
|
|
|
|
const nextValue = e.target.value; |
|
|
|
|
|
setSelectedItemCodes(typeof nextValue === "string" ? nextValue.split(",") : nextValue); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<Box> |
|
|
<Box> |
|
|
@@ -180,31 +350,34 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
</Box> |
|
|
</Box> |
|
|
) : ( |
|
|
) : ( |
|
|
<Box> |
|
|
<Box> |
|
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap', mb: 2 }}> |
|
|
|
|
|
<Button |
|
|
|
|
|
variant={filter === 'all' ? 'contained' : 'outlined'} |
|
|
|
|
|
size="small" |
|
|
|
|
|
onClick={() => setFilter('all')} |
|
|
|
|
|
> |
|
|
|
|
|
{t("All")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
<Button |
|
|
|
|
|
variant={filter === 'drink' ? 'contained' : 'outlined'} |
|
|
|
|
|
size="small" |
|
|
|
|
|
onClick={() => setFilter('drink')} |
|
|
|
|
|
> |
|
|
|
|
|
{t("Drink")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
<Button |
|
|
|
|
|
variant={filter === 'other' ? 'contained' : 'outlined'} |
|
|
|
|
|
size="small" |
|
|
|
|
|
onClick={() => setFilter('other')} |
|
|
|
|
|
> |
|
|
|
|
|
{t("Other")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
</Box> |
|
|
|
|
|
|
|
|
<SearchBox<SearchParam> |
|
|
|
|
|
criteria={searchCriteria} |
|
|
|
|
|
onSearch={handleApplySearch} |
|
|
|
|
|
onReset={handleResetSearch} |
|
|
|
|
|
extraActions={ |
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 260 }}> |
|
|
|
|
|
<InputLabel>{t("Searched Item")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
multiple |
|
|
|
|
|
value={selectedItemCodes} |
|
|
|
|
|
label={t("Item Code")} |
|
|
|
|
|
renderValue={(selected) => |
|
|
|
|
|
(selected as string[]).length === 0 ? t("All") : (selected as string[]).join(", ") |
|
|
|
|
|
} |
|
|
|
|
|
onChange={handleSelectedItemCodesChange} |
|
|
|
|
|
> |
|
|
|
|
|
{searchedItemOptions.map((item) => ( |
|
|
|
|
|
<MenuItem key={item.itemCode} value={item.itemCode}> |
|
|
|
|
|
<Checkbox checked={selectedItemCodes.includes(item.itemCode)} /> |
|
|
|
|
|
<ListItemText primary={[item.itemCode, item.itemName].filter(Boolean).join(" - ")} /> |
|
|
|
|
|
</MenuItem> |
|
|
|
|
|
))} |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> |
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> |
|
|
{t("Total processes")}: {processes.length} |
|
|
|
|
|
|
|
|
{t("Total job orders")}: {totalJobOrders} {selectedItemCodes.length > 0 ? `| ${t("Filtered")}: ${paged.length}` : ""} |
|
|
</Typography> |
|
|
</Typography> |
|
|
|
|
|
|
|
|
<Grid container spacing={2}> |
|
|
<Grid container spacing={2}> |
|
|
@@ -238,6 +411,11 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
.filter(l => String(l.status ?? "").trim() !== "") |
|
|
.filter(l => String(l.status ?? "").trim() !== "") |
|
|
.filter(l => String(l.status).toLowerCase() === "in_progress"); |
|
|
.filter(l => String(l.status).toLowerCase() === "in_progress"); |
|
|
|
|
|
|
|
|
|
|
|
const canQc = |
|
|
|
|
|
process.jobOrderId != null && |
|
|
|
|
|
process.stockInLineId != null && |
|
|
|
|
|
jobOrderQcReadyById.get(process.jobOrderId) === true; |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<Grid key={process.id} item xs={12} sm={6} md={4}> |
|
|
<Grid key={process.id} item xs={12} sm={6} md={4}> |
|
|
<Card |
|
|
<Card |
|
|
@@ -330,13 +508,26 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
> |
|
|
> |
|
|
{t("Matching Stock")} |
|
|
{t("Matching Stock")} |
|
|
</Button> |
|
|
</Button> |
|
|
|
|
|
|
|
|
{statusLower !== "completed" && ( |
|
|
{statusLower !== "completed" && ( |
|
|
<Button variant="contained" size="small" onClick={() => handleUpdateJo(process)}> |
|
|
|
|
|
|
|
|
<Button |
|
|
|
|
|
variant="contained" |
|
|
|
|
|
size="small" |
|
|
|
|
|
onClick={() => |
|
|
|
|
|
openConfirm( |
|
|
|
|
|
t("Confirm to update this Job Order?"), |
|
|
|
|
|
async () => { |
|
|
|
|
|
await handleUpdateJo(process); |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
{t("Update Job Order")} |
|
|
{t("Update Job Order")} |
|
|
</Button> |
|
|
</Button> |
|
|
)} |
|
|
)} |
|
|
{statusLower === "completed" && ( |
|
|
|
|
|
<Button variant="contained" size="small" onClick={() => handleViewStockIn(process)}> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{canQc && ( |
|
|
|
|
|
<Button variant="contained" size="small" onClick={() => handleViewStockIn(process)}> |
|
|
{t("view stockin")} |
|
|
{t("view stockin")} |
|
|
</Button> |
|
|
</Button> |
|
|
)} |
|
|
)} |
|
|
@@ -358,14 +549,32 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess |
|
|
printSource="productionProcess" |
|
|
printSource="productionProcess" |
|
|
uiMode="default" |
|
|
uiMode="default" |
|
|
/> |
|
|
/> |
|
|
{processes.length > 0 && ( |
|
|
|
|
|
|
|
|
<Dialog open={confirmOpen} onClose={closeConfirm} maxWidth="xs" fullWidth> |
|
|
|
|
|
<DialogTitle>{t("Confirm")}</DialogTitle> |
|
|
|
|
|
<DialogContent> |
|
|
|
|
|
<Typography variant="body2">{confirmMessage}</Typography> |
|
|
|
|
|
</DialogContent> |
|
|
|
|
|
<DialogActions> |
|
|
|
|
|
<Button onClick={closeConfirm} disabled={confirmLoading}> |
|
|
|
|
|
{t("Cancel")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
<Button |
|
|
|
|
|
variant="contained" |
|
|
|
|
|
onClick={onConfirm} |
|
|
|
|
|
disabled={confirmLoading || !pendingConfirmAction} |
|
|
|
|
|
> |
|
|
|
|
|
{confirmLoading ? t("Processing...") : t("Confirm")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
</DialogActions> |
|
|
|
|
|
</Dialog> |
|
|
|
|
|
{totalJobOrders > 0 && ( |
|
|
<TablePagination |
|
|
<TablePagination |
|
|
component="div" |
|
|
component="div" |
|
|
count={processes.length} |
|
|
|
|
|
|
|
|
count={totalJobOrders} |
|
|
page={page} |
|
|
page={page} |
|
|
rowsPerPage={PER_PAGE} |
|
|
|
|
|
|
|
|
rowsPerPage={PAGE_SIZE} |
|
|
onPageChange={(e, p) => setPage(p)} |
|
|
onPageChange={(e, p) => setPage(p)} |
|
|
rowsPerPageOptions={[PER_PAGE]} |
|
|
|
|
|
|
|
|
rowsPerPageOptions={[PAGE_SIZE]} |
|
|
/> |
|
|
/> |
|
|
)} |
|
|
)} |
|
|
</Box> |
|
|
</Box> |
|
|
|