complete system message

This commit is contained in:
2025-08-11 11:30:35 +03:30
parent 36fa5fc26f
commit 88f89662c6
34 changed files with 6702 additions and 7025 deletions

6
.idea/prettier.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

View File

@@ -19,6 +19,7 @@
"@mui/material-nextjs": "^7.0.2",
"@mui/x-date-pickers": "^8.2.0",
"@mui/x-tree-view": "^8.2.0",
"@wavesurfer/react": "^1.0.11",
"axios": "^1.9.0",
"date-fns-jalali": "4.1.0-0",
"jalali-moment": "^3.3.11",
@@ -34,6 +35,7 @@
"stylis": "^4.3.6",
"stylis-plugin-rtl": "^2.1.1",
"swr": "^2.3.3",
"wavesurfer.js": "^7.10.1",
"yup": "^1.6.1"
},
"devDependencies": {

4994
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,16 @@
import WithPermission from "@/core/middlewares/withPermission";
import SystemMessagesPage from "@/components/dashboard/monitoring/SystemMessages";
export const metadata = {
title: "پیام های سیستمی",
};
const Page = () => {
return (
<WithPermission permission_name={["all"]}>
<SystemMessagesPage />
</WithPermission>
);
};
export default Page;

View File

@@ -0,0 +1,16 @@
import WithPermission from "@/core/middlewares/withPermission";
import CountryCallsReportPage from "@/components/dashboard/reports/CountryCallsReport";
export const metadata = {
title: "گزارشات",
};
const Page = () => {
return (
<WithPermission permission_name={["all"]}>
<CountryCallsReportPage />
</WithPermission>
);
};
export default Page;

View File

@@ -0,0 +1,16 @@
import WithPermission from "@/core/middlewares/withPermission";
import CountryKeyPressReportPage from "@/components/dashboard/reports/CountryKeyPressReport";
export const metadata = {
title: "گزارشات",
};
const Page = () => {
return (
<WithPermission permission_name={["all"]}>
<CountryKeyPressReportPage />
</WithPermission>
);
};
export default Page;

View File

@@ -0,0 +1,16 @@
import WithPermission from "@/core/middlewares/withPermission";
import CountryMessageRatingReportPage from "@/components/dashboard/reports/CountryMessageRatingReport";
export const metadata = {
title: "گزارشات",
};
const Page = () => {
return (
<WithPermission permission_name={["all"]}>
<CountryMessageRatingReportPage />
</WithPermission>
);
};
export default Page;

View File

@@ -0,0 +1,16 @@
import WithPermission from "@/core/middlewares/withPermission";
import CountryVoiceRatingReportPage from "@/components/dashboard/reports/CountryVoiceRatingReport";
export const metadata = {
title: "گزارشات",
};
const Page = () => {
return (
<WithPermission permission_name={["all"]}>
<CountryVoiceRatingReportPage />
</WithPermission>
);
};
export default Page;

View File

@@ -1,4 +1,5 @@
"use client";
import WithoutAuthMiddleware from "@/core/middlewares/withoutAuth";
import { Suspense } from "react";

View File

@@ -1,6 +1,6 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_ROLE_LIST } from "@/core/utils/routes";
import { ROLE_SERVICE } from "@/core/utils/routes";
import { Box, Typography } from "@mui/material";
import { useMemo } from "react";
import Toolbar from "./Toolbar";
@@ -75,7 +75,7 @@ const DataTable = () => {
need_filter={true}
columns={columns}
sorting={[{ id: "id", desc: true }]}
table_url={GET_ROLE_LIST}
table_url={ROLE_SERVICE}
page_name={"rolesPage"}
table_name={"rolesList"}
TableToolbar={Toolbar}

View File

@@ -1,4 +1,4 @@
import { DELETE_ROLE } from "@/core/utils/routes";
import { ROLE_SERVICE } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
import { useState } from "react";
@@ -9,7 +9,7 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
const handleClick = async () => {
setSubmitting(true);
try {
await requestServer(`${DELETE_ROLE}/${rowId}`, "delete");
await requestServer(`${ROLE_SERVICE}/${rowId}`, "delete");
mutate();
setOpenDeleteDialog(false);
} catch (error) {

View File

@@ -1,6 +1,6 @@
import LtrTextField from "@/core/components/LtrTextField";
import StyledForm from "@/core/components/StyledForm";
import { CREATE_ROLE } from "@/core/utils/routes";
import { ROLE_SERVICE } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { yupResolver } from "@hookform/resolvers/yup";
import {
@@ -24,7 +24,7 @@ import usePermissionsList from "@/lib/hooks/usePermissionsList";
const validationSchema = object().shape({
name: string().required("نام فارسی نقش را وارد کنید"),
name_fa: string().required("نام انگلیسی نقش را وارد کنید"),
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید")
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید"),
});
const UpdateRoleForm = ({ values, setOpen, mutate }) => {
@@ -34,7 +34,7 @@ const UpdateRoleForm = ({ values, setOpen, mutate }) => {
const defaultValues = {
name: values.name,
name_fa: values.name_fa,
permissions: values.permissions?.map(p => p.id) || []
permissions: values.permissions?.map((p) => p.id) || [],
};
const {
@@ -45,7 +45,7 @@ const UpdateRoleForm = ({ values, setOpen, mutate }) => {
const onSubmit = async (data) => {
try {
await requestServer(`${CREATE_ROLE}/${values.id}`, "post", {
await requestServer(`${ROLE_SERVICE}/${values.id}`, "post", {
data: {
permissions: data.permissions,
name: data.name,
@@ -106,17 +106,17 @@ const UpdateRoleForm = ({ values, setOpen, mutate }) => {
name={"name_fa"}
/>
</Grid>
<Box sx={{width: "100%"}}>
<Box sx={{ width: "100%" }}>
<Divider>
<Chip label="سطوح دسترسی" size="medium" />
</Divider>
</Box>
<Grid container spacing={1} sx={{width: "100%"}}>
<Grid container spacing={1} sx={{ width: "100%" }}>
{!loadingPermissions ? (
<Controller
control={control}
name="permissions"
render={({ field, fieldState: {error} }) => (
render={({ field, fieldState: { error } }) => (
<>
{permissions.map((permission) => (
<Grid key={permission.id} size={{ xs: 12, md: 4 }}>
@@ -130,7 +130,9 @@ const UpdateRoleForm = ({ values, setOpen, mutate }) => {
if (e.target.checked) {
field.onChange([...field.value, value]);
} else {
field.onChange(field.value.filter((id) => id !== value));
field.onChange(
field.value.filter((id) => id !== value)
);
}
}}
/>
@@ -139,18 +141,32 @@ const UpdateRoleForm = ({ values, setOpen, mutate }) => {
/>
</Grid>
))}
<FormHelperText sx={{color: "error.main"}}>{!!error && error.message}</FormHelperText>
<FormHelperText sx={{ color: "error.main" }}>
{!!error && error.message}
</FormHelperText>
</>
)}
/>
) : (
<Grid container spacing={2} width="100%">
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
</Grid>
)}
</Grid>

View File

@@ -9,26 +9,28 @@ import {
DialogActions,
DialogContent,
Divider,
FormControlLabel, FormHelperText,
Grid, Skeleton,
FormControlLabel,
FormHelperText,
Grid,
Skeleton,
Stack,
} from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import { array, object, string } from "yup";
import useRequest from "@/lib/hooks/useRequest";
import { CREATE_ROLE } from "@/core/utils/routes";
import { ROLE_SERVICE } from "@/core/utils/routes";
import usePermissionsList from "@/lib/hooks/usePermissionsList";
const defaultValues = {
name: "",
name_fa: "",
permissions: []
permissions: [],
};
const validationSchema = object().shape({
name: string().required("نام فارسی نقش را وارد کنید"),
name_fa: string().required("نام انگلیسی نقش را وارد کنید"),
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید")
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید"),
});
const CreateRoleForm = ({ setOpen, mutate }) => {
@@ -43,7 +45,7 @@ const CreateRoleForm = ({ setOpen, mutate }) => {
const onSubmit = async (data) => {
try {
await requestServer(CREATE_ROLE, "post", {
await requestServer(ROLE_SERVICE, "post", {
data: {
permissions: data.permissions,
name: data.name,
@@ -104,17 +106,17 @@ const CreateRoleForm = ({ setOpen, mutate }) => {
name={"name_fa"}
/>
</Grid>
<Box sx={{width: "100%"}}>
<Box sx={{ width: "100%" }}>
<Divider>
<Chip label="سطوح دسترسی" size="medium" />
</Divider>
</Box>
<Grid container spacing={1} sx={{width: "100%"}}>
<Grid container spacing={1} sx={{ width: "100%" }}>
{!loadingPermissions ? (
<Controller
control={control}
name="permissions"
render={({ field, fieldState: {error} }) => (
render={({ field, fieldState: { error } }) => (
<>
{permissions.map((permission) => (
<Grid key={permission.id} size={{ xs: 12, md: 4 }}>
@@ -128,7 +130,9 @@ const CreateRoleForm = ({ setOpen, mutate }) => {
if (e.target.checked) {
field.onChange([...field.value, value]);
} else {
field.onChange(field.value.filter((id) => id !== value));
field.onChange(
field.value.filter((id) => id !== value)
);
}
}}
/>
@@ -137,18 +141,32 @@ const CreateRoleForm = ({ setOpen, mutate }) => {
/>
</Grid>
))}
<FormHelperText sx={{color: "error.main"}}>{!!error && error.message}</FormHelperText>
<FormHelperText sx={{ color: "error.main" }}>
{!!error && error.message}
</FormHelperText>
</>
)}
/>
) : (
<Grid container spacing={2} width="100%">
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
<Grid size={{ xs: 12, md: 4 }}>
<Skeleton animation="wave" />
</Grid>
</Grid>
)}
</Grid>

View File

@@ -1,13 +1,12 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_USER_LIST } from "@/core/utils/routes";
import { Box, Stack } from "@mui/material";
import { USER_SERVICE } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useMemo } from "react";
import RowActions from "./RowActions";
import Toolbar from "./Toolbar";
import useProvinces from "@/lib/hooks/useProvince";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import { Power, PowerOff } from "@mui/icons-material";
import useRoles from "@/lib/hooks/useRoles";
import OnlineCell from "./RowActions/Online";
import TelephoneId from "./RowActions/TelephoneId";
@@ -197,7 +196,7 @@ const DataTable = () => {
need_filter={true}
columns={columns}
sorting={[{ id: "id", desc: true }]}
table_url={GET_USER_LIST}
table_url={USER_SERVICE}
page_name={"usersPage"}
table_name={"usersList"}
TableToolbar={Toolbar}

View File

@@ -1,4 +1,4 @@
import { DELETE_USER } from "@/core/utils/routes";
import { USER_SERVICE } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
import { useState } from "react";
@@ -9,7 +9,7 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
const handleClick = async () => {
setSubmitting(true);
try {
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
await requestServer(`${USER_SERVICE}/${rowId}`, "delete");
mutate();
setOpenDeleteDialog(false);
} catch (error) {

View File

@@ -1,7 +1,7 @@
import LtrTextField from "@/core/components/LtrTextField";
import StyledForm from "@/core/components/StyledForm";
import validateNationalCode from "@/core/utils/nationalCodeValidation";
import { UPDATE_USER } from "@/core/utils/routes";
import { USER_SERVICE } from "@/core/utils/routes";
import useProvinces from "@/lib/hooks/useProvince";
import useRequest from "@/lib/hooks/useRequest";
import useRoles from "@/lib/hooks/useRoles";
@@ -40,7 +40,7 @@ const validationSchema = object().shape({
position: string().required("سمت را وارد کنید"),
province_id: string().required("استان را وارد کنید"),
role_id: string().required("نقش را وارد کنید"),
telephone_id: string().required("شماره تماس داخلی را وارد کنید")
telephone_id: string().required("شماره تماس داخلی را وارد کنید"),
});
const UpdateUserForm = ({ values, setOpen, mutate }) => {
@@ -57,7 +57,7 @@ const UpdateUserForm = ({ values, setOpen, mutate }) => {
phone_number: values.phone_number,
province_id: values.province_id,
role_id: values.roles[0].id,
telephone_id: values.telephone_id
telephone_id: values.telephone_id,
};
const provinceSelectOptions = useMemo(() => {
@@ -104,7 +104,7 @@ const UpdateUserForm = ({ values, setOpen, mutate }) => {
formData.append(key, data[key]);
});
try {
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
await requestServer(`${USER_SERVICE}/${values.id}`, "post", {
data: formData,
});
setOpen(false);

View File

@@ -26,7 +26,7 @@ import { object, string } from "yup";
import { useMemo, useState } from "react";
import useRoles from "@/lib/hooks/useRoles";
import useRequest from "@/lib/hooks/useRequest";
import { CREATE_USER } from "@/core/utils/routes";
import { USER_SERVICE } from "@/core/utils/routes";
import { Visibility, VisibilityOff } from "@mui/icons-material";
const defaultValues = {
@@ -114,7 +114,7 @@ const CreateUserForm = ({ setOpen, mutate }) => {
formData.append(key, data[key]);
});
try {
await requestServer(CREATE_USER, "post", {
await requestServer(USER_SERVICE, "post", {
data: formData,
});
setOpen(false);

View File

@@ -0,0 +1,118 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_SYSTEM_MESSAGES_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";
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") : <>-</>,
},
{
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} />,
},
{
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={{
message_quality: row.original.message_quality,
format_quality: row.original.format_quality,
description: row.original.description,
}}
/>
);
},
},
],
[]
);
return (
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
need_filter={true}
columns={columns}
table_url={GET_SYSTEM_MESSAGES_SUPERVISOR}
page_name={"systemMessagesPage"}
table_name={"systemMessagesList"}
positionActionsColumn={"last"}
/>
</Box>
);
};
export default DataTable;

View File

@@ -0,0 +1,26 @@
"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;

View File

@@ -0,0 +1,163 @@
"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 = {
message_quality: initialValues?.message_quality ?? "",
format_quality: initialValues?.format_quality ?? "",
description: initialValues?.description ?? "",
};
const validationSchema = object().shape({
message_quality: string().required("وضعیت کیفیت پیام را مشخص کنید"),
format_quality: 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 = {
message_quality: parseInt(data.message_quality),
format_quality: parseInt(data.format_quality),
...(data.description !== "" && { description: data.description }),
};
try {
await requestServer(`${GET_SYSTEM_MESSAGES_SUPERVISOR}/${messageId}`, "post", {
data: fixData,
});
mutate();
reset({
message_quality: initialValues?.message_quality ?? "",
format_quality: initialValues?.format_quality ?? "",
description: initialValues?.description ?? "",
});
} catch (error) {}
};
useEffect(() => {
reset({
message_quality: initialValues?.message_quality ?? "",
format_quality: initialValues?.format_quality ?? "",
description: initialValues?.description ?? "",
});
}, [initialValues.message_quality, initialValues.format_quality, 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="message_quality"
name="message_quality"
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="message_quality"
/>
</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-format-quality" size="small" sx={{ fontSize: "12px" }}>
فرمت پیام
</InputLabel>
<Select
variant="outlined"
labelId="label-format-quality"
id="format_quality"
name="format_quality"
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="format_quality"
/>
</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;

View File

@@ -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;

View File

@@ -0,0 +1,205 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_COUNTRY_CALLS_REPORT } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useMemo } from "react";
const DataTable = () => {
const columns = useMemo(
() => [
{
accessorKey: "province_name",
header: "استان",
id: "province_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "total",
header: "تمامی تماس ها",
id: "total",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
grow: false,
size: 100,
},
{
header: "تماس با تهران",
id: "call_to_tehran",
enableColumnFilter: false,
enableSorting: false,
grow: false,
size: 50,
columns: [
{
accessorKey: "route_to_teh4",
header: "کلید 4",
id: "route_to_teh4",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "route_to_teh5",
header: "کلید 5",
id: "route_to_teh5",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
},
{
header: "تماس با اپراتور استانی",
id: "call_to_ostan",
enableColumnFilter: false,
enableSorting: false,
grow: false,
size: 50,
columns: [
{
accessorKey: "answered",
header: "موفق",
id: "answered",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "missed",
header: "جواب داده نشده",
id: "missed",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "fails",
header: "نا موفق",
id: "fails",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "route_to_os4",
header: "کلید 4",
id: "route_to_os4",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "route_to_os5",
header: "کلید 5",
id: "route_to_os5",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "dnd",
header: "dnd",
id: "dnd",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "transferred",
header: "انتقال تماس",
id: "transferred",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
},
{
accessorKey: "answered_over10",
header: "تماس بالای 10 ثانیه",
id: "answered_over10",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "average_calls_time",
header: "میانگین تماس ها",
id: "average_calls_time",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
[]
);
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
need_filter={true}
columns={columns}
table_url={GET_COUNTRY_CALLS_REPORT}
page_name={"countryCallsReportPage"}
table_name={"countryCallsReportList"}
positionActionsColumn={"last"}
/>
</Box>
</>
);
};
export default DataTable;

View File

@@ -0,0 +1,16 @@
"use client";
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import DataTable from "./DataTable";
const CountryCallsReportPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"تجمعی تمام مراکز"} />
<DataTable />
</Stack>
);
};
export default CountryCallsReportPage;

View File

@@ -0,0 +1,236 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_COUNTRY_KEYPRESS_REPORT } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useMemo } from "react";
const DataTable = () => {
const columns = useMemo(
() => [
{
accessorKey: "province_name",
header: "استان",
id: "province_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "total",
header: "مجموع تماس ها",
id: "total",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
grow: false,
size: 100,
},
{
header: "تماس با اعداد",
id: "call_with_nums",
enableColumnFilter: false,
enableSorting: false,
grow: false,
size: 50,
columns: [
{
accessorKey: "key1",
header: "کلید 1",
id: "key1",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "key2",
header: "کلید 2",
id: "key2",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "key3",
header: "کلید 3",
id: "key3",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "key4",
header: "کلید 4",
id: "key4",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "key5",
header: "کلید 5",
id: "key5",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "key6",
header: "کلید 6",
id: "key6",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
},
{
accessorKey: "key9",
header: "منوی مدیریت",
id: "key9",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
grow: false,
size: 100,
},
{
accessorKey: "key4_1",
header: "تماس با استان",
id: "key4_1",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
grow: false,
size: 100,
},
{
accessorKey: "key4_2",
header: "تماس با تهران",
id: "key4_2",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
grow: false,
size: 100,
},
{
accessorKey: "key5_1",
header: "تماس راننده با استان",
id: "key5_1",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
grow: false,
size: 100,
},
{
accessorKey: "key5_2",
header: "تماس راننده با تهران",
id: "key5_2",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
grow: false,
size: 100,
},
{
header: "محدودیت",
id: "limits",
enableColumnFilter: false,
enableSorting: false,
grow: false,
size: 50,
columns: [
{
accessorKey: "key2_1",
header: "انسداد فصلی",
id: "key2_1",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "key2_2",
header: "تردد وسایل نقلیه سنگین و موتورسیکلت",
id: "key2_2",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "key2_3",
header: "سایر",
id: "key2_3",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
},
],
[]
);
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
need_filter={true}
columns={columns}
table_url={GET_COUNTRY_KEYPRESS_REPORT}
page_name={"countryKeypressReportPage"}
table_name={"countryKeypressReportList"}
positionActionsColumn={"last"}
/>
</Box>
</>
);
};
export default DataTable;

View File

@@ -0,0 +1,14 @@
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import DataTable from "./DataTable";
const CountryKeyPressReportPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"گزارش تماس ها"} />
<DataTable />
</Stack>
);
};
export default CountryKeyPressReportPage;

View File

@@ -0,0 +1,150 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_COUNTRY_MESSAGE_RATING_REPORT } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useMemo } from "react";
const DataTable = () => {
const columns = useMemo(
() => [
{
accessorKey: "province_name",
header: "استان",
id: "province_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "total",
header: "پیام ها",
id: "total",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "listens",
header: "شنیده شده",
id: "listens",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "not_listens",
header: "شنیده نشده",
id: "not_listens",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "review_average",
header: "میانگین زمان بررسی",
id: "review_average",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "action1",
header: "میانگین زمان بررسی (دقیقه)",
id: "action1",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "action2",
header: "عدم نیاز به ثبت (مشکل راه)",
id: "action2",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "action3",
header: "عدم نیاز به ثبت (مشکل حمل و نقلی)",
id: "action3",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "action4",
header: "عدم نیاز به ثبت (مشکل عوارضی)",
id: "action4",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "action5",
header: "ثبت سوانح (مشکل راه)",
id: "action5",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
[]
);
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
need_filter={true}
columns={columns}
table_url={GET_COUNTRY_MESSAGE_RATING_REPORT}
page_name={"countryMessageRatingReportPage"}
table_name={"countryMessageRatingReportList"}
positionActionsColumn={"last"}
/>
</Box>
</>
);
};
export default DataTable;

View File

@@ -0,0 +1,14 @@
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import DataTable from "./DataTable";
const CountryMessageRatingReportPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"تجمعی پیام های مردمی مراکز"} />
<DataTable />
</Stack>
);
};
export default CountryMessageRatingReportPage;

View File

@@ -0,0 +1,110 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_COUNTRY_VOICE_RATING_REPORT } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useMemo } from "react";
const DataTable = () => {
const columns = useMemo(
() => [
{
accessorKey: "province_name",
header: "استان",
id: "province_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
header: "کیفیت",
id: "quality",
enableColumnFilter: false,
enableSorting: false,
grow: false,
size: 50,
columns: [
{
accessorKey: "message_quality_ok",
header: "مناسب",
id: "message_quality_ok",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "message_quality_nok",
header: "نامناسب",
id: "message_quality_nok",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
},
{
header: "فرمت",
id: "format",
enableColumnFilter: false,
enableSorting: false,
grow: false,
size: 50,
columns: [
{
accessorKey: "message_format_ok",
header: "مناسب",
id: "message_format_ok",
enableColumnFilter: true,
datatype: "integer",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "message_format_nok",
header: "نامناسب",
id: "message_format_nok",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
},
],
[]
);
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
need_filter={true}
columns={columns}
table_url={GET_COUNTRY_VOICE_RATING_REPORT}
page_name={"countryVoiceRatingReportPage"}
table_name={"countryVoiceRatingReportList"}
positionActionsColumn={"last"}
/>
</Box>
</>
);
};
export default DataTable;

View File

@@ -0,0 +1,14 @@
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import DataTable from "./DataTable";
const CountryVoiceRatingReportPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"گزارش کیفی استان ها"} />
<DataTable />
</Stack>
);
};
export default CountryVoiceRatingReportPage;

View File

@@ -156,6 +156,9 @@ const DataTable_Main = (props) => {
</>
),
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
meta: {
mutate,
},
...props,
});

View File

@@ -1,5 +1,15 @@
import { Settings } from "@mui/icons-material";
import AssessmentIcon from "@mui/icons-material/Assessment";
import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle";
import VerifiedUserIcon from "@mui/icons-material/VerifiedUser";
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
import PointOfSaleIcon from "@mui/icons-material/PointOfSale";
import ThreePIcon from "@mui/icons-material/ThreeP";
import VoiceChatIcon from "@mui/icons-material/VoiceChat";
import ZoomInMapIcon from "@mui/icons-material/ZoomInMap";
import StarIcon from "@mui/icons-material/Star";
import MarkEmailReadIcon from "@mui/icons-material/MarkEmailRead";
import TtyIcon from "@mui/icons-material/Tty";
export const pageMenu = [
{
@@ -10,6 +20,47 @@ export const pageMenu = [
icon: <SpaceDashboardIcon sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"],
},
{
id: "reports",
label: "گزارشات",
type: "menu",
icon: <AssessmentIcon sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "reports-countryCallReport",
label: "تجمعی تمام مراکز",
type: "page",
icon: <ZoomInMapIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/reports/country-calls-report",
permissions: ["all"],
},
{
id: "reports-countryVoiceRatingReport",
label: "گزارش کیفی استان ها",
type: "page",
icon: <StarIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/reports/country-voice-rating-report",
permissions: ["all"],
},
{
id: "reports-countryMessageRatingReport",
label: "تجمعی پیام های مردمی مراکز",
type: "page",
icon: <MarkEmailReadIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/reports/country-message-rating-report",
permissions: ["all"],
},
{
id: "reports-countryKeypressReport",
label: "گزارش تماس ها",
type: "page",
icon: <TtyIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/reports/country-keypress-report",
permissions: ["all"],
},
],
},
{
id: "systemManagment",
label: "مدیریت سامانه",
@@ -21,6 +72,7 @@ export const pageMenu = [
id: "systemManagment-users",
label: "کاربران",
type: "page",
icon: <SupervisedUserCircleIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/users",
permissions: ["manage_users"],
},
@@ -28,9 +80,43 @@ export const pageMenu = [
id: "systemManagment-roles",
label: "نقش ها",
type: "page",
icon: <VerifiedUserIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/roles",
permissions: ["manage_roles"],
}
},
],
},
{
id: "monitoring",
label: "پایش 141",
type: "menu",
icon: <Settings sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "monitoring-operator-dialogue",
label: "صدای کارشناسان",
type: "page",
icon: <VoiceChatIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/monitoring/operator-dialogue",
permissions: ["all"],
},
{
id: "monitoring-system-messages",
label: "پیام های سیستمی",
type: "page",
icon: <PointOfSaleIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/monitoring/system-messages",
permissions: ["all"],
},
{
id: "monitoring-people-messages",
label: "پیام های مردمی",
type: "page",
icon: <ThreePIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/monitoring/people-messages",
permissions: ["all"],
},
],
},
];

View File

@@ -14,12 +14,14 @@ export const GET_CATEGORY = api + "/categories/list";
export const GET_CALLER_HISTORY = api + "/calls/list";
export const CALL_ACTION = api + "/calls";
export const GET_USER_LIST = api + "/users";
export const CREATE_USER = api + "/users";
export const DELETE_USER = api + "/users";
export const UPDATE_USER = api + "/users";
export const USER_SERVICE = api + "/users";
export const GET_ROLE_LIST = api + "/roles";
export const CREATE_ROLE = api + "/roles";
export const DELETE_ROLE = api + "/roles";
export const UPDATE_ROLE = api + "/roles";
export const ROLE_SERVICE = api + "/roles";
export const GET_COUNTRY_CALLS_REPORT = api + "/cumulative_stats";
export const GET_COUNTRY_VOICE_RATING_REPORT = api + "/cumulative_voice_ratings";
export const GET_COUNTRY_MESSAGE_RATING_REPORT = api + "/cumulative_message_ratings";
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";

View File

@@ -0,0 +1,64 @@
import { useEffect, useRef, useState } from "react";
import WaveSurfer from "wavesurfer.js";
const playerRegistry = new Set();
export default function useWaveSurferPlayer(audioUrl, options = {}) {
const containerRef = useRef(null);
const wavesurferRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
useEffect(() => {
if (!containerRef.current) return;
wavesurferRef.current = WaveSurfer.create({
container: containerRef.current,
waveColor: "#4BB183",
progressColor: "#1b4332",
cursorColor: "#d32f2f",
height: 40,
url: audioUrl,
...options,
});
playerRegistry.add(wavesurferRef.current);
wavesurferRef.current.on("finish", () => setIsPlaying(false));
wavesurferRef.current.on("play", () => setIsPlaying(true));
wavesurferRef.current.on("pause", () => setIsPlaying(false));
return () => {
playerRegistry.delete(wavesurferRef.current);
wavesurferRef.current?.destroy();
};
}, [audioUrl]);
const playPause = () => {
if (!wavesurferRef.current) return;
playerRegistry.forEach((player) => {
if (player !== wavesurferRef.current && player.isPlaying()) {
player.stop();
}
});
wavesurferRef.current.playPause();
};
const stop = () => {
wavesurferRef.current?.stop();
};
const seekTo = (progress) => {
wavesurferRef.current?.seekTo(progress);
};
return {
containerRef,
isPlaying,
playPause,
stop,
seekTo,
wavesurfer: wavesurferRef.current,
};
}