@optifye/dashboard-core 6.10.50 → 6.10.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1058,6 +1058,44 @@ var fetchBackendJson = async (supabase, endpoint, options = {}) => {
1058
1058
  }
1059
1059
  };
1060
1060
 
1061
+ // src/lib/services/lineMetricsSelection.ts
1062
+ var toTimestamp = (value) => {
1063
+ if (typeof value !== "string") return 0;
1064
+ const parsed = Date.parse(value);
1065
+ return Number.isNaN(parsed) ? 0 : parsed;
1066
+ };
1067
+ var compareByLatestThenSku = (a, b) => {
1068
+ const timeDiff = toTimestamp(b.last_updated) - toTimestamp(a.last_updated);
1069
+ if (timeDiff !== 0) return timeDiff;
1070
+ const aSku = typeof a.sku_id === "string" ? a.sku_id : "";
1071
+ const bSku = typeof b.sku_id === "string" ? b.sku_id : "";
1072
+ return aSku.localeCompare(bSku);
1073
+ };
1074
+ var pickPreferredLineMetricsRow = async (supabase, lineId, rows, skuTable = "skus") => {
1075
+ if (!rows || rows.length === 0) {
1076
+ return null;
1077
+ }
1078
+ if (rows.length === 1) {
1079
+ return rows[0];
1080
+ }
1081
+ let dummySkuId = null;
1082
+ try {
1083
+ const { data } = await supabase.from(skuTable).select("id").eq("line_id", lineId).eq("sku_definition", "dummy_definition").eq("is_active", true).limit(1);
1084
+ if (Array.isArray(data) && data.length > 0) {
1085
+ dummySkuId = typeof data[0]?.id === "string" ? data[0].id : null;
1086
+ }
1087
+ } catch (error) {
1088
+ console.warn("[lineMetricsSelection] Failed dummy SKU lookup:", error);
1089
+ }
1090
+ if (dummySkuId) {
1091
+ const dummyRow = rows.find((row) => row.sku_id === dummySkuId);
1092
+ if (dummyRow) {
1093
+ return dummyRow;
1094
+ }
1095
+ }
1096
+ return rows.slice().sort(compareByLatestThenSku)[0] ?? null;
1097
+ };
1098
+
1061
1099
  // src/lib/services/dashboardService.ts
