Skip to content

Commit 08ffc52

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/npm-dependencies-9be70a1f1e
2 parents 5b56f27 + dc42642 commit 08ffc52

File tree

4 files changed

+706
-13
lines changed

4 files changed

+706
-13
lines changed

src/App.tsx

Lines changed: 255 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useCallback, useRef, DragEvent, useEffect } from "react";
1+
import React, { useState, useCallback, useRef, DragEvent, useEffect, useMemo } from "react";
22
import { Upload, GithubLogo, CircleNotch } from "@phosphor-icons/react";
33
import { UserSquare, ChevronRight, Shield } from "lucide-react";
44
import { toast, Toaster } from "sonner";
@@ -25,6 +25,7 @@ import {
2525
ExceededRequestDetail,
2626
ProjectedUserData,
2727
MonthOption,
28+
UserAnalysisData,
2829
aggregateDataByDay,
2930
parseCSV,
3031
getModelUsageSummary,
@@ -43,9 +44,11 @@ import {
4344
getProjectedUsersExceedingQuotaDetails,
4445
getAvailableMonths,
4546
filterDataByMonth,
47+
getUserAnalysisData,
4648
EXCESS_REQUEST_COST
4749
} from "@/lib/utils";
4850
import { MonthSelector } from "@/components/MonthSelector";
51+
import { UserSearch } from "@/components/UserSearch";
4952

5053
function App() {
5154
const [showPrivacyBanner, setShowPrivacyBanner] = useState(true);
@@ -73,6 +76,8 @@ function App() {
7376
const [showPotentialCostDetails, setShowPotentialCostDetails] = useState(false);
7477
const [showProjectedUsersDialog, setShowProjectedUsersDialog] = useState(false);
7578
const [projectedUsersData, setProjectedUsersData] = useState<ProjectedUserData[]>([]);
79+
const [selectedSearchUser, setSelectedSearchUser] = useState<string | null>(null);
80+
const [userAnalysisData, setUserAnalysisData] = useState<UserAnalysisData | null>(null);
7681
const fileInputRef = useRef<HTMLInputElement>(null);
7782

7883
// Recalculate users exceeding quota when plan selection changes
@@ -86,15 +91,74 @@ function App() {
8691

8792
const projectedDetails = getProjectedUsersExceedingQuotaDetails(data, selectedPlan);
8893
setProjectedUsersData(projectedDetails);
94+
95+
// Update user analysis data if a user is selected
96+
if (selectedSearchUser) {
97+
const analysisData = getUserAnalysisData(data, selectedSearchUser);
98+
setUserAnalysisData(analysisData);
99+
}
89100
}
90-
}, [selectedPlan, data]);
101+
}, [selectedPlan, data, selectedSearchUser]);
102+
103+
// Get display data - either filtered by user or all data
104+
const displayData = useMemo(() => {
105+
if (!data) return null;
106+
if (!selectedSearchUser) return data;
107+
return data.filter(item => item.user === selectedSearchUser);
108+
}, [data, selectedSearchUser]);
91109

92110
// Reprocess data when month selection changes
93111
useEffect(() => {
94112
if (rawData && selectedMonth) {
95113
processDataForMonth(rawData, selectedMonth);
96114
}
97115
}, [selectedMonth, rawData, selectedPlan]);
116+
117+
// Reprocess display data when user selection changes
118+
useEffect(() => {
119+
if (displayData && displayData.length > 0) {
120+
// Get unique models from display data
121+
const models = Array.from(new Set(displayData.map(item => item.model)));
122+
setUniqueModels(models);
123+
124+
// Aggregate data by day and model for display data
125+
const aggregated = aggregateDataByDay(displayData);
126+
setAggregatedData(aggregated);
127+
128+
// Get model usage summary for display data
129+
const summary = getModelUsageSummary(displayData);
130+
setModelSummary(summary);
131+
132+
// Get daily model data for bar chart for display data
133+
const dailyData = getDailyModelData(displayData);
134+
setDailyModelData(dailyData);
135+
136+
// Get power users data for display data
137+
const powerUsers = getPowerUsers(displayData);
138+
setPowerUserSummary(powerUsers);
139+
140+
// Get power user daily breakdown for display data
141+
const powerUserNames = powerUsers.powerUsers.map(user => user.user);
142+
const powerUserBreakdown = getPowerUserDailyBreakdown(displayData, powerUserNames);
143+
setPowerUserDailyBreakdown(powerUserBreakdown);
144+
145+
// Get count of users exceeding quota for display data
146+
const exceedingUsersCount = getUniqueUsersExceedingQuota(displayData, selectedPlan);
147+
setUsersExceedingQuota(exceedingUsersCount);
148+
149+
// Get projected count of users who will exceed quota by month-end for display data
150+
const projectedExceedingUsersCount = getProjectedUsersExceedingQuota(displayData, selectedPlan);
151+
setProjectedUsersExceedingQuota(projectedExceedingUsersCount);
152+
153+
// Get projected users details for display data
154+
const projectedDetails = getProjectedUsersExceedingQuotaDetails(displayData, selectedPlan);
155+
setProjectedUsersData(projectedDetails);
156+
157+
// Get the last date available in the display data
158+
const lastDate = getLastDateFromData(displayData);
159+
setLastDateAvailable(lastDate);
160+
}
161+
}, [displayData, selectedPlan]);
98162

