From 8188dab2b4082337dab508090df81573c370c855 Mon Sep 17 00:00:00 2001 From: Dilmurod Date: Mon, 18 Aug 2025 15:28:18 +0500 Subject: [PATCH] optimized statistics page --- package.json | 4 +- src/API/LayoutApi/statistic.ts | 13 +- src/Components/Statistics/Statistic.tsx | 856 ++---------------- .../Statistics/StatisticGeneral.tsx | 146 +++ src/Components/Statistics/StatisticTable.tsx | 136 --- src/Components/Statistics/StatisticTeam.tsx | 324 +++++++ .../Statistics/StatisticTeamTable.tsx | 98 -- .../Statistics/StatisticsChekers.tsx | 249 +++++ .../Statistics/StatisticsSupportTable.tsx | 81 -- .../Statistics/StatisticsTechSupport.tsx | 206 +++++ src/Hooks/{Stats => Statistics}/index.ts | 32 +- src/types/Statistic/TStat.ts | 26 +- yarn.lock | 5 + 13 files changed, 1055 insertions(+), 1121 deletions(-) create mode 100644 src/Components/Statistics/StatisticGeneral.tsx delete mode 100644 src/Components/Statistics/StatisticTable.tsx create mode 100644 src/Components/Statistics/StatisticTeam.tsx delete mode 100644 src/Components/Statistics/StatisticTeamTable.tsx create mode 100644 src/Components/Statistics/StatisticsChekers.tsx delete mode 100644 src/Components/Statistics/StatisticsSupportTable.tsx create mode 100644 src/Components/Statistics/StatisticsTechSupport.tsx rename src/Hooks/{Stats => Statistics}/index.ts (77%) diff --git a/package.json b/package.json index 93f218c..1e58400 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dayjs": "^1.11.10", "file-saver": "^2.0.5", "final-form": "^4.20.10", + "lodash": "^4.17.21", "moment-timezone": "^0.5.43", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -63,6 +64,7 @@ ] }, "devDependencies": { - "@babel/plugin-proposal-private-property-in-object": "^7.21.11" + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@types/lodash": "^4.17.20" } } diff --git a/src/API/LayoutApi/statistic.ts b/src/API/LayoutApi/statistic.ts index 75a2bc3..afcd06d 100644 --- a/src/API/LayoutApi/statistic.ts +++ b/src/API/LayoutApi/statistic.ts @@ -114,15 +114,14 @@ export const statController = { async generalChart(filterObject: TGeneralChartGetParams) { const params = { ...filterObject }; - if (!!filterObject.start_date) params.start_date = filterObject.start_date; - if (!!filterObject.end_date) params.end_date = filterObject.end_date; + if (filterObject.start_date) params.start_date = filterObject.start_date; + if (filterObject.end_date) params.end_date = filterObject.end_date; - const { data } = await instance.get( - `stats/general-stats/`, - { - params, - } + const { data } = await instance.get( + "stats/general-stats", + { params } ); + return data; }, diff --git a/src/Components/Statistics/Statistic.tsx b/src/Components/Statistics/Statistic.tsx index 47bb418..dbbdcb8 100644 --- a/src/Components/Statistics/Statistic.tsx +++ b/src/Components/Statistics/Statistic.tsx @@ -1,56 +1,8 @@ -import { useEffect, useRef, useState } from "react"; -import { - QueryObserverResult, - RefetchOptions, - RefetchQueryFilters, -} from "react-query"; -import { statController } from "../../API/LayoutApi/statistic"; -import { useTeamData } from "../../Hooks/Teams/index"; -import { - useCreatorsData, - useGeneralChartData, - useStatTeamData, - useStatsData, - useTeamChartData, -} from "../../Hooks/Stats"; -import { - TGeneralChartData, - TStatCreators, - TStatTeam, - TteamChartData, -} from "../../types/Statistic/TStat"; -import StatTable from "./StatisticTable"; -import StatTeamTable from "./StatisticTeamTable"; +import { useState } from "react"; +import StatisticsChekers from "./StatisticsChekers"; +import StatTeamTable from "./StatisticTeam"; import dayjs from "dayjs"; -import { - Button, - DatePicker, - DatePickerProps, - Input, - Select, - Space, - Tabs, - Typography, - theme, -} from "antd"; -import { LeftOutlined, RightOutlined, SearchOutlined } from "@ant-design/icons"; -import TabPane from "antd/es/tabs/TabPane"; -import api from "../../API/api"; -// @ts-ignore -import IconSearch from "../../assets/searchIcon.png"; -import { - Bar, - BarChart, - CartesianGrid, - Legend, - Line, - LineChart, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from "recharts"; - +import { DatePicker, DatePickerProps, Tabs, TabsProps, Typography } from "antd"; import generalActive from "../../assets/generalActive.svg"; import general from "../../assets/general.svg"; import checkersIcon from "../../assets/checkerIcon.svg"; @@ -60,83 +12,23 @@ import teamsIconActive from "../../assets/teamsIconActive.svg"; import techSupports from "../../assets/techsupportIcon.svg"; import techSupportsActive from "../../assets/techsupportIconActive.svg"; -import StatisticsSupportTable from "./StatisticsSupportTable"; +import StatisticGeneral from "./StatisticGeneral"; +import StatisticsTechSupport from "./StatisticsTechSupport"; -const Stat = () => { +const Statistic = () => { const now = dayjs(); const { RangePicker } = DatePicker; const moment = require("moment"); const currentDate = moment(); - const nextMonth = currentDate.clone().add(1, "months"); const start_date = `${currentDate.format("YYYY-MM")}-01 00:00:00`; const end_date = `${currentDate .clone() .endOf("month") .format("YYYY-MM-DD")} 23:59:59`; - const { token } = theme.useToken(); - - const [search, setSearch] = useState(""); - const [SupportSearch, setSupportSearch] = useState(""); - const [team, setTeam] = useState(""); + const [activeTab, setActiveTab] = useState("1"); const [startDate, setStartDate] = useState(start_date); const [endDate, setEndDate] = useState(end_date); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(() => { - const saved = localStorage.getItem("general_pageSize"); - return saved ? Number(saved) : 15; - }); - - const [forSalary, setForSalary] = useState(true); - - const pageSizeOptions = [15, 20, 30, 40, 50]; - - const handlePageSizeChange = (value: number) => { - setPageSize(value); - setPage(1); - }; - - useEffect(() => { - localStorage.setItem("general_pageSize", String(pageSize)); - }, [pageSize]); - - const Next = () => { - const a = Number(page) + 1; - setPage(a); - }; - const Previos = () => { - Number(page); - if (page > 1) { - const a = Number(page) - 1; - setPage(a); - } - }; - - const teamData = useTeamData({}); - const teamOptions: { label: string; value: any }[] | undefined = - teamData?.data?.map((item: any) => ({ - label: item?.name, - value: item?.name, - })); - const additionalOption = { - label: "all", - value: "", - }; - if (teamOptions) { - teamOptions.unshift(additionalOption); - } - - const handleSave = (a: string) => { - const trimmedStartDate = startDate.slice(0, 10); - const trimmedEndDate = endDate.slice(0, 10); - const fileName = `${trimmedStartDate}-${trimmedEndDate}`; - if (a === "team") { - const teamName = `${team}_${fileName}`; - statController.saveUsersStats(teamName, startDate, endDate, team); - } else { - statController.saveTeamStats(fileName, startDate, endDate); - } - }; const datePick = (dates: any) => { if (dates && dates[0] && dates[1]) { @@ -152,13 +44,6 @@ const Stat = () => { } else { const firstDate = date.startOf("month"); const secondDate = date?.add(1, "month").startOf("month"); - // const yearStart = Number(firstDate?.year()); - // const monthStart = Number(firstDate?.month()) + 1; - // const yearEnd = Number(secondDate?.year()); - // const monthEnd = Number(secondDate?.month()) + 1; - - // setStartDate(`${yearStart}-${monthStart}-01 00:00:00`); - // setEndDate(`${yearEnd}-${monthEnd}-01 00:00:00`); const formattedStartDate = firstDate.format("YYYY-MM-01 00:00:00"); const formattedEndDate = secondDate @@ -166,183 +51,78 @@ const Stat = () => { .format("YYYY-MM-DD 23:59:59"); setStartDate(formattedStartDate); setEndDate(formattedEndDate); - setForSalary(true); - } - }; - - const { data, refetch, isLoading } = useStatsData({ - search: search, - team: team, - start_date: startDate, - end_date: endDate, - for_salary: forSalary, - page: page, - page_size: pageSize, - }); - - const TeamData = useStatTeamData({ - page: page, - page_size: pageSize, - search: "", - start_date: startDate, - end_date: endDate, - }); - - const CreatorsData = useCreatorsData({ - page: page, - page_size: pageSize, - search: SupportSearch, - start_date: startDate, - end_date: endDate, - }); - - interface TeamschartDataType { - data?: TteamChartData[]; - refetch: ( - options?: (RefetchOptions & RefetchQueryFilters) | undefined - ) => Promise>; - isLoading: boolean; - } - - const today = dayjs().endOf("day"); - - let finalEndDate = dayjs(endDate); - - if (finalEndDate.isAfter(today)) { - finalEndDate = today; - } - const formattedEndDate = finalEndDate.format("YYYY-MM-DD HH:mm:ss"); - - const TeamschartData: TeamschartDataType = useTeamChartData({ - start_date: startDate, - end_date: formattedEndDate, - }); - - const timerRef = useRef(null); - const handleSearchChange = (e: React.ChangeEvent) => { - if (timerRef.current) { - clearTimeout(timerRef.current); } - - const searchText = e.target.value; - timerRef.current = setTimeout(() => { - setSearch(searchText); - }, 1000); }; - const handleTechSupportSearchChange = ( - e: React.ChangeEvent - ) => { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - - const searchText = e.target.value; - timerRef.current = setTimeout(() => { - setSupportSearch(searchText); - }, 1000); - }; - - // const theme = localStorage.getItem("theme") === "true" ? true : false; - - const [activeTab, setActiveTab] = useState("1"); - const disabledDate = (current: any) => { return current && current >= moment().add(1, "month").startOf("month"); }; - const predefinedColors = [ - "#ff2600", // Tomato - "#FF4500", // OrangeRed - "#FF1493", // DeepPink - "#006800", // LimeGreen - "#3CB371", // MediumSeaGreen - "#00BFFF", // DeepSkyBlue - "#FFD700", // Gold - "#F08080", // LightCoral - "#8A2BE2", // BlueViolet - "#FFB6C1", // LightPink + const items: TabsProps["items"] = [ + { + key: "1", + label: ( + + icon + General + + ), + children: , + }, + { + key: "2", + label: ( + + icon + Tech Supports + + ), + children: ( + + ), + }, + { + key: "3", + label: ( + + icon + Checkers + + ), + children: , + }, + { + key: "4", + label: ( + + icon + Teams + + ), + children: , + }, ]; - function updateLines(chartData: any, predefinedColors: any) { - if (!chartData || chartData.length === 0) { - return []; // Agar chartData mavjud bo'lmasa, bo'sh massiv qaytariladi - } - - const keys = Object.keys(chartData[0]).filter((key) => key !== "date"); - - // Har bir key uchun chiziqlar yaratish - const newLines = keys.flatMap((key, index) => { - const color = predefinedColors[index % predefinedColors.length]; // Ranglarni aylantirib foydalanadi - - // Har bir "key" uchun ikkita chiziq (biri number_of_tasks, ikkinchisi total_points) - return [ - { - key: `${key}.total_points`, - color, - name: `${key} points`, - legend_name: `${key}`, - }, - ]; - }); - - return newLines; - } - - const lines = TeamschartData.data - ? updateLines(TeamschartData.data, predefinedColors) - : []; - - const [chartData, setChartData] = useState([]); - const [summaryData, setSummaryData] = useState | null>( - null - ); - - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchData = async () => { - try { - const today = dayjs().endOf("day"); - - let finalEndDate = dayjs(endDate); - - if (finalEndDate.isAfter(today)) { - finalEndDate = today; - } - const formattedEndDate = finalEndDate.format("YYYY-MM-DD HH:mm:ss"); - - const response = await api.get("stats/general-stats", { - params: { start_date: startDate, end_date: formattedEndDate }, - }); - if (response.data.daily_stats) { - setChartData(response.data.daily_stats); - } - if (response.data.summary) { - setSummaryData(response.data.summary); - } - setLoading(false); - } catch (error) { - console.error("Error fetching daily stats:", error); - setLoading(false); - } - }; - - fetchData(); - }, [startDate, endDate]); - - const formatDate = (dateString: string): string => { - const date = new Date(dateString); - return new Intl.DateTimeFormat("en-EN", { - day: "2-digit", - month: "short", - }).format(date); - }; - return (
Statistics @@ -354,8 +134,6 @@ const Stat = () => { disabledDate={disabledDate} defaultValue={now} style={{ marginRight: 10, width: 120, marginBottom: 10 }} - // value={datePickerValue} - // defaultValue={dayjs().startOf("month")} />
@@ -364,502 +142,10 @@ const Stat = () => { defaultActiveKey="1" activeKey={activeTab} onChange={(key) => setActiveTab(key)} - > - - icon - General - - } - key="1" - > -
-
- {summaryData && ( -
-

Total

- {summaryData.total} -

Tasks

-
- )} - {summaryData && ( -
-

Active

- {summaryData.total_completed} -

Tasks

-
- )} - {summaryData && ( -
-

Inactive

- {summaryData.total_incomplete} -

Tasks

-
- )} -
- - - - - - - - - - - - -
-
- - - icon - Tech Supports - - } - key="2" - > - - {/*
- - -
*/} -
- } - onChange={handleTechSupportSearchChange} - /> -
-
- - - - { - let num = e.target.value; - if (Number(num) && num !== "0") { - setPage(Number(num)); - } - }} - /> - - - -
- - - icon - Checkers - - } - key="3" - > - - {/*
- - -
*/} -
- } - onChange={handleSearchChange} - /> -
- ({ - label: `${size}`, - value: size, - }))} - /> - - - { - let num = e.target.value; - if (Number(num) && num !== "0") { - setPage(Number(num)); - } - }} - /> - - - -
- - icon - Teams - - } - key="4" - > - - - -
- - - - - - - ({ - value: line.legend_name, // Legenddagi yozuv sifatida `name` beriladi - type: "line", - id: line.key, - color: line.color, - }))} - /> - - {lines.map((line, index) => ( - - ))} - - -
- - - { - let num = e.target.value; - if (Number(num) && num !== "0") { - setPage(Number(num)); - } - }} - /> - - - -
- + items={items} + />
); }; -export default Stat; +export default Statistic; diff --git a/src/Components/Statistics/StatisticGeneral.tsx b/src/Components/Statistics/StatisticGeneral.tsx new file mode 100644 index 0000000..044d74d --- /dev/null +++ b/src/Components/Statistics/StatisticGeneral.tsx @@ -0,0 +1,146 @@ +import dayjs from "dayjs"; +import { + CartesianGrid, + Legend, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +import { useGeneralStats } from "../../Hooks/Statistics"; + +interface StatisticGeneralProps { + startDate: string; + endDate: string; +} + +const StatisticGeneral: React.FC = ({ + startDate, + endDate, +}) => { + const formatDate = (dateString: string): string => { + const date = new Date(dateString); + return new Intl.DateTimeFormat("en-EN", { + day: "2-digit", + month: "short", + }).format(date); + }; + + const today = dayjs().endOf("day"); + let finalEndDate = dayjs(endDate); + + if (finalEndDate.isAfter(today)) { + finalEndDate = today; + } + const formattedEndDate = finalEndDate.format("YYYY-MM-DD HH:mm:ss"); + + const { data, isLoading, refetch } = useGeneralStats({ + start_date: startDate, + end_date: formattedEndDate, + }); + + return ( +
+
+
+ {data?.summary && ( +
+

Total

+ {data?.summary?.total} +

Tasks

+
+ )} + {data?.summary && ( +
+

Active

+ {data?.summary?.total_completed} +

Tasks

+
+ )} + {data?.summary && ( +
+

Inactive

+ {data?.summary?.total_incomplete} +

Tasks

+
+ )} +
+ + + + + + + + + + + + +
+
+ ); +}; + +export default StatisticGeneral; diff --git a/src/Components/Statistics/StatisticTable.tsx b/src/Components/Statistics/StatisticTable.tsx deleted file mode 100644 index 7b04a07..0000000 --- a/src/Components/Statistics/StatisticTable.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { Table, Tooltip } from "antd"; -import { TStat } from "../../types/Statistic/TStat"; -import { - QueryObserverResult, - RefetchOptions, - RefetchQueryFilters, -} from "react-query"; -import { QuestionCircleOutlined, QuestionOutlined } from "@ant-design/icons"; -// @ts-ignore -import tagIcon from "../../assets/tagIcon.svg"; - -import { theme } from "antd"; - -const StatTable = ({ - data, - isLoading, - refetch, -}: { - refetch: ( - options?: (RefetchOptions & RefetchQueryFilters) | undefined - ) => Promise>; - data: any; - isLoading: boolean; -}) => { - const { token } = theme.useToken(); - - return ( -
- ({ - no: i + 1, - ...u, - }))} - columns={[ - { - title: , - dataIndex: "no", - key: "no", - width: "5%", - }, - { - title: "Support specialist", - dataIndex: "username", - key: "username", - }, - { - title: "Team", - dataIndex: "team_name", - key: "team_name ", - }, - { - title: "Tasks", - dataIndex: "number_of_tasks", - key: "number_of_tasks", - sorter: (a: any, b: any) => a.number_of_tasks - b.number_of_tasks, - sortDirections: ["ascend", "descend"], - }, - { - title: "Points", - dataIndex: "total_points", - key: "total_points", - sorter: (a: any, b: any) => a.total_points - b.total_points, - sortDirections: ["ascend", "descend"], - }, - // { - // title: ( - //
- // Salary    - // - // - // - //
- // ), - // dataIndex: "salary", - // key: "salary", - // render: (text: string, record: any) => ( - // - // {record.salary_type === "hybrid" ? ( - //

- // Fixed Amount: $ - // {record.salary_base_amount} - //

- // ) : ( - // "" - // )} - //

- // Performance based amount: $ - // {record.performance_based_amount} - //

- // - // } - // overlayStyle={{ - // maxWidth: "700px", - // }} - // > - // ${record.salary} - //
- // ), - // sorter: (a: any, b: any) => a.salary - b.total_points, - // sortDirections: ["ascend", "descend"], - // }, - ]} - // pagination={{ - // pageSize: 10, - // size: "default", - // style: { - // margin: 0, - // justifyContent: "end", - // position: "fixed", - // bottom: 0, - // left: 0, - // width: "100%", - // backgroundColor: token.colorBgContainer, - // boxShadow: "0 4px 8px rgba(0, 0, 0, 0.4)", - // padding: "10px 0", - // zIndex: 1000, - // }, - // showLessItems: true, - // }} - pagination={false} - rowClassName={(record, index) => - index % 2 === 0 ? "odd-row" : "even-row" - } - bordered - /> - - ); -}; - -export default StatTable; - -// The calculation of salary begins at the start of the month and continues to the current day. Select a month to review salary details for prior periods. diff --git a/src/Components/Statistics/StatisticTeam.tsx b/src/Components/Statistics/StatisticTeam.tsx new file mode 100644 index 0000000..9d4bc7f --- /dev/null +++ b/src/Components/Statistics/StatisticTeam.tsx @@ -0,0 +1,324 @@ +import { Button, Input, Select, Space, Table, Tag } from "antd"; +import { TStatTeam, TteamChartData } from "../../types/Statistic/TStat"; +import { + QueryObserverResult, + RefetchOptions, + RefetchQueryFilters, +} from "react-query"; +import { + CartesianGrid, + Legend, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +import { RightOutlined, LeftOutlined } from "@ant-design/icons"; + +import { theme } from "antd"; +import tagIcon from "../../assets/tagIcon.svg"; +import { useStatTeamData, useTeamChartData } from "../../Hooks/Statistics"; +import { useEffect, useState } from "react"; +import dayjs from "dayjs"; + +interface StatisticTeamProps { + startDate: string; + endDate: string; +} + +const StatisticTeam: React.FC = ({ + startDate, + endDate, +}) => { + const [page, setPage] = useState(1); + + const [pageSize, setPageSize] = useState(() => { + const saved = localStorage.getItem("general_pageSize"); + return saved ? Number(saved) : 15; + }); + + const pageSizeOptions = [15, 20, 30, 40, 50]; + + const handlePageSizeChange = (value: number) => { + setPageSize(value); + setPage(1); + }; + + useEffect(() => { + localStorage.setItem("general_pageSize", String(pageSize)); + }, [pageSize]); + + const Next = () => { + const a = Number(page) + 1; + setPage(a); + }; + const Previos = () => { + Number(page); + if (page > 1) { + const a = Number(page) - 1; + setPage(a); + } + }; + + const { data, isLoading, refetch } = useStatTeamData({ + page: page, + page_size: pageSize, + start_date: startDate, + end_date: endDate, + }); + + const today = dayjs().endOf("day"); + + let finalEndDate = dayjs(endDate); + + if (finalEndDate.isAfter(today)) { + finalEndDate = today; + } + const formattedEndDate = finalEndDate.format("YYYY-MM-DD HH:mm:ss"); + + interface TeamschartDataType { + data?: TteamChartData[]; + refetch: ( + options?: (RefetchOptions & RefetchQueryFilters) | undefined + ) => Promise>; + isLoading: boolean; + } + + const TeamschartData: TeamschartDataType = useTeamChartData({ + start_date: startDate, + end_date: formattedEndDate, + }); + + const predefinedColors = [ + "#ff2600", + "#FF4500", + "#FF1493", + "#006800", + "#3CB371", + "#00BFFF", + "#FFD700", + "#F08080", + "#8A2BE2", + "#FFB6C1", + ]; + + function updateLines(chartData: any, predefinedColors: any) { + if (!chartData || chartData.length === 0) { + return []; + } + + const keys = Object.keys(chartData[0]).filter((key) => key !== "date"); + + const newLines = keys.flatMap((key, index) => { + const color = predefinedColors[index % predefinedColors.length]; + + return [ + { + key: `${key}.total_points`, + color, + name: `${key} points`, + legend_name: `${key}`, + }, + ]; + }); + + return newLines; + } + + const lines = TeamschartData.data + ? updateLines(TeamschartData.data, predefinedColors) + : []; + + const formatDate = (dateString: string): string => { + const date = new Date(dateString); + return new Intl.DateTimeFormat("en-EN", { + day: "2-digit", + month: "short", + }).format(date); + }; + + const { token } = theme.useToken(); + return ( + <> +
+
({ + no: i + 1, + ...u, + }))} + columns={[ + { + title: , + dataIndex: "no", + }, + { + title: "Team", + dataIndex: "name", + }, + { + title: "Total tasks", + dataIndex: "number_of_tasks", + }, + { + title: "Total points", + dataIndex: "total_points", + }, + { + title: "Is Active", + dataIndex: "is_active", + render: (tag: boolean) => ( + + {tag ? "True" : "False"} + + ), + filters: [ + { + text: "True", + value: true, + }, + { + text: "False", + value: false, + }, + ], + onFilter: ( + value: string | number | boolean, + record: TStatTeam + ) => { + return record.is_active === value; + }, + }, + ]} + pagination={false} + rowClassName={(record, index) => + index % 2 === 0 ? "odd-row" : "even-row" + } + bordered + /> + +
+ + + + + + + ({ + value: line.legend_name, + type: "line", + id: line.key, + color: line.color, + }))} + /> + + {lines.map((line, index) => ( + + ))} + + +
+ + + { + let num = e.target.value; + if (Number(num) && num !== "0") { + setPage(Number(num)); + } + }} + /> + + + + + ); +}; + +export default StatisticTeam; diff --git a/src/Components/Statistics/StatisticTeamTable.tsx b/src/Components/Statistics/StatisticTeamTable.tsx deleted file mode 100644 index 9917d6d..0000000 --- a/src/Components/Statistics/StatisticTeamTable.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { Table, Tag } from "antd"; -import { TStatTeam } from "../../types/Statistic/TStat"; -import { - QueryObserverResult, - RefetchOptions, - RefetchQueryFilters, -} from "react-query"; - -import { theme } from "antd"; -// @ts-ignore -import tagIcon from "../../assets/tagIcon.svg"; -const StatTeamTable = ({ - data, - isLoading, - refetch, -}: { - refetch: ( - options?: (RefetchOptions & RefetchQueryFilters) | undefined - ) => Promise>; - data: any; - isLoading: boolean; -}) => { - const { token } = theme.useToken(); - return ( -
-
({ - no: i + 1, - ...u, - }))} - columns={[ - { - title: , - dataIndex: "no", - }, - { - title: "Team", - dataIndex: "name", - }, - { - title: "Total tasks", - dataIndex: "number_of_tasks", - }, - { - title: "Total points", - dataIndex: "total_points", - }, - { - title: "Is Active", - dataIndex: "is_active", - render: (tag: boolean) => ( - - {tag ? "True" : "False"} - - ), - filters: [ - { - text: "True", - value: true, - }, - { - text: "False", - value: false, - }, - ], - onFilter: (value: string | number | boolean, record: TStatTeam) => { - return record.is_active === value; - }, - }, - ]} - // pagination={{ - // pageSize: 10, - // size: "default", - // style: { - // margin: 0, - // justifyContent: "end", - // position: "fixed", - // bottom: 0, - // left: 0, - // width: "100%", - // backgroundColor: token.colorBgContainer, - // boxShadow: "0 4px 8px rgba(0, 0, 0, 0.4)", - // padding: "10px 0", - // zIndex: 1000, - // }, - // }} - pagination={false} - rowClassName={(record, index) => - index % 2 === 0 ? "odd-row" : "even-row" - } - /> - - ); -}; - -export default StatTeamTable; diff --git a/src/Components/Statistics/StatisticsChekers.tsx b/src/Components/Statistics/StatisticsChekers.tsx new file mode 100644 index 0000000..525c8f8 --- /dev/null +++ b/src/Components/Statistics/StatisticsChekers.tsx @@ -0,0 +1,249 @@ +import { Button, Input, Select, Space, Table, Tooltip } from "antd"; +import { LeftOutlined, RightOutlined, SearchOutlined } from "@ant-design/icons"; +import tagIcon from "../../assets/tagIcon.svg"; + +import { theme } from "antd"; +import { useEffect, useMemo, useState } from "react"; +import { useTeamData } from "../../Hooks/Teams"; +import { useStatsData } from "../../Hooks/Statistics"; +import { statController } from "../../API/LayoutApi/statistic"; +import { debounce } from "lodash"; + +interface StatisticsChekersProps { + startDate: string; + endDate: string; +} + +const StatisticsChekers: React.FC = ({ + startDate, + endDate, +}) => { + const [team, setTeam] = useState(""); + const [search, setSearch] = useState(""); + const { token } = theme.useToken(); + const teamData = useTeamData({}); + const teamOptions: { label: string; value: any }[] | undefined = + teamData?.data?.map((item: any) => ({ + label: item?.name, + value: item?.name, + })); + + const [page, setPage] = useState(1); + + const [pageSize, setPageSize] = useState(() => { + const saved = localStorage.getItem("general_pageSize"); + return saved ? Number(saved) : 15; + }); + + const pageSizeOptions = [15, 20, 30, 40, 50]; + + const handlePageSizeChange = (value: number) => { + setPageSize(value); + setPage(1); + }; + + useEffect(() => { + localStorage.setItem("general_pageSize", String(pageSize)); + }, [pageSize]); + + const Next = () => { + const a = Number(page) + 1; + setPage(a); + }; + const Previos = () => { + Number(page); + if (page > 1) { + const a = Number(page) - 1; + setPage(a); + } + }; + + const handleSearchChange = (e: React.ChangeEvent) => { + debouncedSearch(e.target.value); + }; + + const debouncedSearch = useMemo( + () => + debounce((value: string) => { + setSearch(value); + }, 1000), + [] + ); + + useEffect(() => { + return () => { + debouncedSearch.cancel(); + }; + }, [debouncedSearch]); + + const handleSave = (a: string) => { + const trimmedStartDate = startDate.slice(0, 10); + const trimmedEndDate = endDate.slice(0, 10); + const fileName = `${trimmedStartDate}-${trimmedEndDate}`; + if (a === "team") { + const teamName = `${team}_${fileName}`; + statController.saveUsersStats(teamName, startDate, endDate, team); + } else { + statController.saveTeamStats(fileName, startDate, endDate); + } + }; + + const { data, refetch, isLoading } = useStatsData({ + search: search, + team: team, + start_date: startDate, + end_date: endDate, + page: page, + page_size: pageSize, + }); + + return ( + <> +
+
+ } + onChange={handleSearchChange} + /> +
+
({ + no: i + 1, + ...u, + }))} + columns={[ + { + title: , + dataIndex: "no", + key: "no", + width: "5%", + }, + { + title: "Support specialist", + dataIndex: "username", + key: "username", + }, + { + title: "Team", + dataIndex: "team_name", + key: "team_name ", + }, + { + title: "Tasks", + dataIndex: "number_of_tasks", + key: "number_of_tasks", + sorter: (a: any, b: any) => a.number_of_tasks - b.number_of_tasks, + sortDirections: ["ascend", "descend"], + }, + { + title: "Points", + dataIndex: "total_points", + key: "total_points", + sorter: (a: any, b: any) => a.total_points - b.total_points, + sortDirections: ["ascend", "descend"], + }, + ]} + pagination={false} + rowClassName={(record, index) => + index % 2 === 0 ? "odd-row" : "even-row" + } + bordered + /> + + + + + { + let num = e.target.value; + if (Number(num) && num !== "0") { + setPage(Number(num)); + } + }} + /> + + + + + ); +}; + +export default StatisticsChekers; diff --git a/src/Components/Statistics/StatisticsSupportTable.tsx b/src/Components/Statistics/StatisticsSupportTable.tsx deleted file mode 100644 index 20213ca..0000000 --- a/src/Components/Statistics/StatisticsSupportTable.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; -import { Table, Tag, Tooltip } from "antd"; -import { theme } from "antd"; -import { - QueryObserverResult, - RefetchOptions, - RefetchQueryFilters, -} from "react-query"; -import { TStatCreators } from "../../types/Statistic/TStat"; -import tagIcon from "../../assets/tagIcon.svg"; - -const StatisticsSupportTable = ({ - data, - isLoading, - refetch, -}: { - refetch: ( - options?: (RefetchOptions & RefetchQueryFilters) | undefined - ) => Promise>; - data: any; - isLoading: boolean; -}) => { - const { token } = theme.useToken(); - return ( -
-
({ - no: i + 1, - ...u, - }))} - columns={[ - { - title: , - dataIndex: "no", - key: "no", - width: "5%", - }, - { - title: "Support specialist", - dataIndex: "username", - render: (text, record) => { - return record.full_name.trim() - ? record.full_name - : record.username; - }, - }, - { - title: "Total tasks", - dataIndex: "number_of_tasks", - }, - ]} - // pagination={{ - // pageSize: 10, - // size: "default", - // style: { - // margin: 0, - // justifyContent: "end", - // position: "fixed", - // bottom: 0, - // left: 0, - // width: "100%", - // backgroundColor: token.colorBgContainer, - // boxShadow: "0 4px 8px rgba(0, 0, 0, 0.4)", - // padding: "10px 0", - // zIndex: 1000, - // }, - // showLessItems: true, - // }} - pagination={false} - rowClassName={(record, index) => - index % 2 === 0 ? "odd-row" : "even-row" - } - bordered - /> - - ); -}; - -export default StatisticsSupportTable; diff --git a/src/Components/Statistics/StatisticsTechSupport.tsx b/src/Components/Statistics/StatisticsTechSupport.tsx new file mode 100644 index 0000000..1f79ef6 --- /dev/null +++ b/src/Components/Statistics/StatisticsTechSupport.tsx @@ -0,0 +1,206 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { Button, Input, Select, Space, Table, Tag, Tooltip } from "antd"; +import { theme } from "antd"; +import tagIcon from "../../assets/tagIcon.svg"; +import { useCreatorsData } from "../../Hooks/Statistics"; + +import { debounce } from "lodash"; + +import { SearchOutlined, RightOutlined, LeftOutlined } from "@ant-design/icons"; + +interface StatisticsTechSupportProps { + startDate: string; + endDate: string; +} + +const StatisticsTechSupport: React.FC = ({ + startDate, + endDate, +}) => { + const { token } = theme.useToken(); + + const [SupportSearch, setSupportSearch] = useState(""); + + const [page, setPage] = useState(1); + + const [pageSize, setPageSize] = useState(() => { + const saved = localStorage.getItem("general_pageSize"); + return saved ? Number(saved) : 15; + }); + + const pageSizeOptions = [15, 20, 30, 40, 50]; + + const handlePageSizeChange = (value: number) => { + setPageSize(value); + setPage(1); + }; + + useEffect(() => { + localStorage.setItem("general_pageSize", String(pageSize)); + }, [pageSize]); + + const Next = () => { + const a = Number(page) + 1; + setPage(a); + }; + const Previos = () => { + Number(page); + if (page > 1) { + const a = Number(page) - 1; + setPage(a); + } + }; + + const handleTechSupportSearchChange = ( + e: React.ChangeEvent + ) => { + debouncedSearch(e.target.value); + }; + + const debouncedSearch = useMemo( + () => + debounce((value: string) => { + setSupportSearch(value); + }, 1000), + [] + ); + + useEffect(() => { + return () => { + debouncedSearch.cancel(); + }; + }, [debouncedSearch]); + + const { data, isLoading } = useCreatorsData({ + page: page, + page_size: pageSize, + search: SupportSearch, + start_date: startDate, + end_date: endDate, + }); + + return ( + <> +
+
+ } + onChange={handleTechSupportSearchChange} + /> +
+
+ +
({ + no: i + 1, + ...u, + }))} + columns={[ + { + title: , + dataIndex: "no", + key: "no", + width: "5%", + }, + { + title: "Support specialist", + dataIndex: "username", + render: (text, record) => { + return record.full_name.trim() + ? record.full_name + : record.username; + }, + }, + { + title: "Total tasks", + dataIndex: "number_of_tasks", + }, + ]} + pagination={false} + rowClassName={(record, index) => + index % 2 === 0 ? "odd-row" : "even-row" + } + bordered + /> + + + + { + let num = e.target.value; + if (Number(num) && num !== "0") { + setPage(Number(num)); + } + }} + /> + + + + + ); +}; + +export default StatisticsTechSupport; diff --git a/src/Hooks/Stats/index.ts b/src/Hooks/Statistics/index.ts similarity index 77% rename from src/Hooks/Stats/index.ts rename to src/Hooks/Statistics/index.ts index bbecd6b..03b1428 100644 --- a/src/Hooks/Stats/index.ts +++ b/src/Hooks/Statistics/index.ts @@ -5,6 +5,7 @@ import { } from "./../../API/LayoutApi/statistic"; import { useQuery } from "react-query"; import { TStatGetParams, statController } from "../../API/LayoutApi/statistic"; +import { TGeneralChartData } from "../../types/Statistic/TStat"; export const useStatsData = ({ search, @@ -84,16 +85,16 @@ export const useTeamChartData = ({ { refetchOnWindowFocus: false } ); }; -export const useGeneralChartData = ({ - start_date, - end_date, -}: TGeneralChartGetParams) => { - return useQuery( - [`stats/general-stats/`, start_date, end_date], - () => statController.generalChart({ start_date, end_date }), - { refetchOnWindowFocus: false } - ); -}; +// export const useGeneralChartData = ({ +// start_date, +// end_date, +// }: TGeneralChartGetParams) => { +// return useQuery( +// [`stats/general-stats/`, start_date, end_date], +// () => statController.generalChart({ start_date, end_date }), +// { refetchOnWindowFocus: false } +// ); +// }; export const useCardData = ({ start_date, end_date }: TStatGetParams) => { return useQuery( @@ -110,3 +111,14 @@ export const useStatOne = (statId: number | string | undefined): any => { { refetchOnWindowFocus: false } ); }; + +export const useGeneralStats = (filterObject: TGeneralChartGetParams) => { + return useQuery( + ["stats/general-stats", filterObject], + () => statController.generalChart(filterObject), + { + refetchOnWindowFocus: false, + keepPreviousData: true, + } + ); +}; diff --git a/src/types/Statistic/TStat.ts b/src/types/Statistic/TStat.ts index cd24a42..4a4febf 100644 --- a/src/types/Statistic/TStat.ts +++ b/src/types/Statistic/TStat.ts @@ -39,9 +39,6 @@ export type TGeneralDailyStat = { export type TGeneralWrapper = { daily_stats: TGeneralDailyStat[]; }; -export type TGeneralChartData = { - daily_stats: TGeneralWrapper[]; -}; export type TCard = { all_tasks: number; @@ -50,3 +47,26 @@ export type TCard = { inactive_tasks: number; inactive_tasks_percentage: number; }; + +export type TGeneralChartGetParams = { + start_date: string; + end_date: string; +}; + +export type TDailyStat = { + task_date: string; + total_tasks: number; + completed_tasks: number; + incomplete_tasks: number; +}; + +export type TSummaryData = { + total: number; + total_completed: number; + total_incomplete: number; +}; + +export type TGeneralChartData = { + daily_stats: TDailyStat[]; + summary: TSummaryData; +}; diff --git a/yarn.lock b/yarn.lock index 640b06a..cad273f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2301,6 +2301,11 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash@^4.17.20": + version "4.17.20" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" + integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== + "@types/mime@*", "@types/mime@^1": version "1.3.2" resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz"