diff --git a/package.json b/package.json index 3fe188b..b9ef84e 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@ant-design/icons": "^5.6.1", "@tailwindcss/vite": "^4.1.11", "@reduxjs/toolkit": "^2.8.2", + "dayjs": "^1.11.13", "antd": "^5.26.4", "clsx": "^2.1.1", "lucide-react": "^0.526.0", diff --git a/src/app/routes/navRoutes.tsx b/src/app/routes/navRoutes.tsx index e407a6e..464f2b6 100644 --- a/src/app/routes/navRoutes.tsx +++ b/src/app/routes/navRoutes.tsx @@ -12,7 +12,7 @@ import { PrivacyPolicy } from '@pages/privacyPolicy'; import { SignIn } from '@pages/signIn'; import { SignUp } from '@pages/signUp'; import { TicketsPage } from '@pages/ticketsPage'; -import { PassengersPage } from '@pages/passengersPage'; +import { TablePassengersPage } from '@pages/admin/tablePassengers'; import { TableSeatsPage } from '@pages/admin/tableSeats'; @@ -56,7 +56,7 @@ export const navRoutes: RoutesProps = [ { path: ROUTES.TABLE_BOARDING, element: }, { path: ROUTES.NOT_FOUND, element: }, { path: ROUTES.TABLE_TIMEZONES, element: }, - { path: ROUTES.TABLE_PASSENGERS, element: }, + { path: ROUTES.TABLE_PASSENGERS, element: }, { path: ROUTES.TABLE_TICKETS, element: }, { path: ROUTES.TABLE_BOOKINGS, element: }, { path: ROUTES.TABLE_SEATS, element: }, diff --git a/src/features/tableAircraft/ui/TableAircraft.tsx b/src/features/tableAircraft/ui/TableAircraft.tsx index 5610d8d..e53551b 100644 --- a/src/features/tableAircraft/ui/TableAircraft.tsx +++ b/src/features/tableAircraft/ui/TableAircraft.tsx @@ -7,13 +7,15 @@ import { Input, Space, Button, Spin, type TablePaginationConfig } from 'antd'; import { type FC, useCallback, useEffect, useState } from 'react'; import { MoreHorizontal, Pencil, X } from 'lucide-react'; import { EditOutlined, SaveOutlined, CloseOutlined, HolderOutlined } from '@ant-design/icons'; + import { showNotification } from '@shared/lib/notification'; import { ContextMenu, useContextMenu, } from '@shared/ui/contexMenu'; import styles from './TableAircraft.module.scss'; -import { useUpdateAircraftMutation, useGetAircraftListQuery } from '@features/tableAircraft/models/aircraftApi.ts'; + +import { useUpdateAircraftMutation, useGetAircraftListQuery } from '@features/tableAircraft/models/aircraftApi' import { AddButton } from '@shared/ui/AddButton'; const validateAircraft = (data: Partial): ValidationResult => { @@ -318,4 +320,4 @@ export const TableAircraft: FC = () => { )} ) : null; -}; \ No newline at end of file +}; diff --git a/src/features/tableDestination/ui/TableDestination.tsx b/src/features/tableDestination/ui/TableDestination.tsx index b8cad99..114d25a 100644 --- a/src/features/tableDestination/ui/TableDestination.tsx +++ b/src/features/tableDestination/ui/TableDestination.tsx @@ -2,6 +2,7 @@ import { HolderOutlined, EditOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons'; import { TableHeader } from '@entities/tableHeader'; +import { AddButton } from '@shared/ui/AddButton'; import { DEFAULT_PAGE_LIMIT } from '@shared/config/pagination'; import type { IColumnTableAntd } from '@shared/types'; import type { IContentDestinationTable } from '@shared/types'; diff --git a/src/features/tablePassengers/constants/countries.ts b/src/features/tablePassengers/constants/countries.ts new file mode 100644 index 0000000..1402bb1 --- /dev/null +++ b/src/features/tablePassengers/constants/countries.ts @@ -0,0 +1,191 @@ +export const COUNTRIES_LIST = [ + { value: 'Россия', label: 'Россия' }, + { value: 'Австралия', label: 'Австралия' }, + { value: 'Австрия', label: 'Австрия' }, + { value: 'Азербайджан', label: 'Азербайджан' }, + { value: 'Албания', label: 'Албания' }, + { value: 'Алжир', label: 'Алжир' }, + { value: 'Ангола', label: 'Ангола' }, + { value: 'Андорра', label: 'Андорра' }, + { value: 'Аргентина', label: 'Аргентина' }, + { value: 'Армения', label: 'Армения' }, + { value: 'Афганистан', label: 'Афганистан' }, + { value: 'Багамы', label: 'Багамы' }, + { value: 'Бангладеш', label: 'Бангладеш' }, + { value: 'Барбадос', label: 'Барбадос' }, + { value: 'Бахрейн', label: 'Бахрейн' }, + { value: 'Беларусь', label: 'Беларусь' }, + { value: 'Белиз', label: 'Белиз' }, + { value: 'Бельгия', label: 'Бельгия' }, + { value: 'Бенин', label: 'Бенин' }, + { value: 'Болгария', label: 'Болгария' }, + { value: 'Боливия', label: 'Боливия' }, + { value: 'Босния и Герцеговина', label: 'Босния и Герцеговина' }, + { value: 'Ботсвана', label: 'Ботсвана' }, + { value: 'Бразилия', label: 'Бразилия' }, + { value: 'Бруней', label: 'Бруней' }, + { value: 'Буркина-Фасо', label: 'Буркина-Фасо' }, + { value: 'Бурунди', label: 'Бурунди' }, + { value: 'Бутан', label: 'Бутан' }, + { value: 'Вануату', label: 'Вануату' }, + { value: 'Великобритания', label: 'Великобритания' }, + { value: 'Венгрия', label: 'Венгрия' }, + { value: 'Венесуэла', label: 'Венесуэла' }, + { value: 'Восточный Тимор', label: 'Восточный Тимор' }, + { value: 'Вьетнам', label: 'Вьетнам' }, + { value: 'Габон', label: 'Габон' }, + { value: 'Гаити', label: 'Гаити' }, + { value: 'Гайана', label: 'Гайана' }, + { value: 'Гамбия', label: 'Гамбия' }, + { value: 'Гана', label: 'Гана' }, + { value: 'Гватемала', label: 'Гватемала' }, + { value: 'Гвинея', label: 'Гвинея' }, + { value: 'Гвинея-Бисау', label: 'Гвинея-Бисау' }, + { value: 'Германия', label: 'Германия' }, + { value: 'Гондурас', label: 'Гондурас' }, + { value: 'Гренада', label: 'Гренада' }, + { value: 'Греция', label: 'Греция' }, + { value: 'Грузия', label: 'Грузия' }, + { value: 'Дания', label: 'Дания' }, + { value: 'Джибути', label: 'Джибути' }, + { value: 'Доминика', label: 'Доминика' }, + { value: 'Доминикана', label: 'Доминикана' }, + { value: 'Египет', label: 'Египет' }, + { value: 'Замбия', label: 'Замбия' }, + { value: 'Зимбабве', label: 'Зимбабве' }, + { value: 'Израиль', label: 'Израиль' }, + { value: 'Индия', label: 'Индия' }, + { value: 'Индонезия', label: 'Индонезия' }, + { value: 'Иордания', label: 'Иордания' }, + { value: 'Ирак', label: 'Ирак' }, + { value: 'Иран', label: 'Иран' }, + { value: 'Ирландия', label: 'Ирландия' }, + { value: 'Исландия', label: 'Исландия' }, + { value: 'Испания', label: 'Испания' }, + { value: 'Италия', label: 'Италия' }, + { value: 'Йемен', label: 'Йемен' }, + { value: 'Кабо-Верде', label: 'Кабо-Верде' }, + { value: 'Казахстан', label: 'Казахстан' }, + { value: 'Камбоджа', label: 'Камбоджа' }, + { value: 'Камерун', label: 'Камерун' }, + { value: 'Канада', label: 'Канада' }, + { value: 'Катар', label: 'Катар' }, + { value: 'Кения', label: 'Кения' }, + { value: 'Кипр', label: 'Кипр' }, + { value: 'Киргизия', label: 'Киргизия' }, + { value: 'Кирибати', label: 'Кирибати' }, + { value: 'Китай', label: 'Китай' }, + { value: 'Колумбия', label: 'Колумбия' }, + { value: 'Коморы', label: 'Коморы' }, + { value: 'Конго', label: 'Конго' }, + { value: 'Коста-Рика', label: 'Коста-Рика' }, + { value: 'Куба', label: 'Куба' }, + { value: 'Кувейт', label: 'Кувейт' }, + { value: 'Лаос', label: 'Лаос' }, + { value: 'Латвия', label: 'Латвия' }, + { value: 'Лесото', label: 'Лесото' }, + { value: 'Либерия', label: 'Либерия' }, + { value: 'Ливан', label: 'Ливан' }, + { value: 'Ливия', label: 'Ливия' }, + { value: 'Литва', label: 'Литва' }, + { value: 'Лихтенштейн', label: 'Лихтенштейн' }, + { value: 'Люксембург', label: 'Люксембург' }, + { value: 'Маврикий', label: 'Маврикий' }, + { value: 'Мавритания', label: 'Мавритания' }, + { value: 'Мадагаскар', label: 'Мадагаскар' }, + { value: 'Малави', label: 'Малави' }, + { value: 'Малайзия', label: 'Малайзия' }, + { value: 'Мали', label: 'Мали' }, + { value: 'Мальдивы', label: 'Мальдивы' }, + { value: 'Мальта', label: 'Мальта' }, + { value: 'Марокко', label: 'Марокко' }, + { value: 'Маршалловы Острова', label: 'Маршалловы Острова' }, + { value: 'Мексика', label: 'Мексика' }, + { value: 'Мозамбик', label: 'Мозамбик' }, + { value: 'Молдова', label: 'Молдова' }, + { value: 'Монако', label: 'Монако' }, + { value: 'Монголия', label: 'Монголия' }, + { value: 'Мьянма', label: 'Мьянма' }, + { value: 'Намибия', label: 'Намибия' }, + { value: 'Науру', label: 'Науру' }, + { value: 'Непал', label: 'Непал' }, + { value: 'Нигер', label: 'Нигер' }, + { value: 'Нигерия', label: 'Нигерия' }, + { value: 'Нидерланды', label: 'Нидерланды' }, + { value: 'Никарагуа', label: 'Никарагуа' }, + { value: 'Новая Зеландия', label: 'Новая Зеландия' }, + { value: 'Норвегия', label: 'Норвегия' }, + { value: 'ОАЭ', label: 'ОАЭ' }, + { value: 'Оман', label: 'Оман' }, + { value: 'Пакистан', label: 'Пакистан' }, + { value: 'Палау', label: 'Палау' }, + { value: 'Панама', label: 'Панама' }, + { value: 'Папуа — Новая Гвинея', label: 'Папуа — Новая Гвинея' }, + { value: 'Парагвай', label: 'Парагвай' }, + { value: 'Перу', label: 'Перу' }, + { value: 'Польша', label: 'Польша' }, + { value: 'Португалия', label: 'Португалия' }, + { value: 'Республика Корея', label: 'Республика Корея' }, + { value: 'Руанда', label: 'Руанда' }, + { value: 'Румыния', label: 'Румыния' }, + { value: 'Сальвадор', label: 'Сальвадор' }, + { value: 'Самоа', label: 'Самоа' }, + { value: 'Сан-Марино', label: 'Сан-Марино' }, + { value: 'Сан-Томе и Принсипи', label: 'Сан-Томе и Принсипи' }, + { value: 'Саудовская Аравия', label: 'Саудовская Аравия' }, + { value: 'Северная Корея', label: 'Северная Корея' }, + { value: 'Северная Македония', label: 'Северная Македония' }, + { value: 'Сейшелы', label: 'Сейшелы' }, + { value: 'Сенегал', label: 'Сенегал' }, + { value: 'Сент-Винсент и Гренадины', label: 'Сент-Винсент и Гренадины' }, + { value: 'Сент-Китс и Невис', label: 'Сент-Китс и Невис' }, + { value: 'Сент-Люсия', label: 'Сент-Люсия' }, + { value: 'Сербия', label: 'Сербия' }, + { value: 'Сингапур', label: 'Сингапур' }, + { value: 'Сирия', label: 'Сирия' }, + { value: 'Словакия', label: 'Словакия' }, + { value: 'Словения', label: 'Словения' }, + { value: 'Соломоновы Острова', label: 'Соломоновы Острова' }, + { value: 'Сомали', label: 'Сомали' }, + { value: 'Судан', label: 'Судан' }, + { value: 'Суринам', label: 'Суринам' }, + { value: 'США', label: 'США' }, + { value: 'Сьерра-Леоне', label: 'Сьерра-Леоне' }, + { value: 'Таджикистан', label: 'Таджикистан' }, + { value: 'Таиланд', label: 'Таиланд' }, + { value: 'Танзания', label: 'Танзания' }, + { value: 'Того', label: 'Того' }, + { value: 'Тонга', label: 'Тонга' }, + { value: 'Тринидад и Тобаго', label: 'Тринидад и Тобаго' }, + { value: 'Тувалу', label: 'Тувалу' }, + { value: 'Тунис', label: 'Тунис' }, + { value: 'Туркменистан', label: 'Туркменистан' }, + { value: 'Турция', label: 'Турция' }, + { value: 'Уганда', label: 'Уганда' }, + { value: 'Узбекистан', label: 'Узбекистан' }, + { value: 'Украина', label: 'Украина' }, + { value: 'Уругвай', label: 'Уругвай' }, + { value: 'Фиджи', label: 'Фиджи' }, + { value: 'Филиппины', label: 'Филиппины' }, + { value: 'Финляндия', label: 'Финляндия' }, + { value: 'Франция', label: 'Франция' }, + { value: 'Хорватия', label: 'Хорватия' }, + { value: 'ЦАР', label: 'ЦАР' }, + { value: 'Чад', label: 'Чад' }, + { value: 'Черногория', label: 'Черногория' }, + { value: 'Чехия', label: 'Чехия' }, + { value: 'Чили', label: 'Чили' }, + { value: 'Швейцария', label: 'Швейцария' }, + { value: 'Швеция', label: 'Швеция' }, + { value: 'Шри-Ланка', label: 'Шри-Ланка' }, + { value: 'Эквадор', label: 'Эквадор' }, + { value: 'Экваториальная Гвинея', label: 'Экваториальная Гвинея' }, + { value: 'Эритрея', label: 'Эритрея' }, + { value: 'Эсватини', label: 'Эсватини' }, + { value: 'Эстония', label: 'Эстония' }, + { value: 'Эфиопия', label: 'Эфиопия' }, + { value: 'ЮАР', label: 'ЮАР' }, + { value: 'Южный Судан', label: 'Южный Судан' }, + { value: 'Ямайка', label: 'Ямайка' }, + { value: 'Япония', label: 'Япония' }, +]; diff --git a/src/features/tablePassengers/constants/validationRules.ts b/src/features/tablePassengers/constants/validationRules.ts new file mode 100644 index 0000000..66be334 --- /dev/null +++ b/src/features/tablePassengers/constants/validationRules.ts @@ -0,0 +1,39 @@ +import { type RegisterOptions } from 'react-hook-form'; + +export interface IValidationRules { + required: RegisterOptions['required']; + minLength?: RegisterOptions['minLength']; + pattern?: RegisterOptions['pattern']; +} + +export const nameValidationRules: IValidationRules = { + required: 'Обязательное поле', + minLength: { + value: 2, + message: 'Минимум 2 буквы', + }, + pattern: { + value: /^[a-zA-Zа-яА-ЯёЁ]+$/, + message: 'Только буквы', + }, +}; + +export const phoneValidationRules: IValidationRules = { + required: 'Обязательное поле', + minLength: { + value: 6, + message: 'Минимум 6 цифр', + }, + pattern: { + value: /^[0-9]+$/, + message: 'Только цифры', + }, +}; + +export const serialNumberValidationRules: IValidationRules = { + required: 'Обязательное поле', + pattern: { + value: /^\d{4}\s\d{6}$/, + message: 'Введите данные в формате: 1234 567890', + }, +}; diff --git a/src/features/tablePassengers/model/tablePassengersApi.ts b/src/features/tablePassengers/model/tablePassengersApi.ts new file mode 100644 index 0000000..6742461 --- /dev/null +++ b/src/features/tablePassengers/model/tablePassengersApi.ts @@ -0,0 +1,23 @@ +import { baseAPI } from '@shared/api/baseAPI'; +import { DEFAULT_PAGE_LIMIT } from '@shared/config/pagination'; +import type { IContentPassengerTable, IDataSource } from '@shared/types'; +import type { PaginationParams } from '@shared/types/pagination'; + +const tablePassangersApi = baseAPI.injectEndpoints({ + endpoints: (build) => ({ + getPassengerList: build.query, PaginationParams>({ + query: ({ page = 1, size = DEFAULT_PAGE_LIMIT }) => `passengers?size=${size}&page=${page}`, + providesTags: ['Passenger'], + }), + updatePassenger: build.mutation({ + query: ({ id, ...patch }) => ({ + url: `passengers/${id}`, + method: 'PATCH', + body: { id, ...patch }, + }), + invalidatesTags: ['Passenger'], + }), + }), +}); + +export const { useGetPassengerListQuery, useUpdatePassengerMutation } = tablePassangersApi; diff --git a/src/features/tablePassengers/ui/TablePassengers.tsx b/src/features/tablePassengers/ui/TablePassengers.tsx new file mode 100644 index 0000000..4f34935 --- /dev/null +++ b/src/features/tablePassengers/ui/TablePassengers.tsx @@ -0,0 +1,519 @@ +import { + CloseCircleOutlined, + CloseOutlined, + EditOutlined, + LoadingOutlined, + PlusOutlined, + SaveOutlined, +} from '@ant-design/icons'; +import { TableHeader } from '@entities/tableHeader'; +import { DEFAULT_PAGE_LIMIT } from '@shared/config/pagination'; +import type { IContentPassengerTable, IPassportData } from '@shared/types'; +import { Table } from '@shared/ui/table'; +import { + Button, + DatePicker, + Input, + Result, + Select, + Space, + Spin, + type TablePaginationConfig, + Tooltip, +} from 'antd'; +import dayjs from 'dayjs'; + +import { useCallback, useEffect, useState } from 'react'; + +import { useForm } from 'react-hook-form'; + +import { COUNTRIES_LIST } from '../constants/countries'; +import { + nameValidationRules, + phoneValidationRules, + serialNumberValidationRules, +} from '../constants/validationRules'; +import { useGetPassengerListQuery, useUpdatePassengerMutation } from '../model/tablePassengersApi'; +import styles from './tablePassengers.module.scss'; + +export const PassengersPage = () => { + const [page, setPage] = useState(0); + const { + data: passengerList, + isSuccess, + isLoading: getPassengerLoading, + isError: getPassengerError, + } = useGetPassengerListQuery({ + page: page, + size: DEFAULT_PAGE_LIMIT, + }); + const [updatePassenger, { isLoading: isUpdating }] = useUpdatePassengerMutation(); + const [data, setData] = useState([]); + const [editingKey, setEditingKey] = useState(null); + const [editingData, setEditingData] = useState<{ + [key: number]: Partial; + }>({}); + const [updateError, setUpdateError] = useState(false); + + type DynamicFormFields = { + [key: `lastName-${number}`]: string; + [key: `firstName-${number}`]: string; + [key: `phoneNumber-${number}`]: string; + [key: `serialNumberPassport-${number}`]: string; + }; + + const { + register, + formState: { errors }, + setValue, + trigger, + } = useForm(); + + useEffect(() => { + if (isSuccess && passengerList?.content) { + setData(passengerList.content); + } + }, [passengerList, isSuccess]); + + const isEditing = (record: IContentPassengerTable) => record.id === editingKey; + + const edit = (record: IContentPassengerTable) => { + setEditingKey(record.id); + setEditingData((prev) => ({ + ...prev, + [record.id]: { + ...record, + passport: { + ...record.passport, + middleName: record.passport.middleName === null ? undefined : record.passport.middleName, + }, + }, + })); + + register(`lastName-${record.id}`, nameValidationRules); + register(`firstName-${record.id}`, nameValidationRules); + register(`phoneNumber-${record.id}`, phoneValidationRules); + register(`serialNumberPassport-${record.id}`, serialNumberValidationRules); + + setValue(`lastName-${record.id}`, record.lastName); + setValue(`firstName-${record.id}`, record.firstName); + setValue(`phoneNumber-${record.id}`, record.phoneNumber); + setValue(`serialNumberPassport-${record.id}`, record.passport.serialNumberPassport || ''); + }; + + const cancel = () => { + setEditingKey(null); + setEditingData({}); + }; + + const save = async (id: number) => { + try { + const isValid = await trigger([ + `lastName-${id}`, + `firstName-${id}`, + `phoneNumber-${id}`, + `serialNumberPassport-${id}`, + ]); + + if (!isValid) return; + + const dataToSave = editingData[id]; + if (!dataToSave) return; + + const originalRecord = data.find((item) => item.id === id); + if (!originalRecord) return; + + const hasChanges = (Object.keys(dataToSave) as Array).some( + (key) => { + if (key === 'passport' && dataToSave.passport) { + return (Object.keys(dataToSave.passport) as Array).some( + (passportKey) => + dataToSave.passport?.[passportKey] !== originalRecord.passport[passportKey], + ); + } + return dataToSave[key] !== originalRecord[key]; + }, + ); + + if (!hasChanges) { + cancel(); + return; + } + + const payload = { + ...dataToSave, + passport: { + ...originalRecord.passport, + ...dataToSave.passport, + middleName: + dataToSave.passport?.middleName === '' || dataToSave.passport?.middleName?.length === 1 + ? 'isAbsent' + : dataToSave.passport?.middleName, + }, + }; + + await updatePassenger({ id, ...payload }).unwrap(); + setData((prev) => prev.map((item) => (item.id === id ? { ...item, ...payload } : item))); + cancel(); + } catch (err) { + setUpdateError(true); + setTimeout(() => setUpdateError(false), 3000); + } + }; + + const handleInputChange = useCallback((id: number, changes: Partial) => { + setEditingData((prev) => { + const currentData = prev[id] || {}; + + return { + ...prev, + [id]: { + ...currentData, + ...changes, + passport: changes.passport + ? { ...currentData.passport, ...changes.passport } + : currentData.passport, + }, + }; + }); + }, []); + + const handleTableChange = (pagination: TablePaginationConfig) => { + if (pagination.current !== undefined) { + setPage(pagination.current - 1); + } + + setEditingKey(null); + setEditingData({}); + }; + + const columns = [ + { + title: 'iD', + dataIndex: 'id', + }, + { + title: 'Фамилия, Имя, Отчество', + render: (record: IContentPassengerTable) => { + const editable = isEditing(record); + const currentEditingData = editingData[record.id] || {}; + return editable ? ( +
+ { + const trimmedValue = e.target.value.slice(0, 15); + setValue(`lastName-${record.id}`, trimmedValue); + handleInputChange(record.id, { lastName: trimmedValue }); + await trigger(`lastName-${record.id}`); + }} + placeholder="Фамилия" + status={errors[`lastName-${record.id}`] ? 'error' : ''} + /> + {errors[`lastName-${record.id}`] && ( + {errors[`lastName-${record.id}`]?.message} + )} + { + const trimmedValue = e.target.value.slice(0, 15); + setValue(`firstName-${record.id}`, trimmedValue); + handleInputChange(record.id, { firstName: trimmedValue }); + await trigger(`firstName-${record.id}`); + }} + placeholder="Имя" + status={errors[`firstName-${record.id}`] ? 'error' : ''} + /> + {errors[`firstName-${record.id}`] && ( + {errors[`firstName-${record.id}`]?.message} + )} + { + const lettersOnly = e.target.value.replace(/[^a-zA-Zа-яА-ЯёЁ]/g, ''); + handleInputChange(record.id, { + passport: { + middleName: lettersOnly, + }, + }); + }} + placeholder="Отчество (при наличии)" + /> +
+ ) : ( + `${record.lastName} ${record.firstName} ${record.passport.middleName === 'isAbsent' ? '' : record.passport.middleName}` + ); + }, + }, + { + title: 'Пол', + render: (record: IContentPassengerTable) => { + const editable = isEditing(record); + const currentEditingData = editingData[record.id] || {}; + const currentGender = currentEditingData.passport?.gender ?? record.passport.gender; + return editable ? ( + { + const trimmedValue = e.target.value.slice(0, 15); + setValue(`phoneNumber-${record.id}`, trimmedValue); + handleInputChange(record.id, { phoneNumber: trimmedValue }); + await trigger(`phoneNumber-${record.id}`); + }} + placeholder="Телефон" + status={errors[`phoneNumber-${record.id}`] ? 'error' : ''} + addonBefore="+" + /> + {errors[`phoneNumber-${record.id}`] && ( + + {errors[`phoneNumber-${record.id}`]?.message} + + )} + + ) : ( + `+${record.phoneNumber}` + ); + }, + }, + { + title: 'Дата рождения', + render: (record: IContentPassengerTable) => { + const editable = isEditing(record); + const currentEditingData = editingData[record.id] || {}; + const value = dayjs(currentEditingData.birthDate || record.birthDate, 'YYYY-MM-DD'); + return editable ? ( + { + handleInputChange(record.id, { + birthDate: date?.format('YYYY-MM-DD') || '', + }); + }} + format="DD.MM.YYYY" + allowClear={false} + disabledDate={(current) => current && current > dayjs().endOf('day')} + /> + ) : value?.isValid() ? ( + value.format('DD.MM.YYYY') + ) : ( + 'Не указана' + ); + }, + }, + { + title: 'Серийный номер', + render: (record: IContentPassengerTable) => { + const editable = isEditing(record); + const currentEditingData = editingData[record.id] || {}; + return editable ? ( +
+ { + const trimmedValue = e.target.value.slice(0, 15); + setValue(`serialNumberPassport-${record.id}`, trimmedValue); + handleInputChange(record.id, { + passport: { + ...currentEditingData.passport, + serialNumberPassport: trimmedValue, + }, + }); + await trigger(`serialNumberPassport-${record.id}`); + }} + placeholder="Серия и номер паспорта" + status={errors[`serialNumberPassport-${record.id}`] ? 'error' : ''} + /> + {errors[`serialNumberPassport-${record.id}`] && ( + + {errors[`serialNumberPassport-${record.id}`]?.message} + + )} +
+ ) : ( + record.passport.serialNumberPassport + ); + }, + }, + { + title: 'Гражданство', + render: (record: IContentPassengerTable) => { + const editable = isEditing(record); + const currentEditingData = editingData[record.id] || {}; + return editable ? ( +