From 3cdc0ce06f61405a3389d6b132adbd46cc14e9f1 Mon Sep 17 00:00:00 2001 From: Dilmurod Date: Mon, 15 Sep 2025 19:34:12 +0500 Subject: [PATCH] Add "My Salary" to profile; fix edit modal bugs in shift data tabs; add "Send to Telegram" button --- src/API/LayoutApi/profile.ts | 8 +- src/API/LayoutApi/tasks.ts | 15 ++ src/App.css | 6 + .../Profile/MySalary/CurrentMonthCard.tsx | 141 +++++++++++++ .../Profile/MySalary/SalaryHistoryTable.tsx | 72 +++++++ .../Profile/MySalary/TotalStatistics.tsx | 159 +++++++++++++++ src/Components/Profile/MySalary/index.tsx | 104 ++++++++++ src/Components/Profile/Profile.tsx | 5 +- .../ShiftInfo/ShiftAndCoDriverEditModal.tsx | 188 ++++++++++++++---- .../Tasks/ShiftInfo/ShiftDataTab.tsx | 31 ++- src/Hooks/Profile/index.ts | 11 + src/types/Profile/TProfile.ts | 95 +++++++-- 12 files changed, 772 insertions(+), 63 deletions(-) create mode 100644 src/Components/Profile/MySalary/CurrentMonthCard.tsx create mode 100644 src/Components/Profile/MySalary/SalaryHistoryTable.tsx create mode 100644 src/Components/Profile/MySalary/TotalStatistics.tsx create mode 100644 src/Components/Profile/MySalary/index.tsx diff --git a/src/API/LayoutApi/profile.ts b/src/API/LayoutApi/profile.ts index e69ef69..6b1221b 100644 --- a/src/API/LayoutApi/profile.ts +++ b/src/API/LayoutApi/profile.ts @@ -1,5 +1,6 @@ import { message } from "antd"; import { + MySalaryResponse, TMyTaskHistory, TMystats, TProfile, @@ -71,7 +72,7 @@ export const prof = { if (params.username) { localStorage.setItem("username", params.username); } - window.location.reload() + window.location.reload(); return data; } catch (error: any) { setTimeout(() => { @@ -105,4 +106,9 @@ export const prof = { throw error; } }, + + async mySalary() { + const { data } = await instance.get("users/my-salary/"); + return data; + }, }; diff --git a/src/API/LayoutApi/tasks.ts b/src/API/LayoutApi/tasks.ts index fc417e1..213cdd2 100644 --- a/src/API/LayoutApi/tasks.ts +++ b/src/API/LayoutApi/tasks.ts @@ -188,4 +188,19 @@ export const taskController = { } return { data: res, error }; }, + + async sendTelegram(id: string) { + try { + const { data } = await instance.post(`task-send-to-telegram-bot/${id}/`); + return data; + } catch (error: any) { + if (error.response) { + console.error("Telegram API error:", error.response.data); + } else if (error.request) { + console.error("No response from server:", error.request); + } else { + console.error("Unexpected error:", error.message); + } + } + }, }; diff --git a/src/App.css b/src/App.css index cbe7719..8dbd295 100644 --- a/src/App.css +++ b/src/App.css @@ -146,6 +146,12 @@ text-align: right; } +.profile-my-salary { + display: flex; + flex-direction: column; + gap: 24px; +} + .dot-true { width: 10px; height: 10px; diff --git a/src/Components/Profile/MySalary/CurrentMonthCard.tsx b/src/Components/Profile/MySalary/CurrentMonthCard.tsx new file mode 100644 index 0000000..77a84dc --- /dev/null +++ b/src/Components/Profile/MySalary/CurrentMonthCard.tsx @@ -0,0 +1,141 @@ +import { Card, Statistic, Row, Col } from "antd"; +import dayjs from "dayjs"; +import { CurrentMonth } from "../../../types/Profile/TProfile"; + +type Props = { + current: CurrentMonth; +}; + +const CurrentMonthCard: React.FC = ({ current }) => { + return ( + + + {`Current month / ${dayjs().format("MMMM")}`} + + } + value={current.salary} + prefix="$" + precision={2} + valueStyle={{ + fontFamily: "Inter", + fontSize: "24px", + fontStyle: "normal", + fontWeight: 700, + lineHeight: "28px", + letterSpacing: "-0.96px", + }} + /> + + +
+ + Bonuses: + + +
+ + + +
+ + Charges: + + +
+ + + +
+ + Tasks: + + +
+ + + +
+ + Points: + + +
+ +
+
+ ); +}; + +export default CurrentMonthCard; diff --git a/src/Components/Profile/MySalary/SalaryHistoryTable.tsx b/src/Components/Profile/MySalary/SalaryHistoryTable.tsx new file mode 100644 index 0000000..f3a998e --- /dev/null +++ b/src/Components/Profile/MySalary/SalaryHistoryTable.tsx @@ -0,0 +1,72 @@ +import { Table, Typography } from "antd"; +import { SalaryHistory } from "../../../types/Profile/TProfile"; + +const { Title } = Typography; + +type Props = { + year: number; + salaries: SalaryHistory[]; +}; + +const SalaryHistoryTable: React.FC = ({ year, salaries }) => { + return ( +
+ + {year} + + + dataSource={salaries} + rowKey="id" + pagination={false} + size="large" + columns={[ + { + title: "#", + dataIndex: "no", + width: "5%", + align: "center", + render: (_, __, index) => index + 1, + }, + { + title: "Month", + dataIndex: "month", + }, + { + title: "Total Salary", + dataIndex: "total_salary", + render: (_, record) => ${record.total_salary}, + }, + { + title: "Total Bonuses", + dataIndex: "total_bonuses", + render: (_, record) => ${record.total_bonuses}, + }, + { + title: "Total Charges", + dataIndex: "total_charges", + render: (_, record) => ${record.total_charges}, + }, + { + title: "Total Tasks", + dataIndex: "number_of_tasks", + }, + { + title: "Total Points", + dataIndex: "total_points", + }, + ]} + /> +
+ ); +}; + +export default SalaryHistoryTable; diff --git a/src/Components/Profile/MySalary/TotalStatistics.tsx b/src/Components/Profile/MySalary/TotalStatistics.tsx new file mode 100644 index 0000000..80cbaf8 --- /dev/null +++ b/src/Components/Profile/MySalary/TotalStatistics.tsx @@ -0,0 +1,159 @@ +import { Row, Col, Statistic } from "antd"; +import { Total } from "../../../types/Profile/TProfile"; + +type Props = { + total: Total; +}; + +const TotalStatistics: React.FC = ({ total }) => { + return ( + + +
+ + Total salary + + + ${total.total_earned_salary} + +
+ + + +
+ + Total bonuses + + + +${total.total_bonuses} + +
+ + + +
+ + Total charges + + + -${total.total_charges} + +
+ + + +
+ + Total tasks + + + {total.total_number_of_tasks} + +
+ + + +
+ + Total points + + + {total.total_earned_points} + +
+ +
+ ); +}; + +export default TotalStatistics; diff --git a/src/Components/Profile/MySalary/index.tsx b/src/Components/Profile/MySalary/index.tsx new file mode 100644 index 0000000..a220b77 --- /dev/null +++ b/src/Components/Profile/MySalary/index.tsx @@ -0,0 +1,104 @@ +import React, { useEffect, useState } from "react"; +import { Spin, Typography } from "antd"; +import { useMySalaryData } from "../../../Hooks/Profile"; + +import CurrentMonthCard from "./CurrentMonthCard"; +import TotalStatistics from "./TotalStatistics"; +import SalaryHistoryTable from "./SalaryHistoryTable"; +import { SalaryHistory } from "../../../types/Profile/TProfile"; + +const { Title } = Typography; + +const MySalary: React.FC = () => { + const { data, isLoading } = useMySalaryData(); + + const [years, setYears] = useState([]); + + const extractYears = (history: SalaryHistory[]): number[] => { + return Array.from(new Set(history.map((s) => s.year))).sort( + (a, b) => b - a + ); + }; + useEffect(() => { + if (data?.salary_history && data.salary_history.length > 0) { + setYears(extractYears(data.salary_history)); + } + }, [data?.salary_history]); + + if (isLoading) + return ( +
+ +
+ ); + + if (!data) + return ( +
+

No data

+
+ ); + + return ( +
+
+ + Current Month + + + +
+ +
+ + Total + + + +
+ +
+ {years.map((year) => ( + s.year === year)} + /> + ))} +
+
+ ); +}; + +export default MySalary; diff --git a/src/Components/Profile/Profile.tsx b/src/Components/Profile/Profile.tsx index a98b330..0238147 100644 --- a/src/Components/Profile/Profile.tsx +++ b/src/Components/Profile/Profile.tsx @@ -34,10 +34,10 @@ import { useMystatsData, useProfData, } from "../../Hooks/Profile"; -// @ts-ignore import tagIcon from "../../assets/tagIcon.svg"; import { role } from "../../App"; import ChangePassword from "./ChangePassword"; +import MySalary from "./MySalary"; const { Option } = Select; const Profile = () => { @@ -374,6 +374,9 @@ const Profile = () => { Change Password} key="3"> + My Salary} key="4"> + + diff --git a/src/Components/Tasks/ShiftInfo/ShiftAndCoDriverEditModal.tsx b/src/Components/Tasks/ShiftInfo/ShiftAndCoDriverEditModal.tsx index c2b5520..b247d45 100644 --- a/src/Components/Tasks/ShiftInfo/ShiftAndCoDriverEditModal.tsx +++ b/src/Components/Tasks/ShiftInfo/ShiftAndCoDriverEditModal.tsx @@ -16,42 +16,24 @@ const ShiftAndCoDriverEditModal: React.FC = ({ }) => { const [form] = Form.useForm(); - useEffect(() => { - if (recordTask) { - form.setFieldsValue({ - shift_date: recordTask.shift_date, - shift_location: recordTask.shift_location, - - cycle_date: recordTask.cycle_date, - cycle_location: recordTask.cycle_location, - - pickup_date: recordTask.pickup_date, - pickup_time: recordTask.pickup_time, - pickup_location: recordTask.pickup_location, - - driver_name: recordTask.driver_name, - co_driver_name: recordTask.co_driver_name, - co_driver_pickup_date: recordTask.co_driver_pickup_date, - co_driver_pickup_time: recordTask.co_driver_pickup_time, - co_driver_pickup_location: recordTask.co_driver_pickup_location, - co_driver_drop_date: recordTask.co_driver_drop_date, - co_driver_drop_time: recordTask.co_driver_drop_time, - co_driver_drop_location: recordTask.co_driver_drop_location, - }); - } - }, [recordTask, form]); - const handleOk = async () => { try { const values = await form.validateFields(); - taskController.taskPatch(values, recordTask.id); - onCancel(); + await taskController.taskPatch(values, recordTask.id); form.resetFields(); + onCancel(); } catch (error) { console.log("Validation Failed:", error); } }; + useEffect(() => { + if (recordTask && open) { + form.resetFields(); + form.setFieldsValue(recordTask); + } + }, [recordTask, open]); + return ( = ({ onOk={handleOk} destroyOnClose > -
+ {/* shift */} - + - + {/* cycle */} - + - + @@ -86,28 +109,73 @@ const ShiftAndCoDriverEditModal: React.FC = ({ - + - + - + {/* co driver */} - + - + @@ -116,6 +184,12 @@ const ShiftAndCoDriverEditModal: React.FC = ({ @@ -124,6 +198,12 @@ const ShiftAndCoDriverEditModal: React.FC = ({ @@ -133,18 +213,42 @@ const ShiftAndCoDriverEditModal: React.FC = ({ - + - + @@ -153,6 +257,12 @@ const ShiftAndCoDriverEditModal: React.FC = ({ diff --git a/src/Components/Tasks/ShiftInfo/ShiftDataTab.tsx b/src/Components/Tasks/ShiftInfo/ShiftDataTab.tsx index 9337a9a..a364103 100644 --- a/src/Components/Tasks/ShiftInfo/ShiftDataTab.tsx +++ b/src/Components/Tasks/ShiftInfo/ShiftDataTab.tsx @@ -1,7 +1,8 @@ -import { Button, Card, message } from "antd"; -import { CopyOutlined, EditOutlined } from "@ant-design/icons"; +import { Button, Card, message, notification } from "antd"; +import { CopyOutlined, EditOutlined, SendOutlined } from "@ant-design/icons"; import { useState } from "react"; import ShiftAndCoDriverEditModal from "./ShiftAndCoDriverEditModal"; +import { taskController } from "../../../API/LayoutApi/tasks"; interface ShiftDataTabProps { recordTask?: any; @@ -102,12 +103,38 @@ const ShiftDataTab: React.FC = ({ recordTask }) => { .catch(() => message.error("Failed to copy!")); }; + const handleSendTelegram = async () => { + if (!recordTask?.id) return; + + try { + await taskController.sendTelegram(recordTask.id); + notification.success({ + message: "Success", + description: "Message sent to Telegram successfully!", + placement: "topRight", + }); + } catch (error: any) { + notification.error({ + message: "Error", + description: error?.message || "Failed to send message to Telegram.", + placement: "topRight", + }); + } + }; + return ( <> +