1062
1100
  var getTable = (dbConfig, tableName) => {
1063
1101
  const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
@@ -1076,6 +1114,7 @@ var dashboardService = {
1076
1114
  const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
1077
1115
  const linesTable = getTable(dbConfig, "lines");
1078
1116
  const lineMetricsTable = getTable(dbConfig, "lineMetrics");
1117
+ const skuTable = dbConfig?.tables?.skus ?? "skus";
1079
1118
  const companyId = entityConfig.companyId;
1080
1119
  const metricsTablePrefixStr = getMetricsTablePrefix();
1081
1120
  `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
@@ -1169,9 +1208,14 @@ var dashboardService = {
1169
1208
  if (!lineData) throw new Error(`Line with ID ${lineId} not found`);
1170
1209
  let metricsFromDb = null;
1171
1210
  try {
1172
- const { data: fetchedMetrics, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date).maybeSingle();
1211
+ const { data: fetchedMetricsRows, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date);
1173
1212
  if (error) throw error;
1174
- metricsFromDb = fetchedMetrics;
1213
+ metricsFromDb = await pickPreferredLineMetricsRow(
1214
+ supabase,
1215
+ lineId,
1216
+ fetchedMetricsRows,
1217
+ skuTable
1218
+ );
1175
1219
  } catch (err) {
1176
1220
  console.error(`Error fetching line metrics for ${lineId}:`, err);
1177
1221
  }
@@ -1582,6 +1626,7 @@ var dashboardService = {
1582
1626
  const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
1583
1627
  const linesTable = getTable(dbConfig, "lines");
1584
1628
  const lineMetricsTable = getTable(dbConfig, "lineMetrics");
1629
+ const skuTable = dbConfig?.tables?.skus ?? "skus";
1585
1630
  const companyId = entityConfig.companyId;
1586
1631
  const metricsTablePrefixStr = getMetricsTablePrefix();
1587
1632
  `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
@@ -1680,7 +1725,7 @@ var dashboardService = {
1680
1725
  }
1681
1726
  const [lineResult, metricsResult] = await Promise.all([
1682
1727
  supabase.from(linesTable).select("id, line_name, factory_id, monitoring_mode, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", lineIdToQuery).single(),
1683
- supabase.from(lineMetricsTable).select("*").eq("line_id", lineIdToQuery).eq("shift_id", queryShiftId).eq("date", queryDate).maybeSingle()
1728
+ supabase.from(lineMetricsTable).select("*").eq("line_id", lineIdToQuery).eq("shift_id", queryShiftId).eq("date", queryDate)
1684
1729
  ]);
1685
1730
  if (lineResult.error) throw lineResult.error;
1686
1731
  if (!lineResult.data) {
@@ -1688,7 +1733,12 @@ var dashboardService = {
1688
1733
  }
1689
1734
  if (metricsResult.error) throw metricsResult.error;
1690
1735
  const lineData = lineResult.data;
1691
- const metrics2 = metricsResult.data;
1736
+ const metrics2 = await pickPreferredLineMetricsRow(
1737
+ supabase,
1738
+ lineIdToQuery,
1739
+ metricsResult.data,
1740
+ skuTable
1741
+ );
1692
1742
  return {
1693
1743
  line_id: lineData.id,
1694
1744
  line_name: lineData.line_name,
@@ -17359,6 +17409,7 @@ function useCompanyClipsCost() {
17359
17409
  const [data, setData] = React26.useState(null);
17360
17410
  const [isLoading, setIsLoading] = React26.useState(true);
17361
17411
  const [error, setError] = React26.useState(null);
17412
+ const hasFetchedOnceRef = React26.useRef(false);
17362
17413
  const canViewClipsCost = user?.role_level === "owner" || user?.role_level === "optifye";
17363
17414
  const companyId = user?.properties?.company_id || user?.company_id || entityConfig.companyId;
17364
17415
  const apiBaseUrl = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
@@ -17366,9 +17417,12 @@ function useCompanyClipsCost() {
17366
17417
  if (!canViewClipsCost || !companyId || !supabase || !apiBaseUrl || !session?.access_token) {
17367
17418
  setIsLoading(false);
17368
17419
  setData(null);
17420
+ hasFetchedOnceRef.current = false;
17369
17421
  return;
17370
17422
  }
17371
- setIsLoading(true);
17423
+ if (!hasFetchedOnceRef.current) {
17424
+ setIsLoading(true);
17425
+ }
17372
17426
  setError(null);
17373
17427
  try {
17374
17428
  const [statsResponse, linesResult] = await Promise.all([
@@ -17385,9 +17439,18 @@ function useCompanyClipsCost() {
17385
17439
  }
17386
17440
  const statsResult = await statsResponse.json();
17387
17441
  const totalClassifications = statsResult.total_classifications || 0;
17442
+ const monthlyClassifications = statsResult.monthly_classifications ?? totalClassifications;
17443
+ const monthStart = statsResult.month_start || null;
17444
+ const historicalMonthlyClassifications = Array.isArray(statsResult.historical_monthly_classifications) ? statsResult.historical_monthly_classifications.map((item) => ({
17445
+ monthStart: item?.month_start || "",
17446
+ classifications: Number(item?.classifications || 0)
17447
+ })).filter((item) => item.monthStart && item.classifications > 0) : [];
17388
17448
  const hasVlmEnabledLine = (linesResult.data?.length || 0) > 0;
17389
17449
  setData({
17390
17450
  totalClassifications,
17451
+ monthlyClassifications,
17452
+ monthStart,
17453
+ historicalMonthlyClassifications,
17391
17454
  hasVlmEnabledLine
17392
17455
  });
17393
17456
  } catch (err) {
@@ -17395,6 +17458,7 @@ function useCompanyClipsCost() {
17395
17458
  setError(err instanceof Error ? err.message : "Failed to load clips data");
17396
17459
  setData(null);
17397
17460
  } finally {
17461
+ hasFetchedOnceRef.current = true;
17398
17462
  setIsLoading(false);
17399
17463
  }
17400
17464
  }, [canViewClipsCost, companyId, supabase, apiBaseUrl, session?.access_token]);
@@ -36765,6 +36829,7 @@ var FileManagerFilters = ({
36765
36829
  const endInputRef = React26.useRef(null);
36766
36830
  const [idleLabelFilter, setIdleLabelFilter] = React26.useState(null);
36767
36831
  const [showIdleLabelFilterModal, setShowIdleLabelFilterModal] = React26.useState(false);
36832
+ const [isLoadingIdleReasonOptions, setIsLoadingIdleReasonOptions] = React26.useState(false);
36768
36833
  const timezone = useAppTimezone();
36769
36834
  const supabase = useSupabase();
36770
36835
  const [clipMetadata, setClipMetadata] = React26.useState({});
@@ -36807,18 +36872,27 @@ var FileManagerFilters = ({
36807
36872
  iconColor: colorConfig.text
36808
36873
  };
36809
36874
  };
36810
- const ROOT_CAUSE_OPTIONS = [
36811
- "Operator Absent",
36812
- "Operator Idle",
36813
- "Machine Downtime",
36814
- "No Material"
36815
- ];
36875
+ const normalizeIdleReasonLabel = React26.useCallback((label) => {
36876
+ return label.replace(/_/g, " ").trim();
36877
+ }, []);
36816
36878
  const getIdleTimeRootCause = React26.useCallback((clipId) => {
36817
36879
  const classification = mergedClipClassifications[clipId];
36818
36880
  if (!classification) return "processing";
36819
36881
  if (classification.status === "processing") return "processing";
36820
36882
  return classification.label || "processing";
36821
36883
  }, [mergedClipClassifications]);
36884
+ const idleReasonOptions = React26.useMemo(() => {
36885
+ const idleClips = clipMetadata["idle_time"] || [];
36886
+ const uniqueReasons = /* @__PURE__ */ new Set();
36887
+ idleClips.forEach((clip) => {
36888
+ const clipId = clip.clipId || clip.id;
36889
+ if (!clipId) return;
36890
+ const reason = getIdleTimeRootCause(clipId);
36891
+ if (!reason || reason === "processing") return;
36892
+ uniqueReasons.add(normalizeIdleReasonLabel(reason));
36893
+ });
36894
+ return Array.from(uniqueReasons).sort((a, b) => a.localeCompare(b));
36895
+ }, [clipMetadata, getIdleTimeRootCause, normalizeIdleReasonLabel]);
36822
36896
  const getClipBadge = React26.useCallback((node) => {
36823
36897
  if (node.categoryId === "idle_time" || node.categoryId === "low_value") {
36824
36898
  return { text: "Idle", className: "bg-red-100 text-red-700" };
@@ -36846,6 +36920,79 @@ var FileManagerFilters = ({
36846
36920
  return null;
36847
36921
  }
36848
36922
  }, [supabase]);
36923
+ const fetchClipMetadataPage = React26.useCallback(async (categoryId, page = 1) => {
36924
+ if (!workspaceId || !date || shift === void 0) {
36925
+ throw new Error("Missing required params for clip metadata fetch");
36926
+ }
36927
+ const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
36928
+ method: "POST",
36929
+ headers: {
36930
+ "Content-Type": "application/json"
36931
+ },
36932
+ body: JSON.stringify({
36933
+ action: "clip-metadata",
36934
+ workspaceId,
36935
+ date,
36936
+ shift: shift.toString(),
36937
+ category: categoryId,
36938
+ page,
36939
+ limit: 50,
36940
+ snapshotDateTime,
36941
+ snapshotClipId
36942
+ }),
36943
+ redirectReason: "session_expired"
36944
+ });
36945
+ if (!response.ok) {
36946
+ throw new Error(`API error: ${response.status}`);
36947
+ }
36948
+ return response.json();
36949
+ }, [workspaceId, date, shift, snapshotDateTime, snapshotClipId, supabase]);
36950
+ const seedIdleClassifications = React26.useCallback(async (clips) => {
36951
+ if (!idleTimeVlmEnabled || clips.length === 0) {
36952
+ return;
36953
+ }
36954
+ const authToken = await getAuthToken3();
36955
+ if (!authToken) {
36956
+ return;
36957
+ }
36958
+ const seededClassifications = {};
36959
+ clips.forEach((clip) => {
36960
+ if (!clip.clipId) {
36961
+ return;
36962
+ }
36963
+ if (clip.classification_status) {
36964
+ seededClassifications[clip.clipId] = {
36965
+ status: clip.classification_status,
36966
+ label: clip.classification_label || void 0,
36967
+ confidence: clip.classification_confidence ?? void 0
36968
+ };
36969
+ }
36970
+ });
36971
+ if (Object.keys(seededClassifications).length > 0) {
36972
+ setLocalClipClassifications((prev) => ({
36973
+ ...prev,
36974
+ ...seededClassifications
36975
+ }));
36976
+ }
36977
+ const clipIdsToFetch = clips.map((clip) => clip.clipId || clip.id).filter(Boolean).filter((id3) => {
36978
+ if (!id3) return false;
36979
+ if (mergedClipClassifications[id3]?.status === "classified") return false;
36980
+ if (seededClassifications[id3]?.status === "classified") return false;
36981
+ return true;
36982
+ });
36983
+ if (clipIdsToFetch.length === 0) {
36984
+ return;
36985
+ }
36986
+ try {
36987
+ const classifications = await fetchClassifications(clipIdsToFetch, authToken);
36988
+ setLocalClipClassifications((prev) => ({
36989
+ ...prev,
36990
+ ...classifications
36991
+ }));
36992
+ } catch (error) {
36993
+ console.error("[FileManager] Error fetching idle classifications:", error);
36994
+ }
36995
+ }, [idleTimeVlmEnabled, getAuthToken3, mergedClipClassifications]);
36849
36996
  const fetchClipMetadata = React26.useCallback(async (categoryId, page = 1) => {
36850
36997
  if (!workspaceId || !date || shift === void 0) {
36851
36998
  console.warn("[FileManager] Missing required params for clip metadata fetch");
@@ -36854,69 +37001,13 @@ var FileManagerFilters = ({
36854
37001
  const loadingKey = `${categoryId}-${page}`;
36855
37002
  setLoadingCategories((prev) => /* @__PURE__ */ new Set([...prev, loadingKey]));
36856
37003
  try {
36857
- const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
36858
- method: "POST",
36859
- headers: {
36860
- "Content-Type": "application/json"
36861
- },
36862
- body: JSON.stringify({
36863
- action: "clip-metadata",
36864
- workspaceId,
36865
- date,
36866
- shift: shift.toString(),
36867
- category: categoryId,
36868
- page,
36869
- limit: 50,
36870
- snapshotDateTime,
36871
- snapshotClipId
36872
- }),
36873
- redirectReason: "session_expired"
36874
- });
36875
- if (!response.ok) {
36876
- throw new Error(`API error: ${response.status}`);
36877
- }
36878
- const data = await response.json();
37004
+ const data = await fetchClipMetadataPage(categoryId, page);
36879
37005
  setClipMetadata((prev) => ({
36880
37006
  ...prev,
36881
37007
  [categoryId]: page === 1 ? data.clips : [...prev[categoryId] || [], ...data.clips]
36882
37008
  }));
36883
- const authToken = categoryId === "idle_time" && idleTimeVlmEnabled ? await getAuthToken3() : null;
36884
- if (categoryId === "idle_time" && idleTimeVlmEnabled && authToken) {
36885
- const seededClassifications = {};
36886
- (data.clips || []).forEach((clip) => {
36887
- if (!clip.clipId) {
36888
- return;
36889
- }
36890
- if (clip.classification_status) {
36891
- seededClassifications[clip.clipId] = {
36892
- status: clip.classification_status,
36893
- label: clip.classification_label || void 0,
36894
- confidence: clip.classification_confidence ?? void 0
36895
- };
36896
- }
36897
- });
36898
- if (Object.keys(seededClassifications).length > 0) {
36899
- setLocalClipClassifications((prev) => ({
36900
- ...prev,
36901
- ...seededClassifications
36902
- }));
36903
- }
36904
- const clipIds = (data.clips || []).map((clip) => clip.clipId || clip.id).filter(Boolean);
36905
- const newClipIds = clipIds.filter((id3) => {
36906
- if (mergedClipClassifications[id3]?.status === "classified") return false;
36907
- if (seededClassifications[id3]?.status === "classified") return false;
36908
- return true;
36909
- });
36910
- if (newClipIds.length > 0) {
36911
- fetchClassifications(newClipIds, authToken).then((classifications) => {
36912
- setLocalClipClassifications((prev) => ({
36913
- ...prev,
36914
- ...classifications
36915
- }));
36916
- }).catch((error) => {
36917
- console.error("[FileManager] Error fetching idle classifications:", error);
36918
- });
36919
- }
37009
+ if (categoryId === "idle_time" && idleTimeVlmEnabled) {
37010
+ await seedIdleClassifications(data.clips || []);
36920
37011
  }
36921
37012
  setCategoryPages((prev) => ({ ...prev, [categoryId]: page }));
36922
37013
  setCategoryHasMore((prev) => ({ ...prev, [categoryId]: data.hasMore }));
@@ -36930,7 +37021,64 @@ var FileManagerFilters = ({
36930
37021
  return newSet;
36931
37022
  });
36932
37023
  }
36933
- }, [workspaceId, date, shift, mergedClipClassifications, snapshotDateTime, snapshotClipId, supabase, getAuthToken3, idleTimeVlmEnabled]);
37024
+ }, [workspaceId, date, shift, fetchClipMetadataPage, idleTimeVlmEnabled, seedIdleClassifications]);
37025
+ const ensureAllIdleTimeClipMetadataLoaded = React26.useCallback(async () => {
37026
+ if (!workspaceId || !date || shift === void 0) {
37027
+ return;
37028
+ }
37029
+ let accumulatedClips = [...clipMetadata["idle_time"] || []];
37030
+ let currentPage = categoryPages["idle_time"] || 0;
37031
+ let hasMore = categoryHasMore["idle_time"];
37032
+ if (currentPage === 0) {
37033
+ const firstPageData = await fetchClipMetadataPage("idle_time", 1);
37034
+ accumulatedClips = firstPageData.clips || [];
37035
+ currentPage = 1;
37036
+ hasMore = firstPageData.hasMore;
37037
+ } else if (hasMore === void 0) {
37038
+ hasMore = false;
37039
+ }
37040
+ while (hasMore) {
37041
+ const nextPage = currentPage + 1;
37042
+ const nextPageData = await fetchClipMetadataPage("idle_time", nextPage);
37043
+ accumulatedClips = [...accumulatedClips, ...nextPageData.clips || []];
37044
+ currentPage = nextPage;
37045
+ hasMore = nextPageData.hasMore;
37046
+ }
37047
+ const dedupedClips = accumulatedClips.filter((clip, index, arr) => {
37048
+ const clipKey = clip.clipId || clip.id;
37049
+ return arr.findIndex((item) => (item.clipId || item.id) === clipKey) === index;
37050
+ });
37051
+ setClipMetadata((prev) => ({
37052
+ ...prev,
37053
+ idle_time: dedupedClips
37054
+ }));
37055
+ setCategoryPages((prev) => ({ ...prev, idle_time: currentPage }));
37056
+ setCategoryHasMore((prev) => ({ ...prev, idle_time: false }));
37057
+ if (idleTimeVlmEnabled) {
37058
+ await seedIdleClassifications(dedupedClips);
37059
+ }
37060
+ }, [
37061
+ workspaceId,
37062
+ date,
37063
+ shift,
37064
+ clipMetadata,
37065
+ categoryPages,
37066
+ categoryHasMore,
37067
+ fetchClipMetadataPage,
37068
+ idleTimeVlmEnabled,
37069
+ seedIdleClassifications
37070
+ ]);
37071
+ const handleOpenIdleLabelFilterModal = React26.useCallback(async () => {
37072
+ setShowIdleLabelFilterModal(true);
37073
+ setIsLoadingIdleReasonOptions(true);
37074
+ try {
37075
+ await ensureAllIdleTimeClipMetadataLoaded();
37076
+ } catch (error) {
37077
+ console.error("[FileManager] Error loading idle reason options:", error);
37078
+ } finally {
37079
+ setIsLoadingIdleReasonOptions(false);
37080
+ }
37081
+ }, [ensureAllIdleTimeClipMetadataLoaded]);
36934
37082
  const fetchPercentileClips = React26.useCallback(async (type) => {
36935
37083
  if (!workspaceId || !date || shift === void 0) {
36936
37084
  console.warn("[FileManager] Missing required params for percentile clips fetch");
@@ -37601,7 +37749,7 @@ var FileManagerFilters = ({
37601
37749
  activeFilter === "idle_time" && idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsx(
37602
37750
  "button",
37603
37751
  {
37604
- onClick: () => setShowIdleLabelFilterModal(true),
37752
+ onClick: handleOpenIdleLabelFilterModal,
37605
37753
  className: `p-2 rounded-xl transition-all duration-200 ${idleLabelFilter ? "bg-purple-100 text-purple-600 hover:bg-purple-200 shadow-sm" : "bg-slate-100 text-slate-600 hover:bg-slate-200"}`,
37606
37754
  title: "Filter by idle reason",
37607
37755
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Tag, { className: "h-5 w-5" })
@@ -37689,7 +37837,10 @@ var FileManagerFilters = ({
37689
37837
  }
37690
37838
  )
37691
37839
  ] }),
37692
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 max-h-60 overflow-y-auto", children: ROOT_CAUSE_OPTIONS.map((reason) => {
37840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 max-h-60 overflow-y-auto", children: isLoadingIdleReasonOptions ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-4 text-sm text-slate-500 flex items-center gap-2", children: [
37841
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }),
37842
+ "Loading idle reasons..."
37843
+ ] }) : idleReasonOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-4 text-sm text-slate-500", children: "No classified idle reasons found yet." }) : idleReasonOptions.map((reason) => {
37693
37844
  const config = getRootCauseConfig(reason);
37694
37845
  return /* @__PURE__ */ jsxRuntime.jsxs(
37695
37846
  "button",
@@ -49254,7 +49405,7 @@ var SideNavBar = React26.memo(({
49254
49405
  if (hasClipsCostData) {
49255
49406
  items.push({
49256
49407
  key: "clips-analysis",
49257
- label: "Clips Analysis",
49408
+ label: "Billing",
49258
49409
  icon: outline.CurrencyDollarIcon,
49259
49410
  onClick: () => {
49260
49411
  handleClipsCostClick();
@@ -61537,25 +61688,39 @@ var ProfileView = () => {
61537
61688
  var ProfileView_default = ProfileView;
61538
61689
  var REFRESH_INTERVAL_MS = 30 * 1e3;
61539
61690
  var ClipsCostView = () => {
61540
- const { data, isLoading, hasData, error, refetch } = useCompanyClipsCost();
61691
+ const { data, isLoading, error, refetch } = useCompanyClipsCost();
61541
61692
  const navigation = useNavigation();
61693
+ const refetchRef = React26.useRef(refetch);
61694
+ React26.useEffect(() => {
61695
+ refetchRef.current = refetch;
61696
+ }, [refetch]);
61542
61697
  React26.useEffect(() => {
61543
61698
  const intervalId = setInterval(() => {
61544
- refetch();
61699
+ void refetchRef.current();
61545
61700
  }, REFRESH_INTERVAL_MS);
61546
61701
  return () => clearInterval(intervalId);
61547
- }, [refetch]);
61702
+ }, []);
61548
61703
  const mobileMenuContext = useMobileMenu();
61549
61704
  useHideMobileHeader(!!mobileMenuContext);
61550
61705
  const formatNumber = (num) => {
61551
61706
  return new Intl.NumberFormat("en-US").format(num);
61552
61707
  };
61708
+ const formatMonthLabel = (monthStart) => {
61709
+ if (!monthStart) {
61710
+ return "Current Month";
61711
+ }
61712
+ const parsed = /* @__PURE__ */ new Date(`${monthStart}T00:00:00`);
61713
+ if (Number.isNaN(parsed.getTime())) {
61714
+ return "Current Month";
61715
+ }
61716
+ return new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric" }).format(parsed);
61717
+ };
61553
61718
  if (isLoading) {
61554
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg" }) });
61719
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-screen bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading usage data..." }) });
61555
61720
  }
61556
61721
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
61557
- /* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-4 md:px-6 py-2 sm:py-3", children: [
61558
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm:hidden flex items-center justify-between", children: [
61722
+ /* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-6 lg:px-8 py-3 sm:py-4 w-full", children: [
61723
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
61559
61724
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children: mobileMenuContext && /* @__PURE__ */ jsxRuntime.jsx(
61560
61725
  HamburgerButton,
61561
61726
  {
@@ -61563,46 +61728,57 @@ var ClipsCostView = () => {
61563
61728
  className: "flex-shrink-0 -ml-1"
61564
61729
  }
61565
61730
  ) }),
61566
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold text-gray-900", children: "Clips Analysis" }) }),
61567
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8" }),
61568
- " "
61569
- ] }),
61570
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between relative", children: [
61571
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
61731
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center", children: [
61732
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold text-gray-900", children: "Billing" }),
61733
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: "Track your usage based features spend" })
61734
+ ] }),
61735
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-9" })
61736
+ ] }) }),
61737
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
61738
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4 min-w-[120px]", children: /* @__PURE__ */ jsxRuntime.jsx(
61572
61739
  BackButtonMinimal,
61573
61740
  {
61574
61741
  onClick: () => navigation.goToDashboard(),
61575
61742
  text: "Back",
61576
61743
  size: "default",
61577
- className: "bg-transparent border-transparent hover:bg-gray-100 pl-0"
61744
+ "aria-label": "Navigate back to dashboard"
61578
61745
  }
61579
61746
  ) }),
