commit
1f82cbd187
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"antd",
|
||||
"Previos",
|
||||
"Sider",
|
||||
"tteld",
|
||||
"USDOT"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "newdashboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"@stripe/react-stripe-js": "^2.3.2",
|
||||
"@stripe/stripe-js": "^2.1.11",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/antd": "^1.0.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/moment": "^2.13.0",
|
||||
"@types/node": "^16.18.50",
|
||||
"@types/react": "^18.2.21",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-query": "^1.2.9",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"antd": "^5.9.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"file-saver": "^2.0.5",
|
||||
"final-form": "^4.20.10",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-final-form": "^6.5.9",
|
||||
"react-icons": "^4.11.0",
|
||||
"react-redux": "^8.1.3",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"recharts": "^2.10.4",
|
||||
"redux": "^4.2.1",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4",
|
||||
"yarn": "^1.22.19"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11"
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-TS04B6F9E3"
|
||||
></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
|
||||
gtag("config", "G-TS04B6F9E3");
|
||||
</script>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Admin Panel for Support Specialists in TT ELD - A comprehensive management tool for efficient operations and monitoring of TT ELD services."
|
||||
/>
|
||||
<title>TT ELD</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,35 @@
|
||||
import { message } from "antd";
|
||||
import { TCall } from "../../types/CallRequests/TCall";
|
||||
import instance from "../api";
|
||||
|
||||
export const callController = {
|
||||
async read(obj: { status: string }) {
|
||||
const params = { ...obj };
|
||||
|
||||
if (!!obj.status) params.status = obj.status;
|
||||
|
||||
const { data } = await instance.get<TCall[]>(`callback-requests/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async callPatch(
|
||||
obj: { note?: string; status?: string },
|
||||
id: number
|
||||
) {
|
||||
const params = { ...obj };
|
||||
if (!!obj.note) params.note = obj.note;
|
||||
if (!!obj.status) params.status = obj.status;
|
||||
|
||||
const { data }: { data: any } = await instance
|
||||
.put<TCall>(`callback-request/${id}/`, params)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
@ -0,0 +1,102 @@
|
||||
import { TCompany } from "../../types/Company/TCompany";
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export type TCompanyGetParams = {
|
||||
name?: string;
|
||||
page?: string | number;
|
||||
is_active?: boolean;
|
||||
};
|
||||
export type TCompanyPutParams = {
|
||||
name?: string;
|
||||
owner?: string;
|
||||
is_active?: boolean;
|
||||
};
|
||||
export type TCompanyPostParams = {
|
||||
name?: string;
|
||||
team_id?: number;
|
||||
owner?: string;
|
||||
is_active?: boolean;
|
||||
usdot?: string;
|
||||
api_key?: string;
|
||||
};
|
||||
|
||||
export const companyController = {
|
||||
async read(filterObject: TCompanyGetParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
if (!!filterObject.name) params.name = filterObject.name;
|
||||
if (!!filterObject.is_active) params.is_active = filterObject.is_active;
|
||||
if (!!filterObject.page) params.page = filterObject.page;
|
||||
|
||||
const { data } = await instance.get<TCompany[]>(`companies/`, { params });
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
async companyOne(Id: number | undefined) {
|
||||
if (Id) {
|
||||
const { data } = await instance.get<TCompany>(`company/${Id}/`);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
async companyPatch(obj: TCompanyPutParams, id: string) {
|
||||
const { data }: { data: any } = await instance
|
||||
.put<TCompany>(`company/${id}/`, obj)
|
||||
.then((u) => {
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async addCompanyController(obj: TCompanyPostParams) {
|
||||
try {
|
||||
const { data } = await instance.post<TCompany>("company/", obj);
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
setTimeout(() => {
|
||||
message.error({
|
||||
content: error.response.data.name,
|
||||
key: 2,
|
||||
duration: 2,
|
||||
});
|
||||
}, 1000);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async SyncCompany(id: number) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.post(`company-sync/${id}/`).then((u) => {
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err: any) {}
|
||||
return { data: res, error };
|
||||
},
|
||||
|
||||
async deleteCompanyController(id: string) {
|
||||
message.loading({ content: "Loading..." });
|
||||
let res;
|
||||
let error;
|
||||
try {
|
||||
const { data } = await instance.delete(`company/${id}/`).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({
|
||||
content: "Deleted!",
|
||||
key: id,
|
||||
duration: 2,
|
||||
});
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,99 @@
|
||||
import { TCustomer } from "../../types/Customer/TCustomer";
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export type TCustomerGetParams = {
|
||||
name?: string;
|
||||
pageSize?: string | number;
|
||||
page?: string | number;
|
||||
is_active?: boolean;
|
||||
};
|
||||
export type TCustomerByCompanyGetParams = {
|
||||
name?: string;
|
||||
id?: string;
|
||||
};
|
||||
export type TCustomerPutParams = {
|
||||
name?: string;
|
||||
company_id?: number;
|
||||
};
|
||||
export type TCustomerPostParams = {
|
||||
name?: string;
|
||||
company_id?: number;
|
||||
is_active?: boolean;
|
||||
};
|
||||
|
||||
export const customerController = {
|
||||
async read(filterObject: TCustomerGetParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
if (!!filterObject.name) params.name = filterObject.name;
|
||||
if (!!filterObject.is_active) params.is_active = filterObject.is_active;
|
||||
if (!!filterObject.page) params.page = filterObject.page;
|
||||
if (!!filterObject.page) params.pageSize = filterObject.pageSize;
|
||||
|
||||
const { data } = await instance.get<TCustomer[]>(`customers/`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async customerOne(Id: number | undefined) {
|
||||
if (Id) {
|
||||
const { data } = await instance.get<TCustomer>(`customer/${Id}/`);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
async customerByCompany(id: string | undefined, name: string | undefined) {
|
||||
const params = { name };
|
||||
if (!!name) params.name = name;
|
||||
if (id) {
|
||||
const { data } = await instance.get<TCustomer[]>(
|
||||
`customers-by-company/${id}/`,
|
||||
{ params }
|
||||
);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
async customerPatch(obj: TCustomerPutParams, id: string) {
|
||||
const { data }: { data: any } = await instance
|
||||
.put<TCustomer>(`customer/${id}/`, obj)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async addCustomerController(obj: TCustomerPostParams) {
|
||||
message.loading({ content: "Loading..." });
|
||||
const { data } = await instance
|
||||
.post<TCustomer>("customer/", obj)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteCustomerController(id: string) {
|
||||
message.loading({ content: "Loading..." });
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.delete(`customer/${id}/`).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Deleted!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,103 @@
|
||||
import { message } from "antd";
|
||||
import {
|
||||
TMyTaskHistory,
|
||||
TMystats,
|
||||
TProfile,
|
||||
} from "../../types/Profile/TProfile";
|
||||
import instance from "../api";
|
||||
|
||||
export type TProfilePutParams = {
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
username?: string;
|
||||
};
|
||||
export type TMyTaskHistoryGetParams = {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
};
|
||||
export type TChangePostParams = {
|
||||
old_password?: string;
|
||||
new_password?: string;
|
||||
password_confirm?: string;
|
||||
};
|
||||
export const prof = {
|
||||
async read(filterObject: TMyTaskHistoryGetParams) {
|
||||
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<TMystats>(`stats/my-stats/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
// async read(filterObject: TMyTaskHistoryGetParams) {
|
||||
// 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<TMystats>(`stats/my-stats/`, {
|
||||
// params,
|
||||
// });
|
||||
// return data;
|
||||
// },
|
||||
|
||||
async self() {
|
||||
const { data } = await instance.get<TProfile>(`users/my-profile/`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async myTaskHistory(filterObject: TMyTaskHistoryGetParams) {
|
||||
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<TMyTaskHistory[]>(`my-task-history/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async profPatch(filterObject: TProfilePutParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
params.first_name = filterObject.first_name || params.first_name;
|
||||
params.last_name = filterObject.last_name || params.last_name;
|
||||
params.username = filterObject.username || params.username;
|
||||
try {
|
||||
const { data } = await instance.put<TProfilePutParams>(
|
||||
`users/my-profile/`,
|
||||
{ ...params }
|
||||
);
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
setTimeout(() => {
|
||||
message.error({
|
||||
content: error.response.data.username,
|
||||
key: 2,
|
||||
duration: 2,
|
||||
});
|
||||
}, 1000);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async changePass(obj: TChangePostParams) {
|
||||
try {
|
||||
const { data } = await instance.post<any>(
|
||||
"users/my-profile/change-password/",
|
||||
obj
|
||||
);
|
||||
message.success(data.message);
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
if (error.response && error.response.status === 400) {
|
||||
const errorMessage =
|
||||
error?.response?.data?.old_password ||
|
||||
error?.response?.data?.new_password[0] ||
|
||||
"Bad Request";
|
||||
message.error(errorMessage);
|
||||
} else {
|
||||
message.error("An error occurred");
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
@ -0,0 +1,58 @@
|
||||
import { message } from "antd";
|
||||
import { TRequests } from "../../types/Requests/TRequests";
|
||||
import instance from "../api";
|
||||
|
||||
export type TRequestsGetParams = {
|
||||
search?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export const requestsController = {
|
||||
async read(filterObject: TRequestsGetParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
if (!!filterObject.search) params.search = filterObject.search;
|
||||
if (!!filterObject.status) params.status = filterObject.status;
|
||||
|
||||
const { data } = await instance.get<TRequests[]>(`driver-requests/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async requestsOne(Id: string | number | undefined) {
|
||||
const { data }: { data: any } = await instance(`driver-request/${Id}/`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async requestPatch(obj: TRequestsGetParams, id: string | number | undefined) {
|
||||
const { data } = await instance
|
||||
.put<TRequests>(`driver-request/${id}/`, obj)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async delete(id: string | number | undefined) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance
|
||||
.patch(`driver-request/${id}/`, { status: "Rejected" })
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Rejected!", key: id, duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
import { TRole } from "../../types/Role/TRole";
|
||||
import instance from "../api";
|
||||
|
||||
|
||||
export const roleController = {
|
||||
async read() {
|
||||
const { data } = await instance.get<TRole[]>(`users/roles/`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async roleOne(id: string) {
|
||||
const { data }: { data: any } = await instance.get<TRole>(
|
||||
`users/role/${id}/`
|
||||
);
|
||||
return data;
|
||||
},
|
||||
};
|
@ -0,0 +1,89 @@
|
||||
import { TService } from "../../types/Service/TService";
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export type TServicePutParams = {
|
||||
title?: string;
|
||||
points?: number;
|
||||
};
|
||||
export type TServicePostParams = {
|
||||
title?: string;
|
||||
points?: number;
|
||||
};
|
||||
|
||||
export const serviceController = {
|
||||
async read() {
|
||||
const { data } = await instance.get<TService[]>(`services/`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async serviceOne(Id: number | undefined) {
|
||||
if (Id) {
|
||||
const { data } = await instance.get<TService>(`service/${Id}/`);
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
async servicePatch(obj: TServicePutParams, id: string) {
|
||||
try {
|
||||
const { data } = await instance
|
||||
.put<TService>(`service/${id}/`, obj)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
setTimeout(() => {
|
||||
message.error({
|
||||
content: error?.response?.data?.title,
|
||||
key: 2,
|
||||
duration: 2,
|
||||
});
|
||||
}, 1000);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async addServiceController(obj: TServicePostParams) {
|
||||
try {
|
||||
const { data } = await instance
|
||||
.post<TService>("service/", obj)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
setTimeout(() => {
|
||||
message.error({
|
||||
content: error?.response?.data?.title,
|
||||
key: 2,
|
||||
duration: 2,
|
||||
});
|
||||
}, 1000);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteServiceController(id: string) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.delete(`service/${id}/`).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Deleted!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,132 @@
|
||||
import { TCard, TStat, TStatTeam } from "../../types/Statistic/TStat";
|
||||
import instance from "../api";
|
||||
|
||||
export type TStatGetParams = {
|
||||
search?: string;
|
||||
team?: string;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
};
|
||||
|
||||
export type TStatTeamGetParams = {
|
||||
search?: string;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
};
|
||||
|
||||
export type TStatCreatorsGetParams = {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
};
|
||||
|
||||
export const statController = {
|
||||
async read(filterObject: TStatGetParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
if (!!filterObject.search) params.search = filterObject.search;
|
||||
if (!!filterObject.team) params.team = filterObject.team;
|
||||
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<TStat[]>(`stats/all-users/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async team(filterObject: TStatTeamGetParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
if (!!filterObject.search) params.search = filterObject.search;
|
||||
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<TStatTeam[]>(`stats/all-teams/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async creators(filterObject: TStatCreatorsGetParams) {
|
||||
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<TStatTeam[]>(`stats/task-creators/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
async cards(filterObject: TStatCreatorsGetParams) {
|
||||
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<TCard>(`stats/tasks-comparison/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async saveUsersStats(
|
||||
fileName: string,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
team: string
|
||||
) {
|
||||
const response = await instance.post(
|
||||
`stats/all-users/?start_date=${startDate}&end_date=${endDate}&team=${team}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
"Content-Disposition": `attachment;`,
|
||||
},
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
);
|
||||
const blob = new Blob([response.data], {
|
||||
type: "application/octet-stream",
|
||||
});
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = downloadUrl;
|
||||
a.download = `stats_${fileName}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async saveTeamStats(fileName: string, startDate: string, endDate: string) {
|
||||
const response = await instance.post(
|
||||
`stats/all-teams/?start_date=${startDate}&end_date=${endDate}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
"Content-Disposition": `attachment;`,
|
||||
},
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
);
|
||||
const blob = new Blob([response.data], {
|
||||
type: "application/octet-stream",
|
||||
});
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.style.display = "none";
|
||||
a.href = downloadUrl;
|
||||
a.download = `stats_${fileName}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async statOne(Id: string | number | undefined) {
|
||||
const { data } = await instance.get<TStat>(`stats/${Id}`);
|
||||
return data;
|
||||
},
|
||||
};
|
@ -0,0 +1,118 @@
|
||||
import { TTask, TTaskHistory } from "../../types/Tasks/TTasks";
|
||||
import { TPagination } from "../../types/common/TPagination";
|
||||
import instance from "../api";
|
||||
|
||||
export type TTasksGetParams = {
|
||||
search?: string;
|
||||
status?: string;
|
||||
team?: string;
|
||||
page?: string;
|
||||
};
|
||||
|
||||
export type TTasksPutParams = {
|
||||
company_id?: number;
|
||||
customer_id?: number;
|
||||
service_id?: number;
|
||||
assigned_to_id?: number;
|
||||
note?: string;
|
||||
status?: string;
|
||||
message?: string;
|
||||
pti?: boolean;
|
||||
};
|
||||
|
||||
export type TTasksPostParams = {
|
||||
company_id?: number;
|
||||
customer_id?: number;
|
||||
service_id?: number;
|
||||
provider_id?: number;
|
||||
assigned_to_id?: number;
|
||||
in_charge_id?: number;
|
||||
note?: string;
|
||||
status?: string;
|
||||
is_active?: boolean;
|
||||
pti?: boolean;
|
||||
attachment_ids?: number[];
|
||||
};
|
||||
|
||||
export const taskController = {
|
||||
async read(filterObject: TTasksGetParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
if (!!filterObject.page && filterObject.page !== "0")
|
||||
params.page = filterObject.page;
|
||||
if (!!filterObject.search) params.search = filterObject.search;
|
||||
if (Array.isArray(filterObject.status)) {
|
||||
params.status = filterObject.status.join(",");
|
||||
}
|
||||
if (Array.isArray(filterObject.team)) {
|
||||
params.team = filterObject.team.join(", ");
|
||||
}
|
||||
|
||||
const { data } = await instance.get<TPagination<TTask[]>>(`tasks/`, {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async getHistory(id: number | undefined) {
|
||||
const { data } = await instance.get<TTaskHistory[]>(`task-history/${id}/`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async taskOne(Id: number) {
|
||||
const { data } = await instance.get<TTask>(`task/${Id}/`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async taskPatch(obj: TTasksPutParams, task_id: number | undefined) {
|
||||
const { data } = await instance
|
||||
.put<TTask>(`task/${task_id}/`, obj)
|
||||
.then((u) => {
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async addTaskController(obj: TTasksPostParams) {
|
||||
const { data } = await instance.post<TTask>("task/", obj).then((u) => {
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async addTaskFile(formData: any) {
|
||||
const { data } = await instance.post("attachment/", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data", // Установите правильный Content-Type
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteTaskController(id: number) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.delete(`task/${id}/`).then((u) => {
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
async deleteAttachmentController(id: number) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.delete(`attachment/${id}/`).then((u) => {
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,73 @@
|
||||
import { TTeam } from "../../types/Team/TTeam";
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export type TTeamPutParams = {
|
||||
name?: string;
|
||||
is_active?: boolean;
|
||||
};
|
||||
|
||||
export type TTeamPostParams = {
|
||||
name?: string;
|
||||
is_active?: boolean;
|
||||
};
|
||||
|
||||
export const teamController = {
|
||||
async read(name: string) {
|
||||
const { data } = await instance.get<TTeam[]>(`teams/?name=${name}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async teamOne(Id: string | number | undefined) {
|
||||
const { data } = await instance.get<TTeam>(`team/${Id}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async teamPatch(obj: TTeamPutParams, id: string) {
|
||||
const { data } = await instance.put<TTeam>(`team/${id}/`, obj).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async addTeamController(obj: TTeamPostParams) {
|
||||
try {
|
||||
const { data } = await instance.post<TTeam>("team/", obj).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
setTimeout(() => {
|
||||
message.error({
|
||||
content: error?.response?.data?.name,
|
||||
key: 2,
|
||||
duration: 2,
|
||||
});
|
||||
}, 1000);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteTeamController(id: string) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.delete(`team/${id}`).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Deleted!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,94 @@
|
||||
import { TUpdate } from "../../types/Update/TUpdate";
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export type TUpdatePutParams = {
|
||||
company_id?: number;
|
||||
customer_id?: number;
|
||||
status?: string;
|
||||
note?: string;
|
||||
is_pinned?: boolean;
|
||||
};
|
||||
|
||||
export type TUpdatePostParams = {
|
||||
company_id?: number;
|
||||
customer_id?: number;
|
||||
provider_id?: number;
|
||||
executor_id?: number;
|
||||
status?: string;
|
||||
note?: string;
|
||||
solution?: string;
|
||||
is_active?: boolean;
|
||||
is_pinned?: boolean;
|
||||
attachment_ids?: number[];
|
||||
};
|
||||
|
||||
export const updateController = {
|
||||
async read(status: string) {
|
||||
const { data } = await instance.get<TUpdate[]>(
|
||||
`shift-updates/?status=${status}`
|
||||
);
|
||||
return data;
|
||||
},
|
||||
|
||||
async updateOne(Id: string | number | undefined) {
|
||||
const { data }: { data: any } = await instance(`shift-update/${Id}`);
|
||||
return data;
|
||||
},
|
||||
async addTaskFile(formData: FormData) {
|
||||
const { data } = await instance.post("attachment/", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async updatePut(updateData: TUpdate, update_id: string) {
|
||||
const { data } = await instance(`shift-update/${update_id}/`, {
|
||||
method: "PUT",
|
||||
data: updateData,
|
||||
}).then((u) => {
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
async updatePatch(obj: TUpdatePutParams, id: string | number) {
|
||||
const { data } = await instance(`shift-update/${id}/`, {
|
||||
method: "PUT",
|
||||
data: obj,
|
||||
}).then((u) => {
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async addUpdateController(obj: TUpdatePostParams) {
|
||||
const { data } = await instance
|
||||
.post<TUpdate>("shift-update/", obj)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteUpdateController(id: string) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.delete(`shift-update/${id}`).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Deleted!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,92 @@
|
||||
import { TUser } from "../../types/User/TUser";
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export type TUsersGetParams = {
|
||||
name?: string;
|
||||
team?: string;
|
||||
role?: string;
|
||||
};
|
||||
|
||||
export type TUsersPutParams = {
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
username?: string;
|
||||
team_id?: number;
|
||||
};
|
||||
|
||||
export type TUsersPostParams = {
|
||||
username?: string;
|
||||
password?: string;
|
||||
team_id?: number;
|
||||
groups?: number[];
|
||||
};
|
||||
|
||||
export const userController = {
|
||||
async read(filterObject: TUsersGetParams) {
|
||||
const params = { ...filterObject };
|
||||
|
||||
if (!!filterObject.name) params.name = filterObject.name;
|
||||
if (Array.isArray(filterObject.team)) {
|
||||
params.team = filterObject.team.join(", ");
|
||||
}
|
||||
if (Array.isArray(filterObject.role)) {
|
||||
params.role = filterObject.role.join(", ");
|
||||
}
|
||||
|
||||
const { data } = await instance.get<TUser[]>(`users/`, { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
async CheckUsername(username: string) {
|
||||
const { data } = await instance.get<TUser[]>(`users/check/${username}`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async userOne(Id: string | number | undefined) {
|
||||
const { data }: { data: any } = await instance(`users/admin/${Id}/`);
|
||||
return data;
|
||||
},
|
||||
|
||||
async userPatch(obj: TUsersPutParams, id: string) {
|
||||
const { data } = await instance
|
||||
.put<TUser>(`users/admin/${id}/`, obj)
|
||||
.then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
async addUserController(obj: TUsersPostParams) {
|
||||
message.loading({ content: "Loading..." });
|
||||
let responseData = null;
|
||||
try {
|
||||
const response = await instance.post<TUser>("users/admin/", obj);
|
||||
responseData = response;
|
||||
message.success({ content: "Loaded!", duration: 2 });
|
||||
} catch (err: any) {
|
||||
responseData = err?.response?.data;
|
||||
}
|
||||
return { data: responseData };
|
||||
},
|
||||
|
||||
async deleteUserController(id: string) {
|
||||
let res;
|
||||
let error = "";
|
||||
try {
|
||||
const { data } = await instance.delete(`users/admin/${id}/`).then((u) => {
|
||||
setTimeout(() => {
|
||||
message.success({ content: "Deleted!", key: id, duration: 2 });
|
||||
}, 1000);
|
||||
return u;
|
||||
});
|
||||
res = data;
|
||||
} catch (err) {
|
||||
error = "Oops something went wrong!";
|
||||
}
|
||||
return { data: res, error };
|
||||
},
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import axios from "axios";
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: "http://10.10.10.45:8080/api/v1/",
|
||||
});
|
||||
// const instance = axios.create({
|
||||
// baseURL: "https://api.tteld.co/api/v1/",
|
||||
// });
|
||||
|
||||
const token: string | null = localStorage.getItem("access");
|
||||
if (token) {
|
||||
instance.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response && error.response.status === 401) {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
@ -0,0 +1,42 @@
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
interface loginInterface {
|
||||
username: string;
|
||||
password: string | number;
|
||||
}
|
||||
|
||||
export const LoginApi = async ({ username, password }: loginInterface) => {
|
||||
try {
|
||||
const { data } = await instance("auth/login/", {
|
||||
method: "POST",
|
||||
data: { username, password },
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
console.log(data);
|
||||
|
||||
const userObject = {
|
||||
id: data?.data?.id,
|
||||
first_name: data?.data?.first_name,
|
||||
last_name: data?.data?.last_name,
|
||||
username: data?.data?.username,
|
||||
timezone: data?.data?.timezone,
|
||||
role: data?.data?.role,
|
||||
team_id: data?.data?.team_id,
|
||||
};
|
||||
|
||||
const userJSON = JSON.stringify(userObject);
|
||||
localStorage.setItem("user", userJSON);
|
||||
localStorage.setItem("access", data?.data.access);
|
||||
localStorage.setItem("refresh", data?.data.refresh);
|
||||
localStorage.setItem("admin_id", data?.data.id);
|
||||
document.location.replace("/");
|
||||
} catch (err) {
|
||||
setTimeout(() => {
|
||||
message.error({
|
||||
content: "Username or password incorrect!",
|
||||
duration: 2,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export const LogoutApi = async () => {
|
||||
try {
|
||||
await instance("auth/logout/", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: { refresh_token: localStorage.getItem("refresh") },
|
||||
});
|
||||
localStorage.removeItem("access");
|
||||
localStorage.removeItem("refresh");
|
||||
localStorage.removeItem("user");
|
||||
localStorage.removeItem("admin_id");
|
||||
document.location.replace("/");
|
||||
} catch (err) {
|
||||
setTimeout(() => {
|
||||
message.error({ content: "Something went wrong! ", duration: 2 });
|
||||
}, 1000);
|
||||
throw new Error("Something went wrong");
|
||||
}
|
||||
};
|
@ -0,0 +1,77 @@
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export interface activateInterface {
|
||||
user_id: string;
|
||||
confirmation_token: string;
|
||||
}
|
||||
export interface inviteInterface {
|
||||
user_id: string;
|
||||
confirmation_token: string;
|
||||
role_id: string | null;
|
||||
business_id: string | null;
|
||||
}
|
||||
|
||||
export const registryVerify = async (value: activateInterface) => {
|
||||
try {
|
||||
const { data, status } = await instance("users/verify-registration/", {
|
||||
method: "POST",
|
||||
data: value,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const userObject = {
|
||||
first_name: data?.data.first_name,
|
||||
last_name: data?.data.last_name,
|
||||
username: data?.data.username,
|
||||
id: data?.data.id,
|
||||
timezone: data?.data.timezone,
|
||||
role: data?.data.role,
|
||||
};
|
||||
|
||||
const userJSON = JSON.stringify(userObject);
|
||||
localStorage.setItem("user", userJSON);
|
||||
localStorage.setItem("access_token", data?.data.access_token);
|
||||
localStorage.setItem("refresh_token", data?.data.refresh_token);
|
||||
document.location.replace("/");
|
||||
|
||||
return status;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
setTimeout(() => {
|
||||
message.error({ content: "Something went wrong", duration: 2 });
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
export const inviteVerify = async (value: inviteInterface) => {
|
||||
try {
|
||||
const { data, status } = await instance("users/invite-verify/", {
|
||||
method: "POST",
|
||||
data: value,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const userObject = {
|
||||
first_name: data?.data.first_name,
|
||||
last_name: data?.data.last_name,
|
||||
username: data?.data.username,
|
||||
id: data?.data.id,
|
||||
timezone: data?.data.timezone,
|
||||
role: data?.data.role,
|
||||
};
|
||||
|
||||
const userJSON = JSON.stringify(userObject);
|
||||
localStorage.setItem("user", userJSON);
|
||||
localStorage.setItem("access", data?.data.access);
|
||||
localStorage.setItem("refresh", data?.data.refresh);
|
||||
localStorage.setItem("admin_id", data?.data.id);
|
||||
instance.defaults.headers.common[
|
||||
"Authorization"
|
||||
] = `Bearer ${data?.data.access}`;
|
||||
return status;
|
||||
} catch (error) {
|
||||
setTimeout(() => {
|
||||
message.error({ content: "Something went wrong", duration: 2 });
|
||||
}, 1000);
|
||||
}
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export interface inviteType {
|
||||
role_id: number;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export const inviteVerify = async (value: inviteType) => {
|
||||
try {
|
||||
const {data} = await instance("users/invite/", {
|
||||
method: "POST",
|
||||
data: value,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const succesMessage = data?.message;
|
||||
setTimeout(() => {
|
||||
message.success({ content: succesMessage, duration: 2 });
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
setTimeout(() => {
|
||||
message.error({ content: "Something went wrong", duration: 2 });
|
||||
}, 1000);
|
||||
}
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export interface registerInterface {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
password_confirm: string;
|
||||
}
|
||||
|
||||
export const RegisterApi = async (value: registerInterface) => {
|
||||
try {
|
||||
const { status, data } = await instance("users/register/", {
|
||||
method: "POST",
|
||||
data: value,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const userObject = {
|
||||
first_name: data?.data.first_name,
|
||||
last_name: data?.data.last_name,
|
||||
username: data?.data.username,
|
||||
id: data?.data.id,
|
||||
timezone: data?.data.timezone,
|
||||
role: data?.data.role,
|
||||
};
|
||||
|
||||
const userJSON = JSON.stringify(userObject);
|
||||
localStorage.setItem("user", userJSON);
|
||||
localStorage.setItem("access_token", data?.data.access_token);
|
||||
localStorage.setItem("refresh_token", data?.data.refresh_token);
|
||||
document.location.replace("/");
|
||||
return status;
|
||||
} catch (error:any) {
|
||||
console.log(error);
|
||||
setTimeout(() => {
|
||||
message.error({ content: ' ', duration: 2 });
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
export const validateUsername = async (value: any) => {
|
||||
const {status} = await instance.get(`users/check/${value}/`);
|
||||
return status
|
||||
};
|
@ -0,0 +1,45 @@
|
||||
import instance from "../api";
|
||||
import { message } from "antd";
|
||||
|
||||
export interface resetType {
|
||||
login: string;
|
||||
}
|
||||
export interface resetPassType {
|
||||
user_id: string;
|
||||
confirmation_token: string;
|
||||
new_password: string;
|
||||
password_confirm: string;
|
||||
}
|
||||
|
||||
export const resetPass = async (value: resetType) => {
|
||||
try {
|
||||
const { data } = await instance("users/send-reset-password-link/", {
|
||||
method: "POST",
|
||||
data: value,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
setTimeout(() => {
|
||||
message.error({ content: "Something went wrong", duration: 2 });
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
export const resetPassEmail = async (value: resetPassType) => {
|
||||
try {
|
||||
const { data } = await instance("users/reset-password/", {
|
||||
method: "POST",
|
||||
data: value,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const succesMessage = data?.message;
|
||||
message.success({ content: succesMessage, duration: 2 });
|
||||
setTimeout(() => {
|
||||
document.location.replace('/auth/login')
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
setTimeout(() => {
|
||||
message.error({ content: "Something went wrong", duration: 2 });
|
||||
}, 1000);
|
||||
}
|
||||
};
|
@ -0,0 +1,767 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#components-layout-demo-custom-trigger .trigger {
|
||||
line-height: 64px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
#components-layout-demo-custom-trigger .trigger:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
:where(.css-dev-only-do-not-override-1vtf12y).ant-menu .ant-menu-item {
|
||||
white-space: initial !important;
|
||||
}
|
||||
:where(.css-1vtf12y).ant-menu .ant-menu-item {
|
||||
white-space: unset;
|
||||
}
|
||||
.site-layout .site-layout-background {
|
||||
background: #fff;
|
||||
}
|
||||
.ant-pagination-options-size-changer.ant-select {
|
||||
display: none;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
/* font-family: Arial, Helvetica, sans-serif; */
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
padding: 14px 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.logo-collapsed {
|
||||
font-size: 18px;
|
||||
/* font-family: Arial, Helvetica, sans-serif; */
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.isnot {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-table-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#element::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
background-color: #f9f9fd;
|
||||
}
|
||||
|
||||
#element::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background-color: #595959;
|
||||
}
|
||||
|
||||
#element::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 10px;
|
||||
background-color: #f9f9fd;
|
||||
}
|
||||
|
||||
.ant-layout-sider-trigger {
|
||||
background: none !important;
|
||||
border-top: 1px solid #cecece;
|
||||
}
|
||||
|
||||
.new-status-row {
|
||||
background-color: rgba(170, 170, 170, 0.44);
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
background: rgb(241, 241, 241);
|
||||
border: 0.5px solid rgba(233, 233, 233, 0.11);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
justify-content: space-between;
|
||||
padding: 11px 13px;
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.062);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile-dropdown {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: max-content;
|
||||
margin-left: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.profile-dropdown:hover {
|
||||
border-radius: 8px;
|
||||
background: #7c7c7c;
|
||||
}
|
||||
|
||||
.profile-dropdown-ava {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-dropdown-text {
|
||||
text-align-last: left;
|
||||
align-items: end;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.profile-dropdown .business-name {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: right;
|
||||
padding: 0 20px 0 0;
|
||||
color: #363636;
|
||||
}
|
||||
|
||||
.business-name-dark {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: right;
|
||||
padding: 0 20px 0 0;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.profile-dropdown .username {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dot-true {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #05e776;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.dot-false {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #c71d1d;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.live-p {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
margin-left: 7px;
|
||||
width: max-content;
|
||||
color: #424242;
|
||||
}
|
||||
.live-p-dark {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
margin-left: 7px;
|
||||
width: max-content;
|
||||
color: #a8a8a8;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
padding: 10px 15px 10px 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: rgba(249, 158, 44, 1);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
font-family: Inter;
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
margin-right: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-add:hover {
|
||||
background: rgb(247, 176, 89);
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.btn-refresh-false {
|
||||
padding: 10px 21px 10px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
font-family: Inter;
|
||||
border: 1px solid rgba(215, 216, 224, 1);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
color: rgba(15, 17, 28, 1);
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-refresh-dark {
|
||||
padding: 10px 21px 10px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
font-family: Inter;
|
||||
border: 1px solid #777;
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
background: #333;
|
||||
color: #bbb;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-refresh-false:hover {
|
||||
border: 1px solid rgba(249, 158, 44, 1);
|
||||
transition: 0.5s;
|
||||
}
|
||||
.btn-refresh-dark:hover {
|
||||
border: 1px solid rgba(249, 158, 44, 1);
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: Inter;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
letter-spacing: -0.04em;
|
||||
text-align: left;
|
||||
margin-right: 12px;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin: 16px 0;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.search-input-false {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
color: rgba(15, 17, 28, 1);
|
||||
caret-color: rgba(249, 158, 44, 1);
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
background: #00000000;
|
||||
}
|
||||
|
||||
.search-input-false::placeholder {
|
||||
color: rgba(155, 157, 170, 1);
|
||||
}
|
||||
.search-input-true {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
color: #bbb;
|
||||
caret-color: rgba(249, 158, 44, 1);
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
background: #00000000;
|
||||
}
|
||||
|
||||
.search-input-true::placeholder {
|
||||
color: rgba(155, 157, 170, 1);
|
||||
}
|
||||
|
||||
.search-div {
|
||||
border: 1px solid rgba(150, 150, 150, 0.493);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
padding: 5px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.search-div:hover,
|
||||
.search-div:focus {
|
||||
border: 1px solid rgba(249, 158, 44, 1);
|
||||
}
|
||||
|
||||
.search-div:active {
|
||||
border: 1px solid rgba(249, 158, 44, 1);
|
||||
}
|
||||
|
||||
.status-new {
|
||||
background: rgba(45, 155, 219, 0.1);
|
||||
padding: 6px 0;
|
||||
color: rgb(32, 155, 226);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||
}
|
||||
.status-Pending {
|
||||
border: 0.5px solid rgba(246, 137, 0, 0.3);
|
||||
padding: 3px;
|
||||
background: rgba(246, 137, 0, 0.1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
color: rgba(246, 137, 0, 1);
|
||||
}
|
||||
|
||||
.status-checking {
|
||||
background: rgba(45, 156, 219, 0.1);
|
||||
padding: 6px 0;
|
||||
color: rgba(45, 156, 219, 1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||
}
|
||||
|
||||
.status-in-progress {
|
||||
border: 0.5px solid rgba(246, 137, 0, 0.3);
|
||||
padding: 6px 0;
|
||||
background: rgba(246, 137, 0, 0.1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
color: rgba(246, 137, 0, 1);
|
||||
}
|
||||
.status-Rejected {
|
||||
border: 0.5px solid rgba(246, 0, 0, 0.3);
|
||||
padding: 3px;
|
||||
background: rgba(246, 0, 0, 0.1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.status-done {
|
||||
padding: 6px 0;
|
||||
color: rgba(10, 160, 106, 1);
|
||||
background: rgba(10, 160, 106, 0.1);
|
||||
border: 0.5px solid rgba(10, 160, 106, 0.3);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.status-Assigned {
|
||||
padding: 3px;
|
||||
color: rgba(10, 160, 106, 1);
|
||||
background: rgba(10, 160, 106, 0.1);
|
||||
border: 0.5px solid rgba(10, 160, 106, 0.3);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.status-paper {
|
||||
background: rgba(45, 156, 219, 0.1);
|
||||
padding: 6px 0;
|
||||
color: rgba(45, 156, 219, 1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||
}
|
||||
|
||||
.status-setup {
|
||||
background: rgba(45, 156, 219, 0.1);
|
||||
padding: 6px 0;
|
||||
color: rgba(45, 156, 219, 1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #05e776;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite alternate; /* Анимация будет повторяться бесконечно */
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.circle2 {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #c71d1d;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite alternate; /* Анимация будет повторяться бесконечно */
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-pin {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border: 1px solid rgba(215, 216, 224, 1);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
padding: 7px 10px;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding-top: 7px !important;
|
||||
}
|
||||
|
||||
.btn-unpin {
|
||||
padding: 7px 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(246, 71, 71, 1);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
border: none;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.TaskModal {
|
||||
position: absolute;
|
||||
width: 800px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-left: 1px solid rgba(215, 216, 224, 1);
|
||||
box-shadow: -40px 0px 100px 10000px rgba(0, 0, 0, 0.5);
|
||||
transition: 3s;
|
||||
}
|
||||
|
||||
.TaskModal-dark {
|
||||
position: absolute;
|
||||
width: 800px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
background: rgb(37, 37, 37);
|
||||
border-left: 1px solid rgba(215, 216, 224, 1);
|
||||
box-shadow: -40px 0px 100px 1000px rgba(0, 0, 0, 0.5);
|
||||
transition: 3s;
|
||||
}
|
||||
|
||||
.btn-modal-action-false {
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(215, 216, 224, 1);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
background: rgba(255, 255, 255, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-modal-action-dark {
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #777777;
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
background: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: #bbb;
|
||||
}
|
||||
.btn-modal-action-false:hover {
|
||||
background: #e6e6e6;
|
||||
}
|
||||
.btn-modal-action-dark:hover {
|
||||
background: #e6e6e6;
|
||||
}
|
||||
|
||||
.btn-modal-action-false img,
|
||||
.btn-modal-action-dark img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mdoal-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.TaskModal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 20px 24px 24px;
|
||||
}
|
||||
.TaskModal-header-dark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 20px 24px 24px;
|
||||
}
|
||||
|
||||
.TaskModal-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.TaskModal-title-dark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-New {
|
||||
background: rgba(45, 156, 219, 0.1);
|
||||
padding: 4px 10px;
|
||||
color: rgba(45, 156, 219, 1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.status-Done {
|
||||
padding: 4px 10px;
|
||||
color: rgba(10, 160, 106, 1);
|
||||
background: rgba(10, 160, 106, 0.1);
|
||||
border: 0.5px solid rgba(10, 160, 106, 0.3);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.status-Checking {
|
||||
border: 0.5px solid rgba(246, 137, 0, 0.3);
|
||||
padding: 4px 10px;
|
||||
background: rgba(246, 137, 0, 0.1);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
border-radius: 8px;
|
||||
color: rgba(246, 137, 0, 1);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.p-driver {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.02em;
|
||||
text-align: left;
|
||||
color: rgba(15, 17, 28, 1);
|
||||
}
|
||||
.p-driver-dark {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.02em;
|
||||
text-align: left;
|
||||
color: rgb(211, 211, 211);
|
||||
}
|
||||
|
||||
.info-div {
|
||||
margin: 16px 24px;
|
||||
}
|
||||
|
||||
.info-body {
|
||||
border-radius: 12px;
|
||||
padding: 16px 24px;
|
||||
border: 1px solid rgba(215, 216, 224, 1);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
}
|
||||
|
||||
.info-body tr {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
color: rgba(121, 123, 141, 1);
|
||||
width: 200px;
|
||||
}
|
||||
.sub-dark {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
color: rgb(221, 221, 221);
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
color: rgba(15, 17, 28, 1);
|
||||
}
|
||||
.info-dark {
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: left;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.pin {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border: 1px solid rgba(215, 216, 224, 1);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
padding: 7px 10px;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.unpin {
|
||||
padding: 7px 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(246, 71, 71, 1);
|
||||
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||
border: none;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ant-upload-wrapper .ant-upload-drag {
|
||||
border: 0.5px solid rgba(215, 216, 224, 1);
|
||||
padding: 25px 0;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.card_stat {
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
padding: 20px;
|
||||
background: #deeeff;
|
||||
border-radius: 2px;
|
||||
width: 200px;
|
||||
height: 180px;
|
||||
text-align: center;
|
||||
-webkit-box-shadow: 0px 0px 12px 1px rgba(34, 60, 80, 0.08);
|
||||
-moz-box-shadow: 0px 0px 12px 1px rgba(34, 60, 80, 0.08);
|
||||
box-shadow: 0px 0px 12px 1px rgba(34, 60, 80, 0.08);
|
||||
cursor: pointer;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.card_stat:hover {
|
||||
background: #cfe4fc;
|
||||
}
|
||||
|
||||
.card_stat span {
|
||||
font-weight: 700;
|
||||
font-size: 44px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
color: #464646;
|
||||
}
|
||||
|
||||
.ant-modal-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-driver {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
@ -0,0 +1,381 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./App.css";
|
||||
import { Layout, Menu, ConfigProvider, Dropdown } from "antd";
|
||||
import { Routes, Route, Navigate, useLocation } from "react-router-dom";
|
||||
import { allMenu, mainItems, superItems } from "./Utils/sidebar";
|
||||
import Login from "./Auth/Login";
|
||||
import Notfound from "./Utils/Notfound";
|
||||
import { LogoutApi } from "./API/auth/Logout";
|
||||
import { Link } from "react-router-dom";
|
||||
// @ts-ignore
|
||||
import themeBtn from "./assets/theme-btn.svg";
|
||||
// @ts-ignore
|
||||
import avatar from "./assets/avatar-img.svg";
|
||||
import Register from "./Auth/Register";
|
||||
import Activate from "./Auth/Activate";
|
||||
import Invite from "./Auth/Invite";
|
||||
import ResetPassword from "./Auth/ResetPassword";
|
||||
import ResetByEmail from "./Auth/ResetByEmail";
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
const userJSON: any = localStorage.getItem("user");
|
||||
const userObject = JSON.parse(userJSON);
|
||||
export const timeZone = userObject?.timezone;
|
||||
export const role = userObject?.role;
|
||||
export const admin_id = localStorage.getItem("admin_id");
|
||||
export const team_id = userObject?.team_id;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const isAuthenticated = localStorage.getItem("access") as string;
|
||||
const authorized = isAuthenticated;
|
||||
const [collapsed, setCollapsed] = useState<any>(
|
||||
localStorage.getItem("collapsed") === "true" ? true : false
|
||||
);
|
||||
const [theme, setTheme] = useState<any>(
|
||||
localStorage.getItem("theme") === "true" ? true : false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("theme", theme);
|
||||
}, [theme]);
|
||||
useEffect(() => {
|
||||
localStorage.setItem("collapsed", collapsed);
|
||||
}, [collapsed]);
|
||||
|
||||
let location: any = useLocation();
|
||||
const clickLogout = () => {
|
||||
LogoutApi();
|
||||
};
|
||||
const dark = {
|
||||
components: {
|
||||
Table: {
|
||||
colorBgContainer: "#202020",
|
||||
colorText: "#BBBBBB",
|
||||
headerColor: "#BBBBBB",
|
||||
borderColor: "#3A3A3A",
|
||||
headerSplitColor: "#3A3A3A",
|
||||
rowHoverBg: "#333333",
|
||||
colorBorder: "#3A3A3A",
|
||||
},
|
||||
Layout: {
|
||||
bodyBg: "#181818",
|
||||
},
|
||||
Input: {
|
||||
colorBgContainer: "#2A2A2A",
|
||||
colorBgContainerDisabled: "#2A2A2A",
|
||||
colorText: "#BBBBBB",
|
||||
colorTextPlaceholder: "#BBBBBB",
|
||||
colorBorder: "#3A3A3A",
|
||||
colorFillSecondary: "rgba(0, 0, 0, 0.02)",
|
||||
activeBorderColor: "#3A3A3A",
|
||||
activeShadow: "#3A3A3A",
|
||||
hoverBorderColor: "#3A3A3A",
|
||||
// colorIcon: "#BBBBBB",
|
||||
// colorIconHover: "#BBBBBB",
|
||||
},
|
||||
Select: {
|
||||
colorBgContainer: "#2A2A2A",
|
||||
colorText: "#BBBBBB",
|
||||
colorTextPlaceholder: "#BBBBBB",
|
||||
colorBorder: "rgba(150, 150, 150, 0.493)",
|
||||
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||
colorIconHover: "#BBB",
|
||||
optionSelectedBg: "#2A2A2A",
|
||||
colorBgElevated: "#333",
|
||||
controlOutline: "none",
|
||||
optionActiveBg: "#333333",
|
||||
colorTextQuaternary: "#3A3A3A",
|
||||
},
|
||||
Button: {
|
||||
colorBorderSecondary: "rgba(249, 158, 44, 1)",
|
||||
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||
colorPrimaryHover: "#BBBBBB",
|
||||
colorIcon: "rgba(249, 158, 44, 1)",
|
||||
colorIconHover: "rgba(249, 158, 44, 1)",
|
||||
primaryShadow: "none",
|
||||
dangerShadow: "none",
|
||||
colorTextDisabled: "#AAAAAA",
|
||||
borderColorDisabled: "#3A3A3A",
|
||||
},
|
||||
// Form: {
|
||||
// labelColor: "#BBBBBB",
|
||||
// },
|
||||
Tabs: {
|
||||
itemColor: "#BBBBBB",
|
||||
itemHoverColor: "#FFFFFF",
|
||||
itemSelectedColor: "rgba(249, 158, 44, 1)",
|
||||
colorPrimaryActive: "rgba(249, 158, 44, 1)",
|
||||
inkBarColor: "rgba(249, 158, 44, 1)",
|
||||
},
|
||||
// Upload: {
|
||||
// colorText: "#FFFFFF",
|
||||
// colorInfoBgHover: "#1E1E1E",
|
||||
// },
|
||||
// Pagination: {
|
||||
// colorText: "#BBBBBB",
|
||||
// colorPrimary: "#FFFFFF",
|
||||
// colorBgContainer: "#1A1A1A",
|
||||
// colorBorderSecondary: "#3A3A3A",
|
||||
// },
|
||||
Modal: {
|
||||
contentBg: "#3A3A3A",
|
||||
headerBg: "#3A3A3A",
|
||||
titleColor: "#FFFFFF",
|
||||
colorText: "#BBBBBB",
|
||||
colorBgTextActive: "#BBBBBB",
|
||||
colorBgTextHover: "#BBBBBB",
|
||||
},
|
||||
Menu: {
|
||||
darkItemSelectedBg: "#3A3A3A",
|
||||
colorBgContainer: "#fff",
|
||||
},
|
||||
Switch: {
|
||||
colorPrimary: "#565656",
|
||||
colorPrimaryHover: "#737373",
|
||||
},
|
||||
Radio: {
|
||||
colorText: "#737373",
|
||||
colorBorder: "#3A3A3A",
|
||||
colorPrimaryActive: "#BBBBBB",
|
||||
buttonCheckedBg: "rgba(249, 158, 44, 1)",
|
||||
colorPrimaryHover: "#737373",
|
||||
colorPrimary: "#565656",
|
||||
},
|
||||
Dropdown: {
|
||||
colorBgContainer: "#3A3A3A",
|
||||
colorText: "#BBBBBB",
|
||||
colorPrimaryHover: "#565656",
|
||||
colorPrimary: "#333333",
|
||||
},
|
||||
DatePicker: {
|
||||
colorBgContainer: "#3A3A3A",
|
||||
colorBgElevated: "#3A3A3A",
|
||||
colorText: "#BBBBBB",
|
||||
colorTextPlaceholder: "#BBBBBB",
|
||||
colorIcon: "#fff",
|
||||
colorIconHover: "#fff",
|
||||
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||
hoverBorderColor: "#BBBBBB",
|
||||
},
|
||||
Empty: {
|
||||
colorText: "rgba(249, 158, 44, 1)",
|
||||
colorTextDisabled: "rgba(249, 158, 44, 1)",
|
||||
},
|
||||
},
|
||||
token: {
|
||||
fontFamily: "Inter, sans-serif",
|
||||
colorText: "#bbb",
|
||||
borderRadius: 8,
|
||||
},
|
||||
};
|
||||
const light = {
|
||||
components: {
|
||||
Table: {
|
||||
rowHoverBg: "#bae0ff",
|
||||
headerBg: "none",
|
||||
colorText: "rgba(24, 26, 41, 1)",
|
||||
fontWeightStrong: 500,
|
||||
colorTextHeading: "rgba(161, 162, 171, 1)",
|
||||
},
|
||||
Select: {
|
||||
colorTextPlaceholder: "rgba(155, 157, 170, 1)",
|
||||
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||
},
|
||||
Tabs: {
|
||||
inkBarColor: "rgba(249, 158, 44, 1)",
|
||||
itemSelectedColor: "rgba(24, 26, 41, 1)",
|
||||
itemHoverColor: "rgba(24, 26, 41, 1)",
|
||||
},
|
||||
Input: {
|
||||
hoverBorderColor: "rgba(249, 158, 44, 1)",
|
||||
activeBorderColor: "rgba(249, 158, 44, 1)",
|
||||
colorTextPlaceholder: "rgba(155, 157, 170, 1)",
|
||||
},
|
||||
Upload: {
|
||||
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||
},
|
||||
Button: {
|
||||
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||
},
|
||||
Textarea: {
|
||||
colorBorder: "0px 1px 3px 0px rgba(20, 22, 41, 0.1)",
|
||||
},
|
||||
Menu: {
|
||||
darkItemSelectedBg: "rgba(255, 255, 255, 0.08)",
|
||||
},
|
||||
},
|
||||
token: {
|
||||
fontFamily: "Inter, sans-serif",
|
||||
color: "#262626",
|
||||
borderRadius: 8,
|
||||
},
|
||||
};
|
||||
const rep = () => {
|
||||
document.location.replace("/");
|
||||
};
|
||||
const menu: any = (
|
||||
<Menu>
|
||||
<Menu.Item key="profile">
|
||||
<Link to="profile/">Profile</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="logout" danger onClick={clickLogout}>
|
||||
Logout
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={theme === true ? dark : light}>
|
||||
<div>
|
||||
{!authorized &&
|
||||
!(
|
||||
location.pathname.startsWith("/auth/register") ||
|
||||
location.pathname.startsWith("/auth/activate") ||
|
||||
location.pathname.startsWith("/auth/reset_password") ||
|
||||
location.pathname.startsWith("/auth/reset-password") ||
|
||||
location.pathname.startsWith("/auth/login") ||
|
||||
location.pathname.startsWith("/auth/invite")
|
||||
) && (
|
||||
<Navigate
|
||||
to={{
|
||||
pathname: "/auth/login",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{authorized && location.pathname === "/login" && (
|
||||
<Navigate
|
||||
to={{
|
||||
pathname: "/",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{authorized ? (
|
||||
<Layout>
|
||||
<Sider
|
||||
theme={"dark"}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
onCollapse={(value) => setCollapsed(value)}
|
||||
style={{
|
||||
height: "100vh",
|
||||
background: theme === true ? "#202020" : "rgba(20, 22, 41, 1)",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
onClick={rep}
|
||||
style={{ cursor: "pointer" }}
|
||||
className={collapsed ? "logo-collapsed" : "logo"}
|
||||
>
|
||||
TT ELD
|
||||
</p>
|
||||
<Menu
|
||||
theme={"dark"}
|
||||
mode="inline"
|
||||
defaultSelectedKeys={[location.pathname]}
|
||||
items={allMenu}
|
||||
style={{
|
||||
background:
|
||||
theme === true ? "#202020" : "rgba(20, 22, 41, 1)",
|
||||
color: "rgba(255, 255, 255, 0.6)",
|
||||
}}
|
||||
></Menu>
|
||||
</Sider>
|
||||
<Layout className="site-layout">
|
||||
<Header
|
||||
className="site-layout-background"
|
||||
style={{
|
||||
padding: 0,
|
||||
background:
|
||||
theme === true ? "#202020" : "rgba(215, 216, 224, 1)",
|
||||
display: "flex",
|
||||
justifyContent: "end",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
float: "right",
|
||||
marginRight: "35px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
alignSelf: "center",
|
||||
minWidth: 150,
|
||||
maxWidth: 500,
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="theme-btn"
|
||||
onClick={(e) => setTheme(!theme)}
|
||||
>
|
||||
<img src={themeBtn} alt="" />
|
||||
</button>
|
||||
<Dropdown overlay={menu} trigger={["click"]}>
|
||||
<div
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<div className="profile-dropdown">
|
||||
<div className="profile-dropdown-ava">
|
||||
<img src={avatar} alt="" />
|
||||
</div>
|
||||
<div
|
||||
className="d-flex profile-dropdown-text"
|
||||
style={{ flexDirection: "column" }}
|
||||
>
|
||||
<p
|
||||
className={
|
||||
!theme ? "business-name" : "business-name-dark"
|
||||
}
|
||||
>
|
||||
{userObject?.username}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Header>
|
||||
<Content
|
||||
id="element"
|
||||
style={{
|
||||
padding: 24,
|
||||
minHeight: "92vh",
|
||||
maxHeight: "calc(90vh - 10px)",
|
||||
overflowY: "scroll",
|
||||
background: theme === true ? "#202020" : "#fff",
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
{mainItems &&
|
||||
mainItems.map((u) => (
|
||||
<Route key={u.key} path={u.path} element={u.component} />
|
||||
))}
|
||||
{superItems &&
|
||||
superItems.map((u) => (
|
||||
<Route key={u.key} path={u.path} element={u.component} />
|
||||
))}
|
||||
<Route path="*" element={<Notfound />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Routes>
|
||||
<Route path="/auth/login" element={<Login />} />
|
||||
<Route path="/auth/register" element={<Register />} />
|
||||
<Route path="/auth/activate" element={<Activate />} />
|
||||
<Route path="/auth/invite" element={<Invite />} />
|
||||
<Route path="/auth/reset_password" element={<ResetPassword />} />
|
||||
<Route path="/auth/reset-password" element={<ResetByEmail />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -0,0 +1,21 @@
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
import { registryVerify } from "../API/auth/activate";
|
||||
import { message } from "antd";
|
||||
|
||||
const Activate = () => {
|
||||
const location = useLocation();
|
||||
const queryParameters = new URLSearchParams(location.search);
|
||||
const userId = queryParameters.get("user_id");
|
||||
const confirmationToken = queryParameters.get("confirmation_token");
|
||||
|
||||
if (userId && confirmationToken) {
|
||||
registryVerify({
|
||||
user_id: userId,
|
||||
confirmation_token: confirmationToken,
|
||||
})
|
||||
}
|
||||
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default Activate;
|
@ -0,0 +1,32 @@
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { inviteVerify } from "../API/auth/activate";
|
||||
import { message } from "antd";
|
||||
|
||||
const Invite = () => {
|
||||
const location = useLocation();
|
||||
const queryParameters = new URLSearchParams(location.search);
|
||||
const userId = queryParameters.get("user_id");
|
||||
const confirmationToken = queryParameters.get("confirmation_token");
|
||||
const business_id = queryParameters.get("business_id");
|
||||
const role_id = queryParameters.get("role_id");
|
||||
if (userId && confirmationToken) {
|
||||
inviteVerify({
|
||||
user_id: userId,
|
||||
confirmation_token: confirmationToken,
|
||||
role_id: role_id,
|
||||
business_id: business_id,
|
||||
}).then((status) => {
|
||||
console.log(status);
|
||||
|
||||
if (status === 200) {
|
||||
document.location.replace("/");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
message.warning({ content: "Your Activision is expired" });
|
||||
}
|
||||
|
||||
return <div>hello</div>;
|
||||
};
|
||||
|
||||
export default Invite;
|
@ -0,0 +1,125 @@
|
||||
import React from "react";
|
||||
import { Button, Card, Input, Space } from "antd";
|
||||
import { Form, Field } from "react-final-form";
|
||||
import { LockOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { LoginApi } from "../API/auth/Login";
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const validate = (val: any) => {
|
||||
const err: any = {};
|
||||
if (!val.login) {
|
||||
err.login = "Required";
|
||||
}
|
||||
if (!val.password) {
|
||||
err.password = "Required";
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
const sleep = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const onSubmit = async (values: any) => {
|
||||
await sleep(300);
|
||||
await LoginApi({ username: values.login, password: values.password });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ContainerClassName" style={{ height: "100vh" }}>
|
||||
<Form
|
||||
onSubmit={onSubmit}
|
||||
validate={validate}
|
||||
render={({ submitError, handleSubmit, submitting }) => (
|
||||
<Space
|
||||
direction="horizontal"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
bodyStyle={{ background: "rgb(250, 250, 250)" }}
|
||||
title="Login"
|
||||
className="login-form-card "
|
||||
style={{ width: 400 }}
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex", gap: "30px" }}
|
||||
>
|
||||
<Field name="login">
|
||||
{({ input, meta }) => (
|
||||
<div>
|
||||
<Input
|
||||
prefix={
|
||||
<UserOutlined className="site-form-item-icon" />
|
||||
}
|
||||
size={"large"}
|
||||
{...input}
|
||||
type="text"
|
||||
placeholder="username or e-mail"
|
||||
/>
|
||||
{(meta.error || meta.submitError) && meta.touched && (
|
||||
<span style={{ color: "red" }}>
|
||||
{meta.error || meta.submitError}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="password">
|
||||
{({ input, meta }) => (
|
||||
<div>
|
||||
<Input.Password
|
||||
prefix={
|
||||
<LockOutlined className="site-form-item-icon" />
|
||||
}
|
||||
size={"large"}
|
||||
{...input}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
{meta.error && meta.touched && (
|
||||
<span style={{ color: "red" }}>{meta.error}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{submitError && (
|
||||
<div style={{ color: "red" }} className="error">
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className="Login-form-button"
|
||||
size={"large"}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
disabled={submitting}
|
||||
>
|
||||
Log In
|
||||
</Button>
|
||||
{/* <h5>
|
||||
<Link to='/auth/reset_password'>Forgot password?</Link>
|
||||
<br />
|
||||
Don't have an account?
|
||||
<Link to='/auth/register'> Create one now</Link>
|
||||
</h5> */}
|
||||
</Space>
|
||||
</form>
|
||||
</Card>
|
||||
</Space>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
@ -0,0 +1,105 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Card, Input, Space } from "antd";
|
||||
import { Form, Field } from "react-final-form";
|
||||
import { UserOutlined } from "@ant-design/icons";
|
||||
import { resetPass } from "../API/auth/resetPass";
|
||||
import Success from "./Success";
|
||||
|
||||
const ResetPassword: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const validate = (val: any) => {
|
||||
const err: any = {};
|
||||
if (!val.login) {
|
||||
err.login = "Required";
|
||||
}
|
||||
return err;
|
||||
};
|
||||
|
||||
const sleep = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const onSubmit = async (values: any) => {
|
||||
setEmail(values.login);
|
||||
await sleep(300);
|
||||
await resetPass({ login: values.login }).then(() => {
|
||||
setOpen(true);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ContainerClassName" style={{ height: "100vh" }}>
|
||||
<Form
|
||||
onSubmit={onSubmit}
|
||||
validate={validate}
|
||||
render={({ submitError, handleSubmit, submitting }) => (
|
||||
<Space
|
||||
direction="horizontal"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
bodyStyle={{ background: "rgb(250, 250, 250)" }}
|
||||
title="Login"
|
||||
className="login-form-card "
|
||||
style={{ width: 400 }}
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex", gap: "30px" }}
|
||||
>
|
||||
<Field name="login">
|
||||
{({ input, meta }) => (
|
||||
<div>
|
||||
<Input
|
||||
prefix={
|
||||
<UserOutlined className="site-form-item-icon" />
|
||||
}
|
||||
size={"large"}
|
||||
{...input}
|
||||
type="text"
|
||||
placeholder="username or e-mail"
|
||||
/>
|
||||
{(meta.error || meta.submitError) && meta.touched && (
|
||||
<span style={{ color: "red" }}>
|
||||
{meta.error || meta.submitError}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
{submitError && (
|
||||
<div style={{ color: "red" }} className="error">
|
||||
{submitError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className="Login-form-button"
|
||||
size={"large"}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
disabled={submitting}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Space>
|
||||
</form>
|
||||
</Card>
|
||||
</Space>
|
||||
)}
|
||||
/>
|
||||
<Success open={open} setOpen={setOpen} email={email} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPassword;
|
@ -0,0 +1,35 @@
|
||||
import { Modal } from "antd";
|
||||
|
||||
const Success = ({
|
||||
open,
|
||||
setOpen,
|
||||
email,
|
||||
}: {
|
||||
email: string;
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
}) => {
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal open={open} title="Success!" onCancel={handleCancel} footer={null}>
|
||||
<p>
|
||||
A password-reset-link has been sent to your email! To reset your
|
||||
password you will need to follow the link provided in the message. The
|
||||
letter was sent to:{" "}
|
||||
<span style={{ textDecoration: "underline" }}>{email}</span>
|
||||
<br />
|
||||
<br />
|
||||
If for some reason you do not reset your password, within three days,
|
||||
the link sent to your e-mail will expire. You can request for
|
||||
control letter again.
|
||||
</p>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Success;
|
@ -0,0 +1,33 @@
|
||||
import React, { useState } from "react";
|
||||
import CallTable from "./CallTable";
|
||||
import { useCallData } from "../../Hooks/CallRequests";
|
||||
import { Radio, RadioChangeEvent } from "antd";
|
||||
|
||||
const Call = () => {
|
||||
const [status, setStatus] = useState("Awaiting");
|
||||
const { data, isLoading, refetch } = useCallData({ status: status });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<div className="header d-flex">
|
||||
<p className="title">Call Requests</p>
|
||||
</div>
|
||||
<div className="filter d-flex">
|
||||
<Radio.Group
|
||||
onChange={(e: RadioChangeEvent) => setStatus(e.target.value)}
|
||||
size="middle"
|
||||
value={status}
|
||||
style={{ marginLeft: 20 }}
|
||||
>
|
||||
<Radio.Button value={"Awaiting"}>Awaiting</Radio.Button>
|
||||
<Radio.Button value={"Resolved"}>Resolved</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
</div>
|
||||
<CallTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Call;
|
@ -0,0 +1,155 @@
|
||||
import { Button, Input, Modal, Space, Table } from "antd";
|
||||
import { TCall } from "../../types/CallRequests/TCall";
|
||||
import { EditOutlined } from "@ant-design/icons";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
import moment from "moment";
|
||||
import { callController } from "../../API/LayoutApi/callrequests";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { useState } from "react";
|
||||
const CallTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
refetch,
|
||||
}: {
|
||||
data: TCall[] | undefined;
|
||||
isLoading: boolean;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TCall[], unknown>>;
|
||||
}) => {
|
||||
const statusClick = (record: TCall | any) => {
|
||||
callController
|
||||
.callPatch({ note: undefined, status: "Resolved" }, record.id)
|
||||
.then(() => {
|
||||
refetch();
|
||||
});
|
||||
};
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [note, setNote] = useState("");
|
||||
const [id, setId] = useState<number>();
|
||||
const addNote = (a: any) => {
|
||||
setModalVisible(true);
|
||||
setId(a.id);
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
if (id) {
|
||||
callController
|
||||
.callPatch({ note: note, status: undefined }, id)
|
||||
.then(() => {
|
||||
refetch();
|
||||
setNote("");
|
||||
});
|
||||
}
|
||||
setModalVisible(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setModalVisible(false);
|
||||
};
|
||||
|
||||
const handleNoteChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNote(e.target.value);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
loading={isLoading}
|
||||
dataSource={data?.map((u, i) => ({
|
||||
...u,
|
||||
no: i + 1,
|
||||
id: u?.id,
|
||||
company: u?.company?.name,
|
||||
driver: u?.driver?.name,
|
||||
time: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||
"DD.MM.YYYY HH:mm"
|
||||
),
|
||||
action: u,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
dataIndex: "company",
|
||||
width: "20%",
|
||||
},
|
||||
{
|
||||
title: "Driver",
|
||||
dataIndex: "driver",
|
||||
width: "20%",
|
||||
},
|
||||
{
|
||||
title: "Note",
|
||||
dataIndex: "note",
|
||||
width: "15%",
|
||||
},
|
||||
// {
|
||||
// title: "Status",
|
||||
// dataIndex: "status",
|
||||
// width: "8%",
|
||||
// render: (status: string) => (
|
||||
// <span>
|
||||
// {status === "Awaiting" && (
|
||||
// <p className="status-new">Awaiting</p>
|
||||
// )}
|
||||
// {status === "Resolved" && (
|
||||
// <p className="status-done">Resolved</p>
|
||||
// )}
|
||||
// </span>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
title: "Requested at",
|
||||
dataIndex: "time",
|
||||
width: "15%",
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
dataIndex: "action",
|
||||
width: "100px",
|
||||
render: (text, record) => {
|
||||
return (
|
||||
<div>
|
||||
{record.status !== "Resolved" && (
|
||||
<Button type="primary" onClick={() => statusClick(record)}>
|
||||
Resolve
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
style={{ marginLeft: 16 }}
|
||||
type="primary"
|
||||
onClick={(e) => addNote(record)}
|
||||
>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
<Modal
|
||||
title="Add note"
|
||||
visible={modalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Input value={note} onChange={(e) => handleNoteChange(e)} />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallTable;
|
@ -0,0 +1,115 @@
|
||||
import { Input, Modal, Form as FormAnt, Switch, Select } from "antd";
|
||||
import { companyController } from "../../API/LayoutApi/companies";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { TCompany } from "../../types/Company/TCompany";
|
||||
|
||||
const AddCompany = ({
|
||||
open,
|
||||
setOpen,
|
||||
refetch,
|
||||
}: {
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TCompany[], unknown>>;
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
const eld = [
|
||||
{ name: "Zippy" },
|
||||
{ name: "EVO" },
|
||||
{ name: "Ontime" },
|
||||
{ name: "Zeelog" },
|
||||
{ name: "TT" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Add Company"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form.validateFields().then(async (values) => {
|
||||
await companyController
|
||||
.addCompanyController(values)
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
form.resetFields();
|
||||
setOpen(!open);
|
||||
}
|
||||
});
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
rules={[{ required: true, message: "Please enter company name!" }]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Source"
|
||||
name="source"
|
||||
rules={[{ required: false, message: "Please input owner name!" }]}
|
||||
>
|
||||
<Select
|
||||
options={eld?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.name,
|
||||
}))}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Is Active"
|
||||
name="is_active"
|
||||
rules={[
|
||||
{ required: false, message: "Please input company status!" },
|
||||
]}
|
||||
>
|
||||
<Switch defaultChecked={true} />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="USDOT"
|
||||
name="usdot"
|
||||
rules={[
|
||||
{ required: false, message: "Please input company status!" },
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="API Key"
|
||||
name="api_key"
|
||||
rules={[
|
||||
{ required: false, message: "Please input company status!" },
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCompany;
|
@ -0,0 +1,120 @@
|
||||
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||
import { customerController } from "../../API/LayoutApi/customers";
|
||||
import { useCompanyData, useCompanyOne } from "../../Hooks/Companies";
|
||||
// @ts-ignore
|
||||
import zippy from "../../assets/zippyicon.svg";
|
||||
// @ts-ignore
|
||||
import evo from "../../assets/evoicon.png";
|
||||
// @ts-ignore
|
||||
import zeelog from "../../assets/zeelogicon.svg";
|
||||
// @ts-ignore
|
||||
import ontime from "../../assets/ontimeicon.svg";
|
||||
// @ts-ignore
|
||||
import tt from "../../assets/tticon.svg";
|
||||
import { useState } from "react";
|
||||
const AddDriver = ({
|
||||
open,
|
||||
id,
|
||||
setOpen,
|
||||
}: {
|
||||
id: any;
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
const companyData = useCompanyOne(id);
|
||||
const companyDataAll = useCompanyData({ name: "" });
|
||||
const [companyName, setCompanyName] = useState<string>();
|
||||
const getImageSource = (source: string) => {
|
||||
switch (source) {
|
||||
case "Zippy":
|
||||
return zippy;
|
||||
case "EVO":
|
||||
return evo;
|
||||
case "Ontime":
|
||||
return ontime;
|
||||
case "Zeelog":
|
||||
return zeelog;
|
||||
case "TT":
|
||||
return tt;
|
||||
default:
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Add Driver"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form.validateFields().then(async (values) => {
|
||||
form.resetFields();
|
||||
const updatedValues = { ...values };
|
||||
updatedValues.company_id = id;
|
||||
await customerController.addCustomerController(updatedValues);
|
||||
setOpen(!open);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
rules={[{ required: true, message: "Please input Name!" }]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Company"
|
||||
name="company_id"
|
||||
rules={[{ required: false, message: "Please input company!" }]}
|
||||
>
|
||||
{id ? (
|
||||
<Input defaultValue={companyData?.data?.name} readOnly />
|
||||
) : (
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search Company"
|
||||
onSearch={(value: any) => setCompanyName(value)}
|
||||
options={companyDataAll?.data?.map((item: any) => ({
|
||||
label: (
|
||||
<div>
|
||||
{item?.source && (
|
||||
<img
|
||||
style={{ width: 15, height: 20, paddingTop: 7 }}
|
||||
src={getImageSource(item?.source)}
|
||||
alt=""
|
||||
/>
|
||||
)}{" "}
|
||||
{item?.name}
|
||||
</div>
|
||||
),
|
||||
value: item?.id,
|
||||
}))}
|
||||
value={companyName}
|
||||
filterOption={false}
|
||||
autoClearSearchValue={false}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddDriver;
|
@ -0,0 +1,67 @@
|
||||
import { useRef, useState } from "react";
|
||||
import AddCompany from "./AddCompanies";
|
||||
import CompanyTable from "./CompaniesTable";
|
||||
// @ts-ignore
|
||||
import IconSearch from "../../assets/searchIcon.png";
|
||||
|
||||
import { useCompanyData } from "../../Hooks/Companies";
|
||||
//@ts-ignore
|
||||
import addicon from "../../assets/addiconpng.png";
|
||||
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
|
||||
const Company = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const [search, setSearch] = useState<any>("");
|
||||
const { data, isLoading, refetch } = useCompanyData({
|
||||
name: search,
|
||||
is_active: undefined,
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{open && <AddCompany open={open} refetch={refetch} setOpen={setOpen} />}
|
||||
<div className="header d-flex">
|
||||
<h1 className="title">Companies</h1>
|
||||
<button
|
||||
style={{ marginRight: 0 }}
|
||||
className="btn-add d-flex"
|
||||
onClick={showModal}
|
||||
>
|
||||
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||
Add Company
|
||||
</button>
|
||||
</div>
|
||||
<div className="filter d-flex">
|
||||
<div className="search-div">
|
||||
<img src={IconSearch} alt="" />
|
||||
<input
|
||||
className={`search-input-${theme}`}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CompanyTable data={data} isLoading={isLoading} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Company;
|
@ -0,0 +1,359 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useCompanyOne } from "../../Hooks/Companies";
|
||||
import {
|
||||
Form,
|
||||
Spin,
|
||||
Watermark,
|
||||
Space,
|
||||
Tabs,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
Button,
|
||||
Tag,
|
||||
Radio,
|
||||
RadioChangeEvent,
|
||||
Select,
|
||||
} from "antd";
|
||||
import { companyController } from "../../API/LayoutApi/companies";
|
||||
import { DashboardOutlined } from "@ant-design/icons";
|
||||
import Notfound from "../../Utils/Notfound";
|
||||
import Table from "antd/es/table";
|
||||
import AddDriver from "./AddDriver";
|
||||
import { useCustomerByComanyData } from "../../Hooks/Customers";
|
||||
|
||||
// @ts-ignore
|
||||
import zippy from "../../assets/zippyicon.svg";
|
||||
// @ts-ignore
|
||||
import evo from "../../assets/evoicon.png";
|
||||
// @ts-ignore
|
||||
import zeelog from "../../assets/zeelogicon.svg";
|
||||
// @ts-ignore
|
||||
import ontime from "../../assets/ontimeicon.svg";
|
||||
// @ts-ignore
|
||||
import tt from "../../assets/tticon.svg";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIcon from "../../assets/infoIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIconActive from "../../assets/infoIconActive.png";
|
||||
import { role } from "../../App";
|
||||
import { useTeamData } from "../../Hooks/Teams";
|
||||
import { validateLocaleAndSetLanguage } from "typescript";
|
||||
const TabPane = Tabs.TabPane;
|
||||
type params = {
|
||||
readonly id: any;
|
||||
};
|
||||
type MyObjectType = {
|
||||
[key: string | number]: any;
|
||||
};
|
||||
const CompanyEdit = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { id } = useParams<params>();
|
||||
const customerData = useCustomerByComanyData({ id: id });
|
||||
const { data, refetch, status }: MyObjectType = useCompanyOne(id);
|
||||
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
let navigate = useNavigate();
|
||||
|
||||
const onSubmit = async (value: any) => {
|
||||
value.team_id = team;
|
||||
await companyController.companyPatch(value, id);
|
||||
refetch();
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const ClickDelete = () => {
|
||||
const shouldDelete = window.confirm(
|
||||
"Вы уверены, что хотите удалить эту компанию?"
|
||||
);
|
||||
if (shouldDelete && id !== undefined) {
|
||||
companyController.deleteCompanyController(id).then(() => {
|
||||
document.location.replace(`/#/companies`);
|
||||
});
|
||||
}
|
||||
};
|
||||
const [value, setValue] = useState(1);
|
||||
const onChange = (e: RadioChangeEvent) => {
|
||||
console.log("radio checked", e.target.value);
|
||||
setValue(e.target.value);
|
||||
};
|
||||
const [activeTab, setActiveTab] = useState("1");
|
||||
|
||||
const TeamData = useTeamData("");
|
||||
const noTeamOption = { label: " - - - - - -", value: "" };
|
||||
const TeamOption: { label: string; value: any }[] | undefined =
|
||||
TeamData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}));
|
||||
if (TeamOption) {
|
||||
TeamOption.unshift(noTeamOption);
|
||||
}
|
||||
|
||||
const [team, setTeam] = useState();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Watermark style={{ height: "100%" }}>
|
||||
{status === "loading" ? (
|
||||
<Spin size="large" spinning={!data} />
|
||||
) : data ? (
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key)}
|
||||
>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
style={{ marginRight: 10 }}
|
||||
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||
alt=""
|
||||
/>
|
||||
Information
|
||||
</span>
|
||||
}
|
||||
key="1"
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label={"Name"}
|
||||
name="name"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="USDOT"
|
||||
name="usdot"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Team"
|
||||
>
|
||||
<Select
|
||||
options={TeamOption}
|
||||
defaultValue={data?.team?.name}
|
||||
onChange={(e) => setTeam(e)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="API Key"
|
||||
name="api_key"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Source"
|
||||
name="source"
|
||||
>
|
||||
<Radio.Group
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Radio value="TT">
|
||||
<img
|
||||
style={{ width: 50, height: 50 }}
|
||||
src={tt}
|
||||
alt=""
|
||||
/>
|
||||
</Radio>
|
||||
<Radio value="EVO">
|
||||
<img
|
||||
style={{ width: 50, height: 50 }}
|
||||
src={evo}
|
||||
alt=""
|
||||
/>
|
||||
</Radio>
|
||||
<Radio value="Zippy">
|
||||
<img
|
||||
style={{ width: 50, height: 50 }}
|
||||
src={zippy}
|
||||
alt=""
|
||||
/>
|
||||
</Radio>
|
||||
<Radio value="Ontime">
|
||||
<img
|
||||
style={{ width: 50, height: 50 }}
|
||||
src={ontime}
|
||||
alt=""
|
||||
/>
|
||||
</Radio>
|
||||
<Radio value="Zeelog">
|
||||
<img
|
||||
style={{ width: 50, height: 50 }}
|
||||
src={zeelog}
|
||||
alt=""
|
||||
/>
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item>
|
||||
{role === "Owner" && (
|
||||
<Button
|
||||
onClick={() => ClickDelete()}
|
||||
type="primary"
|
||||
style={{ marginRight: 10 }}
|
||||
danger
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
{role === "Owner" && (
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Space>
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<DashboardOutlined />
|
||||
Drivers
|
||||
</span>
|
||||
}
|
||||
key="2"
|
||||
>
|
||||
<Table
|
||||
onRow={(record) => {
|
||||
let isTextSelected = false;
|
||||
document.addEventListener("selectionchange", () => {
|
||||
const selection = window.getSelection();
|
||||
if (
|
||||
selection !== null &&
|
||||
selection.toString() !== ""
|
||||
) {
|
||||
isTextSelected = true;
|
||||
} else {
|
||||
isTextSelected = false;
|
||||
}
|
||||
});
|
||||
return {
|
||||
onClick: (event: any) => {
|
||||
if (isTextSelected) {
|
||||
}
|
||||
document.location.replace(
|
||||
`/#/customers/${record.id}`
|
||||
);
|
||||
},
|
||||
};
|
||||
}}
|
||||
dataSource={customerData?.data?.map((u, i) => ({
|
||||
...u,
|
||||
no: i + 1,
|
||||
key: u?.id,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
},
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
},
|
||||
{
|
||||
title: "Role",
|
||||
dataIndex: "profession",
|
||||
},
|
||||
{
|
||||
title: "Is Active",
|
||||
dataIndex: "is_active",
|
||||
render: (tag: boolean) => (
|
||||
<Tag color={tag ? "geekblue" : "red"}>
|
||||
{tag ? "True" : "False"}
|
||||
</Tag>
|
||||
),
|
||||
filters: [
|
||||
{
|
||||
text: "True",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
text: "False",
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
onFilter: (value: any, record: any) => {
|
||||
return record.isActive === value;
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{open && (
|
||||
<AddDriver id={id} open={open} setOpen={setOpen} />
|
||||
)}
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginLeft: "auto" }}
|
||||
size={"middle"}
|
||||
onClick={showModal}
|
||||
disabled={role !== "Owner"}
|
||||
>
|
||||
Add Driver
|
||||
</Button>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Space>
|
||||
</Spin>
|
||||
) : (
|
||||
<Notfound />
|
||||
)}
|
||||
</Watermark>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyEdit;
|
@ -0,0 +1,177 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Space, Table, Tooltip, message } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import { SyncOutlined, EyeOutlined } from "@ant-design/icons";
|
||||
import { companyController } from "../../API/LayoutApi/companies";
|
||||
import { TCompany } from "../../types/Company/TCompany";
|
||||
// @ts-ignore
|
||||
import zippy from "../../assets/zippyicon.svg";
|
||||
// @ts-ignore
|
||||
import evo from "../../assets/evoicon.png";
|
||||
// @ts-ignore
|
||||
import zeelog from "../../assets/zeelogicon.svg";
|
||||
// @ts-ignore
|
||||
import ontime from "../../assets/ontimeicon.svg";
|
||||
// @ts-ignore
|
||||
import tt from "../../assets/tticon.svg";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
import { role } from "../../App";
|
||||
|
||||
function CompanyTable({
|
||||
data,
|
||||
isLoading,
|
||||
}: {
|
||||
data?: TCompany[] | undefined;
|
||||
isLoading?: boolean;
|
||||
}) {
|
||||
const moment = require("moment");
|
||||
const [loadings, setLoadings] = useState<boolean[]>([]);
|
||||
function getStatusClassName() {
|
||||
if (role !== "Owner") {
|
||||
return "isnot";
|
||||
} else if (role === "Owner") {
|
||||
return "super";
|
||||
}
|
||||
}
|
||||
|
||||
const getImageSource = (source: string) => {
|
||||
switch (source) {
|
||||
case "Zippy":
|
||||
return zippy;
|
||||
case "EVO":
|
||||
return evo;
|
||||
case "Ontime":
|
||||
return ontime;
|
||||
case "Zeelog":
|
||||
return zeelog;
|
||||
case "TT":
|
||||
return tt;
|
||||
default:
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
dataSource={data?.map((u, i) => ({
|
||||
...u,
|
||||
no: i + 1,
|
||||
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||
"DD.MM.YYYY HH:mm"
|
||||
),
|
||||
key: u?.id,
|
||||
action: { ...u },
|
||||
}))}
|
||||
loading={isLoading}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
dataIndex: "name",
|
||||
width: "25%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (text, record) => (
|
||||
<Tooltip placement="topLeft" title={text}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
{record?.source && (
|
||||
<img
|
||||
src={getImageSource(record?.source)}
|
||||
alt=""
|
||||
style={{ width: 20, height: 20, marginRight: 10 }}
|
||||
/>
|
||||
)}
|
||||
{text}
|
||||
</div>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
dataIndex: "team",
|
||||
render: (status: string, record: TCompany) => (
|
||||
<span className={getStatusClassName()}>{record?.team?.name}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "USDOT",
|
||||
dataIndex: "usdot",
|
||||
},
|
||||
{
|
||||
title: "API KEY",
|
||||
dataIndex: "api_key",
|
||||
render: (status: string, record: TCompany) => (
|
||||
<span className={getStatusClassName()}>{status}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Created at",
|
||||
dataIndex: "created",
|
||||
responsive: ["lg"],
|
||||
},
|
||||
{
|
||||
width: "20%",
|
||||
title: "Actions",
|
||||
dataIndex: "action",
|
||||
render: ({ id, api }: { id: string; api: string }) => {
|
||||
const enterLoading = (index: number, api: string) => {
|
||||
setLoadings((prevLoadings) => {
|
||||
const newLoadings = [...prevLoadings];
|
||||
newLoadings[index] = true;
|
||||
return newLoadings;
|
||||
});
|
||||
if (api && api !== "") {
|
||||
companyController.SyncCompany(index);
|
||||
} else {
|
||||
message.error({
|
||||
content: "This company doesn't have an api key",
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
setLoadings((prevLoadings) => {
|
||||
const newLoadings = [...prevLoadings];
|
||||
newLoadings[index] = false;
|
||||
return newLoadings;
|
||||
});
|
||||
}, 6000);
|
||||
};
|
||||
return (
|
||||
<Space>
|
||||
<Link to={`${id}`}>
|
||||
{role === "Owner" && <Button type="primary">Edit</Button>}
|
||||
{role !== "Owner" && (
|
||||
<Button type="primary" icon={<EyeOutlined />}></Button>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
{role === "Owner" && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SyncOutlined />}
|
||||
loading={loadings[Number(id)]}
|
||||
onClick={() => enterLoading(Number(id), api)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
size="middle"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CompanyTable;
|
@ -0,0 +1,122 @@
|
||||
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||
import { customerController } from "../../API/LayoutApi/customers";
|
||||
import { useState } from "react";
|
||||
import { useCompanyData } from "../../Hooks/Companies";
|
||||
// @ts-ignore
|
||||
import zippy from "../../assets/zippyicon.svg";
|
||||
// @ts-ignore
|
||||
import evo from "../../assets/evoicon.png";
|
||||
// @ts-ignore
|
||||
import zeelog from "../../assets/zeelogicon.svg";
|
||||
// @ts-ignore
|
||||
import ontime from "../../assets/ontimeicon.svg";
|
||||
// @ts-ignore
|
||||
import tt from "../../assets/tticon.svg";
|
||||
|
||||
const AddCustomer = ({
|
||||
open,
|
||||
setOpen,
|
||||
refetch,
|
||||
}: {
|
||||
open: boolean;
|
||||
refetch: any;
|
||||
setOpen(open: boolean): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
const [companyName, setCompanyName] = useState<string>("");
|
||||
const { data } = useCompanyData({ name: companyName });
|
||||
|
||||
const getImageSource = (source: string) => {
|
||||
switch (source) {
|
||||
case "Zippy":
|
||||
return zippy;
|
||||
case "EVO":
|
||||
return evo;
|
||||
case "Ontime":
|
||||
return ontime;
|
||||
case "Zeelog":
|
||||
return zeelog;
|
||||
case "TT":
|
||||
return tt;
|
||||
default:
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Add Driver"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form.validateFields().then(async (values) => {
|
||||
form.resetFields();
|
||||
await customerController
|
||||
.addCustomerController(values)
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
setOpen(!open);
|
||||
}
|
||||
});
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
rules={[{ required: true, message: "Please input Driver name!" }]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Company"
|
||||
name="company_id"
|
||||
rules={[{ required: false, message: "Please input Company name!" }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search Company"
|
||||
onSearch={(value: any) => setCompanyName(value)}
|
||||
options={data?.map((item) => ({
|
||||
label: (
|
||||
<div>
|
||||
{item?.source && (
|
||||
<img
|
||||
style={{ width: 15, height: 20, paddingTop: 7 }}
|
||||
src={getImageSource(item?.source)}
|
||||
alt=""
|
||||
/>
|
||||
)}{" "}
|
||||
{item?.name}
|
||||
</div>
|
||||
),
|
||||
value: item?.id,
|
||||
}))}
|
||||
value={companyName}
|
||||
filterOption={false}
|
||||
autoClearSearchValue={false}
|
||||
allowClear
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCustomer;
|
@ -0,0 +1,61 @@
|
||||
import { useRef, useState } from "react";
|
||||
import AddCustomer from "./AddCustomer";
|
||||
import CustomerTable from "./CustomersTable";
|
||||
import { useCustomerData } from "../../Hooks/Customers";
|
||||
//@ts-ignore
|
||||
import addicon from "../../assets/addiconpng.png";
|
||||
// @ts-ignore
|
||||
import IconSearch from "../../assets/searchIcon.png";
|
||||
|
||||
const Customer = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const { isLoading, data, refetch } = useCustomerData({
|
||||
name: search,
|
||||
is_active: undefined,
|
||||
});
|
||||
|
||||
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 theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
return (
|
||||
<div>
|
||||
{open && <AddCustomer open={open} setOpen={setOpen} refetch={refetch} />}
|
||||
<div className="header d-flex">
|
||||
<p className="title">Drivers</p>
|
||||
<button className="btn-add d-flex" onClick={showModal}>
|
||||
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||
Add Driver
|
||||
</button>
|
||||
</div>
|
||||
<div className="filter d-flex">
|
||||
<div className="search-div">
|
||||
<img src={IconSearch} alt="" />
|
||||
<input
|
||||
className={`search-input-${theme}`}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CustomerTable data={data} isLoading={isLoading} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Customer;
|
@ -0,0 +1,151 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useCustomerOne } from "../../Hooks/Customers";
|
||||
import {
|
||||
Form,
|
||||
Spin,
|
||||
Watermark,
|
||||
Space,
|
||||
Tabs,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
Button,
|
||||
} from "antd";
|
||||
import { customerController } from "../../API/LayoutApi/customers";
|
||||
import Notfound from "../../Utils/Notfound";
|
||||
import { useCompanyOne } from "../../Hooks/Companies";
|
||||
import { role } from "../../App";
|
||||
import { useState } from "react";
|
||||
// @ts-ignore
|
||||
import infoIcon from "../../assets/infoIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIconActive from "../../assets/infoIconActive.png";
|
||||
|
||||
const TabPane = Tabs.TabPane;
|
||||
|
||||
type params = {
|
||||
readonly id: any;
|
||||
};
|
||||
|
||||
const CustomerEdit = () => {
|
||||
const { id } = useParams<params>();
|
||||
const { data, refetch, status } = useCustomerOne(id);
|
||||
const onSubmit = async (value: any) => {
|
||||
await customerController.customerPatch(value, id);
|
||||
refetch();
|
||||
window.location.replace("/#/customers/");
|
||||
};
|
||||
|
||||
const companyData = useCompanyOne(data?.id);
|
||||
|
||||
const ClickDelete = () => {
|
||||
const shouldDelete = window.confirm(
|
||||
"Are you sure, you want to delete this Driver?"
|
||||
);
|
||||
if (shouldDelete && id !== undefined) {
|
||||
customerController.deleteCustomerController(id).then((data: any) => {
|
||||
document.location.replace(`/#/customers`);
|
||||
});
|
||||
}
|
||||
};
|
||||
const [activeTab, setActiveTab] = useState("1");
|
||||
return (
|
||||
<div>
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Watermark style={{ height: "100%" }}>
|
||||
{status === "loading" ? (
|
||||
<Spin size="large" spinning={!data} />
|
||||
) : data ? (
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key)}
|
||||
>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
style={{ marginRight: 10 }}
|
||||
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||
alt=""
|
||||
/>
|
||||
Information
|
||||
</span>
|
||||
}
|
||||
key="1"
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
{companyData?.data && (
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Company"
|
||||
>
|
||||
<Input
|
||||
defaultValue={companyData?.data?.name}
|
||||
readOnly
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Name"
|
||||
name="name"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item>
|
||||
{role === "Owner" && (
|
||||
<Button
|
||||
onClick={() => ClickDelete()}
|
||||
type="primary"
|
||||
style={{ marginRight: 10 }}
|
||||
danger
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Space>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Space>
|
||||
</Spin>
|
||||
) : (
|
||||
<Notfound />
|
||||
)}
|
||||
</Watermark>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerEdit;
|
@ -0,0 +1,69 @@
|
||||
import { Table } from "antd";
|
||||
import { useCompanyData } from "../../Hooks/Companies";
|
||||
import { TCustomer } from "../../types/Customer/TCustomer";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
import { role } from "../../App";
|
||||
|
||||
function CustomerTable({
|
||||
data,
|
||||
isLoading,
|
||||
}: {
|
||||
data?: TCustomer[] | undefined;
|
||||
isLoading?: boolean;
|
||||
}) {
|
||||
const CompanyData = useCompanyData({
|
||||
name: undefined,
|
||||
page: undefined,
|
||||
is_active: undefined,
|
||||
});
|
||||
|
||||
type RowProps = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
const Row = (record: RowProps) => {
|
||||
return {
|
||||
onClick: () => {
|
||||
role !== "Checker" &&
|
||||
document.location.replace(`/#/customers/${record.id}`);
|
||||
},
|
||||
};
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
onRow={(record) => Row(record)}
|
||||
loading={isLoading}
|
||||
dataSource={data?.map((u, i) => ({
|
||||
...u,
|
||||
no: i + 1,
|
||||
company_id:
|
||||
CompanyData?.data?.find(
|
||||
(company: any) => company.id === u?.company_id
|
||||
)?.name || "",
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
dataIndex: "company_id",
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomerTable;
|
@ -0,0 +1,79 @@
|
||||
import { Form as FormAnt, Input, Button } from "antd";
|
||||
import { prof } from "../../API/LayoutApi/profile";
|
||||
|
||||
const ChangePassword = () => {
|
||||
const [form] = FormAnt.useForm();
|
||||
|
||||
const submit = () => {
|
||||
form.validateFields().then(async (values) => {
|
||||
form.resetFields();
|
||||
console.log(values);
|
||||
await prof.changePass(values);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: 500 }}>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Old Password"
|
||||
name="old_password"
|
||||
rules={[{ required: true, message: "Your old password!" }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
name="new_password"
|
||||
label="New Password"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please input your password!",
|
||||
},
|
||||
{
|
||||
min: 8,
|
||||
message: "Password must be at least 8 characters long!",
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
name="password_confirm"
|
||||
label="Confirm Password"
|
||||
dependencies={["new_password"]}
|
||||
hasFeedback
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please confirm your password!",
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || getFieldValue("new_password") === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error("The new password that you entered do not match!")
|
||||
);
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password />
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
<Button onClick={submit} type="primary">
|
||||
save
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangePassword;
|
@ -0,0 +1,271 @@
|
||||
import { useState } from "react";
|
||||
import { TProfilePutParams, prof } from "../../API/LayoutApi/profile";
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
DatePicker,
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
Table,
|
||||
Tabs,
|
||||
Watermark,
|
||||
} from "antd";
|
||||
import TabPane from "antd/es/tabs/TabPane";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
useMyHistoryData,
|
||||
useMystatsData,
|
||||
useProfData,
|
||||
} from "../../Hooks/Profile";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
import { role } from "../../App";
|
||||
import ChangePassword from "./ChangePassword";
|
||||
const { Option } = Select;
|
||||
|
||||
const Profile = () => {
|
||||
const { data, refetch } = useProfData();
|
||||
const [range, setRange] = useState<any>(1);
|
||||
|
||||
const onSubmit = async (value: TProfilePutParams) => {
|
||||
await prof.profPatch(value);
|
||||
refetch();
|
||||
};
|
||||
|
||||
const moment = require("moment-timezone");
|
||||
const nowUtcPlus5 = moment.tz("Asia/Tashkent");
|
||||
const formattedTimeMinusFiveSeconds = nowUtcPlus5
|
||||
.subtract(range, "days")
|
||||
.format("YYYY-MM-DDTHH:mm:ss");
|
||||
|
||||
const historyData = useMyHistoryData({
|
||||
start_date: formattedTimeMinusFiveSeconds,
|
||||
});
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
const currentDate = moment();
|
||||
const start_date = `${currentDate.format("YYYY-MM")}-01 00:00:00`;
|
||||
const [startDate, setStartDate] = useState(start_date);
|
||||
const [endDate, setEndDate] = useState<string | undefined>(undefined);
|
||||
const datePick = (a: any, b: any) => {
|
||||
if (b[0] && b[1]) {
|
||||
setStartDate(`${b[0]} 00:00:00`);
|
||||
setEndDate(`${b[1]} 23:59:59`);
|
||||
}
|
||||
};
|
||||
|
||||
const { data: lineData } = useMystatsData({
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Watermark style={{ height: "100%" }}>
|
||||
<Space direction="vertical" size="middle" style={{ display: "flex" }}>
|
||||
<Tabs>
|
||||
<TabPane tab={<span>Information</span>} key="1">
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
{data !== undefined && (
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
autoComplete="off"
|
||||
onFinish={onSubmit}
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="First name"
|
||||
name="first_name"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Last name"
|
||||
name="last_name"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Username"
|
||||
name="username"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="E-mail"
|
||||
name="email"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)}
|
||||
{data !== undefined && (
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
{data && data.team !== "" && (
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Team"
|
||||
name="team"
|
||||
>
|
||||
<Input readOnly />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
)}
|
||||
<div className="">
|
||||
<h2 style={{ marginBottom: 20 }}>Your Statistics</h2>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<RangePicker onCalendarChange={datePick} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
height: "70vh",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 50,
|
||||
marginTop: 20,
|
||||
marginLeft: 30,
|
||||
width: "80%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<p className="card_stat">
|
||||
Average:{" "}
|
||||
<span>{lineData?.avg_stats_for_period} </span>
|
||||
{role === "Owner" ? "tasks" : "pts"}/day
|
||||
</p>
|
||||
<p className="card_stat">
|
||||
Total: <span>{lineData?.total_for_period} </span>
|
||||
{role === "Owner" ? "tasks" : "pts"}
|
||||
</p>
|
||||
<p className="card_stat">
|
||||
Contribution: <span>{lineData?.contribution}</span>%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
</TabPane>
|
||||
<TabPane tab={<span>History</span>} key="2">
|
||||
<Select
|
||||
style={{ width: "20%", marginBottom: 10 }}
|
||||
placeholder="1 day"
|
||||
onChange={(value: any) =>
|
||||
value ? setRange(value) : setRange("1")
|
||||
}
|
||||
allowClear
|
||||
>
|
||||
<Option value="3">3 days</Option>
|
||||
<Option value="7">a week</Option>
|
||||
<Option value="30">a month</Option>
|
||||
</Select>
|
||||
<Table
|
||||
dataSource={historyData?.data?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
task: { id: u.task },
|
||||
action: u?.action,
|
||||
description:
|
||||
role !== "Owner"
|
||||
? "You finished this task and earned another 5 points!"
|
||||
: `You ${u?.description.slice(
|
||||
u?.description.indexOf(" ") + 1
|
||||
)}`,
|
||||
timestamp: u.timestamp
|
||||
? moment(u.timestamp).format("DD.MM.YYYY, HH:mm")
|
||||
: "",
|
||||
key: u.id,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img alt="" src={tagIcon} />,
|
||||
dataIndex: "no",
|
||||
key: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Task",
|
||||
dataIndex: "task",
|
||||
key: "task",
|
||||
render: ({ id }: { id: number }) => (
|
||||
<Link to={`/${id}`}>{id}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
},
|
||||
{
|
||||
title: "Description",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
},
|
||||
{
|
||||
title: "Timestamp",
|
||||
dataIndex: "timestamp",
|
||||
key: "timestamp",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab={<span>Change Password</span>} key="3">
|
||||
<ChangePassword />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Space>
|
||||
</Watermark>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
@ -0,0 +1,79 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useRequestsData } from "../../Hooks/Requests";
|
||||
import RequestsTable from "./RequestsTable";
|
||||
// @ts-ignore
|
||||
import IconSearch from "../../assets/searchIcon.png";
|
||||
import { Radio, RadioChangeEvent } from "antd";
|
||||
import RequestsEdit from "./RequestsEdit";
|
||||
import { TRequests } from "../../types/Requests/TRequests";
|
||||
|
||||
const Requests = () => {
|
||||
const [search, setSearch] = useState("");
|
||||
const [status, setStatus] = useState("Pending");
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [requestData, setRequestData] = useState<TRequests>();
|
||||
|
||||
const { data, refetch, isLoading } = useRequestsData({
|
||||
search: search,
|
||||
status: status,
|
||||
});
|
||||
|
||||
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 theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
return (
|
||||
<div>
|
||||
{modalOpen && (
|
||||
<RequestsEdit
|
||||
modalOpen={modalOpen}
|
||||
setModalOpen={setModalOpen}
|
||||
requestData={requestData}
|
||||
/>
|
||||
)}
|
||||
<div className="header d-flex">
|
||||
<p className="title">Requests</p>
|
||||
</div>
|
||||
<div className="filter d-flex">
|
||||
<div className="search-div">
|
||||
<img src={IconSearch} alt="" />
|
||||
<input
|
||||
className={`search-input-${theme}`}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
<Radio.Group
|
||||
onChange={(e: RadioChangeEvent) => setStatus(e.target.value)}
|
||||
size="middle"
|
||||
value={status}
|
||||
style={{ marginLeft: 20 }}
|
||||
>
|
||||
<Radio.Button value={"Pending"}>Pending</Radio.Button>
|
||||
<Radio.Button value={"Assigned"}>Assigned</Radio.Button>
|
||||
<Radio.Button value={"Rejected"}>Rejected</Radio.Button>
|
||||
<Radio.Button value={undefined}>All</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<RequestsTable
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
refetch={refetch}
|
||||
setOpenModal={setModalOpen}
|
||||
setRequestData={setRequestData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Requests;
|
@ -0,0 +1,172 @@
|
||||
import { Modal, Select, Button } from "antd";
|
||||
import { TRequests } from "../../types/Requests/TRequests";
|
||||
import { useState } from "react";
|
||||
import { useCustomerData } from "../../Hooks/Customers";
|
||||
import { requestsController } from "../../API/LayoutApi/requests";
|
||||
// @ts-ignore
|
||||
import plus from "../../assets/add-icon.png";
|
||||
const RequestsEdit = ({
|
||||
modalOpen,
|
||||
setModalOpen,
|
||||
requestData,
|
||||
}: {
|
||||
modalOpen: any;
|
||||
setModalOpen: any;
|
||||
requestData: TRequests | undefined;
|
||||
}) => {
|
||||
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
const handleCancel = () => {
|
||||
setModalOpen(!modalOpen);
|
||||
};
|
||||
|
||||
const [customerName, setCustomerName] = useState<string>();
|
||||
const [driverId, setDriverId] = useState<number>();
|
||||
const customerData = useCustomerData({
|
||||
name: customerName,
|
||||
});
|
||||
|
||||
const assignClick = () => {
|
||||
const value = {
|
||||
...requestData,
|
||||
status: "Assigned",
|
||||
customer_id: driverId,
|
||||
};
|
||||
requestsController.requestPatch(value, requestData?.id);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
onCancel={handleCancel}
|
||||
footer={null}
|
||||
open={modalOpen}
|
||||
width={800}
|
||||
maskClosable={true}
|
||||
>
|
||||
<div className="info-div">
|
||||
<div
|
||||
className="d-flex"
|
||||
style={{ alignItems: "center", marginBottom: 16 }}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
lineHeight: "24px",
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
Requested Driver Info
|
||||
</p>
|
||||
<button
|
||||
style={{ marginLeft: 12, display: "flex", alignItems: "center" }}
|
||||
className={`status-${requestData?.status}`}
|
||||
>
|
||||
{requestData?.status}
|
||||
</button>
|
||||
</div>
|
||||
<div className="info-body">
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Full Name</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{requestData?.full_name}
|
||||
</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Company</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{requestData?.company_name}
|
||||
</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>USDOT</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{requestData?.company_usdot}
|
||||
</p>
|
||||
</tr>
|
||||
</div>
|
||||
|
||||
{requestData?.status === "Assigned" && (
|
||||
<div className="info-div" style={{ margin: 0 }}>
|
||||
<div
|
||||
className="d-flex"
|
||||
style={{ alignItems: "center", marginBottom: 16 }}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
lineHeight: "24px",
|
||||
marginTop: 20,
|
||||
letterSpacing: "-0.02em",
|
||||
}}
|
||||
>
|
||||
Assigned Driver Info
|
||||
</p>
|
||||
</div>
|
||||
<div className="info-body">
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Driver name</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{requestData?.potential_driver?.name}
|
||||
</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Company</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{requestData?.potential_driver?.company?.name}
|
||||
</p>
|
||||
</tr>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
)}
|
||||
{requestData?.status === "Pending" && (
|
||||
<div className="search-driver">
|
||||
<div className="d-flex">
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: 325 }}
|
||||
placeholder="Search Driver"
|
||||
onSearch={(value: any) => setCustomerName(value)}
|
||||
onChange={(e: any) => setDriverId(e)}
|
||||
options={customerData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}))}
|
||||
value={customerName}
|
||||
filterOption={false}
|
||||
autoClearSearchValue={false}
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={assignClick}
|
||||
disabled={driverId ? false : true}
|
||||
style={{ marginLeft: 15 }}
|
||||
>
|
||||
Assign
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={assignClick}
|
||||
disabled={driverId ? true : false}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "6px 15px",
|
||||
margin: 0,
|
||||
}}
|
||||
className="btn-add"
|
||||
>
|
||||
<img src={plus} alt="" style={{ marginRight: 8 }} />
|
||||
Add new driver
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestsEdit;
|
@ -0,0 +1,143 @@
|
||||
import { Button, Space, Table } from "antd";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
import moment from "moment";
|
||||
import { TRequests } from "../../types/Requests/TRequests";
|
||||
import { useEffect, useState } from "react";
|
||||
import { requestsController } from "../../API/LayoutApi/requests";
|
||||
|
||||
const RequestsTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
refetch,
|
||||
setOpenModal,
|
||||
setRequestData,
|
||||
}: {
|
||||
setOpenModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setRequestData: React.Dispatch<React.SetStateAction<TRequests | undefined>>;
|
||||
data: TRequests[] | undefined;
|
||||
isLoading: boolean;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TRequests[], unknown>>;
|
||||
}) => {
|
||||
const [isTextSelected, setIsTextSelected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleSelectionChange = () => {
|
||||
const selection = window.getSelection();
|
||||
setIsTextSelected(selection !== null && selection.toString() !== "");
|
||||
};
|
||||
|
||||
document.addEventListener("selectionchange", handleSelectionChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("selectionchange", handleSelectionChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const Row = (record: TRequests, event: any) => {
|
||||
if (isTextSelected) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
event.target.classList.contains("ant-table-cell") &&
|
||||
record.status !== "Rejected"
|
||||
) {
|
||||
setRequestData(record);
|
||||
setOpenModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
const patchRequest = (record: TRequests) => {
|
||||
requestsController.delete(record?.id).then(() => {
|
||||
refetch();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
onRow={(record) => ({
|
||||
onClick: (event) => Row(record, event),
|
||||
})}
|
||||
dataSource={data?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
action: { id: u.id },
|
||||
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||
"DD.MM.YYYY HH:mm"
|
||||
),
|
||||
...u,
|
||||
}))}
|
||||
loading={isLoading}
|
||||
size="middle"
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Full name",
|
||||
dataIndex: "full_name",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
dataIndex: "company_name",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "USDOT",
|
||||
dataIndex: "company_usdot",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Created at",
|
||||
dataIndex: "created",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
dataIndex: "action",
|
||||
render: (text: string, record: TRequests) => {
|
||||
return (
|
||||
<div>
|
||||
{record?.status === "Pending" && (
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => patchRequest(record)}
|
||||
danger
|
||||
>
|
||||
Reject
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestsTable;
|
@ -0,0 +1,71 @@
|
||||
import { Input, Modal, Form as FormAnt } from "antd";
|
||||
import { serviceController } from "../../API/LayoutApi/services";
|
||||
|
||||
const AddService = ({
|
||||
open,
|
||||
setOpen,
|
||||
refetch,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
refetch(): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Add service"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(async (values) => {
|
||||
form.resetFields();
|
||||
await serviceController.addServiceController(values);
|
||||
setOpen(!open);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Title"
|
||||
name="title"
|
||||
rules={[
|
||||
{ required: true, message: "Please input service title!" },
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Points"
|
||||
name="points"
|
||||
rules={[
|
||||
{ required: true, message: "Please input service points!" },
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddService;
|
@ -0,0 +1,146 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useServiceOne } from "../../Hooks/Services";
|
||||
import {
|
||||
Form,
|
||||
Spin,
|
||||
Watermark,
|
||||
Space,
|
||||
Tabs,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
Button,
|
||||
} from "antd";
|
||||
import { serviceController } from "../../API/LayoutApi/services";
|
||||
import { FormOutlined } from "@ant-design/icons";
|
||||
import Notfound from "../../Utils/Notfound";
|
||||
import { role } from "../../App";
|
||||
// @ts-ignore
|
||||
import infoIcon from "../../assets/infoIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIconActive from "../../assets/infoIconActive.png";
|
||||
import { useState } from "react";
|
||||
|
||||
const TabPane = Tabs.TabPane;
|
||||
type params = {
|
||||
readonly id: any;
|
||||
};
|
||||
type MyObjectType = {
|
||||
[key: string | number]: any;
|
||||
};
|
||||
const ServiceEdit = () => {
|
||||
const { id } = useParams<params>();
|
||||
const { data, refetch, status }: MyObjectType = useServiceOne(id);
|
||||
|
||||
const onSubmit = async (value: any) => {
|
||||
await serviceController.servicePatch(value, id);
|
||||
refetch();
|
||||
document.location.replace("/#/services");
|
||||
};
|
||||
|
||||
const ClickDelete = () => {
|
||||
const shouldDelete = window.confirm(
|
||||
"Вы уверены, что хотите удалить эту сервис?"
|
||||
);
|
||||
if (shouldDelete && id !== undefined) {
|
||||
serviceController.deleteServiceController(id).then((data: any) => {
|
||||
document.location.replace(`/#/services`);
|
||||
});
|
||||
}
|
||||
};
|
||||
const [activeTab, setActiveTab] = useState("1");
|
||||
return (
|
||||
<div>
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Watermark style={{ height: "100%" }}>
|
||||
{status === "loading" ? (
|
||||
<Spin size="large" spinning={!data} />
|
||||
) : data ? (
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key)}
|
||||
>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
style={{ marginRight: 10 }}
|
||||
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||
alt=""
|
||||
/>
|
||||
Information
|
||||
</span>
|
||||
}
|
||||
key="1"
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="title"
|
||||
name="title"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="points"
|
||||
name="points"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item>
|
||||
{role !== "Checker" && (
|
||||
<Button
|
||||
onClick={() => ClickDelete()}
|
||||
type="primary"
|
||||
style={{ marginRight: 10 }}
|
||||
danger
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Space>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Space>
|
||||
</Spin>
|
||||
) : (
|
||||
<Notfound />
|
||||
)}
|
||||
</Watermark>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceEdit;
|
@ -0,0 +1,80 @@
|
||||
import React from "react";
|
||||
import { Table } from "antd";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { TService } from "../../types/Service/TService";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
import { role } from "../../App";
|
||||
type numStr = string | number;
|
||||
|
||||
interface serviceSource {
|
||||
no: numStr;
|
||||
title: numStr;
|
||||
points: numStr;
|
||||
id: numStr;
|
||||
action: { id: numStr };
|
||||
key: React.Key;
|
||||
}
|
||||
const ServiceTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
refetch,
|
||||
}: {
|
||||
data: TService[] | undefined;
|
||||
isLoading: boolean | undefined;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TService[], unknown>>;
|
||||
}) => {
|
||||
const columns: object[] = [
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Title",
|
||||
dataIndex: "title",
|
||||
},
|
||||
{
|
||||
title: "Points",
|
||||
dataIndex: "points",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
loading={isLoading}
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: () => {
|
||||
role !== "Checker" &&
|
||||
document.location.replace(`/#/services/${record.id}`);
|
||||
},
|
||||
};
|
||||
}}
|
||||
dataSource={data?.map((u: any, i: number): serviceSource => {
|
||||
const obj: serviceSource = {
|
||||
no: i + 1,
|
||||
title: u?.title,
|
||||
points: u?.points,
|
||||
id: u?.id,
|
||||
action: { id: u.id },
|
||||
key: u.id,
|
||||
};
|
||||
return obj;
|
||||
})}
|
||||
columns={columns}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceTable;
|
@ -0,0 +1,32 @@
|
||||
import { useState } from "react";
|
||||
import { useServiceData } from "../../Hooks/Services";
|
||||
import AddService from "./AddService";
|
||||
import ServiceTable from "./ServiceTable";
|
||||
//@ts-ignore
|
||||
import addicon from "../../assets/addiconpng.png";
|
||||
import { role } from "../../App";
|
||||
|
||||
const Service = () => {
|
||||
const { data, isLoading, refetch } = useServiceData();
|
||||
const [open, setOpen] = useState(false);
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{open && <AddService refetch={refetch} open={open} setOpen={setOpen} />}
|
||||
<div className="header d-flex">
|
||||
<p className="title">Services</p>
|
||||
{role !== "Checker" && (
|
||||
<button onClick={showModal} className="btn-add d-flex">
|
||||
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||
Add
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<ServiceTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Service;
|
@ -0,0 +1,178 @@
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} 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 StatTable from "./StatisticTable";
|
||||
import StatTeamTable from "./StatisticTeamTable";
|
||||
import dayjs from "dayjs";
|
||||
import { Button, DatePicker, DatePickerProps, Select, Tabs } from "antd";
|
||||
import TabPane from "antd/es/tabs/TabPane";
|
||||
// @ts-ignore
|
||||
import IconSearch from "../../assets/searchIcon.png";
|
||||
|
||||
const Stat = () => {
|
||||
const now = dayjs();
|
||||
const { RangePicker } = DatePicker;
|
||||
const moment = require("moment");
|
||||
const currentDate = moment();
|
||||
const nextMonth = currentDate.clone().add(1, "months");
|
||||
const start_date = `${currentDate.format("YYYY-MM")}-01 00:00:00`;
|
||||
const end_date = `${nextMonth.format("YYYY-MM")}-01 00:00:00`;
|
||||
|
||||
const [search, setSearch] = useState<string>("");
|
||||
const [team, setTeam] = useState<any>("");
|
||||
const [startDate, setStartDate] = useState(start_date);
|
||||
const [endDate, setEndDate] = useState(end_date);
|
||||
|
||||
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 handleSave = (a: string) => {
|
||||
const trimmedStartDate = startDate.slice(0, 10);
|
||||
const trimmedEndDate = endDate.slice(0, 10);
|
||||
const fileName = `${trimmedStartDate}-${trimmedEndDate}`;
|
||||
if (a === "team") {
|
||||
const teamName = `${team}_${fileName}`;
|
||||
statController.saveUsersStats(teamName, startDate, endDate, team);
|
||||
} else {
|
||||
statController.saveTeamStats(fileName, startDate, endDate);
|
||||
}
|
||||
};
|
||||
|
||||
const datePick = (a: any, b: any) => {
|
||||
if (b[0] && b[1]) {
|
||||
setStartDate(`${b[0]} 00:00:00`);
|
||||
setEndDate(`${b[1]} 23:59:59`);
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeDate: DatePickerProps["onChange"] = (date) => {
|
||||
if (!date) {
|
||||
setStartDate("");
|
||||
setEndDate("");
|
||||
} else {
|
||||
const firstDate = date;
|
||||
const secondDate = date?.add(1, "month");
|
||||
const yearStart = Number(firstDate?.year());
|
||||
const monthStart = Number(firstDate?.month()) + 1;
|
||||
const yearEnd = Number(secondDate?.year());
|
||||
const monthEnd = Number(secondDate?.month()) + 1;
|
||||
|
||||
setStartDate(`${yearStart}-${monthStart}-01 00:00:00`);
|
||||
setEndDate(`${yearEnd}-${monthEnd}-01 00:00:00`);
|
||||
}
|
||||
};
|
||||
|
||||
const { data, refetch, isLoading } = useStatsData({
|
||||
search: search,
|
||||
team: team,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
});
|
||||
interface DataType {
|
||||
data?: TStatTeam[];
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TStatTeam[], unknown>>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
const TeamData: DataType = useStatTeamData({
|
||||
search: "",
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
});
|
||||
|
||||
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 theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
return (
|
||||
<div>
|
||||
<div className="header d-flex" style={{ marginBottom: 16 }}>
|
||||
<p className="title">Statistics</p>
|
||||
<div className="">
|
||||
<DatePicker
|
||||
onChange={onChangeDate}
|
||||
picker="month"
|
||||
format={"MMMM"}
|
||||
defaultValue={now}
|
||||
style={{ marginRight: 10, width: 120 }}
|
||||
/>
|
||||
<RangePicker style={{ width: 260 }} onCalendarChange={datePick} />
|
||||
</div>
|
||||
</div>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane tab={<span>Users</span>} key="1">
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
<div className="search-div" style={{ marginRight: 12 }}>
|
||||
<img src={IconSearch} alt="" />
|
||||
<input
|
||||
className={`search-input-${theme}`}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
style={{ width: 260 }}
|
||||
placeholder="team"
|
||||
onChange={(value: any) => setTeam(value)}
|
||||
options={teamOptions}
|
||||
/>
|
||||
</span>
|
||||
<StatTable
|
||||
data={{ data: data }}
|
||||
isLoading={isLoading}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<Button type="primary" onClick={(e) => handleSave("team")}>
|
||||
Save as file
|
||||
</Button>
|
||||
</TabPane>
|
||||
<TabPane tab={<span>Teams</span>} key="2">
|
||||
<StatTeamTable
|
||||
data={TeamData?.data}
|
||||
isLoading={TeamData?.isLoading}
|
||||
refetch={TeamData?.refetch}
|
||||
/>
|
||||
<Button type="primary" onClick={(e) => handleSave("")}>
|
||||
Save as file
|
||||
</Button>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Stat;
|
@ -0,0 +1,64 @@
|
||||
import { Table } from "antd";
|
||||
import { TStat } from "../../types/Statistic/TStat";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
const StatTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
refetch,
|
||||
}: {
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TStat[], unknown>>;
|
||||
data: { data: TStat[] | undefined };
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
size="small"
|
||||
loading={isLoading}
|
||||
dataSource={data?.data?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
...u,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
key: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Support specialist",
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
dataIndex: "team_name",
|
||||
key: "team_name ",
|
||||
},
|
||||
{
|
||||
title: "Points",
|
||||
dataIndex: "total_points",
|
||||
key: "total_points",
|
||||
},
|
||||
]}
|
||||
pagination={{
|
||||
pageSize: 14,
|
||||
}}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatTable;
|
@ -0,0 +1,77 @@
|
||||
import { Table, Tag } from "antd";
|
||||
import { TStatTeam } from "../../types/Statistic/TStat";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
const StatTeamTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
refetch,
|
||||
}: {
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TStatTeam[], unknown>>;
|
||||
data: TStatTeam[] | undefined;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div style={{ maxHeight: "400px", overflow: "auto" }}>
|
||||
<Table
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
dataSource={data?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
...u,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
dataIndex: "name",
|
||||
},
|
||||
{
|
||||
title: "Total points",
|
||||
dataIndex: "total_points",
|
||||
},
|
||||
{
|
||||
title: "Is Active",
|
||||
dataIndex: "is_active",
|
||||
render: (tag: boolean) => (
|
||||
<Tag color={tag ? "geekblue" : "red"}>
|
||||
{tag ? "True" : "False"}
|
||||
</Tag>
|
||||
),
|
||||
filters: [
|
||||
{
|
||||
text: "True",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
text: "False",
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
onFilter: (value: string | number | boolean, record: TStatTeam) => {
|
||||
return record.is_active === value;
|
||||
},
|
||||
},
|
||||
]}
|
||||
pagination={{
|
||||
pageSize: 5,
|
||||
}}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatTeamTable;
|
@ -0,0 +1,327 @@
|
||||
import {
|
||||
Input,
|
||||
Modal,
|
||||
Form as FormAnt,
|
||||
Select,
|
||||
Upload,
|
||||
Switch,
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
} from "antd";
|
||||
import { taskController } from "../../API/LayoutApi/tasks";
|
||||
import { useState } from "react";
|
||||
import { useServiceData } from "../../Hooks/Services";
|
||||
import { UploadOutlined } from "@ant-design/icons";
|
||||
import { useTeamData } from "../../Hooks/Teams";
|
||||
import { useCompanyData } from "../../Hooks/Companies";
|
||||
import { useCustomerByComanyData } from "../../Hooks/Customers";
|
||||
// @ts-ignore
|
||||
import zippy from "../../assets/zippyicon.svg";
|
||||
// @ts-ignore
|
||||
import evo from "../../assets/evoicon.png";
|
||||
// @ts-ignore
|
||||
import zeelog from "../../assets/zeelogicon.svg";
|
||||
// @ts-ignore
|
||||
import ontime from "../../assets/ontimeicon.svg";
|
||||
// @ts-ignore
|
||||
import tt from "../../assets/tticon.svg";
|
||||
import AddCustomer from "../Customers/AddCustomer";
|
||||
import AddDriver from "../Companies/AddDriver";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
|
||||
const { Option } = Select;
|
||||
const AddTask = ({
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
const [fileIds, setFileIds] = useState([]);
|
||||
const [companyName, setCompanyName] = useState<string>();
|
||||
const [customerName, setCustomerName] = useState<string>();
|
||||
const [companyId, setCompanyId] = useState<string>();
|
||||
const ServiceData = useServiceData();
|
||||
|
||||
const TeamData = useTeamData("");
|
||||
const companyData = useCompanyData({ name: companyName });
|
||||
const customerData = useCustomerByComanyData({
|
||||
id: companyId,
|
||||
name: customerName,
|
||||
});
|
||||
|
||||
const [driverOpen, setDriverOpen] = useState(false);
|
||||
|
||||
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||
function handlePaste(event: any) {
|
||||
const clipboardData = event.clipboardData || window.Clipboard;
|
||||
if (clipboardData && clipboardData.items.length > 0) {
|
||||
const clipboardItem = clipboardData.items[0];
|
||||
if (clipboardItem.kind === "file") {
|
||||
const file = clipboardItem.getAsFile();
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (e.target && e.target.result) {
|
||||
setPreviewImage(e.target.result as string);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
taskController
|
||||
.addTaskFile(formData)
|
||||
.then((response) => {
|
||||
const fileId = response.id;
|
||||
setFileIds((prevFileIds): any => [...prevFileIds, fileId]);
|
||||
const updatedValues = form.getFieldsValue();
|
||||
updatedValues.attachment_ids = [
|
||||
...updatedValues.attachment_ids,
|
||||
fileId,
|
||||
];
|
||||
form.setFieldsValue(updatedValues);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getImageSource = (source: string) => {
|
||||
switch (source) {
|
||||
case "Zippy":
|
||||
return zippy;
|
||||
case "EVO":
|
||||
return evo;
|
||||
case "Ontime":
|
||||
return ontime;
|
||||
case "Zeelog":
|
||||
return zeelog;
|
||||
case "TT":
|
||||
return tt;
|
||||
default:
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
const [openDrive, setOpenDrive] = useState(false);
|
||||
|
||||
return (
|
||||
<div onPaste={(event) => handlePaste(event)}>
|
||||
{openDrive && (
|
||||
<AddCustomer
|
||||
refetch={customerData.refetch}
|
||||
open={openDrive}
|
||||
setOpen={setOpenDrive}
|
||||
/>
|
||||
)}
|
||||
<AddDriver id={companyId} open={driverOpen} setOpen={setDriverOpen} />
|
||||
<Modal
|
||||
open={open}
|
||||
width={600}
|
||||
title="Add task"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form.validateFields().then(async (values) => {
|
||||
const updatedValues = { ...values };
|
||||
updatedValues.attachment_ids = fileIds;
|
||||
form.resetFields();
|
||||
await taskController.addTaskController(updatedValues);
|
||||
setOpen(!open);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Company"
|
||||
name="company_id"
|
||||
rules={[{ required: false, message: "Please input company!" }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search Company"
|
||||
onSearch={(value: any) => setCompanyName(value)}
|
||||
options={companyData?.data?.map((item) => ({
|
||||
label: (
|
||||
<div>
|
||||
{item?.source && (
|
||||
<img
|
||||
style={{ width: 15, height: 20, paddingTop: 7 }}
|
||||
src={getImageSource(item?.source)}
|
||||
alt=""
|
||||
/>
|
||||
)}{" "}
|
||||
{item?.name}
|
||||
</div>
|
||||
),
|
||||
value: item?.id,
|
||||
}))}
|
||||
value={companyName}
|
||||
filterOption={false}
|
||||
autoClearSearchValue={false}
|
||||
allowClear
|
||||
onChange={(value: any) => setCompanyId(value)}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Driver"
|
||||
name="customer_id"
|
||||
style={{ width: "85%" }}
|
||||
rules={[
|
||||
{ required: false, message: "Please input service points!" },
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search Driver"
|
||||
onSearch={(value: any) => setCustomerName(value)}
|
||||
options={customerData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}))}
|
||||
value={customerName}
|
||||
filterOption={false}
|
||||
autoClearSearchValue={false}
|
||||
allowClear
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
<Button onClick={(e) => setDriverOpen(true)} type="primary">
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<FormAnt.Item
|
||||
label="Service"
|
||||
name="service_id"
|
||||
rules={[{ required: true, message: "Please select service!" }]}
|
||||
>
|
||||
<Select
|
||||
options={ServiceData?.data?.map((item) => ({
|
||||
label: item?.title,
|
||||
value: item?.id,
|
||||
}))}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
|
||||
<FormAnt.Item
|
||||
label="Assigned to"
|
||||
name="assigned_to_id"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please select one of the teams!",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
options={TeamData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}))}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
|
||||
<FormAnt.Item
|
||||
label="Status"
|
||||
name="status"
|
||||
rules={[
|
||||
{ required: false, message: "Please input service points!" },
|
||||
]}
|
||||
>
|
||||
<Select defaultValue="New">
|
||||
<Option value="New">New</Option>
|
||||
<Option value="Checking">Checking</Option>
|
||||
<Option value="Done">Done</Option>
|
||||
</Select>
|
||||
</FormAnt.Item>
|
||||
|
||||
<FormAnt.Item
|
||||
label="PTI"
|
||||
name="pti"
|
||||
rules={[
|
||||
{ required: false, message: "Please input service points!" },
|
||||
]}
|
||||
>
|
||||
<Switch />
|
||||
</FormAnt.Item>
|
||||
|
||||
<FormAnt.Item
|
||||
label="Note"
|
||||
name="note"
|
||||
rules={[
|
||||
{ required: false, message: "Please input service points!" },
|
||||
]}
|
||||
>
|
||||
<TextArea
|
||||
style={{ padding: "7px 11px" }}
|
||||
placeholder="note"
|
||||
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
<FormAnt>
|
||||
<FormAnt.Item name="attachment">
|
||||
<div>
|
||||
<Upload.Dragger
|
||||
name="file"
|
||||
multiple={true}
|
||||
customRequest={({ file, onSuccess }: any) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
taskController
|
||||
.addTaskFile(formData)
|
||||
.then((response) => {
|
||||
const fileId = response.id;
|
||||
setFileIds((prevFileIds): any => [
|
||||
...prevFileIds,
|
||||
fileId,
|
||||
]);
|
||||
onSuccess();
|
||||
const updatedValues = form.getFieldsValue();
|
||||
updatedValues.attachment_ids = [
|
||||
...updatedValues.attachment_ids,
|
||||
fileId,
|
||||
];
|
||||
form.setFieldsValue(updatedValues);
|
||||
})
|
||||
.catch((error) => {
|
||||
onSuccess(error);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<UploadOutlined style={{ color: "rgba(249, 158, 44, 1)" }} />
|
||||
</p>
|
||||
<p
|
||||
className="ant-upload-text"
|
||||
style={{ color: "rgba(249, 158, 44, 1)" }}
|
||||
>
|
||||
Click or drag a file here to upload
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
</div>
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTask;
|
@ -0,0 +1,556 @@
|
||||
import { TTask } from "../../types/Tasks/TTasks";
|
||||
import {
|
||||
Dropdown,
|
||||
MenuProps,
|
||||
Modal,
|
||||
Table,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
message,
|
||||
} from "antd";
|
||||
import TabPane from "antd/es/tabs/TabPane";
|
||||
import { timeZone } from "../../App";
|
||||
import { useTaskHistory } from "../../Hooks/Tasks";
|
||||
// @ts-ignore
|
||||
import closeIcon from "../../assets/closeIcon.png";
|
||||
// @ts-ignore
|
||||
import editIcon from "../../assets/editIcon.png";
|
||||
// @ts-ignore
|
||||
import historyIcon from "../../assets/hisoryIcon.png";
|
||||
// @ts-ignore
|
||||
import attachmentIcon from "../../assets/attachmentIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIcon from "../../assets/infoIcon.png";
|
||||
// @ts-ignore
|
||||
import uploadIcon from "../../assets/uploadIcon.png";
|
||||
// @ts-ignore
|
||||
import pdficon from "../../assets/pdficon.png";
|
||||
// @ts-ignore
|
||||
import letssee from "../../assets/letssee.png";
|
||||
// @ts-ignore
|
||||
import svgicon from "../../assets/svgicon.png";
|
||||
// @ts-ignore
|
||||
import pngicon from "../../assets/pngicon.png";
|
||||
// @ts-ignore
|
||||
import jpgicon from "../../assets/jpgicon.png";
|
||||
// @ts-ignore
|
||||
import jpegicon from "../../assets/jpegicon.png";
|
||||
// @ts-ignore
|
||||
import xlsicon from "../../assets/xlsicon.png";
|
||||
// @ts-ignore
|
||||
import docicon from "../../assets/docicon.png";
|
||||
// @ts-ignore
|
||||
import forwardIcon from "../../assets/forward.png";
|
||||
// @ts-ignore
|
||||
import driverIcon from "../../assets/drivericon.png";
|
||||
// @ts-ignore
|
||||
import userIcon from "../../assets/userIcon.png";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { useState } from "react";
|
||||
import { taskController } from "../../API/LayoutApi/tasks";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { TPagination } from "../../types/common/TPagination";
|
||||
import { useTeamData } from "../../Hooks/Teams";
|
||||
import { TTeam } from "../../types/Team/TTeam";
|
||||
import { EditOutlined, UserOutlined } from "@ant-design/icons";
|
||||
|
||||
const TaskModal = ({
|
||||
modalOpen,
|
||||
setModalOpen,
|
||||
recordTask,
|
||||
uploadOpen,
|
||||
setUploadOpen,
|
||||
refetch,
|
||||
}: {
|
||||
recordTask: TTask | undefined;
|
||||
modalOpen: any;
|
||||
setModalOpen: any;
|
||||
uploadOpen: any;
|
||||
setUploadOpen: any;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TPagination<TTask[]>, unknown>>;
|
||||
}) => {
|
||||
const moment = require("moment-timezone");
|
||||
const handleCancel = () => {
|
||||
setModalOpen(!modalOpen);
|
||||
};
|
||||
const showUploadModal = () => {
|
||||
setUploadOpen(!uploadOpen);
|
||||
};
|
||||
function getFileType(fileName: any) {
|
||||
var fileExtension = fileName.split(".").pop()?.toLowerCase();
|
||||
|
||||
switch (fileExtension) {
|
||||
case "jpg":
|
||||
return <img style={{ marginRight: 12 }} src={jpgicon} alt="" />;
|
||||
case "jpeg":
|
||||
return <img style={{ marginRight: 12 }} src={jpegicon} alt="" />;
|
||||
case "png":
|
||||
return <img style={{ marginRight: 12 }} src={pngicon} alt="" />;
|
||||
case "pdf":
|
||||
return <img style={{ marginRight: 12 }} src={pdficon} alt="" />;
|
||||
case "svg":
|
||||
return <img style={{ marginRight: 12 }} src={svgicon} alt="" />;
|
||||
case "xls":
|
||||
return <img style={{ marginRight: 12 }} src={xlsicon} alt="" />;
|
||||
default:
|
||||
return <img style={{ marginRight: 12 }} src={docicon} alt="" />;
|
||||
}
|
||||
}
|
||||
const [text, setText] = useState<string | undefined>(recordTask?.note);
|
||||
const [pti, setPti] = useState<boolean | undefined>(recordTask?.pti);
|
||||
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
|
||||
const { data, isLoading } = useTaskHistory(recordTask?.id);
|
||||
const patchTask = () => {
|
||||
taskController
|
||||
.taskPatch({ note: text, pti: pti }, recordTask?.id)
|
||||
.then(() => {
|
||||
message.success({ content: "Saved!" });
|
||||
});
|
||||
};
|
||||
|
||||
const [status, setStatus] = useState(recordTask?.status);
|
||||
|
||||
const statuspatch = (status: string) => {
|
||||
setStatus(status);
|
||||
taskController.taskPatch({ status: status }, recordTask?.id).then(() => {
|
||||
message.success({ content: "Success", duration: 1 });
|
||||
});
|
||||
};
|
||||
const [teamName, setTeamName] = useState(recordTask?.assigned_to?.name);
|
||||
const teampatch = (item: TTeam) => {
|
||||
setTeamName(item?.name);
|
||||
taskController
|
||||
.taskPatch({ assigned_to_id: item?.id }, recordTask?.id)
|
||||
.then(() => {
|
||||
message.success({ content: "Success", duration: 1 });
|
||||
});
|
||||
};
|
||||
const teamData = useTeamData("");
|
||||
const teams: MenuProps["items"] = teamData?.data?.map((item) => ({
|
||||
key: item?.id,
|
||||
label: <p>{item?.name}</p>,
|
||||
onClick: () => teampatch(item),
|
||||
}));
|
||||
const items: MenuProps["items"] = [
|
||||
{
|
||||
key: "1",
|
||||
label: <p>New</p>,
|
||||
onClick: () => statuspatch("New"),
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: <p>Checking</p>,
|
||||
onClick: () => statuspatch("Checking"),
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
label: <p>Done</p>,
|
||||
onClick: () => statuspatch("Done"),
|
||||
},
|
||||
];
|
||||
|
||||
const getImageSource = (source: string) => {
|
||||
switch (source) {
|
||||
case "driver":
|
||||
return driverIcon;
|
||||
case "user":
|
||||
return userIcon;
|
||||
default:
|
||||
return userIcon;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onCancel={handleCancel}
|
||||
footer={null}
|
||||
open={modalOpen}
|
||||
width={800}
|
||||
maskClosable={true}
|
||||
// style={{ position: "fixed", right: 0, top: 0, bottom: 0, height: 1000 }}
|
||||
>
|
||||
<div className={!theme ? "TaskModal-header" : "TaskModal-header-dark"}>
|
||||
<div className={!theme ? "TaskModal-title" : "TaskModal-title-dark"}>
|
||||
<p className={!theme ? "p-driver" : "p-driver-dark"}>
|
||||
{recordTask?.customer?.name}
|
||||
</p>
|
||||
<Dropdown
|
||||
menu={{ items }}
|
||||
placement="bottom"
|
||||
arrow={{ pointAtCenter: true }}
|
||||
>
|
||||
<button
|
||||
style={{ marginLeft: 12, display: "flex", alignItems: "center" }}
|
||||
className={`status-${status}`}
|
||||
>
|
||||
{status}
|
||||
<EditOutlined style={{ marginLeft: 4 }} />
|
||||
</button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className="mdoal-actions">
|
||||
<Dropdown
|
||||
disabled={recordTask?.status !== "New"}
|
||||
menu={{ items: teams }}
|
||||
placement="bottom"
|
||||
arrow={{ pointAtCenter: true }}
|
||||
>
|
||||
<button
|
||||
disabled={recordTask?.status !== "New"}
|
||||
style={{ marginRight: 12 }}
|
||||
className={`btn-modal-action-${theme && "dark"}`}
|
||||
>
|
||||
<img src={forwardIcon} alt="" />
|
||||
Forward
|
||||
</button>
|
||||
</Dropdown>
|
||||
<button
|
||||
style={{ marginLeft: 12 }}
|
||||
className={`btn-modal-action-${theme && "dark"}`}
|
||||
onClick={showUploadModal}
|
||||
>
|
||||
<img src={uploadIcon} alt="" />
|
||||
Upload file
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
style={{ marginLeft: 20 }}
|
||||
className={`btn-modal-action-${theme && "dark"}`}
|
||||
>
|
||||
<img style={{ margin: 2 }} src={closeIcon} alt="" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="TaskModal-content">
|
||||
<Tabs>
|
||||
<TabPane
|
||||
tab={
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginLeft: 24,
|
||||
}}
|
||||
>
|
||||
<img style={{ marginRight: 10 }} src={infoIcon} alt="" />
|
||||
Information
|
||||
</span>
|
||||
}
|
||||
key="1"
|
||||
>
|
||||
<div className="info-div">
|
||||
<p
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
lineHeight: "24px",
|
||||
letterSpacing: "-0.02em",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
Information
|
||||
</p>
|
||||
<div className="info-body">
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Comapany</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{recordTask?.company?.name}
|
||||
</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Driver</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{recordTask?.customer?.name}
|
||||
</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Service</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{recordTask?.service?.title}
|
||||
</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Team</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>{teamName}</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Assignee</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{recordTask?.in_charge?.username}
|
||||
</p>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>PTI</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{pti === false ? "Do" : "No need"}
|
||||
</p>
|
||||
<button
|
||||
style={{
|
||||
marginLeft: 10,
|
||||
background: "#cecece",
|
||||
outline: "none",
|
||||
border: "1px solid rgba(246, 137, 0, 1)",
|
||||
padding: 4,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
onClick={(e) => setPti(!pti)}
|
||||
>
|
||||
change
|
||||
</button>
|
||||
</tr>
|
||||
<tr>
|
||||
<p className={!theme ? "sub" : "sub-dark"}>Created at</p>
|
||||
<p className={!theme ? "info" : "info-dark"}>
|
||||
{moment(
|
||||
recordTask?.created_at,
|
||||
"YYYY-MM-DD HH:mm:ss"
|
||||
).format("DD.MM.YYYY HH:mm")}
|
||||
</p>
|
||||
</tr>
|
||||
</div>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<label className={!theme ? "sub" : "sub-dark"}>Note:</label>
|
||||
<TextArea
|
||||
style={{ padding: "7px 11px", marginTop: 10 }}
|
||||
placeholder="Description"
|
||||
defaultValue={text}
|
||||
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
style={{ marginTop: 20 }}
|
||||
className={`btn-modal-action-${theme && "dark"}`}
|
||||
onClick={(e) => patchTask()}
|
||||
>
|
||||
<img src={editIcon} alt="" />
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img style={{ marginRight: 10 }} src={attachmentIcon} alt="" />
|
||||
Attachments
|
||||
</span>
|
||||
}
|
||||
key="2"
|
||||
>
|
||||
<div className="info-div">
|
||||
<p
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
lineHeight: "24px",
|
||||
letterSpacing: "-0.02em",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
Attachments
|
||||
</p>
|
||||
</div>
|
||||
<Table
|
||||
size="small"
|
||||
style={{ margin: "0 12px" }}
|
||||
dataSource={recordTask?.attachment_set?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
...u,
|
||||
created: moment(u?.updated_at)
|
||||
.tz(timeZone)
|
||||
.format("DD.MM.YYYY HH:mm"),
|
||||
action: { ...u },
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: "Name/Description",
|
||||
dataIndex: "file_name",
|
||||
width: "30%",
|
||||
key: 1,
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Uploaded date",
|
||||
dataIndex: "created",
|
||||
key: 2,
|
||||
width: "30%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Attachments",
|
||||
dataIndex: "action",
|
||||
key: 3,
|
||||
width: "40%",
|
||||
render: (text, record) => {
|
||||
return (
|
||||
<a
|
||||
href={record?.path}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{
|
||||
padding: "6px 10px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
borderRadius: 8,
|
||||
color: !theme ? "rgba(15, 17, 28, 1)" : "#bbb",
|
||||
border: "1px solid rgba(215, 216, 224, 1)",
|
||||
boxShadow: "0px 1px 3px 0px rgba(20, 22, 41, 0.1)",
|
||||
}}
|
||||
>
|
||||
{getFileType(record?.file_name)}
|
||||
<span style={{ overflow: "hidden" }}>
|
||||
{record?.file_name}
|
||||
</span>
|
||||
<img
|
||||
src={letssee}
|
||||
alt=""
|
||||
style={{ textAlign: "right" }}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
},
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "even-row" : "odd-row"
|
||||
}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img style={{ marginRight: 10 }} src={historyIcon} alt="" />
|
||||
History
|
||||
</span>
|
||||
}
|
||||
key="3"
|
||||
>
|
||||
<div className="info-div">
|
||||
<p
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontWeight: 700,
|
||||
lineHeight: "24px",
|
||||
letterSpacing: "-0.02em",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
History
|
||||
</p>
|
||||
</div>
|
||||
<Table
|
||||
style={{ margin: "0 24px" }}
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
dataSource={data?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
...u,
|
||||
by: u.user
|
||||
? { user: u?.user?.username }
|
||||
: { driver: u?.driver?.name },
|
||||
created: moment(u?.timestamp)
|
||||
.tz(timeZone)
|
||||
.format("DD.MM.YYYY HH:mm"),
|
||||
// action: { ...u. },
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: "User/Driver",
|
||||
dataIndex: "by",
|
||||
width: "25%",
|
||||
key: 1,
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (text: any, record: any) => (
|
||||
<Tooltip placement="topLeft" title={text}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
{text?.user ? (
|
||||
<>
|
||||
<img
|
||||
src={getImageSource("user")}
|
||||
alt=""
|
||||
style={{ width: 15, height: 15, marginRight: 10 }}
|
||||
/>
|
||||
<p>
|
||||
{typeof text.user === "string" ? text.user : ""}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src={getImageSource("driver")}
|
||||
alt=""
|
||||
style={{ width: 20, height: 15, marginRight: 10 }}
|
||||
/>
|
||||
<p>
|
||||
{typeof text.driver === "string"
|
||||
? text.driver
|
||||
: ""}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
dataIndex: "action",
|
||||
width: "15%",
|
||||
key: 2,
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Description",
|
||||
dataIndex: "description",
|
||||
width: "40%",
|
||||
key: 3,
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Timestamp",
|
||||
dataIndex: "created",
|
||||
key: 4,
|
||||
width: "20%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "even-row" : "odd-row"
|
||||
}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskModal;
|
@ -0,0 +1,354 @@
|
||||
import { Button, Modal, Space, Table, Tooltip } from "antd";
|
||||
import "../../App.css";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { taskController } from "../../API/LayoutApi/tasks";
|
||||
import { TPagination } from "../../types/common/TPagination";
|
||||
import { TTask } from "../../types/Tasks/TTasks";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
UseQueryResult,
|
||||
} from "react-query";
|
||||
import { TCompany } from "../../types/Company/TCompany";
|
||||
import { TService } from "../../types/Service/TService";
|
||||
import { TCustomer } from "../../types/Customer/TCustomer";
|
||||
import { TUser } from "../../types/User/TUser";
|
||||
// @ts-ignore
|
||||
import zippy from "../../assets/zippyicon.svg";
|
||||
// @ts-ignore
|
||||
import evo from "../../assets/evoicon.png";
|
||||
// @ts-ignore
|
||||
import zeelog from "../../assets/zeelogicon.svg";
|
||||
// @ts-ignore
|
||||
import ontime from "../../assets/ontimeicon.svg";
|
||||
// @ts-ignore
|
||||
import tt from "../../assets/tticon.svg";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
import { role } from "../../App";
|
||||
|
||||
const admin_id = localStorage.getItem("admin_id");
|
||||
const TaskTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
refetch,
|
||||
showTaskModal,
|
||||
}: {
|
||||
data: {
|
||||
characters: TTask[] | undefined;
|
||||
CompanyData: UseQueryResult<TCompany[], unknown>;
|
||||
CustomerData: UseQueryResult<TCustomer[], unknown>;
|
||||
ServiceData: UseQueryResult<TService[], unknown>;
|
||||
AdminData: UseQueryResult<TUser[], unknown>;
|
||||
};
|
||||
showTaskModal: any;
|
||||
isLoading: boolean;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TPagination<TTask[]>, unknown>>;
|
||||
}) => {
|
||||
const moment = require("moment");
|
||||
const statusClick = (record: any) => {
|
||||
if (record.status === "New") {
|
||||
Modal.confirm({
|
||||
title: "Confirmation",
|
||||
content: `Are you sure you want to be in charge for this task?`,
|
||||
onOk: () => {
|
||||
const value = {
|
||||
status: "Checking",
|
||||
};
|
||||
taskController.taskPatch(value, record.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
if (record.status === "Checking") {
|
||||
Modal.confirm({
|
||||
title: "Confirmation",
|
||||
content: `Are you sure you want to finish this task?`,
|
||||
onOk: () => {
|
||||
const value = {
|
||||
status: "Done",
|
||||
};
|
||||
taskController.taskPatch(value, record.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const [isTextSelected, setIsTextSelected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleSelectionChange = () => {
|
||||
const selection = window.getSelection();
|
||||
setIsTextSelected(selection !== null && selection.toString() !== "");
|
||||
};
|
||||
|
||||
document.addEventListener("selectionchange", handleSelectionChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("selectionchange", handleSelectionChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleRowClick = (record: TTask, event: any) => {
|
||||
if (isTextSelected) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
event.target.classList.contains("ant-table-cell") &&
|
||||
(record?.in_charge?.id === null ||
|
||||
(!!admin_id && record?.in_charge?.id === +admin_id) ||
|
||||
role !== "Checker")
|
||||
) {
|
||||
showTaskModal(record);
|
||||
}
|
||||
};
|
||||
|
||||
const getImageSource = (source: string) => {
|
||||
switch (source) {
|
||||
case "Zippy":
|
||||
return zippy;
|
||||
case "EVO":
|
||||
return evo;
|
||||
case "Ontime":
|
||||
return ontime;
|
||||
case "Zeelog":
|
||||
return zeelog;
|
||||
case "TT":
|
||||
return tt;
|
||||
default:
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
|
||||
const rowClassName = (record: TTask) => {
|
||||
if (record.status === "New") {
|
||||
return "new-status-row";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const columns = [
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
dataIndex: "company",
|
||||
width: "13%",
|
||||
responsive: ["xl"],
|
||||
ellipsis: {
|
||||
showTitle: true,
|
||||
},
|
||||
render: (text: any, record: any) => (
|
||||
<Tooltip placement="topLeft" title={text?.name}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
{text?.source && (
|
||||
<img
|
||||
src={getImageSource(text?.source)}
|
||||
alt=""
|
||||
style={{ width: 20, height: 20, marginRight: 10 }}
|
||||
/>
|
||||
)}
|
||||
{text?.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Driver",
|
||||
dataIndex: "customer",
|
||||
width: "13%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (item: { name: string; id: number }) => (
|
||||
<Tooltip placement="topLeft" title={item?.name}>
|
||||
{item?.name}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Service",
|
||||
dataIndex: "service",
|
||||
width: "7%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (item: { title: string; id: number }) => (
|
||||
<Tooltip placement="topLeft" title={item?.title}>
|
||||
{item.title}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
width: "8%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (status: string) => (
|
||||
<span>
|
||||
{status === "Done" && <p className="status-done">Done</p>}
|
||||
{status === "Checking" && (
|
||||
<p className="status-in-progress">Checking</p>
|
||||
)}
|
||||
{status === "New" && <p className="status-new">New</p>}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
dataIndex: "assigned_to",
|
||||
width: "8%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (item: { name: string }) => (
|
||||
<Tooltip placement="topLeft" title={item?.name}>
|
||||
{item?.name}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Assignee",
|
||||
dataIndex: "in_charge",
|
||||
width: "12%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (item: { username: string }) => (
|
||||
<Tooltip placement="topLeft" title={item?.username}>
|
||||
{item?.username}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "PTI",
|
||||
dataIndex: "pti",
|
||||
width: "6%",
|
||||
responsive: ["lg"],
|
||||
render: (pti: boolean) => (pti ? "No need" : "Do"),
|
||||
},
|
||||
{
|
||||
title: "Note",
|
||||
dataIndex: "note",
|
||||
width: "12%",
|
||||
responsive: ["lg"],
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Created at",
|
||||
dataIndex: "created",
|
||||
width: "12%",
|
||||
responsive: ["xxl"],
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
dataIndex: "action",
|
||||
width: "8%",
|
||||
render: (text: string, record: TTask) => {
|
||||
return (
|
||||
<div>
|
||||
{role === "Checker" ? (
|
||||
<Space>
|
||||
{record.status === "New" && (
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ background: "#595959" }}
|
||||
onClick={() => statusClick(record)}
|
||||
>
|
||||
Assign
|
||||
</Button>
|
||||
)}
|
||||
{record.status === "Checking" &&
|
||||
!!admin_id &&
|
||||
record?.in_charge?.id === +admin_id && (
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ background: "#595959" }}
|
||||
onClick={() => statusClick(record)}
|
||||
>
|
||||
Finish
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={(e) => {
|
||||
const shouldDelete = window.confirm(
|
||||
"Are you sure, you want to delete this task?"
|
||||
);
|
||||
if (shouldDelete && record.id !== undefined) {
|
||||
taskController.deleteTaskController(record.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (role === "Checker") {
|
||||
const teamColIndex = columns.findIndex((c) => c.title === "Team");
|
||||
|
||||
teamColIndex !== -1 && columns.splice(teamColIndex, 1);
|
||||
}
|
||||
|
||||
return columns;
|
||||
}, [role]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
onRow={(record: any) => ({
|
||||
onClick: (event) => handleRowClick(record, event),
|
||||
})}
|
||||
dataSource={data?.characters?.map((u, i) => ({
|
||||
...u,
|
||||
no: i + 1,
|
||||
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||
"DD.MM.YYYY HH:mm"
|
||||
),
|
||||
key: u?.id,
|
||||
}))}
|
||||
size="small"
|
||||
columns={columns as any}
|
||||
pagination={false}
|
||||
loading={isLoading}
|
||||
rowClassName={rowClassName}
|
||||
bordered
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskTable;
|
@ -0,0 +1,114 @@
|
||||
import { Modal, Upload, Button, Space } from "antd";
|
||||
import { taskController } from "../../API/LayoutApi/tasks";
|
||||
// @ts-ignore
|
||||
import uploadfile from "../../assets/uploadfile.png";
|
||||
// @ts-ignore
|
||||
import createIcon from "../../assets/galkaIcon.png";
|
||||
import { CloseOutlined } from "@ant-design/icons";
|
||||
import { TTask } from "../../types/Tasks/TTasks";
|
||||
import { useState } from "react";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
|
||||
const TaskUploadModal = ({
|
||||
uploadOpen,
|
||||
recordTask,
|
||||
setUploadOpen,
|
||||
}: {
|
||||
recordTask: TTask | undefined;
|
||||
uploadOpen: boolean;
|
||||
setUploadOpen(open: boolean): void;
|
||||
}) => {
|
||||
const handleCancel = () => {
|
||||
setUploadOpen(!uploadOpen);
|
||||
};
|
||||
const [fileData, setFileData] = useState();
|
||||
const [text, setText] = useState<any>();
|
||||
return (
|
||||
<Modal
|
||||
open={uploadOpen}
|
||||
title="Upload file"
|
||||
okText="Upload"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
width={720}
|
||||
footer={
|
||||
<Space>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
style={{ display: "flex", alignItems: "center" }}
|
||||
icon={<CloseOutlined />}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
const updatedValues: any = {};
|
||||
updatedValues.task_id = recordTask?.id;
|
||||
updatedValues.file = fileData;
|
||||
updatedValues.description = text;
|
||||
taskController.addTaskFile(updatedValues).then(() => {
|
||||
setUploadOpen(!uploadOpen);
|
||||
});
|
||||
}}
|
||||
icon={<img alt="" src={createIcon} />}
|
||||
>
|
||||
Upload
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Upload.Dragger
|
||||
name="file"
|
||||
multiple={true}
|
||||
customRequest={({ file, onSuccess }: any) => {
|
||||
setFileData(file);
|
||||
if (file) {
|
||||
onSuccess();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<img src={uploadfile} alt="" />
|
||||
</p>
|
||||
<p
|
||||
className="ant-upload-text"
|
||||
style={{
|
||||
color: "rgba(15, 17, 28, 1)",
|
||||
fontWeight: 600,
|
||||
fontSize: 14,
|
||||
lineHeight: "20px",
|
||||
letterSpacing: "-1%",
|
||||
}}
|
||||
>
|
||||
Drag and drop files or{" "}
|
||||
<span style={{ color: "rgba(249, 158, 44, 1)" }}>
|
||||
Click to select
|
||||
</span>
|
||||
<br />
|
||||
<span
|
||||
style={{
|
||||
color: "rgba(155, 157, 170, 1)",
|
||||
fontWeight: 400,
|
||||
fontSize: 13,
|
||||
lineHeight: "15.73px",
|
||||
letterSpacing: "-2%",
|
||||
}}
|
||||
>
|
||||
Maximum file size is 10 MB
|
||||
</span>
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
<TextArea
|
||||
style={{ padding: "7px 11px", marginTop: 20 }}
|
||||
placeholder="Description"
|
||||
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskUploadModal;
|
@ -0,0 +1,356 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import AddTask from "./AddTask";
|
||||
import { Button, Input, Select, Space, message, notification } from "antd";
|
||||
import TaskTable from "./TaskTable";
|
||||
import { useTeamData } from "../../Hooks/Teams";
|
||||
import { StepForwardOutlined, StepBackwardOutlined } from "@ant-design/icons";
|
||||
import { useTasks } from "../../Hooks/Tasks";
|
||||
import { TTask } from "../../types/Tasks/TTasks";
|
||||
import { useCompanyData } from "../../Hooks/Companies";
|
||||
import { useCustomerData } from "../../Hooks/Customers";
|
||||
import { useUserData } from "../../Hooks/Users";
|
||||
import { useServiceData } from "../../Hooks/Services";
|
||||
import { admin_id, role, team_id } from "../../App";
|
||||
//@ts-ignore
|
||||
import addicon from "../../assets/addiconpng.png";
|
||||
//@ts-ignore
|
||||
import refreshicon from "../../assets/refreshIcon.png";
|
||||
// @ts-ignore
|
||||
import IconSearch from "../../assets/searchIcon.png";
|
||||
import TaskModal from "./TaskModal";
|
||||
import TaskUploadModal from "./TaskUploadModal";
|
||||
import { NotificationPlacement } from "antd/es/notification/interface";
|
||||
import { TCall } from "../../types/CallRequests/TCall";
|
||||
|
||||
const { Option } = Select;
|
||||
const Task = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [characters, setCharacters] = useState<TTask[] | undefined>();
|
||||
const [team, setTeam] = useState<any>();
|
||||
const [search, setSearch] = useState<string>("");
|
||||
const [status, setStatus] = useState<any>();
|
||||
const [page, setPage] = useState<any>(1);
|
||||
const [uploadOpen, setUploadOpen] = useState(false);
|
||||
|
||||
const CompanyData = useCompanyData({});
|
||||
const CustomerData = useCustomerData({});
|
||||
const AdminData = useUserData({});
|
||||
const ServiceData = useServiceData();
|
||||
|
||||
let taskSocket: WebSocket;
|
||||
interface newData {
|
||||
callback_request: TCall;
|
||||
type: string;
|
||||
task: TTask;
|
||||
}
|
||||
|
||||
const [isLive, setIslive] = useState(true);
|
||||
const [socketData, setSocketData] = useState<newData>();
|
||||
const connect = async () => {
|
||||
try {
|
||||
if (!taskSocket || taskSocket.readyState === WebSocket.CLOSED) {
|
||||
taskSocket = new WebSocket(
|
||||
`ws://10.10.10.45:8080/tasks/?user_id=${admin_id}`
|
||||
);
|
||||
// taskSocket = new WebSocket(
|
||||
// `wss://api.tteld.co/tasks/?user_id=${admin_id}`
|
||||
// );
|
||||
|
||||
taskSocket.addEventListener("open", (event) => {
|
||||
console.log("open");
|
||||
setIslive(true);
|
||||
});
|
||||
taskSocket.addEventListener("message", (event) => {
|
||||
const newData: newData = JSON.parse(event.data);
|
||||
setSocketData(newData);
|
||||
});
|
||||
taskSocket.addEventListener("error", (errorEvent) => {
|
||||
console.error("WebSocket error:", errorEvent);
|
||||
});
|
||||
|
||||
taskSocket.addEventListener("close", (event) => {
|
||||
setIslive(false);
|
||||
console.log("close");
|
||||
});
|
||||
}
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
}, []);
|
||||
|
||||
const [api, contextHolder] = notification.useNotification();
|
||||
const openNotification = useCallback(
|
||||
(placement: NotificationPlacement, data: TCall) => {
|
||||
console.log(data);
|
||||
|
||||
api.info({
|
||||
message: `Driver ${data?.driver?.name} from ${data?.company?.name} company has made a call request`,
|
||||
placement,
|
||||
});
|
||||
},
|
||||
[api]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
socketData &&
|
||||
((role !== "Checker" &&
|
||||
(!team || team.includes(socketData?.task?.assigned_to?.id))) ||
|
||||
role === "Checker") &&
|
||||
(!status || status.includes(socketData.task.status))
|
||||
) {
|
||||
setCharacters((prev: TTask[] | undefined) => {
|
||||
if (prev && prev?.length >= 15) {
|
||||
prev?.pop();
|
||||
}
|
||||
if (socketData.type === "task_create") {
|
||||
return [socketData.task, ...(prev || [])];
|
||||
} else if (socketData.type === "task_update") {
|
||||
if (role !== "Checker") {
|
||||
const updatedData =
|
||||
prev?.filter((b: TTask) => b.id !== socketData.task.id) || [];
|
||||
const data: TTask[] = [socketData.task, ...updatedData];
|
||||
data.sort((a: TTask, b: TTask) => {
|
||||
if (a.status === "New" && b.status === "New") {
|
||||
return 0;
|
||||
}
|
||||
if (a.status === "New") {
|
||||
return -1;
|
||||
}
|
||||
if (b.status === "New") {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return data;
|
||||
} else {
|
||||
const data = (prev || []).map((b: TTask) =>
|
||||
b.id === socketData.task.id ? socketData.task : b
|
||||
);
|
||||
return data;
|
||||
}
|
||||
} else if (socketData.type === "task_delete") {
|
||||
const data = (prev || []).filter(
|
||||
(b: TTask) => b.id !== socketData.task.id
|
||||
);
|
||||
return data;
|
||||
} else if (socketData.type === "task_forward") {
|
||||
if (role === "Checker") {
|
||||
if (socketData.task.assigned_to.id === team_id) {
|
||||
return [socketData.task, ...(prev || [])];
|
||||
} else {
|
||||
const data = (prev || []).filter(
|
||||
(b: TTask) => b.id !== socketData.task.id
|
||||
);
|
||||
return data;
|
||||
}
|
||||
} else {
|
||||
const updatedData =
|
||||
prev?.filter((b: TTask) => b.id !== socketData.task.id) || [];
|
||||
const data: TTask[] = [socketData.task, ...updatedData];
|
||||
data.sort((a: TTask, b: TTask) => {
|
||||
if (a.status === "New" && b.status === "New") {
|
||||
return 0;
|
||||
}
|
||||
if (a.status === "New") {
|
||||
return -1;
|
||||
}
|
||||
if (b.status === "New") {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
} else if (socketData.type === "callback_request") {
|
||||
if (socketData?.callback_request) {
|
||||
console.log("run");
|
||||
|
||||
openNotification("bottomRight", socketData?.callback_request);
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
}, [socketData, openNotification]);
|
||||
|
||||
const teamData = useTeamData("");
|
||||
|
||||
const teamOptions: { label: string; value: any }[] | undefined =
|
||||
teamData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}));
|
||||
|
||||
const { data, isLoading, refetch } = useTasks({
|
||||
search,
|
||||
status,
|
||||
team,
|
||||
page,
|
||||
});
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setCharacters(data?.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
}, [search, status, team]);
|
||||
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const [recordTask, setRecordTask] = useState<TTask>();
|
||||
const showTaskModal = (e: TTask) => {
|
||||
setRecordTask(e);
|
||||
setModalOpen(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 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 theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
return (
|
||||
<div>
|
||||
{contextHolder}
|
||||
{open && <AddTask open={open} setOpen={setOpen} />}
|
||||
{uploadOpen && (
|
||||
<TaskUploadModal
|
||||
recordTask={recordTask}
|
||||
uploadOpen={uploadOpen}
|
||||
setUploadOpen={setUploadOpen}
|
||||
/>
|
||||
)}
|
||||
{modalOpen && (
|
||||
<TaskModal
|
||||
uploadOpen={uploadOpen}
|
||||
setUploadOpen={setUploadOpen}
|
||||
recordTask={recordTask}
|
||||
modalOpen={modalOpen}
|
||||
setModalOpen={setModalOpen}
|
||||
refetch={refetch}
|
||||
/>
|
||||
)}
|
||||
<div className="header d-flex">
|
||||
<div className="header-title d-flex">
|
||||
<p className="title">Tasks</p>
|
||||
{isLive ? (
|
||||
<div className="d-flex" style={{ marginRight: 15 }}>
|
||||
<div className="circle"></div>
|
||||
<p className={!theme ? "live-p" : "live-p-dark"}>online</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex" style={{ marginRight: 15 }}>
|
||||
<div className="circle2"></div>
|
||||
<p className="live-p">offline</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{role !== "Checker" && (
|
||||
<button className="btn-add d-flex" onClick={showModal}>
|
||||
<img style={{ marginRight: 8 }} src={addicon} alt="" />
|
||||
Add Task
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={`btn-refresh-${theme && "dark"} d-flex`}
|
||||
onClick={() => {
|
||||
refetch();
|
||||
connect();
|
||||
}}
|
||||
>
|
||||
<img style={{ marginRight: 8 }} src={refreshicon} alt="" />
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="filter d-flex">
|
||||
<div className="search-div">
|
||||
<img src={IconSearch} alt="" />
|
||||
<input
|
||||
className={`search-input-${theme}`}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
style={{ width: 260, marginLeft: 12 }}
|
||||
placeholder="status"
|
||||
onChange={(value: any) => setStatus(value)}
|
||||
mode="multiple"
|
||||
>
|
||||
<Option value="New">New</Option>
|
||||
<Option value="Checking">Checking</Option>
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<TaskTable
|
||||
data={{ characters, CompanyData, CustomerData, ServiceData, AdminData }}
|
||||
isLoading={isLoading}
|
||||
refetch={refetch}
|
||||
showTaskModal={showTaskModal}
|
||||
/>
|
||||
<Space style={{ width: "100%", marginTop: 10 }} direction="vertical">
|
||||
<Space style={{ width: "100%", justifyContent: "flex-end" }} wrap>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<StepBackwardOutlined />}
|
||||
onClick={Previos}
|
||||
></Button>
|
||||
<Input
|
||||
style={{ width: 50, textAlign: "right" }}
|
||||
value={page}
|
||||
onChange={(e) => {
|
||||
let num = e.target.value;
|
||||
if (Number(num) && num !== "0") {
|
||||
setPage(num);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<StepForwardOutlined />}
|
||||
onClick={Next}
|
||||
></Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Task;
|
@ -0,0 +1,76 @@
|
||||
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||
import { teamController } from "../../API/LayoutApi/teams";
|
||||
import { useUserData } from "../../Hooks/Users";
|
||||
|
||||
const AddTeam = ({
|
||||
open,
|
||||
setOpen,
|
||||
refetch,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
refetch(): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
const { data } = useUserData({ name: "", team: "", role: "Checker" });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Add new team"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(async (values) => {
|
||||
form.resetFields();
|
||||
await teamController.addTeamController(values);
|
||||
setOpen(!open);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
rules={[{ required: true, message: "Please input team name!" }]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Include users to this team"
|
||||
name="user_ids"
|
||||
rules={[
|
||||
{ required: false, message: "Please input company status!" },
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
options={data?.map((items) => ({
|
||||
label: items.username,
|
||||
value: items.id,
|
||||
}))}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTeam;
|
@ -0,0 +1,200 @@
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useTeamOne } from "../../Hooks/Teams";
|
||||
import {
|
||||
Form,
|
||||
Spin,
|
||||
Watermark,
|
||||
Space,
|
||||
Tabs,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
Button,
|
||||
Table,
|
||||
} from "antd";
|
||||
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 AddUserToTeam from "./AddUserToTeam";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIcon from "../../assets/infoIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIconActive from "../../assets/infoIconActive.png";
|
||||
|
||||
const TabPane = Tabs.TabPane;
|
||||
|
||||
type params = {
|
||||
readonly id: any;
|
||||
};
|
||||
|
||||
type MyObjectType = {
|
||||
[key: string | number]: any;
|
||||
};
|
||||
const TeamEdit = () => {
|
||||
const { id } = useParams<params>();
|
||||
const { data, refetch, status }: MyObjectType = useTeamOne(id);
|
||||
let navigate = useNavigate();
|
||||
|
||||
const onSubmit = async (value: any) => {
|
||||
await teamController.teamPatch(value, id);
|
||||
refetch();
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
const ClickDelete = () => {
|
||||
const shouldDelete = window.confirm(
|
||||
"Вы уверены, что хотите удалить эту команду?"
|
||||
);
|
||||
if (shouldDelete && id !== undefined) {
|
||||
teamController.deleteTeamController(id).then((data: any) => {
|
||||
navigate(-1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const userData = useUserData({ name: "", team: id });
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const [activeTab, setActiveTab] = useState("1");
|
||||
return (
|
||||
<div>
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Watermark style={{ height: "100%" }}>
|
||||
{status === "loading" ? (
|
||||
<Spin size="large" spinning={!data} />
|
||||
) : data ? (
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key)}
|
||||
>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
style={{ marginRight: 10 }}
|
||||
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||
alt=""
|
||||
/>
|
||||
Information
|
||||
</span>
|
||||
}
|
||||
key="1"
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Name"
|
||||
name="name"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item>
|
||||
{role !== "Checker" && (
|
||||
<Button
|
||||
onClick={() => ClickDelete()}
|
||||
type="primary"
|
||||
style={{ marginRight: 10 }}
|
||||
danger
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Space>
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<FormOutlined />
|
||||
Users
|
||||
</span>
|
||||
}
|
||||
key="2"
|
||||
>
|
||||
<Table
|
||||
dataSource={userData?.data?.map((item, i) => ({
|
||||
no: i + 1,
|
||||
...item,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
},
|
||||
{
|
||||
title: "Username",
|
||||
dataIndex: "username",
|
||||
},
|
||||
{
|
||||
title: "First name",
|
||||
dataIndex: "first_name",
|
||||
},
|
||||
{
|
||||
title: "Last name",
|
||||
dataIndex: "last_name",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<AddUserToTeam
|
||||
team={id}
|
||||
refetch={refetch}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginLeft: "auto" }}
|
||||
size={"large"}
|
||||
onClick={showModal}
|
||||
>
|
||||
Add User
|
||||
</Button>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Space>
|
||||
</Spin>
|
||||
) : (
|
||||
<Notfound />
|
||||
)}
|
||||
</Watermark>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamEdit;
|
@ -0,0 +1,112 @@
|
||||
// // @ts-ignore
|
||||
// import closeIcon from "../../assets/closeIcon.png";
|
||||
// // @ts-ignore
|
||||
// import editIcon from "../../assets/editIcon.png";
|
||||
// // @ts-ignore
|
||||
// import deleteIcon from "../../assets/deleteIconRed.png";
|
||||
// import { TTeam } from "../../types/Team/TTeam";
|
||||
// import { Table } from "antd";
|
||||
// // @ts-ignore
|
||||
// import tagIcon from "../../assets/tagIcon.png";
|
||||
// import { useUserByTeam } from "../../Hooks/Users";
|
||||
|
||||
// const TeamModal = ({
|
||||
// modalOpen,
|
||||
// setModalOpen,
|
||||
// recordTeam,
|
||||
// }: {
|
||||
// recordTeam: TTeam | undefined;
|
||||
// modalOpen: any;
|
||||
// setModalOpen: any;
|
||||
// }) => {
|
||||
// const handleCancel = () => {
|
||||
// setModalOpen(!modalOpen);
|
||||
// };
|
||||
|
||||
// const { data, isLoading, refetch } = useUserByTeam(recordTeam?.uuid);
|
||||
|
||||
// return (
|
||||
// <div className="TaskModal">
|
||||
// <div className="TaskModal-header">
|
||||
// <div className="TaskModal-title">
|
||||
// <p className="p-driver">{recordTeam?.name}</p>
|
||||
// </div>
|
||||
// <div className="mdoal-actions">
|
||||
// <button className="btn-modal-action">
|
||||
// <img src={editIcon} alt="" />
|
||||
// Edit
|
||||
// </button>
|
||||
// <button style={{ marginLeft: 12 }} className="btn-modal-action">
|
||||
// <img src={deleteIcon} alt="" />
|
||||
// Delete
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={handleCancel}
|
||||
// style={{ marginLeft: 20 }}
|
||||
// className="btn-modal-action"
|
||||
// >
|
||||
// <img style={{ margin: 2 }} src={closeIcon} alt="" />
|
||||
// </button>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className="TaskModal-content">
|
||||
// <p
|
||||
// style={{
|
||||
// fontSize: 18,
|
||||
// fontWeight: 700,
|
||||
// lineHeight: "24px",
|
||||
// letterSpacing: "-0.02em",
|
||||
// marginLeft: 24
|
||||
// }}
|
||||
// >
|
||||
// Users
|
||||
// </p>
|
||||
// <div className="users-table-by-team">
|
||||
// <Table
|
||||
// loading={isLoading}
|
||||
// rowClassName={(record, index) =>
|
||||
// index % 2 === 0 ? "even-row" : "odd-row"
|
||||
// }
|
||||
// dataSource={data?.map((u, i) => {
|
||||
// return {
|
||||
// ...u,
|
||||
// no: i + 1,
|
||||
// key: u?.uid,
|
||||
// };
|
||||
// })}
|
||||
// columns={[
|
||||
// {
|
||||
// title: <img src={tagIcon} alt="" />,
|
||||
// dataIndex: "no",
|
||||
// width: "5%",
|
||||
// },
|
||||
// {
|
||||
// title: "Name",
|
||||
// dataIndex: "full_name",
|
||||
// },
|
||||
// {
|
||||
// title: "Username",
|
||||
// dataIndex: "username",
|
||||
// },
|
||||
// {
|
||||
// title: "Actions",
|
||||
// dataIndex: "actions",
|
||||
// },
|
||||
// ]}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default TeamModal;
|
||||
import React from 'react'
|
||||
|
||||
const TeamModal = () => {
|
||||
return (
|
||||
<div>TeamModal</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TeamModal
|
@ -0,0 +1,86 @@
|
||||
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.png";
|
||||
const TeamTable = ({
|
||||
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");
|
||||
|
||||
return (
|
||||
<Table
|
||||
loading={isLoading}
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: () => {
|
||||
navigate(`/teams/${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: "Is Active",
|
||||
dataIndex: "is_active",
|
||||
render: (tag: boolean) => (
|
||||
<Tag color={tag ? "geekblue" : "red"}>{tag ? "True" : "False"}</Tag>
|
||||
),
|
||||
filters: [
|
||||
{
|
||||
text: "True",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
text: "False",
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
onFilter: (value: string | number | boolean, record: TTeam) => {
|
||||
return record.is_active === value;
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamTable;
|
@ -0,0 +1,33 @@
|
||||
import { useState } from "react";
|
||||
import { useTeamData } from "../../Hooks/Teams";
|
||||
import TeamTable from "./TeamTable";
|
||||
//@ts-ignore
|
||||
import addicon from "../../assets/addiconpng.png";
|
||||
import AddTeam from "./AddTeam";
|
||||
|
||||
const Team = () => {
|
||||
const { data, isLoading, refetch } = useTeamData("");
|
||||
const [open, setOpen] = useState(false);
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{open && <AddTeam refetch={refetch} open={open} setOpen={setOpen} />}
|
||||
<div className="header d-flex" style={{ marginBottom: 16 }}>
|
||||
<p className="title">Teams</p>
|
||||
<button
|
||||
className="btn-add d-flex"
|
||||
style={{ marginRight: 0 }}
|
||||
onClick={showModal}
|
||||
>
|
||||
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<TeamTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Team;
|
@ -0,0 +1,198 @@
|
||||
import { Input, Modal, Form as FormAnt, Select, Upload } from "antd";
|
||||
import { updateController } from "../../API/LayoutApi/update";
|
||||
import { useState } from "react";
|
||||
import { UploadOutlined } from "@ant-design/icons";
|
||||
import { taskController } from "../../API/LayoutApi/tasks";
|
||||
import { useCompanyData } from "../../Hooks/Companies";
|
||||
import { useCustomerByComanyData } from "../../Hooks/Customers";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { TUpdate } from "../../types/Update/TUpdate";
|
||||
const { Option } = Select;
|
||||
const AddUpdate = ({
|
||||
refetch,
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TUpdate[], unknown>>;
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
|
||||
const handleCancel = () => {
|
||||
refetch();
|
||||
setOpen(!open);
|
||||
};
|
||||
const [fileIds, setFileIds] = useState([]);
|
||||
const [companyName, setCompanyName] = useState<string>("");
|
||||
const [customerName, setCustomerName] = useState<string>("");
|
||||
const [companyId, setCompanyId] = useState<string>();
|
||||
|
||||
const companyData = useCompanyData({ name: companyName });
|
||||
const customerData = useCustomerByComanyData({
|
||||
id: companyId,
|
||||
name: customerName,
|
||||
});
|
||||
|
||||
const [imgname, setImgname] = useState<any>([]);
|
||||
function handlePaste(event: any) {
|
||||
const clipboardData = event.clipboardData || window.Clipboard;
|
||||
if (clipboardData && clipboardData.items.length > 0) {
|
||||
const clipboardItem = clipboardData.items[0];
|
||||
if (clipboardItem.kind === "file") {
|
||||
const file = clipboardItem.getAsFile();
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
taskController.addTaskFile(formData).then((response) => {
|
||||
const fileId = response.id;
|
||||
const n = [response.file];
|
||||
setImgname((prev: any) => [...prev, ...n]);
|
||||
setFileIds((prevFileIds): any => [...prevFileIds, fileId]);
|
||||
const updatedValues = form.getFieldsValue();
|
||||
updatedValues.attachment_ids = [
|
||||
...updatedValues.attachment_ids,
|
||||
fileId,
|
||||
];
|
||||
form.setFieldsValue(updatedValues);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div onPaste={(event) => handlePaste(event)}>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Add update"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form.validateFields().then(async (values) => {
|
||||
const updatedValues = { ...values };
|
||||
updatedValues.attachment_ids = fileIds;
|
||||
form.resetFields();
|
||||
await updateController.addUpdateController(updatedValues);
|
||||
setOpen(!open);
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="Company"
|
||||
name="company_id"
|
||||
rules={[{ required: false, message: "Please input company!" }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search Company"
|
||||
onSearch={(value: any) => setCompanyName(value)}
|
||||
options={companyData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}))}
|
||||
value={companyName}
|
||||
filterOption={false}
|
||||
autoClearSearchValue={false}
|
||||
allowClear
|
||||
onChange={(value: any) => setCompanyId(value)}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Driver"
|
||||
name="customer_id"
|
||||
rules={[
|
||||
{ required: false, message: "Please input service points!" },
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search Driver"
|
||||
onSearch={(value: any) => setCustomerName(value)}
|
||||
options={customerData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}))}
|
||||
value={customerName}
|
||||
filterOption={false}
|
||||
autoClearSearchValue={false}
|
||||
allowClear
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Note"
|
||||
name="note"
|
||||
rules={[{ required: true, message: "Make note!" }]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Status"
|
||||
name="status"
|
||||
rules={[
|
||||
{ required: false, message: "Please input service points!" },
|
||||
]}
|
||||
>
|
||||
<Select defaultValue="New" style={{ width: 120 }}>
|
||||
<Option value="New">New</Option>
|
||||
<Option value="In Progress">In Progress</Option>
|
||||
<Option value="Done">Done</Option>
|
||||
<Option value="Paper">Paper</Option>
|
||||
<Option value="Setup">Setup</Option>
|
||||
</Select>
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
<FormAnt>
|
||||
<FormAnt.Item label="File" name="attachment">
|
||||
<Upload.Dragger
|
||||
name="file"
|
||||
multiple={true}
|
||||
customRequest={({ file, onSuccess }: any) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
taskController
|
||||
.addTaskFile(formData)
|
||||
.then((response) => {
|
||||
const fileId = response.id;
|
||||
setFileIds((prevFileIds): any => [...prevFileIds, fileId]);
|
||||
onSuccess();
|
||||
const updatedValues = form.getFieldsValue();
|
||||
updatedValues.attachment_ids = [
|
||||
...updatedValues.attachment_ids,
|
||||
fileId,
|
||||
];
|
||||
form.setFieldsValue(updatedValues);
|
||||
})
|
||||
.catch((error) => {
|
||||
onSuccess(error);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<UploadOutlined style={{ color: "#36cfc9" }} />
|
||||
</p>
|
||||
<p className="ant-upload-text" style={{ color: "#36cfc9" }}>
|
||||
Click or drag file to this area to upload
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
<p>{imgname.join(",\n")}</p>
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUpdate;
|
@ -0,0 +1,67 @@
|
||||
import { useState } from "react";
|
||||
import AddUpdate from "./AddUpdate";
|
||||
import { Button, Select } from "antd";
|
||||
import UpdateTable from "./UpdateTable";
|
||||
import { useUpdateData } from "../../Hooks/Update";
|
||||
//@ts-ignore
|
||||
import addicon from "../../assets/addiconpng.png";
|
||||
//@ts-ignore
|
||||
import refreshicon from "../../assets/refreshIcon.png";
|
||||
const Update = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [status, setStatus] = useState<any>([
|
||||
"New",
|
||||
"In Progress",
|
||||
"Paper",
|
||||
"Setup",
|
||||
]);
|
||||
const { Option } = Select;
|
||||
|
||||
const { data, refetch, isLoading } = useUpdateData(status);
|
||||
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
return (
|
||||
<div>
|
||||
{open && <AddUpdate refetch={refetch} open={open} setOpen={setOpen} />}
|
||||
<div className="header d-flex" style={{ marginBottom: 16 }}>
|
||||
<p className="title">Updates</p>
|
||||
<div className="d-flex">
|
||||
<button className="btn-add d-flex" onClick={showModal}>
|
||||
<img style={{ marginRight: 8 }} src={addicon} alt="" />
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
className={`btn-refresh-${theme && "dark"} d-flex`}
|
||||
onClick={() => {
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
<img style={{ marginRight: 8 }} src={refreshicon} alt="" />
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="filter d-flex">
|
||||
<Select
|
||||
style={{ width: 260, marginLeft: 10 }}
|
||||
placeholder="status"
|
||||
onChange={(value: any) => setStatus(value)}
|
||||
mode="multiple"
|
||||
defaultValue={[]}
|
||||
>
|
||||
<Option value="New">New</Option>
|
||||
<Option value="In Progress">In Progress</Option>
|
||||
<Option value="Done">Done</Option>
|
||||
<Option value="Paper">Paper</Option>
|
||||
<Option value="Setup">Setup</Option>
|
||||
</Select>
|
||||
</div>
|
||||
<UpdateTable data={data} refetch={refetch} isLoading={isLoading} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Update;
|
@ -0,0 +1,439 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useUpdateOne } from "../../Hooks/Update";
|
||||
import {
|
||||
Form,
|
||||
Spin,
|
||||
Watermark,
|
||||
Space,
|
||||
Tabs,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
Button,
|
||||
Select,
|
||||
Upload,
|
||||
} from "antd";
|
||||
import { updateController } from "../../API/LayoutApi/update";
|
||||
import { UploadOutlined } from "@ant-design/icons";
|
||||
import Notfound from "../../Utils/Notfound";
|
||||
import { companyController } from "../../API/LayoutApi/companies";
|
||||
import { customerController } from "../../API/LayoutApi/customers";
|
||||
import { taskController } from "../../API/LayoutApi/tasks";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { role } from "../../App";
|
||||
// @ts-ignore
|
||||
import infoIcon from "../../assets/infoIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIconActive from "../../assets/infoIconActive.png";
|
||||
// @ts-ignore
|
||||
import attachmentIcon from "../../assets/attachmentIcon.png";
|
||||
// @ts-ignore
|
||||
import attachmentIconActive from "../../assets/attachmentIconActive.png";
|
||||
|
||||
const { Option } = Select;
|
||||
const TabPane = Tabs.TabPane;
|
||||
type params = {
|
||||
readonly id: any;
|
||||
};
|
||||
type MyObjectType = {
|
||||
[key: string | number]: any;
|
||||
};
|
||||
const UpdateEdit = () => {
|
||||
const { id } = useParams<params>();
|
||||
const { data, refetch, status }: MyObjectType = useUpdateOne(id);
|
||||
const onSubmit = async (value: any) => {
|
||||
if (value.status === "Done") {
|
||||
if (value.solution !== "") {
|
||||
await updateController.updatePut(value, id);
|
||||
refetch();
|
||||
document.location.replace("/#/updates/");
|
||||
} else {
|
||||
alert("solution is empty!!!!!!!!!!!!!!!!!!!!!");
|
||||
}
|
||||
} else {
|
||||
await updateController.updatePut(value, id);
|
||||
refetch();
|
||||
document.location.replace("/#/updates/");
|
||||
}
|
||||
};
|
||||
const admin_id = localStorage.getItem("admin_id");
|
||||
const [companyId, setCompanyId] = useState<any>(null);
|
||||
const [companyValue, setCompanyValue] = useState<any>();
|
||||
const [companyData, setCompanyData] = useState<MyObjectType>();
|
||||
const [customerId, setCustomerId] = useState<any>(null);
|
||||
const [customerValue, setCustomerValue] = useState<any>();
|
||||
const [customerData, setCustomerData] = useState<MyObjectType>();
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
if (data.company_id === null) {
|
||||
setCompanyId(null);
|
||||
}
|
||||
if (data.customer_id === null) {
|
||||
setCustomerId(null);
|
||||
}
|
||||
const companyIdFromData = data.company_id;
|
||||
const customerIdFromData = data.customer_id;
|
||||
setCompanyId(companyIdFromData);
|
||||
setCustomerId(customerIdFromData);
|
||||
}
|
||||
}, [data]);
|
||||
useEffect(() => {
|
||||
if (companyId !== null) {
|
||||
companyController.companyOne(companyId).then((CompanyData) => {
|
||||
setCompanyData(CompanyData);
|
||||
});
|
||||
}
|
||||
}, [companyId]);
|
||||
useEffect(() => {
|
||||
if (customerId !== null) {
|
||||
customerController.customerOne(customerId).then((CustomerData) => {
|
||||
setCustomerData(CustomerData);
|
||||
});
|
||||
}
|
||||
}, [customerId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (companyData && companyData.name) {
|
||||
setCompanyValue(companyData.name);
|
||||
}
|
||||
}, [companyData]);
|
||||
useEffect(() => {
|
||||
if (customerData && customerData.name) {
|
||||
setCustomerValue(customerData.name);
|
||||
}
|
||||
}, [customerData]);
|
||||
|
||||
const handleClickDelete = (id: any) => {
|
||||
if (id !== undefined) {
|
||||
taskController.deleteAttachmentController(id);
|
||||
}
|
||||
};
|
||||
|
||||
const [inCharge, setInChage] = useState<any>();
|
||||
useEffect(() => {
|
||||
if (data?.in_charge_id) {
|
||||
setInChage(data.in_charge_id);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const ClickDelete = () => {
|
||||
const shouldDelete = window.confirm(
|
||||
"Вы уверены, что хотите удалить эту задачу?"
|
||||
);
|
||||
if (shouldDelete && id !== undefined) {
|
||||
updateController.deleteUpdateController(id).then((data: any) => {
|
||||
document.location.replace(`/#/updates/`);
|
||||
});
|
||||
}
|
||||
};
|
||||
const [imgname, setImgname] = useState<any>([]);
|
||||
function handlePaste(event: any) {
|
||||
// Обработка вставки из буфера обмена
|
||||
const clipboardData = event.clipboardData || window.Clipboard;
|
||||
if (clipboardData && clipboardData.items.length > 0) {
|
||||
const clipboardItem = clipboardData.items[0];
|
||||
if (clipboardItem.kind === "file") {
|
||||
const file = clipboardItem.getAsFile();
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("shift_update_id", id);
|
||||
taskController.addTaskFile(formData).then((response) => {
|
||||
const n = [response.file];
|
||||
setImgname((prev: any) => [...prev, ...n]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const [activeTab, setActiveTab] = useState("1");
|
||||
return (
|
||||
<div>
|
||||
{role !== "Checker" || inCharge == admin_id || inCharge == null ? (
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Watermark style={{ height: "100%" }}>
|
||||
{status === "loading" ? (
|
||||
<Spin size="large" spinning={!data} />
|
||||
) : data ? (
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key)}
|
||||
>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
style={{ marginRight: 10 }}
|
||||
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||
alt=""
|
||||
/>
|
||||
Information
|
||||
</span>
|
||||
}
|
||||
key="1"
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Form
|
||||
name="newBasic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
{companyId !== null && (
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Company"
|
||||
>
|
||||
{companyValue !== undefined && (
|
||||
<Input
|
||||
defaultValue={companyValue}
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
{customerId !== null && (
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Driver"
|
||||
>
|
||||
{customerValue !== undefined && (
|
||||
<Input
|
||||
defaultValue={customerValue}
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Note"
|
||||
name="note"
|
||||
>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Solution"
|
||||
name="solution"
|
||||
>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Status"
|
||||
name="status"
|
||||
>
|
||||
<Select style={{ width: 120 }}>
|
||||
<Option value="New">New</Option>
|
||||
<Option value="In Progress">
|
||||
In Progress
|
||||
</Option>
|
||||
<Option value="Done">Done</Option>
|
||||
<Option value="Paper">Paper</Option>
|
||||
<Option value="Setup">Setup</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item>
|
||||
{role !== "Chceker" && (
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
onClick={ClickDelete}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
style={{ margin: 10 }}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Space>
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
style={{ marginRight: 10 }}
|
||||
src={
|
||||
activeTab === "2"
|
||||
? attachmentIconActive
|
||||
: attachmentIcon
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
Attachments
|
||||
</span>
|
||||
}
|
||||
key="2"
|
||||
>
|
||||
<div
|
||||
onPaste={(event) => handlePaste(event)}
|
||||
style={{ height: 800, width: 1000 }}
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Form
|
||||
name="basicFuck"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data.attachment_set[0] }}
|
||||
autoComplete="off"
|
||||
onFinish={onSubmit}
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={24}>
|
||||
<Form.Item wrapperCol={{ span: "100%" }}>
|
||||
{data.attachment_set.map((item: any) => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
background: "rgb(239 239 239)",
|
||||
padding: 15,
|
||||
borderRadius: 8,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
key={item.id}
|
||||
>
|
||||
<a
|
||||
style={{
|
||||
width: "20%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
alignSelf: "center",
|
||||
}}
|
||||
href={item.file_path}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src={item.file_path}
|
||||
alt=""
|
||||
style={{
|
||||
width: "30%",
|
||||
maxHeight: "200px",
|
||||
marginRight: 20,
|
||||
}}
|
||||
/>
|
||||
{item.file_name}
|
||||
</a>
|
||||
<Button
|
||||
onClick={() =>
|
||||
handleClickDelete(item.id)
|
||||
}
|
||||
type="primary"
|
||||
danger
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</Form.Item>
|
||||
<Form.Item label="File" name="attachment">
|
||||
<Upload.Dragger
|
||||
name="file"
|
||||
customRequest={({
|
||||
file,
|
||||
onSuccess,
|
||||
}: any) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("shift_update_id", id);
|
||||
taskController
|
||||
.addTaskFile(formData)
|
||||
.then(() => {
|
||||
onSuccess();
|
||||
})
|
||||
.catch((error) => {
|
||||
onSuccess(error);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<UploadOutlined
|
||||
style={{ color: "#b5f5ec" }}
|
||||
/>
|
||||
</p>
|
||||
<p
|
||||
className="ant-upload-text"
|
||||
style={{ color: "#b5f5ec" }}
|
||||
>
|
||||
Click or drag file to this area to upload
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
<p>{imgname.join(",\n")}</p>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Save
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Space>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Space>
|
||||
</Spin>
|
||||
) : (
|
||||
<Notfound />
|
||||
)}
|
||||
</Watermark>
|
||||
</Spin>
|
||||
) : (
|
||||
<div>
|
||||
<Spin size="large" spinning={!data}></Spin>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateEdit;
|
@ -0,0 +1,141 @@
|
||||
// // @ts-ignore
|
||||
// import closeIcon from "../../assets/closeIcon.png";
|
||||
// // @ts-ignore
|
||||
// import editIcon from "../../assets/editIcon.png";
|
||||
// // @ts-ignore
|
||||
// import deleteIcon from "../../assets/deleteIconRed.png";
|
||||
// // @ts-ignore
|
||||
// import attachmentIcon from "../../assets/attachmentIcon.png";
|
||||
// // @ts-ignore
|
||||
// import infoIcon from "../../assets/infoIcon.png";
|
||||
// // @ts-ignore
|
||||
// import uploadIcon from "../../assets/uploadIcon.png";
|
||||
// import { Tabs } from "antd";
|
||||
// import TabPane from "antd/es/tabs/TabPane";
|
||||
// import { TUpdate } from "../../types/Update/TUpdate";
|
||||
|
||||
// const UpdateModal = ({
|
||||
// modalOpen,
|
||||
// setModalOpen,
|
||||
// recordUpdate,
|
||||
// }: {
|
||||
// recordUpdate: TUpdate | undefined;
|
||||
// modalOpen: any;
|
||||
// setModalOpen: any;
|
||||
// }) => {
|
||||
// const handleCancel = () => {
|
||||
// setModalOpen(!modalOpen);
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <div className="TaskModal">
|
||||
// <div className="TaskModal-header">
|
||||
// <div className="TaskModal-title">
|
||||
// <p className="p-driver">{recordUpdate?.company}</p>
|
||||
// <p
|
||||
// style={{ marginLeft: 12 }}
|
||||
// className={`status-${recordUpdate?.status}`}
|
||||
// >
|
||||
// {recordUpdate?.status}
|
||||
// </p>
|
||||
// </div>
|
||||
// <div className="mdoal-actions">
|
||||
// <button className="btn-modal-action">
|
||||
// <img src={editIcon} alt="" />
|
||||
// Edit
|
||||
// </button>
|
||||
// <button style={{ marginLeft: 12 }} className="btn-modal-action">
|
||||
// <img src={uploadIcon} alt="" />
|
||||
// Upload file
|
||||
// </button>
|
||||
// <button style={{ marginLeft: 12 }} className="btn-modal-action">
|
||||
// <img src={deleteIcon} alt="" />
|
||||
// Delete
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={handleCancel}
|
||||
// style={{ marginLeft: 20 }}
|
||||
// className="btn-modal-action"
|
||||
// >
|
||||
// <img style={{ margin: 2 }} src={closeIcon} alt="" />
|
||||
// </button>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className="TaskModal-content">
|
||||
// <Tabs>
|
||||
// <TabPane
|
||||
// tab={
|
||||
// <span style={{ display: "flex", alignItems: "center" }}>
|
||||
// <img style={{ marginRight: 10 }} src={infoIcon} alt="" />
|
||||
// Information
|
||||
// </span>
|
||||
// }
|
||||
// key="1"
|
||||
// >
|
||||
// <div className="info-div">
|
||||
// <p
|
||||
// style={{
|
||||
// fontSize: 18,
|
||||
// fontWeight: 700,
|
||||
// lineHeight: "24px",
|
||||
// letterSpacing: "-0.02em",
|
||||
// marginBottom: 16,
|
||||
// }}
|
||||
// >
|
||||
// Information
|
||||
// </p>
|
||||
// <div className="info-body">
|
||||
// <tr>
|
||||
// <p className="sub">Comapany</p>
|
||||
// <p className="info">{recordUpdate?.company}</p>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <p className="sub">Driver</p>
|
||||
// <p className="info">{recordUpdate?.customer}</p>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <p className="sub">Created by</p>
|
||||
// <p className="info">{recordUpdate?.provider}</p>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <p className="sub">Created at</p>
|
||||
// <p className="info">{recordUpdate?.created_at}</p>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <p className="sub">Completed_by</p>
|
||||
// <p className="info">{recordUpdate?.executor}</p>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <p className="sub">Solution</p>
|
||||
// <p className="info">{recordUpdate?.solution}</p>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <p className="sub">Note</p>
|
||||
// <p className="info">{recordUpdate?.note}</p>
|
||||
// </tr>
|
||||
// </div>
|
||||
// </div>
|
||||
// </TabPane>
|
||||
// <TabPane
|
||||
// tab={
|
||||
// <span style={{ display: "flex", alignItems: "center" }}>
|
||||
// <img style={{ marginRight: 10 }} src={attachmentIcon} alt="" />
|
||||
// Attachments
|
||||
// </span>
|
||||
// }
|
||||
// key="2"
|
||||
// ></TabPane>
|
||||
// </Tabs>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default UpdateModal;
|
||||
import React from "react";
|
||||
|
||||
const UpdateModal = () => {
|
||||
return <div>UpdateModal</div>;
|
||||
};
|
||||
|
||||
export default UpdateModal;
|
@ -0,0 +1,300 @@
|
||||
import { Space, Table, Tooltip } from "antd";
|
||||
import moment from "moment";
|
||||
import { useCompanyData } from "../../Hooks/Companies";
|
||||
import { useCustomerData } from "../../Hooks/Customers";
|
||||
import { useUserData } from "../../Hooks/Users";
|
||||
import { updateController } from "../../API/LayoutApi/update";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { TUpdate } from "../../types/Update/TUpdate";
|
||||
import { useEffect, useState } from "react";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
// @ts-ignore
|
||||
import pin from "../../assets/pinicon.png";
|
||||
// @ts-ignore
|
||||
import unpin from "../../assets/unpinicon.png";
|
||||
// @ts-ignore
|
||||
import zippy from "../../assets/zippyicon.svg";
|
||||
// @ts-ignore
|
||||
import evo from "../../assets/evoicon.png";
|
||||
// @ts-ignore
|
||||
import zeelog from "../../assets/zeelogicon.svg";
|
||||
// @ts-ignore
|
||||
import ontime from "../../assets/ontimeicon.svg";
|
||||
// @ts-ignore
|
||||
import tt from "../../assets/tticon.svg";
|
||||
const UpdateTable = ({
|
||||
data = [],
|
||||
isLoading,
|
||||
refetch,
|
||||
}: {
|
||||
data: TUpdate[] | undefined;
|
||||
isLoading?: boolean;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TUpdate[], unknown>>;
|
||||
}) => {
|
||||
const CompanyData = useCompanyData({});
|
||||
const CustomerData = useCustomerData({});
|
||||
const AdminData = useUserData({});
|
||||
|
||||
const [isTextSelected, setIsTextSelected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleSelectionChange = () => {
|
||||
const selection = window.getSelection();
|
||||
setIsTextSelected(selection !== null && selection.toString() !== "");
|
||||
};
|
||||
|
||||
document.addEventListener("selectionchange", handleSelectionChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("selectionchange", handleSelectionChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const Row = (record: TUpdate, event: any) => {
|
||||
if (isTextSelected) {
|
||||
return;
|
||||
}
|
||||
if (event.target.classList.contains("ant-table-cell")) {
|
||||
document.location.replace(`/#/updates/${record.id}`);
|
||||
}
|
||||
};
|
||||
const getImageSource = (source: string) => {
|
||||
switch (source) {
|
||||
case "Zippy":
|
||||
return zippy;
|
||||
case "EVO":
|
||||
return evo;
|
||||
case "Ontime":
|
||||
return ontime;
|
||||
case "Zeelog":
|
||||
return zeelog;
|
||||
case "TT":
|
||||
return tt;
|
||||
default:
|
||||
return tt;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
onRow={(record) => ({
|
||||
onClick: (event) => Row(record, event),
|
||||
})}
|
||||
dataSource={data?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
...u,
|
||||
company_name: CompanyData?.data?.find(
|
||||
(company: any) => company.id === u.company_id
|
||||
)?.name,
|
||||
customer_name: CustomerData?.data?.find(
|
||||
(customer: any) => customer.id === u.customer_id
|
||||
)?.name,
|
||||
in_charge_name: AdminData?.data?.find(
|
||||
(admin: any) => admin.id === u.provider_id
|
||||
)?.username,
|
||||
executor_name: AdminData?.data?.find(
|
||||
(admin: any) => admin.id === u.executor_id
|
||||
)?.username,
|
||||
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||
"DD.MM.YYYY HH:mm"
|
||||
),
|
||||
action: { ...u },
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
width: "5%",
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
dataIndex: "company",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
width: "10%",
|
||||
render: (text: any, record: any) => (
|
||||
<Tooltip placement="topLeft" title={text?.name}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
{text?.source && (
|
||||
<img
|
||||
src={getImageSource(text?.source)}
|
||||
alt=""
|
||||
style={{ width: 20, height: 20, marginRight: 10 }}
|
||||
/>
|
||||
)}
|
||||
{text?.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Driver",
|
||||
dataIndex: "customer",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
width: "10%",
|
||||
render: (item: { name: string }) => (
|
||||
<Tooltip placement="topLeft" title={item?.name}>
|
||||
{item?.name}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Created by",
|
||||
dataIndex: "provider ",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
responsive: ["xl"],
|
||||
width: "10%",
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Complited by",
|
||||
dataIndex: "executor",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
responsive: ["lg"],
|
||||
width: "10%",
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
width: "10%",
|
||||
render: (status: string) => (
|
||||
<span>
|
||||
{status === "Done" && <p className="status-done">Done</p>}
|
||||
{status === "Checking" && (
|
||||
<p className="status-in-progress">Checking</p>
|
||||
)}
|
||||
{status === "New" && <p className="status-new">New</p>}
|
||||
{status === "Setup" && <p className="status-setup">Setup</p>}
|
||||
{status === "Paper" && <p className="status-paper">Paper</p>}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Note",
|
||||
dataIndex: "note",
|
||||
width: "10%",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Solution",
|
||||
dataIndex: "solution",
|
||||
width: "10%",
|
||||
responsive: ["lg"],
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Created at",
|
||||
dataIndex: "created",
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
responsive: ["xxl"],
|
||||
width: "10%",
|
||||
render: (note: string) => (
|
||||
<Tooltip placement="topLeft" title={note}>
|
||||
{note}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
dataIndex: "action",
|
||||
width: "8%",
|
||||
render: (record: TUpdate) => {
|
||||
return (
|
||||
<div className="notedit">
|
||||
{record.status !== "Done" && (
|
||||
<Space>
|
||||
{record.is_pinned ? (
|
||||
<button
|
||||
className="btn-unpin"
|
||||
onClick={(e) => {
|
||||
const updateData = {
|
||||
is_pinned: false,
|
||||
};
|
||||
updateController
|
||||
.updatePatch(updateData, record.id)
|
||||
.then(() => {
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={unpin} alt="" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="btn-pin"
|
||||
style={{ paddingTop: 2 }}
|
||||
onClick={(e) => {
|
||||
const updateData = {
|
||||
is_pinned: true,
|
||||
};
|
||||
updateController
|
||||
.updatePatch(updateData, record.id)
|
||||
.then(() => {
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img src={pin} alt="" />
|
||||
</button>
|
||||
)}
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateTable;
|
@ -0,0 +1,87 @@
|
||||
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||
import { useRoleData } from "../../Hooks/Role";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { TUser } from "../../types/User/TUser";
|
||||
import { inviteVerify } from "../../API/auth/invite";
|
||||
|
||||
const AddUser = ({
|
||||
open,
|
||||
setOpen,
|
||||
refetch,
|
||||
}: {
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TUser[], unknown>>;
|
||||
open: boolean;
|
||||
setOpen(open: boolean): void;
|
||||
}) => {
|
||||
const [form] = FormAnt.useForm();
|
||||
const handleCancel = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
const roleData = useRoleData();
|
||||
const filteredRoleData = roleData?.data?.filter(role => role.name !== 'Owner');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Add user"
|
||||
okText="Create"
|
||||
cancelText="Cancel"
|
||||
onCancel={handleCancel}
|
||||
onOk={() => {
|
||||
form.validateFields().then(async (values) => {
|
||||
if (typeof values.groups === "number") {
|
||||
values.groups = [values.groups];
|
||||
}
|
||||
form.resetFields();
|
||||
delete values.Confirm;
|
||||
inviteVerify(values);
|
||||
setOpen(!open);
|
||||
refetch();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormAnt
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ modifier: "public" }}
|
||||
>
|
||||
<FormAnt.Item
|
||||
label="E-mail"
|
||||
name="email"
|
||||
rules={[
|
||||
{ required: true },
|
||||
{
|
||||
type: "email",
|
||||
message: "The input is not valid E-mail!",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</FormAnt.Item>
|
||||
<FormAnt.Item
|
||||
label="Role"
|
||||
name="role_id"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select
|
||||
options={filteredRoleData?.map(role => ({
|
||||
label: role.name,
|
||||
value: role.id,
|
||||
}))}
|
||||
/>
|
||||
</FormAnt.Item>
|
||||
</FormAnt>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUser;
|
@ -0,0 +1,191 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useUserOne } from "../../Hooks/Users";
|
||||
import {
|
||||
Form,
|
||||
Spin,
|
||||
Watermark,
|
||||
Space,
|
||||
Tabs,
|
||||
Row,
|
||||
Col,
|
||||
Input,
|
||||
Button,
|
||||
Select,
|
||||
} from "antd";
|
||||
import { userController } from "../../API/LayoutApi/users";
|
||||
// @ts-ignore
|
||||
import infoIcon from "../../assets/infoIcon.png";
|
||||
// @ts-ignore
|
||||
import infoIconActive from "../../assets/infoIconActive.png";
|
||||
import Notfound from "../../Utils/Notfound";
|
||||
import { useTeamData } from "../../Hooks/Teams";
|
||||
import { useRoleData } from "../../Hooks/Role";
|
||||
import { role } from "../../App";
|
||||
import { useState } from "react";
|
||||
const TabPane = Tabs.TabPane;
|
||||
type params = {
|
||||
readonly id: string;
|
||||
};
|
||||
type MyObjectType = {
|
||||
[key: string | number]: any;
|
||||
};
|
||||
const UserEdit = () => {
|
||||
const { id } = useParams<params>();
|
||||
|
||||
const { data, refetch, status }: MyObjectType = useUserOne(id);
|
||||
|
||||
const onSubmit = async (value: any) => {
|
||||
id && (await userController.userPatch(value, id));
|
||||
refetch();
|
||||
document.location.replace("/#/users/");
|
||||
};
|
||||
const TeamData = useTeamData("");
|
||||
const noTeamOption = { label: " - - - - - -", value: "" };
|
||||
const TeamOption: { label: string; value: any }[] | undefined =
|
||||
TeamData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}));
|
||||
if (TeamOption) {
|
||||
TeamOption.unshift(noTeamOption);
|
||||
}
|
||||
const roleData = useRoleData();
|
||||
|
||||
const ClickDelete = () => {
|
||||
const shouldDelete = window.confirm(
|
||||
"Вы уверены, что хотите удалить этот админ?"
|
||||
);
|
||||
if (shouldDelete && id !== undefined) {
|
||||
userController.deleteUserController(id).then(() => {
|
||||
document.location.replace(`/#/users`);
|
||||
});
|
||||
}
|
||||
};
|
||||
const [activeTab, setActiveTab] = useState("1");
|
||||
return (
|
||||
<div>
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Watermark style={{ height: "100%" }}>
|
||||
{status === "loading" ? (
|
||||
<Spin size="large" spinning={!data} />
|
||||
) : data ? (
|
||||
<Spin size="large" spinning={!data}>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => setActiveTab(key)}
|
||||
>
|
||||
<TabPane
|
||||
tab={
|
||||
<span style={{ display: "flex", alignItems: "center" }}>
|
||||
<img
|
||||
style={{ marginRight: 10 }}
|
||||
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||
alt=""
|
||||
/>
|
||||
Information
|
||||
</span>
|
||||
}
|
||||
key="1"
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="middle"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
layout="vertical"
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{ ...data }}
|
||||
onFinish={onSubmit}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Row gutter={[16, 10]}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="First name"
|
||||
name="first_name"
|
||||
>
|
||||
<Input readOnly />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Last name"
|
||||
name="last_name"
|
||||
>
|
||||
<Input readOnly />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Username"
|
||||
name="username"
|
||||
>
|
||||
<Input readOnly />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Team"
|
||||
name="team_id"
|
||||
>
|
||||
<Select options={TeamOption} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item
|
||||
wrapperCol={{ span: "100%" }}
|
||||
label="Role"
|
||||
name="role_id"
|
||||
>
|
||||
<Select
|
||||
options={roleData?.data?.map((item) => ({
|
||||
label: item?.name,
|
||||
value: item?.id,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item>
|
||||
{role !== "Checker" && (
|
||||
<Button
|
||||
onClick={() => ClickDelete()}
|
||||
type="primary"
|
||||
style={{ marginRight: 10 }}
|
||||
danger
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Space>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Space>
|
||||
</Spin>
|
||||
) : (
|
||||
<Notfound />
|
||||
)}
|
||||
</Watermark>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserEdit;
|
@ -0,0 +1,108 @@
|
||||
import { Table, Tag } from "antd";
|
||||
import { useTeamData } from "../../Hooks/Teams";
|
||||
import { TUser } from "../../types/User/TUser";
|
||||
import {
|
||||
QueryObserverResult,
|
||||
RefetchOptions,
|
||||
RefetchQueryFilters,
|
||||
} from "react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
// @ts-ignore
|
||||
import tagIcon from "../../assets/tagIcon.png";
|
||||
const UserTable = ({
|
||||
data,
|
||||
isLoading,
|
||||
refetch,
|
||||
}: {
|
||||
data: TUser[] | undefined;
|
||||
isLoading: boolean;
|
||||
refetch: <TPageData>(
|
||||
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||
) => Promise<QueryObserverResult<TUser[], unknown>>;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const TeamData = useTeamData("");
|
||||
|
||||
const Row = (record: TUser) => {
|
||||
let isTextSelected = false;
|
||||
document.addEventListener("selectionchange", () => {
|
||||
const selection = window.getSelection();
|
||||
if (selection !== null && selection.toString() !== "") {
|
||||
isTextSelected = true;
|
||||
} else {
|
||||
isTextSelected = false;
|
||||
}
|
||||
});
|
||||
return {
|
||||
onClick: () => {
|
||||
if (isTextSelected) {
|
||||
return;
|
||||
}
|
||||
navigate(`/users/${record.id}`);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
onRow={(record) => Row(record)}
|
||||
dataSource={data?.map((u, i) => ({
|
||||
no: i + 1,
|
||||
team: TeamData?.data?.map((team: any) => {
|
||||
if (team.id === u?.team_id) {
|
||||
return team?.name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
action: { id: u.id },
|
||||
...u,
|
||||
}))}
|
||||
loading={isLoading}
|
||||
size="middle"
|
||||
columns={[
|
||||
{
|
||||
title: <img src={tagIcon} alt="" />,
|
||||
dataIndex: "no",
|
||||
},
|
||||
{
|
||||
title: "Username",
|
||||
dataIndex: "username",
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
dataIndex: "team",
|
||||
},
|
||||
{
|
||||
title: "Is Active",
|
||||
dataIndex: "is_active",
|
||||
render: (tag: boolean) => (
|
||||
<Tag color={tag ? "geekblue" : "red"}>
|
||||
{tag ? "True" : "False"}
|
||||
</Tag>
|
||||
),
|
||||
filters: [
|
||||
{
|
||||
text: "True",
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
text: "False",
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
onFilter: (value: any, record: any) => {
|
||||
return record.is_active === value;
|
||||
},
|
||||
},
|
||||
]}
|
||||
rowClassName={(record, index) =>
|
||||
index % 2 === 0 ? "odd-row" : "even-row"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTable;
|
@ -0,0 +1,63 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useUserData } from "../../Hooks/Users";
|
||||
import AddUser from "./AddUser";
|
||||
import UserTable from "./UserTable";
|
||||
// @ts-ignore
|
||||
import IconSearch from "../../assets/searchIcon.png";
|
||||
//@ts-ignore
|
||||
import addicon from "../../assets/addiconpng.png";
|
||||
|
||||
const User = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const { data, refetch, isLoading } = useUserData({
|
||||
name: search,
|
||||
team: "",
|
||||
});
|
||||
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 theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||
return (
|
||||
<div>
|
||||
{open && <AddUser open={open} setOpen={setOpen} refetch={refetch} />}
|
||||
<div className="header d-flex">
|
||||
<p className="title">Users</p>
|
||||
<button
|
||||
className="btn-add d-flex"
|
||||
style={{ marginRight: 0 }}
|
||||
onClick={showModal}
|
||||
>
|
||||
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||
Invite User
|
||||
</button>
|
||||
</div>
|
||||
<div className="filter d-flex">
|
||||
<div className="search-div">
|
||||
<img src={IconSearch} alt="" />
|
||||
<input
|
||||
className={`search-input-${theme}`}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<UserTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default User;
|
@ -0,0 +1,20 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { callController } from "../../API/LayoutApi/callrequests";
|
||||
|
||||
export const useCallData = ({ status }: { status: string }) => {
|
||||
return useQuery(
|
||||
[`callback-requests/`, { status }],
|
||||
() => callController.read({ status }),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// export const useCompanyOne = (id: number | undefined) => {
|
||||
// return useQuery(
|
||||
// [`company/${id}/`, id],
|
||||
// () => companyController.companyOne(id),
|
||||
// { refetchOnWindowFocus: false }
|
||||
// );
|
||||
// };
|
@ -0,0 +1,26 @@
|
||||
import { useQuery } from "react-query";
|
||||
import {
|
||||
TCompanyGetParams,
|
||||
companyController,
|
||||
} from "../../API/LayoutApi/companies";
|
||||
|
||||
export const useCompanyData = ({
|
||||
name,
|
||||
page,
|
||||
is_active,
|
||||
}: TCompanyGetParams) => {
|
||||
return useQuery(
|
||||
[`companies/`, name, page, is_active],
|
||||
() =>
|
||||
companyController.read({ name: name, page: page, is_active: is_active }),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useCompanyOne = (id: number | undefined) => {
|
||||
return useQuery(
|
||||
[`company/${id}/`, id],
|
||||
() => companyController.companyOne(id),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
import { useQuery } from "react-query";
|
||||
import {
|
||||
TCustomerByCompanyGetParams,
|
||||
TCustomerGetParams,
|
||||
customerController,
|
||||
} from "../../API/LayoutApi/customers";
|
||||
|
||||
export const useCustomerData = ({
|
||||
name,
|
||||
page,
|
||||
is_active,
|
||||
pageSize,
|
||||
}: TCustomerGetParams) => {
|
||||
return useQuery(
|
||||
[`customers/`, name, page, is_active, pageSize],
|
||||
() =>
|
||||
customerController.read({
|
||||
name: name,
|
||||
page: page,
|
||||
is_active: is_active,
|
||||
pageSize: pageSize,
|
||||
}),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useCustomerByComanyData = ({
|
||||
name,
|
||||
id,
|
||||
}: TCustomerByCompanyGetParams) => {
|
||||
return useQuery(
|
||||
[`customers-by-company/${id}`, name],
|
||||
() => customerController.customerByCompany(id, name),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useCustomerOne = (Id: number | undefined) => {
|
||||
return useQuery(
|
||||
[`customer/${Id}/`, Id],
|
||||
() => customerController.customerOne(Id),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { TMyTaskHistoryGetParams, prof } from "../../API/LayoutApi/profile";
|
||||
|
||||
export const useMystatsData = ({
|
||||
start_date,
|
||||
end_date,
|
||||
}: TMyTaskHistoryGetParams) => {
|
||||
return useQuery(
|
||||
[`stats/my-stats/`, start_date, end_date],
|
||||
() => prof.read({ start_date, end_date }),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
}
|
||||
);
|
||||
};
|
||||
// export const useMystatsData = ({
|
||||
// start_date,
|
||||
// end_date,
|
||||
// }: TMyTaskHistoryGetParams) => {
|
||||
// return useQuery(
|
||||
// [`stats/my-stats/`, start_date, end_date],
|
||||
// () => prof.read({ start_date, end_date }),
|
||||
// {
|
||||
// refetchOnWindowFocus: false,
|
||||
// }
|
||||
// );
|
||||
// };
|
||||
|
||||
export const useProfData = () => {
|
||||
return useQuery([`users/my-profile/`], () => prof.self(), {
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const useMyHistoryData = ({
|
||||
start_date,
|
||||
end_date,
|
||||
}: TMyTaskHistoryGetParams) => {
|
||||
return useQuery(
|
||||
[`my-task-history/`, start_date, end_date],
|
||||
() => prof.myTaskHistory({ start_date, end_date }),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { useQuery } from "react-query";
|
||||
import {
|
||||
requestsController,
|
||||
TRequestsGetParams,
|
||||
} from "../../API/LayoutApi/requests";
|
||||
|
||||
export const useRequestsData = ({ search, status }: TRequestsGetParams) => {
|
||||
return useQuery(
|
||||
[`driver-requests/`, { search, status }],
|
||||
() => requestsController.read({ search, status }),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useRequestsOne = (userId: number | string | undefined): any => {
|
||||
return useQuery(
|
||||
[`driver-requests/${userId || "all"}`, userId],
|
||||
() => requestsController.requestsOne(userId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { roleController } from "../../API/LayoutApi/role";
|
||||
|
||||
export const useRoleData = () => {
|
||||
return useQuery(
|
||||
[`users/roles/`],
|
||||
() => roleController.read(),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useRoleOne = (id: string) => {
|
||||
return useQuery(
|
||||
[`users/role/${id}`, id],
|
||||
() => roleController.roleOne(id),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { serviceController } from "../../API/LayoutApi/services";
|
||||
|
||||
export const useServiceData = () => {
|
||||
return useQuery(
|
||||
[`services/`],
|
||||
() => serviceController.read(),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useServiceOne = (serviceId: number | undefined) => {
|
||||
return useQuery(
|
||||
[`service/${serviceId || "all"}`, serviceId],
|
||||
() => serviceController.serviceOne(serviceId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { TStatGetParams, statController } from "../../API/LayoutApi/statistic";
|
||||
|
||||
export const useStatsData = ({search, team, start_date, end_date}: TStatGetParams) => {
|
||||
return useQuery(
|
||||
[`stats/all-users/`, search, team, start_date, end_date],
|
||||
() => statController.read({search, team, start_date, end_date}),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
export const useStatTeamData = ({search, start_date, end_date}: TStatGetParams) => {
|
||||
return useQuery(
|
||||
[`stats/all-teams/`, search, start_date, end_date],
|
||||
() => statController.team({search, start_date, end_date}),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useCreatorsData = ({ start_date, end_date}: TStatGetParams) => {
|
||||
return useQuery(
|
||||
[`stats/task-creators/`, start_date, end_date],
|
||||
() => statController.creators({ start_date, end_date}),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useCardData = ({ start_date, end_date}: TStatGetParams) => {
|
||||
return useQuery(
|
||||
[`stats/tasks-comparison/`, start_date, end_date],
|
||||
() => statController.cards({ start_date, end_date}),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useStatOne = (
|
||||
statId: number | string | undefined
|
||||
): any => {
|
||||
return useQuery(
|
||||
[`stat/${statId || "all"}`, statId],
|
||||
() => statController.statOne(statId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { TTasksGetParams, taskController } from "../../API/LayoutApi/tasks";
|
||||
|
||||
export const useTasks = ({ search, status, team, page }: TTasksGetParams) => {
|
||||
return useQuery(
|
||||
[`tasks/`, search, status, team, page],
|
||||
() => taskController.read({ search, status, team, page }),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useTaskOne = (taskId: number) => {
|
||||
return useQuery(
|
||||
[`task/${taskId}/`, taskId],
|
||||
() => taskController.taskOne(taskId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useTaskHistory = (Id: number | undefined) => {
|
||||
return useQuery(
|
||||
[`customer/${Id}/`, Id],
|
||||
() => taskController.getHistory(Id),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { teamController } from "../../API/LayoutApi/teams";
|
||||
|
||||
export const useTeamData = (name: string) => {
|
||||
return useQuery(
|
||||
[`teams/?name=${name}/`, name],
|
||||
() => teamController.read(name),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useTeamOne = (
|
||||
teamId: number | string | undefined
|
||||
): any => {
|
||||
return useQuery(
|
||||
[`team/${teamId || "all"}`, teamId],
|
||||
() => teamController.teamOne(teamId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { updateController } from "../../API/LayoutApi/update";
|
||||
|
||||
// export const useUpdateData = (status: string): any => {
|
||||
// return useQuery(
|
||||
// [`updates/${status}`, status],
|
||||
// () => updateController.read(status),
|
||||
// { refetchOnWindowFocus: false }
|
||||
// );
|
||||
// };
|
||||
|
||||
export const useUpdateData = (status: string) => {
|
||||
return useQuery(
|
||||
[`shift-updates`, status],
|
||||
() => updateController.read(status),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useUpdateOne = (updateId: number | string | undefined): any => {
|
||||
return useQuery(
|
||||
[`update/${updateId || "all"}`, updateId],
|
||||
() => updateController.updateOne(updateId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import { useQuery } from "react-query";
|
||||
import { TUsersGetParams, userController } from "../../API/LayoutApi/users";
|
||||
|
||||
export const useUserData = ({name, team, role}: TUsersGetParams) => {
|
||||
return useQuery(
|
||||
[`users/admins/`, {name, team, role}],
|
||||
() => userController.read({name, team, role}),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useUserOne = (
|
||||
userId: number | string | undefined
|
||||
): any => {
|
||||
return useQuery(
|
||||
[`user/${userId || "all"}`, userId],
|
||||
() => userController.userOne(userId),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
||||
|
||||
export const useCheckUser = (
|
||||
username: string
|
||||
): any => {
|
||||
return useQuery(
|
||||
[`user/${username}/`],
|
||||
() => userController.CheckUsername(username),
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
const NotInvitedYet = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotInvitedYet
|
@ -0,0 +1,21 @@
|
||||
|
||||
import { Button, Result } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
const Notfound = () => {
|
||||
return (
|
||||
<div>
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, the page you visited does not exist."
|
||||
extra={
|
||||
<Button type="primary">
|
||||
<Link to="/">Come back</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notfound;
|
@ -0,0 +1,103 @@
|
||||
export const common = [
|
||||
"123456",
|
||||
"password",
|
||||
"12345678",
|
||||
"qwerty",
|
||||
"123456789",
|
||||
"12345",
|
||||
"1234",
|
||||
"111111",
|
||||
"1234567",
|
||||
"dragon",
|
||||
"123123",
|
||||
"baseball",
|
||||
"abc123",
|
||||
"football",
|
||||
"monkey",
|
||||
"letmein",
|
||||
"696969",
|
||||
"shadow",
|
||||
"master",
|
||||
"666666",
|
||||
"qwertyuiop",
|
||||
"123321",
|
||||
"mustang",
|
||||
"1234567890",
|
||||
"michael",
|
||||
"654321",
|
||||
"pussy",
|
||||
"superman",
|
||||
"1qaz2wsx",
|
||||
"7777777",
|
||||
"fuckyou",
|
||||
"121212",
|
||||
"000000",
|
||||
"qazwsx",
|
||||
"123qwe",
|
||||
"killer",
|
||||
"trustno1",
|
||||
"jordan",
|
||||
"jennifer",
|
||||
"zxcvbnm",
|
||||
"asdfgh",
|
||||
"hunter",
|
||||
"buster",
|
||||
"soccer",
|
||||
"harley",
|
||||
"batman",
|
||||
"andrew",
|
||||
"tigger",
|
||||
"sunshine",
|
||||
"iloveyou",
|
||||
"fuckme",
|
||||
"2000",
|
||||
"charlie",
|
||||
"robert",
|
||||
"thomas",
|
||||
"hockey",
|
||||
"ranger",
|
||||
"daniel",
|
||||
"starwars",
|
||||
"klaster",
|
||||
"112233",
|
||||
"george",
|
||||
"asshole",
|
||||
"computer",
|
||||
"michelle",
|
||||
"jessica",
|
||||
"pepper",
|
||||
"1111",
|
||||
"zxcvbn",
|
||||
"555555",
|
||||
"11111111",
|
||||
"131313",
|
||||
"freedom",
|
||||
"777777",
|
||||
"pass",
|
||||
"fuck",
|
||||
"maggie",
|
||||
"159753",
|
||||
"aaaaaa",
|
||||
"ginger",
|
||||
"princess",
|
||||
"joshua",
|
||||
"cheese",
|
||||
"amanda",
|
||||
"summer",
|
||||
"love",
|
||||
"ashley",
|
||||
"6969",
|
||||
"nicole",
|
||||
"chelsea",
|
||||
"biteme",
|
||||
"matthew",
|
||||
"access",
|
||||
"yankees",
|
||||
"987654321",
|
||||
"dallas",
|
||||
"austin",
|
||||
"thunder",
|
||||
"taylor",
|
||||
"matrix",
|
||||
"minecraft",
|
||||
];
|
@ -0,0 +1,3 @@
|
||||
export const clear_local_storage = (): void => {
|
||||
localStorage.clear();
|
||||
};
|
@ -0,0 +1,234 @@
|
||||
import { MenuProps } from "antd";
|
||||
import { Link } from "react-router-dom";
|
||||
import Company from "../Components/Companies/Companies";
|
||||
import CompanyEdit from "../Components/Companies/CompaniesEdit";
|
||||
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 Task from "../Components/Tasks/Tasks";
|
||||
import TeamEdit from "../Components/Teams/TeamEdit";
|
||||
import Team from "../Components/Teams/Teams";
|
||||
import User from "../Components/Users/Users";
|
||||
import UserEdit from "../Components/Users/UserEdit";
|
||||
import MenuItem from "antd/es/menu/MenuItem";
|
||||
import Stat from "../Components/Statistics/Statistic";
|
||||
import Profile from "../Components/Profile/Profile";
|
||||
import Update from "../Components/Updates/Update";
|
||||
import UpdateEdit from "../Components/Updates/UpdateEdit";
|
||||
// @ts-ignore
|
||||
import taskIcon from "../assets/tasknavicon.png";
|
||||
// @ts-ignore
|
||||
import companyIcon from "../assets/companynavicon.png";
|
||||
// @ts-ignore
|
||||
import serviceIcon from "../assets/servicenavicon.png";
|
||||
// @ts-ignore
|
||||
import teamIcon from "../assets/teamnavicon.png";
|
||||
// @ts-ignore
|
||||
import statisticIcon from "../assets/statnavicon.png";
|
||||
// @ts-ignore
|
||||
import updateIcon from "../assets/updatenavicon.png";
|
||||
// @ts-ignore
|
||||
import userIcon from "../assets/usernavicon.png";
|
||||
// @ts-ignore
|
||||
import driverIcon from "../assets/customersIcon.png";
|
||||
// @ts-ignore
|
||||
import requestIcon from "../assets/requestIcon.png";
|
||||
// @ts-ignore
|
||||
import callIcon from "../assets/callIcon.png";
|
||||
import Requests from "../Components/Requests/Requests";
|
||||
import Call from "../Components/CallRequests/Call";
|
||||
const loc: any = localStorage.getItem("user");
|
||||
const role = JSON.parse(loc)?.role;
|
||||
|
||||
type MenuItem = Required<MenuProps>["items"][number];
|
||||
|
||||
function getItem(
|
||||
label: React.ReactNode,
|
||||
key: React.Key,
|
||||
icon?: React.ReactNode,
|
||||
children?: MenuItem[]
|
||||
): MenuItem {
|
||||
return {
|
||||
key,
|
||||
icon,
|
||||
children,
|
||||
label,
|
||||
} as MenuItem;
|
||||
}
|
||||
|
||||
export const allMenu: MenuItem[] = [
|
||||
getItem(<Link to="/">Tasks</Link>, "/", <img alt="" src={taskIcon} />),
|
||||
getItem(
|
||||
<Link to="companies/">Companies</Link>,
|
||||
"companies/",
|
||||
<img alt="" src={companyIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="customers/">Drivers</Link>,
|
||||
"customers/",
|
||||
<img alt="" src={driverIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="services/">Services</Link>,
|
||||
"services/",
|
||||
<img alt="" src={serviceIcon} />
|
||||
),
|
||||
];
|
||||
|
||||
if (role === "Tech Support") {
|
||||
allMenu.push(
|
||||
getItem(
|
||||
<Link to="teams/">Teams</Link>,
|
||||
"teams/",
|
||||
<img alt="" src={teamIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="updates/">Updates</Link>,
|
||||
"updates/",
|
||||
<img alt="" src={updateIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="requests/">Driver Requests</Link>,
|
||||
"requests/",
|
||||
<img alt="" src={requestIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="call/">Call Requests</Link>,
|
||||
"call/",
|
||||
<img alt="" src={callIcon} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (role === "Owner") {
|
||||
allMenu.push(
|
||||
getItem(
|
||||
<Link to="users/">Users</Link>,
|
||||
"users/",
|
||||
<img alt="" src={userIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="teams/">Teams</Link>,
|
||||
"teams/",
|
||||
<img alt="" src={teamIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="stats/">Statistics</Link>,
|
||||
"stats/",
|
||||
<img alt="" src={statisticIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="updates/">Updates</Link>,
|
||||
"updates/",
|
||||
<img alt="" src={updateIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="requests/">Driver Requests</Link>,
|
||||
"requests/",
|
||||
<img alt="" src={requestIcon} />
|
||||
),
|
||||
getItem(
|
||||
<Link to="call/">Call Requests</Link>,
|
||||
"call/",
|
||||
<img alt="" src={callIcon} />
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
type TItems = {
|
||||
path: string;
|
||||
component: JSX.Element;
|
||||
key: string;
|
||||
};
|
||||
|
||||
export const mainItems: TItems[] = [
|
||||
{
|
||||
path: "/companies/",
|
||||
component: <Company />,
|
||||
key: "/companies/",
|
||||
},
|
||||
{
|
||||
path: "/companies/:id",
|
||||
component: <CompanyEdit />,
|
||||
key: "/company/:id",
|
||||
},
|
||||
{
|
||||
path: "/customers/",
|
||||
component: <Customer />,
|
||||
key: "/customers/",
|
||||
},
|
||||
{
|
||||
path: "/customers/:id/",
|
||||
component: <CustomerEdit />,
|
||||
key: "/cusotmer/:id/",
|
||||
},
|
||||
{
|
||||
path: "/services/",
|
||||
component: <Service />,
|
||||
key: "/services/",
|
||||
},
|
||||
{
|
||||
path: "/services/:id/",
|
||||
component: <ServiceEdit />,
|
||||
key: "/service/:id/",
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
component: <Task />,
|
||||
key: "tasks",
|
||||
},
|
||||
];
|
||||
|
||||
export const superItems: TItems[] = [
|
||||
{
|
||||
path: "/teams/",
|
||||
component: <Team />,
|
||||
key: "/teams/",
|
||||
},
|
||||
{
|
||||
path: "/teams/:id/",
|
||||
component: <TeamEdit />,
|
||||
key: "/team/:id/",
|
||||
},
|
||||
{
|
||||
path: "/users/",
|
||||
component: <User />,
|
||||
key: "/users/",
|
||||
},
|
||||
{
|
||||
path: "/users/:id/",
|
||||
component: <UserEdit />,
|
||||
key: "/user/:id/",
|
||||
},
|
||||
{
|
||||
path: "/stats/",
|
||||
component: <Stat />,
|
||||
key: "/stats/",
|
||||
},
|
||||
{
|
||||
path: "/profile/",
|
||||
component: <Profile />,
|
||||
key: "/profile/",
|
||||
},
|
||||
{
|
||||
path: "/updates/",
|
||||
component: <Update />,
|
||||
key: "/updates/",
|
||||
},
|
||||
{
|
||||
path: "/updates/:id/",
|
||||
component: <UpdateEdit />,
|
||||
key: "/update/:id/",
|
||||
},
|
||||
{
|
||||
path: "/requests/",
|
||||
component: <Requests />,
|
||||
key: "/requests/",
|
||||
},
|
||||
{
|
||||
path: "/call/",
|
||||
component: <Call />,
|
||||
key: "/call/",
|
||||
},
|
||||
];
|
After Width: | Height: | Size: 360 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 511 B |
After Width: | Height: | Size: 215 B |
After Width: | Height: | Size: 215 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue