@optifye/dashboard-core 6.12.1 → 6.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -13033,7 +13033,8 @@ var toWorkspaceDetailedMetrics = ({
13033
13033
  const targetOutput = coerceNumber(data.target_output ?? data.total_day_output, 0);
13034
13034
  const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
13035
13035
  const outputDifference = totalActions - idealOutput;
13036
- const hourlyTargetOutput = Array.isArray(data.hourly_target_output) ? data.hourly_target_output.map((value) => value === null || value === void 0 ? null : coerceNumber(value, 0)) : null;
13036
+ const hasHourlyTargetOutputField = Object.prototype.hasOwnProperty.call(data, "hourly_target_output");
13037
+ const hourlyTargetOutput = Array.isArray(data.hourly_target_output) ? data.hourly_target_output.map((value) => value === null || value === void 0 ? null : coerceNumber(value, 0)) : hasHourlyTargetOutputField ? null : void 0;
13037
13038
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
13038
13039
  const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
13039
13040
  const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
@@ -35214,6 +35215,261 @@ var Button = React144__namespace.forwardRef(
35214
35215
  }
35215
35216
  );
35216
35217
  Button.displayName = "Button";
35218
+
35219
+ // src/lib/utils/hourlyTargets.ts
35220
+ var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
35221
+ var MINUTES_PER_DAY = 24 * 60;
35222
+ var parseTimeToMinutes2 = (timeString) => {
35223
+ const normalized = stripSeconds2(timeString || "");
35224
+ if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
35225
+ const [hours, minutes] = normalized.split(":").map(Number);
35226
+ return hours * 60 + minutes;
35227
+ };
35228
+ var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
35229
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35230
+ if (!Number.isFinite(shiftStartMinutes)) return [];
35231
+ const normalizedBreaks = [];
35232
+ for (const entry of breaks) {
35233
+ const startRaw = parseTimeToMinutes2(entry.startTime);
35234
+ const endRaw = parseTimeToMinutes2(entry.endTime);
35235
+ if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
35236
+ let start = startRaw;
35237
+ let end = endRaw;
35238
+ if (end <= start) {
35239
+ end += 24 * 60;
35240
+ }
35241
+ if (start < shiftStartMinutes) {
35242
+ start += 24 * 60;
35243
+ end += 24 * 60;
35244
+ }
35245
+ const label = entry.remarks?.trim() || "Break";
35246
+ normalizedBreaks.push({ start, end, label });
35247
+ }
35248
+ return normalizedBreaks;
35249
+ };
35250
+ var roundTarget = (value, mode) => {
35251
+ if (!Number.isFinite(value)) return 0;
35252
+ switch (mode) {
35253
+ case "floor":
35254
+ return Math.floor(value);
35255
+ case "ceil":
35256
+ return Math.ceil(value);
35257
+ case "round":
35258
+ default:
35259
+ return Math.round(value);
35260
+ }
35261
+ };
35262
+ var formatDateKey = (date) => {
35263
+ const year = date.getUTCFullYear();
35264
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
35265
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
35266
+ return `${year}-${month}-${day}`;
35267
+ };
35268
+ var shiftDateKey = (dateKey, deltaDays) => {
35269
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
35270
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
35271
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
35272
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
35273
+ const date = new Date(Date.UTC(year, month - 1, day));
35274
+ date.setUTCDate(date.getUTCDate() + deltaDays);
35275
+ return formatDateKey(date);
35276
+ };
35277
+ var getZonedNowSnapshot = (timeZone, now4) => {
35278
+ const formatter = new Intl.DateTimeFormat("en-US", {
35279
+ timeZone,
35280
+ year: "numeric",
35281
+ month: "2-digit",
35282
+ day: "2-digit",
35283
+ hour: "2-digit",
35284
+ minute: "2-digit",
35285
+ hourCycle: "h23"
35286
+ });
35287
+ const parts = formatter.formatToParts(now4).reduce((acc, part) => {
35288
+ if (part.type !== "literal") {
35289
+ acc[part.type] = part.value;
35290
+ }
35291
+ return acc;
35292
+ }, {});
35293
+ const year = Number(parts.year);
35294
+ const month = Number(parts.month);
35295
+ const day = Number(parts.day);
35296
+ const hour = Number(parts.hour);
35297
+ const minute = Number(parts.minute);
35298
+ return {
35299
+ dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
35300
+ minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
35301
+ };
35302
+ };
35303
+ var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
35304
+ var buildHourlyIntervals = ({
35305
+ shiftStart,
35306
+ shiftEnd,
35307
+ bucketMinutes = 60,
35308
+ fallbackHours = 11
35309
+ }) => {
35310
+ const startMinutes = parseTimeToMinutes2(shiftStart);
35311
+ if (!Number.isFinite(startMinutes)) return [];
35312
+ const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
35313
+ let totalMinutes;
35314
+ const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35315
+ if (!Number.isFinite(endRaw)) {
35316
+ totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
35317
+ } else {
35318
+ let endMinutes = endRaw;
35319
+ if (endMinutes <= startMinutes) {
35320
+ endMinutes += 24 * 60;
35321
+ }
35322
+ totalMinutes = endMinutes - startMinutes;
35323
+ }
35324
+ if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
35325
+ const count = Math.ceil(totalMinutes / bucket);
35326
+ const shiftEndMinutes = startMinutes + totalMinutes;
35327
+ const intervals = [];
35328
+ for (let i = 0; i < count; i += 1) {
35329
+ const start = startMinutes + i * bucket;
35330
+ const end = Math.min(start + bucket, shiftEndMinutes);
35331
+ const minutes = Math.max(0, end - start);
35332
+ if (minutes <= 0) continue;
35333
+ intervals.push({ start, end, minutes });
35334
+ }
35335
+ return intervals;
35336
+ };
35337
+ var computeBreakMinutesByInterval = ({
35338
+ intervals,
35339
+ shiftStart,
35340
+ breaks
35341
+ }) => {
35342
+ if (!intervals.length || !breaks.length) return intervals.map(() => 0);
35343
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35344
+ return intervals.map((interval) => {
35345
+ if (!normalizedBreaks.length) return 0;
35346
+ let total = 0;
35347
+ for (const brk of normalizedBreaks) {
35348
+ const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
35349
+ total += overlap;
35350
+ if (total >= interval.minutes) return interval.minutes;
35351
+ }
35352
+ return Math.min(interval.minutes, total);
35353
+ });
35354
+ };
35355
+ var computeBreakRemarksByInterval = ({
35356
+ intervals,
35357
+ shiftStart,
35358
+ breaks
35359
+ }) => {
35360
+ if (!intervals.length || !breaks.length) return intervals.map(() => "");
35361
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35362
+ return intervals.map((interval) => {
35363
+ const labels = normalizedBreaks.filter((brk) => Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start)) > 0).map((brk) => brk.label).filter((label, index, values) => label && values.indexOf(label) === index);
35364
+ return labels.join(", ");
35365
+ });
35366
+ };
35367
+ var computeEffectiveTargets = ({
35368
+ intervals,
35369
+ breakMinutes,
35370
+ pphThreshold,
35371
+ rounding = "round"
35372
+ }) => {
35373
+ return intervals.map((interval, idx) => {
35374
+ const intervalMinutes = Number(interval?.minutes) || 0;
35375
+ const breakMins = Number(breakMinutes?.[idx]) || 0;
35376
+ const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
35377
+ if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
35378
+ if (plannedWorkMinutes <= 0) return 0;
35379
+ return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
35380
+ });
35381
+ };
35382
+ var buildHourlyTargetPlan = ({
35383
+ shiftStart,
35384
+ shiftEnd,
35385
+ breaks = [],
35386
+ pphThreshold,
35387
+ bucketMinutes = 60,
35388
+ fallbackHours = 11,
35389
+ rounding = "round"
35390
+ }) => {
35391
+ const intervals = buildHourlyIntervals({
35392
+ shiftStart,
35393
+ shiftEnd,
35394
+ bucketMinutes,
35395
+ fallbackHours
35396
+ });
35397
+ const breakMinutes = computeBreakMinutesByInterval({
35398
+ intervals,
35399
+ shiftStart,
35400
+ breaks
35401
+ });
35402
+ const breakRemarks = computeBreakRemarksByInterval({
35403
+ intervals,
35404
+ shiftStart,
35405
+ breaks
35406
+ });
35407
+ const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
35408
+ const targets = computeEffectiveTargets({
35409
+ intervals,
35410
+ breakMinutes,
35411
+ pphThreshold,
35412
+ rounding
35413
+ });
35414
+ return {
35415
+ intervals,
35416
+ breakMinutes,
35417
+ breakRemarks,
35418
+ productiveMinutes,
35419
+ targets
35420
+ };
35421
+ };
35422
+ var isHourlyIntervalComplete = ({
35423
+ reportDate,
35424
+ shiftStart,
35425
+ shiftEnd,
35426
+ interval,
35427
+ timeZone = "Asia/Kolkata",
35428
+ now: now4 = /* @__PURE__ */ new Date()
35429
+ }) => {
35430
+ if (!reportDate) return true;
35431
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35432
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35433
+ const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35434
+ const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
35435
+ if (reportDate === snapshot.dateKey) {
35436
+ return interval.end <= snapshot.minutesOfDay;
35437
+ }
35438
+ if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35439
+ return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
35440
+ }
35441
+ return reportDate < snapshot.dateKey;
35442
+ };
35443
+ var isShiftInProgressForReportDate = ({
35444
+ reportDate,
35445
+ shiftStart,
35446
+ shiftEnd,
35447
+ timeZone = "Asia/Kolkata",
35448
+ now: now4 = /* @__PURE__ */ new Date()
35449
+ }) => {
35450
+ if (!reportDate || !shiftStart || !shiftEnd) return false;
35451
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35452
+ const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
35453
+ if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
35454
+ return false;
35455
+ }
35456
+ let shiftEndMinutes = shiftEndMinutesRaw;
35457
+ const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
35458
+ if (wrapsMidnight) {
35459
+ shiftEndMinutes += MINUTES_PER_DAY;
35460
+ }
35461
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35462
+ let currentMinutes = null;
35463
+ if (reportDate === snapshot.dateKey) {
35464
+ currentMinutes = snapshot.minutesOfDay;
35465
+ } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35466
+ currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
35467
+ }
35468
+ if (currentMinutes === null) {
35469
+ return false;
35470
+ }
35471
+ return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
35472
+ };
35217
35473
  var padTime = (value) => value.toString().padStart(2, "0");
