From 6885eb4bfb3b9d1dcd790348a50800f3a6a97423 Mon Sep 17 00:00:00 2001 From: Sosokker Date: Fri, 28 Mar 2025 01:20:41 +0700 Subject: [PATCH] feat: add farm setup and fetch --- frontend/api/farm.ts | 258 +- .../app/(sidebar)/farms/[farmId]/page.tsx | 912 +-- .../app/(sidebar)/farms/add-farm-form.tsx | 218 +- frontend/app/(sidebar)/farms/farm-card.tsx | 10 +- frontend/app/(sidebar)/farms/page.tsx | 34 +- frontend/app/(sidebar)/layout.tsx | 24 +- frontend/pnpm-lock.yaml | 6305 +++++++++-------- frontend/types.ts | 38 +- 8 files changed, 4228 insertions(+), 3571 deletions(-) diff --git a/frontend/api/farm.ts b/frontend/api/farm.ts index 4e18614..19e09dd 100644 --- a/frontend/api/farm.ts +++ b/frontend/api/farm.ts @@ -1,198 +1,100 @@ import axiosInstance from "./config"; -import type { Crop, CropAnalytics, Farm } from "@/types"; +import type { Farm } from "@/types"; /** - * Fetch a specific crop by id using axios. - * Falls back to dummy data on error. - */ -export async function fetchCropById(id: string): Promise { - try { - const response = await axiosInstance.get(`/api/crops/${id}`); - return response.data; - } catch (error) { - // Fallback dummy data - return { - id, - farmId: "1", - name: "Monthong Durian", - plantedDate: new Date("2024-01-15"), - status: "growing", - variety: "Premium Grade", - expectedHarvest: new Date("2024-07-15"), - area: "2.5 hectares", - healthScore: 85, - }; - } -} - -/** - * Fetch crop analytics by crop id using axios. - * Returns dummy analytics if the API call fails. - */ -export async function fetchAnalyticsByCropId(id: string): Promise { - try { - const response = await axiosInstance.get(`/api/crops/${id}/analytics`); - return response.data; - } catch (error) { - return { - cropId: id, - growthProgress: 45, - humidity: 75, - temperature: 28, - sunlight: 85, - waterLevel: 65, - plantHealth: "good", - nextAction: "Water the plant", - nextActionDue: new Date("2024-02-15"), - soilMoisture: 70, - windSpeed: "12 km/h", - rainfall: "25mm last week", - nutrientLevels: { - nitrogen: 80, - phosphorus: 65, - potassium: 75, - }, - }; - } -} - -/** - * Fetch an array of farms using axios. - * Simulates a delay and a random error; returns dummy data if the API is unavailable. + * Fetch an array of farms. + * Calls GET /farms and returns fallback dummy data on failure. */ export async function fetchFarms(): Promise { - // Simulate network delay - await new Promise((resolve) => setTimeout(resolve, 1000)); - - try { - const response = await axiosInstance.get("/api/farms"); - return response.data; - } catch (error) { - // Optionally, you could simulate a random error here. For now we return fallback data. - return [ - { - id: "1", - name: "Green Valley Farm", - location: "Bangkok", - type: "durian", - createdAt: new Date("2023-01-01"), - area: "12.5 hectares", - crops: 5, - }, - { - id: "2", - name: "Sunrise Orchard", - location: "Chiang Mai", - type: "mango", - createdAt: new Date("2023-02-15"), - area: "8.3 hectares", - crops: 3, - }, - { - id: "3", - name: "Golden Harvest Fields", - location: "Phuket", - type: "rice", - createdAt: new Date("2023-03-22"), - area: "20.1 hectares", - crops: 2, - }, - ]; - } + return axiosInstance.get("/farms").then((res) => res.data); } /** - * Simulates creating a new farm. - * Waits for 800ms and then uses dummy data. + * Create a new farm. + * Calls POST /farms with a payload that uses snake_case keys. */ export async function createFarm(data: Partial): Promise { - await new Promise((resolve) => setTimeout(resolve, 800)); - // In a real implementation you might call: - // const response = await axiosInstance.post("/api/farms", data); - // return response.data; - return { - id: Math.random().toString(36).substr(2, 9), - name: data.name!, - location: data.location!, - type: data.type!, - createdAt: new Date(), - area: data.area || "0 hectares", - crops: 0, - }; + return axiosInstance.post("/farms", data).then((res) => res.data); } -// Additional functions for fetching crop details remain unchanged... - /** - * Fetch detailed information for a specific farm (including its crops) using axios. - * If the API call fails, returns fallback dummy data. + * Fetch a specific farm by ID. + * Calls GET /farms/{farm_id} and returns fallback data on failure. */ -export async function fetchFarmDetails(farmId: string): Promise<{ farm: Farm; crops: Crop[] }> { - // Simulate network delay - await new Promise((resolve) => setTimeout(resolve, 1200)); +export async function getFarm(farmId: string): Promise { + // Simulate a network delay. + await new Promise((resolve) => setTimeout(resolve, 600)); try { - const response = await axiosInstance.get<{ farm: Farm; crops: Crop[] }>(`/api/farms/${farmId}`); + const response = await axiosInstance.get(`/farms/${farmId}`); return response.data; - } catch (error) { - // If the given farmId is "999", simulate a not found error. - if (farmId === "999") { - throw new Error("FARM_NOT_FOUND"); - } - - const farm: Farm = { - id: farmId, - name: "Green Valley Farm", - location: "Bangkok, Thailand", - type: "durian", - createdAt: new Date("2023-01-15"), - area: "12.5 hectares", - crops: 3, - // Additional details such as weather can be included if needed. - weather: { - temperature: 28, - humidity: 75, - rainfall: "25mm last week", - sunlight: 85, - }, + } catch (error: any) { + console.error(`Error fetching farm ${farmId}. Returning fallback data:`, error); + const dummyDate = new Date().toISOString(); + return { + CreatedAt: dummyDate, + FarmType: "conventional", + Lat: 15.87, + Lon: 100.9925, + Name: "Fallback Farm", + OwnerID: "fallback_owner", + TotalSize: "40 hectares", + UUID: farmId, + UpdatedAt: dummyDate, }; - - const crops: Crop[] = [ - { - id: "1", - farmId, - name: "Monthong Durian", - plantedDate: new Date("2023-03-15"), - status: "growing", - variety: "Premium", - area: "4.2 hectares", - healthScore: 92, - progress: 65, - }, - { - id: "2", - farmId, - name: "Chanee Durian", - plantedDate: new Date("2023-02-20"), - status: "planned", - variety: "Standard", - area: "3.8 hectares", - healthScore: 0, - progress: 0, - }, - { - id: "3", - farmId, - name: "Kradum Durian", - plantedDate: new Date("2022-11-05"), - status: "harvested", - variety: "Premium", - area: "4.5 hectares", - healthScore: 100, - progress: 100, - }, - ]; - - return { farm, crops }; + } +} + +/** + * Update an existing farm. + * Calls PUT /farms/{farm_id} with a snake_case payload. + */ +export async function updateFarm( + farmId: string, + data: { + farm_type: string; + lat: number; + lon: number; + name: string; + total_size: string; + } +): Promise { + // Simulate a network delay. + await new Promise((resolve) => setTimeout(resolve, 800)); + + try { + const response = await axiosInstance.put(`/farms/${farmId}`, data); + return response.data; + } catch (error: any) { + console.error(`Error updating farm ${farmId}. Returning fallback data:`, error); + const now = new Date().toISOString(); + return { + CreatedAt: now, + FarmType: data.farm_type, + Lat: data.lat, + Lon: data.lon, + Name: data.name, + OwnerID: "updated_owner", + TotalSize: data.total_size, + UUID: farmId, + UpdatedAt: now, + }; + } +} + +/** + * Delete a specific farm. + * Calls DELETE /farms/{farm_id} and returns a success message. + */ +export async function deleteFarm(farmId: string): Promise<{ message: string }> { + // Simulate a network delay. + await new Promise((resolve) => setTimeout(resolve, 500)); + + try { + await axiosInstance.delete(`/farms/${farmId}`); + return { message: "Farm deleted successfully" }; + } catch (error: any) { + console.error(`Error deleting farm ${farmId}. Assuming deletion was successful:`, error); + return { message: "Farm deleted successfully (dummy)" }; } } diff --git a/frontend/app/(sidebar)/farms/[farmId]/page.tsx b/frontend/app/(sidebar)/farms/[farmId]/page.tsx index 8c0d879..6e776f8 100644 --- a/frontend/app/(sidebar)/farms/[farmId]/page.tsx +++ b/frontend/app/(sidebar)/farms/[farmId]/page.tsx @@ -1,459 +1,463 @@ -"use client"; +// "use client"; -import React, { useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { - ArrowLeft, - MapPin, - Plus, - Sprout, - Calendar, - LayoutGrid, - AlertTriangle, - Loader2, - Home, - ChevronRight, - Droplets, - Sun, - Wind, -} from "lucide-react"; -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 { motion, AnimatePresence } from "framer-motion"; -import type { Farm, Crop } from "@/types"; -import { fetchFarmDetails } from "@/api/farm"; +// import React, { useState, useEffect } from "react"; +// import { useRouter } from "next/navigation"; +// import { +// ArrowLeft, +// MapPin, +// Plus, +// Sprout, +// Calendar, +// LayoutGrid, +// AlertTriangle, +// Loader2, +// Home, +// ChevronRight, +// Droplets, +// Sun, +// Wind, +// } from "lucide-react"; +// 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 { motion, AnimatePresence } from "framer-motion"; +// import type { Farm, Crop } from "@/types"; +// import { fetchFarmDetails } from "@/api/farm"; -/** - * Used in Next.js; params is now a Promise and must be unwrapped with React.use() - */ -interface FarmDetailPageProps { - params: Promise<{ farmId: string }>; -} +// /** +// * Used in Next.js; params is now a Promise and must be unwrapped with React.use() +// */ +// interface FarmDetailPageProps { +// params: Promise<{ farmId: string }>; +// } + +// export default function FarmDetailPage({ params }: FarmDetailPageProps) { +// // Unwrap the promised params using React.use() (experimental) +// const resolvedParams = React.use(params); + +// const router = useRouter(); +// const [farm, setFarm] = useState(null); +// const [crops, setCrops] = useState([]); +// const [isDialogOpen, setIsDialogOpen] = useState(false); +// const [isLoading, setIsLoading] = useState(true); +// const [error, setError] = useState(null); +// const [activeFilter, setActiveFilter] = useState("all"); + +// // Fetch farm details on initial render using the resolved params +// useEffect(() => { +// async function loadFarmDetails() { +// try { +// setIsLoading(true); +// setError(null); +// const { farm, crops } = await fetchFarmDetails(resolvedParams.farmId); +// setFarm(farm); +// setCrops(crops); +// } catch (err) { +// if (err instanceof Error) { +// if (err.message === "FARM_NOT_FOUND") { +// router.push("/not-found"); +// return; +// } +// setError(err.message); +// } else { +// setError("An unknown error occurred"); +// } +// } finally { +// setIsLoading(false); +// } +// } + +// loadFarmDetails(); +// }, [resolvedParams.farmId, router]); + +// /** +// * Handles adding a new crop. +// */ +// const handleAddCrop = async (data: Partial) => { +// try { +// // Simulate API delay +// await new Promise((resolve) => setTimeout(resolve, 800)); + +// const newCrop: Crop = { +// id: Math.random().toString(36).substr(2, 9), +// farmId: farm!.id, +// name: data.name!, +// plantedDate: data.plantedDate!, +// status: data.status!, +// variety: data.variety || "Standard", +// area: data.area || "0 hectares", +// healthScore: data.status === "growing" ? 85 : 0, +// progress: data.status === "growing" ? 10 : 0, +// }; + +// setCrops((prev) => [newCrop, ...prev]); + +// // Update the farm's crop count +// if (farm) { +// setFarm({ ...farm, crops: farm.crops + 1 }); +// } + +// setIsDialogOpen(false); +// } catch (err) { +// setError("Failed to add crop. Please try again."); +// } +// }; + +// // Filter crops based on the active filter +// const filteredCrops = crops.filter((crop) => activeFilter === "all" || crop.status === activeFilter); + +// // Calculate crop counts grouped by status +// const cropCounts = { +// all: crops.length, +// growing: crops.filter((crop) => crop.status === "growing").length, +// planned: crops.filter((crop) => crop.status === "planned").length, +// harvested: crops.filter((crop) => crop.status === "harvested").length, +// }; + +// return ( +//
+//
+//
+// {/* Breadcrumbs */} +// + +// {/* Back button */} +// + +// {/* Error state */} +// {error && ( +// +// +// Error +// {error} +// +// )} + +// {/* Loading state */} +// {isLoading && ( +//
+// +//

Loading farm details...

+//
+// )} + +// {/* Farm details */} +// {!isLoading && !error && farm && ( +// <> +//
+// {/* Farm info card */} +// +// +//
+// +// {farm.type} +// +//
+// +// Created {farm.createdAt.toLocaleDateString()} +//
+//
+//
+//
+// +//
+//
+//

{farm.name}

+//
+// +// {farm.location} +//
+//
+//
+//
+// +//
+//
+//

Total Area

+//

{farm.area}

+//
+//
+//

Total Crops

+//

{farm.crops}

+//
+//
+//

Growing Crops

+//

{cropCounts.growing}

+//
+//
+//

Harvested

+//

{cropCounts.harvested}

+//
+//
+//
+//
+ +// {/* Weather card */} +// +// +// Current Conditions +// Weather at your farm location +// +// +//
+//
+//
+// +//
+//
+//

Temperature

+//

{farm.weather?.temperature}°C

+//
+//
+//
+//
+// +//
+//
+//

Humidity

+//

{farm.weather?.humidity}%

+//
+//
+//
+//
+// +//
+//
+//

Sunlight

+//

{farm.weather?.sunlight}%

+//
+//
+//
+//
+// +//
+//
+//

Rainfall

+//

{farm.weather?.rainfall}

+//
+//
+//
+//
+//
+//
+ +// {/* Crops section */} +//
+//
+//
+//

+// +// Crops +//

+//

Manage and monitor all crops in this farm

+//
+// +//
+ +// +// +// setActiveFilter("all")}> +// All Crops ({cropCounts.all}) +// +// setActiveFilter("growing")}> +// Growing ({cropCounts.growing}) +// +// setActiveFilter("planned")}> +// Planned ({cropCounts.planned}) +// +// setActiveFilter("harvested")}> +// Harvested ({cropCounts.harvested}) +// +// + +// +// {filteredCrops.length === 0 ? ( +//
+//
+// +//
+//

No crops found

+//

+// {activeFilter === "all" +// ? "You haven't added any crops to this farm yet." +// : `No ${activeFilter} crops found. Try a different filter.`} +//

+// +//
+// ) : ( +//
+// +// {filteredCrops.map((crop, index) => ( +// +// router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} +// /> +// +// ))} +// +//
+// )} +//
+ +// {/* Growing tab */} +// +// {filteredCrops.length === 0 ? ( +//
+//
+// +//
+//

No growing crops

+//

+// You don't have any growing crops in this farm yet. +//

+// +//
+// ) : ( +//
+// +// {filteredCrops.map((crop, index) => ( +// +// router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} +// /> +// +// ))} +// +//
+// )} +//
+ +// {/* Planned tab */} +// +// {filteredCrops.length === 0 ? ( +//
+//

No planned crops

+//

+// You don't have any planned crops in this farm yet. +//

+// +//
+// ) : ( +//
+// +// {filteredCrops.map((crop, index) => ( +// +// router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} +// /> +// +// ))} +// +//
+// )} +//
+ +// {/* Harvested tab */} +// +// {filteredCrops.length === 0 ? ( +//
+//

No harvested crops

+//

+// You don't have any harvested crops in this farm yet. +//

+// +//
+// ) : ( +//
+// +// {filteredCrops.map((crop, index) => ( +// +// router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} +// /> +// +// ))} +// +//
+// )} +//
+//
+//
+// +// )} +//
+//
+ +// {/* Add Crop Dialog */} +// +//
+// ); +// } export default function FarmDetailPage({ params }: FarmDetailPageProps) { - // Unwrap the promised params using React.use() (experimental) - const resolvedParams = React.use(params); - - const router = useRouter(); - const [farm, setFarm] = useState(null); - const [crops, setCrops] = useState([]); - const [isDialogOpen, setIsDialogOpen] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [activeFilter, setActiveFilter] = useState("all"); - - // Fetch farm details on initial render using the resolved params - useEffect(() => { - async function loadFarmDetails() { - try { - setIsLoading(true); - setError(null); - const { farm, crops } = await fetchFarmDetails(resolvedParams.farmId); - setFarm(farm); - setCrops(crops); - } catch (err) { - if (err instanceof Error) { - if (err.message === "FARM_NOT_FOUND") { - router.push("/not-found"); - return; - } - setError(err.message); - } else { - setError("An unknown error occurred"); - } - } finally { - setIsLoading(false); - } - } - - loadFarmDetails(); - }, [resolvedParams.farmId, router]); - - /** - * Handles adding a new crop. - */ - const handleAddCrop = async (data: Partial) => { - try { - // Simulate API delay - await new Promise((resolve) => setTimeout(resolve, 800)); - - const newCrop: Crop = { - id: Math.random().toString(36).substr(2, 9), - farmId: farm!.id, - name: data.name!, - plantedDate: data.plantedDate!, - status: data.status!, - variety: data.variety || "Standard", - area: data.area || "0 hectares", - healthScore: data.status === "growing" ? 85 : 0, - progress: data.status === "growing" ? 10 : 0, - }; - - setCrops((prev) => [newCrop, ...prev]); - - // Update the farm's crop count - if (farm) { - setFarm({ ...farm, crops: farm.crops + 1 }); - } - - setIsDialogOpen(false); - } catch (err) { - setError("Failed to add crop. Please try again."); - } - }; - - // Filter crops based on the active filter - const filteredCrops = crops.filter((crop) => activeFilter === "all" || crop.status === activeFilter); - - // Calculate crop counts grouped by status - const cropCounts = { - all: crops.length, - growing: crops.filter((crop) => crop.status === "growing").length, - planned: crops.filter((crop) => crop.status === "planned").length, - harvested: crops.filter((crop) => crop.status === "harvested").length, - }; - - return ( -
-
-
- {/* Breadcrumbs */} - - - {/* Back button */} - - - {/* Error state */} - {error && ( - - - Error - {error} - - )} - - {/* Loading state */} - {isLoading && ( -
- -

Loading farm details...

-
- )} - - {/* Farm details */} - {!isLoading && !error && farm && ( - <> -
- {/* Farm info card */} - - -
- - {farm.type} - -
- - Created {farm.createdAt.toLocaleDateString()} -
-
-
-
- -
-
-

{farm.name}

-
- - {farm.location} -
-
-
-
- -
-
-

Total Area

-

{farm.area}

-
-
-

Total Crops

-

{farm.crops}

-
-
-

Growing Crops

-

{cropCounts.growing}

-
-
-

Harvested

-

{cropCounts.harvested}

-
-
-
-
- - {/* Weather card */} - - - Current Conditions - Weather at your farm location - - -
-
-
- -
-
-

Temperature

-

{farm.weather?.temperature}°C

-
-
-
-
- -
-
-

Humidity

-

{farm.weather?.humidity}%

-
-
-
-
- -
-
-

Sunlight

-

{farm.weather?.sunlight}%

-
-
-
-
- -
-
-

Rainfall

-

{farm.weather?.rainfall}

-
-
-
-
-
-
- - {/* Crops section */} -
-
-
-

- - Crops -

-

Manage and monitor all crops in this farm

-
- -
- - - - setActiveFilter("all")}> - All Crops ({cropCounts.all}) - - setActiveFilter("growing")}> - Growing ({cropCounts.growing}) - - setActiveFilter("planned")}> - Planned ({cropCounts.planned}) - - setActiveFilter("harvested")}> - Harvested ({cropCounts.harvested}) - - - - - {filteredCrops.length === 0 ? ( -
-
- -
-

No crops found

-

- {activeFilter === "all" - ? "You haven't added any crops to this farm yet." - : `No ${activeFilter} crops found. Try a different filter.`} -

- -
- ) : ( -
- - {filteredCrops.map((crop, index) => ( - - router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} - /> - - ))} - -
- )} -
- - {/* Growing tab */} - - {filteredCrops.length === 0 ? ( -
-
- -
-

No growing crops

-

- You don't have any growing crops in this farm yet. -

- -
- ) : ( -
- - {filteredCrops.map((crop, index) => ( - - router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} - /> - - ))} - -
- )} -
- - {/* Planned tab */} - - {filteredCrops.length === 0 ? ( -
-

No planned crops

-

- You don't have any planned crops in this farm yet. -

- -
- ) : ( -
- - {filteredCrops.map((crop, index) => ( - - router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} - /> - - ))} - -
- )} -
- - {/* Harvested tab */} - - {filteredCrops.length === 0 ? ( -
-

No harvested crops

-

- You don't have any harvested crops in this farm yet. -

- -
- ) : ( -
- - {filteredCrops.map((crop, index) => ( - - router.push(`/farms/${crop.farmId}/crops/${crop.id}`)} - /> - - ))} - -
- )} -
-
-
- - )} -
-
- - {/* Add Crop Dialog */} - -
- ); + return
hello
; } diff --git a/frontend/app/(sidebar)/farms/add-farm-form.tsx b/frontend/app/(sidebar)/farms/add-farm-form.tsx index 3a23e92..91fcba2 100644 --- a/frontend/app/(sidebar)/farms/add-farm-form.tsx +++ b/frontend/app/(sidebar)/farms/add-farm-form.tsx @@ -10,10 +10,12 @@ import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, For import { useState } from "react"; import { Loader2 } from "lucide-react"; import type { Farm } from "@/types"; +import GoogleMapWithDrawing from "@/components/google-map-with-drawing"; const farmFormSchema = z.object({ name: z.string().min(2, "Farm name must be at least 2 characters"), - location: z.string().min(2, "Location must be at least 2 characters"), + latitude: z.number().min(-90, "Invalid latitude").max(90, "Invalid latitude"), + longitude: z.number().min(-180, "Invalid longitude").max(180, "Invalid longitude"), type: z.string().min(1, "Please select a farm type"), area: z.string().optional(), }); @@ -30,7 +32,8 @@ export function AddFarmForm({ onSubmit, onCancel }: AddFarmFormProps) { resolver: zodResolver(farmFormSchema), defaultValues: { name: "", - location: "", + latitude: 0, + longitude: 0, type: "", area: "", }, @@ -39,7 +42,14 @@ export function AddFarmForm({ onSubmit, onCancel }: AddFarmFormProps) { const handleSubmit = async (values: z.infer) => { try { setIsSubmitting(true); - await onSubmit(values); + const farmData: Partial = { + Name: values.name, + Lat: values.latitude, + Lon: values.longitude, + FarmType: values.type, + TotalSize: values.area, + }; + await onSubmit(farmData); form.reset(); } catch (error) { console.error("Error submitting form:", error); @@ -48,95 +58,127 @@ export function AddFarmForm({ onSubmit, onCancel }: AddFarmFormProps) { } }; + const handleAreaSelected = (coordinates: { lat: number; lng: number }[]) => { + if (coordinates.length > 0) { + const { lat, lng } = coordinates[0]; + form.setValue("latitude", lat); + form.setValue("longitude", lng); + } + }; + return ( -
- - ( - - Farm Name - - - - This is your farm's display name. - - - )} - /> +
+ {/* Form Section */} +
+ + + ( + + Farm Name + + + + This is your farm's display name. + + + )} + /> - ( - - Location - - - - City, region or specific address - - - )} - /> + ( + + Latitude + + + + + + )} + /> - ( - - Farm Type - - - - )} - /> + ( + + Longitude + + + + + + )} + /> - ( - - Total Area (optional) - - - - The total size of your farm - - - )} - /> + ( + + Farm Type + + + + )} + /> -
- - -
- - + ( + + Total Area (optional) + + + + The total size of your farm + + + )} + /> + +
+ + +
+ + +
+ + {/* Map Section */} +
+ Farm Location + +
+
); } diff --git a/frontend/app/(sidebar)/farms/farm-card.tsx b/frontend/app/(sidebar)/farms/farm-card.tsx index 40afe47..85551e8 100644 --- a/frontend/app/(sidebar)/farms/farm-card.tsx +++ b/frontend/app/(sidebar)/farms/farm-card.tsx @@ -40,7 +40,7 @@ export function FarmCard({ variant, farm, onClick }: FarmCardProps) { year: "numeric", month: "short", day: "numeric", - }).format(farm.createdAt); + }).format(new Date(farm.CreatedAt)); return ( @@ -49,7 +49,7 @@ export function FarmCard({ variant, farm, onClick }: FarmCardProps) { - {farm.type} + {farm.FarmType}
@@ -63,15 +63,15 @@ export function FarmCard({ variant, farm, onClick }: FarmCardProps) {
-

{farm.name}

+

{farm.Name}

- {farm.location} + {farm.Lat}

Area

-

{farm.area}

+

{farm.TotalSize}

Crops

diff --git a/frontend/app/(sidebar)/farms/page.tsx b/frontend/app/(sidebar)/farms/page.tsx index 957b321..04ed915 100644 --- a/frontend/app/(sidebar)/farms/page.tsx +++ b/frontend/app/(sidebar)/farms/page.tsx @@ -54,26 +54,38 @@ export default function FarmSetupPage() { }, }); + // export interface Farm { + // CreatedAt: string; + // FarmType: string; + // Lat: number; + // Lon: number; + // Name: string; + // OwnerID: string; + // TotalSize: string; + // UUID: string; + // UpdatedAt: string; + // } + const filteredAndSortedFarms = (farms || []) .filter( (farm) => - (activeFilter === "all" || farm.type === activeFilter) && - (farm.name.toLowerCase().includes(searchQuery.toLowerCase()) || - farm.location.toLowerCase().includes(searchQuery.toLowerCase()) || - farm.type.toLowerCase().includes(searchQuery.toLowerCase())) + (activeFilter === "all" || farm.FarmType === activeFilter) && + (farm.Name.toLowerCase().includes(searchQuery.toLowerCase()) || + // farm.location.toLowerCase().includes(searchQuery.toLowerCase()) || + farm.FarmType.toLowerCase().includes(searchQuery.toLowerCase())) ) .sort((a, b) => { if (sortOrder === "newest") { - return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + return new Date(b.CreatedAt).getTime() - new Date(a.CreatedAt).getTime(); } else if (sortOrder === "oldest") { - return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); + return new Date(a.CreatedAt).getTime() - new Date(b.CreatedAt).getTime(); } else { - return a.name.localeCompare(b.name); + return a.Name.localeCompare(b.Name); } }); // Get distinct farm types. - const farmTypes = ["all", ...new Set((farms || []).map((farm) => farm.type))]; + const farmTypes = ["all", ...new Set((farms || []).map((farm) => farm.FarmType))]; const handleAddFarm = async (data: Partial) => { await mutation.mutateAsync(data); @@ -188,7 +200,7 @@ export default function FarmSetupPage() {

) : (

- You haven't added any farms yet. Get started by adding your first farm. + You haven't added any farms yet. Get started by adding your first farm.

)}