@optifye/dashboard-core 6.11.14 → 6.11.15

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.css CHANGED
@@ -2639,6 +2639,9 @@ body {
2639
2639
  --tw-bg-opacity: 1;
2640
2640
  background-color: rgb(255 251 235 / var(--tw-bg-opacity, 1));
2641
2641
  }
2642
+ .bg-amber-50\/70 {
2643
+ background-color: rgb(255 251 235 / 0.7);
2644
+ }
2642
2645
  .bg-amber-500 {
2643
2646
  --tw-bg-opacity: 1;
2644
2647
  background-color: rgb(245 158 11 / var(--tw-bg-opacity, 1));
@@ -4253,6 +4256,9 @@ body {
4253
4256
  .tracking-\[0\.05em\] {
4254
4257
  letter-spacing: 0.05em;
4255
4258
  }
4259
+ .tracking-\[0\.08em\] {
4260
+ letter-spacing: 0.08em;
4261
+ }
4256
4262
  .tracking-\[0\.25em\] {
4257
4263
  letter-spacing: 0.25em;
4258
4264
  }
@@ -4369,6 +4375,10 @@ body {
4369
4375
  --tw-text-opacity: 1;
4370
4376
  color: rgb(146 64 14 / var(--tw-text-opacity, 1));
4371
4377
  }
4378
+ .text-amber-900 {
4379
+ --tw-text-opacity: 1;
4380
+ color: rgb(120 53 15 / var(--tw-text-opacity, 1));
4381
+ }
4372
4382
  .text-blue-300 {
4373
4383
  --tw-text-opacity: 1;
4374
4384
  color: rgb(147 197 253 / var(--tw-text-opacity, 1));
package/dist/index.d.mts CHANGED
@@ -124,6 +124,9 @@ interface WorkspaceDetailedMetrics {
124
124
  total_actions: number;
125
125
  hourly_action_counts: number[];
126
126
  hourly_cycle_times?: number[];
127
+ cycle_completion_clip_count?: number | null;
128
+ cycle_time_data_status?: 'available' | 'missing_clips' | null;
129
+ cycle_time_timezone?: string | null;
127
130
  workspace_rank: number;
128
131
  total_workspaces: number;
129
132
  ideal_output_until_now: number;
@@ -2832,6 +2835,7 @@ interface HistoricWorkspaceMetrics {
2832
2835
  workspace_name: string;
2833
2836
  line_id: string;
2834
2837
  line_name: string;
2838
+ line_assembly_enabled?: boolean;
2835
2839
  company_id: string;
2836
2840
  company_name: string;
2837
2841
  date: string;
@@ -2852,9 +2856,15 @@ interface HistoricWorkspaceMetrics {
2852
2856
  idle_time_hourly?: Record<string, string[]>;
2853
2857
  hourly_action_counts: number[];
2854
2858
  hourly_cycle_times?: number[];
2859
+ cycle_completion_clip_count?: number | null;
2860
+ cycle_time_data_status?: 'available' | 'missing_clips' | null;
2861
+ cycle_time_timezone?: string | null;
2855
2862
  shift_start: string;
2856
2863
  shift_end: string;
2857
2864
  action_name: string;
2865
+ action_type?: 'assembly' | 'output' | null;
2866
+ action_family?: 'assembly' | 'output' | 'other' | null;
2867
+ action_display_name?: string | null;
2858
2868
  monitoring_mode?: 'output' | 'uptime';
2859
2869
  workspace_rank: number;
2860
2870
  total_workspaces: number;
@@ -7193,7 +7203,7 @@ interface CycleTimeChartProps {
7193
7203
  declare const CycleTimeChart: React__default.NamedExoticComponent<CycleTimeChartProps>;
7194
7204
 
7195
7205
  interface CycleTimeOverTimeChartProps {
7196
- data: number[];
7206
+ data: Array<number | null>;
7197
7207
  idealCycleTime: number;
7198
7208
  shiftStart: string;
7199
7209
  shiftEnd?: string;
@@ -7201,7 +7211,7 @@ interface CycleTimeOverTimeChartProps {
7201
7211
  datasetKey?: string;
7202
7212
  className?: string;
7203
7213
  showIdleTime?: boolean;
7204
- idleTimeData?: number[];
7214
+ idleTimeData?: Array<number | null>;
7205
7215
  }
7206
7216
  declare const CycleTimeOverTimeChart: React__default.FC<CycleTimeOverTimeChartProps>;
7207
7217
 
package/dist/index.d.ts CHANGED
@@ -124,6 +124,9 @@ interface WorkspaceDetailedMetrics {
124
124
  total_actions: number;
125
125
  hourly_action_counts: number[];
126
126
  hourly_cycle_times?: number[];
127
+ cycle_completion_clip_count?: number | null;
128
+ cycle_time_data_status?: 'available' | 'missing_clips' | null;
129
+ cycle_time_timezone?: string | null;
127
130
  workspace_rank: number;
128
131
  total_workspaces: number;
129
132
  ideal_output_until_now: number;
@@ -2832,6 +2835,7 @@ interface HistoricWorkspaceMetrics {
2832
2835
  workspace_name: string;
2833
2836
  line_id: string;
2834
2837
  line_name: string;
2838
+ line_assembly_enabled?: boolean;
2835
2839
  company_id: string;
2836
2840
  company_name: string;
2837
2841
  date: string;
@@ -2852,9 +2856,15 @@ interface HistoricWorkspaceMetrics {
2852
2856
  idle_time_hourly?: Record<string, string[]>;
2853
2857
  hourly_action_counts: number[];
2854
2858
  hourly_cycle_times?: number[];
2859
+ cycle_completion_clip_count?: number | null;
2860
+ cycle_time_data_status?: 'available' | 'missing_clips' | null;
2861
+ cycle_time_timezone?: string | null;
2855
2862
  shift_start: string;
2856
2863
  shift_end: string;
2857
2864
  action_name: string;
2865
+ action_type?: 'assembly' | 'output' | null;
2866
+ action_family?: 'assembly' | 'output' | 'other' | null;
2867
+ action_display_name?: string | null;
2858
2868
  monitoring_mode?: 'output' | 'uptime';
2859
2869
  workspace_rank: number;
2860
2870
  total_workspaces: number;
@@ -7193,7 +7203,7 @@ interface CycleTimeChartProps {
7193
7203
  declare const CycleTimeChart: React__default.NamedExoticComponent<CycleTimeChartProps>;
7194
7204
 
7195
7205
  interface CycleTimeOverTimeChartProps {
7196
- data: number[];
7206
+ data: Array<number | null>;
7197
7207
  idealCycleTime: number;
7198
7208
  shiftStart: string;
7199
7209
  shiftEnd?: string;
@@ -7201,7 +7211,7 @@ interface CycleTimeOverTimeChartProps {
7201
7211
  datasetKey?: string;
7202
7212
  className?: string;
7203
7213
  showIdleTime?: boolean;
7204
- idleTimeData?: number[];
7214
+ idleTimeData?: Array<number | null>;
7205
7215
  }
7206
7216
  declare const CycleTimeOverTimeChart: React__default.FC<CycleTimeOverTimeChartProps>;
7207
7217
 
package/dist/index.js CHANGED
@@ -11771,6 +11771,9 @@ var toWorkspaceDetailedMetrics = ({
11771
11771
  const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
11772
11772
  const outputDifference = totalActions - idealOutput;
11773
11773
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
11774
+ const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
11775
+ const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
11776
+ const cycleTimeTimezone = typeof data.cycle_time_timezone === "string" ? data.cycle_time_timezone : null;
11774
11777
  const totalWorkspacesValue = coerceNumber(
11775
11778
  data.total_workspaces ?? lineMetricsById?.[data.line_id || ""]?.total_workspaces ?? workspaceConfig.totalWorkspaces,
11776
11779
  0
@@ -11818,6 +11821,9 @@ var toWorkspaceDetailedMetrics = ({
11818
11821
  total_actions: totalActions,
11819
11822
  hourly_action_counts: hourlyActionCounts,
11820
11823
  hourly_cycle_times: hourlyCycleTimes,
11824
+ cycle_completion_clip_count: cycleCompletionClipCount,
11825
+ cycle_time_data_status: cycleTimeDataStatus,
11826
+ cycle_time_timezone: cycleTimeTimezone,
11821
11827
  workspace_rank: coerceNumber(data.workspace_rank, 0),
11822
11828
  total_workspaces: totalWorkspacesValue,
11823
11829
  ideal_output_until_now: idealOutput,
@@ -32698,16 +32704,162 @@ var CycleTimeOverTimeChart = ({
32698
32704
  return `${minutes} minutes ${seconds} seconds ago`;
32699
32705
  }
32700
32706
  };
32707
+ const getNumericValue = React141__namespace.default.useCallback((value) => typeof value === "number" && Number.isFinite(value) ? value : null, []);
32708
+ const renderChartTooltip = React141__namespace.default.useCallback((tooltipProps) => {
32709
+ const { active, payload } = tooltipProps;
32710
+ if (!active || !Array.isArray(payload) || payload.length === 0) {
32711
+ return null;
32712
+ }
32713
+ const visibleEntries = payload.filter((entry) => getNumericValue(entry.value) !== null);
32714
+ if (!visibleEntries.length) {
32715
+ return null;
32716
+ }
32717
+ return /* @__PURE__ */ jsxRuntime.jsxs(
32718
+ "div",
32719
+ {
32720
+ style: {
32721
+ backgroundColor: "white",
32722
+ border: "none",
32723
+ borderRadius: "8px",
32724
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
32725
+ padding: "8px 12px",
32726
+ fontSize: "13px"
32727
+ },
32728
+ children: [
32729
+ /* @__PURE__ */ jsxRuntime.jsx(
32730
+ "div",
32731
+ {
32732
+ style: {
32733
+ color: "#374151",
32734
+ fontWeight: 600,
32735
+ marginBottom: "4px"
32736
+ },
32737
+ children: payload[0]?.payload?.tooltip || ""
32738
+ }
32739
+ ),
32740
+ visibleEntries.map((entry) => {
32741
+ const numericValue = getNumericValue(entry.value);
32742
+ if (numericValue === null) {
32743
+ return null;
32744
+ }
32745
+ return /* @__PURE__ */ jsxRuntime.jsx(
32746
+ "div",
32747
+ {
32748
+ style: {
32749
+ color: "#4B5563",
32750
+ padding: "2px 0"
32751
+ },
32752
+ children: entry.name === "idleMinutes" ? `Idle Time: ${numericValue.toFixed(0)} minutes` : `Cycle Time: ${numericValue.toFixed(1)} seconds`
32753
+ },
32754
+ `${entry.name}-${numericValue}`
32755
+ );
32756
+ })
32757
+ ]
32758
+ }
32759
+ );
32760
+ }, [getNumericValue]);
32761
+ const renderCycleDot = React141__namespace.default.useCallback((props) => {
32762
+ const { cx: cx2, cy, payload } = props;
32763
+ const cycleTime = getNumericValue(payload?.cycleTime);
32764
+ if (cycleTime === null) {
32765
+ return /* @__PURE__ */ jsxRuntime.jsx("g", {});
32766
+ }
32767
+ return /* @__PURE__ */ jsxRuntime.jsx(
32768
+ "circle",
32769
+ {
32770
+ cx: cx2,
32771
+ cy,
32772
+ r: 4,
32773
+ fill: cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32774
+ stroke: "#fff",
32775
+ strokeWidth: 1,
32776
+ style: {
32777
+ filter: "brightness(1)",
32778
+ transition: "filter 0.3s ease, transform 0.3s ease",
32779
+ cursor: "pointer"
32780
+ },
32781
+ onMouseEnter: (e) => {
32782
+ const target = e.target;
32783
+ target.style.filter = "brightness(1.2)";
32784
+ target.style.transform = "scale(1.2)";
32785
+ },
32786
+ onMouseLeave: (e) => {
32787
+ const target = e.target;
32788
+ target.style.filter = "brightness(1)";
32789
+ target.style.transform = "scale(1)";
32790
+ }
32791
+ }
32792
+ );
32793
+ }, [getNumericValue, idealCycleTime]);
32794
+ const renderCycleActiveDot = React141__namespace.default.useCallback((props) => {
32795
+ const { cx: cx2, cy, payload } = props;
32796
+ const cycleTime = getNumericValue(payload?.cycleTime);
32797
+ if (cycleTime === null) {
32798
+ return /* @__PURE__ */ jsxRuntime.jsx("g", {});
32799
+ }
32800
+ return /* @__PURE__ */ jsxRuntime.jsx(
32801
+ "circle",
32802
+ {
32803
+ cx: cx2,
32804
+ cy,
32805
+ r: 6,
32806
+ fill: cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32807
+ stroke: "#fff",
32808
+ strokeWidth: 2,
32809
+ style: {
32810
+ filter: "drop-shadow(0 0 2px rgba(0,0,0,0.2))"
32811
+ }
32812
+ }
32813
+ );
32814
+ }, [getNumericValue, idealCycleTime]);
32815
+ const renderIdleDot = React141__namespace.default.useCallback((props) => {
32816
+ const { cx: cx2, cy, payload } = props;
32817
+ const idleMinutes = getNumericValue(payload?.idleMinutes);
32818
+ if (idleMinutes === null) {
32819
+ return /* @__PURE__ */ jsxRuntime.jsx("g", {});
32820
+ }
32821
+ return /* @__PURE__ */ jsxRuntime.jsx(
32822
+ "circle",
32823
+ {
32824
+ cx: cx2,
32825
+ cy,
32826
+ r: 4,
32827
+ fill: "#f59e0b",
32828
+ stroke: "#fff",
32829
+ strokeWidth: 1
32830
+ }
32831
+ );
32832
+ }, [getNumericValue]);
32833
+ const renderIdleActiveDot = React141__namespace.default.useCallback((props) => {
32834
+ const { cx: cx2, cy, payload } = props;
32835
+ const idleMinutes = getNumericValue(payload?.idleMinutes);
32836
+ if (idleMinutes === null) {
32837
+ return /* @__PURE__ */ jsxRuntime.jsx("g", {});
32838
+ }
32839
+ return /* @__PURE__ */ jsxRuntime.jsx(
32840
+ "circle",
32841
+ {
32842
+ cx: cx2,
32843
+ cy,
32844
+ r: 6,
32845
+ fill: "#f59e0b",
32846
+ stroke: "#fff",
32847
+ strokeWidth: 2
32848
+ }
32849
+ );
32850
+ }, [getNumericValue]);
32701
32851
  const chartData = React141__namespace.default.useMemo(() => Array.from({ length: DURATION }, (_, i) => {
32852
+ const cycleTime = getNumericValue(finalData[i]);
32853
+ const idleMinutes = showIdleTime ? getNumericValue(idleTimeData[i]) : null;
32702
32854
  return {
32703
32855
  timeIndex: i,
32704
32856
  label: formatTimeLabel(i),
32705
32857
  tooltip: formatTooltipTime(i),
32706
- cycleTime: finalData[i] || 0,
32707
- idleMinutes: showIdleTime && idleTimeData && idleTimeData[i] || 0,
32708
- color: (finalData[i] || 0) <= idealCycleTime ? "#00AB45" : "#E34329"
32858
+ cycleTime,
32859
+ idleMinutes,
32860
+ color: cycleTime !== null && cycleTime <= idealCycleTime ? "#00AB45" : "#E34329"
32709
32861
  };
32710
- }), [DURATION, finalData, showIdleTime, idleTimeData, idealCycleTime]);
32862
+ }), [DURATION, finalData, showIdleTime, idleTimeData, idealCycleTime, getNumericValue]);
32711
32863
  const renderLegend = () => {
32712
32864
  if (!showIdleTime) return null;
32713
32865
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-start text-[10px] font-bold text-gray-500 mb-6 tracking-[0.05em] gap-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -32794,36 +32946,7 @@ var CycleTimeOverTimeChart = ({
32794
32946
  recharts.Tooltip,
32795
32947
  {
32796
32948
  cursor: { stroke: "#E5E7EB", strokeWidth: 1 },
32797
- contentStyle: {
32798
- backgroundColor: "white",
32799
- border: "none",
32800
- borderRadius: "8px",
32801
- boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
32802
- padding: "8px 12px",
32803
- fontSize: "13px"
32804
- },
32805
- labelStyle: {
32806
- color: "#374151",
32807
- fontWeight: 600,
32808
- marginBottom: "4px"
32809
- },
32810
- itemStyle: {
32811
- color: "#4B5563",
32812
- padding: "2px 0"
32813
- },
32814
- labelFormatter: (label, payload) => {
32815
- if (payload && payload[0]) {
32816
- return payload[0].payload.tooltip;
32817
- }
32818
- return label;
32819
- },
32820
- formatter: (value, name) => {
32821
- const numValue = typeof value === "number" ? value : Number(value);
32822
- if (name === "idleMinutes") {
32823
- return [`${numValue.toFixed(0)} minutes`, "Idle Time"];
32824
- }
32825
- return [`${numValue.toFixed(1)} seconds`, "Cycle Time"];
32826
- },
32949
+ content: renderChartTooltip,
32827
32950
  animationDuration: 200
32828
32951
  }
32829
32952
  ),
@@ -32852,52 +32975,9 @@ var CycleTimeOverTimeChart = ({
32852
32975
  dataKey: "cycleTime",
32853
32976
  stroke: "#3B82F6",
32854
32977
  strokeWidth: 2,
32855
- dot: (props) => {
32856
- const { cx: cx2, cy, payload } = props;
32857
- return /* @__PURE__ */ jsxRuntime.jsx(
32858
- "circle",
32859
- {
32860
- cx: cx2,
32861
- cy,
32862
- r: 4,
32863
- fill: payload.cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32864
- stroke: "#fff",
32865
- strokeWidth: 1,
32866
- style: {
32867
- filter: "brightness(1)",
32868
- transition: "filter 0.3s ease, transform 0.3s ease",
32869
- cursor: "pointer"
32870
- },
32871
- onMouseEnter: (e) => {
32872
- const target = e.target;
32873
- target.style.filter = "brightness(1.2)";
32874
- target.style.transform = "scale(1.2)";
32875
- },
32876
- onMouseLeave: (e) => {
32877
- const target = e.target;
32878
- target.style.filter = "brightness(1)";
32879
- target.style.transform = "scale(1)";
32880
- }
32881
- }
32882
- );
32883
- },
32884
- activeDot: (props) => {
32885
- const { cx: cx2, cy, payload } = props;
32886
- return /* @__PURE__ */ jsxRuntime.jsx(
32887
- "circle",
32888
- {
32889
- cx: cx2,
32890
- cy,
32891
- r: 6,
32892
- fill: payload.cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32893
- stroke: "#fff",
32894
- strokeWidth: 2,
32895
- style: {
32896
- filter: "drop-shadow(0 0 2px rgba(0,0,0,0.2))"
32897
- }
32898
- }
32899
- );
32900
- },
32978
+ connectNulls: false,
32979
+ dot: renderCycleDot,
32980
+ activeDot: renderCycleActiveDot,
32901
32981
  isAnimationActive: shouldAnimate,
32902
32982
  animationBegin: 0,
32903
32983
  animationDuration: 1200,
@@ -32915,8 +32995,9 @@ var CycleTimeOverTimeChart = ({
32915
32995
  stroke: "#f59e0b",
32916
32996
  strokeWidth: 2,
32917
32997
  strokeDasharray: "4 4",
32918
- dot: { r: 4, fill: "#f59e0b", stroke: "#fff", strokeWidth: 1 },
32919
- activeDot: { r: 6, fill: "#f59e0b", stroke: "#fff", strokeWidth: 2 },
32998
+ connectNulls: false,
32999
+ dot: renderIdleDot,
33000
+ activeDot: renderIdleActiveDot,
32920
33001
  isAnimationActive: shouldAnimate,
32921
33002
  animationBegin: 0,
32922
33003
  animationDuration: 1200,
@@ -34644,8 +34725,20 @@ var CardFooter2 = (props) => {
34644
34725
  return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardFooter, { ...props });
34645
34726
  };
34646
34727
 
34728
+ // src/lib/utils/workspaceDetailCycleTime.ts
34729
+ var resolveWorkspaceDetailActionFamily = (workspace) => {
34730
+ if (workspace?.action_family === "assembly" || workspace?.action_family === "output" || workspace?.action_family === "other") {
34731
+ return workspace.action_family;
34732
+ }
34733
+ if (workspace?.action_type === "assembly" || workspace?.action_type === "output") {
34734
+ return workspace.action_type;
34735
+ }
34736
+ return null;
34737
+ };
34738
+ var shouldUseAssemblyCycleTimeLayout = (workspace) => workspace?.line_assembly_enabled === true && resolveWorkspaceDetailActionFamily(workspace) === "assembly";
34739
+
34647
34740
  // src/components/dashboard/workspace/workspaceDetailCardRules.ts
34648
- var shouldHideWorkspaceEfficiencyCard = (workspace) => workspace?.line_assembly_enabled === true && workspace?.action_type === "assembly";
34741
+ var shouldHideWorkspaceEfficiencyCard = (workspace) => shouldUseAssemblyCycleTimeLayout(workspace);
34649
34742
  var WorkspaceMetricCardsImpl = ({
34650
34743
  workspace,
34651
34744
  className,
@@ -35354,6 +35447,37 @@ var getShiftElapsedMinutes = ({
35354
35447
  const elapsed = dateFns.differenceInMinutes(now4, shiftStartDate);
35355
35448
  return Math.min(Math.max(elapsed, 0), shiftMinutes);
35356
35449
  };
35450
+ var maskFutureHourlySeries = ({
35451
+ data,
35452
+ shiftStart,
35453
+ shiftEnd,
35454
+ shiftDate,
35455
+ timezone,
35456
+ now: now4 = /* @__PURE__ */ new Date()
35457
+ }) => {
35458
+ if (!Array.isArray(data)) {
35459
+ return [];
35460
+ }
35461
+ const normalizedData = data.map((value) => typeof value === "number" && Number.isFinite(value) ? value : null);
35462
+ if (!normalizedData.length) {
35463
+ return normalizedData;
35464
+ }
35465
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35466
+ const elapsedMinutes = getShiftElapsedMinutes({
35467
+ shiftStart,
35468
+ shiftEnd,
35469
+ shiftDate,
35470
+ timezone,
35471
+ now: now4
35472
+ });
35473
+ if (shiftMinutes === null || elapsedMinutes === null || elapsedMinutes >= shiftMinutes) {
35474
+ return normalizedData;
35475
+ }
35476
+ return normalizedData.map((value, index) => {
35477
+ const slotStartMinutes = index * 60;
35478
+ return slotStartMinutes > elapsedMinutes ? null : value;
35479
+ });
35480
+ };
35357
35481
  var buildUptimeSeries = ({
35358
35482
  idleTimeHourly,
35359
35483
  shiftStart,
@@ -48892,7 +49016,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
48892
49016
  setIsGenerating(true);
48893
49017
  try {
48894
49018
  const isUptimeMode = workspace.monitoring_mode === "uptime";
48895
- const isAssemblyCycleMode = !isUptimeMode && workspace.line_assembly_enabled === true && workspace.action_type === "assembly";
49019
+ const isAssemblyCycleMode = !isUptimeMode && shouldUseAssemblyCycleTimeLayout(workspace);
48896
49020
  const shiftMinutes = getShiftDurationMinutes(workspace.shift_start, workspace.shift_end);
48897
49021
  const shiftSeconds = shiftMinutes ? shiftMinutes * 60 : 0;
48898
49022
  const idleSeconds = Math.max(workspace.idle_time || 0, 0);
@@ -68063,8 +68187,74 @@ var TargetsView = ({
68063
68187
  var TargetsViewWithDisplayNames = withAllWorkspaceDisplayNames(TargetsView);
68064
68188
  var TargetsView_default = TargetsViewWithDisplayNames;
68065
68189
  var AuthenticatedTargetsView = withAuth(React141__namespace.default.memo(TargetsViewWithDisplayNames));
68190
+ function useTimezone(options = {}) {
68191
+ const dashboardConfig = useDashboardConfig();
68192
+ const workspaceConfig = useWorkspaceConfig();
68193
+ const defaultTimezone = dashboardConfig?.dateTimeConfig?.defaultTimezone || "Asia/Kolkata";
68194
+ const [timezone, setTimezone] = React141.useState(defaultTimezone);
68195
+ const [isLoading, setIsLoading] = React141.useState(true);
68196
+ const [error, setError] = React141.useState(null);
68197
+ const fetchTimezone = React141.useCallback(async () => {
68198
+ setIsLoading(true);
68199
+ setError(null);
68200
+ try {
68201
+ let fetchedTimezone = defaultTimezone;
68202
+ if (options.lineId) {
68203
+ fetchedTimezone = await timezoneService.getTimezoneForLine(options.lineId, defaultTimezone);
68204
+ } else if (options.workspaceId || workspaceConfig && "id" in workspaceConfig) {
68205
+ const wsId = options.workspaceId || (workspaceConfig && "id" in workspaceConfig ? workspaceConfig.id : void 0);
68206
+ if (wsId) {
68207
+ fetchedTimezone = await timezoneService.getTimezoneForWorkspace(wsId, defaultTimezone);
68208
+ }
68209
+ } else if (options.companyId || dashboardConfig && "company" in dashboardConfig) {
68210
+ const compId = options.companyId || (dashboardConfig && "company" in dashboardConfig ? dashboardConfig.company?.id : void 0);
68211
+ if (compId) {
68212
+ fetchedTimezone = await timezoneService.getTimezoneForCompany(compId, defaultTimezone);
68213
+ }
68214
+ }
68215
+ setTimezone(fetchedTimezone);
68216
+ } catch (err) {
68217
+ console.error("Error fetching timezone:", err);
68218
+ setError(err instanceof Error ? err : new Error("Failed to fetch timezone"));
68219
+ setTimezone(defaultTimezone);
68220
+ } finally {
68221
+ setIsLoading(false);
68222
+ }
68223
+ }, [
68224
+ options.lineId,
68225
+ options.workspaceId,
68226
+ options.companyId,
68227
+ workspaceConfig,
68228
+ dashboardConfig,
68229
+ defaultTimezone
68230
+ ]);
68231
+ React141.useEffect(() => {
68232
+ fetchTimezone();
68233
+ }, [fetchTimezone]);
68234
+ return {
68235
+ timezone,
68236
+ isLoading,
68237
+ error,
68238
+ refetch: fetchTimezone
68239
+ };
68240
+ }
68066
68241
 
68067
68242
  // src/views/workspace-detail-view.utils.ts
68243
+ var getWorkspaceCycleTimePresentation = ({
68244
+ workspace,
68245
+ showCycleTimeChart
68246
+ }) => {
68247
+ if (workspace?.monitoring_mode === "uptime") {
68248
+ return "output";
68249
+ }
68250
+ if (showCycleTimeChart === false) {
68251
+ return "output";
68252
+ }
68253
+ if (!shouldUseAssemblyCycleTimeLayout(workspace)) {
68254
+ return "output";
68255
+ }
68256
+ return workspace?.cycle_time_data_status === "missing_clips" ? "cycle_unavailable" : "cycle_chart";
68257
+ };
68068
68258
  var formatDateInTimezone = (date = /* @__PURE__ */ new Date(), timezone, options) => {
68069
68259
  const defaultOptions = {
68070
68260
  day: "numeric",
@@ -68448,6 +68638,7 @@ var WorkspaceDetailView = ({
68448
68638
  date: cachedOverviewMetrics.date,
68449
68639
  shift_id: cachedOverviewMetrics.shift_id,
68450
68640
  action_name: "",
68641
+ action_family: cachedOverviewMetrics.action_family ?? null,
68451
68642
  action_type: cachedOverviewMetrics.action_type ?? null,
68452
68643
  monitoring_mode: cachedOverviewMetrics.monitoring_mode ?? "output",
68453
68644
  shift_start: shiftDefinition?.startTime || "",
@@ -68471,6 +68662,11 @@ var WorkspaceDetailView = ({
68471
68662
  };
68472
68663
  }, [cachedOverviewMetrics, shiftConfig?.shifts]);
68473
68664
  const workspace = (isHistoricView ? historicMetrics : liveMetrics) || cachedDetailedMetrics || overviewFallback;
68665
+ const { timezone: cycleTimeTimezone } = useTimezone({
68666
+ lineId: effectiveLineId || workspace?.line_id || void 0,
68667
+ workspaceId: workspaceId || void 0
68668
+ });
68669
+ const effectiveCycleTimeTimezone = cycleTimeTimezone || timezone;
68474
68670
  const detailedWorkspaceMetrics = (isHistoricView ? historicMetrics : liveMetrics) || cachedDetailedMetrics;
68475
68671
  const cycleTimeChartData = React141.useMemo(
68476
68672
  () => Array.isArray(workspace?.hourly_cycle_times) ? workspace.hourly_cycle_times.map((value) => {
@@ -68479,12 +68675,31 @@ var WorkspaceDetailView = ({
68479
68675
  }) : [],
68480
68676
  [workspace?.hourly_cycle_times]
68481
68677
  );
68678
+ const maskedCycleTimeChartData = React141.useMemo(
68679
+ () => maskFutureHourlySeries({
68680
+ data: cycleTimeChartData,
68681
+ shiftStart: workspace?.shift_start,
68682
+ shiftEnd: workspace?.shift_end,
68683
+ shiftDate: workspace?.date || date || calculatedOperationalDate || null,
68684
+ timezone: effectiveCycleTimeTimezone
68685
+ }),
68686
+ [
68687
+ cycleTimeChartData,
68688
+ workspace?.shift_start,
68689
+ workspace?.shift_end,
68690
+ workspace?.date,
68691
+ date,
68692
+ calculatedOperationalDate,
68693
+ effectiveCycleTimeTimezone
68694
+ ]
68695
+ );
68482
68696
  const cycleTimeDatasetKey = React141.useMemo(
68483
68697
  () => [
68484
68698
  workspace?.workspace_id || workspaceId || "workspace",
68485
68699
  date || workspace?.date || "live",
68486
68700
  parsedShiftId ?? workspace?.shift_id ?? "current",
68487
- "hourly"
68701
+ "hourly",
68702
+ "backend"
68488
68703
  ].join(":"),
68489
68704
  [workspace?.workspace_id, workspaceId, date, workspace?.date, parsedShiftId, workspace?.shift_id]
68490
68705
  );
@@ -68729,14 +68944,28 @@ var WorkspaceDetailView = ({
68729
68944
  return filterDataByDateKeyRange(monthlyData, range);
68730
68945
  }, [monthlyData, range]);
68731
68946
  const formattedWorkspaceName = displayName || formatWorkspaceName3(workspace?.workspace_name || "", effectiveLineId);
68732
- const workspaceActionType = workspace && "action_type" in workspace ? workspace.action_type : void 0;
68733
- const workspaceAssemblyEnabled = workspace && "line_assembly_enabled" in workspace ? workspace.line_assembly_enabled === true : false;
68734
- const isAssemblyWorkspace = workspaceActionType === "assembly" || workspaceAssemblyEnabled;
68735
- const shouldShowCycleTimeChart = !isUptimeMode && (showCycleTimeChart ?? isAssemblyWorkspace);
68947
+ const workspaceCycleTimeEligibility = workspace ? {
68948
+ line_assembly_enabled: workspace.line_assembly_enabled,
68949
+ action_family: workspace.action_family,
68950
+ action_type: workspace.action_type
68951
+ } : null;
68952
+ const isAssemblyWorkspace = shouldUseAssemblyCycleTimeLayout(workspaceCycleTimeEligibility);
68953
+ const cycleTimePresentation = getWorkspaceCycleTimePresentation({
68954
+ workspace: workspace ? {
68955
+ monitoring_mode: workspace.monitoring_mode,
68956
+ line_assembly_enabled: workspace.line_assembly_enabled,
68957
+ action_family: workspace.action_family,
68958
+ action_type: workspace.action_type,
68959
+ cycle_time_data_status: workspace.cycle_time_data_status
68960
+ } : null,
68961
+ showCycleTimeChart
68962
+ });
68963
+ const shouldShowCycleTimeChart = cycleTimePresentation !== "output";
68964
+ const shouldShowCycleTimeUnavailableState = cycleTimePresentation === "cycle_unavailable";
68736
68965
  const showIdleBreakdownChart = !shouldShowCycleTimeChart && idleTimeVlmEnabled;
68737
68966
  const idleClipDate = date || workspace?.date || calculatedOperationalDate || getOperationalDate(timezone);
68738
68967
  const idleClipShiftId = parsedShiftId ?? workspace?.shift_id;
68739
- const hourlyIdleMinutes = React141.useMemo(() => {
68968
+ const rawHourlyIdleMinutes = React141.useMemo(() => {
68740
68969
  if (!shouldShowCycleTimeChart || !workspace?.idle_time_hourly || !workspace?.shift_start) return [];
68741
68970
  const parseTimeToMinutes3 = (time2) => {
68742
68971
  const [h, m] = time2.split(":").map(Number);
@@ -68770,6 +68999,30 @@ var WorkspaceDetailView = ({
68770
68999
  }
68771
69000
  return result;
68772
69001
  }, [shouldShowCycleTimeChart, workspace?.idle_time_hourly, workspace?.shift_start, workspace?.shift_end]);
69002
+ const hourlyIdleMinutes = React141.useMemo(
69003
+ () => maskFutureHourlySeries({
69004
+ data: rawHourlyIdleMinutes,
69005
+ shiftStart: workspace?.shift_start,
69006
+ shiftEnd: workspace?.shift_end,
69007
+ shiftDate: idleClipDate,
69008
+ timezone: effectiveCycleTimeTimezone
69009
+ }),
69010
+ [
69011
+ rawHourlyIdleMinutes,
69012
+ workspace?.shift_start,
69013
+ workspace?.shift_end,
69014
+ idleClipDate,
69015
+ effectiveCycleTimeTimezone
69016
+ ]
69017
+ );
69018
+ const cycleTimeUnavailableView = React141.useMemo(() => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full h-full rounded-lg border border-amber-200 bg-amber-50/70 px-6 py-5 text-center flex flex-col items-center justify-center", children: [
69019
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-base font-semibold text-amber-900", children: "Cycle data unavailable" }),
69020
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 max-w-md text-sm text-amber-800", children: "This workstation has cycle-time metrics for the selected shift, but no matching `cycle_completion` clips were found for the chart." }),
69021
+ typeof workspace?.cycle_completion_clip_count === "number" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-3 text-xs font-medium uppercase tracking-[0.08em] text-amber-700", children: [
69022
+ "matched cycle clips: ",
69023
+ workspace.cycle_completion_clip_count
69024
+ ] })
69025
+ ] }), [workspace?.cycle_completion_clip_count]);
68773
69026
  const shiftDurationMinutes = React141.useMemo(
68774
69027
  () => getShiftDurationMinutes(workspace?.shift_start, workspace?.shift_end),
68775
69028
  [workspace?.shift_start, workspace?.shift_end]
@@ -69266,7 +69519,7 @@ var WorkspaceDetailView = ({
69266
69519
  children: [
69267
69520
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center mb-4", children: [
69268
69521
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle time trend" : "Hourly Output" }),
69269
- !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsx(
69522
+ !isUptimeMode && !shouldShowCycleTimeUnavailableState && /* @__PURE__ */ jsxRuntime.jsx(
69270
69523
  "button",
69271
69524
  {
69272
69525
  onClick: () => setShowChartIdleTime(!showChartIdleTime),
@@ -69295,10 +69548,10 @@ var WorkspaceDetailView = ({
69295
69548
  timezone,
69296
69549
  elapsedMinutes: elapsedShiftMinutes
69297
69550
  }
69298
- ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
69551
+ ) : shouldShowCycleTimeUnavailableState ? cycleTimeUnavailableView : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
69299
69552
  CycleTimeOverTimeChart,
69300
69553
  {
69301
- data: cycleTimeChartData,
69554
+ data: maskedCycleTimeChartData,
69302
69555
  idealCycleTime: workspace.ideal_cycle_time || 0,
69303
69556
  shiftStart: workspace.shift_start || "",
69304
69557
  shiftEnd: workspace.shift_end || "",
@@ -69399,7 +69652,7 @@ var WorkspaceDetailView = ({
69399
69652
  children: [
69400
69653
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 mb-4 flex-none", children: [
69401
69654
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle time trend" : "Hourly Output" }),
69402
- !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsx(
69655
+ !isUptimeMode && !shouldShowCycleTimeUnavailableState && /* @__PURE__ */ jsxRuntime.jsx(
69403
69656
  "button",
69404
69657
  {
69405
69658
  onClick: () => setShowChartIdleTime(!showChartIdleTime),
@@ -69424,10 +69677,10 @@ var WorkspaceDetailView = ({
69424
69677
  timezone,
69425
69678
  elapsedMinutes: elapsedShiftMinutes
69426
69679
  }
69427
- ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
69680
+ ) : shouldShowCycleTimeUnavailableState ? cycleTimeUnavailableView : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
69428
69681
  CycleTimeOverTimeChart,
69429
69682
  {
69430
- data: cycleTimeChartData,
69683
+ data: maskedCycleTimeChartData,
69431
69684
  idealCycleTime: workspace.ideal_cycle_time || 0,
69432
69685
  shiftStart: workspace.shift_start || "",
69433
69686
  shiftEnd: workspace.shift_end || "",
package/dist/index.mjs CHANGED
@@ -11742,6 +11742,9 @@ var toWorkspaceDetailedMetrics = ({
11742
11742
  const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
11743
11743
  const outputDifference = totalActions - idealOutput;
11744
11744
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
11745
+ const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
11746
+ const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
11747
+ const cycleTimeTimezone = typeof data.cycle_time_timezone === "string" ? data.cycle_time_timezone : null;
11745
11748
  const totalWorkspacesValue = coerceNumber(
11746
11749
  data.total_workspaces ?? lineMetricsById?.[data.line_id || ""]?.total_workspaces ?? workspaceConfig.totalWorkspaces,
11747
11750
  0
@@ -11789,6 +11792,9 @@ var toWorkspaceDetailedMetrics = ({
11789
11792
  total_actions: totalActions,
11790
11793
  hourly_action_counts: hourlyActionCounts,
11791
11794
  hourly_cycle_times: hourlyCycleTimes,
11795
+ cycle_completion_clip_count: cycleCompletionClipCount,
11796
+ cycle_time_data_status: cycleTimeDataStatus,
11797
+ cycle_time_timezone: cycleTimeTimezone,
11792
11798
  workspace_rank: coerceNumber(data.workspace_rank, 0),
11793
11799
  total_workspaces: totalWorkspacesValue,
11794
11800
  ideal_output_until_now: idealOutput,
@@ -32669,16 +32675,162 @@ var CycleTimeOverTimeChart = ({
32669
32675
  return `${minutes} minutes ${seconds} seconds ago`;
32670
32676
  }
32671
32677
  };
32678
+ const getNumericValue = React141__default.useCallback((value) => typeof value === "number" && Number.isFinite(value) ? value : null, []);
32679
+ const renderChartTooltip = React141__default.useCallback((tooltipProps) => {
32680
+ const { active, payload } = tooltipProps;
32681
+ if (!active || !Array.isArray(payload) || payload.length === 0) {
32682
+ return null;
32683
+ }
32684
+ const visibleEntries = payload.filter((entry) => getNumericValue(entry.value) !== null);
32685
+ if (!visibleEntries.length) {
32686
+ return null;
32687
+ }
32688
+ return /* @__PURE__ */ jsxs(
32689
+ "div",
32690
+ {
32691
+ style: {
32692
+ backgroundColor: "white",
32693
+ border: "none",
32694
+ borderRadius: "8px",
32695
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
32696
+ padding: "8px 12px",
32697
+ fontSize: "13px"
32698
+ },
32699
+ children: [
32700
+ /* @__PURE__ */ jsx(
32701
+ "div",
32702
+ {
32703
+ style: {
32704
+ color: "#374151",
32705
+ fontWeight: 600,
32706
+ marginBottom: "4px"
32707
+ },
32708
+ children: payload[0]?.payload?.tooltip || ""
32709
+ }
32710
+ ),
32711
+ visibleEntries.map((entry) => {
32712
+ const numericValue = getNumericValue(entry.value);
32713
+ if (numericValue === null) {
32714
+ return null;
32715
+ }
32716
+ return /* @__PURE__ */ jsx(
32717
+ "div",
32718
+ {
32719
+ style: {
32720
+ color: "#4B5563",
32721
+ padding: "2px 0"
32722
+ },
32723
+ children: entry.name === "idleMinutes" ? `Idle Time: ${numericValue.toFixed(0)} minutes` : `Cycle Time: ${numericValue.toFixed(1)} seconds`
32724
+ },
32725
+ `${entry.name}-${numericValue}`
32726
+ );
32727
+ })
32728
+ ]
32729
+ }
32730
+ );
32731
+ }, [getNumericValue]);
32732
+ const renderCycleDot = React141__default.useCallback((props) => {
32733
+ const { cx: cx2, cy, payload } = props;
32734
+ const cycleTime = getNumericValue(payload?.cycleTime);
32735
+ if (cycleTime === null) {
32736
+ return /* @__PURE__ */ jsx("g", {});
32737
+ }
32738
+ return /* @__PURE__ */ jsx(
32739
+ "circle",
32740
+ {
32741
+ cx: cx2,
32742
+ cy,
32743
+ r: 4,
32744
+ fill: cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32745
+ stroke: "#fff",
32746
+ strokeWidth: 1,
32747
+ style: {
32748
+ filter: "brightness(1)",
32749
+ transition: "filter 0.3s ease, transform 0.3s ease",
32750
+ cursor: "pointer"
32751
+ },
32752
+ onMouseEnter: (e) => {
32753
+ const target = e.target;
32754
+ target.style.filter = "brightness(1.2)";
32755
+ target.style.transform = "scale(1.2)";
32756
+ },
32757
+ onMouseLeave: (e) => {
32758
+ const target = e.target;
32759
+ target.style.filter = "brightness(1)";
32760
+ target.style.transform = "scale(1)";
32761
+ }
32762
+ }
32763
+ );
32764
+ }, [getNumericValue, idealCycleTime]);
32765
+ const renderCycleActiveDot = React141__default.useCallback((props) => {
32766
+ const { cx: cx2, cy, payload } = props;
32767
+ const cycleTime = getNumericValue(payload?.cycleTime);
32768
+ if (cycleTime === null) {
32769
+ return /* @__PURE__ */ jsx("g", {});
32770
+ }
32771
+ return /* @__PURE__ */ jsx(
32772
+ "circle",
32773
+ {
32774
+ cx: cx2,
32775
+ cy,
32776
+ r: 6,
32777
+ fill: cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32778
+ stroke: "#fff",
32779
+ strokeWidth: 2,
32780
+ style: {
32781
+ filter: "drop-shadow(0 0 2px rgba(0,0,0,0.2))"
32782
+ }
32783
+ }
32784
+ );
32785
+ }, [getNumericValue, idealCycleTime]);
32786
+ const renderIdleDot = React141__default.useCallback((props) => {
32787
+ const { cx: cx2, cy, payload } = props;
32788
+ const idleMinutes = getNumericValue(payload?.idleMinutes);
32789
+ if (idleMinutes === null) {
32790
+ return /* @__PURE__ */ jsx("g", {});
32791
+ }
32792
+ return /* @__PURE__ */ jsx(
32793
+ "circle",
32794
+ {
32795
+ cx: cx2,
32796
+ cy,
32797
+ r: 4,
32798
+ fill: "#f59e0b",
32799
+ stroke: "#fff",
32800
+ strokeWidth: 1
32801
+ }
32802
+ );
32803
+ }, [getNumericValue]);
32804
+ const renderIdleActiveDot = React141__default.useCallback((props) => {
32805
+ const { cx: cx2, cy, payload } = props;
32806
+ const idleMinutes = getNumericValue(payload?.idleMinutes);
32807
+ if (idleMinutes === null) {
32808
+ return /* @__PURE__ */ jsx("g", {});
32809
+ }
32810
+ return /* @__PURE__ */ jsx(
32811
+ "circle",
32812
+ {
32813
+ cx: cx2,
32814
+ cy,
32815
+ r: 6,
32816
+ fill: "#f59e0b",
32817
+ stroke: "#fff",
32818
+ strokeWidth: 2
32819
+ }
32820
+ );
32821
+ }, [getNumericValue]);
32672
32822
  const chartData = React141__default.useMemo(() => Array.from({ length: DURATION }, (_, i) => {
32823
+ const cycleTime = getNumericValue(finalData[i]);
32824
+ const idleMinutes = showIdleTime ? getNumericValue(idleTimeData[i]) : null;
32673
32825
  return {
32674
32826
  timeIndex: i,
32675
32827
  label: formatTimeLabel(i),
32676
32828
  tooltip: formatTooltipTime(i),
32677
- cycleTime: finalData[i] || 0,
32678
- idleMinutes: showIdleTime && idleTimeData && idleTimeData[i] || 0,
32679
- color: (finalData[i] || 0) <= idealCycleTime ? "#00AB45" : "#E34329"
32829
+ cycleTime,
32830
+ idleMinutes,
32831
+ color: cycleTime !== null && cycleTime <= idealCycleTime ? "#00AB45" : "#E34329"
32680
32832
  };
32681
- }), [DURATION, finalData, showIdleTime, idleTimeData, idealCycleTime]);
32833
+ }), [DURATION, finalData, showIdleTime, idleTimeData, idealCycleTime, getNumericValue]);
32682
32834
  const renderLegend = () => {
32683
32835
  if (!showIdleTime) return null;
32684
32836
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-start text-[10px] font-bold text-gray-500 mb-6 tracking-[0.05em] gap-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -32765,36 +32917,7 @@ var CycleTimeOverTimeChart = ({
32765
32917
  Tooltip,
32766
32918
  {
32767
32919
  cursor: { stroke: "#E5E7EB", strokeWidth: 1 },
32768
- contentStyle: {
32769
- backgroundColor: "white",
32770
- border: "none",
32771
- borderRadius: "8px",
32772
- boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
32773
- padding: "8px 12px",
32774
- fontSize: "13px"
32775
- },
32776
- labelStyle: {
32777
- color: "#374151",
32778
- fontWeight: 600,
32779
- marginBottom: "4px"
32780
- },
32781
- itemStyle: {
32782
- color: "#4B5563",
32783
- padding: "2px 0"
32784
- },
32785
- labelFormatter: (label, payload) => {
32786
- if (payload && payload[0]) {
32787
- return payload[0].payload.tooltip;
32788
- }
32789
- return label;
32790
- },
32791
- formatter: (value, name) => {
32792
- const numValue = typeof value === "number" ? value : Number(value);
32793
- if (name === "idleMinutes") {
32794
- return [`${numValue.toFixed(0)} minutes`, "Idle Time"];
32795
- }
32796
- return [`${numValue.toFixed(1)} seconds`, "Cycle Time"];
32797
- },
32920
+ content: renderChartTooltip,
32798
32921
  animationDuration: 200
32799
32922
  }
32800
32923
  ),
@@ -32823,52 +32946,9 @@ var CycleTimeOverTimeChart = ({
32823
32946
  dataKey: "cycleTime",
32824
32947
  stroke: "#3B82F6",
32825
32948
  strokeWidth: 2,
32826
- dot: (props) => {
32827
- const { cx: cx2, cy, payload } = props;
32828
- return /* @__PURE__ */ jsx(
32829
- "circle",
32830
- {
32831
- cx: cx2,
32832
- cy,
32833
- r: 4,
32834
- fill: payload.cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32835
- stroke: "#fff",
32836
- strokeWidth: 1,
32837
- style: {
32838
- filter: "brightness(1)",
32839
- transition: "filter 0.3s ease, transform 0.3s ease",
32840
- cursor: "pointer"
32841
- },
32842
- onMouseEnter: (e) => {
32843
- const target = e.target;
32844
- target.style.filter = "brightness(1.2)";
32845
- target.style.transform = "scale(1.2)";
32846
- },
32847
- onMouseLeave: (e) => {
32848
- const target = e.target;
32849
- target.style.filter = "brightness(1)";
32850
- target.style.transform = "scale(1)";
32851
- }
32852
- }
32853
- );
32854
- },
32855
- activeDot: (props) => {
32856
- const { cx: cx2, cy, payload } = props;
32857
- return /* @__PURE__ */ jsx(
32858
- "circle",
32859
- {
32860
- cx: cx2,
32861
- cy,
32862
- r: 6,
32863
- fill: payload.cycleTime <= idealCycleTime ? "#00AB45" : "#E34329",
32864
- stroke: "#fff",
32865
- strokeWidth: 2,
32866
- style: {
32867
- filter: "drop-shadow(0 0 2px rgba(0,0,0,0.2))"
32868
- }
32869
- }
32870
- );
32871
- },
32949
+ connectNulls: false,
32950
+ dot: renderCycleDot,
32951
+ activeDot: renderCycleActiveDot,
32872
32952
  isAnimationActive: shouldAnimate,
32873
32953
  animationBegin: 0,
32874
32954
  animationDuration: 1200,
@@ -32886,8 +32966,9 @@ var CycleTimeOverTimeChart = ({
32886
32966
  stroke: "#f59e0b",
32887
32967
  strokeWidth: 2,
32888
32968
  strokeDasharray: "4 4",
32889
- dot: { r: 4, fill: "#f59e0b", stroke: "#fff", strokeWidth: 1 },
32890
- activeDot: { r: 6, fill: "#f59e0b", stroke: "#fff", strokeWidth: 2 },
32969
+ connectNulls: false,
32970
+ dot: renderIdleDot,
32971
+ activeDot: renderIdleActiveDot,
32891
32972
  isAnimationActive: shouldAnimate,
32892
32973
  animationBegin: 0,
32893
32974
  animationDuration: 1200,
@@ -34615,8 +34696,20 @@ var CardFooter2 = (props) => {
34615
34696
  return /* @__PURE__ */ jsx(RegisteredCardFooter, { ...props });
34616
34697
  };
34617
34698
 
34699
+ // src/lib/utils/workspaceDetailCycleTime.ts
34700
+ var resolveWorkspaceDetailActionFamily = (workspace) => {
34701
+ if (workspace?.action_family === "assembly" || workspace?.action_family === "output" || workspace?.action_family === "other") {
34702
+ return workspace.action_family;
34703
+ }
34704
+ if (workspace?.action_type === "assembly" || workspace?.action_type === "output") {
34705
+ return workspace.action_type;
34706
+ }
34707
+ return null;
34708
+ };
34709
+ var shouldUseAssemblyCycleTimeLayout = (workspace) => workspace?.line_assembly_enabled === true && resolveWorkspaceDetailActionFamily(workspace) === "assembly";
34710
+
34618
34711
  // src/components/dashboard/workspace/workspaceDetailCardRules.ts
34619
- var shouldHideWorkspaceEfficiencyCard = (workspace) => workspace?.line_assembly_enabled === true && workspace?.action_type === "assembly";
34712
+ var shouldHideWorkspaceEfficiencyCard = (workspace) => shouldUseAssemblyCycleTimeLayout(workspace);
34620
34713
  var WorkspaceMetricCardsImpl = ({
34621
34714
  workspace,
34622
34715
  className,
@@ -35325,6 +35418,37 @@ var getShiftElapsedMinutes = ({
35325
35418
  const elapsed = differenceInMinutes(now4, shiftStartDate);
35326
35419
  return Math.min(Math.max(elapsed, 0), shiftMinutes);
35327
35420
  };
35421
+ var maskFutureHourlySeries = ({
35422
+ data,
35423
+ shiftStart,
35424
+ shiftEnd,
35425
+ shiftDate,
35426
+ timezone,
35427
+ now: now4 = /* @__PURE__ */ new Date()
35428
+ }) => {
35429
+ if (!Array.isArray(data)) {
35430
+ return [];
35431
+ }
35432
+ const normalizedData = data.map((value) => typeof value === "number" && Number.isFinite(value) ? value : null);
35433
+ if (!normalizedData.length) {
35434
+ return normalizedData;
35435
+ }
35436
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35437
+ const elapsedMinutes = getShiftElapsedMinutes({
35438
+ shiftStart,
35439
+ shiftEnd,
35440
+ shiftDate,
35441
+ timezone,
35442
+ now: now4
35443
+ });
35444
+ if (shiftMinutes === null || elapsedMinutes === null || elapsedMinutes >= shiftMinutes) {
35445
+ return normalizedData;
35446
+ }
35447
+ return normalizedData.map((value, index) => {
35448
+ const slotStartMinutes = index * 60;
35449
+ return slotStartMinutes > elapsedMinutes ? null : value;
35450
+ });
35451
+ };
35328
35452
  var buildUptimeSeries = ({
35329
35453
  idleTimeHourly,
35330
35454
  shiftStart,
@@ -48863,7 +48987,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
48863
48987
  setIsGenerating(true);
48864
48988
  try {
48865
48989
  const isUptimeMode = workspace.monitoring_mode === "uptime";
48866
- const isAssemblyCycleMode = !isUptimeMode && workspace.line_assembly_enabled === true && workspace.action_type === "assembly";
48990
+ const isAssemblyCycleMode = !isUptimeMode && shouldUseAssemblyCycleTimeLayout(workspace);
48867
48991
  const shiftMinutes = getShiftDurationMinutes(workspace.shift_start, workspace.shift_end);
48868
48992
  const shiftSeconds = shiftMinutes ? shiftMinutes * 60 : 0;
48869
48993
  const idleSeconds = Math.max(workspace.idle_time || 0, 0);
@@ -68034,8 +68158,74 @@ var TargetsView = ({
68034
68158
  var TargetsViewWithDisplayNames = withAllWorkspaceDisplayNames(TargetsView);
68035
68159
  var TargetsView_default = TargetsViewWithDisplayNames;
68036
68160
  var AuthenticatedTargetsView = withAuth(React141__default.memo(TargetsViewWithDisplayNames));
68161
+ function useTimezone(options = {}) {
68162
+ const dashboardConfig = useDashboardConfig();
68163
+ const workspaceConfig = useWorkspaceConfig();
68164
+ const defaultTimezone = dashboardConfig?.dateTimeConfig?.defaultTimezone || "Asia/Kolkata";
68165
+ const [timezone, setTimezone] = useState(defaultTimezone);
68166
+ const [isLoading, setIsLoading] = useState(true);
68167
+ const [error, setError] = useState(null);
68168
+ const fetchTimezone = useCallback(async () => {
68169
+ setIsLoading(true);
68170
+ setError(null);
68171
+ try {
68172
+ let fetchedTimezone = defaultTimezone;
68173
+ if (options.lineId) {
68174
+ fetchedTimezone = await timezoneService.getTimezoneForLine(options.lineId, defaultTimezone);
68175
+ } else if (options.workspaceId || workspaceConfig && "id" in workspaceConfig) {
68176
+ const wsId = options.workspaceId || (workspaceConfig && "id" in workspaceConfig ? workspaceConfig.id : void 0);
68177
+ if (wsId) {
68178
+ fetchedTimezone = await timezoneService.getTimezoneForWorkspace(wsId, defaultTimezone);
68179
+ }
68180
+ } else if (options.companyId || dashboardConfig && "company" in dashboardConfig) {
68181
+ const compId = options.companyId || (dashboardConfig && "company" in dashboardConfig ? dashboardConfig.company?.id : void 0);
68182
+ if (compId) {
68183
+ fetchedTimezone = await timezoneService.getTimezoneForCompany(compId, defaultTimezone);
68184
+ }
68185
+ }
68186
+ setTimezone(fetchedTimezone);
68187
+ } catch (err) {
68188
+ console.error("Error fetching timezone:", err);
68189
+ setError(err instanceof Error ? err : new Error("Failed to fetch timezone"));
68190
+ setTimezone(defaultTimezone);
68191
+ } finally {
68192
+ setIsLoading(false);
68193
+ }
68194
+ }, [
68195
+ options.lineId,
68196
+ options.workspaceId,
68197
+ options.companyId,
68198
+ workspaceConfig,
68199
+ dashboardConfig,
68200
+ defaultTimezone
68201
+ ]);
68202
+ useEffect(() => {
68203
+ fetchTimezone();
68204
+ }, [fetchTimezone]);
68205
+ return {
68206
+ timezone,
68207
+ isLoading,
68208
+ error,
68209
+ refetch: fetchTimezone
68210
+ };
68211
+ }
68037
68212
 
68038
68213
  // src/views/workspace-detail-view.utils.ts
68214
+ var getWorkspaceCycleTimePresentation = ({
68215
+ workspace,
68216
+ showCycleTimeChart
68217
+ }) => {
68218
+ if (workspace?.monitoring_mode === "uptime") {
68219
+ return "output";
68220
+ }
68221
+ if (showCycleTimeChart === false) {
68222
+ return "output";
68223
+ }
68224
+ if (!shouldUseAssemblyCycleTimeLayout(workspace)) {
68225
+ return "output";
68226
+ }
68227
+ return workspace?.cycle_time_data_status === "missing_clips" ? "cycle_unavailable" : "cycle_chart";
68228
+ };
68039
68229
  var formatDateInTimezone = (date = /* @__PURE__ */ new Date(), timezone, options) => {
68040
68230
  const defaultOptions = {
68041
68231
  day: "numeric",
@@ -68419,6 +68609,7 @@ var WorkspaceDetailView = ({
68419
68609
  date: cachedOverviewMetrics.date,
68420
68610
  shift_id: cachedOverviewMetrics.shift_id,
68421
68611
  action_name: "",
68612
+ action_family: cachedOverviewMetrics.action_family ?? null,
68422
68613
  action_type: cachedOverviewMetrics.action_type ?? null,
68423
68614
  monitoring_mode: cachedOverviewMetrics.monitoring_mode ?? "output",
68424
68615
  shift_start: shiftDefinition?.startTime || "",
@@ -68442,6 +68633,11 @@ var WorkspaceDetailView = ({
68442
68633
  };
68443
68634
  }, [cachedOverviewMetrics, shiftConfig?.shifts]);
68444
68635
  const workspace = (isHistoricView ? historicMetrics : liveMetrics) || cachedDetailedMetrics || overviewFallback;
68636
+ const { timezone: cycleTimeTimezone } = useTimezone({
68637
+ lineId: effectiveLineId || workspace?.line_id || void 0,
68638
+ workspaceId: workspaceId || void 0
68639
+ });
68640
+ const effectiveCycleTimeTimezone = cycleTimeTimezone || timezone;
68445
68641
  const detailedWorkspaceMetrics = (isHistoricView ? historicMetrics : liveMetrics) || cachedDetailedMetrics;
68446
68642
  const cycleTimeChartData = useMemo(
68447
68643
  () => Array.isArray(workspace?.hourly_cycle_times) ? workspace.hourly_cycle_times.map((value) => {
@@ -68450,12 +68646,31 @@ var WorkspaceDetailView = ({
68450
68646
  }) : [],
68451
68647
  [workspace?.hourly_cycle_times]
68452
68648
  );
68649
+ const maskedCycleTimeChartData = useMemo(
68650
+ () => maskFutureHourlySeries({
68651
+ data: cycleTimeChartData,
68652
+ shiftStart: workspace?.shift_start,
68653
+ shiftEnd: workspace?.shift_end,
68654
+ shiftDate: workspace?.date || date || calculatedOperationalDate || null,
68655
+ timezone: effectiveCycleTimeTimezone
68656
+ }),
68657
+ [
68658
+ cycleTimeChartData,
68659
+ workspace?.shift_start,
68660
+ workspace?.shift_end,
68661
+ workspace?.date,
68662
+ date,
68663
+ calculatedOperationalDate,
68664
+ effectiveCycleTimeTimezone
68665
+ ]
68666
+ );
68453
68667
  const cycleTimeDatasetKey = useMemo(
68454
68668
  () => [
68455
68669
  workspace?.workspace_id || workspaceId || "workspace",
68456
68670
  date || workspace?.date || "live",
68457
68671
  parsedShiftId ?? workspace?.shift_id ?? "current",
68458
- "hourly"
68672
+ "hourly",
68673
+ "backend"
68459
68674
  ].join(":"),
68460
68675
  [workspace?.workspace_id, workspaceId, date, workspace?.date, parsedShiftId, workspace?.shift_id]
68461
68676
  );
@@ -68700,14 +68915,28 @@ var WorkspaceDetailView = ({
68700
68915
  return filterDataByDateKeyRange(monthlyData, range);
68701
68916
  }, [monthlyData, range]);
68702
68917
  const formattedWorkspaceName = displayName || formatWorkspaceName3(workspace?.workspace_name || "", effectiveLineId);
68703
- const workspaceActionType = workspace && "action_type" in workspace ? workspace.action_type : void 0;
68704
- const workspaceAssemblyEnabled = workspace && "line_assembly_enabled" in workspace ? workspace.line_assembly_enabled === true : false;
68705
- const isAssemblyWorkspace = workspaceActionType === "assembly" || workspaceAssemblyEnabled;
68706
- const shouldShowCycleTimeChart = !isUptimeMode && (showCycleTimeChart ?? isAssemblyWorkspace);
68918
+ const workspaceCycleTimeEligibility = workspace ? {
68919
+ line_assembly_enabled: workspace.line_assembly_enabled,
68920
+ action_family: workspace.action_family,
68921
+ action_type: workspace.action_type
68922
+ } : null;
68923
+ const isAssemblyWorkspace = shouldUseAssemblyCycleTimeLayout(workspaceCycleTimeEligibility);
68924
+ const cycleTimePresentation = getWorkspaceCycleTimePresentation({
68925
+ workspace: workspace ? {
68926
+ monitoring_mode: workspace.monitoring_mode,
68927
+ line_assembly_enabled: workspace.line_assembly_enabled,
68928
+ action_family: workspace.action_family,
68929
+ action_type: workspace.action_type,
68930
+ cycle_time_data_status: workspace.cycle_time_data_status
68931
+ } : null,
68932
+ showCycleTimeChart
68933
+ });
68934
+ const shouldShowCycleTimeChart = cycleTimePresentation !== "output";
68935
+ const shouldShowCycleTimeUnavailableState = cycleTimePresentation === "cycle_unavailable";
68707
68936
  const showIdleBreakdownChart = !shouldShowCycleTimeChart && idleTimeVlmEnabled;
68708
68937
  const idleClipDate = date || workspace?.date || calculatedOperationalDate || getOperationalDate(timezone);
68709
68938
  const idleClipShiftId = parsedShiftId ?? workspace?.shift_id;
68710
- const hourlyIdleMinutes = useMemo(() => {
68939
+ const rawHourlyIdleMinutes = useMemo(() => {
68711
68940
  if (!shouldShowCycleTimeChart || !workspace?.idle_time_hourly || !workspace?.shift_start) return [];
68712
68941
  const parseTimeToMinutes3 = (time2) => {
68713
68942
  const [h, m] = time2.split(":").map(Number);
@@ -68741,6 +68970,30 @@ var WorkspaceDetailView = ({
68741
68970
  }
68742
68971
  return result;
68743
68972
  }, [shouldShowCycleTimeChart, workspace?.idle_time_hourly, workspace?.shift_start, workspace?.shift_end]);
68973
+ const hourlyIdleMinutes = useMemo(
68974
+ () => maskFutureHourlySeries({
68975
+ data: rawHourlyIdleMinutes,
68976
+ shiftStart: workspace?.shift_start,
68977
+ shiftEnd: workspace?.shift_end,
68978
+ shiftDate: idleClipDate,
68979
+ timezone: effectiveCycleTimeTimezone
68980
+ }),
68981
+ [
68982
+ rawHourlyIdleMinutes,
68983
+ workspace?.shift_start,
68984
+ workspace?.shift_end,
68985
+ idleClipDate,
68986
+ effectiveCycleTimeTimezone
68987
+ ]
68988
+ );
68989
+ const cycleTimeUnavailableView = useMemo(() => /* @__PURE__ */ jsxs("div", { className: "w-full h-full rounded-lg border border-amber-200 bg-amber-50/70 px-6 py-5 text-center flex flex-col items-center justify-center", children: [
68990
+ /* @__PURE__ */ jsx("h4", { className: "text-base font-semibold text-amber-900", children: "Cycle data unavailable" }),
68991
+ /* @__PURE__ */ jsx("p", { className: "mt-2 max-w-md text-sm text-amber-800", children: "This workstation has cycle-time metrics for the selected shift, but no matching `cycle_completion` clips were found for the chart." }),
68992
+ typeof workspace?.cycle_completion_clip_count === "number" && /* @__PURE__ */ jsxs("p", { className: "mt-3 text-xs font-medium uppercase tracking-[0.08em] text-amber-700", children: [
68993
+ "matched cycle clips: ",
68994
+ workspace.cycle_completion_clip_count
68995
+ ] })
68996
+ ] }), [workspace?.cycle_completion_clip_count]);
68744
68997
  const shiftDurationMinutes = useMemo(
68745
68998
  () => getShiftDurationMinutes(workspace?.shift_start, workspace?.shift_end),
68746
68999
  [workspace?.shift_start, workspace?.shift_end]
@@ -69237,7 +69490,7 @@ var WorkspaceDetailView = ({
69237
69490
  children: [
69238
69491
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-4", children: [
69239
69492
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-bold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle time trend" : "Hourly Output" }),
69240
- !isUptimeMode && /* @__PURE__ */ jsx(
69493
+ !isUptimeMode && !shouldShowCycleTimeUnavailableState && /* @__PURE__ */ jsx(
69241
69494
  "button",
69242
69495
  {
69243
69496
  onClick: () => setShowChartIdleTime(!showChartIdleTime),
@@ -69266,10 +69519,10 @@ var WorkspaceDetailView = ({
69266
69519
  timezone,
69267
69520
  elapsedMinutes: elapsedShiftMinutes
69268
69521
  }
69269
- ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsx(
69522
+ ) : shouldShowCycleTimeUnavailableState ? cycleTimeUnavailableView : shouldShowCycleTimeChart ? /* @__PURE__ */ jsx(
69270
69523
  CycleTimeOverTimeChart,
69271
69524
  {
69272
- data: cycleTimeChartData,
69525
+ data: maskedCycleTimeChartData,
69273
69526
  idealCycleTime: workspace.ideal_cycle_time || 0,
69274
69527
  shiftStart: workspace.shift_start || "",
69275
69528
  shiftEnd: workspace.shift_end || "",
@@ -69370,7 +69623,7 @@ var WorkspaceDetailView = ({
69370
69623
  children: [
69371
69624
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 mb-4 flex-none", children: [
69372
69625
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-bold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle time trend" : "Hourly Output" }),
69373
- !isUptimeMode && /* @__PURE__ */ jsx(
69626
+ !isUptimeMode && !shouldShowCycleTimeUnavailableState && /* @__PURE__ */ jsx(
69374
69627
  "button",
69375
69628
  {
69376
69629
  onClick: () => setShowChartIdleTime(!showChartIdleTime),
@@ -69395,10 +69648,10 @@ var WorkspaceDetailView = ({
69395
69648
  timezone,
69396
69649
  elapsedMinutes: elapsedShiftMinutes
69397
69650
  }
69398
- ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsx(
69651
+ ) : shouldShowCycleTimeUnavailableState ? cycleTimeUnavailableView : shouldShowCycleTimeChart ? /* @__PURE__ */ jsx(
69399
69652
  CycleTimeOverTimeChart,
69400
69653
  {
69401
- data: cycleTimeChartData,
69654
+ data: maskedCycleTimeChartData,
69402
69655
  idealCycleTime: workspace.ideal_cycle_time || 0,
69403
69656
  shiftStart: workspace.shift_start || "",
69404
69657
  shiftEnd: workspace.shift_end || "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.11.14",
3
+ "version": "6.11.15",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",