"use client"; import React, { useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { ArrowLeft, MapPin, Plus, Sprout, Calendar, LayoutGrid, AlertTriangle, Loader2, Home, ChevronRight, Sun, } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { useParams } from "next/navigation"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { CropDialog } from "./crop-dialog"; import { CropCard } from "./crop-card"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import type { Farm, Cropland } from "@/types"; import { getCropsByFarmId, createCrop, CropResponse } from "@/api/crop"; import { getFarm } from "@/api/farm"; // =================================================================== // Page Component: FarmDetailPage // - Manages farm details, crop listings, filter tabs, and crop creation. // - Performs API requests via React Query. // =================================================================== export default function FarmDetailPage() { // --------------------------------------------------------------- // Routing and URL Params // --------------------------------------------------------------- const params = useParams<{ farmId: string }>(); const farmId = params.farmId; const router = useRouter(); const queryClient = useQueryClient(); // --------------------------------------------------------------- // Local State for dialog and crop filter management // --------------------------------------------------------------- const [isDialogOpen, setIsDialogOpen] = useState(false); const [activeFilter, setActiveFilter] = useState("all"); // --------------------------------------------------------------- // Data fetching: Farm details and Crops using React Query. // - See: https://tanstack.com/query // --------------------------------------------------------------- const { data: farm, isLoading: isLoadingFarm, isError: isErrorFarm, error: errorFarm, } = useQuery({ queryKey: ["farm", farmId], queryFn: () => getFarm(farmId), enabled: !!farmId, staleTime: 60 * 1000, }); const { data: cropData, // Changed name to avoid conflict isLoading: isLoadingCrops, isError: isErrorCrops, error: errorCrops, } = useQuery({ // Use CropResponse type queryKey: ["crops", farmId], queryFn: () => getCropsByFarmId(farmId), // Use updated API function name enabled: !!farmId, staleTime: 60 * 1000, }); // --------------------------------------------------------------- // Mutation: Create Crop // - After creation, invalidate queries to refresh data. // --------------------------------------------------------------- const croplands = useMemo(() => cropData?.croplands || [], [cropData]); const mutation = useMutation({ mutationFn: (newCropData: Partial) => createCrop({ ...newCropData, farmId: farmId }), // Pass farmId here onSuccess: (newlyCreatedCrop) => { console.log("Successfully created crop:", newlyCreatedCrop); queryClient.invalidateQueries({ queryKey: ["crops", farmId] }); queryClient.invalidateQueries({ queryKey: ["farm", farmId] }); // Invalidate farm too to update crop count potentially setIsDialogOpen(false); }, onError: (error) => { console.error("Failed to add crop:", error); // TODO: Show user-friendly error message (e.g., using toast) }, }); const handleAddCrop = async (data: Partial) => { await mutation.mutateAsync(data); }; // --------------------------------------------------------------- // Determine combined loading and error states from individual queries. // --------------------------------------------------------------- const isLoading = isLoadingFarm || isLoadingCrops; const isError = isErrorFarm || isErrorCrops; const error = errorFarm || errorCrops; // --------------------------------------------------------------- // Filter crops based on the active filter tab. // --------------------------------------------------------------- const filteredCrops = useMemo(() => { // Renamed from filteredCrops return croplands.filter( (crop) => activeFilter === "all" || crop.status.toLowerCase() === activeFilter.toLowerCase() // Use camelCase status ); }, [croplands, activeFilter]); // --------------------------------------------------------------- // Calculate counts for each crop status to display in tabs. // --------------------------------------------------------------- const possibleStatuses = ["growing", "planned", "harvested", "fallow"]; // Use lowercase const cropCounts = useMemo(() => { return croplands.reduce( (acc, crop) => { const status = crop.status.toLowerCase(); // Use camelCase status if (acc[status] !== undefined) { acc[status]++; } else { acc["other"] = (acc["other"] || 0) + 1; // Count unknown statuses } acc.all++; return acc; }, { all: 0, ...Object.fromEntries(possibleStatuses.map((s) => [s, 0])) } as Record ); }, [croplands]); // --------------------------------------------------------------- // Derive the unique statuses from the crops list for the tabs. // --------------------------------------------------------------- const availableStatuses = useMemo(() => { return ["all", ...new Set(croplands.map((crop) => crop.status.toLowerCase()))]; // Use camelCase status }, [croplands]); // =================================================================== // Render: Main page layout segmented into breadcrumbs, farm cards, // crop management, and the crop add dialog. // =================================================================== return (
{/* ------------------------------ Breadcrumbs Navigation Section ------------------------------ */} {/* ------------------------------ Back Navigation Button ------------------------------ */} {/* ------------------------------ Error and Loading States ------------------------------ */} {isError && !isLoadingFarm && !farm && ( Error Loading Farm {(error as Error)?.message || "Could not load farm details."} )} {isErrorCrops && ( Error Loading Crops {(errorCrops as Error)?.message || "Could not load crop data."} )} {isLoading && (

Loading farm details...

)} {/* ------------------------------ Farm Details and Statistics ------------------------------ */} {!isLoadingFarm && !isErrorFarm && farm && ( <>
{/* Farm Info Card */}
{farm.farmType}
Created {new Date(farm.createdAt).toLocaleDateString()}

{farm.name}

Lat: {farm.lat?.toFixed(4)}, Lon: {farm.lon?.toFixed(4)}

Total Area

{farm.totalSize}

Total Crops

{isLoadingCrops ? "..." : cropCounts.all ?? 0}

Growing

{isLoadingCrops ? "..." : cropCounts.growing ?? 0}

Harvested

{isLoadingCrops ? "..." : cropCounts.harvested ?? 0}

{/* Weather Overview Card */} Weather Overview Current conditions
Temperature 25°C
Humidity 60%
Wind 10 km/h
Rainfall (24h) 2 mm
{/* ------------------------------ Crops Section: List and Filtering Tabs ------------------------------ */}

Crops / Croplands

Manage and monitor all croplands in this farm

{mutation.isError && ( Failed to Add Crop {(mutation.error as Error)?.message || "Could not add the crop. Please try again."} )} {availableStatuses.map((status) => ( {status === "all" ? "All" : status} ({isLoadingCrops ? "..." : cropCounts[status] ?? 0}) ))} {isLoadingCrops ? (
) : isErrorCrops ? (
Failed to load crops.
) : ( availableStatuses.map((status) => ( {filteredCrops.length === 0 && activeFilter === status ? (

No {status === "all" ? "" : status} crops found

{status === "all" ? "You haven't added any crops to this farm yet." : `No crops with status "${status}" found.`}

) : activeFilter === status && filteredCrops.length > 0 ? (
{filteredCrops.map((crop, index) => ( router.push(`/farms/${farmId}/crops/${crop.uuid}`)} /> ))}
) : null}
)) )}
)}
{/* ------------------------------ Add Crop Dialog Component - Passes the mutation state to display loading indicators. ------------------------------ */}
); }