1610 lines
50 KiB
React
1610 lines
50 KiB
React
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>
|
||
);
|
||
}
|