import { Button, DatePicker, Drawer, Dropdown, Form, Input, Menu, message, Modal, Select, Table, Tooltip, Typography, } from "antd"; import React, { useEffect, useRef, useState } from "react"; import tagIcon from "../../assets/tagIcon.svg"; import { CheckCircleOutlined, CloseOutlined, DeleteOutlined, DollarOutlined, EditOutlined, EllipsisOutlined, InfoCircleOutlined, PlusOutlined, QuestionCircleOutlined, SearchOutlined, } from "@ant-design/icons"; import { theme } from "antd"; import { useAccountingData } from "../../Hooks/Accounting"; import moment from "moment"; import api from "../../API/api"; import dayjs from "dayjs"; import { useTeamData } from "../../Hooks/Teams"; type Employee = { id: number; full_name: string; username: string; }; type BonusData = { employee: Employee; charges: any[]; total: number; }; interface BonusesTableProps { bonusesData: BonusData[]; } const AccountingCurrent: React.FC = () => { const themes = localStorage.getItem("theme") === "true" ? true : false; const [open, setOpen] = useState(false); const [selectedUser, setSelectedUser] = useState<any>(null); const currentDate = new Date(); currentDate.setMonth(currentDate.getMonth() - 1); const previousMonth = currentDate.toLocaleString("default", { month: "long", }); const lastMonth = moment().subtract(1, "months").month(); const lastMonthYear = moment().subtract(1, "months").year(); const lastMonthDefault = dayjs().subtract(1, "month"); const today = moment().endOf("day"); const disabledDate = (current: any) => { return ( current && (current.month() !== lastMonth || current.year() !== lastMonthYear || current > today) ); }; const [form] = Form.useForm(); const [selectedUserId, setSelectedUserId] = useState(null); const [selectedDate, setSelectedDate] = useState<moment.Moment | null>(null); const [isBonusModalVisible, setIsBonusModalVisible] = useState(false); const [isChargeModalVisible, setIsChargeModalVisible] = useState(false); const [bonusesData, setBonusesData] = useState<BonusesTableProps[]>([]); const [chargesData, setChargesData] = useState([]); const [search, setSearch] = useState<string>(""); const [team, setTeam] = useState<any>(""); const showBonusModal = (userId: any) => { setSelectedUserId(userId); // Tanlangan user_id ni saqlash setIsBonusModalVisible(true); }; const showChargeModal = (userId: any) => { setIsChargeModalVisible(true); setSelectedUserId(userId); }; const handleCancel = () => { setIsBonusModalVisible(false); setIsChargeModalVisible(false); }; const { data, refetch, isLoading } = useAccountingData({ month: "last", search: search, team: team, }); const handleDateChange = (date: any) => { setSelectedDate(date); form.setFieldsValue({ date }); }; const handleOkCharge = () => { form .validateFields() .then((values) => { const { date, amount, notes } = values; if (selectedUserId) { api .post(`/add-charge/${selectedUserId}/`, { date: date ? date.toISOString().split("T")[0] : null, amount: amount, reason: notes, }) .then((response) => { message.success(response.data.message); setIsChargeModalVisible(false); form.resetFields(); refetch(); }) .catch((error) => { console.error("API error:", error); }); } }) .catch((info) => { console.log("Validate Failed:", info); }); }; const handleOkBonus = () => { form .validateFields() .then((values) => { const { date, amount, notes } = values; if (selectedUserId) { api .post(`/add-bonus/${selectedUserId}/`, { date: date ? date.toISOString().split("T")[0] : null, amount: amount, reason: notes, }) .then((response) => { message.success(response.data.message); setIsBonusModalVisible(false); form.resetFields(); refetch(); }) .catch((error) => { console.error("API error:", error); }); } }) .catch((info) => { console.log("Validate Failed:", info); }); }; const menu = (userId: any) => ( <Menu> <Menu.Item key="1" onClick={() => showBonusModal(userId)}> <PlusOutlined /> Add Bonus </Menu.Item> <Menu.Item key="2" onClick={() => showChargeModal(userId)}> <PlusOutlined /> Add Charge </Menu.Item> </Menu> ); useEffect(() => { if (isBonusModalVisible || isChargeModalVisible) { setOpen(false); } }, [isBonusModalVisible, isChargeModalVisible]); const handleRowClick = (record: any, e: React.MouseEvent<HTMLElement>) => { setSelectedUser(record); const target = e.target as HTMLElement; const employee_id = record.employee_id; const apiUrl = `/bonuses/${employee_id}`; const chargeUrl = `/charges/${employee_id}`; const params = { month: "last", }; api .get(apiUrl, { params }) .then((response) => { const formattedData = response.data.bonuses.map( (bonus: any, index: any) => ({ no: index + 1, id: bonus.id, // date: bonus.date, amount: bonus.amount, reason: bonus.reason || "", username: response.data.employee.username, total: response.data.total, }) ); setBonusesData(formattedData); }) .catch((error) => { console.error("Error:", error); }); api .get(chargeUrl, { params }) .then((response) => { const formattedData = response.data.charges.map( (charges: any, index: any) => ({ no: index + 1, id: charges.id, // date: charges.date, amount: charges.amount, reason: charges.reason || "", username: response.data.employee.username, total: response.data.total, }) ); setChargesData(formattedData); }) .catch((error) => { console.error("Error:", error); }); if (!target.closest(".ant-dropdown-trigger")) { setOpen(true); } }; const [isEditModalVisible, setIsEditModalVisible] = useState(false); const [isEditBonusModalVisible, setIsEditBonusModalVisible] = useState(false); const [selectedRecord, setSelectedRecord] = useState<{ id?: string } | null>( null ); const [selectedRecordBonus, setSelectedRecordBonus] = useState<{ id?: string; } | null>(null); const showModalBonusEdit = (record: any) => { if (!record || typeof record !== "object") { console.error("Invalid record:", record); return; } // const recordWithoutDate = { ...record }; // delete recordWithoutDate.date; setSelectedRecordBonus(record); form.setFieldsValue(record); setIsEditBonusModalVisible(true); }; const showModal = (record: any) => { if (!record || typeof record !== "object") { console.error("Invalid record:", record); return; } // const recordWithoutDate = { ...record }; // delete recordWithoutDate.date; setSelectedRecord(record); form.setFieldsValue(record); setIsEditModalVisible(true); }; const Cancel = () => { setIsEditModalVisible(false); setIsEditBonusModalVisible(false); form.resetFields(); }; const handleOkBonusEdit = async () => { try { // Formani tekshirish const values = await form.validateFields(); if (!selectedRecordBonus) { throw new Error("No record selected!"); } // Obyektni biriktirish: selectedRecord va formdagi values const updatedData = { ...(selectedRecordBonus || {}), ...values }; // Axios so‘rovini yuborish const response = await api.put( `bonus/${selectedRecordBonus.id}/`, updatedData, { headers: { "Content-Type": "application/json", }, } ); // Javobni tekshirish if (response.status === 202) { setBonusesData((prevData: any) => prevData.map((item: any) => item.id === selectedRecordBonus.id ? { ...item, ...updatedData } : item ) ); refetch(); setIsEditBonusModalVisible(false); } else { throw new Error("Server Error"); } } catch (error) { console.error(error); } }; // EDIT modal Charge const handleOk = async () => { try { const values = await form.validateFields(); if (!selectedRecord) { throw new Error("No record selected!"); } const updatedData = { ...(selectedRecord || {}), ...values, }; const response = await api.put( `charge/${selectedRecord.id}/`, updatedData, { headers: { "Content-Type": "application/json", }, } ); if (response.status === 202) { setChargesData((prevData: any) => prevData.map((item: any) => item.id === selectedRecord.id ? { ...item, ...updatedData } : item ) ); refetch(); setIsEditModalVisible(false); } else { throw new Error("Server Error"); } } catch (error) { console.error(error); } }; const confirmSalaries = async () => { try { const response = await api.post("/employees-salaries-confirm/"); refetch(); } catch (error) { console.error("Error confirming salaries:", error); } }; 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 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 { token } = theme.useToken(); const deleteFunctionBonus = (record: any) => { const { id } = record; const apiUrl = `/bonus/${id}`; api .delete(apiUrl) .then((response) => { if (response.status === 200) { setBonusesData((prevData: any) => { const updatedData = prevData.filter((item: any) => item.id !== id); console.log("Updated Data:", updatedData); return updatedData; }); message.success(response.data.message); } refetch(); }) .catch((error) => { console.error(error); }); }; const deleteFunctionCharge = (record: any) => { const { id } = record; const apiUrl = `/charge/${id}`; api .delete(apiUrl) .then((response) => { if (response.status === 200) { setChargesData((prevData: any) => { const updatedData = prevData.filter((item: any) => item.id !== id); console.log("Updated Data:", updatedData); return updatedData; }); message.success(response.data.message); } refetch(); }) .catch((error) => { console.error(error); }); }; return ( <div style={{ paddingBottom: 40 }}> <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10, }} > <div> <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} /> </div> </div> <Button disabled={data?.some((item) => item.is_confirmed)} type="primary" onClick={() => Modal.confirm({ title: "", icon: null, content: ( <div style={{ display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", padding: "20px", }} > <InfoCircleOutlined style={{ fontSize: "40px", color: "#f99e2c", marginBottom: "12px", }} /> <div style={{ fontSize: "18px", fontWeight: 700, marginBottom: "8px", maxWidth: "300px", }} > Are you sure you want to confirm the salaries for the{" "} {previousMonth}? </div> <div style={{ fontSize: "14px", color: "#0F111C", marginBottom: "24px", maxWidth: "300px", fontWeight: 500, }} > Bonuses and charges cannot be added or altered after the confirmation. </div> </div> ), okButtonProps: { style: { backgroundColor: "#F99E2C", borderColor: "#F99E2C", color: "white", borderRadius: "8px", width: "162px", height: "40px", fontSize: "14px", padding: "0 20px", }, }, cancelButtonProps: { style: { borderRadius: "8px", width: "162px", height: "40px", fontSize: "14px", padding: "0 20px", }, }, footer: (originFooter) => ( <div style={{ display: "flex", justifyContent: "center", gap: "8px", }} > {originFooter} </div> ), okText: "Confirm", onOk() { confirmSalaries(); }, onCancel() {}, }) } > Confirm </Button> </div> <Table size="small" loading={isLoading} dataSource={data?.map((u, i) => ({ no: i + 1, ...u, }))} columns={[ { title: <img src={tagIcon} alt="" />, dataIndex: "no", key: "no", width: "5%", align: "center", }, { title: "Username", dataIndex: "username", key: "username", render: (text, record) => ( <Tooltip title={ record?.full_name?.trim() ? record.full_name : "User does not have a name" } > <span>{text}</span> </Tooltip> ), }, { title: "Role", dataIndex: "role", key: "role", filters: [ { text: "Tech Support", value: "Tech Support", }, { text: "Checker", value: "Checker", }, { text: "Accountant", value: "Accountant", }, ], filterMultiple: false, onFilter: (value: any, record: any) => { return record.role === value; }, }, { title: "Team", dataIndex: "team_name", key: "team_name", }, { 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 <p>Task Based</p>; } else if (record.salary_type === "hybrid") { return <p>Hybrid</p>; } else if (record.salary_type === "fixed") { return <p>Fixed</p>; } else { return <p>{record.salary_type}</p>; } }, filters: [ { text: "Hybrid", value: "hybrid", }, { text: "Task Based", value: "task_based", }, { text: "Fixed", value: "fixed", }, ], 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) => ( <p>${record?.salary_base_amount}</p> ), }, { title: "Performance Salary", dataIndex: "performance_salary", render: (text: string, record: any) => ( <p>${record?.performance_salary}</p> ), }, { title: "Charges", dataIndex: "total_charges", render: (text: string, record: any) => ( <p>${record?.total_charges}</p> ), }, { title: "Bonuses", dataIndex: "total_bonuses", render: (text: string, record: any) => ( <p>${record?.total_bonuses}</p> ), }, { title: "Salary", dataIndex: "salary", key: "salary", render: (text: string, record: any) => ( <span>${record.salary}</span> ), }, { title: "Actions", key: "actions", align: "center", render: (_: any, record: any) => ( <Dropdown overlay={menu(record.employee_id)} trigger={["click"]} disabled={data?.some((item) => item.is_confirmed)} > <Button icon={<EllipsisOutlined />} onClick={(e) => e.stopPropagation()} /> </Dropdown> ), }, ]} 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: (e) => handleRowClick(record, e), })} /> {/* <div style={{ display: "flex", justifyContent: "end", alignItems: "center", marginTop: 24, }} > <Button disabled={data?.some((item) => item.is_confirmed)} type="primary" onClick={() => Modal.confirm({ title: "", icon: null, content: ( <div style={{ display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", padding: "20px", }} > <InfoCircleOutlined style={{ fontSize: "40px", color: "#f99e2c", marginBottom: "12px", }} /> <div style={{ fontSize: "18px", fontWeight: 700, marginBottom: "8px", maxWidth: "300px", }} > Are you sure you want to confirm the salaries for the{" "} {previousMonth}? </div> <div style={{ fontSize: "14px", color: "#0F111C", marginBottom: "24px", maxWidth: "300px", fontWeight: 500, }} > Bonuses and charges cannot be added or altered after the confirmation. </div> </div> ), okButtonProps: { style: { backgroundColor: "#F99E2C", borderColor: "#F99E2C", color: "white", borderRadius: "8px", width: "162px", height: "40px", fontSize: "14px", padding: "0 20px", }, }, cancelButtonProps: { style: { borderRadius: "8px", width: "162px", height: "40px", fontSize: "14px", padding: "0 20px", }, }, footer: (originFooter) => ( <div style={{ display: "flex", justifyContent: "center", gap: "8px", }} > {originFooter} </div> ), okText: "Confirm", onOk() { confirmSalaries(); }, onCancel() {}, }) } > Confirm </Button> </div> */} <Drawer title={ <Typography className="title"> {selectedUser?.full_name?.trim() ? selectedUser.full_name : selectedUser?.username} </Typography> } placement="right" width={850} onClose={() => setOpen(false)} closable={false} open={open} extra={ <Button type="text" icon={<CloseOutlined />} onClick={() => setOpen(false)} /> } > <div className="info-div"> <Typography style={{ fontSize: 18, fontWeight: 700, lineHeight: "24px", letterSpacing: "-0.02em", marginBottom: 16, }} > Information </Typography> <div className="info-body"> <div className="d-flex" style={{ justifyContent: "space-between" }}> <div> <tr> <p className={!themes ? "sub" : "sub-dark"}>Username</p> <p className={!themes ? "info" : "info-dark"}> {selectedUser?.username} </p> </tr> <tr> <p className={!themes ? "sub" : "sub-dark"}>Role</p> <p className={!themes ? "info" : "info-dark"}> {selectedUser?.role} </p> </tr> <tr> <p className={!themes ? "sub" : "sub-dark"}>Tasks</p> <p className={!themes ? "info" : "info-dark"}> {selectedUser?.number_of_tasks} </p> </tr> <tr> <p className={!themes ? "sub" : "sub-dark"}>Points</p> <p className={!themes ? "info" : "info-dark"}> {selectedUser?.total_points} </p> </tr> </div> <div> <tr> <p className={!themes ? "sub" : "sub-dark"}>Last month</p> <p className={!themes ? "info" : "info-dark"}> {previousMonth} </p> </tr> <tr> <p className={!themes ? "sub" : "sub-dark"}>Bonus</p> <p className={!themes ? "info" : "info-dark"}> ${selectedUser?.total_bonuses} </p> </tr> <tr> <p className={!themes ? "sub" : "sub-dark"}>Charge</p> <p className={!themes ? "info" : "info-dark"}> ${selectedUser?.total_charges} </p> </tr> <tr> <p className={!themes ? "sub" : "sub-dark"}>Salary</p> <p className={!themes ? "info" : "info-dark"}> ${selectedUser?.salary} </p> </tr> </div> </div> </div> </div> <div style={{ display: "flex", justifyContent: "space-between", gap: 25, marginTop: 25, }} > <div style={{ width: "50%" }}> <Typography style={{ fontSize: 18, fontWeight: 700, lineHeight: "24px", letterSpacing: "-0.02em", marginBottom: 16, }} > Bonuses </Typography> <Table bordered size="small" dataSource={bonusesData} columns={[ { title: <img src={tagIcon} alt="" />, dataIndex: "no", key: "no", width: "5%", align: "center", }, { title: "Date", dataIndex: "date", render: (text: string) => moment(text).format("MMMM DD"), }, { title: "Amount", dataIndex: "amount", render: (text: string, record: any) => ( <p>${record?.amount}</p> ), }, { title: "Action", // width: "5%", align: "center", render: (_, record) => ( <> <Button icon={<EditOutlined />} disabled={data?.some((item) => item.is_confirmed)} onClick={() => showModalBonusEdit(record)} style={{ marginRight: 8 }} /> <Button style={{ color: "red", borderColor: "red", // Butonning chegarasini qizil qilish }} icon={<DeleteOutlined />} onClick={() => deleteFunctionBonus(record)} disabled={data?.some((item) => item.is_confirmed)} /> </> ), }, ]} pagination={{ pageSize: 10, showLessItems: true, }} /> </div> <div style={{ width: "50%" }}> <Typography style={{ fontSize: 18, fontWeight: 700, lineHeight: "24px", letterSpacing: "-0.02em", marginBottom: 16, }} > Charges </Typography> <Table size="small" bordered dataSource={chargesData} columns={[ { title: <img src={tagIcon} alt="" />, dataIndex: "no", key: "no", width: "5%", align: "center", }, { title: "Date", dataIndex: "date", render: (text: string) => moment(text).format("MMMM DD"), }, { title: "Amount", dataIndex: "amount", render: (text: string, record: any) => ( <p>${record?.amount}</p> ), }, { title: "Action", // width: "5%", align: "center", render: (_, record) => ( <> <Button icon={<EditOutlined />} disabled={data?.some((item) => item.is_confirmed)} onClick={() => showModal(record)} style={{ marginRight: 8 }} /> <Button style={{ color: "red", borderColor: "red", // Butonning chegarasini qizil qilish }} icon={<DeleteOutlined />} onClick={() => deleteFunctionCharge(record)} disabled={data?.some((item) => item.is_confirmed)} /> </> ), }, ]} pagination={{ showLessItems: true, }} /> </div> </div> </Drawer> <Modal title="Add Bonus" visible={isBonusModalVisible} onCancel={handleCancel} onOk={handleOkBonus} maskClosable={false} okText={ <span> <PlusOutlined style={{ marginRight: 4 }} /> Add </span> } cancelText={ <span> <CloseOutlined style={{ marginRight: 4 }} /> Cancel </span> } // footer={null} > <Form form={form} layout="vertical"> {/* Date Picker */} <Form.Item name="date" label="Date" rules={[{ required: true, message: "Please select a date!" }]} > <DatePicker style={{ width: "100%" }} disabledDate={disabledDate} format="YYYY MMMM DD" defaultPickerValue={lastMonthDefault} onChange={handleDateChange} // value={lastMonthDefault} /> </Form.Item> {/* Amount Input */} <Form.Item name="amount" label="Amount" rules={[{ required: true, message: "Please enter an amount!" }]} > <Input prefix={<DollarOutlined />} type="number" min={0} style={{ width: "100%" }} placeholder="0" onKeyPress={(e) => { if (e.key === "-" || e.key === "e") { e.preventDefault(); } }} /> </Form.Item> {/* Textarea */} <Form.Item name="notes" label="Notes"> <Input.TextArea placeholder="Enter notes here" rows={4} /> </Form.Item> </Form> </Modal> {/* Charge Modal */} <Modal title="Add Charge" visible={isChargeModalVisible} onCancel={handleCancel} maskClosable={false} onOk={handleOkCharge} okText={ <span> <PlusOutlined style={{ marginRight: 4 }} /> Add </span> } cancelText={ <span> <CloseOutlined style={{ marginRight: 4 }} /> Cancel </span> } > <Form form={form} layout="vertical"> {/* Date Picker */} <Form.Item name="date" label="Date" rules={[{ required: true, message: "Please select a date!" }]} > <DatePicker style={{ width: "100%" }} disabledDate={disabledDate} defaultPickerValue={lastMonthDefault} format="YYYY MMMM DD" onChange={handleDateChange} // value={selectedDate} /> </Form.Item> {/* Amount Input */} <Form.Item name="amount" label="Amount" rules={[{ required: true, message: "Please enter an amount!" }]} > <Input prefix={<DollarOutlined />} type="number" style={{ width: "100%" }} placeholder="0" onKeyPress={(e) => { if (e.key === "-" || e.key === "e") { e.preventDefault(); } }} /> </Form.Item> {/* Textarea */} <Form.Item name="notes" label="Notes"> <Input.TextArea placeholder="Enter notes here" rows={4} /> </Form.Item> </Form> </Modal> {/* EDIT CHARGE MODAL */} <Modal title="Edit Charge" visible={isEditModalVisible} onCancel={Cancel} onOk={handleOk} maskClosable={false} okText={ <span> <PlusOutlined style={{ marginRight: 4 }} /> Save </span> } cancelText={ <span> <CloseOutlined style={{ marginRight: 4 }} /> Cancel </span> } > <Form form={form} layout="vertical"> <Form.Item name="amount" label="Amount" rules={[{ required: true, message: "Please enter an amount!" }]} > <Input prefix={<DollarOutlined />} type="number" style={{ width: "100%" }} placeholder="0" onKeyPress={(e) => { if (e.key === "-" || e.key === "e") { e.preventDefault(); } }} /> </Form.Item> {/* Textarea */} <Form.Item name="reason" label="Notes"> <Input.TextArea placeholder="Enter notes here" rows={4} /> </Form.Item> </Form> </Modal> {/* EDIT Bonus MODAL */} <Modal title="Edit Bonus" visible={isEditBonusModalVisible} onCancel={Cancel} onOk={handleOkBonusEdit} maskClosable={false} okText={ <span> <PlusOutlined style={{ marginRight: 4 }} /> Save </span> } cancelText={ <span> <CloseOutlined style={{ marginRight: 4 }} /> Cancel </span> } > <Form form={form} layout="vertical"> <Form.Item name="amount" label="Amount" rules={[{ required: true, message: "Please enter an amount!" }]} > <Input prefix={<DollarOutlined />} type="number" style={{ width: "100%" }} placeholder="0" onKeyPress={(e) => { if (e.key === "-" || e.key === "e") { e.preventDefault(); } }} /> </Form.Item> <Form.Item name="reason" label="Notes"> <Input.TextArea placeholder="Enter notes here" rows={4} /> </Form.Item> </Form> </Modal> </div> ); }; export default AccountingCurrent;