import React, { useState, useEffect, useContext } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useHistory } from "react-router-dom"; import moment from "moment"; import { notifyError } from "../utils/toast"; import { Doughnut, Bar, } from "react-chartjs-2"; // import Chart from 'chart.js/auto'; import { Card, CardBody, Table, TableContainer, TableHeader, TableBody, TableRow, TableCell, Select, Button, } from "@windmill/react-ui"; import { SidebarContext } from "../context/SidebarContext"; import PageTitle from "../components/Typography/PageTitle"; import Loading from "../components/preloader/Loading"; import resetLogo from "../assets/img/reset.png"; import ReportServices from "../services/ReportServices"; import EmployeeServices from "../services/EmployeeServices"; /** * Returning required redux state data * @param {Object} state - Redux state */ const getReduxData = (state) => { return { Loader: state.Loader }; }; const DashboardReport = () => { const dispatch = useDispatch(); const navigate = useHistory(); let data = useSelector(getReduxData); let loading = data?.Loader.loading; const localData = JSON.parse(localStorage.getItem('adminInfo')); const [employeeId, setEmployeeId] = useState(localData?.employeeId ? parseInt(localData?.employeeId) : ""); const [latestDesignationSalesPerson, setLatestDesignationSalesPerson] = useState(false); const [rsm, setRsm] = useState(); const [asm, setAsm] = useState(); const [salesPerson, setSalesPerson] = useState(); const [getEmployeesData, setGetEmployeesData] = useState([]); const [rsmList, setRsmList] = useState([]); const [asmList, setAsmList] = useState([]); const [salesPersonList, setSalesPersonList] = useState([]); const [mainLoader, setMainLoader] = useState(true); const { currentPage, setCurrentPage, handleChangePage, toggleDrawer, } = useContext(SidebarContext); function getRandomColor() { var letters = '0123456789ABCDEF'; var color = '#'; for (var i = 0; i < 6; i++) { color += letters[Math.floor(Math.random() * 16)]; } return color; } function returnColorArray(number) { let colorArray = []; for (let i = 0; i < number; i++) { colorArray.push(getRandomColor()); } return colorArray; } const centerTextPlugin = { id: 'centerText', beforeDraw(chart) { const { width, height, ctx } = chart; const datasets = chart.data.datasets[0]; const total = datasets.data.reduce((sum, value) => sum + value, 0); ctx.save(); ctx.font = '16px Arial'; ctx.fillStyle = '#000'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('Total', width / 2, height / 2.8); ctx.font = '14px Arial'; ctx.fillText(`${total}`, width / 2, height / 2.4); ctx.restore(); }, }; // Chart.register(centerTextPlugin); const chartOptions = { responsive: true, plugins: { legend: { display: true, position: "bottom", onClick: null, }, }, }; // STATES FOR FETCHING API RESPONSE const [teamAttendanceReports, setTeamAttendanceReports] = useState(); const [callSummaryReports, setCallSummaryReports] = useState(); const [employeeReports, setEmployeeReports] = useState(); const [catalogReportsForCategory, setCatalogReportsForCategory] = useState(); const [catalogReportsForProduct, setCatalogReportsForProduct] = useState(); // STATES FOR LOADERS const [teamAttendanceReportsLoader, setTeamAttendanceReportsLoader] = useState(false); const [callSummaryReportsLoader, setCallSummaryReportsLoader] = useState(false); const [employeeReportsLoader, setEmployeeReportsLoader] = useState(false); const [catalogReportsForCategoryLoader, setCatalogReportsForCategoryLoader] = useState(false); const [catalogReportsForProductLoader, setCatalogReportsForProductLoader] = useState(false); //DISTRIBUTOR LENGTH const distributorLimit = 5; const employeeLimit = 5; const categoryLimit = 5; const productLimit = 5; const characterLength = 12; const barThickness = 40; // Ensure colors are generated before JSX const distributorColors = returnColorArray(distributorLimit); const employeeColors = returnColorArray(employeeLimit); const categoryColors = returnColorArray(categoryLimit); const productColors = returnColorArray(productLimit); const getEmployees = async (design_id) => { if (localData?.employeeId) { await getTeamById(localData?.employeeId, "rsm"); } else { await EmployeeServices.getEmployees({ design_id: design_id, }) .then((res) => { setGetEmployeesData(res?.data); setEmployeeId(res?.data[0]?.employeeId); setRsm(res?.data[0]?.employeeId); getTeamById(res?.data[0]?.employeeId, "asm"); }) .catch((err) => console.log("ERROR in /getEmployees", err)); } } useEffect(async () => { await getEmployees(3); }, []); const getTeamById = async (id, designation) => { await ReportServices.getTeamById({ employeeId: parseInt(id), store_id: localData?.store_id ? parseInt(localData?.store_id) : "", }) .then(res => { if (designation == "rsm") { setRsmList(res?.data); } else if (designation == "asm") { setAsmList(res?.data); } else if (designation == "salesperson") { setSalesPersonList(res?.data); } else { alert(`Only have ASM and Sales Person getTeamById query!`); } }) .catch(err => console.log("Error in /getTeamById", err)); } const getTeamAttendanceReports = async () => { setTeamAttendanceReportsLoader(true); await ReportServices.getTeamAttendanceReports({ employeeId, date: new Date().toISOString().split("T")[0], }) .then(res => setTeamAttendanceReports(res)) .catch(err => console.log("Error in /getTeamAttendanceReports", err)); setTeamAttendanceReportsLoader(false); }; const getCallSummaryReports = async () => { setCallSummaryReportsLoader(true); await ReportServices.getRetailerReports({ customerId: [], customerType: "None", employeeId: [ employeeId ], // startDate: moment().startOf('month').format("YYYY-MM-DD"), // endDate: moment().endOf('month').format("YYYY-MM-DD"), startDate: new Date().toISOString().split("T")[0], endDate: new Date().toISOString().split("T")[0], frequency: "cumulative", visitType: "callSummary" }) .then(res => setCallSummaryReports(res)) .catch(err => console.log("Error in /getCallSummaryReports", err)); setCallSummaryReportsLoader(false); }; const getEmployeeReports = async () => { setEmployeeReportsLoader(true); await ReportServices.getEmployeeReports({ employeeId, type: "month", }) .then(res => setEmployeeReports(res)) .catch(err => console.log("Error in /getEmployeeReports", err)); setEmployeeReportsLoader(false); }; const getCatalogReportsForCategory = async () => { setCatalogReportsForCategoryLoader(true); await ReportServices.getCatalogReports({ employeeId, type: "month", catalog_type: "category", }) .then(res => setCatalogReportsForCategory(res)) .catch(err => console.log("Error in /getCatalogReportsForCategory", err)); setCatalogReportsForCategoryLoader(false); }; const getCatalogReportsForProduct = async () => { setCatalogReportsForProductLoader(true); await ReportServices.getCatalogReports({ employeeId, type: "month", catalog_type: "product", }) .then(res => setCatalogReportsForProduct(res)) .catch(err => console.log("Error in /getCatalogReportsForProduct", err)); setCatalogReportsForProductLoader(false); }; const runAllReportAPIs = () => { getTeamAttendanceReports(); getCallSummaryReports(); getEmployeeReports(); getCatalogReportsForCategory(); getCatalogReportsForProduct(); } const reset = () => { window.location.reload(); } useEffect(() => { if (!employeeId) { // notifyError(`Employee ID is not available!`); } runAllReportAPIs(); }, [employeeId]); const chartDataForTeamAttendanceReports = { datasets: [ { data: [ teamAttendanceReports?.present?.count || 0, teamAttendanceReports?.leaves?.count || 0, teamAttendanceReports?.defaulters?.count || 0, ], backgroundColor: ["#00a100", "#8bdce4", "#ff0000"], label: "Attendance Summary", total: teamAttendanceReports?.total }, ], labels: [ `Present (${teamAttendanceReports?.present?.percentage || 0}%)`, `Leaves (${teamAttendanceReports?.leaves?.percentage || 0}%)`, `Defaulters (${teamAttendanceReports?.defaulters?.percentage || 0}%)`, ], }; const chartDataForCovergeCallSummaryReports = { datasets: [ { data: [ (callSummaryReports?.data[0]?.coverage_calls && callSummaryReports?.data[0]?.total_customers ? (callSummaryReports?.data[0]?.coverage_calls / callSummaryReports?.data[0]?.total_customers) * 100 : 0) || 0, (callSummaryReports?.data[0]?.coverage_calls && callSummaryReports?.data[0]?.total_customers ? 100 - (callSummaryReports?.data[0]?.coverage_calls / callSummaryReports?.data[0]?.total_customers) * 100 : 100) || 100, // 44 || 0, // 56 || 0, ], backgroundColor: ["#00a100", "#d5d6d7"], label: "Call Summary - Visited Calls", }, ], labels: [ `Visited Calls`, `Remaining`, ], }; const chartDataForProductiveCallSummaryReports = { datasets: [ { data: [ (callSummaryReports?.data[0]?.productive_customers && callSummaryReports?.data[0]?.total_customers ? (callSummaryReports?.data[0]?.productive_customers / callSummaryReports?.data[0]?.total_customers) * 100 : 0) || 0, (callSummaryReports?.data[0]?.productive_customers && callSummaryReports?.data[0]?.total_customers ? 100 - (callSummaryReports?.data[0]?.productive_customers / callSummaryReports?.data[0]?.total_customers) * 100 : 100) || 100, // 8 || 0, // 92 || 0, ], backgroundColor: ["#3498db", "#d5d6d7"], label: "Call Summary - Productive Calls", }, ], labels: [ `Productive Calls`, `Remaining`, ], }; const handleSubmit = () => { let latestEmployeeId; setLatestDesignationSalesPerson(false); // Check which variable exists in the preferred order of precedence (Sales Person > ASM > RSM) if (salesPerson) { latestEmployeeId = parseInt(salesPerson); setLatestDesignationSalesPerson(true); } else if (asm) { latestEmployeeId = parseInt(asm); } else if (rsm) { latestEmployeeId = parseInt(rsm); } if (latestEmployeeId) { setEmployeeId(latestEmployeeId); runAllReportAPIs(); } else { setEmployeeId(localData?.employeeId ? parseInt(localData?.employeeId) : ""); alert(`Please select an employee from the rsm dropdown first!`); } }; return ( <> Dashboard Reports
{/* RSM Selector */}
{/* */}
{/* ASM Selector */} { asmList?.flatMap(elem => elem?.teamMembers)?.length > 0 &&
{/* */}
} {/* Sales Person Selector */} { salesPersonList?.flatMap(elem => elem?.teamMembers)?.length > 0 &&
{/* */}
}
{ // (false) ? (teamAttendanceReportsLoader == true || callSummaryReportsLoader == true) ?

Loading Summary Charts...

: { latestDesignationSalesPerson != true &&
{ teamAttendanceReportsLoader === true ? (

Loading Attendance Summary Chart...    

) : (

Attendance Summary - Today

{ // teamAttendanceReports?.total > 0 ? a + b, 0); ctx.fillStyle = 'black'; const text = `Total: ${total}`; const textX = Math.round((width - ctx.measureText(text).width) / 1.6); const textY = height / 3; ctx.fillText(text, textX, textY); ctx.save(); }, }, ]} /> // : // "No Attendance data available at the moment!" }
) }
}
{ callSummaryReportsLoader === true ? (

Loading Call Summary Chart...

) : (

Call Summary - Today

{ // callSummaryReports?.data[0]?.coverage_calls > 0 ? ( // ) : ( // "No Visited Calls data available at the moment!" // ) }
{ // callSummaryReports?.data[0]?.productive_customers > 0 ? ( // ) : ( // "No productive call data available at the moment!" // ) }
) }
{ callSummaryReportsLoader == true ? "" :
{/* */} {/* */} {/* */} {/* */}
Metric Value
Total Calls{callSummaryReports?.data[0]?.total_customers || 0}{(parseFloat(callSummaryReports?.data[0]?.total_customers || 0))?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
Visited Calls{callSummaryReports?.data[0]?.coverage_calls || 0}{(parseFloat(callSummaryReports?.data[0]?.coverage_calls || 0))?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
Productive Calls{callSummaryReports?.data[0]?.productive_customers || 0}{(parseFloat(callSummaryReports?.data[0]?.productive_customers || 0))?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
Line Items{callSummaryReports?.data[0]?.total_items || 0}{(parseFloat(callSummaryReports?.data[0]?.total_items || 0))?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
LPC {callSummaryReports?.data[0]?.avg_items_per_order || "0.00"}
Revenue {(parseFloat(callSummaryReports?.data[0]?.revenue || 0))?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
}
}

Top Distributors - MTD

{/* Color-coded boxes between heading and chart */} {/*
{employeeReports?.distributorReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, distributorLimit) ?.map((report, index) => (
{report?.store_name.trim()} - {parseFloat(report?.revenue)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
))}

*/} { employeeReports?.distributorReports?.filter((report) => report?.revenue > 0)?.length > 0 && <>
Distributor Quantity Revenue

}
{employeeReports?.distributorReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, distributorLimit) ?.map((report, index) => (
{/* Color box and name */}
{report?.store_name.trim()}
{/* Quantity */}
{(parseFloat(report?.qty || "0"))?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
{/* Revenue */}
{parseFloat(report?.revenue)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0, })}
))}

{/* Chart */} {employeeReportsLoader === true ? (

) : (
report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, distributorLimit) ?.map((report) => report?.store_name.trim()), datasets: [ { label: "Revenue", data: employeeReports?.distributorReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, distributorLimit) ?.map((report) => report?.revenue), backgroundColor: distributorColors, borderWidth: 1, barThickness: barThickness, }, ], }} options={{ responsive: true, plugins: { legend: { display: false, position: "top", }, title: { display: false, text: "Distributor Revenue", }, tooltip: { callbacks: { label: function (context) { const dataLabel = context.label || ""; const value = context.raw; return `Revenue: ₹${value.toFixed(2)}`; }, }, }, }, scales: { y: { beginAtZero: true, ticks: { callback: (value) => `₹${value}`, }, }, x: { beginAtZero: true, ticks: { callback: function (value, index) { const distReport = employeeReports?.distributorReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, distributorLimit)[index]; return (distReport?.store_name.trim().length <= characterLength) ? distReport?.store_name.trim() : `${distReport?.store_name.trim().slice(0, characterLength)}...`; }, }, } }, }} />
)} { latestDesignationSalesPerson != true &&

L1 Team Sales - MTD

{/* Color-coded boxes between heading and chart */} {/*
{employeeReports?.employeeReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, employeeLimit) ?.map((report, index) => (
{report?.employee_name.trim()} - {parseFloat(report?.revenue)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
))}

*/} { employeeReports?.employeeReports?.filter((report) => report?.revenue > 0)?.length > 0 && <>
Employees Quantity Revenue

}
{employeeReports?.employeeReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, employeeLimit) ?.map((report, index) => (
{report?.employee_name.trim()}
{/* Quantity */}
{(parseFloat(report?.qty || "0"))?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
{/* Revenue */}
{parseFloat(report?.revenue)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
))}

{/* Chart */} {employeeReportsLoader === true ? (

) : (
report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, employeeLimit) ?.map((report) => report?.employee_name.trim()), datasets: [ { label: "Revenue", data: employeeReports?.employeeReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, employeeLimit) ?.map((report) => report?.revenue), backgroundColor: employeeColors, borderWidth: 1, barThickness: barThickness, }, ], }} options={{ responsive: true, plugins: { legend: { display: false, position: "top", }, title: { display: false, text: "Employee Revenue", }, tooltip: { callbacks: { label: function (context) { const dataLabel = context.label || ""; const value = context.raw; return `Revenue: ₹${value.toFixed(2)}`; }, }, }, }, indexAxis: 'y', // This makes the bar chart horizontal scales: { x: { beginAtZero: true, ticks: { callback: (value) => `₹${value}`, }, }, y: { beginAtZero: true, ticks: { callback: function (value, index) { const empReport = employeeReports?.employeeReports ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, employeeLimit)[index]; return (empReport?.employee_name.trim().length <= characterLength) ? empReport?.employee_name.trim() : `${empReport?.employee_name.trim().slice(0, characterLength)}...`; }, }, } }, }} />
)} }
{ (catalogReportsForCategoryLoader === true) ? (

) : ( // //
//

Category Sales - MTD

// {/* Color-coded boxes */} //
// {catalogReportsForCategory?.data // ?.filter((report) => report?.revenue > 0) // ?.sort((a, b) => b?.revenue - a?.revenue) // ?.slice(0, categoryLimit) // ?.map((report, index) => ( //
//
// // {report?.category_name.trim()} - {parseFloat(report?.revenue)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })} ({report?.percentage}%) // //
// ))} //

// {/* Chart */} // {catalogReportsForCategoryLoader === true ? ( //

// ) : ( //
// report?.revenue > 0) // ?.sort((a, b) => b?.revenue - a?.revenue) // ?.slice(0, categoryLimit) // ?.map((report) => `${report?.category_name.trim()} (${report?.percentage}%)`), // datasets: [ // { // label: "Revenue", // data: catalogReportsForCategory?.data // ?.filter((report) => report?.revenue > 0) // ?.sort((a, b) => b?.revenue - a?.revenue) // ?.slice(0, categoryLimit) // ?.map((report) => report?.revenue), // backgroundColor: categoryColors, // Use exact same color array // borderWidth: 1, // barThickness: barThickness, // }, // ], // }} // options={{ // responsive: true, // plugins: { // legend: { // display: false, // position: "top", // }, // title: { // display: false, // text: "Category Revenue", // }, // tooltip: { // callbacks: { // label: function (context) { // const dataLabel = context.label || ""; // const value = context.raw; // return `Revenue: ₹${value.toFixed(2)}`; // }, // }, // }, // }, // indexAxis: 'y', // This makes the bar chart horizontal // scales: { // x: { // beginAtZero: true, // ticks: { // callback: (value) => `₹${value}`, // }, // }, // y: { // beginAtZero: true, // ticks: { // callback: function (value, index) { // const category = catalogReportsForCategory?.data // ?.filter((report) => report?.revenue > 0) // ?.sort((a, b) => b?.revenue - a?.revenue) // ?.slice(0, categoryLimit)[index]; // return (category?.category_name.trim().length <= characterLength) ? category?.category_name.trim() : `${category?.category_name.trim().slice(0, characterLength)}...`; // }, // }, // } // }, // }} // /> //
// )} //
// catalogReportsForCategory?.data?.length > 0 ?

Category Sales - MTD

{/* Category ID */} Category Name Revenue Quantity Percentage {catalogReportsForCategory?.data?.length && catalogReportsForCategory?.data?.map((data, i) => ( {/* {data?.category_id} */} {data?.category_name} {parseFloat(data?.revenue || 0)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })} {/* {data?.qty} */} {parseFloat(data?.qty || 0)?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} {data?.percentage}% ))}
:

