commit
1f82cbd187
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"antd",
|
||||||
|
"Previos",
|
||||||
|
"Sider",
|
||||||
|
"tteld",
|
||||||
|
"USDOT"
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "newdashboard",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^1.9.7",
|
||||||
|
"@stripe/react-stripe-js": "^2.3.2",
|
||||||
|
"@stripe/stripe-js": "^2.1.11",
|
||||||
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/antd": "^1.0.0",
|
||||||
|
"@types/axios": "^0.14.0",
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/moment": "^2.13.0",
|
||||||
|
"@types/node": "^16.18.50",
|
||||||
|
"@types/react": "^18.2.21",
|
||||||
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"@types/react-query": "^1.2.9",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"antd": "^5.9.0",
|
||||||
|
"bootstrap-vue": "^2.23.1",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"final-form": "^4.20.10",
|
||||||
|
"moment-timezone": "^0.5.43",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-final-form": "^6.5.9",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-redux": "^8.1.3",
|
||||||
|
"react-router-dom": "^6.15.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"recharts": "^2.10.4",
|
||||||
|
"redux": "^4.2.1",
|
||||||
|
"redux-thunk": "^2.4.2",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"web-vitals": "^2.1.4",
|
||||||
|
"yarn": "^1.22.19"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
src="https://www.googletagmanager.com/gtag/js?id=G-TS04B6F9E3"
|
||||||
|
></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag() {
|
||||||
|
dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
gtag("js", new Date());
|
||||||
|
|
||||||
|
gtag("config", "G-TS04B6F9E3");
|
||||||
|
</script>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Admin Panel for Support Specialists in TT ELD - A comprehensive management tool for efficient operations and monitoring of TT ELD services."
|
||||||
|
/>
|
||||||
|
<title>TT ELD</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,35 @@
|
|||||||
|
import { message } from "antd";
|
||||||
|
import { TCall } from "../../types/CallRequests/TCall";
|
||||||
|
import instance from "../api";
|
||||||
|
|
||||||
|
export const callController = {
|
||||||
|
async read(obj: { status: string }) {
|
||||||
|
const params = { ...obj };
|
||||||
|
|
||||||
|
if (!!obj.status) params.status = obj.status;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TCall[]>(`callback-requests/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async callPatch(
|
||||||
|
obj: { note?: string; status?: string },
|
||||||
|
id: number
|
||||||
|
) {
|
||||||
|
const params = { ...obj };
|
||||||
|
if (!!obj.note) params.note = obj.note;
|
||||||
|
if (!!obj.status) params.status = obj.status;
|
||||||
|
|
||||||
|
const { data }: { data: any } = await instance
|
||||||
|
.put<TCall>(`callback-request/${id}/`, params)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,102 @@
|
|||||||
|
import { TCompany } from "../../types/Company/TCompany";
|
||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export type TCompanyGetParams = {
|
||||||
|
name?: string;
|
||||||
|
page?: string | number;
|
||||||
|
is_active?: boolean;
|
||||||
|
};
|
||||||
|
export type TCompanyPutParams = {
|
||||||
|
name?: string;
|
||||||
|
owner?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
};
|
||||||
|
export type TCompanyPostParams = {
|
||||||
|
name?: string;
|
||||||
|
team_id?: number;
|
||||||
|
owner?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
usdot?: string;
|
||||||
|
api_key?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const companyController = {
|
||||||
|
async read(filterObject: TCompanyGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.name) params.name = filterObject.name;
|
||||||
|
if (!!filterObject.is_active) params.is_active = filterObject.is_active;
|
||||||
|
if (!!filterObject.page) params.page = filterObject.page;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TCompany[]>(`companies/`, { params });
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async companyOne(Id: number | undefined) {
|
||||||
|
if (Id) {
|
||||||
|
const { data } = await instance.get<TCompany>(`company/${Id}/`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async companyPatch(obj: TCompanyPutParams, id: string) {
|
||||||
|
const { data }: { data: any } = await instance
|
||||||
|
.put<TCompany>(`company/${id}/`, obj)
|
||||||
|
.then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addCompanyController(obj: TCompanyPostParams) {
|
||||||
|
try {
|
||||||
|
const { data } = await instance.post<TCompany>("company/", obj);
|
||||||
|
return data;
|
||||||
|
} catch (error: any) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({
|
||||||
|
content: error.response.data.name,
|
||||||
|
key: 2,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async SyncCompany(id: number) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.post(`company-sync/${id}/`).then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err: any) {}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteCompanyController(id: string) {
|
||||||
|
message.loading({ content: "Loading..." });
|
||||||
|
let res;
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`company/${id}/`).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({
|
||||||
|
content: "Deleted!",
|
||||||
|
key: id,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,99 @@
|
|||||||
|
import { TCustomer } from "../../types/Customer/TCustomer";
|
||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export type TCustomerGetParams = {
|
||||||
|
name?: string;
|
||||||
|
pageSize?: string | number;
|
||||||
|
page?: string | number;
|
||||||
|
is_active?: boolean;
|
||||||
|
};
|
||||||
|
export type TCustomerByCompanyGetParams = {
|
||||||
|
name?: string;
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
export type TCustomerPutParams = {
|
||||||
|
name?: string;
|
||||||
|
company_id?: number;
|
||||||
|
};
|
||||||
|
export type TCustomerPostParams = {
|
||||||
|
name?: string;
|
||||||
|
company_id?: number;
|
||||||
|
is_active?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customerController = {
|
||||||
|
async read(filterObject: TCustomerGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.name) params.name = filterObject.name;
|
||||||
|
if (!!filterObject.is_active) params.is_active = filterObject.is_active;
|
||||||
|
if (!!filterObject.page) params.page = filterObject.page;
|
||||||
|
if (!!filterObject.page) params.pageSize = filterObject.pageSize;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TCustomer[]>(`customers/`, { params });
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async customerOne(Id: number | undefined) {
|
||||||
|
if (Id) {
|
||||||
|
const { data } = await instance.get<TCustomer>(`customer/${Id}/`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async customerByCompany(id: string | undefined, name: string | undefined) {
|
||||||
|
const params = { name };
|
||||||
|
if (!!name) params.name = name;
|
||||||
|
if (id) {
|
||||||
|
const { data } = await instance.get<TCustomer[]>(
|
||||||
|
`customers-by-company/${id}/`,
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async customerPatch(obj: TCustomerPutParams, id: string) {
|
||||||
|
const { data }: { data: any } = await instance
|
||||||
|
.put<TCustomer>(`customer/${id}/`, obj)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addCustomerController(obj: TCustomerPostParams) {
|
||||||
|
message.loading({ content: "Loading..." });
|
||||||
|
const { data } = await instance
|
||||||
|
.post<TCustomer>("customer/", obj)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteCustomerController(id: string) {
|
||||||
|
message.loading({ content: "Loading..." });
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`customer/${id}/`).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Deleted!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,103 @@
|
|||||||
|
import { message } from "antd";
|
||||||
|
import {
|
||||||
|
TMyTaskHistory,
|
||||||
|
TMystats,
|
||||||
|
TProfile,
|
||||||
|
} from "../../types/Profile/TProfile";
|
||||||
|
import instance from "../api";
|
||||||
|
|
||||||
|
export type TProfilePutParams = {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
username?: string;
|
||||||
|
};
|
||||||
|
export type TMyTaskHistoryGetParams = {
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
};
|
||||||
|
export type TChangePostParams = {
|
||||||
|
old_password?: string;
|
||||||
|
new_password?: string;
|
||||||
|
password_confirm?: string;
|
||||||
|
};
|
||||||
|
export const prof = {
|
||||||
|
async read(filterObject: TMyTaskHistoryGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
if (!!filterObject.start_date) params.start_date = filterObject.start_date;
|
||||||
|
if (!!filterObject.end_date) params.end_date = filterObject.end_date;
|
||||||
|
const { data } = await instance.get<TMystats>(`stats/my-stats/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
// async read(filterObject: TMyTaskHistoryGetParams) {
|
||||||
|
// const params = { ...filterObject };
|
||||||
|
// if (!!filterObject.start_date) params.start_date = filterObject.start_date;
|
||||||
|
// if (!!filterObject.end_date) params.end_date = filterObject.end_date;
|
||||||
|
// const { data } = await instance.get<TMystats>(`stats/my-stats/`, {
|
||||||
|
// params,
|
||||||
|
// });
|
||||||
|
// return data;
|
||||||
|
// },
|
||||||
|
|
||||||
|
async self() {
|
||||||
|
const { data } = await instance.get<TProfile>(`users/my-profile/`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async myTaskHistory(filterObject: TMyTaskHistoryGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.start_date) params.start_date = filterObject.start_date;
|
||||||
|
if (!!filterObject.end_date) params.end_date = filterObject.end_date;
|
||||||
|
const { data } = await instance.get<TMyTaskHistory[]>(`my-task-history/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async profPatch(filterObject: TProfilePutParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
params.first_name = filterObject.first_name || params.first_name;
|
||||||
|
params.last_name = filterObject.last_name || params.last_name;
|
||||||
|
params.username = filterObject.username || params.username;
|
||||||
|
try {
|
||||||
|
const { data } = await instance.put<TProfilePutParams>(
|
||||||
|
`users/my-profile/`,
|
||||||
|
{ ...params }
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error: any) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({
|
||||||
|
content: error.response.data.username,
|
||||||
|
key: 2,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changePass(obj: TChangePostParams) {
|
||||||
|
try {
|
||||||
|
const { data } = await instance.post<any>(
|
||||||
|
"users/my-profile/change-password/",
|
||||||
|
obj
|
||||||
|
);
|
||||||
|
message.success(data.message);
|
||||||
|
return data;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.response && error.response.status === 400) {
|
||||||
|
const errorMessage =
|
||||||
|
error?.response?.data?.old_password ||
|
||||||
|
error?.response?.data?.new_password[0] ||
|
||||||
|
"Bad Request";
|
||||||
|
message.error(errorMessage);
|
||||||
|
} else {
|
||||||
|
message.error("An error occurred");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,58 @@
|
|||||||
|
import { message } from "antd";
|
||||||
|
import { TRequests } from "../../types/Requests/TRequests";
|
||||||
|
import instance from "../api";
|
||||||
|
|
||||||
|
export type TRequestsGetParams = {
|
||||||
|
search?: string;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestsController = {
|
||||||
|
async read(filterObject: TRequestsGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.search) params.search = filterObject.search;
|
||||||
|
if (!!filterObject.status) params.status = filterObject.status;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TRequests[]>(`driver-requests/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestsOne(Id: string | number | undefined) {
|
||||||
|
const { data }: { data: any } = await instance(`driver-request/${Id}/`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestPatch(obj: TRequestsGetParams, id: string | number | undefined) {
|
||||||
|
const { data } = await instance
|
||||||
|
.put<TRequests>(`driver-request/${id}/`, obj)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async delete(id: string | number | undefined) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance
|
||||||
|
.patch(`driver-request/${id}/`, { status: "Rejected" })
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Rejected!", key: id, duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
import { TRole } from "../../types/Role/TRole";
|
||||||
|
import instance from "../api";
|
||||||
|
|
||||||
|
|
||||||
|
export const roleController = {
|
||||||
|
async read() {
|
||||||
|
const { data } = await instance.get<TRole[]>(`users/roles/`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async roleOne(id: string) {
|
||||||
|
const { data }: { data: any } = await instance.get<TRole>(
|
||||||
|
`users/role/${id}/`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,89 @@
|
|||||||
|
import { TService } from "../../types/Service/TService";
|
||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export type TServicePutParams = {
|
||||||
|
title?: string;
|
||||||
|
points?: number;
|
||||||
|
};
|
||||||
|
export type TServicePostParams = {
|
||||||
|
title?: string;
|
||||||
|
points?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serviceController = {
|
||||||
|
async read() {
|
||||||
|
const { data } = await instance.get<TService[]>(`services/`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async serviceOne(Id: number | undefined) {
|
||||||
|
if (Id) {
|
||||||
|
const { data } = await instance.get<TService>(`service/${Id}/`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async servicePatch(obj: TServicePutParams, id: string) {
|
||||||
|
try {
|
||||||
|
const { data } = await instance
|
||||||
|
.put<TService>(`service/${id}/`, obj)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error: any) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({
|
||||||
|
content: error?.response?.data?.title,
|
||||||
|
key: 2,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async addServiceController(obj: TServicePostParams) {
|
||||||
|
try {
|
||||||
|
const { data } = await instance
|
||||||
|
.post<TService>("service/", obj)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error: any) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({
|
||||||
|
content: error?.response?.data?.title,
|
||||||
|
key: 2,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteServiceController(id: string) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`service/${id}/`).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Deleted!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,132 @@
|
|||||||
|
import { TCard, TStat, TStatTeam } from "../../types/Statistic/TStat";
|
||||||
|
import instance from "../api";
|
||||||
|
|
||||||
|
export type TStatGetParams = {
|
||||||
|
search?: string;
|
||||||
|
team?: string;
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TStatTeamGetParams = {
|
||||||
|
search?: string;
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TStatCreatorsGetParams = {
|
||||||
|
start_date?: string;
|
||||||
|
end_date?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const statController = {
|
||||||
|
async read(filterObject: TStatGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.search) params.search = filterObject.search;
|
||||||
|
if (!!filterObject.team) params.team = filterObject.team;
|
||||||
|
if (!!filterObject.start_date) params.start_date = filterObject.start_date;
|
||||||
|
if (!!filterObject.end_date) params.end_date = filterObject.end_date;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TStat[]>(`stats/all-users/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async team(filterObject: TStatTeamGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.search) params.search = filterObject.search;
|
||||||
|
if (!!filterObject.start_date) params.start_date = filterObject.start_date;
|
||||||
|
if (!!filterObject.end_date) params.end_date = filterObject.end_date;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TStatTeam[]>(`stats/all-teams/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async creators(filterObject: TStatCreatorsGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.start_date) params.start_date = filterObject.start_date;
|
||||||
|
if (!!filterObject.end_date) params.end_date = filterObject.end_date;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TStatTeam[]>(`stats/task-creators/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
async cards(filterObject: TStatCreatorsGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.start_date) params.start_date = filterObject.start_date;
|
||||||
|
if (!!filterObject.end_date) params.end_date = filterObject.end_date;
|
||||||
|
|
||||||
|
const { data } = await instance.get<TCard>(`stats/tasks-comparison/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveUsersStats(
|
||||||
|
fileName: string,
|
||||||
|
startDate: string,
|
||||||
|
endDate: string,
|
||||||
|
team: string
|
||||||
|
) {
|
||||||
|
const response = await instance.post(
|
||||||
|
`stats/all-users/?start_date=${startDate}&end_date=${endDate}&team=${team}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
"Content-Disposition": `attachment;`,
|
||||||
|
},
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const blob = new Blob([response.data], {
|
||||||
|
type: "application/octet-stream",
|
||||||
|
});
|
||||||
|
const downloadUrl = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
|
a.href = downloadUrl;
|
||||||
|
a.download = `stats_${fileName}.csv`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveTeamStats(fileName: string, startDate: string, endDate: string) {
|
||||||
|
const response = await instance.post(
|
||||||
|
`stats/all-teams/?start_date=${startDate}&end_date=${endDate}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
"Content-Disposition": `attachment;`,
|
||||||
|
},
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const blob = new Blob([response.data], {
|
||||||
|
type: "application/octet-stream",
|
||||||
|
});
|
||||||
|
const downloadUrl = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.style.display = "none";
|
||||||
|
a.href = downloadUrl;
|
||||||
|
a.download = `stats_${fileName}.csv`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async statOne(Id: string | number | undefined) {
|
||||||
|
const { data } = await instance.get<TStat>(`stats/${Id}`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,118 @@
|
|||||||
|
import { TTask, TTaskHistory } from "../../types/Tasks/TTasks";
|
||||||
|
import { TPagination } from "../../types/common/TPagination";
|
||||||
|
import instance from "../api";
|
||||||
|
|
||||||
|
export type TTasksGetParams = {
|
||||||
|
search?: string;
|
||||||
|
status?: string;
|
||||||
|
team?: string;
|
||||||
|
page?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTasksPutParams = {
|
||||||
|
company_id?: number;
|
||||||
|
customer_id?: number;
|
||||||
|
service_id?: number;
|
||||||
|
assigned_to_id?: number;
|
||||||
|
note?: string;
|
||||||
|
status?: string;
|
||||||
|
message?: string;
|
||||||
|
pti?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTasksPostParams = {
|
||||||
|
company_id?: number;
|
||||||
|
customer_id?: number;
|
||||||
|
service_id?: number;
|
||||||
|
provider_id?: number;
|
||||||
|
assigned_to_id?: number;
|
||||||
|
in_charge_id?: number;
|
||||||
|
note?: string;
|
||||||
|
status?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
pti?: boolean;
|
||||||
|
attachment_ids?: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const taskController = {
|
||||||
|
async read(filterObject: TTasksGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.page && filterObject.page !== "0")
|
||||||
|
params.page = filterObject.page;
|
||||||
|
if (!!filterObject.search) params.search = filterObject.search;
|
||||||
|
if (Array.isArray(filterObject.status)) {
|
||||||
|
params.status = filterObject.status.join(",");
|
||||||
|
}
|
||||||
|
if (Array.isArray(filterObject.team)) {
|
||||||
|
params.team = filterObject.team.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await instance.get<TPagination<TTask[]>>(`tasks/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getHistory(id: number | undefined) {
|
||||||
|
const { data } = await instance.get<TTaskHistory[]>(`task-history/${id}/`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async taskOne(Id: number) {
|
||||||
|
const { data } = await instance.get<TTask>(`task/${Id}/`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async taskPatch(obj: TTasksPutParams, task_id: number | undefined) {
|
||||||
|
const { data } = await instance
|
||||||
|
.put<TTask>(`task/${task_id}/`, obj)
|
||||||
|
.then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addTaskController(obj: TTasksPostParams) {
|
||||||
|
const { data } = await instance.post<TTask>("task/", obj).then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addTaskFile(formData: any) {
|
||||||
|
const { data } = await instance.post("attachment/", formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data", // Установите правильный Content-Type
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteTaskController(id: number) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`task/${id}/`).then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
async deleteAttachmentController(id: number) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`attachment/${id}/`).then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,73 @@
|
|||||||
|
import { TTeam } from "../../types/Team/TTeam";
|
||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export type TTeamPutParams = {
|
||||||
|
name?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TTeamPostParams = {
|
||||||
|
name?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const teamController = {
|
||||||
|
async read(name: string) {
|
||||||
|
const { data } = await instance.get<TTeam[]>(`teams/?name=${name}`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async teamOne(Id: string | number | undefined) {
|
||||||
|
const { data } = await instance.get<TTeam>(`team/${Id}`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async teamPatch(obj: TTeamPutParams, id: string) {
|
||||||
|
const { data } = await instance.put<TTeam>(`team/${id}/`, obj).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addTeamController(obj: TTeamPostParams) {
|
||||||
|
try {
|
||||||
|
const { data } = await instance.post<TTeam>("team/", obj).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error: any) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({
|
||||||
|
content: error?.response?.data?.name,
|
||||||
|
key: 2,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteTeamController(id: string) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`team/${id}`).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Deleted!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,94 @@
|
|||||||
|
import { TUpdate } from "../../types/Update/TUpdate";
|
||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export type TUpdatePutParams = {
|
||||||
|
company_id?: number;
|
||||||
|
customer_id?: number;
|
||||||
|
status?: string;
|
||||||
|
note?: string;
|
||||||
|
is_pinned?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdatePostParams = {
|
||||||
|
company_id?: number;
|
||||||
|
customer_id?: number;
|
||||||
|
provider_id?: number;
|
||||||
|
executor_id?: number;
|
||||||
|
status?: string;
|
||||||
|
note?: string;
|
||||||
|
solution?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
is_pinned?: boolean;
|
||||||
|
attachment_ids?: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateController = {
|
||||||
|
async read(status: string) {
|
||||||
|
const { data } = await instance.get<TUpdate[]>(
|
||||||
|
`shift-updates/?status=${status}`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateOne(Id: string | number | undefined) {
|
||||||
|
const { data }: { data: any } = await instance(`shift-update/${Id}`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
async addTaskFile(formData: FormData) {
|
||||||
|
const { data } = await instance.post("attachment/", formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async updatePut(updateData: TUpdate, update_id: string) {
|
||||||
|
const { data } = await instance(`shift-update/${update_id}/`, {
|
||||||
|
method: "PUT",
|
||||||
|
data: updateData,
|
||||||
|
}).then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
async updatePatch(obj: TUpdatePutParams, id: string | number) {
|
||||||
|
const { data } = await instance(`shift-update/${id}/`, {
|
||||||
|
method: "PUT",
|
||||||
|
data: obj,
|
||||||
|
}).then((u) => {
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addUpdateController(obj: TUpdatePostParams) {
|
||||||
|
const { data } = await instance
|
||||||
|
.post<TUpdate>("shift-update/", obj)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteUpdateController(id: string) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`shift-update/${id}`).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Deleted!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,92 @@
|
|||||||
|
import { TUser } from "../../types/User/TUser";
|
||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export type TUsersGetParams = {
|
||||||
|
name?: string;
|
||||||
|
team?: string;
|
||||||
|
role?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUsersPutParams = {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
username?: string;
|
||||||
|
team_id?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUsersPostParams = {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
team_id?: number;
|
||||||
|
groups?: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userController = {
|
||||||
|
async read(filterObject: TUsersGetParams) {
|
||||||
|
const params = { ...filterObject };
|
||||||
|
|
||||||
|
if (!!filterObject.name) params.name = filterObject.name;
|
||||||
|
if (Array.isArray(filterObject.team)) {
|
||||||
|
params.team = filterObject.team.join(", ");
|
||||||
|
}
|
||||||
|
if (Array.isArray(filterObject.role)) {
|
||||||
|
params.role = filterObject.role.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await instance.get<TUser[]>(`users/`, { params });
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async CheckUsername(username: string) {
|
||||||
|
const { data } = await instance.get<TUser[]>(`users/check/${username}`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async userOne(Id: string | number | undefined) {
|
||||||
|
const { data }: { data: any } = await instance(`users/admin/${Id}/`);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async userPatch(obj: TUsersPutParams, id: string) {
|
||||||
|
const { data } = await instance
|
||||||
|
.put<TUser>(`users/admin/${id}/`, obj)
|
||||||
|
.then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addUserController(obj: TUsersPostParams) {
|
||||||
|
message.loading({ content: "Loading..." });
|
||||||
|
let responseData = null;
|
||||||
|
try {
|
||||||
|
const response = await instance.post<TUser>("users/admin/", obj);
|
||||||
|
responseData = response;
|
||||||
|
message.success({ content: "Loaded!", duration: 2 });
|
||||||
|
} catch (err: any) {
|
||||||
|
responseData = err?.response?.data;
|
||||||
|
}
|
||||||
|
return { data: responseData };
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteUserController(id: string) {
|
||||||
|
let res;
|
||||||
|
let error = "";
|
||||||
|
try {
|
||||||
|
const { data } = await instance.delete(`users/admin/${id}/`).then((u) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: "Deleted!", key: id, duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
res = data;
|
||||||
|
} catch (err) {
|
||||||
|
error = "Oops something went wrong!";
|
||||||
|
}
|
||||||
|
return { data: res, error };
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: "http://10.10.10.45:8080/api/v1/",
|
||||||
|
});
|
||||||
|
// const instance = axios.create({
|
||||||
|
// baseURL: "https://api.tteld.co/api/v1/",
|
||||||
|
// });
|
||||||
|
|
||||||
|
const token: string | null = localStorage.getItem("access");
|
||||||
|
if (token) {
|
||||||
|
instance.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
localStorage.clear();
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default instance;
|
@ -0,0 +1,42 @@
|
|||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
interface loginInterface {
|
||||||
|
username: string;
|
||||||
|
password: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoginApi = async ({ username, password }: loginInterface) => {
|
||||||
|
try {
|
||||||
|
const { data } = await instance("auth/login/", {
|
||||||
|
method: "POST",
|
||||||
|
data: { username, password },
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
const userObject = {
|
||||||
|
id: data?.data?.id,
|
||||||
|
first_name: data?.data?.first_name,
|
||||||
|
last_name: data?.data?.last_name,
|
||||||
|
username: data?.data?.username,
|
||||||
|
timezone: data?.data?.timezone,
|
||||||
|
role: data?.data?.role,
|
||||||
|
team_id: data?.data?.team_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userJSON = JSON.stringify(userObject);
|
||||||
|
localStorage.setItem("user", userJSON);
|
||||||
|
localStorage.setItem("access", data?.data.access);
|
||||||
|
localStorage.setItem("refresh", data?.data.refresh);
|
||||||
|
localStorage.setItem("admin_id", data?.data.id);
|
||||||
|
document.location.replace("/");
|
||||||
|
} catch (err) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({
|
||||||
|
content: "Username or password incorrect!",
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export const LogoutApi = async () => {
|
||||||
|
try {
|
||||||
|
await instance("auth/logout/", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
data: { refresh_token: localStorage.getItem("refresh") },
|
||||||
|
});
|
||||||
|
localStorage.removeItem("access");
|
||||||
|
localStorage.removeItem("refresh");
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
localStorage.removeItem("admin_id");
|
||||||
|
document.location.replace("/");
|
||||||
|
} catch (err) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({ content: "Something went wrong! ", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
throw new Error("Something went wrong");
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,77 @@
|
|||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export interface activateInterface {
|
||||||
|
user_id: string;
|
||||||
|
confirmation_token: string;
|
||||||
|
}
|
||||||
|
export interface inviteInterface {
|
||||||
|
user_id: string;
|
||||||
|
confirmation_token: string;
|
||||||
|
role_id: string | null;
|
||||||
|
business_id: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registryVerify = async (value: activateInterface) => {
|
||||||
|
try {
|
||||||
|
const { data, status } = await instance("users/verify-registration/", {
|
||||||
|
method: "POST",
|
||||||
|
data: value,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
const userObject = {
|
||||||
|
first_name: data?.data.first_name,
|
||||||
|
last_name: data?.data.last_name,
|
||||||
|
username: data?.data.username,
|
||||||
|
id: data?.data.id,
|
||||||
|
timezone: data?.data.timezone,
|
||||||
|
role: data?.data.role,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userJSON = JSON.stringify(userObject);
|
||||||
|
localStorage.setItem("user", userJSON);
|
||||||
|
localStorage.setItem("access_token", data?.data.access_token);
|
||||||
|
localStorage.setItem("refresh_token", data?.data.refresh_token);
|
||||||
|
document.location.replace("/");
|
||||||
|
|
||||||
|
return status;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({ content: "Something went wrong", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const inviteVerify = async (value: inviteInterface) => {
|
||||||
|
try {
|
||||||
|
const { data, status } = await instance("users/invite-verify/", {
|
||||||
|
method: "POST",
|
||||||
|
data: value,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
const userObject = {
|
||||||
|
first_name: data?.data.first_name,
|
||||||
|
last_name: data?.data.last_name,
|
||||||
|
username: data?.data.username,
|
||||||
|
id: data?.data.id,
|
||||||
|
timezone: data?.data.timezone,
|
||||||
|
role: data?.data.role,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userJSON = JSON.stringify(userObject);
|
||||||
|
localStorage.setItem("user", userJSON);
|
||||||
|
localStorage.setItem("access", data?.data.access);
|
||||||
|
localStorage.setItem("refresh", data?.data.refresh);
|
||||||
|
localStorage.setItem("admin_id", data?.data.id);
|
||||||
|
instance.defaults.headers.common[
|
||||||
|
"Authorization"
|
||||||
|
] = `Bearer ${data?.data.access}`;
|
||||||
|
return status;
|
||||||
|
} catch (error) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({ content: "Something went wrong", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,25 @@
|
|||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export interface inviteType {
|
||||||
|
role_id: number;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const inviteVerify = async (value: inviteType) => {
|
||||||
|
try {
|
||||||
|
const {data} = await instance("users/invite/", {
|
||||||
|
method: "POST",
|
||||||
|
data: value,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
const succesMessage = data?.message;
|
||||||
|
setTimeout(() => {
|
||||||
|
message.success({ content: succesMessage, duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({ content: "Something went wrong", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export interface registerInterface {
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
password_confirm: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegisterApi = async (value: registerInterface) => {
|
||||||
|
try {
|
||||||
|
const { status, data } = await instance("users/register/", {
|
||||||
|
method: "POST",
|
||||||
|
data: value,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
const userObject = {
|
||||||
|
first_name: data?.data.first_name,
|
||||||
|
last_name: data?.data.last_name,
|
||||||
|
username: data?.data.username,
|
||||||
|
id: data?.data.id,
|
||||||
|
timezone: data?.data.timezone,
|
||||||
|
role: data?.data.role,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userJSON = JSON.stringify(userObject);
|
||||||
|
localStorage.setItem("user", userJSON);
|
||||||
|
localStorage.setItem("access_token", data?.data.access_token);
|
||||||
|
localStorage.setItem("refresh_token", data?.data.refresh_token);
|
||||||
|
document.location.replace("/");
|
||||||
|
return status;
|
||||||
|
} catch (error:any) {
|
||||||
|
console.log(error);
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({ content: ' ', duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateUsername = async (value: any) => {
|
||||||
|
const {status} = await instance.get(`users/check/${value}/`);
|
||||||
|
return status
|
||||||
|
};
|
@ -0,0 +1,45 @@
|
|||||||
|
import instance from "../api";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
export interface resetType {
|
||||||
|
login: string;
|
||||||
|
}
|
||||||
|
export interface resetPassType {
|
||||||
|
user_id: string;
|
||||||
|
confirmation_token: string;
|
||||||
|
new_password: string;
|
||||||
|
password_confirm: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resetPass = async (value: resetType) => {
|
||||||
|
try {
|
||||||
|
const { data } = await instance("users/send-reset-password-link/", {
|
||||||
|
method: "POST",
|
||||||
|
data: value,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({ content: "Something went wrong", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const resetPassEmail = async (value: resetPassType) => {
|
||||||
|
try {
|
||||||
|
const { data } = await instance("users/reset-password/", {
|
||||||
|
method: "POST",
|
||||||
|
data: value,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
const succesMessage = data?.message;
|
||||||
|
message.success({ content: succesMessage, duration: 2 });
|
||||||
|
setTimeout(() => {
|
||||||
|
document.location.replace('/auth/login')
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
setTimeout(() => {
|
||||||
|
message.error({ content: "Something went wrong", duration: 2 });
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,767 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#components-layout-demo-custom-trigger .trigger {
|
||||||
|
line-height: 64px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-layout-demo-custom-trigger .trigger:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:where(.css-dev-only-do-not-override-1vtf12y).ant-menu .ant-menu-item {
|
||||||
|
white-space: initial !important;
|
||||||
|
}
|
||||||
|
:where(.css-1vtf12y).ant-menu .ant-menu-item {
|
||||||
|
white-space: unset;
|
||||||
|
}
|
||||||
|
.site-layout .site-layout-background {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.ant-pagination-options-size-changer.ant-select {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
font-size: 28px;
|
||||||
|
/* font-family: Arial, Helvetica, sans-serif; */
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 0;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-collapsed {
|
||||||
|
font-size: 18px;
|
||||||
|
/* font-family: Arial, Helvetica, sans-serif; */
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isnot {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#element::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
background-color: #f9f9fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#element::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #595959;
|
||||||
|
}
|
||||||
|
|
||||||
|
#element::-webkit-scrollbar-track {
|
||||||
|
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #f9f9fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-sider-trigger {
|
||||||
|
background: none !important;
|
||||||
|
border-top: 1px solid #cecece;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-status-row {
|
||||||
|
background-color: rgba(170, 170, 170, 0.44);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-btn {
|
||||||
|
background: rgb(241, 241, 241);
|
||||||
|
border: 0.5px solid rgba(233, 233, 233, 0.11);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
outline: none;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 11px 13px;
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.062);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-dropdown {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: max-content;
|
||||||
|
margin-left: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-dropdown:hover {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #7c7c7c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-dropdown-ava {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-dropdown-text {
|
||||||
|
text-align-last: left;
|
||||||
|
align-items: end;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-dropdown .business-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
color: #363636;
|
||||||
|
}
|
||||||
|
|
||||||
|
.business-name-dark {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-dropdown .username {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-true {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: #05e776;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-false {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: #c71d1d;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.live-p {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 7px;
|
||||||
|
width: max-content;
|
||||||
|
color: #424242;
|
||||||
|
}
|
||||||
|
.live-p-dark {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 7px;
|
||||||
|
width: max-content;
|
||||||
|
color: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add {
|
||||||
|
padding: 10px 15px 10px 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(249, 158, 44, 1);
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
font-family: Inter;
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
margin-right: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add:hover {
|
||||||
|
background: rgb(247, 176, 89);
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-refresh-false {
|
||||||
|
padding: 10px 21px 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
font-family: Inter;
|
||||||
|
border: 1px solid rgba(215, 216, 224, 1);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
color: rgba(15, 17, 28, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.btn-refresh-dark {
|
||||||
|
padding: 10px 21px 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
font-family: Inter;
|
||||||
|
border: 1px solid #777;
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
background: #333;
|
||||||
|
color: #bbb;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-refresh-false:hover {
|
||||||
|
border: 1px solid rgba(249, 158, 44, 1);
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
.btn-refresh-dark:hover {
|
||||||
|
border: 1px solid rgba(249, 158, 44, 1);
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 28px;
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
text-align: left;
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
margin: 16px 0;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-false {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
color: rgba(15, 17, 28, 1);
|
||||||
|
caret-color: rgba(249, 158, 44, 1);
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #00000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-false::placeholder {
|
||||||
|
color: rgba(155, 157, 170, 1);
|
||||||
|
}
|
||||||
|
.search-input-true {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
color: #bbb;
|
||||||
|
caret-color: rgba(249, 158, 44, 1);
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #00000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-true::placeholder {
|
||||||
|
color: rgba(155, 157, 170, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-div {
|
||||||
|
border: 1px solid rgba(150, 150, 150, 0.493);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
padding: 5px 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-div:hover,
|
||||||
|
.search-div:focus {
|
||||||
|
border: 1px solid rgba(249, 158, 44, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-div:active {
|
||||||
|
border: 1px solid rgba(249, 158, 44, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-new {
|
||||||
|
background: rgba(45, 155, 219, 0.1);
|
||||||
|
padding: 6px 0;
|
||||||
|
color: rgb(32, 155, 226);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||||
|
}
|
||||||
|
.status-Pending {
|
||||||
|
border: 0.5px solid rgba(246, 137, 0, 0.3);
|
||||||
|
padding: 3px;
|
||||||
|
background: rgba(246, 137, 0, 0.1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: rgba(246, 137, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-checking {
|
||||||
|
background: rgba(45, 156, 219, 0.1);
|
||||||
|
padding: 6px 0;
|
||||||
|
color: rgba(45, 156, 219, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-in-progress {
|
||||||
|
border: 0.5px solid rgba(246, 137, 0, 0.3);
|
||||||
|
padding: 6px 0;
|
||||||
|
background: rgba(246, 137, 0, 0.1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: rgba(246, 137, 0, 1);
|
||||||
|
}
|
||||||
|
.status-Rejected {
|
||||||
|
border: 0.5px solid rgba(246, 0, 0, 0.3);
|
||||||
|
padding: 3px;
|
||||||
|
background: rgba(246, 0, 0, 0.1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-done {
|
||||||
|
padding: 6px 0;
|
||||||
|
color: rgba(10, 160, 106, 1);
|
||||||
|
background: rgba(10, 160, 106, 0.1);
|
||||||
|
border: 0.5px solid rgba(10, 160, 106, 0.3);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.status-Assigned {
|
||||||
|
padding: 3px;
|
||||||
|
color: rgba(10, 160, 106, 1);
|
||||||
|
background: rgba(10, 160, 106, 0.1);
|
||||||
|
border: 0.5px solid rgba(10, 160, 106, 0.3);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-paper {
|
||||||
|
background: rgba(45, 156, 219, 0.1);
|
||||||
|
padding: 6px 0;
|
||||||
|
color: rgba(45, 156, 219, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-setup {
|
||||||
|
background: rgba(45, 156, 219, 0.1);
|
||||||
|
padding: 6px 0;
|
||||||
|
color: rgba(45, 156, 219, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #05e776;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s infinite alternate; /* Анимация будет повторяться бесконечно */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle2 {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #c71d1d;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s infinite alternate; /* Анимация будет повторяться бесконечно */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pin {
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
border: 1px solid rgba(215, 216, 224, 1);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
padding: 7px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-top: 7px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-unpin {
|
||||||
|
padding: 7px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(246, 71, 71, 1);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TaskModal {
|
||||||
|
position: absolute;
|
||||||
|
width: 800px;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
border-left: 1px solid rgba(215, 216, 224, 1);
|
||||||
|
box-shadow: -40px 0px 100px 10000px rgba(0, 0, 0, 0.5);
|
||||||
|
transition: 3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TaskModal-dark {
|
||||||
|
position: absolute;
|
||||||
|
width: 800px;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background: rgb(37, 37, 37);
|
||||||
|
border-left: 1px solid rgba(215, 216, 224, 1);
|
||||||
|
box-shadow: -40px 0px 100px 1000px rgba(0, 0, 0, 0.5);
|
||||||
|
transition: 3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modal-action-false {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(215, 216, 224, 1);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.btn-modal-action-dark {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #777777;
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
background: #333;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
.btn-modal-action-false:hover {
|
||||||
|
background: #e6e6e6;
|
||||||
|
}
|
||||||
|
.btn-modal-action-dark:hover {
|
||||||
|
background: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modal-action-false img,
|
||||||
|
.btn-modal-action-dark img {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdoal-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TaskModal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 20px 24px 24px;
|
||||||
|
}
|
||||||
|
.TaskModal-header-dark {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 20px 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TaskModal-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.TaskModal-title-dark {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-New {
|
||||||
|
background: rgba(45, 156, 219, 0.1);
|
||||||
|
padding: 4px 10px;
|
||||||
|
color: rgba(45, 156, 219, 1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 0.5px solid rgba(45, 156, 219, 0.3);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-Done {
|
||||||
|
padding: 4px 10px;
|
||||||
|
color: rgba(10, 160, 106, 1);
|
||||||
|
background: rgba(10, 160, 106, 0.1);
|
||||||
|
border: 0.5px solid rgba(10, 160, 106, 0.3);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-Checking {
|
||||||
|
border: 0.5px solid rgba(246, 137, 0, 0.3);
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: rgba(246, 137, 0, 0.1);
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: rgba(246, 137, 0, 1);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-driver {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
text-align: left;
|
||||||
|
color: rgba(15, 17, 28, 1);
|
||||||
|
}
|
||||||
|
.p-driver-dark {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
text-align: left;
|
||||||
|
color: rgb(211, 211, 211);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-div {
|
||||||
|
margin: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-body {
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border: 1px solid rgba(215, 216, 224, 1);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-body tr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
color: rgba(121, 123, 141, 1);
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.sub-dark {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
color: rgb(221, 221, 221);
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
color: rgba(15, 17, 28, 1);
|
||||||
|
}
|
||||||
|
.info-dark {
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
text-align: left;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pin {
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
border: 1px solid rgba(215, 216, 224, 1);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
padding: 7px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unpin {
|
||||||
|
padding: 7px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(246, 71, 71, 1);
|
||||||
|
box-shadow: 0px 1px 3px 0px rgba(20, 22, 41, 0.1);
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-wrapper .ant-upload-drag {
|
||||||
|
border: 0.5px solid rgba(215, 216, 224, 1);
|
||||||
|
padding: 25px 0;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card_stat {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #deeeff;
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 200px;
|
||||||
|
height: 180px;
|
||||||
|
text-align: center;
|
||||||
|
-webkit-box-shadow: 0px 0px 12px 1px rgba(34, 60, 80, 0.08);
|
||||||
|
-moz-box-shadow: 0px 0px 12px 1px rgba(34, 60, 80, 0.08);
|
||||||
|
box-shadow: 0px 0px 12px 1px rgba(34, 60, 80, 0.08);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card_stat:hover {
|
||||||
|
background: #cfe4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card_stat span {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 44px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
color: #464646;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-driver {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
@ -0,0 +1,381 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import "./App.css";
|
||||||
|
import { Layout, Menu, ConfigProvider, Dropdown } from "antd";
|
||||||
|
import { Routes, Route, Navigate, useLocation } from "react-router-dom";
|
||||||
|
import { allMenu, mainItems, superItems } from "./Utils/sidebar";
|
||||||
|
import Login from "./Auth/Login";
|
||||||
|
import Notfound from "./Utils/Notfound";
|
||||||
|
import { LogoutApi } from "./API/auth/Logout";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
// @ts-ignore
|
||||||
|
import themeBtn from "./assets/theme-btn.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import avatar from "./assets/avatar-img.svg";
|
||||||
|
import Register from "./Auth/Register";
|
||||||
|
import Activate from "./Auth/Activate";
|
||||||
|
import Invite from "./Auth/Invite";
|
||||||
|
import ResetPassword from "./Auth/ResetPassword";
|
||||||
|
import ResetByEmail from "./Auth/ResetByEmail";
|
||||||
|
|
||||||
|
const { Header, Sider, Content } = Layout;
|
||||||
|
const userJSON: any = localStorage.getItem("user");
|
||||||
|
const userObject = JSON.parse(userJSON);
|
||||||
|
export const timeZone = userObject?.timezone;
|
||||||
|
export const role = userObject?.role;
|
||||||
|
export const admin_id = localStorage.getItem("admin_id");
|
||||||
|
export const team_id = userObject?.team_id;
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const isAuthenticated = localStorage.getItem("access") as string;
|
||||||
|
const authorized = isAuthenticated;
|
||||||
|
const [collapsed, setCollapsed] = useState<any>(
|
||||||
|
localStorage.getItem("collapsed") === "true" ? true : false
|
||||||
|
);
|
||||||
|
const [theme, setTheme] = useState<any>(
|
||||||
|
localStorage.getItem("theme") === "true" ? true : false
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("theme", theme);
|
||||||
|
}, [theme]);
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("collapsed", collapsed);
|
||||||
|
}, [collapsed]);
|
||||||
|
|
||||||
|
let location: any = useLocation();
|
||||||
|
const clickLogout = () => {
|
||||||
|
LogoutApi();
|
||||||
|
};
|
||||||
|
const dark = {
|
||||||
|
components: {
|
||||||
|
Table: {
|
||||||
|
colorBgContainer: "#202020",
|
||||||
|
colorText: "#BBBBBB",
|
||||||
|
headerColor: "#BBBBBB",
|
||||||
|
borderColor: "#3A3A3A",
|
||||||
|
headerSplitColor: "#3A3A3A",
|
||||||
|
rowHoverBg: "#333333",
|
||||||
|
colorBorder: "#3A3A3A",
|
||||||
|
},
|
||||||
|
Layout: {
|
||||||
|
bodyBg: "#181818",
|
||||||
|
},
|
||||||
|
Input: {
|
||||||
|
colorBgContainer: "#2A2A2A",
|
||||||
|
colorBgContainerDisabled: "#2A2A2A",
|
||||||
|
colorText: "#BBBBBB",
|
||||||
|
colorTextPlaceholder: "#BBBBBB",
|
||||||
|
colorBorder: "#3A3A3A",
|
||||||
|
colorFillSecondary: "rgba(0, 0, 0, 0.02)",
|
||||||
|
activeBorderColor: "#3A3A3A",
|
||||||
|
activeShadow: "#3A3A3A",
|
||||||
|
hoverBorderColor: "#3A3A3A",
|
||||||
|
// colorIcon: "#BBBBBB",
|
||||||
|
// colorIconHover: "#BBBBBB",
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
colorBgContainer: "#2A2A2A",
|
||||||
|
colorText: "#BBBBBB",
|
||||||
|
colorTextPlaceholder: "#BBBBBB",
|
||||||
|
colorBorder: "rgba(150, 150, 150, 0.493)",
|
||||||
|
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||||
|
colorIconHover: "#BBB",
|
||||||
|
optionSelectedBg: "#2A2A2A",
|
||||||
|
colorBgElevated: "#333",
|
||||||
|
controlOutline: "none",
|
||||||
|
optionActiveBg: "#333333",
|
||||||
|
colorTextQuaternary: "#3A3A3A",
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
colorBorderSecondary: "rgba(249, 158, 44, 1)",
|
||||||
|
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||||
|
colorPrimaryHover: "#BBBBBB",
|
||||||
|
colorIcon: "rgba(249, 158, 44, 1)",
|
||||||
|
colorIconHover: "rgba(249, 158, 44, 1)",
|
||||||
|
primaryShadow: "none",
|
||||||
|
dangerShadow: "none",
|
||||||
|
colorTextDisabled: "#AAAAAA",
|
||||||
|
borderColorDisabled: "#3A3A3A",
|
||||||
|
},
|
||||||
|
// Form: {
|
||||||
|
// labelColor: "#BBBBBB",
|
||||||
|
// },
|
||||||
|
Tabs: {
|
||||||
|
itemColor: "#BBBBBB",
|
||||||
|
itemHoverColor: "#FFFFFF",
|
||||||
|
itemSelectedColor: "rgba(249, 158, 44, 1)",
|
||||||
|
colorPrimaryActive: "rgba(249, 158, 44, 1)",
|
||||||
|
inkBarColor: "rgba(249, 158, 44, 1)",
|
||||||
|
},
|
||||||
|
// Upload: {
|
||||||
|
// colorText: "#FFFFFF",
|
||||||
|
// colorInfoBgHover: "#1E1E1E",
|
||||||
|
// },
|
||||||
|
// Pagination: {
|
||||||
|
// colorText: "#BBBBBB",
|
||||||
|
// colorPrimary: "#FFFFFF",
|
||||||
|
// colorBgContainer: "#1A1A1A",
|
||||||
|
// colorBorderSecondary: "#3A3A3A",
|
||||||
|
// },
|
||||||
|
Modal: {
|
||||||
|
contentBg: "#3A3A3A",
|
||||||
|
headerBg: "#3A3A3A",
|
||||||
|
titleColor: "#FFFFFF",
|
||||||
|
colorText: "#BBBBBB",
|
||||||
|
colorBgTextActive: "#BBBBBB",
|
||||||
|
colorBgTextHover: "#BBBBBB",
|
||||||
|
},
|
||||||
|
Menu: {
|
||||||
|
darkItemSelectedBg: "#3A3A3A",
|
||||||
|
colorBgContainer: "#fff",
|
||||||
|
},
|
||||||
|
Switch: {
|
||||||
|
colorPrimary: "#565656",
|
||||||
|
colorPrimaryHover: "#737373",
|
||||||
|
},
|
||||||
|
Radio: {
|
||||||
|
colorText: "#737373",
|
||||||
|
colorBorder: "#3A3A3A",
|
||||||
|
colorPrimaryActive: "#BBBBBB",
|
||||||
|
buttonCheckedBg: "rgba(249, 158, 44, 1)",
|
||||||
|
colorPrimaryHover: "#737373",
|
||||||
|
colorPrimary: "#565656",
|
||||||
|
},
|
||||||
|
Dropdown: {
|
||||||
|
colorBgContainer: "#3A3A3A",
|
||||||
|
colorText: "#BBBBBB",
|
||||||
|
colorPrimaryHover: "#565656",
|
||||||
|
colorPrimary: "#333333",
|
||||||
|
},
|
||||||
|
DatePicker: {
|
||||||
|
colorBgContainer: "#3A3A3A",
|
||||||
|
colorBgElevated: "#3A3A3A",
|
||||||
|
colorText: "#BBBBBB",
|
||||||
|
colorTextPlaceholder: "#BBBBBB",
|
||||||
|
colorIcon: "#fff",
|
||||||
|
colorIconHover: "#fff",
|
||||||
|
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||||
|
hoverBorderColor: "#BBBBBB",
|
||||||
|
},
|
||||||
|
Empty: {
|
||||||
|
colorText: "rgba(249, 158, 44, 1)",
|
||||||
|
colorTextDisabled: "rgba(249, 158, 44, 1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
token: {
|
||||||
|
fontFamily: "Inter, sans-serif",
|
||||||
|
colorText: "#bbb",
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const light = {
|
||||||
|
components: {
|
||||||
|
Table: {
|
||||||
|
rowHoverBg: "#bae0ff",
|
||||||
|
headerBg: "none",
|
||||||
|
colorText: "rgba(24, 26, 41, 1)",
|
||||||
|
fontWeightStrong: 500,
|
||||||
|
colorTextHeading: "rgba(161, 162, 171, 1)",
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
colorTextPlaceholder: "rgba(155, 157, 170, 1)",
|
||||||
|
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||||
|
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||||
|
},
|
||||||
|
Tabs: {
|
||||||
|
inkBarColor: "rgba(249, 158, 44, 1)",
|
||||||
|
itemSelectedColor: "rgba(24, 26, 41, 1)",
|
||||||
|
itemHoverColor: "rgba(24, 26, 41, 1)",
|
||||||
|
},
|
||||||
|
Input: {
|
||||||
|
hoverBorderColor: "rgba(249, 158, 44, 1)",
|
||||||
|
activeBorderColor: "rgba(249, 158, 44, 1)",
|
||||||
|
colorTextPlaceholder: "rgba(155, 157, 170, 1)",
|
||||||
|
},
|
||||||
|
Upload: {
|
||||||
|
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
colorPrimary: "rgba(249, 158, 44, 1)",
|
||||||
|
colorPrimaryHover: "rgba(249, 158, 44, 1)",
|
||||||
|
},
|
||||||
|
Textarea: {
|
||||||
|
colorBorder: "0px 1px 3px 0px rgba(20, 22, 41, 0.1)",
|
||||||
|
},
|
||||||
|
Menu: {
|
||||||
|
darkItemSelectedBg: "rgba(255, 255, 255, 0.08)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
token: {
|
||||||
|
fontFamily: "Inter, sans-serif",
|
||||||
|
color: "#262626",
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const rep = () => {
|
||||||
|
document.location.replace("/");
|
||||||
|
};
|
||||||
|
const menu: any = (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item key="profile">
|
||||||
|
<Link to="profile/">Profile</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="logout" danger onClick={clickLogout}>
|
||||||
|
Logout
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider theme={theme === true ? dark : light}>
|
||||||
|
<div>
|
||||||
|
{!authorized &&
|
||||||
|
!(
|
||||||
|
location.pathname.startsWith("/auth/register") ||
|
||||||
|
location.pathname.startsWith("/auth/activate") ||
|
||||||
|
location.pathname.startsWith("/auth/reset_password") ||
|
||||||
|
location.pathname.startsWith("/auth/reset-password") ||
|
||||||
|
location.pathname.startsWith("/auth/login") ||
|
||||||
|
location.pathname.startsWith("/auth/invite")
|
||||||
|
) && (
|
||||||
|
<Navigate
|
||||||
|
to={{
|
||||||
|
pathname: "/auth/login",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{authorized && location.pathname === "/login" && (
|
||||||
|
<Navigate
|
||||||
|
to={{
|
||||||
|
pathname: "/",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{authorized ? (
|
||||||
|
<Layout>
|
||||||
|
<Sider
|
||||||
|
theme={"dark"}
|
||||||
|
collapsible
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={(value) => setCollapsed(value)}
|
||||||
|
style={{
|
||||||
|
height: "100vh",
|
||||||
|
background: theme === true ? "#202020" : "rgba(20, 22, 41, 1)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
onClick={rep}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
className={collapsed ? "logo-collapsed" : "logo"}
|
||||||
|
>
|
||||||
|
TT ELD
|
||||||
|
</p>
|
||||||
|
<Menu
|
||||||
|
theme={"dark"}
|
||||||
|
mode="inline"
|
||||||
|
defaultSelectedKeys={[location.pathname]}
|
||||||
|
items={allMenu}
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
theme === true ? "#202020" : "rgba(20, 22, 41, 1)",
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
></Menu>
|
||||||
|
</Sider>
|
||||||
|
<Layout className="site-layout">
|
||||||
|
<Header
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 0,
|
||||||
|
background:
|
||||||
|
theme === true ? "#202020" : "rgba(215, 216, 224, 1)",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "end",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
float: "right",
|
||||||
|
marginRight: "35px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
alignSelf: "center",
|
||||||
|
minWidth: 150,
|
||||||
|
maxWidth: 500,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="theme-btn"
|
||||||
|
onClick={(e) => setTheme(!theme)}
|
||||||
|
>
|
||||||
|
<img src={themeBtn} alt="" />
|
||||||
|
</button>
|
||||||
|
<Dropdown overlay={menu} trigger={["click"]}>
|
||||||
|
<div
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<div className="profile-dropdown">
|
||||||
|
<div className="profile-dropdown-ava">
|
||||||
|
<img src={avatar} alt="" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="d-flex profile-dropdown-text"
|
||||||
|
style={{ flexDirection: "column" }}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className={
|
||||||
|
!theme ? "business-name" : "business-name-dark"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{userObject?.username}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</Header>
|
||||||
|
<Content
|
||||||
|
id="element"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
minHeight: "92vh",
|
||||||
|
maxHeight: "calc(90vh - 10px)",
|
||||||
|
overflowY: "scroll",
|
||||||
|
background: theme === true ? "#202020" : "#fff",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Routes>
|
||||||
|
{mainItems &&
|
||||||
|
mainItems.map((u) => (
|
||||||
|
<Route key={u.key} path={u.path} element={u.component} />
|
||||||
|
))}
|
||||||
|
{superItems &&
|
||||||
|
superItems.map((u) => (
|
||||||
|
<Route key={u.key} path={u.path} element={u.component} />
|
||||||
|
))}
|
||||||
|
<Route path="*" element={<Notfound />} />
|
||||||
|
</Routes>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<Routes>
|
||||||
|
<Route path="/auth/login" element={<Login />} />
|
||||||
|
<Route path="/auth/register" element={<Register />} />
|
||||||
|
<Route path="/auth/activate" element={<Activate />} />
|
||||||
|
<Route path="/auth/invite" element={<Invite />} />
|
||||||
|
<Route path="/auth/reset_password" element={<ResetPassword />} />
|
||||||
|
<Route path="/auth/reset-password" element={<ResetByEmail />} />
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Navigate, useLocation } from "react-router-dom";
|
||||||
|
import { registryVerify } from "../API/auth/activate";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
const Activate = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const queryParameters = new URLSearchParams(location.search);
|
||||||
|
const userId = queryParameters.get("user_id");
|
||||||
|
const confirmationToken = queryParameters.get("confirmation_token");
|
||||||
|
|
||||||
|
if (userId && confirmationToken) {
|
||||||
|
registryVerify({
|
||||||
|
user_id: userId,
|
||||||
|
confirmation_token: confirmationToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Activate;
|
@ -0,0 +1,32 @@
|
|||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { inviteVerify } from "../API/auth/activate";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
const Invite = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const queryParameters = new URLSearchParams(location.search);
|
||||||
|
const userId = queryParameters.get("user_id");
|
||||||
|
const confirmationToken = queryParameters.get("confirmation_token");
|
||||||
|
const business_id = queryParameters.get("business_id");
|
||||||
|
const role_id = queryParameters.get("role_id");
|
||||||
|
if (userId && confirmationToken) {
|
||||||
|
inviteVerify({
|
||||||
|
user_id: userId,
|
||||||
|
confirmation_token: confirmationToken,
|
||||||
|
role_id: role_id,
|
||||||
|
business_id: business_id,
|
||||||
|
}).then((status) => {
|
||||||
|
console.log(status);
|
||||||
|
|
||||||
|
if (status === 200) {
|
||||||
|
document.location.replace("/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.warning({ content: "Your Activision is expired" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>hello</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Invite;
|
@ -0,0 +1,125 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Button, Card, Input, Space } from "antd";
|
||||||
|
import { Form, Field } from "react-final-form";
|
||||||
|
import { LockOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
|
import { LoginApi } from "../API/auth/Login";
|
||||||
|
|
||||||
|
const Login: React.FC = () => {
|
||||||
|
const validate = (val: any) => {
|
||||||
|
const err: any = {};
|
||||||
|
if (!val.login) {
|
||||||
|
err.login = "Required";
|
||||||
|
}
|
||||||
|
if (!val.password) {
|
||||||
|
err.password = "Required";
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
const sleep = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
const onSubmit = async (values: any) => {
|
||||||
|
await sleep(300);
|
||||||
|
await LoginApi({ username: values.login, password: values.password });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ContainerClassName" style={{ height: "100vh" }}>
|
||||||
|
<Form
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
validate={validate}
|
||||||
|
render={({ submitError, handleSubmit, submitting }) => (
|
||||||
|
<Space
|
||||||
|
direction="horizontal"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
bodyStyle={{ background: "rgb(250, 250, 250)" }}
|
||||||
|
title="Login"
|
||||||
|
className="login-form-card "
|
||||||
|
style={{ width: 400 }}
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex", gap: "30px" }}
|
||||||
|
>
|
||||||
|
<Field name="login">
|
||||||
|
{({ input, meta }) => (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
prefix={
|
||||||
|
<UserOutlined className="site-form-item-icon" />
|
||||||
|
}
|
||||||
|
size={"large"}
|
||||||
|
{...input}
|
||||||
|
type="text"
|
||||||
|
placeholder="username or e-mail"
|
||||||
|
/>
|
||||||
|
{(meta.error || meta.submitError) && meta.touched && (
|
||||||
|
<span style={{ color: "red" }}>
|
||||||
|
{meta.error || meta.submitError}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="password">
|
||||||
|
{({ input, meta }) => (
|
||||||
|
<div>
|
||||||
|
<Input.Password
|
||||||
|
prefix={
|
||||||
|
<LockOutlined className="site-form-item-icon" />
|
||||||
|
}
|
||||||
|
size={"large"}
|
||||||
|
{...input}
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
/>
|
||||||
|
{meta.error && meta.touched && (
|
||||||
|
<span style={{ color: "red" }}>{meta.error}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
{submitError && (
|
||||||
|
<div style={{ color: "red" }} className="error">
|
||||||
|
{submitError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="Login-form-button"
|
||||||
|
size={"large"}
|
||||||
|
htmlType="submit"
|
||||||
|
type="primary"
|
||||||
|
disabled={submitting}
|
||||||
|
>
|
||||||
|
Log In
|
||||||
|
</Button>
|
||||||
|
{/* <h5>
|
||||||
|
<Link to='/auth/reset_password'>Forgot password?</Link>
|
||||||
|
<br />
|
||||||
|
Don't have an account?
|
||||||
|
<Link to='/auth/register'> Create one now</Link>
|
||||||
|
</h5> */}
|
||||||
|
</Space>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Login;
|
@ -0,0 +1,105 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button, Card, Input, Space } from "antd";
|
||||||
|
import { Form, Field } from "react-final-form";
|
||||||
|
import { UserOutlined } from "@ant-design/icons";
|
||||||
|
import { resetPass } from "../API/auth/resetPass";
|
||||||
|
import Success from "./Success";
|
||||||
|
|
||||||
|
const ResetPassword: React.FC = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const validate = (val: any) => {
|
||||||
|
const err: any = {};
|
||||||
|
if (!val.login) {
|
||||||
|
err.login = "Required";
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sleep = (ms: any) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
const onSubmit = async (values: any) => {
|
||||||
|
setEmail(values.login);
|
||||||
|
await sleep(300);
|
||||||
|
await resetPass({ login: values.login }).then(() => {
|
||||||
|
setOpen(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ContainerClassName" style={{ height: "100vh" }}>
|
||||||
|
<Form
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
validate={validate}
|
||||||
|
render={({ submitError, handleSubmit, submitting }) => (
|
||||||
|
<Space
|
||||||
|
direction="horizontal"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
bodyStyle={{ background: "rgb(250, 250, 250)" }}
|
||||||
|
title="Login"
|
||||||
|
className="login-form-card "
|
||||||
|
style={{ width: 400 }}
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex", gap: "30px" }}
|
||||||
|
>
|
||||||
|
<Field name="login">
|
||||||
|
{({ input, meta }) => (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
prefix={
|
||||||
|
<UserOutlined className="site-form-item-icon" />
|
||||||
|
}
|
||||||
|
size={"large"}
|
||||||
|
{...input}
|
||||||
|
type="text"
|
||||||
|
placeholder="username or e-mail"
|
||||||
|
/>
|
||||||
|
{(meta.error || meta.submitError) && meta.touched && (
|
||||||
|
<span style={{ color: "red" }}>
|
||||||
|
{meta.error || meta.submitError}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
{submitError && (
|
||||||
|
<div style={{ color: "red" }} className="error">
|
||||||
|
{submitError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="Login-form-button"
|
||||||
|
size={"large"}
|
||||||
|
htmlType="submit"
|
||||||
|
type="primary"
|
||||||
|
disabled={submitting}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Success open={open} setOpen={setOpen} email={email} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResetPassword;
|
@ -0,0 +1,35 @@
|
|||||||
|
import { Modal } from "antd";
|
||||||
|
|
||||||
|
const Success = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
email,
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal open={open} title="Success!" onCancel={handleCancel} footer={null}>
|
||||||
|
<p>
|
||||||
|
A password-reset-link has been sent to your email! To reset your
|
||||||
|
password you will need to follow the link provided in the message. The
|
||||||
|
letter was sent to:{" "}
|
||||||
|
<span style={{ textDecoration: "underline" }}>{email}</span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
If for some reason you do not reset your password, within three days,
|
||||||
|
the link sent to your e-mail will expire. You can request for
|
||||||
|
control letter again.
|
||||||
|
</p>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Success;
|
@ -0,0 +1,33 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import CallTable from "./CallTable";
|
||||||
|
import { useCallData } from "../../Hooks/CallRequests";
|
||||||
|
import { Radio, RadioChangeEvent } from "antd";
|
||||||
|
|
||||||
|
const Call = () => {
|
||||||
|
const [status, setStatus] = useState("Awaiting");
|
||||||
|
const { data, isLoading, refetch } = useCallData({ status: status });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div className="header d-flex">
|
||||||
|
<p className="title">Call Requests</p>
|
||||||
|
</div>
|
||||||
|
<div className="filter d-flex">
|
||||||
|
<Radio.Group
|
||||||
|
onChange={(e: RadioChangeEvent) => setStatus(e.target.value)}
|
||||||
|
size="middle"
|
||||||
|
value={status}
|
||||||
|
style={{ marginLeft: 20 }}
|
||||||
|
>
|
||||||
|
<Radio.Button value={"Awaiting"}>Awaiting</Radio.Button>
|
||||||
|
<Radio.Button value={"Resolved"}>Resolved</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CallTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Call;
|
@ -0,0 +1,155 @@
|
|||||||
|
import { Button, Input, Modal, Space, Table } from "antd";
|
||||||
|
import { TCall } from "../../types/CallRequests/TCall";
|
||||||
|
import { EditOutlined } from "@ant-design/icons";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
import moment from "moment";
|
||||||
|
import { callController } from "../../API/LayoutApi/callrequests";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { useState } from "react";
|
||||||
|
const CallTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
data: TCall[] | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TCall[], unknown>>;
|
||||||
|
}) => {
|
||||||
|
const statusClick = (record: TCall | any) => {
|
||||||
|
callController
|
||||||
|
.callPatch({ note: undefined, status: "Resolved" }, record.id)
|
||||||
|
.then(() => {
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [note, setNote] = useState("");
|
||||||
|
const [id, setId] = useState<number>();
|
||||||
|
const addNote = (a: any) => {
|
||||||
|
setModalVisible(true);
|
||||||
|
setId(a.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
if (id) {
|
||||||
|
callController
|
||||||
|
.callPatch({ note: note, status: undefined }, id)
|
||||||
|
.then(() => {
|
||||||
|
refetch();
|
||||||
|
setNote("");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNoteChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setNote(e.target.value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
loading={isLoading}
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
...u,
|
||||||
|
no: i + 1,
|
||||||
|
id: u?.id,
|
||||||
|
company: u?.company?.name,
|
||||||
|
driver: u?.driver?.name,
|
||||||
|
time: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||||
|
"DD.MM.YYYY HH:mm"
|
||||||
|
),
|
||||||
|
action: u,
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Company",
|
||||||
|
dataIndex: "company",
|
||||||
|
width: "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Driver",
|
||||||
|
dataIndex: "driver",
|
||||||
|
width: "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Note",
|
||||||
|
dataIndex: "note",
|
||||||
|
width: "15%",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Status",
|
||||||
|
// dataIndex: "status",
|
||||||
|
// width: "8%",
|
||||||
|
// render: (status: string) => (
|
||||||
|
// <span>
|
||||||
|
// {status === "Awaiting" && (
|
||||||
|
// <p className="status-new">Awaiting</p>
|
||||||
|
// )}
|
||||||
|
// {status === "Resolved" && (
|
||||||
|
// <p className="status-done">Resolved</p>
|
||||||
|
// )}
|
||||||
|
// </span>
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: "Requested at",
|
||||||
|
dataIndex: "time",
|
||||||
|
width: "15%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Actions",
|
||||||
|
dataIndex: "action",
|
||||||
|
width: "100px",
|
||||||
|
render: (text, record) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{record.status !== "Resolved" && (
|
||||||
|
<Button type="primary" onClick={() => statusClick(record)}>
|
||||||
|
Resolve
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 16 }}
|
||||||
|
type="primary"
|
||||||
|
onClick={(e) => addNote(record)}
|
||||||
|
>
|
||||||
|
<EditOutlined />
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
title="Add note"
|
||||||
|
visible={modalVisible}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
>
|
||||||
|
<Input value={note} onChange={(e) => handleNoteChange(e)} />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CallTable;
|
@ -0,0 +1,115 @@
|
|||||||
|
import { Input, Modal, Form as FormAnt, Switch, Select } from "antd";
|
||||||
|
import { companyController } from "../../API/LayoutApi/companies";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { TCompany } from "../../types/Company/TCompany";
|
||||||
|
|
||||||
|
const AddCompany = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TCompany[], unknown>>;
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
const eld = [
|
||||||
|
{ name: "Zippy" },
|
||||||
|
{ name: "EVO" },
|
||||||
|
{ name: "Ontime" },
|
||||||
|
{ name: "Zeelog" },
|
||||||
|
{ name: "TT" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="Add Company"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form.validateFields().then(async (values) => {
|
||||||
|
await companyController
|
||||||
|
.addCompanyController(values)
|
||||||
|
.then((data) => {
|
||||||
|
if (data) {
|
||||||
|
form.resetFields();
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: "Please enter company name!" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Source"
|
||||||
|
name="source"
|
||||||
|
rules={[{ required: false, message: "Please input owner name!" }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={eld?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.name,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Is Active"
|
||||||
|
name="is_active"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input company status!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Switch defaultChecked={true} />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="USDOT"
|
||||||
|
name="usdot"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input company status!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="API Key"
|
||||||
|
name="api_key"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input company status!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCompany;
|
@ -0,0 +1,120 @@
|
|||||||
|
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||||
|
import { customerController } from "../../API/LayoutApi/customers";
|
||||||
|
import { useCompanyData, useCompanyOne } from "../../Hooks/Companies";
|
||||||
|
// @ts-ignore
|
||||||
|
import zippy from "../../assets/zippyicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import evo from "../../assets/evoicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zeelog from "../../assets/zeelogicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import ontime from "../../assets/ontimeicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tt from "../../assets/tticon.svg";
|
||||||
|
import { useState } from "react";
|
||||||
|
const AddDriver = ({
|
||||||
|
open,
|
||||||
|
id,
|
||||||
|
setOpen,
|
||||||
|
}: {
|
||||||
|
id: any;
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
const companyData = useCompanyOne(id);
|
||||||
|
const companyDataAll = useCompanyData({ name: "" });
|
||||||
|
const [companyName, setCompanyName] = useState<string>();
|
||||||
|
const getImageSource = (source: string) => {
|
||||||
|
switch (source) {
|
||||||
|
case "Zippy":
|
||||||
|
return zippy;
|
||||||
|
case "EVO":
|
||||||
|
return evo;
|
||||||
|
case "Ontime":
|
||||||
|
return ontime;
|
||||||
|
case "Zeelog":
|
||||||
|
return zeelog;
|
||||||
|
case "TT":
|
||||||
|
return tt;
|
||||||
|
default:
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="Add Driver"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form.validateFields().then(async (values) => {
|
||||||
|
form.resetFields();
|
||||||
|
const updatedValues = { ...values };
|
||||||
|
updatedValues.company_id = id;
|
||||||
|
await customerController.addCustomerController(updatedValues);
|
||||||
|
setOpen(!open);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: "Please input Name!" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Company"
|
||||||
|
name="company_id"
|
||||||
|
rules={[{ required: false, message: "Please input company!" }]}
|
||||||
|
>
|
||||||
|
{id ? (
|
||||||
|
<Input defaultValue={companyData?.data?.name} readOnly />
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="Search Company"
|
||||||
|
onSearch={(value: any) => setCompanyName(value)}
|
||||||
|
options={companyDataAll?.data?.map((item: any) => ({
|
||||||
|
label: (
|
||||||
|
<div>
|
||||||
|
{item?.source && (
|
||||||
|
<img
|
||||||
|
style={{ width: 15, height: 20, paddingTop: 7 }}
|
||||||
|
src={getImageSource(item?.source)}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
)}{" "}
|
||||||
|
{item?.name}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
value={companyName}
|
||||||
|
filterOption={false}
|
||||||
|
autoClearSearchValue={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddDriver;
|
@ -0,0 +1,67 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
import AddCompany from "./AddCompanies";
|
||||||
|
import CompanyTable from "./CompaniesTable";
|
||||||
|
// @ts-ignore
|
||||||
|
import IconSearch from "../../assets/searchIcon.png";
|
||||||
|
|
||||||
|
import { useCompanyData } from "../../Hooks/Companies";
|
||||||
|
//@ts-ignore
|
||||||
|
import addicon from "../../assets/addiconpng.png";
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
|
||||||
|
const Company = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [search, setSearch] = useState<any>("");
|
||||||
|
const { data, isLoading, refetch } = useCompanyData({
|
||||||
|
name: search,
|
||||||
|
is_active: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchText = e.target.value;
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setSearch(searchText);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{open && <AddCompany open={open} refetch={refetch} setOpen={setOpen} />}
|
||||||
|
<div className="header d-flex">
|
||||||
|
<h1 className="title">Companies</h1>
|
||||||
|
<button
|
||||||
|
style={{ marginRight: 0 }}
|
||||||
|
className="btn-add d-flex"
|
||||||
|
onClick={showModal}
|
||||||
|
>
|
||||||
|
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||||
|
Add Company
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="filter d-flex">
|
||||||
|
<div className="search-div">
|
||||||
|
<img src={IconSearch} alt="" />
|
||||||
|
<input
|
||||||
|
className={`search-input-${theme}`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CompanyTable data={data} isLoading={isLoading} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Company;
|
@ -0,0 +1,359 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { useCompanyOne } from "../../Hooks/Companies";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Spin,
|
||||||
|
Watermark,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Tag,
|
||||||
|
Radio,
|
||||||
|
RadioChangeEvent,
|
||||||
|
Select,
|
||||||
|
} from "antd";
|
||||||
|
import { companyController } from "../../API/LayoutApi/companies";
|
||||||
|
import { DashboardOutlined } from "@ant-design/icons";
|
||||||
|
import Notfound from "../../Utils/Notfound";
|
||||||
|
import Table from "antd/es/table";
|
||||||
|
import AddDriver from "./AddDriver";
|
||||||
|
import { useCustomerByComanyData } from "../../Hooks/Customers";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import zippy from "../../assets/zippyicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import evo from "../../assets/evoicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zeelog from "../../assets/zeelogicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import ontime from "../../assets/ontimeicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tt from "../../assets/tticon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIconActive from "../../assets/infoIconActive.png";
|
||||||
|
import { role } from "../../App";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams";
|
||||||
|
import { validateLocaleAndSetLanguage } from "typescript";
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
type params = {
|
||||||
|
readonly id: any;
|
||||||
|
};
|
||||||
|
type MyObjectType = {
|
||||||
|
[key: string | number]: any;
|
||||||
|
};
|
||||||
|
const CompanyEdit = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const { id } = useParams<params>();
|
||||||
|
const customerData = useCustomerByComanyData({ id: id });
|
||||||
|
const { data, refetch, status }: MyObjectType = useCompanyOne(id);
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
|
const onSubmit = async (value: any) => {
|
||||||
|
value.team_id = team;
|
||||||
|
await companyController.companyPatch(value, id);
|
||||||
|
refetch();
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClickDelete = () => {
|
||||||
|
const shouldDelete = window.confirm(
|
||||||
|
"Вы уверены, что хотите удалить эту компанию?"
|
||||||
|
);
|
||||||
|
if (shouldDelete && id !== undefined) {
|
||||||
|
companyController.deleteCompanyController(id).then(() => {
|
||||||
|
document.location.replace(`/#/companies`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [value, setValue] = useState(1);
|
||||||
|
const onChange = (e: RadioChangeEvent) => {
|
||||||
|
console.log("radio checked", e.target.value);
|
||||||
|
setValue(e.target.value);
|
||||||
|
};
|
||||||
|
const [activeTab, setActiveTab] = useState("1");
|
||||||
|
|
||||||
|
const TeamData = useTeamData("");
|
||||||
|
const noTeamOption = { label: " - - - - - -", value: "" };
|
||||||
|
const TeamOption: { label: string; value: any }[] | undefined =
|
||||||
|
TeamData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}));
|
||||||
|
if (TeamOption) {
|
||||||
|
TeamOption.unshift(noTeamOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [team, setTeam] = useState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Watermark style={{ height: "100%" }}>
|
||||||
|
{status === "loading" ? (
|
||||||
|
<Spin size="large" spinning={!data} />
|
||||||
|
) : data ? (
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="1"
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(key) => setActiveTab(key)}
|
||||||
|
>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
Information
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
onFinish={onSubmit}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label={"Name"}
|
||||||
|
name="name"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="USDOT"
|
||||||
|
name="usdot"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Team"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={TeamOption}
|
||||||
|
defaultValue={data?.team?.name}
|
||||||
|
onChange={(e) => setTeam(e)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="API Key"
|
||||||
|
name="api_key"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Source"
|
||||||
|
name="source"
|
||||||
|
>
|
||||||
|
<Radio.Group
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Radio value="TT">
|
||||||
|
<img
|
||||||
|
style={{ width: 50, height: 50 }}
|
||||||
|
src={tt}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Radio>
|
||||||
|
<Radio value="EVO">
|
||||||
|
<img
|
||||||
|
style={{ width: 50, height: 50 }}
|
||||||
|
src={evo}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Radio>
|
||||||
|
<Radio value="Zippy">
|
||||||
|
<img
|
||||||
|
style={{ width: 50, height: 50 }}
|
||||||
|
src={zippy}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Radio>
|
||||||
|
<Radio value="Ontime">
|
||||||
|
<img
|
||||||
|
style={{ width: 50, height: 50 }}
|
||||||
|
src={ontime}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Radio>
|
||||||
|
<Radio value="Zeelog">
|
||||||
|
<img
|
||||||
|
style={{ width: 50, height: 50 }}
|
||||||
|
src={zeelog}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item>
|
||||||
|
{role === "Owner" && (
|
||||||
|
<Button
|
||||||
|
onClick={() => ClickDelete()}
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{role === "Owner" && (
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span>
|
||||||
|
<DashboardOutlined />
|
||||||
|
Drivers
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="2"
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
onRow={(record) => {
|
||||||
|
let isTextSelected = false;
|
||||||
|
document.addEventListener("selectionchange", () => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (
|
||||||
|
selection !== null &&
|
||||||
|
selection.toString() !== ""
|
||||||
|
) {
|
||||||
|
isTextSelected = true;
|
||||||
|
} else {
|
||||||
|
isTextSelected = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
onClick: (event: any) => {
|
||||||
|
if (isTextSelected) {
|
||||||
|
}
|
||||||
|
document.location.replace(
|
||||||
|
`/#/customers/${record.id}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
dataSource={customerData?.data?.map((u, i) => ({
|
||||||
|
...u,
|
||||||
|
no: i + 1,
|
||||||
|
key: u?.id,
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Role",
|
||||||
|
dataIndex: "profession",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Is Active",
|
||||||
|
dataIndex: "is_active",
|
||||||
|
render: (tag: boolean) => (
|
||||||
|
<Tag color={tag ? "geekblue" : "red"}>
|
||||||
|
{tag ? "True" : "False"}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: "True",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "False",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value: any, record: any) => {
|
||||||
|
return record.isActive === value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{open && (
|
||||||
|
<AddDriver id={id} open={open} setOpen={setOpen} />
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginLeft: "auto" }}
|
||||||
|
size={"middle"}
|
||||||
|
onClick={showModal}
|
||||||
|
disabled={role !== "Owner"}
|
||||||
|
>
|
||||||
|
Add Driver
|
||||||
|
</Button>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Space>
|
||||||
|
</Spin>
|
||||||
|
) : (
|
||||||
|
<Notfound />
|
||||||
|
)}
|
||||||
|
</Watermark>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CompanyEdit;
|
@ -0,0 +1,177 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button, Space, Table, Tooltip, message } from "antd";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { SyncOutlined, EyeOutlined } from "@ant-design/icons";
|
||||||
|
import { companyController } from "../../API/LayoutApi/companies";
|
||||||
|
import { TCompany } from "../../types/Company/TCompany";
|
||||||
|
// @ts-ignore
|
||||||
|
import zippy from "../../assets/zippyicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import evo from "../../assets/evoicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zeelog from "../../assets/zeelogicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import ontime from "../../assets/ontimeicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tt from "../../assets/tticon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
import { role } from "../../App";
|
||||||
|
|
||||||
|
function CompanyTable({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
}: {
|
||||||
|
data?: TCompany[] | undefined;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}) {
|
||||||
|
const moment = require("moment");
|
||||||
|
const [loadings, setLoadings] = useState<boolean[]>([]);
|
||||||
|
function getStatusClassName() {
|
||||||
|
if (role !== "Owner") {
|
||||||
|
return "isnot";
|
||||||
|
} else if (role === "Owner") {
|
||||||
|
return "super";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImageSource = (source: string) => {
|
||||||
|
switch (source) {
|
||||||
|
case "Zippy":
|
||||||
|
return zippy;
|
||||||
|
case "EVO":
|
||||||
|
return evo;
|
||||||
|
case "Ontime":
|
||||||
|
return ontime;
|
||||||
|
case "Zeelog":
|
||||||
|
return zeelog;
|
||||||
|
case "TT":
|
||||||
|
return tt;
|
||||||
|
default:
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
...u,
|
||||||
|
no: i + 1,
|
||||||
|
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||||
|
"DD.MM.YYYY HH:mm"
|
||||||
|
),
|
||||||
|
key: u?.id,
|
||||||
|
action: { ...u },
|
||||||
|
}))}
|
||||||
|
loading={isLoading}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Company",
|
||||||
|
dataIndex: "name",
|
||||||
|
width: "25%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (text, record) => (
|
||||||
|
<Tooltip placement="topLeft" title={text}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{record?.source && (
|
||||||
|
<img
|
||||||
|
src={getImageSource(record?.source)}
|
||||||
|
alt=""
|
||||||
|
style={{ width: 20, height: 20, marginRight: 10 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
dataIndex: "team",
|
||||||
|
render: (status: string, record: TCompany) => (
|
||||||
|
<span className={getStatusClassName()}>{record?.team?.name}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "USDOT",
|
||||||
|
dataIndex: "usdot",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "API KEY",
|
||||||
|
dataIndex: "api_key",
|
||||||
|
render: (status: string, record: TCompany) => (
|
||||||
|
<span className={getStatusClassName()}>{status}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Created at",
|
||||||
|
dataIndex: "created",
|
||||||
|
responsive: ["lg"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: "20%",
|
||||||
|
title: "Actions",
|
||||||
|
dataIndex: "action",
|
||||||
|
render: ({ id, api }: { id: string; api: string }) => {
|
||||||
|
const enterLoading = (index: number, api: string) => {
|
||||||
|
setLoadings((prevLoadings) => {
|
||||||
|
const newLoadings = [...prevLoadings];
|
||||||
|
newLoadings[index] = true;
|
||||||
|
return newLoadings;
|
||||||
|
});
|
||||||
|
if (api && api !== "") {
|
||||||
|
companyController.SyncCompany(index);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: "This company doesn't have an api key",
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoadings((prevLoadings) => {
|
||||||
|
const newLoadings = [...prevLoadings];
|
||||||
|
newLoadings[index] = false;
|
||||||
|
return newLoadings;
|
||||||
|
});
|
||||||
|
}, 6000);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Link to={`${id}`}>
|
||||||
|
{role === "Owner" && <Button type="primary">Edit</Button>}
|
||||||
|
{role !== "Owner" && (
|
||||||
|
<Button type="primary" icon={<EyeOutlined />}></Button>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{role === "Owner" && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SyncOutlined />}
|
||||||
|
loading={loadings[Number(id)]}
|
||||||
|
onClick={() => enterLoading(Number(id), api)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CompanyTable;
|
@ -0,0 +1,122 @@
|
|||||||
|
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||||
|
import { customerController } from "../../API/LayoutApi/customers";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useCompanyData } from "../../Hooks/Companies";
|
||||||
|
// @ts-ignore
|
||||||
|
import zippy from "../../assets/zippyicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import evo from "../../assets/evoicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zeelog from "../../assets/zeelogicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import ontime from "../../assets/ontimeicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tt from "../../assets/tticon.svg";
|
||||||
|
|
||||||
|
const AddCustomer = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
refetch: any;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [companyName, setCompanyName] = useState<string>("");
|
||||||
|
const { data } = useCompanyData({ name: companyName });
|
||||||
|
|
||||||
|
const getImageSource = (source: string) => {
|
||||||
|
switch (source) {
|
||||||
|
case "Zippy":
|
||||||
|
return zippy;
|
||||||
|
case "EVO":
|
||||||
|
return evo;
|
||||||
|
case "Ontime":
|
||||||
|
return ontime;
|
||||||
|
case "Zeelog":
|
||||||
|
return zeelog;
|
||||||
|
case "TT":
|
||||||
|
return tt;
|
||||||
|
default:
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="Add Driver"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form.validateFields().then(async (values) => {
|
||||||
|
form.resetFields();
|
||||||
|
await customerController
|
||||||
|
.addCustomerController(values)
|
||||||
|
.then((data) => {
|
||||||
|
if (data) {
|
||||||
|
setOpen(!open);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: "Please input Driver name!" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Company"
|
||||||
|
name="company_id"
|
||||||
|
rules={[{ required: false, message: "Please input Company name!" }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="Search Company"
|
||||||
|
onSearch={(value: any) => setCompanyName(value)}
|
||||||
|
options={data?.map((item) => ({
|
||||||
|
label: (
|
||||||
|
<div>
|
||||||
|
{item?.source && (
|
||||||
|
<img
|
||||||
|
style={{ width: 15, height: 20, paddingTop: 7 }}
|
||||||
|
src={getImageSource(item?.source)}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
)}{" "}
|
||||||
|
{item?.name}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
value={companyName}
|
||||||
|
filterOption={false}
|
||||||
|
autoClearSearchValue={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCustomer;
|
@ -0,0 +1,61 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
import AddCustomer from "./AddCustomer";
|
||||||
|
import CustomerTable from "./CustomersTable";
|
||||||
|
import { useCustomerData } from "../../Hooks/Customers";
|
||||||
|
//@ts-ignore
|
||||||
|
import addicon from "../../assets/addiconpng.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import IconSearch from "../../assets/searchIcon.png";
|
||||||
|
|
||||||
|
const Customer = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const { isLoading, data, refetch } = useCustomerData({
|
||||||
|
name: search,
|
||||||
|
is_active: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchText = e.target.value;
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setSearch(searchText);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{open && <AddCustomer open={open} setOpen={setOpen} refetch={refetch} />}
|
||||||
|
<div className="header d-flex">
|
||||||
|
<p className="title">Drivers</p>
|
||||||
|
<button className="btn-add d-flex" onClick={showModal}>
|
||||||
|
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||||
|
Add Driver
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="filter d-flex">
|
||||||
|
<div className="search-div">
|
||||||
|
<img src={IconSearch} alt="" />
|
||||||
|
<input
|
||||||
|
className={`search-input-${theme}`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CustomerTable data={data} isLoading={isLoading} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Customer;
|
@ -0,0 +1,151 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useCustomerOne } from "../../Hooks/Customers";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Spin,
|
||||||
|
Watermark,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
} from "antd";
|
||||||
|
import { customerController } from "../../API/LayoutApi/customers";
|
||||||
|
import Notfound from "../../Utils/Notfound";
|
||||||
|
import { useCompanyOne } from "../../Hooks/Companies";
|
||||||
|
import { role } from "../../App";
|
||||||
|
import { useState } from "react";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIconActive from "../../assets/infoIconActive.png";
|
||||||
|
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
|
||||||
|
type params = {
|
||||||
|
readonly id: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomerEdit = () => {
|
||||||
|
const { id } = useParams<params>();
|
||||||
|
const { data, refetch, status } = useCustomerOne(id);
|
||||||
|
const onSubmit = async (value: any) => {
|
||||||
|
await customerController.customerPatch(value, id);
|
||||||
|
refetch();
|
||||||
|
window.location.replace("/#/customers/");
|
||||||
|
};
|
||||||
|
|
||||||
|
const companyData = useCompanyOne(data?.id);
|
||||||
|
|
||||||
|
const ClickDelete = () => {
|
||||||
|
const shouldDelete = window.confirm(
|
||||||
|
"Are you sure, you want to delete this Driver?"
|
||||||
|
);
|
||||||
|
if (shouldDelete && id !== undefined) {
|
||||||
|
customerController.deleteCustomerController(id).then((data: any) => {
|
||||||
|
document.location.replace(`/#/customers`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [activeTab, setActiveTab] = useState("1");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Watermark style={{ height: "100%" }}>
|
||||||
|
{status === "loading" ? (
|
||||||
|
<Spin size="large" spinning={!data} />
|
||||||
|
) : data ? (
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="1"
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(key) => setActiveTab(key)}
|
||||||
|
>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
Information
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
onFinish={onSubmit}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
{companyData?.data && (
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Company"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
defaultValue={companyData?.data?.name}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
{role === "Owner" && (
|
||||||
|
<Button
|
||||||
|
onClick={() => ClickDelete()}
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Space>
|
||||||
|
</Spin>
|
||||||
|
) : (
|
||||||
|
<Notfound />
|
||||||
|
)}
|
||||||
|
</Watermark>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomerEdit;
|
@ -0,0 +1,69 @@
|
|||||||
|
import { Table } from "antd";
|
||||||
|
import { useCompanyData } from "../../Hooks/Companies";
|
||||||
|
import { TCustomer } from "../../types/Customer/TCustomer";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
import { role } from "../../App";
|
||||||
|
|
||||||
|
function CustomerTable({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
}: {
|
||||||
|
data?: TCustomer[] | undefined;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}) {
|
||||||
|
const CompanyData = useCompanyData({
|
||||||
|
name: undefined,
|
||||||
|
page: undefined,
|
||||||
|
is_active: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
type RowProps = {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Row = (record: RowProps) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {
|
||||||
|
role !== "Checker" &&
|
||||||
|
document.location.replace(`/#/customers/${record.id}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
onRow={(record) => Row(record)}
|
||||||
|
loading={isLoading}
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
...u,
|
||||||
|
no: i + 1,
|
||||||
|
company_id:
|
||||||
|
CompanyData?.data?.find(
|
||||||
|
(company: any) => company.id === u?.company_id
|
||||||
|
)?.name || "",
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Company",
|
||||||
|
dataIndex: "company_id",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomerTable;
|
@ -0,0 +1,79 @@
|
|||||||
|
import { Form as FormAnt, Input, Button } from "antd";
|
||||||
|
import { prof } from "../../API/LayoutApi/profile";
|
||||||
|
|
||||||
|
const ChangePassword = () => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
form.validateFields().then(async (values) => {
|
||||||
|
form.resetFields();
|
||||||
|
console.log(values);
|
||||||
|
await prof.changePass(values);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: 500 }}>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Old Password"
|
||||||
|
name="old_password"
|
||||||
|
rules={[{ required: true, message: "Your old password!" }]}
|
||||||
|
>
|
||||||
|
<Input.Password />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
name="new_password"
|
||||||
|
label="New Password"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "Please input your password!",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: 8,
|
||||||
|
message: "Password must be at least 8 characters long!",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
hasFeedback
|
||||||
|
>
|
||||||
|
<Input.Password />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
name="password_confirm"
|
||||||
|
label="Confirm Password"
|
||||||
|
dependencies={["new_password"]}
|
||||||
|
hasFeedback
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "Please confirm your password!",
|
||||||
|
},
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value || getFieldValue("new_password") === value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(
|
||||||
|
new Error("The new password that you entered do not match!")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.Password />
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
<Button onClick={submit} type="primary">
|
||||||
|
save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChangePassword;
|
@ -0,0 +1,271 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { TProfilePutParams, prof } from "../../API/LayoutApi/profile";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Col,
|
||||||
|
DatePicker,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Spin,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Watermark,
|
||||||
|
} from "antd";
|
||||||
|
import TabPane from "antd/es/tabs/TabPane";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
useMyHistoryData,
|
||||||
|
useMystatsData,
|
||||||
|
useProfData,
|
||||||
|
} from "../../Hooks/Profile";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
import { role } from "../../App";
|
||||||
|
import ChangePassword from "./ChangePassword";
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
const { data, refetch } = useProfData();
|
||||||
|
const [range, setRange] = useState<any>(1);
|
||||||
|
|
||||||
|
const onSubmit = async (value: TProfilePutParams) => {
|
||||||
|
await prof.profPatch(value);
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const moment = require("moment-timezone");
|
||||||
|
const nowUtcPlus5 = moment.tz("Asia/Tashkent");
|
||||||
|
const formattedTimeMinusFiveSeconds = nowUtcPlus5
|
||||||
|
.subtract(range, "days")
|
||||||
|
.format("YYYY-MM-DDTHH:mm:ss");
|
||||||
|
|
||||||
|
const historyData = useMyHistoryData({
|
||||||
|
start_date: formattedTimeMinusFiveSeconds,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
const currentDate = moment();
|
||||||
|
const start_date = `${currentDate.format("YYYY-MM")}-01 00:00:00`;
|
||||||
|
const [startDate, setStartDate] = useState(start_date);
|
||||||
|
const [endDate, setEndDate] = useState<string | undefined>(undefined);
|
||||||
|
const datePick = (a: any, b: any) => {
|
||||||
|
if (b[0] && b[1]) {
|
||||||
|
setStartDate(`${b[0]} 00:00:00`);
|
||||||
|
setEndDate(`${b[1]} 23:59:59`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: lineData } = useMystatsData({
|
||||||
|
start_date: startDate,
|
||||||
|
end_date: endDate,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Watermark style={{ height: "100%" }}>
|
||||||
|
<Space direction="vertical" size="middle" style={{ display: "flex" }}>
|
||||||
|
<Tabs>
|
||||||
|
<TabPane tab={<span>Information</span>} key="1">
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
{data !== undefined && (
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
autoComplete="off"
|
||||||
|
onFinish={onSubmit}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="First name"
|
||||||
|
name="first_name"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Last name"
|
||||||
|
name="last_name"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Username"
|
||||||
|
name="username"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="E-mail"
|
||||||
|
name="email"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
{data !== undefined && (
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
{data && data.team !== "" && (
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Team"
|
||||||
|
name="team"
|
||||||
|
>
|
||||||
|
<Input readOnly />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
<div className="">
|
||||||
|
<h2 style={{ marginBottom: 20 }}>Your Statistics</h2>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RangePicker onCalendarChange={datePick} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
height: "70vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 50,
|
||||||
|
marginTop: 20,
|
||||||
|
marginLeft: 30,
|
||||||
|
width: "80%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="card_stat">
|
||||||
|
Average:{" "}
|
||||||
|
<span>{lineData?.avg_stats_for_period} </span>
|
||||||
|
{role === "Owner" ? "tasks" : "pts"}/day
|
||||||
|
</p>
|
||||||
|
<p className="card_stat">
|
||||||
|
Total: <span>{lineData?.total_for_period} </span>
|
||||||
|
{role === "Owner" ? "tasks" : "pts"}
|
||||||
|
</p>
|
||||||
|
<p className="card_stat">
|
||||||
|
Contribution: <span>{lineData?.contribution}</span>%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab={<span>History</span>} key="2">
|
||||||
|
<Select
|
||||||
|
style={{ width: "20%", marginBottom: 10 }}
|
||||||
|
placeholder="1 day"
|
||||||
|
onChange={(value: any) =>
|
||||||
|
value ? setRange(value) : setRange("1")
|
||||||
|
}
|
||||||
|
allowClear
|
||||||
|
>
|
||||||
|
<Option value="3">3 days</Option>
|
||||||
|
<Option value="7">a week</Option>
|
||||||
|
<Option value="30">a month</Option>
|
||||||
|
</Select>
|
||||||
|
<Table
|
||||||
|
dataSource={historyData?.data?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
task: { id: u.task },
|
||||||
|
action: u?.action,
|
||||||
|
description:
|
||||||
|
role !== "Owner"
|
||||||
|
? "You finished this task and earned another 5 points!"
|
||||||
|
: `You ${u?.description.slice(
|
||||||
|
u?.description.indexOf(" ") + 1
|
||||||
|
)}`,
|
||||||
|
timestamp: u.timestamp
|
||||||
|
? moment(u.timestamp).format("DD.MM.YYYY, HH:mm")
|
||||||
|
: "",
|
||||||
|
key: u.id,
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img alt="" src={tagIcon} />,
|
||||||
|
dataIndex: "no",
|
||||||
|
key: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Task",
|
||||||
|
dataIndex: "task",
|
||||||
|
key: "task",
|
||||||
|
render: ({ id }: { id: number }) => (
|
||||||
|
<Link to={`/${id}`}>{id}</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Action",
|
||||||
|
dataIndex: "action",
|
||||||
|
key: "action",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Description",
|
||||||
|
dataIndex: "description",
|
||||||
|
key: "description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Timestamp",
|
||||||
|
dataIndex: "timestamp",
|
||||||
|
key: "timestamp",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab={<span>Change Password</span>} key="3">
|
||||||
|
<ChangePassword />
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Space>
|
||||||
|
</Watermark>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
@ -0,0 +1,79 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { useRequestsData } from "../../Hooks/Requests";
|
||||||
|
import RequestsTable from "./RequestsTable";
|
||||||
|
// @ts-ignore
|
||||||
|
import IconSearch from "../../assets/searchIcon.png";
|
||||||
|
import { Radio, RadioChangeEvent } from "antd";
|
||||||
|
import RequestsEdit from "./RequestsEdit";
|
||||||
|
import { TRequests } from "../../types/Requests/TRequests";
|
||||||
|
|
||||||
|
const Requests = () => {
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [status, setStatus] = useState("Pending");
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [requestData, setRequestData] = useState<TRequests>();
|
||||||
|
|
||||||
|
const { data, refetch, isLoading } = useRequestsData({
|
||||||
|
search: search,
|
||||||
|
status: status,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchText = e.target.value;
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setSearch(searchText);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{modalOpen && (
|
||||||
|
<RequestsEdit
|
||||||
|
modalOpen={modalOpen}
|
||||||
|
setModalOpen={setModalOpen}
|
||||||
|
requestData={requestData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="header d-flex">
|
||||||
|
<p className="title">Requests</p>
|
||||||
|
</div>
|
||||||
|
<div className="filter d-flex">
|
||||||
|
<div className="search-div">
|
||||||
|
<img src={IconSearch} alt="" />
|
||||||
|
<input
|
||||||
|
className={`search-input-${theme}`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Radio.Group
|
||||||
|
onChange={(e: RadioChangeEvent) => setStatus(e.target.value)}
|
||||||
|
size="middle"
|
||||||
|
value={status}
|
||||||
|
style={{ marginLeft: 20 }}
|
||||||
|
>
|
||||||
|
<Radio.Button value={"Pending"}>Pending</Radio.Button>
|
||||||
|
<Radio.Button value={"Assigned"}>Assigned</Radio.Button>
|
||||||
|
<Radio.Button value={"Rejected"}>Rejected</Radio.Button>
|
||||||
|
<Radio.Button value={undefined}>All</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
<RequestsTable
|
||||||
|
data={data}
|
||||||
|
isLoading={isLoading}
|
||||||
|
refetch={refetch}
|
||||||
|
setOpenModal={setModalOpen}
|
||||||
|
setRequestData={setRequestData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Requests;
|
@ -0,0 +1,172 @@
|
|||||||
|
import { Modal, Select, Button } from "antd";
|
||||||
|
import { TRequests } from "../../types/Requests/TRequests";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useCustomerData } from "../../Hooks/Customers";
|
||||||
|
import { requestsController } from "../../API/LayoutApi/requests";
|
||||||
|
// @ts-ignore
|
||||||
|
import plus from "../../assets/add-icon.png";
|
||||||
|
const RequestsEdit = ({
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
requestData,
|
||||||
|
}: {
|
||||||
|
modalOpen: any;
|
||||||
|
setModalOpen: any;
|
||||||
|
requestData: TRequests | undefined;
|
||||||
|
}) => {
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
const handleCancel = () => {
|
||||||
|
setModalOpen(!modalOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [customerName, setCustomerName] = useState<string>();
|
||||||
|
const [driverId, setDriverId] = useState<number>();
|
||||||
|
const customerData = useCustomerData({
|
||||||
|
name: customerName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const assignClick = () => {
|
||||||
|
const value = {
|
||||||
|
...requestData,
|
||||||
|
status: "Assigned",
|
||||||
|
customer_id: driverId,
|
||||||
|
};
|
||||||
|
requestsController.requestPatch(value, requestData?.id);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={null}
|
||||||
|
open={modalOpen}
|
||||||
|
width={800}
|
||||||
|
maskClosable={true}
|
||||||
|
>
|
||||||
|
<div className="info-div">
|
||||||
|
<div
|
||||||
|
className="d-flex"
|
||||||
|
style={{ alignItems: "center", marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: "24px",
|
||||||
|
letterSpacing: "-0.02em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Requested Driver Info
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
style={{ marginLeft: 12, display: "flex", alignItems: "center" }}
|
||||||
|
className={`status-${requestData?.status}`}
|
||||||
|
>
|
||||||
|
{requestData?.status}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="info-body">
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Full Name</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{requestData?.full_name}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Company</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{requestData?.company_name}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>USDOT</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{requestData?.company_usdot}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{requestData?.status === "Assigned" && (
|
||||||
|
<div className="info-div" style={{ margin: 0 }}>
|
||||||
|
<div
|
||||||
|
className="d-flex"
|
||||||
|
style={{ alignItems: "center", marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: "24px",
|
||||||
|
marginTop: 20,
|
||||||
|
letterSpacing: "-0.02em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Assigned Driver Info
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="info-body">
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Driver name</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{requestData?.potential_driver?.name}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Company</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{requestData?.potential_driver?.company?.name}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
</div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{requestData?.status === "Pending" && (
|
||||||
|
<div className="search-driver">
|
||||||
|
<div className="d-flex">
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
style={{ width: 325 }}
|
||||||
|
placeholder="Search Driver"
|
||||||
|
onSearch={(value: any) => setCustomerName(value)}
|
||||||
|
onChange={(e: any) => setDriverId(e)}
|
||||||
|
options={customerData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
value={customerName}
|
||||||
|
filterOption={false}
|
||||||
|
autoClearSearchValue={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={assignClick}
|
||||||
|
disabled={driverId ? false : true}
|
||||||
|
style={{ marginLeft: 15 }}
|
||||||
|
>
|
||||||
|
Assign
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={assignClick}
|
||||||
|
disabled={driverId ? true : false}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "6px 15px",
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
className="btn-add"
|
||||||
|
>
|
||||||
|
<img src={plus} alt="" style={{ marginRight: 8 }} />
|
||||||
|
Add new driver
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestsEdit;
|
@ -0,0 +1,143 @@
|
|||||||
|
import { Button, Space, Table } from "antd";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
import moment from "moment";
|
||||||
|
import { TRequests } from "../../types/Requests/TRequests";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { requestsController } from "../../API/LayoutApi/requests";
|
||||||
|
|
||||||
|
const RequestsTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
setOpenModal,
|
||||||
|
setRequestData,
|
||||||
|
}: {
|
||||||
|
setOpenModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
setRequestData: React.Dispatch<React.SetStateAction<TRequests | undefined>>;
|
||||||
|
data: TRequests[] | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TRequests[], unknown>>;
|
||||||
|
}) => {
|
||||||
|
const [isTextSelected, setIsTextSelected] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSelectionChange = () => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
setIsTextSelected(selection !== null && selection.toString() !== "");
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("selectionchange", handleSelectionChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const Row = (record: TRequests, event: any) => {
|
||||||
|
if (isTextSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
event.target.classList.contains("ant-table-cell") &&
|
||||||
|
record.status !== "Rejected"
|
||||||
|
) {
|
||||||
|
setRequestData(record);
|
||||||
|
setOpenModal(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const patchRequest = (record: TRequests) => {
|
||||||
|
requestsController.delete(record?.id).then(() => {
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
onRow={(record) => ({
|
||||||
|
onClick: (event) => Row(record, event),
|
||||||
|
})}
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
action: { id: u.id },
|
||||||
|
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||||
|
"DD.MM.YYYY HH:mm"
|
||||||
|
),
|
||||||
|
...u,
|
||||||
|
}))}
|
||||||
|
loading={isLoading}
|
||||||
|
size="middle"
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Full name",
|
||||||
|
dataIndex: "full_name",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Company",
|
||||||
|
dataIndex: "company_name",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "USDOT",
|
||||||
|
dataIndex: "company_usdot",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Created at",
|
||||||
|
dataIndex: "created",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Actions",
|
||||||
|
dataIndex: "action",
|
||||||
|
render: (text: string, record: TRequests) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{record?.status === "Pending" && (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => patchRequest(record)}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestsTable;
|
@ -0,0 +1,71 @@
|
|||||||
|
import { Input, Modal, Form as FormAnt } from "antd";
|
||||||
|
import { serviceController } from "../../API/LayoutApi/services";
|
||||||
|
|
||||||
|
const AddService = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
refetch(): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="Add service"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then(async (values) => {
|
||||||
|
form.resetFields();
|
||||||
|
await serviceController.addServiceController(values);
|
||||||
|
setOpen(!open);
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Title"
|
||||||
|
name="title"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "Please input service title!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Points"
|
||||||
|
name="points"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "Please input service points!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddService;
|
@ -0,0 +1,146 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useServiceOne } from "../../Hooks/Services";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Spin,
|
||||||
|
Watermark,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
} from "antd";
|
||||||
|
import { serviceController } from "../../API/LayoutApi/services";
|
||||||
|
import { FormOutlined } from "@ant-design/icons";
|
||||||
|
import Notfound from "../../Utils/Notfound";
|
||||||
|
import { role } from "../../App";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIconActive from "../../assets/infoIconActive.png";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
type params = {
|
||||||
|
readonly id: any;
|
||||||
|
};
|
||||||
|
type MyObjectType = {
|
||||||
|
[key: string | number]: any;
|
||||||
|
};
|
||||||
|
const ServiceEdit = () => {
|
||||||
|
const { id } = useParams<params>();
|
||||||
|
const { data, refetch, status }: MyObjectType = useServiceOne(id);
|
||||||
|
|
||||||
|
const onSubmit = async (value: any) => {
|
||||||
|
await serviceController.servicePatch(value, id);
|
||||||
|
refetch();
|
||||||
|
document.location.replace("/#/services");
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClickDelete = () => {
|
||||||
|
const shouldDelete = window.confirm(
|
||||||
|
"Вы уверены, что хотите удалить эту сервис?"
|
||||||
|
);
|
||||||
|
if (shouldDelete && id !== undefined) {
|
||||||
|
serviceController.deleteServiceController(id).then((data: any) => {
|
||||||
|
document.location.replace(`/#/services`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [activeTab, setActiveTab] = useState("1");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Watermark style={{ height: "100%" }}>
|
||||||
|
{status === "loading" ? (
|
||||||
|
<Spin size="large" spinning={!data} />
|
||||||
|
) : data ? (
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="1"
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(key) => setActiveTab(key)}
|
||||||
|
>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
Information
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
onFinish={onSubmit}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="title"
|
||||||
|
name="title"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="points"
|
||||||
|
name="points"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item>
|
||||||
|
{role !== "Checker" && (
|
||||||
|
<Button
|
||||||
|
onClick={() => ClickDelete()}
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Space>
|
||||||
|
</Spin>
|
||||||
|
) : (
|
||||||
|
<Notfound />
|
||||||
|
)}
|
||||||
|
</Watermark>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServiceEdit;
|
@ -0,0 +1,80 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Table } from "antd";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { TService } from "../../types/Service/TService";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
import { role } from "../../App";
|
||||||
|
type numStr = string | number;
|
||||||
|
|
||||||
|
interface serviceSource {
|
||||||
|
no: numStr;
|
||||||
|
title: numStr;
|
||||||
|
points: numStr;
|
||||||
|
id: numStr;
|
||||||
|
action: { id: numStr };
|
||||||
|
key: React.Key;
|
||||||
|
}
|
||||||
|
const ServiceTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
data: TService[] | undefined;
|
||||||
|
isLoading: boolean | undefined;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TService[], unknown>>;
|
||||||
|
}) => {
|
||||||
|
const columns: object[] = [
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Title",
|
||||||
|
dataIndex: "title",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Points",
|
||||||
|
dataIndex: "points",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
loading={isLoading}
|
||||||
|
onRow={(record) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {
|
||||||
|
role !== "Checker" &&
|
||||||
|
document.location.replace(`/#/services/${record.id}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
dataSource={data?.map((u: any, i: number): serviceSource => {
|
||||||
|
const obj: serviceSource = {
|
||||||
|
no: i + 1,
|
||||||
|
title: u?.title,
|
||||||
|
points: u?.points,
|
||||||
|
id: u?.id,
|
||||||
|
action: { id: u.id },
|
||||||
|
key: u.id,
|
||||||
|
};
|
||||||
|
return obj;
|
||||||
|
})}
|
||||||
|
columns={columns}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServiceTable;
|
@ -0,0 +1,32 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useServiceData } from "../../Hooks/Services";
|
||||||
|
import AddService from "./AddService";
|
||||||
|
import ServiceTable from "./ServiceTable";
|
||||||
|
//@ts-ignore
|
||||||
|
import addicon from "../../assets/addiconpng.png";
|
||||||
|
import { role } from "../../App";
|
||||||
|
|
||||||
|
const Service = () => {
|
||||||
|
const { data, isLoading, refetch } = useServiceData();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{open && <AddService refetch={refetch} open={open} setOpen={setOpen} />}
|
||||||
|
<div className="header d-flex">
|
||||||
|
<p className="title">Services</p>
|
||||||
|
{role !== "Checker" && (
|
||||||
|
<button onClick={showModal} className="btn-add d-flex">
|
||||||
|
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ServiceTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Service;
|
@ -0,0 +1,178 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { statController } from "../../API/LayoutApi/statistic";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams/index";
|
||||||
|
import { useStatTeamData, useStatsData } from "../../Hooks/Stats";
|
||||||
|
import { TStatTeam } from "../../types/Statistic/TStat";
|
||||||
|
import StatTable from "./StatisticTable";
|
||||||
|
import StatTeamTable from "./StatisticTeamTable";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { Button, DatePicker, DatePickerProps, Select, Tabs } from "antd";
|
||||||
|
import TabPane from "antd/es/tabs/TabPane";
|
||||||
|
// @ts-ignore
|
||||||
|
import IconSearch from "../../assets/searchIcon.png";
|
||||||
|
|
||||||
|
const Stat = () => {
|
||||||
|
const now = dayjs();
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
const moment = require("moment");
|
||||||
|
const currentDate = moment();
|
||||||
|
const nextMonth = currentDate.clone().add(1, "months");
|
||||||
|
const start_date = `${currentDate.format("YYYY-MM")}-01 00:00:00`;
|
||||||
|
const end_date = `${nextMonth.format("YYYY-MM")}-01 00:00:00`;
|
||||||
|
|
||||||
|
const [search, setSearch] = useState<string>("");
|
||||||
|
const [team, setTeam] = useState<any>("");
|
||||||
|
const [startDate, setStartDate] = useState(start_date);
|
||||||
|
const [endDate, setEndDate] = useState(end_date);
|
||||||
|
|
||||||
|
const teamData = useTeamData("");
|
||||||
|
const teamOptions: { label: string; value: any }[] | undefined =
|
||||||
|
teamData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.name,
|
||||||
|
}));
|
||||||
|
const additionalOption = {
|
||||||
|
label: "all",
|
||||||
|
value: "",
|
||||||
|
};
|
||||||
|
if (teamOptions) {
|
||||||
|
teamOptions.unshift(additionalOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = (a: string) => {
|
||||||
|
const trimmedStartDate = startDate.slice(0, 10);
|
||||||
|
const trimmedEndDate = endDate.slice(0, 10);
|
||||||
|
const fileName = `${trimmedStartDate}-${trimmedEndDate}`;
|
||||||
|
if (a === "team") {
|
||||||
|
const teamName = `${team}_${fileName}`;
|
||||||
|
statController.saveUsersStats(teamName, startDate, endDate, team);
|
||||||
|
} else {
|
||||||
|
statController.saveTeamStats(fileName, startDate, endDate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const datePick = (a: any, b: any) => {
|
||||||
|
if (b[0] && b[1]) {
|
||||||
|
setStartDate(`${b[0]} 00:00:00`);
|
||||||
|
setEndDate(`${b[1]} 23:59:59`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeDate: DatePickerProps["onChange"] = (date) => {
|
||||||
|
if (!date) {
|
||||||
|
setStartDate("");
|
||||||
|
setEndDate("");
|
||||||
|
} else {
|
||||||
|
const firstDate = date;
|
||||||
|
const secondDate = date?.add(1, "month");
|
||||||
|
const yearStart = Number(firstDate?.year());
|
||||||
|
const monthStart = Number(firstDate?.month()) + 1;
|
||||||
|
const yearEnd = Number(secondDate?.year());
|
||||||
|
const monthEnd = Number(secondDate?.month()) + 1;
|
||||||
|
|
||||||
|
setStartDate(`${yearStart}-${monthStart}-01 00:00:00`);
|
||||||
|
setEndDate(`${yearEnd}-${monthEnd}-01 00:00:00`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data, refetch, isLoading } = useStatsData({
|
||||||
|
search: search,
|
||||||
|
team: team,
|
||||||
|
start_date: startDate,
|
||||||
|
end_date: endDate,
|
||||||
|
});
|
||||||
|
interface DataType {
|
||||||
|
data?: TStatTeam[];
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TStatTeam[], unknown>>;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
const TeamData: DataType = useStatTeamData({
|
||||||
|
search: "",
|
||||||
|
start_date: startDate,
|
||||||
|
end_date: endDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchText = e.target.value;
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setSearch(searchText);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="header d-flex" style={{ marginBottom: 16 }}>
|
||||||
|
<p className="title">Statistics</p>
|
||||||
|
<div className="">
|
||||||
|
<DatePicker
|
||||||
|
onChange={onChangeDate}
|
||||||
|
picker="month"
|
||||||
|
format={"MMMM"}
|
||||||
|
defaultValue={now}
|
||||||
|
style={{ marginRight: 10, width: 120 }}
|
||||||
|
/>
|
||||||
|
<RangePicker style={{ width: 260 }} onCalendarChange={datePick} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Tabs defaultActiveKey="1">
|
||||||
|
<TabPane tab={<span>Users</span>} key="1">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="search-div" style={{ marginRight: 12 }}>
|
||||||
|
<img src={IconSearch} alt="" />
|
||||||
|
<input
|
||||||
|
className={`search-input-${theme}`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
style={{ width: 260 }}
|
||||||
|
placeholder="team"
|
||||||
|
onChange={(value: any) => setTeam(value)}
|
||||||
|
options={teamOptions}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<StatTable
|
||||||
|
data={{ data: data }}
|
||||||
|
isLoading={isLoading}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
<Button type="primary" onClick={(e) => handleSave("team")}>
|
||||||
|
Save as file
|
||||||
|
</Button>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab={<span>Teams</span>} key="2">
|
||||||
|
<StatTeamTable
|
||||||
|
data={TeamData?.data}
|
||||||
|
isLoading={TeamData?.isLoading}
|
||||||
|
refetch={TeamData?.refetch}
|
||||||
|
/>
|
||||||
|
<Button type="primary" onClick={(e) => handleSave("")}>
|
||||||
|
Save as file
|
||||||
|
</Button>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Stat;
|
@ -0,0 +1,64 @@
|
|||||||
|
import { Table } from "antd";
|
||||||
|
import { TStat } from "../../types/Statistic/TStat";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
const StatTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TStat[], unknown>>;
|
||||||
|
data: { data: TStat[] | undefined };
|
||||||
|
isLoading: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
size="small"
|
||||||
|
loading={isLoading}
|
||||||
|
dataSource={data?.data?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
...u,
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
key: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Support specialist",
|
||||||
|
dataIndex: "username",
|
||||||
|
key: "username",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
dataIndex: "team_name",
|
||||||
|
key: "team_name ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Points",
|
||||||
|
dataIndex: "total_points",
|
||||||
|
key: "total_points",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 14,
|
||||||
|
}}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatTable;
|
@ -0,0 +1,77 @@
|
|||||||
|
import { Table, Tag } from "antd";
|
||||||
|
import { TStatTeam } from "../../types/Statistic/TStat";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
const StatTeamTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TStatTeam[], unknown>>;
|
||||||
|
data: TStatTeam[] | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div style={{ maxHeight: "400px", overflow: "auto" }}>
|
||||||
|
<Table
|
||||||
|
loading={isLoading}
|
||||||
|
size="small"
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
...u,
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
dataIndex: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Total points",
|
||||||
|
dataIndex: "total_points",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Is Active",
|
||||||
|
dataIndex: "is_active",
|
||||||
|
render: (tag: boolean) => (
|
||||||
|
<Tag color={tag ? "geekblue" : "red"}>
|
||||||
|
{tag ? "True" : "False"}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: "True",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "False",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value: string | number | boolean, record: TStatTeam) => {
|
||||||
|
return record.is_active === value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
pagination={{
|
||||||
|
pageSize: 5,
|
||||||
|
}}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatTeamTable;
|
@ -0,0 +1,327 @@
|
|||||||
|
import {
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
Form as FormAnt,
|
||||||
|
Select,
|
||||||
|
Upload,
|
||||||
|
Switch,
|
||||||
|
Button,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
} from "antd";
|
||||||
|
import { taskController } from "../../API/LayoutApi/tasks";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useServiceData } from "../../Hooks/Services";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams";
|
||||||
|
import { useCompanyData } from "../../Hooks/Companies";
|
||||||
|
import { useCustomerByComanyData } from "../../Hooks/Customers";
|
||||||
|
// @ts-ignore
|
||||||
|
import zippy from "../../assets/zippyicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import evo from "../../assets/evoicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zeelog from "../../assets/zeelogicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import ontime from "../../assets/ontimeicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tt from "../../assets/tticon.svg";
|
||||||
|
import AddCustomer from "../Customers/AddCustomer";
|
||||||
|
import AddDriver from "../Companies/AddDriver";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const AddTask = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
const [fileIds, setFileIds] = useState([]);
|
||||||
|
const [companyName, setCompanyName] = useState<string>();
|
||||||
|
const [customerName, setCustomerName] = useState<string>();
|
||||||
|
const [companyId, setCompanyId] = useState<string>();
|
||||||
|
const ServiceData = useServiceData();
|
||||||
|
|
||||||
|
const TeamData = useTeamData("");
|
||||||
|
const companyData = useCompanyData({ name: companyName });
|
||||||
|
const customerData = useCustomerByComanyData({
|
||||||
|
id: companyId,
|
||||||
|
name: customerName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [driverOpen, setDriverOpen] = useState(false);
|
||||||
|
|
||||||
|
const [previewImage, setPreviewImage] = useState<string | null>(null);
|
||||||
|
function handlePaste(event: any) {
|
||||||
|
const clipboardData = event.clipboardData || window.Clipboard;
|
||||||
|
if (clipboardData && clipboardData.items.length > 0) {
|
||||||
|
const clipboardItem = clipboardData.items[0];
|
||||||
|
if (clipboardItem.kind === "file") {
|
||||||
|
const file = clipboardItem.getAsFile();
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
if (e.target && e.target.result) {
|
||||||
|
setPreviewImage(e.target.result as string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
taskController
|
||||||
|
.addTaskFile(formData)
|
||||||
|
.then((response) => {
|
||||||
|
const fileId = response.id;
|
||||||
|
setFileIds((prevFileIds): any => [...prevFileIds, fileId]);
|
||||||
|
const updatedValues = form.getFieldsValue();
|
||||||
|
updatedValues.attachment_ids = [
|
||||||
|
...updatedValues.attachment_ids,
|
||||||
|
fileId,
|
||||||
|
];
|
||||||
|
form.setFieldsValue(updatedValues);
|
||||||
|
})
|
||||||
|
.catch((error) => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getImageSource = (source: string) => {
|
||||||
|
switch (source) {
|
||||||
|
case "Zippy":
|
||||||
|
return zippy;
|
||||||
|
case "EVO":
|
||||||
|
return evo;
|
||||||
|
case "Ontime":
|
||||||
|
return ontime;
|
||||||
|
case "Zeelog":
|
||||||
|
return zeelog;
|
||||||
|
case "TT":
|
||||||
|
return tt;
|
||||||
|
default:
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [openDrive, setOpenDrive] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onPaste={(event) => handlePaste(event)}>
|
||||||
|
{openDrive && (
|
||||||
|
<AddCustomer
|
||||||
|
refetch={customerData.refetch}
|
||||||
|
open={openDrive}
|
||||||
|
setOpen={setOpenDrive}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<AddDriver id={companyId} open={driverOpen} setOpen={setDriverOpen} />
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
width={600}
|
||||||
|
title="Add task"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form.validateFields().then(async (values) => {
|
||||||
|
const updatedValues = { ...values };
|
||||||
|
updatedValues.attachment_ids = fileIds;
|
||||||
|
form.resetFields();
|
||||||
|
await taskController.addTaskController(updatedValues);
|
||||||
|
setOpen(!open);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Company"
|
||||||
|
name="company_id"
|
||||||
|
rules={[{ required: false, message: "Please input company!" }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="Search Company"
|
||||||
|
onSearch={(value: any) => setCompanyName(value)}
|
||||||
|
options={companyData?.data?.map((item) => ({
|
||||||
|
label: (
|
||||||
|
<div>
|
||||||
|
{item?.source && (
|
||||||
|
<img
|
||||||
|
style={{ width: 15, height: 20, paddingTop: 7 }}
|
||||||
|
src={getImageSource(item?.source)}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
)}{" "}
|
||||||
|
{item?.name}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
value={companyName}
|
||||||
|
filterOption={false}
|
||||||
|
autoClearSearchValue={false}
|
||||||
|
allowClear
|
||||||
|
onChange={(value: any) => setCompanyId(value)}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Driver"
|
||||||
|
name="customer_id"
|
||||||
|
style={{ width: "85%" }}
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input service points!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="Search Driver"
|
||||||
|
onSearch={(value: any) => setCustomerName(value)}
|
||||||
|
options={customerData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
value={customerName}
|
||||||
|
filterOption={false}
|
||||||
|
autoClearSearchValue={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
<Button onClick={(e) => setDriverOpen(true)} type="primary">
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Service"
|
||||||
|
name="service_id"
|
||||||
|
rules={[{ required: true, message: "Please select service!" }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={ServiceData?.data?.map((item) => ({
|
||||||
|
label: item?.title,
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Assigned to"
|
||||||
|
name="assigned_to_id"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "Please select one of the teams!",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={TeamData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Status"
|
||||||
|
name="status"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input service points!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select defaultValue="New">
|
||||||
|
<Option value="New">New</Option>
|
||||||
|
<Option value="Checking">Checking</Option>
|
||||||
|
<Option value="Done">Done</Option>
|
||||||
|
</Select>
|
||||||
|
</FormAnt.Item>
|
||||||
|
|
||||||
|
<FormAnt.Item
|
||||||
|
label="PTI"
|
||||||
|
name="pti"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input service points!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</FormAnt.Item>
|
||||||
|
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Note"
|
||||||
|
name="note"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input service points!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
style={{ padding: "7px 11px" }}
|
||||||
|
placeholder="note"
|
||||||
|
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
<FormAnt>
|
||||||
|
<FormAnt.Item name="attachment">
|
||||||
|
<div>
|
||||||
|
<Upload.Dragger
|
||||||
|
name="file"
|
||||||
|
multiple={true}
|
||||||
|
customRequest={({ file, onSuccess }: any) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
taskController
|
||||||
|
.addTaskFile(formData)
|
||||||
|
.then((response) => {
|
||||||
|
const fileId = response.id;
|
||||||
|
setFileIds((prevFileIds): any => [
|
||||||
|
...prevFileIds,
|
||||||
|
fileId,
|
||||||
|
]);
|
||||||
|
onSuccess();
|
||||||
|
const updatedValues = form.getFieldsValue();
|
||||||
|
updatedValues.attachment_ids = [
|
||||||
|
...updatedValues.attachment_ids,
|
||||||
|
fileId,
|
||||||
|
];
|
||||||
|
form.setFieldsValue(updatedValues);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
onSuccess(error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<UploadOutlined style={{ color: "rgba(249, 158, 44, 1)" }} />
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="ant-upload-text"
|
||||||
|
style={{ color: "rgba(249, 158, 44, 1)" }}
|
||||||
|
>
|
||||||
|
Click or drag a file here to upload
|
||||||
|
</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
</div>
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddTask;
|
@ -0,0 +1,556 @@
|
|||||||
|
import { TTask } from "../../types/Tasks/TTasks";
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
MenuProps,
|
||||||
|
Modal,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Tooltip,
|
||||||
|
message,
|
||||||
|
} from "antd";
|
||||||
|
import TabPane from "antd/es/tabs/TabPane";
|
||||||
|
import { timeZone } from "../../App";
|
||||||
|
import { useTaskHistory } from "../../Hooks/Tasks";
|
||||||
|
// @ts-ignore
|
||||||
|
import closeIcon from "../../assets/closeIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import editIcon from "../../assets/editIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import historyIcon from "../../assets/hisoryIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import attachmentIcon from "../../assets/attachmentIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import uploadIcon from "../../assets/uploadIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import pdficon from "../../assets/pdficon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import letssee from "../../assets/letssee.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import svgicon from "../../assets/svgicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import pngicon from "../../assets/pngicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import jpgicon from "../../assets/jpgicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import jpegicon from "../../assets/jpegicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import xlsicon from "../../assets/xlsicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import docicon from "../../assets/docicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import forwardIcon from "../../assets/forward.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import driverIcon from "../../assets/drivericon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import userIcon from "../../assets/userIcon.png";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { taskController } from "../../API/LayoutApi/tasks";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { TPagination } from "../../types/common/TPagination";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams";
|
||||||
|
import { TTeam } from "../../types/Team/TTeam";
|
||||||
|
import { EditOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
const TaskModal = ({
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
recordTask,
|
||||||
|
uploadOpen,
|
||||||
|
setUploadOpen,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
recordTask: TTask | undefined;
|
||||||
|
modalOpen: any;
|
||||||
|
setModalOpen: any;
|
||||||
|
uploadOpen: any;
|
||||||
|
setUploadOpen: any;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TPagination<TTask[]>, unknown>>;
|
||||||
|
}) => {
|
||||||
|
const moment = require("moment-timezone");
|
||||||
|
const handleCancel = () => {
|
||||||
|
setModalOpen(!modalOpen);
|
||||||
|
};
|
||||||
|
const showUploadModal = () => {
|
||||||
|
setUploadOpen(!uploadOpen);
|
||||||
|
};
|
||||||
|
function getFileType(fileName: any) {
|
||||||
|
var fileExtension = fileName.split(".").pop()?.toLowerCase();
|
||||||
|
|
||||||
|
switch (fileExtension) {
|
||||||
|
case "jpg":
|
||||||
|
return <img style={{ marginRight: 12 }} src={jpgicon} alt="" />;
|
||||||
|
case "jpeg":
|
||||||
|
return <img style={{ marginRight: 12 }} src={jpegicon} alt="" />;
|
||||||
|
case "png":
|
||||||
|
return <img style={{ marginRight: 12 }} src={pngicon} alt="" />;
|
||||||
|
case "pdf":
|
||||||
|
return <img style={{ marginRight: 12 }} src={pdficon} alt="" />;
|
||||||
|
case "svg":
|
||||||
|
return <img style={{ marginRight: 12 }} src={svgicon} alt="" />;
|
||||||
|
case "xls":
|
||||||
|
return <img style={{ marginRight: 12 }} src={xlsicon} alt="" />;
|
||||||
|
default:
|
||||||
|
return <img style={{ marginRight: 12 }} src={docicon} alt="" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [text, setText] = useState<string | undefined>(recordTask?.note);
|
||||||
|
const [pti, setPti] = useState<boolean | undefined>(recordTask?.pti);
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
|
||||||
|
const { data, isLoading } = useTaskHistory(recordTask?.id);
|
||||||
|
const patchTask = () => {
|
||||||
|
taskController
|
||||||
|
.taskPatch({ note: text, pti: pti }, recordTask?.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({ content: "Saved!" });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const [status, setStatus] = useState(recordTask?.status);
|
||||||
|
|
||||||
|
const statuspatch = (status: string) => {
|
||||||
|
setStatus(status);
|
||||||
|
taskController.taskPatch({ status: status }, recordTask?.id).then(() => {
|
||||||
|
message.success({ content: "Success", duration: 1 });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const [teamName, setTeamName] = useState(recordTask?.assigned_to?.name);
|
||||||
|
const teampatch = (item: TTeam) => {
|
||||||
|
setTeamName(item?.name);
|
||||||
|
taskController
|
||||||
|
.taskPatch({ assigned_to_id: item?.id }, recordTask?.id)
|
||||||
|
.then(() => {
|
||||||
|
message.success({ content: "Success", duration: 1 });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const teamData = useTeamData("");
|
||||||
|
const teams: MenuProps["items"] = teamData?.data?.map((item) => ({
|
||||||
|
key: item?.id,
|
||||||
|
label: <p>{item?.name}</p>,
|
||||||
|
onClick: () => teampatch(item),
|
||||||
|
}));
|
||||||
|
const items: MenuProps["items"] = [
|
||||||
|
{
|
||||||
|
key: "1",
|
||||||
|
label: <p>New</p>,
|
||||||
|
onClick: () => statuspatch("New"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "2",
|
||||||
|
label: <p>Checking</p>,
|
||||||
|
onClick: () => statuspatch("Checking"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "3",
|
||||||
|
label: <p>Done</p>,
|
||||||
|
onClick: () => statuspatch("Done"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getImageSource = (source: string) => {
|
||||||
|
switch (source) {
|
||||||
|
case "driver":
|
||||||
|
return driverIcon;
|
||||||
|
case "user":
|
||||||
|
return userIcon;
|
||||||
|
default:
|
||||||
|
return userIcon;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={null}
|
||||||
|
open={modalOpen}
|
||||||
|
width={800}
|
||||||
|
maskClosable={true}
|
||||||
|
// style={{ position: "fixed", right: 0, top: 0, bottom: 0, height: 1000 }}
|
||||||
|
>
|
||||||
|
<div className={!theme ? "TaskModal-header" : "TaskModal-header-dark"}>
|
||||||
|
<div className={!theme ? "TaskModal-title" : "TaskModal-title-dark"}>
|
||||||
|
<p className={!theme ? "p-driver" : "p-driver-dark"}>
|
||||||
|
{recordTask?.customer?.name}
|
||||||
|
</p>
|
||||||
|
<Dropdown
|
||||||
|
menu={{ items }}
|
||||||
|
placement="bottom"
|
||||||
|
arrow={{ pointAtCenter: true }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
style={{ marginLeft: 12, display: "flex", alignItems: "center" }}
|
||||||
|
className={`status-${status}`}
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
<EditOutlined style={{ marginLeft: 4 }} />
|
||||||
|
</button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
<div className="mdoal-actions">
|
||||||
|
<Dropdown
|
||||||
|
disabled={recordTask?.status !== "New"}
|
||||||
|
menu={{ items: teams }}
|
||||||
|
placement="bottom"
|
||||||
|
arrow={{ pointAtCenter: true }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
disabled={recordTask?.status !== "New"}
|
||||||
|
style={{ marginRight: 12 }}
|
||||||
|
className={`btn-modal-action-${theme && "dark"}`}
|
||||||
|
>
|
||||||
|
<img src={forwardIcon} alt="" />
|
||||||
|
Forward
|
||||||
|
</button>
|
||||||
|
</Dropdown>
|
||||||
|
<button
|
||||||
|
style={{ marginLeft: 12 }}
|
||||||
|
className={`btn-modal-action-${theme && "dark"}`}
|
||||||
|
onClick={showUploadModal}
|
||||||
|
>
|
||||||
|
<img src={uploadIcon} alt="" />
|
||||||
|
Upload file
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleCancel}
|
||||||
|
style={{ marginLeft: 20 }}
|
||||||
|
className={`btn-modal-action-${theme && "dark"}`}
|
||||||
|
>
|
||||||
|
<img style={{ margin: 2 }} src={closeIcon} alt="" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="TaskModal-content">
|
||||||
|
<Tabs>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
marginLeft: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img style={{ marginRight: 10 }} src={infoIcon} alt="" />
|
||||||
|
Information
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<div className="info-div">
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: "24px",
|
||||||
|
letterSpacing: "-0.02em",
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Information
|
||||||
|
</p>
|
||||||
|
<div className="info-body">
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Comapany</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{recordTask?.company?.name}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Driver</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{recordTask?.customer?.name}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Service</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{recordTask?.service?.title}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Team</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>{teamName}</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Assignee</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{recordTask?.in_charge?.username}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>PTI</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{pti === false ? "Do" : "No need"}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
marginLeft: 10,
|
||||||
|
background: "#cecece",
|
||||||
|
outline: "none",
|
||||||
|
border: "1px solid rgba(246, 137, 0, 1)",
|
||||||
|
padding: 4,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
onClick={(e) => setPti(!pti)}
|
||||||
|
>
|
||||||
|
change
|
||||||
|
</button>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<p className={!theme ? "sub" : "sub-dark"}>Created at</p>
|
||||||
|
<p className={!theme ? "info" : "info-dark"}>
|
||||||
|
{moment(
|
||||||
|
recordTask?.created_at,
|
||||||
|
"YYYY-MM-DD HH:mm:ss"
|
||||||
|
).format("DD.MM.YYYY HH:mm")}
|
||||||
|
</p>
|
||||||
|
</tr>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<label className={!theme ? "sub" : "sub-dark"}>Note:</label>
|
||||||
|
<TextArea
|
||||||
|
style={{ padding: "7px 11px", marginTop: 10 }}
|
||||||
|
placeholder="Description"
|
||||||
|
defaultValue={text}
|
||||||
|
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
style={{ marginTop: 20 }}
|
||||||
|
className={`btn-modal-action-${theme && "dark"}`}
|
||||||
|
onClick={(e) => patchTask()}
|
||||||
|
>
|
||||||
|
<img src={editIcon} alt="" />
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img style={{ marginRight: 10 }} src={attachmentIcon} alt="" />
|
||||||
|
Attachments
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="2"
|
||||||
|
>
|
||||||
|
<div className="info-div">
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: "24px",
|
||||||
|
letterSpacing: "-0.02em",
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Attachments
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
size="small"
|
||||||
|
style={{ margin: "0 12px" }}
|
||||||
|
dataSource={recordTask?.attachment_set?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
...u,
|
||||||
|
created: moment(u?.updated_at)
|
||||||
|
.tz(timeZone)
|
||||||
|
.format("DD.MM.YYYY HH:mm"),
|
||||||
|
action: { ...u },
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Name/Description",
|
||||||
|
dataIndex: "file_name",
|
||||||
|
width: "30%",
|
||||||
|
key: 1,
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Uploaded date",
|
||||||
|
dataIndex: "created",
|
||||||
|
key: 2,
|
||||||
|
width: "30%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Attachments",
|
||||||
|
dataIndex: "action",
|
||||||
|
key: 3,
|
||||||
|
width: "40%",
|
||||||
|
render: (text, record) => {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={record?.path}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
style={{
|
||||||
|
padding: "6px 10px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
borderRadius: 8,
|
||||||
|
color: !theme ? "rgba(15, 17, 28, 1)" : "#bbb",
|
||||||
|
border: "1px solid rgba(215, 216, 224, 1)",
|
||||||
|
boxShadow: "0px 1px 3px 0px rgba(20, 22, 41, 0.1)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getFileType(record?.file_name)}
|
||||||
|
<span style={{ overflow: "hidden" }}>
|
||||||
|
{record?.file_name}
|
||||||
|
</span>
|
||||||
|
<img
|
||||||
|
src={letssee}
|
||||||
|
alt=""
|
||||||
|
style={{ textAlign: "right" }}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "even-row" : "odd-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img style={{ marginRight: 10 }} src={historyIcon} alt="" />
|
||||||
|
History
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="3"
|
||||||
|
>
|
||||||
|
<div className="info-div">
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: "24px",
|
||||||
|
letterSpacing: "-0.02em",
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
History
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
style={{ margin: "0 24px" }}
|
||||||
|
loading={isLoading}
|
||||||
|
size="small"
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
...u,
|
||||||
|
by: u.user
|
||||||
|
? { user: u?.user?.username }
|
||||||
|
: { driver: u?.driver?.name },
|
||||||
|
created: moment(u?.timestamp)
|
||||||
|
.tz(timeZone)
|
||||||
|
.format("DD.MM.YYYY HH:mm"),
|
||||||
|
// action: { ...u. },
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "User/Driver",
|
||||||
|
dataIndex: "by",
|
||||||
|
width: "25%",
|
||||||
|
key: 1,
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (text: any, record: any) => (
|
||||||
|
<Tooltip placement="topLeft" title={text}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{text?.user ? (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
src={getImageSource("user")}
|
||||||
|
alt=""
|
||||||
|
style={{ width: 15, height: 15, marginRight: 10 }}
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{typeof text.user === "string" ? text.user : ""}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
src={getImageSource("driver")}
|
||||||
|
alt=""
|
||||||
|
style={{ width: 20, height: 15, marginRight: 10 }}
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{typeof text.driver === "string"
|
||||||
|
? text.driver
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Action",
|
||||||
|
dataIndex: "action",
|
||||||
|
width: "15%",
|
||||||
|
key: 2,
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Description",
|
||||||
|
dataIndex: "description",
|
||||||
|
width: "40%",
|
||||||
|
key: 3,
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Timestamp",
|
||||||
|
dataIndex: "created",
|
||||||
|
key: 4,
|
||||||
|
width: "20%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "even-row" : "odd-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskModal;
|
@ -0,0 +1,354 @@
|
|||||||
|
import { Button, Modal, Space, Table, Tooltip } from "antd";
|
||||||
|
import "../../App.css";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { taskController } from "../../API/LayoutApi/tasks";
|
||||||
|
import { TPagination } from "../../types/common/TPagination";
|
||||||
|
import { TTask } from "../../types/Tasks/TTasks";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
UseQueryResult,
|
||||||
|
} from "react-query";
|
||||||
|
import { TCompany } from "../../types/Company/TCompany";
|
||||||
|
import { TService } from "../../types/Service/TService";
|
||||||
|
import { TCustomer } from "../../types/Customer/TCustomer";
|
||||||
|
import { TUser } from "../../types/User/TUser";
|
||||||
|
// @ts-ignore
|
||||||
|
import zippy from "../../assets/zippyicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import evo from "../../assets/evoicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zeelog from "../../assets/zeelogicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import ontime from "../../assets/ontimeicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tt from "../../assets/tticon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
import { role } from "../../App";
|
||||||
|
|
||||||
|
const admin_id = localStorage.getItem("admin_id");
|
||||||
|
const TaskTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
showTaskModal,
|
||||||
|
}: {
|
||||||
|
data: {
|
||||||
|
characters: TTask[] | undefined;
|
||||||
|
CompanyData: UseQueryResult<TCompany[], unknown>;
|
||||||
|
CustomerData: UseQueryResult<TCustomer[], unknown>;
|
||||||
|
ServiceData: UseQueryResult<TService[], unknown>;
|
||||||
|
AdminData: UseQueryResult<TUser[], unknown>;
|
||||||
|
};
|
||||||
|
showTaskModal: any;
|
||||||
|
isLoading: boolean;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TPagination<TTask[]>, unknown>>;
|
||||||
|
}) => {
|
||||||
|
const moment = require("moment");
|
||||||
|
const statusClick = (record: any) => {
|
||||||
|
if (record.status === "New") {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "Confirmation",
|
||||||
|
content: `Are you sure you want to be in charge for this task?`,
|
||||||
|
onOk: () => {
|
||||||
|
const value = {
|
||||||
|
status: "Checking",
|
||||||
|
};
|
||||||
|
taskController.taskPatch(value, record.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (record.status === "Checking") {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "Confirmation",
|
||||||
|
content: `Are you sure you want to finish this task?`,
|
||||||
|
onOk: () => {
|
||||||
|
const value = {
|
||||||
|
status: "Done",
|
||||||
|
};
|
||||||
|
taskController.taskPatch(value, record.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [isTextSelected, setIsTextSelected] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSelectionChange = () => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
setIsTextSelected(selection !== null && selection.toString() !== "");
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("selectionchange", handleSelectionChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleRowClick = (record: TTask, event: any) => {
|
||||||
|
if (isTextSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
event.target.classList.contains("ant-table-cell") &&
|
||||||
|
(record?.in_charge?.id === null ||
|
||||||
|
(!!admin_id && record?.in_charge?.id === +admin_id) ||
|
||||||
|
role !== "Checker")
|
||||||
|
) {
|
||||||
|
showTaskModal(record);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageSource = (source: string) => {
|
||||||
|
switch (source) {
|
||||||
|
case "Zippy":
|
||||||
|
return zippy;
|
||||||
|
case "EVO":
|
||||||
|
return evo;
|
||||||
|
case "Ontime":
|
||||||
|
return ontime;
|
||||||
|
case "Zeelog":
|
||||||
|
return zeelog;
|
||||||
|
case "TT":
|
||||||
|
return tt;
|
||||||
|
default:
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowClassName = (record: TTask) => {
|
||||||
|
if (record.status === "New") {
|
||||||
|
return "new-status-row";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Company",
|
||||||
|
dataIndex: "company",
|
||||||
|
width: "13%",
|
||||||
|
responsive: ["xl"],
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: true,
|
||||||
|
},
|
||||||
|
render: (text: any, record: any) => (
|
||||||
|
<Tooltip placement="topLeft" title={text?.name}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{text?.source && (
|
||||||
|
<img
|
||||||
|
src={getImageSource(text?.source)}
|
||||||
|
alt=""
|
||||||
|
style={{ width: 20, height: 20, marginRight: 10 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{text?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Driver",
|
||||||
|
dataIndex: "customer",
|
||||||
|
width: "13%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (item: { name: string; id: number }) => (
|
||||||
|
<Tooltip placement="topLeft" title={item?.name}>
|
||||||
|
{item?.name}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Service",
|
||||||
|
dataIndex: "service",
|
||||||
|
width: "7%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (item: { title: string; id: number }) => (
|
||||||
|
<Tooltip placement="topLeft" title={item?.title}>
|
||||||
|
{item.title}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Status",
|
||||||
|
dataIndex: "status",
|
||||||
|
width: "8%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (status: string) => (
|
||||||
|
<span>
|
||||||
|
{status === "Done" && <p className="status-done">Done</p>}
|
||||||
|
{status === "Checking" && (
|
||||||
|
<p className="status-in-progress">Checking</p>
|
||||||
|
)}
|
||||||
|
{status === "New" && <p className="status-new">New</p>}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
dataIndex: "assigned_to",
|
||||||
|
width: "8%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (item: { name: string }) => (
|
||||||
|
<Tooltip placement="topLeft" title={item?.name}>
|
||||||
|
{item?.name}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Assignee",
|
||||||
|
dataIndex: "in_charge",
|
||||||
|
width: "12%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (item: { username: string }) => (
|
||||||
|
<Tooltip placement="topLeft" title={item?.username}>
|
||||||
|
{item?.username}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "PTI",
|
||||||
|
dataIndex: "pti",
|
||||||
|
width: "6%",
|
||||||
|
responsive: ["lg"],
|
||||||
|
render: (pti: boolean) => (pti ? "No need" : "Do"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Note",
|
||||||
|
dataIndex: "note",
|
||||||
|
width: "12%",
|
||||||
|
responsive: ["lg"],
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Created at",
|
||||||
|
dataIndex: "created",
|
||||||
|
width: "12%",
|
||||||
|
responsive: ["xxl"],
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Actions",
|
||||||
|
dataIndex: "action",
|
||||||
|
width: "8%",
|
||||||
|
render: (text: string, record: TTask) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{role === "Checker" ? (
|
||||||
|
<Space>
|
||||||
|
{record.status === "New" && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ background: "#595959" }}
|
||||||
|
onClick={() => statusClick(record)}
|
||||||
|
>
|
||||||
|
Assign
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{record.status === "Checking" &&
|
||||||
|
!!admin_id &&
|
||||||
|
record?.in_charge?.id === +admin_id && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ background: "#595959" }}
|
||||||
|
onClick={() => statusClick(record)}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
onClick={(e) => {
|
||||||
|
const shouldDelete = window.confirm(
|
||||||
|
"Are you sure, you want to delete this task?"
|
||||||
|
);
|
||||||
|
if (shouldDelete && record.id !== undefined) {
|
||||||
|
taskController.deleteTaskController(record.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (role === "Checker") {
|
||||||
|
const teamColIndex = columns.findIndex((c) => c.title === "Team");
|
||||||
|
|
||||||
|
teamColIndex !== -1 && columns.splice(teamColIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}, [role]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
onRow={(record: any) => ({
|
||||||
|
onClick: (event) => handleRowClick(record, event),
|
||||||
|
})}
|
||||||
|
dataSource={data?.characters?.map((u, i) => ({
|
||||||
|
...u,
|
||||||
|
no: i + 1,
|
||||||
|
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||||
|
"DD.MM.YYYY HH:mm"
|
||||||
|
),
|
||||||
|
key: u?.id,
|
||||||
|
}))}
|
||||||
|
size="small"
|
||||||
|
columns={columns as any}
|
||||||
|
pagination={false}
|
||||||
|
loading={isLoading}
|
||||||
|
rowClassName={rowClassName}
|
||||||
|
bordered
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskTable;
|
@ -0,0 +1,114 @@
|
|||||||
|
import { Modal, Upload, Button, Space } from "antd";
|
||||||
|
import { taskController } from "../../API/LayoutApi/tasks";
|
||||||
|
// @ts-ignore
|
||||||
|
import uploadfile from "../../assets/uploadfile.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import createIcon from "../../assets/galkaIcon.png";
|
||||||
|
import { CloseOutlined } from "@ant-design/icons";
|
||||||
|
import { TTask } from "../../types/Tasks/TTasks";
|
||||||
|
import { useState } from "react";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
|
||||||
|
const TaskUploadModal = ({
|
||||||
|
uploadOpen,
|
||||||
|
recordTask,
|
||||||
|
setUploadOpen,
|
||||||
|
}: {
|
||||||
|
recordTask: TTask | undefined;
|
||||||
|
uploadOpen: boolean;
|
||||||
|
setUploadOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const handleCancel = () => {
|
||||||
|
setUploadOpen(!uploadOpen);
|
||||||
|
};
|
||||||
|
const [fileData, setFileData] = useState();
|
||||||
|
const [text, setText] = useState<any>();
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={uploadOpen}
|
||||||
|
title="Upload file"
|
||||||
|
okText="Upload"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
width={720}
|
||||||
|
footer={
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
onClick={handleCancel}
|
||||||
|
style={{ display: "flex", alignItems: "center" }}
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
const updatedValues: any = {};
|
||||||
|
updatedValues.task_id = recordTask?.id;
|
||||||
|
updatedValues.file = fileData;
|
||||||
|
updatedValues.description = text;
|
||||||
|
taskController.addTaskFile(updatedValues).then(() => {
|
||||||
|
setUploadOpen(!uploadOpen);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
icon={<img alt="" src={createIcon} />}
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Upload.Dragger
|
||||||
|
name="file"
|
||||||
|
multiple={true}
|
||||||
|
customRequest={({ file, onSuccess }: any) => {
|
||||||
|
setFileData(file);
|
||||||
|
if (file) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<img src={uploadfile} alt="" />
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="ant-upload-text"
|
||||||
|
style={{
|
||||||
|
color: "rgba(15, 17, 28, 1)",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "20px",
|
||||||
|
letterSpacing: "-1%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Drag and drop files or{" "}
|
||||||
|
<span style={{ color: "rgba(249, 158, 44, 1)" }}>
|
||||||
|
Click to select
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: "rgba(155, 157, 170, 1)",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: 13,
|
||||||
|
lineHeight: "15.73px",
|
||||||
|
letterSpacing: "-2%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Maximum file size is 10 MB
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
<TextArea
|
||||||
|
style={{ padding: "7px 11px", marginTop: 20 }}
|
||||||
|
placeholder="Description"
|
||||||
|
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskUploadModal;
|
@ -0,0 +1,356 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import AddTask from "./AddTask";
|
||||||
|
import { Button, Input, Select, Space, message, notification } from "antd";
|
||||||
|
import TaskTable from "./TaskTable";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams";
|
||||||
|
import { StepForwardOutlined, StepBackwardOutlined } from "@ant-design/icons";
|
||||||
|
import { useTasks } from "../../Hooks/Tasks";
|
||||||
|
import { TTask } from "../../types/Tasks/TTasks";
|
||||||
|
import { useCompanyData } from "../../Hooks/Companies";
|
||||||
|
import { useCustomerData } from "../../Hooks/Customers";
|
||||||
|
import { useUserData } from "../../Hooks/Users";
|
||||||
|
import { useServiceData } from "../../Hooks/Services";
|
||||||
|
import { admin_id, role, team_id } from "../../App";
|
||||||
|
//@ts-ignore
|
||||||
|
import addicon from "../../assets/addiconpng.png";
|
||||||
|
//@ts-ignore
|
||||||
|
import refreshicon from "../../assets/refreshIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import IconSearch from "../../assets/searchIcon.png";
|
||||||
|
import TaskModal from "./TaskModal";
|
||||||
|
import TaskUploadModal from "./TaskUploadModal";
|
||||||
|
import { NotificationPlacement } from "antd/es/notification/interface";
|
||||||
|
import { TCall } from "../../types/CallRequests/TCall";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const Task = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [characters, setCharacters] = useState<TTask[] | undefined>();
|
||||||
|
const [team, setTeam] = useState<any>();
|
||||||
|
const [search, setSearch] = useState<string>("");
|
||||||
|
const [status, setStatus] = useState<any>();
|
||||||
|
const [page, setPage] = useState<any>(1);
|
||||||
|
const [uploadOpen, setUploadOpen] = useState(false);
|
||||||
|
|
||||||
|
const CompanyData = useCompanyData({});
|
||||||
|
const CustomerData = useCustomerData({});
|
||||||
|
const AdminData = useUserData({});
|
||||||
|
const ServiceData = useServiceData();
|
||||||
|
|
||||||
|
let taskSocket: WebSocket;
|
||||||
|
interface newData {
|
||||||
|
callback_request: TCall;
|
||||||
|
type: string;
|
||||||
|
task: TTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [isLive, setIslive] = useState(true);
|
||||||
|
const [socketData, setSocketData] = useState<newData>();
|
||||||
|
const connect = async () => {
|
||||||
|
try {
|
||||||
|
if (!taskSocket || taskSocket.readyState === WebSocket.CLOSED) {
|
||||||
|
taskSocket = new WebSocket(
|
||||||
|
`ws://10.10.10.45:8080/tasks/?user_id=${admin_id}`
|
||||||
|
);
|
||||||
|
// taskSocket = new WebSocket(
|
||||||
|
// `wss://api.tteld.co/tasks/?user_id=${admin_id}`
|
||||||
|
// );
|
||||||
|
|
||||||
|
taskSocket.addEventListener("open", (event) => {
|
||||||
|
console.log("open");
|
||||||
|
setIslive(true);
|
||||||
|
});
|
||||||
|
taskSocket.addEventListener("message", (event) => {
|
||||||
|
const newData: newData = JSON.parse(event.data);
|
||||||
|
setSocketData(newData);
|
||||||
|
});
|
||||||
|
taskSocket.addEventListener("error", (errorEvent) => {
|
||||||
|
console.error("WebSocket error:", errorEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
taskSocket.addEventListener("close", (event) => {
|
||||||
|
setIslive(false);
|
||||||
|
console.log("close");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
connect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [api, contextHolder] = notification.useNotification();
|
||||||
|
const openNotification = useCallback(
|
||||||
|
(placement: NotificationPlacement, data: TCall) => {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
api.info({
|
||||||
|
message: `Driver ${data?.driver?.name} from ${data?.company?.name} company has made a call request`,
|
||||||
|
placement,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[api]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
socketData &&
|
||||||
|
((role !== "Checker" &&
|
||||||
|
(!team || team.includes(socketData?.task?.assigned_to?.id))) ||
|
||||||
|
role === "Checker") &&
|
||||||
|
(!status || status.includes(socketData.task.status))
|
||||||
|
) {
|
||||||
|
setCharacters((prev: TTask[] | undefined) => {
|
||||||
|
if (prev && prev?.length >= 15) {
|
||||||
|
prev?.pop();
|
||||||
|
}
|
||||||
|
if (socketData.type === "task_create") {
|
||||||
|
return [socketData.task, ...(prev || [])];
|
||||||
|
} else if (socketData.type === "task_update") {
|
||||||
|
if (role !== "Checker") {
|
||||||
|
const updatedData =
|
||||||
|
prev?.filter((b: TTask) => b.id !== socketData.task.id) || [];
|
||||||
|
const data: TTask[] = [socketData.task, ...updatedData];
|
||||||
|
data.sort((a: TTask, b: TTask) => {
|
||||||
|
if (a.status === "New" && b.status === "New") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (a.status === "New") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b.status === "New") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
const data = (prev || []).map((b: TTask) =>
|
||||||
|
b.id === socketData.task.id ? socketData.task : b
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} else if (socketData.type === "task_delete") {
|
||||||
|
const data = (prev || []).filter(
|
||||||
|
(b: TTask) => b.id !== socketData.task.id
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} else if (socketData.type === "task_forward") {
|
||||||
|
if (role === "Checker") {
|
||||||
|
if (socketData.task.assigned_to.id === team_id) {
|
||||||
|
return [socketData.task, ...(prev || [])];
|
||||||
|
} else {
|
||||||
|
const data = (prev || []).filter(
|
||||||
|
(b: TTask) => b.id !== socketData.task.id
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const updatedData =
|
||||||
|
prev?.filter((b: TTask) => b.id !== socketData.task.id) || [];
|
||||||
|
const data: TTask[] = [socketData.task, ...updatedData];
|
||||||
|
data.sort((a: TTask, b: TTask) => {
|
||||||
|
if (a.status === "New" && b.status === "New") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (a.status === "New") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b.status === "New") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} else if (socketData.type === "callback_request") {
|
||||||
|
if (socketData?.callback_request) {
|
||||||
|
console.log("run");
|
||||||
|
|
||||||
|
openNotification("bottomRight", socketData?.callback_request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [socketData, openNotification]);
|
||||||
|
|
||||||
|
const teamData = useTeamData("");
|
||||||
|
|
||||||
|
const teamOptions: { label: string; value: any }[] | undefined =
|
||||||
|
teamData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { data, isLoading, refetch } = useTasks({
|
||||||
|
search,
|
||||||
|
status,
|
||||||
|
team,
|
||||||
|
page,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
setCharacters(data?.data);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPage(1);
|
||||||
|
}, [search, status, team]);
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
const [recordTask, setRecordTask] = useState<TTask>();
|
||||||
|
const showTaskModal = (e: TTask) => {
|
||||||
|
setRecordTask(e);
|
||||||
|
setModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Next = () => {
|
||||||
|
const a = Number(page) + 1;
|
||||||
|
setPage(a);
|
||||||
|
};
|
||||||
|
const Previos = () => {
|
||||||
|
Number(page);
|
||||||
|
if (page > 1) {
|
||||||
|
const a = Number(page) - 1;
|
||||||
|
setPage(a);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchText = e.target.value;
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setSearch(searchText);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{contextHolder}
|
||||||
|
{open && <AddTask open={open} setOpen={setOpen} />}
|
||||||
|
{uploadOpen && (
|
||||||
|
<TaskUploadModal
|
||||||
|
recordTask={recordTask}
|
||||||
|
uploadOpen={uploadOpen}
|
||||||
|
setUploadOpen={setUploadOpen}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{modalOpen && (
|
||||||
|
<TaskModal
|
||||||
|
uploadOpen={uploadOpen}
|
||||||
|
setUploadOpen={setUploadOpen}
|
||||||
|
recordTask={recordTask}
|
||||||
|
modalOpen={modalOpen}
|
||||||
|
setModalOpen={setModalOpen}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="header d-flex">
|
||||||
|
<div className="header-title d-flex">
|
||||||
|
<p className="title">Tasks</p>
|
||||||
|
{isLive ? (
|
||||||
|
<div className="d-flex" style={{ marginRight: 15 }}>
|
||||||
|
<div className="circle"></div>
|
||||||
|
<p className={!theme ? "live-p" : "live-p-dark"}>online</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="d-flex" style={{ marginRight: 15 }}>
|
||||||
|
<div className="circle2"></div>
|
||||||
|
<p className="live-p">offline</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="d-flex">
|
||||||
|
{role !== "Checker" && (
|
||||||
|
<button className="btn-add d-flex" onClick={showModal}>
|
||||||
|
<img style={{ marginRight: 8 }} src={addicon} alt="" />
|
||||||
|
Add Task
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className={`btn-refresh-${theme && "dark"} d-flex`}
|
||||||
|
onClick={() => {
|
||||||
|
refetch();
|
||||||
|
connect();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img style={{ marginRight: 8 }} src={refreshicon} alt="" />
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="filter d-flex">
|
||||||
|
<div className="search-div">
|
||||||
|
<img src={IconSearch} alt="" />
|
||||||
|
<input
|
||||||
|
className={`search-input-${theme}`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
style={{ width: 260, marginLeft: 12 }}
|
||||||
|
placeholder="status"
|
||||||
|
onChange={(value: any) => setStatus(value)}
|
||||||
|
mode="multiple"
|
||||||
|
>
|
||||||
|
<Option value="New">New</Option>
|
||||||
|
<Option value="Checking">Checking</Option>
|
||||||
|
<Option value="Done">Done</Option>
|
||||||
|
</Select>
|
||||||
|
{role !== "Checker" && (
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
style={{ width: 260, marginLeft: 12 }}
|
||||||
|
placeholder="team"
|
||||||
|
onChange={(value: any) => setTeam(value)}
|
||||||
|
options={teamOptions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<TaskTable
|
||||||
|
data={{ characters, CompanyData, CustomerData, ServiceData, AdminData }}
|
||||||
|
isLoading={isLoading}
|
||||||
|
refetch={refetch}
|
||||||
|
showTaskModal={showTaskModal}
|
||||||
|
/>
|
||||||
|
<Space style={{ width: "100%", marginTop: 10 }} direction="vertical">
|
||||||
|
<Space style={{ width: "100%", justifyContent: "flex-end" }} wrap>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<StepBackwardOutlined />}
|
||||||
|
onClick={Previos}
|
||||||
|
></Button>
|
||||||
|
<Input
|
||||||
|
style={{ width: 50, textAlign: "right" }}
|
||||||
|
value={page}
|
||||||
|
onChange={(e) => {
|
||||||
|
let num = e.target.value;
|
||||||
|
if (Number(num) && num !== "0") {
|
||||||
|
setPage(num);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<StepForwardOutlined />}
|
||||||
|
onClick={Next}
|
||||||
|
></Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Task;
|
@ -0,0 +1,76 @@
|
|||||||
|
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||||
|
import { teamController } from "../../API/LayoutApi/teams";
|
||||||
|
import { useUserData } from "../../Hooks/Users";
|
||||||
|
|
||||||
|
const AddTeam = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
refetch(): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
const { data } = useUserData({ name: "", team: "", role: "Checker" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="Add new team"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then(async (values) => {
|
||||||
|
form.resetFields();
|
||||||
|
await teamController.addTeamController(values);
|
||||||
|
setOpen(!open);
|
||||||
|
refetch();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: "Please input team name!" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Include users to this team"
|
||||||
|
name="user_ids"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input company status!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
options={data?.map((items) => ({
|
||||||
|
label: items.username,
|
||||||
|
value: items.id,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddTeam;
|
@ -0,0 +1,200 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { useTeamOne } from "../../Hooks/Teams";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Spin,
|
||||||
|
Watermark,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Table,
|
||||||
|
} from "antd";
|
||||||
|
import { teamController } from "../../API/LayoutApi/teams";
|
||||||
|
import { FormOutlined } from "@ant-design/icons";
|
||||||
|
import Notfound from "../../Utils/Notfound";
|
||||||
|
import { role } from "../../App";
|
||||||
|
import { useUserData } from "../../Hooks/Users";
|
||||||
|
import AddUserToTeam from "./AddUserToTeam";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIconActive from "../../assets/infoIconActive.png";
|
||||||
|
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
|
||||||
|
type params = {
|
||||||
|
readonly id: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MyObjectType = {
|
||||||
|
[key: string | number]: any;
|
||||||
|
};
|
||||||
|
const TeamEdit = () => {
|
||||||
|
const { id } = useParams<params>();
|
||||||
|
const { data, refetch, status }: MyObjectType = useTeamOne(id);
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
|
const onSubmit = async (value: any) => {
|
||||||
|
await teamController.teamPatch(value, id);
|
||||||
|
refetch();
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClickDelete = () => {
|
||||||
|
const shouldDelete = window.confirm(
|
||||||
|
"Вы уверены, что хотите удалить эту команду?"
|
||||||
|
);
|
||||||
|
if (shouldDelete && id !== undefined) {
|
||||||
|
teamController.deleteTeamController(id).then((data: any) => {
|
||||||
|
navigate(-1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const userData = useUserData({ name: "", team: id });
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
const [activeTab, setActiveTab] = useState("1");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Watermark style={{ height: "100%" }}>
|
||||||
|
{status === "loading" ? (
|
||||||
|
<Spin size="large" spinning={!data} />
|
||||||
|
) : data ? (
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="1"
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(key) => setActiveTab(key)}
|
||||||
|
>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
Information
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
onFinish={onSubmit}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item>
|
||||||
|
{role !== "Checker" && (
|
||||||
|
<Button
|
||||||
|
onClick={() => ClickDelete()}
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span>
|
||||||
|
<FormOutlined />
|
||||||
|
Users
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="2"
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
dataSource={userData?.data?.map((item, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
...item,
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Username",
|
||||||
|
dataIndex: "username",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "First name",
|
||||||
|
dataIndex: "first_name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Last name",
|
||||||
|
dataIndex: "last_name",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<AddUserToTeam
|
||||||
|
team={id}
|
||||||
|
refetch={refetch}
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
style={{ marginLeft: "auto" }}
|
||||||
|
size={"large"}
|
||||||
|
onClick={showModal}
|
||||||
|
>
|
||||||
|
Add User
|
||||||
|
</Button>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Space>
|
||||||
|
</Spin>
|
||||||
|
) : (
|
||||||
|
<Notfound />
|
||||||
|
)}
|
||||||
|
</Watermark>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeamEdit;
|
@ -0,0 +1,112 @@
|
|||||||
|
// // @ts-ignore
|
||||||
|
// import closeIcon from "../../assets/closeIcon.png";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import editIcon from "../../assets/editIcon.png";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import deleteIcon from "../../assets/deleteIconRed.png";
|
||||||
|
// import { TTeam } from "../../types/Team/TTeam";
|
||||||
|
// import { Table } from "antd";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
// import { useUserByTeam } from "../../Hooks/Users";
|
||||||
|
|
||||||
|
// const TeamModal = ({
|
||||||
|
// modalOpen,
|
||||||
|
// setModalOpen,
|
||||||
|
// recordTeam,
|
||||||
|
// }: {
|
||||||
|
// recordTeam: TTeam | undefined;
|
||||||
|
// modalOpen: any;
|
||||||
|
// setModalOpen: any;
|
||||||
|
// }) => {
|
||||||
|
// const handleCancel = () => {
|
||||||
|
// setModalOpen(!modalOpen);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const { data, isLoading, refetch } = useUserByTeam(recordTeam?.uuid);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div className="TaskModal">
|
||||||
|
// <div className="TaskModal-header">
|
||||||
|
// <div className="TaskModal-title">
|
||||||
|
// <p className="p-driver">{recordTeam?.name}</p>
|
||||||
|
// </div>
|
||||||
|
// <div className="mdoal-actions">
|
||||||
|
// <button className="btn-modal-action">
|
||||||
|
// <img src={editIcon} alt="" />
|
||||||
|
// Edit
|
||||||
|
// </button>
|
||||||
|
// <button style={{ marginLeft: 12 }} className="btn-modal-action">
|
||||||
|
// <img src={deleteIcon} alt="" />
|
||||||
|
// Delete
|
||||||
|
// </button>
|
||||||
|
// <button
|
||||||
|
// onClick={handleCancel}
|
||||||
|
// style={{ marginLeft: 20 }}
|
||||||
|
// className="btn-modal-action"
|
||||||
|
// >
|
||||||
|
// <img style={{ margin: 2 }} src={closeIcon} alt="" />
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="TaskModal-content">
|
||||||
|
// <p
|
||||||
|
// style={{
|
||||||
|
// fontSize: 18,
|
||||||
|
// fontWeight: 700,
|
||||||
|
// lineHeight: "24px",
|
||||||
|
// letterSpacing: "-0.02em",
|
||||||
|
// marginLeft: 24
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Users
|
||||||
|
// </p>
|
||||||
|
// <div className="users-table-by-team">
|
||||||
|
// <Table
|
||||||
|
// loading={isLoading}
|
||||||
|
// rowClassName={(record, index) =>
|
||||||
|
// index % 2 === 0 ? "even-row" : "odd-row"
|
||||||
|
// }
|
||||||
|
// dataSource={data?.map((u, i) => {
|
||||||
|
// return {
|
||||||
|
// ...u,
|
||||||
|
// no: i + 1,
|
||||||
|
// key: u?.uid,
|
||||||
|
// };
|
||||||
|
// })}
|
||||||
|
// columns={[
|
||||||
|
// {
|
||||||
|
// title: <img src={tagIcon} alt="" />,
|
||||||
|
// dataIndex: "no",
|
||||||
|
// width: "5%",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Name",
|
||||||
|
// dataIndex: "full_name",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Username",
|
||||||
|
// dataIndex: "username",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Actions",
|
||||||
|
// dataIndex: "actions",
|
||||||
|
// },
|
||||||
|
// ]}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default TeamModal;
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const TeamModal = () => {
|
||||||
|
return (
|
||||||
|
<div>TeamModal</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TeamModal
|
@ -0,0 +1,86 @@
|
|||||||
|
import { Table, Tag } from "antd";
|
||||||
|
import { TTeam } from "../../types/Team/TTeam";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { timeZone } from "../../App";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
const TeamTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
data: TTeam[] | undefined;
|
||||||
|
isLoading: boolean | undefined;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TTeam[], unknown>>;
|
||||||
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const moment = require("moment-timezone");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
loading={isLoading}
|
||||||
|
onRow={(record) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {
|
||||||
|
navigate(`/teams/${record.id}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
...u,
|
||||||
|
no: i + 1,
|
||||||
|
action: { id: u.id },
|
||||||
|
created: moment(u?.created_at).tz(timeZone).format("DD.MM.YYYY HH:mm"),
|
||||||
|
key: u.id,
|
||||||
|
}))}
|
||||||
|
size="middle"
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Created at",
|
||||||
|
dataIndex: "created",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Is Active",
|
||||||
|
dataIndex: "is_active",
|
||||||
|
render: (tag: boolean) => (
|
||||||
|
<Tag color={tag ? "geekblue" : "red"}>{tag ? "True" : "False"}</Tag>
|
||||||
|
),
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: "True",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "False",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value: string | number | boolean, record: TTeam) => {
|
||||||
|
return record.is_active === value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeamTable;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams";
|
||||||
|
import TeamTable from "./TeamTable";
|
||||||
|
//@ts-ignore
|
||||||
|
import addicon from "../../assets/addiconpng.png";
|
||||||
|
import AddTeam from "./AddTeam";
|
||||||
|
|
||||||
|
const Team = () => {
|
||||||
|
const { data, isLoading, refetch } = useTeamData("");
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{open && <AddTeam refetch={refetch} open={open} setOpen={setOpen} />}
|
||||||
|
<div className="header d-flex" style={{ marginBottom: 16 }}>
|
||||||
|
<p className="title">Teams</p>
|
||||||
|
<button
|
||||||
|
className="btn-add d-flex"
|
||||||
|
style={{ marginRight: 0 }}
|
||||||
|
onClick={showModal}
|
||||||
|
>
|
||||||
|
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<TeamTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Team;
|
@ -0,0 +1,198 @@
|
|||||||
|
import { Input, Modal, Form as FormAnt, Select, Upload } from "antd";
|
||||||
|
import { updateController } from "../../API/LayoutApi/update";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
|
import { taskController } from "../../API/LayoutApi/tasks";
|
||||||
|
import { useCompanyData } from "../../Hooks/Companies";
|
||||||
|
import { useCustomerByComanyData } from "../../Hooks/Customers";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { TUpdate } from "../../types/Update/TUpdate";
|
||||||
|
const { Option } = Select;
|
||||||
|
const AddUpdate = ({
|
||||||
|
refetch,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
}: {
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TUpdate[], unknown>>;
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
refetch();
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
const [fileIds, setFileIds] = useState([]);
|
||||||
|
const [companyName, setCompanyName] = useState<string>("");
|
||||||
|
const [customerName, setCustomerName] = useState<string>("");
|
||||||
|
const [companyId, setCompanyId] = useState<string>();
|
||||||
|
|
||||||
|
const companyData = useCompanyData({ name: companyName });
|
||||||
|
const customerData = useCustomerByComanyData({
|
||||||
|
id: companyId,
|
||||||
|
name: customerName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [imgname, setImgname] = useState<any>([]);
|
||||||
|
function handlePaste(event: any) {
|
||||||
|
const clipboardData = event.clipboardData || window.Clipboard;
|
||||||
|
if (clipboardData && clipboardData.items.length > 0) {
|
||||||
|
const clipboardItem = clipboardData.items[0];
|
||||||
|
if (clipboardItem.kind === "file") {
|
||||||
|
const file = clipboardItem.getAsFile();
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
taskController.addTaskFile(formData).then((response) => {
|
||||||
|
const fileId = response.id;
|
||||||
|
const n = [response.file];
|
||||||
|
setImgname((prev: any) => [...prev, ...n]);
|
||||||
|
setFileIds((prevFileIds): any => [...prevFileIds, fileId]);
|
||||||
|
const updatedValues = form.getFieldsValue();
|
||||||
|
updatedValues.attachment_ids = [
|
||||||
|
...updatedValues.attachment_ids,
|
||||||
|
fileId,
|
||||||
|
];
|
||||||
|
form.setFieldsValue(updatedValues);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onPaste={(event) => handlePaste(event)}>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="Add update"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form.validateFields().then(async (values) => {
|
||||||
|
const updatedValues = { ...values };
|
||||||
|
updatedValues.attachment_ids = fileIds;
|
||||||
|
form.resetFields();
|
||||||
|
await updateController.addUpdateController(updatedValues);
|
||||||
|
setOpen(!open);
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Company"
|
||||||
|
name="company_id"
|
||||||
|
rules={[{ required: false, message: "Please input company!" }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="Search Company"
|
||||||
|
onSearch={(value: any) => setCompanyName(value)}
|
||||||
|
options={companyData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
value={companyName}
|
||||||
|
filterOption={false}
|
||||||
|
autoClearSearchValue={false}
|
||||||
|
allowClear
|
||||||
|
onChange={(value: any) => setCompanyId(value)}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Driver"
|
||||||
|
name="customer_id"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input service points!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="Search Driver"
|
||||||
|
onSearch={(value: any) => setCustomerName(value)}
|
||||||
|
options={customerData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
value={customerName}
|
||||||
|
filterOption={false}
|
||||||
|
autoClearSearchValue={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Note"
|
||||||
|
name="note"
|
||||||
|
rules={[{ required: true, message: "Make note!" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Status"
|
||||||
|
name="status"
|
||||||
|
rules={[
|
||||||
|
{ required: false, message: "Please input service points!" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select defaultValue="New" style={{ width: 120 }}>
|
||||||
|
<Option value="New">New</Option>
|
||||||
|
<Option value="In Progress">In Progress</Option>
|
||||||
|
<Option value="Done">Done</Option>
|
||||||
|
<Option value="Paper">Paper</Option>
|
||||||
|
<Option value="Setup">Setup</Option>
|
||||||
|
</Select>
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
<FormAnt>
|
||||||
|
<FormAnt.Item label="File" name="attachment">
|
||||||
|
<Upload.Dragger
|
||||||
|
name="file"
|
||||||
|
multiple={true}
|
||||||
|
customRequest={({ file, onSuccess }: any) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
taskController
|
||||||
|
.addTaskFile(formData)
|
||||||
|
.then((response) => {
|
||||||
|
const fileId = response.id;
|
||||||
|
setFileIds((prevFileIds): any => [...prevFileIds, fileId]);
|
||||||
|
onSuccess();
|
||||||
|
const updatedValues = form.getFieldsValue();
|
||||||
|
updatedValues.attachment_ids = [
|
||||||
|
...updatedValues.attachment_ids,
|
||||||
|
fileId,
|
||||||
|
];
|
||||||
|
form.setFieldsValue(updatedValues);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
onSuccess(error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<UploadOutlined style={{ color: "#36cfc9" }} />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text" style={{ color: "#36cfc9" }}>
|
||||||
|
Click or drag file to this area to upload
|
||||||
|
</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
<p>{imgname.join(",\n")}</p>
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddUpdate;
|
@ -0,0 +1,67 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import AddUpdate from "./AddUpdate";
|
||||||
|
import { Button, Select } from "antd";
|
||||||
|
import UpdateTable from "./UpdateTable";
|
||||||
|
import { useUpdateData } from "../../Hooks/Update";
|
||||||
|
//@ts-ignore
|
||||||
|
import addicon from "../../assets/addiconpng.png";
|
||||||
|
//@ts-ignore
|
||||||
|
import refreshicon from "../../assets/refreshIcon.png";
|
||||||
|
const Update = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [status, setStatus] = useState<any>([
|
||||||
|
"New",
|
||||||
|
"In Progress",
|
||||||
|
"Paper",
|
||||||
|
"Setup",
|
||||||
|
]);
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const { data, refetch, isLoading } = useUpdateData(status);
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{open && <AddUpdate refetch={refetch} open={open} setOpen={setOpen} />}
|
||||||
|
<div className="header d-flex" style={{ marginBottom: 16 }}>
|
||||||
|
<p className="title">Updates</p>
|
||||||
|
<div className="d-flex">
|
||||||
|
<button className="btn-add d-flex" onClick={showModal}>
|
||||||
|
<img style={{ marginRight: 8 }} src={addicon} alt="" />
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`btn-refresh-${theme && "dark"} d-flex`}
|
||||||
|
onClick={() => {
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img style={{ marginRight: 8 }} src={refreshicon} alt="" />
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="filter d-flex">
|
||||||
|
<Select
|
||||||
|
style={{ width: 260, marginLeft: 10 }}
|
||||||
|
placeholder="status"
|
||||||
|
onChange={(value: any) => setStatus(value)}
|
||||||
|
mode="multiple"
|
||||||
|
defaultValue={[]}
|
||||||
|
>
|
||||||
|
<Option value="New">New</Option>
|
||||||
|
<Option value="In Progress">In Progress</Option>
|
||||||
|
<Option value="Done">Done</Option>
|
||||||
|
<Option value="Paper">Paper</Option>
|
||||||
|
<Option value="Setup">Setup</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<UpdateTable data={data} refetch={refetch} isLoading={isLoading} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Update;
|
@ -0,0 +1,439 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useUpdateOne } from "../../Hooks/Update";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Spin,
|
||||||
|
Watermark,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
Upload,
|
||||||
|
} from "antd";
|
||||||
|
import { updateController } from "../../API/LayoutApi/update";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
|
import Notfound from "../../Utils/Notfound";
|
||||||
|
import { companyController } from "../../API/LayoutApi/companies";
|
||||||
|
import { customerController } from "../../API/LayoutApi/customers";
|
||||||
|
import { taskController } from "../../API/LayoutApi/tasks";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
import { role } from "../../App";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIconActive from "../../assets/infoIconActive.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import attachmentIcon from "../../assets/attachmentIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import attachmentIconActive from "../../assets/attachmentIconActive.png";
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
type params = {
|
||||||
|
readonly id: any;
|
||||||
|
};
|
||||||
|
type MyObjectType = {
|
||||||
|
[key: string | number]: any;
|
||||||
|
};
|
||||||
|
const UpdateEdit = () => {
|
||||||
|
const { id } = useParams<params>();
|
||||||
|
const { data, refetch, status }: MyObjectType = useUpdateOne(id);
|
||||||
|
const onSubmit = async (value: any) => {
|
||||||
|
if (value.status === "Done") {
|
||||||
|
if (value.solution !== "") {
|
||||||
|
await updateController.updatePut(value, id);
|
||||||
|
refetch();
|
||||||
|
document.location.replace("/#/updates/");
|
||||||
|
} else {
|
||||||
|
alert("solution is empty!!!!!!!!!!!!!!!!!!!!!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await updateController.updatePut(value, id);
|
||||||
|
refetch();
|
||||||
|
document.location.replace("/#/updates/");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const admin_id = localStorage.getItem("admin_id");
|
||||||
|
const [companyId, setCompanyId] = useState<any>(null);
|
||||||
|
const [companyValue, setCompanyValue] = useState<any>();
|
||||||
|
const [companyData, setCompanyData] = useState<MyObjectType>();
|
||||||
|
const [customerId, setCustomerId] = useState<any>(null);
|
||||||
|
const [customerValue, setCustomerValue] = useState<any>();
|
||||||
|
const [customerData, setCustomerData] = useState<MyObjectType>();
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
if (data.company_id === null) {
|
||||||
|
setCompanyId(null);
|
||||||
|
}
|
||||||
|
if (data.customer_id === null) {
|
||||||
|
setCustomerId(null);
|
||||||
|
}
|
||||||
|
const companyIdFromData = data.company_id;
|
||||||
|
const customerIdFromData = data.customer_id;
|
||||||
|
setCompanyId(companyIdFromData);
|
||||||
|
setCustomerId(customerIdFromData);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (companyId !== null) {
|
||||||
|
companyController.companyOne(companyId).then((CompanyData) => {
|
||||||
|
setCompanyData(CompanyData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [companyId]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (customerId !== null) {
|
||||||
|
customerController.customerOne(customerId).then((CustomerData) => {
|
||||||
|
setCustomerData(CustomerData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [customerId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (companyData && companyData.name) {
|
||||||
|
setCompanyValue(companyData.name);
|
||||||
|
}
|
||||||
|
}, [companyData]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (customerData && customerData.name) {
|
||||||
|
setCustomerValue(customerData.name);
|
||||||
|
}
|
||||||
|
}, [customerData]);
|
||||||
|
|
||||||
|
const handleClickDelete = (id: any) => {
|
||||||
|
if (id !== undefined) {
|
||||||
|
taskController.deleteAttachmentController(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [inCharge, setInChage] = useState<any>();
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.in_charge_id) {
|
||||||
|
setInChage(data.in_charge_id);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const ClickDelete = () => {
|
||||||
|
const shouldDelete = window.confirm(
|
||||||
|
"Вы уверены, что хотите удалить эту задачу?"
|
||||||
|
);
|
||||||
|
if (shouldDelete && id !== undefined) {
|
||||||
|
updateController.deleteUpdateController(id).then((data: any) => {
|
||||||
|
document.location.replace(`/#/updates/`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [imgname, setImgname] = useState<any>([]);
|
||||||
|
function handlePaste(event: any) {
|
||||||
|
// Обработка вставки из буфера обмена
|
||||||
|
const clipboardData = event.clipboardData || window.Clipboard;
|
||||||
|
if (clipboardData && clipboardData.items.length > 0) {
|
||||||
|
const clipboardItem = clipboardData.items[0];
|
||||||
|
if (clipboardItem.kind === "file") {
|
||||||
|
const file = clipboardItem.getAsFile();
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
formData.append("shift_update_id", id);
|
||||||
|
taskController.addTaskFile(formData).then((response) => {
|
||||||
|
const n = [response.file];
|
||||||
|
setImgname((prev: any) => [...prev, ...n]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [activeTab, setActiveTab] = useState("1");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{role !== "Checker" || inCharge == admin_id || inCharge == null ? (
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Watermark style={{ height: "100%" }}>
|
||||||
|
{status === "loading" ? (
|
||||||
|
<Spin size="large" spinning={!data} />
|
||||||
|
) : data ? (
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="1"
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(key) => setActiveTab(key)}
|
||||||
|
>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
Information
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="newBasic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
{companyId !== null && (
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Company"
|
||||||
|
>
|
||||||
|
{companyValue !== undefined && (
|
||||||
|
<Input
|
||||||
|
defaultValue={companyValue}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
{customerId !== null && (
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Driver"
|
||||||
|
>
|
||||||
|
{customerValue !== undefined && (
|
||||||
|
<Input
|
||||||
|
defaultValue={customerValue}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
onFinish={onSubmit}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Note"
|
||||||
|
name="note"
|
||||||
|
>
|
||||||
|
<TextArea />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Solution"
|
||||||
|
name="solution"
|
||||||
|
>
|
||||||
|
<TextArea />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Status"
|
||||||
|
name="status"
|
||||||
|
>
|
||||||
|
<Select style={{ width: 120 }}>
|
||||||
|
<Option value="New">New</Option>
|
||||||
|
<Option value="In Progress">
|
||||||
|
In Progress
|
||||||
|
</Option>
|
||||||
|
<Option value="Done">Done</Option>
|
||||||
|
<Option value="Paper">Paper</Option>
|
||||||
|
<Option value="Setup">Setup</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item>
|
||||||
|
{role !== "Chceker" && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
onClick={ClickDelete}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
style={{ margin: 10 }}
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
src={
|
||||||
|
activeTab === "2"
|
||||||
|
? attachmentIconActive
|
||||||
|
: attachmentIcon
|
||||||
|
}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
Attachments
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onPaste={(event) => handlePaste(event)}
|
||||||
|
style={{ height: 800, width: 1000 }}
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basicFuck"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data.attachment_set[0] }}
|
||||||
|
autoComplete="off"
|
||||||
|
onFinish={onSubmit}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item wrapperCol={{ span: "100%" }}>
|
||||||
|
{data.attachment_set.map((item: any) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignSelf: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
background: "rgb(239 239 239)",
|
||||||
|
padding: 15,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginBottom: 20,
|
||||||
|
}}
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
style={{
|
||||||
|
width: "20%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
alignSelf: "center",
|
||||||
|
}}
|
||||||
|
href={item.file_path}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={item.file_path}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
width: "30%",
|
||||||
|
maxHeight: "200px",
|
||||||
|
marginRight: 20,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{item.file_name}
|
||||||
|
</a>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
handleClickDelete(item.id)
|
||||||
|
}
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="File" name="attachment">
|
||||||
|
<Upload.Dragger
|
||||||
|
name="file"
|
||||||
|
customRequest={({
|
||||||
|
file,
|
||||||
|
onSuccess,
|
||||||
|
}: any) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
formData.append("shift_update_id", id);
|
||||||
|
taskController
|
||||||
|
.addTaskFile(formData)
|
||||||
|
.then(() => {
|
||||||
|
onSuccess();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
onSuccess(error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<UploadOutlined
|
||||||
|
style={{ color: "#b5f5ec" }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="ant-upload-text"
|
||||||
|
style={{ color: "#b5f5ec" }}
|
||||||
|
>
|
||||||
|
Click or drag file to this area to upload
|
||||||
|
</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
<p>{imgname.join(",\n")}</p>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Space>
|
||||||
|
</Spin>
|
||||||
|
) : (
|
||||||
|
<Notfound />
|
||||||
|
)}
|
||||||
|
</Watermark>
|
||||||
|
</Spin>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Spin size="large" spinning={!data}></Spin>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateEdit;
|
@ -0,0 +1,141 @@
|
|||||||
|
// // @ts-ignore
|
||||||
|
// import closeIcon from "../../assets/closeIcon.png";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import editIcon from "../../assets/editIcon.png";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import deleteIcon from "../../assets/deleteIconRed.png";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import attachmentIcon from "../../assets/attachmentIcon.png";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// // @ts-ignore
|
||||||
|
// import uploadIcon from "../../assets/uploadIcon.png";
|
||||||
|
// import { Tabs } from "antd";
|
||||||
|
// import TabPane from "antd/es/tabs/TabPane";
|
||||||
|
// import { TUpdate } from "../../types/Update/TUpdate";
|
||||||
|
|
||||||
|
// const UpdateModal = ({
|
||||||
|
// modalOpen,
|
||||||
|
// setModalOpen,
|
||||||
|
// recordUpdate,
|
||||||
|
// }: {
|
||||||
|
// recordUpdate: TUpdate | undefined;
|
||||||
|
// modalOpen: any;
|
||||||
|
// setModalOpen: any;
|
||||||
|
// }) => {
|
||||||
|
// const handleCancel = () => {
|
||||||
|
// setModalOpen(!modalOpen);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div className="TaskModal">
|
||||||
|
// <div className="TaskModal-header">
|
||||||
|
// <div className="TaskModal-title">
|
||||||
|
// <p className="p-driver">{recordUpdate?.company}</p>
|
||||||
|
// <p
|
||||||
|
// style={{ marginLeft: 12 }}
|
||||||
|
// className={`status-${recordUpdate?.status}`}
|
||||||
|
// >
|
||||||
|
// {recordUpdate?.status}
|
||||||
|
// </p>
|
||||||
|
// </div>
|
||||||
|
// <div className="mdoal-actions">
|
||||||
|
// <button className="btn-modal-action">
|
||||||
|
// <img src={editIcon} alt="" />
|
||||||
|
// Edit
|
||||||
|
// </button>
|
||||||
|
// <button style={{ marginLeft: 12 }} className="btn-modal-action">
|
||||||
|
// <img src={uploadIcon} alt="" />
|
||||||
|
// Upload file
|
||||||
|
// </button>
|
||||||
|
// <button style={{ marginLeft: 12 }} className="btn-modal-action">
|
||||||
|
// <img src={deleteIcon} alt="" />
|
||||||
|
// Delete
|
||||||
|
// </button>
|
||||||
|
// <button
|
||||||
|
// onClick={handleCancel}
|
||||||
|
// style={{ marginLeft: 20 }}
|
||||||
|
// className="btn-modal-action"
|
||||||
|
// >
|
||||||
|
// <img style={{ margin: 2 }} src={closeIcon} alt="" />
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// <div className="TaskModal-content">
|
||||||
|
// <Tabs>
|
||||||
|
// <TabPane
|
||||||
|
// tab={
|
||||||
|
// <span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
// <img style={{ marginRight: 10 }} src={infoIcon} alt="" />
|
||||||
|
// Information
|
||||||
|
// </span>
|
||||||
|
// }
|
||||||
|
// key="1"
|
||||||
|
// >
|
||||||
|
// <div className="info-div">
|
||||||
|
// <p
|
||||||
|
// style={{
|
||||||
|
// fontSize: 18,
|
||||||
|
// fontWeight: 700,
|
||||||
|
// lineHeight: "24px",
|
||||||
|
// letterSpacing: "-0.02em",
|
||||||
|
// marginBottom: 16,
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Information
|
||||||
|
// </p>
|
||||||
|
// <div className="info-body">
|
||||||
|
// <tr>
|
||||||
|
// <p className="sub">Comapany</p>
|
||||||
|
// <p className="info">{recordUpdate?.company}</p>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <p className="sub">Driver</p>
|
||||||
|
// <p className="info">{recordUpdate?.customer}</p>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <p className="sub">Created by</p>
|
||||||
|
// <p className="info">{recordUpdate?.provider}</p>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <p className="sub">Created at</p>
|
||||||
|
// <p className="info">{recordUpdate?.created_at}</p>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <p className="sub">Completed_by</p>
|
||||||
|
// <p className="info">{recordUpdate?.executor}</p>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <p className="sub">Solution</p>
|
||||||
|
// <p className="info">{recordUpdate?.solution}</p>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <p className="sub">Note</p>
|
||||||
|
// <p className="info">{recordUpdate?.note}</p>
|
||||||
|
// </tr>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </TabPane>
|
||||||
|
// <TabPane
|
||||||
|
// tab={
|
||||||
|
// <span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
// <img style={{ marginRight: 10 }} src={attachmentIcon} alt="" />
|
||||||
|
// Attachments
|
||||||
|
// </span>
|
||||||
|
// }
|
||||||
|
// key="2"
|
||||||
|
// ></TabPane>
|
||||||
|
// </Tabs>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default UpdateModal;
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const UpdateModal = () => {
|
||||||
|
return <div>UpdateModal</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateModal;
|
@ -0,0 +1,300 @@
|
|||||||
|
import { Space, Table, Tooltip } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import { useCompanyData } from "../../Hooks/Companies";
|
||||||
|
import { useCustomerData } from "../../Hooks/Customers";
|
||||||
|
import { useUserData } from "../../Hooks/Users";
|
||||||
|
import { updateController } from "../../API/LayoutApi/update";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { TUpdate } from "../../types/Update/TUpdate";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import pin from "../../assets/pinicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import unpin from "../../assets/unpinicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zippy from "../../assets/zippyicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import evo from "../../assets/evoicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import zeelog from "../../assets/zeelogicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import ontime from "../../assets/ontimeicon.svg";
|
||||||
|
// @ts-ignore
|
||||||
|
import tt from "../../assets/tticon.svg";
|
||||||
|
const UpdateTable = ({
|
||||||
|
data = [],
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
data: TUpdate[] | undefined;
|
||||||
|
isLoading?: boolean;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TUpdate[], unknown>>;
|
||||||
|
}) => {
|
||||||
|
const CompanyData = useCompanyData({});
|
||||||
|
const CustomerData = useCustomerData({});
|
||||||
|
const AdminData = useUserData({});
|
||||||
|
|
||||||
|
const [isTextSelected, setIsTextSelected] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSelectionChange = () => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
setIsTextSelected(selection !== null && selection.toString() !== "");
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("selectionchange", handleSelectionChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const Row = (record: TUpdate, event: any) => {
|
||||||
|
if (isTextSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.target.classList.contains("ant-table-cell")) {
|
||||||
|
document.location.replace(`/#/updates/${record.id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getImageSource = (source: string) => {
|
||||||
|
switch (source) {
|
||||||
|
case "Zippy":
|
||||||
|
return zippy;
|
||||||
|
case "EVO":
|
||||||
|
return evo;
|
||||||
|
case "Ontime":
|
||||||
|
return ontime;
|
||||||
|
case "Zeelog":
|
||||||
|
return zeelog;
|
||||||
|
case "TT":
|
||||||
|
return tt;
|
||||||
|
default:
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
onRow={(record) => ({
|
||||||
|
onClick: (event) => Row(record, event),
|
||||||
|
})}
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
...u,
|
||||||
|
company_name: CompanyData?.data?.find(
|
||||||
|
(company: any) => company.id === u.company_id
|
||||||
|
)?.name,
|
||||||
|
customer_name: CustomerData?.data?.find(
|
||||||
|
(customer: any) => customer.id === u.customer_id
|
||||||
|
)?.name,
|
||||||
|
in_charge_name: AdminData?.data?.find(
|
||||||
|
(admin: any) => admin.id === u.provider_id
|
||||||
|
)?.username,
|
||||||
|
executor_name: AdminData?.data?.find(
|
||||||
|
(admin: any) => admin.id === u.executor_id
|
||||||
|
)?.username,
|
||||||
|
created: moment(u?.created_at, "YYYY-MM-DD HH:mm:ss").format(
|
||||||
|
"DD.MM.YYYY HH:mm"
|
||||||
|
),
|
||||||
|
action: { ...u },
|
||||||
|
}))}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Company",
|
||||||
|
dataIndex: "company",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
width: "10%",
|
||||||
|
render: (text: any, record: any) => (
|
||||||
|
<Tooltip placement="topLeft" title={text?.name}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{text?.source && (
|
||||||
|
<img
|
||||||
|
src={getImageSource(text?.source)}
|
||||||
|
alt=""
|
||||||
|
style={{ width: 20, height: 20, marginRight: 10 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{text?.name}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Driver",
|
||||||
|
dataIndex: "customer",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
width: "10%",
|
||||||
|
render: (item: { name: string }) => (
|
||||||
|
<Tooltip placement="topLeft" title={item?.name}>
|
||||||
|
{item?.name}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Created by",
|
||||||
|
dataIndex: "provider ",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
responsive: ["xl"],
|
||||||
|
width: "10%",
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Complited by",
|
||||||
|
dataIndex: "executor",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
responsive: ["lg"],
|
||||||
|
width: "10%",
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Status",
|
||||||
|
dataIndex: "status",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
width: "10%",
|
||||||
|
render: (status: string) => (
|
||||||
|
<span>
|
||||||
|
{status === "Done" && <p className="status-done">Done</p>}
|
||||||
|
{status === "Checking" && (
|
||||||
|
<p className="status-in-progress">Checking</p>
|
||||||
|
)}
|
||||||
|
{status === "New" && <p className="status-new">New</p>}
|
||||||
|
{status === "Setup" && <p className="status-setup">Setup</p>}
|
||||||
|
{status === "Paper" && <p className="status-paper">Paper</p>}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Note",
|
||||||
|
dataIndex: "note",
|
||||||
|
width: "10%",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Solution",
|
||||||
|
dataIndex: "solution",
|
||||||
|
width: "10%",
|
||||||
|
responsive: ["lg"],
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Created at",
|
||||||
|
dataIndex: "created",
|
||||||
|
ellipsis: {
|
||||||
|
showTitle: false,
|
||||||
|
},
|
||||||
|
responsive: ["xxl"],
|
||||||
|
width: "10%",
|
||||||
|
render: (note: string) => (
|
||||||
|
<Tooltip placement="topLeft" title={note}>
|
||||||
|
{note}
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Actions",
|
||||||
|
dataIndex: "action",
|
||||||
|
width: "8%",
|
||||||
|
render: (record: TUpdate) => {
|
||||||
|
return (
|
||||||
|
<div className="notedit">
|
||||||
|
{record.status !== "Done" && (
|
||||||
|
<Space>
|
||||||
|
{record.is_pinned ? (
|
||||||
|
<button
|
||||||
|
className="btn-unpin"
|
||||||
|
onClick={(e) => {
|
||||||
|
const updateData = {
|
||||||
|
is_pinned: false,
|
||||||
|
};
|
||||||
|
updateController
|
||||||
|
.updatePatch(updateData, record.id)
|
||||||
|
.then(() => {
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={unpin} alt="" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className="btn-pin"
|
||||||
|
style={{ paddingTop: 2 }}
|
||||||
|
onClick={(e) => {
|
||||||
|
const updateData = {
|
||||||
|
is_pinned: true,
|
||||||
|
};
|
||||||
|
updateController
|
||||||
|
.updatePatch(updateData, record.id)
|
||||||
|
.then(() => {
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={pin} alt="" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
loading={isLoading}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateTable;
|
@ -0,0 +1,87 @@
|
|||||||
|
import { Input, Modal, Form as FormAnt, Select } from "antd";
|
||||||
|
import { useRoleData } from "../../Hooks/Role";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { TUser } from "../../types/User/TUser";
|
||||||
|
import { inviteVerify } from "../../API/auth/invite";
|
||||||
|
|
||||||
|
const AddUser = ({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TUser[], unknown>>;
|
||||||
|
open: boolean;
|
||||||
|
setOpen(open: boolean): void;
|
||||||
|
}) => {
|
||||||
|
const [form] = FormAnt.useForm();
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
const roleData = useRoleData();
|
||||||
|
const filteredRoleData = roleData?.data?.filter(role => role.name !== 'Owner');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="Add user"
|
||||||
|
okText="Create"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onOk={() => {
|
||||||
|
form.validateFields().then(async (values) => {
|
||||||
|
if (typeof values.groups === "number") {
|
||||||
|
values.groups = [values.groups];
|
||||||
|
}
|
||||||
|
form.resetFields();
|
||||||
|
delete values.Confirm;
|
||||||
|
inviteVerify(values);
|
||||||
|
setOpen(!open);
|
||||||
|
refetch();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormAnt
|
||||||
|
form={form}
|
||||||
|
layout="horizontal"
|
||||||
|
name="form_in_modal"
|
||||||
|
initialValues={{ modifier: "public" }}
|
||||||
|
>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="E-mail"
|
||||||
|
name="email"
|
||||||
|
rules={[
|
||||||
|
{ required: true },
|
||||||
|
{
|
||||||
|
type: "email",
|
||||||
|
message: "The input is not valid E-mail!",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</FormAnt.Item>
|
||||||
|
<FormAnt.Item
|
||||||
|
label="Role"
|
||||||
|
name="role_id"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={filteredRoleData?.map(role => ({
|
||||||
|
label: role.name,
|
||||||
|
value: role.id,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</FormAnt.Item>
|
||||||
|
</FormAnt>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddUser;
|
@ -0,0 +1,191 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useUserOne } from "../../Hooks/Users";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Spin,
|
||||||
|
Watermark,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Input,
|
||||||
|
Button,
|
||||||
|
Select,
|
||||||
|
} from "antd";
|
||||||
|
import { userController } from "../../API/LayoutApi/users";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIcon from "../../assets/infoIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import infoIconActive from "../../assets/infoIconActive.png";
|
||||||
|
import Notfound from "../../Utils/Notfound";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams";
|
||||||
|
import { useRoleData } from "../../Hooks/Role";
|
||||||
|
import { role } from "../../App";
|
||||||
|
import { useState } from "react";
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
type params = {
|
||||||
|
readonly id: string;
|
||||||
|
};
|
||||||
|
type MyObjectType = {
|
||||||
|
[key: string | number]: any;
|
||||||
|
};
|
||||||
|
const UserEdit = () => {
|
||||||
|
const { id } = useParams<params>();
|
||||||
|
|
||||||
|
const { data, refetch, status }: MyObjectType = useUserOne(id);
|
||||||
|
|
||||||
|
const onSubmit = async (value: any) => {
|
||||||
|
id && (await userController.userPatch(value, id));
|
||||||
|
refetch();
|
||||||
|
document.location.replace("/#/users/");
|
||||||
|
};
|
||||||
|
const TeamData = useTeamData("");
|
||||||
|
const noTeamOption = { label: " - - - - - -", value: "" };
|
||||||
|
const TeamOption: { label: string; value: any }[] | undefined =
|
||||||
|
TeamData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}));
|
||||||
|
if (TeamOption) {
|
||||||
|
TeamOption.unshift(noTeamOption);
|
||||||
|
}
|
||||||
|
const roleData = useRoleData();
|
||||||
|
|
||||||
|
const ClickDelete = () => {
|
||||||
|
const shouldDelete = window.confirm(
|
||||||
|
"Вы уверены, что хотите удалить этот админ?"
|
||||||
|
);
|
||||||
|
if (shouldDelete && id !== undefined) {
|
||||||
|
userController.deleteUserController(id).then(() => {
|
||||||
|
document.location.replace(`/#/users`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [activeTab, setActiveTab] = useState("1");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Watermark style={{ height: "100%" }}>
|
||||||
|
{status === "loading" ? (
|
||||||
|
<Spin size="large" spinning={!data} />
|
||||||
|
) : data ? (
|
||||||
|
<Spin size="large" spinning={!data}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="1"
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(key) => setActiveTab(key)}
|
||||||
|
>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<img
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
src={activeTab === "1" ? infoIconActive : infoIcon}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
Information
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size="middle"
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basic"
|
||||||
|
layout="vertical"
|
||||||
|
wrapperCol={{ span: 16 }}
|
||||||
|
initialValues={{ ...data }}
|
||||||
|
onFinish={onSubmit}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 10]}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="First name"
|
||||||
|
name="first_name"
|
||||||
|
>
|
||||||
|
<Input readOnly />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Last name"
|
||||||
|
name="last_name"
|
||||||
|
>
|
||||||
|
<Input readOnly />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Username"
|
||||||
|
name="username"
|
||||||
|
>
|
||||||
|
<Input readOnly />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Team"
|
||||||
|
name="team_id"
|
||||||
|
>
|
||||||
|
<Select options={TeamOption} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Form.Item
|
||||||
|
wrapperCol={{ span: "100%" }}
|
||||||
|
label="Role"
|
||||||
|
name="role_id"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={roleData?.data?.map((item) => ({
|
||||||
|
label: item?.name,
|
||||||
|
value: item?.id,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item>
|
||||||
|
{role !== "Checker" && (
|
||||||
|
<Button
|
||||||
|
onClick={() => ClickDelete()}
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Space>
|
||||||
|
</Spin>
|
||||||
|
) : (
|
||||||
|
<Notfound />
|
||||||
|
)}
|
||||||
|
</Watermark>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserEdit;
|
@ -0,0 +1,108 @@
|
|||||||
|
import { Table, Tag } from "antd";
|
||||||
|
import { useTeamData } from "../../Hooks/Teams";
|
||||||
|
import { TUser } from "../../types/User/TUser";
|
||||||
|
import {
|
||||||
|
QueryObserverResult,
|
||||||
|
RefetchOptions,
|
||||||
|
RefetchQueryFilters,
|
||||||
|
} from "react-query";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
// @ts-ignore
|
||||||
|
import tagIcon from "../../assets/tagIcon.png";
|
||||||
|
const UserTable = ({
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
}: {
|
||||||
|
data: TUser[] | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
refetch: <TPageData>(
|
||||||
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
||||||
|
) => Promise<QueryObserverResult<TUser[], unknown>>;
|
||||||
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const TeamData = useTeamData("");
|
||||||
|
|
||||||
|
const Row = (record: TUser) => {
|
||||||
|
let isTextSelected = false;
|
||||||
|
document.addEventListener("selectionchange", () => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection !== null && selection.toString() !== "") {
|
||||||
|
isTextSelected = true;
|
||||||
|
} else {
|
||||||
|
isTextSelected = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
onClick: () => {
|
||||||
|
if (isTextSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigate(`/users/${record.id}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
onRow={(record) => Row(record)}
|
||||||
|
dataSource={data?.map((u, i) => ({
|
||||||
|
no: i + 1,
|
||||||
|
team: TeamData?.data?.map((team: any) => {
|
||||||
|
if (team.id === u?.team_id) {
|
||||||
|
return team?.name;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
action: { id: u.id },
|
||||||
|
...u,
|
||||||
|
}))}
|
||||||
|
loading={isLoading}
|
||||||
|
size="middle"
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: <img src={tagIcon} alt="" />,
|
||||||
|
dataIndex: "no",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Username",
|
||||||
|
dataIndex: "username",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
dataIndex: "team",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Is Active",
|
||||||
|
dataIndex: "is_active",
|
||||||
|
render: (tag: boolean) => (
|
||||||
|
<Tag color={tag ? "geekblue" : "red"}>
|
||||||
|
{tag ? "True" : "False"}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: "True",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "False",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value: any, record: any) => {
|
||||||
|
return record.is_active === value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rowClassName={(record, index) =>
|
||||||
|
index % 2 === 0 ? "odd-row" : "even-row"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserTable;
|
@ -0,0 +1,63 @@
|
|||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { useUserData } from "../../Hooks/Users";
|
||||||
|
import AddUser from "./AddUser";
|
||||||
|
import UserTable from "./UserTable";
|
||||||
|
// @ts-ignore
|
||||||
|
import IconSearch from "../../assets/searchIcon.png";
|
||||||
|
//@ts-ignore
|
||||||
|
import addicon from "../../assets/addiconpng.png";
|
||||||
|
|
||||||
|
const User = () => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const showModal = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data, refetch, isLoading } = useUserData({
|
||||||
|
name: search,
|
||||||
|
team: "",
|
||||||
|
});
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchText = e.target.value;
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setSearch(searchText);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
const theme = localStorage.getItem("theme") === "true" ? true : false;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{open && <AddUser open={open} setOpen={setOpen} refetch={refetch} />}
|
||||||
|
<div className="header d-flex">
|
||||||
|
<p className="title">Users</p>
|
||||||
|
<button
|
||||||
|
className="btn-add d-flex"
|
||||||
|
style={{ marginRight: 0 }}
|
||||||
|
onClick={showModal}
|
||||||
|
>
|
||||||
|
<img src={addicon} style={{ marginRight: 8 }} alt="" />
|
||||||
|
Invite User
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="filter d-flex">
|
||||||
|
<div className="search-div">
|
||||||
|
<img src={IconSearch} alt="" />
|
||||||
|
<input
|
||||||
|
className={`search-input-${theme}`}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UserTable data={data} isLoading={isLoading} refetch={refetch} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default User;
|
@ -0,0 +1,20 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { callController } from "../../API/LayoutApi/callrequests";
|
||||||
|
|
||||||
|
export const useCallData = ({ status }: { status: string }) => {
|
||||||
|
return useQuery(
|
||||||
|
[`callback-requests/`, { status }],
|
||||||
|
() => callController.read({ status }),
|
||||||
|
{
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const useCompanyOne = (id: number | undefined) => {
|
||||||
|
// return useQuery(
|
||||||
|
// [`company/${id}/`, id],
|
||||||
|
// () => companyController.companyOne(id),
|
||||||
|
// { refetchOnWindowFocus: false }
|
||||||
|
// );
|
||||||
|
// };
|
@ -0,0 +1,26 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import {
|
||||||
|
TCompanyGetParams,
|
||||||
|
companyController,
|
||||||
|
} from "../../API/LayoutApi/companies";
|
||||||
|
|
||||||
|
export const useCompanyData = ({
|
||||||
|
name,
|
||||||
|
page,
|
||||||
|
is_active,
|
||||||
|
}: TCompanyGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`companies/`, name, page, is_active],
|
||||||
|
() =>
|
||||||
|
companyController.read({ name: name, page: page, is_active: is_active }),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCompanyOne = (id: number | undefined) => {
|
||||||
|
return useQuery(
|
||||||
|
[`company/${id}/`, id],
|
||||||
|
() => companyController.companyOne(id),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import {
|
||||||
|
TCustomerByCompanyGetParams,
|
||||||
|
TCustomerGetParams,
|
||||||
|
customerController,
|
||||||
|
} from "../../API/LayoutApi/customers";
|
||||||
|
|
||||||
|
export const useCustomerData = ({
|
||||||
|
name,
|
||||||
|
page,
|
||||||
|
is_active,
|
||||||
|
pageSize,
|
||||||
|
}: TCustomerGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`customers/`, name, page, is_active, pageSize],
|
||||||
|
() =>
|
||||||
|
customerController.read({
|
||||||
|
name: name,
|
||||||
|
page: page,
|
||||||
|
is_active: is_active,
|
||||||
|
pageSize: pageSize,
|
||||||
|
}),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCustomerByComanyData = ({
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
}: TCustomerByCompanyGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`customers-by-company/${id}`, name],
|
||||||
|
() => customerController.customerByCompany(id, name),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCustomerOne = (Id: number | undefined) => {
|
||||||
|
return useQuery(
|
||||||
|
[`customer/${Id}/`, Id],
|
||||||
|
() => customerController.customerOne(Id),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { TMyTaskHistoryGetParams, prof } from "../../API/LayoutApi/profile";
|
||||||
|
|
||||||
|
export const useMystatsData = ({
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
}: TMyTaskHistoryGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`stats/my-stats/`, start_date, end_date],
|
||||||
|
() => prof.read({ start_date, end_date }),
|
||||||
|
{
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// export const useMystatsData = ({
|
||||||
|
// start_date,
|
||||||
|
// end_date,
|
||||||
|
// }: TMyTaskHistoryGetParams) => {
|
||||||
|
// return useQuery(
|
||||||
|
// [`stats/my-stats/`, start_date, end_date],
|
||||||
|
// () => prof.read({ start_date, end_date }),
|
||||||
|
// {
|
||||||
|
// refetchOnWindowFocus: false,
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const useProfData = () => {
|
||||||
|
return useQuery([`users/my-profile/`], () => prof.self(), {
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useMyHistoryData = ({
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
}: TMyTaskHistoryGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`my-task-history/`, start_date, end_date],
|
||||||
|
() => prof.myTaskHistory({ start_date, end_date }),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import {
|
||||||
|
requestsController,
|
||||||
|
TRequestsGetParams,
|
||||||
|
} from "../../API/LayoutApi/requests";
|
||||||
|
|
||||||
|
export const useRequestsData = ({ search, status }: TRequestsGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`driver-requests/`, { search, status }],
|
||||||
|
() => requestsController.read({ search, status }),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRequestsOne = (userId: number | string | undefined): any => {
|
||||||
|
return useQuery(
|
||||||
|
[`driver-requests/${userId || "all"}`, userId],
|
||||||
|
() => requestsController.requestsOne(userId),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,19 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { roleController } from "../../API/LayoutApi/role";
|
||||||
|
|
||||||
|
export const useRoleData = () => {
|
||||||
|
return useQuery(
|
||||||
|
[`users/roles/`],
|
||||||
|
() => roleController.read(),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRoleOne = (id: string) => {
|
||||||
|
return useQuery(
|
||||||
|
[`users/role/${id}`, id],
|
||||||
|
() => roleController.roleOne(id),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { serviceController } from "../../API/LayoutApi/services";
|
||||||
|
|
||||||
|
export const useServiceData = () => {
|
||||||
|
return useQuery(
|
||||||
|
[`services/`],
|
||||||
|
() => serviceController.read(),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useServiceOne = (serviceId: number | undefined) => {
|
||||||
|
return useQuery(
|
||||||
|
[`service/${serviceId || "all"}`, serviceId],
|
||||||
|
() => serviceController.serviceOne(serviceId),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,43 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { TStatGetParams, statController } from "../../API/LayoutApi/statistic";
|
||||||
|
|
||||||
|
export const useStatsData = ({search, team, start_date, end_date}: TStatGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`stats/all-users/`, search, team, start_date, end_date],
|
||||||
|
() => statController.read({search, team, start_date, end_date}),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const useStatTeamData = ({search, start_date, end_date}: TStatGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`stats/all-teams/`, search, start_date, end_date],
|
||||||
|
() => statController.team({search, start_date, end_date}),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreatorsData = ({ start_date, end_date}: TStatGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`stats/task-creators/`, start_date, end_date],
|
||||||
|
() => statController.creators({ start_date, end_date}),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCardData = ({ start_date, end_date}: TStatGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`stats/tasks-comparison/`, start_date, end_date],
|
||||||
|
() => statController.cards({ start_date, end_date}),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useStatOne = (
|
||||||
|
statId: number | string | undefined
|
||||||
|
): any => {
|
||||||
|
return useQuery(
|
||||||
|
[`stat/${statId || "all"}`, statId],
|
||||||
|
() => statController.statOne(statId),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { TTasksGetParams, taskController } from "../../API/LayoutApi/tasks";
|
||||||
|
|
||||||
|
export const useTasks = ({ search, status, team, page }: TTasksGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`tasks/`, search, status, team, page],
|
||||||
|
() => taskController.read({ search, status, team, page }),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTaskOne = (taskId: number) => {
|
||||||
|
return useQuery(
|
||||||
|
[`task/${taskId}/`, taskId],
|
||||||
|
() => taskController.taskOne(taskId),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTaskHistory = (Id: number | undefined) => {
|
||||||
|
return useQuery(
|
||||||
|
[`customer/${Id}/`, Id],
|
||||||
|
() => taskController.getHistory(Id),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { teamController } from "../../API/LayoutApi/teams";
|
||||||
|
|
||||||
|
export const useTeamData = (name: string) => {
|
||||||
|
return useQuery(
|
||||||
|
[`teams/?name=${name}/`, name],
|
||||||
|
() => teamController.read(name),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTeamOne = (
|
||||||
|
teamId: number | string | undefined
|
||||||
|
): any => {
|
||||||
|
return useQuery(
|
||||||
|
[`team/${teamId || "all"}`, teamId],
|
||||||
|
() => teamController.teamOne(teamId),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { updateController } from "../../API/LayoutApi/update";
|
||||||
|
|
||||||
|
// export const useUpdateData = (status: string): any => {
|
||||||
|
// return useQuery(
|
||||||
|
// [`updates/${status}`, status],
|
||||||
|
// () => updateController.read(status),
|
||||||
|
// { refetchOnWindowFocus: false }
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const useUpdateData = (status: string) => {
|
||||||
|
return useQuery(
|
||||||
|
[`shift-updates`, status],
|
||||||
|
() => updateController.read(status),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateOne = (updateId: number | string | undefined): any => {
|
||||||
|
return useQuery(
|
||||||
|
[`update/${updateId || "all"}`, updateId],
|
||||||
|
() => updateController.updateOne(updateId),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { TUsersGetParams, userController } from "../../API/LayoutApi/users";
|
||||||
|
|
||||||
|
export const useUserData = ({name, team, role}: TUsersGetParams) => {
|
||||||
|
return useQuery(
|
||||||
|
[`users/admins/`, {name, team, role}],
|
||||||
|
() => userController.read({name, team, role}),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserOne = (
|
||||||
|
userId: number | string | undefined
|
||||||
|
): any => {
|
||||||
|
return useQuery(
|
||||||
|
[`user/${userId || "all"}`, userId],
|
||||||
|
() => userController.userOne(userId),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCheckUser = (
|
||||||
|
username: string
|
||||||
|
): any => {
|
||||||
|
return useQuery(
|
||||||
|
[`user/${username}/`],
|
||||||
|
() => userController.CheckUsername(username),
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
const NotInvitedYet = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotInvitedYet
|
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
import { Button, Result } from "antd";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
const Notfound = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Result
|
||||||
|
status="404"
|
||||||
|
title="404"
|
||||||
|
subTitle="Sorry, the page you visited does not exist."
|
||||||
|
extra={
|
||||||
|
<Button type="primary">
|
||||||
|
<Link to="/">Come back</Link>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notfound;
|
@ -0,0 +1,103 @@
|
|||||||
|
export const common = [
|
||||||
|
"123456",
|
||||||
|
"password",
|
||||||
|
"12345678",
|
||||||
|
"qwerty",
|
||||||
|
"123456789",
|
||||||
|
"12345",
|
||||||
|
"1234",
|
||||||
|
"111111",
|
||||||
|
"1234567",
|
||||||
|
"dragon",
|
||||||
|
"123123",
|
||||||
|
"baseball",
|
||||||
|
"abc123",
|
||||||
|
"football",
|
||||||
|
"monkey",
|
||||||
|
"letmein",
|
||||||
|
"696969",
|
||||||
|
"shadow",
|
||||||
|
"master",
|
||||||
|
"666666",
|
||||||
|
"qwertyuiop",
|
||||||
|
"123321",
|
||||||
|
"mustang",
|
||||||
|
"1234567890",
|
||||||
|
"michael",
|
||||||
|
"654321",
|
||||||
|
"pussy",
|
||||||
|
"superman",
|
||||||
|
"1qaz2wsx",
|
||||||
|
"7777777",
|
||||||
|
"fuckyou",
|
||||||
|
"121212",
|
||||||
|
"000000",
|
||||||
|
"qazwsx",
|
||||||
|
"123qwe",
|
||||||
|
"killer",
|
||||||
|
"trustno1",
|
||||||
|
"jordan",
|
||||||
|
"jennifer",
|
||||||
|
"zxcvbnm",
|
||||||
|
"asdfgh",
|
||||||
|
"hunter",
|
||||||
|
"buster",
|
||||||
|
"soccer",
|
||||||
|
"harley",
|
||||||
|
"batman",
|
||||||
|
"andrew",
|
||||||
|
"tigger",
|
||||||
|
"sunshine",
|
||||||
|
"iloveyou",
|
||||||
|
"fuckme",
|
||||||
|
"2000",
|
||||||
|
"charlie",
|
||||||
|
"robert",
|
||||||
|
"thomas",
|
||||||
|
"hockey",
|
||||||
|
"ranger",
|
||||||
|
"daniel",
|
||||||
|
"starwars",
|
||||||
|
"klaster",
|
||||||
|
"112233",
|
||||||
|
"george",
|
||||||
|
"asshole",
|
||||||
|
"computer",
|
||||||
|
"michelle",
|
||||||
|
"jessica",
|
||||||
|
"pepper",
|
||||||
|
"1111",
|
||||||
|
"zxcvbn",
|
||||||
|
"555555",
|
||||||
|
"11111111",
|
||||||
|
"131313",
|
||||||
|
"freedom",
|
||||||
|
"777777",
|
||||||
|
"pass",
|
||||||
|
"fuck",
|
||||||
|
"maggie",
|
||||||
|
"159753",
|
||||||
|
"aaaaaa",
|
||||||
|
"ginger",
|
||||||
|
"princess",
|
||||||
|
"joshua",
|
||||||
|
"cheese",
|
||||||
|
"amanda",
|
||||||
|
"summer",
|
||||||
|
"love",
|
||||||
|
"ashley",
|
||||||
|
"6969",
|
||||||
|
"nicole",
|
||||||
|
"chelsea",
|
||||||
|
"biteme",
|
||||||
|
"matthew",
|
||||||
|
"access",
|
||||||
|
"yankees",
|
||||||
|
"987654321",
|
||||||
|
"dallas",
|
||||||
|
"austin",
|
||||||
|
"thunder",
|
||||||
|
"taylor",
|
||||||
|
"matrix",
|
||||||
|
"minecraft",
|
||||||
|
];
|
@ -0,0 +1,3 @@
|
|||||||
|
export const clear_local_storage = (): void => {
|
||||||
|
localStorage.clear();
|
||||||
|
};
|
@ -0,0 +1,234 @@
|
|||||||
|
import { MenuProps } from "antd";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import Company from "../Components/Companies/Companies";
|
||||||
|
import CompanyEdit from "../Components/Companies/CompaniesEdit";
|
||||||
|
import Customer from "../Components/Customers/Customers";
|
||||||
|
import CustomerEdit from "../Components/Customers/CustomersEdit";
|
||||||
|
import Service from "../Components/Services/Services";
|
||||||
|
import ServiceEdit from "../Components/Services/ServiceEdit";
|
||||||
|
import Task from "../Components/Tasks/Tasks";
|
||||||
|
import TeamEdit from "../Components/Teams/TeamEdit";
|
||||||
|
import Team from "../Components/Teams/Teams";
|
||||||
|
import User from "../Components/Users/Users";
|
||||||
|
import UserEdit from "../Components/Users/UserEdit";
|
||||||
|
import MenuItem from "antd/es/menu/MenuItem";
|
||||||
|
import Stat from "../Components/Statistics/Statistic";
|
||||||
|
import Profile from "../Components/Profile/Profile";
|
||||||
|
import Update from "../Components/Updates/Update";
|
||||||
|
import UpdateEdit from "../Components/Updates/UpdateEdit";
|
||||||
|
// @ts-ignore
|
||||||
|
import taskIcon from "../assets/tasknavicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import companyIcon from "../assets/companynavicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import serviceIcon from "../assets/servicenavicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import teamIcon from "../assets/teamnavicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import statisticIcon from "../assets/statnavicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import updateIcon from "../assets/updatenavicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import userIcon from "../assets/usernavicon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import driverIcon from "../assets/customersIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import requestIcon from "../assets/requestIcon.png";
|
||||||
|
// @ts-ignore
|
||||||
|
import callIcon from "../assets/callIcon.png";
|
||||||
|
import Requests from "../Components/Requests/Requests";
|
||||||
|
import Call from "../Components/CallRequests/Call";
|
||||||
|
const loc: any = localStorage.getItem("user");
|
||||||
|
const role = JSON.parse(loc)?.role;
|
||||||
|
|
||||||
|
type MenuItem = Required<MenuProps>["items"][number];
|
||||||
|
|
||||||
|
function getItem(
|
||||||
|
label: React.ReactNode,
|
||||||
|
key: React.Key,
|
||||||
|
icon?: React.ReactNode,
|
||||||
|
children?: MenuItem[]
|
||||||
|
): MenuItem {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
label,
|
||||||
|
} as MenuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const allMenu: MenuItem[] = [
|
||||||
|
getItem(<Link to="/">Tasks</Link>, "/", <img alt="" src={taskIcon} />),
|
||||||
|
getItem(
|
||||||
|
<Link to="companies/">Companies</Link>,
|
||||||
|
"companies/",
|
||||||
|
<img alt="" src={companyIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="customers/">Drivers</Link>,
|
||||||
|
"customers/",
|
||||||
|
<img alt="" src={driverIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="services/">Services</Link>,
|
||||||
|
"services/",
|
||||||
|
<img alt="" src={serviceIcon} />
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (role === "Tech Support") {
|
||||||
|
allMenu.push(
|
||||||
|
getItem(
|
||||||
|
<Link to="teams/">Teams</Link>,
|
||||||
|
"teams/",
|
||||||
|
<img alt="" src={teamIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="updates/">Updates</Link>,
|
||||||
|
"updates/",
|
||||||
|
<img alt="" src={updateIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="requests/">Driver Requests</Link>,
|
||||||
|
"requests/",
|
||||||
|
<img alt="" src={requestIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="call/">Call Requests</Link>,
|
||||||
|
"call/",
|
||||||
|
<img alt="" src={callIcon} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role === "Owner") {
|
||||||
|
allMenu.push(
|
||||||
|
getItem(
|
||||||
|
<Link to="users/">Users</Link>,
|
||||||
|
"users/",
|
||||||
|
<img alt="" src={userIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="teams/">Teams</Link>,
|
||||||
|
"teams/",
|
||||||
|
<img alt="" src={teamIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="stats/">Statistics</Link>,
|
||||||
|
"stats/",
|
||||||
|
<img alt="" src={statisticIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="updates/">Updates</Link>,
|
||||||
|
"updates/",
|
||||||
|
<img alt="" src={updateIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="requests/">Driver Requests</Link>,
|
||||||
|
"requests/",
|
||||||
|
<img alt="" src={requestIcon} />
|
||||||
|
),
|
||||||
|
getItem(
|
||||||
|
<Link to="call/">Call Requests</Link>,
|
||||||
|
"call/",
|
||||||
|
<img alt="" src={callIcon} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type TItems = {
|
||||||
|
path: string;
|
||||||
|
component: JSX.Element;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mainItems: TItems[] = [
|
||||||
|
{
|
||||||
|
path: "/companies/",
|
||||||
|
component: <Company />,
|
||||||
|
key: "/companies/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/companies/:id",
|
||||||
|
component: <CompanyEdit />,
|
||||||
|
key: "/company/:id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/customers/",
|
||||||
|
component: <Customer />,
|
||||||
|
key: "/customers/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/customers/:id/",
|
||||||
|
component: <CustomerEdit />,
|
||||||
|
key: "/cusotmer/:id/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/services/",
|
||||||
|
component: <Service />,
|
||||||
|
key: "/services/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/services/:id/",
|
||||||
|
component: <ServiceEdit />,
|
||||||
|
key: "/service/:id/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
component: <Task />,
|
||||||
|
key: "tasks",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const superItems: TItems[] = [
|
||||||
|
{
|
||||||
|
path: "/teams/",
|
||||||
|
component: <Team />,
|
||||||
|
key: "/teams/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/teams/:id/",
|
||||||
|
component: <TeamEdit />,
|
||||||
|
key: "/team/:id/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/users/",
|
||||||
|
component: <User />,
|
||||||
|
key: "/users/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/users/:id/",
|
||||||
|
component: <UserEdit />,
|
||||||
|
key: "/user/:id/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/stats/",
|
||||||
|
component: <Stat />,
|
||||||
|
key: "/stats/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/profile/",
|
||||||
|
component: <Profile />,
|
||||||
|
key: "/profile/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/updates/",
|
||||||
|
component: <Update />,
|
||||||
|
key: "/updates/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/updates/:id/",
|
||||||
|
component: <UpdateEdit />,
|
||||||
|
key: "/update/:id/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/requests/",
|
||||||
|
component: <Requests />,
|
||||||
|
key: "/requests/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/call/",
|
||||||
|
component: <Call />,
|
||||||
|
key: "/call/",
|
||||||
|
},
|
||||||
|
];
|
After Width: | Height: | Size: 360 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 511 B |
After Width: | Height: | Size: 215 B |
After Width: | Height: | Size: 215 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue