diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index ef18fd4..e3d9c37 100644 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -65,15 +65,44 @@ async function request(endpoint, token, options = {}) { } export function createProject(token, { name, city_input }) { + if (!name.trim() || !city_input.trim()) { + console.error("Field cannot be empty") + return + } return request("/projects", token, { method: "POST", body: JSON.stringify({ name, city_input }) }) } -export function updateProject(token, projectId, updates) { - return request(`/projects/${projectId}`, token, { +export function updateResource(token, endpoint, updates) { + if (!updates || typeof updates !== "object" || Object.keys(updates).length === 0) { + console.error("No updates provided") + return + } + + return request(endpoint, token, { method: "PATCH", body: JSON.stringify(updates) }) +} + +export function updateResourceField(token, endpoint, field, value, options = {}) { + const { trim = false, normalize = (currentValue) => currentValue } = options + + if (value === null || value === undefined) { + return + } + + let normalizedValue = normalize(value) + + if (trim && typeof normalizedValue === "string") { + normalizedValue = normalizedValue.trim() + } + + return updateResource(token, endpoint, { [field]: normalizedValue }) +} + +export function updateProject(token, projectId, updates) { + return updateResource(token, `/projects/${projectId}`, updates) } \ No newline at end of file diff --git a/frontend/src/screens/CreateScenario.jsx b/frontend/src/screens/CreateScenario.jsx index ef8dd69..4235fdf 100644 --- a/frontend/src/screens/CreateScenario.jsx +++ b/frontend/src/screens/CreateScenario.jsx @@ -37,6 +37,19 @@ export default function CreateScenario() { const { token } = useContext(AuthContext) const { data: project, loading: loading, error: error } = useApi(`/api/projects/${projectId}`) + const [scenarioName, setScenarioName] = useState("Scenario Name") + + async function handleNameSubmit(e) { + e?.preventDefault?.() + + try { + await updateResourceField(token, `/projects/${projectId}/scenarios/${scenarioId}`, "name", scenarioName, { trim: true }) + } catch (error) { + console.error("Error updating scenario name:", error) + } + console.log("Scenario name updated:", scenarioName) + } + if (loading) return

Loading...

if (error) return

Something went wrong.

@@ -62,9 +75,9 @@ export default function CreateScenario() {
- setProjectName(e.target.value)} + setScenarioName(e.target.value)} />
@@ -76,17 +89,9 @@ export default function CreateScenario() {

System configuration

- {/*
- - setProjectLocation(e.target.value)} - /> -
*/} - Module amount - + Quantity of modules in the system @@ -99,10 +104,10 @@ export default function CreateScenario() { - Orientation + Azimuth - The angle relative to the South, in degrees. South orientation is 0° + Orientation relative to the South, in degrees. South orientation is 0°
@@ -125,12 +130,6 @@ export default function CreateScenario() { Selected solar module - {/* - - Technology - Nominal power - - */} Technology diff --git a/frontend/src/screens/Project.jsx b/frontend/src/screens/Project.jsx index 02c79a9..845f724 100644 --- a/frontend/src/screens/Project.jsx +++ b/frontend/src/screens/Project.jsx @@ -14,20 +14,23 @@ import Header from "../components/Header"; import ScenarioCard from "../components/ScenarioCard"; import { AuthContext } from "../lib/AuthContext" -import { useApi } from "../lib/api" +import { useApi, updateResourceField } from "../lib/api" const Project = () => { const { projectId, scenarioId } = useParams(); const { token } = useContext(AuthContext) - const { data: project, loading: projLoading, error: projError } = useApi(`/api/projects/${params.projectId}`) - const { data: scenarios, loading: scenLoading, error: scenError } = useApi(`/api/projects/${params.projectId}/scenarios`) + const { data: project, loading: projLoading, error: projError } = useApi(`/api/projects/${projectId}`) + const { data: scenarios, loading: scenLoading, error: scenError } = useApi(`/api/projects/${projectId}/scenarios`) const [projectName, setProjectName] = useState("") const [projectLocation, setProjectLocation] = useState("") useEffect(() => { - if (project) setProjectName(project.name) + if (project) { + setProjectName(project.name ?? "") + setProjectLocation(project.location ?? "") + } }, [project]) useEffect(() => { @@ -40,53 +43,23 @@ const Project = () => { async function handleNameSubmit(e) { e.preventDefault() - if (!projectName.trim()) { - console.error("Project name cannot be empty") - return - } - try { - const response = await fetch(`/api/projects/${params.projectId}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}` - }, - body: JSON.stringify({ name: projectName.trim() }) - }) - - if (!response.ok) { - console.error("Failed to update project name") - } + await updateResourceField(token, `/projects/${projectId}`, "name", projectName, { trim: true }) } catch (error) { console.error("Error updating project name:", error) } + console.log("Project name updated:", projectName) } - async function handleLocationSubmit(e) { + async function handleLocationSubmit(e) { e.preventDefault() - if (!projectLocation.trim()) { - console.error("Project location cannot be empty") - return - } - try { - const response = await fetch(`/api/projects/${params.projectId}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}` - }, - body: JSON.stringify({ location: projectLocation.trim() }) - }) - - if (!response.ok) { - console.error("Failed to update project location") - } + await updateResourceField(token, `/projects/${projectId}`, "location", projectLocation, { trim: true }) } catch (error) { console.error("Error updating project location:", error) } + console.log("Location updated:", projectLocation) } return ( diff --git a/frontend/src/screens/Scenario.jsx b/frontend/src/screens/Scenario.jsx index f7266de..47c1dbd 100644 --- a/frontend/src/screens/Scenario.jsx +++ b/frontend/src/screens/Scenario.jsx @@ -1,3 +1,198 @@ +import { useContext, useEffect, useState } from "react"; +import { Link, useParams } from "react-router" + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb" +import { Button } from "@/components/ui/button" +import { + Field, + FieldDescription, + FieldGroup, + FieldLabel, +} from "@/components/ui/field" +import Header from "../components/Header"; +import { Input } from "@/components/ui/input" +import { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" + +import { AuthContext } from "../lib/AuthContext" +import { useApi, updateResourceField } from "../lib/api" + export default function Scenario() { - return

Scenario Screen

; -} + const { projectId, scenarioId } = useParams(); + const { token } = useContext(AuthContext) + const { data: project, loading: projLoading, error: projError } = useApi(`/api/projects/${projectId}`) + const { data: scenario, loading: scenLoading, error: scenError } = useApi(`/api/projects/${projectId}/scenarios/${scenarioId}`) + + const [scenarioName, setScenarioName] = useState("") + const [moduleAmount, setModuleAmount] = useState("") + const [tilt, setTilt] = useState("") + const [orientation, setOrientation] = useState("") + const [manufacturer, setManufacturer] = useState("") + const [model, setModel] = useState("") + + useEffect(() => { + if (scenario) { + setScenarioName(scenario.name ?? "") + setModuleAmount(scenario.module_amount ?? "") + setTilt(scenario.tilt ?? "") + setOrientation(scenario.azimuth ?? "") + setManufacturer(scenario.manufacturer ?? "") + setModel(scenario.model ?? "") + } + }, [scenario]) + + async function handleNameSubmit(e) { + e?.preventDefault?.() + + try { + await updateResourceField(token, `/projects/${projectId}/scenarios/${scenarioId}`, "name", scenarioName, { trim: true }) + } catch (error) { + console.error("Error updating scenario name:", error) + } + console.log("Scenario name updated:", scenarioName) + } + + async function handleScenarioFieldUpdate(field, value, options = {}) { + try { + await updateResourceField(token, `/projects/${projectId}/scenarios/${scenarioId}`, field, value, options) + } catch (error) { + console.error(`Error updating scenario ${field}:`, error) + } + } + + if (projLoading || scenLoading) return

Loading...

+ if (projError || scenError) return

Something went wrong.

+ + return ( + <> +
+
+
+ + + + Your projects + + + + {project.name} + + + + {scenario.name} + + + +
+
+ handleNameSubmit()} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleNameSubmit(e) + } + }} + onChange={(e) => setScenarioName(e.target.value)} + /> + + + + +
+
+
+
+ +

System configuration

+ + Module amount + handleScenarioFieldUpdate("module_amount", Number(moduleAmount))} + onChange={(e) => setModuleAmount(e.target.value)} /> + + Quantity of modules in the system + + + + Tilt + handleScenarioFieldUpdate("tilt", Number(tilt))} + onChange={(e) => setTilt(e.target.value)} + placeholder="e.g. 30" min="0" max="360"/> + + The angle towards the Sun, in degrees (between 0° and 360°) + + + + Azimuth + handleScenarioFieldUpdate("azimuth", Number(orientation))} + onChange={(e) => setOrientation(e.target.value)} placeholder="e.g. 0" min="0" max="360"/> + + Orientation relative to the South, in degrees. South orientation is 0° + + +
+
+ +

Solar module

+ + Manufacturer + + + The company that produced the solar module + + + + Model + + + The model name of the solar module + + +
+ Selected solar module + + + Technology + Mono c-Si + + + Nominal power + 400 Wp + + + Area + 1.6 m² + + + Temperature coefficient + -0.48%/°C + + + +
+ +
+
+ + + + ) +} \ No newline at end of file