@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.mjs CHANGED
@@ -13004,7 +13004,8 @@ var toWorkspaceDetailedMetrics = ({
13004
13004
  const targetOutput = coerceNumber(data.target_output ?? data.total_day_output, 0);
13005
13005
  const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
13006
13006
  const outputDifference = totalActions - idealOutput;
13007
- const hourlyTargetOutput = Array.isArray(data.hourly_target_output) ? data.hourly_target_output.map((value) => value === null || value === void 0 ? null : coerceNumber(value, 0)) : null;
13007
+ const hasHourlyTargetOutputField = Object.prototype.hasOwnProperty.call(data, "hourly_target_output");
13008
+ 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;
13008
13009
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
13009
13010
  const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
13010
13011
  const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
@@ -35185,6 +35186,261 @@ var Button = React144.forwardRef(
35185
35186
  }
35186
35187
  );
35187
35188
  Button.displayName = "Button";
35189
+
35190
+ // src/lib/utils/hourlyTargets.ts
35191
+ var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
35192
+ var MINUTES_PER_DAY = 24 * 60;
35193
+ var parseTimeToMinutes2 = (timeString) => {
35194
+ const normalized = stripSeconds2(timeString || "");
35195
+ if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
35196
+ const [hours, minutes] = normalized.split(":").map(Number);
35197
+ return hours * 60 + minutes;
35198
+ };
35199
+ var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
35200
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35201
+ if (!Number.isFinite(shiftStartMinutes)) return [];
35202
+ const normalizedBreaks = [];
35203
+ for (const entry of breaks) {
35204
+ const startRaw = parseTimeToMinutes2(entry.startTime);
35205
+ const endRaw = parseTimeToMinutes2(entry.endTime);
35206
+ if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
35207
+ let start = startRaw;
35208
+ let end = endRaw;
35209
+ if (end <= start) {
35210
+ end += 24 * 60;
35211
+ }
35212
+ if (start < shiftStartMinutes) {
35213
+ start += 24 * 60;
35214
+ end += 24 * 60;
35215
+ }
35216
+ const label = entry.remarks?.trim() || "Break";
35217
+ normalizedBreaks.push({ start, end, label });
35218
+ }
35219
+ return normalizedBreaks;
35220
+ };
35221
+ var roundTarget = (value, mode) => {
35222
+ if (!Number.isFinite(value)) return 0;
35223
+ switch (mode) {
35224
+ case "floor":
35225
+ return Math.floor(value);
35226
+ case "ceil":
35227
+ return Math.ceil(value);
35228
+ case "round":
35229
+ default:
35230
+ return Math.round(value);
35231
+ }
35232
+ };
35233
+ var formatDateKey = (date) => {
35234
+ const year = date.getUTCFullYear();
35235
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
35236
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
35237
+ return `${year}-${month}-${day}`;
35238
+ };
35239
+ var shiftDateKey = (dateKey, deltaDays) => {
35240
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
35241
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
35242
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
35243
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
35244
+ const date = new Date(Date.UTC(year, month - 1, day));
35245
+ date.setUTCDate(date.getUTCDate() + deltaDays);
35246
+ return formatDateKey(date);
35247
+ };
35248
+ var getZonedNowSnapshot = (timeZone, now4) => {
35249
+ const formatter = new Intl.DateTimeFormat("en-US", {
35250
+ timeZone,
35251
+ year: "numeric",
35252
+ month: "2-digit",
35253
+ day: "2-digit",
35254
+ hour: "2-digit",
35255
+ minute: "2-digit",
35256
+ hourCycle: "h23"
35257
+ });
35258
+ const parts = formatter.formatToParts(now4).reduce((acc, part) => {
35259
+ if (part.type !== "literal") {
35260
+ acc[part.type] = part.value;
35261
+ }
35262
+ return acc;
35263
+ }, {});
35264
+ const year = Number(parts.year);
35265
+ const month = Number(parts.month);
35266
+ const day = Number(parts.day);
35267
+ const hour = Number(parts.hour);
35268
+ const minute = Number(parts.minute);
35269
+ return {
35270
+ dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
35271
+ minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
35272
+ };
35273
+ };
35274
+ var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
35275
+ var buildHourlyIntervals = ({
35276
+ shiftStart,
35277
+ shiftEnd,
35278
+ bucketMinutes = 60,
35279
+ fallbackHours = 11
35280
+ }) => {
35281
+ const startMinutes = parseTimeToMinutes2(shiftStart);
35282
+ if (!Number.isFinite(startMinutes)) return [];
35283
+ const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
35284
+ let totalMinutes;
35285
+ const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35286
+ if (!Number.isFinite(endRaw)) {
35287
+ totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
35288
+ } else {
35289
+ let endMinutes = endRaw;
35290
+ if (endMinutes <= startMinutes) {
35291
+ endMinutes += 24 * 60;
35292
+ }
35293
+ totalMinutes = endMinutes - startMinutes;
35294
+ }
35295
+ if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
35296
+ const count = Math.ceil(totalMinutes / bucket);
35297
+ const shiftEndMinutes = startMinutes + totalMinutes;
35298
+ const intervals = [];
35299
+ for (let i = 0; i < count; i += 1) {
35300
+ const start = startMinutes + i * bucket;
35301
+ const end = Math.min(start + bucket, shiftEndMinutes);
35302
+ const minutes = Math.max(0, end - start);
35303
+ if (minutes <= 0) continue;
35304
+ intervals.push({ start, end, minutes });
35305
+ }
35306
+ return intervals;
35307
+ };
35308
+ var computeBreakMinutesByInterval = ({
35309
+ intervals,
35310
+ shiftStart,
35311
+ breaks
35312
+ }) => {
35313
+ if (!intervals.length || !breaks.length) return intervals.map(() => 0);
35314
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35315
+ return intervals.map((interval) => {
35316
+ if (!normalizedBreaks.length) return 0;
35317
+ let total = 0;
35318
+ for (const brk of normalizedBreaks) {
35319
+ const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
35320
+ total += overlap;
35321
+ if (total >= interval.minutes) return interval.minutes;
35322
+ }
35323
+ return Math.min(interval.minutes, total);
35324
+ });
35325
+ };
35326
+ var computeBreakRemarksByInterval = ({
35327
+ intervals,
35328
+ shiftStart,
35329
+ breaks
35330
+ }) => {
35331
+ if (!intervals.length || !breaks.length) return intervals.map(() => "");
35332
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35333
+ return intervals.map((interval) => {
35334
+ 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);
35335
+ return labels.join(", ");
35336
+ });
35337
+ };
35338
+ var computeEffectiveTargets = ({
35339
+ intervals,
35340
+ breakMinutes,
35341
+ pphThreshold,
35342
+ rounding = "round"
35343
+ }) => {
35344
+ return intervals.map((interval, idx) => {
35345
+ const intervalMinutes = Number(interval?.minutes) || 0;
35346
+ const breakMins = Number(breakMinutes?.[idx]) || 0;
35347
+ const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
35348
+ if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
35349
+ if (plannedWorkMinutes <= 0) return 0;
35350
+ return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
35351
+ });
35352
+ };
35353
+ var buildHourlyTargetPlan = ({
35354
+ shiftStart,
35355
+ shiftEnd,
35356
+ breaks = [],
35357
+ pphThreshold,
35358
+ bucketMinutes = 60,
35359
+ fallbackHours = 11,
35360
+ rounding = "round"
35361
+ }) => {
35362
+ const intervals = buildHourlyIntervals({
35363
+ shiftStart,
35364
+ shiftEnd,
35365
+ bucketMinutes,
35366
+ fallbackHours
35367
+ });
35368
+ const breakMinutes = computeBreakMinutesByInterval({
35369
+ intervals,
35370
+ shiftStart,
35371
+ breaks
35372
+ });
35373
+ const breakRemarks = computeBreakRemarksByInterval({
35374
+ intervals,
35375
+ shiftStart,
35376
+ breaks
35377
+ });
35378
+ const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
35379
+ const targets = computeEffectiveTargets({
35380
+ intervals,
35381
+ breakMinutes,
35382
+ pphThreshold,
35383
+ rounding
35384
+ });
35385
+ return {
35386
+ intervals,
35387
+ breakMinutes,
35388
+ breakRemarks,
35389
+ productiveMinutes,
35390
+ targets
35391
+ };
35392
+ };
35393
+ var isHourlyIntervalComplete = ({
35394
+ reportDate,
35395
+ shiftStart,
35396
+ shiftEnd,
35397
+ interval,
35398
+ timeZone = "Asia/Kolkata",
35399
+ now: now4 = /* @__PURE__ */ new Date()
35400
+ }) => {
35401
+ if (!reportDate) return true;
35402
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35403
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35404
+ const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35405
+ const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
35406
+ if (reportDate === snapshot.dateKey) {
35407
+ return interval.end <= snapshot.minutesOfDay;
35408
+ }
35409
+ if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35410
+ return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
35411
+ }
35412
+ return reportDate < snapshot.dateKey;
35413
+ };
35414
+ var isShiftInProgressForReportDate = ({
35415
+ reportDate,
35416
+ shiftStart,
35417
+ shiftEnd,
35418
+ timeZone = "Asia/Kolkata",
35419
+ now: now4 = /* @__PURE__ */ new Date()
35420
+ }) => {
35421
+ if (!reportDate || !shiftStart || !shiftEnd) return false;
35422
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35423
+ const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
35424
+ if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
35425
+ return false;
35426
+ }
35427
+ let shiftEndMinutes = shiftEndMinutesRaw;
35428
+ const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
35429
+ if (wrapsMidnight) {
35430
+ shiftEndMinutes += MINUTES_PER_DAY;
35431
+ }
35432
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35433
+ let currentMinutes = null;
35434
+ if (reportDate === snapshot.dateKey) {
35435
+ currentMinutes = snapshot.minutesOfDay;
35436
+ } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35437
+ currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
35438
+ }
35439
+ if (currentMinutes === null) {
35440
+ return false;
35441
+ }
35442
+ return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
35443
+ };
35188
35444
  var padTime = (value) => value.toString().padStart(2, "0");
