Spaces:
Running
Running
| import { useState, type FormEvent, type ReactNode } from "react"; | |
| import { Drawer } from "../ui/Drawer"; | |
| import type { ProjectInput, Team } from "../../types"; | |
| const PROJECT_COLORS = ["#0ea5e9", "#16a34a", "#f97316", "#a855f7", "#facc15", "#ef4444", "#0f766e"]; | |
| const LIFECYCLE_STATUSES = [ | |
| { value: "planning", label: "Planning" }, | |
| { value: "active", label: "Active" }, | |
| { value: "on_hold", label: "On hold" }, | |
| { value: "completed", label: "Completed" }, | |
| ] as const; | |
| interface ProjectDrawerProps { | |
| open: boolean; | |
| editingId: number | null; | |
| form: ProjectInput; | |
| setForm: (next: ProjectInput) => void; | |
| teams: Team[]; | |
| onClose: () => void; | |
| onSubmit: (event: FormEvent<HTMLFormElement>) => void; | |
| isSaving: boolean; | |
| footerExtras?: ReactNode; | |
| } | |
| export function ProjectDrawer({ | |
| open, | |
| editingId, | |
| form, | |
| setForm, | |
| teams, | |
| onClose, | |
| onSubmit, | |
| isSaving, | |
| footerExtras, | |
| }: ProjectDrawerProps) { | |
| const [tab, setTab] = useState<"details" | "notes">("details"); | |
| const title = editingId ? `Edit ${form.name || "project"}` : "New project"; | |
| return ( | |
| <Drawer | |
| open={open} | |
| onClose={onClose} | |
| title={title} | |
| icon={<span className="project-swatch drawer-project-swatch" style={{ background: form.color }} />} | |
| tabs={ | |
| <> | |
| <button | |
| className={`drawer-tab ${tab === "details" ? "active" : ""}`} | |
| onClick={() => setTab("details")} | |
| type="button" | |
| > | |
| Details | |
| </button> | |
| <button | |
| className={`drawer-tab ${tab === "notes" ? "active" : ""}`} | |
| onClick={() => setTab("notes")} | |
| type="button" | |
| > | |
| Notes | |
| </button> | |
| </> | |
| } | |
| footer={ | |
| <> | |
| {footerExtras} | |
| <span className="drawer-footer-spacer" /> | |
| <button className="secondary-button" onClick={onClose} type="button"> | |
| Cancel | |
| </button> | |
| <button className="primary-button" disabled={isSaving} form="project-drawer-form" type="submit"> | |
| Save | |
| </button> | |
| </> | |
| } | |
| > | |
| <form className="drawer-form" id="project-drawer-form" onSubmit={onSubmit}> | |
| {tab === "details" ? ( | |
| <> | |
| <label> | |
| Project Name | |
| <input | |
| onChange={(event) => setForm({ ...form, name: event.target.value })} | |
| required | |
| type="text" | |
| value={form.name} | |
| /> | |
| </label> | |
| <label> | |
| Status | |
| <select | |
| onChange={(event) => setForm({ ...form, is_tentative: event.target.value === "tentative" })} | |
| value={form.is_tentative ? "tentative" : "confirmed"} | |
| > | |
| <option value="confirmed">Confirmed</option> | |
| <option value="tentative">Tentative</option> | |
| </select> | |
| </label> | |
| <label> | |
| Primary Team <small>(optional)</small> | |
| <select | |
| onChange={(event) => | |
| setForm({ ...form, team_id: event.target.value ? Number(event.target.value) : null }) | |
| } | |
| value={form.team_id ?? ""} | |
| > | |
| <option value="">No team</option> | |
| {teams.map((team) => ( | |
| <option key={team.id} value={team.id}> | |
| {team.name} | |
| </option> | |
| ))} | |
| </select> | |
| </label> | |
| <label> | |
| Lifecycle <small>(internal)</small> | |
| <select onChange={(event) => setForm({ ...form, status: event.target.value })} value={form.status}> | |
| {LIFECYCLE_STATUSES.map((status) => ( | |
| <option key={status.value} value={status.value}> | |
| {status.label} | |
| </option> | |
| ))} | |
| </select> | |
| </label> | |
| <label> | |
| Project type | |
| <select onChange={(event) => setForm({ ...form, type: event.target.value })} value={form.type}> | |
| <option value="infrastructure">Infrastructure</option> | |
| <option value="development">Development</option> | |
| <option value="support">Support</option> | |
| <option value="other">Other</option> | |
| </select> | |
| </label> | |
| <label> | |
| Color | |
| <div className="color-picker"> | |
| {PROJECT_COLORS.map((color) => ( | |
| <button | |
| aria-label={`Use color ${color}`} | |
| className={`color-swatch ${form.color === color ? "selected" : ""}`} | |
| key={color} | |
| onClick={() => setForm({ ...form, color })} | |
| style={{ background: color }} | |
| type="button" | |
| /> | |
| ))} | |
| </div> | |
| </label> | |
| </> | |
| ) : null} | |
| {tab === "notes" ? ( | |
| <label> | |
| Notes | |
| <textarea | |
| onChange={(event) => setForm({ ...form, description: event.target.value })} | |
| placeholder="Project notes and context" | |
| rows={8} | |
| value={form.description ?? ""} | |
| /> | |
| </label> | |
| ) : null} | |
| </form> | |
| </Drawer> | |
| ); | |
| } | |