@optifye/dashboard-core 6.10.49 → 6.10.51

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,
@@ -17351,6 +17401,79 @@ function useCompanyUsersUsage(companyId, options = {}) {
17351
17401
  refetchToday: fetchTodayUsage
17352
17402
  };
17353
17403
  }
17404
+ function useCompanyClipsCost() {
17405
+ const { user, session } = useAuth();
17406
+ const supabase = useSupabase();
17407
+ const config = useDashboardConfig();
17408
+ const entityConfig = useEntityConfig();
17409
+ const [data, setData] = React26.useState(null);
17410
+ const [isLoading, setIsLoading] = React26.useState(true);
17411
+ const [error, setError] = React26.useState(null);
17412
+ const hasFetchedOnceRef = React26.useRef(false);
17413
+ const canViewClipsCost = user?.role_level === "owner" || user?.role_level === "optifye";
17414
+ const companyId = user?.properties?.company_id || user?.company_id || entityConfig.companyId;
17415
+ const apiBaseUrl = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
17416
+ const fetchData = React26.useCallback(async () => {
17417
+ if (!canViewClipsCost || !companyId || !supabase || !apiBaseUrl || !session?.access_token) {
17418
+ setIsLoading(false);
17419
+ setData(null);
17420
+ hasFetchedOnceRef.current = false;
17421
+ return;
17422
+ }
17423
+ if (!hasFetchedOnceRef.current) {
17424
+ setIsLoading(true);
17425
+ }
17426
+ setError(null);
17427
+ try {
17428
+ const [statsResponse, linesResult] = await Promise.all([
17429
+ fetch(`${apiBaseUrl}/api/classification/company-stats?company_id=${encodeURIComponent(companyId)}`, {
17430
+ headers: {
17431
+ "Authorization": `Bearer ${session.access_token}`
17432
+ }
17433
+ }),
17434
+ supabase.from("lines").select("id").eq("company_id", companyId).eq("idle_time_vlm_enabled", true).limit(1)
17435
+ ]);
17436
+ if (!statsResponse.ok) {
17437
+ const errorData = await statsResponse.json().catch(() => ({}));
17438
+ throw new Error(errorData.detail || `Failed to fetch classification stats: ${statsResponse.status}`);
17439
+ }
17440
+ const statsResult = await statsResponse.json();
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) : [];
17448
+ const hasVlmEnabledLine = (linesResult.data?.length || 0) > 0;
17449
+ setData({
17450
+ totalClassifications,
17451
+ monthlyClassifications,
17452
+ monthStart,
17453
+ historicalMonthlyClassifications,
17454
+ hasVlmEnabledLine
17455
+ });
17456
+ } catch (err) {
17457
+ console.error("[useCompanyClipsCost] Error:", err);
17458
+ setError(err instanceof Error ? err.message : "Failed to load clips data");
17459
+ setData(null);
17460
+ } finally {
17461
+ hasFetchedOnceRef.current = true;
17462
+ setIsLoading(false);
17463
+ }
17464
+ }, [canViewClipsCost, companyId, supabase, apiBaseUrl, session?.access_token]);
17465
+ React26.useEffect(() => {
17466
+ fetchData();
17467
+ }, [fetchData]);
17468
+ const hasData = !!(data && data.totalClassifications > 0 && data.hasVlmEnabledLine);
17469
+ return {
17470
+ data,
17471
+ isLoading,
17472
+ error,
17473
+ hasData,
17474
+ refetch: fetchData
17475
+ };
17476
+ }
17354
17477
 
17355
17478
  // src/lib/utils/api.ts
