push rasad front on new repo
This commit is contained in:
572
src/features/ticket/components/create-ticket/CreateTicket.js
Normal file
572
src/features/ticket/components/create-ticket/CreateTicket.js
Normal file
@@ -0,0 +1,572 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import useUserProfile from "../../../authentication/hooks/useUserProfile";
|
||||
import { useFormik } from "formik";
|
||||
import { Yup } from "../../../../lib/yup/yup";
|
||||
import { fixBase64 } from "../../../../utils/toBase64";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
// Paper,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
Chip,
|
||||
Box,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
// import LaunchIcon from "@mui/icons-material/Launch";
|
||||
// import SettingsSuggestIcon from "@mui/icons-material/SettingsSuggest";
|
||||
// import MarkAsUnreadIcon from "@mui/icons-material/MarkAsUnread";
|
||||
import { ImageUpload } from "../../../../components/image-upload/ImageUpload";
|
||||
import { getFaUserRole } from "../../../../utils/getFaUserRole";
|
||||
import { useDispatch } from "react-redux";
|
||||
import {
|
||||
getTicketPermission,
|
||||
getTicketUsersFromRole,
|
||||
} from "../../services/get-ticket-permission";
|
||||
import { AppContext } from "../../../../contexts/AppContext";
|
||||
import { CreateTicketService } from "../../services/create-ticket";
|
||||
import { sendResponseTicketService } from "../../services/send-response";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
// import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
const VisuallyHiddenInput = styled("input")({
|
||||
clip: "rect(0 0 0 0)",
|
||||
clipPath: "inset(50%)",
|
||||
height: 1,
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: "nowrap",
|
||||
width: 1,
|
||||
});
|
||||
|
||||
export const CreateTicket = ({ id, getMessages, fetchMessages }) => {
|
||||
// const ticketTypes = [
|
||||
// {
|
||||
// title: "درخواست از مدیر",
|
||||
// icon: <LaunchIcon fontSize="40%" />,
|
||||
// color: "#797979",
|
||||
// },
|
||||
// {
|
||||
// title: "مشکلات فنی",
|
||||
// icon: <SettingsSuggestIcon fontSize="40%" />,
|
||||
// color: "#797979",
|
||||
// },
|
||||
// {
|
||||
// title: "پیشنهاد و انتقاد",
|
||||
// icon: <MarkAsUnreadIcon fontSize="40%" />,
|
||||
// color: "#797979",
|
||||
// },
|
||||
// ];
|
||||
|
||||
const isAdmin = () => {
|
||||
if (
|
||||
selectedRole === "CityOperator" ||
|
||||
selectedRole === "ProvinceOperator" ||
|
||||
selectedRole === "AdminX" ||
|
||||
selectedRole === "Supporter" ||
|
||||
selectedRole === "SuperAdmin"
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const [role] = useUserProfile();
|
||||
const [selectedRole, setSelectedRole] = useState(role[0]);
|
||||
const [value, setValue] = useState(isAdmin() ? "toRole" : "toUser");
|
||||
const [openNotif] = useContext(AppContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// const handleChangeRadioButton = (event) => {
|
||||
// formik.setFieldValue("roles", []);
|
||||
// setValue(event.target.value);
|
||||
// };
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const handleRoleChange = (event) => {
|
||||
setSelectedRole(event.target.value);
|
||||
};
|
||||
|
||||
const [isChecked, setChecked] = useState(false);
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
setChecked(!isChecked);
|
||||
};
|
||||
|
||||
const handleFileUpload = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
formik.setFieldValue("uploadedFile", file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteFile = () => {
|
||||
formik.setFieldValue("uploadedFile", null);
|
||||
};
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
title: "",
|
||||
text: "",
|
||||
users: [],
|
||||
roles: [],
|
||||
image: "",
|
||||
uploadedFile: null,
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
title: Yup.string().required("عنوان تیکت ضروری است"),
|
||||
text: Yup.string().required("متن تیکت ضروری است"),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
// console.log(values);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
formik.validateForm();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAdmin()) {
|
||||
setValue("toUser");
|
||||
}
|
||||
}, [value, selectedRole]);
|
||||
|
||||
const [profileImages, setProfileImages] = useState([]);
|
||||
|
||||
const factorPaymentHandler = (imageList, addUpdateIndex) => {
|
||||
if (imageList[0]) {
|
||||
formik.setFieldValue("image", fixBase64(imageList[0]?.data_url));
|
||||
}
|
||||
setProfileImages(imageList);
|
||||
};
|
||||
|
||||
const [permissionList, setPermissionList] = useState([]);
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getTicketPermission({ role: selectedRole })).then((r) => {
|
||||
setPermissionList(r.payload.data);
|
||||
});
|
||||
}, [selectedRole]);
|
||||
useEffect(() => {
|
||||
if (formik.values.roles.length && value === "toUser") {
|
||||
dispatch(getTicketUsersFromRole({ role: formik.values.roles })).then(
|
||||
(r) => {
|
||||
setUsers(r.payload.data);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
setUsers([]);
|
||||
}
|
||||
}, [formik.values.roles, value]);
|
||||
|
||||
const handleCheckboxChangeToRole = (event) => {
|
||||
if (event.target.checked) {
|
||||
setValue("toRole");
|
||||
formik.setFieldValue("users", []);
|
||||
formik.setFieldValue("roles", []);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckboxChangeToUser = (event) => {
|
||||
if (event.target.checked) {
|
||||
setValue("toUser");
|
||||
formik.setFieldValue("roles", []);
|
||||
formik.setFieldValue("users", []);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
gap={2}
|
||||
width="100%"
|
||||
>
|
||||
{/* <Grid item xs={12} container justifyContent="center">
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
gap={2}
|
||||
mb={3}
|
||||
sx={{
|
||||
"& > *": { width: "100px", height: "120px", flex: "0 0 auto" },
|
||||
}}
|
||||
>
|
||||
{ticketTypes.map((item) => (
|
||||
<Paper
|
||||
key={item.title}
|
||||
sx={{
|
||||
p: 3,
|
||||
textAlign: "center",
|
||||
cursor: "pointer",
|
||||
|
||||
borderRadius: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
|
||||
minHeight: 150,
|
||||
transition: "all 0.3s ease",
|
||||
"&:hover": {
|
||||
transform: "translateY(-5px)",
|
||||
boxShadow: 4,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
color: item.color,
|
||||
mb: 2,
|
||||
fontSize: "2.5rem",
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</Box>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ fontWeight: "medium", fontSize: "12px" }}
|
||||
>
|
||||
{item.title}
|
||||
</Typography>
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
</Grid> */}
|
||||
{isNaN(id) && (
|
||||
<Grid container justifyContent="center" xs={12}>
|
||||
<Grid item xs={12} container justifyContent="center">
|
||||
<Stack direction="row" spacing={4}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={value === "toRole"}
|
||||
onChange={handleCheckboxChangeToRole}
|
||||
disabled={!isAdmin()}
|
||||
/>
|
||||
}
|
||||
label="ارسال به نقش"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={value === "toUser"}
|
||||
onChange={handleCheckboxChangeToUser}
|
||||
/>
|
||||
}
|
||||
label="ارسال به اشخاص"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} container justifyContent="center">
|
||||
{permissionList?.roles?.length &&
|
||||
(value === "toRole" ? isAdmin() : true) ? (
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="role-select-label">انتخاب نقش</InputLabel>
|
||||
<Select
|
||||
multiple={value !== "toUser"}
|
||||
labelId="role-select-label"
|
||||
value={formik.values.roles}
|
||||
onChange={(event) => {
|
||||
formik.setFieldValue("roles", event.target.value);
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
{permissionList?.roles.map((roleItem, i) => (
|
||||
<MenuItem key={i} value={roleItem}>
|
||||
{getFaUserRole(roleItem)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body1" color="error">
|
||||
نقش انتخابی اجازه ارسال تیکت ندارد!
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
{value === "toUser" && (
|
||||
<>
|
||||
{users?.length ? (
|
||||
<Grid item xs={12} mt={2} v>
|
||||
<Autocomplete
|
||||
multiple
|
||||
id="tags-standard"
|
||||
options={users}
|
||||
getOptionLabel={(option) => option.fullname}
|
||||
onChange={(event, value) => {
|
||||
formik.setFieldValue("users", value);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
variant="outlined"
|
||||
label="انتخاب کاربر"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography color="error" variant="body2">
|
||||
موردی یافت نشد!
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
{role.length > 1 && isNaN(id) && (
|
||||
<Grid item xs={12} container justifyContent="center">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="role-select-label">انتخاب نقش</InputLabel>
|
||||
<Select
|
||||
labelId="role-select-label"
|
||||
value={selectedRole}
|
||||
onChange={handleRoleChange}
|
||||
>
|
||||
{role.map((roleItem, i) => (
|
||||
<MenuItem key={i} value={roleItem}>
|
||||
{getFaUserRole(roleItem)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{isNaN(id) && (
|
||||
<Grid item xs={12} container justifyContent="center">
|
||||
<TextField
|
||||
id="title"
|
||||
name="title"
|
||||
label="موضوع"
|
||||
value={formik.values.title}
|
||||
onChange={formik.handleChange}
|
||||
error={formik.touched.title && Boolean(formik.errors.title)}
|
||||
helperText={formik.touched.title && formik.errors.title}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} container justifyContent="center">
|
||||
<TextField
|
||||
fullWidth
|
||||
id="text"
|
||||
name="text"
|
||||
label="پیام خود را وارد کنید"
|
||||
multiline
|
||||
rows={4}
|
||||
value={formik.values.text}
|
||||
onChange={formik.handleChange}
|
||||
error={formik.touched.text && Boolean(formik.errors.text)}
|
||||
helperText={formik.touched.text && formik.errors.text}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} mt={2}>
|
||||
<Button
|
||||
component="label"
|
||||
variant="outlined"
|
||||
startIcon={<CloudUploadIcon />}
|
||||
sx={{ mb: 1 }}
|
||||
>
|
||||
پیوست فایل
|
||||
<VisuallyHiddenInput type="file" onChange={handleFileUpload} />
|
||||
</Button>
|
||||
{formik.values.uploadedFile && (
|
||||
<Box display="flex" alignItems="center" mt={1}>
|
||||
<Chip
|
||||
label={formik.values.uploadedFile.name}
|
||||
onDelete={handleDeleteFile}
|
||||
deleteIcon={<DeleteIcon />}
|
||||
variant="outlined"
|
||||
/>
|
||||
<Typography variant="caption" ml={1}>
|
||||
حجم: {(formik.values.uploadedFile.size / 1024 / 1024).toFixed(2)}{" "}
|
||||
MB
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{formik.values.uploadedFile?.size > 5 * 1024 * 1024 && (
|
||||
<Typography color="error" variant="body2">
|
||||
حداکثر حجم مجاز جهت ارسال فایل 5 مگابایت است!
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{isAdmin() && isNaN(id) && (
|
||||
<Grid container xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
disabled={
|
||||
value === "toUser" &&
|
||||
Array.isArray(formik.values.users) &&
|
||||
formik.values.users.length === 1
|
||||
}
|
||||
size="small"
|
||||
checked={isChecked}
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
}
|
||||
label="فقط خواندنی"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid
|
||||
container
|
||||
justifyContent="center"
|
||||
style={{ marginTop: "16px" }}
|
||||
gap={2}
|
||||
>
|
||||
<ImageUpload
|
||||
onChange={factorPaymentHandler}
|
||||
images={profileImages}
|
||||
maxNumber={1}
|
||||
title={"ارسال تصویر"}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Button
|
||||
disabled={
|
||||
formik.values.uploadedFile?.size > 5 * 1024 * 1024 ||
|
||||
(!isNaN(id)
|
||||
? !formik.values.text
|
||||
: value === "toUser"
|
||||
? !formik.isValid || !formik.values.users.length
|
||||
: !formik.isValid || !formik.values.roles.length)
|
||||
}
|
||||
onClick={() => {
|
||||
if (!isNaN(id)) {
|
||||
const formData = new FormData();
|
||||
formData.append("message", formik.values.text);
|
||||
formData.append("sender", isAdmin() ? "user" : "admin");
|
||||
formData.append("send_message", false);
|
||||
formData.append("ticket", id);
|
||||
if (formik.values.image) {
|
||||
formData.append("image", formik.values.image);
|
||||
}
|
||||
|
||||
if (formik.values.uploadedFile) {
|
||||
formData.append("file", formik.values.uploadedFile);
|
||||
}
|
||||
|
||||
dispatch(sendResponseTicketService(formData)).then((r) => {
|
||||
if (r.payload.error) {
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: r.payload.data.result,
|
||||
severity: "error",
|
||||
});
|
||||
} else {
|
||||
formik.resetForm();
|
||||
setProfileImages([]);
|
||||
fetchMessages();
|
||||
getMessages();
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "عملیات با موفقیت انجام شد.",
|
||||
severity: "success",
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (value === "toUser") {
|
||||
dispatch(
|
||||
CreateTicketService({
|
||||
type_ticket:
|
||||
formik.values.users?.length === 1 ? "single" : "public",
|
||||
to_user: formik.values.users.map((item) => {
|
||||
return item?.key;
|
||||
}),
|
||||
image: formik.values.image ? formik.values.image : null,
|
||||
title: formik.values.title,
|
||||
sender: isAdmin() ? "user" : "admin",
|
||||
message: formik.values.text,
|
||||
read_only:
|
||||
formik.values.users?.length === 1 ? false : isChecked,
|
||||
role: selectedRole,
|
||||
})
|
||||
).then((r) => {
|
||||
if (r.payload.error) {
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: r.payload.data.result,
|
||||
severity: "error",
|
||||
});
|
||||
} else {
|
||||
formik.resetForm();
|
||||
navigate(-1);
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "عملیات با موفقیت انجام شد.",
|
||||
severity: "success",
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dispatch(
|
||||
CreateTicketService({
|
||||
type_ticket: "public",
|
||||
to_role: formik.values.roles,
|
||||
image: formik.values.image ? formik.values.image : null,
|
||||
title: formik.values.title,
|
||||
sender: isAdmin() ? "user" : "admin",
|
||||
message: formik.values.text,
|
||||
read_only: isChecked,
|
||||
role: selectedRole,
|
||||
})
|
||||
).then((r) => {
|
||||
if (r.payload.error) {
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: r.payload.error,
|
||||
severity: "error",
|
||||
});
|
||||
} else {
|
||||
formik.resetForm();
|
||||
navigate(-1);
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "عملیات با موفقیت انجام شد.",
|
||||
severity: "success",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
type="submit"
|
||||
style={{ marginTop: "16px" }}
|
||||
>
|
||||
ارسال
|
||||
</Button>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
168
src/features/ticket/components/show-messages/ShowMissages.js
Normal file
168
src/features/ticket/components/show-messages/ShowMissages.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import React from "react";
|
||||
import { Button, Divider, Typography } from "@mui/material";
|
||||
import Tooltip, { tooltipClasses } from "@mui/material/Tooltip";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import DoneAllIcon from "@mui/icons-material/DoneAll";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import ShowImage from "../../../../components/show-image/ShowImage";
|
||||
import persianDate from "persian-date";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { styled } from "@mui/material/styles";
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
visible: { opacity: 1, y: 0, transition: { duration: 0.3 } },
|
||||
};
|
||||
|
||||
const HtmlTooltip = styled(({ className, ...props }) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: "#f5f5f9",
|
||||
color: "rgba(0, 0, 0, 0.87)",
|
||||
maxWidth: 220,
|
||||
fontSize: theme.typography.pxToRem(12),
|
||||
border: "1px solid #dadde9",
|
||||
},
|
||||
}));
|
||||
|
||||
const formatMessage = (message) => {
|
||||
if (!message) return "";
|
||||
|
||||
const formattedText = message.replace(/\*\*\*/g, "\n").trim();
|
||||
|
||||
const lines = formattedText.split("\n");
|
||||
|
||||
return lines.map((line, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{line}
|
||||
<br />
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
|
||||
export const ShowMissages = ({ data }) => {
|
||||
const isReffered = (item) => {
|
||||
if (
|
||||
item?.message?.includes("ارجاع داده شد.") &&
|
||||
item?.message?.includes("تیکت شماره")
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container gap={2}>
|
||||
{data?.map((item, i) => (
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
key={i}
|
||||
component={motion.div}
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
sx={{
|
||||
border: "1px ridge gray",
|
||||
borderRadius: "10px",
|
||||
p: 2,
|
||||
backgroundColor: isReffered(item) ? "#e7b2b2" : "background.paper",
|
||||
boxShadow: 4,
|
||||
}}
|
||||
>
|
||||
<Grid container justifyContent="space-between" alignItems="center">
|
||||
<Typography color="text.secondary">
|
||||
{item?.createdBy?.fullname}
|
||||
</Typography>
|
||||
<Grid
|
||||
container
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
<Typography color="text.secondary">
|
||||
{`${new persianDate(new Date(item?.createdAt)).format(
|
||||
"dddd DD MMMM"
|
||||
)} (${new Date(item?.createdAt).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
})})`}
|
||||
</Typography>
|
||||
{item?.lastSeen ? (
|
||||
<HtmlTooltip
|
||||
disableHoverListener={!item?.readBy}
|
||||
title={
|
||||
<Grid container xs={12} direction="column">
|
||||
<Typography variant="body1" color="primary">
|
||||
بازدید شده توسط
|
||||
</Typography>
|
||||
{item?.readBy?.map((item, i) => (
|
||||
<Typography variant="body2" key={i}>
|
||||
{item?.fullname} ({item?.mobile})
|
||||
</Typography>
|
||||
))}
|
||||
</Grid>
|
||||
}
|
||||
>
|
||||
<DoneAllIcon sx={{ marginLeft: "10px" }} color="primary" />
|
||||
</HtmlTooltip>
|
||||
) : (
|
||||
<CheckIcon sx={{ marginLeft: "10px" }} color="error" />
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Divider sx={{ my: 1 }} />
|
||||
|
||||
<Typography
|
||||
color={isReffered(item) ? "#202077" : "black"}
|
||||
mt={1}
|
||||
sx={{
|
||||
textAlign: "left",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{formatMessage(item?.message)}
|
||||
</Typography>
|
||||
|
||||
{(item?.picture || item?.file) && (
|
||||
<>
|
||||
<Divider sx={{ width: "100%", my: 2 }} />
|
||||
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
mt={2}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
>
|
||||
{item?.picture && (
|
||||
<ShowImage src={item?.picture} size="100px" />
|
||||
)}
|
||||
|
||||
{item?.file && (
|
||||
<Button
|
||||
color="success"
|
||||
onClick={() => {
|
||||
const link = item?.file;
|
||||
window.location.href = link;
|
||||
}}
|
||||
endIcon={<DownloadIcon />}
|
||||
>
|
||||
دانلود فایل پیوست
|
||||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import useUserProfile from "../../../authentication/hooks/useUserProfile";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { getFaUserRole } from "../../../../utils/getFaUserRole";
|
||||
import { Yup } from "../../../../lib/yup/yup";
|
||||
import { useFormik } from "formik";
|
||||
import {
|
||||
getTicketPermission,
|
||||
getTicketUsersFromRole,
|
||||
} from "../../services/get-ticket-permission";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { EditTicketService } from "../../services/create-ticket";
|
||||
import { AppContext } from "../../../../contexts/AppContext";
|
||||
import { CLOSE_MODAL } from "../../../../lib/redux/slices/appSlice";
|
||||
import { sortRoles } from "../../../../utils/sortRoles";
|
||||
|
||||
export const SubmitRefferTicket = ({ fetchMessages, ticket }) => {
|
||||
const [role] = useUserProfile();
|
||||
const [openNotif] = useContext(AppContext);
|
||||
const [users, setUsers] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
users: "",
|
||||
roles: [],
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
users: Yup.array().required("حداقل یک کاربر انتخاب کنید!"),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
// console.log(values);
|
||||
},
|
||||
});
|
||||
|
||||
const [permissionList, setPermissionList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getTicketPermission({ role: sortRoles(role)[0] })).then((r) => {
|
||||
setPermissionList(r.payload.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (formik.values.roles.length) {
|
||||
dispatch(getTicketUsersFromRole({ role: formik.values.roles })).then(
|
||||
(r) => {
|
||||
setUsers(r.payload.data);
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [formik.values.roles]);
|
||||
|
||||
useEffect(() => {
|
||||
formik.validateForm();
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
xs={12}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
direction="column"
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
{permissionList?.roles?.length ? (
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="role-select-label">انتخاب نقش</InputLabel>
|
||||
<Select
|
||||
labelId="role-select-label"
|
||||
value={formik.values.roles}
|
||||
onChange={(event) => {
|
||||
formik.setFieldValue("roles", event.target.value);
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
{permissionList?.roles.map((roleItem, i) => (
|
||||
<MenuItem key={i} value={roleItem}>
|
||||
{getFaUserRole(roleItem)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography variant="body1" color="error">
|
||||
نقش انتخابی اجازه ارسال تیکت ندارد!
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<Grid xs={12}>
|
||||
{users?.length ? (
|
||||
<Grid item xs={12}>
|
||||
<Autocomplete
|
||||
multiple
|
||||
id="tags-standard"
|
||||
options={users}
|
||||
getOptionLabel={(option) =>
|
||||
`${option.fullname || "-"} (${option.mobile})`
|
||||
}
|
||||
onChange={(event, value) => {
|
||||
formik.setFieldValue("users", value);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
variant="outlined"
|
||||
label="انتخاب کاربر"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
) : (
|
||||
<Typography color="error" variant="body2">
|
||||
موردی یافت نشد!
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
<Button
|
||||
disabled={!formik.isValid}
|
||||
fullWidth
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
EditTicketService({
|
||||
ticket: ticket,
|
||||
referred_to: formik.values.users.map((item) => {
|
||||
return item?.key;
|
||||
}),
|
||||
})
|
||||
).then((r) => {
|
||||
if (r.payload.error) {
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: r.payload.error,
|
||||
severity: "error",
|
||||
});
|
||||
} else {
|
||||
fetchMessages();
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "عملیات با موفقیت انجام شد.",
|
||||
severity: "success",
|
||||
});
|
||||
dispatch(CLOSE_MODAL());
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
ارجاع
|
||||
</Button>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import React, { useState } from "react";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import { Tab, Tabs } from "@mui/material";
|
||||
import { getRoleFromUrl } from "../../../../utils/getRoleFromUrl";
|
||||
|
||||
export const TicketAllView = () => {
|
||||
const [value, setValue] = useState("0");
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
return (
|
||||
<Grid container xs={12} justifyContent="center" alignItems="center">
|
||||
{(getRoleFromUrl() === "AdminX" ||
|
||||
getRoleFromUrl() === "SuperAdmin" ||
|
||||
getRoleFromUrl() === "ProvinceOperator") && (
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
aria-label="secondary tabs example"
|
||||
>
|
||||
<Tab value="0" label="ارسال تکی" />
|
||||
<Tab value="1" label="ارسال یکجا" />
|
||||
</Tabs>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
166
src/features/ticket/components/ticket-create/TicketCreate.js
Normal file
166
src/features/ticket/components/ticket-create/TicketCreate.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useFormik } from "formik";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import { ImageUpload } from "../../../../components/image-upload/ImageUpload";
|
||||
import { AppContext } from "../../../../contexts/AppContext";
|
||||
import { SPACING } from "../../../../data/spacing";
|
||||
import {
|
||||
DRAWER,
|
||||
LOADING_END,
|
||||
LOADING_START,
|
||||
} from "../../../../lib/redux/slices/appSlice";
|
||||
import { Yup } from "../../../../lib/yup/yup";
|
||||
import { fixBase64 } from "../../../../utils/toBase64";
|
||||
import { ticketCreateTicket } from "../../services/ticket-create-ticket";
|
||||
import { ticketGetCreatedTickets } from "../../services/ticket-get-created-tickets";
|
||||
|
||||
export const TicketCreate = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [openNotif] = useContext(AppContext);
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
title: "",
|
||||
supportUnit: "",
|
||||
content: "",
|
||||
image: [],
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
title: Yup.string().required("این فیلد اجباری است!"),
|
||||
supportUnit: Yup.string().required("این فیلد اجباری است!"),
|
||||
content: Yup.string().required("این فیلد اجباری است!"),
|
||||
}),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
formik.validateForm();
|
||||
}, []);
|
||||
|
||||
const [attachmentImages, setAttachmentImages] = useState([]);
|
||||
|
||||
const attachmentsHandler = (imageList, addUpdateIndex) => {
|
||||
const base64Urls = imageList.map((image) => fixBase64(image.data_url));
|
||||
formik.setFieldValue("image", base64Urls);
|
||||
setAttachmentImages(imageList);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container justifyContent="space-between">
|
||||
<Grid container flexGrow="1" gap={SPACING.SMALL} direction="column">
|
||||
<Grid>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="title"
|
||||
label="موضوع"
|
||||
value={formik.values.title}
|
||||
error={formik.touched.title ? Boolean(formik.errors.title) : null}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
helperText={
|
||||
formik.touched.title && Boolean(formik.errors.title)
|
||||
? formik.errors.title
|
||||
: null
|
||||
}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="demo-simple-select-label">واحد مربوطه</InputLabel>
|
||||
<Select
|
||||
fullWidth
|
||||
value={formik.values.supportUnit}
|
||||
id="supportUnit"
|
||||
label="واحد مربوطه"
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue("supportUnit", e.target.value);
|
||||
}}
|
||||
>
|
||||
<MenuItem value={"CityOperator"}>شهرستان</MenuItem>
|
||||
<MenuItem value={"ProvinceOperator"}>تخصیص استان</MenuItem>
|
||||
<MenuItem value={"ProvinceInspector"}>بازرسی استان</MenuItem>
|
||||
<MenuItem value={"ProvinceFinancial"}>واحد مالی استان</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
id="content"
|
||||
label="توضیحات"
|
||||
multiline
|
||||
rows={5}
|
||||
variant="outlined"
|
||||
sx={{ width: "100%", height: "100%" }}
|
||||
value={formik.values.content}
|
||||
error={
|
||||
formik.touched.content ? Boolean(formik.errors.content) : null
|
||||
}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
helperText={
|
||||
formik.touched.content && Boolean(formik.errors.content)
|
||||
? formik.errors.content
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ImageUpload
|
||||
onChange={attachmentsHandler}
|
||||
images={attachmentImages}
|
||||
maxNumber={3}
|
||||
title={"افزودن پیوست"}
|
||||
/>
|
||||
</Grid>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
disabled={!formik.isValid}
|
||||
onClick={() => {
|
||||
dispatch(LOADING_START());
|
||||
dispatch(
|
||||
ticketCreateTicket({
|
||||
title: formik.values.title,
|
||||
content: formik.values.content,
|
||||
support_unit: formik.values.supportUnit,
|
||||
image: formik.values.image,
|
||||
})
|
||||
).then((r) => {
|
||||
dispatch(LOADING_END());
|
||||
|
||||
if (r.error) {
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "مشکلی پیش آمده است!",
|
||||
severity: "error",
|
||||
});
|
||||
} else {
|
||||
dispatch(ticketGetCreatedTickets());
|
||||
dispatch(
|
||||
DRAWER({ right: false, bottom: false, content: null })
|
||||
);
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "عملیات با موفقیت انجام شد.",
|
||||
severity: "success",
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
ارسال تیکت
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
TimelineConnector,
|
||||
TimelineContent,
|
||||
TimelineDot,
|
||||
TimelineItem,
|
||||
TimelineSeparator,
|
||||
} from "@mui/lab";
|
||||
import { Typography } from "@mui/material";
|
||||
import { PropTypes } from "prop-types";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import { SPACING } from "../../../../data/spacing";
|
||||
import { formatTime } from "../../../../utils/formatTime";
|
||||
import QuestionMarkIcon from "@mui/icons-material/QuestionMark";
|
||||
|
||||
export const TicketCustomerItem = ({ item }) => {
|
||||
return (
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot color="warning">
|
||||
<QuestionMarkIcon fontSize="small" />
|
||||
</TimelineDot>
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<Grid container direction="column" mt={1}>
|
||||
<TimelineContent>
|
||||
<Grid container gap={SPACING.TINY}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant="body1">-</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color={(prop) => prop.palette.grey["A700"]}
|
||||
>
|
||||
{formatTime(item.createDate)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</TimelineContent>
|
||||
<TimelineContent>
|
||||
<Grid container direction="column" gap={SPACING.SMALL}>
|
||||
<Grid>
|
||||
<Typography varinat="body2">{item.content}</Typography>
|
||||
</Grid>
|
||||
{Boolean(item.image?.length) && (
|
||||
<>
|
||||
<Grid>
|
||||
<Typography
|
||||
varinat="body1"
|
||||
color={(prop) => prop.palette.grey["A700"]}
|
||||
>
|
||||
پیوست ها
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container gap={SPACING.SMALL}>
|
||||
{item.image.map((img, i) => {
|
||||
return (
|
||||
<a key={"ticket-img" + i} href={img}>
|
||||
<img
|
||||
src={img}
|
||||
width="100"
|
||||
alt="ticket"
|
||||
style={{ borderRadius: "10px" }}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</TimelineContent>
|
||||
</Grid>
|
||||
</TimelineItem>
|
||||
);
|
||||
};
|
||||
|
||||
TicketCustomerItem.propTypes = {
|
||||
item: PropTypes.any,
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
TimelineConnector,
|
||||
TimelineContent,
|
||||
TimelineDot,
|
||||
TimelineItem,
|
||||
TimelineSeparator,
|
||||
} from "@mui/lab";
|
||||
import { Typography } from "@mui/material";
|
||||
import { PropTypes } from "prop-types";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import { SPACING } from "../../../../data/spacing";
|
||||
import { formatTime } from "../../../../utils/formatTime";
|
||||
import SupportAgentIcon from "@mui/icons-material/SupportAgent";
|
||||
|
||||
export const TicketOperatorItem = ({ item }) => {
|
||||
return (
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot color="primary">
|
||||
<SupportAgentIcon fontSize="small" />
|
||||
</TimelineDot>
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<Grid container direction="column" mt={1}>
|
||||
<TimelineContent>
|
||||
<Grid container gap={SPACING.TINY}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant="body1">-</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color={(prop) => prop.palette.grey["A700"]}
|
||||
>
|
||||
{formatTime(item.createDate)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</TimelineContent>
|
||||
<TimelineContent>
|
||||
<Grid container direction="column" gap={SPACING.SMALL}>
|
||||
<Grid>
|
||||
<Typography varinat="body2">{item.content}</Typography>
|
||||
</Grid>
|
||||
{Boolean(item.image?.length) && (
|
||||
<>
|
||||
<Grid>
|
||||
<Typography
|
||||
varinat="body1"
|
||||
color={(prop) => prop.palette.grey["A700"]}
|
||||
>
|
||||
پیوست ها
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container gap={SPACING.SMALL}>
|
||||
{item.image.map((img, i) => {
|
||||
return (
|
||||
<a key={"ticket-img" + i} href={img}>
|
||||
<img
|
||||
src={img}
|
||||
width="100"
|
||||
alt="ticket"
|
||||
style={{ borderRadius: "10px" }}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</TimelineContent>
|
||||
</Grid>
|
||||
</TimelineItem>
|
||||
);
|
||||
};
|
||||
|
||||
TicketOperatorItem.propTypes = {
|
||||
item: PropTypes.any,
|
||||
};
|
||||
157
src/features/ticket/components/ticket-respond/TicketRespond.js
Normal file
157
src/features/ticket/components/ticket-respond/TicketRespond.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Button, TextField } from "@mui/material";
|
||||
import { useFormik } from "formik";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import { Yup } from "../../../../lib/yup/yup";
|
||||
import { PropTypes } from "prop-types";
|
||||
import { SPACING } from "../../../../data/spacing";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { fixBase64 } from "../../../../utils/toBase64";
|
||||
import {
|
||||
DRAWER,
|
||||
LOADING_END,
|
||||
LOADING_START,
|
||||
} from "../../../../lib/redux/slices/appSlice";
|
||||
import { ticketRespondTicket } from "../../services/ticket-respond-ticket";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { AppContext } from "../../../../contexts/AppContext";
|
||||
import { ImageUpload } from "../../../../components/image-upload/ImageUpload";
|
||||
import { ticketCreateTicket } from "../../services/ticket-create-ticket";
|
||||
import { ticketGetCreatedTickets } from "../../services/ticket-get-created-tickets";
|
||||
import { ticketGetOperatorTickets } from "../../services/ticket-get-operator-tickets";
|
||||
|
||||
export const TicketRespond = ({
|
||||
ticketKey,
|
||||
questionKey,
|
||||
customer,
|
||||
supportUnit,
|
||||
}) => {
|
||||
const [openNotif] = useContext(AppContext);
|
||||
const dispatch = useDispatch();
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
content: "",
|
||||
title: "",
|
||||
image: [],
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
content: Yup.string().required("این فیلد اجباری است!"),
|
||||
title: Yup.string().required("این فیلد اجباری است!"),
|
||||
}),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
formik.validateForm();
|
||||
}, []);
|
||||
|
||||
const [attachmentImages, setAttachmentImages] = useState([]);
|
||||
const attachmentsHandler = (imageList, addUpdateIndex) => {
|
||||
const base64Urls = imageList.map((image) => fixBase64(image.data_url));
|
||||
formik.setFieldValue("image", base64Urls);
|
||||
setAttachmentImages(imageList);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container direction="column" gap={SPACING.TINY}>
|
||||
<Grid>
|
||||
<TextField
|
||||
id="title"
|
||||
label="عنوان پاسخ"
|
||||
variant="outlined"
|
||||
value={formik.values.title}
|
||||
error={formik.touched.title ? Boolean(formik.errors.title) : null}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
helperText={
|
||||
formik.touched.title && Boolean(formik.errors.title)
|
||||
? formik.errors.title
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
multiline
|
||||
rows={4}
|
||||
fullWidth
|
||||
id="content"
|
||||
label="توضیحات"
|
||||
variant="outlined"
|
||||
value={formik.values.content}
|
||||
error={formik.touched.content ? Boolean(formik.errors.content) : null}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
helperText={
|
||||
formik.touched.content && Boolean(formik.errors.content)
|
||||
? formik.errors.content
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<ImageUpload
|
||||
onChange={attachmentsHandler}
|
||||
images={attachmentImages}
|
||||
maxNumber={3}
|
||||
title={"افزودن پیوست"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
disabled={!formik.isValid}
|
||||
onClick={() => {
|
||||
dispatch(LOADING_START());
|
||||
const respondType = customer
|
||||
? ticketCreateTicket({
|
||||
ticket_key: ticketKey,
|
||||
support_unit: supportUnit,
|
||||
title: formik.values.title,
|
||||
content: formik.values.content,
|
||||
image: formik.values.image,
|
||||
})
|
||||
: ticketRespondTicket({
|
||||
title: formik.values.title,
|
||||
content: formik.values.content,
|
||||
ticket_key: ticketKey,
|
||||
// question_key: questionKey,
|
||||
image: formik.values.image,
|
||||
});
|
||||
dispatch(respondType).then((r) => {
|
||||
dispatch(LOADING_END());
|
||||
if (r.error) {
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "مشکلی پیش آمده است!",
|
||||
severity: "error",
|
||||
});
|
||||
} else {
|
||||
dispatch(ticketGetCreatedTickets());
|
||||
dispatch(ticketGetOperatorTickets());
|
||||
dispatch(
|
||||
DRAWER({ right: false, bottom: false, content: null })
|
||||
);
|
||||
openNotif({
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
msg: "عملیات با موفقیت انجام شد.",
|
||||
severity: "success",
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
ثبت
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
TicketRespond.propTypes = {
|
||||
ticketKey: PropTypes.any,
|
||||
questionKey: PropTypes.any,
|
||||
customer: PropTypes.any,
|
||||
supportUnit: PropTypes.any,
|
||||
};
|
||||
187
src/features/ticket/components/ticket-view/TicketView.js
Normal file
187
src/features/ticket/components/ticket-view/TicketView.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Grid } from "../../../../components/grid/Grid";
|
||||
import { BackButton } from "../../../../components/back-button/BackButton";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { CreateTicket } from "../create-ticket/CreateTicket";
|
||||
import { ShowMissages } from "../show-messages/ShowMissages";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { getMessagesService } from "../../services/get-messages";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import { formatJustDate } from "../../../../utils/formatTime";
|
||||
import ForwardToInboxIcon from "@mui/icons-material/ForwardToInbox";
|
||||
import { OPEN_MODAL } from "../../../../lib/redux/slices/appSlice";
|
||||
import { SubmitRefferTicket } from "../submit-reffer-ticket/SubmitRefferTicket";
|
||||
|
||||
export const TicketView = () => {
|
||||
const { create, id } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const [data, setData] = useState();
|
||||
const [canSeeMessages, setCanSeeMessages] = useState();
|
||||
const fetchMessages = () => {
|
||||
dispatch(getMessagesService({ ticket: id })).then((r) => {
|
||||
setData(r.payload.data);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let intervalId;
|
||||
|
||||
if (create === "false") {
|
||||
fetchMessages();
|
||||
intervalId = setInterval(fetchMessages, 3000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
};
|
||||
}, [dispatch, id, create]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.length) {
|
||||
if (
|
||||
data[0]?.ticket?.readOnly === true ||
|
||||
data[0]?.ticket?.status === "closed"
|
||||
) {
|
||||
setCanSeeMessages(false);
|
||||
}
|
||||
} else {
|
||||
setCanSeeMessages(true);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const isReffered = (data) => {
|
||||
if (data) {
|
||||
if (
|
||||
data.some(
|
||||
(item) =>
|
||||
item.message?.includes("ارجاع داده شد.") &&
|
||||
item?.message?.includes("تیکت شماره")
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container xs={12} justifyContent="center">
|
||||
<Grid container xs={12}>
|
||||
<BackButton />
|
||||
</Grid>
|
||||
|
||||
{data && (
|
||||
<Grid
|
||||
container
|
||||
xs={12}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
p={2}
|
||||
mb={2}
|
||||
gap={2}
|
||||
direction={{ xs: "column", sm: "row" }}
|
||||
sx={{
|
||||
borderStyle: "solid",
|
||||
borderWidth: "1px",
|
||||
borderRadius: "30px",
|
||||
backgroundColor: "#e5e5e5",
|
||||
}}
|
||||
>
|
||||
<Grid container alignItems="center">
|
||||
<Typography>عنوان: {""}</Typography>
|
||||
<Typography>{data[0]?.ticket?.title}</Typography>
|
||||
</Grid>
|
||||
<Grid container alignItems="center">
|
||||
<Typography>تاریخ ایجاد: {""}</Typography>
|
||||
<Typography>
|
||||
{formatJustDate(data[0]?.ticket?.createDate)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid container alignItems="center">
|
||||
<Typography>وضعیت تیکت: {""}</Typography>
|
||||
<Typography>
|
||||
{data[0]?.ticket?.status === "open"
|
||||
? "باز"
|
||||
: data[0]?.ticket?.status === "answered"
|
||||
? "باز"
|
||||
: "بسته"}
|
||||
{isReffered(data) && " (ارجاع داده شده) "}
|
||||
{data[0]?.ticket?.readOnly && "(فقط خواندنی)"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{Object.prototype.hasOwnProperty.call(data[0], "readBy") && (
|
||||
<Grid container alignItems="center">
|
||||
<Button
|
||||
variant="outlined"
|
||||
endIcon={<ForwardToInboxIcon />}
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
OPEN_MODAL({
|
||||
title: "ارجاع تیکت",
|
||||
content: (
|
||||
<SubmitRefferTicket
|
||||
fetchMessages={fetchMessages}
|
||||
ticket={id}
|
||||
/>
|
||||
),
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
ارجاع
|
||||
</Button>
|
||||
</Grid>
|
||||
)}
|
||||
{/* <Grid container alignItems="center">
|
||||
<Typography>دریافت کنندگان: {""}</Typography>
|
||||
<Typography>
|
||||
{data[0]?.ticket?.toUser.length
|
||||
? data[0]?.ticket?.toUser?.map(
|
||||
(option, index) =>
|
||||
`${option?.fullname} ${
|
||||
index + 1 !== data[0]?.ticket?.toUser?.length
|
||||
? " - "
|
||||
: ""
|
||||
}`
|
||||
)
|
||||
: data[0]?.ticket?.toRole?.map(
|
||||
(option, index) =>
|
||||
`${getFaUserRole(option.name)} ${
|
||||
index + 1 !== data[0]?.ticket?.toRole?.length
|
||||
? " - "
|
||||
: ""
|
||||
}`
|
||||
)}
|
||||
</Typography>
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{canSeeMessages && (
|
||||
<Grid
|
||||
xs={12}
|
||||
md={4}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
sx={{ paddingRight: "17px" }}
|
||||
>
|
||||
<CreateTicket id={id} fetchMessages={fetchMessages} />
|
||||
</Grid>
|
||||
)}
|
||||
{create === "false" && (
|
||||
<Grid
|
||||
xs={12}
|
||||
md={8}
|
||||
sx={{ marginTop: { xs: 2, md: 0 } }}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<ShowMissages id={id} data={data} />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
44
src/features/ticket/services/create-ticket.js
Normal file
44
src/features/ticket/services/create-ticket.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
import { LOADING_END, LOADING_START } from "../../../lib/redux/slices/appSlice";
|
||||
|
||||
export const CreateTicketService = createAsyncThunk(
|
||||
"CREATE_TICKET",
|
||||
async (d, { dispatch }) => {
|
||||
dispatch(LOADING_START());
|
||||
|
||||
try {
|
||||
const { data, status } = await axios.post("ticket/", d);
|
||||
dispatch(LOADING_END());
|
||||
return { data, status };
|
||||
} catch (e) {
|
||||
dispatch(LOADING_END());
|
||||
return { error: e.response.data.result };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const CloseTicketService = createAsyncThunk(
|
||||
"CLOSE_TICKET",
|
||||
async (d, { dispatch }) => {
|
||||
dispatch(LOADING_START());
|
||||
const { data, status } = await axios.put("ticket/0/", d);
|
||||
dispatch(LOADING_END());
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
|
||||
export const EditTicketService = createAsyncThunk(
|
||||
"EDIT_TICKET",
|
||||
async (d, { dispatch }) => {
|
||||
dispatch(LOADING_START());
|
||||
try {
|
||||
const { data, status } = await axios.put("ticket/0/", d);
|
||||
dispatch(LOADING_END());
|
||||
return { data, status };
|
||||
} catch (e) {
|
||||
dispatch(LOADING_END());
|
||||
return { error: e.response.data.result };
|
||||
}
|
||||
}
|
||||
);
|
||||
14
src/features/ticket/services/get-messages.js
Normal file
14
src/features/ticket/services/get-messages.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
|
||||
export const getMessagesService = createAsyncThunk(
|
||||
"GET_MESSAGES",
|
||||
async (d, { dispatch }) => {
|
||||
const { data, status } = await axios.get(`message/`, {
|
||||
params: {
|
||||
ticket: d.ticket,
|
||||
},
|
||||
});
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
34
src/features/ticket/services/get-ticket-permission.js
Normal file
34
src/features/ticket/services/get-ticket-permission.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
import { LOADING_END, LOADING_START } from "../../../lib/redux/slices/appSlice";
|
||||
|
||||
export const getTicketPermission = createAsyncThunk(
|
||||
"GET_TICKET_PERMISSION",
|
||||
async (d, { dispatch }) => {
|
||||
dispatch(LOADING_START());
|
||||
const { data, status } = await axios.get(`ticket-permission/`, {
|
||||
params: {
|
||||
role: d.role,
|
||||
},
|
||||
});
|
||||
dispatch(LOADING_END());
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
|
||||
export const getTicketUsersFromRole = createAsyncThunk(
|
||||
"GET_TICKET_USERS_FROM_ROLE",
|
||||
async (d, { dispatch }) => {
|
||||
dispatch(LOADING_START());
|
||||
|
||||
const roles = Array.isArray(d.role) ? d.role.join(",") : d.role;
|
||||
|
||||
const { data, status } = await axios.get(`get-user-from-role/`, {
|
||||
params: {
|
||||
role: roles,
|
||||
},
|
||||
});
|
||||
dispatch(LOADING_END());
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
13
src/features/ticket/services/send-response.js
Normal file
13
src/features/ticket/services/send-response.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
import { LOADING_END, LOADING_START } from "../../../lib/redux/slices/appSlice";
|
||||
|
||||
export const sendResponseTicketService = createAsyncThunk(
|
||||
"RESPONSE_TICKET",
|
||||
async (d, { dispatch }) => {
|
||||
dispatch(LOADING_START());
|
||||
const { data, status } = await axios.post("message/", d);
|
||||
dispatch(LOADING_END());
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
10
src/features/ticket/services/ticket-close-ticket.js
Normal file
10
src/features/ticket/services/ticket-close-ticket.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
|
||||
export const ticketCloseTicket = createAsyncThunk(
|
||||
"TICKET_CLOSE_TICKET",
|
||||
async (d) => {
|
||||
const { data, status } = await axios.put("/create_ticket/0/", d);
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
10
src/features/ticket/services/ticket-create-ticket.js
Normal file
10
src/features/ticket/services/ticket-create-ticket.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
|
||||
export const ticketCreateTicket = createAsyncThunk(
|
||||
"TICKET_CREATE_TICKET",
|
||||
async (d) => {
|
||||
const { data, status } = await axios.post("/create_ticket/", d);
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
12
src/features/ticket/services/ticket-get-created-tickets.js
Normal file
12
src/features/ticket/services/ticket-get-created-tickets.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
|
||||
export const ticketGetCreatedTickets = createAsyncThunk(
|
||||
"TICKET_GET_CREATED_TICKETS",
|
||||
async () => {
|
||||
const { data, status } = await axios.get("/create_ticket/", {
|
||||
params: { all: true },
|
||||
});
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
12
src/features/ticket/services/ticket-get-operator-tickets.js
Normal file
12
src/features/ticket/services/ticket-get-operator-tickets.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
|
||||
export const ticketGetOperatorTickets = createAsyncThunk(
|
||||
"TICKET_GET_OPERATOR_TICKETS",
|
||||
async () => {
|
||||
const { data, status } = await axios.get("/respond/", {
|
||||
params: { all: true },
|
||||
});
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
10
src/features/ticket/services/ticket-respond-ticket.js
Normal file
10
src/features/ticket/services/ticket-respond-ticket.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
|
||||
export const ticketRespondTicket = createAsyncThunk(
|
||||
"TICKET_RESPOND_TICKET",
|
||||
async (d) => {
|
||||
const { data, status } = await axios.post("/respond/", d);
|
||||
return { data, status };
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user