@@ -2,7 +2,8 @@ | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { revalidateTag } from "next/cache"; | |||||
import { revalidatePath, revalidateTag } from "next/cache"; | |||||
import { QcItemResult } from '@/app/api/settings/qcItem'; | |||||
export interface SaveQcItemInputs { | export interface SaveQcItemInputs { | ||||
id?: number; | id?: number; | ||||
@@ -26,7 +27,19 @@ export const saveQcItem = async (data: SaveQcItemInputs) => { | |||||
headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
}) | }) | ||||
revalidateTag(`qcItems`) | |||||
revalidateTag("qcItems") | |||||
return response | |||||
} | |||||
export const deleteQcItem = async (id: number) => { | |||||
const response = await serverFetchJson<QcItemResult[]>(`${BASE_API_URL}/qcItems/${id}`, { | |||||
method: "DELETE", | |||||
headers: { "Content-Type": "application/json" }, | |||||
}) | |||||
revalidateTag("qcItems") | |||||
revalidatePath("/(main)/settings/qcItem") | |||||
return response | return response | ||||
} | } |
@@ -22,11 +22,11 @@ export const fetchQcItems = cache(async () => { | |||||
}); | }); | ||||
}); | }); | ||||
export const fetchQcItemDetail = cache(async (qcItemId: string) => { | |||||
export const fetchQcItemDetails = cache(async (qcItemId: string) => { | |||||
return serverFetchJson<SaveQcItemInputs>( | return serverFetchJson<SaveQcItemInputs>( | ||||
`${BASE_API_URL}/qcItems/qcItemDetail/${qcItemId}`, | |||||
`${BASE_API_URL}/qcItems/qcItemDetails/${qcItemId}`, | |||||
{ | { | ||||
next: { tags: [`qcItemDetail_${qcItemId}`] } | |||||
next: { tags: [`qcItemDetails_${qcItemId}`] } | |||||
} | } | ||||
) | ) | ||||
}) | }) |
@@ -45,6 +45,7 @@ export const serverFetch: typeof fetch = async (input, init) => { | |||||
...(accessToken | ...(accessToken | ||||
? { | ? { | ||||
Authorization: `Bearer ${accessToken}`, | Authorization: `Bearer ${accessToken}`, | ||||
} | } | ||||
: {}), | : {}), | ||||
}, | }, | ||||
@@ -56,13 +57,17 @@ type FetchParams = Parameters<typeof fetch>; | |||||
export async function serverFetchJson<T>(...args: FetchParams) { | export async function serverFetchJson<T>(...args: FetchParams) { | ||||
const response = await serverFetch(...args); | const response = await serverFetch(...args); | ||||
if (response.ok) { | if (response.ok) { | ||||
if (response.status === 204) { | |||||
return response.status as T | |||||
} | |||||
return response.json() as T; | return response.json() as T; | ||||
} else { | } else { | ||||
switch (response.status) { | switch (response.status) { | ||||
case 401: | case 401: | ||||
signOutUser(); | signOutUser(); | ||||
default: | default: | ||||
throw Error("Something went wrong fetching data in server."); | |||||
throw new ServerFetchError("Something went wrong fetching data in server.", response); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -38,9 +38,10 @@ const QcItemDetails = () => { | |||||
})} | })} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<Grid item xs={12}> | |||||
<TextField | <TextField | ||||
label={t("Description")} | label={t("Description")} | ||||
// multiline | |||||
fullWidth | fullWidth | ||||
{...register("description", { | {...register("description", { | ||||
maxLength: 100 | maxLength: 100 | ||||
@@ -1,12 +1,14 @@ | |||||
"use client" | "use client" | ||||
import { saveQcItem, SaveQcItemInputs } from "@/app/api/settings/qcItem/actions"; | |||||
import { Stack } from "@mui/material"; | |||||
import { deleteQcItem, saveQcItem, SaveQcItemInputs } from "@/app/api/settings/qcItem/actions"; | |||||
import { Button, Stack } from "@mui/material"; | |||||
import { useCallback } from "react"; | import { useCallback } from "react"; | ||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | ||||
import { errorDialogWithContent, submitDialog } from "../Swal/CustomAlerts"; | |||||
import { deleteDialog, errorDialogWithContent, submitDialog, successDialog } from "../Swal/CustomAlerts"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
import QcItemDetails from "./QcItemDetails"; | |||||
import { Check, Close, Delete } from "@mui/icons-material"; | |||||
interface Props { | interface Props { | ||||
defaultInputs?: SaveQcItemInputs; | defaultInputs?: SaveQcItemInputs; | ||||
@@ -38,7 +40,10 @@ const QcItemSave: React.FC<Props> = ({ | |||||
errorDialogWithContent(t("Submit Error"), errorContents, t) | errorDialogWithContent(t("Submit Error"), errorContents, t) | ||||
} else { | } else { | ||||
router.push("/settings/qcItem") | |||||
await successDialog( | |||||
t("Submit Success"), | |||||
t, | |||||
() => router.push("/settings/qcItem")) | |||||
} | } | ||||
}, | }, | ||||
[] | [] | ||||
@@ -51,6 +56,22 @@ const QcItemSave: React.FC<Props> = ({ | |||||
}, [] | }, [] | ||||
) | ) | ||||
const handleCancel = () => { | |||||
router.replace("/qcItem"); | |||||
}; | |||||
const handleDelete = () => { | |||||
deleteDialog(async () => { | |||||
await deleteQcItem(formProps.getValues("id")!); | |||||
await successDialog( | |||||
t("Delete Success"), | |||||
t, | |||||
() => router.replace("/settings/qcItem") | |||||
); | |||||
}, t); | |||||
}; | |||||
return ( | return ( | ||||
<> | <> | ||||
<FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
@@ -59,7 +80,32 @@ const QcItemSave: React.FC<Props> = ({ | |||||
component={"form"} | component={"form"} | ||||
onSubmit={formProps.handleSubmit(onSubmit)} | onSubmit={formProps.handleSubmit(onSubmit)} | ||||
> | > | ||||
<QcItemDetails /> | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
{defaultInputs?.id && <Button | |||||
variant="outlined" | |||||
color="error" | |||||
startIcon={<Delete />} | |||||
onClick={handleDelete} | |||||
> | |||||
{t("Delete")} | |||||
</Button>} | |||||
<Button | |||||
variant="outlined" | |||||
startIcon={<Close />} | |||||
onClick={handleCancel} | |||||
> | |||||
{t("Cancel")} | |||||
</Button> | |||||
<Button | |||||
name="submit" | |||||
variant="contained" | |||||
startIcon={<Check />} | |||||
type="submit" | |||||
> | |||||
{t("Submit")} | |||||
</Button> | |||||
</Stack> | |||||
</Stack> | </Stack> | ||||
</FormProvider> | </FormProvider> | ||||
</> | </> | ||||
@@ -1,7 +1,7 @@ | |||||
import React from "react" | import React from "react" | ||||
import QcItemSaveLoading from "./QcItemSaveLoading" | import QcItemSaveLoading from "./QcItemSaveLoading" | ||||
import QcItemSave from "./QcItemSave"; | import QcItemSave from "./QcItemSave"; | ||||
import { fetchQcItemDetail } from "@/app/api/settings/qcItem"; | |||||
import { fetchQcItemDetails } from "@/app/api/settings/qcItem"; | |||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof QcItemSaveLoading; | Loading: typeof QcItemSaveLoading; | ||||
@@ -16,7 +16,7 @@ type Props = SaveQcItemProps | |||||
const QcItemSaveWrapper: React.FC<Props> & SubComponents = async ( | const QcItemSaveWrapper: React.FC<Props> & SubComponents = async ( | ||||
props | props | ||||
) => { | ) => { | ||||
const qcItem = props.id ? await fetchQcItemDetail(props.id) : undefined; | |||||
const qcItem = props.id ? await fetchQcItemDetails(props.id) : undefined; | |||||
return <QcItemSave defaultInputs={qcItem} />; | return <QcItemSave defaultInputs={qcItem} />; | ||||
}; | }; | ||||
@@ -6,9 +6,12 @@ import { useTranslation } from "react-i18next"; | |||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
import EditNote from "@mui/icons-material/EditNote"; | import EditNote from "@mui/icons-material/EditNote"; | ||||
import { QcItemResult } from "@/app/api/settings/qcItem"; | import { QcItemResult } from "@/app/api/settings/qcItem"; | ||||
import { useRouter } from "next/navigation"; | |||||
import { usePathname, useRouter } from "next/navigation"; | |||||
import QrCodeScanner from "../QrCodeScanner"; | import QrCodeScanner from "../QrCodeScanner"; | ||||
import { Button } from "@mui/material"; | import { Button } from "@mui/material"; | ||||
import { Delete } from "@mui/icons-material"; | |||||
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||||
import { deleteQcItem } from "@/app/api/settings/qcItem/actions"; | |||||
interface Props { | interface Props { | ||||
qcItems: QcItemResult[]; | qcItems: QcItemResult[]; | ||||
@@ -20,6 +23,7 @@ type SearchParamNames = keyof SearchQuery; | |||||
const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | ||||
const { t } = useTranslation("qcItems"); | const { t } = useTranslation("qcItems"); | ||||
const router = useRouter(); | const router = useRouter(); | ||||
const pathname = usePathname() | |||||
// If qcItem searching is done on the server-side, then no need for this. | // If qcItem searching is done on the server-side, then no need for this. | ||||
const [filteredQcItems, setFilteredQcItems] = useState(qcItems); | const [filteredQcItems, setFilteredQcItems] = useState(qcItems); | ||||
@@ -40,11 +44,24 @@ const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||||
const onQcItemClick = useCallback( | const onQcItemClick = useCallback( | ||||
(qcItem: QcItemResult) => { | (qcItem: QcItemResult) => { | ||||
router.push(`/edit/${qcItem.id}`); | |||||
router.push(`${pathname}/edit?id=${qcItem.id}`); | |||||
}, | }, | ||||
[router] | [router] | ||||
); | ); | ||||
const handleDelete = useCallback( | |||||
(qcItem: QcItemResult) => { | |||||
deleteDialog(async () => { | |||||
qcItems = await deleteQcItem(qcItem.id); | |||||
setFilteredQcItems(qcItems) | |||||
await successDialog( | |||||
t("Delete Success"), | |||||
t | |||||
); | |||||
}, t); | |||||
}, []) | |||||
const columns = useMemo<Column<QcItemResult>[]>( | const columns = useMemo<Column<QcItemResult>[]>( | ||||
() => [ | () => [ | ||||
{ | { | ||||
@@ -55,28 +72,35 @@ const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||||
}, | }, | ||||
{ name: "code", label: t("Code") }, | { name: "code", label: t("Code") }, | ||||
{ name: "name", label: t("Name") }, | { name: "name", label: t("Name") }, | ||||
{ | |||||
name: "id", | |||||
label: t("Delete"), | |||||
onClick: handleDelete, | |||||
buttonIcon: <Delete />, | |||||
buttonColor: "error" | |||||
} | |||||
], | ], | ||||
[t, onQcItemClick] | [t, onQcItemClick] | ||||
); | ); | ||||
const [isOpenScanner, setOpenScanner] = useState(false) | |||||
const onOpenScanner = useCallback(() => { | |||||
setOpenScanner(true) | |||||
}, []) | |||||
// const [isOpenScanner, setOpenScanner] = useState(false) | |||||
// const onOpenScanner = useCallback(() => { | |||||
// setOpenScanner(true) | |||||
// }, []) | |||||
const onCloseScanner = useCallback(() => { | |||||
setOpenScanner(false) | |||||
}, []) | |||||
// const onCloseScanner = useCallback(() => { | |||||
// setOpenScanner(false) | |||||
// }, []) | |||||
const handleScanSuccess = useCallback((result: string) => { | |||||
console.log(result) | |||||
}, []) | |||||
// const handleScanSuccess = useCallback((result: string) => { | |||||
// console.log(result) | |||||
// }, []) | |||||
return ( | return ( | ||||
<> | <> | ||||
<QrCodeScanner isOpen={isOpenScanner} onClose={onCloseScanner} onScanSuccess={handleScanSuccess} /> | |||||
<Button onClick={onOpenScanner}>abc</Button> | |||||
{/* <SearchBox | |||||
{/* <QrCodeScanner isOpen={isOpenScanner} onClose={onCloseScanner} onScanSuccess={handleScanSuccess} /> | |||||
<Button onClick={onOpenScanner}>abc</Button> */} | |||||
<SearchBox | |||||
criteria={searchCriteria} | criteria={searchCriteria} | ||||
onSearch={(query) => { | onSearch={(query) => { | ||||
setFilteredQcItems( | setFilteredQcItems( | ||||
@@ -89,9 +113,9 @@ const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||||
}} | }} | ||||
onReset={onReset} | onReset={onReset} | ||||
/> | /> | ||||
<SearchResults<QcItemResult> items={filteredQcItems} columns={columns} /> */} | |||||
<SearchResults<QcItemResult> items={filteredQcItems} columns={columns} /> | |||||
</> | </> | ||||
) | |||||
) | |||||
}; | }; | ||||
export default qcItemSearch; | export default qcItemSearch; |
@@ -159,14 +159,14 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
return ( | return ( | ||||
<> | <> | ||||
<Stack spacing={2}> | <Stack spacing={2}> | ||||
{title ? <Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> | |||||
{title && <Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> | |||||
{"Title"} | {"Title"} | ||||
</Typography> : null} | |||||
</Typography>} | |||||
<Grid container alignItems="center" justifyContent="center" rowSpacing={2} columns={{ xs: 6, sm: 12 }}> | <Grid container alignItems="center" justifyContent="center" rowSpacing={2} columns={{ xs: 6, sm: 12 }}> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<div style={{ textAlign: "center", margin: "auto", justifyContent: "center" }} id="qr-reader" hidden={isScanned} /> | <div style={{ textAlign: "center", margin: "auto", justifyContent: "center" }} id="qr-reader" hidden={isScanned} /> | ||||
</Grid> | </Grid> | ||||
{cameraList.length > 0 ? <Grid item xs={6} > | |||||
{cameraList.length > 0 && <Grid item xs={6} > | |||||
<Autocomplete | <Autocomplete | ||||
disableClearable | disableClearable | ||||
noOptionsText={t("No Options")} | noOptionsText={t("No Options")} | ||||
@@ -183,11 +183,11 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
/> | /> | ||||
)} | )} | ||||
/> | /> | ||||
</Grid> : null} | |||||
</Grid>} | |||||
{ | { | ||||
contents ? contents.map((string) => { | |||||
contents && contents.map((string) => { | |||||
return <Grid item xs={8}>{string}</Grid> | return <Grid item xs={8}>{string}</Grid> | ||||
}) : null | |||||
}) | |||||
} | } | ||||
</Grid> | </Grid> | ||||
<Stack direction="row" justifyContent={"flex-end"} spacing={2} sx={{ margin: 2 }}> | <Stack direction="row" justifyContent={"flex-end"} spacing={2} sx={{ margin: 2 }}> | ||||
@@ -195,7 +195,7 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
size="small" | size="small" | ||||
onClick={switchScanStatus} | onClick={switchScanStatus} | ||||
variant="contained" | variant="contained" | ||||
sx={{ margin: 2 }} | |||||
// sx={{ margin: 2 }} | |||||
startIcon={isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon />} | startIcon={isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon />} | ||||
> | > | ||||
{isScanned ? t("Start Scanning") : t("Stop Scanning")} | {isScanned ? t("Start Scanning") : t("Stop Scanning")} | ||||
@@ -203,9 +203,9 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||||
<Button | <Button | ||||
size="small" | size="small" | ||||
onClick={handleScanCloseButton} | onClick={handleScanCloseButton} | ||||
variant="contained" | |||||
color="error" | |||||
sx={{ margin: 2 }} | |||||
variant="outlined" | |||||
// color="error" | |||||
// sx={{ margin: 2 }} | |||||
startIcon={<CloseOutlinedIcon />} | startIcon={<CloseOutlinedIcon />} | ||||
> | > | ||||
{t("Cancel")} | {t("Cancel")} | ||||
@@ -11,7 +11,7 @@ import TablePagination, { | |||||
TablePaginationProps, | TablePaginationProps, | ||||
} from "@mui/material/TablePagination"; | } from "@mui/material/TablePagination"; | ||||
import TableRow from "@mui/material/TableRow"; | import TableRow from "@mui/material/TableRow"; | ||||
import IconButton from "@mui/material/IconButton"; | |||||
import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton"; | |||||
export interface ResultWithId { | export interface ResultWithId { | ||||
id: string | number; | id: string | number; | ||||
@@ -25,6 +25,7 @@ interface BaseColumn<T extends ResultWithId> { | |||||
interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | ||||
onClick: (item: T) => void; | onClick: (item: T) => void; | ||||
buttonIcon: React.ReactNode; | buttonIcon: React.ReactNode; | ||||
buttonColor?: IconButtonOwnProps["color"]; | |||||
} | } | ||||
export type Column<T extends ResultWithId> = | export type Column<T extends ResultWithId> = | ||||
@@ -91,7 +92,7 @@ function SearchResults<T extends ResultWithId>({ | |||||
<TableCell key={`${columnName.toString()}-${idx}`}> | <TableCell key={`${columnName.toString()}-${idx}`}> | ||||
{isActionColumn(column) ? ( | {isActionColumn(column) ? ( | ||||
<IconButton | <IconButton | ||||
color="primary" | |||||
color={column.buttonColor ?? "primary"} | |||||
onClick={() => column.onClick(item)} | onClick={() => column.onClick(item)} | ||||
> | > | ||||
{column.buttonIcon} | {column.buttonIcon} | ||||
@@ -2,7 +2,7 @@ import Swal, { SweetAlertOptions } from "sweetalert2"; | |||||
import "./sweetalert2.css"; | import "./sweetalert2.css"; | ||||
import { TFunction } from "i18next"; | import { TFunction } from "i18next"; | ||||
export type SweetAlertTitle = string | HTMLElement | JQuery | undefined | |||||
export type SweetAlertTitle = string | HTMLElement | JQuery | undefined | |||||
export type SweetAlertHtml = string | HTMLElement | JQuery | undefined | export type SweetAlertHtml = string | HTMLElement | JQuery | undefined | ||||
export type SweetAlertConfirmButtonText = string | undefined | export type SweetAlertConfirmButtonText = string | undefined | ||||
@@ -29,13 +29,17 @@ export const popup = (options: SweetAlertOptions) => { | |||||
Swal.fire(options); | Swal.fire(options); | ||||
}; | }; | ||||
export const successDialog = (title: SweetAlertTitle, t: Transaction) => { | |||||
return Swal.fire({ | |||||
export const successDialog = async (title: SweetAlertTitle, t: Transaction, confirmAction?: () => void) => { | |||||
const result = await Swal.fire({ | |||||
icon: "success", | icon: "success", | ||||
title: title, | title: title, | ||||
confirmButtonText: t("Confirm"), | confirmButtonText: t("Confirm"), | ||||
showConfirmButton: true, | showConfirmButton: true, | ||||
}); | }); | ||||
if (result.isConfirmed && confirmAction) { | |||||
confirmAction(); | |||||
} | |||||
}; | }; | ||||
export const successDialogWithContent = (title: SweetAlertTitle, html: SweetAlertHtml, t: Transaction) => { | export const successDialogWithContent = (title: SweetAlertTitle, html: SweetAlertHtml, t: Transaction) => { | ||||
@@ -81,7 +85,7 @@ export const submitDialog = async ( | |||||
t: Transaction, | t: Transaction, | ||||
{ ...props } = { | { ...props } = { | ||||
title: t("Do you want to submit?") as SweetAlertTitle, | title: t("Do you want to submit?") as SweetAlertTitle, | ||||
confirmButtonText: t("Submit") as SweetAlertConfirmButtonText, | |||||
confirmButtonText: t("Submit") as SweetAlertConfirmButtonText, | |||||
} | } | ||||
) => { | ) => { | ||||
// console.log(props) | // console.log(props) | ||||