Feature/amiriis damage items

This commit is contained in:
AmirHossein Mahmoodi
2025-02-18 10:07:12 +00:00
parent 6cdfc8aea1
commit 0febf9b149
15 changed files with 548 additions and 4 deletions

View File

@@ -0,0 +1,13 @@
import DamagesPage from "@/components/dashboard/receipt/damages";
import WithPermission from "@/core/middlewares/withPermission";
const Page = () => {
return (
<WithPermission
permission_name={["manage-damage"]}
>
<DamagesPage />
</WithPermission>
);
};
export default Page;

View File

@@ -0,0 +1,145 @@
import NumberField from "@/core/components/NumberField";
import StyledForm from "@/core/components/StyledForm";
import { CREATE_RECEIPT_DAMAGE_ITEMS } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { yupResolver } from "@hookform/resolvers/yup";
import { Beenhere, ExitToApp } from "@mui/icons-material";
import { Button, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material";
import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog";
import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup";
const validationSchema = object({
title: string().required("وارد کردن عنوان الزامیست!"),
unit: string().required("وارد کردن واحد الزامیست!"),
base_price: string().required("وارد کردن فی الزامیست!"),
});
const CreateFormContent = ({ setOpen, mutate }) => {
const requestServer = useRequest({ notificationSuccess: true });
const defaultValues = {
title: "",
unit: "",
base_price: null,
};
const {
control,
handleSubmit,
formState: { isSubmitting },
} = useForm({
defaultValues,
resolver: yupResolver(validationSchema),
mode: "onBlur",
});
const onSubmit = async (data) => {
const formData = new FormData();
formData.append("title", data.title);
formData.append("unit", data.unit);
formData.append("base_price", data.base_price);
try {
await requestServer(CREATE_RECEIPT_DAMAGE_ITEMS, "post", {
data: formData,
});
mutate();
setOpen(false);
} catch (error) {}
};
return (
<>
<DialogHeader>
<DialogTitle>ثبت آیتم جدید</DialogTitle>
</DialogHeader>
<DialogContent dividers>
<StyledForm onSubmit={handleSubmit(onSubmit)} id="createDamageForm">
<Stack spacing={3}>
<Controller
name="title"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
fullWidth
size="small"
label="عنوان"
value={value}
onChange={onChange}
placeholder="عنوان را وارد کنید"
error={!!error}
helperText={error ? error.message : null}
variant="outlined"
InputLabelProps={{ shrink: true }}
/>
)}
/>
<Controller
name="unit"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
fullWidth
size="small"
label="واحد"
value={value}
onChange={onChange}
error={!!error}
placeholder="واحد را وارد کنید"
helperText={error ? error.message : null}
variant="outlined"
InputLabelProps={{ shrink: true }}
/>
)}
/>
<Controller
name="base_price"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<NumberField
label={`فی`}
error={!!error}
value={value || ""}
type="text"
size={"small"}
unit={"ریال"}
inputProps={{
placeholder: "فی را وارد کنید",
inputMode: "number",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
if (!isNaN(event.target.value)) {
onChange(event.target.value);
} else {
onChange(value);
}
}}
helperText={error ? error.message : null}
variant="outlined"
fullWidth
/>
)}
/>
</Stack>
</StyledForm>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)} variant="outlined" color="secondary" startIcon={<ExitToApp />}>
بستن
</Button>
<Button
variant="contained"
disabled={isSubmitting}
type={"submit"}
form="createDamageForm"
endIcon={<Beenhere />}
>
{isSubmitting ? "در حال ثبت آیتم" : "ثبت آیتم"}
</Button>
</DialogActions>
</>
);
};
export default CreateFormContent;

View File

@@ -0,0 +1,11 @@
import { Dialog } from "@mui/material";
import CreateFormContent from "./CreateFormContent";
const CreateForm = ({ open, setOpen, mutate }) => {
return (
<Dialog open={open} fullWidth maxWidth="xs">
{open && <CreateFormContent setOpen={setOpen} mutate={mutate} />}
</Dialog>
);
};
export default CreateForm;

View File

@@ -0,0 +1,36 @@
import { AddCircle } from "@mui/icons-material";
import { Button, IconButton, useMediaQuery, useTheme } from "@mui/material";
import CreateForm from "./Form";
import { useState } from "react";
const CreateDamage = ({ mutate }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
return (
<>
{isMobile ? (
<IconButton aria-label="ثبت آیتم" color="primary" onClick={handleOpen}>
<AddCircle sx={{ fontSize: "25px" }} />
</IconButton>
) : (
<Button
size={"small"}
variant="contained"
color="primary"
startIcon={<AddCircle />}
onClick={handleOpen}
>
ثبت آیتم
</Button>
)}
<CreateForm open={open} setOpen={setOpen} mutate={mutate} />
</>
);
};
export default CreateDamage;

