diff --git a/frontend/package.json b/frontend/package.json
index d3a96de..6d49b97 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,6 +15,7 @@
"leaflet": "^1.9.4",
"react-leaflet": "^4.2.1",
"leaflet-draw": "^1.0.4",
+ "@turf/turf": "^6.5.0",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"react-hook-form": "^7.48.2",
diff --git a/frontend/src/pages/Properties/PropertyDetail.js b/frontend/src/pages/Properties/PropertyDetail.js
index 4c53f96..43cac9c 100644
--- a/frontend/src/pages/Properties/PropertyDetail.js
+++ b/frontend/src/pages/Properties/PropertyDetail.js
@@ -1,15 +1,391 @@
-import React from 'react';
-import { useParams } from 'react-router-dom';
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { MapContainer, TileLayer, Marker, Popup, Polygon, useMapEvents } from 'react-leaflet';
+import { Icon } from 'leaflet';
+import * as turf from '@turf/turf';
+import {
+ PlusIcon,
+ TrashIcon,
+ ArrowLeftIcon,
+ MapPinIcon,
+ SwatchIcon
+} from '@heroicons/react/24/outline';
+import { propertiesAPI } from '../../services/api';
+import LoadingSpinner from '../../components/UI/LoadingSpinner';
+import toast from 'react-hot-toast';
+import 'leaflet/dist/leaflet.css';
+
+// Fix for default markers
+delete Icon.Default.prototype._getIconUrl;
+Icon.Default.mergeOptions({
+ iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
+ iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
+ shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
+});
+
+// Colors for lawn sections
+const SECTION_COLORS = [
+ { name: 'Green', value: '#22c55e' },
+ { name: 'Blue', value: '#3b82f6' },
+ { name: 'Red', value: '#ef4444' },
+ { name: 'Yellow', value: '#eab308' },
+ { name: 'Purple', value: '#a855f7' },
+ { name: 'Orange', value: '#f97316' },
+];
+
+// Calculate area in square feet
+const calculateAreaInSqFt = (coordinates) => {
+ try {
+ const polygon = turf.polygon([coordinates.map(coord => [coord[1], coord[0]])]);
+ const areaInSqMeters = turf.area(polygon);
+ return Math.round(areaInSqMeters * 10.7639); // Convert to sq ft
+ } catch (error) {
+ console.error('Area calculation error:', error);
+ return 0;
+ }
+};
+
+// Component for drawing polygons
+function PolygonDrawer({ isDrawing, onPolygonComplete, currentColor }) {
+ const [currentPolygon, setCurrentPolygon] = useState([]);
+
+ useMapEvents({
+ click(e) {
+ if (!isDrawing) return;
+
+ const newPoint = [e.latlng.lat, e.latlng.lng];
+ setCurrentPolygon(prev => [...prev, newPoint]);
+ },
+ dblclick() {
+ if (isDrawing && currentPolygon.length >= 3) {
+ onPolygonComplete(currentPolygon);
+ setCurrentPolygon([]);
+ }
+ }
+ });
+
+ return currentPolygon.length > 0 ? (
+
+ ) : null;
+}
const PropertyDetail = () => {
const { id } = useParams();
-
+ const navigate = useNavigate();
+ const [property, setProperty] = useState(null);
+ const [lawnSections, setLawnSections] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [isDrawing, setIsDrawing] = useState(false);
+ const [currentColor, setCurrentColor] = useState(SECTION_COLORS[0]);
+ const [showNameModal, setShowNameModal] = useState(false);
+ const [pendingSection, setPendingSection] = useState(null);
+ const [sectionName, setSectionName] = useState('');
+
+ useEffect(() => {
+ fetchPropertyDetails();
+ }, [id]);
+
+ const fetchPropertyDetails = async () => {
+ try {
+ setLoading(true);
+ const response = await propertiesAPI.getById(id);
+ console.log('Property details:', response);
+ setProperty(response.data.data);
+ } catch (error) {
+ console.error('Failed to fetch property:', error);
+ toast.error('Failed to load property');
+ navigate('/properties');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handlePolygonComplete = (coordinates) => {
+ if (coordinates.length < 3) {
+ toast.error('Polygon needs at least 3 points');
+ return;
+ }
+
+ const area = calculateAreaInSqFt([...coordinates, coordinates[0]]);
+ setPendingSection({ coordinates, color: currentColor, area });
+ setShowNameModal(true);
+ setIsDrawing(false);
+ };
+
+ const saveLawnSection = () => {
+ if (!sectionName.trim()) {
+ toast.error('Please enter a section name');
+ return;
+ }
+
+ const newSection = {
+ id: Date.now(),
+ name: sectionName,
+ coordinates: pendingSection.coordinates,
+ color: pendingSection.color,
+ area: pendingSection.area
+ };
+
+ setLawnSections(prev => [...prev, newSection]);
+ toast.success(`${sectionName} section created!`);
+
+ // Reset and cycle color
+ setSectionName('');
+ setPendingSection(null);
+ setShowNameModal(false);
+ const nextIndex = (SECTION_COLORS.findIndex(c => c.value === currentColor.value) + 1) % SECTION_COLORS.length;
+ setCurrentColor(SECTION_COLORS[nextIndex]);
+ };
+
+ const deleteLawnSection = (sectionId) => {
+ if (window.confirm('Delete this lawn section?')) {
+ setLawnSections(prev => prev.filter(s => s.id !== sectionId));
+ toast.success('Section deleted');
+ }
+ };
+
+ const getTotalArea = () => {
+ return lawnSections.reduce((total, section) => total + section.area, 0);
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!property) {
+ return (
+
+
+
Property Not Found
+
+
+
+ );
+ }
+
return (
-
Property Details
-
-
Property {id} details coming soon...
+ {/* Header */}
+
+
+
+
+
{property.name}
+
+
+ {property.address}
+
+
+
+
+
+
+ {/* Map */}
+
+
+
+
+
+
+
+ {property.name}
+
+
+ {lawnSections.map((section) => (
+
+
+
+ {section.name}
+ {section.area.toLocaleString()} sq ft
+
+
+
+
+ ))}
+
+ {isDrawing && (
+
+ )}
+
+
+
+ {isDrawing && (
+
+
+ Drawing Mode: Click to add points. Double-click to complete polygon.
+
+
+ )}
+
+
+
+ {/* Sidebar */}
+
+ {/* Color Selector */}
+ {isDrawing && (
+
+
+
+ Section Color
+
+
+ {SECTION_COLORS.map((color) => (
+
+
+ )}
+
+ {/* Property Summary */}
+
+
Property Summary
+
+
+ Total Sections:
+ {lawnSections.length}
+
+
+ Total Area:
+ {getTotalArea().toLocaleString()} sq ft
+
+
+
+
+ {/* Lawn Sections */}
+
+
Lawn Sections
+ {lawnSections.length === 0 ? (
+
No sections yet.
+ ) : (
+
+ {lawnSections.map((section) => (
+
+
+
+
+
{section.name}
+
{section.area.toLocaleString()} sq ft
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ {/* Name Modal */}
+ {showNameModal && (
+
+
+
Name Your Lawn Section
+
+
setSectionName(e.target.value)}
+ placeholder="e.g., Front Yard, Back Lawn"
+ autoFocus
+ />
+
+
+
{pendingSection?.area.toLocaleString()} sq ft
+
+
+
+
+
+
+
+
+ )}
);
};