Первый комит - руссификация

This commit is contained in:
anibilag 2025-06-29 17:37:49 +03:00
parent 78d9777689
commit 39c23eef2f
8 changed files with 186 additions and 68 deletions

BIN
lawn_scheduler.db Normal file

Binary file not shown.

9
package-lock.json generated
View File

@ -2102,9 +2102,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001667",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
"integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
"version": "1.0.30001726",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz",
"integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==",
"dev": true,
"funding": [
{
@ -2119,7 +2119,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "2.4.2",

View File

@ -12,14 +12,14 @@
"preview": "vite preview"
},
"dependencies": {
"@libsql/client": "^0.4.0",
"cors": "^2.8.5",
"express": "^4.18.2",
"lucide-react": "^0.344.0",
"multer": "^1.4.5-lts.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.0",
"@libsql/client": "^0.4.0",
"express": "^4.18.2",
"cors": "^2.8.5",
"multer": "^1.4.5-lts.1"
"react-router-dom": "^6.26.0"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
@ -27,16 +27,16 @@
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.18",
"concurrently": "^8.2.2",
"eslint": "^9.9.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^15.9.0",
"nodemon": "^3.0.2",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2",
"concurrently": "^8.2.2",
"nodemon": "^3.0.2"
"vite": "^5.4.2"
}
}
}

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Plus, Filter, Calendar, Scissors, AlertTriangle, Square, CheckCircle, Clock, Map } from 'lucide-react';
import { Plus, Filter, Calendar, Scissors, AlertTriangle, Square, CheckCircle, Clock, Map } from './Icons';
import { Zone } from '../types/zone';
import { api } from '../services/api';
import ZoneCard from './ZoneCard';
@ -128,9 +128,9 @@ const Dashboard: React.FC = () => {
<div>
<h1 className="text-3xl font-bold text-gray-900 flex items-center gap-3">
<Scissors className="h-8 w-8 text-green-600" />
Lawn Care Manager
Менеджер по уходу за газоном
</h1>
<p className="mt-2 text-gray-600">Keep track of your lawn mowing schedule</p>
<p className="mt-2 text-gray-600">Следите за своим графиком стрижки газона</p>
</div>
<div className="flex items-center gap-3">
{/* View Toggle */}
@ -144,7 +144,7 @@ const Dashboard: React.FC = () => {
}`}
>
<Calendar className="h-4 w-4 inline mr-2" />
Dashboard
Панель
</button>
<button
onClick={() => setView('sitePlan')}
@ -155,7 +155,7 @@ const Dashboard: React.FC = () => {
}`}
>
<Map className="h-4 w-4 inline mr-2" />
Site Plan
План участка
</button>
</div>
@ -164,7 +164,7 @@ const Dashboard: React.FC = () => {
className="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg flex items-center gap-2 transition-colors duration-200 shadow-lg hover:shadow-xl"
>
<Plus className="h-5 w-5" />
Add Zone
Новая зона
</button>
</div>
</div>
@ -183,7 +183,7 @@ const Dashboard: React.FC = () => {
<div className="bg-white rounded-lg shadow-md p-6 border-l-4 border-green-500">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total Zones</p>
<p className="text-sm font-medium text-gray-600">Количество зон</p>
<p className="text-2xl font-bold text-gray-900">{zones.length}</p>
</div>
<Calendar className="h-8 w-8 text-green-500" />
@ -193,9 +193,9 @@ const Dashboard: React.FC = () => {
<div className="bg-white rounded-lg shadow-md p-6 border-l-4 border-purple-500">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total Area</p>
<p className="text-sm font-medium text-gray-600">Общая площадь</p>
<p className="text-2xl font-bold text-gray-900">{totalArea.toLocaleString()}</p>
<p className="text-xs text-gray-500">sq ft</p>
<p className="text-xs text-gray-500">м2</p>
</div>
<Square className="h-8 w-8 text-purple-500" />
</div>
@ -204,9 +204,9 @@ const Dashboard: React.FC = () => {
<div className="bg-white rounded-lg shadow-md p-6 border-l-4 border-emerald-500">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Mowed Area</p>
<p className="text-sm font-medium text-gray-600">Скошенная площадь</p>
<p className="text-2xl font-bold text-gray-900">{mowedArea.toLocaleString()}</p>
<p className="text-xs text-gray-500">sq ft ({mowedPercentage.toFixed(1)}%)</p>
<p className="text-xs text-gray-500">м2 ({mowedPercentage.toFixed(1)}%)</p>
</div>
<CheckCircle className="h-8 w-8 text-emerald-500" />
</div>
@ -215,9 +215,9 @@ const Dashboard: React.FC = () => {
<div className="bg-white rounded-lg shadow-md p-6 border-l-4 border-amber-500">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Needs Mowing</p>
<p className="text-sm font-medium text-gray-600">Нужно скосить</p>
<p className="text-2xl font-bold text-gray-900">{remainingArea.toLocaleString()}</p>
<p className="text-xs text-gray-500">sq ft ({(100 - mowedPercentage).toFixed(1)}%)</p>
<p className="text-xs text-gray-500">м2 ({(100 - mowedPercentage).toFixed(1)}%)</p>
</div>
<Clock className="h-8 w-8 text-amber-500" />
</div>
@ -226,7 +226,7 @@ const Dashboard: React.FC = () => {
<div className="bg-white rounded-lg shadow-md p-6 border-l-4 border-orange-500">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Due Today</p>
<p className="text-sm font-medium text-gray-600">Срок - сегодня</p>
<p className="text-2xl font-bold text-gray-900">{dueCount}</p>
</div>
<Scissors className="h-8 w-8 text-orange-500" />
@ -236,7 +236,7 @@ const Dashboard: React.FC = () => {
<div className="bg-white rounded-lg shadow-md p-6 border-l-4 border-red-500">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Overdue</p>
<p className="text-sm font-medium text-gray-600">Срок прошел</p>
<p className="text-2xl font-bold text-gray-900">{overdueCount}</p>
</div>
<AlertTriangle className="h-8 w-8 text-red-500" />
@ -248,9 +248,9 @@ const Dashboard: React.FC = () => {
{totalArea > 0 && (
<div className="bg-white rounded-lg shadow-md p-6 mb-8">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900">Mowing Progress</h3>
<h3 className="text-lg font-semibold text-gray-900">Ход скашивания</h3>
<span className="text-sm text-gray-600">
{mowedArea.toLocaleString()} / {totalArea.toLocaleString()} sq ft
{mowedArea.toLocaleString()} / {totalArea.toLocaleString()} м2
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-4 mb-2">
@ -262,11 +262,11 @@ const Dashboard: React.FC = () => {
<div className="flex justify-between text-sm text-gray-600">
<span className="flex items-center gap-1">
<CheckCircle className="h-4 w-4 text-emerald-500" />
{mowedPercentage.toFixed(1)}% Complete
{mowedPercentage.toFixed(1)}% Завершено
</span>
<span className="flex items-center gap-1">
<Clock className="h-4 w-4 text-amber-500" />
{(100 - mowedPercentage).toFixed(1)}% Remaining
{(100 - mowedPercentage).toFixed(1)}% Осталось
</span>
</div>
</div>
@ -276,12 +276,12 @@ const Dashboard: React.FC = () => {
<div className="mb-6">
<div className="flex items-center gap-2">
<Filter className="h-5 w-5 text-gray-500" />
<span className="text-sm font-medium text-gray-700">Filter:</span>
<span className="text-sm font-medium text-gray-700">Фильтр:</span>
<div className="flex gap-2">
{[
{ key: 'all' as FilterType, label: 'All Zones', count: zones.length },
{ key: 'due' as FilterType, label: 'Due Today', count: dueCount },
{ key: 'overdue' as FilterType, label: 'Overdue', count: overdueCount },
{ key: 'all' as FilterType, label: 'Все зоны', count: zones.length },
{ key: 'due' as FilterType, label: 'Срок - сегодня', count: dueCount },
{ key: 'overdue' as FilterType, label: 'Срок прошел', count: overdueCount },
].map(({ key, label, count }) => (
<button
key={key}

117
src/components/Icons.tsx Normal file
View File

@ -0,0 +1,117 @@
import React from 'react';
// Custom SVG icons to replace Lucide React
export const Plus: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
);
export const Filter: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
);
export const Calendar: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
);
export const Scissors: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="6" cy="6" r="3" />
<circle cx="6" cy="18" r="3" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="m21 12-7-7m7 7-7 7m7-7H8" />
</svg>
);
export const AlertTriangle: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 18.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
);
export const Square: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
</svg>
);
export const CheckCircle: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
);
export const Clock: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" />
<polyline points="12,6 12,12 16,14" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
</svg>
);
export const Map: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polygon points="1,6 1,22 8,18 16,22 23,18 23,2 16,6 8,2" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
<line x1="8" y1="2" x2="8" y2="18" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
<line x1="16" y1="6" x2="16" y2="22" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
</svg>
);
export const Edit: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
);
export const Trash2: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<polyline points="3,6 5,6 21,6" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="m19,6v14a2,2 0 0,1-2,2H7a2,2 0 0,1-2-2V6m3,0V4a2,2 0 0,1,2-2h4a2,2 0 0,1,2,2v2" />
<line x1="10" y1="11" x2="10" y2="17" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
<line x1="14" y1="11" x2="14" y2="17" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
</svg>
);
export const Camera: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2v11z" />
<circle cx="12" cy="13" r="4" />
</svg>
);
export const X: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
<line x1="6" y1="6" x2="18" y2="18" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
</svg>
);
export const Upload: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
);
export const MapPin: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z" />
<circle cx="12" cy="10" r="3" />
</svg>
);
export const Eye: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</svg>
);
export const EyeOff: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => (
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} />
</svg>
);

