@@ -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