| @@ -140,3 +140,18 @@ export const fetchPoInClient = cache(async (id: number) => { | |||||
| } | } | ||||
| }); | }); | ||||
| export const testing = cache(async (queryParams?: Record<string, any>) => { | |||||
| if (queryParams) { | |||||
| const queryString = new URLSearchParams(queryParams).toString(); | |||||
| return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/testing?${queryString}`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["po"] }, | |||||
| }); | |||||
| } else { | |||||
| return serverFetchJson<RecordsRes<PoResult[]>>(`${BASE_API_URL}/po/testing`, { | |||||
| method: 'GET', | |||||
| next: { tags: ["po"] }, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| @@ -295,7 +295,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| color={buttonData.buttonColor as ButtonProps["color"]} | color={buttonData.buttonColor as ButtonProps["color"]} | ||||
| startIcon={buttonData.buttonIcon} | startIcon={buttonData.buttonIcon} | ||||
| > | > | ||||
| {t(buttonData.buttonText)} | |||||
| {buttonData.buttonText} | |||||
| </Button> | </Button> | ||||
| </Grid> | </Grid> | ||||
| {/* {purchaseOrder.status.toLowerCase() === "pending" && ( | {/* {purchaseOrder.status.toLowerCase() === "pending" && ( | ||||
| @@ -510,20 +510,20 @@ function PoInputGrid({ | |||||
| color="inherit" | color="inherit" | ||||
| key="edit" | key="edit" | ||||
| />, | />, | ||||
| <GridActionsCellItem | |||||
| icon={<QrCodeIcon />} | |||||
| label="putaway" | |||||
| sx={{ | |||||
| color: "primary.main", | |||||
| // marginRight: 1, | |||||
| }} | |||||
| disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} | |||||
| // set _isNew to false after posting | |||||
| // or check status | |||||
| onClick={handleQrCode(params.row.id, params)} | |||||
| color="inherit" | |||||
| key="edit" | |||||
| />, | |||||
| // <GridActionsCellItem | |||||
| // icon={<QrCodeIcon />} | |||||
| // label="putaway" | |||||
| // sx={{ | |||||
| // color: "primary.main", | |||||
| // // marginRight: 1, | |||||
| // }} | |||||
| // disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} | |||||
| // // set _isNew to false after posting | |||||
| // // or check status | |||||
| // onClick={handleQrCode(params.row.id, params)} | |||||
| // color="inherit" | |||||
| // key="edit" | |||||
| // />, | |||||
| <GridActionsCellItem | <GridActionsCellItem | ||||
| icon={ | icon={ | ||||
| stockInLineStatusMap[status] >= 1 ? ( | stockInLineStatusMap[status] >= 1 ? ( | ||||
| @@ -237,6 +237,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setValue("status", "completed"); | setValue("status", "completed"); | ||||
| setValue("warehouseId", options[0].value); | |||||
| }, []); | }, []); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -245,7 +246,16 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
| clearErrors("warehouseId") | clearErrors("warehouseId") | ||||
| } | } | ||||
| }, [warehouseId]); | }, [warehouseId]); | ||||
| const getWarningTextHardcode = useCallback((): string | undefined => { | |||||
| const defaultWarehouseId = options[0].value | |||||
| const currWarehouseId = watch("warehouseId") | |||||
| if (defaultWarehouseId !== currWarehouseId) { | |||||
| return t("not default warehosue") | |||||
| } | |||||
| return undefined | |||||
| }, [options]) | |||||
| return ( | return ( | ||||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| @@ -331,7 +341,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
| disableClearable | disableClearable | ||||
| disabled | disabled | ||||
| fullWidth | fullWidth | ||||
| defaultValue={options.find((o) => o.value === 1)} /// modify this later | |||||
| defaultValue={options[0]} /// modify this later | |||||
| // onChange={onChange} | // onChange={onChange} | ||||
| getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
| options={options} | options={options} | ||||
| @@ -396,6 +406,8 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
| // value={warehouseId > 0 | // value={warehouseId > 0 | ||||
| // ? options.find((o) => o.value === warehouseId) | // ? options.find((o) => o.value === warehouseId) | ||||
| // : undefined} | // : undefined} | ||||
| defaultValue={options[0]} | |||||
| // defaultValue={options.find((o) => o.value === 1)} | |||||
| value={currentValue} | value={currentValue} | ||||
| onChange={onChange} | onChange={onChange} | ||||
| getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
| @@ -406,7 +418,9 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
| // label={"Select warehouse"} | // label={"Select warehouse"} | ||||
| disabled={disabled} | disabled={disabled} | ||||
| error={Boolean(errors.warehouseId?.message)} | error={Boolean(errors.warehouseId?.message)} | ||||
| helperText={errors.warehouseId?.message} | |||||
| helperText={errors.warehouseId?.message ?? | |||||
| getWarningTextHardcode() | |||||
| } | |||||
| // helperText={warehouseHelperText} | // helperText={warehouseHelperText} | ||||
| /> | /> | ||||
| )} | )} | ||||
| @@ -1,6 +1,14 @@ | |||||
| "use client"; | "use client"; | ||||
| import { Box, Button, Grid, Modal, ModalProps, Stack, Typography } from "@mui/material"; | |||||
| import { | |||||
| Box, | |||||
| Button, | |||||
| Grid, | |||||
| Modal, | |||||
| ModalProps, | |||||
| Stack, | |||||
| Typography, | |||||
| } from "@mui/material"; | |||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
| import ReactQrCodeScanner, { | import ReactQrCodeScanner, { | ||||
| ScannerConfig, | ScannerConfig, | ||||
| @@ -74,18 +82,18 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
| ); | ); | ||||
| // QR Code Scanner | // QR Code Scanner | ||||
| const scanner = useQcCodeScanner() | |||||
| const scanner = useQcCodeScanner(); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (open && !scanner.isScanning) { | if (open && !scanner.isScanning) { | ||||
| scanner.startScan() | |||||
| scanner.startScan(); | |||||
| } else if (!open && scanner.isScanning) { | } else if (!open && scanner.isScanning) { | ||||
| scanner.stopScan() | |||||
| scanner.stopScan(); | |||||
| } | } | ||||
| }, [open]) | |||||
| }, [open]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (scanner.values.length > 0 && !Boolean(itemDetail)) { | if (scanner.values.length > 0 && !Boolean(itemDetail)) { | ||||
| console.log(scanner.values[0]) | |||||
| console.log(scanner.values[0]); | |||||
| const data: QrCodeInfo = JSON.parse(scanner.values[0]); | const data: QrCodeInfo = JSON.parse(scanner.values[0]); | ||||
| console.log(data); | console.log(data); | ||||
| if (data.stockInLineId) { | if (data.stockInLineId) { | ||||
| @@ -93,28 +101,29 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
| console.log(data.stockInLineId); | console.log(data.stockInLineId); | ||||
| setStockInLineId(data.stockInLineId); | setStockInLineId(data.stockInLineId); | ||||
| } | } | ||||
| scanner.resetScan() | |||||
| scanner.resetScan(); | |||||
| } | } | ||||
| }, [scanner.values]) | |||||
| }, [scanner.values]); | |||||
| const [itemDetail, setItemDetail] = useState<StockInLine>(); | const [itemDetail, setItemDetail] = useState<StockInLine>(); | ||||
| const [disabledSubmit, setDisabledSubmit] = useState(false); | const [disabledSubmit, setDisabledSubmit] = useState(false); | ||||
| const [unavailableText, setUnavailableText] = useState<string | undefined>(undefined) | |||||
| const [unavailableText, setUnavailableText] = useState<string | undefined>(undefined); | |||||
| const fetchStockInLine = useCallback( | const fetchStockInLine = useCallback( | ||||
| async (stockInLineId: number) => { | async (stockInLineId: number) => { | ||||
| setUnavailableText(undefined) | |||||
| setUnavailableText(undefined); | |||||
| const res = await fetchStockInLineInfo(stockInLineId); | const res = await fetchStockInLineInfo(stockInLineId); | ||||
| if (res.status.toLowerCase() === "received") { | if (res.status.toLowerCase() === "received") { | ||||
| console.log(res.acceptedQty) | |||||
| formProps.setValue("acceptedQty", res.acceptedQty) | |||||
| setDisabledSubmit(false) | |||||
| console.log(res.acceptedQty); | |||||
| formProps.setValue("acceptedQty", res.acceptedQty); | |||||
| setDisabledSubmit(false); | |||||
| setItemDetail(res); | setItemDetail(res); | ||||
| } else if (res.status.toLowerCase() === "completed") { | } else if (res.status.toLowerCase() === "completed") { | ||||
| setDisabledSubmit(true) | |||||
| setDisabledSubmit(true); | |||||
| } else { | } else { | ||||
| // | // | ||||
| setUnavailableText("Item Not Available") | |||||
| setDisabledSubmit(true) | |||||
| setUnavailableText("Item Not Available"); | |||||
| setDisabledSubmit(true); | |||||
| } | } | ||||
| // return | // return | ||||
| }, | }, | ||||
| @@ -156,13 +165,13 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
| setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
| return false; | return false; | ||||
| } | } | ||||
| return; | |||||
| // return; | |||||
| const res = await updateStockInLine(args); | const res = await updateStockInLine(args); | ||||
| if (Boolean(res.id)) { | if (Boolean(res.id)) { | ||||
| // update entries | // update entries | ||||
| console.log(res); | console.log(res); | ||||
| // add loading | // add loading | ||||
| // closeHandler({}, "backdropClick"); | |||||
| closeHandler({}, "backdropClick"); | |||||
| } | } | ||||
| console.log(res); | console.log(res); | ||||
| // if (res) | // if (res) | ||||
| @@ -185,32 +194,39 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||||
| > | > | ||||
| <Grid container xs={12}> | <Grid container xs={12}> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| { | |||||
| itemDetail != undefined ? ( | |||||
| unavailableText != undefined ? <Typography variant="h4" marginInlineEnd={2}>{unavailableText}</Typography> | |||||
| : ( | |||||
| <> | |||||
| <PutawayForm itemDetail={itemDetail} warehouse={warehouse} disabled={false} /> | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| <Button | |||||
| name="submit" | |||||
| variant="contained" | |||||
| startIcon={<Check />} | |||||
| type="submit" | |||||
| disabled={disabledSubmit} | |||||
| > | |||||
| {t("submit")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </> | |||||
| ) | |||||
| {itemDetail != undefined ? ( | |||||
| unavailableText != undefined ? ( | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {unavailableText} | |||||
| </Typography> | |||||
| ) : ( | |||||
| <> | |||||
| <PutawayForm | |||||
| itemDetail={itemDetail} | |||||
| warehouse={warehouse} | |||||
| disabled={false} | |||||
| /> | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| <Button | |||||
| name="submit" | |||||
| variant="contained" | |||||
| startIcon={<Check />} | |||||
| type="submit" | |||||
| disabled={disabledSubmit} | |||||
| > | |||||
| {t("submit")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </> | |||||
| ) | ) | ||||
| : ( | |||||
| // <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||||
| <Typography variant="h4">{t("Will start binding procedure after scanning item qr code.")}</Typography> | |||||
| ) | |||||
| } | |||||
| ) : ( | |||||
| // <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||||
| <Typography variant="h4"> | |||||
| {t( | |||||
| "Will start binding procedure after scanning item qr code." | |||||
| )} | |||||
| </Typography> | |||||
| )} | |||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| </Box> | </Box> | ||||
| @@ -10,10 +10,14 @@ import { EditNote } from "@mui/icons-material"; | |||||
| import { Button, Grid, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | import { Button, Grid, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | ||||
| import QrModal from "../PoDetail/QrModal"; | import QrModal from "../PoDetail/QrModal"; | ||||
| import { WarehouseResult } from "@/app/api/warehouse"; | import { WarehouseResult } from "@/app/api/warehouse"; | ||||
| import NotificationIcon from '@mui/icons-material/NotificationImportant'; | |||||
| import NotificationIcon from "@mui/icons-material/NotificationImportant"; | |||||
| import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | import { defaultPagingController } from "../SearchResults/SearchResults"; | ||||
| import { fetchPoListClient } from "@/app/api/po/actions"; | |||||
| import { fetchPoListClient, testing } from "@/app/api/po/actions"; | |||||
| import dayjs from "dayjs"; | |||||
| import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||||
| dayjs.extend(arraySupport); | |||||
| type Props = { | type Props = { | ||||
| po: PoResult[]; | po: PoResult[]; | ||||
| @@ -24,23 +28,39 @@ type SearchQuery = Partial<Omit<PoResult, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
| // cal offset (pageSize) | // cal offset (pageSize) | ||||
| // cal limit (pageSize) | |||||
| const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount }) => { | |||||
| // cal limit (pageSize) | |||||
| const PoSearch: React.FC<Props> = ({ | |||||
| po, | |||||
| warehouse, | |||||
| totalCount: initTotalCount, | |||||
| }) => { | |||||
| const [filteredPo, setFilteredPo] = useState<PoResult[]>(po); | const [filteredPo, setFilteredPo] = useState<PoResult[]>(po); | ||||
| const [filterArgs, setFilterArgs] = useState<Record<string, any>>({}); | |||||
| const { t } = useTranslation("purchaseOrder"); | const { t } = useTranslation("purchaseOrder"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [pagingController, setPagingController] = useState(defaultPagingController) | |||||
| const [totalCount, setTotalCount] = useState(initTotalCount) | |||||
| const [pagingController, setPagingController] = useState( | |||||
| defaultPagingController | |||||
| ); | |||||
| const [totalCount, setTotalCount] = useState(initTotalCount); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => { | const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => { | ||||
| var searchCriteria: Criterion<SearchParamNames>[] = [ | var searchCriteria: Criterion<SearchParamNames>[] = [ | ||||
| { label: t("Code"), paramName: "code", type: "text" }, | { label: t("Code"), paramName: "code", type: "text" }, | ||||
| { label: t("Status"), paramName: "status", type: "select", options: ["PENDING", "RECEIVING", "COMPLETED"] }, | |||||
| { label: t("Escalated"), paramName: "escalated", type: "select", options: [t("Escalated"), t("NotEscalated")] }, | |||||
| { | |||||
| label: t("Status"), | |||||
| paramName: "status", | |||||
| type: "select", | |||||
| options: [t(`pending`), t(`receiving`), t(`completed`)], | |||||
| }, | |||||
| { | |||||
| label: t("Escalated"), | |||||
| paramName: "escalated", | |||||
| type: "select", | |||||
| options: [t("Escalated"), t("NotEscalated")], | |||||
| }, | |||||
| ]; | ]; | ||||
| return searchCriteria; | return searchCriteria; | ||||
| }, [t, po]); | }, [t, po]); | ||||
| const onDetailClick = useCallback( | const onDetailClick = useCallback( | ||||
| (po: PoResult) => { | (po: PoResult) => { | ||||
| router.push(`/po/edit?id=${po.id}`); | router.push(`/po/edit?id=${po.id}`); | ||||
| @@ -65,6 +85,11 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
| { | { | ||||
| name: "orderDate", | name: "orderDate", | ||||
| label: t("OrderDate"), | label: t("OrderDate"), | ||||
| renderCell: (params) => { | |||||
| return dayjs(params.orderDate) | |||||
| .add(-1, "month") | |||||
| .format(OUTPUT_DATE_FORMAT); | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| name: "supplier", | name: "supplier", | ||||
| @@ -73,13 +98,18 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
| { | { | ||||
| name: "status", | name: "status", | ||||
| label: t("Status"), | label: t("Status"), | ||||
| renderCell: (params) => { | |||||
| return t(`${params.status.toLowerCase()}`); | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| name: "escalated", | name: "escalated", | ||||
| label: t("Escalated"), | label: t("Escalated"), | ||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| return params.escalated ? <NotificationIcon color="warning"/> : undefined | |||||
| } | |||||
| return params.escalated ? ( | |||||
| <NotificationIcon color="warning" /> | |||||
| ) : undefined; | |||||
| }, | |||||
| }, | }, | ||||
| // { | // { | ||||
| // name: "name", | // name: "name", | ||||
| @@ -108,17 +138,30 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
| setOpenScanner(false); | setOpenScanner(false); | ||||
| }, []); | }, []); | ||||
| const newPageFetch = useCallback(async (pagingController: Record<string, number>) => { | |||||
| const res = await fetchPoListClient(pagingController) | |||||
| if (res) { | |||||
| setFilteredPo(res.records) | |||||
| setTotalCount(res.total) | |||||
| } | |||||
| }, [fetchPoListClient, pagingController]) | |||||
| const newPageFetch = useCallback( | |||||
| async ( | |||||
| pagingController: Record<string, number>, | |||||
| filterArgs: Record<string, number> | |||||
| ) => { | |||||
| console.log(pagingController); | |||||
| const params = { | |||||
| ...pagingController, | |||||
| ...filterArgs, | |||||
| }; | |||||
| // const res = await fetchPoListClient(params); | |||||
| const res = await testing(params); | |||||
| if (res) { | |||||
| console.log(res.records); | |||||
| setFilteredPo(res.records); | |||||
| setTotalCount(res.total); | |||||
| } | |||||
| }, | |||||
| [fetchPoListClient, pagingController] | |||||
| ); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| newPageFetch(pagingController) | |||||
| }, [newPageFetch, pagingController]) | |||||
| newPageFetch(pagingController, filterArgs); | |||||
| }, [newPageFetch, pagingController, filterArgs]); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Grid container> | <Grid container> | ||||
| @@ -127,47 +170,32 @@ const PoSearch: React.FC<Props> = ({ po, warehouse, totalCount: initTotalCount } | |||||
| {t("Purchase Order")} | {t("Purchase Order")} | ||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| <Grid | |||||
| item | |||||
| xs={4} | |||||
| display="flex" | |||||
| justifyContent="end" | |||||
| alignItems="end" | |||||
| > | |||||
| <QrModal | |||||
| open={isOpenScanner} | |||||
| onClose={onCloseScanner} | |||||
| warehouse={warehouse} | |||||
| /> | |||||
| <Button onClick={onOpenScanner}>bind</Button> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilteredPo((prev) => | |||||
| prev.filter((p) => { | |||||
| return ( | |||||
| p.code.toLowerCase().includes(query.code.toLowerCase()) && | |||||
| (query.status === "All" || p.status === query.status) && | |||||
| (query.escalated === "All" || p.escalated === (query.escalated === t("Escalated"))) | |||||
| ) | |||||
| }) | |||||
| ); | |||||
| }} | |||||
| onReset={onReset} | |||||
| <Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||||
| <QrModal | |||||
| open={isOpenScanner} | |||||
| onClose={onCloseScanner} | |||||
| warehouse={warehouse} | |||||
| /> | /> | ||||
| <SearchResults<PoResult> | |||||
| items={filteredPo} | |||||
| columns={columns} | |||||
| pagingController={pagingController} | |||||
| setPagingController={setPagingController} | |||||
| totalCount={totalCount} | |||||
| isAutoPaging={false} | |||||
| /> | |||||
| </> | |||||
| <Button onClick={onOpenScanner}>bind</Button> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilterArgs({ ...query }); | |||||
| }} | |||||
| onReset={onReset} | |||||
| /> | |||||
| <SearchResults<PoResult> | |||||
| items={filteredPo} | |||||
| columns={columns} | |||||
| pagingController={pagingController} | |||||
| setPagingController={setPagingController} | |||||
| totalCount={totalCount} | |||||
| isAutoPaging={false} | |||||
| /> | |||||
| </> | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -6,6 +6,7 @@ | |||||
| "Supplier": "供應商", | "Supplier": "供應商", | ||||
| "Status": "狀態", | "Status": "狀態", | ||||
| "Escalated": "已上報", | "Escalated": "已上報", | ||||
| "NotEscalated": "無上報", | |||||
| "Do you want to start?": "確定開始嗎?", | "Do you want to start?": "確定開始嗎?", | ||||
| "Start": "開始", | "Start": "開始", | ||||