customer feedback
parent
bd4385e452
commit
cdd2192274
File diff suppressed because it is too large
Load Diff
|
@ -16,6 +16,7 @@
|
||||||
"i18next-browser-languagedetector": "^7.1.0",
|
"i18next-browser-languagedetector": "^7.1.0",
|
||||||
"i18next-http-backend": "^2.2.1",
|
"i18next-http-backend": "^2.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-countup": "^6.5.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-highlight-words": "^0.20.0",
|
"react-highlight-words": "^0.20.0",
|
||||||
"react-i18next": "^13.0.1",
|
"react-i18next": "^13.0.1",
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
"react-stl-viewer": "^2.2.5",
|
"react-stl-viewer": "^2.2.5",
|
||||||
"react-virtuoso": "^4.5.1",
|
"react-virtuoso": "^4.5.1",
|
||||||
"react-webcam": "^7.1.1",
|
"react-webcam": "^7.1.1",
|
||||||
|
"recharts": "^2.12.7",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"equipmentDocumentation": "Gerätedokumentation",
|
"equipmentDocumentation": "Gerätedokumentation",
|
||||||
"consoles": "Konsolen",
|
"consoles": "Konsolen",
|
||||||
|
"customerFeedback": "Bewertungen",
|
||||||
"groupTasks": {
|
"groupTasks": {
|
||||||
"menuCategory": "Gruppenaufgaben",
|
"menuCategory": "Gruppenaufgaben",
|
||||||
"overview": "Kategorien",
|
"overview": "Kategorien",
|
||||||
|
@ -705,5 +706,12 @@
|
||||||
"noLogTypesFound": "Keine Log-Typen gefunden",
|
"noLogTypesFound": "Keine Log-Typen gefunden",
|
||||||
"noLogManagerServerSpecified": "Kein Log-Manager-Server angegeben",
|
"noLogManagerServerSpecified": "Kein Log-Manager-Server angegeben",
|
||||||
"couldntReachlogManagerServer": "Verbindung zum Log-Manager-Server konnte nicht hergestellt werden"
|
"couldntReachlogManagerServer": "Verbindung zum Log-Manager-Server konnte nicht hergestellt werden"
|
||||||
|
},
|
||||||
|
"customerFeedback": {
|
||||||
|
"origin": "Herkunft:",
|
||||||
|
"feedbacks": "Bewertungen",
|
||||||
|
"table": {
|
||||||
|
"createdAt":"Erstellt am"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"equipmentDocumentation": "Equipment Documentation",
|
"equipmentDocumentation": "Equipment Documentation",
|
||||||
"consoles": "Consoles",
|
"consoles": "Consoles",
|
||||||
|
"customerFeedback": "Feedbacks",
|
||||||
"groupTasks": {
|
"groupTasks": {
|
||||||
"menuCategory": "Group Tasks",
|
"menuCategory": "Group Tasks",
|
||||||
"overview": "Categories",
|
"overview": "Categories",
|
||||||
|
@ -709,5 +710,12 @@
|
||||||
"noLogTypesFound": "No log types found",
|
"noLogTypesFound": "No log types found",
|
||||||
"noLogManagerServerSpecified": "No log manager specified",
|
"noLogManagerServerSpecified": "No log manager specified",
|
||||||
"couldntReachlogManagerServer": "Connection to log manager server failed"
|
"couldntReachlogManagerServer": "Connection to log manager server failed"
|
||||||
|
},
|
||||||
|
"customerFeedback": {
|
||||||
|
"origin": "Origin:",
|
||||||
|
"feedbacks": "Feedbacks",
|
||||||
|
"table": {
|
||||||
|
"createdAt":"Created at"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
src/App.js
31
src/App.js
|
@ -16,6 +16,7 @@ import HeaderProvider from "./Contexts/HeaderContext";
|
||||||
import ConsolesProvider from "./Contexts/ConsolesContext";
|
import ConsolesProvider from "./Contexts/ConsolesContext";
|
||||||
import ScannerProvider from "./Contexts/ScannerContext";
|
import ScannerProvider from "./Contexts/ScannerContext";
|
||||||
import { CrmProvider } from "./Contexts/CrmContext";
|
import { CrmProvider } from "./Contexts/CrmContext";
|
||||||
|
import { CustomerFeedbackProvider } from "./Contexts/CustomerFeedbackContext";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [notificationApi, notificationContextHolder] =
|
const [notificationApi, notificationContextHolder] =
|
||||||
|
@ -49,22 +50,24 @@ export default function App() {
|
||||||
<ConsolesProvider>
|
<ConsolesProvider>
|
||||||
<ScannerProvider>
|
<ScannerProvider>
|
||||||
<CrmProvider>
|
<CrmProvider>
|
||||||
<WebSocketProvider
|
<CustomerFeedbackProvider>
|
||||||
userSession={userSession}
|
<WebSocketProvider
|
||||||
setUserSession={setUserSession}
|
|
||||||
isWebSocketReady={isWebSocketReady}
|
|
||||||
setIsWebSocketReady={setIsWebSocketReady}
|
|
||||||
notificationApi={notificationApi}
|
|
||||||
>
|
|
||||||
<ReconnectingView
|
|
||||||
isWebSocketReady={isWebSocketReady}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DashboardLayout
|
|
||||||
userSession={userSession}
|
userSession={userSession}
|
||||||
setUserSession={setUserSession}
|
setUserSession={setUserSession}
|
||||||
/>
|
isWebSocketReady={isWebSocketReady}
|
||||||
</WebSocketProvider>
|
setIsWebSocketReady={setIsWebSocketReady}
|
||||||
|
notificationApi={notificationApi}
|
||||||
|
>
|
||||||
|
<ReconnectingView
|
||||||
|
isWebSocketReady={isWebSocketReady}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DashboardLayout
|
||||||
|
userSession={userSession}
|
||||||
|
setUserSession={setUserSession}
|
||||||
|
/>
|
||||||
|
</WebSocketProvider>
|
||||||
|
</CustomerFeedbackProvider>
|
||||||
</CrmProvider>
|
</CrmProvider>
|
||||||
</ScannerProvider>
|
</ScannerProvider>
|
||||||
</ConsolesProvider>
|
</ConsolesProvider>
|
||||||
|
|
|
@ -23,8 +23,9 @@ const ViewEquipmentDocumentations = lazy(() =>
|
||||||
);
|
);
|
||||||
const Consoles = lazy(() => import("../../Pages/Consoles"));
|
const Consoles = lazy(() => import("../../Pages/Consoles"));
|
||||||
const RoboticsRobots = lazy(() => import("../../Pages/Robotics/Robots"));
|
const RoboticsRobots = lazy(() => import("../../Pages/Robotics/Robots"));
|
||||||
const Crm = lazy(() => import("../../Pages/Crm"));
|
// const Crm = lazy(() => import("../../Pages/Crm"));
|
||||||
const CrmTest = lazy(() => import("../../Pages/CrmTest/CrmTest"));
|
const CrmTest = lazy(() => import("../../Pages/CrmTest/CrmTest"));
|
||||||
|
const CustomerFeedback = lazy(() => import("../../Pages/CustomerFeedback"));
|
||||||
|
|
||||||
export default function AppRoutes({ userSession, setUserSession }) {
|
export default function AppRoutes({ userSession, setUserSession }) {
|
||||||
const appContext = useAppContext();
|
const appContext = useAppContext();
|
||||||
|
@ -258,6 +259,34 @@ export default function AppRoutes({ userSession, setUserSession }) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{hasPermission(
|
||||||
|
appContext.userPermissions,
|
||||||
|
Constants.PERMISSIONS.CUSTOMERFEEDBACK.VIEW
|
||||||
|
) && (
|
||||||
|
<Route
|
||||||
|
path={Constants.ROUTE_PATHS.CUSTOMERFEEDBACK_VIEW}
|
||||||
|
element={
|
||||||
|
<MySupsenseFallback>
|
||||||
|
<CustomerFeedback />
|
||||||
|
</MySupsenseFallback>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasPermission(
|
||||||
|
appContext.userPermissions,
|
||||||
|
Constants.PERMISSIONS.CUSTOMERFEEDBACK.VIEW
|
||||||
|
) && (
|
||||||
|
<Route
|
||||||
|
path={`${Constants.ROUTE_PATHS.CUSTOMERFEEDBACK_VIEW}/:paramOrigin`}
|
||||||
|
element={
|
||||||
|
<MySupsenseFallback>
|
||||||
|
<CustomerFeedback />
|
||||||
|
</MySupsenseFallback>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
element={
|
element={
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const RequestState = {
|
||||||
|
INIT: -1,
|
||||||
|
NOTHING: 0,
|
||||||
|
REQUESTING: 1,
|
||||||
|
SUCCESS: 2,
|
||||||
|
FAILED: 3,
|
||||||
|
};
|
|
@ -82,6 +82,20 @@ export function SideMenuContent({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// customer feedback
|
||||||
|
if (
|
||||||
|
hasPermission(
|
||||||
|
appContext.userPermissions,
|
||||||
|
Constants.PERMISSIONS.CUSTOMERFEEDBACK.VIEW
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
items.push({
|
||||||
|
label: t("sideMenu.customerFeedback"),
|
||||||
|
icon: <SnippetsOutlined />,
|
||||||
|
key: Constants.ROUTE_PATHS.CUSTOMERFEEDBACK_VIEW,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// group tasks
|
// group tasks
|
||||||
let groupTasksGroup = {
|
let groupTasksGroup = {
|
||||||
label: t("sideMenu.groupTasks.menuCategory"),
|
label: t("sideMenu.groupTasks.menuCategory"),
|
||||||
|
@ -149,6 +163,7 @@ export function SideMenuContent({
|
||||||
label: t("sideMenu.robotics.robots"),
|
label: t("sideMenu.robotics.robots"),
|
||||||
icon: <RobotOutlined />,
|
icon: <RobotOutlined />,
|
||||||
key: Constants.ROUTE_PATHS.ROBOTICS_ROBOTS,
|
key: Constants.ROUTE_PATHS.ROBOTICS_ROBOTS,
|
||||||
|
disabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
items.push(roboticsGroup);
|
items.push(roboticsGroup);
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { createContext, useContext, useState } from "react";
|
||||||
|
|
||||||
|
const preview = {
|
||||||
|
origins: [],
|
||||||
|
setOrigins: () => {},
|
||||||
|
customerFeedbacks: [],
|
||||||
|
setCustomerFeedbacks: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomerFeedbackContext = createContext(preview);
|
||||||
|
|
||||||
|
export const useCustomerFeedbackContext = () =>
|
||||||
|
useContext(CustomerFeedbackContext);
|
||||||
|
|
||||||
|
export function CustomerFeedbackProvider({ children }) {
|
||||||
|
const [origins, setOrigins] = useState([]);
|
||||||
|
const [customerFeedbacks, setCustomerFeedbacks] = useState([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomerFeedbackContext.Provider
|
||||||
|
value={{
|
||||||
|
origins,
|
||||||
|
setOrigins,
|
||||||
|
customerFeedbacks,
|
||||||
|
setCustomerFeedbacks,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CustomerFeedbackContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import { useHeaderContext } from "./HeaderContext";
|
||||||
import { useConsolesContext } from "./ConsolesContext";
|
import { useConsolesContext } from "./ConsolesContext";
|
||||||
import { useScannerContext } from "./ScannerContext";
|
import { useScannerContext } from "./ScannerContext";
|
||||||
import { useCrmContext } from "./CrmContext";
|
import { useCrmContext } from "./CrmContext";
|
||||||
|
import { useCustomerFeedbackContext } from "./CustomerFeedbackContext";
|
||||||
|
|
||||||
const WebSocketContext = createContext(null);
|
const WebSocketContext = createContext(null);
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ export default function WebSocketProvider({
|
||||||
const consolesContext = useConsolesContext();
|
const consolesContext = useConsolesContext();
|
||||||
const scannerContext = useScannerContext();
|
const scannerContext = useScannerContext();
|
||||||
const crmContext = useCrmContext();
|
const crmContext = useCrmContext();
|
||||||
|
const customerFeedbackContext = useCustomerFeedbackContext();
|
||||||
|
|
||||||
if (wsConnectionEvent === null) {
|
if (wsConnectionEvent === null) {
|
||||||
wsConnectionEvent = new CustomEvent(wsConnectionCustomEventName, {
|
wsConnectionEvent = new CustomEvent(wsConnectionCustomEventName, {
|
||||||
|
@ -106,7 +108,8 @@ export default function WebSocketProvider({
|
||||||
usersContext,
|
usersContext,
|
||||||
consolesContext,
|
consolesContext,
|
||||||
scannerContext,
|
scannerContext,
|
||||||
crmContext
|
crmContext,
|
||||||
|
customerFeedbackContext
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ export const ReceivedMessagesCommands = {
|
||||||
CrmLinkCreated: 51,
|
CrmLinkCreated: 51,
|
||||||
CrmLinkUsed: 52,
|
CrmLinkUsed: 52,
|
||||||
CrmLinkDeleted: 53,
|
CrmLinkDeleted: 53,
|
||||||
|
CustomerFeedbackAddFeedback: 54,
|
||||||
};
|
};
|
||||||
|
|
||||||
// commands sent to the backend server
|
// commands sent to the backend server
|
||||||
|
@ -100,7 +101,8 @@ export function handleWebSocketMessage(
|
||||||
usersContext,
|
usersContext,
|
||||||
consolesContext,
|
consolesContext,
|
||||||
scannerContext,
|
scannerContext,
|
||||||
crmContext
|
crmContext,
|
||||||
|
customerFeedbackContext
|
||||||
) {
|
) {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
@ -1120,6 +1122,9 @@ export function handleWebSocketMessage(
|
||||||
arr.filter((link) => link.Id !== body)
|
arr.filter((link) => link.Id !== body)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case ReceivedMessagesCommands.CustomerFeedbackAddFeedback:
|
||||||
|
customerFeedbackContext.setCustomerFeedbacks((arr) => [...arr, body]);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("unknown command", cmd);
|
console.error("unknown command", cmd);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -0,0 +1,436 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Constants,
|
||||||
|
FormatDatetime,
|
||||||
|
myFetch,
|
||||||
|
wsConnectionCustomEventName,
|
||||||
|
} from "../../utils";
|
||||||
|
import { useCustomerFeedbackContext } from "../../Contexts/CustomerFeedbackContext";
|
||||||
|
import { Card, Col, Result, Row, Select, Spin, Statistic, Table } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { RequestState } from "../../Components/MyRequestState";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import CountUp from "react-countup";
|
||||||
|
import {
|
||||||
|
LineChart,
|
||||||
|
Line,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ResponsiveContainer,
|
||||||
|
} from "recharts";
|
||||||
|
|
||||||
|
function calculateAverage(arr) {
|
||||||
|
if (arr.length === 0) return 0;
|
||||||
|
const sum = arr.reduce(
|
||||||
|
(accumulator, currentValue) => accumulator + currentValue,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const average = sum / arr.length;
|
||||||
|
|
||||||
|
return average.toFixed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CustomerFeedback() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const customerFeedbackContext = useCustomerFeedbackContext();
|
||||||
|
const { paramOrigin } = useParams();
|
||||||
|
|
||||||
|
const [isRequesting, setIsRequesting] = useState(RequestState.INIT);
|
||||||
|
const [selectedOrigin, setSelectedOrigin] = useState("");
|
||||||
|
|
||||||
|
const getTableContent = () => {
|
||||||
|
const feedbackColumns = [];
|
||||||
|
const columns = [];
|
||||||
|
|
||||||
|
for (const feedback of customerFeedbackContext.customerFeedbacks) {
|
||||||
|
if (feedback.Data === null) continue;
|
||||||
|
|
||||||
|
for (const key in feedback.Data) {
|
||||||
|
if (!columns.includes(key)) {
|
||||||
|
feedbackColumns.push({
|
||||||
|
title: key,
|
||||||
|
dataIndex: key,
|
||||||
|
key: key,
|
||||||
|
});
|
||||||
|
|
||||||
|
columns.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...feedbackColumns,
|
||||||
|
{
|
||||||
|
title: t("customerFeedback.table.createdAt"),
|
||||||
|
dataIndex: "createdAt",
|
||||||
|
key: "createdAt",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTableItems = () => {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
customerFeedbackContext.customerFeedbacks.sort(
|
||||||
|
(a, b) => new Date(b.CreatedAt) - new Date(a.CreatedAt)
|
||||||
|
);
|
||||||
|
|
||||||
|
customerFeedbackContext.customerFeedbacks.forEach((customerFeedback) => {
|
||||||
|
let dynamicItems = {};
|
||||||
|
|
||||||
|
if (customerFeedback.Data !== null) {
|
||||||
|
for (const key in customerFeedback.Data) {
|
||||||
|
dynamicItems[key] = customerFeedback.Data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
key: customerFeedback.Id,
|
||||||
|
...dynamicItems,
|
||||||
|
createdAt: FormatDatetime(customerFeedback.CreatedAt),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigateToOrigin = (origin) =>
|
||||||
|
navigate(`${Constants.ROUTE_PATHS.CUSTOMERFEEDBACK_VIEW}/${origin}`);
|
||||||
|
|
||||||
|
const StatisticCard = () => {
|
||||||
|
const data = {};
|
||||||
|
|
||||||
|
for (const customerFeedback of customerFeedbackContext.customerFeedbacks) {
|
||||||
|
if (customerFeedback.Data === null) continue;
|
||||||
|
|
||||||
|
for (const feedbackDataKey in customerFeedback.Data) {
|
||||||
|
if (typeof customerFeedback.Data[feedbackDataKey] !== "number")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!data.hasOwnProperty(feedbackDataKey)) {
|
||||||
|
data[feedbackDataKey] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
data[feedbackDataKey].push(customerFeedback.Data[feedbackDataKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = [
|
||||||
|
<Statistic
|
||||||
|
title={t("customerFeedback.feedbacks")}
|
||||||
|
value={customerFeedbackContext.customerFeedbacks.length}
|
||||||
|
formatter={(value) => <CountUp end={value} />}
|
||||||
|
/>,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const statistic in data) {
|
||||||
|
elements.push(
|
||||||
|
<Statistic
|
||||||
|
key={statistic}
|
||||||
|
title={statistic}
|
||||||
|
value={calculateAverage(data[statistic])}
|
||||||
|
formatter={(value) => (
|
||||||
|
<span>
|
||||||
|
<CountUp end={value} prefix="Ø " decimals={1} /> / ∑{" "}
|
||||||
|
{data[statistic].length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkedElements = elements.reduce((acc, element, index) => {
|
||||||
|
const chunkIndex = Math.floor(index / 8);
|
||||||
|
if (!acc[chunkIndex]) {
|
||||||
|
acc[chunkIndex] = [];
|
||||||
|
}
|
||||||
|
acc[chunkIndex].push(element);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ marginTop: 20 }}>
|
||||||
|
{chunkedElements.map((chunk, chunkIndex) => (
|
||||||
|
<Row gutter={16} key={chunkIndex}>
|
||||||
|
{chunk.map((element, elementIndex) => (
|
||||||
|
<Col xs={24} sm={12} md={4} key={elementIndex}>
|
||||||
|
{element}
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Graphs = () => {
|
||||||
|
if (
|
||||||
|
!customerFeedbackContext.customerFeedbacks ||
|
||||||
|
customerFeedbackContext.customerFeedbacks.length === 0
|
||||||
|
) {
|
||||||
|
return <div>No data available</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to count number of feedbacks for each date
|
||||||
|
const countFeedbacksByDate = (feedbacks) => {
|
||||||
|
const dateCounts = {};
|
||||||
|
|
||||||
|
// Iterate through feedbacks
|
||||||
|
feedbacks.forEach((feedback) => {
|
||||||
|
const date = new Date(feedback.CreatedAt).toISOString().split("T")[0];
|
||||||
|
|
||||||
|
// Increment count for date or initialize it with 1
|
||||||
|
dateCounts[date] = (dateCounts[date] || 0) + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert date-counts map to array of objects and sort by date ascending
|
||||||
|
return Object.keys(dateCounts)
|
||||||
|
.map((date) => ({
|
||||||
|
date,
|
||||||
|
count: dateCounts[date],
|
||||||
|
}))
|
||||||
|
.sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = countFeedbacksByDate(
|
||||||
|
customerFeedbackContext.customerFeedbacks
|
||||||
|
);
|
||||||
|
|
||||||
|
// Function to extract data for each key in Data
|
||||||
|
const getDataByKey = (feedbacks, key) => {
|
||||||
|
const dateValuesMap = {};
|
||||||
|
|
||||||
|
// Iterate through feedbacks
|
||||||
|
feedbacks.forEach((feedback) => {
|
||||||
|
if (feedback.Data && typeof feedback.Data === "object") {
|
||||||
|
const date = new Date(feedback.CreatedAt).toISOString().split("T")[0];
|
||||||
|
const value = feedback.Data[key];
|
||||||
|
|
||||||
|
// Increment count for date or initialize it with value
|
||||||
|
dateValuesMap[date] =
|
||||||
|
(dateValuesMap[date] || 0) + (typeof value === "number" ? 1 : 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert date-values map to array of objects and sort by date ascending
|
||||||
|
return Object.keys(dateValuesMap)
|
||||||
|
.map((date) => ({
|
||||||
|
date,
|
||||||
|
value: dateValuesMap[date],
|
||||||
|
}))
|
||||||
|
.sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card style={{ marginTop: 20 }}>
|
||||||
|
<h2>Feedback Count</h2>
|
||||||
|
<ResponsiveContainer width="100%" height={400}>
|
||||||
|
<LineChart data={data}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="date" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip />
|
||||||
|
<Legend />
|
||||||
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dataKey="count"
|
||||||
|
stroke="#8884d8"
|
||||||
|
activeDot={{ r: 8 }}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
{Object.keys(customerFeedbackContext.customerFeedbacks[0]?.Data || {})
|
||||||
|
.filter(
|
||||||
|
(key) =>
|
||||||
|
typeof customerFeedbackContext.customerFeedbacks[0]?.Data[key] ===
|
||||||
|
"number"
|
||||||
|
)
|
||||||
|
.map((key) => (
|
||||||
|
<Card key={key} style={{ marginTop: 20 }}>
|
||||||
|
<h2>{key}</h2>
|
||||||
|
<ResponsiveContainer width="100%" height={400}>
|
||||||
|
<LineChart
|
||||||
|
data={getDataByKey(
|
||||||
|
customerFeedbackContext.customerFeedbacks,
|
||||||
|
key
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="date" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip />
|
||||||
|
<Legend />
|
||||||
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke="#8884d8"
|
||||||
|
activeDot={{ r: 8 }}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to group data by date and count occurrences
|
||||||
|
/*const getDataByDate = (feedbacks) => {
|
||||||
|
const dateCounts = feedbacks.reduce((acc, feedback) => {
|
||||||
|
const date = new Date(feedback.CreatedAt).toISOString().split("T")[0]; // Extract date part
|
||||||
|
acc[date] = (acc[date] || 0) + 1;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const sortedDates = Object.keys(dateCounts).sort(
|
||||||
|
(a, b) => new Date(a) - new Date(b)
|
||||||
|
);
|
||||||
|
|
||||||
|
return sortedDates.map((date) => ({
|
||||||
|
date,
|
||||||
|
count: dateCounts[date],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = getDataByDate(customerFeedbackContext.customerFeedbacks);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ marginTop: 20 }}>
|
||||||
|
<ResponsiveContainer width="100%" height={400}>
|
||||||
|
<LineChart data={data}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="date" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip />
|
||||||
|
<Legend />
|
||||||
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dataKey="count"
|
||||||
|
stroke="#8884d8"
|
||||||
|
activeDot={{ r: 8 }}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}; */
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const customersRequest = () => {
|
||||||
|
setIsRequesting(RequestState.REQUESTING);
|
||||||
|
|
||||||
|
myFetch(`/customerfeedback/origins`, "GET").then((data) => {
|
||||||
|
if (data === undefined || data === null) return;
|
||||||
|
|
||||||
|
customerFeedbackContext.setOrigins(data);
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
setIsRequesting(RequestState.NOTHING);
|
||||||
|
} else if (paramOrigin === undefined || !data.includes(paramOrigin)) {
|
||||||
|
navigateToOrigin(data[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
customersRequest();
|
||||||
|
|
||||||
|
const handleCustomersRequest = () => customersRequest();
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
wsConnectionCustomEventName,
|
||||||
|
handleCustomersRequest
|
||||||
|
);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
document.removeEventListener(
|
||||||
|
wsConnectionCustomEventName,
|
||||||
|
handleCustomersRequest
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (paramOrigin === undefined) return;
|
||||||
|
|
||||||
|
setIsRequesting(RequestState.REQUESTING);
|
||||||
|
setSelectedOrigin(paramOrigin);
|
||||||
|
|
||||||
|
myFetch(`/customerfeedback/origin/${paramOrigin}`, "GET").then((data) => {
|
||||||
|
if (data === undefined || data === null) return;
|
||||||
|
|
||||||
|
customerFeedbackContext.setCustomerFeedbacks(data);
|
||||||
|
setIsRequesting(RequestState.SUCCESS);
|
||||||
|
});
|
||||||
|
}, [paramOrigin]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: 10,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{t("customerFeedback.origin")}</span>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
value={selectedOrigin}
|
||||||
|
onSelect={(value) => navigateToOrigin(value)}
|
||||||
|
options={customerFeedbackContext.origins.map((origin) => {
|
||||||
|
return {
|
||||||
|
value: origin,
|
||||||
|
label: origin,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
></Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isRequesting === RequestState.INIT ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "80vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin size="large"></Spin>
|
||||||
|
</div>
|
||||||
|
) : isRequesting === RequestState.NOTHING ? (
|
||||||
|
<Result
|
||||||
|
status="404"
|
||||||
|
title="404"
|
||||||
|
subTitle="Sorry, the page you visited does not exist."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<StatisticCard />
|
||||||
|
|
||||||
|
<Graphs />
|
||||||
|
|
||||||
|
<Table
|
||||||
|
style={{ paddingTop: 20 }}
|
||||||
|
scroll={{ x: "max-content" }}
|
||||||
|
columns={getTableContent()}
|
||||||
|
dataSource={getTableItems()}
|
||||||
|
loading={isRequesting === RequestState.REQUESTING}
|
||||||
|
pagination
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -97,6 +97,7 @@ export const Constants = {
|
||||||
ROBOTICS_ROBOTS: "/robotics/robots",
|
ROBOTICS_ROBOTS: "/robotics/robots",
|
||||||
CRM: "/crm/",
|
CRM: "/crm/",
|
||||||
CRM_TEST: "/crm/test",
|
CRM_TEST: "/crm/test",
|
||||||
|
CUSTOMERFEEDBACK_VIEW: "/customer-feedback",
|
||||||
},
|
},
|
||||||
CRM_TYPE: {
|
CRM_TYPE: {
|
||||||
TEST_CUSTOMERS: "test-customers",
|
TEST_CUSTOMERS: "test-customers",
|
||||||
|
@ -224,6 +225,9 @@ export const Constants = {
|
||||||
VIEW: "crm.setter_closer.view",
|
VIEW: "crm.setter_closer.view",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
CUSTOMERFEEDBACK: {
|
||||||
|
VIEW: "customerfeedback.view",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
SYSTEM_LOG_TYPE: {
|
SYSTEM_LOG_TYPE: {
|
||||||
INFO: 0,
|
INFO: 0,
|
||||||
|
|
Loading…
Reference in New Issue