First Commit

main
Abdujamol 7 months ago
commit 1f82cbd187

23
.gitignore vendored

@ -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"
]
}

@ -0,0 +1,47 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
# newdash

Binary file not shown.

33429
package-lock.json generated

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,273 @@
import React, { useState } from "react";
import { Button, Space, Card, Form, Input, Row, Col } from "antd";
import { RegisterApi, validateUsername } from "../API/auth/register";
import { LockOutlined, UserOutlined, MailOutlined } from "@ant-design/icons";
import { message } from "antd";
import Success from "./Success";
import { common } from "../Utils/common";
import { useLocation } from "react-router-dom";
const Register: React.FC = () => {
const [form] = Form.useForm();
const [open, setOpen] = useState(false);
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const location = useLocation();
const queryParameters = new URLSearchParams(location.search);
const business_id = queryParameters.get("business_id");
const role_id = queryParameters.get("role_id");
const emailDom = queryParameters.get("email");
const onFinish = (values: any) => {
if (role_id && business_id) {
values.role_id = role_id;
values.business_id = business_id;
}
if (emailDom) {
values.email = emailDom;
}
validateUsername(values.username).then((status) => {
if (status === 204) {
setLoading(true);
RegisterApi(values).then((status) => {
if (status === 201) {
setLoading(false);
setOpen(true);
setEmail(values.email);
} else {
setLoading(false);
}
});
} else if (status === 200) {
setTimeout(() => {
message.error({
content: "This username already exists!",
duration: 2,
});
}, 1000);
}
});
};
return (
<div className="">
<Form
form={form}
name="register"
onFinish={onFinish}
initialValues={{
email: emailDom,
}}
scrollToFirstError
>
<Space
direction="horizontal"
style={{
width: "100%",
height: "100vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
}}
>
<h1>Sign up</h1>
{emailDom && (
<Card
bodyStyle={{ background: "rgb(250, 250, 250)" }}
title={emailDom}
className="login-form-card "
style={{ width: 600, textAlign: "left" }}
>
<Space
direction="vertical"
size="middle"
style={{ display: "flex", gap: "20px", margin: "auto" }}
>
<Row gutter={24}>
<Col span={12}>
<Form.Item
name="first_name"
rules={[
{
required: true,
message: "Please input your first name!",
whitespace: true,
},
]}
>
<Input
// prefix={<UserOutlined className="site-form-item-icon" />}
placeholder="First name"
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="last_name"
rules={[
{
required: true,
message: "Please input your last name!",
whitespace: true,
},
]}
>
<Input
// prefix={<UserOutlined className="site-form-item-icon" />}
placeholder="Last name"
/>
</Form.Item>
</Col>
</Row>
{/* <Form.Item
name="email"
rules={[
{
type: "email",
message: "The input is not valid E-mail!",
},
{
required: true,
message: "Please input your E-mail!",
},
]}
>
{emailDom ? (
<Input
prefix={<MailOutlined className="site-form-item-icon" />}
placeholder="E-mail"
defaultValue={emailDom}
value={emailDom}
readOnly
/>
) : (
<Input
prefix={<MailOutlined className="site-form-item-icon" />}
placeholder="E-mail"
/>
)}
</Form.Item> */}
<Form.Item
name="username"
tooltip="What do you want others to call you?"
rules={[
{
required: true,
message: "Please input your username!",
whitespace: true,
},
]}
>
<Input
prefix={<UserOutlined className="site-form-item-icon" />}
placeholder="Username"
/>
</Form.Item>
<Form.Item
name="password"
rules={[
{
required: true,
message: "Please input your password!",
},
{
min: 8,
message:
"Your password must contain at least 8 characters.",
},
() => ({
validator(_, value) {
if (!value || /[^0-9]+/.test(value)) {
return Promise.resolve();
}
return Promise.reject(
new Error("Your password cant be entirely numeric.")
);
},
}),
() => ({
validator(_, value) {
// Список общеупотребимых паролей (пример)
const commonPasswords = common;
if (!value || !commonPasswords.includes(value)) {
return Promise.resolve();
}
return Promise.reject(
new Error(
"Your password cant be a commonly used password."
)
);
},
}),
({ getFieldValue }) => ({
validator(_, value) {
const personalInfo = getFieldValue("username");
if (!value || !value.includes(personalInfo)) {
return Promise.resolve();
}
return Promise.reject(
new Error(
"Your password cant be too similar to your other personal information."
)
);
},
}),
]}
hasFeedback
>
<Input.Password
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Password"
/>
</Form.Item>
<Form.Item
name="password_confirm"
dependencies={["password"]}
hasFeedback
rules={[
{
required: true,
message: "Please confirm your password!",
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
new Error(
"The new password that you entered do not match!"
)
);
},
}),
]}
>
<Input.Password
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Re-enter Password"
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading}>
Confirm
</Button>
</Form.Item>
</Space>
</Card>
)}
</Space>
</Form>
<Success open={open} setOpen={setOpen} email={email} />
</div>
);
};
export default Register;

