diff --git a/src/App.tsx b/src/App.tsx index c7adacc..3230d38 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,7 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import AppLayout from "./components/layout/AppLayout"; +import ProjectForm from "./components/projects/ProjectForm"; +import ProjectDashboard from "./components/projects/ProjectDashboard"; function EmptyState() { return ( @@ -15,9 +17,9 @@ function App() { }> } /> - Create project (coming next)} /> - Project dashboard (coming next)} /> - Edit project (coming next)} /> + } /> + } /> + } /> } /> diff --git a/src/components/projects/ProjectDashboard.tsx b/src/components/projects/ProjectDashboard.tsx new file mode 100644 index 0000000..4646cbe --- /dev/null +++ b/src/components/projects/ProjectDashboard.tsx @@ -0,0 +1,76 @@ +import { useEffect, useState } from "react"; +import { useParams, Link, useNavigate } from "react-router-dom"; +import { getProject, deleteProject } from "../../lib/api"; +import type { Project } from "../../lib/types"; + +export default function ProjectDashboard() { + const { projectId } = useParams(); + const navigate = useNavigate(); + const [project, setProject] = useState(null); + + useEffect(() => { + if (projectId) { + getProject(projectId).then(setProject); + } + }, [projectId]); + + async function handleDelete() { + if (!projectId) return; + if (!window.confirm(`Delete project "${project?.name}"?`)) return; + + await deleteProject(projectId); + window.dispatchEvent(new Event("orchai:refresh-projects")); + navigate("/"); + } + + if (!project) { + return
Loading...
; + } + + return ( +
+
+

{project.name}

+
+ + Edit + + +
+
+ +
+
+ Path: + {project.path} +
+ {project.cloned_from && ( +
+ Cloned from: + {project.cloned_from} +
+ )} +
+ Base branch: + {project.base_branch} +
+
+ Created: + {new Date(project.created_at).toLocaleDateString()} +
+
+ +
+ Tracker surveillance and ticket processing will be available in the next update. +
+
+ ); +} diff --git a/src/components/projects/ProjectForm.tsx b/src/components/projects/ProjectForm.tsx new file mode 100644 index 0000000..6c46845 --- /dev/null +++ b/src/components/projects/ProjectForm.tsx @@ -0,0 +1,174 @@ +import { useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { open } from "@tauri-apps/plugin-dialog"; +import { createProject, getProject, updateProject } from "../../lib/api"; + +export default function ProjectForm() { + const navigate = useNavigate(); + const { projectId } = useParams(); + const isEditing = Boolean(projectId); + + const [name, setName] = useState(""); + const [pathOrUrl, setPathOrUrl] = useState(""); + const [baseBranch, setBaseBranch] = useState("main"); + const [mode, setMode] = useState<"local" | "clone">("local"); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (projectId) { + getProject(projectId).then((project) => { + setName(project.name); + setPathOrUrl(project.path); + setBaseBranch(project.base_branch); + if (project.cloned_from) { + setMode("clone"); + } + }); + } + }, [projectId]); + + async function handleBrowse() { + const selected = await open({ directory: true, multiple: false }); + if (selected) { + setPathOrUrl(selected as string); + } + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + setLoading(true); + + try { + if (isEditing && projectId) { + await updateProject(projectId, name, baseBranch); + } else { + await createProject(name, pathOrUrl, baseBranch); + } + window.dispatchEvent(new Event("orchai:refresh-projects")); + navigate("/"); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + setError(message); + } finally { + setLoading(false); + } + } + + return ( +
+

+ {isEditing ? "Edit project" : "New project"} +

+ +
+
+ + setName(e.target.value)} + required + className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ + {!isEditing && ( + <> +
+ +
+ + +
+
+ +
+ +
+ setPathOrUrl(e.target.value)} + required + placeholder={ + mode === "local" + ? "/home/user/code/myproject" + : "https://github.com/org/repo.git" + } + className="flex-1 border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + {mode === "local" && ( + + )} +
+
+ + )} + +
+ + setBaseBranch(e.target.value)} + required + className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ + {error && ( +
+ {error} +
+ )} + +
+ + +
+
+
+ ); +}