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

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