You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

546 lines
16 KiB

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 { useStatTeamData, useStatsData } from "../../Hooks/Stats";
import { TStatTeam } from "../../types/Statistic/TStat";
import StatTable from "./StatisticTable";
import StatTeamTable from "./StatisticTeamTable";
import dayjs from "dayjs";
import {
Button,
DatePicker,
DatePickerProps,
Select,
Tabs,
Typography,
} from "antd";
import TabPane from "antd/es/tabs/TabPane";
// @ts-ignore
import IconSearch from "../../assets/searchIcon.png";
import {
Bar,
BarChart,
CartesianGrid,
Legend,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import generalActive from "../../assets/generalActive.svg";
import general from "../../assets/general.svg";
import checkersIcon from "../../assets/checkerIcon.svg";
import chekersIconActive from "../../assets/checkersIconActive.svg";
import teamsIcon from "../../assets/teamsIcon.svg";
import teamsIconActive from "../../assets/teamsIconActive.svg";
import axios from "axios";
const Stat = () => {
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 [search, setSearch] = useState<string>("");
const [team, setTeam] = useState<any>("");
const [startDate, setStartDate] = useState(start_date);
const [endDate, setEndDate] = useState(end_date);
const [forSalary, setForSalary] = useState(true);
const teamData = useTeamData({});
const teamOptions: { label: string; value: any }[] | undefined =
teamData?.data?.map((item) => ({
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]) {
setStartDate(dates[0].startOf("day").format("YYYY-MM-DD HH:mm:ss"));
setEndDate(dates[1].endOf("day").format("YYYY-MM-DD HH:mm:ss"));
}
};
const onChangeDate: DatePickerProps["onChange"] = (date) => {
if (!date) {
setStartDate("");
setEndDate("");
} 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
.subtract(1, "day")
.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,
});
interface DataType {
data?: TStatTeam[];
refetch: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<TStatTeam[], unknown>>;
isLoading: boolean;
}
const TeamData: DataType = useStatTeamData({
search: "",
start_date: startDate,
end_date: endDate,
});
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 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 chartData = [
// {
// date: "2024-12-01",
// ABM: { points: 4500, count: 20 },
// ZNX: { points: 2500, count: 18 },
// TYP: { points: 3900, count: 24 },
// LMO: { points: 3100, count: 16 },
// QWE: { points: 2800, count: 21 },
// RST: { points: 4400, count: 19 },
// UVX: { points: 3950, count: 22 },
// WYZ: { points: 4100, count: 20 },
// ERT: { points: 3700, count: 17 },
// KLM: { points: 4200, count: 25 },
// LBS: { points: 1200, count: 25 },
// },
// {
// date: "2024-12-02",
// ABM: { points: 4600, count: 21 },
// ZNX: { points: 2400, count: 17 },
// TYP: { points: 3850, count: 23 },
// LMO: { points: 3200, count: 15 },
// QWE: { points: 2750, count: 20 },
// RST: { points: 4450, count: 18 },
// UVX: { points: 4000, count: 23 },
// WYZ: { points: 4200, count: 19 },
// ERT: { points: 3750, count: 16 },
// KLM: { points: 4250, count: 24 },
// LBS: { points: 1250, count: 26 },
// },
// {
// date: "2024-12-03",
// ABM: { points: 4550, count: 19 },
// ZNX: { points: 2550, count: 19 },
// TYP: { points: 3950, count: 25 },
// LMO: { points: 3150, count: 17 },
// QWE: { points: 2850, count: 22 },
// RST: { points: 4500, count: 20 },
// UVX: { points: 3900, count: 21 },
// WYZ: { points: 4300, count: 21 },
// ERT: { points: 3800, count: 18 },
// KLM: { points: 4300, count: 26 },
// LBS: { points: 1300, count: 27 },
// },
// ];
// const lines = [
// { key: "ABM.points", color: "#FF5733" }, // orange-red
// { key: "ZNX.points", color: "#00b822" }, // lime-green
// { key: "TYP.points", color: "#5733FF" }, // blue-purple
// { key: "LMO.points", color: "#FFC300" }, // gold
// { key: "QWE.points", color: "#009790" }, // turquoise
// { key: "RST.points", color: "#FF33A1" }, // hot pink
// { key: "UVX.points", color: "#8D33FF" }, // violet
// { key: "WYZ.points", color: "#FF8D33" }, // orange
// { key: "ERT.points", color: "#33A1FF" }, // sky blue
// { key: "KLM.points", color: "#4a8a01" }, // light green
// ];
// console.log(chartData, );
// const predefinedColors = ["#FF5733", "#00b822"];
// function updateLines(chartData: any, predefinedColors: any) {
// const keys = Object.keys(chartData[0]).filter((key) => key !== "date");
// const newLines = keys.map((key, index) => {
// const color = predefinedColors[index % predefinedColors.length]; // Ranglarni aylantirib foydalanadi
// return { key: `${key}.points`, color, name: `${key}` };
// });
// return newLines;
// }
// lines massivini yangilash
// const lines = updateLines(chartData, predefinedColors);
const [chartData, setChartData] = useState([]);
const [summaryData, setSummaryData] = useState<Record<string, any> | null>(
null
);
const token = localStorage.getItem("access");
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 axios.get(
"https://api.tteld.co/api/v1/stats/general-stats",
{
params: { start_date: startDate, end_date: formattedEndDate },
headers: { Authorization: `Bearer ${token}` },
}
);
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();
}, [token, startDate, endDate]);
if (loading) return <p>Loading...</p>;
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 "
style={{ marginBottom: 16 }}
>
<Typography className="title">Statistics</Typography>
<div>
<DatePicker
onChange={onChangeDate}
picker="month"
format={"MMMM"}
disabledDate={disabledDate}
defaultValue={now}
style={{ marginRight: 10, width: 120, marginBottom: 10 }}
// value={datePickerValue}
// defaultValue={dayjs().startOf("month")}
/>
<RangePicker style={{ width: 260 }} onCalendarChange={datePick} />
</div>
</div>
<Tabs
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}
// margin={{
// top: 5,
// right: 30,
// left: 20,
// bottom: 5,
// }}
>
<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" ? chekersIconActive : checkersIcon}
alt="icon"
/>
Checkers
</span>
}
key="2"
>
<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>
<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 }}
>
Save as file
</Button>
</TabPane>
<TabPane
tab={
<span style={{ display: "flex", alignItems: "center" }}>
<img
style={{ marginRight: 5 }}
src={activeTab === "3" ? teamsIconActive : teamsIcon}
alt="icon"
/>
Teams
</span>
}
key="3"
>
<StatTeamTable
data={TeamData?.data}
isLoading={TeamData?.isLoading}
refetch={TeamData?.refetch}
/>
<Button
type="primary"
onClick={(e) => handleSave("")}
style={{ marginTop: 10 }}
>
Save as file
</Button>
{/* <ResponsiveContainer width="100%" height={400}>
<LineChart
width={800}
height={400}
data={chartData}
margin={{ top: 20, right: 30, left: 20, bottom: 10 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
{lines.map((line, index) => (
<Line
key={index}
type="linear"
dataKey={line.key}
name={line.name}
stroke={line.color}
activeDot={{ r: 7 }}
/>
))}
</LineChart>
</ResponsiveContainer> */}
</TabPane>
</Tabs>
</div>
);
};
export default Stat;