220 lines
8.6 KiB
TypeScript
220 lines
8.6 KiB
TypeScript
import React from 'react';
|
||
import {Plant, Task, MaintenanceRecord, HarvestRecord, PlantTitles, Maintenances} from '../types';
|
||
import { Calendar, AlertTriangle, CheckCircle, Sprout, Scissors, Droplets } from 'lucide-react';
|
||
|
||
interface DashboardProps {
|
||
plants: Plant[];
|
||
tasks: Task[];
|
||
maintenanceRecords: MaintenanceRecord[];
|
||
harvestRecords: HarvestRecord[];
|
||
onNavigate: (tab: string) => void;
|
||
}
|
||
|
||
const Dashboard: React.FC<DashboardProps> = ({
|
||
plants,
|
||
tasks,
|
||
maintenanceRecords,
|
||
harvestRecords,
|
||
onNavigate
|
||
}) => {
|
||
const upcomingTasks = tasks
|
||
.filter(task => !task.completed && new Date(task.deadline) >= new Date())
|
||
.sort((a, b) => new Date(a.deadline).getTime() - new Date(b.deadline).getTime())
|
||
.slice(0, 5);
|
||
|
||
const plantsNeedingAttention = plants.filter(plant => plant.healthStatus === 'needs-attention');
|
||
|
||
const recentMaintenanceRecords = maintenanceRecords
|
||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||
.slice(0, 5);
|
||
|
||
const stats = [
|
||
{
|
||
label: 'Всего растений',
|
||
value: plants.length,
|
||
icon: Sprout,
|
||
color: 'text-green-600',
|
||
bgColor: 'bg-green-100'
|
||
},
|
||
{
|
||
label: 'Отложенные работы',
|
||
value: tasks.filter(task => !task.completed).length,
|
||
icon: Calendar,
|
||
color: 'text-blue-600',
|
||
bgColor: 'bg-blue-100'
|
||
},
|
||
{
|
||
label: 'Требует внимания',
|
||
value: plantsNeedingAttention.length,
|
||
icon: AlertTriangle,
|
||
color: 'text-yellow-600',
|
||
bgColor: 'bg-yellow-100'
|
||
},
|
||
{
|
||
label: ' Урожай этого года',
|
||
value: harvestRecords.filter(record =>
|
||
new Date(record.date).getFullYear() === new Date().getFullYear()
|
||
).length,
|
||
icon: CheckCircle,
|
||
color: 'text-purple-600',
|
||
bgColor: 'bg-purple-100'
|
||
}
|
||
];
|
||
|
||
const getMaintenanceIcon = (type: string) => {
|
||
switch (type) {
|
||
case 'pruning': return Scissors;
|
||
case 'watering': return Droplets;
|
||
default: return Calendar;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-8">
|
||
<div>
|
||
<h2 className="text-3xl font-bold text-green-800 mb-2">Сводка по саду</h2>
|
||
<p className="text-green-600">Добро пожаловать в мой центр управления садом</p>
|
||
</div>
|
||
|
||
{/* Stats Grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
{stats.map((stat, index) => {
|
||
const Icon = stat.icon;
|
||
return (
|
||
<div key={index} className="bg-white rounded-lg shadow-sm border border-green-100 p-6 hover:shadow-md transition-shadow">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-gray-600">{stat.label}</p>
|
||
<p className="text-3xl font-bold text-gray-900 mt-1">{stat.value}</p>
|
||
</div>
|
||
<div className={`${stat.bgColor} p-3 rounded-lg`}>
|
||
<Icon className={`h-6 w-6 ${stat.color}`} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||
{/* Upcoming Tasks */}
|
||
<div className="bg-white rounded-lg shadow-sm border border-green-100">
|
||
<div className="p-6 border-b border-green-100">
|
||
<div className="flex justify-between items-center">
|
||
<h3 className="text-lg font-semibold text-green-800">Предстоящие работы</h3>
|
||
<button
|
||
onClick={() => onNavigate('tasks')}
|
||
className="text-sm text-green-600 hover:text-green-700 transition-colors"
|
||
>
|
||
Посмотреть все →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="p-6">
|
||
{upcomingTasks.length > 0 ? (
|
||
<div className="space-y-4">
|
||
{upcomingTasks.map((task) => {
|
||
const plant = plants.find(p => p.id === task.plantId);
|
||
const isOverdue = new Date(task.deadline) < new Date();
|
||
return (
|
||
<div key={task.id} className="flex items-start space-x-3 p-3 bg-green-50 rounded-lg">
|
||
<Calendar className={`h-5 w-5 mt-0.5 ${isOverdue ? 'text-red-500' : 'text-green-600'}`} />
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium text-gray-900">{task.title}</p>
|
||
{plant && (
|
||
<p className="text-sm text-gray-600">{plant.variety}</p>
|
||
)}
|
||
<p className={`text-sm ${isOverdue ? 'text-red-600' : 'text-gray-500'}`}>
|
||
Срок: {new Date(task.deadline).toLocaleDateString()}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
) : (
|
||
<p className="text-gray-500 text-center py-8">Предстоящих работ нет</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Plants Needing Attention */}
|
||
<div className="bg-white rounded-lg shadow-sm border border-green-100">
|
||
<div className="p-6 border-b border-green-100">
|
||
<div className="flex justify-between items-center">
|
||
<h3 className="text-lg font-semibold text-green-800">Растения, требующие внимания</h3>
|
||
<button
|
||
onClick={() => onNavigate('plants')}
|
||
className="text-sm text-green-600 hover:text-green-700 transition-colors"
|
||
>
|
||
Посмотреть все →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="p-6">
|
||
{plantsNeedingAttention.length > 0 ? (
|
||
<div className="space-y-4">
|
||
{plantsNeedingAttention.map((plant) => (
|
||
<div key={plant.id} className="flex items-start space-x-3 p-3 bg-yellow-50 rounded-lg">
|
||
<AlertTriangle className="h-5 w-5 text-yellow-600 mt-0.5" />
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium text-gray-900">{plant.variety}</p>
|
||
<p className="text-sm text-gray-600 capitalize">{PlantTitles[plant.type]}</p>
|
||
{plant.notes && (
|
||
<p className="text-sm text-gray-500 mt-1">{plant.notes}</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<p className="text-gray-500 text-center py-8">All plants are healthy!</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Recent Maintenance */}
|
||
<div className="bg-white rounded-lg shadow-sm border border-green-100">
|
||
<div className="p-6 border-b border-green-100">
|
||
<div className="flex justify-between items-center">
|
||
<h3 className="text-lg font-semibold text-green-800">Недавние работы</h3>
|
||
<button
|
||
onClick={() => onNavigate('maintenance')}
|
||
className="text-sm text-green-600 hover:text-green-700 transition-colors"
|
||
>
|
||
Посмотреть все →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="p-6">
|
||
{recentMaintenanceRecords.length > 0 ? (
|
||
<div className="space-y-4">
|
||
{recentMaintenanceRecords.map((record) => {
|
||
const plant = plants.find(p => p.id === record.plantId);
|
||
const Icon = getMaintenanceIcon(record.type);
|
||
return (
|
||
<div key={record.id} className="flex items-start space-x-3 p-3 bg-green-50 rounded-lg">
|
||
<Icon className="h-5 w-5 text-green-600 mt-0.5" />
|
||
<div className="flex-1 min-w-0">
|
||
<p className="font-medium text-gray-900 capitalize">{Maintenances[record.type]}</p>
|
||
<p className="text-sm text-gray-600">{plant?.variety}</p>
|
||
<p className="text-sm text-gray-500">{record.description}</p>
|
||
<p className="text-xs text-gray-400 mt-1">
|
||
{new Date(record.date).toLocaleDateString()}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
) : (
|
||
<p className="text-gray-500 text-center py-8">Записей о выполненных работах пока нет</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Dashboard; |