Files
mkd/components/applications/OutageCardDetail.tsx

174 lines
7.6 KiB
TypeScript
Raw Normal View History

2026-02-04 00:17:04 +05:00
import React, { useState, useEffect } from 'react';
import { X, Calendar, MapPin, Zap, FileText, User } from 'lucide-react';
import { Outage } from '../../types';
import { backendApi } from '../../services/apiClient';
const WORK_TYPE_LABELS: Record<string, string> = {
absent: 'Отсутствует',
planned: 'Плановый',
emergency: 'Аварийный',
};
interface Props {
outageId: number;
onClose: () => void;
onUpdated?: () => void;
}
export const OutageCardDetail: React.FC<Props> = ({ outageId, onClose, onUpdated }) => {
const [outage, setOutage] = useState<Outage | null>(null);
const [loading, setLoading] = useState(true);
const loadOutage = () => {
backendApi.getOutage(outageId).then(setOutage).catch(() => setOutage(null));
};
useEffect(() => {
setLoading(true);
loadOutage();
setLoading(false);
}, [outageId]);
const handleSetInactive = async () => {
if (!outage) return;
try {
await backendApi.updateOutage(outageId, { active: false });
loadOutage();
onUpdated?.();
} catch (err) {
console.error(err);
}
};
if (loading || !outage) {
return (
<div className="fixed inset-0 z-[110] flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm animate-fade-in">
<div className="bg-white rounded-3xl w-full max-w-2xl p-12 text-center text-slate-500 shadow-2xl">
{loading ? 'Загрузка…' : 'Отключение не найдено'}
</div>
</div>
);
}
const formatDate = (s: string) => {
const d = new Date(s);
return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
};
const workTypeLabel = outage.workType ? (WORK_TYPE_LABELS[outage.workType] || outage.workType) : '—';
const classifierText = [outage.category, outage.problemDetail].filter(Boolean).join(' » ') || outage.type || '—';
const address = outage.buildingAddress || outage.buildingId;
const endDate = outage.endAt ? new Date(outage.endAt) : null;
const now = new Date();
const remainingMs = endDate && endDate > now ? endDate.getTime() - now.getTime() : null;
const remainingStr = remainingMs != null
? `${String(Math.floor(remainingMs / 3600000)).padStart(2, '0')}:${String(Math.floor((remainingMs % 3600000) / 60000)).padStart(2, '0')}`
: null;
return (
<div className="fixed inset-0 z-[110] flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm overflow-y-auto animate-fade-in">
<div className="bg-white rounded-3xl w-full max-w-2xl max-h-[95vh] overflow-y-auto shadow-2xl my-8 animate-scale-in">
{/* Header */}
<div className="sticky top-0 bg-white border-b border-slate-200 px-6 py-4 rounded-t-3xl z-10 flex justify-between items-start">
<div>
<h1 className="text-lg font-black text-slate-800">Отключение {outage.id}</h1>
<p className="text-xs text-slate-500 mt-1">
Дата создания {formatDate(outage.createdAt)}, автор {outage.authorName || 'Администратор'} (вы)
</p>
</div>
<div className="flex items-center gap-2">
{outage.active && (
<span className="inline-flex items-center gap-1 text-[10px] font-black uppercase tracking-wider text-rose-700 bg-rose-100 px-3 py-1.5 rounded-xl">
<Zap className="w-3.5 h-3.5" /> Актуально
</span>
)}
{!outage.active && (
<span className="inline-flex items-center gap-1 text-[10px] font-black uppercase tracking-wider text-slate-600 bg-slate-200 px-3 py-1.5 rounded-xl">
Завершено
</span>
)}
<button type="button" onClick={onClose} className="p-2 text-slate-400 hover:bg-slate-100 hover:text-slate-600 rounded-xl transition-colors">
<X className="w-5 h-5" />
</button>
</div>
</div>
<div className="px-6 py-4 space-y-4">
{/* Адрес */}
<div>
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-wider mb-1">Адрес</label>
<p className="text-sm font-medium text-slate-800 flex items-center gap-1">
<MapPin className="w-4 h-4 text-slate-400 shrink-0" />
<span className="text-primary-600 underline cursor-pointer hover:no-underline">{address}</span>
</p>
</div>
{/* Дата проведения работ */}
<div>
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-wider mb-1">Дата проведения работ</label>
<p className="text-sm font-medium text-slate-800 flex items-center gap-1 flex-wrap">
<Calendar className="w-4 h-4 text-slate-400 shrink-0" />
с {formatDate(outage.startAt)}
{outage.endAt && (
<>
{' '}до {formatDate(outage.endAt)}
{remainingStr && outage.active && (
<span className="text-orange-600 font-bold text-xs">(осталось {remainingStr})</span>
)}
</>
)}
</p>
</div>
{/* Классификатор */}
<div>
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-wider mb-1">Классификатор</label>
<p className="text-sm text-slate-800">{classifierText}</p>
</div>
{/* Тип работ */}
<div>
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-wider mb-1">Тип работ</label>
<p className="text-sm text-slate-800">{workTypeLabel}</p>
</div>
{/* Что случилось */}
<div>
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-wider mb-1">Что случилось</label>
<p className="text-sm text-slate-800 flex items-start gap-1">
<FileText className="w-4 h-4 text-slate-400 shrink-0 mt-0.5" />
{outage.description || '—'}
</p>
</div>
{/* Что сказать жителю */}
<div>
<label className="block text-[10px] font-black text-slate-400 uppercase tracking-wider mb-1">Что сказать жителю</label>
<p className="text-sm text-slate-800 flex items-start gap-1">
<User className="w-4 h-4 text-slate-400 shrink-0 mt-0.5" />
{outage.residentMessage || '—'}
</p>
</div>
</div>
{/* Действия */}
<div className="sticky bottom-0 bg-white border-t border-slate-200 px-6 py-4 rounded-b-3xl flex flex-wrap gap-2">
{outage.active && (
<button
type="button"
onClick={handleSetInactive}
className="px-4 py-2.5 bg-slate-100 text-slate-700 rounded-xl font-black text-xs uppercase tracking-wider hover:bg-slate-200 transition-colors"
>
Завершить отключение
</button>
)}
<button type="button" onClick={onClose} className="px-4 py-2.5 bg-primary-600 text-white rounded-xl font-black text-xs uppercase tracking-wider hover:bg-primary-700 transition-colors">
Закрыть
</button>
</div>
</div>
</div>
);
};