diff --git a/build.zip b/build.zip index 418566c..3f1b99e 100644 Binary files a/build.zip and b/build.zip differ diff --git a/src/API/LayoutApi/tasks.ts b/src/API/LayoutApi/tasks.ts index 5d1a1a7..8e1e31f 100644 --- a/src/API/LayoutApi/tasks.ts +++ b/src/API/LayoutApi/tasks.ts @@ -43,6 +43,9 @@ export type TTaskPostFileParams = { shift_update_id?: number; description?: string; }; +interface TMessageResponse { + message: string; +} export const taskController = { async read(filterObject: TTasksGetParams) { @@ -76,12 +79,22 @@ export const taskController = { }, async taskPatch(obj: TTasksPutParams, task_id: number | undefined) { - const { data } = await instance - .put(`task/${task_id}/`, obj) - .then((u) => { - return u; - }); - return data; + try { + const response = await instance.put( + `task/${task_id}/`, + obj + ); + if (response.status === 202) { + const data = response.data as TMessageResponse; + message.success({ content: data.message }); + } + return { data: response?.data as TTask, status: response?.status }; + } catch (error: any) { + return { + data: error?.response?.data.data, + status: error?.response.status, + }; + } }, async addTaskController(obj: TTasksPostParams) { diff --git a/src/API/api.ts b/src/API/api.ts index 6c0d45b..c8b4ab2 100644 --- a/src/API/api.ts +++ b/src/API/api.ts @@ -1,7 +1,7 @@ import axios from "axios"; // const instance = axios.create({ -// baseURL: "http://10.10.10.12:8080/api/v1/", +// baseURL: "http://10.10.10.19:8080/api/v1/", // }); const instance = axios.create({ baseURL: "https://api.tteld.co/api/v1/", diff --git a/src/API/auth/register.ts b/src/API/auth/register.ts index a895e4b..4ee10ac 100644 --- a/src/API/auth/register.ts +++ b/src/API/auth/register.ts @@ -22,24 +22,23 @@ export const RegisterApi = async (value: registerInterface) => { last_name: data?.data.last_name, username: data?.data.username, id: data?.data.id, - timezone: data?.data.timezone, + timezone: data?.data.timezone, role: data?.data.role, }; - + message.success({ + content: "Email sent successfully!", + duration: 3, + }); 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) { - setTimeout(() => { - message.error({ content: ' ', duration: 2 }); - }, 1000); - } + } catch (e) {} }; export const validateUsername = async (value: any) => { - const {status} = await instance.get(`users/check/${value}/`); - return status + const { status } = await instance.get(`users/check/${value}/`); + return status; }; diff --git a/src/App.tsx b/src/App.tsx index ba0fea8..6c250c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -73,7 +73,7 @@ const App: React.FC = () => { setData(data); }); } - }, [admin_id]); + }, []); useEffect(() => { if (data) { @@ -137,8 +137,8 @@ const App: React.FC = () => { ), getItem( - Call Requests, - "call/", + Call Requests, + "calls/", ) ); @@ -209,8 +209,52 @@ const App: React.FC = () => { ); + + // const [isOnline, setIsOnline] = useState(navigator.onLine); + // const reconnectingMessageKey = "reconnectingMessage"; + // const reconnectingMessageContent = "Reconnecting..."; + + // useEffect(() => { + // let reconnectingTimeout: NodeJS.Timeout | null = null; + + // const handleOnlineStatus = () => { + // setIsOnline(true); + // message.success({ content: "Reconnected!" }); + // if (isOnline === false) { + // message.destroy(reconnectingMessageKey); + // } + // if (reconnectingTimeout) { + // clearTimeout(reconnectingTimeout); + // } + // }; + + // const handleOfflineStatus = () => { + // setIsOnline(false); + // if (isOnline !== false) { + // message.loading({ + // content: reconnectingMessageContent, + // key: reconnectingMessageKey, + // duration: 0, + // }); + // reconnectingTimeout = setTimeout(() => { + // message.destroy(reconnectingMessageKey); + // }, 30 * 60 * 1000); // 30 minutes + // } + // }; + + // window.addEventListener("online", handleOnlineStatus); + // window.addEventListener("offline", handleOfflineStatus); + + // return () => { + // window.removeEventListener("online", handleOnlineStatus); + // window.removeEventListener("offline", handleOfflineStatus); + // if (reconnectingTimeout) { + // clearTimeout(reconnectingTimeout); + // } + // }; + // }, [isOnline]); let taskSocket: WebSocket; - const [isLive, setIslive] = useState(true); + const [isLive, setIslive] = useState(false); const [socketData, setSocketData] = useState(); const connect = async () => { try { @@ -219,7 +263,7 @@ const App: React.FC = () => { admin_id ) { // taskSocket = new WebSocket( - // `ws://10.10.10.12:8080/global/?user_id=${admin_id}` + // `ws://10.10.10.19:8080/global/?user_id=${admin_id}` // ); taskSocket = new WebSocket( `wss://api.tteld.co/global/?user_id=${admin_id}` @@ -236,6 +280,7 @@ const App: React.FC = () => { console.error("WebSocket error:", errorEvent); }); taskSocket.addEventListener("close", (event) => { + console.log("WebSocket: clocse"); setIslive(false); }); } @@ -245,6 +290,15 @@ const App: React.FC = () => { useEffect(() => { connect(); }, []); + + // function checkConnection() { + // if (!isLive) { + // connect(); + // } + // } + + // setInterval(checkConnection, 5000); + const [api, contextHolder] = notification.useNotification(); const openNotification = useCallback( (placement: NotificationPlacement, data: TCall) => { @@ -436,7 +490,13 @@ const App: React.FC = () => { } + element={ + + } /> {mainItems && mainItems.map((u) => ( diff --git a/src/Auth/Register.tsx b/src/Auth/Register.tsx index d24064d..5aca587 100644 --- a/src/Auth/Register.tsx +++ b/src/Auth/Register.tsx @@ -38,12 +38,10 @@ const Register: React.FC = () => { } }); } else if (status === 200) { - setTimeout(() => { - message.error({ - content: "This username already exists!", - duration: 2, - }); - }, 1000); + message.error({ + content: "This username already exists!", + duration: 2, + }); } }); }; @@ -72,55 +70,55 @@ const Register: React.FC = () => { >

Sign up

{/* { && ( */} - + - - - - - } - placeholder="First name" - /> - - - - - } - placeholder="Last name" - /> - - - + + + + } + placeholder="First name" + /> + + + + + } + placeholder="Last name" + /> + + + - { )} - - } - placeholder="Username" - /> - + + } + placeholder="Username" + /> + - ({ + validator(_, value) { + if (!value || /[^0-9]+/.test(value)) { + return Promise.resolve(); + } + return Promise.reject( + new Error("Your password can’t be entirely numeric.") + ); }, - { - min: 8, - message: - "Your password must contain at least 8 characters.", + }), + () => ({ + validator(_, value) { + // Список общеупотребимых паролей (пример) + const commonPasswords = common; + if (!value || !commonPasswords.includes(value)) { + return Promise.resolve(); + } + return Promise.reject( + new Error( + "Your password can’t be a commonly used password." + ) + ); }, - () => ({ - validator(_, value) { - if (!value || /[^0-9]+/.test(value)) { - return Promise.resolve(); - } - return Promise.reject( - new Error("Your password can’t be entirely numeric.") - ); - }, - }), - () => ({ - validator(_, value) { - // Список общеупотребимых паролей (пример) - const commonPasswords = common; - if (!value || !commonPasswords.includes(value)) { - return Promise.resolve(); - } - return Promise.reject( - new Error( - "Your password can’t be a commonly used password." - ) - ); - }, - }), - ({ getFieldValue }) => ({ - validator(_, value) { - const personalInfo = getFieldValue("username"); - if (!value || !value.includes(personalInfo)) { - return Promise.resolve(); - } - return Promise.reject( - new Error( - "Your password can’t be too similar to your other personal information." - ) - ); - }, - }), - ]} - hasFeedback - > - } - type="password" - placeholder="Password" - /> - + }), + ({ getFieldValue }) => ({ + validator(_, value) { + const personalInfo = getFieldValue("username"); + if (!value || !value.includes(personalInfo)) { + return Promise.resolve(); + } + return Promise.reject( + new Error( + "Your password can’t be too similar to your other personal information." + ) + ); + }, + }), + ]} + hasFeedback + > + } + type="password" + placeholder="Password" + /> + - ({ + validator(_, value) { + if (!value || getFieldValue("password") === value) { + return Promise.resolve(); + } + return Promise.reject( + new Error( + "The new password that you entered do not match!" + ) + ); }, - ({ getFieldValue }) => ({ - validator(_, value) { - if (!value || getFieldValue("password") === value) { - return Promise.resolve(); - } - return Promise.reject( - new Error( - "The new password that you entered do not match!" - ) - ); - }, - }), - ]} - > - } - type="password" - placeholder="Re-enter Password" - /> - + }), + ]} + > + } + type="password" + placeholder="Re-enter Password" + /> + - - - - - + + + + + {/* )} */} diff --git a/src/Components/Customers/CustomersEdit.tsx b/src/Components/Customers/CustomersEdit.tsx index 790c0a5..45ba639 100644 --- a/src/Components/Customers/CustomersEdit.tsx +++ b/src/Components/Customers/CustomersEdit.tsx @@ -11,6 +11,7 @@ import { Col, Input, Button, + Select, } from "antd"; import { customerController } from "../../API/LayoutApi/customers"; import Notfound from "../../Utils/Notfound"; @@ -20,6 +21,17 @@ import { useState } from "react"; import infoIcon from "../../assets/infoIcon.png"; // @ts-ignore import infoIconActive from "../../assets/infoIconActive.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"; +import { useCompanyData } from "../../Hooks/Companies"; const TabPane = Tabs.TabPane; @@ -29,11 +41,12 @@ type params = { const CustomerEdit = () => { const { id } = useParams(); - const { data, refetch, status } = useCustomerOne(id); + const { data, status } = useCustomerOne(id); let navigate = useNavigate(); const onSubmit = async (value: any) => { + value.company_id = companyVal; await customerController.customerPatch(value, id); - navigate(-1); + window.location.replace("/#/customers/"); }; const ClickDelete = () => { @@ -46,6 +59,28 @@ const CustomerEdit = () => { } }; const [activeTab, setActiveTab] = useState("1"); + + 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 [companyName, setCompanyName] = useState(""); + const [companyVal, setCompanyVal] = useState(); + const { data: companyData } = useCompanyData({ name: companyName }); + return (
@@ -96,9 +131,35 @@ const CustomerEdit = () => { wrapperCol={{ span: "100%" }} label="Company" > - setCompanyName(value)} + onChange={(e: any) => { + setCompanyVal(e); + }} + options={companyData?.map((item) => ({ + label: ( +
+ {item?.source && ( + + )}{" "} + {item?.name} +
+ ), + value: item?.id, + }))} + filterOption={false} + autoClearSearchValue={false} + allowClear + // value={companyName} /> diff --git a/src/Components/Tasks/ErrorUncompletedTasksModal.tsx b/src/Components/Tasks/ErrorUncompletedTasksModal.tsx new file mode 100644 index 0000000..1080083 --- /dev/null +++ b/src/Components/Tasks/ErrorUncompletedTasksModal.tsx @@ -0,0 +1,45 @@ +import { TTask } from "../../types/Tasks/TTasks"; +import { Modal } from "antd"; +import TaskTable from "./TaskTable"; + +const ErrorUncompletedTasksModal = ({ + errorModal, + setErrorModal, + uncomletedData, +}: { + uncomletedData: TTask[] | undefined; + errorModal: boolean; + setErrorModal: any; +}) => { + const handleCancel = () => { + setErrorModal(!errorModal); + }; + return ( + +
+

+ You have unfinished tasks. You cannot assign another one until they + are completed. +

+ +
+
+ ); +}; + +export default ErrorUncompletedTasksModal; diff --git a/src/Components/Tasks/TaskModal.tsx b/src/Components/Tasks/TaskModal.tsx index dab6880..4ce4d87 100644 --- a/src/Components/Tasks/TaskModal.tsx +++ b/src/Components/Tasks/TaskModal.tsx @@ -16,7 +16,7 @@ import { useEffect, useState } from "react"; import { taskController } from "../../API/LayoutApi/tasks"; import { useTeamData } from "../../Hooks/Teams"; import { TTeam } from "../../types/Team/TTeam"; -import { EditOutlined, CaretRightOutlined } from "@ant-design/icons"; +import { EditOutlined } from "@ant-design/icons"; import { TSocket } from "../../types/common/TSocket"; // @ts-ignore import closeIcon from "../../assets/closeIcon.png"; @@ -201,9 +201,9 @@ const TaskModal = ({ setModalOpen(!modalOpen); }; - const nextStatus = (status : string) => { - console.log(); - } + // const nextStatus = (status : string) => { + // console.log(); + // } useEffect(() => { if (socketData && socketData.task) { @@ -243,7 +243,7 @@ const TaskModal = ({ {/* */}
diff --git a/src/Components/Tasks/TaskTable.tsx b/src/Components/Tasks/TaskTable.tsx index 71f899f..8241a30 100644 --- a/src/Components/Tasks/TaskTable.tsx +++ b/src/Components/Tasks/TaskTable.tsx @@ -25,12 +25,16 @@ const TaskTable = ({ data, isLoading, showTaskModal, + showErrorModal, + setErrorModal, }: { data: { characters: TTask[] | undefined; }; showTaskModal: any; + showErrorModal: any; isLoading: boolean; + setErrorModal: React.Dispatch>; }) => { const moment = require("moment"); const statusClick = (record: any) => { @@ -42,7 +46,13 @@ const TaskTable = ({ const value = { status: "Checking", }; - taskController.taskPatch(value, record.id); + taskController + .taskPatch(value, record?.id) + .then((response: { data: TTask; status: number }) => { + if (response?.status == 403) { + showErrorModal(response); + } + }); }, }); } @@ -54,7 +64,9 @@ const TaskTable = ({ const value = { status: "Done", }; - taskController.taskPatch(value, record.id); + taskController.taskPatch(value, record.id).then(() => { + setErrorModal(false); + }); }, }); } @@ -138,7 +150,7 @@ const TaskTable = ({ justifyContent: "space-around", }} > - {record.via_telegram && ( + {record?.via_telegram && ( @@ -162,7 +174,7 @@ const TaskTable = ({ width: isMobile ? "1%" : "5%", fixed: isMobile ? "left" : false, key: "2", - render: (text: any, record: TTask) => ( + render: (text?: any, record?: TTask) => (
( + render: (item?: { title?: string; id: number }, record?: TTask) => ( - {item.title} + {item?.title} ), }, @@ -234,7 +246,7 @@ const TaskTable = ({ ellipsis: { showTitle: false, }, - render: (status: string) => ( + render: (status?: string) => ( {status === "Done" &&

Done

} {status === "Checking" && ( diff --git a/src/Components/Tasks/Tasks.tsx b/src/Components/Tasks/Tasks.tsx index 518c3d4..fb822cb 100644 --- a/src/Components/Tasks/Tasks.tsx +++ b/src/Components/Tasks/Tasks.tsx @@ -16,9 +16,18 @@ import IconSearch from "../../assets/searchIcon.png"; import TaskModal from "./TaskModal"; import TaskUploadModal from "./TaskUploadModal"; import { TSocket } from "../../types/common/TSocket"; +import ErrorUncompletedTasksModal from "./ErrorUncompletedTasksModal"; const { Option } = Select; -const Task = ({ socketData }: { socketData: TSocket | undefined }) => { +const Task = ({ + socketData, + connect, + isLive, +}: { + socketData: TSocket | undefined; + connect: () => Promise; + isLive: boolean; +}) => { const [open, setOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [characters, setCharacters] = useState(); @@ -27,6 +36,8 @@ const Task = ({ socketData }: { socketData: TSocket | undefined }) => { const [status, setStatus] = useState(); const [page, setPage] = useState(1); const [uploadOpen, setUploadOpen] = useState(false); + const [errorModal, setErrorModal] = useState(false); + const [uncomletedData, setUncomletedData] = useState(); useEffect(() => { if ( @@ -34,8 +45,8 @@ const Task = ({ socketData }: { socketData: TSocket | undefined }) => { socketData.task && ((role !== "Checker" && (!team || team.includes(socketData?.task?.assigned_to?.id))) || - role === "Checker") && - (!status || status.includes(socketData?.task?.status)) + role === "Checker") + // &&(!status || status.includes(socketData?.task?.status)) ) { setCharacters((prev: any) => { if (prev && prev?.length >= 15) { @@ -139,6 +150,11 @@ const Task = ({ socketData }: { socketData: TSocket | undefined }) => { setRecordTask(e); setModalOpen(true); }; + const showErrorModal = (e: { data: TTask[]; status: number }) => { + setUncomletedData(e?.data); + + setErrorModal(true); + }; const Next = () => { const a = Number(page) + 1; @@ -164,6 +180,7 @@ const Task = ({ socketData }: { socketData: TSocket | undefined }) => { }, 1000); }; const theme = localStorage.getItem("theme") === "true" ? true : false; + return (
{open && } @@ -185,6 +202,13 @@ const Task = ({ socketData }: { socketData: TSocket | undefined }) => { setModalOpen={setModalOpen} /> )} + {errorModal && ( + + )}

Tasks

@@ -204,7 +228,9 @@ const Task = ({ socketData }: { socketData: TSocket | undefined }) => { className={`btn-refresh-${theme && "dark"} d-flex`} onClick={() => { refetch(); - // connect(); + if (!isLive) { + connect(); + } }} > { data={{ characters }} isLoading={isLoading} showTaskModal={showTaskModal} + showErrorModal={showErrorModal} + setErrorModal={setErrorModal} /> diff --git a/src/types/Tasks/TTasks.ts b/src/types/Tasks/TTasks.ts index 8bfddb2..1f32b70 100644 --- a/src/types/Tasks/TTasks.ts +++ b/src/types/Tasks/TTasks.ts @@ -1,12 +1,5 @@ import { TUser } from "../User/TUser"; -type data = { - id: number; - name?: string; - title?: string; - username?: string; -}; - export type TTask = { id: number; note: string;