Files
adminpanel/src/pages/EditSpecialistPage.jsx
T
2026-05-27 19:36:33 +03:00

1610 lines
50 KiB
React
Raw 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 {useEffect, useState, useMemo, useRef} from 'react'
import {useParams, useNavigate, useLocation } from 'react-router-dom'
import DatePicker from "react-datepicker";
import { apiUrl } from '@/config/api';
import { useUpdateSpecialistMutation, useDeleteSpecialistMutation, useUploadSpecialistPictureMutation } from '../api/apiSpecialist'
import { useCreateLocationMutation, useUpdateLocationMutation, useDeleteLocationMutation } from '/src/api/apiLocation.js'
import {
useCreateCertificateMutation,
useUpdateCertificateMutation,
useUploadSertificatePictureMutation,
useDeleteCertificateMutation,
} from '/src/api/apiCertificate.js'
import { useAddSpecialistMutation, useRemoveSpecialistMutation } from '../api/apiStock'
import { useSpecialist } from '../hooks/useSpecialist';
//
import { LoadingComponent } from '../components/Placeholders/LoadingComponent';
import { ErrorComponent } from '../components/Placeholders/ErrorComponent';
import { NotFindElement } from '../components/Placeholders/NotFindElement';
import { EditElementForm } from '../components/Forms/EditElementForm';
import { TagInput } from '../components/Input/Taginput';
import { TagStaticInput } from '../components/Input/TagStaticInput';
import { TagKodoperStaticInput } from '../components/Input/TagKodoperStatic';
import { TextEditor } from '../components/Editors/TextEditor'
import Modal from '../components/Modals/Modal';
import DcodeModal from '../components/Modals/DcodeModal';
import KodoperModal from '../components/Modals/KodoperModal';
import { CertificatesForm } from '../components/Docs/Certificates';
import { PortfolioForm } from '../components/Docs/Portfolio';
import { StocksForm } from '../components/Docs/Stocks'
import { StockModal } from '../components/Modals/StockModal'
import PHOTO_PLACEHOLDER from '../assets/photo-placeholder.png'
const formatStockDate = (date) => {
const dateStart = new Date(date);
const year = dateStart.getFullYear();
const month = String(dateStart.getMonth() + 1).padStart(2, '0');
const day = String(dateStart.getDate()).padStart(2, '0');
const hours = String(dateStart.getHours()).padStart(2, '0');
const minutes = String(dateStart.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}, ${day}.${month}.${year}`;
}
const regionOptions = {
91: 'Саратов' ,
92: 'Волгоград',
93: 'Воронеж',
94: 'Краснодар',
};
const sTypeOptions = {
0: 'Взрослый врач',
1: 'Детский врач',
2: 'Администрация',
3: 'Стоматология',
4: 'Мед. сестра',
};
const getYear = (date) => {
const dateObject = new Date(date);
return String(dateObject.getFullYear())
}
const formatCategory = (rawCategory) => {
if (!rawCategory) return 'Не указано'
const categoryLowerCase = rawCategory.toLowerCase();
switch (categoryLowerCase) {
case 'высшая':
case 'ведущий специалист':
return 'Высшая';
case 'первая':
case 'главный специалист':
return 'Первая';
case 'вторая':
return 'Вторая';
default:
return 'Не указано'
}
}
export const EditSpecialistPage = () => {
const { id } = useParams();
const { state } = useLocation();
const queryParams = {
search: state?.search ?? '',
region: state?.region ?? '',
page: state?.page ?? null,
}
const navigate = useNavigate();
const navigateBack = () => {
let query = '';
Object.keys(queryParams).map( param => {
if (queryParams[param]) query += `&${param}=${queryParams[param]}`;
});
if (query) query = `?${query.slice(1)}`;
navigate( `/specialist${query}` );
}
const dateInputRef = useRef(null);
const patientAgeInputRef = useRef(null);
const [ locations, setLocations ] = useState([]);
const [ initialLocations, setInitialLocations ] = useState([]);
const setFromRawLocations = (rawLocations) => {
const selectedDcodes = [...new Set(rawLocations.map(({dcode}) => dcode))];
const arr = selectedDcodes.filter(dcode => !dcodes.includes(dcode)).map(dc => String(dc));
setDcodes(arr)
setLocations(rawLocations)
}
const [ stocks, setStocks ] = useState([]);
const [ stocksFromChild, setStocksFromChild ] = useState([]);
const [ anons, setAnons ] = useState('');
const [ content, setContent ] = useState('');
const [displayKodoper, setDisplayKodoper] = useState([]);
const [errors, setErrors] = useState({
lastName: '',
firstName: '',
middleName: '',
videoUrl: '',
videoCardUrl: '',
patientAge: '',
prodoctorLink: '',
});
const categoryList = [
'Не указано',
'Вторая',
'Первая',
'Высшая',
];
const [isModalKodopers, setModalKodopers] = useState(false);
const [kodopers, setKodopers] = useState([]);
const [certificates, setCertificates] = useState([]);
const [portfolio, setPortfolio] = useState([]);
const [ form, setForm ] = useState({
nameString: '',
lastName: '',
firstName: '',
middleName: '',
active: false, // boolean
displaySchedule: null, // boolean
//
hideSchedule: false, // booleal
//
alias: undefined, // string
post: '', // string
experience: undefined, // number
sType: null, // number
regionId: null, // number
anons: '', // string + add redactor
content: '', // string + add redactor
tags: [], // json
highlightedTags: [], // json
videoUrl: '', // string
videoCardUrl: '', // string
scheduleText: '',
//isCastom: false, // has autoupdate - boolean
category: '', // +++++++
previewPicture: undefined, // string
filialId: null, // from locations.locations[0].filial
kodoper: null, // <<<---???
prodoctor: '',
prodoctorLink: '',
prodoctorText: '',
onlyOnlineMode: '',
//
isChildrenDoctor: undefined, // from sType
patientAge: undefined, // integer
initStocks: [],
//isCastomChecker: undefined,
//
degree: '',
isLeadingSpecialist: false,
isChiefSpecialist: false,
education: '',
academicDegree: '',
professionalCompetencies:'',
advancedTraining: '',
certificates: '',
certificatesGallery: [],
adultsReception: '',
cost: '',
hasPromotion: false,
onlineConsultationLink: '',
onlyOnline: false,
hasProDoctorsAward: false,
proDoctorsAwardText: '',
proDoctorsAwardLink: '',
postTags: [],
operationPhotoUrl: '',
});
const updateField = ( key, value ) => {
setForm(prev => ({ ...prev, [key]: value }));
};
const [isModalStocks, setModalStocks] = useState(false);
const regexCyrillic = /^[А-ЯЁ][а-яё]+$/;
const regexRutube = /^https:\/\/rutube\.ru\/.*$/;
const regexProdoctor = /^https:\/\/prodoctorov\.ru\/.*$/;
const validateField = (field, value) => {
let error = '';
const currentYear = new Date().getFullYear();
switch (field) {
case 'lastName':
case 'firstName':
case 'middleName':
if (!value) error = 'Обязательное поле';
else if (!regexCyrillic.test(value)) error = 'Только кириллица, с заглавной буквы';
break;
case 'experience':
if (!value) {
error = 'Обязательное поле';
} else if (Number(getYear(value)) > Number(currentYear)) {
error = 'Год должен быть не больше текущего';
}
break;
case 'videoUrl':
case 'videoCardUrl':
if (value && !regexRutube.test(value)) error = 'Должно быть ссылкой с rutube';
break;
case 'prodoctorLink':
if (form.prodoctor) {
if (!value) {
error = 'Укажите ссылку';
} else {
if (value && !regexProdoctor.test(value)) error = 'Должно быть ссылкой с prodoctorov.ru';
}
} else {
error = ''
}
break;
case 'patientAge':
if (form.isChildrenDoctor) {
if ( !value ) {
error = 'Укажите возраст';
} else {
const num = Number(value);
if (!Number.isInteger(num) || num < 0 || num > 18) error = 'Возраст не может быть меньше 0 и больше 18';
}
} else {
error = ''
}
break;
}
setErrors(prev => ({ ...prev, [field]: error }));
};
const handleBlur = e => {
const { name, value } = e.target;
validateField(name, value);
};
const isFormValid = () => {
['lastName', 'firstName', 'middleName', 'videoUrl', 'videoCardUrl', 'patientAge', 'prodoctorLink', 'experience'].forEach(f => validateField(f, form[f]));
const a = form.prodoctor ? ( form.prodoctorLink.length > 0 ) : true;
const b = form.isChildrenDoctor ? ( form.patientAge ) : true;
return Object.entries(errors).every(
([key, err]) => {
return !err;
}
) && form.lastName && form.firstName && form.middleName && a && b && form.experience;
};
const [ tags, setTags ] = useState([]);
const [ highlightedTags, setHighlightedTags ] = useState([]);
const [ postTags, setPostTags ] = useState([]);
const [ dcodes, setDcodes ] = useState([]);
const photoInputRef = useRef(null)
const photoOperationInputRef = useRef(null)
const certificatesGalleryInputRef = useRef(null)
const { isLoading, error, specialist, filials, regions, departments, kodoperDetails } = useSpecialist(id);
const [updateSpecialist, { isLoading: isUpdating, isError: updateError }] =
useUpdateSpecialistMutation();
const [uploadPicture, { isLoadingPicture, isErrorOicture }] = useUploadSpecialistPictureMutation();
const [deleteSpecialist, { isLoading: isDeleting, isError: deleteError }] =
useDeleteSpecialistMutation();
const [createLocation] = useCreateLocationMutation();
const [updateLocation]= useUpdateLocationMutation();
const [deleteLocation] = useDeleteLocationMutation();
const [addSpecialistToStock] = useAddSpecialistMutation();
const [removeSpecialistToStock] = useRemoveSpecialistMutation();
const [createCertifcate] = useCreateCertificateMutation();
const [updateCertifcate] = useUpdateCertificateMutation();
const [uploadCertifcatePicture] = useUploadSertificatePictureMutation();
const [deleteCertifcate] = useDeleteCertificateMutation();
const filialsList = useMemo(() => {
const arr = filials?.data ?? [];
return arr.map(({ fid, regionId, shortName }) => ({ id: fid, regionId, shortName }));
}, [filials?.data]);
const departmentsList = useMemo(() => {
const arr = departments?.data ?? [];
return arr.map(({ did, name }) => ({ id: did, name }));
}, [departments?.data]);
useEffect( () => {
if ( specialist ) {
const initial = specialist.post
? specialist.post.split(',').map(s => s.trim())
: [];
const reorderInit = [];
initial.map((element, index) => {
if (element.length < 4) {
if (index > 0) {
reorderInit.pop();
reorderInit.push(`${ initial[index-1]}, ${element}`);
return
}
reorderInit.push(element);
return
}
reorderInit.push(element);
return
});
setPostTags(reorderInit);
if (specialist.locations) {
setInitialLocations(specialist.locations);
const rawLocations = specialist.locations.map(loc => {
return { ...loc, name: specialist.nameString, }
});
setLocations(rawLocations.map((location) => {
const parsedDate = new Date(location.nearestDate);
const now = new Date();
if (parsedDate > now) {
return { ...location, checkedDate: true, key: `${location.dcode}${location.department}${location.filial}` }
}
return { ...location, checkedDate: false, key: `${location.dcode}${location.department}${location.filial}` }
}));
}
updateField('lastName', specialist.fullName?.lastName);
updateField('firstName', specialist.fullName?.firstName);
updateField('middleName', specialist.fullName?.middleName);
updateField('alias', specialist.alias);
updateField('active', specialist.active);
setAnons(specialist.anons);
setContent(specialist.content);
updateField('regionId', specialist.regionId);
updateField('sType', specialist.sType);
updateField('scheduleText', specialist.scheduleText);
updateField('videoUrl', specialist.video);
updateField('videoCardUrl', specialist.videoVertical);
updateField('kodoper', specialist.kodoper);
setKodopers(specialist.kodoper);
updateField('onlyOnlineMode', Boolean(specialist.onlyOnlineMode));
updateField('prodoctor', specialist.prodoctor);
updateField('degree', specialist.degree);
updateField('prodoctorLink', specialist.prodoctorLink ?? '');
updateField('prodoctorText', specialist.prodoctorText ?? '');
setDisplayKodoper([...kodoperDetails])
if (specialist?.dcodes) setDcodes([...specialist.dcodes.split(',')])
if ( specialist.tags ) setTags([...specialist.tags])
if ( specialist.highlightedTags ) setHighlightedTags([...specialist.highlightedTags])
if ( specialist.stocks.length > 0 ) {
setStocks([...specialist.stocks]);
setStocksFromChild([...specialist.stocks])
updateField('initStocks', specialist.stocks);
}
updateField('category', formatCategory(specialist.category));
updateField('previewPicture', apiUrl(specialist.pictureLink));
const formattedDate = specialist.experience
? `${specialist.experience}-01-01`
: '';
updateField('experience', formattedDate);
updateField('hideSchedule', !specialist.displaySchedule);
updateField('patientAge', specialist.patientAge);
updateField('isChildrenDoctor', specialist.sType === 1 ? true : false);
if (specialist.specialistDocs) {
specialist.specialistDocs.forEach(doc => {
if (doc.type === 'certificate') {
setCertificates(prev => {
if (prev.some(item => item.id === doc.id)) {
return prev;
}
return [...prev, doc];
});
}
if (doc.type === 'portfolio') {
setPortfolio(prev => {
if (prev.some(item => item.id === doc.id)) {
return prev;
}
return [...prev, doc];
});
}
});
}
}
}, [ specialist, kodoperDetails ] );
const [previewFile, setPreviewFile] = useState(null)
const isValidImage = (file) => {
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
if (allowedTypes.includes(file.type)) return true;
const ext = file.name.split('.').pop().toLowerCase();
return ['jpg', 'jpeg', 'png'].includes(ext);
};
const handlePhotoUpload = () => {
const file = photoInputRef.current.files[0]
if (!file) return window.alert('Файл не выбран')
if (!isValidImage(file)) {
return window.alert('Изображения должны быть только формата JPG, JPEG или PNG');
}
updateField('previewPicture', window.URL.createObjectURL(file));
setPreviewFile(file);
}
const handlePhotoOperationUpload = () => {
const file = photoOperationInputRef.current.files[0]
if (!file) return window.alert('Файл не выбран')
updateField('operationPhotoUrl', { file, url: window.URL.createObjectURL(file) });
}
const handleWrapper = () => {
if (!isFormValid()) {
window.alert('Пожалуйста, исправьте ошибки в форме');
return;
}
handleSave();
}
const [certsFromChild, setCertsFromChild] = useState([]);
const [portfolioFromChild, setPortfolioFromChild] = useState([]);
const handleSave = async () => {
if (!isFormValid()) {
window.alert('Пожалуйста, исправьте ошибки в форме');
return;
}
const arr = [];
dcodes.map(dcode => {
if (arr.includes(String(dcode))) return
arr.push(String(dcode))
})
locations.map(location => {
if (arr.includes(String(location.dcode))) return
arr.push(String(location.dcode))
})
const data = {
nameString: `${form.lastName} ${form.firstName} ${form.middleName}`,
fullName: {
lastName: form.firstName,
firstName: form.lastName,
middleName: form.middleName
},
active: form.active ?? false,
displaySchedule: !form.hideSchedule,
alias: form.alias ?? '',
degree: form.degree ?? '',
post: postTags.join(', ') ?? '',
experience: getYear(form.experience) ?? '',
sType: Number(form.sType),
regionId: Number(form.regionId),
anons: anons ?? '',
content: content ?? '',
tags: tags ?? [],
highlightedTags: highlightedTags ?? [],
video: form.videoUrl ?? '',
videoVertical: form.videoCardUrl ?? '',
scheduleText: form.scheduleText ?? '',
dcodes: arr.map((dcode) => String(dcode).trim()).join(',') ?? '',// dcodes.map((dcode) => dcode.trim()).join(','),
category: form.category === 'Не указано' ? '' : form.category,
kodoper: kodopers ?? [],
patientAge: form.isChildrenDoctor ? (!isNaN(Number(form.patientAge)) ? Number(form.patientAge) : null) : null,
onlyOnlineMode: form.onlyOnlineMode ?? false,
prodoctor: form.prodoctor,
prodoctorLink: form.prodoctor ? (form.prodoctorLink.length > 0 ? form.prodoctorLink : '') : '',
prodoctorText: form.prodoctor ? (form.prodoctorText.length > 0 ? form.prodoctorText : '') : '',
};
const formattedLocations = locations.map(location => ({
active: location?.active ? location.active : false,
dcode: location.dcode,
department: location.department,
filial: location.filial,
id: location?.id,
onlineMode: location.onlineMode,
nearestDate: location.nearestDate,
}));
function isEqualExceptId(a, b) {
for (const key in a) {
if (key === 'id') continue;
if (a[key] !== b[key]) return false;
}
for (const key in b) {
if (key === 'id') continue;
if (a[key] !== b[key]) return false;
}
return true;
}
function diffArrays(initial, current) {
const currentMap = new Map(current.map(item => [item.id, item]));
const initialMap = new Map(initial.map(item => [item.id, item]));
const deletedLocations = [];
const changedLocations = [];
const addedLocations = [];
for (const item of initial) {
const curr = currentMap.get(item.id);
if (!curr) {
deletedLocations.push(item);
} else if (!isEqualExceptId(item, curr)) {
changedLocations.push({ ...curr });
}
}
for (const item of current) {
if (!initialMap.has(item.id)) {
addedLocations.push(item);
}
}
return { deletedLocations, changedLocations, addedLocations };
}
const { deletedLocations, changedLocations, addedLocations } = diffArrays(initialLocations, formattedLocations);
try {
await updateSpecialist({
specialistId: id,
data: data,
}).unwrap();
console.log('success specialist update')
for (let i = 0; i < deletedLocations.length; i += 1) {
await deleteLocation(deletedLocations[i].id).unwrap()
}
console.log('success delete locations')
for (let i = 0; i < addedLocations.length; i += 1) {
const [day, month, year] = addedLocations[i].nearestDate.split('.');
const isoDate = `${year}-${month}-${day}`;
await createLocation({ specialistId: id, data: {
active: addedLocations[i].active,
dcode: addedLocations[i].dcode,
department: addedLocations[i].department,
filial: addedLocations[i].filial,
nearestDate: isoDate,
onlineMode: addedLocations[i].onlineMode,
} }).unwrap()
}
console.log('success create locations')
for (let i = 0; i < changedLocations.length; i += 1) {
await updateLocation({
specialistId: id,
locationId: changedLocations[i].id,
data: {
active: changedLocations[i].active,
},
}).unwrap()
}
console.log('success update locations')
//--------------------------------------------------
const initMap = new Map([...certificates.map(c => [c.id, c]), ...portfolio.map(c => [c.id, c])]);
const currMap = new Map([...certsFromChild.filter(c => c.id).map(c => [c.id, c]), ...portfolioFromChild.filter(c => c.id).map(c => [c.id, c])]);
const created = [...certsFromChild.filter(c => c.id == null), ...portfolioFromChild.filter(c => c.id == null)];
const deleted = [...certificates.filter(c => !currMap.has(c.id)), ...portfolio.filter(c => !currMap.has(c.id))];
const modified = [
...certsFromChild
.filter(c => c.id != null && initMap.has(c.id))
.filter(c => {
const orig = initMap.get(c.id);
return (
c.name !== orig.name ||
c.description !== orig.description ||
c.picture !== orig.picture ||
c.active !== orig.active
);
}),
...portfolioFromChild
.filter(c => c.id != null && initMap.has(c.id))
.filter(c => {
const orig = initMap.get(c.id);
return (
c.name !== orig.name ||
c.description !== orig.description ||
c.picture !== orig.picture ||
c.active !== orig.active
);
})
];
for (let i = 0; i < created.length; i += 1) {
if (created[i].name.trim().length === 0 && created[i].description.trim().length === 0) continue
const response = await createCertifcate({
specialistId: id,
data: {
name: created[i].name,
description: String(created[i].description),
active: created[i].active,
type: created[i].type,
},
}).unwrap()
if (created[i].picture) await uploadCertifcatePicture({
id: response.id,
file: created[i]._file,
}).unwrap()
}
console.log('success create cert')
for (let i = 0; i < modified.length; i += 1) {
if (modified[i].name.trim().length === 0 && modified[i].description.trim().length === 0) continue
await updateCertifcate({
specialistId: id,
id: modified[i].id,
data: {
active: modified[i].active,
description: modified[i].description,
name: modified[i].name,
type: modified[i].type,
},
}).unwrap()
if (modified[i]?._file) {
await uploadCertifcatePicture({
id: modified[i].id,
file: modified[i]._file,
}).unwrap()
}
}
console.log('success update cert')
for (let i = 0; i < deleted.length; i += 1) {
await deleteCertifcate({
id: deleted[i].id,
}).unwrap()
}
console.log('success delete cert')
if (previewFile) {
await uploadPicture({ id, file: previewFile }).unwrap();
console.log('success photo update')
}
const initIds = new Set(form.initStocks.map(s => s.id));
const childIds = new Set(stocksFromChild.map(s => s.id));
const deletedStocs = form.initStocks.filter(s => !childIds.has(s.id));
const addedStocks = stocksFromChild.filter(s => !initIds.has(s.id));
for (let i = 0; i < addedStocks.length; i += 1) {
await addSpecialistToStock({
stockId: addedStocks[i].id,
specialistId: id,
}).unwrap()
}
console.log('success added promotions')
for (let i = 0; i < deletedStocs.length; i += 1) {
await removeSpecialistToStock({
stockId: deletedStocs[i].id,
specialistId: id,
}).unwrap()
}
console.log('success delete promotions')
setModalSuccess(true)
window.setTimeout(() => {
window.location.reload()
}, 2000);
} catch (err) {
console.error('Ошибка при обновлении специалиста:', err)
}
}
const handleDelete = async () => {
try {
await deleteSpecialist(id).unwrap()
console.log('success delete')
setModalSuccess(true)
window.setTimeout(() => {
navigateBack()
}, 2000);
} catch (err) {
console.error('Ошибка при удалении специалиста:', err)
}
}
/*
useEffect(() => {
if (patientAgeInputRef.current) {
patientAgeInputRef.current.focus();
}
}, [ form.isChildrenDoctor ]);
*/
const [isModalCustomOpen, setModalCustomOpen] = useState(false);
const [isModalDeleteOpen, setModalDeleteOpen] = useState(false);
const [isModalSuccess, setModalSuccess] = useState(false);
const [isModalDcodes, setModalDcodes] = useState(false);
if ( isLoading ) return <LoadingComponent />
if ( error ) return <ErrorComponent />
if ( !specialist ) return <NotFindElement
message={ `Врач #${id} не найден.` }
navigateBack={ navigateBack }
/>
return (
<EditElementForm
navigateBack={ navigateBack }
header={ `Редактировать врача #${ id }` }
handleSave={handleWrapper}
//handleSave={ handleSave }
handleDelete={ () => setModalDeleteOpen(true) }
// addSpecialist={ navigateAddSpecialist }
>
<div className="card-body mb-1 mt-1 pt-1 pl-1">
<a
style={{ fontSize: '1.1rem', fontWeight: 'bold' }}
href={`https://cabinet.sovamed.ru/specialist/${specialist.id}-${specialist.alias}`}
>
Личный кабинет врача
</a>
</div>
<div className='card mb-3 shadow'>
<div className="card-body d-flex ">
<div className='align-self-start'>
<div className="form-group d-flex flex-column" style={{ gap: '0.75rem', marginTop: '2rem' }}>
<img
src={ form.previewPicture }
alt="Фото врача"
style={{ width: '12rem', height: '12rem', objectFit: 'cover', borderRadius: '3%' }}
className=""
onError={ e => e.currentTarget.src = PHOTO_PLACEHOLDER ?? '/assets/photo-placeholder.png' }
/>
<button
type="button"
className="btn btn-outline-secondary"
style={{ width: '100%' }}
onClick={ () => photoInputRef.current.click() }
>
Загрузить фото
</button>
<input
type="file"
accept="image/*"
ref={ photoInputRef }
style={{ display: 'none' }}
onChange={handlePhotoUpload}
/>
</div>
</div>
<div style={{ flexGrow: 1 }} className='ml-3 align-self-start'>
<div className='d-flex justify-content-between' style={{ gap: '20px' }}>
<div className="form-group flex-grow-1">
<label>
Фамилия
</label>
<input
name="lastName"
className="form-control"
value={ form.lastName }
onChange={ e => updateField('lastName', e.target.value) }
onBlur={handleBlur}
/>
{errors.lastName && <small className="text-danger">{errors.lastName}</small>}
</div>
<div className="form-group flex-grow-1">
<label>
Имя
</label>
<input
name="firstName"
className="form-control"
value={ form.firstName }
onChange={ e => updateField('firstName', e.target.value) }
onBlur={handleBlur}
/>
{errors.firstName && <small className="text-danger">{errors.firstName}</small>}
</div>
<div className="form-group flex-grow-1">
<label>
Отчество
</label>
<input
name="middleName"
className="form-control"
value={ form.middleName }
onChange={ e => updateField('middleName', e.target.value) }
pattern="[А-ЯЁ][а-яё]+"
title="Только буквы кириллицы, первая буква заглавная"
onBlur={handleBlur}
/>
{errors.middleName && <small className="text-danger">{errors.middleName}</small>}
</div>
</div>
<div className="form-group">
<label>
Символьный код
</label>
<input
className="form-control"
value={ form?.alias || '' }
onChange={ e => updateField('alias', e.target.value) }
/>
</div>
<div className="form-group d-flex">
<div className='w-100'>
<label htmlFor="regionSelect">
Город
</label>
<select
id="regionSelect"
className="form-control mr-3"
value={ form.regionId }
onChange={e => updateField('regionId', e.target.value)}
>
{
Object.keys(regionOptions).map( regionId => (
<option key={ regionId } value={ regionId }>
{ regionOptions[regionId] }
</option>))
}
</select>
</div>
</div>
<div className="form-group d-flex">
<div className='w-100'>
<label htmlFor="sTypeSelect">
Категория специалиста
</label>
<select
id="sTypeSelect"
className="form-control mr-3"
value={ form.sType }
onChange={e => {
updateField('sType', e.target.value);
if (Number(e.target.value) === 1) {
updateField('isChildrenDoctor', !form.isChildrenDoctor)
updateField('patientAge', 0)
if (form.isChildrenDoctor) setErrors(prev => ({ ...prev, patientAge: '' }));
} else {
updateField('isChildrenDoctor', false)
updateField('patientAge', null)
}
}}
>
{
Object.keys(sTypeOptions).map( sType => (
<option key={ sType } value={ sType }>
{ sTypeOptions[sType] }
</option>))
}
</select>
</div>
</div>
<div className='d-flex align-items-start'>
{form.isChildrenDoctor &&
<div className="form-group flex-grow-1">
<div className="input-group">
<span className="input-group-text text-muted "
onClick={ () => {
if (patientAgeInputRef.current) patientAgeInputRef.current.focus()
}}
style={{ backgroundColor: 'white', borderTopRightRadius: 0, borderBottomRightRadius: 0, borderRight: 'none', height: 'calc(1.5em + .75rem + 2px)' }}
>Прием детей с </span>
<input
name="patientAge"
id="patientAge"
type="number"
min={-1}
max={18}
className="form-control"
ref={ patientAgeInputRef }
style={{ borderRight: 'none', borderLeft: 'none' }}
placeholder=""
value={ form.patientAge }
onChange={e => {
updateField('patientAge', e.target.value)
validateField('patientAge', form.patientAge);
}}
onBlur={handleBlur}
/>
<span className="input-group-text text-muted"
onClick={ () => {
if (patientAgeInputRef.current) patientAgeInputRef.current.focus()
}}
style={{ backgroundColor: 'white', borderTopLeftRadius: 0, borderBottomLeftRadius: 0, borderLeft: 'none', height: 'calc(1.5em + .75rem + 2px)' }}
>лет</span>
</div>
{errors.patientAge && <small className="text-danger">{errors.patientAge}</small>}
</div>
}
</div>
</div>
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-group">
<label>
Должность
</label>
<TagInput
initialTags={ postTags }
onChange={ setPostTags }
placeholder="Напишите должность и нажмите Enter"
/>
</div>
<div className="form-group">
<label>
Ученая степень
</label>
<input
className="form-control"
value={ form?.degree || '' }
onChange={ e => updateField('degree', e.target.value) }
/>
</div>
<div className="form-group d-flex mb-2">
<div>
<label htmlFor="categorySelect">
Категория
</label>
<select
id="categorySelect"
className="form-control mr-3"
style={{ width: '20vw' }}
value={ form.category }
onChange={e => updateField('category', e.target.value)}
>
{
categoryList.map( (cat, index) => (
<option key={ index } value={ cat }>
{ cat }
</option>))
}
</select>
</div>
{/*
<div className="form-group">
<label>Стаж работы (с года)</label>
<input
type="date"
ref={dateInputRef}
className="form-control"
value={form?.experience || ''}
onClick={() => {
if (dateInputRef.current?.showPicker) {
dateInputRef.current.showPicker();
}
}}
onChange={e => {
updateField('experience', e.target.value)
}}
/>
</div>
*/}
<div className="form-group">
<label>Стаж работы (с года)</label>
<div className='yearpicker-wrapper'>
<DatePicker
ref={dateInputRef}
onBlur={handleBlur}
selected={form.experience}
onChange={ e => {
updateField('experience', e);
validateField('experience', e);
}}
className="form-control"
calendarClassName="react-datepicker-bootstrap"
popperClassName="react-datepicker-popper"
showYearPicker
dateFormat="yyyy"
yearItemNumber={10}
/>
</div>
{errors.experience && <small className="text-danger">{errors.experience}</small>}
</div>
</div>
<div className="form-check mb-3">
<input
id="active"
type="checkbox"
className="form-check-input"
checked={ form.active }
onChange={e => updateField('active', e.target.checked)}
/>
<label htmlFor="active" className="form-check-label">
Отображать на сайте
</label>
</div>
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-group">
<label>
Текст под расписанием
</label>
<input
className="form-control"
value={ form.scheduleText }
onChange={e => updateField('scheduleText', e.target.value)}
/>
</div>
<div className='d-flex'>
<div className="form-check mb-3 mr-3">
<input
id="hideSchedule"
type="checkbox"
className="form-check-input"
checked={ form.hideSchedule }
onChange={e => updateField('hideSchedule', e.target.checked)}
/>
<label htmlFor="hideSchedule" className="form-check-label">
Скрывать расписание
</label>
</div>
<div className="form-check mb-3">
<input
id="onlyOnlineMode"
type="checkbox"
className="form-check-input"
checked={ form.onlyOnlineMode }
onChange={e => updateField('onlyOnlineMode', e.target.checked)}
/>
<label htmlFor="onlyOnlineMode" className="form-check-label">
Только онлайн прием
</label>
</div>
</div>
<div className='d-flex mb-3'>
<h6 className='align-self-end'>
Расписание из Инфоклиники
</h6>
<button
type="button"
className="btn btn-outline-success ml-auto"
onClick={ () => setModalDcodes(true) }
>
Добавить
</button>
</div>
{locations.length > 0 &&
<div className="mt-3">
{/*
<h6>
Филиалы:
</h6>*/
}
<div className="table-responsive">
<table className="table table-hover table-bordered">
<thead>
<tr>
<th className='align-middle'>dcode</th>
<th className='align-middle'>Филиал</th>
<th className='align-middle'>Отделение</th>
<th className='align-middle'>Ближайшая дата приема</th>
<th className='align-middle'>Онлайн прием</th>
<th className='align-middle'>Отображать на сайте</th>
<th></th>
</tr>
</thead>
<tbody>
{locations.map((location, index) => {
return (
<tr key={index}>
<td>{ location.dcode }</td>
<td>{ filialsList.find(filial => filial.id === location.filial)?.shortName }</td>
<td>{ departmentsList.find(department => Number(department.id) === Number(location.department))?.name }</td>
<td>{ location.nearestDate ? location.nearestDate : '' }</td>
<td>{ location.onlineMode ? 'да' : 'нет' }</td>
<td className='text-center'>
<input
type="checkbox"
style={{ marginLeft: 'auto', marginRight: 'auto' }}
className=""
id={String(location.key)}
checked={ location.active }
onChange={e => {
const checked = e.target.checked;
setLocations(prev =>
prev.map(l =>
l.key === location.key
? { ...l, active: checked }
: l
));
}
//updateField('active', e.target.checked)
}
/></td>
<td><button
className="btn btn-danger ml-auto"
onClick={ () => {
setDcodes(prev =>
prev.filter(dcode =>
dcode !== String(location.dcode)
))
setLocations(prev =>
prev.filter(l =>
l.key !== location.key
))
}}
>
Удалить
</button></td>
</tr>
)
}
)}
</tbody>
</table>
</div>
</div>
}
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className='d-flex mb-3'>
<h6 className='align-self-end'>
Услуги врача
</h6>
<button
type="button"
className="btn btn-outline-success ml-auto"
onClick={ () => setModalKodopers(true) }
>
Добавить
</button>
</div>
{Boolean(kodopers) && kodopers.length > 0 &&
<TagKodoperStaticInput
tags={ kodopers }
disabled={true}
/>
}
{displayKodoper.length > 0 &&
<div className="mt-3">
<h6>
Добавленные услуги:
</h6>
<div className="table-responsive">
<table className="table table-hover table-bordered">
<thead>
<tr>
<th className='align-middle'>Мед. код</th>
<th className='align-middle'>Услуга</th>
<th className='align-middle'>Отделение</th>
<th className='align-middle'>Филиал</th>
<th className='align-middle'>Стоимость</th>
<th></th>
</tr>
</thead>
<tbody>
{ displayKodoper.map((item, index) =>
<tr key={index}>
<td>{ item.kodoper }</td>
<td>{ item.schname }</td>
<td>{ item.specname }</td>
<td>{ item.fname }</td>
<td>{ `${item.priceInfo.price}` }</td>
<td>
<button
className="btn btn-danger ml-auto"
onClick={ () => {
setKodopers(prev =>
prev.filter(kodoper => kodoper !== String(item.kodoper))
)
setDisplayKodoper(prev =>
prev.filter(dk => dk.kodoper !== String(item.kodoper))
)
}}
>
Удалить
</button>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
}
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className='d-flex mb-3'>
<h6 className='align-self-end'>
Акции
</h6>
<button
type="button"
className="btn btn-outline-success ml-auto"
onClick={ () => setModalStocks(true) }
>
Добавить
</button>
</div>
{ stocksFromChild.length > 0 &&
<div className="table-responsive">
<table className="table table-hover table-bordered">
<thead>
<tr>
<th className='align-middle'>Название</th>
<th className='align-middle'>Начало акции</th>
<th className='align-middle'>Окончание акции</th>
<th></th>
</tr>
</thead>
<tbody>
{
stocksFromChild.map((stock, index) =>
<tr key={index}>
<td>{ stock.name }</td>
<td>{ formatStockDate(stock.startDate) }</td>
<td>{ formatStockDate(stock.endDate) }</td>
<td className='text-center'>
<button
className="btn btn-danger ml-auto"
onClick={ () => {
setStocksFromChild(prev =>
prev.filter(({id}) => id !== stock.id)
)
}}
>
Удалить
</button>
</td>
</tr>
)
}
</tbody>
</table>
</div>
}
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-group">
<label className='mb-3'>
Анонс
</label>
{
/*
<input
className="form-control"
value={ form.anons }
onChange={e => updateField('anons', e.target.value)}
/>
*/
}
<TextEditor
content={anons}
setContent={setAnons}
/>
</div>
<div className="form-group">
<label className='mb-3'>
Детальное описание
</label>
{
/*
<input
className="form-control"
value={ form.content }
onChange={e => updateField('content', e.target.value)}
/>
*/
}
<TextEditor
content={content}
setContent={setContent}
/>
</div>
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-group">
<label>
Сертификаты
</label>
</div>
{
<CertificatesForm
initCertificates={ certificates }
onChange={setCertsFromChild}
/>
}
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-group">
<label>
Портфолио
</label>
</div>
{
<PortfolioForm
initPortfolios={ portfolio }
onChange={setPortfolioFromChild}
/>
}
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-group">
<label>
Теги
</label>
<TagInput
initialTags={ tags }
onChange={ setTags }
placeholder="Напишите и нажмите Enter"
/>
</div>
<div className="form-group">
<label>
Выделенные теги
</label>
<TagInput
initialTags={ highlightedTags }
onChange={ setHighlightedTags }
placeholder="Напишите и нажмите Enter"
/>
</div>
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-group">
<label>Видео</label>
<input
name="videoUrl"
className="form-control"
type="url"
value={ form.videoUrl }
onChange={ e => updateField('videoUrl', e.target.value) }
onBlur={handleBlur}
/>
{errors.videoUrl && <small className="text-danger">{errors.videoUrl}</small>}
</div>
<div className="form-group">
<label>Видеовизитка</label>
<input
name="videoCardUrl"
className="form-control"
type="url"
value={ form.videoCardUrl }
onChange={ e => updateField('videoCardUrl', e.target.value) }
onBlur={handleBlur}
/>
{errors.videoCardUrl && <small className="text-danger">{errors.videoCardUrl}</small>}
</div>
</div>
</div>
<div className="card mb-3 shadow">
<div className="card-body">
<div className="form-check mb-3">
<input
id="prodoctor"
type="checkbox"
className="form-check-input"
checked={ form.prodoctor }
onChange={e => {
updateField('prodoctor', e.target.checked)
if (!e.target.checked) {
updateField('prodoctorText', '')
updateField('prodoctorLink', '')
setErrors(prev => ({ ...prev, prodoctorLink: '' }));
}
}}
/>
<label htmlFor="prodoctor" className="form-check-label">
Награда "Продокторов"
</label>
</div>
{
form.prodoctor && <>
<div className="form-group"
style={{ visibility: form.prodoctor }}
>
<label>
Текст награды "Продокторов"
</label>
<input
className="form-control"
value={ form.prodoctorText }
onChange={e => updateField('prodoctorText', e.target.value)}
/>
</div>
<div className="form-group">
<label>
Ссылка награды "Продокторов"
</label>
<input
name='prodoctorLink'
className="form-control"
value={ form.prodoctorLink }
onChange={e =>{
updateField('prodoctorLink', e.target.value)
validateField('prodoctorLink', form.prodoctorLink)
}}
onBlur={handleBlur}
/>
{errors.prodoctorLink && <small className="text-danger">{errors.prodoctorLink}</small>}
</div>
</>
}
</div>
</div>
<Modal
isOpen={isModalDeleteOpen}
onCancel={() => setModalDeleteOpen(false)}
onConfirm={() => {
setModalDeleteOpen(false)
handleDelete()
}}
title="Вы уверены?"
hasButtons={true}
confirmText={'Удалить врача'}
>
<p className='mb-1'>Этим действием, вы удалите врача.</p>
<p className='mb-1'>Вы уверены?</p>
</Modal>
<Modal
isOpen={isModalSuccess}
title="Изменения внесены"
hasButtons={false}
confirmText={'Удалить врача'}
>
<p className='mb-1'>Изменения успешно внесены.</p>
</Modal>
<DcodeModal
isOpen={isModalDcodes}
initialSelectedItems={locations}
// onConfirm={setLocations}
onConfirm={setFromRawLocations}
onCancel={() => setModalDcodes(false)}
departments={departmentsList}
filials={filialsList}
/>
<KodoperModal
isOpen={isModalKodopers}
initialSelectedItems={kodopers}
onConfirm={(selectedCodopers, displayedCodopers) => {
setKodopers(selectedCodopers)
setDisplayKodoper(displayedCodopers)
}}
onCancel={() => setModalKodopers(false)}
/>
<StockModal
isOpen={isModalStocks}
initialSelectedItems={stocksFromChild}
onConfirm={(selectedStocks) => {
setStocks(selectedStocks)
}}
onCancel={() => setModalStocks(false)}
/>
</EditElementForm>
);
}