@prmichaelsen/acp-visualizer 0.10.0 → 0.10.2

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/acp-visualizer",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "type": "module",
5
5
  "description": "Browser-based dashboard for visualizing ACP progress.yaml data",
6
6
  "bin": {
@@ -127,8 +127,17 @@ export function DependencyGraph({ data }: DependencyGraphProps) {
127
127
  }
128
128
 
129
129
  return (
130
- <div className="border border-gray-800 rounded-lg overflow-auto bg-gray-950/50">
131
- <svg width={width} height={height} className="min-w-full">
130
+ <div className="border border-gray-800 rounded-lg bg-gray-950/50">
131
+ {/* Mobile hint */}
132
+ <div className="lg:hidden bg-blue-900/20 border-b border-blue-800/30 px-4 py-3 text-sm text-blue-300">
133
+ <p className="font-medium mb-1">Complex visualization</p>
134
+ <p className="text-xs text-blue-400/80">
135
+ This dependency graph is optimized for desktop. Scroll to explore or switch to Table/Tree view for better mobile experience.
136
+ </p>
137
+ </div>
138
+
139
+ <div className="overflow-auto">
140
+ <svg width={width} height={height} className="min-w-full">
132
141
  <defs>
133
142
  <marker
134
143
  id="arrowhead"
@@ -192,6 +201,7 @@ export function DependencyGraph({ data }: DependencyGraphProps) {
192
201
  )
193
202
  })}
194
203
  </svg>
204
+ </div>
195
205
  </div>
196
206
  )
197
207
  }
@@ -52,6 +52,16 @@ function buildEstimateData(data: ProgressData): MilestoneEstimate[] {
52
52
 
53
53
  export function EstimateChart({ data }: EstimateChartProps) {
54
54
  const chartData = buildEstimateData(data)
55
+ const [isMobile, setIsMobile] = useState(false)
56
+
57
+ useEffect(() => {
58
+ const checkMobile = () => {
59
+ setIsMobile(window.innerWidth < 1024)
60
+ }
61
+ checkMobile()
62
+ window.addEventListener('resize', checkMobile)
63
+ return () => window.removeEventListener('resize', checkMobile)
64
+ }, [])
55
65
 
56
66
  if (chartData.length === 0) {
57
67
  return null
@@ -59,15 +69,22 @@ export function EstimateChart({ data }: EstimateChartProps) {
59
69
 
60
70
  const hasActuals = chartData.some((d) => d.actual !== null)
61
71
 
72
+ // Mobile: LR (horizontal bars), Desktop: TB (vertical bars) for better space usage
73
+ const layout = isMobile ? 'vertical' : 'vertical'
74
+ const chartHeight = isMobile
75
+ ? Math.max(200, chartData.length * 50 + 40) // LR: height scales with items
76
+ : Math.max(300, chartData.length * 50 + 40)
77
+ const yAxisWidth = isMobile ? 120 : 180
78
+
62
79
  return (
63
80
  <div className="bg-gray-900/50 border border-gray-800 rounded-xl p-5">
64
81
  <h3 className="text-sm font-semibold text-gray-300 mb-4">
65
82
  Estimated vs Actual Hours
66
83
  </h3>
67
- <ResponsiveContainer width="100%" height={Math.max(200, chartData.length * 50 + 40)}>
84
+ <ResponsiveContainer width="100%" height={chartHeight}>
68
85
  <BarChart
69
86
  data={chartData}
70
- layout="vertical"
87
+ layout={layout}
71
88
  margin={{ top: 5, right: 20, bottom: 5, left: 10 }}
72
89
  >
73
90
  <CartesianGrid strokeDasharray="3 3" stroke="#1f2937" horizontal={false} />
@@ -81,7 +98,7 @@ export function EstimateChart({ data }: EstimateChartProps) {
81
98
  <YAxis
82
99
  type="category"
83
100
  dataKey="name"
84
- width={180}
101
+ width={yAxisWidth}
85
102
  tick={{ fill: '#9ca3af', fontSize: 11 }}
86
103
  tickLine={false}
87
104
  axisLine={{ stroke: '#374151' }}
@@ -91,21 +91,29 @@ export function MilestoneGantt({ milestones, tasks }: MilestoneGanttProps) {
91
91
 
92
92
  return (
93
93
  <div className="border border-gray-800 rounded-lg overflow-hidden">
94
- {/* Timeline header */}
95
- <div className="relative h-8 bg-gray-900/50 border-b border-gray-800">
96
- {monthLabels.map((m, i) => (
97
- <div
98
- key={i}
99
- className="absolute top-0 h-full border-l border-gray-800 flex items-center"
100
- style={{ left: `${Math.max(0, m.left)}%` }}
101
- >
102
- <span className="text-[10px] text-gray-500 pl-1.5">{m.label}</span>
103
- </div>
104
- ))}
94
+ {/* Mobile hint */}
95
+ <div className="lg:hidden bg-gray-900/30 border-b border-gray-800 px-3 py-2 text-xs text-gray-500">
96
+ Scroll horizontally to view timeline →
105
97
  </div>
106
98
 
107
- {/* Bars */}
108
- <div className="divide-y divide-gray-800/50">
99
+ {/* Scrollable container */}
100
+ <div className="overflow-x-auto">
101
+ <div className="min-w-[800px]">
102
+ {/* Timeline header */}
103
+ <div className="relative h-8 bg-gray-900/50 border-b border-gray-800">
104
+ {monthLabels.map((m, i) => (
105
+ <div
106
+ key={i}
107
+ className="absolute top-0 h-full border-l border-gray-800 flex items-center"
108
+ style={{ left: `${Math.max(0, m.left)}%` }}
109
+ >
110
+ <span className="text-xs lg:text-[10px] text-gray-500 pl-1.5">{m.label}</span>
111
+ </div>
112
+ ))}
113
+ </div>
114
+
115
+ {/* Bars */}
116
+ <div className="divide-y divide-gray-800/50">
109
117
  {bars.map(({ milestone, start, end }) => {
110
118
  const barStart = start ? (daysBetween(minDate, start) / totalDays) * 100 : 0
111
119
  const barEnd = end ? (daysBetween(minDate, end) / totalDays) * 100 : barStart + 5
@@ -121,7 +129,7 @@ export function MilestoneGantt({ milestones, tasks }: MilestoneGanttProps) {
121
129
  return (
122
130
  <div key={milestone.id} className="flex items-center h-12 px-3 hover:bg-gray-200/20 dark:hover:bg-gray-800/20">
123
131
  {/* Label */}
124
- <div className="w-48 shrink-0 flex items-center gap-2">
132
+ <div className="w-32 lg:w-48 shrink-0 flex items-center gap-2">
125
133
  <span className="text-xs text-gray-700 dark:text-gray-300 truncate">{formatMilestoneName(milestone)}</span>
126
134
  </div>
127
135
  {/* Bar area */}
@@ -145,6 +153,8 @@ export function MilestoneGantt({ milestones, tasks }: MilestoneGanttProps) {
145
153
  </div>
146
154
  )
147
155
  })}
156
+ </div>
157
+ </div>
148
158
  </div>
149
159
  </div>
150
160
  )