Files
mkd/components/development/EditOSSModal.tsx
2026-02-04 00:17:04 +05:00

228 lines
12 KiB
TypeScript
Executable File
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 React, { useState, useEffect } from 'react';
import { X, Plus, Trash2 } from 'lucide-react';
import { DevOSSSession } from '../../types';
import { backendApi } from '../../services/apiClient';
interface Props {
isOpen: boolean;
onClose: () => void;
onSuccess: () => void;
session: DevOSSSession | null;
}
export const EditOSSModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, session }) => {
const [formData, setFormData] = useState({
address: '',
startDate: '',
endDate: '',
status: 'planned' as 'active' | 'planned' | 'completed',
type: 'extraordinary' as 'annual' | 'extraordinary',
description: '',
agendaItems: [] as string[],
});
const [loading, setLoading] = useState(false);
useEffect(() => {
if (isOpen && session) {
setFormData({
address: session.address || '',
startDate: session.startDate ? session.startDate.slice(0, 10) : '',
endDate: session.endDate ? session.endDate.slice(0, 10) : '',
status: session.status || 'planned',
type: session.type || 'extraordinary',
description: (session.description ?? '') || '',
agendaItems: Array.isArray(session.agendaItems) && session.agendaItems.length > 0
? [...session.agendaItems]
: [''],
});
}
}, [isOpen, session]);
if (!isOpen || !session) return null;
const defaultEndDate = formData.startDate
? new Date(new Date(formData.startDate).getTime() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
: '';
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const address = formData.address?.trim();
const startDate = formData.startDate?.trim();
const endDate = formData.endDate?.trim() || defaultEndDate;
if (!address || !startDate || !endDate) {
alert('Заполните обязательные поля: адрес, дата начала, дата окончания');
return;
}
if (new Date(endDate) <= new Date(startDate)) {
alert('Дата окончания должна быть позже даты начала');
return;
}
try {
setLoading(true);
const agenda = formData.agendaItems.filter((t) => t.trim()).length > 0
? formData.agendaItems.filter((t) => t.trim())
: undefined;
await backendApi.updateDevelopmentOSS(session.id, {
address,
startDate,
endDate,
status: formData.status,
type: formData.type,
description: formData.description?.trim() || null,
agendaItems: agenda,
});
window.dispatchEvent(new CustomEvent('mkd-oss-changed'));
window.dispatchEvent(new CustomEvent('mkd-dev-summary-changed'));
onClose();
onSuccess();
} catch (error: any) {
console.error('Error updating OSS:', error);
const errorMessage = error?.response?.data?.error || error?.message || error?.error || 'Ошибка при сохранении ОСС';
alert(errorMessage);
} finally {
setLoading(false);
}
};
return (
<div
className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-slate-900/80 backdrop-blur-md animate-fade-in"
onClick={onClose}
>
<div
className="bg-white rounded-2xl w-full max-w-2xl shadow-2xl animate-slide-up max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="sticky top-0 bg-white border-b border-slate-200 px-6 py-4 flex justify-between items-center">
<h3 className="text-lg font-black text-slate-800">Редактировать ОСС</h3>
<button onClick={onClose} className="p-2 hover:bg-slate-100 rounded-xl transition-colors">
<X className="w-5 h-5 text-slate-400" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-6">
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">Адрес *</label>
<input
type="text"
required
value={formData.address}
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
placeholder="ул. Примерная, д. 1"
className="w-full p-2.5 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
/>
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">Тип собрания</label>
<select
value={formData.type}
onChange={(e) => setFormData({ ...formData, type: e.target.value as 'annual' | 'extraordinary' })}
className="w-full p-2.5 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
>
<option value="annual">Ежегодное отчетное собрание</option>
<option value="extraordinary">Внеочередное ОСС</option>
</select>
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">Статус</label>
<select
value={formData.status}
onChange={(e) => setFormData({ ...formData, status: e.target.value as 'active' | 'planned' | 'completed' })}
className="w-full p-2.5 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
>
<option value="planned">Запланировано</option>
<option value="active">Идёт голосование</option>
<option value="completed">Завершено</option>
</select>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">Дата начала *</label>
<input
type="date"
required
value={formData.startDate}
onChange={(e) => setFormData({ ...formData, startDate: e.target.value })}
className="w-full p-2.5 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
/>
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">Дата окончания *</label>
<input
type="date"
required
value={formData.endDate || (formData.startDate ? defaultEndDate : '')}
onChange={(e) => setFormData({ ...formData, endDate: e.target.value })}
min={formData.startDate || undefined}
className="w-full p-2.5 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
/>
</div>
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">Пункты повестки</label>
<div className="space-y-2">
{(formData.agendaItems.length === 0 ? [''] : formData.agendaItems).map((text, idx) => (
<div key={idx} className="flex gap-2 items-center">
<span className="text-[10px] font-bold text-slate-400 w-6">{idx + 1}.</span>
<input
type="text"
value={text}
onChange={(e) => {
const next = [...(formData.agendaItems.length ? formData.agendaItems : [''])];
next[idx] = e.target.value;
setFormData({ ...formData, agendaItems: next });
}}
placeholder="Формулировка пункта"
className="flex-1 p-2 rounded-xl border border-slate-200 text-sm"
/>
<button
type="button"
onClick={() => {
const next = formData.agendaItems.length ? formData.agendaItems.filter((_, i) => i !== idx) : [];
setFormData({ ...formData, agendaItems: next });
}}
className="p-2 text-slate-400 hover:text-red-600"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))}
<button
type="button"
onClick={() => setFormData({ ...formData, agendaItems: [...(formData.agendaItems.length ? formData.agendaItems : ['']), ''] })}
className="flex items-center gap-2 text-xs font-bold text-primary-600 hover:text-primary-700"
>
<Plus className="w-4 h-4" /> Добавить пункт
</button>
</div>
</div>
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">Описание</label>
<textarea
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={2}
placeholder="Внеочередное ОСС по вопросу смены УК"
className="w-full p-2.5 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none resize-none"
/>
</div>
<div className="flex gap-3 justify-end pt-4 border-t border-slate-200">
<button type="button" onClick={onClose} className="px-6 py-2.5 bg-slate-100 text-slate-700 rounded-xl text-sm font-bold hover:bg-slate-200 transition-colors">
Отмена
</button>
<button type="submit" disabled={loading} className="px-6 py-2.5 bg-primary-600 text-white rounded-xl text-sm font-bold hover:bg-primary-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
{loading ? 'Сохранение...' : 'Сохранить'}
</button>
</div>
</form>
</div>
</div>
);
};