// crop-dialog.tsx "use client"; import React, { useState, useMemo, useCallback, useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { useMapsLibrary } from "@vis.gl/react-google-maps"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Check, Sprout, AlertTriangle, Loader2, CalendarDays, Thermometer, Droplets, MapPin, Maximize, } from "lucide-react"; import { cn } from "@/lib/utils"; // Import the updated/new types import type { Cropland, GeoFeatureData, GeoPosition } from "@/types"; import { PlantResponse } from "@/api/plant"; import { getPlants } from "@/api/plant"; // Import the map component and the ShapeData type (ensure ShapeData in types.ts matches this) import GoogleMapWithDrawing, { type ShapeData } from "@/components/google-map-with-drawing"; interface CropDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onSubmit: (data: Partial>) => Promise; isSubmitting: boolean; initialData?: Cropland | null; isEditing?: boolean; } export function CropDialog({ open, onOpenChange, onSubmit, isSubmitting, initialData, isEditing }: CropDialogProps) { // --- State --- const [selectedPlantUUID, setSelectedPlantUUID] = useState(null); const [geoFeature, setGeoFeature] = useState(null); const [calculatedArea, setCalculatedArea] = useState(null); // --- Load Google Maps Geometry Library --- const geometryLib = useMapsLibrary("geometry"); // --- Fetch Plants --- const { data: plantData, isLoading: isLoadingPlants, isError: isErrorPlants, error: errorPlants, } = useQuery({ queryKey: ["plants"], queryFn: getPlants, staleTime: 1000 * 60 * 60, refetchOnWindowFocus: false, }); const plants = useMemo(() => plantData?.plants || [], [plantData]); const selectedPlant = useMemo(() => { return plants.find((p) => p.uuid === selectedPlantUUID); }, [plants, selectedPlantUUID]); // --- Reset State on Dialog Close --- useEffect(() => { if (!open) { setSelectedPlantUUID(null); setGeoFeature(null); setCalculatedArea(null); } else if (initialData) { setSelectedPlantUUID(initialData.plantId); setGeoFeature(initialData.geoFeature ?? null); setCalculatedArea(initialData.landSize ?? null); } }, [open, initialData]); // --- Map Interaction Handler --- const handleShapeDrawn = useCallback( (data: ShapeData) => { console.log("Shape drawn:", data); if (!geometryLib) { console.warn("Geometry library not loaded yet."); return; } let feature: GeoFeatureData | null = null; let area: number | null = null; // Helper to ensure path points are valid GeoPositions const mapPath = (path?: { lat: number; lng: number }[]): GeoPosition[] => (path || []).map((p) => ({ lat: p.lat, lng: p.lng })); // Helper to ensure position is a valid GeoPosition const mapPosition = (pos?: { lat: number; lng: number }): GeoPosition | null => pos ? { lat: pos.lat, lng: pos.lng } : null; if (data.type === "polygon" && data.path && data.path.length > 0) { const geoPath = mapPath(data.path); feature = { type: "polygon", path: geoPath }; // Use original path for calculation if library expects {lat, lng} area = geometryLib.spherical.computeArea(data.path); console.log("Polygon drawn, Area:", area, "m²"); } else if (data.type === "polyline" && data.path && data.path.length > 0) { const geoPath = mapPath(data.path); feature = { type: "polyline", path: geoPath }; area = null; console.log("Polyline drawn, Path:", data.path); } else if (data.type === "marker" && data.position) { const geoPos = mapPosition(data.position); if (geoPos) { feature = { type: "marker", position: geoPos }; } area = null; console.log("Marker drawn at:", data.position); } else { console.log(`Ignoring shape type: ${data.type} or empty path/position`); feature = null; area = null; } setGeoFeature(feature); setCalculatedArea(area); }, [geometryLib] // Depend on geometryLib ); // --- Submit Handler --- const handleSubmit = async () => { // Check for geoFeature instead of just drawnPath if (!selectedPlantUUID || !geoFeature) { alert("Please select a plant and define a feature (marker, polygon, or polyline) on the map."); return; } // selectedPlant is derived from state using useMemo if (!selectedPlant) { alert("Selected plant not found."); // Should not happen if UUID is set return; } const cropData: Partial = { // Default name, consider making this editable name: `${selectedPlant.name} Field ${Math.floor(100 + Math.random() * 900)}`, plantId: selectedPlant.uuid, status: "planned", // Default status // Use calculatedArea if available (only for polygons), otherwise maybe 0 // The backend might ignore this if it calculates based on GeoFeature landSize: calculatedArea ?? 0, growthStage: "Planned", // Default growth stage priority: 1, // Default priority geoFeature: geoFeature, // Add the structured geoFeature data // FarmID will be added in the page component mutationFn }; console.log("Submitting Cropland Data:", cropData); try { await onSubmit(cropData); // State reset handled by useEffect watching 'open' } catch (error) { console.error("Submission failed in dialog:", error); // Optionally show an error message to the user within the dialog } }; // --- Render --- return ( {isEditing ? "Edit Cropland" : "Create New Cropland"} {isEditing ? "Update the cropland details and location." : "Select a plant and draw the cropland boundary or mark its location on the map."}
{/* Left Side: Plant Selection */}

1. Select Plant

{/* Plant selection UI */} {isLoadingPlants && (
Loading plants...
)} {isErrorPlants && (
Error loading plants: {(errorPlants as Error)?.message}
)} {!isLoadingPlants && !isErrorPlants && plants.length === 0 && (
No plants available.
)} {!isLoadingPlants && !isErrorPlants && plants.length > 0 && (
{plants.map((plant) => ( setSelectedPlantUUID(plant.uuid)}>

{plant.name} ({plant.variety})

{selectedPlantUUID === plant.uuid && ( )}

Maturity: ~{plant.daysToMaturity ?? "N/A"} days

Temp: {plant.optimalTemp ?? "N/A"}°C

Water: {plant.waterNeeds ?? "N/A"}

))}
)}
{/* Right Side: Map */}

2. Define Boundary / Location

{/* Display feedback based on drawn shape */} {geoFeature?.type === "polygon" && calculatedArea !== null && (
Area: {calculatedArea.toFixed(2)} m²
)} {geoFeature?.type === "polyline" && geoFeature.path && (
Boundary path defined ({geoFeature.path.length} points).
)} {geoFeature?.type === "marker" && geoFeature.position && (
Marker set at {geoFeature.position.lat.toFixed(4)}, {geoFeature.position.lng.toFixed(4)}.
)} {!geometryLib && (
Loading map tools...
)}

Use the drawing tools (Polygon , Polyline{" "} , Marker ) above the map. Area is calculated for polygons.

{/* Dialog Footer */} {/* Disable submit if no plant OR no feature is selected */}
); }