99163
/**
100164
* Process data for a specific month and update all derived state
@@ -148,12 +212,28 @@ function App() {
148212

149213
// Reset selected power user when month changes
150214
setSelectedPowerUser(null);
151-
}, [selectedPlan]);
215+
216+
// Reset selected search user when month changes and recalculate analysis
217+
if (selectedSearchUser) {
218+
const analysisData = getUserAnalysisData(filteredData, selectedSearchUser);
219+
setUserAnalysisData(analysisData);
220+
}
221+
}, [selectedPlan, selectedSearchUser]);
152222

153223
const handlePowerUserSelect = useCallback((userName: string | null) => {
154224
setSelectedPowerUser(userName);
155225
}, []);
156226

227+
const handleSearchUserSelect = useCallback((userName: string | null) => {
228+
setSelectedSearchUser(userName);
229+
if (userName && data) {
230+
const analysisData = getUserAnalysisData(data, userName);
231+
setUserAnalysisData(analysisData);
232+
} else {
233+
setUserAnalysisData(null);
234+
}
235+
}, [data]);
236+
157237
// Generate filtered power user daily breakdown based on selected user
158238
const getFilteredPowerUserBreakdown = useCallback(() => {
159239
if (!selectedPowerUser || !data) {
@@ -596,12 +676,24 @@ function App() {
596676
</Card>
597677
)}
598678

599-
{data && data.length > 0 && (
679+
{displayData && displayData.length > 0 && (
600680
<div className="space-y-8">
601681
<div>
602682
<div className="flex items-center justify-between mb-4">
603683
<div>
604-
<h2 className="text-2xl font-semibold mb-2">Usage Statistics</h2>
684+
<h2 className="text-2xl font-semibold mb-2">
685+
Usage Statistics
686+
{selectedSearchUser && (
687+
<span className="ml-2 text-lg font-medium text-blue-600">
688+
- {selectedSearchUser}
689+
</span>
690+
)}
691+
</h2>
692+
{selectedSearchUser && (
693+
<p className="text-sm text-muted-foreground">
694+
Showing data filtered for selected user. All panels below reflect this user's activity only.
695+
</p>
696+
)}
605697
</div>
606698
<div className="flex items-center gap-4">
607699
<MonthSelector
@@ -613,6 +705,135 @@ function App() {
613705
/>
614706
</div>
615707
</div>
708+
<Separator className="mb-4" />
709+
710+
{/* User Analysis Section */}
711+
<div className="mb-6">
712+
<div className="flex items-center justify-between mb-4">
713+
<div>
714+
<h2 className="text-2xl font-semibold mb-2">User Analysis</h2>
715+
<p className="text-muted-foreground">
716+
{selectedSearchUser
717+
? `Currently viewing data for ${selectedSearchUser}. All panels are filtered to show only this user's activity.`
718+
: "Search for a specific user to view their detailed usage statistics"
719+
}
720+
</p>
721+
</div>
722+
{selectedSearchUser && (
723+
<Button
724+
variant="outline"
725+
onClick={() => handleSearchUserSelect(null)}
726+
className="ml-4"
727+
>
728+
View All Users
729+
</Button>
730+
)}
731+
</div>
732+
<div className="mb-4">
733+
<UserSearch
734+
data={data}
735+
selectedUser={selectedSearchUser}
736+
onUserChange={handleSearchUserSelect}
737+
disabled={isProcessing}
738+
/>
739+
</div>
740+
741+
{userAnalysisData && (
742+
<Card>
743+
<div className="p-5">
744+
<div className="flex items-center justify-between mb-4">
745+
<h3 className="text-lg font-semibold">Analysis for {userAnalysisData.user}</h3>
746+
</div>
747+
748+
{/* User Statistics Summary */}
749+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
750+
<div>
751+
<div className="text-sm text-muted-foreground">Total Requests</div>
752+
<div className="text-lg font-bold">{userAnalysisData.totalRequests.toLocaleString()}</div>
753+
</div>
754+
<div>
755+
<div className="text-sm text-muted-foreground">Daily Average</div>
756+
<div className="text-lg font-bold">{userAnalysisData.dailyAverage.toLocaleString(undefined, {maximumFractionDigits: 1})}</div>
757+
</div>
758+
<div>
759+
<div className="text-sm text-muted-foreground">Models Used</div>
760+
<div className="text-lg font-bold">{userAnalysisData.uniqueModels.length}</div>
761+
</div>
762+
<div>
763+
<div className="text-sm text-muted-foreground">Exceeds Free Budget</div>
764+
<div className={`text-lg font-bold ${userAnalysisData.exceedsFreeBudget ? 'text-red-600' : 'text-green-600'}`}>
765+
{userAnalysisData.exceedsFreeBudget ? 'Yes' : 'No'}
766+
</div>
767+
</div>
768+
</div>
769+
770+
{/* Weekly Breakdown */}
771+
<div className="mb-4">
772+
<h4 className="text-md font-medium mb-3">Weekly Breakdown (ISO weeks)</h4>
773+
<div className="overflow-auto max-h-60">
774+
<Table>
775+
<TableHeader>
776+
<TableRow>
777+
<TableHead>Week</TableHead>
778+
<TableHead>Date Range</TableHead>
779+
<TableHead className="text-right">Compliant Requests</TableHead>
780+
<TableHead className="text-right">Exceeding Requests</TableHead>
781+
<TableHead className="text-right">Total Requests</TableHead>
782+
<TableHead>Models Used</TableHead>
783+
</TableRow>
784+
</TableHeader>
785+
<TableBody>
786+
{userAnalysisData.weeklyBreakdown.map((week) => (
787+
<TableRow key={`${week.year}-W${week.week}`}>
788+
<TableCell className="font-medium">
789+
{week.year}-W{week.week.toString().padStart(2, '0')}
790+
</TableCell>
791+
<TableCell>
792+
{week.startDate} to {week.endDate}
793+
</TableCell>
794+
<TableCell className="text-right">
795+
{week.compliantRequests.toLocaleString()}
796+
</TableCell>
797+
<TableCell className="text-right">
798+
<span className={week.exceedingRequests > 0 ? 'text-red-600 font-medium' : ''}>
799+
{week.exceedingRequests.toLocaleString()}
800+
</span>
801+
</TableCell>
802+
<TableCell className="text-right font-medium">
803+
{week.totalRequests.toLocaleString()}
804+
</TableCell>
805+
<TableCell>
806+
<div className="flex flex-wrap gap-1">
807+
{week.modelsUsed.map((model) => (
808+
<span key={model} className="px-1.5 py-0.5 bg-secondary text-xs rounded">
809+
{model}
810+
</span>
811+
))}
812+
</div>
813+
</TableCell>
814+
</TableRow>
815+
))}
816+
</TableBody>
817+
</Table>
818+
</div>
819+
</div>
820+
821+
{/* Models Used */}
822+
<div>
823+
<h4 className="text-md font-medium mb-3">All Models Used</h4>
824+
<div className="flex flex-wrap gap-2">
825+
{userAnalysisData.uniqueModels.map((model) => (
826+
<span key={model} className="px-3 py-1 bg-secondary text-sm rounded-md">
827+
{model}
828+
</span>
829+
))}
830+
</div>
831+
</div>
832+
</div>
833+
</Card>
834+
)}
835+
</div>
836+
616837
<Separator className="mb-4" />
617838
<div className="mb-4">
618839
<Card>
@@ -621,13 +842,13 @@ function App() {
621842
<div className="flex items-center gap-2">
622843
<span className="text-sm text-muted-foreground">Total Requests:</span>
623844
<span className="text-lg font-bold">
624-
{data.reduce((sum, item) => sum + item.requestsUsed, 0).toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}
845+
{displayData.reduce((sum, item) => sum + item.requestsUsed, 0).toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}
625846
</span>
626847
</div>
627848
<div className="flex items-center gap-2">
628849
<span className="text-sm text-muted-foreground">Unique Users:</span>
629850
<span className="text-lg font-bold">
630-
{new Set(data.map(item => item.user)).size.toLocaleString()}
851+
{new Set(displayData.map(item => item.user)).size.toLocaleString()}
631852
</span>
632853
</div>
633854
<div className="flex items-center gap-2">
@@ -660,7 +881,7 @@ function App() {
660881
>
661882
<span className="text-sm text-muted-foreground">Potential Cost:</span>
662883
<span className="value">
663-
${(data.reduce((sum, item) => sum + item.requestsUsed, 0) * EXCESS_REQUEST_COST).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}
884+
${(displayData.reduce((sum, item) => sum + item.requestsUsed, 0) * EXCESS_REQUEST_COST).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}
664885
</span>
665886
<ChevronRight className="icon" />
666887
</div>
@@ -1004,7 +1225,14 @@ function App() {
10041225
<div className="mb-6">
10051226
<Card className="p-5">
10061227
<div className="flex items-center justify-between mb-3">
1007-
<h3 className="text-md font-medium">Requests per Model</h3>
1228+
<h3 className="text-md font-medium">
1229+
Requests per Model
1230+
{selectedSearchUser && (
1231+
<span className="ml-2 text-sm font-normal text-blue-600">
1232+
- {selectedSearchUser}
1233+
</span>
1234+
)}
1235+
</h3>
10081236
<div className="flex items-center gap-2">
10091237
<span className="text-sm text-muted-foreground">Plan Type:</span>
10101238
<Select value={selectedPlan} onValueChange={setSelectedPlan}>
@@ -1072,7 +1300,14 @@ function App() {
10721300

10731301
<div>
10741302
<div className="flex justify-between items-center mb-2">
1075-
<h2 className="text-2xl font-semibold">Daily Usage Overview</h2>
1303+
<h2 className="text-2xl font-semibold">
1304+
Daily Usage Overview
1305+
{selectedSearchUser && (
1306+
<span className="ml-2 text-lg font-medium text-blue-600">
1307+
- {selectedSearchUser}
1308+
</span>
1309+
)}
1310+
</h2>
10761311
{lastDateAvailable && (
10771312
<div className="text-sm text-muted-foreground">
10781313
Data available through: <span className="font-medium">{lastDateAvailable}</span>
@@ -1159,7 +1394,14 @@ function App() {
11591394

11601395
{/* Bar Chart - Requests per Model per Day */}
11611396
<div className="flex justify-between items-center mb-2">
1162-
<h2 className="text-2xl font-semibold">Requests per Model per Day</h2>
1397+
<h2 className="text-2xl font-semibold">
1398+
Requests per Model per Day
1399+
{selectedSearchUser && (
1400+
<span className="ml-2 text-lg font-medium text-blue-600">
1401+
- {selectedSearchUser}
1402+
</span>
1403+
)}
1404+
</h2>
11631405
{lastDateAvailable && (
11641406
<div className="text-sm text-muted-foreground">
11651407
Data available through: <span className="font-medium">{lastDateAvailable}</span>
@@ -1245,11 +1487,11 @@ function App() {
12451487
{exceededDetailsData.length > 0 ? (
12461488
<>
12471489
{/* Summary Card */}
1248-
{selectedPowerUser && data && (
1490+
{selectedPowerUser && displayData && (
12491491
<Card className="p-4">
12501492
<h3 className="text-md font-medium mb-3">User Summary</h3>
12511493
{(() => {
1252-
const userSummary = getUserExceededRequestSummary(data, selectedPowerUser);
1494+
const userSummary = getUserExceededRequestSummary(displayData, selectedPowerUser);
12531495
return (
12541496
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
12551497
<div>

0 commit comments

Comments
 (0)