diff --git a/src/API/LayoutApi/accounting.ts b/src/API/LayoutApi/accounting.ts new file mode 100644 index 0000000..e4adfb1 --- /dev/null +++ b/src/API/LayoutApi/accounting.ts @@ -0,0 +1,28 @@ +import { + TAccounting, + TAccountingHistory, +} from "../../types/Accounting/TAccounting"; +import instance from "../api"; + +export type TAccountingGetParams = { + month: string; +}; + +export const AccountingController = { + async read(filterObject: TAccountingGetParams) { + const params = { ...filterObject }; + + if (!!filterObject.month) params.month = filterObject.month; + + const { data } = await instance.get(`/employees-salaries/`, { + params, + }); + return data; + }, + async history() { + const { data } = await instance.get( + `/employees-salaries-history/` + ); + return data; + }, +}; diff --git a/src/API/LayoutApi/statistic.ts b/src/API/LayoutApi/statistic.ts index 277e52a..60ab01f 100644 --- a/src/API/LayoutApi/statistic.ts +++ b/src/API/LayoutApi/statistic.ts @@ -1,4 +1,11 @@ -import { TCard, TStat, TStatTeam } from "../../types/Statistic/TStat"; +import { + TCard, + TGeneralChartData, + TStat, + TStatCreators, + TStatTeam, + TteamChartData, +} from "../../types/Statistic/TStat"; import instance from "../api"; export type TStatGetParams = { @@ -18,6 +25,15 @@ export type TStatTeamGetParams = { export type TStatCreatorsGetParams = { start_date?: string; end_date?: string; + search?: string; +}; +export type TteamChartGetParams = { + start_date?: string; + end_date?: string; +}; +export type TGeneralChartGetParams = { + start_date?: string; + end_date?: string; }; export const statController = { @@ -53,10 +69,14 @@ export const statController = { if (!!filterObject.start_date) params.start_date = filterObject.start_date; if (!!filterObject.end_date) params.end_date = filterObject.end_date; + if (!!filterObject.search) params.search = filterObject.search; - const { data } = await instance.get(`stats/task-creators/`, { - params, - }); + const { data } = await instance.get( + `stats/task-creators/`, + { + params, + } + ); return data; }, async cards(filterObject: TStatCreatorsGetParams) { @@ -70,6 +90,34 @@ export const statController = { }); return data; }, + async teamChart(filterObject: TteamChartGetParams) { + const params = { ...filterObject }; + + 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/teams-line-chart/`, + { + params, + } + ); + return data; + }, + 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; + + const { data } = await instance.get( + `stats/general-stats/`, + { + params, + } + ); + return data; + }, async saveUsersStats( fileName: string, diff --git a/src/API/LayoutApi/users.ts b/src/API/LayoutApi/users.ts index d6bd14d..47d896a 100644 --- a/src/API/LayoutApi/users.ts +++ b/src/API/LayoutApi/users.ts @@ -6,6 +6,8 @@ export type TUsersGetParams = { name?: string; team?: string; role?: string; + page?: number; + page_size?: number; }; export type TUsersPutParams = { @@ -26,6 +28,10 @@ export const userController = { async read(filterObject: TUsersGetParams) { const params = { ...filterObject }; + if (!!filterObject.page && filterObject.page !== 0) + params.page = filterObject.page; + params.page_size = filterObject.page_size; + if (!!filterObject.name) params.name = filterObject.name; if (Array.isArray(filterObject.team)) { params.team = filterObject.team.join(", "); diff --git a/src/App.css b/src/App.css index db6e516..60cd075 100644 --- a/src/App.css +++ b/src/App.css @@ -894,6 +894,11 @@ } } +.requests-filter { + justify-content: space-between; + align-items: center; +} + @media (max-width: 768px) { .btn-add { padding: 7px 10px 7px 10px; diff --git a/src/App.tsx b/src/App.tsx index ab3194b..caffc44 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,6 +41,9 @@ import driverIcon from "./assets/customersIcon.png"; import requestIcon from "./assets/requestIcon.png"; // @ts-ignore import callIcon from "./assets/callIcon.png"; + +import accountingIcon from "./assets/acoountingIcon.svg"; + import Register from "./Auth/Register"; import Activate from "./Auth/Activate"; import Invite from "./Auth/Invite"; @@ -53,6 +56,7 @@ import Requests from "./Components/Requests/Requests"; import { callController } from "./API/LayoutApi/callrequests"; import Call from "./Components/CallRequests/Call"; import { dark, light } from "./Utils/styles"; +import Accounting from "./Components/Accounting/Accounting"; // import Input from "antd/es/input/Input"; const { Header, Sider, Content } = Layout; const userJSON: any = localStorage.getItem("user"); @@ -190,6 +194,11 @@ const App: React.FC = () => { , "calls/", + ), + getItem( + Accounting, + "accounting/", + ) ); } diff --git a/src/Components/Accounting/Accounting.tsx b/src/Components/Accounting/Accounting.tsx new file mode 100644 index 0000000..9bb3e02 --- /dev/null +++ b/src/Components/Accounting/Accounting.tsx @@ -0,0 +1,62 @@ +import { Tabs, Typography } from "antd"; + +import React, { useState } from "react"; + +import TabPane from "antd/es/tabs/TabPane"; +import AccountingCurrent from "./AccountingCurrent"; +import AccountingLast from "./AccountingLast"; +import AccountingHistory from "./AccountingHistory"; + +const Accounting: React.FC = () => { + const [activeTab, setActiveTab] = useState("1"); + + return ( +
+
+ Accounting +
+ + setActiveTab(key)} + > + + Current Month + + } + key="1" + > + + + + Last Month + + } + key="2" + > + + + + History + + } + key="3" + > + + + +
+ ); +}; + +export default Accounting; diff --git a/src/Components/Accounting/AccountingCurrent.tsx b/src/Components/Accounting/AccountingCurrent.tsx new file mode 100644 index 0000000..76f1bae --- /dev/null +++ b/src/Components/Accounting/AccountingCurrent.tsx @@ -0,0 +1,178 @@ +import { Table, Tooltip } from "antd"; +import React from "react"; +import tagIcon from "../../assets/tagIcon.png"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { theme } from "antd"; +import { useAccountingData } from "../../Hooks/Accounting"; + +const AccountingCurrent: React.FC = () => { + const { data, refetch, isLoading } = useAccountingData({ + month: "current", + }); + + const { token } = theme.useToken(); + + return ( +
+ ({ + no: i + 1, + ...u, + }))} + columns={[ + { + title: , + dataIndex: "no", + key: "no", + width: "5%", + }, + { + title: "Username", + dataIndex: "username", + key: "username", + }, + { + title: "Tasks", + dataIndex: "number_of_tasks", + key: "number_of_tasks", + }, + { + title: "Points", + dataIndex: "total_points", + key: "total_points", + }, + { + title: "Salary Type", + dataIndex: "salary_type", + render: (value: any, record: any) => { + if (record.salary_type === "task_based") { + return

Task Based

; + } else if (record.salary_type === "hybrid") { + return

Hybrid

; + } else { + return

{record.salary_type}

; // Agar boshqa qiymat bo'lsa, oddiy qilib chiqariladi + } + }, + filters: [ + { + text: "Hybrid", + value: "hybrid", + }, + { + text: "Task Based", + value: "task_based", + }, + ], + filterMultiple: false, + // defaultFilteredValue: ["hybrid"], + onFilter: (value: any, record: any) => { + return record.salary_type === value; + }, + }, + { + title: "Base Salary", + dataIndex: "salary_base_amount", + render: (text: string, record: any) => ( +

${record?.salary_base_amount}

+ ), + }, + { + title: "Performance Salary", + dataIndex: "performance_salary", + render: (text: string, record: any) => ( +

${record?.performance_salary}

+ ), + }, + { + title: "Charges", + dataIndex: "total_charges", + render: (text: string, record: any) => ( +

${record?.total_charges}

+ ), + }, + { + title: "Bonuses", + dataIndex: "total_bonuses", + render: (text: string, record: any) => ( +

${record?.total_bonuses}

+ ), + }, + { + 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"], + }, + ]} + rowClassName={(record, index) => + index % 2 === 0 ? "odd-row" : "even-row" + } + bordered + 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, + }} + + // onRow={(record) => ({ + // onClick: () => { + // if (record.user && record.user.id) { + // navigate(`/accounting/${record.user.id}`); // `user.id`ni olish + // } else { + // console.error("User ID mavjud emas"); + // } + // }, + // })} + /> + + ); +}; + +export default AccountingCurrent; diff --git a/src/Components/Accounting/AccountingDetails.tsx b/src/Components/Accounting/AccountingDetails.tsx new file mode 100644 index 0000000..f51ff70 --- /dev/null +++ b/src/Components/Accounting/AccountingDetails.tsx @@ -0,0 +1,188 @@ +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import axios from "axios"; +import Typography from "antd/es/typography/Typography"; +import { DatePicker, DatePickerProps, Table } from "antd"; +import tagIcon from "../../assets/tagIcon.png"; +import dayjs from "dayjs"; + +interface User { + id: number; + first_name: string; + last_name: string; + username: string; +} + +interface Salary { + id: number; + month: string; + year: number; + number_of_tasks: number; + total_points: number; + salary_type: string; + base_salary: number; + performance_salary: number; + total_salary: number; +} + +interface Data { + user: User; + salaries: Salary[]; + total: number; +} + +const AccountingDetails = () => { + const { id } = useParams(); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + const now = dayjs(); + const moment = require("moment"); + const currentDate = moment(); + // const defaultDate = `${currentDate.format("YYYY")}`; + + const disabledDate = (current: any) => { + return current && current.year() > moment().year(); + }; + + const [date, setDate] = useState(null); + + const onChangeDate: DatePickerProps["onChange"] = (date) => { + if (!date) { + setDate(""); + } else { + const year = date.format("YYYY"); + setDate(year); + } + }; + + useEffect(() => { + const fetchUserDetails = async () => { + const API_URL = `https://api.tteld.co/api/v1/user-salaries/${id}`; + const AUTH_TOKEN = localStorage.getItem("access"); + + try { + const response = await axios.get(API_URL, { + params: { + user_id: id, + year: date, + }, + headers: { + Authorization: `Bearer ${AUTH_TOKEN}`, + "Content-Type": "application/json", + }, + maxRedirects: 0, + }); + + setUser(response.data); + + setLoading(false); + } catch (error: any) { + console.error("Error:", error.response?.status, error.response?.data); + setLoading(false); + } + }; + + fetchUserDetails(); + }, [date]); + + return ( + <> +
+
+ {user?.user.first_name && user.user.last_name ? ( + + {user?.user.first_name} {user?.user.last_name} + + ) : ( + {user?.user.username} + )} + + Total: ${user?.total} + +
+
+ +
+
+
({ + no: i + 1, + ...u, + }))} + columns={[ + { + title: , + dataIndex: "no", + key: "no", + width: "5%", + }, + { + title: "Salary", + dataIndex: "total_salary", + render(value, record, index) { + return

${record?.total_salary}

; + }, + }, + { + title: "Salary Type", + dataIndex: "salary_type", + render: (value: any, record: any) => { + if (record.salary_type === "task_based") { + return

Task Based

; + } else if (record.salary_type === "hybrid") { + return

Hybrid

; + } else { + return

{record.salary_type}

; // Agar boshqa qiymat bo'lsa, oddiy qilib chiqariladi + } + }, + }, + { + title: "Base Salary", + dataIndex: "base_salary", + render(value, record, index) { + return

${record?.base_salary}

; + }, + }, + { + title: "Performance Salary", + dataIndex: "performance_salary", + render(value, record, index) { + return

${record?.performance_salary}

; + }, + }, + { + title: "Month", + dataIndex: "month", + }, + { + title: "Year", + dataIndex: "year", + }, + ]} + bordered + pagination={false} + /> + + ); +}; + +export default AccountingDetails; diff --git a/src/Components/Accounting/AccountingHistory.tsx b/src/Components/Accounting/AccountingHistory.tsx new file mode 100644 index 0000000..4add5d8 --- /dev/null +++ b/src/Components/Accounting/AccountingHistory.tsx @@ -0,0 +1,158 @@ +import { Table, Tooltip } from "antd"; +import React from "react"; +import tagIcon from "../../assets/tagIcon.png"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { theme } from "antd"; +import { useAccountingHistory } from "../../Hooks/Accounting"; + +const AccountingHistory: React.FC = () => { + const { data, refetch, isLoading } = useAccountingHistory(); + + const { token } = theme.useToken(); + + return ( +
+
({ + no: i + 1, + ...u, + }))} + columns={[ + { + title: , + dataIndex: "no", + key: "no", + width: "5%", + }, + { + title: "Username", + dataIndex: "username", + key: "username", + }, + { + title: "Total Tasks", + dataIndex: "total_number_of_tasks", + key: "total_number_of_tasks", + }, + { + title: "Total Points", + dataIndex: "total_earned_points", + key: "total_earned_points", + }, + { + title: "Total worked months", + dataIndex: "salary_months_count", + key: "salary_months_count", + }, + { + title: "Salary Type", + dataIndex: "salary_type", + render: (value: any, record: any) => { + if (record.salary_type === "task_based") { + return

Task Based

; + } else if (record.salary_type === "hybrid") { + return

Hybrid

; + } else { + return

{record.salary_type}

; + } + }, + filters: [ + { + text: "Hybrid", + value: "hybrid", + }, + { + text: "Task Based", + value: "task_based", + }, + ], + filterMultiple: false, + onFilter: (value: any, record: any) => { + return record.salary_type === value; + }, + }, + { + title: "Base Salary", + dataIndex: "total_base_salary", + render: (text: string, record: any) => ( +

${record?.total_base_salary}

+ ), + }, + { + title: "Performance Salary", + dataIndex: "total_performance_salary", + render: (text: string, record: any) => ( +

${record?.total_performance_salary}

+ ), + }, + { + title: "Total Charges", + dataIndex: "total_charges", + render: (text: string, record: any) => ( +

${record?.total_charges}

+ ), + }, + { + title: "Total Bonuses", + dataIndex: "total_bonuses", + render: (text: string, record: any) => ( +

${record?.total_bonuses}

+ ), + }, + { + title: ( +
+ Total Salary  + + + +
+ ), + dataIndex: "total_earned_salary", + key: "total_earned_salary", + render: (text: string, record: any) => ( + ${record.total_earned_salary} + ), + // sorter: (a: any, b: any) => a.salary - b.total_points, + // sortDirections: ["ascend", "descend"], + }, + ]} + rowClassName={(record, index) => + index % 2 === 0 ? "odd-row" : "even-row" + } + bordered + 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, + }} + + // onRow={(record) => ({ + // onClick: () => { + // if (record.user && record.user.id) { + // navigate(`/accounting/${record.user.id}`); // `user.id`ni olish + // } else { + // console.error("User ID mavjud emas"); + // } + // }, + // })} + /> + + ); +}; + +export default AccountingHistory; diff --git a/src/Components/Accounting/AccountingLast.tsx b/src/Components/Accounting/AccountingLast.tsx new file mode 100644 index 0000000..da24569 --- /dev/null +++ b/src/Components/Accounting/AccountingLast.tsx @@ -0,0 +1,161 @@ +import { Button, Table, Tooltip } from "antd"; +import React, { useEffect, useState } from "react"; +import api from "../../API/api"; +import tagIcon from "../../assets/tagIcon.png"; +import { QuestionCircleOutlined } from "@ant-design/icons"; +import { theme } from "antd"; +import { useAccountingData } from "../../Hooks/Accounting"; + +const AccountingCurrent: React.FC = () => { + const { data, refetch, isLoading } = useAccountingData({ + month: "last", + }); + + const { token } = theme.useToken(); + + return ( +
+
({ + no: i + 1, + ...u, + }))} + columns={[ + { + title: , + dataIndex: "no", + key: "no", + width: "5%", + }, + { + title: "Username", + dataIndex: "username", + key: "username", + }, + { + title: "Tasks", + dataIndex: "number_of_tasks", + key: "number_of_tasks", + }, + { + title: "Points", + dataIndex: "total_points", + key: "total_points", + }, + { + title: "Salary Type", + dataIndex: "salary_type", + render: (value: any, record: any) => { + if (record.salary_type === "task_based") { + return

Task Based

; + } else if (record.salary_type === "hybrid") { + return

Hybrid

; + } else { + return

{record.salary_type}

; // Agar boshqa qiymat bo'lsa, oddiy qilib chiqariladi + } + }, + filters: [ + { + text: "Hybrid", + value: "hybrid", + }, + { + text: "Task Based", + value: "task_based", + }, + ], + filterMultiple: false, + // defaultFilteredValue: ["hybrid"], + onFilter: (value: any, record: any) => { + return record.salary_type === value; + }, + }, + { + title: "Base Salary", + dataIndex: "salary_base_amount", + render: (text: string, record: any) => ( +

${record?.salary_base_amount}

+ ), + }, + { + title: "Performance Salary", + dataIndex: "performance_salary", + render: (text: string, record: any) => ( +

${record?.performance_salary}

+ ), + }, + { + title: "Charges", + dataIndex: "total_charges", + render: (text: string, record: any) => ( +

${record?.total_charges}

+ ), + }, + { + title: "Bonuses", + dataIndex: "total_bonuses", + render: (text: string, record: any) => ( +

${record?.total_bonuses}

+ ), + }, + { + title: ( +
+ Salary   + + + +
+ ), + dataIndex: "salary", + key: "salary", + render: (text: string, record: any) => ( + ${record.salary} + ), + // sorter: (a: any, b: any) => a.salary - b.total_points, + // sortDirections: ["ascend", "descend"], + }, + ]} + rowClassName={(record, index) => + index % 2 === 0 ? "odd-row" : "even-row" + } + bordered + 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, + }} + + // onRow={(record) => ({ + // onClick: () => { + // if (record.user && record.user.id) { + // navigate(`/accounting/${record.user.id}`); // `user.id`ni olish + // } else { + // console.error("User ID mavjud emas"); + // } + // }, + // })} + /> + + {/* */} + + ); +}; + +export default AccountingCurrent; diff --git a/src/Components/Companies/Companies.tsx b/src/Components/Companies/Companies.tsx index 923e266..9f8e97c 100644 --- a/src/Components/Companies/Companies.tsx +++ b/src/Components/Companies/Companies.tsx @@ -8,7 +8,7 @@ import { RightOutlined, } from "@ant-design/icons"; import { useCompanyPaginated } from "../../Hooks/Companies"; -import { Button, Input, Pagination, Space, Typography } from "antd"; +import { Button, Input, Space, Typography } from "antd"; import { theme } from "antd"; // @ts-ignore @@ -30,7 +30,7 @@ const Company = () => { name: search, is_active: undefined, page: page, - page_size: 15, + page_size: 10, }); const timerRef = useRef(null); @@ -59,12 +59,6 @@ const Company = () => { const { token } = theme.useToken(); - const page_size = 15; - - const handlePageChange = (page: number) => { - setPage(page); - }; - const themes = localStorage.getItem("theme") === "true" ? true : false; return ( @@ -152,12 +146,6 @@ const Company = () => { > - {/* */} diff --git a/src/Components/Requests/RequestsEdit.tsx b/src/Components/Requests/RequestsEdit.tsx index b6fa030..bd35053 100644 --- a/src/Components/Requests/RequestsEdit.tsx +++ b/src/Components/Requests/RequestsEdit.tsx @@ -99,7 +99,7 @@ const RequestsEdit = ({ onCancel={handleCancel} footer={null} open={modalOpen} - width={1000} + width={800} maskClosable={true} >
diff --git a/src/Components/Statistics/Statistic.tsx b/src/Components/Statistics/Statistic.tsx index 3c1d586..be2713c 100644 --- a/src/Components/Statistics/Statistic.tsx +++ b/src/Components/Statistics/Statistic.tsx @@ -6,8 +6,19 @@ import { } 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 { + 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 dayjs from "dayjs"; @@ -41,8 +52,11 @@ 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 techSupports from "../../assets/techsupportIcon.svg"; +import techSupportsActive from "../../assets/techsupportIconActive.svg"; import axios from "axios"; +import StatisticsSupportTable from "./StatisticsSupportTable"; const Stat = () => { const now = dayjs(); @@ -57,6 +71,7 @@ const Stat = () => { .format("YYYY-MM-DD")} 23:59:59`; const [search, setSearch] = useState(""); + const [SupportSearch, setSupportSearch] = useState(""); const [team, setTeam] = useState(""); const [startDate, setStartDate] = useState(start_date); const [endDate, setEndDate] = useState(end_date); @@ -141,6 +156,42 @@ const Stat = () => { end_date: endDate, }); + interface TaskCreatorsType { + data?: TStatCreators[]; + refetch: ( + options?: (RefetchOptions & RefetchQueryFilters) | undefined + ) => Promise>; + isLoading: boolean; + } + + const CreatorsData: TaskCreatorsType = useCreatorsData({ + 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) { @@ -152,6 +203,20 @@ const Stat = () => { 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"); @@ -160,81 +225,47 @@ const Stat = () => { 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 predefinedColors = [ + "#ff2600", // Tomato + "#FF4500", // OrangeRed + "#FF1493", // DeepPink + "#006800", // LimeGreen + "#3CB371", // MediumSeaGreen + "#00BFFF", // DeepSkyBlue + "#FFD700", // Gold + "#F08080", // LightCoral + "#8A2BE2", // BlueViolet + "#FFB6C1", // LightPink + ]; + + 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>( @@ -279,8 +310,6 @@ const Stat = () => { fetchData(); }, [token, startDate, endDate]); - if (loading) return

Loading...

; - const formatDate = (dateString: string): string => { const date = new Date(dateString); return new Intl.DateTimeFormat("en-EN", { @@ -388,15 +417,7 @@ const Stat = () => { height={517} style={{ textTransform: "capitalize" }} > - + {
+ icon - Checkers + Tech Supports } key="2" + > + +
+ + +
+
+ +
+ + + icon + Checkers + + } + key="3" > { icon Teams } - key="3" + key="4" > { Save as file - {/* - - - - - - +
+ + + + ( - - ))} - - */} + + + ({ + value: line.legend_name, // Legenddagi yozuv sifatida `name` beriladi + type: "line", + id: line.key, + color: line.color, + }))} + /> + + {lines.map((line, index) => ( + + ))} + + +
diff --git a/src/Components/Statistics/StatisticTable.tsx b/src/Components/Statistics/StatisticTable.tsx index b3f6059..476d24c 100644 --- a/src/Components/Statistics/StatisticTable.tsx +++ b/src/Components/Statistics/StatisticTable.tsx @@ -54,49 +54,55 @@ const StatTable = ({ 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} -
- ), - }, + // { + // 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, diff --git a/src/Components/Statistics/StatisticsSupportTable.tsx b/src/Components/Statistics/StatisticsSupportTable.tsx new file mode 100644 index 0000000..5c17972 --- /dev/null +++ b/src/Components/Statistics/StatisticsSupportTable.tsx @@ -0,0 +1,79 @@ +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.png"; + +const StatisticsSupportTable = ({ + data, + isLoading, + refetch, +}: { + refetch: ( + options?: (RefetchOptions & RefetchQueryFilters) | undefined + ) => Promise>; + data: TStatCreators[] | undefined; + 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, + }, + }} + rowClassName={(record, index) => + index % 2 === 0 ? "odd-row" : "even-row" + } + bordered + /> + + ); +}; + +export default StatisticsSupportTable; diff --git a/src/Components/Tasks/TaskModal.tsx b/src/Components/Tasks/TaskModal.tsx index 031121f..cd2f1b6 100644 --- a/src/Components/Tasks/TaskModal.tsx +++ b/src/Components/Tasks/TaskModal.tsx @@ -241,25 +241,27 @@ const TaskModal = ({
- - - + + + )} - {/* */}
diff --git a/src/Components/Updates/UpdateEdit.tsx b/src/Components/Updates/UpdateEdit.tsx index 7b211cc..b46d2af 100644 --- a/src/Components/Updates/UpdateEdit.tsx +++ b/src/Components/Updates/UpdateEdit.tsx @@ -31,6 +31,8 @@ import attachmentIcon from "../../assets/attachmentIcon.png"; // @ts-ignore import attachmentIconActive from "../../assets/attachmentIconActive.png"; +import uploadImg from "../../assets/upload-file.png"; + const { Option } = Select; const TabPane = Tabs.TabPane; type params = { @@ -395,15 +397,19 @@ const UpdateEdit = () => { }} >

- + upload image +

+

+ Drag and drop files or Click to select

- Click or drag file to this area to upload + Maximum file size is 10 MB

{imgname.join(",\n")}

diff --git a/src/Components/Users/UserTable.tsx b/src/Components/Users/UserTable.tsx index d460303..6ab364e 100644 --- a/src/Components/Users/UserTable.tsx +++ b/src/Components/Users/UserTable.tsx @@ -107,7 +107,7 @@ const UserTable = ({ } scroll={{ x: "768px" }} pagination={{ - pageSize: 15, + pageSize: 10, size: "default", style: { margin: 0, @@ -123,6 +123,7 @@ const UserTable = ({ }, showLessItems: true, }} + // pagination={false} bordered /> diff --git a/src/Components/Users/Users.tsx b/src/Components/Users/Users.tsx index d5baedd..5cd6823 100644 --- a/src/Components/Users/Users.tsx +++ b/src/Components/Users/Users.tsx @@ -2,23 +2,39 @@ import { useRef, useState } from "react"; import { useUserData } from "../../Hooks/Users"; import AddUser from "./AddUser"; import UserTable from "./UserTable"; +import { LeftOutlined, RightOutlined } from "@ant-design/icons"; +import { theme } from "antd"; // @ts-ignore import IconSearch from "../../assets/searchIcon.png"; //@ts-ignore import addicon from "../../assets/addiconpng.png"; -import { Pagination, Space, Typography } from "antd"; +import { Button, Input, Pagination, Space, Typography } from "antd"; const User = () => { + const [page, setPage] = useState(1); const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const showModal = () => { setOpen(true); }; + 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, refetch, isLoading } = useUserData({ name: search, team: "", }); + const timerRef = useRef(null); const handleSearchChange = (e: React.ChangeEvent) => { if (timerRef.current) { @@ -30,7 +46,9 @@ const User = () => { setSearch(searchText); }, 1000); }; - const theme = localStorage.getItem("theme") === "true" ? true : false; + const themes = localStorage.getItem("theme") === "true" ? true : false; + + const { token } = theme.useToken(); return (
@@ -50,7 +68,7 @@ const User = () => {
{ bottom: 0, left: 0, width: "100%", - // backgroundColor: token.colorBgContainer, + backgroundColor: token.colorBgContainer, boxShadow: "0 4px 8px rgba(0, 0, 0, 0.4)", padding: "10px 0", zIndex: 1000, }} wrap > - { let num = e.target.value; @@ -89,15 +123,17 @@ const User = () => { }} /> - - */}
diff --git a/src/Hooks/Accounting/index.ts b/src/Hooks/Accounting/index.ts new file mode 100644 index 0000000..72a1bae --- /dev/null +++ b/src/Hooks/Accounting/index.ts @@ -0,0 +1,26 @@ +import { useQuery } from "react-query"; +import { + AccountingController, + TAccountingGetParams, +} from "../../API/LayoutApi/accounting"; + +export const useAccountingData = ({ month }: TAccountingGetParams) => { + return useQuery( + [`stats/all-users/`, month], + () => + AccountingController.read({ + month, + }), + { refetchOnWindowFocus: false } + ); +}; + +export const useAccountingHistory = () => { + return useQuery( + [`employees-salaries-history/`], + () => AccountingController.history(), + { + refetchOnWindowFocus: false, + } + ); +}; diff --git a/src/Hooks/Stats/index.ts b/src/Hooks/Stats/index.ts index d91d274..4f8bf74 100644 --- a/src/Hooks/Stats/index.ts +++ b/src/Hooks/Stats/index.ts @@ -1,3 +1,8 @@ +import { + TGeneralChartGetParams, + TStatCreatorsGetParams, + TteamChartGetParams, +} from "./../../API/LayoutApi/statistic"; import { useQuery } from "react-query"; import { TStatGetParams, statController } from "../../API/LayoutApi/statistic"; @@ -27,10 +32,34 @@ export const useStatTeamData = ({ ); }; -export const useCreatorsData = ({ start_date, end_date }: TStatGetParams) => { +export const useCreatorsData = ({ + start_date, + end_date, + search, +}: TStatCreatorsGetParams) => { + return useQuery( + [`stats/task-creators/`, search, start_date, end_date], + () => statController.creators({ start_date, end_date, search }), + { refetchOnWindowFocus: false } + ); +}; +export const useTeamChartData = ({ + start_date, + end_date, +}: TteamChartGetParams) => { + return useQuery( + [`stats/teams-line-chart/`, start_date, end_date], + () => statController.teamChart({ start_date, end_date }), + { refetchOnWindowFocus: false } + ); +}; +export const useGeneralChartData = ({ + start_date, + end_date, +}: TGeneralChartGetParams) => { return useQuery( - [`stats/task-creators/`, start_date, end_date], - () => statController.creators({ start_date, end_date }), + [`stats/general-stats/`, start_date, end_date], + () => statController.generalChart({ start_date, end_date }), { refetchOnWindowFocus: false } ); }; diff --git a/src/Hooks/Users/index.ts b/src/Hooks/Users/index.ts index e9fb266..77b36d9 100644 --- a/src/Hooks/Users/index.ts +++ b/src/Hooks/Users/index.ts @@ -1,10 +1,16 @@ import { useQuery } from "react-query"; import { TUsersGetParams, userController } from "../../API/LayoutApi/users"; -export const useUserData = ({ name, team, role }: TUsersGetParams) => { +export const useUserData = ({ + name, + team, + role, + page, + page_size, +}: TUsersGetParams) => { return useQuery( - [`users/admins/`, { name, team, role }], - () => userController.read({ name, team, role }), + [`users/admins/`, { name, team, role, page, page_size }], + () => userController.read({ name, team, role, page, page_size }), { refetchOnWindowFocus: false } ); }; diff --git a/src/Utils/sidebar.tsx b/src/Utils/sidebar.tsx index 94f1117..d0dec53 100644 --- a/src/Utils/sidebar.tsx +++ b/src/Utils/sidebar.tsx @@ -12,6 +12,8 @@ import Stat from "../Components/Statistics/Statistic"; import Profile from "../Components/Profile/Profile"; import Update from "../Components/Updates/Update"; import UpdateEdit from "../Components/Updates/UpdateEdit"; +import Accounting from "../Components/Accounting/Accounting"; +import AccountingDetails from "../Components/Accounting/AccountingDetails"; type TItems = { path: string; @@ -50,6 +52,16 @@ export const mainItems: TItems[] = [ component: , key: "/service/:id/", }, + { + path: "/accounting/", + component: , + key: "/accounting/", + }, + { + path: "/accounting/:id/", + component: , + key: "/accounting/:id/", + }, ]; export const superItems: TItems[] = [ @@ -75,7 +87,7 @@ export const superItems: TItems[] = [ }, { path: "/stats/", - component: , + component: , key: "/stats/", }, { diff --git a/src/Utils/styles.ts b/src/Utils/styles.ts index 161ab88..817138b 100644 --- a/src/Utils/styles.ts +++ b/src/Utils/styles.ts @@ -104,6 +104,16 @@ export const dark = { colorText: "#fff", colorTextDisabled: "#fff", }, + Drawer: { + colorBgElevated: "#262626", // Drawer foni + colorText: "#ffffff", // Matn rangi + colorPrimary: "#f99e2c", // Asosiy tugma rangi + colorPrimaryHover: "#ffb347", // Tugmalar hover rangi + paddingLG: 24, // Ichki padding + borderRadiusLG: 10, // Burchaklarni yumshatish + colorIcon: "#ffffff", // Close tugmasining rangi (✖) + colorIconHover: "#ffb347", + }, Empty: { colorText: "rgba(249, 158, 44, 1)", colorTextDisabled: "rgba(249, 158, 44, 1)", diff --git a/src/assets/acoountingIcon.svg b/src/assets/acoountingIcon.svg new file mode 100644 index 0000000..3a89d09 --- /dev/null +++ b/src/assets/acoountingIcon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/techsupportIcon.svg b/src/assets/techsupportIcon.svg new file mode 100644 index 0000000..466f48a --- /dev/null +++ b/src/assets/techsupportIcon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/techsupportIconActive.svg b/src/assets/techsupportIconActive.svg new file mode 100644 index 0000000..5c27e23 --- /dev/null +++ b/src/assets/techsupportIconActive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/types/Accounting/TAccounting.ts b/src/types/Accounting/TAccounting.ts new file mode 100644 index 0000000..b706697 --- /dev/null +++ b/src/types/Accounting/TAccounting.ts @@ -0,0 +1,28 @@ +export type TAccounting = { + full_name: string; + is_confirmed: boolean; + id: number; + number_of_tasks: number; + performance_based_amount: string; + salary: string; + salary_base_amount: string; + salary_type: string; + total_bonuses: string; + total_charges: string; + total_points: number; + username: string; +}; + +export type TAccountingHistory = { + id: number; + full_name: string; + username: string; + total_number_of_tasks: number; + salary_type: string; + total_bonuses: string; + total_charges: string; + total_earned_points: string; + total_base_salary: string; + total_performance_salary: string; + total_earned_salary: string; +}; diff --git a/src/types/Statistic/TStat.ts b/src/types/Statistic/TStat.ts index de65e1d..cd24a42 100644 --- a/src/types/Statistic/TStat.ts +++ b/src/types/Statistic/TStat.ts @@ -13,6 +13,36 @@ export type TStatTeam = { total_points: number; }; +export type TStatCreators = { + id: number; + username: string; + full_name: string; + number_of_tasks: number; +}; + +type TaskPerformance = { + number_of_tasks: number; + total_points: number; +}; +export type TteamChartData = { + date: string; + [category: string]: TaskPerformance | string; +}; + +export type TGeneralDailyStat = { + task_date: string; + total_tasks: number; + completed_tasks: number; + incomplete_tasks: number; +}; + +export type TGeneralWrapper = { + daily_stats: TGeneralDailyStat[]; +}; +export type TGeneralChartData = { + daily_stats: TGeneralWrapper[]; +}; + export type TCard = { all_tasks: number; active_tasks: number; diff --git a/src/types/User/TUser.ts b/src/types/User/TUser.ts index e945509..ed4385b 100644 --- a/src/types/User/TUser.ts +++ b/src/types/User/TUser.ts @@ -9,4 +9,13 @@ export type TUser = { is_superuser: boolean; salary_type: string; salary_base_amount: number; -} \ No newline at end of file +} + +export type TUserResponse = { + page: number; + page_size: number; + next: string | null; + previous: string | null; + current_time: string; + data: TUser[]; +}; \ No newline at end of file