Skip to content

Commit 35f599d

Browse files
authored
Merge branch 'main' into bolt-perf-matching-array-consolidation-4431554792924675838
2 parents 79cbd22 + bef1246 commit 35f599d

4 files changed

Lines changed: 85 additions & 66 deletions

File tree

src/hooks/use-platform-stats.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,36 @@ export interface PlatformStats {
99
techCount: number;
1010
}
1111

12+
// ⚡ Bolt: Consolidated multiple array passes (.map.filter, .filter, .flatMap) into a single O(n) loop
1213
function computeStats(
1314
profiles: Array<{ city?: string | null; open_to_collaboration?: boolean | null; tech_stack?: string[] | null }>
1415
): PlatformStats {
1516
const total = profiles.length;
1617
if (total === 0) return { totalUsers: 0, totalCities: 0, collaborationRate: 0, techCount: 0 };
1718

18-
// ⚡ Bolt: Consolidate data aggregation into a single O(n) pass
19-
// Avoids multiple redundant iterations and intermediate array allocations
20-
const citySet = new Set<string>();
21-
const techSet = new Set<string>();
2219
let collab = 0;
20+
const citiesSet = new Set<string>();
21+
const techsSet = new Set<string>();
2322

24-
for (let i = 0; i < total; i++) {
25-
const p = profiles[i];
26-
if (p.city) citySet.add(p.city);
27-
if (p.open_to_collaboration) collab++;
23+
for (const p of profiles) {
24+
if (p.open_to_collaboration) {
25+
collab++;
26+
}
27+
if (p.city) {
28+
citiesSet.add(p.city);
29+
}
2830
if (p.tech_stack) {
29-
for (let j = 0; j < p.tech_stack.length; j++) {
30-
techSet.add(p.tech_stack[j]);
31+
for (const tech of p.tech_stack) {
32+
techsSet.add(tech);
3133
}
3234
}
3335
}
3436

3537
return {
3638
totalUsers: total,
37-
totalCities: citySet.size,
39+
totalCities: citiesSet.size,
3840
collaborationRate: Math.round((collab / total) * 100),
39-
techCount: techSet.size,
41+
techCount: techsSet.size,
4042
};
4143
}
4244

src/pages/about-page.tsx

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const CATEGORY_COLORS: Record<string, string> = {
105105
"Autres profils": "bg-chart-5",
106106
};
107107

108+
// ⚡ Bolt: Consolidated multiple iterations into a single O(n) pass over profiles.
108109
function computeStats(profiles: RawProfile[]): ComputedStats {
109110
const total = profiles.length;
110111
if (total === 0) {
@@ -123,9 +124,64 @@ function computeStats(profiles: RawProfile[]): ComputedStats {
123124
}
124125

125126
const roleCounts: Record<string, number> = {};
126-
for (const p of profiles)
127+
const expCounts: Record<string, number> = {};
128+
const techCounts: Record<string, number> = {};
129+
const cityCounts: Record<string, number> = {};
130+
131+
let collab = 0;
132+
let activeLastWeek = 0;
133+
134+
const now = new Date();
135+
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
136+
137+
const months = Array.from({ length: 6 }, (_, i) => {
138+
const d = new Date(now.getFullYear(), now.getMonth() - 5 + i, 1);
139+
return {
140+
month: d.toLocaleDateString("fr-FR", { month: "short" }),
141+
year: d.getFullYear(),
142+
monthNum: d.getMonth(),
143+
count: 0,
144+
};
145+
});
146+
147+
// ⚡ Bolt: Single pass loop for all aggregations
148+
for (const p of profiles) {
149+
// Roles
127150
roleCounts[p.role_type] = (roleCounts[p.role_type] || 0) + 1;
128151

152+
// Experience
153+
expCounts[p.experience_level] = (expCounts[p.experience_level] || 0) + 1;
154+
155+
// Techs
156+
if (p.tech_stack) {
157+
for (const t of p.tech_stack) {
158+
techCounts[t] = (techCounts[t] || 0) + 1;
159+
}
160+
}
161+
162+
// Cities
163+
if (p.city) {
164+
cityCounts[p.city] = (cityCounts[p.city] || 0) + 1;
165+
}
166+
167+
// Growth
168+
const d = new Date(p.created_at);
169+
const entry = months.find(
170+
(m) => m.year === d.getFullYear() && m.monthNum === d.getMonth()
171+
);
172+
if (entry) entry.count++;
173+
174+
// Collaboration
175+
if (p.open_to_collaboration) {
176+
collab++;
177+
}
178+
179+
// Active last week
180+
if (p.last_seen_at && new Date(p.last_seen_at) >= weekAgo) {
181+
activeLastWeek++;
182+
}
183+
}
184+
129185
// Regroupement dynamique : agrège tous les role_type connus dans des
130186
// catégories plus larges, n'affiche que celles ayant au moins 1 profil
131187
// et trie par population décroissante.
@@ -143,9 +199,6 @@ function computeStats(profiles: RawProfile[]): ComputedStats {
143199
.filter((r) => r.pct > 0)
144200
.sort((a, b) => b.pct - a.pct);
145201

146-
const expCounts: Record<string, number> = {};
147-
for (const p of profiles)
148-
expCounts[p.experience_level] = (expCounts[p.experience_level] || 0) + 1;
149202
const experience = [
150203
{
151204
label: "Junior",
@@ -164,10 +217,6 @@ function computeStats(profiles: RawProfile[]): ComputedStats {
164217
},
165218
];
166219

167-
const techCounts: Record<string, number> = {};
168-
for (const p of profiles)
169-
for (const t of p.tech_stack ?? [])
170-
techCounts[t] = (techCounts[t] || 0) + 1;
171220
const techs = Object.entries(techCounts)
172221
.sort((a, b) => b[1] - a[1])
173222
.slice(0, 6)
@@ -176,9 +225,6 @@ function computeStats(profiles: RawProfile[]): ComputedStats {
176225
pct: Math.round((count / total) * 100),
177226
}));
178227

179-
const cityCounts: Record<string, number> = {};
180-
for (const p of profiles)
181-
if (p.city) cityCounts[p.city] = (cityCounts[p.city] || 0) + 1;
182228
const sortedCities = Object.entries(cityCounts).sort((a, b) => b[1] - a[1]);
183229
const cities = sortedCities
184230
.slice(0, 3)
@@ -187,49 +233,15 @@ function computeStats(profiles: RawProfile[]): ComputedStats {
187233
if (otherCount > 0)
188234
cities.push({ name: "Autres", pct: Math.round((otherCount / total) * 100) });
189235

190-
const now = new Date();
191-
const months = Array.from({ length: 6 }, (_, i) => {
192-
const d = new Date(now.getFullYear(), now.getMonth() - 5 + i, 1);
193-
return {
194-
month: d.toLocaleDateString("fr-FR", { month: "short" }),
195-
year: d.getFullYear(),
196-
monthNum: d.getMonth(),
197-
count: 0,
198-
};
199-
});
200-
for (const p of profiles) {
201-
const d = new Date(p.created_at);
202-
const entry = months.find(
203-
(m) => m.year === d.getFullYear() && m.monthNum === d.getMonth()
204-
);
205-
if (entry) entry.count++;
206-
}
207236
const maxCount = Math.max(...months.map((m) => m.count), 1);
208237
const growth = months.map((m) => ({
209238
month: m.month,
210239
value: Math.round((m.count / maxCount) * 100),
211240
}));
212241

213-
// ⚡ Bolt: Consolidate data aggregation into a single O(n) pass
214-
// Avoids multiple redundant iterations and intermediate array allocations
215-
let collab = 0;
216-
let activeLastWeek = 0;
217-
const citySet = new Set<string>();
218-
const techSet = new Set<string>();
219-
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
220-
221-
for (let i = 0; i < profiles.length; i++) {
222-
const p = profiles[i];
223-
if (p.open_to_collaboration) collab++;
224-
if (p.city) citySet.add(p.city);
225-
if (p.last_seen_at && new Date(p.last_seen_at) >= weekAgo) activeLastWeek++;
226-
227-
if (p.tech_stack) {
228-
for (let j = 0; j < p.tech_stack.length; j++) {
229-
techSet.add(p.tech_stack[j]);
230-
}
231-
}
232-
}
242+
// ⚡ Bolt: Derive distinct counts from keys instead of doing additional passes
243+
const totalCities = Object.keys(cityCounts).length;
244+
const allTechs = Object.keys(techCounts).length;
233245

234246
return {
235247
total,

src/pages/admin-page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect, useState, useMemo } from "react";
22
import { useNavigate, Link } from "react-router-dom";
33
import { Shield, Send, UserPlus, UserMinus, Users, Building2, Handshake, Download, Search, TrendingUp, ArrowUpRight, TriangleAlert, Clock, GitCommitHorizontal, UserRoundPlus, Layers as Layers3, Flag, UserCheck, MapPin, Trash2 } from "lucide-react";
44
import { Button } from "@/components/ui/button";
@@ -171,11 +171,11 @@ export function AdminPage() {
171171
);
172172
}
173173

174-
const filteredProfiles = profiles.filter((p) => {
174+
const filteredProfiles = useMemo(() => profiles.filter((p) => {
175175
if (!searchQuery) return true;
176176
const q = searchQuery.toLowerCase();
177177
return p.full_name.toLowerCase().includes(q) || p.username.toLowerCase().includes(q) || p.city?.toLowerCase().includes(q);
178-
});
178+
}), [profiles, searchQuery]);
179179

180180
// ⚡ Bolt: Consolidate data aggregation into a single O(n) pass
181181
// Avoids multiple redundant iterations and intermediate array allocations

src/pages/matching-page.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,12 +508,17 @@ function ProjetsTab({
508508
}
509509
}, [user, interested]);
510510

511-
const displayList: { project: Project; score?: number; reasons?: string[] }[] =
512-
profile && projectMatches.length > 0
511+
// ⚡ Bolt: Memoize the mapping logic to prevent O(N) object allocations on every render
512+
// This avoids redundant work when local interactive states (like visibleCount or interested) change.
513+
// Impact: Reduces main-thread blocking during re-renders by caching derived datasets.
514+
const displayList: { project: Project; score?: number; reasons?: string[] }[] = useMemo(() => {
515+
return profile && projectMatches.length > 0
513516
? projectMatches.map((m) => ({ project: m.project, score: m.score, reasons: m.reasons }))
514517
: projects.map((p) => ({ project: p }));
518+
}, [profile, projectMatches, projects]);
515519

516-
const visible = displayList.slice(0, visibleCount);
520+
// ⚡ Bolt: Memoize slicing to prevent re-instantiating the visible array on unrelated renders.
521+
const visible = useMemo(() => displayList.slice(0, visibleCount), [displayList, visibleCount]);
517522

518523
if (isLoading) {
519524
return (

0 commit comments

Comments
 (0)