148 lines
9.6 KiB
TypeScript
148 lines
9.6 KiB
TypeScript
|
|
|
|||
|
|
import React, { useState } from 'react';
|
|||
|
|
import { Building } from '../../types';
|
|||
|
|
import { generateBuildingAudit } from '../../services/geminiService';
|
|||
|
|
import {
|
|||
|
|
Users,
|
|||
|
|
Wallet,
|
|||
|
|
Activity,
|
|||
|
|
TrendingDown,
|
|||
|
|
Bot,
|
|||
|
|
Sparkles,
|
|||
|
|
CheckCircle2,
|
|||
|
|
Calendar
|
|||
|
|
} from 'lucide-react';
|
|||
|
|
|
|||
|
|
type TabType = 'overview' | 'finance' | 'inspections' | 'residents' | 'passport' | 'accounts';
|
|||
|
|
|
|||
|
|
export const OverviewView: React.FC<{ building: Building, onNavigate: (tab: TabType) => void }> = ({ building, onNavigate }) => {
|
|||
|
|
const [auditResult, setAuditResult] = useState<string | null>(null);
|
|||
|
|
const [loadingAudit, setLoadingAudit] = useState(false);
|
|||
|
|
|
|||
|
|
const handleAudit = async () => {
|
|||
|
|
setLoadingAudit(true);
|
|||
|
|
const res = await generateBuildingAudit(building);
|
|||
|
|
setAuditResult(res);
|
|||
|
|
setLoadingAudit(false);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="space-y-6 animate-fade-in">
|
|||
|
|
{/* Main Info Card */}
|
|||
|
|
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
|||
|
|
<div className="h-48 w-full bg-slate-200 relative">
|
|||
|
|
<img src={building.imageUrl} alt="Building" className="w-full h-full object-cover" />
|
|||
|
|
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-6">
|
|||
|
|
<div className="flex justify-between items-end text-white">
|
|||
|
|
<div>
|
|||
|
|
<h2 className="text-2xl font-bold">{building.passport.address}</h2>
|
|||
|
|
<p className="text-sm opacity-90">{building.passport.general.constructionYear} г. • {building.passport.general.floors} эт. • {building.passport.general.totalArea} м²</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-right">
|
|||
|
|
<p className="text-sm opacity-75">NPS</p>
|
|||
|
|
<p className={`text-3xl font-black ${building.nps >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>{building.nps > 0 ? '+' : ''}{building.nps}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="p-4 grid grid-cols-2 md:grid-cols-4 gap-4 divide-x divide-slate-100">
|
|||
|
|
<div className="text-center px-2 cursor-pointer hover:bg-slate-50 transition-colors rounded" onClick={() => onNavigate('residents')}>
|
|||
|
|
<p className="text-slate-400 text-xs font-bold uppercase">Жителей</p>
|
|||
|
|
<p className="text-xl font-black text-slate-800 flex items-center justify-center gap-1">
|
|||
|
|
<Users className="w-4 h-4 text-slate-400"/> {building.accounts.reduce((sum, acc) => {
|
|||
|
|
const count = acc.registered?.length || acc.registeredCount || 0;
|
|||
|
|
return sum + (typeof count === 'number' ? count : 0);
|
|||
|
|
}, 0)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-center px-2 cursor-pointer hover:bg-slate-50 transition-colors rounded" onClick={() => onNavigate('finance')}>
|
|||
|
|
<p className="text-slate-400 text-xs font-bold uppercase">Баланс</p>
|
|||
|
|
<p className={`text-xl font-black flex items-center justify-center gap-1 ${building.financials.balance >= 0 ? 'text-emerald-600' : 'text-red-600'}`}>
|
|||
|
|
<Wallet className="w-4 h-4 text-slate-400"/> {(building.financials.balance / 1000).toFixed(0)}k
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-center px-2 cursor-pointer hover:bg-slate-50 transition-colors rounded" onClick={() => onNavigate('inspections')}>
|
|||
|
|
<p className="text-slate-400 text-xs font-bold uppercase">Состояние</p>
|
|||
|
|
<p className="text-xl font-black text-slate-800 flex items-center justify-center gap-1">
|
|||
|
|
<Activity className="w-4 h-4 text-amber-500"/> Удовл.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-center px-2 cursor-pointer hover:bg-slate-50 transition-colors rounded" onClick={() => onNavigate('passport')}>
|
|||
|
|
<p className="text-slate-400 text-xs font-bold uppercase">Износ</p>
|
|||
|
|
<p className="text-xl font-black text-slate-800 flex items-center justify-center gap-1">
|
|||
|
|
<TrendingDown className="w-4 h-4 text-slate-400"/> 35%
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* AI Audit Section */}
|
|||
|
|
<div className="bg-gradient-to-br from-indigo-600 to-violet-700 rounded-2xl shadow-xl text-white p-6 relative overflow-hidden">
|
|||
|
|
<div className="absolute top-0 right-0 p-8 opacity-10">
|
|||
|
|
<Bot className="w-32 h-32" />
|
|||
|
|
</div>
|
|||
|
|
<h3 className="text-lg font-bold flex items-center gap-2 mb-2"><Sparkles className="w-5 h-5 text-yellow-300"/> Анализ Дома</h3>
|
|||
|
|
<p className="text-indigo-100 text-sm mb-4 max-w-lg">Краткая сводка состояния дома, финансовых показателей и рекомендаций по эксплуатации на основе данных паспорта и истории осмотров.</p>
|
|||
|
|
|
|||
|
|
{!auditResult ? (
|
|||
|
|
<button
|
|||
|
|
onClick={handleAudit}
|
|||
|
|
disabled={loadingAudit}
|
|||
|
|
className="bg-white text-indigo-600 px-4 py-2 rounded-xl font-bold text-sm shadow-lg hover:bg-indigo-50 transition-transform active:scale-95 flex items-center gap-2 disabled:opacity-70"
|
|||
|
|
>
|
|||
|
|
{loadingAudit ? 'Анализирую...' : 'Сгенерировать отчет'}
|
|||
|
|
</button>
|
|||
|
|
) : (
|
|||
|
|
<div className="bg-white/10 backdrop-blur-md rounded-xl p-4 text-sm animate-fade-in border border-white/20">
|
|||
|
|
<div className="prose prose-invert prose-sm max-w-none">
|
|||
|
|
<pre className="whitespace-pre-wrap font-sans">{auditResult}</pre>
|
|||
|
|
</div>
|
|||
|
|
<button onClick={() => setAuditResult(null)} className="mt-3 text-xs text-indigo-200 hover:text-white underline">Скрыть отчет</button>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Tasks Widget */}
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|||
|
|
<div className="bg-white p-5 rounded-xl border border-slate-200 shadow-sm">
|
|||
|
|
<h3 className="font-bold text-slate-700 mb-4 flex items-center gap-2"><CheckCircle2 className="w-5 h-5 text-primary-500"/> Текущие задачи</h3>
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
{building.tasks.map(task => (
|
|||
|
|
<div key={task.id} className="flex items-start gap-3 p-2 hover:bg-slate-50 rounded-lg transition-colors cursor-pointer">
|
|||
|
|
<div className={`mt-1 w-4 h-4 rounded-full border-2 ${task.status === 'done' ? 'bg-emerald-500 border-emerald-500' : 'border-slate-300'}`}></div>
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<p className={`text-sm font-medium ${task.status === 'done' ? 'text-slate-400 line-through' : 'text-slate-800'}`}>{task.title}</p>
|
|||
|
|
<p className="text-xs text-slate-500 flex items-center gap-2 mt-1">
|
|||
|
|
<Calendar className="w-3 h-3"/> {task.deadline}
|
|||
|
|
<span className={`px-1.5 py-0.5 rounded text-[10px] font-bold uppercase ${task.priority === 'high' ? 'bg-red-100 text-red-600' : 'bg-slate-100 text-slate-500'}`}>{task.priority}</span>
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
<button className="w-full py-2 text-xs font-bold text-primary-600 border border-dashed border-primary-200 rounded-lg hover:bg-primary-50 mt-2">
|
|||
|
|
+ Добавить задачу
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="bg-white p-5 rounded-xl border border-slate-200 shadow-sm">
|
|||
|
|
<h3 className="font-bold text-slate-700 mb-4 flex items-center gap-2"><Calendar className="w-5 h-5 text-primary-500"/> План работ (Май)</h3>
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
{building.annualPlan.map(plan => (
|
|||
|
|
<div key={plan.id}>
|
|||
|
|
<div className="flex justify-between text-xs mb-1">
|
|||
|
|
<span className="font-bold text-slate-700">{plan.workName}</span>
|
|||
|
|
<span className="text-slate-500">{plan.progress}%</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="w-full bg-slate-100 rounded-full h-2">
|
|||
|
|
<div className={`h-2 rounded-full ${plan.progress === 100 ? 'bg-emerald-500' : 'bg-primary-500'}`} style={{ width: `${plan.progress}%` }}></div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|