299 lines
9.3 KiB
React
299 lines
9.3 KiB
React
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>
|
||
)
|
||
} |