feat: add mobile support for Workspaces page with workspace creation

- WorkspacesPage.tsx: detect mobile viewport, show MobileListView / MobileDetailView / MobileFAB
- Mobile list: tap workspace to view details (branch, status, path, instances, sync time)
- Mobile detail: view workspace fields with Edit and Delete actions
- Mobile FAB: opens inline WorkspaceCreateForm
- New styles/pages/workspaces.css with responsive grid and card styles
- Import workspaces.css in main.tsx

Quality gates: tsc --noEmit pass, npm run build pass, 80/80 tests pass
This commit is contained in:
Developer
2026-06-07 14:09:28 +00:00
parent 734bd9529a
commit 4ea2e3659d
3 changed files with 238 additions and 0 deletions
+111
View File
@@ -2,14 +2,23 @@
import { useState } from "react";
import { Icon } from "../components/icon";
import { useMobileViewport } from "../hooks/use-mobile-viewport";
import { useWorkspaces } from "../hooks/use-workspaces";
import { useWorkspaceActions } from "../hooks/use-workspace-actions";
import { WorkspaceCard } from "../components/features/workspace/workspace-card";
import { WorkspaceCreateForm } from "../components/features/workspace/workspace-create-form";
import { MobileListView } from "../components/features/mobile/mobile-list-view";
import { MobileDetailView } from "../components/features/mobile/mobile-detail-view";
import { MobileFAB } from "../components/features/mobile/mobile-fab";
import { ToolStarter } from "../components/features/tool/tool-starter";
import type { Workspace } from "../types/workspace";
type MobileView = "list" | "detail" | "create";
export function WorkspacesPage() {
const isMobile = useMobileViewport();
const [mobileView, setMobileView] = useState<MobileView>("list");
const [selectedWorkspace, setSelectedWorkspace] = useState<Workspace | null>(null);
const [showCreate, setShowCreate] = useState(false);
const [startWorkspace, setStartWorkspace] = useState<Workspace | null>(null);
@@ -28,6 +37,108 @@ export function WorkspacesPage() {
);
};
/* ── Mobile views ── */
if (isMobile) {
if (mobileView === "create") {
return (
<div className="mobile-page">
<WorkspaceCreateForm
onSubmit={async () => {
setMobileView("list");
await refresh();
}}
onCancel={() => setMobileView("list")}
/>
</div>
);
}
if (mobileView === "detail" && selectedWorkspace) {
const ws = selectedWorkspace;
return (
<MobileDetailView
title={ws.name}
subtitle={`${ws.project_name} / ${ws.repo_name}`}
fields={[
{ label: "Branch", value: ws.branch },
{ label: "Status", value: ws.status },
{ label: "Path", value: ws.path },
{ label: "Instances", value: ws.instance_count },
{ label: "Last Sync", value: ws.last_sync_at ?? "Never" },
{ label: "Created", value: ws.created_at },
]}
onEdit={() => {
setStartWorkspace(ws);
}}
onDelete={() => {
void handleDelete(ws);
setMobileView("list");
setSelectedWorkspace(null);
}}
onBack={() => {
setMobileView("list");
setSelectedWorkspace(null);
}}
/>
);
}
/* Mobile list view */
return (
<div className="mobile-page">
<div className="mobile-page-header">
<h1>Workspaces</h1>
<span className="muted">{workspaces.length} workspace{workspaces.length !== 1 ? "s" : ""}</span>
</div>
{error && <div className="alert alert-error">{error}</div>}
{loading && workspaces.length === 0 ? (
<div className="loading-state">Loading workspaces...</div>
) : (
<MobileListView
items={workspaces.map((ws) => ({
id: ws.id,
title: ws.name,
subtitle: `${ws.project_name} · ${ws.repo_name} · ${ws.branch}`,
status: ws.status,
}))}
onItemClick={(id) => {
const ws = workspaces.find((w) => w.id === id);
if (ws) {
setSelectedWorkspace(ws);
setMobileView("detail");
}
}}
emptyMessage="No workspaces yet"
/>
)}
<MobileFAB
onClick={() => setMobileView("create")}
label="Create workspace"
/>
{startWorkspace && (
<div className="modal-overlay" onClick={() => setStartWorkspace(null)}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h3>Start Tool</h3>
<ToolStarter
workspace={startWorkspace}
onStarted={() => {
setStartWorkspace(null);
void refresh();
}}
onCancel={() => setStartWorkspace(null)}
/>
</div>
</div>
)}
</div>
);
}
/* ── Desktop view ── */
return (
<div className="page workspaces-page">
<header className="page-header">