View File

@@ -0,0 +1,84 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_RECEIPT_DAMAGE_ITEMS_LIST } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useMemo } from "react";
import Toolbar from "./Toolbar";
import RowActions from "./RowActions";
const DamagesList = () => {
const columns = useMemo(
() => [
{
accessorKey: "id",
header: "کد یکتا",
id: "id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "title",
header: "عنوان",
id: "title",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "unit",
header: "واحد",
id: "unit",
enableColumnFilter: false,
grow: false,
size: 100,
},
{
accessorKey: "base_price",
header: "فی (ریال)",
id: "base_price",
enableColumnFilter: false,
grow: false,
size: 100,
Cell: ({ renderedCellValue }) => (renderedCellValue / 1).toLocaleString(),
},
{
accessorKey: "status",
header: "وضعیت",
id: "status",
enableColumnFilter: false,
grow: false,
size: 100,
Cell: ({ renderedCellValue }) => (renderedCellValue == 1 ? "فعال" : "غیرفعال"),
},
],
[]
);
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
need_filter={true}
columns={columns}
sorting={[{ id: "id", desc: true }]}
table_url={GET_RECEIPT_DAMAGE_ITEMS_LIST}
page_name={"receiptDamanagePage"}
table_name={"damanageList"}
TableToolbar={Toolbar}
enableRowActions
positionActionsColumn={"last"}
RowActions={RowActions}
/>
</Box>
</>
);
};
export default DamagesList;

View File

@@ -0,0 +1,31 @@
import { ATIVITY_RECEIPT_DAMAGE_ITEMS } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { Switch } from "@mui/material";
import { useState } from "react";
const Activity = ({ rowId, mutate, status }) => {
const [disabled, setDisabled] = useState(false)
const [checked, setChecked] = useState(status == 1);
const requestServer = useRequest();
const handleSwitch = (event) => {
const newChecked = event.target.checked;
setChecked(newChecked);
setDisabled(true)
requestServer(`${ATIVITY_RECEIPT_DAMAGE_ITEMS}/${rowId}`, "post")
.then(() => {
mutate();
})
.catch(() => {
setChecked(!newChecked);
})
.finally(() => {
setDisabled(false)
})
};
return <Switch size="small" disabled={disabled} checked={checked} onChange={handleSwitch} />;
};
export default Activity;

View File

@@ -0,0 +1,145 @@
import NumberField from "@/core/components/NumberField";
import StyledForm from "@/core/components/StyledForm";
import { UPDATE_RECEIPT_DAMAGE_ITEMS } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { yupResolver } from "@hookform/resolvers/yup";
import { Beenhere, ExitToApp } from "@mui/icons-material";
import { Button, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material";
import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog";
import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup";
const validationSchema = object({
title: string().required("وارد کردن عنوان الزامیست!"),
unit: string().required("وارد کردن واحد الزامیست!"),
base_price: string().required("وارد کردن فی الزامیست!"),
});
const EditFormContent = ({ row, rowId, mutate, setOpen }) => {
const requestServer = useRequest({ notificationSuccess: true });
const defaultValues = {
title: row.original.title,
unit: row.original.unit,
base_price: row.original.base_price,
};
const {
control,
handleSubmit,
formState: { isSubmitting },
} = useForm({
defaultValues,
resolver: yupResolver(validationSchema),
mode: "onBlur",
});
const onSubmit = async (data) => {
const formData = new FormData();
formData.append("title", data.title);
formData.append("unit", data.unit);
formData.append("base_price", data.base_price);
try {
await requestServer(`${UPDATE_RECEIPT_DAMAGE_ITEMS}/${rowId}`, "post", {
data: formData,
});
mutate();
setOpen(false);
} catch (error) {}
};
return (
<>
<DialogHeader>
<DialogTitle>ویرایش آیتم</DialogTitle>
</DialogHeader>
<DialogContent dividers>
<StyledForm onSubmit={handleSubmit(onSubmit)} id="updateDamageForm">
<Stack spacing={3}>
<Controller
name="title"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
fullWidth
size="small"
label="عنوان"
value={value}
onChange={onChange}
placeholder="عنوان را وارد کنید"
error={!!error}
helperText={error ? error.message : null}
variant="outlined"
InputLabelProps={{ shrink: true }}
/>
)}
/>
<Controller
name="unit"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
fullWidth
size="small"
label="واحد"
value={value}
onChange={onChange}
error={!!error}
placeholder="واحد را وارد کنید"
helperText={error ? error.message : null}
variant="outlined"
InputLabelProps={{ shrink: true }}
/>
)}
/>
<Controller
name="base_price"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<NumberField
label={`فی`}
error={!!error}
value={value || ""}
type="text"
size={"small"}
unit={"ریال"}
inputProps={{
placeholder: "فی را وارد کنید",
inputMode: "number",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
if (!isNaN(event.target.value)) {
onChange(event.target.value);
} else {
onChange(value);
}
}}
helperText={error ? error.message : null}
variant="outlined"
fullWidth
/>
)}
/>
</Stack>
</StyledForm>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)} variant="outlined" color="secondary" startIcon={<ExitToApp />}>
بستن
</Button>
<Button
variant="contained"
disabled={isSubmitting}
type={"submit"}
form="updateDamageForm"
endIcon={<Beenhere />}
>
{isSubmitting ? "در حال ویرایش آیتم" : "ویرایش آیتم"}
</Button>
</DialogActions>
</>
);
};
export default EditFormContent;

