lawnmowing/src/components/ZoneCard.tsx

177 lines
5.8 KiB
TypeScript
Raw 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 from 'react';
import { Scissors, Edit, Trash2, Calendar, Camera, Square, Clock, AlertTriangle } from './Icons';
import { Zone } from '../types/zone';
interface ZoneCardProps {
zone: Zone;
onMarkAsMowed: (zone: Zone) => void;
onEdit: (zone: Zone) => void;
onDelete: (id: number) => void;
}
const ZoneCard: React.FC<ZoneCardProps> = ({
zone,
onMarkAsMowed,
onEdit,
onDelete,
}) => {
const getStatusColor = (status: string) => {
switch (status) {
case 'overdue':
return 'border-red-500 bg-red-50';
case 'due':
return 'border-orange-500 bg-orange-50';
case 'new':
return 'border-blue-500 bg-blue-50';
default:
return 'border-green-500 bg-green-50';
}
};
const getStatusText = (zone: Zone) => {
if (zone.isNew) {
return 'Еще не косилась';
} else if (zone.isOverdue) {
return `${Math.abs(zone.daysUntilNext!)} дней посроченно`;
} else if (zone.isDueToday) {
return 'Срок - сегодня';
} else {
return `${zone.daysUntilNext} дней осталось`;
}
};
const getStatusTextColor = (status: string) => {
switch (status) {
case 'overdue':
return 'text-red-700';
case 'due':
return 'text-orange-700';
case 'new':
return 'text-blue-700';
default:
return 'text-green-700';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'overdue':
return <AlertTriangle className="h-4 w-4 text-red-600" />;
case 'due':
return <Clock className="h-4 w-4 text-orange-600" />;
case 'new':
return <Calendar className="h-4 w-4 text-blue-600" />;
default:
return <Scissors className="h-4 w-4 text-green-600" />;
}
};
const formatDate = (dateString: string) => {
if (!dateString) return 'Никогда';
return new Date(dateString).toLocaleDateString('ru-RU', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
};
const formatArea = (area: number) => {
if (area === 0) return 'Not specified';
return `${area.toLocaleString()} м2`;
};
const getScheduleText = (zone: Zone) => {
if (zone.scheduleType === 'specific') {
if (zone.nextMowDate) {
return `Next: ${formatDate(zone.nextMowDate)}`;
}
return 'Specific date scheduling';
} else {
return zone.intervalDays ? `Каждые ${zone.intervalDays} дней` : 'Не запланированно';
}
};
return (
<div className={`bg-white rounded-lg shadow-md border-l-4 ${getStatusColor(zone.status)} hover:shadow-lg transition-shadow duration-200`}>
{/* Zone Image */}
{zone.imagePath ? (
<div className="relative h-48 overflow-hidden rounded-t-lg">
<img
src={`http://localhost:3001${zone.imagePath}`}
alt={zone.name}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-black bg-opacity-20"></div>
</div>
) : (
<div className="h-48 bg-gray-100 rounded-t-lg flex items-center justify-center">
<div className="text-center text-gray-400">
<Camera className="h-12 w-12 mx-auto mb-2" />
<p className="text-sm">Нет изображения</p>
</div>
</div>
)}
<div className="p-6">
{/* Zone Name and Status */}
<div className="mb-4">
<h3 className="text-lg font-semibold text-gray-900 mb-2">{zone.name}</h3>
<div className={`flex items-center gap-2 text-sm font-medium ${getStatusTextColor(zone.status)}`}>
{getStatusIcon(zone.status)}
<span>{getStatusText(zone)}</span>
</div>
</div>
{/* Zone Details */}
<div className="space-y-2 mb-4 text-sm text-gray-600">
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4" />
<span>Была скошена: {zone.lastMowedDate ? formatDate(zone.lastMowedDate) : '—'}</span>
</div>
<div className="flex items-center gap-2">
<Clock className="h-4 w-4" />
<span>{getScheduleText(zone)}</span>
</div>
<div className="flex items-center gap-2">
<Square className="h-4 w-4" />
<span>Площадь: {formatArea(zone.area)}</span>
</div>
</div>
{/* Action Buttons */}
<div className="flex gap-2">
<button
onClick={() => onMarkAsMowed(zone)}
disabled={zone.status === 'ok' && zone.daysUntilNext! > 1}
className={`flex-1 py-2 px-3 rounded-md text-sm font-medium transition-colors duration-200 ${
zone.status === 'ok' && zone.daysUntilNext! > 1
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: zone.isNew
? 'bg-blue-600 hover:bg-blue-700 text-white shadow-sm hover:shadow-md'
: 'bg-green-600 hover:bg-green-700 text-white shadow-sm hover:shadow-md'
}`}
>
<Scissors className="h-4 w-4 inline mr-1" />
{zone.isNew ? 'Первый покос' : 'Скошенно'}
</button>
<button
onClick={() => onEdit(zone)}
className="p-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors duration-200"
>
<Edit className="h-4 w-4" />
</button>
<button
onClick={() => onDelete(zone.id)}
className="p-2 text-gray-600 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors duration-200"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</div>
</div>
);
};
export default ZoneCard;