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

174 lines
7.6 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, 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>
);
};