mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 14:04:08 +01:00
Merge branch 'feature-farm-setup' into feature-inventory
This commit is contained in:
commit
7b69c68056
@ -20,17 +20,37 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type harvestSchema = z.infer<typeof harvestDetailsFormSchema>;
|
||||
|
||||
export default function HarvestDetailsForm() {
|
||||
export default function HarvestDetailsForm({
|
||||
onChange,
|
||||
}: {
|
||||
onChange: (data: harvestSchema) => void;
|
||||
}) {
|
||||
const form = useForm<harvestSchema>({
|
||||
resolver: zodResolver(harvestDetailsFormSchema),
|
||||
defaultValues: {},
|
||||
defaultValues: {
|
||||
daysToFlower: 0,
|
||||
daysToMaturity: 0,
|
||||
harvestWindow: 0,
|
||||
estimatedLossRate: 0,
|
||||
harvestUnits: "",
|
||||
estimatedRevenue: 0,
|
||||
expectedYieldPer100ft: 0,
|
||||
expectedYieldPerAcre: 0,
|
||||
},
|
||||
});
|
||||
const onSubmit: (data: harvestSchema) => void = (data) => {
|
||||
onChange(data);
|
||||
};
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form className="grid grid-cols-3 gap-5">
|
||||
<form
|
||||
className="grid grid-cols-3 gap-5"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="daysToFlower"
|
||||
@ -47,6 +67,13 @@ export default function HarvestDetailsForm() {
|
||||
id="daysToFlower"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -71,6 +98,13 @@ export default function HarvestDetailsForm() {
|
||||
id="daysToMaturity"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -95,6 +129,13 @@ export default function HarvestDetailsForm() {
|
||||
id="harvestWindow"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -119,6 +160,13 @@ export default function HarvestDetailsForm() {
|
||||
id="estimatedLossRate"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -168,6 +216,13 @@ export default function HarvestDetailsForm() {
|
||||
id="estimatedRevenue"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -192,6 +247,13 @@ export default function HarvestDetailsForm() {
|
||||
id="expectedYieldPer100ft"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -216,6 +278,13 @@ export default function HarvestDetailsForm() {
|
||||
id="expectedYieldPerAcre"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -224,6 +293,9 @@ export default function HarvestDetailsForm() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="col-span-3 flex justify-center">
|
||||
<Button type="submit">Save</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@ -1,34 +1,124 @@
|
||||
"use client";
|
||||
import { SetStateAction, useEffect, useState } from "react";
|
||||
import PlantingDetailsForm from "./planting-detail-form";
|
||||
import HarvestDetailsForm from "./harvest-detail-form";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import GoogleMapWithDrawing from "@/components/google-map-with-drawing";
|
||||
import {
|
||||
plantingDetailsFormSchema,
|
||||
harvestDetailsFormSchema,
|
||||
} from "@/schemas/application.schema";
|
||||
import { z } from "zod";
|
||||
|
||||
type plantingSchema = z.infer<typeof plantingDetailsFormSchema>;
|
||||
type harvestSchema = z.infer<typeof harvestDetailsFormSchema>;
|
||||
|
||||
export default function SetupPage() {
|
||||
const [plantingDetails, setPlantingDetails] = useState<plantingSchema | null>(
|
||||
null
|
||||
);
|
||||
const [harvestDetails, setHarvestDetails] = useState<harvestSchema | null>(
|
||||
null
|
||||
);
|
||||
const [mapData, setMapData] = useState<{ lat: number; lng: number }[] | null>(
|
||||
null
|
||||
);
|
||||
|
||||
// handle planting details submission
|
||||
const handlePlantingDetailsChange = (data: plantingSchema) => {
|
||||
setPlantingDetails(data);
|
||||
};
|
||||
|
||||
// handle harvest details submission
|
||||
const handleHarvestDetailsChange = (data: harvestSchema) => {
|
||||
setHarvestDetails(data);
|
||||
};
|
||||
|
||||
// handle map area selection
|
||||
const handleMapDataChange = (data: { lat: number; lng: number }[]) => {
|
||||
setMapData((prevMapData) => {
|
||||
if (prevMapData) {
|
||||
return [...prevMapData, ...data];
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// log the changes
|
||||
useEffect(() => {
|
||||
// console.log(plantingDetails);
|
||||
// console.log(harvestDetails);
|
||||
console.table(mapData);
|
||||
}, [plantingDetails, harvestDetails, mapData]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!plantingDetails || !harvestDetails || !mapData) {
|
||||
alert("Please complete all sections before submitting.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = {
|
||||
plantingDetails,
|
||||
harvestDetails,
|
||||
mapData,
|
||||
};
|
||||
|
||||
console.log("Form data to be submitted:", formData);
|
||||
|
||||
fetch("/api/submit", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Response from backend:", data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error submitting form:", error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-5">
|
||||
{/* Planting Details Section */}
|
||||
<div className="flex justify-center">
|
||||
<h1 className="flex text-2xl ">Plating Details</h1>
|
||||
<h1 className="text-2xl">Planting Details</h1>
|
||||
</div>
|
||||
<Separator className="mt-3" />
|
||||
<div className="mt-10 flex justify-center">
|
||||
<PlantingDetailsForm />
|
||||
<PlantingDetailsForm onChange={handlePlantingDetailsChange} />
|
||||
</div>
|
||||
|
||||
{/* Harvest Details Section */}
|
||||
<div className="flex justify-center mt-20">
|
||||
<h1 className="flex text-2xl ">Harvest Details</h1>
|
||||
<h1 className="text-2xl">Harvest Details</h1>
|
||||
</div>
|
||||
<Separator className="mt-3" />
|
||||
<div className="mt-10 flex justify-center">
|
||||
<HarvestDetailsForm />
|
||||
<HarvestDetailsForm onChange={handleHarvestDetailsChange} />
|
||||
</div>
|
||||
|
||||
{/* Map Section */}
|
||||
<div className="mt-10">
|
||||
<div className="flex justify-center mt-20">
|
||||
<h1 className="flex text-2xl ">Map</h1>
|
||||
<h1 className="text-2xl">Map</h1>
|
||||
</div>
|
||||
<Separator className="mt-3" />
|
||||
<div className="mt-10">
|
||||
<GoogleMapWithDrawing />
|
||||
<GoogleMapWithDrawing onAreaSelected={handleMapDataChange} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="mt-10 flex justify-center">
|
||||
<button onClick={handleSubmit} className="btn btn-primary">
|
||||
Submit All Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -22,17 +22,42 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type plantingSchema = z.infer<typeof plantingDetailsFormSchema>;
|
||||
|
||||
export default function PlantingDetailsForm() {
|
||||
const form = useForm<plantingSchema>({
|
||||
export default function PlantingDetailsForm({
|
||||
onChange,
|
||||
}: {
|
||||
onChange: (data: plantingSchema) => void;
|
||||
}) {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(plantingDetailsFormSchema),
|
||||
defaultValues: {},
|
||||
defaultValues: {
|
||||
daysToEmerge: 0,
|
||||
plantSpacing: 0,
|
||||
rowSpacing: 0,
|
||||
plantingDepth: 0,
|
||||
averageHeight: 0,
|
||||
startMethod: "",
|
||||
lightProfile: "",
|
||||
soilConditions: "",
|
||||
plantingDetails: "",
|
||||
pruningDetails: "",
|
||||
isPerennial: false,
|
||||
autoCreateTasks: false,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: plantingSchema) => {
|
||||
onChange(data);
|
||||
};
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form className="grid grid-cols-3 gap-5">
|
||||
<form
|
||||
className="grid grid-cols-3 gap-5"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="daysToEmerge"
|
||||
@ -47,6 +72,13 @@ export default function PlantingDetailsForm() {
|
||||
id="daysToEmerge"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,6 +101,13 @@ export default function PlantingDetailsForm() {
|
||||
id="plantSpacing"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -91,6 +130,13 @@ export default function PlantingDetailsForm() {
|
||||
id="rowSpacing"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -115,6 +161,13 @@ export default function PlantingDetailsForm() {
|
||||
id="plantingDepth"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -139,6 +192,13 @@ export default function PlantingDetailsForm() {
|
||||
id="averageHeight"
|
||||
className="w-96"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
// convert to number
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -187,9 +247,9 @@ export default function PlantingDetailsForm() {
|
||||
<SelectValue placeholder="Select light profile" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="xp">Seed</SelectItem>
|
||||
<SelectItem value="xa">Transplant</SelectItem>
|
||||
<SelectItem value="xz">Cutting</SelectItem>
|
||||
<SelectItem value="Seed">Seed</SelectItem>
|
||||
<SelectItem value="Transplant">Transplant</SelectItem>
|
||||
<SelectItem value="Cutting">Cutting</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
@ -214,9 +274,9 @@ export default function PlantingDetailsForm() {
|
||||
<SelectValue placeholder="Select a soil condition" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="xp">Seed</SelectItem>
|
||||
<SelectItem value="xa">Transplant</SelectItem>
|
||||
<SelectItem value="xz">Cutting</SelectItem>
|
||||
<SelectItem value="Seed">Seed</SelectItem>
|
||||
<SelectItem value="Transplant">Transplant</SelectItem>
|
||||
<SelectItem value="Cutting">Cutting</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
@ -289,7 +349,7 @@ export default function PlantingDetailsForm() {
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="isPerennial"
|
||||
name="autoCreateTasks"
|
||||
render={({ field }: { field: any }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
@ -308,6 +368,9 @@ export default function PlantingDetailsForm() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="col-span-3 flex justify-center">
|
||||
<Button type="submit">Save</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
import { GoogleMap, LoadScript, DrawingManager } from "@react-google-maps/api";
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
@ -10,17 +8,80 @@ const containerStyle = {
|
||||
|
||||
const center = { lat: 13.7563, lng: 100.5018 }; // Example: Bangkok, Thailand
|
||||
|
||||
const GoogleMapWithDrawing = () => {
|
||||
interface GoogleMapWithDrawingProps {
|
||||
onAreaSelected: (data: { lat: number; lng: number }[]) => void;
|
||||
}
|
||||
|
||||
const GoogleMapWithDrawing = ({
|
||||
onAreaSelected,
|
||||
}: GoogleMapWithDrawingProps) => {
|
||||
const [map, setMap] = useState<google.maps.Map | null>(null);
|
||||
|
||||
// Handles drawing complete
|
||||
const onDrawingComplete = useCallback((overlay: google.maps.drawing.OverlayCompleteEvent) => {
|
||||
console.log("Drawing complete:", overlay);
|
||||
}, []);
|
||||
const onDrawingComplete = useCallback(
|
||||
(overlay: google.maps.drawing.OverlayCompleteEvent) => {
|
||||
const shape = overlay.overlay;
|
||||
|
||||
// check the shape of the drawing and extract lat/lng values
|
||||
if (shape instanceof google.maps.Polygon) {
|
||||
const path = shape.getPath();
|
||||
const coordinates = path.getArray().map((latLng) => ({
|
||||
lat: latLng.lat(),
|
||||
lng: latLng.lng(),
|
||||
}));
|
||||
console.log("Polygon coordinates:", coordinates);
|
||||
onAreaSelected(coordinates);
|
||||
} else if (shape instanceof google.maps.Rectangle) {
|
||||
const bounds = shape.getBounds();
|
||||
if (bounds) {
|
||||
const northEast = bounds.getNorthEast();
|
||||
const southWest = bounds.getSouthWest();
|
||||
const coordinates = [
|
||||
{ lat: northEast.lat(), lng: northEast.lng() },
|
||||
{ lat: southWest.lat(), lng: southWest.lng() },
|
||||
];
|
||||
console.log("Rectangle coordinates:", coordinates);
|
||||
onAreaSelected(coordinates);
|
||||
}
|
||||
} else if (shape instanceof google.maps.Circle) {
|
||||
const center = shape.getCenter();
|
||||
const radius = shape.getRadius();
|
||||
if (center) {
|
||||
const coordinates = [
|
||||
{
|
||||
lat: center.lat(),
|
||||
lng: center.lng(),
|
||||
radius: radius, // circle's radius in meters
|
||||
},
|
||||
];
|
||||
console.log("Circle center:", coordinates);
|
||||
onAreaSelected(coordinates);
|
||||
}
|
||||
} else if (shape instanceof google.maps.Polyline) {
|
||||
const path = shape.getPath();
|
||||
const coordinates = path.getArray().map((latLng) => ({
|
||||
lat: latLng.lat(),
|
||||
lng: latLng.lng(),
|
||||
}));
|
||||
console.log("Polyline coordinates:", coordinates);
|
||||
onAreaSelected(coordinates);
|
||||
} else {
|
||||
console.log("Unknown shape detected:", shape);
|
||||
}
|
||||
},
|
||||
[onAreaSelected]
|
||||
);
|
||||
|
||||
return (
|
||||
<LoadScript googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!} libraries={["drawing"]}>
|
||||
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={10} onLoad={(map) => setMap(map)}>
|
||||
<LoadScript
|
||||
googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}
|
||||
libraries={["drawing"]}
|
||||
>
|
||||
<GoogleMap
|
||||
mapContainerStyle={containerStyle}
|
||||
center={center}
|
||||
zoom={10}
|
||||
onLoad={(map) => setMap(map)}
|
||||
>
|
||||
{map && (
|
||||
<DrawingManager
|
||||
onOverlayComplete={onDrawingComplete}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user