61580
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "absolute left-1/2 -translate-x-1/2 text-xl md:text-2xl lg:text-3xl font-semibold text-gray-900", children: "Clips Analysis" }),
61581
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-10" }),
61582
- " "
61747
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
61748
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "Billing" }),
61749
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Track your usage based features spend" })
61750
+ ] }),
61751
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-[120px]" })
61583
61752
  ] }) })
61584
61753
  ] }) }),
61585
- /* @__PURE__ */ jsxRuntime.jsxs("main", { className: "flex-1 p-4 sm:p-6 lg:p-8 max-w-2xl mx-auto w-full", children: [
61586
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700", children: error }),
61587
- !hasData && !isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 p-8 text-center", children: [
61588
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Film, { className: "h-12 w-12 text-gray-300 mx-auto mb-4" }),
61589
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-2", children: "No Data Available" }),
61590
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "Clip analysis data will appear here once clips have been analyzed." })
61591
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden", children: [
61592
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 border-b border-gray-100", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
61593
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 bg-blue-50 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Film, { className: "h-5 w-5 text-blue-600" }) }),
61594
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
61595
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Clips Analysis" }),
61596
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "AI-powered clip classification usage" })
61597
- ] })
61598
- ] }) }),
61599
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-4", children: [
61600
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-500 mb-2", children: "Total Clips Analyzed" }),
61601
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-4xl font-bold text-gray-900", children: formatNumber(data?.totalClassifications || 0) }),
61602
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "clips processed through AI classification" })
61603
- ] }) })
61754
+ /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 p-4 sm:p-6 lg:p-8 max-w-7xl mx-auto w-full", children: error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg flex items-start gap-3 text-red-700 animate-in fade-in slide-in-from-top-2 max-w-2xl mx-auto", children: [
61755
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-5 w-5 flex-shrink-0 mt-0.5" }),
61756
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
61757
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-medium", children: "Error loading usage data" }),
61758
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm mt-1 text-red-600", children: error })
61604
61759
  ] })
