@optifye/dashboard-core 6.10.9 → 6.10.11

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.d.mts CHANGED
@@ -4816,6 +4816,7 @@ declare class WorkspaceHealthService {
4816
4816
  private getFromCache;
4817
4817
  private setCache;
4818
4818
  private getShiftTiming;
4819
+ private getShiftTimingForDateShift;
4819
4820
  private normalizeHourBucket;
4820
4821
  private normalizeOutputHourly;
4821
4822
  private interpretUptimeValue;
package/dist/index.d.ts CHANGED
@@ -4816,6 +4816,7 @@ declare class WorkspaceHealthService {
4816
4816
  private getFromCache;
4817
4817
  private setCache;
4818
4818
  private getShiftTiming;
4819
+ private getShiftTimingForDateShift;
4819
4820
  private normalizeHourBucket;
4820
4821
  private normalizeOutputHourly;
4821
4822
  private interpretUptimeValue;
package/dist/index.js CHANGED
@@ -536,8 +536,13 @@ var memoizedOutputArrayAggregation = createMemoizedFunction(
536
536
  var getOperationalDate = (timezone, date = /* @__PURE__ */ new Date(), shiftStartTime = "06:00") => {
537
537
  const zonedDate = dateFnsTz.toZonedTime(date, timezone);
538
538
  const hours = zonedDate.getHours();
539
- const [startHour = 6] = shiftStartTime.split(":").map(Number);
540
- const operationalDate = hours < startHour ? dateFns.subDays(zonedDate, 1) : zonedDate;
539
+ const minutes = zonedDate.getMinutes();
540
+ const [startHourRaw, startMinuteRaw] = shiftStartTime.split(":").map(Number);
541
+ const startHour = Number.isFinite(startHourRaw) ? startHourRaw : 6;
542
+ const startMinute = Number.isFinite(startMinuteRaw) ? startMinuteRaw : 0;
543
+ const currentTotalMinutes = hours * 60 + minutes;
544
+ const shiftStartTotalMinutes = startHour * 60 + startMinute;
545
+ const operationalDate = currentTotalMinutes < shiftStartTotalMinutes ? dateFns.subDays(zonedDate, 1) : zonedDate;
541
546
  return dateFns.format(operationalDate, "yyyy-MM-dd");
542
547
  };
543
548
  function formatTimeInZone(time2, timezone, formatString = "HH:mm:ss") {
@@ -2457,6 +2462,39 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2457
2462
  pendingMinutes
2458
2463
  };
2459
2464
  }
2465
+ getShiftTimingForDateShift(shiftConfig, queryDate, queryShiftId) {
2466
+ const targetShiftConfig = shiftConfig?.shifts?.find((s) => s.shiftId === queryShiftId);
2467
+ const startTime = targetShiftConfig?.startTime || "06:00";
2468
+ const endTime = targetShiftConfig?.endTime || "18:00";
2469
+ const [year, month, day] = queryDate.split("-").map(Number);
2470
+ const [startHour = 0, startMin = 0] = startTime.split(":").map(Number);
2471
+ const [endHour = 0, endMin = 0] = endTime.split(":").map(Number);
2472
+ const shiftStartDate = new Date(year, month - 1, day, startHour, startMin, 0);
2473
+ let shiftEndDate;
2474
+ if (endHour < startHour || endHour === startHour && endMin < startMin) {
2475
+ shiftEndDate = new Date(year, month - 1, day + 1, endHour, endMin, 0);
2476
+ } else {
2477
+ shiftEndDate = new Date(year, month - 1, day, endHour, endMin, 0);
2478
+ }
2479
+ const totalMinutes = Math.floor((shiftEndDate.getTime() - shiftStartDate.getTime()) / (1e3 * 60));
2480
+ const now2 = /* @__PURE__ */ new Date();
2481
+ let completedMinutes;
2482
+ if (shiftEndDate < now2) {
2483
+ completedMinutes = totalMinutes;
2484
+ } else if (shiftStartDate > now2) {
2485
+ completedMinutes = 0;
2486
+ } else {
2487
+ completedMinutes = Math.floor((now2.getTime() - shiftStartDate.getTime()) / (1e3 * 60));
2488
+ }
2489
+ const pendingMinutes = totalMinutes - completedMinutes;
2490
+ return {
2491
+ shiftStartDate,
2492
+ shiftEndDate,
2493
+ totalMinutes,
2494
+ completedMinutes,
2495
+ pendingMinutes
2496
+ };
2497
+ }
2460
2498
  normalizeHourBucket(bucket) {
2461
2499
  if (Array.isArray(bucket)) return bucket;
2462
2500
  if (bucket && Array.isArray(bucket.values)) return bucket.values;
@@ -2926,14 +2964,22 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2926
2964
  if (!passedShiftConfig) {
2927
2965
  console.warn("[workspaceHealthService.calculateWorkspaceUptime] \u26A0\uFE0F No shiftConfig passed! Falling back to static config. This may cause incorrect shift_id queries.");
2928
2966
  }
2929
- const {
2930
- shiftId: currentShiftId,
2931
- date: currentDate,
2932
- shiftStartDate,
2933
- completedMinutes
2934
- } = this.getShiftTiming(effectiveTimezone, shiftConfig);
2935
- const queryDate = overrideDate ?? currentDate;
2936
- const queryShiftId = overrideShiftId ?? currentShiftId;
2967
+ const currentTiming = this.getShiftTiming(effectiveTimezone, shiftConfig);
2968
+ const queryDate = overrideDate ?? currentTiming.date;
2969
+ const queryShiftId = overrideShiftId ?? currentTiming.shiftId;
2970
+ let shiftStartDate = currentTiming.shiftStartDate;
2971
+ let completedMinutes = currentTiming.completedMinutes;
2972
+ const isHistoricalDate = overrideDate && overrideDate !== currentTiming.date;
2973
+ const isDifferentShift = overrideShiftId !== void 0 && overrideShiftId !== currentTiming.shiftId;
2974
+ if (isHistoricalDate || isDifferentShift) {
2975
+ const overrideTiming = this.getShiftTimingForDateShift(
2976
+ shiftConfig,
2977
+ queryDate,
2978
+ queryShiftId
2979
+ );
2980
+ shiftStartDate = overrideTiming.shiftStartDate;
2981
+ completedMinutes = overrideTiming.completedMinutes;
2982
+ }
2937
2983
  const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
2938
2984
  try {
2939
2985
  const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", queryDate).eq("shift_id", queryShiftId);
@@ -3008,12 +3054,27 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
3008
3054
  const queryConfigs = [];
3009
3055
  lineShiftConfigs.forEach((config, lineId) => {
3010
3056
  const timing = this.getShiftTiming(timezone, config);
3057
+ const queryDate = overrideDate ?? timing.date;
3058
+ const queryShiftId = overrideShiftId ?? timing.shiftId;
3059
+ let shiftStartDate = timing.shiftStartDate;
3060
+ let completedMinutes = timing.completedMinutes;
3061
+ const isHistoricalDate = overrideDate && overrideDate !== timing.date;
3062
+ const isDifferentShift = overrideShiftId !== void 0 && overrideShiftId !== timing.shiftId;
3063
+ if (isHistoricalDate || isDifferentShift) {
3064
+ const overrideTiming = this.getShiftTimingForDateShift(
3065
+ config,
3066
+ queryDate,
3067
+ queryShiftId
3068
+ );
3069
+ shiftStartDate = overrideTiming.shiftStartDate;
3070
+ completedMinutes = overrideTiming.completedMinutes;
3071
+ }
3011
3072
  queryConfigs.push({
3012
3073
  lineId,
3013
- date: overrideDate ?? timing.date,
3014
- shiftId: overrideShiftId ?? timing.shiftId,
3015
- shiftStartDate: timing.shiftStartDate,
3016
- completedMinutes: timing.completedMinutes
3074
+ date: queryDate,
3075
+ shiftId: queryShiftId,
3076
+ shiftStartDate,
3077
+ completedMinutes
3017
3078
  });
3018
3079
  });
3019
3080
  const uniqueQueries = /* @__PURE__ */ new Map();
@@ -3038,7 +3099,12 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
3038
3099
  });
3039
3100
  const queryPromises = Array.from(uniqueQueries.entries()).map(async ([key, { date, shiftId, lineConfigs }]) => {
3040
3101
  try {
3041
- const { data, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", date).eq("shift_id", shiftId);
3102
+ const lineIds = Array.from(new Set(lineConfigs.map((lc) => lc.lineId).filter(Boolean)));
3103
+ let query = supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", date).eq("shift_id", shiftId);
3104
+ if (lineIds.length > 0) {
3105
+ query = query.in("line_id", lineIds);
3106
+ }
3107
+ const { data, error } = await query;
3042
3108
  if (error) {
3043
3109
  console.error(`[calculateWorkspaceUptimeMultiLine] Error fetching metrics for ${key}:`, error);
3044
3110
  return { records: [], lineConfigs };
@@ -3059,11 +3125,16 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
3059
3125
  recordWorkspaceDisplayName: record.workspace_display_name,
3060
3126
  availableLineIds: lineConfigs.map((lc) => lc.lineId),
3061
3127
  matchFound: !!lineConfig,
3062
- matchedLineId: lineConfig?.lineId || "FALLBACK to " + lineConfigs[0]?.lineId
3128
+ matchedLineId: lineConfig?.lineId || "none"
3063
3129
  });
3064
- const effectiveLineConfig = lineConfig || lineConfigs[0];
3065
- if (!effectiveLineConfig) continue;
3066
- const { shiftStartDate, completedMinutes } = effectiveLineConfig;
3130
+ if (!lineConfig) {
3131
+ console.warn("[calculateWorkspaceUptimeMultiLine] No line config match for record, skipping", {
3132
+ recordLineId: record.line_id,
3133
+ recordWorkspaceId: record.workspace_id
3134
+ });
3135
+ continue;
3136
+ }
3137
+ const { shiftStartDate, completedMinutes } = lineConfig;
3067
3138
  const outputHourly = this.normalizeOutputHourly(record.output_hourly || {});
3068
3139
  const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
3069
3140
  let uptimeMinutes = 0;
@@ -37421,50 +37492,61 @@ var LinePdfGenerator = ({
37421
37492
  doc.setLineWidth(0.8);
37422
37493
  doc.line(20, 118, 190, 118);
37423
37494
  const hourlyOverviewStartY = 123;
37495
+ const parseTimeToMinutes2 = (timeStr) => {
37496
+ const [hours, minutes] = timeStr.split(":");
37497
+ const hour = parseInt(hours, 10);
37498
+ const minute = parseInt(minutes || "0", 10);
37499
+ if (Number.isNaN(hour) || Number.isNaN(minute)) {
37500
+ return NaN;
37501
+ }
37502
+ return (hour * 60 + minute) % (24 * 60);
37503
+ };
37504
+ const formatMinutesLabel = (totalMinutes) => {
37505
+ const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
37506
+ const hour = Math.floor(normalized / 60);
37507
+ const minute = normalized % 60;
37508
+ const time2 = /* @__PURE__ */ new Date();
37509
+ time2.setHours(hour);
37510
+ time2.setMinutes(minute);
37511
+ time2.setSeconds(0);
37512
+ time2.setMilliseconds(0);
37513
+ return time2.toLocaleTimeString("en-IN", {
37514
+ hour: "2-digit",
37515
+ minute: "2-digit",
37516
+ hour12: true,
37517
+ timeZone: "Asia/Kolkata"
37518
+ });
37519
+ };
37520
+ const buildRange = (startMinutes, minutes) => {
37521
+ const endMinutes = startMinutes + minutes;
37522
+ return {
37523
+ label: `${formatMinutesLabel(startMinutes)} - ${formatMinutesLabel(endMinutes)}`,
37524
+ minutes
37525
+ };
37526
+ };
37424
37527
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
37425
- const [hours, minutes] = startTimeStr.split(":");
37426
- const startHour = parseInt(hours);
37427
- const startMinute = parseInt(minutes || "0");
37428
- let SHIFT_DURATION = 11;
37429
- let endHour = startHour + 11;
37430
- let endMinute = startMinute;
37431
- if (endTimeStr) {
37432
- const [endHours, endMinutes] = endTimeStr.split(":");
37433
- endHour = parseInt(endHours);
37434
- endMinute = parseInt(endMinutes || "0");
37435
- let duration = endHour - startHour;
37436
- if (duration <= 0) {
37437
- duration += 24;
37438
- }
37439
- const hasPartialLastHour = endMinute > 0 && endMinute < 60;
37440
- SHIFT_DURATION = hasPartialLastHour ? Math.ceil(duration) : Math.round(duration);
37441
- }
37442
- return Array.from({ length: SHIFT_DURATION }, (_, i) => {
37443
- const isLastHour = i === SHIFT_DURATION - 1;
37444
- const hourStartTime = /* @__PURE__ */ new Date();
37445
- hourStartTime.setHours((startHour + i) % 24);
37446
- hourStartTime.setMinutes(startMinute);
37447
- hourStartTime.setSeconds(0);
37448
- hourStartTime.setMilliseconds(0);
37449
- let hourEndTime = /* @__PURE__ */ new Date();
37450
- if (isLastHour && endTimeStr) {
37451
- hourEndTime.setHours(endHour % 24);
37452
- hourEndTime.setMinutes(endMinute);
37453
- } else {
37454
- hourEndTime.setHours((startHour + i + 1) % 24);
37455
- hourEndTime.setMinutes(startMinute);
37456
- }
37457
- hourEndTime.setSeconds(0);
37458
- hourEndTime.setMilliseconds(0);
37459
- const formatTime5 = (date2) => {
37460
- return date2.toLocaleTimeString("en-IN", {
37461
- hour: "2-digit",
37462
- minute: "2-digit",
37463
- hour12: true,
37464
- timeZone: "Asia/Kolkata"
37465
- });
37466
- };
37467
- return `${formatTime5(hourStartTime)} - ${formatTime5(hourEndTime)}`;
37528
+ const startMinutes = parseTimeToMinutes2(startTimeStr);
37529
+ if (Number.isNaN(startMinutes)) {
37530
+ return [];
37531
+ }
37532
+ if (!endTimeStr) {
37533
+ const defaultHours = 11;
37534
+ return Array.from({ length: defaultHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
37535
+ }
37536
+ const endMinutes = parseTimeToMinutes2(endTimeStr);
37537
+ if (Number.isNaN(endMinutes)) {
37538
+ const fallbackHours = 11;
37539
+ return Array.from({ length: fallbackHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
37540
+ }
37541
+ let durationMinutes = endMinutes - startMinutes;
37542
+ if (durationMinutes <= 0) {
37543
+ durationMinutes += 24 * 60;
37544
+ }
37545
+ const rangeCount = Math.max(1, Math.ceil(durationMinutes / 60));
37546
+ return Array.from({ length: rangeCount }, (_, i) => {
37547
+ const remainingMinutes = durationMinutes - i * 60;
37548
+ const rangeMinutes = remainingMinutes >= 60 ? 60 : remainingMinutes;
37549
+ return buildRange(startMinutes + i * 60, rangeMinutes);
37468
37550
  });
37469
37551
  };
37470
37552
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
@@ -37476,8 +37558,13 @@ var LinePdfGenerator = ({
37476
37558
  const startHour = parseInt(startHourStr);
37477
37559
  const startMinute = parseInt(startMinuteStr || "0");
37478
37560
  const [endHourStr, endMinuteStr] = (lineInfo.metrics.shift_end || "18:00").split(":");
37561
+ const endHour = parseInt(endHourStr);
37479
37562
  const endMinute = parseInt(endMinuteStr || "0");
37480
- const hasPartialLastHour = endMinute > 0 && endMinute < 60;
37563
+ let durationMinutes = endHour * 60 + endMinute - (startHour * 60 + startMinute);
37564
+ if (durationMinutes <= 0) {
37565
+ durationMinutes += 24 * 60;
37566
+ }
37567
+ const hasPartialLastHour = durationMinutes % 60 !== 0;
37481
37568
  const expectedHours = [];
37482
37569
  for (let i = 0; i < shiftDuration; i++) {
37483
37570
  expectedHours.push((startHour + i) % 24);
@@ -37668,19 +37755,21 @@ var LinePdfGenerator = ({
37668
37755
  const hourNumber = (startHour + index) % 24;
37669
37756
  const dataCollected = !isTodayForTable || hourNumber < currentHour;
37670
37757
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
37671
- const targetStr = targetOutputPerHour.toString();
37672
37758
  if (index < totalRows - 1) {
37673
37759
  const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
37674
37760
  doc.setDrawColor(200, 200, 200);
37675
37761
  doc.line(20, rowBottomY, 190, rowBottomY);
37676
37762
  }
37677
- doc.text(timeRange, 25, yPos);
37763
+ const rangeMinutes = timeRange.minutes || 60;
37764
+ const targetForRange = targetOutputPerHour * (rangeMinutes / 60);
37765
+ const targetStr = rangeMinutes === 60 ? targetOutputPerHour.toString() : targetForRange.toFixed(1).replace(/\.0$/, "");
37766
+ doc.text(timeRange.label, 25, yPos);
37678
37767
  doc.text(outputStr, 75, yPos);
37679
37768
  doc.text(targetStr, 105, yPos);
37680
37769
  if (!dataCollected) {
37681
37770
  doc.setTextColor(100, 100, 100);
37682
37771
  doc.text("-", 135, yPos);
37683
- } else if (actualOutput >= targetOutputPerHour) {
37772
+ } else if (actualOutput >= targetForRange) {
37684
37773
  doc.setTextColor(0, 171, 69);
37685
37774
  doc.setFont("ZapfDingbats", "normal");
37686
37775
  doc.text("4", 135, yPos);
@@ -39091,23 +39180,29 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
39091
39180
  const colBoundaries = [20, 70, 100, 130, 155, 190];
39092
39181
  const totalRows = hourlyData.length;
39093
39182
  const gridBottomY = headerBottomY + totalRows * rowHeight;
39183
+ const tableHeight = gridBottomY - gridTopY;
39184
+ doc.setFillColor(255, 255, 255);
39185
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "F");
39186
+ doc.setDrawColor(230, 230, 230);
39187
+ doc.setLineWidth(0.2);
39188
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "S");
39094
39189
  doc.setDrawColor(200, 200, 200);
39095
39190
  doc.setLineWidth(0.3);
39096
- doc.line(20, gridTopY, 190, gridTopY);
39097
39191
  doc.line(20, headerBottomY, 190, headerBottomY);
39098
- colBoundaries.forEach((x) => {
39192
+ colBoundaries.slice(1, -1).forEach((x) => {
39099
39193
  doc.line(x, gridTopY, x, gridBottomY);
39100
39194
  });
39101
39195
  doc.setFontSize(headerFontSize);
39102
39196
  doc.setFont("helvetica", "bold");
39103
- doc.text("Time Range", 25, headerY);
39104
- doc.text("Output", 75, headerY);
39105
- doc.text("Target", 105, headerY);
39106
- doc.text("Status", 135, headerY);
39107
- doc.text("Remarks", 160, headerY);
39197
+ const headerTextY = gridTopY + 5.5;
39198
+ doc.text("Time Range", 25, headerTextY);
39199
+ doc.text("Output", 75, headerTextY);
39200
+ doc.text("Target", 105, headerTextY);
39201
+ doc.text("Status", 135, headerTextY);
39202
+ doc.text("Remarks", 160, headerTextY);
39108
39203
  doc.setFontSize(contentFontSize);
39109
39204
  doc.setFont("helvetica", "normal");
39110
- let yPos = tableStartY;
39205
+ let yPos = headerBottomY + 5.5;
39111
39206
  const workspaceDate = new Date(workspace.date);
39112
39207
  const today = /* @__PURE__ */ new Date();
39113
39208
  today.setHours(0, 0, 0, 0);
@@ -39135,9 +39230,11 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
39135
39230
  const dataCollected = !isToday2 || hourNumber < currentHour;
39136
39231
  const outputStr = dataCollected ? output.toString() : "TBD";
39137
39232
  const targetStr = hourlyTarget.toString();
39138
- const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39139
- doc.setDrawColor(200, 200, 200);
39140
- doc.line(20, rowBottomY, 190, rowBottomY);
39233
+ if (index < totalRows - 1) {
39234
+ const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39235
+ doc.setDrawColor(200, 200, 200);
39236
+ doc.line(20, rowBottomY, 190, rowBottomY);
39237
+ }
39141
39238
  doc.text(timeRange, 25, yPos);
39142
39239
  doc.text(outputStr, 75, yPos);
39143
39240
  doc.text(targetStr, 105, yPos);
@@ -58013,6 +58110,8 @@ var ClipVideoCarousel = ({ clips, clipsService }) => {
58013
58110
  const [videos, setVideos] = React24.useState([]);
58014
58111
  const [loading, setLoading] = React24.useState(false);
58015
58112
  const [error, setError] = React24.useState(null);
58113
+ const currentVideo = videos[currentIndex];
58114
+ const { crop: workspaceCrop } = useWorkspaceCrop(currentVideo?.workspace_id || null);
58016
58115
  React24.useEffect(() => {
58017
58116
  let cancelled = false;
58018
58117
  const load = async () => {
@@ -58032,7 +58131,8 @@ var ClipVideoCarousel = ({ clips, clipsService }) => {
58032
58131
  const cycleTime = typeof c.cycle_time_seconds === "number" ? c.cycle_time_seconds : typeof c.cycle_sec === "number" ? c.cycle_sec : void 0;
58033
58132
  return {
58034
58133
  ...video,
58035
- cycle_time_seconds: cycleTime ?? video.cycle_time_seconds
58134
+ cycle_time_seconds: cycleTime ?? video.cycle_time_seconds,
58135
+ workspace_id: c.workspace_id
58036
58136
  };
58037
58137
  })
58038
58138
  );
@@ -58063,17 +58163,17 @@ var ClipVideoCarousel = ({ clips, clipsService }) => {
58063
58163
  setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
58064
58164
  };
58065
58165
  if (!clips || clips.length === 0) return null;
58066
- const currentVideo = videos[currentIndex];
58067
58166
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group bg-gray-900 rounded-lg overflow-hidden aspect-video", children: [
58068
58167
  loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: "Loading clips\u2026" }),
58069
58168
  !loading && error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: error }),
58070
58169
  !loading && !error && currentVideo?.src && /* @__PURE__ */ jsxRuntime.jsx(
58071
- VideoPlayer,
58170
+ CroppedVideoPlayer,
58072
58171
  {
58073
58172
  src: currentVideo.src,
58074
58173
  className: "w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity",
58075
58174
  controls: true,
58076
- playsInline: true
58175
+ playsInline: true,
58176
+ crop: workspaceCrop?.crop || null
58077
58177
  },
58078
58178
  currentVideo.src
58079
58179
  ),
package/dist/index.mjs CHANGED
@@ -507,8 +507,13 @@ var memoizedOutputArrayAggregation = createMemoizedFunction(
507
507
  var getOperationalDate = (timezone, date = /* @__PURE__ */ new Date(), shiftStartTime = "06:00") => {
508
508
  const zonedDate = toZonedTime(date, timezone);
509
509
  const hours = zonedDate.getHours();
510
- const [startHour = 6] = shiftStartTime.split(":").map(Number);
511
- const operationalDate = hours < startHour ? subDays(zonedDate, 1) : zonedDate;
510
+ const minutes = zonedDate.getMinutes();
511
+ const [startHourRaw, startMinuteRaw] = shiftStartTime.split(":").map(Number);
512
+ const startHour = Number.isFinite(startHourRaw) ? startHourRaw : 6;
513
+ const startMinute = Number.isFinite(startMinuteRaw) ? startMinuteRaw : 0;
514
+ const currentTotalMinutes = hours * 60 + minutes;
515
+ const shiftStartTotalMinutes = startHour * 60 + startMinute;
516
+ const operationalDate = currentTotalMinutes < shiftStartTotalMinutes ? subDays(zonedDate, 1) : zonedDate;
512
517
  return format(operationalDate, "yyyy-MM-dd");
513
518
  };
514
519
  function formatTimeInZone(time2, timezone, formatString = "HH:mm:ss") {
@@ -2428,6 +2433,39 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2428
2433
  pendingMinutes
2429
2434
  };
2430
2435
  }
2436
+ getShiftTimingForDateShift(shiftConfig, queryDate, queryShiftId) {
2437
+ const targetShiftConfig = shiftConfig?.shifts?.find((s) => s.shiftId === queryShiftId);
2438
+ const startTime = targetShiftConfig?.startTime || "06:00";
2439
+ const endTime = targetShiftConfig?.endTime || "18:00";
2440
+ const [year, month, day] = queryDate.split("-").map(Number);
2441
+ const [startHour = 0, startMin = 0] = startTime.split(":").map(Number);
2442
+ const [endHour = 0, endMin = 0] = endTime.split(":").map(Number);
2443
+ const shiftStartDate = new Date(year, month - 1, day, startHour, startMin, 0);
2444
+ let shiftEndDate;
2445
+ if (endHour < startHour || endHour === startHour && endMin < startMin) {
2446
+ shiftEndDate = new Date(year, month - 1, day + 1, endHour, endMin, 0);
2447
+ } else {
2448
+ shiftEndDate = new Date(year, month - 1, day, endHour, endMin, 0);
2449
+ }
2450
+ const totalMinutes = Math.floor((shiftEndDate.getTime() - shiftStartDate.getTime()) / (1e3 * 60));
2451
+ const now2 = /* @__PURE__ */ new Date();
2452
+ let completedMinutes;
2453
+ if (shiftEndDate < now2) {
2454
+ completedMinutes = totalMinutes;
2455
+ } else if (shiftStartDate > now2) {
2456
+ completedMinutes = 0;
2457
+ } else {
2458
+ completedMinutes = Math.floor((now2.getTime() - shiftStartDate.getTime()) / (1e3 * 60));
2459
+ }
2460
+ const pendingMinutes = totalMinutes - completedMinutes;
2461
+ return {
2462
+ shiftStartDate,
2463
+ shiftEndDate,
2464
+ totalMinutes,
2465
+ completedMinutes,
2466
+ pendingMinutes
2467
+ };
2468
+ }
2431
2469
  normalizeHourBucket(bucket) {
2432
2470
  if (Array.isArray(bucket)) return bucket;
2433
2471
  if (bucket && Array.isArray(bucket.values)) return bucket.values;
@@ -2897,14 +2935,22 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2897
2935
  if (!passedShiftConfig) {
2898
2936
  console.warn("[workspaceHealthService.calculateWorkspaceUptime] \u26A0\uFE0F No shiftConfig passed! Falling back to static config. This may cause incorrect shift_id queries.");
2899
2937
  }
2900
- const {
2901
- shiftId: currentShiftId,
2902
- date: currentDate,
2903
- shiftStartDate,
2904
- completedMinutes
2905
- } = this.getShiftTiming(effectiveTimezone, shiftConfig);
2906
- const queryDate = overrideDate ?? currentDate;
2907
- const queryShiftId = overrideShiftId ?? currentShiftId;
2938
+ const currentTiming = this.getShiftTiming(effectiveTimezone, shiftConfig);
2939
+ const queryDate = overrideDate ?? currentTiming.date;
2940
+ const queryShiftId = overrideShiftId ?? currentTiming.shiftId;
2941
+ let shiftStartDate = currentTiming.shiftStartDate;
2942
+ let completedMinutes = currentTiming.completedMinutes;
2943
+ const isHistoricalDate = overrideDate && overrideDate !== currentTiming.date;
2944
+ const isDifferentShift = overrideShiftId !== void 0 && overrideShiftId !== currentTiming.shiftId;
2945
+ if (isHistoricalDate || isDifferentShift) {
2946
+ const overrideTiming = this.getShiftTimingForDateShift(
2947
+ shiftConfig,
2948
+ queryDate,
2949
+ queryShiftId
2950
+ );
2951
+ shiftStartDate = overrideTiming.shiftStartDate;
2952
+ completedMinutes = overrideTiming.completedMinutes;
2953
+ }
2908
2954
  const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
2909
2955
  try {
2910
2956
  const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", queryDate).eq("shift_id", queryShiftId);
@@ -2979,12 +3025,27 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2979
3025
  const queryConfigs = [];
2980
3026
  lineShiftConfigs.forEach((config, lineId) => {
2981
3027
  const timing = this.getShiftTiming(timezone, config);
3028
+ const queryDate = overrideDate ?? timing.date;
3029
+ const queryShiftId = overrideShiftId ?? timing.shiftId;
3030
+ let shiftStartDate = timing.shiftStartDate;
3031
+ let completedMinutes = timing.completedMinutes;
3032
+ const isHistoricalDate = overrideDate && overrideDate !== timing.date;
3033
+ const isDifferentShift = overrideShiftId !== void 0 && overrideShiftId !== timing.shiftId;
3034
+ if (isHistoricalDate || isDifferentShift) {
3035
+ const overrideTiming = this.getShiftTimingForDateShift(
3036
+ config,
3037
+ queryDate,
3038
+ queryShiftId
3039
+ );
3040
+ shiftStartDate = overrideTiming.shiftStartDate;
3041
+ completedMinutes = overrideTiming.completedMinutes;
3042
+ }
2982
3043
  queryConfigs.push({
2983
3044
  lineId,
2984
- date: overrideDate ?? timing.date,
2985
- shiftId: overrideShiftId ?? timing.shiftId,
2986
- shiftStartDate: timing.shiftStartDate,
2987
- completedMinutes: timing.completedMinutes
3045
+ date: queryDate,
3046
+ shiftId: queryShiftId,
3047
+ shiftStartDate,
3048
+ completedMinutes
2988
3049
  });
2989
3050
  });
2990
3051
  const uniqueQueries = /* @__PURE__ */ new Map();
@@ -3009,7 +3070,12 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
3009
3070
  });
3010
3071
  const queryPromises = Array.from(uniqueQueries.entries()).map(async ([key, { date, shiftId, lineConfigs }]) => {
3011
3072
  try {
3012
- const { data, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", date).eq("shift_id", shiftId);
3073
+ const lineIds = Array.from(new Set(lineConfigs.map((lc) => lc.lineId).filter(Boolean)));
3074
+ let query = supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", date).eq("shift_id", shiftId);
3075
+ if (lineIds.length > 0) {
3076
+ query = query.in("line_id", lineIds);
3077
+ }
3078
+ const { data, error } = await query;
3013
3079
  if (error) {
3014
3080
  console.error(`[calculateWorkspaceUptimeMultiLine] Error fetching metrics for ${key}:`, error);
3015
3081
  return { records: [], lineConfigs };
@@ -3030,11 +3096,16 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
3030
3096
  recordWorkspaceDisplayName: record.workspace_display_name,
3031
3097
  availableLineIds: lineConfigs.map((lc) => lc.lineId),
3032
3098
  matchFound: !!lineConfig,
3033
- matchedLineId: lineConfig?.lineId || "FALLBACK to " + lineConfigs[0]?.lineId
3099
+ matchedLineId: lineConfig?.lineId || "none"
3034
3100
  });
3035
- const effectiveLineConfig = lineConfig || lineConfigs[0];
3036
- if (!effectiveLineConfig) continue;
3037
- const { shiftStartDate, completedMinutes } = effectiveLineConfig;
3101
+ if (!lineConfig) {
3102
+ console.warn("[calculateWorkspaceUptimeMultiLine] No line config match for record, skipping", {
3103
+ recordLineId: record.line_id,
3104
+ recordWorkspaceId: record.workspace_id
3105
+ });
3106
+ continue;
3107
+ }
3108
+ const { shiftStartDate, completedMinutes } = lineConfig;
3038
3109
  const outputHourly = this.normalizeOutputHourly(record.output_hourly || {});
3039
3110
  const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
3040
3111
  let uptimeMinutes = 0;
@@ -37392,50 +37463,61 @@ var LinePdfGenerator = ({
37392
37463
  doc.setLineWidth(0.8);
37393
37464
  doc.line(20, 118, 190, 118);
37394
37465
  const hourlyOverviewStartY = 123;
37466
+ const parseTimeToMinutes2 = (timeStr) => {
37467
+ const [hours, minutes] = timeStr.split(":");
37468
+ const hour = parseInt(hours, 10);
37469
+ const minute = parseInt(minutes || "0", 10);
37470
+ if (Number.isNaN(hour) || Number.isNaN(minute)) {
37471
+ return NaN;
37472
+ }
37473
+ return (hour * 60 + minute) % (24 * 60);
37474
+ };
37475
+ const formatMinutesLabel = (totalMinutes) => {
37476
+ const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
37477
+ const hour = Math.floor(normalized / 60);
37478
+ const minute = normalized % 60;
37479
+ const time2 = /* @__PURE__ */ new Date();
37480
+ time2.setHours(hour);
37481
+ time2.setMinutes(minute);
37482
+ time2.setSeconds(0);
37483
+ time2.setMilliseconds(0);
37484
+ return time2.toLocaleTimeString("en-IN", {
37485
+ hour: "2-digit",
37486
+ minute: "2-digit",
37487
+ hour12: true,
37488
+ timeZone: "Asia/Kolkata"
37489
+ });
37490
+ };
37491
+ const buildRange = (startMinutes, minutes) => {
37492
+ const endMinutes = startMinutes + minutes;
37493
+ return {
37494
+ label: `${formatMinutesLabel(startMinutes)} - ${formatMinutesLabel(endMinutes)}`,
37495
+ minutes
37496
+ };
37497
+ };
37395
37498
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
37396
- const [hours, minutes] = startTimeStr.split(":");
37397
- const startHour = parseInt(hours);
37398
- const startMinute = parseInt(minutes || "0");
37399
- let SHIFT_DURATION = 11;
37400
- let endHour = startHour + 11;
37401
- let endMinute = startMinute;
37402
- if (endTimeStr) {
37403
- const [endHours, endMinutes] = endTimeStr.split(":");
37404
- endHour = parseInt(endHours);
37405
- endMinute = parseInt(endMinutes || "0");
37406
- let duration = endHour - startHour;
37407
- if (duration <= 0) {
37408
- duration += 24;
37409
- }
37410
- const hasPartialLastHour = endMinute > 0 && endMinute < 60;
37411
- SHIFT_DURATION = hasPartialLastHour ? Math.ceil(duration) : Math.round(duration);
37412
- }
37413
- return Array.from({ length: SHIFT_DURATION }, (_, i) => {
37414
- const isLastHour = i === SHIFT_DURATION - 1;
37415
- const hourStartTime = /* @__PURE__ */ new Date();
37416
- hourStartTime.setHours((startHour + i) % 24);
37417
- hourStartTime.setMinutes(startMinute);
37418
- hourStartTime.setSeconds(0);
37419
- hourStartTime.setMilliseconds(0);
37420
- let hourEndTime = /* @__PURE__ */ new Date();
37421
- if (isLastHour && endTimeStr) {
37422
- hourEndTime.setHours(endHour % 24);
37423
- hourEndTime.setMinutes(endMinute);
37424
- } else {
37425
- hourEndTime.setHours((startHour + i + 1) % 24);
37426
- hourEndTime.setMinutes(startMinute);
37427
- }
37428
- hourEndTime.setSeconds(0);
37429
- hourEndTime.setMilliseconds(0);
37430
- const formatTime5 = (date2) => {
37431
- return date2.toLocaleTimeString("en-IN", {
37432
- hour: "2-digit",
37433
- minute: "2-digit",
37434
- hour12: true,
37435
- timeZone: "Asia/Kolkata"
37436
- });
37437
- };
37438
- return `${formatTime5(hourStartTime)} - ${formatTime5(hourEndTime)}`;
37499
+ const startMinutes = parseTimeToMinutes2(startTimeStr);
37500
+ if (Number.isNaN(startMinutes)) {
37501
+ return [];
37502
+ }
37503
+ if (!endTimeStr) {
37504
+ const defaultHours = 11;
37505
+ return Array.from({ length: defaultHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
37506
+ }
37507
+ const endMinutes = parseTimeToMinutes2(endTimeStr);
37508
+ if (Number.isNaN(endMinutes)) {
37509
+ const fallbackHours = 11;
37510
+ return Array.from({ length: fallbackHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
37511
+ }
37512
+ let durationMinutes = endMinutes - startMinutes;
37513
+ if (durationMinutes <= 0) {
37514
+ durationMinutes += 24 * 60;
37515
+ }
37516
+ const rangeCount = Math.max(1, Math.ceil(durationMinutes / 60));
37517
+ return Array.from({ length: rangeCount }, (_, i) => {
37518
+ const remainingMinutes = durationMinutes - i * 60;
37519
+ const rangeMinutes = remainingMinutes >= 60 ? 60 : remainingMinutes;
37520
+ return buildRange(startMinutes + i * 60, rangeMinutes);
37439
37521
  });
37440
37522
  };
37441
37523
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
@@ -37447,8 +37529,13 @@ var LinePdfGenerator = ({
37447
37529
  const startHour = parseInt(startHourStr);
37448
37530
  const startMinute = parseInt(startMinuteStr || "0");
37449
37531
  const [endHourStr, endMinuteStr] = (lineInfo.metrics.shift_end || "18:00").split(":");
37532
+ const endHour = parseInt(endHourStr);
37450
37533
  const endMinute = parseInt(endMinuteStr || "0");
37451
- const hasPartialLastHour = endMinute > 0 && endMinute < 60;
37534
+ let durationMinutes = endHour * 60 + endMinute - (startHour * 60 + startMinute);
37535
+ if (durationMinutes <= 0) {
37536
+ durationMinutes += 24 * 60;
37537
+ }
37538
+ const hasPartialLastHour = durationMinutes % 60 !== 0;
37452
37539
  const expectedHours = [];
37453
37540
  for (let i = 0; i < shiftDuration; i++) {
37454
37541
  expectedHours.push((startHour + i) % 24);
@@ -37639,19 +37726,21 @@ var LinePdfGenerator = ({
37639
37726
  const hourNumber = (startHour + index) % 24;
37640
37727
  const dataCollected = !isTodayForTable || hourNumber < currentHour;
37641
37728
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
37642
- const targetStr = targetOutputPerHour.toString();
37643
37729
  if (index < totalRows - 1) {
37644
37730
  const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
37645
37731
  doc.setDrawColor(200, 200, 200);
37646
37732
  doc.line(20, rowBottomY, 190, rowBottomY);
37647
37733
  }
37648
- doc.text(timeRange, 25, yPos);
37734
+ const rangeMinutes = timeRange.minutes || 60;
37735
+ const targetForRange = targetOutputPerHour * (rangeMinutes / 60);
37736
+ const targetStr = rangeMinutes === 60 ? targetOutputPerHour.toString() : targetForRange.toFixed(1).replace(/\.0$/, "");
37737
+ doc.text(timeRange.label, 25, yPos);
37649
37738
  doc.text(outputStr, 75, yPos);
37650
37739
  doc.text(targetStr, 105, yPos);
37651
37740
  if (!dataCollected) {
37652
37741
  doc.setTextColor(100, 100, 100);
37653
37742
  doc.text("-", 135, yPos);
37654
- } else if (actualOutput >= targetOutputPerHour) {
37743
+ } else if (actualOutput >= targetForRange) {
37655
37744
  doc.setTextColor(0, 171, 69);
37656
37745
  doc.setFont("ZapfDingbats", "normal");
37657
37746
  doc.text("4", 135, yPos);
@@ -39062,23 +39151,29 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
39062
39151
  const colBoundaries = [20, 70, 100, 130, 155, 190];
39063
39152
  const totalRows = hourlyData.length;
39064
39153
  const gridBottomY = headerBottomY + totalRows * rowHeight;
39154
+ const tableHeight = gridBottomY - gridTopY;
39155
+ doc.setFillColor(255, 255, 255);
39156
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "F");
39157
+ doc.setDrawColor(230, 230, 230);
39158
+ doc.setLineWidth(0.2);
39159
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "S");
39065
39160
  doc.setDrawColor(200, 200, 200);
39066
39161
  doc.setLineWidth(0.3);
39067
- doc.line(20, gridTopY, 190, gridTopY);
39068
39162
  doc.line(20, headerBottomY, 190, headerBottomY);
39069
- colBoundaries.forEach((x) => {
39163
+ colBoundaries.slice(1, -1).forEach((x) => {
39070
39164
  doc.line(x, gridTopY, x, gridBottomY);
39071
39165
  });
39072
39166
  doc.setFontSize(headerFontSize);
39073
39167
  doc.setFont("helvetica", "bold");
39074
- doc.text("Time Range", 25, headerY);
39075
- doc.text("Output", 75, headerY);
39076
- doc.text("Target", 105, headerY);
39077
- doc.text("Status", 135, headerY);
39078
- doc.text("Remarks", 160, headerY);
39168
+ const headerTextY = gridTopY + 5.5;
39169
+ doc.text("Time Range", 25, headerTextY);
39170
+ doc.text("Output", 75, headerTextY);
39171
+ doc.text("Target", 105, headerTextY);
39172
+ doc.text("Status", 135, headerTextY);
39173
+ doc.text("Remarks", 160, headerTextY);
39079
39174
  doc.setFontSize(contentFontSize);
39080
39175
  doc.setFont("helvetica", "normal");
39081
- let yPos = tableStartY;
39176
+ let yPos = headerBottomY + 5.5;
39082
39177
  const workspaceDate = new Date(workspace.date);
39083
39178
  const today = /* @__PURE__ */ new Date();
39084
39179
  today.setHours(0, 0, 0, 0);
@@ -39106,9 +39201,11 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
39106
39201
  const dataCollected = !isToday2 || hourNumber < currentHour;
39107
39202
  const outputStr = dataCollected ? output.toString() : "TBD";
39108
39203
  const targetStr = hourlyTarget.toString();
39109
- const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39110
- doc.setDrawColor(200, 200, 200);
39111
- doc.line(20, rowBottomY, 190, rowBottomY);
39204
+ if (index < totalRows - 1) {
39205
+ const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39206
+ doc.setDrawColor(200, 200, 200);
39207
+ doc.line(20, rowBottomY, 190, rowBottomY);
39208
+ }
39112
39209
  doc.text(timeRange, 25, yPos);
39113
39210
  doc.text(outputStr, 75, yPos);
39114
39211
  doc.text(targetStr, 105, yPos);
@@ -57984,6 +58081,8 @@ var ClipVideoCarousel = ({ clips, clipsService }) => {
57984
58081
  const [videos, setVideos] = useState([]);
57985
58082
  const [loading, setLoading] = useState(false);
57986
58083
  const [error, setError] = useState(null);
58084
+ const currentVideo = videos[currentIndex];
58085
+ const { crop: workspaceCrop } = useWorkspaceCrop(currentVideo?.workspace_id || null);
57987
58086
  useEffect(() => {
57988
58087
  let cancelled = false;
57989
58088
  const load = async () => {
@@ -58003,7 +58102,8 @@ var ClipVideoCarousel = ({ clips, clipsService }) => {
58003
58102
  const cycleTime = typeof c.cycle_time_seconds === "number" ? c.cycle_time_seconds : typeof c.cycle_sec === "number" ? c.cycle_sec : void 0;
58004
58103
  return {
58005
58104
  ...video,
58006
- cycle_time_seconds: cycleTime ?? video.cycle_time_seconds
58105
+ cycle_time_seconds: cycleTime ?? video.cycle_time_seconds,
58106
+ workspace_id: c.workspace_id
58007
58107
  };
58008
58108
  })
58009
58109
  );
@@ -58034,17 +58134,17 @@ var ClipVideoCarousel = ({ clips, clipsService }) => {
58034
58134
  setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
58035
58135
  };
58036
58136
  if (!clips || clips.length === 0) return null;
58037
- const currentVideo = videos[currentIndex];
58038
58137
  return /* @__PURE__ */ jsxs("div", { className: "relative group bg-gray-900 rounded-lg overflow-hidden aspect-video", children: [
58039
58138
  loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: "Loading clips\u2026" }),
58040
58139
  !loading && error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: error }),
58041
58140
  !loading && !error && currentVideo?.src && /* @__PURE__ */ jsx(
58042
- VideoPlayer,
58141
+ CroppedVideoPlayer,
58043
58142
  {
58044
58143
  src: currentVideo.src,
58045
58144
  className: "w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity",
58046
58145
  controls: true,
58047
- playsInline: true
58146
+ playsInline: true,
58147
+ crop: workspaceCrop?.crop || null
58048
58148
  },
58049
58149
  currentVideo.src
58050
58150
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.10.9",
3
+ "version": "6.10.11",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",