@@ -19,12 +19,15 @@ import {
1919 ModelUsageSummary ,
2020 DailyModelData ,
2121 PowerUserSummary ,
22+ PowerUserDailyBreakdown ,
2223 aggregateDataByDay ,
2324 parseCSV ,
2425 getModelUsageSummary ,
2526 getDailyModelData ,
2627 getPowerUsers ,
27- getPowerUserDailyData
28+ getPowerUserDailyData ,
29+ getPowerUserDailyBreakdown ,
30+ getLastDateFromData
2831} from "@/lib/utils" ;
2932
3033function App ( ) {
@@ -34,10 +37,27 @@ function App() {
3437 const [ modelSummary , setModelSummary ] = useState < ModelUsageSummary [ ] > ( [ ] ) ;
3538 const [ dailyModelData , setDailyModelData ] = useState < DailyModelData [ ] > ( [ ] ) ;
3639 const [ powerUserSummary , setPowerUserSummary ] = useState < PowerUserSummary | null > ( null ) ;
40+ const [ powerUserDailyBreakdown , setPowerUserDailyBreakdown ] = useState < PowerUserDailyBreakdown [ ] > ( [ ] ) ;
41+ const [ selectedPowerUser , setSelectedPowerUser ] = useState < string | null > ( null ) ;
42+ const [ lastDateAvailable , setLastDateAvailable ] = useState < string | null > ( null ) ;
3743 const [ isDragging , setIsDragging ] = useState ( false ) ;
3844 const [ isProcessing , setIsProcessing ] = useState ( false ) ;
3945 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
4046
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+
4161 const processFile = useCallback ( ( file : File ) => {
4262 if ( ! file ) return ;
4363
@@ -94,6 +114,18 @@ function App() {
94114 const powerUsers = getPowerUsers ( parsedData ) ;
95115 setPowerUserSummary ( powerUsers ) ;
96116
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+
122+ // Get the last date available in the CSV
123+ const lastDate = getLastDateFromData ( parsedData ) ;
124+ setLastDateAvailable ( lastDate ) ;
125+
126+ // Reset selected power user when new data is loaded
127+ setSelectedPowerUser ( null ) ;
128+
97129 setIsProcessing ( false ) ;
98130 toast . success ( `Loaded ${ parsedData . length } records successfully` ) ;
99131 } catch ( error ) {
@@ -133,6 +165,9 @@ function App() {
133165 setModelSummary ( [ ] ) ;
134166 setDailyModelData ( [ ] ) ;
135167 setPowerUserSummary ( null ) ;
168+ setPowerUserDailyBreakdown ( [ ] ) ;
169+ setSelectedPowerUser ( null ) ;
170+ setLastDateAvailable ( null ) ;
136171 }
137172 } ;
138173
@@ -453,7 +488,8 @@ function App() {
453488 < XAxis
454489 dataKey = "date"
455490 tick = { { fill : 'var(--foreground)' } }
456- tickLine = { { stroke : 'var(--border)' } }
491+ tickLine = { { stroke : 'var(--border)' } }
492+ domain = { [ 'dataMin' , lastDateAvailable || 'dataMax' ] }
457493 />
458494 < YAxis
459495 tick = { { fill : 'var(--foreground)' } }
@@ -488,6 +524,103 @@ function App() {
488524 </ div >
489525 </ Card >
490526
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+
491624 { /* Individual Power Users List */ }
492625 < Card className = "p-4" >
493626 < h3 className = "text-md font-medium mb-3" > Individual Power Users</ h3 >
@@ -497,14 +630,22 @@ function App() {
497630 < TableRow >
498631 < TableHead > User</ TableHead >
499632 < TableHead className = "text-right" > Total Requests</ TableHead >
633+ < TableHead className = "text-right" > Exceeding Requests</ TableHead >
500634 < TableHead className = "text-right" > Models Used</ TableHead >
501635 </ TableRow >
502636 </ TableHeader >
503637 < TableBody >
504638 { powerUserSummary . powerUsers . map ( ( user ) => (
505639 < TableRow key = { user . user } >
506- < 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 >
507647 < 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 >
508649 < TableCell className = "text-right" > { Object . keys ( user . requestsByModel ) . length } </ TableCell >
509650 </ TableRow >
510651 ) ) }
@@ -559,7 +700,14 @@ function App() {
559700 </ div >
560701
561702 < div >
562- < h2 className = "text-2xl font-semibold mb-2" > Daily Usage Overview</ h2 >
703+ < div className = "flex justify-between items-center mb-2" >
704+ < h2 className = "text-2xl font-semibold" > Daily Usage Overview</ h2 >
705+ { lastDateAvailable && (
706+ < div className = "text-sm text-muted-foreground" >
707+ Data available through: < span className = "font-medium" > { lastDateAvailable } </ span >
708+ </ div >
709+ ) }
710+ </ div >
563711 < Separator className = "mb-6" />
564712 < div className = "bg-card p-4 rounded-lg border mb-8" >
565713 < ChartContainer
@@ -574,7 +722,8 @@ function App() {
574722 < XAxis
575723 dataKey = "date"
576724 tick = { { fill : 'var(--foreground)' } }
577- tickLine = { { stroke : 'var(--border)' } }
725+ tickLine = { { stroke : 'var(--border)' } }
726+ domain = { [ 'dataMin' , lastDateAvailable || 'dataMax' ] }
578727 />
579728 < YAxis
580729 tick = { { fill : 'var(--foreground)' } }
@@ -640,7 +789,14 @@ function App() {
640789 </ div >
641790
642791 { /* Bar Chart - Requests per Model per Day */ }
643- < h2 className = "text-2xl font-semibold mb-2" > Requests per Model per Day</ h2 >
792+ < div className = "flex justify-between items-center mb-2" >
793+ < h2 className = "text-2xl font-semibold" > Requests per Model per Day</ h2 >
794+ { lastDateAvailable && (
795+ < div className = "text-sm text-muted-foreground" >
796+ Data available through: < span className = "font-medium" > { lastDateAvailable } </ span >
797+ </ div >
798+ ) }
799+ </ div >
644800 < Separator className = "mb-6" />
645801 < div className = "bg-card p-4 rounded-lg border" >
646802 < ChartContainer
@@ -653,6 +809,7 @@ function App() {
653809 dataKey = "date"
654810 tick = { { fill : 'var(--foreground)' } }
655811 tickLine = { { stroke : 'var(--border)' } }
812+ domain = { [ 'dataMin' , lastDateAvailable || 'dataMax' ] }
656813 />
657814 < YAxis
658815 tick = { { fill : 'var(--foreground)' } }
0 commit comments