chore: initial import for test contour

This commit is contained in:
sova-bootstrap
2026-05-27 19:36:33 +03:00
commit ffd4cf9031
105 changed files with 10772 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
import { useState, useEffect, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { apiUrl } from '@/config/api';
import {
useGetStockQuery,
useUpdateStockMutation,
useUploadStockPictureMutation,
useDeleteStockMutation,
} from '/src/api/apiStock';
/**/
import { selectUtils } from '../store/slice/utilsSlice';
/**/
import { TextEditor } from '../components/Editors/TextEditor'
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 { ResponseModals } from '../components/Modals/ResponseModals';
/**/
import { Modal} from '../components/Modals/Modal';
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);
};
export function EditStockPage() {
const { id } = useParams();
const navigate = useNavigate();
const navigateBack = () => navigate( `/promotions` );
const { data: stock, isFetching, error } = useGetStockQuery( { stockId: id } );
const [ updateStock ] = useUpdateStockMutation();
const [ uploadPicture ] = useUploadStockPictureMutation();
const [ deleteStock ] = useDeleteStockMutation();
const [ isModalSuccess, setModalSuccess ] = useState( false );
const [ errors, setErrors ] = useState({
name: '',
dateRange: ''
});
const [ form, setForm ] = useState({
name: '',
anons: '',
picture: '',
});
const [ anons, setAnons ] = useState( '' );
const [ content, setContent ] = useState( '' );
const fileInputRef = useRef();
useEffect( () => {
if ( !stock ) return;
let datetimeLocalStart = '';
let datetimeLocalEnd = '';
if ( stock.startDate ) {
const dateStart = new Date( stock.startDate );
const yearStart = dateStart.getFullYear();
const monthStart = String( dateStart.getMonth() + 1 ).padStart( 2, '0' );
const dayStart = String( dateStart.getDate() ).padStart( 2, '0' );
const hoursStart = String( dateStart.getHours() ).padStart( 2, '0' );
const minutesStart = String( dateStart.getMinutes() ).padStart( 2, '0' );
datetimeLocalStart = `${ yearStart }-${ monthStart }-${ dayStart }T${ hoursStart }:${ minutesStart }`;
}
if ( stock.endDate ) {
const dateEnd = new Date( stock.endDate );
const yearEnd = dateEnd.getFullYear();
const monthEnd = String( dateEnd.getMonth() + 1 ).padStart( 2, '0' );
const dayEnd = String( dateEnd.getDate() ).padStart( 2, '0' );
const hoursEnd = String( dateEnd.getHours() ).padStart( 2, '0' );
const minutesEnd = String( dateEnd.getMinutes() ).padStart( 2, '0' );
datetimeLocalEnd = `${ yearEnd }-${ monthEnd }-${ dayEnd }T${ hoursEnd }:${ minutesEnd }`;
}
setForm(prev => ({
...prev,
name: stock.name ?? '',
startDate: datetimeLocalStart,
endDate: datetimeLocalEnd,
picture: stock.picture ? apiUrl(`/uploads/${stock.picture}`) : '',
}));
setAnons( stock.anons )
setContent( stock.content )
}, [ stock ]);
const startDateInputRef = useRef(null);
const endDateInputRef = useRef(null);
const handleChange = (key) => (e) => {
setForm(f => ({ ...f, [key]: e.target.value }));
setErrors(err => ({ ...err, [key === 'name' ? 'name' : 'dateRange']: '' }));
};
const handleFile = (e) => {
const file = e.target.files[0];
if (!file) return window.alert('Файл не выбран')
if (!isValidImage(file)) {
return window.alert('Изображения должны быть только формата JPG, JPEG или PNG');
}
const url = window.URL.createObjectURL(file);
setForm(f => ({ ...f, _file: file, picture: url }));
};
const handleSave = async () => {
let hasError = false;
const newErrors = { name:'', dateRange:'' };
if (!form.name.trim()) {
newErrors.name = 'Название не может быть пустым';
hasError = true;
}
const start = new Date(form.startDate);
const end = new Date(form.endDate);
if (isNaN(start) || isNaN(end) || start > end) {
newErrors.dateRange = 'Дата начал акции должна быть не позже даты окончания';
hasError = true;
}
if (hasError) {
setErrors(newErrors);
window.alert('Пожалуйста исправьте ошибки в форме.');
return;
}
try {
await updateStock({
stockId: id,
data: {
name: form.name,
anons: anons,
content: content,
startDate: String(form.startDate),
endDate: String(form.endDate),
}
}).unwrap();
if (form._file) {
await uploadPicture({ id, file: form._file }).unwrap();
}
} catch (err) {
console.error('Ошибка при обновлении акции:', err)
}
setModalSuccess(true)
window.setTimeout(() => {
window.location.reload()
}, 2000);
console.log('success update promotion')
};
const handleDelete = async () => {
try {
await deleteStock({stockId: id}).unwrap()
console.log('success delete')
setModalSuccess(true)
window.setTimeout(() => {
navigateBack()
}, 2000);
} catch (err) {
console.error('Ошибка при удалении специалиста:', err)
}
}
if ( isFetching ) return <LoadingComponent />
if ( error ) return <ErrorComponent />
if ( !stock ) return (
<NotFindElement
message={ `Акция с ID=${id} не найдена.` }
navigateBack={ navigateBack }
/>
)
return (
<EditElementForm
navigateBack={ navigateBack }
header={ `Редактирование акции #${ id }` }
handleSave={ handleSave }
handleDelete={ handleDelete }
>
<div className="form-group">
<label>Название</label>
<input
className="form-control"
value={form.name}
onChange={handleChange('name')}
/>
{errors.name && <small className="text-danger">{errors.name}</small>}
</div>
<div className='d-flex flex-column mb-1'>
<div className='d-flex justify-content-between' style={{ gap: '1rem'}}>
<div className="form-group flex-grow-1">
<label>Начало акции:</label>
<input
type="datetime-local"
ref={startDateInputRef}
className="form-control"
value={form.startDate}
onClick={() => {
if (startDateInputRef.current?.showPicker) {
startDateInputRef.current.showPicker();
}
}}
onChange={handleChange('startDate')}
/>
</div>
<div className="form-group flex-grow-1">
<label>Окончание акции:</label>
<input
type="datetime-local"
ref={endDateInputRef}
className="form-control"
value={form.endDate}
onClick={() => {
if (endDateInputRef.current?.showPicker) {
endDateInputRef.current.showPicker();
}
}}
onChange={handleChange('endDate')}
/>
</div>
</div>
{errors.dateRange && (
<div className="mb-3">
<small className="text-danger">{errors.dateRange}</small>
</div>
)}
<label className='mt-0'>*Время указывается по МСК</label>
</div>
<div className='d-flex justify-content-between' style={{ gap: '1rem'}}>
<div className="form-group flex-grow-1">
<label>Анонс</label>
<TextEditor
content={anons}
setContent={setAnons}
/>
</div>
<div className="form-group">
<label>Изображение</label>
<div className="d-flex align-items-center flex-column" style={{ gap: '1rem'}}>
{form.picture && (
<img
src={form.picture}
alt=""
style={{ width: '13rem', height: '13rem', objectFit: 'cover', marginRight: 16 }}
/>
)}
<button
className="btn btn-outline-primary"
onClick={() => fileInputRef.current.click()}
>
Загрузить
</button>
<input
type="file"
accept="image/jpeg,image/png"
ref={fileInputRef}
style={{ display: 'none' }}
onChange={handleFile}
/>
</div>
</div>
</div>
<div className="form-group">
<label>Полное описание</label>
<TextEditor
content={content}
setContent={setContent}
/>
</div>
<Modal
isOpen={isModalSuccess}
title="Изменения внесены"
hasButtons={false}
confirmText={'Удалить врача'}
>
<p className='mb-1'>Изменения успешно внесены.</p>
</Modal>
</EditElementForm>
)
}