Files

636 lines
29 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useContext, useState, useMemo } from "react";
import { TaxiOrder_type, TaxiOrder_initial } from '../types/taxiOrderType';
import { addDays, format } from 'date-fns';
import { getCsrfToken } from "../../../../../resources/js/services/getCsrfService";
import { Button, TextInput, Select, Option } from '@SharePoint/rencredit_uikit';
import FormValidErr, { FormValidErrObject } from "../../../../../resources/js/components/formValidErr/FormValidErr";
import { PopupContext } from "../../../../../resources/js/contexts/PopupContext";
import { EntityHistoryProps } from "../../../../../resources/js/components/entityHistory/EntityHistory";
import { PreloaderContext } from "../../../../../resources/js/contexts/PreloaderContext";
//Гаврилов типы для пропсов?
// export default function TaxiOrder( {rqstId, setPreloaderProp}: {rqstId: number | undefined, setPreloaderProp: CallableFunction} )
export default function TaxiOrder( {rqstId}: {rqstId: number | undefined} )
{
let newDate = new Date('2024-06-05');
newDate.setHours(0, 0, 0);
let fakeObjArr: EntityHistoryProps[] = [
{
changeAction: 'insert',
changeAuthor: 'login',
changeDate: newDate,
changeDetails: [
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле2',
propValue: 'значение2'
}
]
},
{
changeAction: 'insert',
changeAuthor: 'login',
changeDate: new Date('2024-06-05'),
changeDetails: [
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле2',
propValue: 'значение2'
}
]
},
{
changeAction: 'insert',
changeAuthor: 'login',
changeDate: new Date('2024-06-05'),
changeDetails: [
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле',
propValue: 'значение'
},
{
propName: 'поле2',
propValue: 'значение2'
}
]
},
{
changeAction: 'update',
changeAuthor: 'login2',
changeDate: new Date('2025-06-05'),
changeDetails: [
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле5',
propValue: 'значение6'
}
]
},
{
changeAction: 'update',
changeAuthor: 'login2',
changeDate: new Date('2025-06-05'),
changeDetails: [
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле3',
propValue: 'значение4'
},
{
propName: 'поле5',
propValue: 'значение6'
}
]
}
];
const preloaderContext = useContext(PreloaderContext);
//ГАВРИЛОВ ПОСТОЯННЫЙ ПЕРЕРЕНДЕРИНГ СПРОСИТЬ У САШИ. ЭЛЕМЕНТ НЕ МИГАЕТ В КОНСОЛИ, НО ПИШЕТ ПОСТОЯННО ПЕРЕРЕНДЕР
console.log('🔁 TaxiForm перерендерился!');
interface UserData {
emp_id: number,
emp_address: string,
emp_lastname: string,
emp_name: string,
emp_surname: string,
emp_login: string | null
emp_phone: string,
//Все остальные поля объекта могут быть пустыми
[key: string]: unknown,
full_name: string,
};
const UserData_initial: UserData = {
emp_id: 0,
emp_address: '',
emp_lastname: '',
emp_name: '',
emp_surname: '',
emp_login: '',
emp_phone: '',
full_name: '',
// _token: getCsrfToken()
};
//Проверка загрузки необходимых для начала работ данных. После успешного получения всех данных происходит рендеринг
const [checkLoad, setCheckLoad] = useState(
{
ccEmp: false,
orderData: false,
checkEditOrderUserData: false,
getTimePeriods: false,
checkOfficeAddress: false,
}
);
const todayDate = new Date();
//Данные по всем пользователям
const [ccEmp, setCcEmp] = useState<UserData[]>( [UserData_initial] );
//Данные по редактируемому запросу (если вызвано редактирование)
const [orderData, setOrderData] = useState<TaxiOrder_type>(TaxiOrder_initial);
//Данные по пользователю, чей запрос редактируется (если вызвано редактирование запроса)
const [editOrderUserData, setEditOrderUserData] = useState<UserData>(UserData_initial);
//Данные по временным промежуткам
const [orderTimePeriods, setOrderTimePeriods] = useState< {time_period: string, is_morning_time: number}[] >(
[ {time_period: '', is_morning_time: 0} ]
);
//Массив доступных для заказа такси дат для их дальнейшего преобразования в теги option селекта
const [dateOrderArr, setDateOrderArr] = useState<string[]>(
[
format(todayDate, "yyyy-MM-dd"),
format(addDays(todayDate, 1), "yyyy-MM-dd")
]
);
const popupContext = useContext(PopupContext);
useEffect ( () => {
setFormCreateVisible(true);
setFormCreateValidObj([{fieldName: 'testField', fieldErrors: ['errOne']}]);
console.log(popupContext)
// console.log()
// setTimeout(() => {
// popupContext.addPopupArrTest([
// {message: 'для информации', timeOut: true, type: 'attention'}
// ])
// }, 1000);
// setTimeout(() => {
// setPopupArrProp([
// {message: 'для информации', timeOut: true, type: 'attention'},
// ])
// }, 1000);
// setTimeout(() => {
// setPopupArrProp([
// {message: 'для информации', timeOut: false, type: 'info'},
// ])
// }, 2000);
// setTimeout(() => {
// setPopupArrProp([
// {message: 'для информации', timeOut: false, type: 'info'},
// ])
// }, 3000);
// setPopupArrProp([
// {message: 'для информации', timeOut: false, type: 'info'},
// {message: 'успешно', timeOut: true, type: 'success'},
// {message: 'ошибка', timeOut: true, type: 'error'},
// {message: 'обратить внимание', timeOut: true, type: 'attention'}
// ])
}, [])
const [formCreateVisible, setFormCreateVisible] = useState<boolean>(false);
const [formCreateValidErrObj, setFormCreateValidObj] = useState<FormValidErrObject[]>([{fieldName: null, fieldErrors: []}])
//ГАВРИЛОВ ДОБАВИТЬ ВСПЛЫВАЮЩЕЕ ОКНО ЕСЛИ ПРОИСХОДИТ НЕСООТВЕТСТВИЕ ВРЕМЕНИ И ДАТЫ ЗАКАЗА ТАКСИ? А НЕ ПРОСТО УДАЛЯТЬ ЗНАЧЕНИЕ ИЗ СОСЕДНЕГО ПОЛЯ?
//Адреса офисов
const [officeAddressInfo, setOfficeAddressInfo] = useState< {place: string, address: string}[] >(
[ {place: '', address: ''} ]
);
//Адрес офиса, где работает сотрудник. Определяется на этапе выбора логина
const [empOfficeAddress, setEmpOfficeAddress] = useState<string | null>(null);
//const [orderTime, setOrderTime] = useState<string|null>(null);
//const [orderDate, setOrderDate] = useState<string|null>(null);
// const [orderAddressFrom, setOrderAddressFrom] = useState<string|null>(null);
// const [orderAddressTo, setOrderAddressTo] = useState<string|null>(null);
function gotoHome()
{
document.location.href = '/public/taxi/home';
}
//Гаврилов. Не передаешь CSRF токен
//Отправка заказа на такси
function sendTaxiOrder ()
{
console.log(orderData)
console.log(editOrderUserData)
//return
// setPreloaderProp(true, 'создаем заявку')
preloaderContext.setPreloaderVisible(true);
preloaderContext.setPreloaderText('создаем заявку');
let popupType;
fetch('/public/api/taxi/' + (rqstId ? `editOrder/${rqstId}` : 'createRqst'), {
credentials: 'include',
method: (rqstId ? "PATCH" : "POST"),
body: JSON.stringify(orderData),
headers: {
'content-type': 'application/json',
'Accept': 'application/json'
}
}).then(result => {
preloaderContext.setPreloaderVisible(false);
// setPreloaderProp(false)
result.json().then(jsonResult => {
console.log(jsonResult)
if (!result.ok) {
//ГАВРИЛОВ здесь обработка ошибок валидации формы
if (result.status == 422) {
// setPopupArrProp(
// [
// //ГАВРИЛОВ обработка ошибки
// {message: 'Произошла ошибка! Заявка не создана', type: 'error'},
// ]
// )
} else {
// setPopupArrProp(
// [
// //ГАВРИЛОВ обработка ошибки
// {message: jsonResult.result_msg, type: 'error'},
// ]
// )
}
popupType = 'error'
} else {
popupType = 'success'
}
popupContext.addPopupArrTest(
[
{message: jsonResult.message, type: popupType},
]
)
})
})
}
//Функция, собирающая нужный массив для формирования из него списка option для select
function transformToOptionsFunc <T,>(
dataArr: T[],
dataKey?: keyof T
): Option<string>[] {
let optionArr = dataArr.map(item => {
//Без проверки ниже, Typescript ругается на условный атрибут dataKey, который может отсутствовать, но при этом укаан как относящийся к Дженерику
const value = dataKey !== undefined
? item[dataKey]
: item;
return {
caption: String(value),
value: String(value),
id: String(value)
};
});
return optionArr;
}
/**
* Метод проверки соответствует ли дата и время заказа логике. Наприме, если дата заказа сегодняшняя - нельзя выбрать утренний промежуток времени
* @param orderTime время заказа такси
* @param orderDate дата заказа такси
* @returns результат проверки соответствия даты и времени заказа
*/
function checkTimeAndDate (orderTime: string|null, orderDate: string|null): boolean
{
// console.log(orderTime)
// console.log(!orderTime)
// console.log(orderDate)
// console.log(!orderDate)
if (!orderTime || !orderDate) {
//console.log(1)
return true;
}
let isMorningTimeCheck = orderTimePeriods.filter( e => e.time_period == orderTime )[0].is_morning_time;
// console.log(isMorningTimeCheck)
//Если выбранная дата заказа больше текущей - заказ на завтра, можно выбирать любую дату
if (!(new Date(orderDate) > todayDate)) {
//console.log(2)
return !isMorningTimeCheck;
}
return true;
}
// const ccEmpList: Option<string>[] = useMemo(() => {
// return ccEmp.map(empData => {
// return {
// caption: empData.emp_login,
// value: empData.emp_login,
// id: empData.emp_login
// }
// })
// }, [ccEmp]);
// console.log(ccEmpList)
const ccEmpList = useMemo( () => {
return transformToOptionsFunc(ccEmp, 'emp_login');
}, [ccEmp]);
const orderDateList = useMemo( () => {
return transformToOptionsFunc(dateOrderArr);
}, [dateOrderArr]);
const orderTimeList = useMemo( () => {
return transformToOptionsFunc(orderTimePeriods, 'time_period')
}, [orderTimePeriods]);
//Загрузка данных для старта работ
useEffect ( () => {
//Получаем информацию по сотруднику при загрузке страницы
fetch('/public/api/taxi/getEmpInfo', {
credentials: 'include',
method: 'GET',
// headers: new Headers({
// 'Authorization': `Bearer ${sanctumToken()}`
// })
}).then(empInfo_resp => empInfo_resp.json().then(empInfo_json => {
console.log(empInfo_json)
console.log(editOrderUserData)
setCcEmp(empInfo_json);
setCheckLoad(loadObj => ( {...loadObj, ccEmp: true} ));
}));
//Получаем доступные временные промежутки для заказа такси
fetch('/public/api/taxi/getTimePeriods', {
method: 'GET',
credentials: 'include',
// headers: new Headers({
// 'Authorization': `Bearer ${sanctumToken()}`
// })
}).then(timePeriods_resp => timePeriods_resp.json().then(timePeriods_json => {
setOrderTimePeriods(timePeriods_json);
setCheckLoad(loadObj => ( {...loadObj, getTimePeriods: true} ));
}));
//ГАВРИЛОВ. ВЕЗДЕ В FETCH ЗАПРОСАХ ПЕРЕХВАТЫВАЙ ОШИБКИ И ВЫВОДИ СООБЩЕНИЯ ОБ ОШИБКЕ. НАПРИМЕМР С 401. В ЭТОМ СЛУЧАЕ НУЖНО ПЕРЕБРАСЫВАТЬ ПОЛЬЗОВАТЕЛЯ НА СТРАНИЦУ АУТЕНТИФИКАЦИИ
//Получае адреса всех офисов, куда могут заказываться такси?
fetch('/public/api/taxi/getOfficeAddress', {
method: 'GET',
credentials: 'include',
}).then(officeAddress_resp => {officeAddress_resp.json().then(officeAddress_json => {
setOfficeAddressInfo(officeAddress_json);
setCheckLoad(loadObj => ( {...loadObj, checkOfficeAddress: true} ));
})});
//Если в пропсах передается rqstId, происходит редактирование заявки, а не создание новой - получаем данные по редактируемой заявке на такси
if (rqstId) {
fetch(`/public/api/taxi/getOrderById/${rqstId}`, {
method: 'GET',
credentials: 'include',
// headers: new Headers({
// 'Authorization': `Bearer ${sanctumToken()}`
// })
}).then(orderData_resp => orderData_resp.json().then( orderData_json => {
console.log(orderData_json)
setOrderData(orderData_json);
setCheckLoad(loadObj => ({...loadObj, orderData: true}));
}));
}
}, [])
//гаврилов. Нужно ввести объект с данными по запросу и туда класть только нужные данные , а не все параметры пользователя. и тогда не нужно нигде проверять rqstId. Внизу в методе класть в этот объект все нужные параметры
useEffect ( () => {
//Гаврилов. Причем тут проверка логина? Зачем она?
if (rqstId && orderData.emp_login) {
//Если передан id запроса на редактирование, устанавливаем значение с данными пользователя редактируемого запроса
fetch(`/public/api/taxi/getEmpInfo/${orderData.emp_login}`).then(userData_resp => userData_resp.json().then(userData_json => {
//гаврилов. Вот здесь нужно класть данные в orderData, а также заполнять адрес из и адрес куда
setEditOrderUserData(userData_json[0]);
//setOrderData(prevData => ({...prevData, userData_json[0]}));
//setEmpAddress(editOrderUserData['emp_address']);
setCheckLoad(loadObj => ( {...loadObj, checkEditOrderUserData: true} ));
//ГАВРИЛОВ. вынеси в отедльную функцию определение адреса офиса?
setEmpOfficeAddress(officeAddressInfo.filter(addressInfo => addressInfo.place == userData_json[0].emp_area)[0].address);
}));
}
}, [orderData.emp_login])
//Итоговая проверка все ли необходимые данные для рендеринга получены
const isReady = rqstId ?
checkLoad.ccEmp && checkLoad.checkEditOrderUserData && checkLoad.orderData && checkLoad.checkOfficeAddress && checkLoad.getTimePeriods
: checkLoad.ccEmp;
//Регулировка видимости прелоадера в зависимости от того все ли данные для рендеринга получены или нет
useEffect ( () => {
//isReady ? setPreloaderProp(false) : setPreloaderProp(true);
isReady ? preloaderContext.setPreloaderVisible(false) : preloaderContext.setPreloaderVisible(true);
}, [isReady])
if (!isReady) {
return;
}
return (
<div id = "taxi__order-create">
{/* <EntityHistory
entityId={1}
entityProps= {fakeObjArr}
/> */}
<div className = "order-create__btn-block">
<Button
type = 'button'
onClick = {gotoHome}
text = 'Домой'
ui = 'secondary'
/>
</div>
<h2>{rqstId ? 'Редактирование' : 'Создание'} заявки на такси</h2>
<div className="form-container form-container--medium-size form-containter--left-pos">
{/* <FormValidErr
visible = {formCreateVisible}
validErrorsObj = {formCreateValidErrObj}
/> */}
<form>
{/* ГАВРИЛОВ как защищаешься от csrf если комментируешь поле ниже? */}
{/* <input
type="hidden"
name="_token"
value={getCsrfToken()}
/> */}
<div className="form__field-block">
<Select
//ГАВРИЛОВ. ПЕРЕПИШИ НА ОПРЕДЕЛЕНИЕ НАЗВАНИЯ ПОЛЯ, СОГЛАСНО TaxiOrder_fieldName ИЗ TAXIORDERTYPE
labelText = 'Дата заказа'
options = {orderDateList}
//Гаврилов. Может где-то сверху нужно класть в объект с данными формы данные из orderData (если она есть) .тогда не нужно проверять props.rqstId ?
value = {orderData.taxi_date ? orderDateList.find(dateData => dateData.value === orderData.taxi_date) : null}
size = 'm'
onChange = {
(_, sel:{caption:string, id:string, value:string} ) => {
let selOrderDate: string|null = (sel ? sel.value : null);
//setEditOrderUserData(prevData => ({...prevData, taxi_date: selOrderDate}));
setOrderData(prevData => ( {...prevData, taxi_date: selOrderDate} ));
//Если проверка соответствия времени и даты заказа не выполняется, сбрасываем время.
// if (!checkTimeAndDate(orderData.taxi_time, selOrderDate)) {
popupContext.addPopupArrTest( [{message: 'Несоответствие времени и даты', type: 'error'}] )
// setOrderData(prevData => ( {...prevData, taxi_time: null} ));
// }
}}
/>
</div>
<div className="form__field-block">
<Select
labelText = "Время заказа"
options = {orderTimeList}
value = {orderData.taxi_time ? orderTimeList.find(dateData => dateData.value === orderData.taxi_time) : null}
size = 'm'
onChange = {(_, sel:{caption:string, id:string, value:string}) => {
let selOrderTime = (sel ? sel.value : null);
setOrderData(prevData => ( {...prevData, taxi_time: selOrderTime} ));
//Проверяем относится ли выбранный промежуток времени заказа к утру или нет. Если выбран утренний промежуток, дата откуда - адрес сотрудника, адрес куда - офис, если выбран вечерний промежуток - наоборот
if (orderTimePeriods.filter( e => selOrderTime == e.time_period )[0].is_morning_time == 1) {
console.log(1)
setOrderData(prevData => ( {...prevData, taxi_address_from: editOrderUserData.emp_address, taxi_address_to: empOfficeAddress} ));
} else {
console.log(2)
console.log(empOfficeAddress)
setOrderData(prevData => ( {...prevData, taxi_address_from: empOfficeAddress, taxi_address_to: editOrderUserData.emp_address} ));
}
//Если проверка соответствия и времени заказа не выполняется, сбрасываем дату
if (!checkTimeAndDate(selOrderTime, orderData.taxi_date)) {
popupContext.addPopupArrTest(
[{message: 'Несоответствие времени и даты', type: 'error'}]
)
setOrderData(prevData => ({...prevData, taxi_date: null}));
}
}}
/>
</div>
<div className="form__field-block">
<Select
labelText = 'Логин сотрудника'
options = {ccEmpList}
value = {orderData.emp_login}
size = 'm'
//В компоненте Select значение "выбранного" option помещается во второй аргумент onChange
onChange = { (_, sel:{caption:string, id:string, value:string}) => {
if (sel) {
let empData = ccEmp.filter(empData => {return empData.emp_login == sel.value})[0];
//При создании нового запроса, на этом действии время и дата могут быть уже записаны в объект editOrderUserData, но при выборе нового логина, если передать в сеттер setEditOrderUserData только объект empData, он перезапишет значения времени и даты, поэтому конкатенируем объект, отдавая приоритет при замене дублирующих значений empData (правый объект перезапишет левый)
setEditOrderUserData( {...editOrderUserData, ...empData} );
setOrderData(prevData => ({...prevData, emp_login: sel.value, emp_phone: empData.emp_phone}));
//Гаврилов. Если площадка РКЦ - выводить ошибку? Так как у этой площадки нет адреса офиса
//ГАВРИЛОВ. ВЫНЕСИ ОПРЕДЕЛЕНИЕ АДРЕСА В ОТДЕЛЬНУЮ ФУНКЦИЮ?
let officeAddress: string | null = empData.emp_area == 'РКЦ' ? null : officeAddressInfo.filter(addressInfo => addressInfo.place == empData.emp_area)[0].address;
setEmpOfficeAddress(officeAddress);
setOrderData(prevData => {
let setAddressTo: string|null, setAddressFrom: string|null;
//Если время выставлено в форме
if (orderData.taxi_time) {
//Проверяем относится ли выбранный промежуток времени заказа к утру или нет. Если выбран утренний промежуток, дата откуда - адрес сотрудника, адрес куда - офис, если выбран вечерний промежуток - наоборот
if (orderTimePeriods.filter( e => orderData.taxi_time == e.time_period )[0].is_morning_time == 1) {
setAddressTo = officeAddress;
setAddressFrom = empData.emp_address;
} else {
setAddressTo = empData.emp_address;
setAddressFrom = officeAddress;
}
//Если время заказа не выбрано, сбрасываем дату откуда и дату куда
} else {
setAddressTo = null;
setAddressFrom = null;
}
return {...prevData, taxi_address_from: setAddressFrom, taxi_address_to: setAddressTo};
});
} else {
setOrderData(prevData => ({...prevData, emp_login: null, emp_phone: null}));
setEditOrderUserData(UserData_initial);
setEmpOfficeAddress(null);
setOrderData(prevData => ({...prevData, taxi_address_from: null, taxi_address_to: null}));
}
} }
//При редактировании существующего запроса нельзя менять выбранный логин сотрудника для заказа
disabled = {rqstId ? true : false}
/>
</div>
<div className="form__field-block">
<TextInput
labelText = 'ФИО'
value = {editOrderUserData.full_name}
disabled = {true}
/>
</div>
<div className="form__field-block">
<TextInput
labelText = 'Мобильный номер телефона'
value = {orderData.emp_phone}
onChange = {(e: string) => {
setOrderData(prevData => ({...prevData, emp_phone: e}));
}}
/>
</div>
<div className="form__field-block">
<TextInput
labelText = 'Адрес (откуда)'
value = {
//Если в пропсы передается id запроса, проставляем значение из параметров имеющегося запроса. В противном случае определяем является ли выбранный временной промежуток утренним. Если да - прописываем, что едем из адреса сотрудника, в противном случае - из офиса
//Гаврилов. Будет ли при редактировании уже созданного запроса, работать смена адресов при изменении времени запроса? Или всегда будет искаться props.rqstId и логика будет исходить из этого?
orderData.taxi_address_from
}
onChange = { (e: string) => {
setOrderData(prevData => ({...prevData, taxi_address_from: e}));
}}
/>
</div>
<div className = "form__field-block">
<TextInput
labelText = 'Адрес (куда)'
value = {orderData.taxi_address_to}
onChange = {(e: string) => {
setOrderData(prevData => ({...prevData, taxi_address_to: e}));
}}
/>
</div>
<div className = "order-create__btn-block">
<Button
type = 'button'
onClick = {sendTaxiOrder}
text = 'Отправить'
/>
</div>
</form>
</div>
</div>
)
}