61605
- ] })
61760
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-xl mt-8 animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
61761
+ /* @__PURE__ */ jsxRuntime.jsx(Card2, { className: "overflow-hidden shadow-sm border-gray-200 bg-white", children: /* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "p-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center text-center", children: [
61762
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 bg-blue-50 rounded-full mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Film, { className: "h-8 w-8 text-blue-600" }) }),
61763
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-medium text-gray-500 uppercase tracking-wider mb-2", children: "Clips Analyzed" }),
61764
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mb-3", children: formatMonthLabel(data?.monthStart) }),
61765
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-5xl font-bold text-gray-900 mb-2 tabular-nums tracking-tight", children: formatNumber(data?.monthlyClassifications || 0) })
61766
+ ] }) }) }),
61767
+ /* @__PURE__ */ jsxRuntime.jsx(Card2, { className: "mt-4 overflow-hidden shadow-sm border-gray-200 bg-white", children: /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { className: "p-6", children: [
61768
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-900 mb-3 uppercase tracking-wider", children: "Previous Months" }),
61769
+ (data?.historicalMonthlyClassifications?.length || 0) === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No historical month usage yet." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: data?.historicalMonthlyClassifications.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
61770
+ "div",
61771
+ {
61772
+ className: "flex items-center justify-between rounded-md border border-gray-100 bg-gray-50 px-3 py-2",
61773
+ children: [
61774
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: formatMonthLabel(item.monthStart) }),
61775
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-gray-900 tabular-nums", children: formatNumber(item.classifications) })
61776
+ ]
61777
+ },
61778
+ item.monthStart
61779
+ )) })
61780
+ ] }) })
61781
+ ] }) })
61606
61782
  ] });
