@@ -254,4 +254,85 @@ const Panels = {
254254 } ) . join ( '' ) }
255255 ` ;
256256 } ,
257+
258+ /* ═══════════════════════════════════════════════════════
259+ ACTIVITY BREAKDOWN + ONE-SHOT RATE
260+ ═══════════════════════════════════════════════════════ */
261+
262+ ACTIVITY_COLORS : {
263+ coding : '#34d399' , debugging : '#f87171' , testing : '#fbbf24' ,
264+ exploration : '#22d3ee' , refactoring : '#c084fc' , git_ops : '#60a5fa' ,
265+ planning : '#f97316' , other : '#71717a' ,
266+ } ,
267+
268+ renderActivity ( data ) {
269+ const el = document . getElementById ( 'activity-content' ) ;
270+ const badge = document . getElementById ( 'oneshot-badge' ) ;
271+ if ( ! data || ! data . activities ) {
272+ el . innerHTML = '<div class="panel-empty">No activity data</div>' ;
273+ return ;
274+ }
275+
276+ const acts = data . activities ;
277+ const rate = data . oneshot_rate || 0 ;
278+ const total = Object . values ( acts ) . reduce ( ( s , v ) => s + v , 0 ) ;
279+
280+ if ( badge ) {
281+ const rateColor = rate >= 80 ? 'var(--success)' : rate >= 60 ? 'var(--warning)' : 'var(--error)' ;
282+ badge . innerHTML = `<span style="color:${ rateColor } ;font-weight:700">${ rate . toFixed ( 0 ) } % one-shot</span>` ;
283+ }
284+
285+ if ( total === 0 ) {
286+ el . innerHTML = '<div class="panel-empty">No sessions</div>' ;
287+ return ;
288+ }
289+
290+ const maxVal = Math . max ( ...Object . values ( acts ) ) ;
291+
292+ el . innerHTML = Object . entries ( acts )
293+ . filter ( ( [ , v ] ) => v > 0 )
294+ . map ( ( [ act , count ] ) => {
295+ const pct = ( count / total * 100 ) . toFixed ( 0 ) ;
296+ const color = Panels . ACTIVITY_COLORS [ act ] || '#71717a' ;
297+ const barW = Math . max ( ( count / maxVal * 100 ) , 2 ) . toFixed ( 1 ) ;
298+ const name = act . replace ( '_' , ' ' ) ;
299+ return `
300+ <div class="activity-row">
301+ <span class="activity-name">${ name } </span>
302+ <div class="activity-bar-track">
303+ <div class="activity-bar-fill" style="width:${ barW } %;background:${ color } "></div>
304+ </div>
305+ <span class="activity-pct" style="color:${ color } ">${ pct } %</span>
306+ <span class="activity-count">${ count } </span>
307+ </div>` ;
308+ } ) . join ( '' ) ;
309+ } ,
310+
311+ /* ═══════════════════════════════════════════════════════
312+ COST BY PROJECT
313+ ═══════════════════════════════════════════════════════ */
314+
315+ renderProjectCost ( data ) {
316+ const el = document . getElementById ( 'project-cost-content' ) ;
317+ if ( ! data || ! data . cost_by_project || data . cost_by_project . length === 0 ) {
318+ el . innerHTML = '<div class="panel-empty">No project data</div>' ;
319+ return ;
320+ }
321+
322+ const projects = data . cost_by_project ;
323+ const maxCost = projects [ 0 ] . cost || 1 ;
324+
325+ el . innerHTML = projects . map ( p => {
326+ const barW = Math . max ( ( p . cost / maxCost * 100 ) , 2 ) . toFixed ( 1 ) ;
327+ return `
328+ <div class="pcost-row">
329+ <span class="pcost-name">${ p . project } </span>
330+ <div class="pcost-bar-track">
331+ <div class="pcost-bar-fill" style="width:${ barW } %"></div>
332+ </div>
333+ <span class="pcost-val">${ App . formatCost ( p . cost ) } </span>
334+ <span class="pcost-sess">${ p . sessions } s</span>
335+ </div>` ;
336+ } ) . join ( '' ) ;
337+ } ,
257338} ;
0 commit comments