| @@ -118,4 +118,27 @@ export const fetchBagLotLines = cache(async (bagId: number) => | |||||
| export const fetchBagConsumptions = cache(async (bagLotLineId: number) => | export const fetchBagConsumptions = cache(async (bagLotLineId: number) => | ||||
| serverFetchJson<BagConsumptionResponse[]>(`${BASE_API_URL}/bag/lot-lines/${bagLotLineId}/consumptions`, { method: "GET" }) | serverFetchJson<BagConsumptionResponse[]>(`${BASE_API_URL}/bag/lot-lines/${bagLotLineId}/consumptions`, { method: "GET" }) | ||||
| ); | |||||
| ); | |||||
| export interface SoftDeleteBagResponse { | |||||
| id: number | null; | |||||
| code: string | null; | |||||
| name: string | null; | |||||
| type: string | null; | |||||
| message: string | null; | |||||
| errorPosition: string | null; | |||||
| entity: any | null; | |||||
| } | |||||
| export const softDeleteBagByItemId = async (itemId: number): Promise<SoftDeleteBagResponse> => { | |||||
| const response = await serverFetchJson<SoftDeleteBagResponse>( | |||||
| `${BASE_API_URL}/bag/by-item/${itemId}/soft-delete`, | |||||
| { | |||||
| method: "PUT", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| } | |||||
| ); | |||||
| revalidateTag("bagInfo"); | |||||
| revalidateTag("bags"); | |||||
| return response; | |||||
| }; | |||||
| @@ -197,9 +197,12 @@ export const fetchTicketReleaseTable = cache(async (startDate: string, endDate: | |||||
| ); | ); | ||||
| }); | }); | ||||
| export const fetchTruckScheduleDashboard = cache(async () => { | |||||
| export const fetchTruckScheduleDashboard = cache(async (date?: string) => { | |||||
| const url = date | |||||
| ? `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard?date=${date}` | |||||
| : `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard`; | |||||
| return await serverFetchJson<TruckScheduleDashboardItem[]>( | return await serverFetchJson<TruckScheduleDashboardItem[]>( | ||||
| `${BASE_API_URL}/doPickOrder/truck-schedule-dashboard`, | |||||
| url, | |||||
| { | { | ||||
| method: "GET", | method: "GET", | ||||
| } | } | ||||
| @@ -5,8 +5,8 @@ import { | |||||
| type TruckScheduleDashboardItem | type TruckScheduleDashboardItem | ||||
| } from "./actions"; | } from "./actions"; | ||||
| export const fetchTruckScheduleDashboardClient = async (): Promise<TruckScheduleDashboardItem[]> => { | |||||
| return await fetchTruckScheduleDashboard(); | |||||
| export const fetchTruckScheduleDashboardClient = async (date?: string): Promise<TruckScheduleDashboardItem[]> => { | |||||
| return await fetchTruckScheduleDashboard(date); | |||||
| }; | }; | ||||
| export type { TruckScheduleDashboardItem }; | export type { TruckScheduleDashboardItem }; | ||||
| @@ -45,6 +45,7 @@ export type CreateItemInputs = { | |||||
| isEgg?: boolean | undefined; | isEgg?: boolean | undefined; | ||||
| isFee?: boolean | undefined; | isFee?: boolean | undefined; | ||||
| isBag?: boolean | undefined; | isBag?: boolean | undefined; | ||||
| qcType?: string | undefined; | |||||
| }; | }; | ||||
| export const saveItem = async (data: CreateItemInputs) => { | export const saveItem = async (data: CreateItemInputs) => { | ||||
| @@ -0,0 +1,28 @@ | |||||
| "use client"; | |||||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||||
| import { QcItemInfo } from "./index"; | |||||
| export const fetchQcItemsByCategoryId = async (categoryId: number): Promise<QcItemInfo[]> => { | |||||
| const token = localStorage.getItem("accessToken"); | |||||
| const response = await fetch(`${NEXT_PUBLIC_API_URL}/qcCategories/${categoryId}/items`, { | |||||
| method: "GET", | |||||
| headers: { | |||||
| "Content-Type": "application/json", | |||||
| ...(token && { Authorization: `Bearer ${token}` }), | |||||
| }, | |||||
| }); | |||||
| if (!response.ok) { | |||||
| if (response.status === 401) { | |||||
| throw new Error("Unauthorized: Please log in again"); | |||||
| } | |||||
| throw new Error(`Failed to fetch QC items: ${response.status} ${response.statusText}`); | |||||
| } | |||||
| return response.json(); | |||||
| }; | |||||
| @@ -17,6 +17,15 @@ export interface QcCategoryCombo { | |||||
| label: string; | label: string; | ||||
| } | } | ||||
| export interface QcItemInfo { | |||||
| id: number; | |||||
| qcItemId: number; | |||||
| code: string; | |||||
| name?: string; | |||||
| order: number; | |||||
| description?: string; | |||||
| } | |||||
| export const preloadQcCategory = () => { | export const preloadQcCategory = () => { | ||||
| fetchQcCategories(); | fetchQcCategories(); | ||||
| }; | }; | ||||
| @@ -31,6 +31,7 @@ import { saveItemQcChecks } from "@/app/api/settings/qcCheck/actions"; | |||||
| import { useGridApiRef } from "@mui/x-data-grid"; | import { useGridApiRef } from "@mui/x-data-grid"; | ||||
| import { QcCategoryCombo } from "@/app/api/settings/qcCategory"; | import { QcCategoryCombo } from "@/app/api/settings/qcCategory"; | ||||
| import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
| import { softDeleteBagByItemId } from "@/app/api/bag/action"; | |||||
| type Props = { | type Props = { | ||||
| isEditMode: boolean; | isEditMode: boolean; | ||||
| @@ -173,6 +174,16 @@ const CreateItem: React.FC<Props> = ({ | |||||
| ); | ); | ||||
| } else if (!Boolean(responseQ.id)) { | } else if (!Boolean(responseQ.id)) { | ||||
| } else if (Boolean(responseI.id) && Boolean(responseQ.id)) { | } else if (Boolean(responseI.id) && Boolean(responseQ.id)) { | ||||
| // If special type is not "isBag", soft-delete the bag record if it exists | |||||
| if (data.isBag !== true && data.id) { | |||||
| try { | |||||
| const itemId = typeof data.id === "string" ? parseInt(data.id) : data.id; | |||||
| await softDeleteBagByItemId(itemId); | |||||
| } catch (bagError) { | |||||
| // Log error but don't block the save operation | |||||
| console.log("Error soft-deleting bag:", bagError); | |||||
| } | |||||
| } | |||||
| router.replace(redirPath); | router.replace(redirPath); | ||||
| } | } | ||||
| } | } | ||||
| @@ -29,8 +29,10 @@ import { InputDataGridProps, TableRow } from "../InputDataGrid/InputDataGrid"; | |||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
| import { CreateItemInputs } from "@/app/api/settings/item/actions"; | import { CreateItemInputs } from "@/app/api/settings/item/actions"; | ||||
| import { ItemQc } from "@/app/api/settings/item"; | import { ItemQc } from "@/app/api/settings/item"; | ||||
| import { QcCategoryCombo } from "@/app/api/settings/qcCategory"; | |||||
| import { QcCategoryCombo, QcItemInfo } from "@/app/api/settings/qcCategory"; | |||||
| import { fetchQcItemsByCategoryId } from "@/app/api/settings/qcCategory/client"; | |||||
| import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
| import QcItemsList from "./QcItemsList"; | |||||
| type Props = { | type Props = { | ||||
| // isEditMode: boolean; | // isEditMode: boolean; | ||||
| // type: TypeEnum; | // type: TypeEnum; | ||||
| @@ -43,11 +45,13 @@ type Props = { | |||||
| }; | }; | ||||
| const ProductDetails: React.FC<Props> = ({ isEditMode, qcCategoryCombo, warehouses, defaultValues: initialDefaultValues }) => { | const ProductDetails: React.FC<Props> = ({ isEditMode, qcCategoryCombo, warehouses, defaultValues: initialDefaultValues }) => { | ||||
| const [qcItems, setQcItems] = useState<QcItemInfo[]>([]); | |||||
| const [qcItemsLoading, setQcItemsLoading] = useState(false); | |||||
| const { | const { | ||||
| t, | t, | ||||
| i18n: { language }, | i18n: { language }, | ||||
| } = useTranslation(); | |||||
| } = useTranslation("items"); | |||||
| const { | const { | ||||
| register, | register, | ||||
| @@ -121,6 +125,30 @@ const ProductDetails: React.FC<Props> = ({ isEditMode, qcCategoryCombo, warehous | |||||
| } | } | ||||
| }, [initialDefaultValues, setValue, getValues]); | }, [initialDefaultValues, setValue, getValues]); | ||||
| // Watch qcCategoryId and fetch QC items when it changes | |||||
| const qcCategoryId = watch("qcCategoryId"); | |||||
| useEffect(() => { | |||||
| const fetchItems = async () => { | |||||
| if (qcCategoryId) { | |||||
| setQcItemsLoading(true); | |||||
| try { | |||||
| const items = await fetchQcItemsByCategoryId(qcCategoryId); | |||||
| setQcItems(items); | |||||
| } catch (error) { | |||||
| console.error("Failed to fetch QC items:", error); | |||||
| setQcItems([]); | |||||
| } finally { | |||||
| setQcItemsLoading(false); | |||||
| } | |||||
| } else { | |||||
| setQcItems([]); | |||||
| } | |||||
| }; | |||||
| fetchItems(); | |||||
| }, [qcCategoryId]); | |||||
| return ( | return ( | ||||
| <Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
| <CardContent component={Stack} spacing={4}> | <CardContent component={Stack} spacing={4}> | ||||
| @@ -216,6 +244,26 @@ const ProductDetails: React.FC<Props> = ({ isEditMode, qcCategoryCombo, warehous | |||||
| )} | )} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| <Controller | |||||
| control={control} | |||||
| name="qcType" | |||||
| render={({ field }) => ( | |||||
| <FormControl fullWidth> | |||||
| <InputLabel>{t("QC Type")}</InputLabel> | |||||
| <Select | |||||
| value={field.value || ""} | |||||
| label={t("QC Type")} | |||||
| onChange={field.onChange} | |||||
| onBlur={field.onBlur} | |||||
| > | |||||
| <MenuItem value="IPQC">{t("IPQC")}</MenuItem> | |||||
| <MenuItem value="EPQC">{t("EPQC")}</MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| )} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <Controller | <Controller | ||||
| control={control} | control={control} | ||||
| @@ -292,6 +340,13 @@ const ProductDetails: React.FC<Props> = ({ isEditMode, qcCategoryCombo, warehous | |||||
| </RadioGroup> | </RadioGroup> | ||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12}> | |||||
| <QcItemsList | |||||
| qcItems={qcItems} | |||||
| loading={qcItemsLoading} | |||||
| categorySelected={!!qcCategoryId} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack | <Stack | ||||
| direction="row" | direction="row" | ||||
| @@ -0,0 +1,200 @@ | |||||
| "use client"; | |||||
| import { QcItemInfo } from "@/app/api/settings/qcCategory"; | |||||
| import { | |||||
| Box, | |||||
| Card, | |||||
| CircularProgress, | |||||
| Divider, | |||||
| List, | |||||
| ListItem, | |||||
| Stack, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { CheckCircleOutline, FormatListNumbered } from "@mui/icons-material"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| type Props = { | |||||
| qcItems: QcItemInfo[]; | |||||
| loading?: boolean; | |||||
| categorySelected?: boolean; | |||||
| }; | |||||
| const QcItemsList: React.FC<Props> = ({ | |||||
| qcItems, | |||||
| loading = false, | |||||
| categorySelected = false, | |||||
| }) => { | |||||
| const { t } = useTranslation("items"); | |||||
| // Sort items by order | |||||
| const sortedItems = [...qcItems].sort((a, b) => a.order - b.order); | |||||
| if (loading) { | |||||
| return ( | |||||
| <Box | |||||
| display="flex" | |||||
| justifyContent="center" | |||||
| alignItems="center" | |||||
| py={4} | |||||
| sx={{ | |||||
| backgroundColor: "grey.50", | |||||
| borderRadius: 2, | |||||
| border: "1px dashed", | |||||
| borderColor: "grey.300", | |||||
| }} | |||||
| > | |||||
| <CircularProgress size={24} sx={{ mr: 1.5 }} /> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("Loading QC items...")} | |||||
| </Typography> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| if (!categorySelected) { | |||||
| return ( | |||||
| <Box | |||||
| display="flex" | |||||
| flexDirection="column" | |||||
| alignItems="center" | |||||
| py={4} | |||||
| sx={{ | |||||
| backgroundColor: "grey.50", | |||||
| borderRadius: 2, | |||||
| border: "1px dashed", | |||||
| borderColor: "grey.300", | |||||
| }} | |||||
| > | |||||
| <FormatListNumbered | |||||
| sx={{ fontSize: 40, color: "grey.400", mb: 1 }} | |||||
| /> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("Select a QC template to view items")} | |||||
| </Typography> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| if (sortedItems.length === 0) { | |||||
| return ( | |||||
| <Box | |||||
| display="flex" | |||||
| flexDirection="column" | |||||
| alignItems="center" | |||||
| py={4} | |||||
| sx={{ | |||||
| backgroundColor: "grey.50", | |||||
| borderRadius: 2, | |||||
| border: "1px dashed", | |||||
| borderColor: "grey.300", | |||||
| }} | |||||
| > | |||||
| <CheckCircleOutline | |||||
| sx={{ fontSize: 40, color: "grey.400", mb: 1 }} | |||||
| /> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("No QC items in this template")} | |||||
| </Typography> | |||||
| </Box> | |||||
| ); | |||||
| } | |||||
| return ( | |||||
| <Card | |||||
| variant="outlined" | |||||
| sx={{ | |||||
| borderRadius: 2, | |||||
| backgroundColor: "background.paper", | |||||
| overflow: "hidden", | |||||
| }} | |||||
| > | |||||
| <Box | |||||
| sx={{ | |||||
| px: 2, | |||||
| py: 1.5, | |||||
| backgroundColor: "primary.main", | |||||
| color: "primary.contrastText", | |||||
| }} | |||||
| > | |||||
| <Stack direction="row" alignItems="center" spacing={1}> | |||||
| <FormatListNumbered fontSize="small" /> | |||||
| <Typography variant="subtitle2" fontWeight={600}> | |||||
| {t("QC Checklist")} ({sortedItems.length}) | |||||
| </Typography> | |||||
| </Stack> | |||||
| </Box> | |||||
| <List disablePadding> | |||||
| {sortedItems.map((item, index) => ( | |||||
| <Box key={item.id}> | |||||
| {index > 0 && <Divider />} | |||||
| <ListItem | |||||
| sx={{ | |||||
| py: 1.5, | |||||
| px: 2, | |||||
| "&:hover": { | |||||
| backgroundColor: "action.hover", | |||||
| }, | |||||
| }} | |||||
| > | |||||
| <Stack | |||||
| direction="row" | |||||
| spacing={2} | |||||
| alignItems="flex-start" | |||||
| width="100%" | |||||
| > | |||||
| {/* Order Number */} | |||||
| <Typography | |||||
| variant="body1" | |||||
| fontWeight={600} | |||||
| color="text.secondary" | |||||
| sx={{ minWidth: 24 }} | |||||
| > | |||||
| {item.order}. | |||||
| </Typography> | |||||
| {/* Content */} | |||||
| <Stack | |||||
| direction="row" | |||||
| alignItems="center" | |||||
| spacing={2} | |||||
| flex={1} | |||||
| minWidth={0} | |||||
| > | |||||
| <Typography | |||||
| variant="body1" | |||||
| fontWeight={500} | |||||
| sx={{ | |||||
| overflow: "hidden", | |||||
| textOverflow: "ellipsis", | |||||
| whiteSpace: "nowrap", | |||||
| flexShrink: 0, | |||||
| }} | |||||
| > | |||||
| {item.name || item.code} | |||||
| </Typography> | |||||
| {item.description && ( | |||||
| <Typography | |||||
| variant="body2" | |||||
| color="text.secondary" | |||||
| sx={{ | |||||
| overflow: "hidden", | |||||
| textOverflow: "ellipsis", | |||||
| whiteSpace: "nowrap", | |||||
| }} | |||||
| > | |||||
| {item.description} | |||||
| </Typography> | |||||
| )} | |||||
| </Stack> | |||||
| </Stack> | |||||
| </ListItem> | |||||
| </Box> | |||||
| ))} | |||||
| </List> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default QcItemsList; | |||||
| @@ -35,6 +35,7 @@ interface CompletedTracker { | |||||
| const TruckScheduleDashboard: React.FC = () => { | const TruckScheduleDashboard: React.FC = () => { | ||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const [selectedStore, setSelectedStore] = useState<string>(""); | const [selectedStore, setSelectedStore] = useState<string>(""); | ||||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | |||||
| const [data, setData] = useState<TruckScheduleDashboardItem[]>([]); | const [data, setData] = useState<TruckScheduleDashboardItem[]>([]); | ||||
| const [loading, setLoading] = useState<boolean>(true); | const [loading, setLoading] = useState<boolean>(true); | ||||
| // Initialize as null to avoid SSR/client hydration mismatch | // Initialize as null to avoid SSR/client hydration mismatch | ||||
| @@ -43,6 +44,23 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| const completedTrackerRef = useRef<Map<string, CompletedTracker>>(new Map()); | const completedTrackerRef = useRef<Map<string, CompletedTracker>>(new Map()); | ||||
| const refreshCountRef = useRef<number>(0); | const refreshCountRef = useRef<number>(0); | ||||
| // Get date label for display (e.g., "2026-01-17") | |||||
| const getDateLabel = (offset: number): string => { | |||||
| return dayjs().add(offset, 'day').format('YYYY-MM-DD'); | |||||
| }; | |||||
| // Convert date option to YYYY-MM-DD format for API | |||||
| const getDateParam = (dateOption: string): string => { | |||||
| if (dateOption === "today") { | |||||
| return dayjs().format('YYYY-MM-DD'); | |||||
| } else if (dateOption === "tomorrow") { | |||||
| return dayjs().add(1, 'day').format('YYYY-MM-DD'); | |||||
| } else if (dateOption === "dayAfterTomorrow") { | |||||
| return dayjs().add(2, 'day').format('YYYY-MM-DD'); | |||||
| } | |||||
| return dayjs().add(1, 'day').format('YYYY-MM-DD'); | |||||
| }; | |||||
| // Set client flag and time on mount | // Set client flag and time on mount | ||||
| useEffect(() => { | useEffect(() => { | ||||
| setIsClient(true); | setIsClient(true); | ||||
| @@ -136,7 +154,8 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| // Load data from API | // Load data from API | ||||
| const loadData = useCallback(async () => { | const loadData = useCallback(async () => { | ||||
| try { | try { | ||||
| const result = await fetchTruckScheduleDashboardClient(); | |||||
| const dateParam = getDateParam(selectedDate); | |||||
| const result = await fetchTruckScheduleDashboardClient(dateParam); | |||||
| // Update completed tracker | // Update completed tracker | ||||
| refreshCountRef.current += 1; | refreshCountRef.current += 1; | ||||
| @@ -175,7 +194,7 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| } finally { | } finally { | ||||
| setLoading(false); | setLoading(false); | ||||
| } | } | ||||
| }, []); | |||||
| }, [selectedDate]); | |||||
| // Initial load and auto-refresh every 5 minutes | // Initial load and auto-refresh every 5 minutes | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -183,7 +202,7 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| const refreshInterval = setInterval(() => { | const refreshInterval = setInterval(() => { | ||||
| loadData(); | loadData(); | ||||
| }, 5 * 60 * 1000); // 5 minutes | |||||
| }, 0.1 * 60 * 1000); // 5 minutes | |||||
| return () => clearInterval(refreshInterval); | return () => clearInterval(refreshInterval); | ||||
| }, [loadData]); | }, [loadData]); | ||||
| @@ -256,6 +275,23 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| <MenuItem value="4/F">4/F</MenuItem> | <MenuItem value="4/F">4/F</MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <FormControl sx={{ minWidth: 200 }} size="small"> | |||||
| <InputLabel id="date-select-label" shrink={true}> | |||||
| {t("Select Date")} | |||||
| </InputLabel> | |||||
| <Select | |||||
| labelId="date-select-label" | |||||
| id="date-select" | |||||
| value={selectedDate} | |||||
| label={t("Select Date")} | |||||
| onChange={(e) => setSelectedDate(e.target.value)} | |||||
| > | |||||
| <MenuItem value="today">{t("Today")} ({getDateLabel(0)})</MenuItem> | |||||
| <MenuItem value="tomorrow">{t("Tomorrow")} ({getDateLabel(1)})</MenuItem> | |||||
| <MenuItem value="dayAfterTomorrow">{t("Day After Tomorrow")} ({getDateLabel(2)})</MenuItem> | |||||
| </Select> | |||||
| </FormControl> | |||||
| <Typography variant="body2" sx={{ alignSelf: 'center', color: 'text.secondary' }}> | <Typography variant="body2" sx={{ alignSelf: 'center', color: 'text.secondary' }}> | ||||
| {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {isClient && currentTime ? currentTime.format('HH:mm:ss') : '--:--:--'} | {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {isClient && currentTime ? currentTime.format('HH:mm:ss') : '--:--:--'} | ||||
| @@ -290,7 +326,7 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| <TableRow> | <TableRow> | ||||
| <TableCell colSpan={10} align="center"> | <TableCell colSpan={10} align="center"> | ||||
| <Typography variant="body2" color="text.secondary"> | <Typography variant="body2" color="text.secondary"> | ||||
| {t("No truck schedules available for today")} | |||||
| {t("No truck schedules available")} ({getDateParam(selectedDate)}) | |||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| @@ -73,6 +73,11 @@ | |||||
| "Last Ticket End": "Last Ticket End", | "Last Ticket End": "Last Ticket End", | ||||
| "Pick Time (min)": "Pick Time (min)", | "Pick Time (min)": "Pick Time (min)", | ||||
| "No truck schedules available for today": "No truck schedules available for today", | "No truck schedules available for today": "No truck schedules available for today", | ||||
| "No truck schedules available": "No truck schedules available", | |||||
| "Select Date": "Select Date", | |||||
| "Today": "Today", | |||||
| "Tomorrow": "Tomorrow", | |||||
| "Day After Tomorrow": "Day After Tomorrow", | |||||
| "Goods Receipt Status": "Goods Receipt Status", | "Goods Receipt Status": "Goods Receipt Status", | ||||
| "Filter": "Filter", | "Filter": "Filter", | ||||
| "All": "All", | "All": "All", | ||||
| @@ -9,5 +9,12 @@ | |||||
| "Back": "Back", | "Back": "Back", | ||||
| "Status": "Status", | "Status": "Status", | ||||
| "Complete": "Complete", | "Complete": "Complete", | ||||
| "Missing Data": "Missing Data" | |||||
| "Missing Data": "Missing Data", | |||||
| "Loading QC items...": "Loading QC items...", | |||||
| "Select a QC template to view items": "Select a QC template to view items", | |||||
| "No QC items in this template": "No QC items in this template", | |||||
| "QC Checklist": "QC Checklist", | |||||
| "QC Type": "QC Type", | |||||
| "IPQC": "IPQC", | |||||
| "EPQC": "EPQC" | |||||
| } | } | ||||
| @@ -73,6 +73,11 @@ | |||||
| "Last Ticket End": "末單結束時間", | "Last Ticket End": "末單結束時間", | ||||
| "Pick Time (min)": "揀貨時間(分鐘)", | "Pick Time (min)": "揀貨時間(分鐘)", | ||||
| "No truck schedules available for today": "今日無車輛調度計劃", | "No truck schedules available for today": "今日無車輛調度計劃", | ||||
| "No truck schedules available": "無車輛調度計劃", | |||||
| "Select Date": "請選擇日期", | |||||
| "Today": "是日", | |||||
| "Tomorrow": "翌日", | |||||
| "Day After Tomorrow": "後日", | |||||
| "Goods Receipt Status": "貨物接收狀態", | "Goods Receipt Status": "貨物接收狀態", | ||||
| "Filter": "篩選", | "Filter": "篩選", | ||||
| "All": "全部", | "All": "全部", | ||||
| @@ -43,5 +43,12 @@ | |||||
| "Back": "返回", | "Back": "返回", | ||||
| "Status": "狀態", | "Status": "狀態", | ||||
| "Complete": "完成", | "Complete": "完成", | ||||
| "Missing Data": "缺少資料" | |||||
| "Missing Data": "缺少資料", | |||||
| "Loading QC items...": "正在加載質檢項目...", | |||||
| "Select a QC template to view items": "選擇質檢模板以查看項目", | |||||
| "No QC items in this template": "此模板無質檢項目", | |||||
| "QC Checklist": "質檢項目", | |||||
| "QC Type": "質檢種類", | |||||
| "IPQC": "IPQC", | |||||
| "EPQC": "EPQC" | |||||
| } | } | ||||