optimized statistics page

dilmurod
Dilmurod 3 weeks ago
parent f7c8743ed9
commit 8188dab2b4

@ -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"
}
}

@ -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<TGeneralChartData[]>(
`stats/general-stats/`,
{
params,
}
const { data } = await instance.get<TGeneralChartData>(
"stats/general-stats",
{ params }
);
return data;
},

@ -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<string>("");
const [SupportSearch, setSupportSearch] = useState<string>("");
const [team, setTeam] = useState<any>("");
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<number>(() => {
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: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<TteamChartData[], unknown>>;
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<NodeJS.Timeout | null>(null);
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
const searchText = e.target.value;
timerRef.current = setTimeout(() => {
setSearch(searchText);
}, 1000);
};
const handleTechSupportSearchChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
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: (
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "1" ? generalActive : general}
alt="icon"
/>
General
</span>
),
children: <StatisticGeneral startDate={startDate} endDate={endDate} />,
},
{
key: "2",
label: (
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "2" ? techSupportsActive : techSupports}
alt="icon"
/>
Tech Supports
</span>
),
children: (
<StatisticsTechSupport startDate={startDate} endDate={endDate} />
),
},
{
key: "3",
label: (
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "3" ? chekersIconActive : checkersIcon}
alt="icon"
/>
Checkers
</span>
),
children: <StatisticsChekers startDate={startDate} endDate={endDate} />,
},
{
key: "4",
label: (
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "4" ? teamsIconActive : teamsIcon}
alt="icon"
/>
Teams
</span>
),
children: <StatTeamTable startDate={startDate} endDate={endDate} />,
},
];
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<Record<string, any> | 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 (
<div>
<div
className="header d-flex statistics-header "
className="header d-flex statistics-header"
style={{ marginBottom: 16 }}
>
<Typography className="title">Statistics</Typography>
@ -354,8 +134,6 @@ const Stat = () => {
disabledDate={disabledDate}
defaultValue={now}
style={{ marginRight: 10, width: 120, marginBottom: 10 }}
// value={datePickerValue}
// defaultValue={dayjs().startOf("month")}
/>
<RangePicker style={{ width: 260 }} onCalendarChange={datePick} />
</div>
@ -364,502 +142,10 @@ const Stat = () => {
defaultActiveKey="1"
activeKey={activeTab}
onChange={(key) => setActiveTab(key)}
>
<TabPane
tab={
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "1" ? generalActive : general}
alt="icon"
/>
General
</span>
}
key="1"
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 20,
marginTop: 40,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-around",
flexDirection: "column",
gap: 20,
flexWrap: "wrap",
}}
>
{summaryData && (
<div
className="card_stat"
style={{
backgroundColor: "#F99E2C",
}}
>
<p>Total</p>
<span>{summaryData.total}</span>
<p>Tasks</p>
</div>
)}
{summaryData && (
<div
className="card_stat"
style={{
backgroundColor: "#27AE60",
}}
>
<p>Active</p>
<span>{summaryData.total_completed}</span>
<p>Tasks</p>
</div>
)}
{summaryData && (
<div
className="card_stat"
style={{
backgroundColor: "#F64747",
}}
>
<p>Inactive</p>
<span>{summaryData.total_incomplete}</span>
<p>Tasks</p>
</div>
)}
</div>
<ResponsiveContainer
width="100%"
height={517}
style={{ textTransform: "capitalize" }}
>
<LineChart data={chartData}>
<CartesianGrid vertical={false} stroke="#D7D8E080" />
<XAxis
dataKey="task_date"
style={{
color: "#9B9DAA",
fontSize: 10,
lineHeight: "12.4px",
fontWeight: 400,
}}
tickFormatter={formatDate}
/>
<YAxis
style={{
color: "#9B9DAA",
fontSize: 10,
fontWeight: 400,
}}
/>
<Tooltip />
<Legend />
<Line
dataKey="total_tasks"
stroke="#F99E2C"
name="Total Tasks"
/>
<Line
dataKey="completed_tasks"
stroke="#27AE60"
name="Active tasks"
/>
<Line
dataKey="incomplete_tasks"
stroke="#F64747"
name="Inactive Tasks"
/>
</LineChart>
</ResponsiveContainer>
</div>
</TabPane>
<TabPane
tab={
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "2" ? techSupportsActive : techSupports}
alt="icon"
/>
Tech Supports
</span>
}
key="2"
>
<span
style={{
display: "flex",
alignItems: "center",
marginBottom: 10,
}}
>
{/* <div className="search-div">
<img src={IconSearch} alt="" />
<input
className={`search-input-${theme}`}
type="text"
placeholder="Search"
onChange={handleTechSupportSearchChange}
/>
</div> */}
<div>
<Input
placeholder="Search"
prefix={<SearchOutlined />}
onChange={handleTechSupportSearchChange}
/>
</div>
</span>
<StatisticsSupportTable
data={CreatorsData?.data}
isLoading={CreatorsData?.isLoading}
refetch={CreatorsData?.refetch}
/>
<Space style={{ width: "100%", marginTop: 40 }} direction="vertical">
<Space
style={{
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,
}}
wrap
>
<Select
value={pageSize}
onChange={handlePageSizeChange}
style={{ width: 65, marginRight: 16 }}
options={pageSizeOptions.map((size) => ({
label: `${size}`,
value: size,
}))}
/>
<Button
onClick={Previos}
disabled={CreatorsData?.data?.previous ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<LeftOutlined />
</Button>
<Input
disabled
style={{
width: 40,
textAlign: "center",
background: token.colorBgContainer,
border: "1px solid",
borderColor: token.colorText,
color: token.colorText,
}}
value={page}
onChange={(e) => {
let num = e.target.value;
if (Number(num) && num !== "0") {
setPage(Number(num));
}
}}
/>
<Button
onClick={Next}
disabled={CreatorsData?.data?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</TabPane>
<TabPane
tab={
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "3" ? chekersIconActive : checkersIcon}
alt="icon"
/>
Checkers
</span>
}
key="3"
>
<span
style={{
display: "flex",
alignItems: "center",
marginBottom: 10,
}}
>
{/* <div className="search-div" style={{ marginRight: 12 }}>
<img src={IconSearch} alt="" />
<input
className={`search-input-${theme}`}
type="text"
placeholder="Search"
onChange={handleSearchChange}
/>
</div> */}
<div style={{ marginRight: 12 }}>
<Input
placeholder="Search"
prefix={<SearchOutlined />}
onChange={handleSearchChange}
/>
</div>
<Select
style={{ width: 260 }}
placeholder="Team"
onChange={(value: any) => setTeam(value)}
options={teamOptions}
/>
</span>
<StatTable
data={data?.data}
isLoading={isLoading}
refetch={refetch}
/>
<Button
type="primary"
onClick={(e) => handleSave("team")}
style={{ marginTop: 10, marginBottom: 40 }}
>
Save as file
</Button>
<Space style={{ width: "100%", marginTop: 40 }} direction="vertical">
<Space
style={{
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,
}}
wrap
>
<Select
value={pageSize}
onChange={handlePageSizeChange}
style={{ width: 65, marginRight: 16 }}
options={pageSizeOptions.map((size) => ({
label: `${size}`,
value: size,
}))}
/>
<Button
onClick={Previos}
disabled={data?.previous ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<LeftOutlined />
</Button>
<Input
disabled
style={{
width: 40,
textAlign: "center",
background: token.colorBgContainer,
border: "1px solid",
borderColor: token.colorText,
color: token.colorText,
}}
value={page}
onChange={(e) => {
let num = e.target.value;
if (Number(num) && num !== "0") {
setPage(Number(num));
}
}}
/>
<Button
onClick={Next}
disabled={data?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</TabPane>
<TabPane
tab={
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "4" ? teamsIconActive : teamsIcon}
alt="icon"
/>
Teams
</span>
}
key="4"
>
<StatTeamTable
data={TeamData?.data?.data}
isLoading={TeamData?.isLoading}
refetch={TeamData?.refetch}
/>
<Button
type="primary"
onClick={(e) => handleSave("")}
style={{ marginTop: 10 }}
>
Save as file
</Button>
<div style={{ display: "flex", alignItems: "center", marginTop: 30 }}>
<ResponsiveContainer width="100%" height={517}>
<LineChart
data={TeamschartData.data || []}
margin={{ top: 20, right: 30, left: 20, bottom: 10 }}
>
<CartesianGrid vertical={false} stroke="#D7D8E080" />
<XAxis
dataKey="date"
style={{
color: "#9B9DAA",
fontSize: 12,
lineHeight: "15.4px",
fontWeight: 400,
letterSpacing: 0.8,
}}
tickFormatter={formatDate}
/>
<YAxis
style={{
color: "#9B9DAA",
fontSize: 10,
fontWeight: 400,
}}
/>
<Tooltip />
<Legend
payload={lines.map((line) => ({
value: line.legend_name, // Legenddagi yozuv sifatida `name` beriladi
type: "line",
id: line.key,
color: line.color,
}))}
/>
{lines.map((line, index) => (
<Line
key={index}
type="linear"
dataKey={line.key}
name={line.name}
stroke={line.color}
activeDot={{ r: 7 }}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
<Space style={{ width: "100%", marginTop: 40 }} direction="vertical">
<Space
style={{
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,
}}
wrap
>
<Select
value={pageSize}
onChange={handlePageSizeChange}
style={{ width: 65, marginRight: 16 }}
options={pageSizeOptions.map((size) => ({
label: `${size}`,
value: size,
}))}
/>
<Button
onClick={Previos}
disabled={TeamData?.data?.previous ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<LeftOutlined />
</Button>
<Input
disabled
style={{
width: 40,
textAlign: "center",
background: token.colorBgContainer,
border: "1px solid",
borderColor: token.colorText,
color: token.colorText,
}}
value={page}
onChange={(e) => {
let num = e.target.value;
if (Number(num) && num !== "0") {
setPage(Number(num));
}
}}
/>
<Button
onClick={Next}
disabled={TeamData?.data?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</TabPane>
</Tabs>
items={items}
/>
</div>
);
};
export default Stat;
export default Statistic;

@ -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<StatisticGeneralProps> = ({
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 (
<div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 20,
marginTop: 40,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-around",
flexDirection: "column",
gap: 20,
flexWrap: "wrap",
}}
>
{data?.summary && (
<div
className="card_stat"
style={{
backgroundColor: "#F99E2C",
}}
>
<p>Total</p>
<span>{data?.summary?.total}</span>
<p>Tasks</p>
</div>
)}
{data?.summary && (
<div
className="card_stat"
style={{
backgroundColor: "#27AE60",
}}
>
<p>Active</p>
<span>{data?.summary?.total_completed}</span>
<p>Tasks</p>
</div>
)}
{data?.summary && (
<div
className="card_stat"
style={{
backgroundColor: "#F64747",
}}
>
<p>Inactive</p>
<span>{data?.summary?.total_incomplete}</span>
<p>Tasks</p>
</div>
)}
</div>
<ResponsiveContainer
width="100%"
height={517}
style={{ textTransform: "capitalize" }}
>
<LineChart data={data?.daily_stats}>
<CartesianGrid vertical={false} stroke="#D7D8E080" />
<XAxis
dataKey="task_date"
style={{
color: "#9B9DAA",
fontSize: 10,
lineHeight: "12.4px",
fontWeight: 400,
}}
tickFormatter={formatDate}
/>
<YAxis
style={{
color: "#9B9DAA",
fontSize: 10,
fontWeight: 400,
}}
/>
<Tooltip />
<Legend />
<Line dataKey="total_tasks" stroke="#F99E2C" name="Total Tasks" />
<Line
dataKey="completed_tasks"
stroke="#27AE60"
name="Active tasks"
/>
<Line
dataKey="incomplete_tasks"
stroke="#F64747"
name="Inactive Tasks"
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
);
};
export default StatisticGeneral;

@ -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: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<TStat[], unknown>>;
data: any;
isLoading: boolean;
}) => {
const { token } = theme.useToken();
return (
<div>
<Table
size="small"
loading={isLoading}
dataSource={data?.map((u: any, i: any) => ({
no: i + 1,
...u,
}))}
columns={[
{
title: <img src={tagIcon} alt="" />,
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: (
// <div>
// <span>Salary</span> &nbsp;&nbsp;
// <Tooltip title="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.">
// <QuestionCircleOutlined />
// </Tooltip>
// </div>
// ),
// dataIndex: "salary",
// key: "salary",
// render: (text: string, record: any) => (
// <Tooltip
// title={
// <div>
// {record.salary_type === "hybrid" ? (
// <p>
// <strong>Fixed Amount:</strong> $
// {record.salary_base_amount}
// </p>
// ) : (
// ""
// )}
// <p>
// <strong>Performance based amount:</strong> $
// {record.performance_based_amount}
// </p>
// </div>
// }
// overlayStyle={{
// maxWidth: "700px",
// }}
// >
// <span>${record.salary}</span>
// </Tooltip>
// ),
// 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
/>
</div>
);
};
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.

@ -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<StatisticTeamProps> = ({
startDate,
endDate,
}) => {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState<number>(() => {
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: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<TteamChartData[], unknown>>;
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 (
<>
<div>
<Table
loading={isLoading}
size="small"
dataSource={data?.data?.map((u: any, i: any) => ({
no: i + 1,
...u,
}))}
columns={[
{
title: <img src={tagIcon} alt="" />,
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 color={tag ? "geekblue" : "red"}>
{tag ? "True" : "False"}
</Tag>
),
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
/>
</div>
<div style={{ display: "flex", alignItems: "center", marginTop: 30 }}>
<ResponsiveContainer width="100%" height={517}>
<LineChart
data={TeamschartData.data || []}
margin={{ top: 20, right: 30, left: 20, bottom: 10 }}
>
<CartesianGrid vertical={false} stroke="#D7D8E080" />
<XAxis
dataKey="date"
style={{
color: "#9B9DAA",
fontSize: 12,
lineHeight: "15.4px",
fontWeight: 400,
letterSpacing: 0.8,
}}
tickFormatter={formatDate}
/>
<YAxis
style={{
color: "#9B9DAA",
fontSize: 10,
fontWeight: 400,
}}
/>
<Tooltip />
<Legend
payload={lines.map((line) => ({
value: line.legend_name,
type: "line",
id: line.key,
color: line.color,
}))}
/>
{lines.map((line, index) => (
<Line
key={index}
type="linear"
dataKey={line.key}
name={line.name}
stroke={line.color}
activeDot={{ r: 7 }}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
<Space style={{ width: "100%", marginTop: 40 }} direction="vertical">
<Space
style={{
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,
}}
wrap
>
<Select
value={pageSize}
onChange={handlePageSizeChange}
style={{ width: 65, marginRight: 16 }}
options={pageSizeOptions.map((size) => ({
label: `${size}`,
value: size,
}))}
/>
<Button
onClick={Previos}
disabled={data?.data?.previous ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<LeftOutlined />
</Button>
<Input
disabled
style={{
width: 40,
textAlign: "center",
background: token.colorBgContainer,
border: "1px solid",
borderColor: token.colorText,
color: token.colorText,
}}
value={page}
onChange={(e) => {
let num = e.target.value;
if (Number(num) && num !== "0") {
setPage(Number(num));
}
}}
/>
<Button
onClick={Next}
disabled={data?.data?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</>
);
};
export default StatisticTeam;

@ -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: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<TStatTeam[], unknown>>;
data: any;
isLoading: boolean;
}) => {
const { token } = theme.useToken();
return (
<div style={{ maxHeight: "400px", overflow: "auto" }}>
<Table
loading={isLoading}
size="small"
dataSource={data?.map((u: any, i: any) => ({
no: i + 1,
...u,
}))}
columns={[
{
title: <img src={tagIcon} alt="" />,
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 color={tag ? "geekblue" : "red"}>
{tag ? "True" : "False"}
</Tag>
),
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"
}
/>
</div>
);
};
export default StatTeamTable;

@ -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<StatisticsChekersProps> = ({
startDate,
endDate,
}) => {
const [team, setTeam] = useState<any>("");
const [search, setSearch] = useState<string>("");
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<number>(() => {
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<HTMLInputElement>) => {
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 (
<>
<div
style={{
display: "flex",
alignItems: "center",
marginBottom: 10,
}}
>
<div style={{ marginRight: 12 }}>
<Input
placeholder="Search"
prefix={<SearchOutlined />}
onChange={handleSearchChange}
/>
</div>
<Select
style={{ width: 260 }}
placeholder="Team"
onChange={(value: any) => setTeam(value)}
options={teamOptions}
allowClear
/>
</div>
<Table
size="small"
loading={isLoading}
dataSource={data?.data?.map((u: any, i: any) => ({
no: i + 1,
...u,
}))}
columns={[
{
title: <img src={tagIcon} alt="" />,
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
/>
<Button
type="primary"
onClick={(e) => handleSave("team")}
style={{ marginTop: 10, marginBottom: 40 }}
>
Save as file
</Button>
<Space style={{ width: "100%", marginTop: 40 }} direction="vertical">
<Space
style={{
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,
}}
wrap
>
<Select
value={pageSize}
onChange={handlePageSizeChange}
style={{ width: 65, marginRight: 16 }}
options={pageSizeOptions.map((size) => ({
label: `${size}`,
value: size,
}))}
/>
<Button
onClick={Previos}
disabled={data?.data?.previous ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<LeftOutlined />
</Button>
<Input
disabled
style={{
width: 40,
textAlign: "center",
background: token.colorBgContainer,
border: "1px solid",
borderColor: token.colorText,
color: token.colorText,
}}
value={page}
onChange={(e) => {
let num = e.target.value;
if (Number(num) && num !== "0") {
setPage(Number(num));
}
}}
/>
<Button
onClick={Next}
disabled={data?.data?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</>
);
};
export default StatisticsChekers;

@ -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: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<TStatCreators[], unknown>>;
data: any;
isLoading: boolean;
}) => {
const { token } = theme.useToken();
return (
<div style={{ paddingBottom: 40 }}>
<Table
loading={isLoading}
size="small"
dataSource={data?.data?.map((u: any, i: any) => ({
no: i + 1,
...u,
}))}
columns={[
{
title: <img src={tagIcon} alt="" />,
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
/>
</div>
);
};
export default StatisticsSupportTable;

@ -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<StatisticsTechSupportProps> = ({
startDate,
endDate,
}) => {
const { token } = theme.useToken();
const [SupportSearch, setSupportSearch] = useState<string>("");
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState<number>(() => {
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<HTMLInputElement>
) => {
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 (
<>
<div
style={{
display: "flex",
alignItems: "center",
marginBottom: 10,
}}
>
<div>
<Input
placeholder="Search"
prefix={<SearchOutlined />}
onChange={handleTechSupportSearchChange}
/>
</div>
</div>
<Table
loading={isLoading}
size="small"
dataSource={data?.data?.map((u: any, i: any) => ({
no: i + 1,
...u,
}))}
columns={[
{
title: <img src={tagIcon} alt="" />,
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
/>
<Space style={{ width: "100%", marginTop: 40 }} direction="vertical">
<Space
style={{
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,
}}
wrap
>
<Select
value={pageSize}
onChange={handlePageSizeChange}
style={{ width: 65, marginRight: 16 }}
options={pageSizeOptions.map((size) => ({
label: `${size}`,
value: size,
}))}
/>
<Button
onClick={Previos}
disabled={data?.data?.previous ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<LeftOutlined />
</Button>
<Input
disabled
style={{
width: 40,
textAlign: "center",
background: token.colorBgContainer,
border: "1px solid",
borderColor: token.colorText,
color: token.colorText,
}}
value={page}
onChange={(e) => {
let num = e.target.value;
if (Number(num) && num !== "0") {
setPage(Number(num));
}
}}
/>
<Button
onClick={Next}
disabled={data?.data?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</>
);
};
export default StatisticsTechSupport;

@ -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<TGeneralChartData, Error>(
["stats/general-stats", filterObject],
() => statController.generalChart(filterObject),
{
refetchOnWindowFocus: false,
keepPreviousData: true,
}
);
};

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

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

Loading…
Cancel
Save