@@ -2,7 +2,8 @@ | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
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 { | |||
id?: number; | |||
@@ -26,7 +27,19 @@ export const saveQcItem = async (data: SaveQcItemInputs) => { | |||
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 | |||
} |
@@ -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>( | |||
`${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 | |||
? { | |||
Authorization: `Bearer ${accessToken}`, | |||
} | |||
: {}), | |||
}, | |||
@@ -56,13 +57,17 @@ type FetchParams = Parameters<typeof fetch>; | |||
export async function serverFetchJson<T>(...args: FetchParams) { | |||
const response = await serverFetch(...args); | |||
if (response.ok) { | |||
if (response.status === 204) { | |||
return response.status as T | |||
} | |||
return response.json() as T; | |||
} else { | |||
switch (response.status) { | |||
case 401: | |||
signOutUser(); | |||
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 item xs={6}> | |||
<Grid item xs={12}> | |||
<TextField | |||
label={t("Description")} | |||
// multiline | |||
fullWidth | |||
{...register("description", { | |||
maxLength: 100 | |||
@@ -1,12 +1,14 @@ | |||
"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 { 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 { useRouter } from "next/navigation"; | |||
import QcItemDetails from "./QcItemDetails"; | |||
import { Check, Close, Delete } from "@mui/icons-material"; | |||
interface Props { | |||
defaultInputs?: SaveQcItemInputs; | |||
@@ -38,7 +40,10 @@ const QcItemSave: React.FC<Props> = ({ | |||
errorDialogWithContent(t("Submit Error"), errorContents, t) | |||
} 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 ( | |||
<> | |||
<FormProvider {...formProps}> | |||
@@ -59,7 +80,32 @@ const QcItemSave: React.FC<Props> = ({ | |||
component={"form"} | |||
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> | |||
</FormProvider> | |||
</> | |||
@@ -1,7 +1,7 @@ | |||
import React from "react" | |||
import QcItemSaveLoading from "./QcItemSaveLoading" | |||
import QcItemSave from "./QcItemSave"; | |||
import { fetchQcItemDetail } from "@/app/api/settings/qcItem"; | |||
import { fetchQcItemDetails } from "@/app/api/settings/qcItem"; | |||
interface SubComponents { | |||
Loading: typeof QcItemSaveLoading; | |||
@@ -16,7 +16,7 @@ type Props = SaveQcItemProps | |||
const QcItemSaveWrapper: React.FC<Props> & SubComponents = async ( | |||
props | |||
) => { | |||
const qcItem = props.id ? await fetchQcItemDetail(props.id) : undefined; | |||
const qcItem = props.id ? await fetchQcItemDetails(props.id) : undefined; | |||
return <QcItemSave defaultInputs={qcItem} />; | |||
}; | |||
@@ -6,9 +6,12 @@ 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 { usePathname, useRouter } from "next/navigation"; | |||
import QrCodeScanner from "../QrCodeScanner"; | |||
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 { | |||
qcItems: QcItemResult[]; | |||
@@ -20,6 +23,7 @@ type SearchParamNames = keyof SearchQuery; | |||
const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||
const { t } = useTranslation("qcItems"); | |||
const router = useRouter(); | |||
const pathname = usePathname() | |||
// If qcItem searching is done on the server-side, then no need for this. | |||
const [filteredQcItems, setFilteredQcItems] = useState(qcItems); | |||
@@ -40,11 +44,24 @@ const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||
const onQcItemClick = useCallback( | |||
(qcItem: QcItemResult) => { | |||
router.push(`/edit/${qcItem.id}`); | |||
router.push(`${pathname}/edit?id=${qcItem.id}`); | |||
}, | |||
[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>[]>( | |||
() => [ | |||
{ | |||
@@ -55,28 +72,35 @@ const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||
}, | |||
{ name: "code", label: t("Code") }, | |||
{ name: "name", label: t("Name") }, | |||
{ | |||
name: "id", | |||
label: t("Delete"), | |||
onClick: handleDelete, | |||
buttonIcon: <Delete />, | |||
buttonColor: "error" | |||
} | |||
], | |||
[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 ( | |||
<> | |||
<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} | |||
onSearch={(query) => { | |||
setFilteredQcItems( | |||
@@ -89,9 +113,9 @@ const qcItemSearch: React.FC<Props> = ({ qcItems }) => { | |||
}} | |||
onReset={onReset} | |||
/> | |||
<SearchResults<QcItemResult> items={filteredQcItems} columns={columns} /> */} | |||
<SearchResults<QcItemResult> items={filteredQcItems} columns={columns} /> | |||
</> | |||
) | |||
) | |||
}; | |||
export default qcItemSearch; |
@@ -159,14 +159,14 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||
return ( | |||
<> | |||
<Stack spacing={2}> | |||
{title ? <Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> | |||
{title && <Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> | |||
{"Title"} | |||
</Typography> : null} | |||
</Typography>} | |||
<Grid container alignItems="center" justifyContent="center" rowSpacing={2} columns={{ xs: 6, sm: 12 }}> | |||
<Grid item xs={12}> | |||
<div style={{ textAlign: "center", margin: "auto", justifyContent: "center" }} id="qr-reader" hidden={isScanned} /> | |||
</Grid> | |||
{cameraList.length > 0 ? <Grid item xs={6} > | |||
{cameraList.length > 0 && <Grid item xs={6} > | |||
<Autocomplete | |||
disableClearable | |||
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> | |||
}) : null | |||
}) | |||
} | |||
</Grid> | |||
<Stack direction="row" justifyContent={"flex-end"} spacing={2} sx={{ margin: 2 }}> | |||
@@ -195,7 +195,7 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||
size="small" | |||
onClick={switchScanStatus} | |||
variant="contained" | |||
sx={{ margin: 2 }} | |||
// sx={{ margin: 2 }} | |||
startIcon={isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon />} | |||
> | |||
{isScanned ? t("Start Scanning") : t("Stop Scanning")} | |||
@@ -203,9 +203,9 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ | |||
<Button | |||
size="small" | |||
onClick={handleScanCloseButton} | |||
variant="contained" | |||
color="error" | |||
sx={{ margin: 2 }} | |||
variant="outlined" | |||
// color="error" | |||
// sx={{ margin: 2 }} | |||
startIcon={<CloseOutlinedIcon />} | |||
> | |||
{t("Cancel")} | |||
@@ -11,7 +11,7 @@ import TablePagination, { | |||
TablePaginationProps, | |||
} from "@mui/material/TablePagination"; | |||
import TableRow from "@mui/material/TableRow"; | |||
import IconButton from "@mui/material/IconButton"; | |||
import IconButton, { IconButtonOwnProps } from "@mui/material/IconButton"; | |||
export interface ResultWithId { | |||
id: string | number; | |||
@@ -25,6 +25,7 @@ interface BaseColumn<T extends ResultWithId> { | |||
interface ColumnWithAction<T extends ResultWithId> extends BaseColumn<T> { | |||
onClick: (item: T) => void; | |||
buttonIcon: React.ReactNode; | |||
buttonColor?: IconButtonOwnProps["color"]; | |||
} | |||
export type Column<T extends ResultWithId> = | |||
@@ -91,7 +92,7 @@ function SearchResults<T extends ResultWithId>({ | |||
<TableCell key={`${columnName.toString()}-${idx}`}> | |||
{isActionColumn(column) ? ( | |||
<IconButton | |||
color="primary" | |||
color={column.buttonColor ?? "primary"} | |||
onClick={() => column.onClick(item)} | |||
> | |||
{column.buttonIcon} | |||
@@ -2,7 +2,7 @@ import Swal, { SweetAlertOptions } from "sweetalert2"; | |||
import "./sweetalert2.css"; | |||
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 SweetAlertConfirmButtonText = string | undefined | |||
@@ -29,13 +29,17 @@ export const popup = (options: SweetAlertOptions) => { | |||
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", | |||
title: title, | |||
confirmButtonText: t("Confirm"), | |||
showConfirmButton: true, | |||
}); | |||
if (result.isConfirmed && confirmAction) { | |||
confirmAction(); | |||
} | |||
}; | |||
export const successDialogWithContent = (title: SweetAlertTitle, html: SweetAlertHtml, t: Transaction) => { | |||
@@ -81,7 +85,7 @@ export const submitDialog = async ( | |||
t: Transaction, | |||
{ ...props } = { | |||
title: t("Do you want to submit?") as SweetAlertTitle, | |||
confirmButtonText: t("Submit") as SweetAlertConfirmButtonText, | |||
confirmButtonText: t("Submit") as SweetAlertConfirmButtonText, | |||
} | |||
) => { | |||
// console.log(props) | |||