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" />
|
||||
<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="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>
|
||||
</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 SystemMessagesPage from "@/components/dashboard/monitoring/SystemMessages";
|
||||
import SystemMessagesPage from "@/components/dashboard/monitoring/operator/SystemMessage";
|
||||
|
||||
export const metadata = {
|
||||
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,
|
||||
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",
|
||||
@@ -50,6 +62,7 @@ const DataTable = () => {
|
||||
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||
},
|
||||
{
|
||||
accessorKey: "duration",
|
||||
header: "طول تماس ( ثانیه )",
|
||||
id: "duration",
|
||||
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,
|
||||
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: "صدای کارشناسان",
|
||||
type: "page",
|
||||
icon: <VoiceChatIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
route: "/dashboard/monitoring/operator-dialogue",
|
||||
route: "/dashboard/monitoring/operator-dialogue/operator",
|
||||
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: "پیام های سیستمی",
|
||||
type: "page",
|
||||
icon: <PointOfSaleIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
route: "/dashboard/monitoring/system-messages",
|
||||
route: "/dashboard/monitoring/system-messages/operator",
|
||||
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: "پیام های مردمی",
|
||||
type: "page",
|
||||
icon: <ThreePIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
route: "/dashboard/monitoring/people-messages",
|
||||
route: "/dashboard/monitoring/people-messages/operator",
|
||||
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_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_DAILY_REPORT = api + "/report/cumulative";
|
||||
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 = {}) {
|
||||
const containerRef = useRef(null);
|
||||
const wavesurferRef = useRef(null);
|
||||
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
@@ -17,7 +21,6 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
||||
progressColor: "#1b4332",
|
||||
cursorColor: "#d32f2f",
|
||||
height: 40,
|
||||
url: audioUrl,
|
||||
...options,
|
||||
});
|
||||
|
||||
@@ -26,12 +29,24 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
||||
wavesurferRef.current.on("finish", () => setIsPlaying(false));
|
||||
wavesurferRef.current.on("play", () => setIsPlaying(true));
|
||||
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 () => {
|
||||
playerRegistry.delete(wavesurferRef.current);
|
||||
wavesurferRef.current?.destroy();
|
||||
};
|
||||
}, [audioUrl]);
|
||||
}, []);
|
||||
|
||||
const playPause = () => {
|
||||
if (!wavesurferRef.current) return;
|
||||
@@ -42,7 +57,16 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
||||
}
|
||||
});
|
||||
|
||||
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 = () => {
|
||||
@@ -59,6 +83,8 @@ export default function useWaveSurferPlayer(audioUrl, options = {}) {
|
||||
playPause,
|
||||
stop,
|
||||
seekTo,
|
||||
currentTime,
|
||||
duration,
|
||||
wavesurfer: wavesurferRef.current,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user