parent
f7c8743ed9
commit
8188dab2b4
@ -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>
|
||||
// <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;
|
Loading…
Reference in new issue