Skip to content

Commit 678325b

Browse files
committed
Make all date ranges fully dynamic based on dateRange parameter
- Trend sampling interval derives from dateRange (~10-12 data points) - Throughput sparklines use min(dateRange, 14) days - Reliability heatmap periods derive from dateRange (4-8 periods) - Period labels show actual date ranges (e.g., "Nov 10-17") - Remove all hardcoded period counts (4 weeks, 14 days, etc.) - Use dynamic values array instead of fixed week1-week4 structure
1 parent 97d5e69 commit 678325b

File tree

2 files changed

+50
-54
lines changed

2 files changed

+50
-54
lines changed

src/components/analytics/QRMDashboardCharts.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ export const ReliabilityHeatmap = ({ data }: { data: QRMDashboardMetrics["reliab
311311
<thead>
312312
<tr>
313313
<th className="text-left font-medium text-muted-foreground pb-2">{t("qrm.reliability.cell")}</th>
314-
{data.weekLabels.map((label, i) => (
314+
{data.periodLabels.map((label, i) => (
315315
<th key={i} className="text-center font-medium text-muted-foreground pb-2 text-xs">{label}</th>
316316
))}
317317
</tr>
@@ -320,7 +320,7 @@ export const ReliabilityHeatmap = ({ data }: { data: QRMDashboardMetrics["reliab
320320
{data.heatmap.map((row, i) => (
321321
<tr key={i} className="border-b border-border/50 last:border-0">
322322
<td className="py-2 font-medium">{row.cellName}</td>
323-
{[row.week1, row.week2, row.week3, row.week4].map((val, j) => (
323+
{row.values.map((val, j) => (
324324
<td key={j} className="py-2 text-center">
325325
<div
326326
className="inline-flex items-center justify-center w-8 h-8 rounded-md text-xs font-bold text-white"

src/hooks/useQRMDashboardMetrics.ts

Lines changed: 48 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export interface QRMDashboardMetrics {
3131
byCell: { cellName: string; current: number; trend: number[] }[];
3232
};
3333
reliability: {
34-
heatmap: { cellName: string; week1: number; week2: number; week3: number; week4: number }[];
35-
weekLabels: string[];
34+
heatmap: { cellName: string; values: number[] }[];
35+
periodLabels: string[];
3636
};
3737
}
3838

@@ -89,9 +89,15 @@ export function useQRMDashboardMetrics(dateRange: number = 30) {
8989
// --- Calculations ---
9090

9191
// Generate date range for trends
92-
const endDate = new Date();
93-
const startDateTrend = subDays(endDate, dateRange);
94-
const dateInterval = eachDayOfInterval({ start: startDateTrend, end: endDate });
92+
const now = new Date();
93+
const startDateTrend = subDays(now, dateRange);
94+
const dateInterval = eachDayOfInterval({ start: startDateTrend, end: now });
95+
96+
// Calculate sampling interval to get ~10-12 data points for trends
97+
const trendSampleInterval = Math.max(1, Math.floor(dateRange / 10));
98+
99+
// Calculate number of periods for reliability (weeks for 30+ days, days for shorter)
100+
const numPeriods = Math.min(8, Math.max(4, Math.ceil(dateRange / 7)));
95101

96102
// MCT & OTP with trends
97103
const completedJobs = jobs?.filter(j => j.status === 'completed' && j.updated_at) || [];
@@ -114,7 +120,7 @@ export function useQRMDashboardMetrics(dateRange: number = 30) {
114120
mctByDay.get(day)!.push(mctDays);
115121
});
116122

117-
const mctTrend = dateInterval.filter((_, i) => i % 3 === 0).map(date => {
123+
const mctTrend = dateInterval.filter((_, i) => i % trendSampleInterval === 0).map(date => {
118124
const day = format(date, 'yyyy-MM-dd');
119125
const dayMcts = mctByDay.get(day) || [];
120126
const avgDayMct = dayMcts.length ? dayMcts.reduce((a, b) => a + b, 0) / dayMcts.length : avgMct;
@@ -133,7 +139,7 @@ export function useQRMDashboardMetrics(dateRange: number = 30) {
133139
}
134140
});
135141

136-
const otpTrend = dateInterval.filter((_, i) => i % 3 === 0).map(date => {
142+
const otpTrend = dateInterval.filter((_, i) => i % trendSampleInterval === 0).map(date => {
137143
const day = format(date, 'yyyy-MM-dd');
138144
const dayData = otpByDay.get(day);
139145
const dayOtp = dayData && dayData.total > 0 ? (dayData.onTime / dayData.total) * 100 : otp;
@@ -225,11 +231,12 @@ export function useQRMDashboardMetrics(dateRange: number = 30) {
225231
}
226232
});
227233

228-
// Generate last 14 days for trend
229-
const last14Days = eachDayOfInterval({ start: subDays(new Date(), 13), end: new Date() });
234+
// Generate trend days based on dateRange (use ~14 points for sparklines)
235+
const sparklineDays = Math.min(dateRange, 14);
236+
const trendDays = eachDayOfInterval({ start: subDays(now, sparklineDays - 1), end: now });
230237

231238
const throughputByCell = Array.from(throughputByCellDay.entries()).map(([name, dayMap]) => {
232-
const trend = last14Days.map(date => {
239+
const trend = trendDays.map(date => {
233240
const day = format(date, 'yyyy-MM-dd');
234241
return dayMap.get(day) || 0;
235242
});
@@ -242,18 +249,21 @@ export function useQRMDashboardMetrics(dateRange: number = 30) {
242249
};
243250
}).sort((a, b) => b.current - a.current);
244251

245-
// Reliability Heatmap - calculate OTP per cell per week (real data)
246-
// Week boundaries for last 4 weeks
247-
const now = new Date();
248-
const weekStarts = [
249-
startOfWeek(subWeeks(now, 3)),
250-
startOfWeek(subWeeks(now, 2)),
251-
startOfWeek(subWeeks(now, 1)),
252-
startOfWeek(now)
253-
];
252+
// Reliability Heatmap - calculate OTP per cell per period (dynamic based on dateRange)
253+
// Generate period boundaries based on numPeriods
254+
const periodDays = Math.floor(dateRange / numPeriods);
255+
const periodStarts: Date[] = [];
256+
const periodLabels: string[] = [];
257+
258+
for (let i = numPeriods - 1; i >= 0; i--) {
259+
const periodStart = subDays(now, i * periodDays + periodDays);
260+
const periodEnd = subDays(now, i * periodDays);
261+
periodStarts.push(periodStart);
262+
periodLabels.push(`${format(periodStart, 'MMM d')}-${format(periodEnd, 'd')}`);
263+
}
254264

255-
// Group operations by cell and week, calculate reliability (completed on time vs planned)
256-
const reliabilityByCell = new Map<string, { week1: { onTime: number; total: number }; week2: { onTime: number; total: number }; week3: { onTime: number; total: number }; week4: { onTime: number; total: number } }>();
265+
// Group operations by cell and period, calculate reliability
266+
const reliabilityByCell = new Map<string, { onTime: number; total: number }[]>();
257267

258268
completedOps.forEach(op => {
259269
if (!op.cells?.name || !op.completed_at) return;
@@ -263,46 +273,32 @@ export function useQRMDashboardMetrics(dateRange: number = 30) {
263273

264274
// Initialize cell if needed
265275
if (!reliabilityByCell.has(cellName)) {
266-
reliabilityByCell.set(cellName, {
267-
week1: { onTime: 0, total: 0 },
268-
week2: { onTime: 0, total: 0 },
269-
week3: { onTime: 0, total: 0 },
270-
week4: { onTime: 0, total: 0 }
271-
});
276+
reliabilityByCell.set(cellName, Array.from({ length: numPeriods }, () => ({ onTime: 0, total: 0 })));
272277
}
273278

274279
const cellData = reliabilityByCell.get(cellName)!;
275280

276-
// Determine which week this operation belongs to
277-
let weekKey: 'week1' | 'week2' | 'week3' | 'week4' | null = null;
278-
if (completedDate >= weekStarts[3]) weekKey = 'week4';
279-
else if (completedDate >= weekStarts[2]) weekKey = 'week3';
280-
else if (completedDate >= weekStarts[1]) weekKey = 'week2';
281-
else if (completedDate >= weekStarts[0]) weekKey = 'week1';
282-
283-
if (weekKey) {
284-
cellData[weekKey].total++;
285-
// On-time if completed before or on planned_end, or if no planned_end (assume on-time)
286-
if (!op.planned_end || completedDate <= new Date(op.planned_end)) {
287-
cellData[weekKey].onTime++;
281+
// Determine which period this operation belongs to
282+
for (let p = 0; p < numPeriods; p++) {
283+
const periodStart = periodStarts[p];
284+
const periodEnd = p < numPeriods - 1 ? periodStarts[p + 1] : now;
285+
286+
if (completedDate >= periodStart && completedDate < periodEnd) {
287+
cellData[p].total++;
288+
// On-time if completed before or on planned_end, or if no planned_end (assume on-time)
289+
if (!op.planned_end || completedDate <= new Date(op.planned_end)) {
290+
cellData[p].onTime++;
291+
}
292+
break;
288293
}
289294
}
290295
});
291296

292-
// Generate week labels with actual date ranges (e.g., "Nov 4-10")
293-
const weekLabels = weekStarts.map((weekStart, i) => {
294-
const weekEnd = endOfWeek(weekStart);
295-
return `${format(weekStart, 'MMM d')}-${format(weekEnd, 'd')}`;
296-
});
297-
298-
// Convert to percentage-based heatmap
297+
// Convert to percentage-based heatmap with dynamic values array
299298
const reliabilityHeatmap = Array.from(reliabilityByCell.entries())
300-
.map(([cellName, weeks]) => ({
299+
.map(([cellName, periods]) => ({
301300
cellName,
302-
week1: weeks.week1.total > 0 ? Math.round((weeks.week1.onTime / weeks.week1.total) * 100) : 100,
303-
week2: weeks.week2.total > 0 ? Math.round((weeks.week2.onTime / weeks.week2.total) * 100) : 100,
304-
week3: weeks.week3.total > 0 ? Math.round((weeks.week3.onTime / weeks.week3.total) * 100) : 100,
305-
week4: weeks.week4.total > 0 ? Math.round((weeks.week4.onTime / weeks.week4.total) * 100) : 100
301+
values: periods.map(p => p.total > 0 ? Math.round((p.onTime / p.total) * 100) : 100)
306302
}))
307303
.sort((a, b) => a.cellName.localeCompare(b.cellName))
308304
.slice(0, 8); // Limit to 8 cells
@@ -343,7 +339,7 @@ export function useQRMDashboardMetrics(dateRange: number = 30) {
343339
},
344340
reliability: {
345341
heatmap: reliabilityHeatmap,
346-
weekLabels,
342+
periodLabels,
347343
},
348344
};
349345
},

0 commit comments

Comments
 (0)