@prmichaelsen/acp-visualizer 0.9.0 → 0.9.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 +1 -1
- package/src/components/DependencyGraph.tsx +3 -2
- package/src/components/MilestoneGantt.tsx +4 -3
- package/src/components/MilestoneKanban.tsx +2 -1
- package/src/components/MilestonePreview.tsx +2 -1
- package/src/components/MilestoneTable.tsx +2 -1
- package/src/components/MilestoneTree.tsx +2 -1
- package/src/components/PreviewButton.tsx +2 -2
- package/src/components/SidePanel.tsx +7 -2
- package/src/components/TaskList.tsx +2 -1
- package/src/components/TaskPreview.tsx +3 -2
- package/src/lib/display.ts +51 -0
- package/src/lib/useTheme.ts +11 -0
- package/src/routes/milestones.$milestoneId.tsx +8 -7
- package/src/routes/search.tsx +7 -6
- package/src/routes/tasks.$taskId.tsx +11 -10
- package/src/routes/tasks.index.tsx +9 -8
- package/src/styles.css +2 -0
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import dagre from 'dagre'
|
|
3
|
+
import { formatTaskName, formatMilestoneName } from '../lib/display'
|
|
3
4
|
import type { ProgressData, Task, Status } from '../lib/types'
|
|
4
5
|
|
|
5
6
|
interface DependencyGraphProps {
|
|
@@ -43,7 +44,7 @@ function buildGraph(data: ProgressData): { nodes: GraphNode[]; edges: GraphEdge[
|
|
|
43
44
|
for (const milestone of data.milestones) {
|
|
44
45
|
const tasks = data.tasks[milestone.id] || []
|
|
45
46
|
for (const task of tasks) {
|
|
46
|
-
allTasks.push({ ...task, milestoneName: milestone
|
|
47
|
+
allTasks.push({ ...task, milestoneName: formatMilestoneName(milestone) })
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -80,7 +81,7 @@ function buildGraph(data: ProgressData): { nodes: GraphNode[]; edges: GraphEdge[
|
|
|
80
81
|
const node = g.node(String(task.id))
|
|
81
82
|
return {
|
|
82
83
|
id: String(task.id),
|
|
83
|
-
label: task
|
|
84
|
+
label: formatTaskName(task),
|
|
84
85
|
status: task.status,
|
|
85
86
|
milestone: task.milestoneName,
|
|
86
87
|
x: node.x,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { StatusBadge } from './StatusBadge'
|
|
3
|
+
import { formatMilestoneName } from '../lib/display'
|
|
3
4
|
import type { Milestone, Task } from '../lib/types'
|
|
4
5
|
|
|
5
6
|
interface MilestoneGanttProps {
|
|
@@ -118,17 +119,17 @@ export function MilestoneGantt({ milestones, tasks }: MilestoneGanttProps) {
|
|
|
118
119
|
: 'bg-gray-500/30 border-gray-500/40'
|
|
119
120
|
|
|
120
121
|
return (
|
|
121
|
-
<div key={milestone.id} className="flex items-center h-12 px-3 hover:bg-gray-800/20">
|
|
122
|
+
<div key={milestone.id} className="flex items-center h-12 px-3 hover:bg-gray-200/20 dark:hover:bg-gray-800/20">
|
|
122
123
|
{/* Label */}
|
|
123
124
|
<div className="w-48 shrink-0 flex items-center gap-2">
|
|
124
|
-
<span className="text-xs text-gray-300 truncate">{milestone
|
|
125
|
+
<span className="text-xs text-gray-700 dark:text-gray-300 truncate">{formatMilestoneName(milestone)}</span>
|
|
125
126
|
</div>
|
|
126
127
|
{/* Bar area */}
|
|
127
128
|
<div className="flex-1 relative h-6">
|
|
128
129
|
<div
|
|
129
130
|
className={`absolute top-1 h-4 rounded-sm border ${barColor} transition-all`}
|
|
130
131
|
style={{ left: `${barStart}%`, width: `${barWidth}%` }}
|
|
131
|
-
title={`${milestone
|
|
132
|
+
title={`${formatMilestoneName(milestone)}: ${start ? formatDate(start) : '?'} → ${end ? formatDate(end) : '?'} (${milestone.progress}%)`}
|
|
132
133
|
>
|
|
133
134
|
{/* Progress fill within bar */}
|
|
134
135
|
<div
|
|
@@ -4,6 +4,7 @@ import { PriorityBadge } from './PriorityBadge'
|
|
|
4
4
|
import { ProgressBar } from './ProgressBar'
|
|
5
5
|
import { PreviewButton } from './PreviewButton'
|
|
6
6
|
import { TaskList } from './TaskList'
|
|
7
|
+
import { formatMilestoneName } from '../lib/display'
|
|
7
8
|
import type { Milestone, Task, Status } from '../lib/types'
|
|
8
9
|
import { useState } from 'react'
|
|
9
10
|
import { ChevronDown, ChevronRight } from 'lucide-react'
|
|
@@ -38,7 +39,7 @@ function KanbanCard({
|
|
|
38
39
|
params={{ milestoneId: milestone.id }}
|
|
39
40
|
className="text-sm font-medium leading-tight text-gray-900 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
|
|
40
41
|
>
|
|
41
|
-
{milestone
|
|
42
|
+
{formatMilestoneName(milestone)}
|
|
42
43
|
</Link>
|
|
43
44
|
<PreviewButton type="milestone" id={milestone.id} />
|
|
44
45
|
</div>
|
|
@@ -8,6 +8,7 @@ import { StatusDot } from './StatusDot'
|
|
|
8
8
|
import { PriorityBadge } from './PriorityBadge'
|
|
9
9
|
import { MarkdownContent, buildLinkMap } from './MarkdownContent'
|
|
10
10
|
import { getMarkdownContent, resolveMilestoneFile } from '../services/markdown.service'
|
|
11
|
+
import { formatMilestoneName } from '../lib/display'
|
|
11
12
|
import type { MarkdownResult, ResolveFileResult } from '../services/markdown.service'
|
|
12
13
|
|
|
13
14
|
interface MilestonePreviewProps {
|
|
@@ -89,7 +90,7 @@ export function MilestonePreview({ milestoneId }: MilestonePreviewProps) {
|
|
|
89
90
|
return (
|
|
90
91
|
<div>
|
|
91
92
|
<div className="flex items-start justify-between mb-4">
|
|
92
|
-
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{milestone
|
|
93
|
+
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{formatMilestoneName(milestone)}</h1>
|
|
93
94
|
<Link
|
|
94
95
|
to="/milestones/$milestoneId"
|
|
95
96
|
params={{ milestoneId }}
|
|
@@ -14,6 +14,7 @@ import { PriorityBadge } from './PriorityBadge'
|
|
|
14
14
|
import { ProgressBar } from './ProgressBar'
|
|
15
15
|
import { PreviewButton } from './PreviewButton'
|
|
16
16
|
import { TaskList } from './TaskList'
|
|
17
|
+
import { formatMilestoneName } from '../lib/display'
|
|
17
18
|
import type { Milestone, Task } from '../lib/types'
|
|
18
19
|
|
|
19
20
|
const columnHelper = createColumnHelper<Milestone>()
|
|
@@ -62,7 +63,7 @@ export function MilestoneTable({ milestones, tasks }: MilestoneTableProps) {
|
|
|
62
63
|
className="text-sm font-medium text-gray-900 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
|
|
63
64
|
onClick={(e) => e.stopPropagation()}
|
|
64
65
|
>
|
|
65
|
-
{info.
|
|
66
|
+
{formatMilestoneName(info.row.original)}
|
|
66
67
|
</Link>
|
|
67
68
|
<PreviewButton type="milestone" id={info.row.original.id} />
|
|
68
69
|
</div>
|
|
@@ -7,6 +7,7 @@ import { ProgressBar } from './ProgressBar'
|
|
|
7
7
|
import { PreviewButton } from './PreviewButton'
|
|
8
8
|
import { TaskList } from './TaskList'
|
|
9
9
|
import { useCollapse } from '../lib/useCollapse'
|
|
10
|
+
import { formatMilestoneName } from '../lib/display'
|
|
10
11
|
import type { Milestone, Task } from '../lib/types'
|
|
11
12
|
|
|
12
13
|
interface MilestoneTreeProps {
|
|
@@ -45,7 +46,7 @@ function MilestoneTreeRow({
|
|
|
45
46
|
className="text-sm font-medium text-gray-900 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
|
|
46
47
|
onClick={(e) => e.stopPropagation()}
|
|
47
48
|
>
|
|
48
|
-
{milestone
|
|
49
|
+
{formatMilestoneName(milestone)}
|
|
49
50
|
</Link>
|
|
50
51
|
<PreviewButton type="milestone" id={milestone.id} />
|
|
51
52
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PanelRight } from 'lucide-react'
|
|
2
2
|
import { useSidePanel } from '../contexts/SidePanelContext'
|
|
3
3
|
|
|
4
4
|
interface PreviewButtonProps {
|
|
@@ -27,7 +27,7 @@ export function PreviewButton({ type, id, className = '' }: PreviewButtonProps)
|
|
|
27
27
|
title={`Preview ${type}`}
|
|
28
28
|
aria-label={`Preview ${type}`}
|
|
29
29
|
>
|
|
30
|
-
<
|
|
30
|
+
<PanelRight className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400" />
|
|
31
31
|
</button>
|
|
32
32
|
)
|
|
33
33
|
}
|
|
@@ -6,6 +6,11 @@ import { TaskPreview } from './TaskPreview'
|
|
|
6
6
|
export function SidePanel() {
|
|
7
7
|
const { content, isOpen, close } = useSidePanel()
|
|
8
8
|
|
|
9
|
+
// Don't render at all until first open
|
|
10
|
+
if (!content && !isOpen) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
return (
|
|
10
15
|
<>
|
|
11
16
|
{/* Backdrop */}
|
|
@@ -18,8 +23,8 @@ export function SidePanel() {
|
|
|
18
23
|
|
|
19
24
|
{/* Panel */}
|
|
20
25
|
<div
|
|
21
|
-
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 transition-transform duration-300 overflow-auto ${
|
|
22
|
-
isOpen ? 'translate-x-0' : '
|
|
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 transition-transform duration-300 overflow-auto translate-x-full ${
|
|
27
|
+
isOpen ? '!translate-x-0' : ''
|
|
23
28
|
}`}
|
|
24
29
|
>
|
|
25
30
|
{/* Header */}
|
|
@@ -3,6 +3,7 @@ import { StatusDot } from './StatusDot'
|
|
|
3
3
|
import { PriorityBadge } from './PriorityBadge'
|
|
4
4
|
import { PreviewButton } from './PreviewButton'
|
|
5
5
|
import { ExtraFieldsBadge } from './ExtraFieldsBadge'
|
|
6
|
+
import { formatTaskName } from '../lib/display'
|
|
6
7
|
import type { Task } from '../lib/types'
|
|
7
8
|
|
|
8
9
|
export function TaskList({ tasks }: { tasks: Task[] }) {
|
|
@@ -26,7 +27,7 @@ export function TaskList({ tasks }: { tasks: Task[] }) {
|
|
|
26
27
|
task.status === 'completed' ? 'text-gray-500 dark:text-gray-500' : 'text-gray-900 dark:text-gray-200'
|
|
27
28
|
}`}
|
|
28
29
|
>
|
|
29
|
-
{task
|
|
30
|
+
{formatTaskName(task)}
|
|
30
31
|
</Link>
|
|
31
32
|
<PreviewButton type="task" id={task.id} />
|
|
32
33
|
<PriorityBadge priority={task.priority} />
|
|
@@ -6,6 +6,7 @@ import { DetailHeader } from './DetailHeader'
|
|
|
6
6
|
import { PriorityBadge } from './PriorityBadge'
|
|
7
7
|
import { MarkdownContent, buildLinkMap } from './MarkdownContent'
|
|
8
8
|
import { getMarkdownContent, resolveTaskFile } from '../services/markdown.service'
|
|
9
|
+
import { formatTaskName, formatMilestoneName } from '../lib/display'
|
|
9
10
|
import type { MarkdownResult } from '../services/markdown.service'
|
|
10
11
|
|
|
11
12
|
interface TaskPreviewProps {
|
|
@@ -100,7 +101,7 @@ export function TaskPreview({ taskId }: TaskPreviewProps) {
|
|
|
100
101
|
params={{ milestoneId: milestone.id }}
|
|
101
102
|
className="text-blue-500 dark:text-blue-400 hover:underline"
|
|
102
103
|
>
|
|
103
|
-
{milestone
|
|
104
|
+
{formatMilestoneName(milestone)}
|
|
104
105
|
</Link>
|
|
105
106
|
),
|
|
106
107
|
},
|
|
@@ -109,7 +110,7 @@ export function TaskPreview({ taskId }: TaskPreviewProps) {
|
|
|
109
110
|
return (
|
|
110
111
|
<div>
|
|
111
112
|
<div className="flex items-start justify-between mb-4">
|
|
112
|
-
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{task
|
|
113
|
+
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{formatTaskName(task)}</h1>
|
|
113
114
|
<Link
|
|
114
115
|
to="/tasks/$taskId"
|
|
115
116
|
params={{ taskId }}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Milestone, Task } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract milestone number from ID (e.g., "M1" -> "1", "milestone_1" -> "1")
|
|
5
|
+
*/
|
|
6
|
+
export function getMilestoneNumber(id: string): string {
|
|
7
|
+
// Handle M1, M2, M3... format
|
|
8
|
+
if (/^M\d+$/.test(id)) {
|
|
9
|
+
return id.substring(1)
|
|
10
|
+
}
|
|
11
|
+
// Handle milestone_1, milestone_2... format
|
|
12
|
+
if (/^milestone_\d+$/.test(id)) {
|
|
13
|
+
return id.replace('milestone_', '')
|
|
14
|
+
}
|
|
15
|
+
// Fallback: try to extract any number
|
|
16
|
+
const match = id.match(/\d+/)
|
|
17
|
+
return match ? match[0] : id
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract task number from ID (e.g., "task_1" -> "1", "79" -> "79")
|
|
22
|
+
*/
|
|
23
|
+
export function getTaskNumber(id: string): string {
|
|
24
|
+
// Handle task_1, task_2... format
|
|
25
|
+
if (/^task_\d+$/.test(id)) {
|
|
26
|
+
return id.replace('task_', '')
|
|
27
|
+
}
|
|
28
|
+
// Handle numeric IDs
|
|
29
|
+
if (/^\d+$/.test(id)) {
|
|
30
|
+
return id
|
|
31
|
+
}
|
|
32
|
+
// Fallback: try to extract any number
|
|
33
|
+
const match = id.match(/\d+/)
|
|
34
|
+
return match ? match[0] : id
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format milestone display name with prefix (e.g., "M1 — Project Setup")
|
|
39
|
+
*/
|
|
40
|
+
export function formatMilestoneName(milestone: Milestone): string {
|
|
41
|
+
const num = getMilestoneNumber(milestone.id)
|
|
42
|
+
return `M${num} — ${milestone.name}`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Format task display name with prefix (e.g., "T1 — Install dependencies")
|
|
47
|
+
*/
|
|
48
|
+
export function formatTaskName(task: Task): string {
|
|
49
|
+
const num = getTaskNumber(task.id)
|
|
50
|
+
return `T${num} — ${task.name}`
|
|
51
|
+
}
|
package/src/lib/useTheme.ts
CHANGED
|
@@ -2,6 +2,17 @@ import { useState, useEffect } from 'react'
|
|
|
2
2
|
|
|
3
3
|
type Theme = 'dark' | 'light'
|
|
4
4
|
|
|
5
|
+
// Initialize theme immediately on load to prevent flash
|
|
6
|
+
if (typeof window !== 'undefined') {
|
|
7
|
+
const stored = localStorage.getItem('theme')
|
|
8
|
+
const initialTheme = (stored === 'light' || stored === 'dark') ? stored : 'dark'
|
|
9
|
+
if (initialTheme === 'dark') {
|
|
10
|
+
document.documentElement.classList.add('dark')
|
|
11
|
+
} else {
|
|
12
|
+
document.documentElement.classList.remove('dark')
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
export function useTheme() {
|
|
6
17
|
const [theme, setTheme] = useState<Theme>(() => {
|
|
7
18
|
if (typeof window === 'undefined') return 'dark'
|
|
@@ -8,6 +8,7 @@ import { StatusDot } from '../components/StatusDot'
|
|
|
8
8
|
import { PriorityBadge } from '../components/PriorityBadge'
|
|
9
9
|
import { MarkdownContent, buildLinkMap } from '../components/MarkdownContent'
|
|
10
10
|
import { getMarkdownContent, resolveMilestoneFile } from '../services/markdown.service'
|
|
11
|
+
import { formatMilestoneName, formatTaskName } from '../lib/display'
|
|
11
12
|
import type { MarkdownResult, ResolveFileResult } from '../services/markdown.service'
|
|
12
13
|
|
|
13
14
|
export const Route = createFileRoute('/milestones/$milestoneId')({
|
|
@@ -93,11 +94,11 @@ function MilestoneDetailPage() {
|
|
|
93
94
|
<Breadcrumb
|
|
94
95
|
items={[
|
|
95
96
|
{ label: 'Milestones', href: '/milestones' },
|
|
96
|
-
{ label:
|
|
97
|
+
{ label: formatMilestoneName(milestone) },
|
|
97
98
|
]}
|
|
98
99
|
/>
|
|
99
100
|
|
|
100
|
-
<h1 className="text-xl font-semibold text-gray-100 mb-3">{milestone
|
|
101
|
+
<h1 className="text-xl font-semibold text-gray-100 dark:text-gray-100 mb-3">{formatMilestoneName(milestone)}</h1>
|
|
101
102
|
|
|
102
103
|
<div className="flex items-center gap-3 mb-4">
|
|
103
104
|
<div className="flex-1 max-w-xs">
|
|
@@ -133,21 +134,21 @@ function MilestoneDetailPage() {
|
|
|
133
134
|
<h2 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">
|
|
134
135
|
Tasks
|
|
135
136
|
</h2>
|
|
136
|
-
<div className="bg-gray-900/50 border border-gray-800 rounded-xl divide-y divide-gray-800">
|
|
137
|
+
<div className="bg-gray-100 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800 rounded-xl divide-y divide-gray-200 dark:divide-gray-800">
|
|
137
138
|
{tasks.map((task) => (
|
|
138
139
|
<Link
|
|
139
140
|
key={task.id}
|
|
140
141
|
to="/tasks/$taskId"
|
|
141
142
|
params={{ taskId: task.id }}
|
|
142
|
-
className="flex items-center gap-2 px-4 py-2.5 text-sm hover:bg-gray-800/50 transition-colors first:rounded-t-xl last:rounded-b-xl"
|
|
143
|
+
className="flex items-center gap-2 px-4 py-2.5 text-sm hover:bg-gray-200/50 dark:hover:bg-gray-800/50 transition-colors first:rounded-t-xl last:rounded-b-xl"
|
|
143
144
|
>
|
|
144
145
|
<StatusDot status={task.status} />
|
|
145
|
-
<span className={task.status === 'completed' ? 'text-gray-500' : 'text-gray-200'}>
|
|
146
|
-
{task
|
|
146
|
+
<span className={task.status === 'completed' ? 'text-gray-500 dark:text-gray-500' : 'text-gray-900 dark:text-gray-200'}>
|
|
147
|
+
{formatTaskName(task)}
|
|
147
148
|
</span>
|
|
148
149
|
<PriorityBadge priority={task.priority} />
|
|
149
150
|
{task.estimated_hours && (
|
|
150
|
-
<span className="text-xs text-gray-600 ml-auto">{task.estimated_hours}h</span>
|
|
151
|
+
<span className="text-xs text-gray-600 dark:text-gray-600 ml-auto">{task.estimated_hours}h</span>
|
|
151
152
|
)}
|
|
152
153
|
</Link>
|
|
153
154
|
))}
|
package/src/routes/search.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { StatusBadge } from '../components/StatusBadge'
|
|
|
5
5
|
import { StatusDot } from '../components/StatusDot'
|
|
6
6
|
import { buildSearchIndex } from '../lib/search'
|
|
7
7
|
import { useProgressData } from '../contexts/ProgressContext'
|
|
8
|
+
import { formatTaskName, formatMilestoneName } from '../lib/display'
|
|
8
9
|
|
|
9
10
|
export const Route = createFileRoute('/search')({
|
|
10
11
|
component: SearchPage,
|
|
@@ -45,21 +46,21 @@ function SearchPage() {
|
|
|
45
46
|
{results.map((result, i) => (
|
|
46
47
|
<div
|
|
47
48
|
key={i}
|
|
48
|
-
className="bg-gray-900/50 border border-gray-800 rounded-lg px-4 py-3"
|
|
49
|
+
className="bg-gray-100 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-800 rounded-lg px-4 py-3"
|
|
49
50
|
>
|
|
50
51
|
<div className="flex items-center gap-3">
|
|
51
52
|
{result.item.type === 'task' && result.item.task ? (
|
|
52
53
|
<>
|
|
53
54
|
<StatusDot status={result.item.task.status} />
|
|
54
|
-
<span className="text-sm">{result.item.task
|
|
55
|
-
<span className="text-xs text-gray-600 ml-auto">
|
|
56
|
-
{result.item.milestone
|
|
55
|
+
<span className="text-sm text-gray-900 dark:text-gray-200">{formatTaskName(result.item.task)}</span>
|
|
56
|
+
<span className="text-xs text-gray-600 dark:text-gray-600 ml-auto">
|
|
57
|
+
{formatMilestoneName(result.item.milestone)}
|
|
57
58
|
</span>
|
|
58
59
|
</>
|
|
59
60
|
) : (
|
|
60
61
|
<>
|
|
61
|
-
<span className="text-sm font-medium">
|
|
62
|
-
{result.item.milestone
|
|
62
|
+
<span className="text-sm font-medium text-gray-900 dark:text-gray-200">
|
|
63
|
+
{formatMilestoneName(result.item.milestone)}
|
|
63
64
|
</span>
|
|
64
65
|
<StatusBadge status={result.item.milestone.status} />
|
|
65
66
|
</>
|
|
@@ -7,6 +7,7 @@ import { PriorityBadge } from '../components/PriorityBadge'
|
|
|
7
7
|
import { MarkdownContent, buildLinkMap } from '../components/MarkdownContent'
|
|
8
8
|
import { getMarkdownContent } from '../services/markdown.service'
|
|
9
9
|
import { resolveTaskFile } from '../services/markdown.service'
|
|
10
|
+
import { formatTaskName, formatMilestoneName } from '../lib/display'
|
|
10
11
|
import type { MarkdownResult } from '../services/markdown.service'
|
|
11
12
|
|
|
12
13
|
export const Route = createFileRoute('/tasks/$taskId')({
|
|
@@ -109,9 +110,9 @@ function TaskDetailPage() {
|
|
|
109
110
|
<Link
|
|
110
111
|
to="/milestones/$milestoneId"
|
|
111
112
|
params={{ milestoneId: milestone.id }}
|
|
112
|
-
className="text-blue-400 hover:underline"
|
|
113
|
+
className="text-blue-500 dark:text-blue-400 hover:underline"
|
|
113
114
|
>
|
|
114
|
-
{milestone
|
|
115
|
+
{formatMilestoneName(milestone)}
|
|
115
116
|
</Link>
|
|
116
117
|
),
|
|
117
118
|
},
|
|
@@ -122,12 +123,12 @@ function TaskDetailPage() {
|
|
|
122
123
|
<Breadcrumb
|
|
123
124
|
items={[
|
|
124
125
|
{ label: 'Milestones', href: '/milestones' },
|
|
125
|
-
{ label:
|
|
126
|
-
{ label: task
|
|
126
|
+
{ label: formatMilestoneName(milestone), href: `/milestones/${milestone.id}` },
|
|
127
|
+
{ label: formatTaskName(task) },
|
|
127
128
|
]}
|
|
128
129
|
/>
|
|
129
130
|
|
|
130
|
-
<h1 className="text-xl font-semibold text-gray-100 mb-3">{task
|
|
131
|
+
<h1 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-3">{formatTaskName(task)}</h1>
|
|
131
132
|
|
|
132
133
|
<div className="flex items-center gap-2 mb-4">
|
|
133
134
|
<PriorityBadge priority={task.priority} />
|
|
@@ -152,14 +153,14 @@ function TaskDetailPage() {
|
|
|
152
153
|
|
|
153
154
|
{/* Prev / Next navigation */}
|
|
154
155
|
{(siblings.prev || siblings.next) && (
|
|
155
|
-
<div className="mt-8 flex items-center justify-between border-t border-gray-800 pt-4">
|
|
156
|
+
<div className="mt-8 flex items-center justify-between border-t border-gray-200 dark:border-gray-800 pt-4">
|
|
156
157
|
{siblings.prev ? (
|
|
157
158
|
<Link
|
|
158
159
|
to="/tasks/$taskId"
|
|
159
160
|
params={{ taskId: siblings.prev.id }}
|
|
160
|
-
className="text-sm text-gray-400 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"
|
|
161
162
|
>
|
|
162
|
-
← {siblings.prev
|
|
163
|
+
← {formatTaskName(siblings.prev)}
|
|
163
164
|
</Link>
|
|
164
165
|
) : (
|
|
165
166
|
<span />
|
|
@@ -168,9 +169,9 @@ function TaskDetailPage() {
|
|
|
168
169
|
<Link
|
|
169
170
|
to="/tasks/$taskId"
|
|
170
171
|
params={{ taskId: siblings.next.id }}
|
|
171
|
-
className="text-sm text-gray-400 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"
|
|
172
173
|
>
|
|
173
|
-
{siblings.next
|
|
174
|
+
{formatTaskName(siblings.next)} →
|
|
174
175
|
</Link>
|
|
175
176
|
) : (
|
|
176
177
|
<span />
|
|
@@ -2,6 +2,7 @@ import { createFileRoute, Link } from '@tanstack/react-router'
|
|
|
2
2
|
import { StatusDot } from '../components/StatusDot'
|
|
3
3
|
import { ExtraFieldsBadge } from '../components/ExtraFieldsBadge'
|
|
4
4
|
import { useProgressData } from '../contexts/ProgressContext'
|
|
5
|
+
import { formatTaskName, formatMilestoneName } from '../lib/display'
|
|
5
6
|
import type { Task } from '../lib/types'
|
|
6
7
|
|
|
7
8
|
export const Route = createFileRoute('/tasks/')({
|
|
@@ -14,7 +15,7 @@ function TasksPage() {
|
|
|
14
15
|
if (!progressData) {
|
|
15
16
|
return (
|
|
16
17
|
<div className="p-6">
|
|
17
|
-
<p className="text-gray-600 text-sm">No data loaded</p>
|
|
18
|
+
<p className="text-gray-600 dark:text-gray-600 text-sm">No data loaded</p>
|
|
18
19
|
</div>
|
|
19
20
|
)
|
|
20
21
|
}
|
|
@@ -23,7 +24,7 @@ function TasksPage() {
|
|
|
23
24
|
for (const milestone of progressData.milestones) {
|
|
24
25
|
const tasks = progressData.tasks[milestone.id] || []
|
|
25
26
|
for (const task of tasks) {
|
|
26
|
-
allTasks.push({ ...task, milestoneName: milestone
|
|
27
|
+
allTasks.push({ ...task, milestoneName: formatMilestoneName(milestone) })
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -32,24 +33,24 @@ function TasksPage() {
|
|
|
32
33
|
<h2 className="text-lg font-semibold mb-4">
|
|
33
34
|
All Tasks ({allTasks.length})
|
|
34
35
|
</h2>
|
|
35
|
-
<div className="border border-gray-800 rounded-lg overflow-hidden">
|
|
36
|
+
<div className="border border-gray-200 dark:border-gray-800 rounded-lg overflow-hidden">
|
|
36
37
|
{allTasks.map((task) => (
|
|
37
38
|
<Link
|
|
38
39
|
key={task.id}
|
|
39
40
|
to="/tasks/$taskId"
|
|
40
41
|
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
|
+
className="flex items-center gap-3 px-4 py-2.5 border-b border-gray-200 dark:border-gray-800/50 hover:bg-gray-200/50 dark:hover:bg-gray-800/30 transition-colors"
|
|
42
43
|
>
|
|
43
44
|
<StatusDot status={task.status} />
|
|
44
45
|
<span
|
|
45
46
|
className={`flex-1 text-sm ${
|
|
46
|
-
task.status === 'completed' ? 'text-gray-500' : 'text-gray-200'
|
|
47
|
+
task.status === 'completed' ? 'text-gray-500 dark:text-gray-500' : 'text-gray-900 dark:text-gray-200'
|
|
47
48
|
}`}
|
|
48
49
|
>
|
|
49
|
-
{task
|
|
50
|
+
{formatTaskName(task)}
|
|
50
51
|
</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">
|
|
52
|
+
<span className="text-xs text-gray-600 dark:text-gray-600">{task.milestoneName}</span>
|
|
53
|
+
<span className="text-xs text-gray-500 dark:text-gray-500 font-mono w-8 text-right">
|
|
53
54
|
{task.estimated_hours}h
|
|
54
55
|
</span>
|
|
55
56
|
<ExtraFieldsBadge fields={task.extra} />
|