Add detail pages, property panels, and restyle list pages

New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-17 09:57:06 -06:00
parent fad1bd27ce
commit f4339668f3
22 changed files with 1833 additions and 293 deletions

View File

@@ -0,0 +1,142 @@
import { useState } from "react";
import { useDialog } from "../context/DialogContext";
import { useCompany } from "../context/CompanyContext";
import { issuesApi } from "../api/issues";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
interface NewIssueDialogProps {
onCreated?: () => void;
}
export function NewIssueDialog({ onCreated }: NewIssueDialogProps) {
const { newIssueOpen, newIssueDefaults, closeNewIssue } = useDialog();
const { selectedCompanyId } = useCompany();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [status, setStatus] = useState(newIssueDefaults.status ?? "todo");
const [priority, setPriority] = useState(newIssueDefaults.priority ?? "medium");
const [submitting, setSubmitting] = useState(false);
function reset() {
setTitle("");
setDescription("");
setStatus("todo");
setPriority("medium");
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!selectedCompanyId || !title.trim()) return;
setSubmitting(true);
try {
await issuesApi.create(selectedCompanyId, {
title: title.trim(),
description: description.trim() || undefined,
status,
priority,
});
reset();
closeNewIssue();
onCreated?.();
} finally {
setSubmitting(false);
}
}
return (
<Dialog
open={newIssueOpen}
onOpenChange={(open) => {
if (!open) {
reset();
closeNewIssue();
}
}}
>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>New Issue</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="issue-title">Title</Label>
<Input
id="issue-title"
placeholder="Issue title"
value={title}
onChange={(e) => setTitle(e.target.value)}
autoFocus
/>
</div>
<div className="space-y-2">
<Label htmlFor="issue-desc">Description</Label>
<Textarea
id="issue-desc"
placeholder="Add a description..."
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
/>
</div>
<div className="flex gap-4">
<div className="space-y-2 flex-1">
<Label>Status</Label>
<Select value={status} onValueChange={setStatus}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="backlog">Backlog</SelectItem>
<SelectItem value="todo">Todo</SelectItem>
<SelectItem value="in_progress">In Progress</SelectItem>
<SelectItem value="in_review">In Review</SelectItem>
<SelectItem value="done">Done</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 flex-1">
<Label>Priority</Label>
<Select value={priority} onValueChange={setPriority}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="critical">Critical</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="low">Low</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={closeNewIssue}>
Cancel
</Button>
<Button type="submit" disabled={!title.trim() || submitting}>
{submitting ? "Creating..." : "Create Issue"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}