feat: workspace frontend core (PR-3)

- Workspace types, API client, hooks (useWorkspaces, useWorkspaceActions)
- WorkspaceCard, WorkspaceCreateForm, StartToolModal components
- WorkspacesPage with list, create, sync, delete, start-tool flow
- Sidebar navigation: new 'Workspaces' entry
- Router: /workspaces route
- TypeScript + eslint clean
This commit is contained in:
2026-05-31 23:27:10 +02:00
parent 47b1af8e92
commit 986091ac56
10 changed files with 623 additions and 0 deletions
@@ -0,0 +1,73 @@
/** Card component for displaying a workspace. */
import { Icon } from "./icon";
import type { Workspace } from "../types/workspace";
export interface WorkspaceCardProps {
workspace: Workspace;
loading?: boolean;
onStartTool: (workspace: Workspace) => void;
onSync: (workspace: Workspace) => void;
onDelete: (workspace: Workspace) => void;
}
export function WorkspaceCard({
workspace,
loading = false,
onStartTool,
onSync,
onDelete,
}: WorkspaceCardProps) {
const statusClass =
workspace.status === "ready"
? "status-ready"
: workspace.status === "syncing"
? "status-syncing"
: "status-error";
return (
<article className={`card workspace-card ${loading ? "loading" : ""}`}>
<div className="workspace-header">
<h4>{workspace.name}</h4>
<span className={`status-badge ${statusClass}`}>{workspace.status}</span>
</div>
<div className="workspace-meta">
<p className="workspace-project">
{workspace.project_name} / {workspace.repo_name}
</p>
<p className="workspace-branch">
<Icon name="branch" size="sm" /> {workspace.branch}
</p>
{workspace.instance_count > 0 && (
<p className="workspace-instances">
{workspace.instance_count} active tool
{workspace.instance_count > 1 ? "s" : ""}
</p>
)}
</div>
<div className="workspace-actions">
<button
className="btn btn-primary"
onClick={() => onStartTool(workspace)}
disabled={loading}
>
<Icon name="play" size="sm" /> Start Tool
</button>
<button
className="btn btn-secondary"
onClick={() => onSync(workspace)}
disabled={loading}
>
<Icon name="refresh" size="sm" /> Sync
</button>
<button
className="btn btn-danger"
onClick={() => onDelete(workspace)}
disabled={loading}
>
<Icon name="delete" size="sm" /> Delete
</button>
</div>
</article>
);
}