@@ -0,0 +1,44 @@ | |||||
import { Metadata } from "next"; | |||||
import { getServerI18n, I18nProvider } from "@/i18n"; | |||||
import Typography from "@mui/material/Typography"; | |||||
import { Button, Link, Stack } from "@mui/material"; | |||||
import { Add } from "@mui/icons-material"; | |||||
import { Suspense } from "react"; | |||||
import { preloadQcItem } from "@/app/api/settings/qcItem"; | |||||
import QcItemSearch from "@/components/QcItemSearch"; | |||||
export const metadata: Metadata = { | |||||
title: "Qc Item", | |||||
}; | |||||
const qcItem: React.FC = async () => { | |||||
const { t } = await getServerI18n("qcItem") | |||||
preloadQcItem() | |||||
return <> | |||||
<Stack | |||||
direction="row" | |||||
justifyContent="space-between" | |||||
flexWrap="wrap" | |||||
rowGap={2} | |||||
> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Qc Item")} | |||||
</Typography> | |||||
<Button | |||||
variant="contained" | |||||
startIcon={<Add />} | |||||
LinkComponent={Link} | |||||
href="qcItem/create" | |||||
> | |||||
{t("Create Qc Item")} | |||||
</Button> | |||||
</Stack> | |||||
<Suspense fallback={<QcItemSearch.Loading />}> | |||||
<QcItemSearch /> | |||||
</Suspense> | |||||
</>; | |||||
}; | |||||
export default qcItem; |
@@ -0,0 +1,32 @@ | |||||
"use server" | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { revalidateTag } from "next/cache"; | |||||
export interface SaveQcItemInputs { | |||||
id?: number; | |||||
code: string; | |||||
name: string; | |||||
description?: string; | |||||
} | |||||
export interface SaveQcItemResponse { | |||||
id?: number; | |||||
code: string; | |||||
name: string; | |||||
description?: string; | |||||
errors: Record<keyof SaveQcItemInputs, string> | |||||
} | |||||
export const saveQcItem = async (data: SaveQcItemInputs) => { | |||||
const response = await serverFetchJson<SaveQcItemResponse>(`${BASE_API_URL}/qcItems/save`, { | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}) | |||||
revalidateTag(`qcItems`) | |||||
return response | |||||
} |
@@ -0,0 +1,32 @@ | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { cache } from "react"; | |||||
import "server-only"; | |||||
import { SaveQcItemInputs } from "./actions"; | |||||
import next from "next"; | |||||
export interface QcItemResult { | |||||
id: number; | |||||
code: string; | |||||
name: string; | |||||
description: string; | |||||
} | |||||
export const preloadQcItem = () => { | |||||
fetchQcItems(); | |||||
}; | |||||
export const fetchQcItems = cache(async () => { | |||||
return serverFetchJson<QcItemResult[]>(`${BASE_API_URL}/qcItems`, { | |||||
next: { tags: ["qcItems"] } | |||||
}); | |||||
}); | |||||
export const fetchQcItemDetail = cache(async (qcItemId: string) => { | |||||
return serverFetchJson<SaveQcItemInputs>( | |||||
`${BASE_API_URL}/qcItems/qcItemDetail/${qcItemId}`, | |||||
{ | |||||
next: { tags: [`qcItemDetail_${qcItemId}`] } | |||||
} | |||||
) | |||||
}) |
@@ -12,6 +12,8 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
"/projects/create": "Create Project", | "/projects/create": "Create Project", | ||||
"/tasks": "Task Template", | "/tasks": "Task Template", | ||||
"/tasks/create": "Create Task Template", | "/tasks/create": "Create Task Template", | ||||
"/settings/qcItem": "Qc Item", | |||||
}; | }; | ||||
const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
@@ -224,12 +224,12 @@ const NavigationContent: React.FC = () => { | |||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
label: "QC Check Item", | label: "QC Check Item", | ||||
path: "/settings/user", | |||||
path: "/settings/qcItem", | |||||
}, | }, | ||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
label: "QC Category", | label: "QC Category", | ||||
path: "/settings/user", | |||||
path: "/settings/qcCategory", | |||||
}, | }, | ||||
{ | { | ||||
icon: <RequestQuote />, | icon: <RequestQuote />, | ||||
@@ -0,0 +1,57 @@ | |||||
import { SaveQcItemInputs } from "@/app/api/settings/qcItem/actions" | |||||
import { Box, Card, CardContent, Grid, Stack, TextField, Typography } from "@mui/material" | |||||
import { useFormContext } from "react-hook-form" | |||||
import { useTranslation } from "react-i18next" | |||||
const QcItemDetails = () => { | |||||
const { t } = useTranslation() | |||||
const { | |||||
register | |||||
} = useFormContext<SaveQcItemInputs>() | |||||
return ( | |||||
<Card sx={{ display: "block" }}> | |||||
<CardContent component={Stack} spacing={4}> | |||||
<Box> | |||||
<Typography variant={"overline"} display={"block"} marginBlockEnd={1}> | |||||
{t("Qc Item Details")} | |||||
</Typography> | |||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Code")} | |||||
fullWidth | |||||
{...register("code", { | |||||
required: "Code required!", | |||||
maxLength: 30 | |||||
})} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Name")} | |||||
fullWidth | |||||
{...register("name", { | |||||
required: "Name required!", | |||||
maxLength: 30 | |||||
})} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Description")} | |||||
fullWidth | |||||
{...register("description", { | |||||
maxLength: 100 | |||||
})} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
</Box> | |||||
</CardContent> | |||||
</Card> | |||||
) | |||||
} | |||||
export default QcItemDetails |
@@ -0,0 +1,70 @@ | |||||
"use client" | |||||
import { saveQcItem, SaveQcItemInputs } from "@/app/api/settings/qcItem/actions"; | |||||
import { Stack } from "@mui/material"; | |||||
import { useCallback } from "react"; | |||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||||
import { errorDialogWithContent, submitDialog } from "../Swal/CustomAlerts"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { useRouter } from "next/navigation"; | |||||
interface Props { | |||||
defaultInputs?: SaveQcItemInputs; | |||||
} | |||||
const QcItemSave: React.FC<Props> = ({ | |||||
defaultInputs, | |||||
}) => { | |||||
const { t } = useTranslation("qcItem") | |||||
const router = useRouter() | |||||
const formProps = useForm<SaveQcItemInputs>({ | |||||
defaultValues: { | |||||
...defaultInputs | |||||
} | |||||
}) | |||||
const handleSubmit = useCallback(async (data: SaveQcItemInputs) => { | |||||
const response = await saveQcItem(data) | |||||
const errors = response.errors | |||||
if (errors) { | |||||
let errorContents = "" | |||||
for (const [key, value] of Object.entries(errors)) { | |||||
formProps.setError(key as keyof SaveQcItemInputs, { type: "custom", message: value }) | |||||
errorContents = errorContents + t(value) + "<br>" | |||||
} | |||||
errorDialogWithContent(t("Submit Error"), errorContents, t) | |||||
} else { | |||||
router.push("/settings/qcItem") | |||||
} | |||||
}, | |||||
[] | |||||
) | |||||
const onSubmit = useCallback<SubmitHandler<SaveQcItemInputs>>( | |||||
async (data) => { | |||||
await submitDialog(() => handleSubmit(data), t) | |||||
}, [] | |||||
) | |||||
return ( | |||||
<> | |||||
<FormProvider {...formProps}> | |||||
<Stack | |||||
spacing={2} | |||||
component={"form"} | |||||
onSubmit={formProps.handleSubmit(onSubmit)} | |||||
> | |||||
</Stack> | |||||
</FormProvider> | |||||
</> | |||||
) | |||||
}; | |||||
export default QcItemSave; |
@@ -0,0 +1,40 @@ | |||||
import Card from "@mui/material/Card"; | |||||
import CardContent from "@mui/material/CardContent"; | |||||
import Skeleton from "@mui/material/Skeleton"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import React from "react"; | |||||
// Can make this nicer | |||||
export const QcItemSaveLoading: React.FC = () => { | |||||
return ( | |||||
<> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton | |||||
variant="rounded" | |||||
height={50} | |||||
width={100} | |||||
sx={{ alignSelf: "flex-end" }} | |||||
/> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
</> | |||||
); | |||||
}; | |||||
export default QcItemSaveLoading; |
@@ -0,0 +1,26 @@ | |||||
import React from "react" | |||||
import QcItemSaveLoading from "./QcItemSaveLoading" | |||||
import QcItemSave from "./QcItemSave"; | |||||
import { fetchQcItemDetail } from "@/app/api/settings/qcItem"; | |||||
interface SubComponents { | |||||
Loading: typeof QcItemSaveLoading; | |||||
} | |||||
type SaveQcItemProps = { | |||||
id?: string | |||||
} | |||||
type Props = SaveQcItemProps | |||||
const QcItemSaveWrapper: React.FC<Props> & SubComponents = async ( | |||||
props | |||||
) => { | |||||
const qcItem = props.id ? await fetchQcItemDetail(props.id) : undefined; | |||||
return <QcItemSave defaultInputs={qcItem} />; | |||||
}; | |||||
QcItemSaveWrapper.Loading = QcItemSaveLoading; | |||||
export default QcItemSaveWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./QcItemSaveWrapper"; |
@@ -0,0 +1,97 @@ | |||||
"use client"; | |||||
import { Html5QrcodeResult, Html5QrcodeScanner, QrcodeErrorCallback, QrcodeSuccessCallback } from "html5-qrcode"; | |||||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import SearchBox, { Criterion } from "../SearchBox"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import SearchResults, { Column } from "../SearchResults"; | |||||
import EditNote from "@mui/icons-material/EditNote"; | |||||
import { QcItemResult } from "@/app/api/settings/qcItem"; | |||||
import { useRouter } from "next/navigation"; | |||||
import QrCodeScanner from "../QrCodeScanner"; | |||||
import { Button } from "@mui/material"; | |||||
interface Props { | |||||
qcItems: QcItemResult[]; | |||||
} | |||||
type SearchQuery = Partial<Omit<QcItemResult, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | |||||
const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||||
const { t } = useTranslation("qcItems"); | |||||
const router = useRouter(); | |||||
// If qcItem searching is done on the server-side, then no need for this. | |||||
const [filteredQcItems, setFilteredQcItems] = useState(qcItems); | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
() => [ | |||||
{ label: t("Code"), paramName: "code", type: "text" }, | |||||
{ label: t("Name"), paramName: "name", type: "text" }, | |||||
], | |||||
[t] | |||||
); | |||||
const onReset = useCallback( | |||||
() => { | |||||
setFilteredQcItems(qcItems); | |||||
}, [qcItems] | |||||
); | |||||
const onQcItemClick = useCallback( | |||||
(qcItem: QcItemResult) => { | |||||
router.push(`/edit/${qcItem.id}`); | |||||
}, | |||||
[router] | |||||
); | |||||
const columns = useMemo<Column<QcItemResult>[]>( | |||||
() => [ | |||||
{ | |||||
name: "id", | |||||
label: t("Details"), | |||||
onClick: onQcItemClick, | |||||
buttonIcon: <EditNote />, | |||||
}, | |||||
{ name: "code", label: t("Code") }, | |||||
{ name: "name", label: t("Name") }, | |||||
], | |||||
[t, onQcItemClick] | |||||
); | |||||
const [isOpenScanner, setOpenScanner] = useState(false) | |||||
const onOpenScanner = useCallback(() => { | |||||
setOpenScanner(true) | |||||
}, []) | |||||
const onCloseScanner = useCallback(() => { | |||||
setOpenScanner(false) | |||||
}, []) | |||||
const handleScanSuccess = useCallback((result: string) => { | |||||
console.log(result) | |||||
}, []) | |||||
return ( | |||||
<> | |||||
<QrCodeScanner isOpen={isOpenScanner} onClose={onCloseScanner} onScanSuccess={handleScanSuccess} /> | |||||
<Button onClick={onOpenScanner}>abc</Button> | |||||
{/* <SearchBox | |||||
criteria={searchCriteria} | |||||
onSearch={(query) => { | |||||
setFilteredQcItems( | |||||
qcItems.filter( | |||||
(qi) => | |||||
qi.code.toLowerCase().includes(query.code.toLowerCase()) && | |||||
qi.name.toLowerCase().includes(query.name.toLowerCase()) | |||||
) | |||||
); | |||||
}} | |||||
onReset={onReset} | |||||
/> | |||||
<SearchResults<QcItemResult> items={filteredQcItems} columns={columns} /> */} | |||||
</> | |||||
) | |||||
}; | |||||
export default qcItemSearch; |
@@ -0,0 +1,40 @@ | |||||
import Card from "@mui/material/Card"; | |||||
import CardContent from "@mui/material/CardContent"; | |||||
import Skeleton from "@mui/material/Skeleton"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import React from "react"; | |||||
// Can make this nicer | |||||
export const QcCategorySearchLoading: React.FC = () => { | |||||
return ( | |||||
<> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton variant="rounded" height={60} /> | |||||
<Skeleton | |||||
variant="rounded" | |||||
height={50} | |||||
width={100} | |||||
sx={{ alignSelf: "flex-end" }} | |||||
/> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
<Card> | |||||
<CardContent> | |||||
<Stack spacing={2}> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
<Skeleton variant="rounded" height={40} /> | |||||
</Stack> | |||||
</CardContent> | |||||
</Card> | |||||
</> | |||||
); | |||||
}; | |||||
export default QcCategorySearchLoading; |
@@ -0,0 +1,23 @@ | |||||
import React from "react" | |||||
import QcItemSearchLoading from "./QcItemSearchLoading" | |||||
import QcItemSearch from "./QcItemSearch"; | |||||
import { fetchQcItems } from "@/app/api/settings/qcItem"; | |||||
interface SubComponents { | |||||
Loading: typeof QcItemSearchLoading; | |||||
} | |||||
const QcItemSearchWrapper: React.FC & SubComponents = async () => { | |||||
const [ | |||||
qcItems | |||||
] = await Promise.all([ | |||||
fetchQcItems() | |||||
]); | |||||
return <QcItemSearch qcItems={qcItems} />; | |||||
}; | |||||
QcItemSearchWrapper.Loading = QcItemSearchLoading; | |||||
export default QcItemSearchWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./QcItemSearchWrapper"; |
@@ -1,8 +1,8 @@ | |||||
import { Button, Card, CardContent, Modal, ModalProps, SxProps } from "@mui/material"; | |||||
import { Html5QrcodeResult, Html5QrcodeScanner, QrcodeErrorCallback, QrcodeSuccessCallback } from "html5-qrcode"; | |||||
import { Button, Card, CardContent, Grid, Modal, ModalProps, Stack, SxProps, Typography } from "@mui/material"; | |||||
import { Html5Qrcode, Html5QrcodeCameraScanConfig, Html5QrcodeFullConfig, Html5QrcodeResult, Html5QrcodeScanner, QrcodeErrorCallback, QrcodeSuccessCallback } from "html5-qrcode"; | |||||
import { Html5QrcodeError } from "html5-qrcode/esm/core"; | import { Html5QrcodeError } from "html5-qrcode/esm/core"; | ||||
import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner"; | import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner"; | ||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; | |||||
import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"; | |||||
const scannerSx: React.CSSProperties = { | const scannerSx: React.CSSProperties = { | ||||
position: "absolute", | position: "absolute", | ||||
@@ -16,82 +16,115 @@ const scannerSx: React.CSSProperties = { | |||||
type QrCodeScannerProps = { | type QrCodeScannerProps = { | ||||
onScanSuccess: (result: string) => void, | onScanSuccess: (result: string) => void, | ||||
onScanError?: (error: string) => void | |||||
} | |||||
const scannerConfig: Html5QrcodeScannerConfig = { | |||||
fps: 10, | |||||
qrbox: { width: 400, height: 400 }, | |||||
aspectRatio: 2.5, | |||||
onScanError?: (error: string) => void, | |||||
isOpen: boolean | |||||
} | } | ||||
const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | ||||
onScanSuccess, | onScanSuccess, | ||||
onScanError | |||||
onScanError, | |||||
isOpen | |||||
}) => { | }) => { | ||||
const [isScanned, setIsScanned] = useState<boolean>(false) | const [isScanned, setIsScanned] = useState<boolean>(false) | ||||
const [scanner, setScanner] = useState<Html5QrcodeScanner | null>(null) | |||||
const [scanner, setScanner] = useState<Html5Qrcode | null>(null) | |||||
const scannerConfig: Html5QrcodeFullConfig = { | |||||
verbose: false | |||||
} | |||||
const cameraConfig: Html5QrcodeCameraScanConfig = { | |||||
fps: 10, | |||||
qrbox: { width: 400, height: 400 }, | |||||
// aspectRatio: cardRef.current ? (cardRef.current.offsetWidth / cardRef.current.offsetHeight) : 1.78, | |||||
aspectRatio: 2.5 | |||||
}; | |||||
// MediaTrackConstraintSet | |||||
const mediaTrackConstraintSet = { | |||||
facingMode: "environment" | |||||
} | |||||
useEffect(() => { | useEffect(() => { | ||||
setScanner(new Html5QrcodeScanner( | |||||
setScanner(new Html5Qrcode( | |||||
"qr-reader", | "qr-reader", | ||||
scannerConfig, | |||||
false | |||||
scannerConfig | |||||
)) | )) | ||||
}, []) | }, []) | ||||
const handleStartScan = useCallback(() => { | const handleStartScan = useCallback(() => { | ||||
setIsScanned(false) | |||||
scanner?.resume(); | |||||
if (scanner) { | |||||
setIsScanned(false) | |||||
scanner.resume(); | |||||
} | |||||
}, [scanner]) | |||||
const handleScanSuccess = useCallback<QrcodeSuccessCallback>((decodedText, result) => { | |||||
if (scanner) { | |||||
console.log(`Decoded text: ${decodedText}`); | |||||
// Handle the decoded text as needed | |||||
setIsScanned(true) | |||||
scanner.pause(); | |||||
onScanSuccess(decodedText) | |||||
} | |||||
}, [scanner, onScanSuccess]) | |||||
const handleScanError = useCallback<QrcodeErrorCallback>((errorMessage, error) => { | |||||
console.log(`Error: ${errorMessage}`); | |||||
if (onScanError) { | |||||
onScanError(errorMessage) | |||||
} | |||||
}, [scanner, onScanError]) | |||||
const handleScanClose = useCallback(async () => { | |||||
if (scanner) { | |||||
console.log("Cleaning up scanner..."); | |||||
await scanner.stop() | |||||
await scanner.clear() | |||||
} | |||||
}, [scanner]) | }, [scanner]) | ||||
useEffect(() => { | useEffect(() => { | ||||
if (scanner) { | if (scanner) { | ||||
console.log("Scanner Instance:", scanner); | console.log("Scanner Instance:", scanner); | ||||
const success: QrcodeSuccessCallback = (decodedText, result) => { | |||||
console.log(`Decoded text: ${decodedText}`); | |||||
// Handle the decoded text as needed | |||||
setIsScanned(true) | |||||
scanner.pause(); | |||||
onScanSuccess(decodedText) | |||||
}; | |||||
const error: QrcodeErrorCallback = (errorMessage, error) => { | |||||
console.log(`Error: ${errorMessage}`); | |||||
scanner.start( | |||||
mediaTrackConstraintSet, | |||||
cameraConfig, | |||||
handleScanSuccess, | |||||
handleScanError | |||||
) | |||||
if (onScanError) { | |||||
onScanError(errorMessage) | |||||
} | |||||
}; | |||||
try { | |||||
scanner.render(success, error); | |||||
console.log("Scanner render called"); | |||||
} catch (err) { | |||||
console.error("Failed to render scanner:", err); | |||||
} | |||||
return () => { | return () => { | ||||
console.log("Cleaning up scanner..."); | |||||
scanner.clear().catch((error) => { | |||||
console.error("Failed to clear html5QrcodeScanner. ", error); | |||||
}); | |||||
handleScanClose() | |||||
}; | }; | ||||
} | } | ||||
}, [scanner]); | }, [scanner]); | ||||
return ( | return ( | ||||
<> | <> | ||||
<div id="qr-reader" hidden={isScanned} /> | |||||
<Button | |||||
size="small" | |||||
onClick={handleStartScan} | |||||
variant="contained" | |||||
> | |||||
{isScanned ? "Re-Scan" : "Stop-Scanning"} | |||||
</Button> | |||||
<Stack spacing={2}> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> | |||||
{"Title"} | |||||
</Typography> | |||||
<Grid container columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={12} justifyContent={"center"}> | |||||
<div style={{textAlign: "center", margin: "auto", justifyContent: "center"}} id="qr-reader" hidden={isScanned} /> | |||||
</Grid> | |||||
</Grid> | |||||
<Stack direction="row" justifyContent={"flex-end"} spacing={2} sx={{ margin: 2 }}> | |||||
<Button | |||||
size="small" | |||||
onClick={handleStartScan} | |||||
variant="contained" | |||||
sx={{ margin: 2 }} | |||||
> | |||||
{isScanned ? "Re-Scan" : "Stop-Scanning"} | |||||
</Button> | |||||
</Stack> | |||||
</Stack> | |||||
</> | </> | ||||
) | ) | ||||
} | } | ||||
@@ -1,6 +1,6 @@ | |||||
import { Button, CardContent, Card, Modal, SxProps, ModalProps } from "@mui/material"; | |||||
import { Button, CardContent, Card, Modal, SxProps, ModalProps, Box, CardActions } from "@mui/material"; | |||||
import QrCodeScanner from "./QrCodeScanner"; | import QrCodeScanner from "./QrCodeScanner"; | ||||
import { useCallback } from "react"; | |||||
import { useCallback, useRef } from "react"; | |||||
const modalSx: SxProps = { | const modalSx: SxProps = { | ||||
position: "absolute", | position: "absolute", | ||||
@@ -39,7 +39,14 @@ const QrCodeScannerModal: React.FC<QrCodeScannerModalProps> = ({ | |||||
<Modal open={isOpen} onClose={onModalClose}> | <Modal open={isOpen} onClose={onModalClose}> | ||||
<Card sx={modalSx}> | <Card sx={modalSx}> | ||||
<CardContent> | <CardContent> | ||||
<QrCodeScanner onScanSuccess={onScanSuccess} onScanError={onScanError}/> | |||||
<Box | |||||
sx={{ | |||||
marginInline: -3, | |||||
marginBlock: 4, | |||||
}} | |||||
> | |||||
<QrCodeScanner onScanSuccess={onScanSuccess} onScanError={onScanError} isOpen={isOpen}/> | |||||
</Box> | |||||
</CardContent> | </CardContent> | ||||
</Card> | </Card> | ||||
</Modal> | </Modal> | ||||
@@ -1,7 +1,14 @@ | |||||
import Swal from "sweetalert2"; | |||||
import Swal, { SweetAlertOptions } from "sweetalert2"; | |||||
import "./sweetalert2.css"; | import "./sweetalert2.css"; | ||||
import { TFunction } from "i18next"; | |||||
export const msg = (text) => { | |||||
export type SweetAlertTitle = string | HTMLElement | JQuery | undefined | |||||
export type SweetAlertHtml = string | HTMLElement | JQuery | undefined | |||||
export type SweetAlertConfirmButtonText = string | undefined | |||||
type Transaction = TFunction<["translation", ...string[]], undefined> | |||||
export const msg = (title: SweetAlertTitle) => { | |||||
Swal.mixin({ | Swal.mixin({ | ||||
toast: true, | toast: true, | ||||
position: "bottom-end", | position: "bottom-end", | ||||
@@ -13,68 +20,68 @@ export const msg = (text) => { | |||||
toast.onmouseleave = Swal.resumeTimer; | toast.onmouseleave = Swal.resumeTimer; | ||||
}, | }, | ||||
}).fire({ | }).fire({ | ||||
icon: "Success", | |||||
title: text, | |||||
icon: "success", | |||||
title: title, | |||||
}); | }); | ||||
}; | }; | ||||
export const popup = (text) => { | |||||
Swal.fire(text); | |||||
export const popup = (options: SweetAlertOptions) => { | |||||
Swal.fire(options); | |||||
}; | }; | ||||
export const successDialog = (text, t) => { | |||||
export const successDialog = (title: SweetAlertTitle, t: Transaction) => { | |||||
return Swal.fire({ | return Swal.fire({ | ||||
icon: "success", | icon: "success", | ||||
title: text, | |||||
title: title, | |||||
confirmButtonText: t("Confirm"), | confirmButtonText: t("Confirm"), | ||||
showConfirmButton: true, | showConfirmButton: true, | ||||
}); | }); | ||||
}; | }; | ||||
export const successDialogWithContent = (title, text, t) => { | |||||
export const successDialogWithContent = (title: SweetAlertTitle, html: SweetAlertHtml, t: Transaction) => { | |||||
return Swal.fire({ | return Swal.fire({ | ||||
icon: "success", | icon: "success", | ||||
title: title, | title: title, | ||||
html: text, | |||||
html: html, | |||||
confirmButtonText: t("Confirm"), | confirmButtonText: t("Confirm"), | ||||
showConfirmButton: true, | showConfirmButton: true, | ||||
}); | }); | ||||
}; | }; | ||||
export const errorDialog = (text, t) => { | |||||
export const errorDialog = (title: SweetAlertTitle, t: Transaction) => { | |||||
return Swal.fire({ | return Swal.fire({ | ||||
icon: "error", | icon: "error", | ||||
title: text, | |||||
title: title, | |||||
confirmButtonText: t("Confirm"), | confirmButtonText: t("Confirm"), | ||||
showConfirmButton: true, | showConfirmButton: true, | ||||
}); | }); | ||||
}; | }; | ||||
export const errorDialogWithContent = (title, text, t) => { | |||||
export const errorDialogWithContent = (title: SweetAlertTitle, html: SweetAlertHtml, t: Transaction) => { | |||||
return Swal.fire({ | return Swal.fire({ | ||||
icon: "error", | icon: "error", | ||||
title: title, | title: title, | ||||
html: text, | |||||
html: html, | |||||
confirmButtonText: t("Confirm"), | confirmButtonText: t("Confirm"), | ||||
showConfirmButton: true, | showConfirmButton: true, | ||||
}); | }); | ||||
}; | }; | ||||
export const warningDialog = (text, t) => { | |||||
export const warningDialog = (title: SweetAlertTitle, t: Transaction) => { | |||||
return Swal.fire({ | return Swal.fire({ | ||||
icon: "warning", | icon: "warning", | ||||
title: text, | |||||
title: title, | |||||
confirmButtonText: t("Confirm"), | confirmButtonText: t("Confirm"), | ||||
showConfirmButton: true, | showConfirmButton: true, | ||||
}); | }); | ||||
}; | }; | ||||
export const submitDialog = async ( | export const submitDialog = async ( | ||||
confirmAction, | |||||
t, | |||||
confirmAction: () => void, | |||||
t: Transaction, | |||||
{ ...props } = { | { ...props } = { | ||||
title: t("Do you want to submit?"), | |||||
confirmButtonText: t("Submit"), | |||||
title: t("Do you want to submit?") as SweetAlertTitle, | |||||
confirmButtonText: t("Submit") as SweetAlertConfirmButtonText, | |||||
} | } | ||||
) => { | ) => { | ||||
// console.log(props) | // console.log(props) | ||||
@@ -97,12 +104,12 @@ export const submitDialog = async ( | |||||
}; | }; | ||||
export const submitDialogWithWarning = async ( | export const submitDialogWithWarning = async ( | ||||
confirmAction, | |||||
t, | |||||
confirmAction: () => void, | |||||
t: Transaction, | |||||
{ ...props } = { | { ...props } = { | ||||
title: t("Do you want to submit?"), | |||||
text: t("Warning!"), | |||||
confirmButtonText: t("Submit"), | |||||
title: t("Do you want to submit?") as SweetAlertTitle, | |||||
html: t("Warning!") as SweetAlertHtml, | |||||
confirmButtonText: t("Submit") as SweetAlertConfirmButtonText, | |||||
} | } | ||||
) => { | ) => { | ||||
// console.log(props) | // console.log(props) | ||||
@@ -110,7 +117,7 @@ export const submitDialogWithWarning = async ( | |||||
const result = await Swal.fire({ | const result = await Swal.fire({ | ||||
icon: "warning", | icon: "warning", | ||||
title: props?.title, | title: props?.title, | ||||
html: props?.text, | |||||
html: props?.html, | |||||
cancelButtonText: t("Cancel"), | cancelButtonText: t("Cancel"), | ||||
confirmButtonText: props?.confirmButtonText, | confirmButtonText: props?.confirmButtonText, | ||||
showCancelButton: true, | showCancelButton: true, | ||||
@@ -125,7 +132,7 @@ export const submitDialogWithWarning = async ( | |||||
} | } | ||||
}; | }; | ||||
export const deleteDialog = async (confirmAction, t) => { | |||||
export const deleteDialog = async (confirmAction: () => void, t: Transaction) => { | |||||
// const { t } = useTranslation("common") | // const { t } = useTranslation("common") | ||||
const result = await Swal.fire({ | const result = await Swal.fire({ | ||||
icon: "question", | icon: "question", |