@@ -13,7 +13,7 @@ import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/
1313import { Table , TableBody , TableCell , TableFooter , TableHead , TableHeader , TableRow } from "@/components/ui/table" ;
1414import { Sheet , SheetContent , SheetHeader , SheetTitle , SheetTrigger } from "@/components/ui/sheet" ;
1515import { Select , SelectContent , SelectItem , SelectTrigger , SelectValue } from "@/components/ui/select" ;
16- import { Dialog , DialogContent , DialogHeader , DialogTitle } from "@/components/ui/dialog" ;
16+ import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription } from "@/components/ui/dialog" ;
1717import { DeploymentFooter } from "@/components/DeploymentFooter" ;
1818import {
1919 AggregatedData ,
@@ -23,6 +23,7 @@ import {
2323 PowerUserSummary ,
2424 PowerUserDailyBreakdown ,
2525 ExceededRequestDetail ,
26+ ExceededUserSummary ,
2627 ProjectedUserData ,
2728 MonthOption ,
2829 UserAnalysisData ,
@@ -40,6 +41,7 @@ import {
4041 getExceededRequestDetails ,
4142 getUserExceededRequestSummary ,
4243 getUniqueUsersExceedingQuota ,
44+ getExceededUsersOverview ,
4345 getTotalRequestsForUsersExceedingQuota ,
4446 getProjectedUsersExceedingQuota ,
4547 getProjectedUsersExceedingQuotaDetails ,
@@ -502,6 +504,8 @@ function App() {
502504 const [ showPotentialCostDetails , setShowPotentialCostDetails ] = useState ( false ) ;
503505 const [ showProjectedUsersDialog , setShowProjectedUsersDialog ] = useState ( false ) ;
504506 const [ projectedUsersData , setProjectedUsersData ] = useState < ProjectedUserData [ ] > ( [ ] ) ;
507+ const [ showExceededUsersOverview , setShowExceededUsersOverview ] = useState ( false ) ;
508+ const [ exceededUsersOverviewData , setExceededUsersOverviewData ] = useState < ExceededUserSummary [ ] > ( [ ] ) ;
505509 const [ selectedSearchUser , setSelectedSearchUser ] = useState < string | null > ( null ) ;
506510 const [ userAnalysisData , setUserAnalysisData ] = useState < UserAnalysisData | null > ( null ) ;
507511 const [ expectedExcessCost , setExpectedExcessCost ] = useState < number > ( 0 ) ;
@@ -576,6 +580,9 @@ function App() {
576580 const exceedingUsersCount = getUniqueUsersExceedingQuota ( displayData , selectedPlan ) ;
577581 setUsersExceedingQuota ( exceedingUsersCount ) ;
578582
583+ // Compute exceeded users overview for the overview dialog
584+ setExceededUsersOverviewData ( getExceededUsersOverview ( displayData ) ) ;
585+
579586 // Get projected count of users who will exceed quota by month-end for display data
580587 const projectedExceedingUsersCount = getProjectedUsersExceedingQuota ( displayData , selectedPlan ) ;
581588 setProjectedUsersExceedingQuota ( projectedExceedingUsersCount ) ;
@@ -628,6 +635,9 @@ function App() {
628635 const exceedingUsersCount = getUniqueUsersExceedingQuota ( filteredData , selectedPlan ) ;
629636 setUsersExceedingQuota ( exceedingUsersCount ) ;
630637
638+ // Compute exceeded users overview for the overview dialog
639+ setExceededUsersOverviewData ( getExceededUsersOverview ( filteredData ) ) ;
640+
631641 // Get projected count of users who will exceed quota by month-end for the selected month
632642 const projectedExceedingUsersCount = getProjectedUsersExceedingQuota ( filteredData , selectedPlan ) ;
633643 setProjectedUsersExceedingQuota ( projectedExceedingUsersCount ) ;
@@ -1355,11 +1365,16 @@ function App() {
13551365 { uniqueModels . length }
13561366 </ span >
13571367 </ div >
1358- < div className = "flex items-center gap-2" >
1368+ < div
1369+ className = { usersExceedingQuota > 0 ? "xebia-action-button" : "flex items-center gap-2" }
1370+ onClick = { usersExceedingQuota > 0 ? ( ) => setShowExceededUsersOverview ( true ) : undefined }
1371+ title = { usersExceedingQuota > 0 ? "Click to see which users exceeded their quota" : undefined }
1372+ >
13591373 < span className = "text-sm text-muted-foreground" > Users Exceeding Quota:</ span >
1360- < span className = " text-lg font-bold text-red -600">
1374+ < span className = { ` ${ usersExceedingQuota > 0 ? "value text-red-600" : "text- lg font-bold text-green -600"} ` } >
13611375 { usersExceedingQuota . toLocaleString ( ) }
13621376 </ span >
1377+ { usersExceedingQuota > 0 && < ChevronRight className = "icon" /> }
13631378 </ div >
13641379 < div
13651380 className = "xebia-action-button"
@@ -2094,6 +2109,95 @@ function App() {
20942109 </ div >
20952110 ) }
20962111
2112+ { /* Exceeded Users Overview Dialog */ }
2113+ < Dialog open = { showExceededUsersOverview } onOpenChange = { setShowExceededUsersOverview } >
2114+ < DialogContent className = "w-[98vw] max-w-none max-h-[85vh] overflow-y-auto" style = { { width : '98vw' , maxWidth : 'none' } } >
2115+ < DialogHeader >
2116+ < DialogTitle > Users Exceeding Quota</ DialogTitle >
2117+ < DialogDescription >
2118+ All users who have exceeded their quota at least once — including how often, how many exceeded requests they made, and how many other requests they made on the same days.
2119+ </ DialogDescription >
2120+ </ DialogHeader >
2121+ < div className = "space-y-4" >
2122+ { exceededUsersOverviewData . length > 0 ? (
2123+ < >
2124+ { /* Summary row */ }
2125+ < div className = "grid grid-cols-2 md:grid-cols-4 gap-4" >
2126+ < Card className = "p-4" >
2127+ < div className = "text-sm text-muted-foreground" > Users Exceeded</ div >
2128+ < div className = "text-2xl font-bold text-red-600" > { exceededUsersOverviewData . length . toLocaleString ( ) } </ div >
2129+ </ Card >
2130+ < Card className = "p-4" >
2131+ < div className = "text-sm text-muted-foreground" > Total Exceeded Requests</ div >
2132+ < div className = "text-2xl font-bold text-red-600" >
2133+ { exceededUsersOverviewData . reduce ( ( s , u ) => s + u . totalExceededRequests , 0 ) . toLocaleString ( ) }
2134+ </ div >
2135+ </ Card >
2136+ < Card className = "p-4" >
2137+ < div className = "text-sm text-muted-foreground" > Total Days with Exceeded Requests</ div >
2138+ < div className = "text-2xl font-bold" >
2139+ { exceededUsersOverviewData . reduce ( ( s , u ) => s + u . daysExceeded , 0 ) . toLocaleString ( ) }
2140+ </ div >
2141+ </ Card >
2142+ < Card className = "p-4" >
2143+ < div className = "text-sm text-muted-foreground" > Avg Days per User</ div >
2144+ < div className = "text-2xl font-bold" >
2145+ { ( exceededUsersOverviewData . reduce ( ( s , u ) => s + u . daysExceeded , 0 ) / exceededUsersOverviewData . length ) . toFixed ( 1 ) }
2146+ </ div >
2147+ </ Card >
2148+ </ div >
2149+
2150+ { /* Per-user table */ }
2151+ < Card className = "p-4" >
2152+ < h3 className = "text-md font-medium mb-3" > Per-User Breakdown</ h3 >
2153+ < div className = "overflow-auto" >
2154+ < Table >
2155+ < TableHeader >
2156+ < TableRow >
2157+ < TableHead className = "min-w-[160px]" > User</ TableHead >
2158+ < TableHead className = "text-right min-w-[110px]" > Days Exceeded</ TableHead >
2159+ < TableHead className = "text-right min-w-[160px]" > Total Exceeded Requests</ TableHead >
2160+ < TableHead className = "text-right min-w-[160px]" > Other Requests (Same Days)</ TableHead >
2161+ < TableHead className = "text-right min-w-[160px]" > Total Requests (Same Days)</ TableHead >
2162+ < TableHead className = "min-w-[130px]" > Worst Day</ TableHead >
2163+ </ TableRow >
2164+ </ TableHeader >
2165+ < TableBody >
2166+ { exceededUsersOverviewData . map ( ( row ) => (
2167+ < TableRow key = { row . user } >
2168+ < TableCell className = "font-medium" > { row . user } </ TableCell >
2169+ < TableCell className = "text-right" > { row . daysExceeded . toLocaleString ( ) } </ TableCell >
2170+ < TableCell className = "text-right text-red-600 font-medium" >
2171+ { row . totalExceededRequests . toLocaleString ( ) }
2172+ </ TableCell >
2173+ < TableCell className = "text-right" >
2174+ { row . compliantRequestsOnExceededDays . toLocaleString ( ) }
2175+ </ TableCell >
2176+ < TableCell className = "text-right font-medium" >
2177+ { row . totalRequestsOnExceededDays . toLocaleString ( ) }
2178+ </ TableCell >
2179+ < TableCell className = "text-sm" >
2180+ < div className = "font-medium" > { row . worstDay . date } </ div >
2181+ < div className = "text-muted-foreground text-xs" >
2182+ { row . worstDay . exceededRequests . toLocaleString ( ) } exceeded of { row . worstDay . totalRequests . toLocaleString ( ) } total
2183+ </ div >
2184+ </ TableCell >
2185+ </ TableRow >
2186+ ) ) }
2187+ </ TableBody >
2188+ </ Table >
2189+ </ div >
2190+ </ Card >
2191+ </ >
2192+ ) : (
2193+ < Card className = "p-8 text-center" >
2194+ < p className = "text-muted-foreground" > No users have exceeded their quota in the selected period. 🎉</ p >
2195+ </ Card >
2196+ ) }
2197+ </ div >
2198+ </ DialogContent >
2199+ </ Dialog >
2200+
20972201 { /* Exceeded Request Details Dialog */ }
20982202 < Dialog open = { showExceededDetails } onOpenChange = { setShowExceededDetails } >
20992203 < DialogContent className = "w-[98vw] max-w-none max-h-[85vh] overflow-y-auto" style = { { width : '98vw' , maxWidth : 'none' } } >
0 commit comments