@@ -19,12 +19,14 @@ import {
1919 ModelUsageSummary ,
2020 DailyModelData ,
2121 PowerUserSummary ,
22+ PowerUserDailyBreakdown ,
2223 aggregateDataByDay ,
2324 parseCSV ,
2425 getModelUsageSummary ,
2526 getDailyModelData ,
2627 getPowerUsers ,
2728 getPowerUserDailyData ,
29+ getPowerUserDailyBreakdown ,
2830 getLastDateFromData
2931} from "@/lib/utils" ;
3032
@@ -35,11 +37,27 @@ function App() {
3537 const [ modelSummary , setModelSummary ] = useState < ModelUsageSummary [ ] > ( [ ] ) ;
3638 const [ dailyModelData , setDailyModelData ] = useState < DailyModelData [ ] > ( [ ] ) ;
3739 const [ powerUserSummary , setPowerUserSummary ] = useState < PowerUserSummary | null > ( null ) ;
40+ const [ powerUserDailyBreakdown , setPowerUserDailyBreakdown ] = useState < PowerUserDailyBreakdown [ ] > ( [ ] ) ;
41+ const [ selectedPowerUser , setSelectedPowerUser ] = useState < string | null > ( null ) ;
3842 const [ lastDateAvailable , setLastDateAvailable ] = useState < string | null > ( null ) ;
3943 const [ isDragging , setIsDragging ] = useState ( false ) ;
4044 const [ isProcessing , setIsProcessing ] = useState ( false ) ;
4145 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
4246
47+ const handlePowerUserSelect = useCallback ( ( userName : string | null ) => {
48+ setSelectedPowerUser ( userName ) ;
49+ } , [ ] ) ;
50+
51+ // Generate filtered power user daily breakdown based on selected user
52+ const getFilteredPowerUserBreakdown = useCallback ( ( ) => {
53+ if ( ! selectedPowerUser || ! data ) {
54+ return powerUserDailyBreakdown ;
55+ }
56+
57+ // Filter the original data to only include the selected user, then regenerate breakdown
58+ return getPowerUserDailyBreakdown ( data , [ selectedPowerUser ] ) ;
59+ } , [ selectedPowerUser , data , powerUserDailyBreakdown ] ) ;
60+
4361 const processFile = useCallback ( ( file : File ) => {
4462 if ( ! file ) return ;
4563
@@ -96,10 +114,18 @@ function App() {
96114 const powerUsers = getPowerUsers ( parsedData ) ;
97115 setPowerUserSummary ( powerUsers ) ;
98116
117+ // Get power user daily breakdown for the stacked bar chart
118+ const powerUserNames = powerUsers . powerUsers . map ( user => user . user ) ;
119+ const powerUserBreakdown = getPowerUserDailyBreakdown ( parsedData , powerUserNames ) ;
120+ setPowerUserDailyBreakdown ( powerUserBreakdown ) ;
121+
99122 // Get the last date available in the CSV
100123 const lastDate = getLastDateFromData ( parsedData ) ;
101124 setLastDateAvailable ( lastDate ) ;
102125
126+ // Reset selected power user when new data is loaded
127+ setSelectedPowerUser ( null ) ;
128+
103129 setIsProcessing ( false ) ;
104130 toast . success ( `Loaded ${ parsedData . length } records successfully` ) ;
105131 } catch ( error ) {
@@ -139,6 +165,8 @@ function App() {
139165 setModelSummary ( [ ] ) ;
140166 setDailyModelData ( [ ] ) ;
141167 setPowerUserSummary ( null ) ;
168+ setPowerUserDailyBreakdown ( [ ] ) ;
169+ setSelectedPowerUser ( null ) ;
142170 setLastDateAvailable ( null ) ;
143171 }
144172 } ;
@@ -496,6 +524,103 @@ function App() {
496524 </ div >
497525 </ Card >
498526
527+ { /* Power User Requests Breakdown - Stacked Bar Chart */ }
528+ < Card className = "p-4" >
529+ < div className = "flex items-center justify-between mb-3" >
530+ < h3
531+ className = { `text-md font-medium ${ selectedPowerUser ? 'cursor-pointer hover:text-blue-600 transition-colors' : '' } ` }
532+ onClick = { ( ) => selectedPowerUser && handlePowerUserSelect ( null ) }
533+ title = { selectedPowerUser ? 'Click to show all power users' : undefined }
534+ >
535+ Power User Requests Breakdown (Compliant vs Exceeding)
536+ { selectedPowerUser && (
537+ < span className = "text-sm font-normal text-muted-foreground ml-2" >
538+ - { selectedPowerUser }
539+ </ span >
540+ ) }
541+ </ h3 >
542+ { selectedPowerUser && (
543+ < Button
544+ variant = "outline"
545+ size = "sm"
546+ onClick = { ( ) => handlePowerUserSelect ( null ) }
547+ >
548+ Show All
549+ </ Button >
550+ ) }
551+ </ div >
552+ < div className = "h-[300px]" >
553+ < ChartContainer
554+ config = { {
555+ compliantRequests : { color : "#10b981" } , // green
556+ exceedingRequests : { color : "#ef4444" } , // red
557+ } }
558+ className = "h-full w-full"
559+ >
560+ < BarChart data = { getFilteredPowerUserBreakdown ( ) } >
561+ < CartesianGrid strokeDasharray = "3 3" opacity = { 0.2 } />
562+ < XAxis
563+ dataKey = "date"
564+ tick = { { fill : 'var(--foreground)' } }
565+ tickLine = { { stroke : 'var(--border)' } }
566+ domain = { [ 'dataMin' , lastDateAvailable || 'dataMax' ] }
567+ />
568+ < YAxis
569+ tick = { { fill : 'var(--foreground)' } }
570+ tickLine = { { stroke : 'var(--border)' } }
571+ />
572+ < ChartTooltip
573+ content = { ( { active, payload, label } ) => {
574+ if ( active && payload && payload . length ) {
575+ const compliant = payload . find ( p => p . dataKey === 'compliantRequests' ) ?. value || 0 ;
576+ const exceeding = payload . find ( p => p . dataKey === 'exceedingRequests' ) ?. value || 0 ;
577+ const total = Number ( compliant ) + Number ( exceeding ) ;
578+
579+ return (
580+ < div className = "border rounded-lg bg-background shadow-lg p-3 text-xs" >
581+ < div className = "font-medium mb-2" > { label } </ div >
582+ < div className = "space-y-2" >
583+ < div className = "grid grid-cols-2 gap-2" >
584+ < div className = "flex items-center gap-1.5" >
585+ < div className = "w-2 h-2 rounded-full bg-[#10b981]" />
586+ < span > Compliant:</ span >
587+ </ div >
588+ < div className = "text-right" > { Number ( compliant ) . toLocaleString ( undefined , { maximumFractionDigits : 2 , minimumFractionDigits : 0 } ) } </ div >
589+ < div className = "flex items-center gap-1.5" >
590+ < div className = "w-2 h-2 rounded-full bg-[#ef4444]" />
591+ < span > Exceeding:</ span >
592+ </ div >
593+ < div className = "text-right" > { Number ( exceeding ) . toLocaleString ( undefined , { maximumFractionDigits : 2 , minimumFractionDigits : 0 } ) } </ div >
594+ < div className = "font-medium" > Total:</ div >
595+ < div className = "text-right font-medium" > { Number ( total ) . toLocaleString ( undefined , { maximumFractionDigits : 2 , minimumFractionDigits : 0 } ) } </ div >
596+ </ div >
597+ </ div >
598+ </ div >
599+ ) ;
600+ }
601+ return null ;
602+ } }
603+ />
604+ < Legend />
605+
606+ { /* Stacked bars for compliant and exceeding requests */ }
607+ < Bar
608+ dataKey = "compliantRequests"
609+ name = "Compliant Requests"
610+ stackId = "requests"
611+ fill = "#10b981"
612+ />
613+ < Bar
614+ dataKey = "exceedingRequests"
615+ name = "Exceeding Requests"
616+ stackId = "requests"
617+ fill = "#ef4444"
618+ />
619+ </ BarChart >
620+ </ ChartContainer >
621+ </ div >
622+ </ Card >
623+
499624 { /* Individual Power Users List */ }
500625 < Card className = "p-4" >
501626 < h3 className = "text-md font-medium mb-3" > Individual Power Users</ h3 >
@@ -505,14 +630,22 @@ function App() {
505630 < TableRow >
506631 < TableHead > User</ TableHead >
507632 < TableHead className = "text-right" > Total Requests</ TableHead >
633+ < TableHead className = "text-right" > Exceeding Requests</ TableHead >
508634 < TableHead className = "text-right" > Models Used</ TableHead >
509635 </ TableRow >
510636 </ TableHeader >
511637 < TableBody >
512638 { powerUserSummary . powerUsers . map ( ( user ) => (
513639 < TableRow key = { user . user } >
514- < TableCell className = "font-medium" > { user . user } </ TableCell >
640+ < TableCell
641+ className = { `font-medium cursor-pointer hover:text-blue-600 transition-colors ${ selectedPowerUser === user . user ? 'text-blue-600 font-bold' : '' } ` }
642+ onClick = { ( ) => handlePowerUserSelect ( user . user ) }
643+ title = "Click to filter chart to this user"
644+ >
645+ { user . user }
646+ </ TableCell >
515647 < TableCell className = "text-right" > { user . totalRequests . toLocaleString ( undefined , { maximumFractionDigits : 2 , minimumFractionDigits : 0 } ) } </ TableCell >
648+ < TableCell className = "text-right" > { user . exceedingRequests . toLocaleString ( undefined , { maximumFractionDigits : 2 , minimumFractionDigits : 0 } ) } </ TableCell >
516649 < TableCell className = "text-right" > { Object . keys ( user . requestsByModel ) . length } </ TableCell >
517650 </ TableRow >
518651 ) ) }
0 commit comments