Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
## 2024-05-19 - Avoid repeated Array.filter().length inside JSX
**Learning:** Found multiple instances where `.filter().length` was called directly inside render maps (e.g., in StaffModule, EscalationModule, VendorsModule). This causes multiple O(N) array traversals on every single render.
**Action:** Replace direct `.filter().length` calls inside JSX loops with a single `useMemo` block using `.reduce()` to calculate all necessary counts in a single O(N) pass, and add clear explanatory comments for these optimizations.
## 2024-05-20 - Array.filter() chained inside useMemo
**Learning:** Found O(N * M) performance degradation inside `ReportsModule.jsx` where `.filter()` was chained inside `.map()` arrays on every render (even when wrapped in `useMemo`). This resulted in iterating through arrays of 50k+ elements unnecessarily.
**Action:** Replace multiple `.filter().length` chained passes with a single `.reduce()` structure, accumulating independent counts and arrays inside one loop.
28 changes: 22 additions & 6 deletions src/components/ReportsModule.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,36 @@ import { cd } from "@/lib/data";
export default function Rpt({ P }) {
// Memoize top level ticket/task computations to avoid O(N) operations on every render
const stats = useMemo(() => {
// ⚑ Bolt Optimization: Aggregated O(N) filtering operations into a single O(N) reduce pass
// Reduces multiple traversals of the P.tickets array on every render.
const tStats = P.tickets.reduce((acc, t) => {
if (t.status === "resolved" || t.status === "closed") acc.resolved++;
if (t.tatMins) {
acc.tatSum += t.tatMins;
acc.tatCount++;
}
return acc;
}, { resolved: 0, tatSum: 0, tatCount: 0 });

const totalTickets = P.tickets.length;
const resolved = P.tickets.filter(t => t.status === "resolved" || t.status === "closed").length;
const tatList = P.tickets.filter(t => t.tatMins);
const avgTAT = tatList.reduce((a, t) => a + t.tatMins, 0) / (tatList.length || 1);
const overdueCount = P.tasks.filter(t => t.status === "overdue").length;
return { totalTickets, resolved, avgTAT, overdueCount };
const avgTAT = tStats.tatCount > 0 ? (tStats.tatSum / tStats.tatCount) : 0;
const overdueCount = P.tasks.reduce((acc, t) => t.status === "overdue" ? acc + 1 : acc, 0);
return { totalTickets, resolved: tStats.resolved, avgTAT, overdueCount };
}, [P.tickets, P.tasks]);

// Memoize priority distributions
const priorityDist = useMemo(() => {
// ⚑ Bolt Optimization: Calculate priority counts in a single pass instead of looping through all tickets for every priority
const counts = P.tickets.reduce((acc, t) => {
if (acc[t.priority] !== undefined) acc[t.priority]++;
return acc;
}, { critical: 0, high: 0, medium: 0, low: 0 });

const priorities = ["critical", "high", "medium", "low"];
const colors = { critical: "#dc2626", high: "#ea580c", medium: "#d97706", low: "#2563eb" };

return priorities.map(p => {
const cnt = P.tickets.filter(t => t.priority === p).length;
const cnt = counts[p];
const pct = stats.totalTickets > 0 ? Math.round((cnt / stats.totalTickets) * 100) : 0;
return { p, cnt, pct, color: colors[p] };
});
Expand Down