| @@ -508,6 +508,11 @@ export interface ProductProcessLineInfoResponse { | |||
| startTime: string, | |||
| endTime: string | |||
| } | |||
| export interface FloorPickCount { | |||
| floor: string; | |||
| finishedCount: number; | |||
| totalCount: number; | |||
| } | |||
| export interface AllJoPickOrderResponse { | |||
| id: number; | |||
| pickOrderId: number | null; | |||
| @@ -523,6 +528,7 @@ export interface AllJoPickOrderResponse { | |||
| uomName: string; | |||
| jobOrderStatus: string; | |||
| finishedPickOLineCount: number; | |||
| floorPickCounts: FloorPickCount[]; | |||
| } | |||
| export interface UpdateJoPickOrderHandledByRequest { | |||
| pickOrderId: number; | |||
| @@ -1384,4 +1384,55 @@ export const fetchReleasedDoPickOrders = async (): Promise<ReleasedDoPickOrderRe | |||
| }, | |||
| ); | |||
| return response; | |||
| }; | |||
| // 新增:Released Do Pick Order 列表項目(對應後端 ReleasedDoPickOrderListItem) | |||
| export interface ReleasedDoPickOrderListItem { | |||
| id: number; | |||
| requiredDeliveryDate: string | null; | |||
| shopCode: string | null; | |||
| shopName: string | null; | |||
| storeId: string | null; | |||
| truckLanceCode: string | null; | |||
| truckDepartureTime: string | null; | |||
| deliveryOrderCodes: string[]; | |||
| } | |||
| // 修改:fetchReleasedDoPickOrders 支援 shopName 篩選,並回傳新結構 | |||
| export const fetchReleasedDoPickOrdersForSelection = async ( | |||
| shopName?: string, | |||
| storeId?: string, | |||
| truck?: string | |||
| ): Promise<ReleasedDoPickOrderListItem[]> => { | |||
| const params = new URLSearchParams(); | |||
| if (shopName?.trim()) params.append("shopName", shopName.trim()); | |||
| if (storeId?.trim()) params.append("storeId", storeId.trim()); | |||
| if (truck?.trim()) params.append("truck", truck.trim()); | |||
| const query = params.toString(); | |||
| const url = `${BASE_API_URL}/doPickOrder/released${query ? `?${query}` : ""}`; | |||
| const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { | |||
| method: "GET", | |||
| }); | |||
| return response ?? []; | |||
| }; | |||
| export const fetchReleasedDoPickOrderCountByStore = async ( | |||
| storeId: string | |||
| ): Promise<number> => { | |||
| const list = await fetchReleasedDoPickOrdersForSelection(undefined, storeId); | |||
| return list.length; | |||
| }; | |||
| // 新增:依 doPickOrderId 分配 | |||
| export const assignByDoPickOrderId = async ( | |||
| userId: number, | |||
| doPickOrderId: number | |||
| ): Promise<PostPickOrderResponse> => { | |||
| const response = await serverFetchJson<PostPickOrderResponse>( | |||
| `${BASE_API_URL}/doPickOrder/assign-by-id`, | |||
| { | |||
| method: "POST", | |||
| headers: { "Content-Type": "application/json" }, | |||
| body: JSON.stringify({ userId, doPickOrderId }), | |||
| } | |||
| ); | |||
| revalidateTag("pickorder"); | |||
| return response; | |||
| }; | |||
| @@ -95,6 +95,7 @@ export interface AllPickedStockTakeListReponse { | |||
| totalItemNumber: number; | |||
| startTime: string | null; | |||
| endTime: string | null; | |||
| planStartDate: string | null; | |||
| reStockTakeTrueFalse: boolean; | |||
| } | |||
| @@ -5,9 +5,10 @@ import { useCallback, useEffect, useState } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useSession } from "next-auth/react"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| import { fetchStoreLaneSummary, assignByLane, type StoreLaneSummary } from "@/app/api/pickOrder/actions"; | |||
| import { fetchStoreLaneSummary,fetchReleasedDoPickOrdersForSelection,fetchReleasedDoPickOrderCountByStore, assignByLane, type StoreLaneSummary } from "@/app/api/pickOrder/actions"; | |||
| import Swal from "sweetalert2"; | |||
| import dayjs from "dayjs"; | |||
| import ReleasedDoPickOrderSelectModal from "./ReleasedDoPickOrderSelectModal"; | |||
| interface Props { | |||
| onPickOrderAssigned?: () => void; | |||
| @@ -18,7 +19,11 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||
| const { t } = useTranslation("pickOrder"); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | |||
| const [selectedStore, setSelectedStore] = useState<string>("2/F"); | |||
| const [selectedTruck, setSelectedTruck] = useState<string>(""); | |||
| const [modalOpen, setModalOpen] = useState(false); | |||
| const [truckCounts2F, setTruckCounts2F] = useState<{ truck: string; count: number }[]>([]); | |||
| const [truckCounts4F, setTruckCounts4F] = useState<{ truck: string; count: number }[]>([]); | |||
| const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null); | |||
| const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | |||
| const [isLoadingSummary, setIsLoadingSummary] = useState(false); | |||
| @@ -56,7 +61,33 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||
| useEffect(() => { | |||
| loadSummaries(); | |||
| }, [loadSummaries]); | |||
| useEffect(() => { | |||
| const loadCounts = async () => { | |||
| try { | |||
| const [list2F, list4F] = await Promise.all([ | |||
| fetchReleasedDoPickOrdersForSelection(undefined, "2/F"), | |||
| fetchReleasedDoPickOrdersForSelection(undefined, "4/F"), | |||
| ]); | |||
| const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | |||
| const map: Record<string, number> = {}; | |||
| list.forEach((item) => { | |||
| const t = item.truckLanceCode || "-"; | |||
| map[t] = (map[t] || 0) + 1; | |||
| }); | |||
| return Object.entries(map) | |||
| .map(([truck, count]) => ({ truck, count })) | |||
| .sort((a, b) => a.truck.localeCompare(b.truck)); | |||
| }; | |||
| setTruckCounts2F(groupByTruck(list2F)); | |||
| setTruckCounts4F(groupByTruck(list4F)); | |||
| } catch (e) { | |||
| console.error("Error loading counts:", e); | |||
| setTruckCounts2F([]); | |||
| setTruckCounts4F([]); | |||
| } | |||
| }; | |||
| loadCounts(); | |||
| }, [loadSummaries]); | |||
| const handleAssignByLane = useCallback(async ( | |||
| storeId: string, | |||
| truckDepartureTime: string, | |||
| @@ -468,6 +499,194 @@ const getDateLabel = (offset: number) => { | |||
| </Box> | |||
| </Stack> | |||
| </Grid> | |||
| {/* 2/F 未完成已放單 - 與上方相同 UI */} | |||
| <Grid item xs={12}> | |||
| <Box | |||
| sx={{ | |||
| py: 2, | |||
| mt: 1, | |||
| mb: 0.5, | |||
| borderTop: "1px solid #e0e0e0", | |||
| }} | |||
| > | |||
| <Typography | |||
| variant="subtitle1" | |||
| sx={{ fontWeight: 600, mb: 0.5 }} | |||
| > | |||
| {t("Not yet finished released do pick orders")} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Released orders not yet completed - click lane to select and assign")} | |||
| </Typography> | |||
| </Box> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | |||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}> | |||
| 2/F | |||
| </Typography> | |||
| <Box | |||
| sx={{ | |||
| border: "1px solid #e0e0e0", | |||
| borderRadius: 1, | |||
| p: 1, | |||
| backgroundColor: "#fafafa", | |||
| flex: 1, | |||
| }} | |||
| > | |||
| {truckCounts2F.length === 0 ? ( | |||
| <Typography | |||
| variant="body2" | |||
| color="text.secondary" | |||
| sx={{ | |||
| fontWeight: 600, | |||
| fontSize: "1rem", | |||
| textAlign: "center", | |||
| py: 1, | |||
| }} | |||
| > | |||
| {t("No entries available")} | |||
| </Typography> | |||
| ) : ( | |||
| <Grid container spacing={1}> | |||
| {truckCounts2F.map(({ truck, count }) => ( | |||
| <Grid item xs={6} sm={4} md={3} key={`2F-${truck}`} sx={{ display: "flex" }}> | |||
| <Button | |||
| variant="outlined" | |||
| size="medium" | |||
| onClick={() => { | |||
| setSelectedStore("2/F"); | |||
| setSelectedTruck(truck); | |||
| setModalOpen(true); | |||
| }} | |||
| sx={{ | |||
| flex: 1, | |||
| fontSize: "1.1rem", | |||
| py: 1, | |||
| px: 1.5, | |||
| borderWidth: 1, | |||
| borderColor: "#ccc", | |||
| fontWeight: 500, | |||
| "&:hover": { | |||
| borderColor: "#999", | |||
| backgroundColor: "#f5f5f5", | |||
| }, | |||
| }} | |||
| > | |||
| {`${truck} (${count})`} | |||
| </Button> | |||
| </Grid> | |||
| ))} | |||
| </Grid> | |||
| )} | |||
| </Box> | |||
| </Stack> | |||
| </Grid> | |||
| {/* 4/F 未完成已放單 - 與上方相同 UI */} | |||
| <Grid item xs={12}> | |||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | |||
| <Typography variant="h6" sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}> | |||
| 4/F | |||
| </Typography> | |||
| <Box | |||
| sx={{ | |||
| border: "1px solid #e0e0e0", | |||
| borderRadius: 1, | |||
| p: 1, | |||
| backgroundColor: "#fafafa", | |||
| flex: 1, | |||
| }} | |||
| > | |||
| {truckCounts4F.length === 0 ? ( | |||
| <Typography | |||
| variant="body2" | |||
| color="text.secondary" | |||
| sx={{ | |||
| fontWeight: 600, | |||
| fontSize: "1rem", | |||
| textAlign: "center", | |||
| py: 1, | |||
| }} | |||
| > | |||
| {t("No entries available")} | |||
| </Typography> | |||
| ) : ( | |||
| <Grid container spacing={1}> | |||
| {truckCounts4F.map(({ truck, count }) => ( | |||
| <Grid item xs={6} sm={4} md={3} key={`4F-${truck}`} sx={{ display: "flex" }}> | |||
| <Button | |||
| variant="outlined" | |||
| size="medium" | |||
| onClick={() => { | |||
| setSelectedStore("4/F"); | |||
| setSelectedTruck(truck); | |||
| setModalOpen(true); | |||
| }} | |||
| sx={{ | |||
| flex: 1, | |||
| fontSize: "1.1rem", | |||
| py: 1, | |||
| px: 1.5, | |||
| borderWidth: 1, | |||
| borderColor: "#ccc", | |||
| fontWeight: 500, | |||
| "&:hover": { | |||
| borderColor: "#999", | |||
| backgroundColor: "#f5f5f5", | |||
| }, | |||
| }} | |||
| > | |||
| {`${truck} (${count})`} | |||
| </Button> | |||
| </Grid> | |||
| ))} | |||
| </Grid> | |||
| )} | |||
| </Box> | |||
| </Stack> | |||
| </Grid> | |||
| <ReleasedDoPickOrderSelectModal | |||
| open={modalOpen} | |||
| storeId={selectedStore} | |||
| truck={selectedTruck} | |||
| onClose={() => setModalOpen(false)} | |||
| onAssigned={() => { | |||
| loadSummaries(); | |||
| const loadCounts = async () => { | |||
| try { | |||
| const [list2F, list4F] = await Promise.all([ | |||
| fetchReleasedDoPickOrdersForSelection(undefined, "2/F"), | |||
| fetchReleasedDoPickOrdersForSelection(undefined, "4/F"), | |||
| ]); | |||
| const groupByTruck = (list: { truckLanceCode?: string | null }[]) => { | |||
| const map: Record<string, number> = {}; | |||
| list.forEach((item) => { | |||
| const t = item.truckLanceCode || "-"; | |||
| map[t] = (map[t] || 0) + 1; | |||
| }); | |||
| return Object.entries(map) | |||
| .map(([truck, count]) => ({ truck, count })) | |||
| .sort((a, b) => a.truck.localeCompare(b.truck)); | |||
| }; | |||
| setTruckCounts2F(groupByTruck(list2F)); | |||
| setTruckCounts4F(groupByTruck(list4F)); | |||
| } catch (e) { | |||
| setTruckCounts2F([]); | |||
| setTruckCounts4F([]); | |||
| } | |||
| }; | |||
| loadCounts(); | |||
| onPickOrderAssigned?.(); | |||
| onSwitchToDetailTab?.(); | |||
| }} | |||
| /> | |||
| </Grid> | |||
| @@ -1097,7 +1097,7 @@ const fetchFgPickOrdersData = useCallback(async () => { | |||
| const paginatedData = useMemo(() => { | |||
| // ✅ Fix: Add safety check to ensure combinedLotData is an array | |||
| if (!Array.isArray(combinedLotData)) { | |||
| console.warn("⚠️ combinedLotData is not an array:", combinedLotData); | |||
| console.warn(" combinedLotData is not an array:", combinedLotData); | |||
| return []; | |||
| } | |||
| @@ -3124,7 +3124,7 @@ paginatedData.map((lot, index) => { | |||
| }} | |||
| > | |||
| {lot.lotNo || | |||
| t('No Stock Available')} | |||
| t('This lot is not available, please scan another lot.')} | |||
| </Typography> | |||
| </Box> | |||
| </TableCell> | |||
| @@ -72,7 +72,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo, jobT | |||
| } | |||
| return response.json(); | |||
| }; | |||
| /* | |||
| useEffect(() => { | |||
| const fetchDetailedJos = async () => { | |||
| const detailedMap = new Map<number, JobOrder>(); | |||
| @@ -98,6 +98,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo, jobT | |||
| fetchDetailedJos(); | |||
| } | |||
| }, [filteredJos]); | |||
| */ | |||
| /* | |||
| useEffect(() => { | |||
| const fetchInventoryData = async () => { | |||
| @@ -140,6 +140,11 @@ const JoPickOrderList: React.FC<Props> = ({ onSwitchToRecordTab }) =>{ | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Required Qty")}: {pickOrder.reqQty} ({pickOrder.uomName}) | |||
| </Typography> | |||
| {pickOrder.floorPickCounts?.map(({ floor, finishedCount, totalCount }) => ( | |||
| <Typography key={floor} variant="body2" color="text.secondary" component="span" sx={{ mr: 1 }}> | |||
| {floor}: {finishedCount}/{totalCount} | |||
| </Typography> | |||
| ))} | |||
| {statusLower !== "pending" && finishedCount > 0 && ( | |||
| <Box sx={{ mt: 1 }}> | |||
| <Typography variant="body2" fontWeight={600}> | |||
| @@ -186,7 +186,8 @@ const QrCodeModal: React.FC<{ | |||
| const { t } = useTranslation("jo"); | |||
| const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | |||
| const [manualInput, setManualInput] = useState<string>(''); | |||
| // 楼层筛选状态 | |||
| const [selectedFloor, setSelectedFloor] = useState<string | null>(null); | |||
| const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false); | |||
| const [manualInputError, setManualInputError] = useState<boolean>(false); | |||
| const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false); | |||
| @@ -474,7 +475,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| // Add QR modal states | |||
| const [qrModalOpen, setQrModalOpen] = useState(false); | |||
| const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null); | |||
| const [selectedFloor, setSelectedFloor] = useState<string | null>(null); | |||
| // Add GoodPickExecutionForm states | |||
| const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); | |||
| const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); | |||
| @@ -545,6 +546,17 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| return allLots; | |||
| }, []); | |||
| const extractFloor = (lot: any): string => { | |||
| const raw = lot.routerRoute || lot.routerArea || lot.location || ''; | |||
| const match = raw.match(/^(\d+F?)/i) || raw.split('-')[0]; | |||
| return (match?.[1] || match || raw || '').toUpperCase().replace(/(\d)F?/i, '$1F'); | |||
| }; | |||
| // 楼层排序权重:4F > 3F > 2F(数字越大越靠前) | |||
| const floorSortOrder = (floor: string): number => { | |||
| const n = parseInt(floor.replace(/\D/g, ''), 10); | |||
| return isNaN(n) ? 0 : n; | |||
| }; | |||
| const combinedLotData = useMemo(() => { | |||
| return getAllLotsFromHierarchical(jobOrderData); | |||
| }, [jobOrderData, getAllLotsFromHierarchical]); | |||
| @@ -1910,23 +1922,31 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| const scannedItemsCount = useMemo(() => { | |||
| return combinedLotData.filter(lot => lot.stockOutLineStatus === 'checked').length; | |||
| }, [combinedLotData]); | |||
| // Progress bar data (align with Finished Good execution detail) | |||
| const progress = useMemo(() => { | |||
| if (combinedLotData.length === 0) { | |||
| return { completed: 0, total: 0 }; | |||
| } | |||
| const nonPendingCount = combinedLotData.filter((lot) => { | |||
| const status = lot.stockOutLineStatus?.toLowerCase(); | |||
| return status !== 'pending'; | |||
| }).length; | |||
| return { | |||
| completed: nonPendingCount, | |||
| total: combinedLotData.length, | |||
| }; | |||
| // 先定义 filteredByFloor 和 availableFloors | |||
| const availableFloors = useMemo(() => { | |||
| const floors = new Set<string>(); | |||
| combinedLotData.forEach(lot => { | |||
| const f = extractFloor(lot); | |||
| if (f) floors.add(f); | |||
| }); | |||
| return Array.from(floors).sort((a, b) => floorSortOrder(b) - floorSortOrder(a)); | |||
| }, [combinedLotData]); | |||
| const filteredByFloor = useMemo(() => { | |||
| if (!selectedFloor) return combinedLotData; | |||
| return combinedLotData.filter(lot => extractFloor(lot) === selectedFloor); | |||
| }, [combinedLotData, selectedFloor]); | |||
| // Progress bar data - 现在可以正确引用 filteredByFloor | |||
| const progress = useMemo(() => { | |||
| const data = selectedFloor ? filteredByFloor : combinedLotData; | |||
| if (data.length === 0) return { completed: 0, total: 0 }; | |||
| const nonPendingCount = data.filter(lot => | |||
| lot.stockOutLineStatus?.toLowerCase() !== 'pending' | |||
| ).length; | |||
| return { completed: nonPendingCount, total: data.length }; | |||
| }, [selectedFloor, filteredByFloor, combinedLotData]); | |||
| // Handle reject lot | |||
| const handleRejectLot = useCallback(async (lot: any) => { | |||
| if (!lot.stockOutLineId) { | |||
| @@ -2057,15 +2077,18 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| // Pagination data with sorting by routerIndex | |||
| const paginatedData = useMemo(() => { | |||
| // Sort by routerIndex first, then by other criteria | |||
| const sortedData = [...combinedLotData].sort((a, b) => { | |||
| const aIndex = a.routerIndex || 0; | |||
| const bIndex = b.routerIndex || 0; | |||
| // Primary sort: by routerIndex | |||
| if (aIndex !== bIndex) { | |||
| return aIndex - bIndex; | |||
| } | |||
| const sourceData = selectedFloor ? filteredByFloor : combinedLotData; | |||
| const sortedData = [...sourceData].sort((a, b) => { | |||
| const floorA = extractFloor(a); | |||
| const floorB = extractFloor(b); | |||
| const orderA = floorSortOrder(floorA); | |||
| const orderB = floorSortOrder(floorB); | |||
| if (orderA !== orderB) return orderB - orderA; // 4F, 3F, 2F | |||
| // 同楼层再按 routerIndex、pickOrderCode、lotNo | |||
| const aIndex = a.routerIndex ?? 0; | |||
| const bIndex = b.routerIndex ?? 0; | |||
| if (aIndex !== bIndex) return aIndex - bIndex; | |||
| // Secondary sort: by pickOrderCode if routerIndex is the same | |||
| if (a.pickOrderCode !== b.pickOrderCode) { | |||
| @@ -2079,7 +2102,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| const startIndex = paginationController.pageNum * paginationController.pageSize; | |||
| const endIndex = startIndex + paginationController.pageSize; | |||
| return sortedData.slice(startIndex, endIndex); | |||
| }, [combinedLotData, paginationController]); | |||
| }, [selectedFloor, filteredByFloor, combinedLotData, paginationController]); | |||
| // Add these functions for manual scanning | |||
| const handleStartScan = useCallback(() => { | |||
| @@ -2188,7 +2211,25 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| successMessage={t("QR code verified.")} | |||
| /> | |||
| </Box> | |||
| <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}> | |||
| <Button | |||
| variant={selectedFloor === null ? 'contained' : 'outlined'} | |||
| size="small" | |||
| onClick={() => setSelectedFloor(null)} | |||
| > | |||
| {t("All")} | |||
| </Button> | |||
| {availableFloors.map(floor => ( | |||
| <Button | |||
| key={floor} | |||
| variant={selectedFloor === floor ? 'contained' : 'outlined'} | |||
| size="small" | |||
| onClick={() => setSelectedFloor(floor)} | |||
| > | |||
| {floor} | |||
| </Button> | |||
| ))} | |||
| </Box> | |||
| {/* Job Order Header */} | |||
| {jobOrderData && ( | |||
| <Paper sx={{ p: 2 }}> | |||
| @@ -2479,7 +2520,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||
| <TablePagination | |||
| component="div" | |||
| count={combinedLotData.length} | |||
| count={selectedFloor ? filteredByFloor.length : combinedLotData.length} | |||
| page={paginationController.pageNum} | |||
| rowsPerPage={paginationController.pageSize} | |||
| onPageChange={handlePageChange} | |||
| @@ -165,7 +165,11 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ onCardClick, onReStockT | |||
| if (session.totalInventoryLotNumber === 0) return 0; | |||
| return Math.round((session.currentStockTakeItemNumber / session.totalInventoryLotNumber) * 100); | |||
| }; | |||
| const planStartDate = (() => { | |||
| const first = stockTakeSessions.find(s => s.planStartDate); | |||
| if (!first?.planStartDate) return null; | |||
| return dayjs(first.planStartDate).format(OUTPUT_DATE_FORMAT); | |||
| })(); | |||
| if (loading) { | |||
| return ( | |||
| <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}> | |||
| @@ -177,9 +181,15 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ onCardClick, onReStockT | |||
| return ( | |||
| <Box> | |||
| <Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 }}> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Total Sections")}: {stockTakeSessions.length} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {t("Start Stock Take Date")}: {planStartDate || "-"} | |||
| </Typography> | |||
| <Button | |||
| variant="contained" | |||
| color="primary" | |||
| @@ -214,12 +224,12 @@ const PickerCardList: React.FC<PickerCardListProps> = ({ onCardClick, onReStockT | |||
| <Typography variant="subtitle1" fontWeight={600}> | |||
| {t("Section")}: {session.stockTakeSession} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}> | |||
| {t("Last Stock Take Date")}: {lastStockTakeDate || "-"} | |||
| </Typography> | |||
| </Stack> | |||
| <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}> | |||
| {t("Last Stock Take Date")}: {lastStockTakeDate || "-"} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>{t("Stock Taker")}: {session.stockTakerName}</Typography> | |||
| <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1 }}> | |||
| @@ -390,9 +390,9 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({ | |||
| <TableCell>{t("Item-lotNo-ExpiryDate")}</TableCell> | |||
| <TableCell>{t("UOM")}</TableCell> | |||
| <TableCell>{t("Stock Take Qty(include Bad Qty)= Available Qty")}</TableCell> | |||
| <TableCell>{t("Action")}</TableCell> | |||
| <TableCell>{t("Remark")}</TableCell> | |||
| <TableCell>{t("Record Status")}</TableCell> | |||
| <TableCell>{t("Action")}</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| @@ -562,6 +562,18 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({ | |||
| )} | |||
| </Stack> | |||
| </TableCell> | |||
| <TableCell> | |||
| <Stack direction="row" spacing={1}> | |||
| <Button | |||
| size="small" | |||
| variant="contained" | |||
| onClick={() => handleSaveStockTake(detail)} | |||
| disabled={saving || submitDisabled } | |||
| > | |||
| {t("Save")} | |||
| </Button> | |||
| </Stack> | |||
| </TableCell> | |||
| <TableCell sx={{ width: 180 }}> | |||
| {!submitDisabled && isSecondSubmit ? ( | |||
| <> | |||
| @@ -595,18 +607,7 @@ const PickerReStockTake: React.FC<PickerReStockTakeProps> = ({ | |||
| <Chip size="small" label={t(detail.stockTakeRecordStatus || "")} color="default" /> | |||
| )} | |||
| </TableCell> | |||
| <TableCell> | |||
| <Stack direction="row" spacing={1}> | |||
| <Button | |||
| size="small" | |||
| variant="contained" | |||
| onClick={() => handleSaveStockTake(detail)} | |||
| disabled={saving || submitDisabled } | |||
| > | |||
| {t("Save")} | |||
| </Button> | |||
| </Stack> | |||
| </TableCell> | |||
| </TableRow> | |||
| ); | |||
| }) | |||
| @@ -517,10 +517,10 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ | |||
| <TableCell>{t("Item-lotNo-ExpiryDate")}</TableCell> | |||
| <TableCell>{t("UOM")}</TableCell> | |||
| <TableCell>{t("Stock Take Qty(include Bad Qty)= Available Qty")}</TableCell> | |||
| <TableCell>{t("Action")}</TableCell> | |||
| <TableCell>{t("Remark")}</TableCell> | |||
| <TableCell>{t("Record Status")}</TableCell> | |||
| <TableCell>{t("Action")}</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| @@ -728,7 +728,21 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ | |||
| )} | |||
| </Stack> | |||
| </TableCell> | |||
| <TableCell> | |||
| <Stack direction="row" spacing={1}> | |||
| <Button | |||
| size="small" | |||
| variant="contained" | |||
| onClick={() => handleSaveStockTake(detail)} | |||
| disabled={saving || submitDisabled} | |||
| > | |||
| {t("Save")} | |||
| </Button> | |||
| </Stack> | |||
| </TableCell> | |||
| {/* Remark */} | |||
| <TableCell sx={{ width: 180 }}> | |||
| {!submitDisabled && isSecondSubmit ? ( | |||
| @@ -755,7 +769,7 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ | |||
| </TableCell> | |||
| <TableCell> | |||
| {detail.stockTakeRecordStatus === "completed" ? ( | |||
| <Chip | |||
| @@ -784,21 +798,7 @@ const PickerStockTake: React.FC<PickerStockTakeProps> = ({ | |||
| )} | |||
| </TableCell> | |||
| <TableCell> | |||
| <Stack direction="row" spacing={1}> | |||
| <Button | |||
| size="small" | |||
| variant="contained" | |||
| onClick={() => handleSaveStockTake(detail)} | |||
| disabled={saving || submitDisabled} | |||
| > | |||
| {t("Save")} | |||
| </Button> | |||
| </Stack> | |||
| </TableCell> | |||
| </TableRow> | |||
| ); | |||
| }) | |||