claude-team-dashboard 1.2.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.
Potentially problematic release.
This version of claude-team-dashboard might be problematic. Click here for more details.
- package/CHANGELOG.md +76 -0
- package/LICENSE +21 -0
- package/README.md +722 -0
- package/cleanup.js +73 -0
- package/config.js +50 -0
- package/dist/assets/icons-Ijf8rQIc.js +1 -0
- package/dist/assets/index-Cqc1m1x_.css +1 -0
- package/dist/assets/index-jGy3ms0W.js +9 -0
- package/dist/assets/react-vendor-DbmSkCAF.js +1 -0
- package/dist/index.html +16 -0
- package/index.html +13 -0
- package/package.json +93 -0
- package/server.js +953 -0
- package/src/App.jsx +372 -0
- package/src/animations-enhanced.css +929 -0
- package/src/animations.css +783 -0
- package/src/components/ActivityFeed.jsx +289 -0
- package/src/components/AgentActivity.jsx +104 -0
- package/src/components/AgentCard.jsx +163 -0
- package/src/components/AgentOutputViewer.jsx +334 -0
- package/src/components/ArchiveViewer.jsx +283 -0
- package/src/components/ConnectionStatus.jsx +124 -0
- package/src/components/DetailedTaskProgress.jsx +126 -0
- package/src/components/ErrorBoundary.jsx +132 -0
- package/src/components/Header.jsx +154 -0
- package/src/components/LiveAgentStream.jsx +176 -0
- package/src/components/LiveCommunication.jsx +326 -0
- package/src/components/LiveMetrics.jsx +100 -0
- package/src/components/RealTimeMessages.jsx +298 -0
- package/src/components/SkeletonLoader.jsx +384 -0
- package/src/components/StatsOverview.jsx +209 -0
- package/src/components/SystemStatus.jsx +57 -0
- package/src/components/TaskList.jsx +306 -0
- package/src/components/TeamCard.jsx +126 -0
- package/src/components/TeamHistory.jsx +204 -0
- package/src/components/__tests__/ConnectionStatus.test.jsx +54 -0
- package/src/components/__tests__/StatsOverview.test.jsx +66 -0
- package/src/config/constants.js +59 -0
- package/src/hooks/useCounterAnimation.js +219 -0
- package/src/hooks/useWebSocket.js +76 -0
- package/src/index.css +1818 -0
- package/src/main.jsx +17 -0
- package/src/polish-enhancements.css +303 -0
- package/src/premium-visual-polish.css +830 -0
- package/src/responsive-enhancements.css +666 -0
- package/src/styles/theme.css +395 -0
- package/src/test/setup.js +19 -0
- package/start.js +36 -0
- package/vite.config.js +37 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCounterAnimation Hook
|
|
3
|
+
*
|
|
4
|
+
* Smoothly animates number changes with easing
|
|
5
|
+
* Perfect for stat counters, progress indicators, and real-time data updates
|
|
6
|
+
*
|
|
7
|
+
* @param {number} targetValue - The target number to animate to
|
|
8
|
+
* @param {number} duration - Animation duration in milliseconds (default: 600ms)
|
|
9
|
+
* @returns {number} - The current animated display value
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const animatedCount = useCounterAnimation(stats.totalTasks, 600);
|
|
13
|
+
* return <span>{animatedCount}</span>;
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useEffect, useState, useRef } from 'react';
|
|
17
|
+
|
|
18
|
+
export function useCounterAnimation(targetValue, duration = 600) {
|
|
19
|
+
const [displayValue, setDisplayValue] = useState(targetValue);
|
|
20
|
+
const prevValueRef = useRef(targetValue);
|
|
21
|
+
const animationFrameRef = useRef(null);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
// Skip animation if target value is the same
|
|
25
|
+
if (targetValue === prevValueRef.current) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const startValue = prevValueRef.current;
|
|
30
|
+
const difference = targetValue - startValue;
|
|
31
|
+
const startTime = performance.now();
|
|
32
|
+
|
|
33
|
+
// Cancel any ongoing animation
|
|
34
|
+
if (animationFrameRef.current) {
|
|
35
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const animate = (currentTime) => {
|
|
39
|
+
const elapsed = currentTime - startTime;
|
|
40
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
41
|
+
|
|
42
|
+
// Ease-out cubic for smooth deceleration
|
|
43
|
+
// Formula: 1 - (1 - x)^3
|
|
44
|
+
const easeOutCubic = 1 - Math.pow(1 - progress, 3);
|
|
45
|
+
|
|
46
|
+
// Calculate current value
|
|
47
|
+
const current = Math.round(startValue + difference * easeOutCubic);
|
|
48
|
+
|
|
49
|
+
setDisplayValue(current);
|
|
50
|
+
|
|
51
|
+
if (progress < 1) {
|
|
52
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
53
|
+
} else {
|
|
54
|
+
// Animation complete - update ref
|
|
55
|
+
prevValueRef.current = targetValue;
|
|
56
|
+
animationFrameRef.current = null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
61
|
+
|
|
62
|
+
// Cleanup on unmount
|
|
63
|
+
return () => {
|
|
64
|
+
if (animationFrameRef.current) {
|
|
65
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}, [targetValue, duration]);
|
|
69
|
+
|
|
70
|
+
return displayValue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* useCounterAnimationWithHighlight Hook
|
|
75
|
+
*
|
|
76
|
+
* Same as useCounterAnimation but also returns whether value increased/decreased
|
|
77
|
+
* Useful for applying visual highlights when values change
|
|
78
|
+
*
|
|
79
|
+
* @param {number} targetValue - The target number to animate to
|
|
80
|
+
* @param {number} duration - Animation duration in milliseconds (default: 600ms)
|
|
81
|
+
* @returns {object} - { value, isIncreasing, isDecreasing, hasChanged }
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* const { value, isIncreasing } = useCounterAnimationWithHighlight(stats.totalTasks);
|
|
85
|
+
* return <span className={isIncreasing ? 'counter-increased' : ''}>{value}</span>;
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
export function useCounterAnimationWithHighlight(targetValue, duration = 600) {
|
|
89
|
+
const [displayValue, setDisplayValue] = useState(targetValue);
|
|
90
|
+
const [changeDirection, setChangeDirection] = useState(null); // 'increase', 'decrease', or null
|
|
91
|
+
const prevValueRef = useRef(targetValue);
|
|
92
|
+
const animationFrameRef = useRef(null);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (targetValue === prevValueRef.current) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const startValue = prevValueRef.current;
|
|
100
|
+
const difference = targetValue - startValue;
|
|
101
|
+
const startTime = performance.now();
|
|
102
|
+
|
|
103
|
+
// Determine direction
|
|
104
|
+
setChangeDirection(difference > 0 ? 'increase' : 'decrease');
|
|
105
|
+
|
|
106
|
+
if (animationFrameRef.current) {
|
|
107
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const animate = (currentTime) => {
|
|
111
|
+
const elapsed = currentTime - startTime;
|
|
112
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
113
|
+
|
|
114
|
+
const easeOutCubic = 1 - Math.pow(1 - progress, 3);
|
|
115
|
+
const current = Math.round(startValue + difference * easeOutCubic);
|
|
116
|
+
|
|
117
|
+
setDisplayValue(current);
|
|
118
|
+
|
|
119
|
+
if (progress < 1) {
|
|
120
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
121
|
+
} else {
|
|
122
|
+
prevValueRef.current = targetValue;
|
|
123
|
+
animationFrameRef.current = null;
|
|
124
|
+
|
|
125
|
+
// Reset change direction after a delay
|
|
126
|
+
setTimeout(() => setChangeDirection(null), 1000);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
131
|
+
|
|
132
|
+
return () => {
|
|
133
|
+
if (animationFrameRef.current) {
|
|
134
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}, [targetValue, duration]);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
value: displayValue,
|
|
141
|
+
isIncreasing: changeDirection === 'increase',
|
|
142
|
+
isDecreasing: changeDirection === 'decrease',
|
|
143
|
+
hasChanged: changeDirection !== null
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* useStaggeredAnimation Hook
|
|
149
|
+
*
|
|
150
|
+
* Returns staggered delay for list items
|
|
151
|
+
* Perfect for animating lists of cards, tasks, or activity items
|
|
152
|
+
*
|
|
153
|
+
* @param {number} index - Item index in the list
|
|
154
|
+
* @param {number} delayIncrement - Delay between items in milliseconds (default: 60ms)
|
|
155
|
+
* @returns {object} - { animationDelay: string }
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* const { animationDelay } = useStaggeredAnimation(index, 80);
|
|
159
|
+
* return <div style={{ animationDelay }}>{item}</div>;
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
export function useStaggeredAnimation(index, delayIncrement = 60) {
|
|
163
|
+
const delay = index * delayIncrement;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
animationDelay: `${delay}ms`,
|
|
167
|
+
style: {
|
|
168
|
+
animationDelay: `${delay}ms`
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* useAnimationObserver Hook
|
|
175
|
+
*
|
|
176
|
+
* Observes when element enters viewport and triggers animation
|
|
177
|
+
* Perfect for lazy-loading animations on scroll
|
|
178
|
+
*
|
|
179
|
+
* @param {object} options - IntersectionObserver options
|
|
180
|
+
* @returns {[ref, isVisible]} - Ref to attach to element and visibility state
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* const [ref, isVisible] = useAnimationObserver();
|
|
184
|
+
* return <div ref={ref} className={isVisible ? 'animate-in' : ''}>{content}</div>;
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
export function useAnimationObserver(options = {}) {
|
|
188
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
189
|
+
const [hasAnimated, setHasAnimated] = useState(false);
|
|
190
|
+
const elementRef = useRef(null);
|
|
191
|
+
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
const element = elementRef.current;
|
|
194
|
+
if (!element || hasAnimated) return;
|
|
195
|
+
|
|
196
|
+
const observer = new IntersectionObserver(
|
|
197
|
+
([entry]) => {
|
|
198
|
+
if (entry.isIntersecting && !hasAnimated) {
|
|
199
|
+
setIsVisible(true);
|
|
200
|
+
setHasAnimated(true);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
threshold: 0.1,
|
|
205
|
+
...options
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
observer.observe(element);
|
|
210
|
+
|
|
211
|
+
return () => {
|
|
212
|
+
if (element) {
|
|
213
|
+
observer.unobserve(element);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}, [hasAnimated, options]);
|
|
217
|
+
|
|
218
|
+
return [elementRef, isVisible];
|
|
219
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WebSocket hook for managing WebSocket connections
|
|
5
|
+
* @param {string} url - WebSocket URL to connect to
|
|
6
|
+
* @returns {{data: any, isConnected: boolean, error: string|null}} WebSocket state
|
|
7
|
+
*/
|
|
8
|
+
export function useWebSocket(url) {
|
|
9
|
+
const [data, setData] = useState(null);
|
|
10
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const wsRef = useRef(null);
|
|
13
|
+
const reconnectTimeoutRef = useRef(null);
|
|
14
|
+
const reconnectAttempts = useRef(0);
|
|
15
|
+
|
|
16
|
+
const connect = useCallback(() => {
|
|
17
|
+
try {
|
|
18
|
+
const ws = new WebSocket(url);
|
|
19
|
+
wsRef.current = ws;
|
|
20
|
+
|
|
21
|
+
ws.onopen = () => {
|
|
22
|
+
console.log('WebSocket connected');
|
|
23
|
+
setIsConnected(true);
|
|
24
|
+
setError(null);
|
|
25
|
+
reconnectAttempts.current = 0;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
ws.onmessage = (event) => {
|
|
29
|
+
try {
|
|
30
|
+
const message = JSON.parse(event.data);
|
|
31
|
+
setData(message);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error('Error parsing WebSocket message:', err);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
ws.onerror = (err) => {
|
|
38
|
+
console.error('WebSocket error:', err);
|
|
39
|
+
setError('Connection error');
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
ws.onclose = () => {
|
|
43
|
+
console.log('WebSocket disconnected');
|
|
44
|
+
setIsConnected(false);
|
|
45
|
+
wsRef.current = null;
|
|
46
|
+
|
|
47
|
+
// Attempt to reconnect with exponential backoff
|
|
48
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000);
|
|
49
|
+
reconnectAttempts.current++;
|
|
50
|
+
|
|
51
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
52
|
+
console.log(`Attempting to reconnect (attempt ${reconnectAttempts.current})...`);
|
|
53
|
+
connect();
|
|
54
|
+
}, delay);
|
|
55
|
+
};
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error('Error creating WebSocket:', err);
|
|
58
|
+
setError('Failed to connect');
|
|
59
|
+
}
|
|
60
|
+
}, [url]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
connect();
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
if (reconnectTimeoutRef.current) {
|
|
67
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
68
|
+
}
|
|
69
|
+
if (wsRef.current) {
|
|
70
|
+
wsRef.current.close();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}, [connect]);
|
|
74
|
+
|
|
75
|
+
return { data, isConnected, error };
|
|
76
|
+
}
|