Files
mkd/components/applications/StatusChangeModal.tsx
2026-02-04 00:17:04 +05:00

199 lines
7.7 KiB
TypeScript
Executable File
Raw Permalink 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, Send, Calendar } from 'lucide-react';
import { DomaApplicationStatus } from '../../types';
interface Props {
isOpen: boolean;
onClose: () => void;
application: {
id: number;
number: string;
address: string;
currentStatus: DomaApplicationStatus;
};
newStatus: DomaApplicationStatus;
onConfirm: (comment: string, deferredUntil?: string) => Promise<void>;
}
export const StatusChangeModal: React.FC<Props> = ({
isOpen,
onClose,
application,
newStatus,
onConfirm,
}) => {
const [comment, setComment] = useState('');
const [deferredUntil, setDeferredUntil] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
if (!isOpen) return null;
const statusLabels: Record<DomaApplicationStatus, string> = {
new: 'Новая',
in_progress: 'В работу',
deferred: 'Отложить',
done: 'Завершить',
canceled: 'Отменить',
};
const statusMessages: Record<DomaApplicationStatus, string> = {
new: 'Укажите причину возврата заявки в статус "Новая"',
in_progress: 'Укажите комментарий для жителя о начале работы',
deferred: 'Укажите причину отложения и новую дату переноса заявки',
done: 'Укажите комментарий для жителя о выполнении работы',
canceled: 'Укажите причину отмены заявки',
};
const isReasonOnly = newStatus === 'canceled' || newStatus === 'deferred';
const commentLabel = isReasonOnly ? (newStatus === 'canceled' ? 'Причина отмены' : 'Причина отложения') : 'Комментарий для жителя';
const requiresDate = newStatus === 'deferred';
const requiresComment = true;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
if (requiresComment && !comment.trim()) {
setError('Комментарий обязателен для заполнения');
return;
}
if (requiresDate && !deferredUntil) {
setError('Укажите дату отложения заявки');
return;
}
setLoading(true);
try {
await onConfirm(comment.trim(), deferredUntil || undefined);
// Сброс формы после успешного подтверждения
setComment('');
setDeferredUntil('');
onClose();
} catch (err: any) {
setError(err.message || 'Ошибка при обновлении статуса заявки');
} finally {
setLoading(false);
}
};
const handleClose = () => {
if (!loading) {
setComment('');
setDeferredUntil('');
setError(null);
onClose();
}
};
// Минимальная дата - сегодня
const minDate = new Date().toISOString().split('T')[0];
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in">
<div className="bg-white rounded-3xl shadow-2xl max-w-md w-full max-h-[90vh] overflow-y-auto animate-scale-in">
{/* Header */}
<div className="sticky top-0 bg-white border-b border-slate-200 px-6 py-4 flex items-center justify-between rounded-t-3xl">
<div>
<h2 className="text-lg font-black text-slate-800">
{statusLabels[newStatus]} заявку
</h2>
<p className="text-xs text-slate-500 mt-1">
{application.number} {application.address}
</p>
</div>
<button
onClick={handleClose}
disabled={loading}
className="p-2 hover:bg-slate-100 rounded-xl transition-colors disabled:opacity-50"
>
<X className="w-5 h-5 text-slate-400" />
</button>
</div>
{/* Content */}
<form onSubmit={handleSubmit} className="p-6 space-y-4">
<div className="text-sm text-slate-600 bg-slate-50 p-3 rounded-xl">
{statusMessages[newStatus]}
</div>
{/* Причина / Комментарий */}
<div>
<label className="block text-xs font-black text-slate-700 mb-2 uppercase tracking-wider">
{commentLabel} {requiresComment && <span className="text-red-500">*</span>}
</label>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder={isReasonOnly ? 'Например: вопрос решил житель самостоятельно' : 'Введите комментарий...'}
required={requiresComment}
disabled={loading}
rows={4}
className="w-full px-4 py-3 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 resize-none disabled:opacity-50 disabled:cursor-not-allowed"
/>
</div>
{/* Дата отложения */}
{requiresDate && (
<div>
<label className="block text-xs font-black text-slate-700 mb-2 uppercase tracking-wider">
Дата отложения <span className="text-red-500">*</span>
</label>
<div className="relative">
<Calendar className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<input
type="date"
value={deferredUntil}
onChange={(e) => setDeferredUntil(e.target.value)}
min={minDate}
required
disabled={loading}
className="w-full pl-11 pr-4 py-3 border border-slate-200 rounded-xl text-sm outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 disabled:opacity-50 disabled:cursor-not-allowed"
/>
</div>
</div>
)}
{/* Ошибка */}
{error && (
<div className="bg-red-50 border border-red-200 text-red-600 text-sm px-4 py-3 rounded-xl">
{error}
</div>
)}
{/* Actions */}
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={handleClose}
disabled={loading}
className="flex-1 px-4 py-3 bg-slate-100 text-slate-700 font-black text-sm rounded-xl hover:bg-slate-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Отмена
</button>
<button
type="submit"
disabled={loading || (requiresComment && !comment.trim()) || (requiresDate && !deferredUntil)}
className="flex-1 px-4 py-3 bg-primary-600 text-white font-black text-sm rounded-xl hover:bg-primary-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{loading ? (
<>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
Сохранение...
</>
) : (
<>
<Send className="w-4 h-4" />
Подтвердить
</>
)}
</button>
</div>
</form>
</div>
</div>
);
};