View File

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from 'react';
import { MapPin, Upload, X, Eye, EyeOff } from 'lucide-react';
import { MapPin, Upload, X, Eye, EyeOff } from './Icons';
import { Zone } from '../types/zone';
interface ZoneMarker {
@ -100,9 +100,9 @@ const SitePlan: React.FC<SitePlanProps> = ({ zones, onZoneSelect, selectedZoneId
<div className="text-center">
<div className="mb-6">
<MapPin className="mx-auto h-16 w-16 text-gray-400" />
<h3 className="mt-4 text-lg font-semibold text-gray-900">Site Plan</h3>
<h3 className="mt-4 text-lg font-semibold text-gray-900">План участка</h3>
<p className="mt-2 text-gray-600">
Upload an image of your property to visualize zone locations
Загрузите изображение участка, чтобы визуализировать расположение зон
</p>
</div>
@ -111,7 +111,7 @@ const SitePlan: React.FC<SitePlanProps> = ({ zones, onZoneSelect, selectedZoneId
className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg flex items-center gap-2 mx-auto transition-colors duration-200"
>
<Upload className="h-5 w-5" />
Upload Site Plan
Загрузка плана участка
</button>
<input
@ -123,7 +123,7 @@ const SitePlan: React.FC<SitePlanProps> = ({ zones, onZoneSelect, selectedZoneId
/>
<p className="mt-4 text-sm text-gray-500">
Supported formats: JPG, PNG, GIF (max 10MB)
Поддерживаемые форматы: JPG, PNG, GIF (максимально 10MB)
</p>
</div>
</div>
@ -137,7 +137,7 @@ const SitePlan: React.FC<SitePlanProps> = ({ zones, onZoneSelect, selectedZoneId
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<MapPin className="h-6 w-6 text-blue-600" />
<h3 className="text-lg font-semibold text-gray-900">Site Plan</h3>
<h3 className="text-lg font-semibold text-gray-900">План участка</h3>
<span className="text-sm text-gray-500">
({zoneMarkers.length} of {zones.length} zones marked)
</span>
@ -199,7 +199,7 @@ const SitePlan: React.FC<SitePlanProps> = ({ zones, onZoneSelect, selectedZoneId
<img
ref={imageRef}
src={sitePlanImage}
alt="Site Plan"
alt="План участка"
className={`w-full h-auto max-h-[600px] object-contain ${
isEditMode ? 'cursor-crosshair' : 'cursor-default'
}`}
@ -246,7 +246,7 @@ const SitePlan: React.FC<SitePlanProps> = ({ zones, onZoneSelect, selectedZoneId
<div className="bg-gray-900 text-white text-xs rounded-md px-2 py-1 whitespace-nowrap">
<div className="font-medium">{zone.name}</div>
<div className="text-gray-300">
{zone.isOverdue ? 'Overdue' : zone.isDueToday ? 'Due today' : `${zone.daysUntilNext} days`}
{zone.isOverdue ? 'Срок прошел' : zone.isDueToday ? 'Срок - сегодня' : `${zone.daysUntilNext} дней`}
</div>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
</div>
@ -280,7 +280,7 @@ const SitePlan: React.FC<SitePlanProps> = ({ zones, onZoneSelect, selectedZoneId
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-orange-500 border border-orange-600"></div>
<span className="text-gray-700">Due today</span>
<span className="text-gray-700">Срок - сегодня</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500 border border-red-600"></div>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Scissors, Edit, Trash2, Calendar, Camera, Square } from 'lucide-react';
import { Scissors, Edit, Trash2, Calendar, Camera, Square } from './Icons';
import { Zone } from '../types/zone';
interface ZoneCardProps {
@ -28,11 +28,11 @@ const ZoneCard: React.FC<ZoneCardProps> = ({
const getStatusText = (zone: Zone) => {
if (zone.isOverdue) {
return `${Math.abs(zone.daysUntilNext)} days overdue`;
return `${Math.abs(zone.daysUntilNext)} дней посроченно`;
} else if (zone.isDueToday) {
return 'Due today';
return 'Срок - сегодня';
} else {
return `${zone.daysUntilNext} days remaining`;
return `${zone.daysUntilNext} дней осталось`;
}
};
@ -57,7 +57,7 @@ const ZoneCard: React.FC<ZoneCardProps> = ({
const formatArea = (area: number) => {
if (area === 0) return 'Not specified';
return `${area.toLocaleString()} sq ft`;
return `${area.toLocaleString()} м2`;
};
return (
@ -94,15 +94,15 @@ const ZoneCard: React.FC<ZoneCardProps> = ({
<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>Last mowed: {formatDate(zone.lastMowedDate)}</span>
<span>Было скошенно: {formatDate(zone.lastMowedDate)}</span>
</div>
<div className="flex items-center gap-2">
<Scissors className="h-4 w-4" />
<span>Every {zone.intervalDays} days</span>
<span>Каждые {zone.intervalDays} дней</span>
</div>
<div className="flex items-center gap-2">
<Square className="h-4 w-4" />
<span>Area: {formatArea(zone.area)}</span>
<span>Площадь: {formatArea(zone.area)}</span>
</div>
</div>
@ -118,7 +118,7 @@ const ZoneCard: React.FC<ZoneCardProps> = ({
}`}
>
<Scissors className="h-4 w-4 inline mr-1" />
Mowed
Скошенно
</button>
<button

View File

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from 'react';
import { X, Upload, Camera } from 'lucide-react';
import { X, Upload, Camera } from './Icons';
import { Zone, ZoneFormData } from '../types/zone';
import { api } from '../services/api';
@ -60,7 +60,7 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
<div className="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between p-6 border-b">
<h2 className="text-xl font-semibold text-gray-900">
{zone ? 'Edit Zone' : 'Add New Zone'}
{zone ? 'Редактирование Зоны' : 'Добавление новой Зоны'}
</h2>
<button
onClick={onCancel}
@ -80,7 +80,7 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
{/* Zone Name */}
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-2">
Zone Name
Наименование зоны
</label>
<input
type="text"
@ -89,14 +89,14 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
required
placeholder="e.g., Front Yard, Back Garden"
placeholder="например Задний Двор, Входная Зона"
/>
</div>
{/* Area */}
{/* Площадь */}
<div>
<label htmlFor="area" className="block text-sm font-medium text-gray-700 mb-2">
Area (square feet)
Площадь (квадратные метры)
</label>
<input
type="number"
@ -108,13 +108,13 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
step="0.1"
placeholder="e.g., 150.5"
/>
<p className="text-xs text-gray-500 mt-1">Optional - helps track total lawn area</p>
<p className="text-xs text-gray-500 mt-1">Дополнительно - помогает отслеживать общую площадь газона</p>
</div>
{/* Mowing Interval */}
<div>
<label htmlFor="intervalDays" className="block text-sm font-medium text-gray-700 mb-2">
Mowing Interval (days)
Интервал покоса (дни)
</label>
<input
type="number"
@ -131,7 +131,7 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
{/* Last Mowed Date */}
<div>
<label htmlFor="lastMowedDate" className="block text-sm font-medium text-gray-700 mb-2">
Last Mowed Date
Дата последнего покоса
</label>
<input
type="date"
@ -146,7 +146,7 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
{/* Zone Image */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Zone Image (optional)
Изображение зоны (не обязательно)
</label>
{imagePreview ? (
@ -176,8 +176,8 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
className="border-2 border-dashed border-gray-300 rounded-md p-6 text-center cursor-pointer hover:border-green-500 hover:bg-green-50 transition-colors duration-200"
>
<Upload className="mx-auto h-8 w-8 text-gray-400 mb-2" />
<p className="text-sm text-gray-600">Click to upload zone image</p>
<p className="text-xs text-gray-500 mt-1">PNG, JPG up to 5MB</p>
<p className="text-sm text-gray-600">Нажмите для загрузки изображения зоны</p>
<p className="text-xs text-gray-500 mt-1">PNG, JPG до 5MB</p>
</div>
)}
@ -197,14 +197,14 @@ const ZoneForm: React.FC<ZoneFormProps> = ({ zone, onSubmit, onCancel }) => {
onClick={onCancel}
className="flex-1 py-2 px-4 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 transition-colors duration-200"
>
Cancel
Отмена
</button>
<button
type="submit"
disabled={loading}
className="flex-1 py-2 px-4 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
>
{loading ? 'Saving...' : zone ? 'Update Zone' : 'Create Zone'}
{loading ? 'Сохраняется...' : zone ? 'Изменение Зоны' : 'Создание Зоны'}
</button>
</div>
</form>