@ -0,0 +1,162 @@
import React, { useState } from "react";
import { Button, Space, Card, Form, Input } from "antd";
import { LockOutlined } from "@ant-design/icons";
import { common } from "../Utils/common";
import { Navigate, useLocation } from "react-router-dom";
import { resetPassEmail } from "../API/auth/resetPass";
const ResetByEmail: React.FC = () => {
const [form] = Form.useForm();
const location = useLocation();
const queryParameters = new URLSearchParams(location.search);
const confirmation_token = queryParameters.get("confirmation_token");
const user_id = queryParameters.get("user_id");
const onFinish = (values: any) => {
values.confirmation_token = confirmation_token;
values.user_id = user_id;
if(values.user_id){
resetPassEmail(values).then(() => {
<Navigate
to={{
pathname: "/auth/login",
}}
/>
});
}
};
return (
<div className="">
<Form
form={form}
name="register"
onFinish={onFinish}
scrollToFirstError
>
<Space
direction="horizontal"
style={{
width: "100%",
height: "100vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
}}
>
<Card
bodyStyle={{ background: "rgb(250, 250, 250)" }}
title="Reset Password"
className="login-form-card "
style={{ width: 400, textAlign: "left" }}
>
<Space
direction="vertical"
size="middle"
style={{ display: "flex", gap: "20px", margin: "auto" }}
>
<Form.Item
name="new_password"
rules={[
{
required: true,
message: "Please input your password!",
},
{
min: 8,
message:
"Your password must contain at least 8 characters.",
},
() => ({
validator(_, value) {
if (!value || /[^0-9]+/.test(value)) {
return Promise.resolve();
}
return Promise.reject(
new Error("Your password cant be entirely numeric.")
);
},
}),
() => ({
validator(_, value) {
// Список общеупотребимых паролей (пример)
const commonPasswords = common;
if (!value || !commonPasswords.includes(value)) {
return Promise.resolve();
}
return Promise.reject(
new Error(
"Your password cant be a commonly used password."
)
);
},
}),
({ getFieldValue }) => ({
validator(_, value) {
const personalInfo = getFieldValue("username");
if (!value || !value.includes(personalInfo)) {
return Promise.resolve();
}
return Promise.reject(
new Error(
"Your password cant be too similar to your other personal information."
)
);
},
}),
]}
hasFeedback
>
<Input.Password
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Password"
/>
</Form.Item>
<Form.Item
name="password_confirm"
dependencies={["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
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Re-enter Password"
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Reset
</Button>
</Form.Item>
</Space>
</Card>
</Space>
</Form>
</div>
);
};
export default ResetByEmail;

@ -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,177 @@
import { Input, Modal, Form as FormAnt, Select } from "antd";
import { userController } from "../../API/LayoutApi/users";
import { useTeamOne } from "../../Hooks/Teams";
import { message } from "antd";
import { useRoleData } from "../../Hooks/Role";
import { common } from "../../Utils/common";
import {
QueryObserverResult,
RefetchOptions,
RefetchQueryFilters,
} from "react-query";
import { TUser } from "../../types/User/TUser";
const AddUserToTeam = ({
open,
setOpen,
refetch,
team,
}: {
team: string;
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 oneTeam = useTeamOne(team);
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;
values.team_id = team;
await userController.addUserController(values).then((data: any) => {
const formattedPassword = data.data.password;
if (formattedPassword) {
for (var i = 0; i < formattedPassword.length; i++) {
message.error({ content: formattedPassword[i] });
}
}
});
setOpen(!open);
refetch();
});
}}
>
<FormAnt
form={form}
layout="horizontal"
name="form_in_modal"
initialValues={{ modifier: "public" }}
>
<FormAnt.Item
label="Username"
name="username"
rules={[{ required: true, message: "Username is required!" }]}
>
<Input />
</FormAnt.Item>
<FormAnt.Item
label="Password"
name="password"
rules={[
{
required: true,
message: "Please input your password!",
},
{
min: 8,
message: "Your password must contain at least 8 characters.",
},
() => ({
validator(_, value) {
if (!value || /[^0-9]+/.test(value)) {
return Promise.resolve();
}
return Promise.reject(
new Error("Your password cant be entirely numeric.")
);
},
}),
() => ({
validator(_, value) {
// Список общеупотребимых паролей (пример)
const commonPasswords = common;
if (!value || !commonPasswords.includes(value)) {
return Promise.resolve();
}
return Promise.reject(
new Error(
"Your password cant be a commonly used password."
)
);
},
}),
({ getFieldValue }) => ({
validator(_, value) {
const personalInfo = getFieldValue("username");
if (!value || !value.includes(personalInfo)) {
return Promise.resolve();
}
return Promise.reject(
new Error(
"Your password cant be too similar to your other personal information."
)
);
},
}),
]}
hasFeedback
>
<Input.Password />
</FormAnt.Item>
<FormAnt.Item
name="Confirm"
label="Confirm Password"
dependencies={["password"]}
hasFeedback
rules={[
{
required: true,
message: "Please confirm your password!",
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
new Error("The passwords that you entered do not match!")
);
},
}),
]}
>
<Input.Password />
</FormAnt.Item>
<FormAnt.Item
label="Team"
name="team_id"
rules={[{ required: false }]}
>
<Input defaultValue={oneTeam?.data?.name} readOnly />
</FormAnt.Item>
<FormAnt.Item label="Role" name="groups" rules={[{ required: true }]}>
<Select
options={roleData?.data?.map((item) => ({
label: item?.name,
value: item?.id,
}))}
/>
</FormAnt.Item>
</FormAnt>
</Modal>
</div>
);
};
export default AddUserToTeam;

@ -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/",
},
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save