View File

@@ -0,0 +1,30 @@
import BorderColorIcon from "@mui/icons-material/BorderColor";
import { Dialog, IconButton, Tooltip } from "@mui/material";
import EditFormContent from "./Form";
import { useState } from "react";
const Edit = ({ row, mutate, rowId }) => {
const [openEditDialog, setOpenEditDialog] = useState(false);
return (
<>
<Tooltip title="ویرایش" arrow placement="right">
<IconButton
color="primary"
sx={{ textTransform: "unset", alignSelf: "center" }}
onClick={() => {
setOpenEditDialog(true);
}}
>
<BorderColorIcon />
</IconButton>
</Tooltip>
<Dialog open={openEditDialog} fullWidth maxWidth="xs">
{openEditDialog && (
<EditFormContent rowId={rowId} row={row} setOpen={setOpenEditDialog} mutate={mutate} />
)}
</Dialog>
</>
);
};
export default Edit;

View File

@@ -0,0 +1,13 @@
import { Box } from "@mui/material";
import Activity from "./Activity";
import Edit from "./Edit";
const RowActions = ({ row, mutate }) => {
return (
<Box sx={{ display: "flex", gap: 1, alignItems: 'center' }}>
<Edit row={row} mutate={mutate} rowId={row.getValue("id")} />
<Activity mutate={mutate} rowId={row.getValue("id")} status={row.original.status} />
</Box>
);
};
export default RowActions;

View File

@@ -0,0 +1,10 @@
import CreateDamage from "./Actions/Create";
const Toolbar = ({ mutate }) => {
return (
<>
<CreateDamage mutate={mutate} />
</>
);
};
export default Toolbar;

View File

@@ -0,0 +1,13 @@
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import DamagesList from "./DamagesList";
const DamagesPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"آیتم خسارات"} />
<DamagesList />
</Stack>
);
};
export default DamagesPage;

View File

@@ -4,7 +4,7 @@ import CreateFormContent from "@/components/dashboard/roadItems/operator/Actions
import { CREATE_ROAD_ITEMS } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
const OperatorCreateForm = ({ open, setOpen, mutate, rowId }) => {
const OperatorCreateForm = ({ open, setOpen, mutate }) => {
const requestServer = useRequest({ notificationSuccess: true });
const HandleSubmit = async ({ result }) => {
const formData = new FormData();

View File

@@ -1,8 +1,7 @@
import React, { useState } from "react";
import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
import { useState } from "react";
import DeleteContent from "./DeleteContent";
import useRequest from "@/lib/hooks/useRequest";
const DeleteForm = ({ rowId, mutate }) => {
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);

View File

@@ -473,6 +473,14 @@ export const pageMenu = [
},
],
},
{
id: "damageItems",
label: "آیتم خسارات",
type: "page",
route: "/dashboard/receipt/damages",
icon: <GppMaybeIcon sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["manage-damage"],
},
{
id: "receiptManagmentReport",
label: "گزارش ها",

View File

@@ -76,5 +76,11 @@ export const GET_CITY_ACTIVITY_PER_ITEM = api + "/api/v3/road_item_reports/provi
export const GET_PROVINCE_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/country_activity_per_sub_item";
export const GET_CITY_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/province_activity_per_sub_item";
// damage items
export const GET_RECEIPT_DAMAGE_ITEMS_LIST = api + "/api/v3/damages";
export const CREATE_RECEIPT_DAMAGE_ITEMS = api + "/api/v3/damages";
export const UPDATE_RECEIPT_DAMAGE_ITEMS = api + "/api/v3/damages";
export const ATIVITY_RECEIPT_DAMAGE_ITEMS = api + "/api/v3/damages/activate";
// activity code log
export const ACTIVITY_LOG = api + "/api/v3/profile/add_activity";