Merge branch 'feature/operator_rating_page' into 'develop'
Feature/operator rating page See merge request witel-front-end/crm-app!48
This commit is contained in:
5
.idea/inspectionProfiles/Project_Default.xml
generated
5
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -3,5 +3,10 @@
|
|||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="JSAnnotator" enabled="false" level="ERROR" enabled_by_default="false" />
|
<inspection_tool class="JSAnnotator" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import OperatorDialoguePage from "@/components/dashboard/monitoring/operator/OperatorDialogue";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<OperatorDialoguePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import OperatorDialoguePage from "@/components/dashboard/monitoring/supervisor/OperatorDialogue";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ارزیابی پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<OperatorDialoguePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import PeopleMessagePage from "@/components/dashboard/monitoring/operator/PeopleMessage";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<PeopleMessagePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import PeopleMessagePage from "@/components/dashboard/monitoring/supervisor/PeopleMessage";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<PeopleMessagePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import WithPermission from "@/core/middlewares/withPermission";
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
import SystemMessagesPage from "@/components/dashboard/monitoring/SystemMessages";
|
import SystemMessagesPage from "@/components/dashboard/monitoring/operator/SystemMessage";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "پیام های سیستمی",
|
title: "پیام های سیستمی",
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import SystemMessagesPage from "@/components/dashboard/monitoring/supervisor/SystemMessages";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<SystemMessagesPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_DIALOGUE_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "src",
|
||||||
|
header: "شماره تماس گیرنده",
|
||||||
|
id: "src",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dst",
|
||||||
|
header: "کارشناس",
|
||||||
|
id: "dst",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "disposition",
|
||||||
|
header: "وضعیت",
|
||||||
|
id: "disposition",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "طول تماس ( ثانیه )",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.recordingfile} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_DIALOGUE_OPERATOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, FormHelperText, Grid, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string, number } from "yup";
|
||||||
|
import { GET_SYSTEM_MESSAGES_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ date, messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
supervisor_id: string().required("کارشناس بررسی کننده را مشخص کنید"),
|
||||||
|
is_good: string().required("نحوه پاسخگویی را مشخص کنید"),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
date: date,
|
||||||
|
cdr_unique_id: messageId,
|
||||||
|
supervisor_id: data.supervisor_id,
|
||||||
|
is_good: data.is_good,
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_SYSTEM_MESSAGES_SUPERVISOR}`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.supervisor_id, initialValues.is_good, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کارشناس بررسی کننده"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="supervisor_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="نحوه پاسخگویی"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="is_good"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const OperatorDialoguePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"صدای کارشناسان"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OperatorDialoguePage;
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
import RateMessage from "./RateMessage";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "caller_phone_number",
|
||||||
|
header: "شماره تماس",
|
||||||
|
id: "caller_phone_number",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.voice} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "listen_at",
|
||||||
|
header: "زمان شنیده شدن",
|
||||||
|
id: "listen_at",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "review_duration",
|
||||||
|
header: "زمان بررسی (دقیقه)",
|
||||||
|
id: "review_duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ارزیابی",
|
||||||
|
id: "rate",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ cell, row, table }) => {
|
||||||
|
const mutate = table.options.meta?.mutate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RateMessage
|
||||||
|
mutate={mutate}
|
||||||
|
messageId={row.original.id}
|
||||||
|
initialValues={{
|
||||||
|
savaneh_id: row.original.savaneh_id,
|
||||||
|
action_id: row.original.action_id,
|
||||||
|
description: row.original.description,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, Grid, InputLabel, MenuItem, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string } from "yup";
|
||||||
|
import { GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
savaneh_id: string().required("وضعیت کیفیت پیام را مشخص کنید"),
|
||||||
|
action_id: string().optional(),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
savaneh_id: parseInt(data.savaneh_id),
|
||||||
|
...(data.action_id !== "" && { action_id: data.action_id }),
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR}/${messageId}`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.savaneh_id, initialValues.action_id, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-message-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
پیگیری
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-message-quality"
|
||||||
|
id="savaneh_id"
|
||||||
|
name="savaneh_id"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="پیگیری"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={1}>عدم نیاز به ثبت (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={2}>عدم نیاز به ثبت (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
<MenuItem value={3}>عدم نیاز به ثبت (مشکل عوارضی)</MenuItem>
|
||||||
|
<MenuItem value={4}>ثبت سوانح (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={5}>ثبت سوانح (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="savaneh_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کد واقعه"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="action_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const PeopleMessagePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"پیام های مردمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PeopleMessagePage;
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_SYSTEM_MESSAGES_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "type_name",
|
||||||
|
header: "نوع",
|
||||||
|
id: "type_name",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "طول تماس ( ثانیه )",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.voice} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_SYSTEM_MESSAGES_OPERATOR}
|
||||||
|
page_name={"systemMessagesPage"}
|
||||||
|
table_name={"systemMessagesList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_DIALOGUE_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import RateMessage from "./RateMessage";
|
||||||
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "src",
|
||||||
|
header: "شماره تماس گیرنده",
|
||||||
|
id: "src",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dst",
|
||||||
|
header: "کارشناس",
|
||||||
|
id: "dst",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "disposition",
|
||||||
|
header: "وضعیت",
|
||||||
|
id: "disposition",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "طول تماس ( ثانیه )",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.recordingfile} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ارزیابی",
|
||||||
|
id: "rate",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ cell, row, table }) => {
|
||||||
|
const mutate = table.options.meta?.mutate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RateMessage
|
||||||
|
mutate={mutate}
|
||||||
|
date={row.original.calldate}
|
||||||
|
messageId={row.original.uniqueid}
|
||||||
|
initialValues={{
|
||||||
|
supervisor_id: row.original.supervisor_id,
|
||||||
|
is_good: row.original.is_good,
|
||||||
|
description: row.original.description,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_DIALOGUE_SUPERVISOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause } = useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1, justifyContent: "center" }}>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? <PauseIcon sx={{ color: "#d32f2f" }} /> : <PlayArrowIcon sx={{ color: "#1b4332" }} />}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, Grid, InputLabel, MenuItem, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string } from "yup";
|
||||||
|
import { GET_OPERATOR_DIALOGUE_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ date, messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
supervisor_id: string().required("کارشناس بررسی کننده را مشخص کنید"),
|
||||||
|
is_good: string().required("نحوه پاسخگویی را مشخص کنید"),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
date: date,
|
||||||
|
cdr_unique_id: messageId,
|
||||||
|
supervisor_id: data.supervisor_id,
|
||||||
|
is_good: parseInt(data.is_good),
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_OPERATOR_DIALOGUE_SUPERVISOR}/rate`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.supervisor_id, initialValues.is_good, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کارشناس بررسی کننده"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="supervisor_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-message-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
نحوه پاسخگویی
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-message-quality"
|
||||||
|
id="is_good"
|
||||||
|
name="is_good"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="نحوه پاسخگویی"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>نامناسب</MenuItem>
|
||||||
|
<MenuItem value={1}>مناسب</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="is_good"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const OperatorDialoguePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"ارزیابی صدای کارشناسان"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OperatorDialoguePage;
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_PEOPLE_MESSAGE_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
import RateMessage from "./RateMessage";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "caller_phone_number",
|
||||||
|
header: "شماره تماس",
|
||||||
|
id: "caller_phone_number",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.voice} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "listen_at",
|
||||||
|
header: "زمان شنیده شدن",
|
||||||
|
id: "listen_at",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "review_duration",
|
||||||
|
header: "زمان بررسی (دقیقه)",
|
||||||
|
id: "review_duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ارزیابی",
|
||||||
|
id: "rate",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ cell, row, table }) => {
|
||||||
|
const mutate = table.options.meta?.mutate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RateMessage
|
||||||
|
mutate={mutate}
|
||||||
|
messageId={row.original.id}
|
||||||
|
initialValues={{
|
||||||
|
savaneh_id: row.original.savaneh_id,
|
||||||
|
action_id: row.original.action_id,
|
||||||
|
description: row.original.description,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_PEOPLE_MESSAGE_SUPERVISOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, FormHelperText, Grid, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string, number } from "yup";
|
||||||
|
import { GET_SYSTEM_MESSAGES_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
savaneh_id: string().required("وضعیت کیفیت پیام را مشخص کنید"),
|
||||||
|
action_id: string().optional(),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
savaneh_id: parseInt(data.savaneh_id),
|
||||||
|
...(data.action_id !== "" && { action_id: data.action_id }),
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_SYSTEM_MESSAGES_SUPERVISOR}/${messageId}`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.savaneh_id, initialValues.action_id, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-message-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
پیگیری
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-message-quality"
|
||||||
|
id="savaneh_id"
|
||||||
|
name="savaneh_id"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="پیگیری"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={1}>عدم نیاز به ثبت (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={2}>عدم نیاز به ثبت (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
<MenuItem value={3}>عدم نیاز به ثبت (مشکل عوارضی)</MenuItem>
|
||||||
|
<MenuItem value={4}>ثبت سوانح (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={5}>ثبت سوانح (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="savaneh_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کد واقعه"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="action_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const PeopleMessagePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"ارزیابی پیام های مردمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PeopleMessagePage;
|
||||||
@@ -23,6 +23,18 @@ const DataTable = () => {
|
|||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "type_name",
|
||||||
|
header: "نوع",
|
||||||
|
id: "type_name",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: "تاریخ",
|
header: "تاریخ",
|
||||||
id: "date",
|
id: "date",
|
||||||
@@ -50,6 +62,7 @@ const DataTable = () => {
|
|||||||
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
header: "طول تماس ( ثانیه )",
|
header: "طول تماس ( ثانیه )",
|
||||||
id: "duration",
|
id: "duration",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const SystemMessagesPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"ارزیابی پیام های سیستمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemMessagesPage;
|
||||||
@@ -138,27 +138,51 @@ export const pageMenu = [
|
|||||||
hasSubitems: true,
|
hasSubitems: true,
|
||||||
Subitems: [
|
Subitems: [
|
||||||
{
|
{
|
||||||
id: "monitoring-operator-dialogue",
|
id: "monitoring-operator-dialogue-supervisor",
|
||||||
|
label: "ارزیابی صدای کارشناسان",
|
||||||
|
type: "page",
|
||||||
|
icon: <VoiceChatIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
|
route: "/dashboard/monitoring/operator-dialogue/supervisor",
|
||||||
|
permissions: ["all"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "monitoring-operator-dialogue-operator",
|
||||||
label: "صدای کارشناسان",
|
label: "صدای کارشناسان",
|
||||||
type: "page",
|
type: "page",
|
||||||
icon: <VoiceChatIcon sx={{ width: "inherit", height: "inherit" }} />,
|
icon: <VoiceChatIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
route: "/dashboard/monitoring/operator-dialogue",
|
route: "/dashboard/monitoring/operator-dialogue/operator",
|
||||||
permissions: ["all"],
|
permissions: ["all"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "monitoring-system-messages",
|
id: "monitoring-system-messages-supervisor",
|
||||||
|
label: "ارزیابی پیام های سیستمی",
|
||||||
|
type: "page",
|
||||||
|
icon: <PointOfSaleIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
|
route: "/dashboard/monitoring/system-messages/supervisor",
|
||||||
|
permissions: ["all"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "monitoring-system-messages-operator",
|
||||||
label: "پیام های سیستمی",
|
label: "پیام های سیستمی",
|
||||||
type: "page",
|
type: "page",
|
||||||
icon: <PointOfSaleIcon sx={{ width: "inherit", height: "inherit" }} />,
|
icon: <PointOfSaleIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
route: "/dashboard/monitoring/system-messages",
|
route: "/dashboard/monitoring/system-messages/operator",
|
||||||
permissions: ["all"],
|
permissions: ["all"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "monitoring-people-messages",
|
id: "monitoring-people-messages-supervisor",
|
||||||
|
label: "ارزیابی پیام های مردمی",
|
||||||
|
type: "page",
|
||||||
|
icon: <ThreePIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
|
route: "/dashboard/monitoring/people-messages/supervisor",
|
||||||
|
permissions: ["all"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "monitoring-people-messages-operator",
|
||||||
label: "پیام های مردمی",
|
label: "پیام های مردمی",
|
||||||
type: "page",
|
type: "page",
|
||||||
icon: <ThreePIcon sx={{ width: "inherit", height: "inherit" }} />,
|
icon: <ThreePIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||||
route: "/dashboard/monitoring/people-messages",
|
route: "/dashboard/monitoring/people-messages/operator",
|
||||||
permissions: ["all"],
|
permissions: ["all"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ export const GET_COUNTRY_KEYPRESS_REPORT = api + "/cumulative_keypress_stats";
|
|||||||
export const GET_SYSTEM_MESSAGES_SUPERVISOR = api + "/system_messages/supervisor";
|
export const GET_SYSTEM_MESSAGES_SUPERVISOR = api + "/system_messages/supervisor";
|
||||||
export const GET_SYSTEM_MESSAGES_OPERATOR = api + "/system_messages/operator";
|
export const GET_SYSTEM_MESSAGES_OPERATOR = api + "/system_messages/operator";
|
||||||
|
|
||||||
|
export const GET_OPERATOR_DIALOGUE_SUPERVISOR = api + "/dialogs/supervisor";
|
||||||
|
export const GET_OPERATOR_DIALOGUE_OPERATOR = api + "/dialogs/operator";
|
||||||
|
|
||||||
|
export const GET_OPERATOR_PEOPLE_MESSAGE_SUPERVISOR = api + "/people_messages/supervisor";
|
||||||
|
export const GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR = api + "/people_messages/operator";
|
||||||
|
|
||||||
// export const GET_PROVINCE_CUMULATIVE_CALLS_TOTAL_REPORT = api + "/report/cumulative_total";
|
// export const GET_PROVINCE_CUMULATIVE_CALLS_TOTAL_REPORT = api + "/report/cumulative_total";
|
||||||
export const GET_PROVINCE_CUMULATIVE_CALLS_DAILY_REPORT = api + "/report/cumulative";
|
export const GET_PROVINCE_CUMULATIVE_CALLS_DAILY_REPORT = api + "/report/cumulative";
|
||||||
export const GET_PROVINCE_PEOPLE_MESSAGES_REPORT = api + "/report/people_messages";
|
export const GET_PROVINCE_PEOPLE_MESSAGES_REPORT = api + "/report/people_messages";
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ const playerRegistry = new Set();
|
|||||||
export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const wavesurferRef = useRef(null);
|
const wavesurferRef = useRef(null);
|
||||||
|
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
const [currentTime, setCurrentTime] = useState(0);
|
||||||
|
const [duration, setDuration] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
@@ -17,7 +21,6 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
|||||||
progressColor: "#1b4332",
|
progressColor: "#1b4332",
|
||||||
cursorColor: "#d32f2f",
|
cursorColor: "#d32f2f",
|
||||||
height: 40,
|
height: 40,
|
||||||
url: audioUrl,
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,12 +29,24 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
|||||||
wavesurferRef.current.on("finish", () => setIsPlaying(false));
|
wavesurferRef.current.on("finish", () => setIsPlaying(false));
|
||||||
wavesurferRef.current.on("play", () => setIsPlaying(true));
|
wavesurferRef.current.on("play", () => setIsPlaying(true));
|
||||||
wavesurferRef.current.on("pause", () => setIsPlaying(false));
|
wavesurferRef.current.on("pause", () => setIsPlaying(false));
|
||||||
|
wavesurferRef.current.on("ready", () => {
|
||||||
|
setIsLoaded(true);
|
||||||
|
setDuration(wavesurferRef.current.getDuration());
|
||||||
|
});
|
||||||
|
|
||||||
|
wavesurferRef.current.on("audioprocess", () => {
|
||||||
|
setCurrentTime(wavesurferRef.current.getCurrentTime());
|
||||||
|
});
|
||||||
|
|
||||||
|
wavesurferRef.current.on("seek", () => {
|
||||||
|
setCurrentTime(wavesurferRef.current.getCurrentTime());
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
playerRegistry.delete(wavesurferRef.current);
|
playerRegistry.delete(wavesurferRef.current);
|
||||||
wavesurferRef.current?.destroy();
|
wavesurferRef.current?.destroy();
|
||||||
};
|
};
|
||||||
}, [audioUrl]);
|
}, []);
|
||||||
|
|
||||||
const playPause = () => {
|
const playPause = () => {
|
||||||
if (!wavesurferRef.current) return;
|
if (!wavesurferRef.current) return;
|
||||||
@@ -42,7 +57,16 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
wavesurferRef.current.playPause();
|
if (!isLoaded) {
|
||||||
|
setIsLoaded(true);
|
||||||
|
wavesurferRef.current.load(audioUrl);
|
||||||
|
wavesurferRef.current.once("ready", () => {
|
||||||
|
setDuration(wavesurferRef.current.getDuration());
|
||||||
|
wavesurferRef.current.play();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
wavesurferRef.current.playPause();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
@@ -59,6 +83,8 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
|||||||
playPause,
|
playPause,
|
||||||
stop,
|
stop,
|
||||||
seekTo,
|
seekTo,
|
||||||
|
currentTime,
|
||||||
|
duration,
|
||||||
wavesurfer: wavesurferRef.current,
|
wavesurfer: wavesurferRef.current,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user