35189
35445
  var parseTime = (timeValue) => {
35190
35446
  if (!timeValue) return null;
@@ -35566,6 +35822,7 @@ var HourlyOutputChartComponent = ({
35566
35822
  hourlyTargetOutput,
35567
35823
  shiftStart,
35568
35824
  shiftEnd,
35825
+ shiftBreaks = [],
35569
35826
  showIdleTime = false,
35570
35827
  idleTimeHourly,
35571
35828
  shiftDate,
@@ -35803,13 +36060,41 @@ var HourlyOutputChartComponent = ({
35803
36060
  end: index === skuTimelineSegments.length - 1 ? Math.max(segment.start, targetLineEndOffset) : segment.end
35804
36061
  })).filter((segment) => segment.end > segment.start);
35805
36062
  }, [skuTimelineSegments, targetLineEndOffset]);
35806
- const hasHourlyTargetOutputProp = React144__default.useMemo(
36063
+ const hasExplicitHourlyTargetOutputProp = React144__default.useMemo(
35807
36064
  () => hourlyTargetOutput !== void 0,
35808
36065
  [hourlyTargetOutput]
35809
36066
  );
36067
+ const fallbackHourlyTargetOutput = React144__default.useMemo(() => {
36068
+ if (hasExplicitHourlyTargetOutputProp) return void 0;
36069
+ if (skuTimelineSegments.length > 0) return void 0;
36070
+ const plan = buildHourlyTargetPlan({
36071
+ shiftStart,
36072
+ shiftEnd,
36073
+ breaks: shiftBreaks,
36074
+ pphThreshold,
36075
+ rounding: "floor"
36076
+ });
36077
+ if (!plan.targets.length) return void 0;
36078
+ return plan.targets.map((value) => Number.isFinite(value) ? value : null);
36079
+ }, [
36080
+ hasExplicitHourlyTargetOutputProp,
36081
+ skuTimelineSegments.length,
36082
+ shiftStart,
36083
+ shiftEnd,
36084
+ shiftBreaks,
36085
+ pphThreshold
36086
+ ]);
36087
+ const effectiveHourlyTargetOutput = React144__default.useMemo(
36088
+ () => hasExplicitHourlyTargetOutputProp ? hourlyTargetOutput : fallbackHourlyTargetOutput,
36089
+ [hasExplicitHourlyTargetOutputProp, hourlyTargetOutput, fallbackHourlyTargetOutput]
36090
+ );
36091
+ const hasHourlyTargetOutputProp = React144__default.useMemo(
36092
+ () => effectiveHourlyTargetOutput !== void 0,
36093
+ [effectiveHourlyTargetOutput]
36094
+ );
35810
36095
  const hasExplicitHourlyTargets = React144__default.useMemo(
35811
- () => Array.isArray(hourlyTargetOutput) && hourlyTargetOutput.some((value) => value !== null && value !== void 0),
35812
- [hourlyTargetOutput]
36096
+ () => Array.isArray(effectiveHourlyTargetOutput) && effectiveHourlyTargetOutput.some((value) => value !== null && value !== void 0),
36097
+ [effectiveHourlyTargetOutput]
35813
36098
  );
35814
36099
  const hourlyTargetSegments = React144__default.useMemo(() => {
35815
36100
  if (!hasExplicitHourlyTargets) return [];
@@ -35823,7 +36108,7 @@ var HourlyOutputChartComponent = ({
35823
36108
  runValue = null;
35824
36109
  };
35825
36110
  for (let i = 0; i < SHIFT_DURATION; i += 1) {
35826
- const rawValue = Array.isArray(hourlyTargetOutput) ? hourlyTargetOutput[i] : null;
36111
+ const rawValue = Array.isArray(effectiveHourlyTargetOutput) ? effectiveHourlyTargetOutput[i] : null;
35827
36112
  const value = rawValue === null || rawValue === void 0 ? null : Number(rawValue);
35828
36113
  if (value === null || !Number.isFinite(value)) {
35829
36114
  flush(i);
@@ -35842,7 +36127,7 @@ var HourlyOutputChartComponent = ({
35842
36127
  }
35843
36128
  flush(SHIFT_DURATION);
35844
36129
  return segments.filter((segment) => segment.end > segment.start);
35845
- }, [SHIFT_DURATION, hasExplicitHourlyTargets, hourlyTargetOutput]);
36130
+ }, [SHIFT_DURATION, hasExplicitHourlyTargets, effectiveHourlyTargetOutput]);
35846
36131
  const activeSkuHourIndices = React144__default.useMemo(() => {
35847
36132
  const indices = /* @__PURE__ */ new Set();
35848
36133
  const targets = Array(SHIFT_DURATION).fill(pphThreshold);
@@ -35880,7 +36165,7 @@ var HourlyOutputChartComponent = ({
35880
36165
  const { indices, targets, hasTimeline } = activeSkuHourIndices;
35881
36166
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
35882
36167
  const idleSlot = idleSlots[i];
35883
- const explicitTarget = hasHourlyTargetOutputProp ? hourlyTargetOutput?.[i] ?? null : void 0;
36168
+ const explicitTarget = hasHourlyTargetOutputProp ? effectiveHourlyTargetOutput?.[i] ?? null : void 0;
35884
36169
  const currentTarget = hasHourlyTargetOutputProp ? explicitTarget : targets[i] || pphThreshold;
35885
36170
  const comparisonTarget = currentTarget === null || currentTarget === void 0 ? targets[i] || pphThreshold : currentTarget;
35886
36171
  return {
@@ -35899,7 +36184,7 @@ var HourlyOutputChartComponent = ({
35899
36184
  isDimmed: hasTimeline && !!activeSkuId && !indices.has(i)
35900
36185
  };
35901
36186
  });
35902
- }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, hourlyTargetOutput, hasHourlyTargetOutputProp]);
36187
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, effectiveHourlyTargetOutput, hasHourlyTargetOutputProp]);
35903
36188
  const renderSkuTimelineRail = React144__default.useCallback((props) => {
35904
36189
  if (!skuTimelineSegments.length || SHIFT_DURATION <= 0) return null;
35905
36190
  const offset = props?.offset;
@@ -36558,6 +36843,16 @@ var HourlyOutputChart = React144__default.memo(
36558
36843
  if (!prevProps.data.every((val, idx) => val === nextProps.data[idx])) {
36559
36844
  return false;
36560
36845
  }
36846
+ const prevHasHourlyTargetOutputProp = prevProps.hourlyTargetOutput !== void 0;
36847
+ const nextHasHourlyTargetOutputProp = nextProps.hourlyTargetOutput !== void 0;
36848
+ if (prevHasHourlyTargetOutputProp !== nextHasHourlyTargetOutputProp) {
36849
+ return false;
36850
+ }
36851
+ if (prevProps.hourlyTargetOutput === null || nextProps.hourlyTargetOutput === null) {
36852
+ if (prevProps.hourlyTargetOutput !== nextProps.hourlyTargetOutput) {
36853
+ return false;
36854
+ }
36855
+ }
36561
36856
  const prevHourlyTargets = prevProps.hourlyTargetOutput || [];
36562
36857
  const nextHourlyTargets = nextProps.hourlyTargetOutput || [];
36563
36858
  if (prevHourlyTargets.length !== nextHourlyTargets.length) {
@@ -36568,6 +36863,18 @@ var HourlyOutputChart = React144__default.memo(
36568
36863
  return false;
36569
36864
  }
36570
36865
  }
36866
+ const prevShiftBreaks = prevProps.shiftBreaks || [];
36867
+ const nextShiftBreaks = nextProps.shiftBreaks || [];
36868
+ if (prevShiftBreaks.length !== nextShiftBreaks.length) {
36869
+ return false;
36870
+ }
36871
+ for (let i = 0; i < prevShiftBreaks.length; i += 1) {
36872
+ const prevBreak = prevShiftBreaks[i] || {};
36873
+ const nextBreak = nextShiftBreaks[i] || {};
36874
+ if (prevBreak.startTime !== nextBreak.startTime || prevBreak.endTime !== nextBreak.endTime || prevBreak.duration !== nextBreak.duration || prevBreak.remarks !== nextBreak.remarks) {
36875
+ return false;
36876
+ }
36877
+ }
36571
36878
  const prevIdle = prevProps.idleTimeHourly || {};
36572
36879
  const nextIdle = nextProps.idleTimeHourly || {};
36573
36880
  const prevKeys = Object.keys(prevIdle);
@@ -50282,261 +50589,6 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
50282
50589
  }
50283
50590
  );
50284
50591
  };
50285
-
50286
- // src/lib/utils/hourlyTargets.ts
50287
- var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
50288
- var MINUTES_PER_DAY = 24 * 60;
50289
- var parseTimeToMinutes2 = (timeString) => {
50290
- const normalized = stripSeconds2(timeString || "");
50291
- if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
50292
- const [hours, minutes] = normalized.split(":").map(Number);
50293
- return hours * 60 + minutes;
50294
- };
50295
- var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
50296
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50297
- if (!Number.isFinite(shiftStartMinutes)) return [];
50298
- const normalizedBreaks = [];
50299
- for (const entry of breaks) {
50300
- const startRaw = parseTimeToMinutes2(entry.startTime);
50301
- const endRaw = parseTimeToMinutes2(entry.endTime);
50302
- if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
50303
- let start = startRaw;
50304
- let end = endRaw;
50305
- if (end <= start) {
50306
- end += 24 * 60;
50307
- }
50308
- if (start < shiftStartMinutes) {
50309
- start += 24 * 60;
50310
- end += 24 * 60;
50311
- }
50312
- const label = entry.remarks?.trim() || "Break";
50313
- normalizedBreaks.push({ start, end, label });
50314
- }
50315
- return normalizedBreaks;
50316
- };
50317
- var roundTarget = (value, mode) => {
50318
- if (!Number.isFinite(value)) return 0;
50319
- switch (mode) {
50320
- case "floor":
50321
- return Math.floor(value);
50322
- case "ceil":
50323
- return Math.ceil(value);
50324
- case "round":
50325
- default:
50326
- return Math.round(value);
50327
- }
50328
- };
50329
- var formatDateKey = (date) => {
50330
- const year = date.getUTCFullYear();
50331
- const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
50332
- const day = `${date.getUTCDate()}`.padStart(2, "0");
50333
- return `${year}-${month}-${day}`;
50334
- };
50335
- var shiftDateKey = (dateKey, deltaDays) => {
50336
- const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50337
- const year = Number.isFinite(yearPart) ? yearPart : 1970;
50338
- const month = Number.isFinite(monthPart) ? monthPart : 1;
50339
- const day = Number.isFinite(dayPart) ? dayPart : 1;
50340
- const date = new Date(Date.UTC(year, month - 1, day));
50341
- date.setUTCDate(date.getUTCDate() + deltaDays);
50342
- return formatDateKey(date);
50343
- };
50344
- var getZonedNowSnapshot = (timeZone, now4) => {
50345
- const formatter = new Intl.DateTimeFormat("en-US", {
50346
- timeZone,
50347
- year: "numeric",
50348
- month: "2-digit",
50349
- day: "2-digit",
50350
- hour: "2-digit",
50351
- minute: "2-digit",
50352
- hourCycle: "h23"
50353
- });
50354
- const parts = formatter.formatToParts(now4).reduce((acc, part) => {
50355
- if (part.type !== "literal") {
50356
- acc[part.type] = part.value;
50357
- }
50358
- return acc;
50359
- }, {});
50360
- const year = Number(parts.year);
50361
- const month = Number(parts.month);
50362
- const day = Number(parts.day);
50363
- const hour = Number(parts.hour);
50364
- const minute = Number(parts.minute);
50365
- return {
50366
- dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
50367
- minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
50368
- };
50369
- };
50370
- var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
50371
- var buildHourlyIntervals = ({
50372
- shiftStart,
50373
- shiftEnd,
50374
- bucketMinutes = 60,
50375
- fallbackHours = 11
50376
- }) => {
50377
- const startMinutes = parseTimeToMinutes2(shiftStart);
50378
- if (!Number.isFinite(startMinutes)) return [];
50379
- const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
50380
- let totalMinutes;
50381
- const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50382
- if (!Number.isFinite(endRaw)) {
50383
- totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
50384
- } else {
50385
- let endMinutes = endRaw;
50386
- if (endMinutes <= startMinutes) {
50387
- endMinutes += 24 * 60;
50388
- }
50389
- totalMinutes = endMinutes - startMinutes;
50390
- }
50391
- if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
50392
- const count = Math.ceil(totalMinutes / bucket);
50393
- const shiftEndMinutes = startMinutes + totalMinutes;
50394
- const intervals = [];
50395
- for (let i = 0; i < count; i += 1) {
50396
- const start = startMinutes + i * bucket;
50397
- const end = Math.min(start + bucket, shiftEndMinutes);
50398
- const minutes = Math.max(0, end - start);
50399
- if (minutes <= 0) continue;
50400
- intervals.push({ start, end, minutes });
50401
- }
50402
- return intervals;
50403
- };
50404
- var computeBreakMinutesByInterval = ({
50405
- intervals,
50406
- shiftStart,
50407
- breaks
50408
- }) => {
50409
- if (!intervals.length || !breaks.length) return intervals.map(() => 0);
50410
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50411
- return intervals.map((interval) => {
50412
- if (!normalizedBreaks.length) return 0;
50413
- let total = 0;
50414
- for (const brk of normalizedBreaks) {
50415
- const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
50416
- total += overlap;
50417
- if (total >= interval.minutes) return interval.minutes;
50418
- }
50419
- return Math.min(interval.minutes, total);
50420
- });
50421
- };
50422
- var computeBreakRemarksByInterval = ({
50423
- intervals,
50424
- shiftStart,
50425
- breaks
50426
- }) => {
50427
- if (!intervals.length || !breaks.length) return intervals.map(() => "");
50428
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50429
- return intervals.map((interval) => {
50430
- 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);
50431
- return labels.join(", ");
50432
- });
50433
- };
50434
- var computeEffectiveTargets = ({
50435
- intervals,
50436
- breakMinutes,
50437
- pphThreshold,
50438
- rounding = "round"
50439
- }) => {
50440
- return intervals.map((interval, idx) => {
50441
- const intervalMinutes = Number(interval?.minutes) || 0;
50442
- const breakMins = Number(breakMinutes?.[idx]) || 0;
50443
- const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
50444
- if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
50445
- if (plannedWorkMinutes <= 0) return 0;
50446
- return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
50447
- });
50448
- };
50449
- var buildHourlyTargetPlan = ({
50450
- shiftStart,
50451
- shiftEnd,
50452
- breaks = [],
50453
- pphThreshold,
50454
- bucketMinutes = 60,
50455
- fallbackHours = 11,
50456
- rounding = "round"
50457
- }) => {
50458
- const intervals = buildHourlyIntervals({
50459
- shiftStart,
50460
- shiftEnd,
50461
- bucketMinutes,
50462
- fallbackHours
50463
- });
50464
- const breakMinutes = computeBreakMinutesByInterval({
50465
- intervals,
50466
- shiftStart,
50467
- breaks
50468
- });
50469
- const breakRemarks = computeBreakRemarksByInterval({
50470
- intervals,
50471
- shiftStart,
50472
- breaks
50473
- });
50474
- const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
50475
- const targets = computeEffectiveTargets({
50476
- intervals,
50477
- breakMinutes,
50478
- pphThreshold,
50479
- rounding
50480
- });
50481
- return {
50482
- intervals,
50483
- breakMinutes,
50484
- breakRemarks,
50485
- productiveMinutes,
50486
- targets
50487
- };
50488
- };
50489
- var isHourlyIntervalComplete = ({
50490
- reportDate,
50491
- shiftStart,
50492
- shiftEnd,
50493
- interval,
50494
- timeZone = "Asia/Kolkata",
50495
- now: now4 = /* @__PURE__ */ new Date()
50496
- }) => {
50497
- if (!reportDate) return true;
50498
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50499
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50500
- const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50501
- const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
50502
- if (reportDate === snapshot.dateKey) {
50503
- return interval.end <= snapshot.minutesOfDay;
50504
- }
50505
- if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50506
- return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
50507
- }
50508
- return reportDate < snapshot.dateKey;
50509
- };
50510
- var isShiftInProgressForReportDate = ({
50511
- reportDate,
50512
- shiftStart,
50513
- shiftEnd,
50514
- timeZone = "Asia/Kolkata",
50515
- now: now4 = /* @__PURE__ */ new Date()
50516
- }) => {
50517
- if (!reportDate || !shiftStart || !shiftEnd) return false;
50518
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50519
- const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
50520
- if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
50521
- return false;
50522
- }
50523
- let shiftEndMinutes = shiftEndMinutesRaw;
50524
- const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
50525
- if (wrapsMidnight) {
50526
- shiftEndMinutes += MINUTES_PER_DAY;
50527
- }
50528
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50529
- let currentMinutes = null;
50530
- if (reportDate === snapshot.dateKey) {
50531
- currentMinutes = snapshot.minutesOfDay;
50532
- } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50533
- currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
50534
- }
50535
- if (currentMinutes === null) {
50536
- return false;
50537
- }
50538
- return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
50539
- };
50540
50592
  var formatOperationalDateKey = (dateKey, options) => {
50541
50593
  const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50542
50594
  const year = Number.isFinite(yearPart) ? yearPart : 1970;
@@ -64679,7 +64731,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
64679
64731
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
64680
64732
  output_array: metrics2.output_array || [],
64681
64733
  output_hourly: metrics2.output_hourly,
64682
- hourly_target_output: metrics2.hourly_target_output ?? null,
64734
+ hourly_target_output: metrics2.hourly_target_output,
64683
64735
  line_threshold: metrics2.line_threshold ?? 0,
64684
64736
  threshold_pph: metrics2.threshold_pph ?? 0,
64685
64737
  shift_start: metrics2.shift_start || "06:00",
@@ -64766,7 +64818,7 @@ var transformLineMetrics = (lineId, detailResponse, queryDate, queryShiftId) =>
64766
64818
  underperforming_workspace_names: [],
64767
64819
  underperforming_workspace_uuids: [],
64768
64820
  output_array: [],
64769
- hourly_target_output: null,
64821
+ hourly_target_output: void 0,
64770
64822
  line_threshold: 0,
64771
64823
  threshold_pph: 0,
64772
64824
  shift_start: "06:00",
@@ -65805,6 +65857,7 @@ var BottomSection = memo$1(({
65805
65857
  hourlyOutputData,
65806
65858
  hourlyThreshold,
65807
65859
  hourlyTargetOutput,
65860
+ shiftBreaks,
65808
65861
  idleTimeHourly,
65809
65862
  timezone,
65810
65863
  urlDate,
@@ -65980,6 +66033,7 @@ var BottomSection = memo$1(({
65980
66033
  hourlyTargetOutput,
65981
66034
  shiftStart: lineInfo.metrics.shift_start || "06:00",
65982
66035
  shiftEnd: lineInfo.metrics.shift_end,
66036
+ shiftBreaks,
65983
66037
  idleTimeHourly,
65984
66038
  shiftDate: lineInfo.date,
65985
66039
  timezone,
@@ -66003,6 +66057,9 @@ var BottomSection = memo$1(({
66003
66057
  if (prevProps.lineInfo.monitoring_mode !== nextProps.lineInfo.monitoring_mode) return false;
66004
66058
  if (prevProps.skuAware !== nextProps.skuAware) return false;
66005
66059
  if (prevProps.activeSkuId !== nextProps.activeSkuId) return false;
66060
+ if (JSON.stringify(prevProps.shiftBreaks || []) !== JSON.stringify(nextProps.shiftBreaks || [])) {
66061
+ return false;
66062
+ }
66006
66063
  if (JSON.stringify(prevProps.hourlyTargetOutput || []) !== JSON.stringify(nextProps.hourlyTargetOutput || [])) {
66007
66064
  return false;
66008
66065
  }
@@ -66604,7 +66661,7 @@ var KPIDetailView = ({
66604
66661
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
66605
66662
  output_array: metrics2.output_array || [],
66606
66663
  output_hourly: metrics2.output_hourly,
66607
- hourly_target_output: metrics2.hourly_target_output ?? null,
66664
+ hourly_target_output: metrics2.hourly_target_output,
66608
66665
  line_threshold: metrics2.line_threshold ?? 0,
66609
66666
  threshold_pph: metrics2.threshold_pph ?? 0,
66610
66667
  shift_start: metrics2.shift_start || "06:00",
@@ -67568,7 +67625,8 @@ var KPIDetailView = ({
67568
67625
  workspaceDisplayNames,
67569
67626
  hourlyOutputData,
67570
67627
  hourlyThreshold,
67571
- hourlyTargetOutput: chartMetrics?.hourly_target_output ?? null,
67628
+ hourlyTargetOutput: chartMetrics?.hourly_target_output,
67629
+ shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
67572
67630
  idleTimeHourly: chartMetrics?.idle_time_hourly,
67573
67631
  timezone: lineTimezone,
67574
67632
  urlDate,
@@ -75332,6 +75390,7 @@ var WorkspaceDetailView = ({
75332
75390
  hourlyTargetOutput: workspace.hourly_target_output,
75333
75391
  shiftStart: workspace.shift_start || "06:00",
75334
75392
  shiftEnd: workspace.shift_end,
75393
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75335
75394
  showIdleTime: showChartIdleTime,
75336
75395
  idleTimeHourly: workspace.idle_time_hourly,
75337
75396
  idleTimeClips,
@@ -75479,6 +75538,7 @@ var WorkspaceDetailView = ({
75479
75538
  hourlyTargetOutput: workspace.hourly_target_output,
75480
75539
  shiftStart: workspace.shift_start || "06:00",
75481
75540
  shiftEnd: workspace.shift_end,
75541
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75482
75542
  showIdleTime: showChartIdleTime,
75483
75543
  idleTimeHourly: workspace.idle_time_hourly,
75484
75544
  idleTimeClips,