61607
61783
  };
61608
61784
  var ClipsCostView_default = ClipsCostView;
@@ -62276,6 +62452,21 @@ var ShiftsView = ({
62276
62452
  }
62277
62453
  const factoryId = existingLineThreshold?.factory_id || lineFactoryId;
62278
62454
  if (factoryId) {
62455
+ let resolvedLineThresholdSkuId = existingLineThreshold?.sku_id || null;
62456
+ if (!resolvedLineThresholdSkuId) {
62457
+ const { data: dummySkuRows, error: dummySkuError } = await supabase.from("skus").select("id").eq("line_id", lineId).eq("sku_definition", "dummy_definition").eq("is_active", true).limit(1);
62458
+ if (dummySkuError) {
62459
+ throw new Error(
62460
+ `Failed to resolve dummy SKU for line ${lineId}, shift ${shift.shiftId}: ${dummySkuError.message}`
62461
+ );
62462
+ }
62463
+ resolvedLineThresholdSkuId = dummySkuRows?.[0]?.id || null;
62464
+ }
62465
+ if (!resolvedLineThresholdSkuId) {
62466
+ throw new Error(
62467
+ `No active SKU found for line ${lineId}, shift ${shift.shiftId} while updating line thresholds`
62468
+ );
62469
+ }
62279
62470
  const lineThresholdPayload = {
62280
62471
  factory_id: factoryId,
62281
62472
  line_id: lineId,
@@ -62283,12 +62474,10 @@ var ShiftsView = ({
62283
62474
  shift_id: shift.shiftId,
62284
62475
  product_code: existingLineThreshold?.product_code || "",
62285
62476
  threshold_day_output: thresholdDayOutput,
62286
- threshold_pph: thresholdPPH
62477
+ threshold_pph: thresholdPPH,
62478
+ sku_id: resolvedLineThresholdSkuId
62287
62479
  };
62288
- if (existingLineThreshold?.sku_id) {
62289
- lineThresholdPayload.sku_id = existingLineThreshold.sku_id;
62290
- }
62291
- const { error: lineThresholdUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdPayload, { onConflict: "factory_id,line_id,date,shift_id" });
62480
+ const { error: lineThresholdUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdPayload, { onConflict: "factory_id,line_id,date,shift_id,sku_id" });
62292
62481
  if (lineThresholdUpsertError) {
62293
62482
  throw new Error(
62294
62483
  `Failed to update line thresholds for line ${lineId}, shift ${shift.shiftId}: ${lineThresholdUpsertError.message}`
@@ -64076,6 +64265,17 @@ var TargetsView = ({
64076
64265
  await workspaceService.updateActionThresholds(workspaceThresholdUpdates);
64077
64266
  console.log(`[handleSaveLine] Successfully updated action thresholds for ${lineId}`);
64078
64267
  const packagingWorkspaces = lineDataToSave.workspaces.filter((ws) => ws.actionType === "packaging");
64268
+ let resolvedLineThresholdSkuId = lineDataToSave.selectedSKU?.id || null;
64269
+ if (!resolvedLineThresholdSkuId) {
64270
+ const { data: dummySkuRows, error: dummySkuError } = await supabase.from("skus").select("id").eq("line_id", lineId).eq("sku_definition", "dummy_definition").eq("is_active", true).limit(1);
64271
+ if (dummySkuError) {
64272
+ throw dummySkuError;
64273
+ }
64274
+ resolvedLineThresholdSkuId = dummySkuRows?.[0]?.id || null;
64275
+ }
64276
+ if (!resolvedLineThresholdSkuId) {
64277
+ throw new Error(`No active SKU found for line ${lineId} while saving line thresholds`);
64278
+ }
64079
64279
  const lineThresholdData = {
64080
64280
  factory_id: currentFactoryId,
64081
64281
  line_id: lineId,
@@ -64085,10 +64285,10 @@ var TargetsView = ({
64085
64285
  threshold_day_output: packagingWorkspaces.reduce((acc, ws) => acc + (Number(ws.targetDayOutput) || 0), 0),
64086
64286
  threshold_pph: packagingWorkspaces.reduce((acc, ws) => acc + (ws.targetPPH ? Math.round(Number(ws.targetPPH)) : 0), 0),
64087
64287
  // Round each PPH value
64088
- ...skuEnabled && lineDataToSave.selectedSKU ? { sku_id: lineDataToSave.selectedSKU.id } : {}
64288
+ sku_id: resolvedLineThresholdSkuId
64089
64289
  };
64090
64290
  console.log(`[handleSaveLine] lineThresholdData for upsert on ${lineId}:`, lineThresholdData);
64091
- const { error: lineUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdData, { onConflict: "factory_id,line_id,date,shift_id" });
64291
+ const { error: lineUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdData, { onConflict: "factory_id,line_id,date,shift_id,sku_id" });
64092
64292
  if (lineUpsertError) {
64093
64293
  console.error(`[handleSaveLine] Error upserting line_thresholds for ${lineId}:`, lineUpsertError);
64094
64294
  sonner.toast.error(`Failed to save line thresholds for ${mergedLineNames[lineId] || lineId}`);
@@ -64884,6 +65084,7 @@ var WorkspaceDetailView = ({
64884
65084
  }, [monthlyData, range]);
64885
65085
  const formattedWorkspaceName = displayName || formatWorkspaceName3(workspace?.workspace_name || "", effectiveLineId);
64886
65086
  const shouldShowCycleTimeChart = !isUptimeMode && (showCycleTimeChart ?? formattedWorkspaceName.startsWith("FINAL ASSY"));
65087
+ const showIdleBreakdownChart = !shouldShowCycleTimeChart && idleTimeVlmEnabled;
64887
65088
  const idleClipDate = date || workspace?.date || calculatedOperationalDate || getOperationalDate(timezone);
64888
65089
  const idleClipShiftId = parsedShiftId ?? workspace?.shift_id;
64889
65090
  const shiftDurationMinutes = React26.useMemo(
@@ -65487,107 +65688,123 @@ var WorkspaceDetailView = ({
65487
65688
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(WorkspaceMetricCards, { workspace, legend: efficiencyLegend, className: "flex-1" }) })
65488
65689
  ] }),
65489
65690
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden lg:flex lg:flex-col lg:h-full lg:min-h-0 gap-3", children: [
65490
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("grid grid-cols-1 lg:grid-cols-10 gap-3 min-h-0", desktopTopSectionClass), children: [
65491
- !shouldShowCycleTimeChart && !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsxs(
65492
- motion.div,
65493
- {
65494
- className: "bg-white rounded-lg shadow-sm p-4 lg:col-span-2 flex flex-col min-h-0",
65495
- variants: chartCardVariants,
65496
- initial: "initial",
65497
- animate: "animate",
65498
- children: [
65499
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Today's Output" }),
65500
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65501
- OutputProgressChart,
65502
- {
65503
- currentOutput: workspace.total_actions || 0,
65504
- targetOutput: workspace.target_output || 0
65505
- }
65506
- ) })
65507
- ]
65508
- }
65509
- ),
65510
- /* @__PURE__ */ jsxRuntime.jsxs(
65511
- motion.div,
65512
- {
65513
- className: `bg-white rounded-lg shadow-sm p-4 flex flex-col min-h-0 ${shouldShowCycleTimeChart || isUptimeMode ? "lg:col-span-10" : idleTimeVlmEnabled ? "lg:col-span-6" : "lg:col-span-8"}`,
65514
- variants: chartCardVariants,
65515
- initial: "initial",
65516
- animate: "animate",
65517
- children: [
65518
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 mb-4 flex-none", children: [
65519
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle Time (last 60 minutes)" : "Hourly Output" }),
65520
- !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsx(
65521
- "button",
65522
- {
65523
- onClick: () => setShowChartIdleTime(!showChartIdleTime),
65524
- className: `inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${showChartIdleTime ? "bg-blue-50 text-blue-700 border border-blue-200" : "bg-white text-gray-700 border border-gray-300 hover:bg-gray-50"}`,
65525
- children: showChartIdleTime ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65526
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: "w-4 h-4 mr-1.5" }),
65527
- "Hide Idle Time"
65528
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65529
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "w-4 h-4 mr-1.5" }),
65530
- "Show Idle Time"
65531
- ] })
65532
- }
65533
- )
65534
- ] }),
65535
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: isUptimeMode ? /* @__PURE__ */ jsxRuntime.jsx(
65536
- HourlyUptimeChart,
65537
- {
65538
- idleTimeHourly: workspace.idle_time_hourly,
65539
- shiftStart: workspace.shift_start,
65540
- shiftEnd: workspace.shift_end,
65541
- shiftDate: idleClipDate,
65542
- timezone,
65543
- elapsedMinutes: elapsedShiftMinutes
65544
- }
65545
- ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
65546
- CycleTimeOverTimeChart,
65547
- {
65548
- data: workspace.hourly_action_counts || [],
65549
- idealCycleTime: workspace.ideal_cycle_time || 0,
65550
- shiftStart: workspace.shift_start || ""
65551
- }
65552
- ) : /* @__PURE__ */ jsxRuntime.jsx(
65553
- HourlyOutputChart2,
65554
- {
65555
- data: workspace.hourly_action_counts || [],
65556
- pphThreshold: workspace.pph_threshold || 0,
65557
- shiftStart: workspace.shift_start || "06:00",
65558
- shiftEnd: workspace.shift_end,
65559
- showIdleTime: showChartIdleTime,
65560
- idleTimeHourly: workspace.idle_time_hourly,
65561
- idleTimeClips,
65562
- idleTimeClipClassifications,
65563
- shiftDate: idleClipDate,
65564
- timezone
65565
- }
65566
- ) })
65567
- ]
65568
- }
65569
- ),
65570
- !shouldShowCycleTimeChart && idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
65571
- motion.div,
65572
- {
65573
- className: "bg-white rounded-lg shadow-sm p-4 lg:col-span-2 flex flex-col min-h-0",
65574
- variants: chartCardVariants,
65575
- initial: "initial",
65576
- animate: "animate",
65577
- children: [
65578
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Idle Time Breakdown" }),
65579
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65580
- IdleTimeReasonChart,
65581
- {
65582
- data: idleTimeData.chartData,
65583
- isLoading: idleTimeData.isLoading,
65584
- error: idleTimeData.error
65585
- }
65586
- ) })
65587
- ]
65588
- }
65589
- )
65590
- ] }),
65691
+ /* @__PURE__ */ jsxRuntime.jsxs(
65692
+ "div",
65693
+ {
65694
+ className: clsx(
65695
+ "grid grid-cols-1 gap-3 min-h-0",
65696
+ isUptimeMode && showIdleBreakdownChart ? "lg:grid-cols-3" : "lg:grid-cols-10",
65697
+ desktopTopSectionClass
65698
+ ),
65699
+ children: [
65700
+ !shouldShowCycleTimeChart && !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsxs(
65701
+ motion.div,
65702
+ {
65703
+ className: "bg-white rounded-lg shadow-sm p-4 lg:col-span-2 flex flex-col min-h-0",
65704
+ variants: chartCardVariants,
65705
+ initial: "initial",
65706
+ animate: "animate",
65707
+ children: [
65708
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Today's Output" }),
65709
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65710
+ OutputProgressChart,
65711
+ {
65712
+ currentOutput: workspace.total_actions || 0,
65713
+ targetOutput: workspace.target_output || 0
65714
+ }
65715
+ ) })
65716
+ ]
65717
+ }
65718
+ ),
65719
+ /* @__PURE__ */ jsxRuntime.jsxs(
65720
+ motion.div,
65721
+ {
65722
+ className: clsx(
65723
+ "bg-white rounded-lg shadow-sm p-4 flex flex-col min-h-0",
65724
+ isUptimeMode && showIdleBreakdownChart ? "lg:col-span-2" : shouldShowCycleTimeChart || isUptimeMode ? "lg:col-span-10" : idleTimeVlmEnabled ? "lg:col-span-6" : "lg:col-span-8"
65725
+ ),
65726
+ variants: chartCardVariants,
65727
+ initial: "initial",
65728
+ animate: "animate",
65729
+ children: [
65730
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 mb-4 flex-none", children: [
65731
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle Time (last 60 minutes)" : "Hourly Output" }),
65732
+ !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsx(
65733
+ "button",
65734
+ {
65735
+ onClick: () => setShowChartIdleTime(!showChartIdleTime),
65736
+ className: `inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${showChartIdleTime ? "bg-blue-50 text-blue-700 border border-blue-200" : "bg-white text-gray-700 border border-gray-300 hover:bg-gray-50"}`,
65737
+ children: showChartIdleTime ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65738
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: "w-4 h-4 mr-1.5" }),
65739
+ "Hide Idle Time"
65740
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65741
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "w-4 h-4 mr-1.5" }),
65742
+ "Show Idle Time"
65743
+ ] })
65744
+ }
65745
+ )
65746
+ ] }),
65747
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: isUptimeMode ? /* @__PURE__ */ jsxRuntime.jsx(
65748
+ HourlyUptimeChart,
65749
+ {
65750
+ idleTimeHourly: workspace.idle_time_hourly,
65751
+ shiftStart: workspace.shift_start,
65752
+ shiftEnd: workspace.shift_end,
65753
+ shiftDate: idleClipDate,
65754
+ timezone,
65755
+ elapsedMinutes: elapsedShiftMinutes
65756
+ }
65757
+ ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
65758
+ CycleTimeOverTimeChart,
65759
+ {
65760
+ data: workspace.hourly_action_counts || [],
65761
+ idealCycleTime: workspace.ideal_cycle_time || 0,
65762
+ shiftStart: workspace.shift_start || ""
65763
+ }
65764
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
65765
+ HourlyOutputChart2,
65766
+ {
65767
+ data: workspace.hourly_action_counts || [],
65768
+ pphThreshold: workspace.pph_threshold || 0,
65769
+ shiftStart: workspace.shift_start || "06:00",
65770
+ shiftEnd: workspace.shift_end,
65771
+ showIdleTime: showChartIdleTime,
65772
+ idleTimeHourly: workspace.idle_time_hourly,
65773
+ idleTimeClips,
65774
+ idleTimeClipClassifications,
65775
+ shiftDate: idleClipDate,
65776
+ timezone
65777
+ }
65778
+ ) })
65779
+ ]
65780
+ }
65781
+ ),
65782
+ !shouldShowCycleTimeChart && idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
65783
+ motion.div,
65784
+ {
65785
+ className: clsx(
65786
+ "bg-white rounded-lg shadow-sm p-4 flex flex-col min-h-0",
65787
+ isUptimeMode ? "lg:col-span-1" : "lg:col-span-2"
65788
+ ),
65789
+ variants: chartCardVariants,
65790
+ initial: "initial",
65791
+ animate: "animate",
65792
+ children: [
65793
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Idle Time Breakdown" }),
65794
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65795
+ IdleTimeReasonChart,
65796
+ {
65797
+ data: idleTimeData.chartData,
65798
+ isLoading: idleTimeData.isLoading,
65799
+ error: idleTimeData.error
65800
+ }
65801
+ ) })
65802
+ ]
65803
+ }
65804
+ )
65805
+ ]
65806
+ }
65807
+ ),
65591
65808
  isUptimeMode ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("flex min-h-0", desktopBottomSectionClass), children: /* @__PURE__ */ jsxRuntime.jsx(UptimeMetricCards, { workspace, uptimePieData, className: "flex-1" }) }) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 min-h-0", desktopBottomSectionClass), children: [
65592
65809
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
65593
65810
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Efficiency" }) }),