@prmichaelsen/acp-visualizer 0.6.0 → 0.6.1
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
CHANGED
|
@@ -8,8 +8,7 @@ interface MarkdownContentProps {
|
|
|
8
8
|
|
|
9
9
|
export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|
10
10
|
return (
|
|
11
|
-
<
|
|
12
|
-
rehypePlugins={[rehypeHighlight]}
|
|
11
|
+
<div
|
|
13
12
|
className={`prose prose-invert prose-sm max-w-none
|
|
14
13
|
prose-pre:bg-gray-900 prose-pre:border prose-pre:border-gray-800
|
|
15
14
|
prose-code:bg-gray-800 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-gray-200
|
|
@@ -23,7 +22,9 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|
|
23
22
|
prose-blockquote:border-gray-700 prose-blockquote:text-gray-400
|
|
24
23
|
${className ?? ''}`}
|
|
25
24
|
>
|
|
26
|
-
{
|
|
27
|
-
|
|
25
|
+
<ReactMarkdown rehypePlugins={[rehypeHighlight]}>
|
|
26
|
+
{content}
|
|
27
|
+
</ReactMarkdown>
|
|
28
|
+
</div>
|
|
28
29
|
)
|
|
29
30
|
}
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -14,6 +14,8 @@ import { Route as SearchRouteImport } from './routes/search'
|
|
|
14
14
|
import { Route as MilestonesRouteImport } from './routes/milestones'
|
|
15
15
|
import { Route as ActivityRouteImport } from './routes/activity'
|
|
16
16
|
import { Route as IndexRouteImport } from './routes/index'
|
|
17
|
+
import { Route as TasksIndexRouteImport } from './routes/tasks.index'
|
|
18
|
+
import { Route as MilestonesIndexRouteImport } from './routes/milestones.index'
|
|
17
19
|
import { Route as TasksTaskIdRouteImport } from './routes/tasks.$taskId'
|
|
18
20
|
import { Route as MilestonesMilestoneIdRouteImport } from './routes/milestones.$milestoneId'
|
|
19
21
|
import { Route as ApiWatchRouteImport } from './routes/api/watch'
|
|
@@ -43,6 +45,16 @@ const IndexRoute = IndexRouteImport.update({
|
|
|
43
45
|
path: '/',
|
|
44
46
|
getParentRoute: () => rootRouteImport,
|
|
45
47
|
} as any)
|
|
48
|
+
const TasksIndexRoute = TasksIndexRouteImport.update({
|
|
49
|
+
id: '/',
|
|
50
|
+
path: '/',
|
|
51
|
+
getParentRoute: () => TasksRoute,
|
|
52
|
+
} as any)
|
|
53
|
+
const MilestonesIndexRoute = MilestonesIndexRouteImport.update({
|
|
54
|
+
id: '/',
|
|
55
|
+
path: '/',
|
|
56
|
+
getParentRoute: () => MilestonesRoute,
|
|
57
|
+
} as any)
|
|
46
58
|
const TasksTaskIdRoute = TasksTaskIdRouteImport.update({
|
|
47
59
|
id: '/$taskId',
|
|
48
60
|
path: '/$taskId',
|
|
@@ -68,16 +80,18 @@ export interface FileRoutesByFullPath {
|
|
|
68
80
|
'/api/watch': typeof ApiWatchRoute
|
|
69
81
|
'/milestones/$milestoneId': typeof MilestonesMilestoneIdRoute
|
|
70
82
|
'/tasks/$taskId': typeof TasksTaskIdRoute
|
|
83
|
+
'/milestones/': typeof MilestonesIndexRoute
|
|
84
|
+
'/tasks/': typeof TasksIndexRoute
|
|
71
85
|
}
|
|
72
86
|
export interface FileRoutesByTo {
|
|
73
87
|
'/': typeof IndexRoute
|
|
74
88
|
'/activity': typeof ActivityRoute
|
|
75
|
-
'/milestones': typeof MilestonesRouteWithChildren
|
|
76
89
|
'/search': typeof SearchRoute
|
|
77
|
-
'/tasks': typeof TasksRouteWithChildren
|
|
78
90
|
'/api/watch': typeof ApiWatchRoute
|
|
79
91
|
'/milestones/$milestoneId': typeof MilestonesMilestoneIdRoute
|
|
80
92
|
'/tasks/$taskId': typeof TasksTaskIdRoute
|
|
93
|
+
'/milestones': typeof MilestonesIndexRoute
|
|
94
|
+
'/tasks': typeof TasksIndexRoute
|
|
81
95
|
}
|
|
82
96
|
export interface FileRoutesById {
|
|
83
97
|
__root__: typeof rootRouteImport
|
|
@@ -89,6 +103,8 @@ export interface FileRoutesById {
|
|
|
89
103
|
'/api/watch': typeof ApiWatchRoute
|
|
90
104
|
'/milestones/$milestoneId': typeof MilestonesMilestoneIdRoute
|
|
91
105
|
'/tasks/$taskId': typeof TasksTaskIdRoute
|
|
106
|
+
'/milestones/': typeof MilestonesIndexRoute
|
|
107
|
+
'/tasks/': typeof TasksIndexRoute
|
|
92
108
|
}
|
|
93
109
|
export interface FileRouteTypes {
|
|
94
110
|
fileRoutesByFullPath: FileRoutesByFullPath
|
|
@@ -101,16 +117,18 @@ export interface FileRouteTypes {
|
|
|
101
117
|
| '/api/watch'
|
|
102
118
|
| '/milestones/$milestoneId'
|
|
103
119
|
| '/tasks/$taskId'
|
|
120
|
+
| '/milestones/'
|
|
121
|
+
| '/tasks/'
|
|
104
122
|
fileRoutesByTo: FileRoutesByTo
|
|
105
123
|
to:
|
|
106
124
|
| '/'
|
|
107
125
|
| '/activity'
|
|
108
|
-
| '/milestones'
|
|
109
126
|
| '/search'
|
|
110
|
-
| '/tasks'
|
|
111
127
|
| '/api/watch'
|
|
112
128
|
| '/milestones/$milestoneId'
|
|
113
129
|
| '/tasks/$taskId'
|
|
130
|
+
| '/milestones'
|
|
131
|
+
| '/tasks'
|
|
114
132
|
id:
|
|
115
133
|
| '__root__'
|
|
116
134
|
| '/'
|
|
@@ -121,6 +139,8 @@ export interface FileRouteTypes {
|
|
|
121
139
|
| '/api/watch'
|
|
122
140
|
| '/milestones/$milestoneId'
|
|
123
141
|
| '/tasks/$taskId'
|
|
142
|
+
| '/milestones/'
|
|
143
|
+
| '/tasks/'
|
|
124
144
|
fileRoutesById: FileRoutesById
|
|
125
145
|
}
|
|
126
146
|
export interface RootRouteChildren {
|
|
@@ -169,6 +189,20 @@ declare module '@tanstack/react-router' {
|
|
|
169
189
|
preLoaderRoute: typeof IndexRouteImport
|
|
170
190
|
parentRoute: typeof rootRouteImport
|
|
171
191
|
}
|
|
192
|
+
'/tasks/': {
|
|
193
|
+
id: '/tasks/'
|
|
194
|
+
path: '/'
|
|
195
|
+
fullPath: '/tasks/'
|
|
196
|
+
preLoaderRoute: typeof TasksIndexRouteImport
|
|
197
|
+
parentRoute: typeof TasksRoute
|
|
198
|
+
}
|
|
199
|
+
'/milestones/': {
|
|
200
|
+
id: '/milestones/'
|
|
201
|
+
path: '/'
|
|
202
|
+
fullPath: '/milestones/'
|
|
203
|
+
preLoaderRoute: typeof MilestonesIndexRouteImport
|
|
204
|
+
parentRoute: typeof MilestonesRoute
|
|
205
|
+
}
|
|
172
206
|
'/tasks/$taskId': {
|
|
173
207
|
id: '/tasks/$taskId'
|
|
174
208
|
path: '/$taskId'
|
|
@@ -195,10 +229,12 @@ declare module '@tanstack/react-router' {
|
|
|
195
229
|
|
|
196
230
|
interface MilestonesRouteChildren {
|
|
197
231
|
MilestonesMilestoneIdRoute: typeof MilestonesMilestoneIdRoute
|
|
232
|
+
MilestonesIndexRoute: typeof MilestonesIndexRoute
|
|
198
233
|
}
|
|
199
234
|
|
|
200
235
|
const MilestonesRouteChildren: MilestonesRouteChildren = {
|
|
201
236
|
MilestonesMilestoneIdRoute: MilestonesMilestoneIdRoute,
|
|
237
|
+
MilestonesIndexRoute: MilestonesIndexRoute,
|
|
202
238
|
}
|
|
203
239
|
|
|
204
240
|
const MilestonesRouteWithChildren = MilestonesRoute._addFileChildren(
|
|
@@ -207,10 +243,12 @@ const MilestonesRouteWithChildren = MilestonesRoute._addFileChildren(
|
|
|
207
243
|
|
|
208
244
|
interface TasksRouteChildren {
|
|
209
245
|
TasksTaskIdRoute: typeof TasksTaskIdRoute
|
|
246
|
+
TasksIndexRoute: typeof TasksIndexRoute
|
|
210
247
|
}
|
|
211
248
|
|
|
212
249
|
const TasksRouteChildren: TasksRouteChildren = {
|
|
213
250
|
TasksTaskIdRoute: TasksTaskIdRoute,
|
|
251
|
+
TasksIndexRoute: TasksIndexRoute,
|
|
214
252
|
}
|
|
215
253
|
|
|
216
254
|
const TasksRouteWithChildren = TasksRoute._addFileChildren(TasksRouteChildren)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { useState, lazy, Suspense } from 'react'
|
|
3
|
+
import { MilestoneTable } from '../components/MilestoneTable'
|
|
4
|
+
import { MilestoneTree } from '../components/MilestoneTree'
|
|
5
|
+
import { MilestoneKanban } from '../components/MilestoneKanban'
|
|
6
|
+
import { MilestoneGantt } from '../components/MilestoneGantt'
|
|
7
|
+
import { ViewToggle, type ViewMode } from '../components/ViewToggle'
|
|
8
|
+
import { FilterBar } from '../components/FilterBar'
|
|
9
|
+
import { SearchInput } from '../components/SearchInput'
|
|
10
|
+
import { useFilteredData } from '../lib/useFilteredData'
|
|
11
|
+
import { useProgressData } from '../contexts/ProgressContext'
|
|
12
|
+
import type { Status } from '../lib/types'
|
|
13
|
+
|
|
14
|
+
// Lazy-load DependencyGraph to keep dagre out of the SSR bundle
|
|
15
|
+
// (dagre uses CommonJS require() which fails on Cloudflare Workers)
|
|
16
|
+
const DependencyGraph = lazy(() => import('../components/DependencyGraph').then(m => ({ default: m.DependencyGraph })))
|
|
17
|
+
|
|
18
|
+
export const Route = createFileRoute('/milestones/')({
|
|
19
|
+
component: MilestonesPage,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function MilestonesPage() {
|
|
23
|
+
const progressData = useProgressData()
|
|
24
|
+
const [view, setView] = useState<ViewMode>('table')
|
|
25
|
+
const [status, setStatus] = useState<Status | 'all'>('all')
|
|
26
|
+
const [search, setSearch] = useState('')
|
|
27
|
+
|
|
28
|
+
const filtered = useFilteredData(progressData, { status, search })
|
|
29
|
+
|
|
30
|
+
if (!filtered) {
|
|
31
|
+
return (
|
|
32
|
+
<div className="p-6">
|
|
33
|
+
<p className="text-gray-600 text-sm">No data loaded</p>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="p-6">
|
|
40
|
+
<div className="flex items-center justify-between mb-4">
|
|
41
|
+
<h2 className="text-lg font-semibold">Milestones</h2>
|
|
42
|
+
<ViewToggle value={view} onChange={setView} />
|
|
43
|
+
</div>
|
|
44
|
+
{view !== 'kanban' && (
|
|
45
|
+
<div className="flex items-center gap-3 mb-4">
|
|
46
|
+
<FilterBar status={status} onStatusChange={setStatus} />
|
|
47
|
+
<div className="w-64">
|
|
48
|
+
<SearchInput value={search} onChange={setSearch} placeholder="Filter milestones..." />
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
{view === 'table' ? (
|
|
53
|
+
<MilestoneTable milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
54
|
+
) : view === 'tree' ? (
|
|
55
|
+
<MilestoneTree milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
56
|
+
) : view === 'kanban' ? (
|
|
57
|
+
<MilestoneKanban milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
58
|
+
) : view === 'gantt' ? (
|
|
59
|
+
<MilestoneGantt milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
60
|
+
) : (
|
|
61
|
+
<Suspense fallback={<p className="text-gray-500 text-sm">Loading graph...</p>}>
|
|
62
|
+
<DependencyGraph data={filtered} />
|
|
63
|
+
</Suspense>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -1,67 +1,9 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { useState, lazy, Suspense } from 'react'
|
|
3
|
-
import { MilestoneTable } from '../components/MilestoneTable'
|
|
4
|
-
import { MilestoneTree } from '../components/MilestoneTree'
|
|
5
|
-
import { MilestoneKanban } from '../components/MilestoneKanban'
|
|
6
|
-
import { MilestoneGantt } from '../components/MilestoneGantt'
|
|
7
|
-
import { ViewToggle, type ViewMode } from '../components/ViewToggle'
|
|
8
|
-
import { FilterBar } from '../components/FilterBar'
|
|
9
|
-
import { SearchInput } from '../components/SearchInput'
|
|
10
|
-
import { useFilteredData } from '../lib/useFilteredData'
|
|
11
|
-
import { useProgressData } from '../contexts/ProgressContext'
|
|
12
|
-
import type { Status } from '../lib/types'
|
|
13
|
-
|
|
14
|
-
// Lazy-load DependencyGraph to keep dagre out of the SSR bundle
|
|
15
|
-
// (dagre uses CommonJS require() which fails on Cloudflare Workers)
|
|
16
|
-
const DependencyGraph = lazy(() => import('../components/DependencyGraph').then(m => ({ default: m.DependencyGraph })))
|
|
1
|
+
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
|
17
2
|
|
|
18
3
|
export const Route = createFileRoute('/milestones')({
|
|
19
|
-
component:
|
|
4
|
+
component: MilestonesLayout,
|
|
20
5
|
})
|
|
21
6
|
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
const [view, setView] = useState<ViewMode>('table')
|
|
25
|
-
const [status, setStatus] = useState<Status | 'all'>('all')
|
|
26
|
-
const [search, setSearch] = useState('')
|
|
27
|
-
|
|
28
|
-
const filtered = useFilteredData(progressData, { status, search })
|
|
29
|
-
|
|
30
|
-
if (!filtered) {
|
|
31
|
-
return (
|
|
32
|
-
<div className="p-6">
|
|
33
|
-
<p className="text-gray-600 text-sm">No data loaded</p>
|
|
34
|
-
</div>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div className="p-6">
|
|
40
|
-
<div className="flex items-center justify-between mb-4">
|
|
41
|
-
<h2 className="text-lg font-semibold">Milestones</h2>
|
|
42
|
-
<ViewToggle value={view} onChange={setView} />
|
|
43
|
-
</div>
|
|
44
|
-
{view !== 'kanban' && (
|
|
45
|
-
<div className="flex items-center gap-3 mb-4">
|
|
46
|
-
<FilterBar status={status} onStatusChange={setStatus} />
|
|
47
|
-
<div className="w-64">
|
|
48
|
-
<SearchInput value={search} onChange={setSearch} placeholder="Filter milestones..." />
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
)}
|
|
52
|
-
{view === 'table' ? (
|
|
53
|
-
<MilestoneTable milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
54
|
-
) : view === 'tree' ? (
|
|
55
|
-
<MilestoneTree milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
56
|
-
) : view === 'kanban' ? (
|
|
57
|
-
<MilestoneKanban milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
58
|
-
) : view === 'gantt' ? (
|
|
59
|
-
<MilestoneGantt milestones={filtered.milestones} tasks={filtered.tasks} />
|
|
60
|
-
) : (
|
|
61
|
-
<Suspense fallback={<p className="text-gray-500 text-sm">Loading graph...</p>}>
|
|
62
|
-
<DependencyGraph data={filtered} />
|
|
63
|
-
</Suspense>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
)
|
|
7
|
+
function MilestonesLayout() {
|
|
8
|
+
return <Outlet />
|
|
67
9
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
+
import { StatusDot } from '../components/StatusDot'
|
|
3
|
+
import { ExtraFieldsBadge } from '../components/ExtraFieldsBadge'
|
|
4
|
+
import { useProgressData } from '../contexts/ProgressContext'
|
|
5
|
+
import type { Task } from '../lib/types'
|
|
6
|
+
|
|
7
|
+
export const Route = createFileRoute('/tasks/')({
|
|
8
|
+
component: TasksPage,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
function TasksPage() {
|
|
12
|
+
const progressData = useProgressData()
|
|
13
|
+
|
|
14
|
+
if (!progressData) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="p-6">
|
|
17
|
+
<p className="text-gray-600 text-sm">No data loaded</p>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const allTasks: Array<Task & { milestoneName: string }> = []
|
|
23
|
+
for (const milestone of progressData.milestones) {
|
|
24
|
+
const tasks = progressData.tasks[milestone.id] || []
|
|
25
|
+
for (const task of tasks) {
|
|
26
|
+
allTasks.push({ ...task, milestoneName: milestone.name })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="p-6">
|
|
32
|
+
<h2 className="text-lg font-semibold mb-4">
|
|
33
|
+
All Tasks ({allTasks.length})
|
|
34
|
+
</h2>
|
|
35
|
+
<div className="border border-gray-800 rounded-lg overflow-hidden">
|
|
36
|
+
{allTasks.map((task) => (
|
|
37
|
+
<Link
|
|
38
|
+
key={task.id}
|
|
39
|
+
to="/tasks/$taskId"
|
|
40
|
+
params={{ taskId: task.id }}
|
|
41
|
+
className="flex items-center gap-3 px-4 py-2.5 border-b border-gray-800/50 hover:bg-gray-800/30 transition-colors"
|
|
42
|
+
>
|
|
43
|
+
<StatusDot status={task.status} />
|
|
44
|
+
<span
|
|
45
|
+
className={`flex-1 text-sm ${
|
|
46
|
+
task.status === 'completed' ? 'text-gray-500' : 'text-gray-200'
|
|
47
|
+
}`}
|
|
48
|
+
>
|
|
49
|
+
{task.name}
|
|
50
|
+
</span>
|
|
51
|
+
<span className="text-xs text-gray-600">{task.milestoneName}</span>
|
|
52
|
+
<span className="text-xs text-gray-500 font-mono w-8 text-right">
|
|
53
|
+
{task.estimated_hours}h
|
|
54
|
+
</span>
|
|
55
|
+
<ExtraFieldsBadge fields={task.extra} />
|
|
56
|
+
</Link>
|
|
57
|
+
))}
|
|
58
|
+
{allTasks.length === 0 && (
|
|
59
|
+
<div className="px-4 py-6 text-center">
|
|
60
|
+
<p className="text-gray-600 text-sm">No tasks defined</p>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
package/src/routes/tasks.tsx
CHANGED
|
@@ -1,64 +1,9 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { StatusDot } from '../components/StatusDot'
|
|
3
|
-
import { ExtraFieldsBadge } from '../components/ExtraFieldsBadge'
|
|
4
|
-
import { useProgressData } from '../contexts/ProgressContext'
|
|
5
|
-
import type { Task } from '../lib/types'
|
|
1
|
+
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
|
6
2
|
|
|
7
3
|
export const Route = createFileRoute('/tasks')({
|
|
8
|
-
component:
|
|
4
|
+
component: TasksLayout,
|
|
9
5
|
})
|
|
10
6
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!progressData) {
|
|
15
|
-
return (
|
|
16
|
-
<div className="p-6">
|
|
17
|
-
<p className="text-gray-600 text-sm">No data loaded</p>
|
|
18
|
-
</div>
|
|
19
|
-
)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const allTasks: Array<Task & { milestoneName: string }> = []
|
|
23
|
-
for (const milestone of progressData.milestones) {
|
|
24
|
-
const tasks = progressData.tasks[milestone.id] || []
|
|
25
|
-
for (const task of tasks) {
|
|
26
|
-
allTasks.push({ ...task, milestoneName: milestone.name })
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div className="p-6">
|
|
32
|
-
<h2 className="text-lg font-semibold mb-4">
|
|
33
|
-
All Tasks ({allTasks.length})
|
|
34
|
-
</h2>
|
|
35
|
-
<div className="border border-gray-800 rounded-lg overflow-hidden">
|
|
36
|
-
{allTasks.map((task) => (
|
|
37
|
-
<div
|
|
38
|
-
key={task.id}
|
|
39
|
-
className="flex items-center gap-3 px-4 py-2.5 border-b border-gray-800/50 hover:bg-gray-800/30 transition-colors"
|
|
40
|
-
>
|
|
41
|
-
<StatusDot status={task.status} />
|
|
42
|
-
<span
|
|
43
|
-
className={`flex-1 text-sm ${
|
|
44
|
-
task.status === 'completed' ? 'text-gray-500' : 'text-gray-200'
|
|
45
|
-
}`}
|
|
46
|
-
>
|
|
47
|
-
{task.name}
|
|
48
|
-
</span>
|
|
49
|
-
<span className="text-xs text-gray-600">{task.milestoneName}</span>
|
|
50
|
-
<span className="text-xs text-gray-500 font-mono w-8 text-right">
|
|
51
|
-
{task.estimated_hours}h
|
|
52
|
-
</span>
|
|
53
|
-
<ExtraFieldsBadge fields={task.extra} />
|
|
54
|
-
</div>
|
|
55
|
-
))}
|
|
56
|
-
{allTasks.length === 0 && (
|
|
57
|
-
<div className="px-4 py-6 text-center">
|
|
58
|
-
<p className="text-gray-600 text-sm">No tasks defined</p>
|
|
59
|
-
</div>
|
|
60
|
-
)}
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
)
|
|
7
|
+
function TasksLayout() {
|
|
8
|
+
return <Outlet />
|
|
64
9
|
}
|