No Category Sales available!

) } { (catalogReportsForProductLoader === true) ? (

) : (

Top Selling Products - MTD

{/* Color-coded boxes */} {/*
{catalogReportsForProduct?.data ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, productLimit) ?.map((report, index) => (
{report?.product_name.trim()} - {parseFloat(report?.revenue)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })} ({report?.percentage}%)
))}

*/} { catalogReportsForProduct?.data?.filter((report) => report?.revenue > 0)?.length > 0 && <>
Products Quantity Revenue

}
{catalogReportsForProduct?.data ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, productLimit) ?.map((report, index) => (
{report?.product_name.trim()}
{/* Quantity */}
{(parseFloat(report?.qty || "0"))?.toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
{/* Revenue */}
{parseFloat(report?.revenue)?.toLocaleString('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 0, maximumFractionDigits: 0 })} ({report?.percentage}%)
))}

{/* Chart */} {catalogReportsForProductLoader === true ? (

) : (
report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, productLimit) ?.map((report) => `${report?.product_name.trim()} (${report?.percentage}%)`), datasets: [ { label: "Revenue", data: catalogReportsForProduct?.data ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, productLimit) ?.map((report) => parseFloat(report?.revenue)), backgroundColor: productColors, // Use exact same color array borderWidth: 1, barThickness: barThickness, }, ], }} options={{ responsive: true, plugins: { legend: { display: false, position: "top", }, title: { display: false, text: "Product Revenue", }, tooltip: { callbacks: { label: function (context) { const dataLabel = context.label || ""; const value = context.raw; return `Revenue: ₹${value.toFixed(2)}`; }, }, }, }, scales: { x: { beginAtZero: true, ticks: { callback: function (value, index) { const product = catalogReportsForProduct?.data ?.filter((report) => report?.revenue > 0) ?.sort((a, b) => b?.revenue - a?.revenue) ?.slice(0, productLimit)[index]; return (product?.product_name.trim().length <= characterLength) ? product?.product_name.trim() : `${product?.product_name.trim().slice(0, characterLength)}...`; }, }, }, y: { beginAtZero: true, ticks: { callback: function (value) { // Add the rupee symbol to the y-axis ticks return `₹${value.toLocaleString()}`; }, }, }, }, }} />
)}
) }
); }; export default DashboardReport;