17356
17479
  var apiUtils = {
@@ -34408,6 +34531,8 @@ var HlsVideoPlayer = React26.forwardRef(({
34408
34531
  if (effectiveSrc.startsWith("#EXTM3U")) {
34409
34532
  const safariMode = isSafari();
34410
34533
  const browserName = getBrowserName();
34534
+ const r2WorkerHost = r2WorkerDomain.replace(/^https?:\/\//, "");
34535
+ const isR2Playlist = effectiveSrc.includes(r2WorkerHost);
34411
34536
  const remuxUrl = extractRemuxUrl(effectiveSrc);
34412
34537
  if (remuxUrl) {
34413
34538
  console.log(`[HlsVideoPlayer] Remuxing enabled - using remux playlist URL: ${remuxUrl}`);
@@ -34451,10 +34576,58 @@ var HlsVideoPlayer = React26.forwardRef(({
34451
34576
  } else {
34452
34577
  video.src = remuxUrl;
34453
34578
  }
34579
+ } else if (safariMode && isR2Playlist && Hls__default.default.isSupported()) {
34580
+ const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
34581
+ const blobUrl = URL.createObjectURL(blob);
34582
+ blobUrlRef.current = blobUrl;
34583
+ console.log(`[HlsVideoPlayer] Safari R2 stream - using HLS.js for auth header support (${browserName})`);
34584
+ const hls = new Hls__default.default(mergedHlsConfig);
34585
+ hlsRef.current = hls;
34586
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
34587
+ console.log("[HlsVideoPlayer] Manifest parsed (Safari R2 HLS.js), ready to play");
34588
+ setIsReady(true);
34589
+ eventCallbacksRef.current.onReady?.(player);
34590
+ });
34591
+ hls.on(Hls.Events.ERROR, (event, data) => {
34592
+ console.error("[HlsVideoPlayer] HLS.js error (Safari R2):", data);
34593
+ if (maybeHandleR2Fallback(data)) {
34594
+ return;
34595
+ }
34596
+ if (data.fatal) {
34597
+ let errorInfo;
34598
+ switch (data.type) {
34599
+ case Hls.ErrorTypes.NETWORK_ERROR:
34600
+ errorInfo = ERROR_MAPPING.networkError;
34601
+ console.log("[HlsVideoPlayer] Attempting to recover from network error (Safari R2)");
34602
+ hls.startLoad();
34603
+ break;
34604
+ case Hls.ErrorTypes.MEDIA_ERROR:
34605
+ errorInfo = ERROR_MAPPING.mediaError;
34606
+ console.log("[HlsVideoPlayer] Attempting to recover from media error (Safari R2)");
34607
+ hls.recoverMediaError();
34608
+ break;
34609
+ case Hls.ErrorTypes.MUX_ERROR:
34610
+ errorInfo = ERROR_MAPPING.muxError;
34611
+ break;
34612
+ default:
34613
+ errorInfo = ERROR_MAPPING.otherError;
34614
+ break;
34615
+ }
34616
+ errorInfo.details = data.details;
34617
+ eventCallbacksRef.current.onError?.(player, errorInfo);
34618
+ }
34619
+ });
34620
+ hls.on(Hls.Events.FRAG_LOADED, () => {
34621
+ setIsLoading(false);
34622
+ eventCallbacksRef.current.onLoadingChange?.(false);
34623
+ });
34624
+ hls.loadSource(blobUrl);
34625
+ hls.attachMedia(video);
34454
34626
  } else if (safariMode) {
34455
34627
  const clipIdMatch = effectiveSrc.match(/# Clip ID: ([a-f0-9-]+)/i);
34456
34628
  if (clipIdMatch) {
34457
- const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
34629
+ const safariParam = isR2Playlist ? "?safari=true" : "";
34630
+ const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}${safariParam}`;
34458
34631
  console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
34459
34632
  video.src = proxyUrl;
34460
34633
  } else {
@@ -36656,6 +36829,7 @@ var FileManagerFilters = ({
36656
36829
  const endInputRef = React26.useRef(null);
36657
36830
  const [idleLabelFilter, setIdleLabelFilter] = React26.useState(null);
36658
36831
  const [showIdleLabelFilterModal, setShowIdleLabelFilterModal] = React26.useState(false);
36832
+ const [isLoadingIdleReasonOptions, setIsLoadingIdleReasonOptions] = React26.useState(false);
36659
36833
  const timezone = useAppTimezone();
36660
36834
  const supabase = useSupabase();
36661
36835
  const [clipMetadata, setClipMetadata] = React26.useState({});
@@ -36698,18 +36872,27 @@ var FileManagerFilters = ({
36698
36872
  iconColor: colorConfig.text
36699
36873
  };
36700
36874
  };
36701
- const ROOT_CAUSE_OPTIONS = [
36702
- "Operator Absent",
36703
- "Operator Idle",
36704
- "Machine Downtime",
36705
- "No Material"
36706
- ];
36875
+ const normalizeIdleReasonLabel = React26.useCallback((label) => {
36876
+ return label.replace(/_/g, " ").trim();
36877
+ }, []);
36707
36878
  const getIdleTimeRootCause = React26.useCallback((clipId) => {
36708
36879
  const classification = mergedClipClassifications[clipId];
36709
36880
  if (!classification) return "processing";
36710
36881
  if (classification.status === "processing") return "processing";
36711
36882
  return classification.label || "processing";
36712
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]);
36713
36896
  const getClipBadge = React26.useCallback((node) => {
36714
36897
  if (node.categoryId === "idle_time" || node.categoryId === "low_value") {
36715
36898
  return { text: "Idle", className: "bg-red-100 text-red-700" };
@@ -36737,6 +36920,79 @@ var FileManagerFilters = ({
36737
36920
  return null;
36738
36921
  }
36739
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]);
36740
36996
  const fetchClipMetadata = React26.useCallback(async (categoryId, page = 1) => {
36741
36997
  if (!workspaceId || !date || shift === void 0) {
36742
36998
  console.warn("[FileManager] Missing required params for clip metadata fetch");
@@ -36745,69 +37001,13 @@ var FileManagerFilters = ({
36745
37001
  const loadingKey = `${categoryId}-${page}`;
36746
37002
  setLoadingCategories((prev) => /* @__PURE__ */ new Set([...prev, loadingKey]));
36747
37003
  try {
36748
- const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
36749
- method: "POST",
36750
- headers: {
36751
- "Content-Type": "application/json"
36752
- },
36753
- body: JSON.stringify({
36754
- action: "clip-metadata",
36755
- workspaceId,
36756
- date,
36757
- shift: shift.toString(),
36758
- category: categoryId,
36759
- page,
36760
- limit: 50,
36761
- snapshotDateTime,
36762
- snapshotClipId
36763
- }),
36764
- redirectReason: "session_expired"
36765
- });
36766
- if (!response.ok) {
36767
- throw new Error(`API error: ${response.status}`);
36768
- }
36769
- const data = await response.json();
37004
+ const data = await fetchClipMetadataPage(categoryId, page);
36770
37005
  setClipMetadata((prev) => ({
36771
37006
  ...prev,
36772
37007
  [categoryId]: page === 1 ? data.clips : [...prev[categoryId] || [], ...data.clips]
36773
37008
  }));
36774
- const authToken = categoryId === "idle_time" && idleTimeVlmEnabled ? await getAuthToken3() : null;
36775
- if (categoryId === "idle_time" && idleTimeVlmEnabled && authToken) {
36776
- const seededClassifications = {};
36777
- (data.clips || []).forEach((clip) => {
36778
- if (!clip.clipId) {
36779
- return;
36780
- }
36781
- if (clip.classification_status) {
36782
- seededClassifications[clip.clipId] = {
36783
- status: clip.classification_status,
36784
- label: clip.classification_label || void 0,
36785
- confidence: clip.classification_confidence ?? void 0
36786
- };
36787
- }
36788
- });
36789
- if (Object.keys(seededClassifications).length > 0) {
36790
- setLocalClipClassifications((prev) => ({
36791
- ...prev,
36792
- ...seededClassifications
36793
- }));
36794
- }
36795
- const clipIds = (data.clips || []).map((clip) => clip.clipId || clip.id).filter(Boolean);
36796
- const newClipIds = clipIds.filter((id3) => {
36797
- if (mergedClipClassifications[id3]?.status === "classified") return false;
36798
- if (seededClassifications[id3]?.status === "classified") return false;
36799
- return true;
36800
- });
36801
- if (newClipIds.length > 0) {
36802
- fetchClassifications(newClipIds, authToken).then((classifications) => {
36803
- setLocalClipClassifications((prev) => ({
36804
- ...prev,
36805
- ...classifications
36806
- }));
36807
- }).catch((error) => {
36808
- console.error("[FileManager] Error fetching idle classifications:", error);
36809
- });
36810
- }
37009
+ if (categoryId === "idle_time" && idleTimeVlmEnabled) {
37010
+ await seedIdleClassifications(data.clips || []);
36811
37011
  }
36812
37012
  setCategoryPages((prev) => ({ ...prev, [categoryId]: page }));
36813
37013
  setCategoryHasMore((prev) => ({ ...prev, [categoryId]: data.hasMore }));
@@ -36821,7 +37021,64 @@ var FileManagerFilters = ({
36821
37021
  return newSet;
36822
37022
  });
36823
37023
  }
36824
- }, [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]);
36825
37082
  const fetchPercentileClips = React26.useCallback(async (type) => {
36826
37083
  if (!workspaceId || !date || shift === void 0) {
36827
37084
  console.warn("[FileManager] Missing required params for percentile clips fetch");
@@ -37492,7 +37749,7 @@ var FileManagerFilters = ({
37492
37749
  activeFilter === "idle_time" && idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsx(
37493
37750
  "button",
37494
37751
  {
37495
- onClick: () => setShowIdleLabelFilterModal(true),
37752
+ onClick: handleOpenIdleLabelFilterModal,
37496
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"}`,
37497
37754
  title: "Filter by idle reason",
37498
37755
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Tag, { className: "h-5 w-5" })
@@ -37580,7 +37837,10 @@ var FileManagerFilters = ({
37580
37837
  }
37581
37838
  )
37582
37839
  ] }),
37583
- /* @__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) => {
37584
37844
  const config = getRootCauseConfig(reason);
37585
37845
  return /* @__PURE__ */ jsxRuntime.jsxs(
37586
37846
  "button",
@@ -48917,6 +49177,7 @@ var SideNavBar = React26.memo(({
48917
49177
  dashboardConfig?.supervisorConfig?.enabled || false;
48918
49178
  const showSupervisorManagement = false;
48919
49179
  const ticketsEnabled = dashboardConfig?.ticketsConfig?.enabled ?? true;
49180
+ const { hasData: hasClipsCostData } = useCompanyClipsCost();
48920
49181
  console.log("\u{1F50D} [SideNavBar] dashboardConfig:", dashboardConfig);
48921
49182
  console.log("\u{1F50D} [SideNavBar] ticketsConfig:", dashboardConfig?.ticketsConfig);
48922
49183
  console.log("\u{1F50D} [SideNavBar] ticketsEnabled:", ticketsEnabled);
@@ -49073,6 +49334,17 @@ var SideNavBar = React26.memo(({
49073
49334
  });
49074
49335
  onMobileMenuClose?.();
49075
49336
  }, [navigate, onMobileMenuClose]);
49337
+ const handleClipsCostClick = React26.useCallback(() => {
49338
+ navigate("/clips-cost", {
49339
+ trackingEvent: {
49340
+ name: "Clips Cost Page Clicked",
49341
+ properties: {
49342
+ source: "side_nav"
49343
+ }
49344
+ }
49345
+ });
49346
+ onMobileMenuClose?.();
49347
+ }, [navigate, onMobileMenuClose]);
49076
49348
  const [isSettingsOpen, setIsSettingsOpen] = React26.useState(false);
49077
49349
  const settingsTriggerRef = React26.useRef(null);
49078
49350
  const settingsItems = React26.useMemo(() => {
@@ -49130,6 +49402,18 @@ var SideNavBar = React26.memo(({
49130
49402
  isActive: pathname === "/tickets" || pathname.startsWith("/tickets/")
49131
49403
  });
49132
49404
  }
49405
+ if (hasClipsCostData) {
49406
+ items.push({
49407
+ key: "clips-analysis",
49408
+ label: "Billing",
49409
+ icon: outline.CurrencyDollarIcon,
49410
+ onClick: () => {
49411
+ handleClipsCostClick();
49412
+ setIsSettingsOpen(false);
49413
+ },
49414
+ isActive: pathname === "/clips-cost" || pathname.startsWith("/clips-cost/")
49415
+ });
49416
+ }
49133
49417
  if (canAccessPath("/help")) {
49134
49418
  items.push({
49135
49419
  key: "help",
@@ -49143,7 +49427,7 @@ var SideNavBar = React26.memo(({
49143
49427
  });
49144
49428
  }
49145
49429
  return items;
49146
- }, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleHelpClick, pathname, ticketsEnabled, canAccessPath]);
49430
+ }, [handleTargetsClick, handleShiftsClick, handleTeamManagementClick, handleProfileClick, handleTicketsClick, handleClipsCostClick, handleHelpClick, pathname, ticketsEnabled, hasClipsCostData, canAccessPath]);
49147
49431
  const handleLogout = React26.useCallback(async () => {
49148
49432
  setIsSettingsOpen(false);
49149
49433
  try {
@@ -61402,6 +61686,102 @@ var ProfileView = () => {
61402
61686
  ] });
61403
61687
  };
61404
61688
  var ProfileView_default = ProfileView;
61689
+ var REFRESH_INTERVAL_MS = 30 * 1e3;
61690
+ var ClipsCostView = () => {
61691
+ const { data, isLoading, error, refetch } = useCompanyClipsCost();
61692
+ const navigation = useNavigation();
61693
+ const refetchRef = React26.useRef(refetch);
61694
+ React26.useEffect(() => {
61695
+ refetchRef.current = refetch;
61696
+ }, [refetch]);
61697
+ React26.useEffect(() => {
61698
+ const intervalId = setInterval(() => {
61699
+ void refetchRef.current();
61700
+ }, REFRESH_INTERVAL_MS);
61701
+ return () => clearInterval(intervalId);
61702
+ }, []);
61703
+ const mobileMenuContext = useMobileMenu();
61704
+ useHideMobileHeader(!!mobileMenuContext);
61705
+ const formatNumber = (num) => {
61706
+ return new Intl.NumberFormat("en-US").format(num);
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
+ };
61718
+ if (isLoading) {
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..." }) });
61720
+ }
61721
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", 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: [
61724
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children: mobileMenuContext && /* @__PURE__ */ jsxRuntime.jsx(
61725
+ HamburgerButton,
61726
+ {
61727
+ onClick: mobileMenuContext.onMobileMenuOpen,
61728
+ className: "flex-shrink-0 -ml-1"
61729
+ }
61730
+ ) }),
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(
61739
+ BackButtonMinimal,
61740
+ {
61741
+ onClick: () => navigation.goToDashboard(),
61742
+ text: "Back",
61743
+ size: "default",
61744
+ "aria-label": "Navigate back to dashboard"
61745
+ }
61746
+ ) }),
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]" })
61752
+ ] }) })
61753
+ ] }) }),
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 })
61759
+ ] })
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
+ ] }) })
61782
+ ] });
61783
+ };
61784
+ var ClipsCostView_default = ClipsCostView;
61405
61785
 
