add v1 for Monitoring Team development

dilmurod
Dilmurod 6 months ago
parent be0f954ff3
commit 0fde4aa50e

@ -9,6 +9,7 @@ export type TTasksGetParams = {
team?: string;
page?: number;
page_size: number;
team_monitoring?: string;
};
export type TTasksPutParams = {

@ -19,6 +19,25 @@ export type TTeamPostParams = {
is_active?: boolean;
};
export interface TeamMonitorCreatePayload {
name: string;
company_ids: number[];
}
export interface TeamMonitorUpdatePayload {
name?: string;
is_active?: boolean;
}
export interface UpdateTeamMonitorVars {
id: string;
payload: {
name?: string;
is_active?: boolean;
company_ids?: any[];
};
}
export const teamController = {
async read(obj: TTeamGetParams) {
const params = { ...obj };
@ -86,4 +105,47 @@ export const teamController = {
}
return { data: res, error };
},
// Team Monitoring
async readTeamsMonitor(obj: TTeamGetParams) {
const params: any = {};
if (obj.page) params.page = obj.page;
if (obj.page_size) params.page_size = obj.page_size;
if (obj.name) params.name = obj.name;
const { data } = await instance.get("/teams-monitor/", {
params,
});
return data;
},
async createTeamMonitor(payload: TeamMonitorCreatePayload) {
const { data } = await instance.post("/team-monitor/", payload);
return data;
},
async readTeamsGetOne(id: any) {
const { data } = await instance.get(`/team-monitor/${id}/`);
return data;
},
async deleteTeamMonitor(id: string) {
try {
const { data } = await instance.delete(`/team-monitor/${id}`);
return data;
} catch (err) {
throw new Error("Oops something went wrong!");
}
},
async updateTeamMonitor(id: string, payload: TeamMonitorUpdatePayload) {
try {
const { data } = await instance.put(`/team-monitor/${id}/`, payload);
return data;
} catch (error) {
throw new Error("Failed to update team monitor");
}
},
};

@ -654,6 +654,14 @@ const AccountingCurrent: React.FC = () => {
}
},
},
{
title: "Salary Rate",
dataIndex: "salary_rate",
key: "salary_rate",
render: (text: string, record: any) => (
<p>${record?.salary_rate}</p>
),
},
{
title: "Base Salary",
dataIndex: "salary_base_amount",

@ -305,6 +305,14 @@ const AccountingHistory: React.FC = () => {
}
},
},
{
title: "Salary Rate",
dataIndex: "salary_rate",
key: "salary_rate",
render: (text: string, record: any) => (
<p>${record?.salary_rate}</p>
),
},
{
title: "Base Salary",
dataIndex: "total_base_salary",

@ -818,6 +818,14 @@ const AccountingCurrent: React.FC = () => {
}
},
},
{
title: "Salary Rate",
dataIndex: "salary_rate",
key: "salary_rate",
render: (text: string, record: any) => (
<p>${record?.salary_rate}</p>
),
},
{
title: "Base Salary",
dataIndex: "salary_base_amount",

@ -78,7 +78,7 @@ function CompanyTable({
{
title: "Company",
dataIndex: "name",
width: "25%",
width: "20%",
ellipsis: {
showTitle: false,
},
@ -98,12 +98,21 @@ function CompanyTable({
),
},
{
title: "Team",
title: "Service Team",
dataIndex: "team",
render: (status: string, record: TCompany) => (
<span className={getStatusClassName()}>{record?.team?.name}</span>
),
},
{
title: "Monitoring Team",
dataIndex: "team_monitoring",
render: (status: string, record: TCompany) => (
<span className={getStatusClassName()}>
{record?.team_monitoring?.name}
</span>
),
},
{
title: "USDOT",
dataIndex: "usdot",

@ -31,7 +31,6 @@ import tt from "../../assets/tticon.svg";
import addicon from "../../assets/addiconpng.png";
//ts-ignore
import fileUpload from "../../assets/upload-file.png";
import closeIcon from "../../assets/closeIcon.png";
import AddCustomer from "../Customers/AddCustomer";
import AddDriver from "../Companies/AddDriver";
@ -210,6 +209,23 @@ const AddTask = ({
return tt;
}
};
const handleSubmit = async (values: any) => {
values.attachment_ids = fileIds;
values.note =
(text ? text + ", " : "") +
(note ? note + ", " : "") +
(note2 ? note2 + ", " : "");
try {
await taskController.addTaskController(values);
form.resetFields();
setOpen(false);
} catch (error) {
console.error(error);
}
};
return (
<div onPaste={(event) => handlePaste(event)}>
{openDrive && <AddCustomer open={openDrive} setOpen={setOpenDrive} />}
@ -235,23 +251,13 @@ const AddTask = ({
}
okText="Create"
cancelText="Cancel"
centered={false}
bodyStyle={{
height: 685,
}}
onCancel={handleCancel}
onOk={() => {
form.validateFields().then(async (values) => {
values.attachment_ids = fileIds;
values.note =
(text ? text + ", " : "") +
(note ? note + ", " : "") +
(note2 ? note2 + ", " : "");
form.resetFields();
await taskController.addTaskController(values);
setOpen(!open);
});
onCancel={() => {
form.resetFields();
setOpen(false);
}}
onOk={() => form.submit()}
bodyStyle={{ height: 685 }}
centered={false}
>
<FormAnt
form={form}
@ -259,6 +265,13 @@ const AddTask = ({
layout="vertical"
name="form_in_modal"
initialValues={{ modifier: "public" }}
onFinish={handleSubmit}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
form.submit();
}
}}
>
<Row gutter={[16, 0]}>
<Col span={24}>
@ -346,13 +359,16 @@ const AddTask = ({
name="service_id"
rules={[{ required: true, message: "Please select service!" }]}
>
<Select options={serviceOptions?.sort(sortByLabel)} />
<Select
options={serviceOptions?.sort(sortByLabel)}
placeholder="Select service"
/>
</FormAnt.Item>
</Col>
<Col span={24}>
<FormAnt.Item
label="Assigned to"
label="Service Team"
name="assigned_to_id"
rules={[
{

@ -399,7 +399,7 @@ const TaskTable = ({
),
},
{
title: "Team",
title: "Service Team",
dataIndex: "assigned_to",
width: isMobile ? "3%" : "7%",
key: "7",
@ -412,6 +412,20 @@ const TaskTable = ({
</Tooltip>
),
},
{
title: "Monitoring Team",
dataIndex: "team_monitoring",
width: isMobile ? "3%" : "8%",
key: "7",
ellipsis: {
showTitle: false,
},
render: (item: { name: string }) => (
<Tooltip placement="topLeft" title={item?.name}>
{item?.name}
</Tooltip>
),
},
{
title: "Assignee",
dataIndex: "in_charge",
@ -429,7 +443,7 @@ const TaskTable = ({
{
title: "PTI",
dataIndex: "pti",
width: "8%",
width: "7%",
key: "8",
responsive: ["lg"],
render: (pti: boolean, record: TTask) =>
@ -606,9 +620,17 @@ const TaskTable = ({
];
if (role === "Checker") {
const teamColIndex = columns.findIndex((c) => c.title === "Team");
const forbiddenTitles = ["Service Team", "Monitoring Team"];
teamColIndex !== -1 && columns.splice(teamColIndex, 1);
forbiddenTitles.forEach((title) => {
const index = columns.findIndex(
(c) => typeof c.title === "string" && c.title === title
);
if (index !== -1) {
columns.splice(index, 1);
}
});
}
return columns;

@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import AddTask from "./AddTask";
import { Button, Input, Pagination, Select, Space, Typography } from "antd";
import TaskTable from "./TaskTable";
import { useTeamData } from "../../Hooks/Teams";
import { useTeamData, useTeamsMonitorData } from "../../Hooks/Teams";
import { useTasks } from "../../Hooks/Tasks";
import { TTask } from "../../types/Tasks/TTasks";
import { isMobile, role, team_id } from "../../App";
@ -40,6 +40,7 @@ const Task = ({
const [modalOpen, setModalOpen] = useState(false);
const [characters, setCharacters] = useState<TTask[] | undefined>();
const [team, setTeam] = useState<any>();
const [teamMonitoring, setTeamMonitoring] = useState<any>();
const [search, setSearch] = useState<string>("");
const [status, setStatus] = useState<any>();
const [page, setPage] = useState(1);
@ -144,17 +145,29 @@ const Task = ({
const teamData = useTeamData({});
const TeamMonitorData = useTeamsMonitorData({});
console.log(TeamMonitorData);
const teamOptions: { label: string; value: any }[] | undefined =
teamData?.data?.map((item: any) => ({
label: item?.name,
value: item?.id,
}));
const teamMonitorOptions: { label: string; value: any }[] | undefined =
TeamMonitorData?.data?.map((item: any) => ({
label: item?.name,
value: item?.name,
}));
const { data, isLoading, refetch } = useTasks({
search,
status,
team,
page,
page_size: pageSize,
team_monitoring: teamMonitoring,
});
useEffect(() => {
if (data) {
@ -241,14 +254,6 @@ const Task = ({
</div>
<div className="d-flex">
{role !== "Checker" && (
// <button className="btn-add d-flex" onClick={showModal}>
// <img
// style={{ marginRight: isMobile ? "0px" : "8px" }}
// src={addicon}
// alt="Add Icon"
// />
// {!isMobile && "Add Task"}
// </button>
<Button
size="middle"
className="d-flex"
@ -305,18 +310,8 @@ const Task = ({
</div>
</div>
<div className={`filter ${isMobile ? "mobile-filter" : "d-flex"}`}>
{/* <div className="search-div">
<img src={IconSearch} alt="" />
<input
className={`search-input-${themes}`}
type="text"
placeholder="Search"
onChange={handleSearchChange}
/>
</div> */}
<div>
<Input
// className={`search-input-${themes}`}
placeholder="Search"
prefix={<SearchOutlined />}
onChange={handleSearchChange}
@ -338,13 +333,22 @@ const Task = ({
<Option value="Done">Done</Option>
</Select>
{role !== "Checker" && (
<Select
mode="multiple"
style={{ width: 260, marginLeft: 12 }}
placeholder="Team"
onChange={(value: any) => setTeam(value)}
options={teamOptions}
/>
<>
<Select
mode="multiple"
style={{ width: 260, marginLeft: 12 }}
placeholder="Service team"
onChange={(value: any) => setTeam(value)}
options={teamOptions}
/>
<Select
allowClear
style={{ width: 260, marginLeft: 12 }}
placeholder="Monitoring team"
onChange={(value: any) => setTeamMonitoring(value)}
options={teamMonitorOptions}
/>
</>
)}
</div>
<TaskTable

@ -0,0 +1,121 @@
import { Input, Modal, Form, Select, message, Spin } from "antd";
import { useCompanyData } from "../../../Hooks/Companies";
import { useMemo, useState } from "react";
import { debounce } from "lodash";
import { useCreateTeamMonitor } from "../../../Hooks/Teams";
interface Props {
open: boolean;
setOpen(open: boolean): void;
refetch(): void;
}
const CreateModal = ({ open, setOpen, refetch }: Props) => {
const [form] = Form.useForm();
const [search, setSearch] = useState("");
const debounceSearch = useMemo(
() =>
debounce((val: string) => {
setSearch(val);
}, 500),
[]
);
const handleSearch = (val: string) => {
debounceSearch(val);
};
const { data: companies, isLoading } = useCompanyData({ name: search });
//@ts-ignores
const ComaniesOptions = companies?.map((company) => ({
label: company.name,
value: company.id,
}));
const { mutate, isLoading: isSubmitting } = useCreateTeamMonitor();
const handleCancel = () => {
form.resetFields();
setOpen(false);
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
mutate(
{
name: values.name,
company_ids: values.company_ids || [],
},
{
onSuccess: () => {
message.success("Team successfully created");
handleCancel();
refetch();
},
onError: (error: any) => {
message.error("Failed to create team");
console.error(error);
},
}
);
} catch (validationError: any) {
if (validationError.errorFields) {
message.error("Please fill all required fields correctly");
} else {
message.error("An unexpected error occurred");
console.error(validationError);
}
}
};
return (
<Modal
open={open}
title="Add New Team"
okText="Create"
cancelText="Cancel"
onCancel={handleCancel}
onOk={handleSubmit}
confirmLoading={isSubmitting}
okButtonProps={{ disabled: isSubmitting }}
destroyOnClose
>
<Form form={form} layout="vertical" preserve={false}>
<Form.Item
label="Team Name"
name="name"
rules={[
{
required: true,
message: "Please enter team name",
},
]}
>
<Input placeholder="Enter team name" />
</Form.Item>
<Form.Item label="Assign Companies" name="company_ids">
<Select
mode="multiple"
showSearch
placeholder="Select companies"
onSearch={handleSearch}
filterOption={false}
notFoundContent={isLoading ? <Spin size="small" /> : null}
options={ComaniesOptions}
optionLabelProp="label"
allowClear
/>
</Form.Item>
</Form>
</Modal>
);
};
export default CreateModal;

@ -0,0 +1,133 @@
import { Modal, Form, Input, Button, Spin, message, Select } from "antd";
import { useEffect, useMemo, useState } from "react";
import {
useTeamsMonitorGetOne,
useUpdateTeamMonitor,
} from "../../../Hooks/Teams";
import { debounce } from "lodash";
import { useCompanyData } from "../../../Hooks/Companies";
interface Props {
open: boolean;
id: any;
onClose(): void;
onSuccess(): void;
}
const EditTeamMonitorModal = ({ open, id, onClose, onSuccess }: Props) => {
const [form] = Form.useForm();
const { data, isLoading } = useTeamsMonitorGetOne(id);
const { mutate, isLoading: isSubmitting } = useUpdateTeamMonitor();
/* ===== company search ===== */
const [search, setSearch] = useState("");
const debounceSearch = useMemo(
() =>
debounce((val: string) => {
setSearch(val);
}, 500),
[]
);
const { data: companies, isLoading: companiesLoading } = useCompanyData({
name: search,
});
const companyOptions = companies?.map((company) => ({
label: company.name,
value: company.id,
}));
/* ===== backend → form mapping ===== */
useEffect(() => {
if (data) {
form.setFieldsValue({
name: data.name,
company_ids: data.companies?.map((c: any) => c.id),
});
}
}, [data, form]);
/* ===== submit ===== */
const handleSubmit = (values: any) => {
mutate(
{
id,
payload: {
name: values.name,
company_ids: values.company_ids,
},
},
{
onSuccess: () => {
message.success("Team updated successfully");
onSuccess();
},
onError: () => {
message.error("Failed to update team");
},
}
);
};
return (
<Modal
open={open}
title="Edit Monitoring Team"
onCancel={onClose}
destroyOnClose
confirmLoading={isSubmitting}
onOk={() => form.submit()}
footer={[
<Button key="cancel" onClick={onClose}>
Cancel
</Button>,
<Button
key="submit"
type="primary"
loading={isSubmitting}
onClick={() => form.submit()}
>
Save
</Button>,
]}
>
{isLoading ? (
<Spin />
) : (
<Form
form={form}
key={id}
layout="vertical"
onFinish={handleSubmit}
preserve={false}
>
<Form.Item
label="Team name"
name="name"
rules={[{ required: true, message: "Please enter team name" }]}
>
<Input placeholder="Team name" />
</Form.Item>
<Form.Item label="Assign Companies" name="company_ids">
<Select
mode="multiple"
showSearch
placeholder="Select companies"
onSearch={debounceSearch}
filterOption={false}
options={companyOptions}
notFoundContent={companiesLoading ? <Spin size="small" /> : null}
allowClear
/>
</Form.Item>
</Form>
)}
</Modal>
);
};
export default EditTeamMonitorModal;

@ -0,0 +1,175 @@
import { useEffect, useMemo, useState } from "react";
import { useTeamData, useTeamsMonitorData } from "../../../Hooks/Teams";
//@ts-ignore
import addicon from "../../../assets/addiconpng.png";
import { Button, Input, Select, Space, theme } from "antd";
import {
LeftOutlined,
PlusOutlined,
RightOutlined,
SearchOutlined,
} from "@ant-design/icons";
import TeamMonitoringTable from "./TeamMonitoringTable";
import { debounce } from "lodash";
import CreateModal from "./CreateModal";
const TeamMonitoring = () => {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState<number>(() => {
const saved = localStorage.getItem("general_pageSize");
return saved ? Number(saved) : 15;
});
const [search, setSearch] = useState("");
const debouncedSearch = useMemo(
() =>
debounce((value: string) => {
setSearch(value);
}, 500),
[]
);
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
debouncedSearch(value);
};
const { data, isLoading, refetch } = useTeamsMonitorData({
page: page,
page_size: pageSize,
name: search,
});
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
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 { token } = theme.useToken();
return (
<div>
{open && <CreateModal refetch={refetch} open={open} setOpen={setOpen} />}
<div className="header d-flex" style={{ marginBottom: 16 }}>
<div>
<Input
placeholder="Search"
prefix={<SearchOutlined />}
onChange={handleSearchChange}
/>
</div>
<Button
className="btn-add d-flex"
style={{
backgroundColor: "#f99e2c",
color: "white",
padding: 18,
}}
onClick={showModal}
icon={<PlusOutlined />}
>
Add Team
</Button>
</div>
<TeamMonitoringTable
data={data?.data}
isLoading={isLoading}
refetch={refetch}
/>
<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?.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?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</div>
);
};
export default TeamMonitoring;

@ -0,0 +1,150 @@
import { useNavigate, useParams } from "react-router-dom";
import type { ColumnsType } from "antd/es/table";
import {
useDeleteTeamMonitor,
useTeamsMonitorGetOne,
useUpdateTeamMonitor,
} from "../../../Hooks/Teams";
import {
Form,
Spin,
Space,
Row,
Col,
Input,
Button,
Table,
Modal,
message,
Typography,
} from "antd";
import Notfound from "../../../Utils/Notfound";
import tagIcon from "../../../assets/tagIcon.svg";
import { role } from "../../../App";
import moment from "moment";
const { Title, Text } = Typography;
type Params = {
id: any;
};
const TeamMonitoringPreview = () => {
const { id } = useParams<Params>();
const navigate = useNavigate();
const { data, isLoading, refetch } = useTeamsMonitorGetOne(id);
const { mutate: deleteTeam, isLoading: deleteLoading } =
useDeleteTeamMonitor();
const { mutateAsync: updateTeam, isLoading: isSubmitting } =
useUpdateTeamMonitor();
if (isLoading) {
return <Spin size="large" style={{ marginTop: 100 }} />;
}
if (!data) {
return <Notfound />;
}
const handleSubmit = async (values: any) => {
try {
await updateTeam({
id,
payload: { name: values.name },
});
message.success("Team updated successfully");
refetch();
} catch {
message.error("Failed to update team");
}
};
const handleDelete = () => {
if (deleteLoading) return;
Modal.confirm({
title: "Are you sure you want to delete this team?",
okText: "Yes",
okType: "danger",
cancelText: "No",
onOk: () =>
new Promise<void>((resolve, reject) => {
deleteTeam(id, {
onSuccess: () => {
message.success("Team deleted successfully");
navigate(-1);
resolve();
},
onError: () => {
message.error("Failed to delete team");
reject();
},
});
}),
});
};
const columns: ColumnsType<any> = [
{
title: <img src={tagIcon} alt="" />,
key: "no",
width: 60,
align: "center",
render: (_: any, __: any, index: number) => index + 1,
},
{
title: "Company name",
dataIndex: "name",
key: "name",
},
];
return (
<Space direction="vertical" size="large" style={{ width: "100%" }}>
<div>
<Title level={3} style={{ marginBottom: 0 }}>
{data.name}
</Title>
<Text type="secondary">
Created at: {moment(data.created_at).format("DD.MM.YYYY HH:mm")}
</Text>
</div>
{/* EDIT FORM */}
<Form
layout="vertical"
initialValues={{ name: data.name }}
onFinish={handleSubmit}
>
{/* COMPANIES TABLE */}
<Table
rowKey="id"
columns={columns}
dataSource={data.companies}
// pagination={false}
size="middle"
bordered
/>
{/* ACTION BUTTONS */}
<Form.Item style={{ marginTop: 24 }}>
{role !== "Checker" && (
<Button
danger
type="primary"
onClick={handleDelete}
loading={deleteLoading}
style={{ marginRight: 8 }}
>
Delete
</Button>
)}
</Form.Item>
</Form>
</Space>
);
};
export default TeamMonitoringPreview;

@ -0,0 +1,108 @@
import { Button, Table } from "antd";
import { EditOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router-dom";
import {
QueryObserverResult,
RefetchOptions,
RefetchQueryFilters,
} from "react-query";
import tagIcon from "../../../assets/tagIcon.svg";
import { TTeam } from "../../../types/Team/TTeam";
import { timeZone } from "../../../App";
import { useState } from "react";
import EditTeamMonitorModal from "./EditTeamMonitorModal";
const TeamMonitoringTable = ({
data,
isLoading,
refetch,
}: {
data: TTeam[] | undefined;
isLoading: boolean | undefined;
refetch: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<TTeam[], unknown>>;
}) => {
const navigate = useNavigate();
const moment = require("moment-timezone");
const [editOpen, setEditOpen] = useState(false);
const [selectedId, setSelectedId] = useState<number | null>(null);
const openEditModal = (id: number) => {
setSelectedId(id);
setEditOpen(true);
};
return (
<>
{editOpen && selectedId && (
<EditTeamMonitorModal
open={editOpen}
id={selectedId}
onClose={() => setEditOpen(false)}
onSuccess={() => {
setEditOpen(false);
refetch();
}}
/>
)}
<Table
loading={isLoading}
onRow={(record) => {
return {
onClick: () => {
navigate(`/teams-monitoring/${record.id}`);
},
};
}}
dataSource={data?.map((u, i) => ({
...u,
no: i + 1,
action: { id: u.id },
created: moment(u?.created_at)
.tz(timeZone)
.format("DD.MM.YYYY HH:mm"),
key: u.id,
}))}
size="middle"
columns={[
{
title: <img src={tagIcon} alt="" />,
dataIndex: "no",
width: "5%",
},
{
title: "Name",
dataIndex: "name",
},
{
title: "Created at",
dataIndex: "created",
},
{
title: "Actions",
key: "actions",
align: "center",
width: "10%",
render: (_: any, record: TTeam) => (
<Button
icon={<EditOutlined />}
type="default"
onClick={(e) => {
e.stopPropagation();
openEditModal(record.id);
}}
>
Edit
</Button>
),
},
]}
pagination={false}
bordered
/>
</>
);
};
export default TeamMonitoringTable;

@ -1,6 +1,6 @@
import { Input, Modal, Form as FormAnt, Select } from "antd";
import { teamController } from "../../API/LayoutApi/teams";
import { useUserData } from "../../Hooks/Users";
import { useUserData } from "../../../Hooks/Users";
import { teamController } from "../../../API/LayoutApi/teams";
const AddTeam = ({
open,

@ -1,16 +1,17 @@
import { Input, Modal, Form as FormAnt, Select } from "antd";
import { userController } from "../../API/LayoutApi/users";
import { useTeamOne } from "../../Hooks/Teams";
import { message } from "antd";
import { useRoleData } from "../../Hooks/Role";
import { common } from "../../Utils/common";
import {
QueryObserverResult,
RefetchOptions,
RefetchQueryFilters,
} from "react-query";
import { TUser } from "../../types/User/TUser";
import { TUser } from "../../../types/User/TUser";
import { common } from "../../../Utils/common";
import { useRoleData } from "../../../Hooks/Role";
import { useTeamOne } from "../../../Hooks/Teams";
import { userController } from "../../../API/LayoutApi/users";
const AddUserToTeam = ({
open,

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useTeamOne } from "../../Hooks/Teams";
import { useTeamOne } from "../../../Hooks/Teams";
import {
Form,
Spin,
@ -13,18 +13,18 @@ import {
Button,
Table,
} from "antd";
import { teamController } from "../../API/LayoutApi/teams";
import { teamController } from "../../../API/LayoutApi/teams";
import { FormOutlined } from "@ant-design/icons";
import Notfound from "../../Utils/Notfound";
import { role } from "../../App";
import { useUserData } from "../../Hooks/Users";
import Notfound from "../../../Utils/Notfound";
import { role } from "../../../App";
import { useUserData } from "../../../Hooks/Users";
import AddUserToTeam from "./AddUserToTeam";
// @ts-ignore
import tagIcon from "../../assets/tagIcon.svg";
import tagIcon from "../../../assets/tagIcon.svg";
// @ts-ignore
import infoIcon from "../../assets/infoIcon.png";
import infoIcon from "../../../assets/infoIcon.png";
// @ts-ignore
import infoIconActive from "../../assets/infoIconActive.png";
import infoIconActive from "../../../assets/infoIconActive.png";
const TabPane = Tabs.TabPane;

@ -0,0 +1,169 @@
import { useEffect, useMemo, useState } from "react";
import { useTeamData } from "../../../Hooks/Teams";
import TeamTable from "./TeamTable";
//@ts-ignore
import addicon from "../../../assets/addiconpng.png";
import AddTeam from "./AddTeam";
import { Button, Input, Select, Space, Typography, theme } from "antd";
import {
LeftOutlined,
PlusOutlined,
RightOutlined,
SearchOutlined,
} from "@ant-design/icons";
import { debounce } from "lodash";
const TeamServices = () => {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState<number>(() => {
const saved = localStorage.getItem("general_pageSize");
return saved ? Number(saved) : 15;
});
const [search, setSearch] = useState("");
const { data, isLoading, refetch } = useTeamData({
page: page,
page_size: pageSize,
name: search,
});
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
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 debouncedSearch = useMemo(
() =>
debounce((value: string) => {
setSearch(value);
}, 500),
[]
);
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
debouncedSearch(value);
};
const { token } = theme.useToken();
return (
<div>
{open && <AddTeam refetch={refetch} open={open} setOpen={setOpen} />}
<div className="header d-flex" style={{ marginBottom: 16 }}>
<div>
<Input
placeholder="Search"
prefix={<SearchOutlined />}
onChange={handleSearchChange}
/>
</div>
<Button
className="btn-add d-flex"
style={{
backgroundColor: "#f99e2c",
color: "white",
padding: 18,
}}
onClick={showModal}
icon={<PlusOutlined />}
>
Add Team
</Button>
</div>
<TeamTable data={data?.data} isLoading={isLoading} refetch={refetch} />
<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?.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?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
</div>
);
};
export default TeamServices;

@ -1,16 +1,18 @@
import { Table, Tag } from "antd";
import { TTeam } from "../../types/Team/TTeam";
import { useNavigate } from "react-router-dom";
import {
QueryObserverResult,
RefetchOptions,
RefetchQueryFilters,
} from "react-query";
import { timeZone } from "../../App";
// @ts-ignore
import tagIcon from "../../assets/tagIcon.svg";
import tagIcon from "../../../assets/tagIcon.svg";
import { theme } from "antd";
import { TTeam } from "../../../types/Team/TTeam";
import { timeZone } from "../../../App";
const TeamTable = ({
data,
@ -34,7 +36,7 @@ const TeamTable = ({
onRow={(record) => {
return {
onClick: () => {
navigate(`/teams/${record.id}`);
navigate(`/teams-services/${record.id}`);
},
};
}}

@ -1,151 +1,32 @@
import { useEffect, useState } from "react";
import { useTeamData } from "../../Hooks/Teams";
import TeamTable from "./TeamTable";
//@ts-ignore
import addicon from "../../assets/addiconpng.png";
import AddTeam from "./AddTeam";
import { Button, Input, Select, Space, Typography, theme } from "antd";
import { LeftOutlined, PlusOutlined, RightOutlined } from "@ant-design/icons";
const Team = () => {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState<number>(() => {
const saved = localStorage.getItem("general_pageSize");
return saved ? Number(saved) : 15;
});
const { data, isLoading, refetch } = useTeamData({
page: page,
page_size: pageSize,
});
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
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 { token } = theme.useToken();
import { Tabs, Typography } from "antd";
import TeamServices from "./TeamServices/TeamServices";
import TeamMonitoring from "./TeamMonitoring/TeamMonitoring";
const Teams = () => {
const items = [
{
key: "services",
label: "Service Team",
children: <TeamServices />,
},
{
key: "monitoring",
label: "Monitoring Team",
children: <TeamMonitoring />,
},
];
return (
<div>
{open && <AddTeam refetch={refetch} open={open} setOpen={setOpen} />}
<div className="header d-flex" style={{ marginBottom: 16 }}>
<div
className="header d-flex statistics-header"
style={{ marginBottom: 16 }}
>
<Typography className="title">Teams</Typography>
{/* <button
className="btn-add d-flex"
style={{ marginRight: 0 }}
onClick={showModal}
>
<img src={addicon} style={{ marginRight: 8 }} alt="" />
Add Team
</button> */}
<Button
className="btn-add d-flex"
style={{
backgroundColor: "#f99e2c",
color: "white",
padding: 18,
}}
onClick={showModal}
icon={<PlusOutlined />} // Ant-design ikonkasi
>
Add Team
</Button>
</div>
<TeamTable data={data?.data} isLoading={isLoading} refetch={refetch} />
<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?.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?.next ? false : true}
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
border: "none",
}}
>
<RightOutlined />
</Button>
</Space>
</Space>
<Tabs items={items} />
</div>
);
};
export default Team;
export default Teams;

@ -7,10 +7,19 @@ export const useTasks = ({
team,
page,
page_size,
team_monitoring,
}: TTasksGetParams) => {
return useQuery(
[`tasks/`, search, status, team, page, page_size],
() => taskController.read({ search, status, team, page, page_size }),
[`tasks/`, search, status, team, page, page_size, team_monitoring],
() =>
taskController.read({
search,
status,
team,
page,
page_size,
team_monitoring,
}),
{ refetchOnWindowFocus: false }
);
};

@ -1,5 +1,9 @@
import { useQuery } from "react-query";
import { TTeamGetParams, teamController } from "../../API/LayoutApi/teams";
import { useMutation, useQuery, useQueryClient } from "react-query";
import {
TTeamGetParams,
UpdateTeamMonitorVars,
teamController,
} from "../../API/LayoutApi/teams";
export const useTeamData = ({
name,
@ -23,3 +27,69 @@ export const useTeamOne = (teamId: number | string | undefined): any => {
{ refetchOnWindowFocus: false }
);
};
// Team monitor
export const useTeamsMonitorData = ({
name,
page,
page_size,
}: TTeamGetParams) => {
return useQuery(
["teams-monitor", name, page, page_size],
() => teamController.readTeamsMonitor({ name, page, page_size }),
{
refetchOnWindowFocus: false,
}
);
};
export const useCreateTeamMonitor = () => {
const queryClient = useQueryClient();
return useMutation(teamController.createTeamMonitor, {
onSuccess: () => {
queryClient.invalidateQueries("teams-monitor");
},
onError: (error) => {
console.error("Failed to create team monitor:", error);
},
});
};
export const useTeamsMonitorGetOne = (id: any) => {
return useQuery(["teams-monitor"], () => teamController.readTeamsGetOne(id), {
refetchOnWindowFocus: false,
});
};
export const useDeleteTeamMonitor = () => {
const queryClient = useQueryClient();
return useMutation(teamController.deleteTeamMonitor, {
onSuccess: () => {
queryClient.invalidateQueries("teams-monitor");
},
onError: (error) => {
console.error("Delete failed:", error);
},
});
};
export const useUpdateTeamMonitor = () => {
const queryClient = useQueryClient();
return useMutation(
({ id, payload }: UpdateTeamMonitorVars) =>
teamController.updateTeamMonitor(id, payload),
{
onSuccess: () => {
queryClient.invalidateQueries("teams-monitor");
},
onError: (error) => {
console.error("Update team monitor failed:", error);
},
}
);
};

@ -4,7 +4,8 @@ import Customer from "../Components/Customers/Customers";
import CustomerEdit from "../Components/Customers/CustomersEdit";
import Service from "../Components/Services/Services";
import ServiceEdit from "../Components/Services/ServiceEdit";
import TeamEdit from "../Components/Teams/TeamEdit";
import TeamEdit from "../Components/Teams/TeamServices/TeamEdit";
import TeamMonitoringPreview from "../Components/Teams/TeamMonitoring/TeamMonitoringPreview";
import Team from "../Components/Teams/Teams";
import User from "../Components/Users/Users";
import UserEdit from "../Components/Users/UserEdit";
@ -71,9 +72,14 @@ export const superItems: TItems[] = [
key: "/teams/",
},
{
path: "/teams/:id/",
path: "/teams-services/:id/",
component: <TeamEdit />,
key: "/team/:id/",
key: "/teams-services/:id/",
},
{
path: "/teams-monitoring/:id/",
component: <TeamMonitoringPreview />,
key: "/teams-monitoring/:id/",
},
{
path: "/users/",

@ -6,6 +6,7 @@ export type TCompany = {
is_active: boolean;
usdot: string;
team: { name: string; id: number };
team_monitoring: { name: string; id: number };
api_key: string;
created_at: Date;
};

Loading…
Cancel
Save