35218
35474
  var parseTime = (timeValue) => {
35219
35475
  if (!timeValue) return null;
@@ -35595,6 +35851,7 @@ var HourlyOutputChartComponent = ({
35595
35851
  hourlyTargetOutput,
35596
35852
  shiftStart,
35597
35853
  shiftEnd,
35854
+ shiftBreaks = [],
35598
35855
  showIdleTime = false,
35599
35856
  idleTimeHourly,
35600
35857
  shiftDate,
@@ -35832,13 +36089,41 @@ var HourlyOutputChartComponent = ({
35832
36089
  end: index === skuTimelineSegments.length - 1 ? Math.max(segment.start, targetLineEndOffset) : segment.end
35833
36090
  })).filter((segment) => segment.end > segment.start);
35834
36091
  }, [skuTimelineSegments, targetLineEndOffset]);
35835
- const hasHourlyTargetOutputProp = React144__namespace.default.useMemo(
36092
+ const hasExplicitHourlyTargetOutputProp = React144__namespace.default.useMemo(
35836
36093
  () => hourlyTargetOutput !== void 0,
35837
36094
  [hourlyTargetOutput]
35838
36095
  );
36096
+ const fallbackHourlyTargetOutput = React144__namespace.default.useMemo(() => {
36097
+ if (hasExplicitHourlyTargetOutputProp) return void 0;
36098
+ if (skuTimelineSegments.length > 0) return void 0;
36099
+ const plan = buildHourlyTargetPlan({
36100
+ shiftStart,
36101
+ shiftEnd,
36102
+ breaks: shiftBreaks,
36103
+ pphThreshold,
36104
+ rounding: "floor"
36105
+ });
36106
+ if (!plan.targets.length) return void 0;
36107
+ return plan.targets.map((value) => Number.isFinite(value) ? value : null);
36108
+ }, [
36109
+ hasExplicitHourlyTargetOutputProp,
36110
+ skuTimelineSegments.length,
36111
+ shiftStart,
36112
+ shiftEnd,
36113
+ shiftBreaks,
36114
+ pphThreshold
36115
+ ]);
36116
+ const effectiveHourlyTargetOutput = React144__namespace.default.useMemo(
36117
+ () => hasExplicitHourlyTargetOutputProp ? hourlyTargetOutput : fallbackHourlyTargetOutput,
36118
+ [hasExplicitHourlyTargetOutputProp, hourlyTargetOutput, fallbackHourlyTargetOutput]
36119
+ );
36120
+ const hasHourlyTargetOutputProp = React144__namespace.default.useMemo(
36121
+ () => effectiveHourlyTargetOutput !== void 0,
36122
+ [effectiveHourlyTargetOutput]
36123
+ );
35839
36124
  const hasExplicitHourlyTargets = React144__namespace.default.useMemo(
35840
- () => Array.isArray(hourlyTargetOutput) && hourlyTargetOutput.some((value) => value !== null && value !== void 0),
35841
- [hourlyTargetOutput]
36125
+ () => Array.isArray(effectiveHourlyTargetOutput) && effectiveHourlyTargetOutput.some((value) => value !== null && value !== void 0),
36126
+ [effectiveHourlyTargetOutput]
35842
36127
  );
35843
36128
  const hourlyTargetSegments = React144__namespace.default.useMemo(() => {
35844
36129
  if (!hasExplicitHourlyTargets) return [];
@@ -35852,7 +36137,7 @@ var HourlyOutputChartComponent = ({
35852
36137
  runValue = null;
35853
36138
  };
35854
36139
  for (let i = 0; i < SHIFT_DURATION; i += 1) {
35855
- const rawValue = Array.isArray(hourlyTargetOutput) ? hourlyTargetOutput[i] : null;
36140
+ const rawValue = Array.isArray(effectiveHourlyTargetOutput) ? effectiveHourlyTargetOutput[i] : null;
35856
36141
  const value = rawValue === null || rawValue === void 0 ? null : Number(rawValue);
35857
36142
  if (value === null || !Number.isFinite(value)) {
35858
36143
  flush(i);
@@ -35871,7 +36156,7 @@ var HourlyOutputChartComponent = ({
35871
36156
  }
35872
36157
  flush(SHIFT_DURATION);
35873
36158
  return segments.filter((segment) => segment.end > segment.start);
35874
- }, [SHIFT_DURATION, hasExplicitHourlyTargets, hourlyTargetOutput]);
36159
+ }, [SHIFT_DURATION, hasExplicitHourlyTargets, effectiveHourlyTargetOutput]);
35875
36160
  const activeSkuHourIndices = React144__namespace.default.useMemo(() => {
35876
36161
  const indices = /* @__PURE__ */ new Set();
35877
36162
  const targets = Array(SHIFT_DURATION).fill(pphThreshold);
@@ -35909,7 +36194,7 @@ var HourlyOutputChartComponent = ({
35909
36194
  const { indices, targets, hasTimeline } = activeSkuHourIndices;
35910
36195
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
35911
36196
  const idleSlot = idleSlots[i];
35912
- const explicitTarget = hasHourlyTargetOutputProp ? hourlyTargetOutput?.[i] ?? null : void 0;
36197
+ const explicitTarget = hasHourlyTargetOutputProp ? effectiveHourlyTargetOutput?.[i] ?? null : void 0;
35913
36198
  const currentTarget = hasHourlyTargetOutputProp ? explicitTarget : targets[i] || pphThreshold;
35914
36199
  const comparisonTarget = currentTarget === null || currentTarget === void 0 ? targets[i] || pphThreshold : currentTarget;
35915
36200
  return {
@@ -35928,7 +36213,7 @@ var HourlyOutputChartComponent = ({
35928
36213
  isDimmed: hasTimeline && !!activeSkuId && !indices.has(i)
35929
36214
  };
35930
36215
  });
35931
- }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, hourlyTargetOutput, hasHourlyTargetOutputProp]);
36216
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, effectiveHourlyTargetOutput, hasHourlyTargetOutputProp]);
35932
36217
  const renderSkuTimelineRail = React144__namespace.default.useCallback((props) => {
35933
36218
  if (!skuTimelineSegments.length || SHIFT_DURATION <= 0) return null;
35934
36219
  const offset = props?.offset;
@@ -36587,6 +36872,16 @@ var HourlyOutputChart = React144__namespace.default.memo(
36587
36872
  if (!prevProps.data.every((val, idx) => val === nextProps.data[idx])) {
36588
36873
  return false;
36589
36874
  }
36875
+ const prevHasHourlyTargetOutputProp = prevProps.hourlyTargetOutput !== void 0;
36876
+ const nextHasHourlyTargetOutputProp = nextProps.hourlyTargetOutput !== void 0;
36877
+ if (prevHasHourlyTargetOutputProp !== nextHasHourlyTargetOutputProp) {
36878
+ return false;
36879
+ }
36880
+ if (prevProps.hourlyTargetOutput === null || nextProps.hourlyTargetOutput === null) {
36881
+ if (prevProps.hourlyTargetOutput !== nextProps.hourlyTargetOutput) {
36882
+ return false;
36883
+ }
36884
+ }
36590
36885
  const prevHourlyTargets = prevProps.hourlyTargetOutput || [];
36591
36886
  const nextHourlyTargets = nextProps.hourlyTargetOutput || [];
36592
36887
  if (prevHourlyTargets.length !== nextHourlyTargets.length) {
@@ -36597,6 +36892,18 @@ var HourlyOutputChart = React144__namespace.default.memo(
36597
36892
  return false;
36598
36893
  }
36599
36894
  }
36895
+ const prevShiftBreaks = prevProps.shiftBreaks || [];
36896
+ const nextShiftBreaks = nextProps.shiftBreaks || [];
36897
+ if (prevShiftBreaks.length !== nextShiftBreaks.length) {
36898
+ return false;
36899
+ }
36900
+ for (let i = 0; i < prevShiftBreaks.length; i += 1) {
36901
+ const prevBreak = prevShiftBreaks[i] || {};
36902
+ const nextBreak = nextShiftBreaks[i] || {};
36903
+ if (prevBreak.startTime !== nextBreak.startTime || prevBreak.endTime !== nextBreak.endTime || prevBreak.duration !== nextBreak.duration || prevBreak.remarks !== nextBreak.remarks) {
36904
+ return false;
36905
+ }
36906
+ }
36600
36907
  const prevIdle = prevProps.idleTimeHourly || {};
36601
36908
  const nextIdle = nextProps.idleTimeHourly || {};
36602
36909
  const prevKeys = Object.keys(prevIdle);
@@ -50311,261 +50618,6 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
50311
50618
  }
50312
50619
  );
50313
50620
  };
50314
-
50315
- // src/lib/utils/hourlyTargets.ts
50316
- var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
50317
- var MINUTES_PER_DAY = 24 * 60;
50318
- var parseTimeToMinutes2 = (timeString) => {
50319
- const normalized = stripSeconds2(timeString || "");
50320
- if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
50321
- const [hours, minutes] = normalized.split(":").map(Number);
50322
- return hours * 60 + minutes;
50323
- };
50324
- var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
50325
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50326
- if (!Number.isFinite(shiftStartMinutes)) return [];
50327
- const normalizedBreaks = [];
50328
- for (const entry of breaks) {
50329
- const startRaw = parseTimeToMinutes2(entry.startTime);
50330
- const endRaw = parseTimeToMinutes2(entry.endTime);
50331
- if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
50332
- let start = startRaw;
50333
- let end = endRaw;
50334
- if (end <= start) {
50335
- end += 24 * 60;
50336
- }
50337
- if (start < shiftStartMinutes) {
50338
- start += 24 * 60;
50339
- end += 24 * 60;
50340
- }
50341
- const label = entry.remarks?.trim() || "Break";
50342
- normalizedBreaks.push({ start, end, label });
50343
- }
50344
- return normalizedBreaks;
50345
- };
50346
- var roundTarget = (value, mode) => {
50347
- if (!Number.isFinite(value)) return 0;
50348
- switch (mode) {
50349
- case "floor":
50350
- return Math.floor(value);
50351
- case "ceil":
50352
- return Math.ceil(value);
50353
- case "round":
50354
- default:
50355
- return Math.round(value);
50356
- }
50357
- };
50358
- var formatDateKey = (date) => {
50359
- const year = date.getUTCFullYear();
50360
- const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
50361
- const day = `${date.getUTCDate()}`.padStart(2, "0");
50362
- return `${year}-${month}-${day}`;
50363
- };
50364
- var shiftDateKey = (dateKey, deltaDays) => {
50365
- const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50366
- const year = Number.isFinite(yearPart) ? yearPart : 1970;
50367
- const month = Number.isFinite(monthPart) ? monthPart : 1;
50368
- const day = Number.isFinite(dayPart) ? dayPart : 1;
50369
- const date = new Date(Date.UTC(year, month - 1, day));
50370
- date.setUTCDate(date.getUTCDate() + deltaDays);
50371
- return formatDateKey(date);
50372
- };
50373
- var getZonedNowSnapshot = (timeZone, now4) => {
50374
- const formatter = new Intl.DateTimeFormat("en-US", {
50375
- timeZone,
50376
- year: "numeric",
50377
- month: "2-digit",
50378
- day: "2-digit",
50379
- hour: "2-digit",
50380
- minute: "2-digit",
50381
- hourCycle: "h23"
50382
- });
50383
- const parts = formatter.formatToParts(now4).reduce((acc, part) => {
50384
- if (part.type !== "literal") {
50385
- acc[part.type] = part.value;
50386
- }
50387
- return acc;
50388
- }, {});
50389
- const year = Number(parts.year);
50390
- const month = Number(parts.month);
50391
- const day = Number(parts.day);
50392
- const hour = Number(parts.hour);
50393
- const minute = Number(parts.minute);
50394
- return {
50395
- dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
50396
- minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
50397
- };
50398
- };
50399
- var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
50400
- var buildHourlyIntervals = ({
50401
- shiftStart,
50402
- shiftEnd,
50403
- bucketMinutes = 60,
50404
- fallbackHours = 11
50405
- }) => {
50406
- const startMinutes = parseTimeToMinutes2(shiftStart);
50407
- if (!Number.isFinite(startMinutes)) return [];
50408
- const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
50409
- let totalMinutes;
50410
- const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50411
- if (!Number.isFinite(endRaw)) {
50412
- totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
50413
- } else {
50414
- let endMinutes = endRaw;
50415
- if (endMinutes <= startMinutes) {
50416
- endMinutes += 24 * 60;
50417
- }
50418
- totalMinutes = endMinutes - startMinutes;
50419
- }
50420
- if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
50421
- const count = Math.ceil(totalMinutes / bucket);
50422
- const shiftEndMinutes = startMinutes + totalMinutes;
50423
- const intervals = [];
50424
- for (let i = 0; i < count; i += 1) {
50425
- const start = startMinutes + i * bucket;
50426
- const end = Math.min(start + bucket, shiftEndMinutes);
50427
- const minutes = Math.max(0, end - start);
50428
- if (minutes <= 0) continue;
50429
- intervals.push({ start, end, minutes });
50430
- }
50431
- return intervals;
50432
- };
50433
- var computeBreakMinutesByInterval = ({
50434
- intervals,
50435
- shiftStart,
50436
- breaks
50437
- }) => {
50438
- if (!intervals.length || !breaks.length) return intervals.map(() => 0);
50439
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50440
- return intervals.map((interval) => {
50441
- if (!normalizedBreaks.length) return 0;
50442
- let total = 0;
50443
- for (const brk of normalizedBreaks) {
50444
- const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
50445
- total += overlap;
50446
- if (total >= interval.minutes) return interval.minutes;
50447
- }
50448
- return Math.min(interval.minutes, total);
50449
- });
50450
- };
50451
- var computeBreakRemarksByInterval = ({
50452
- intervals,
50453
- shiftStart,
50454
- breaks
50455
- }) => {
50456
- if (!intervals.length || !breaks.length) return intervals.map(() => "");
50457
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50458
- return intervals.map((interval) => {
50459
- const labels = normalizedBreaks.filter((brk) => Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start)) > 0).map((brk) => brk.label).filter((label, index, values) => label && values.indexOf(label) === index);
50460
- return labels.join(", ");
50461
- });
50462
- };
50463
- var computeEffectiveTargets = ({
50464
- intervals,
50465
- breakMinutes,
50466
- pphThreshold,
50467
- rounding = "round"
50468
- }) => {
50469
- return intervals.map((interval, idx) => {
50470
- const intervalMinutes = Number(interval?.minutes) || 0;
50471
- const breakMins = Number(breakMinutes?.[idx]) || 0;
50472
- const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
50473
- if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
50474
- if (plannedWorkMinutes <= 0) return 0;
50475
- return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
50476
- });
50477
- };
50478
- var buildHourlyTargetPlan = ({
50479
- shiftStart,
50480
- shiftEnd,
50481
- breaks = [],
50482
- pphThreshold,
50483
- bucketMinutes = 60,
50484
- fallbackHours = 11,
50485
- rounding = "round"
50486
- }) => {
50487
- const intervals = buildHourlyIntervals({
50488
- shiftStart,
50489
- shiftEnd,
50490
- bucketMinutes,
50491
- fallbackHours
50492
- });
50493
- const breakMinutes = computeBreakMinutesByInterval({
50494
- intervals,
50495
- shiftStart,
50496
- breaks
50497
- });
50498
- const breakRemarks = computeBreakRemarksByInterval({
50499
- intervals,
50500
- shiftStart,
50501
- breaks
50502
- });
50503
- const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
50504
- const targets = computeEffectiveTargets({
50505
- intervals,
50506
- breakMinutes,
50507
- pphThreshold,
50508
- rounding
50509
- });
50510
- return {
50511
- intervals,
50512
- breakMinutes,
50513
- breakRemarks,
50514
- productiveMinutes,
50515
- targets
50516
- };
50517
- };
50518
- var isHourlyIntervalComplete = ({
50519
- reportDate,
50520
- shiftStart,
50521
- shiftEnd,
50522
- interval,
50523
- timeZone = "Asia/Kolkata",
50524
- now: now4 = /* @__PURE__ */ new Date()
50525
- }) => {
50526
- if (!reportDate) return true;
50527
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50528
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50529
- const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50530
- const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
50531
- if (reportDate === snapshot.dateKey) {
50532
- return interval.end <= snapshot.minutesOfDay;
50533
- }
50534
- if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50535
- return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
50536
- }
50537
- return reportDate < snapshot.dateKey;
50538
- };
50539
- var isShiftInProgressForReportDate = ({
50540
- reportDate,
50541
- shiftStart,
50542
- shiftEnd,
50543
- timeZone = "Asia/Kolkata",
50544
- now: now4 = /* @__PURE__ */ new Date()
50545
- }) => {
50546
- if (!reportDate || !shiftStart || !shiftEnd) return false;
50547
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50548
- const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
50549
- if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
50550
- return false;
50551
- }
50552
- let shiftEndMinutes = shiftEndMinutesRaw;
50553
- const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
50554
- if (wrapsMidnight) {
50555
- shiftEndMinutes += MINUTES_PER_DAY;
50556
- }
50557
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50558
- let currentMinutes = null;
50559
- if (reportDate === snapshot.dateKey) {
50560
- currentMinutes = snapshot.minutesOfDay;
50561
- } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50562
- currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
50563
- }
50564
- if (currentMinutes === null) {
50565
- return false;
50566
- }
50567
- return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
50568
- };
50569
50621
  var formatOperationalDateKey = (dateKey, options) => {
50570
50622
  const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50571
50623
  const year = Number.isFinite(yearPart) ? yearPart : 1970;
@@ -64708,7 +64760,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
64708
64760
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
64709
64761
  output_array: metrics2.output_array || [],
64710
64762
  output_hourly: metrics2.output_hourly,
64711
- hourly_target_output: metrics2.hourly_target_output ?? null,
64763
+ hourly_target_output: metrics2.hourly_target_output,
64712
64764
  line_threshold: metrics2.line_threshold ?? 0,
64713
64765
  threshold_pph: metrics2.threshold_pph ?? 0,
64714
64766
  shift_start: metrics2.shift_start || "06:00",
@@ -64795,7 +64847,7 @@ var transformLineMetrics = (lineId, detailResponse, queryDate, queryShiftId) =>
64795
64847
  underperforming_workspace_names: [],
64796
64848
  underperforming_workspace_uuids: [],
64797
64849
  output_array: [],
64798
- hourly_target_output: null,
64850
+ hourly_target_output: void 0,
64799
64851
  line_threshold: 0,
64800
64852
  threshold_pph: 0,
64801
64853
  shift_start: "06:00",
@@ -65834,6 +65886,7 @@ var BottomSection = React144.memo(({
65834
65886
  hourlyOutputData,
65835
65887
  hourlyThreshold,
65836
65888
  hourlyTargetOutput,
65889
+ shiftBreaks,
65837
65890
  idleTimeHourly,
65838
65891
  timezone,
65839
65892
  urlDate,
@@ -66009,6 +66062,7 @@ var BottomSection = React144.memo(({
66009
66062
  hourlyTargetOutput,
66010
66063
  shiftStart: lineInfo.metrics.shift_start || "06:00",
66011
66064
  shiftEnd: lineInfo.metrics.shift_end,
66065
+ shiftBreaks,
66012
66066
  idleTimeHourly,
66013
66067
  shiftDate: lineInfo.date,
66014
66068
  timezone,
@@ -66032,6 +66086,9 @@ var BottomSection = React144.memo(({
66032
66086
  if (prevProps.lineInfo.monitoring_mode !== nextProps.lineInfo.monitoring_mode) return false;
66033
66087
  if (prevProps.skuAware !== nextProps.skuAware) return false;
66034
66088
  if (prevProps.activeSkuId !== nextProps.activeSkuId) return false;
66089
+ if (JSON.stringify(prevProps.shiftBreaks || []) !== JSON.stringify(nextProps.shiftBreaks || [])) {
66090
+ return false;
66091
+ }
66035
66092
  if (JSON.stringify(prevProps.hourlyTargetOutput || []) !== JSON.stringify(nextProps.hourlyTargetOutput || [])) {
66036
66093
  return false;
66037
66094
  }
@@ -66633,7 +66690,7 @@ var KPIDetailView = ({
66633
66690
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
66634
66691
  output_array: metrics2.output_array || [],
66635
66692
  output_hourly: metrics2.output_hourly,
66636
- hourly_target_output: metrics2.hourly_target_output ?? null,
66693
+ hourly_target_output: metrics2.hourly_target_output,
66637
66694
  line_threshold: metrics2.line_threshold ?? 0,
66638
66695
  threshold_pph: metrics2.threshold_pph ?? 0,
66639
66696
  shift_start: metrics2.shift_start || "06:00",
@@ -67597,7 +67654,8 @@ var KPIDetailView = ({
67597
67654
  workspaceDisplayNames,
67598
67655
  hourlyOutputData,
67599
67656
  hourlyThreshold,
67600
- hourlyTargetOutput: chartMetrics?.hourly_target_output ?? null,
67657
+ hourlyTargetOutput: chartMetrics?.hourly_target_output,
67658
+ shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
67601
67659
  idleTimeHourly: chartMetrics?.idle_time_hourly,
67602
67660
  timezone: lineTimezone,
67603
67661
  urlDate,
@@ -75361,6 +75419,7 @@ var WorkspaceDetailView = ({
75361
75419
  hourlyTargetOutput: workspace.hourly_target_output,
75362
75420
  shiftStart: workspace.shift_start || "06:00",
75363
75421
  shiftEnd: workspace.shift_end,
75422
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75364
75423
  showIdleTime: showChartIdleTime,
75365
75424
  idleTimeHourly: workspace.idle_time_hourly,
75366
75425
  idleTimeClips,
@@ -75508,6 +75567,7 @@ var WorkspaceDetailView = ({
75508
75567
  hourlyTargetOutput: workspace.hourly_target_output,
75509
75568
  shiftStart: workspace.shift_start || "06:00",
75510
75569
  shiftEnd: workspace.shift_end,
75570
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75511
75571
  showIdleTime: showChartIdleTime,
75512
75572
  idleTimeHourly: workspace.idle_time_hourly,
75513
75573
  idleTimeClips,