@prmichaelsen/acp-visualizer 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/Header.tsx +9 -6
- package/src/components/MilestoneTable.tsx +70 -3
- package/src/components/SidePanel.tsx +1 -1
- package/src/components/Sidebar.tsx +14 -4
- package/src/routes/__root.tsx +44 -12
- package/src/routes/milestones.$milestoneId.tsx +2 -2
- package/src/routes/tasks.$taskId.tsx +5 -5
package/package.json
CHANGED
|
@@ -14,15 +14,18 @@ export function Header({ data }: HeaderProps) {
|
|
|
14
14
|
if (!data) return null
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<header className="h-14 border-b border-gray-200 dark:border-gray-800 flex items-center
|
|
18
|
-
<h1 className="text-sm font-medium text-gray-900 dark:text-gray-200">{data.project.name}</h1>
|
|
19
|
-
<span className="text-xs text-gray-500 dark:text-gray-500 font-mono">v{data.project.version}</span>
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
<header className="h-14 border-b border-gray-200 dark:border-gray-800 flex items-center pl-16 lg:pl-6 pr-4 lg:pr-6 gap-2 lg:gap-4 shrink-0 bg-white dark:bg-gray-950">
|
|
18
|
+
<h1 className="text-sm font-medium text-gray-900 dark:text-gray-200 truncate">{data.project.name}</h1>
|
|
19
|
+
<span className="hidden sm:inline text-xs text-gray-500 dark:text-gray-500 font-mono">v{data.project.version}</span>
|
|
20
|
+
<div className="hidden sm:block">
|
|
21
|
+
<StatusBadge status={data.project.status} />
|
|
22
|
+
</div>
|
|
23
|
+
<div className="ml-auto flex items-center gap-2 lg:gap-4">
|
|
24
|
+
<div className="hidden md:flex items-center gap-3 w-32 lg:w-48">
|
|
23
25
|
<ProgressBar value={data.progress.overall} size="sm" />
|
|
24
26
|
<span className="text-xs text-gray-600 dark:text-gray-400 font-mono">{data.progress.overall}%</span>
|
|
25
27
|
</div>
|
|
28
|
+
<span className="md:hidden text-xs text-gray-600 dark:text-gray-400 font-mono">{data.progress.overall}%</span>
|
|
26
29
|
<button
|
|
27
30
|
onClick={toggleTheme}
|
|
28
31
|
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
|
@@ -36,6 +36,63 @@ export function MilestoneTable({ milestones, tasks }: MilestoneTableProps) {
|
|
|
36
36
|
})
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Mobile card view
|
|
40
|
+
const MobileCard = ({ milestone }: { milestone: Milestone }) => {
|
|
41
|
+
const milestoneTasks = tasks[milestone.id] || []
|
|
42
|
+
const isExpanded = expanded.has(milestone.id)
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="border border-gray-200 dark:border-gray-800 rounded-lg p-4 space-y-3">
|
|
46
|
+
<div className="flex items-start justify-between gap-2">
|
|
47
|
+
<div className="flex-1 min-w-0">
|
|
48
|
+
<Link
|
|
49
|
+
to="/milestones/$milestoneId"
|
|
50
|
+
params={{ milestoneId: milestone.id }}
|
|
51
|
+
className="text-sm font-medium text-gray-900 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400"
|
|
52
|
+
>
|
|
53
|
+
{formatMilestoneName(milestone)}
|
|
54
|
+
</Link>
|
|
55
|
+
</div>
|
|
56
|
+
<PreviewButton type="milestone" id={milestone.id} />
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className="flex items-center gap-2">
|
|
60
|
+
<StatusBadge status={milestone.status} />
|
|
61
|
+
<PriorityBadge priority={milestone.priority} />
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div className="flex items-center gap-2">
|
|
65
|
+
<div className="flex-1">
|
|
66
|
+
<ProgressBar value={milestone.progress} size="sm" />
|
|
67
|
+
</div>
|
|
68
|
+
<span className="text-xs text-gray-600 dark:text-gray-500 font-mono">{milestone.progress}%</span>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div className="flex items-center justify-between text-xs text-gray-600 dark:text-gray-500">
|
|
72
|
+
<span>Tasks: {milestone.tasks_completed}/{milestone.tasks_total}</span>
|
|
73
|
+
{milestoneTasks.length > 0 && (
|
|
74
|
+
<button
|
|
75
|
+
onClick={() => toggle(milestone.id)}
|
|
76
|
+
className="flex items-center gap-1 hover:text-gray-900 dark:hover:text-gray-300"
|
|
77
|
+
>
|
|
78
|
+
{isExpanded ? (
|
|
79
|
+
<ChevronDown className="w-4 h-4" />
|
|
80
|
+
) : (
|
|
81
|
+
<ChevronRight className="w-4 h-4" />
|
|
82
|
+
)}
|
|
83
|
+
</button>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{isExpanded && milestoneTasks.length > 0 && (
|
|
88
|
+
<div className="pt-2 border-t border-gray-200 dark:border-gray-800">
|
|
89
|
+
<TaskList tasks={milestoneTasks} />
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
39
96
|
const columns = [
|
|
40
97
|
columnHelper.display({
|
|
41
98
|
id: 'expand',
|
|
@@ -135,8 +192,17 @@ export function MilestoneTable({ milestones, tasks }: MilestoneTableProps) {
|
|
|
135
192
|
}
|
|
136
193
|
|
|
137
194
|
return (
|
|
138
|
-
|
|
139
|
-
|
|
195
|
+
<>
|
|
196
|
+
{/* Mobile Card View */}
|
|
197
|
+
<div className="lg:hidden space-y-3">
|
|
198
|
+
{milestones.map((milestone) => (
|
|
199
|
+
<MobileCard key={milestone.id} milestone={milestone} />
|
|
200
|
+
))}
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
{/* Desktop Table View */}
|
|
204
|
+
<div className="hidden lg:block border border-gray-800 rounded-lg overflow-hidden">
|
|
205
|
+
<table className="w-full">
|
|
140
206
|
<thead>
|
|
141
207
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
142
208
|
<tr key={headerGroup.id} className="border-b border-gray-800 bg-gray-900/50">
|
|
@@ -181,6 +247,7 @@ export function MilestoneTable({ milestones, tasks }: MilestoneTableProps) {
|
|
|
181
247
|
))}
|
|
182
248
|
</tbody>
|
|
183
249
|
</table>
|
|
184
|
-
|
|
250
|
+
</div>
|
|
251
|
+
</>
|
|
185
252
|
)
|
|
186
253
|
}
|
|
@@ -23,7 +23,7 @@ export function SidePanel() {
|
|
|
23
23
|
|
|
24
24
|
{/* Panel */}
|
|
25
25
|
<div
|
|
26
|
-
className={`fixed top-0 right-0 h-full w-full max-w-2xl bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-800 shadow-2xl z-50 overflow-auto transition-transform duration-300 ${
|
|
26
|
+
className={`fixed top-0 right-0 h-full w-full lg:max-w-2xl bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-800 shadow-2xl z-50 overflow-auto transition-transform duration-300 ${
|
|
27
27
|
isOpen ? 'translate-x-0' : 'translate-x-full'
|
|
28
28
|
}`}
|
|
29
29
|
>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Link, useRouterState } from '@tanstack/react-router'
|
|
2
|
-
import { LayoutDashboard, Flag, CheckSquare, Clock, Search, PenTool, Puzzle, FileBarChart } from 'lucide-react'
|
|
2
|
+
import { LayoutDashboard, Flag, CheckSquare, Clock, Search, PenTool, Puzzle, FileBarChart, X } from 'lucide-react'
|
|
3
3
|
import { ProjectSelector } from './ProjectSelector'
|
|
4
4
|
import { GitHubInput } from './GitHubInput'
|
|
5
5
|
import type { AcpProject } from '../services/projects.service'
|
|
@@ -19,17 +19,27 @@ interface SidebarProps {
|
|
|
19
19
|
currentProject?: string | null
|
|
20
20
|
onProjectSelect?: (projectId: string) => void
|
|
21
21
|
onGitHubLoad?: (owner: string, repo: string) => Promise<void>
|
|
22
|
+
onClose?: () => void
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export function Sidebar({ projects = [], currentProject = null, onProjectSelect, onGitHubLoad }: SidebarProps) {
|
|
25
|
+
export function Sidebar({ projects = [], currentProject = null, onProjectSelect, onGitHubLoad, onClose }: SidebarProps) {
|
|
25
26
|
const location = useRouterState({ select: (s) => s.location })
|
|
26
27
|
|
|
27
28
|
return (
|
|
28
|
-
<nav className="w-56 border-r border-gray-200 dark:border-gray-800 bg-gray-100 dark:bg-gray-950 flex flex-col shrink-0">
|
|
29
|
-
<div className="p-4 border-b border-gray-200 dark:border-gray-800">
|
|
29
|
+
<nav className="w-56 h-full border-r border-gray-200 dark:border-gray-800 bg-gray-100 dark:bg-gray-950 flex flex-col shrink-0">
|
|
30
|
+
<div className="p-4 border-b border-gray-200 dark:border-gray-800 flex items-center justify-between">
|
|
30
31
|
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300 tracking-wide">
|
|
31
32
|
ACP Visualizer
|
|
32
33
|
</span>
|
|
34
|
+
{onClose && (
|
|
35
|
+
<button
|
|
36
|
+
onClick={onClose}
|
|
37
|
+
className="lg:hidden p-1.5 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors"
|
|
38
|
+
aria-label="Close menu"
|
|
39
|
+
>
|
|
40
|
+
<X className="w-4 h-4 text-gray-700 dark:text-gray-300" />
|
|
41
|
+
</button>
|
|
42
|
+
)}
|
|
33
43
|
</div>
|
|
34
44
|
{projects.length > 1 && onProjectSelect && (
|
|
35
45
|
<div className="px-3 pt-3">
|
package/src/routes/__root.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HeadContent, Scripts, createRootRoute, Outlet, useRouter, useRouterState } from '@tanstack/react-router'
|
|
2
2
|
import { useState, useCallback, useEffect } from 'react'
|
|
3
|
+
import { Menu, X } from 'lucide-react'
|
|
3
4
|
import { useAutoRefresh } from '../lib/useAutoRefresh'
|
|
4
5
|
import { Sidebar } from '../components/Sidebar'
|
|
5
6
|
import { Header } from '../components/Header'
|
|
@@ -109,6 +110,7 @@ function RootLayout() {
|
|
|
109
110
|
context.progressData?.project.name || null,
|
|
110
111
|
)
|
|
111
112
|
const [initialLoadDone, setInitialLoadDone] = useState(false)
|
|
113
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
112
114
|
|
|
113
115
|
// On mount, check for ?repo= param and auto-load
|
|
114
116
|
useEffect(() => {
|
|
@@ -157,21 +159,51 @@ function RootLayout() {
|
|
|
157
159
|
<>
|
|
158
160
|
<AutoRefresh />
|
|
159
161
|
<div className="flex h-screen bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100">
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
{/* Mobile Menu Button */}
|
|
163
|
+
<button
|
|
164
|
+
onClick={() => setMobileMenuOpen(true)}
|
|
165
|
+
className="lg:hidden fixed top-4 left-4 z-50 p-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg"
|
|
166
|
+
aria-label="Open menu"
|
|
167
|
+
>
|
|
168
|
+
<Menu className="w-5 h-5 text-gray-900 dark:text-gray-100" />
|
|
169
|
+
</button>
|
|
170
|
+
|
|
171
|
+
{/* Mobile Backdrop */}
|
|
172
|
+
{mobileMenuOpen && (
|
|
173
|
+
<div
|
|
174
|
+
className="lg:hidden fixed inset-0 bg-black/30 backdrop-blur-sm z-40"
|
|
175
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{/* Sidebar - Desktop: Always visible | Mobile: Drawer */}
|
|
180
|
+
<div
|
|
181
|
+
className={`fixed lg:relative inset-y-0 left-0 z-50 transition-transform duration-300 lg:translate-x-0 ${
|
|
182
|
+
mobileMenuOpen ? 'translate-x-0' : '-translate-x-full'
|
|
183
|
+
}`}
|
|
184
|
+
>
|
|
185
|
+
<Sidebar
|
|
186
|
+
projects={context.projects}
|
|
187
|
+
currentProject={currentProject}
|
|
188
|
+
onProjectSelect={handleProjectSwitch}
|
|
189
|
+
onGitHubLoad={handleGitHubLoad}
|
|
190
|
+
onClose={() => setMobileMenuOpen(false)}
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
166
194
|
<ProgressProvider data={progressData}>
|
|
167
195
|
<SidePanelProvider>
|
|
168
|
-
<div className="flex-1 flex
|
|
169
|
-
|
|
170
|
-
<
|
|
171
|
-
<
|
|
172
|
-
|
|
196
|
+
<div className="flex-1 flex overflow-hidden">
|
|
197
|
+
{/* Main content area */}
|
|
198
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
199
|
+
<Header data={progressData} />
|
|
200
|
+
<main className="flex-1 overflow-auto bg-gray-50 dark:bg-gray-900">
|
|
201
|
+
<Outlet />
|
|
202
|
+
</main>
|
|
203
|
+
</div>
|
|
204
|
+
{/* Side panel - renders in flex layout, not overlay */}
|
|
205
|
+
<SidePanel />
|
|
173
206
|
</div>
|
|
174
|
-
<SidePanel />
|
|
175
207
|
</SidePanelProvider>
|
|
176
208
|
</ProgressProvider>
|
|
177
209
|
</div>
|
|
@@ -90,7 +90,7 @@ function MilestoneDetailPage() {
|
|
|
90
90
|
]
|
|
91
91
|
|
|
92
92
|
return (
|
|
93
|
-
<div className="p-6 max-w-4xl">
|
|
93
|
+
<div className="p-4 lg:p-6 max-w-4xl">
|
|
94
94
|
<Breadcrumb
|
|
95
95
|
items={[
|
|
96
96
|
{ label: 'Milestones', href: '/milestones' },
|
|
@@ -98,7 +98,7 @@ function MilestoneDetailPage() {
|
|
|
98
98
|
]}
|
|
99
99
|
/>
|
|
100
100
|
|
|
101
|
-
<h1 className="text-xl font-semibold text-gray-100 dark:text-gray-100 mb-3">{formatMilestoneName(milestone)}</h1>
|
|
101
|
+
<h1 className="text-lg lg:text-xl font-semibold text-gray-100 dark:text-gray-100 mb-3">{formatMilestoneName(milestone)}</h1>
|
|
102
102
|
|
|
103
103
|
<div className="flex items-center gap-3 mb-4">
|
|
104
104
|
<div className="flex-1 max-w-xs">
|
|
@@ -119,7 +119,7 @@ function TaskDetailPage() {
|
|
|
119
119
|
]
|
|
120
120
|
|
|
121
121
|
return (
|
|
122
|
-
<div className="p-6 max-w-4xl">
|
|
122
|
+
<div className="p-4 lg:p-6 max-w-4xl">
|
|
123
123
|
<Breadcrumb
|
|
124
124
|
items={[
|
|
125
125
|
{ label: 'Milestones', href: '/milestones' },
|
|
@@ -128,7 +128,7 @@ function TaskDetailPage() {
|
|
|
128
128
|
]}
|
|
129
129
|
/>
|
|
130
130
|
|
|
131
|
-
<h1 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-3">{formatTaskName(task)}</h1>
|
|
131
|
+
<h1 className="text-lg lg:text-xl font-semibold text-gray-900 dark:text-gray-100 mb-3">{formatTaskName(task)}</h1>
|
|
132
132
|
|
|
133
133
|
<div className="flex items-center gap-2 mb-4">
|
|
134
134
|
<PriorityBadge priority={task.priority} />
|
|
@@ -153,12 +153,12 @@ function TaskDetailPage() {
|
|
|
153
153
|
|
|
154
154
|
{/* Prev / Next navigation */}
|
|
155
155
|
{(siblings.prev || siblings.next) && (
|
|
156
|
-
<div className="mt-8 flex items-center justify-between border-t border-gray-200 dark:border-gray-800 pt-4">
|
|
156
|
+
<div className="mt-8 flex items-center justify-between gap-4 border-t border-gray-200 dark:border-gray-800 pt-4">
|
|
157
157
|
{siblings.prev ? (
|
|
158
158
|
<Link
|
|
159
159
|
to="/tasks/$taskId"
|
|
160
160
|
params={{ taskId: siblings.prev.id }}
|
|
161
|
-
className="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
|
|
161
|
+
className="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors truncate"
|
|
162
162
|
>
|
|
163
163
|
← {formatTaskName(siblings.prev)}
|
|
164
164
|
</Link>
|
|
@@ -169,7 +169,7 @@ function TaskDetailPage() {
|
|
|
169
169
|
<Link
|
|
170
170
|
to="/tasks/$taskId"
|
|
171
171
|
params={{ taskId: siblings.next.id }}
|
|
172
|
-
className="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
|
|
172
|
+
className="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors truncate text-right"
|
|
173
173
|
>
|
|
174
174
|
{formatTaskName(siblings.next)} →
|
|
175
175
|
</Link>
|