61406
61786
  // src/lib/constants/actions.ts
61407
61787
  var ACTION_NAMES = {
@@ -64680,6 +65060,7 @@ var WorkspaceDetailView = ({
64680
65060
  }, [monthlyData, range]);
64681
65061
  const formattedWorkspaceName = displayName || formatWorkspaceName3(workspace?.workspace_name || "", effectiveLineId);
64682
65062
  const shouldShowCycleTimeChart = !isUptimeMode && (showCycleTimeChart ?? formattedWorkspaceName.startsWith("FINAL ASSY"));
65063
+ const showIdleBreakdownChart = !shouldShowCycleTimeChart && idleTimeVlmEnabled;
64683
65064
  const idleClipDate = date || workspace?.date || calculatedOperationalDate || getOperationalDate(timezone);
64684
65065
  const idleClipShiftId = parsedShiftId ?? workspace?.shift_id;
64685
65066
  const shiftDurationMinutes = React26.useMemo(
@@ -65283,107 +65664,123 @@ var WorkspaceDetailView = ({
65283
65664
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(WorkspaceMetricCards, { workspace, legend: efficiencyLegend, className: "flex-1" }) })
65284
65665
  ] }),
65285
65666
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden lg:flex lg:flex-col lg:h-full lg:min-h-0 gap-3", children: [
65286
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("grid grid-cols-1 lg:grid-cols-10 gap-3 min-h-0", desktopTopSectionClass), children: [
65287
- !shouldShowCycleTimeChart && !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsxs(
65288
- motion.div,
65289
- {
65290
- className: "bg-white rounded-lg shadow-sm p-4 lg:col-span-2 flex flex-col min-h-0",
65291
- variants: chartCardVariants,
65292
- initial: "initial",
65293
- animate: "animate",
65294
- children: [
65295
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Today's Output" }),
65296
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65297
- OutputProgressChart,
65298
- {
65299
- currentOutput: workspace.total_actions || 0,
65300
- targetOutput: workspace.target_output || 0
65301
- }
65302
- ) })
65303
- ]
65304
- }
65305
- ),
65306
- /* @__PURE__ */ jsxRuntime.jsxs(
65307
- motion.div,
65308
- {
65309
- 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"}`,
65310
- variants: chartCardVariants,
65311
- initial: "initial",
65312
- animate: "animate",
65313
- children: [
65314
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 mb-4 flex-none", children: [
65315
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle Time (last 60 minutes)" : "Hourly Output" }),
65316
- !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsx(
65317
- "button",
65318
- {
65319
- onClick: () => setShowChartIdleTime(!showChartIdleTime),
65320
- 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"}`,
65321
- children: showChartIdleTime ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65322
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: "w-4 h-4 mr-1.5" }),
65323
- "Hide Idle Time"
65324
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65325
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "w-4 h-4 mr-1.5" }),
65326
- "Show Idle Time"
65327
- ] })
65328
- }
65329
- )
65330
- ] }),
65331
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: isUptimeMode ? /* @__PURE__ */ jsxRuntime.jsx(
65332
- HourlyUptimeChart,
65333
- {
65334
- idleTimeHourly: workspace.idle_time_hourly,
65335
- shiftStart: workspace.shift_start,
65336
- shiftEnd: workspace.shift_end,
65337
- shiftDate: idleClipDate,
65338
- timezone,
65339
- elapsedMinutes: elapsedShiftMinutes
65340
- }
65341
- ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
65342
- CycleTimeOverTimeChart,
65343
- {
65344
- data: workspace.hourly_action_counts || [],
65345
- idealCycleTime: workspace.ideal_cycle_time || 0,
65346
- shiftStart: workspace.shift_start || ""
65347
- }
65348
- ) : /* @__PURE__ */ jsxRuntime.jsx(
65349
- HourlyOutputChart2,
65350
- {
65351
- data: workspace.hourly_action_counts || [],
65352
- pphThreshold: workspace.pph_threshold || 0,
65353
- shiftStart: workspace.shift_start || "06:00",
65354
- shiftEnd: workspace.shift_end,
65355
- showIdleTime: showChartIdleTime,
65356
- idleTimeHourly: workspace.idle_time_hourly,
65357
- idleTimeClips,
65358
- idleTimeClipClassifications,
65359
- shiftDate: idleClipDate,
65360
- timezone
65361
- }
65362
- ) })
65363
- ]
65364
- }
65365
- ),
65366
- !shouldShowCycleTimeChart && idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
65367
- motion.div,
65368
- {
65369
- className: "bg-white rounded-lg shadow-sm p-4 lg:col-span-2 flex flex-col min-h-0",
65370
- variants: chartCardVariants,
65371
- initial: "initial",
65372
- animate: "animate",
65373
- children: [
65374
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Idle Time Breakdown" }),
65375
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65376
- IdleTimeReasonChart,
65377
- {
65378
- data: idleTimeData.chartData,
65379
- isLoading: idleTimeData.isLoading,
65380
- error: idleTimeData.error
65381
- }
65382
- ) })
65383
- ]
65384
- }
65385
- )
65386
- ] }),
65667
+ /* @__PURE__ */ jsxRuntime.jsxs(
65668
+ "div",
65669
+ {
65670
+ className: clsx(
65671
+ "grid grid-cols-1 gap-3 min-h-0",
65672
+ isUptimeMode && showIdleBreakdownChart ? "lg:grid-cols-3" : "lg:grid-cols-10",
65673
+ desktopTopSectionClass
65674
+ ),
65675
+ children: [
65676
+ !shouldShowCycleTimeChart && !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsxs(
65677
+ motion.div,
65678
+ {
65679
+ className: "bg-white rounded-lg shadow-sm p-4 lg:col-span-2 flex flex-col min-h-0",
65680
+ variants: chartCardVariants,
65681
+ initial: "initial",
65682
+ animate: "animate",
65683
+ children: [
65684
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Today's Output" }),
65685
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65686
+ OutputProgressChart,
65687
+ {
65688
+ currentOutput: workspace.total_actions || 0,
65689
+ targetOutput: workspace.target_output || 0
65690
+ }
65691
+ ) })
65692
+ ]
65693
+ }
65694
+ ),
65695
+ /* @__PURE__ */ jsxRuntime.jsxs(
65696
+ motion.div,
65697
+ {
65698
+ className: clsx(
65699
+ "bg-white rounded-lg shadow-sm p-4 flex flex-col min-h-0",
65700
+ isUptimeMode && showIdleBreakdownChart ? "lg:col-span-2" : shouldShowCycleTimeChart || isUptimeMode ? "lg:col-span-10" : idleTimeVlmEnabled ? "lg:col-span-6" : "lg:col-span-8"
65701
+ ),
65702
+ variants: chartCardVariants,
65703
+ initial: "initial",
65704
+ animate: "animate",
65705
+ children: [
65706
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 mb-4 flex-none", children: [
65707
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700", children: isUptimeMode ? "Machine Utilization" : shouldShowCycleTimeChart ? "Cycle Time (last 60 minutes)" : "Hourly Output" }),
65708
+ !isUptimeMode && /* @__PURE__ */ jsxRuntime.jsx(
65709
+ "button",
65710
+ {
65711
+ onClick: () => setShowChartIdleTime(!showChartIdleTime),
65712
+ 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"}`,
65713
+ children: showChartIdleTime ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65714
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: "w-4 h-4 mr-1.5" }),
65715
+ "Hide Idle Time"
65716
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
65717
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "w-4 h-4 mr-1.5" }),
65718
+ "Show Idle Time"
65719
+ ] })
65720
+ }
65721
+ )
65722
+ ] }),
65723
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: isUptimeMode ? /* @__PURE__ */ jsxRuntime.jsx(
65724
+ HourlyUptimeChart,
65725
+ {
65726
+ idleTimeHourly: workspace.idle_time_hourly,
65727
+ shiftStart: workspace.shift_start,
65728
+ shiftEnd: workspace.shift_end,
65729
+ shiftDate: idleClipDate,
65730
+ timezone,
65731
+ elapsedMinutes: elapsedShiftMinutes
65732
+ }
65733
+ ) : shouldShowCycleTimeChart ? /* @__PURE__ */ jsxRuntime.jsx(
65734
+ CycleTimeOverTimeChart,
65735
+ {
65736
+ data: workspace.hourly_action_counts || [],
65737
+ idealCycleTime: workspace.ideal_cycle_time || 0,
65738
+ shiftStart: workspace.shift_start || ""
65739
+ }
65740
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
65741
+ HourlyOutputChart2,
65742
+ {
65743
+ data: workspace.hourly_action_counts || [],
65744
+ pphThreshold: workspace.pph_threshold || 0,
65745
+ shiftStart: workspace.shift_start || "06:00",
65746
+ shiftEnd: workspace.shift_end,
65747
+ showIdleTime: showChartIdleTime,
65748
+ idleTimeHourly: workspace.idle_time_hourly,
65749
+ idleTimeClips,
65750
+ idleTimeClipClassifications,
65751
+ shiftDate: idleClipDate,
65752
+ timezone
65753
+ }
65754
+ ) })
65755
+ ]
65756
+ }
65757
+ ),
65758
+ !shouldShowCycleTimeChart && idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
65759
+ motion.div,
65760
+ {
65761
+ className: clsx(
65762
+ "bg-white rounded-lg shadow-sm p-4 flex flex-col min-h-0",
65763
+ isUptimeMode ? "lg:col-span-1" : "lg:col-span-2"
65764
+ ),
65765
+ variants: chartCardVariants,
65766
+ initial: "initial",
65767
+ animate: "animate",
65768
+ children: [
65769
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-700 mb-4 text-center", children: "Idle Time Breakdown" }),
65770
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[220px] min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(
65771
+ IdleTimeReasonChart,
65772
+ {
65773
+ data: idleTimeData.chartData,
65774
+ isLoading: idleTimeData.isLoading,
65775
+ error: idleTimeData.error
65776
+ }
65777
+ ) })
65778
+ ]
65779
+ }
65780
+ )
65781
+ ]
65782
+ }
65783
+ ),
65387
65784
  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: [
65388
65785
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
65389
65786
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Efficiency" }) }),
@@ -71708,6 +72105,7 @@ exports.CardHeader = CardHeader2;
71708
72105
  exports.CardTitle = CardTitle2;
71709
72106
  exports.ChangeRoleDialog = ChangeRoleDialog;
71710
72107
  exports.ClipFilterProvider = ClipFilterProvider;
72108
+ exports.ClipsCostView = ClipsCostView_default;
71711
72109
  exports.CompactWorkspaceHealthCard = CompactWorkspaceHealthCard;
71712
72110
  exports.ConfirmRemoveUserDialog = ConfirmRemoveUserDialog;
71713
72111
  exports.CongratulationsOverlay = CongratulationsOverlay;
@@ -72073,6 +72471,7 @@ exports.useClipFilter = useClipFilter;
72073
72471
  exports.useClipTypes = useClipTypes;
72074
72472
  exports.useClipTypesWithCounts = useClipTypesWithCounts;
72075
72473
  exports.useClipsInit = useClipsInit;
72474
+ exports.useCompanyClipsCost = useCompanyClipsCost;
72076
72475
  exports.useCompanyUsersUsage = useCompanyUsersUsage;
72077
72476
  exports.useComponentOverride = useComponentOverride;
72078
72477
  exports.useCustomConfig = useCustomConfig;