108 lines
5.6 KiB
TypeScript
Executable File
108 lines
5.6 KiB
TypeScript
Executable File
|
||
import React, { useMemo } from 'react';
|
||
import { DomaApplication } from '../../types';
|
||
import { LayoutGrid, Building2, Inbox, AlertTriangle, ChevronRight, Users, Trash2, Pencil } from 'lucide-react';
|
||
|
||
const UNASSIGNED_MANAGER = 'Не назначен';
|
||
|
||
interface Props {
|
||
title: string;
|
||
subtitle?: string;
|
||
applications: DomaApplication[];
|
||
onClick: () => void;
|
||
type: 'district' | 'building';
|
||
buildingCount?: number;
|
||
staffCount?: number;
|
||
onDelete?: () => void;
|
||
onViewStaff?: () => void;
|
||
onEdit?: () => void;
|
||
}
|
||
|
||
export const PerformanceCard: React.FC<Props> = ({ title, subtitle, applications, onClick, type, buildingCount, staffCount, onDelete, onViewStaff, onEdit }) => {
|
||
const stats = useMemo(() => {
|
||
const now = new Date();
|
||
const active = applications.filter(a => a.status === 'new' || a.status === 'in_progress');
|
||
const overdue = active.filter(a => a.deadlineAt && new Date(a.deadlineAt) < now);
|
||
const newApps = applications.filter(a => a.status === 'new');
|
||
const performance = active.length > 0 ? Math.round((1 - (overdue.length / active.length)) * 100) : 100;
|
||
return { overdue, newApps, performance };
|
||
}, [applications]);
|
||
|
||
const perfColor = stats.performance > 85 ? 'text-emerald-500' : stats.performance > 60 ? 'text-amber-500' : 'text-red-500';
|
||
const Icon = type === 'district' ? LayoutGrid : Building2;
|
||
|
||
return (
|
||
<div
|
||
onClick={onClick}
|
||
className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow cursor-pointer active:scale-[0.99] flex gap-4 items-center group relative"
|
||
style={{ position: 'relative' }}
|
||
>
|
||
<div className="p-3 bg-primary-50 rounded-lg text-primary-600 hidden sm:block">
|
||
<Icon className="w-6 h-6"/>
|
||
</div>
|
||
<div className="flex-1 min-w-0 pr-12">
|
||
<h4 className="font-bold text-slate-800 truncate">{title}</h4>
|
||
{(subtitle != null && subtitle !== '') && (
|
||
<p className={`text-xs truncate ${subtitle === UNASSIGNED_MANAGER ? 'text-red-600 font-semibold' : 'text-slate-500'}`}>{subtitle}</p>
|
||
)}
|
||
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2 text-xs">
|
||
{type === 'district' && (
|
||
<div className="flex items-center gap-1.5 text-slate-500">
|
||
<Building2 className="w-3.5 h-3.5" /> {buildingCount} домов
|
||
</div>
|
||
)}
|
||
<div className="flex items-center gap-1.5 text-slate-500">
|
||
<Inbox className="w-3.5 h-3.5 text-blue-500" /> <span className="font-bold text-slate-700">{stats.newApps.length}</span> новых
|
||
</div>
|
||
<div className="flex items-center gap-1.5 text-slate-500">
|
||
<AlertTriangle className="w-3.5 h-3.5 text-red-500" /> <span className="font-bold text-slate-700">{stats.overdue.length}</span> просрочено
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="text-right flex items-center gap-4">
|
||
{type === 'district' && onViewStaff && (
|
||
<div className="flex items-center gap-2">
|
||
{staffCount !== undefined && (
|
||
<span className="text-sm font-bold text-slate-600 bg-slate-100 px-2 py-1 rounded-lg">
|
||
{staffCount}
|
||
</span>
|
||
)}
|
||
<button onClick={(e) => { e.stopPropagation(); onViewStaff(); }} className="p-2 bg-slate-50 hover:bg-primary-50 text-slate-400 hover:text-primary-600 rounded-lg transition-colors border border-slate-100">
|
||
<Users className="w-5 h-5"/>
|
||
</button>
|
||
</div>
|
||
)}
|
||
<div className="hidden md:block">
|
||
<p className={`text-2xl font-black ${perfColor}`}>{stats.performance}%</p>
|
||
<p className="text-[10px] text-slate-400 font-bold uppercase">Успеваемость</p>
|
||
</div>
|
||
<ChevronRight className="w-5 h-5 text-slate-300 group-hover:text-primary-400 transition-colors" />
|
||
</div>
|
||
|
||
<div className="absolute top-2 right-2 flex items-center gap-1 z-30">
|
||
{type === 'district' && onEdit && (
|
||
<button
|
||
onClick={(e) => { e.stopPropagation(); e.preventDefault(); onEdit(); }}
|
||
className="p-1.5 bg-white hover:bg-primary-50 text-slate-400 hover:text-primary-600 rounded-full transition-all border border-slate-200 shadow-md hover:shadow-lg"
|
||
title="Редактировать участок"
|
||
type="button"
|
||
>
|
||
<Pencil className="w-3.5 h-3.5" />
|
||
</button>
|
||
)}
|
||
{onDelete && (
|
||
<button
|
||
onClick={(e) => { e.stopPropagation(); e.preventDefault(); onDelete(); }}
|
||
className="p-1.5 bg-white hover:bg-red-50 text-slate-400 hover:text-red-500 rounded-full transition-all border border-slate-200 shadow-md hover:shadow-lg"
|
||
title="Удалить"
|
||
type="button"
|
||
>
|
||
<Trash2 className="w-3.5 h-3.5" />
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|