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

213 lines
10 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 } from 'react';
import { X } from 'lucide-react';
import { backendApi } from '../../services/apiClient';
interface Props {
isOpen: boolean;
onClose: () => void;
onSuccess: () => void;
ossId: string;
ossAddress: string;
agendaItems?: string[];
}
export const AddBallotModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, ossId, ossAddress, agendaItems = [] }) => {
const [formData, setFormData] = useState({
apartment: '',
ownerName: '',
area: 0,
voteResult: '' as '' | 'for' | 'against' | 'abstain',
notes: '',
votesByItem: {} as Record<string, 'for' | 'against' | 'abstain' | ''>,
});
const [loading, setLoading] = useState(false);
const hasAgendaItems = Array.isArray(agendaItems) && agendaItems.length > 0;
if (!isOpen) return null;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.apartment || !formData.area) {
alert('Заполните обязательные поля: номер помещения и площадь');
return;
}
try {
setLoading(true);
const votesByItem: Record<string, string> = {};
if (hasAgendaItems && formData.votesByItem) {
Object.entries(formData.votesByItem).forEach(([idx, v]: [string, string]) => {
if (v && ['for', 'against', 'abstain'].includes(v)) votesByItem[idx] = v;
});
}
await backendApi.submitOSSBallot(ossId, {
apartment: formData.apartment,
owner_name: formData.ownerName || undefined,
area: formData.area,
vote_result: hasAgendaItems ? undefined : (formData.voteResult || undefined),
notes: formData.notes || undefined,
votes_by_item: Object.keys(votesByItem).length > 0 ? votesByItem : undefined,
});
window.dispatchEvent(new CustomEvent('mkd-oss-changed'));
onSuccess();
onClose();
setFormData({
apartment: '',
ownerName: '',
area: 0,
voteResult: '',
notes: '',
votesByItem: {},
});
} catch (error: any) {
console.error('Error submitting ballot:', error);
const errorMessage = 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-md shadow-2xl animate-slide-up"
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">
<div>
<h3 className="text-lg font-black text-slate-800">Внести бюллетень</h3>
<p className="text-xs text-slate-500 mt-1">{ossAddress}</p>
</div>
<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-4">
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">
Номер помещения *
</label>
<input
type="text"
required
value={formData.apartment}
onChange={(e) => setFormData({ ...formData, apartment: e.target.value })}
placeholder="15, 25А, 101"
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="text"
value={formData.ownerName}
onChange={(e) => setFormData({ ...formData, ownerName: e.target.value })}
placeholder="Иванов Иван Иванович"
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="number"
required
min="0"
step="0.01"
value={formData.area}
onChange={(e) => setFormData({ ...formData, area: parseFloat(e.target.value) || 0 })}
className="w-full p-2.5 rounded-xl border border-slate-200 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
/>
</div>
{hasAgendaItems ? (
<div className="space-y-2">
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">
Голос по пунктам повестки
</label>
{agendaItems.map((text, idx) => (
<div key={idx} className="flex items-center gap-2">
<span className="text-xs text-slate-600 w-6">{idx + 1}.</span>
<span className="text-xs text-slate-500 flex-1 truncate" title={text}>{text || 'Пункт ' + (idx + 1)}</span>
<select
value={formData.votesByItem[String(idx)] ?? ''}
onChange={(e) => setFormData({
...formData,
votesByItem: { ...formData.votesByItem, [String(idx)]: e.target.value as any },
})}
className="p-1.5 rounded-lg border border-slate-200 text-sm w-32"
>
<option value=""></option>
<option value="for">За</option>
<option value="against">Против</option>
<option value="abstain">Воздержался</option>
</select>
</div>
))}
</div>
) : (
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">
Результат голосования
</label>
<select
value={formData.voteResult}
onChange={(e) => setFormData({ ...formData, voteResult: e.target.value as any })}
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="">Не указано</option>
<option value="for">За</option>
<option value="against">Против</option>
<option value="abstain">Воздержался</option>
</select>
</div>
)}
<div>
<label className="text-[10px] text-slate-500 font-bold uppercase block mb-2">
Примечания
</label>
<textarea
value={formData.notes}
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
rows={2}
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>
);
};