добавляю скрипты по части фронта

This commit is contained in:
vasya
2026-04-05 19:32:39 +03:00
parent e927910fda
commit 48409575a9
14 changed files with 1239 additions and 0 deletions
@@ -0,0 +1,635 @@
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>
)
}