@optifye/dashboard-core 6.11.8 → 6.11.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.css CHANGED
@@ -5545,6 +5545,9 @@ input[type=range]:active::-moz-range-thumb {
5545
5545
  --tw-bg-opacity: 1;
5546
5546
  background-color: rgb(30 41 59 / var(--tw-bg-opacity, 1));
5547
5547
  }
5548
+ .hover\:bg-transparent:hover {
5549
+ background-color: transparent;
5550
+ }
5548
5551
  .hover\:bg-white:hover {
5549
5552
  --tw-bg-opacity: 1;
5550
5553
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
@@ -5981,6 +5984,9 @@ input[type=range]:active::-moz-range-thumb {
5981
5984
  --tw-bg-opacity: 1;
5982
5985
  background-color: rgb(96 165 250 / var(--tw-bg-opacity, 1));
5983
5986
  }
5987
+ .disabled\:opacity-40:disabled {
5988
+ opacity: 0.4;
5989
+ }
5984
5990
  .disabled\:opacity-50:disabled {
5985
5991
  opacity: 0.5;
5986
5992
  }
package/dist/index.js CHANGED
@@ -74667,33 +74667,51 @@ var OverviewImprovementsSkeleton = () => /* @__PURE__ */ jsxRuntime.jsx("div", {
74667
74667
  var OperationsOverviewHeader = React141__namespace.default.memo(({
74668
74668
  dateRange,
74669
74669
  trendMode,
74670
+ lineOptions,
74671
+ selectedLineIds,
74670
74672
  appTimezone,
74671
74673
  mobileMenuContext,
74672
74674
  onDateRangeChange,
74673
- onTrendModeChange
74675
+ onTrendModeChange,
74676
+ onSelectedLineIdsChange
74674
74677
  }) => {
74675
74678
  bumpRenderCounter();
74676
74679
  const [isFilterOpen, setIsFilterOpen] = React141__namespace.default.useState(false);
74680
+ const [isLinesDropdownOpen, setIsLinesDropdownOpen] = React141__namespace.default.useState(false);
74677
74681
  const filterRef = React141__namespace.default.useRef(null);
74678
74682
  const filterButtonRef = React141__namespace.default.useRef(null);
74679
74683
  const mobileFilterButtonRef = React141__namespace.default.useRef(null);
74680
- const currentWeekRange = React141__namespace.default.useMemo(
74681
- () => getCurrentWeekToDateRange(appTimezone),
74682
- [appTimezone]
74683
- );
74684
- const isCurrentWeekToDateRange = dateRange.startKey === currentWeekRange.startKey && dateRange.endKey === currentWeekRange.endKey;
74684
+ const linesDropdownRef = React141__namespace.default.useRef(null);
74685
74685
  const mobileSubtitle = React141__namespace.default.useMemo(() => {
74686
- if (isCurrentWeekToDateRange) {
74687
- return `Week of ${dateFns.format(parseDateKeyToDate(dateRange.startKey), "do MMM")}`;
74686
+ if (dateRange.startKey === dateRange.endKey) {
74687
+ return dateFns.format(parseDateKeyToDate(dateRange.startKey), "do MMM, yyyy");
74688
74688
  }
74689
74689
  return `${dateFns.format(parseDateKeyToDate(dateRange.startKey), "do MMM")} - ${dateFns.format(parseDateKeyToDate(dateRange.endKey), "do MMM, yyyy")}`;
74690
- }, [dateRange.endKey, dateRange.startKey, isCurrentWeekToDateRange]);
74690
+ }, [dateRange.endKey, dateRange.startKey]);
74691
74691
  const desktopSubtitle = React141__namespace.default.useMemo(() => {
74692
- if (isCurrentWeekToDateRange) {
74693
- return `Week of ${dateFns.format(parseDateKeyToDate(dateRange.startKey), "do MMMM, yyyy")}`;
74692
+ if (dateRange.startKey === dateRange.endKey) {
74693
+ return dateFns.format(parseDateKeyToDate(dateRange.startKey), "do MMMM, yyyy");
74694
74694
  }
74695
74695
  return `${dateFns.format(parseDateKeyToDate(dateRange.startKey), "do MMMM, yyyy")} - ${dateFns.format(parseDateKeyToDate(dateRange.endKey), "do MMMM, yyyy")}`;
74696
- }, [dateRange.endKey, dateRange.startKey, isCurrentWeekToDateRange]);
74696
+ }, [dateRange.endKey, dateRange.startKey]);
74697
+ const availableLineIds = React141__namespace.default.useMemo(
74698
+ () => lineOptions.map((line) => line.id),
74699
+ [lineOptions]
74700
+ );
74701
+ const selectedLineIdSet = React141__namespace.default.useMemo(
74702
+ () => new Set(selectedLineIds),
74703
+ [selectedLineIds]
74704
+ );
74705
+ const isAllLinesSelected = React141__namespace.default.useMemo(() => {
74706
+ if (availableLineIds.length === 0) return true;
74707
+ return availableLineIds.every((lineId) => selectedLineIdSet.has(lineId));
74708
+ }, [availableLineIds, selectedLineIdSet]);
74709
+ const activeFilterCount = React141__namespace.default.useMemo(() => {
74710
+ let count = 0;
74711
+ if (trendMode !== "all") count += 1;
74712
+ if (!isAllLinesSelected) count += 1;
74713
+ return count;
74714
+ }, [isAllLinesSelected, trendMode]);
74697
74715
  const handleFilterToggle = React141__namespace.default.useCallback(() => {
74698
74716
  trackCoreEvent("Operations Overview Filter Toggled", {
74699
74717
  action: !isFilterOpen ? "open" : "close"
@@ -74707,10 +74725,43 @@ var OperationsOverviewHeader = React141__namespace.default.memo(({
74707
74725
  });
74708
74726
  onTrendModeChange(nextMode);
74709
74727
  }, [onTrendModeChange]);
74728
+ const handleAllLinesToggle = React141__namespace.default.useCallback(() => {
74729
+ trackCoreEvent("Operations Overview Line Filter Changed", {
74730
+ selected_line_ids: availableLineIds,
74731
+ selected_line_count: availableLineIds.length,
74732
+ mode: "all"
74733
+ });
74734
+ onSelectedLineIdsChange(availableLineIds);
74735
+ }, [availableLineIds, onSelectedLineIdsChange]);
74736
+ const handleLineToggle = React141__namespace.default.useCallback((lineId) => {
74737
+ const current = new Set(selectedLineIds);
74738
+ if (current.has(lineId)) {
74739
+ if (current.size <= 1) return;
74740
+ current.delete(lineId);
74741
+ } else {
74742
+ current.add(lineId);
74743
+ }
74744
+ const next = availableLineIds.filter((id3) => current.has(id3));
74745
+ trackCoreEvent("Operations Overview Line Filter Changed", {
74746
+ selected_line_ids: next,
74747
+ selected_line_count: next.length,
74748
+ mode: "custom"
74749
+ });
74750
+ onSelectedLineIdsChange(next);
74751
+ }, [availableLineIds, onSelectedLineIdsChange, selectedLineIds]);
74752
+ const handleClearAllFilters = React141__namespace.default.useCallback(() => {
74753
+ onTrendModeChange("all");
74754
+ onSelectedLineIdsChange(availableLineIds);
74755
+ setIsFilterOpen(false);
74756
+ }, [availableLineIds, onSelectedLineIdsChange, onTrendModeChange]);
74710
74757
  React141__namespace.default.useEffect(() => {
74711
74758
  const handleClickOutside = (event) => {
74712
- if (filterRef.current && !filterRef.current.contains(event.target) && filterButtonRef.current && !filterButtonRef.current.contains(event.target) && mobileFilterButtonRef.current && !mobileFilterButtonRef.current.contains(event.target)) {
74759
+ const target = event.target;
74760
+ if (filterRef.current && !filterRef.current.contains(target) && filterButtonRef.current && !filterButtonRef.current.contains(target) && mobileFilterButtonRef.current && !mobileFilterButtonRef.current.contains(target)) {
74713
74761
  setIsFilterOpen(false);
74762
+ setIsLinesDropdownOpen(false);
74763
+ } else if (linesDropdownRef.current && !linesDropdownRef.current.contains(target)) {
74764
+ setIsLinesDropdownOpen(false);
74714
74765
  }
74715
74766
  };
74716
74767
  document.addEventListener("mousedown", handleClickOutside);
@@ -74749,11 +74800,11 @@ var OperationsOverviewHeader = React141__namespace.default.memo(({
74749
74800
  {
74750
74801
  ref: mobileFilterButtonRef,
74751
74802
  onClick: handleFilterToggle,
74752
- className: `p-2 rounded-full transition-colors relative ${isFilterOpen || trendMode !== "all" ? "bg-blue-50" : "active:bg-gray-100"}`,
74803
+ className: `p-2 rounded-full transition-colors relative ${isFilterOpen || activeFilterCount > 0 ? "bg-blue-50" : "active:bg-gray-100"}`,
74753
74804
  "aria-label": "Open filters",
74754
74805
  children: [
74755
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: `w-5 h-5 ${trendMode !== "all" ? "text-blue-600" : "text-gray-700"}` }),
74756
- trendMode !== "all" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex items-center justify-center w-4 h-4 bg-blue-600 text-white text-[10px] rounded-full font-bold", children: "1" }) : null
74806
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: `w-5 h-5 ${activeFilterCount > 0 ? "text-blue-600" : "text-gray-700"}` }),
74807
+ activeFilterCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex items-center justify-center w-4 h-4 bg-blue-600 text-white text-[10px] rounded-full font-bold", children: activeFilterCount }) : null
74757
74808
  ]
74758
74809
  }
74759
74810
  )
@@ -74781,10 +74832,10 @@ var OperationsOverviewHeader = React141__namespace.default.memo(({
74781
74832
  {
74782
74833
  ref: filterButtonRef,
74783
74834
  onClick: handleFilterToggle,
74784
- className: `flex items-center gap-2 px-3 py-1.5 rounded-lg border text-sm font-medium transition-all shadow-sm ${isFilterOpen || trendMode !== "all" ? "border-blue-500 bg-blue-50 text-blue-700 ring-1 ring-blue-500" : "border-slate-200 bg-white text-slate-700 hover:bg-slate-50"}`,
74835
+ className: `flex items-center gap-2 px-3 py-1.5 rounded-lg border text-sm font-medium transition-all shadow-sm ${isFilterOpen || activeFilterCount > 0 ? "border-blue-500 bg-blue-50 text-blue-700 ring-1 ring-blue-500" : "border-slate-200 bg-white text-slate-700 hover:bg-slate-50"}`,
74785
74836
  "aria-label": "Open filters",
74786
74837
  children: [
74787
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: `w-[18px] h-[18px] ${trendMode !== "all" ? "text-blue-600" : "text-slate-500"}` }),
74838
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: `w-[18px] h-[18px] ${activeFilterCount > 0 ? "text-blue-600" : "text-slate-500"}` }),
74788
74839
  "Filters",
74789
74840
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `w-4 h-4 ml-0.5 transition-transform duration-200 ${isFilterOpen ? "rotate-180" : ""}` })
74790
74841
  ]
@@ -74795,40 +74846,94 @@ var OperationsOverviewHeader = React141__namespace.default.memo(({
74795
74846
  isFilterOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: filterRef, className: "absolute right-3 sm:right-4 md:right-5 lg:right-6 top-full mt-2 w-72 bg-white rounded-xl shadow-xl border border-gray-100 p-4 z-50", children: [
74796
74847
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
74797
74848
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-900", children: "Filter View" }),
74798
- trendMode !== "all" ? /* @__PURE__ */ jsxRuntime.jsx(
74849
+ activeFilterCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
74799
74850
  "button",
74800
74851
  {
74801
- onClick: () => {
74802
- onTrendModeChange("all");
74803
- setIsFilterOpen(false);
74804
- },
74852
+ onClick: handleClearAllFilters,
74805
74853
  className: "text-xs text-red-600 hover:text-red-700 font-medium",
74806
74854
  children: "Clear all"
74807
74855
  }
74808
74856
  ) : null
74809
74857
  ] }),
74810
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
74811
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Shift" }),
74812
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsxs(
74813
- "select",
74814
- {
74815
- value: trendMode,
74816
- onChange: handleTrendModeChange,
74817
- className: "w-full appearance-none pl-3 pr-8 py-2 text-sm bg-gray-50 border border-gray-200 hover:border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all cursor-pointer",
74818
- style: {
74819
- backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
74820
- backgroundPosition: "right 0.75rem center",
74821
- backgroundRepeat: "no-repeat",
74822
- backgroundSize: "1.2em 1.2em"
74823
- },
74824
- children: [
74825
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Shifts" }),
74826
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day Shift" }),
74827
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "night", children: "Night Shift" })
74828
- ]
74829
- }
74830
- ) })
74831
- ] }) })
74858
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
74859
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
74860
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Shift" }),
74861
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsxs(
74862
+ "select",
74863
+ {
74864
+ value: trendMode,
74865
+ onChange: handleTrendModeChange,
74866
+ className: "w-full appearance-none pl-3 pr-8 py-2 text-sm bg-gray-50 border border-gray-200 hover:border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all cursor-pointer",
74867
+ style: {
74868
+ backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
74869
+ backgroundPosition: "right 0.75rem center",
74870
+ backgroundRepeat: "no-repeat",
74871
+ backgroundSize: "1.2em 1.2em"
74872
+ },
74873
+ children: [
74874
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Shifts" }),
74875
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day Shift" }),
74876
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "night", children: "Night Shift" })
74877
+ ]
74878
+ }
74879
+ ) })
74880
+ ] }),
74881
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", ref: linesDropdownRef, children: [
74882
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Lines" }),
74883
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
74884
+ /* @__PURE__ */ jsxRuntime.jsxs(
74885
+ "button",
74886
+ {
74887
+ type: "button",
74888
+ onClick: () => setIsLinesDropdownOpen((prev) => !prev),
74889
+ className: "w-full flex items-center justify-between pl-3 pr-3 py-2 text-sm bg-gray-50 border border-gray-200 hover:border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all cursor-pointer text-left",
74890
+ children: [
74891
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate pr-2", children: isAllLinesSelected ? "All Lines" : selectedLineIds.length === 1 ? lineOptions.find((l) => l.id === selectedLineIds[0])?.name || "1 Line Selected" : `${selectedLineIds.length} Lines Selected` }),
74892
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `w-4 h-4 text-gray-500 flex-shrink-0 transition-transform ${isLinesDropdownOpen ? "rotate-180" : ""}` })
74893
+ ]
74894
+ }
74895
+ ),
74896
+ isLinesDropdownOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-full left-0 right-0 mt-1 max-h-48 overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg p-1.5 space-y-0.5 z-[60]", children: [
74897
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-gray-900 hover:bg-gray-50 cursor-pointer", children: [
74898
+ /* @__PURE__ */ jsxRuntime.jsx(
74899
+ "input",
74900
+ {
74901
+ type: "checkbox",
74902
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
74903
+ checked: isAllLinesSelected,
74904
+ onChange: handleAllLinesToggle
74905
+ }
74906
+ ),
74907
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "All Lines" })
74908
+ ] }),
74909
+ lineOptions.map((line) => {
74910
+ const isChecked = selectedLineIdSet.has(line.id);
74911
+ const disableUncheck = isChecked && selectedLineIds.length <= 1;
74912
+ return /* @__PURE__ */ jsxRuntime.jsxs(
74913
+ "label",
74914
+ {
74915
+ className: `flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm cursor-pointer transition-colors ${disableUncheck ? "text-gray-400 hover:bg-transparent" : "text-gray-800 hover:bg-gray-50"}`,
74916
+ children: [
74917
+ /* @__PURE__ */ jsxRuntime.jsx(
74918
+ "input",
74919
+ {
74920
+ type: "checkbox",
74921
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500 disabled:opacity-40",
74922
+ checked: isChecked,
74923
+ disabled: disableUncheck,
74924
+ onChange: () => handleLineToggle(line.id)
74925
+ }
74926
+ ),
74927
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: line.name })
74928
+ ]
74929
+ },
74930
+ line.id
74931
+ );
74932
+ })
74933
+ ] }) : null
74934
+ ] })
74935
+ ] })
74936
+ ] })
74832
74937
  ] }) : null
74833
74938
  ] }) });
74834
74939
  });
@@ -75727,6 +75832,7 @@ var PlantHeadView = () => {
75727
75832
  const [dateRange, setDateRange] = React141__namespace.default.useState(() => getCurrentWeekToDateRange(appTimezone));
75728
75833
  const [usesThisWeekComparison, setUsesThisWeekComparison] = React141__namespace.default.useState(true);
75729
75834
  const [trendMode, setTrendMode] = React141__namespace.default.useState("all");
75835
+ const [selectedLineIds, setSelectedLineIds] = React141__namespace.default.useState([]);
75730
75836
  React141__namespace.default.useEffect(() => {
75731
75837
  trackCorePageView("Operations Overview", {
75732
75838
  dashboard_surface: "operations_overview"
@@ -75745,9 +75851,27 @@ var PlantHeadView = () => {
75745
75851
  () => normalizedLineIds.join(","),
75746
75852
  [normalizedLineIds]
75747
75853
  );
75854
+ const lineOptions = React141__namespace.default.useMemo(
75855
+ () => normalizedLineIds.map((lineId) => ({
75856
+ id: lineId,
75857
+ name: getLineDisplayName(entityConfig, lineId)
75858
+ })),
75859
+ [entityConfig, normalizedLineIds]
75860
+ );
75861
+ React141__namespace.default.useEffect(() => {
75862
+ setSelectedLineIds((previous) => {
75863
+ if (normalizedLineIds.length === 0) return [];
75864
+ const previousSet = new Set(previous);
75865
+ const next = normalizedLineIds.filter((lineId) => previousSet.has(lineId));
75866
+ if (next.length === 0) {
75867
+ return normalizedLineIds;
75868
+ }
75869
+ return next;
75870
+ });
75871
+ }, [lineIdsKey, normalizedLineIds]);
75748
75872
  const scopedLineIds = React141__namespace.default.useMemo(
75749
- () => lineIdsKey ? lineIdsKey.split(",") : [],
75750
- [lineIdsKey]
75873
+ () => selectedLineIds.length > 0 ? selectedLineIds : normalizedLineIds,
75874
+ [normalizedLineIds, selectedLineIds]
75751
75875
  );
75752
75876
  const initializedTimezoneRef = React141__namespace.default.useRef(appTimezone);
75753
75877
  React141__namespace.default.useEffect(() => {
@@ -75775,6 +75899,16 @@ var PlantHeadView = () => {
75775
75899
  const handleTrendModeChange = React141__namespace.default.useCallback((mode) => {
75776
75900
  setTrendMode(mode);
75777
75901
  }, []);
75902
+ const handleSelectedLineIdsChange = React141__namespace.default.useCallback((lineIds) => {
75903
+ if (normalizedLineIds.length === 0) {
75904
+ setSelectedLineIds([]);
75905
+ return;
75906
+ }
75907
+ const uniqueSelected = Array.from(new Set(lineIds));
75908
+ const selectedSet = new Set(uniqueSelected);
75909
+ const next = normalizedLineIds.filter((lineId) => selectedSet.has(lineId));
75910
+ setSelectedLineIds(next.length > 0 ? next : normalizedLineIds);
75911
+ }, [normalizedLineIds]);
75778
75912
  const buildLineMonthlyHistoryUrl = React141__namespace.default.useCallback((lineId) => {
75779
75913
  const rangeStartDate = parseDateKeyToDate(dateRange.startKey);
75780
75914
  const params = new URLSearchParams();
@@ -75841,10 +75975,13 @@ var PlantHeadView = () => {
75841
75975
  {
75842
75976
  dateRange,
75843
75977
  trendMode,
75978
+ lineOptions,
75979
+ selectedLineIds: scopedLineIds,
75844
75980
  appTimezone,
75845
75981
  mobileMenuContext,
75846
75982
  onDateRangeChange: handleDateRangeChange,
75847
- onTrendModeChange: handleTrendModeChange
75983
+ onTrendModeChange: handleTrendModeChange,
75984
+ onSelectedLineIdsChange: handleSelectedLineIdsChange
75848
75985
  }
75849
75986
  ),
75850
75987
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 sm:p-6 pb-6 max-w-[1800px] mx-auto w-full flex-1 min-h-0 overflow-y-auto flex flex-col gap-5", children: [
package/dist/index.mjs CHANGED
@@ -74638,33 +74638,51 @@ var OverviewImprovementsSkeleton = () => /* @__PURE__ */ jsx("div", { className:
74638
74638
  var OperationsOverviewHeader = React141__default.memo(({
74639
74639
  dateRange,
74640
74640
  trendMode,
74641
+ lineOptions,
74642
+ selectedLineIds,
74641
74643
  appTimezone,
74642
74644
  mobileMenuContext,
74643
74645
  onDateRangeChange,
74644
- onTrendModeChange
74646
+ onTrendModeChange,
74647
+ onSelectedLineIdsChange
74645
74648
  }) => {
74646
74649
  bumpRenderCounter();
74647
74650
  const [isFilterOpen, setIsFilterOpen] = React141__default.useState(false);
74651
+ const [isLinesDropdownOpen, setIsLinesDropdownOpen] = React141__default.useState(false);
74648
74652
  const filterRef = React141__default.useRef(null);
74649
74653
  const filterButtonRef = React141__default.useRef(null);
74650
74654
  const mobileFilterButtonRef = React141__default.useRef(null);
74651
- const currentWeekRange = React141__default.useMemo(
74652
- () => getCurrentWeekToDateRange(appTimezone),
74653
- [appTimezone]
74654
- );
74655
- const isCurrentWeekToDateRange = dateRange.startKey === currentWeekRange.startKey && dateRange.endKey === currentWeekRange.endKey;
74655
+ const linesDropdownRef = React141__default.useRef(null);
74656
74656
  const mobileSubtitle = React141__default.useMemo(() => {
74657
- if (isCurrentWeekToDateRange) {
74658
- return `Week of ${format(parseDateKeyToDate(dateRange.startKey), "do MMM")}`;
74657
+ if (dateRange.startKey === dateRange.endKey) {
74658
+ return format(parseDateKeyToDate(dateRange.startKey), "do MMM, yyyy");
74659
74659
  }
74660
74660
  return `${format(parseDateKeyToDate(dateRange.startKey), "do MMM")} - ${format(parseDateKeyToDate(dateRange.endKey), "do MMM, yyyy")}`;
74661
- }, [dateRange.endKey, dateRange.startKey, isCurrentWeekToDateRange]);
74661
+ }, [dateRange.endKey, dateRange.startKey]);
74662
74662
  const desktopSubtitle = React141__default.useMemo(() => {
74663
- if (isCurrentWeekToDateRange) {
74664
- return `Week of ${format(parseDateKeyToDate(dateRange.startKey), "do MMMM, yyyy")}`;
74663
+ if (dateRange.startKey === dateRange.endKey) {
74664
+ return format(parseDateKeyToDate(dateRange.startKey), "do MMMM, yyyy");
74665
74665
  }
74666
74666
  return `${format(parseDateKeyToDate(dateRange.startKey), "do MMMM, yyyy")} - ${format(parseDateKeyToDate(dateRange.endKey), "do MMMM, yyyy")}`;
74667
- }, [dateRange.endKey, dateRange.startKey, isCurrentWeekToDateRange]);
74667
+ }, [dateRange.endKey, dateRange.startKey]);
74668
+ const availableLineIds = React141__default.useMemo(
74669
+ () => lineOptions.map((line) => line.id),
74670
+ [lineOptions]
74671
+ );
74672
+ const selectedLineIdSet = React141__default.useMemo(
74673
+ () => new Set(selectedLineIds),
74674
+ [selectedLineIds]
74675
+ );
74676
+ const isAllLinesSelected = React141__default.useMemo(() => {
74677
+ if (availableLineIds.length === 0) return true;
74678
+ return availableLineIds.every((lineId) => selectedLineIdSet.has(lineId));
74679
+ }, [availableLineIds, selectedLineIdSet]);
74680
+ const activeFilterCount = React141__default.useMemo(() => {
74681
+ let count = 0;
74682
+ if (trendMode !== "all") count += 1;
74683
+ if (!isAllLinesSelected) count += 1;
74684
+ return count;
74685
+ }, [isAllLinesSelected, trendMode]);
74668
74686
  const handleFilterToggle = React141__default.useCallback(() => {
74669
74687
  trackCoreEvent("Operations Overview Filter Toggled", {
74670
74688
  action: !isFilterOpen ? "open" : "close"
@@ -74678,10 +74696,43 @@ var OperationsOverviewHeader = React141__default.memo(({
74678
74696
  });
74679
74697
  onTrendModeChange(nextMode);
74680
74698
  }, [onTrendModeChange]);
74699
+ const handleAllLinesToggle = React141__default.useCallback(() => {
74700
+ trackCoreEvent("Operations Overview Line Filter Changed", {
74701
+ selected_line_ids: availableLineIds,
74702
+ selected_line_count: availableLineIds.length,
74703
+ mode: "all"
74704
+ });
74705
+ onSelectedLineIdsChange(availableLineIds);
74706
+ }, [availableLineIds, onSelectedLineIdsChange]);
74707
+ const handleLineToggle = React141__default.useCallback((lineId) => {
74708
+ const current = new Set(selectedLineIds);
74709
+ if (current.has(lineId)) {
74710
+ if (current.size <= 1) return;
74711
+ current.delete(lineId);
74712
+ } else {
74713
+ current.add(lineId);
74714
+ }
74715
+ const next = availableLineIds.filter((id3) => current.has(id3));
74716
+ trackCoreEvent("Operations Overview Line Filter Changed", {
74717
+ selected_line_ids: next,
74718
+ selected_line_count: next.length,
74719
+ mode: "custom"
74720
+ });
74721
+ onSelectedLineIdsChange(next);
74722
+ }, [availableLineIds, onSelectedLineIdsChange, selectedLineIds]);
74723
+ const handleClearAllFilters = React141__default.useCallback(() => {
74724
+ onTrendModeChange("all");
74725
+ onSelectedLineIdsChange(availableLineIds);
74726
+ setIsFilterOpen(false);
74727
+ }, [availableLineIds, onSelectedLineIdsChange, onTrendModeChange]);
74681
74728
  React141__default.useEffect(() => {
74682
74729
  const handleClickOutside = (event) => {
74683
- if (filterRef.current && !filterRef.current.contains(event.target) && filterButtonRef.current && !filterButtonRef.current.contains(event.target) && mobileFilterButtonRef.current && !mobileFilterButtonRef.current.contains(event.target)) {
74730
+ const target = event.target;
74731
+ if (filterRef.current && !filterRef.current.contains(target) && filterButtonRef.current && !filterButtonRef.current.contains(target) && mobileFilterButtonRef.current && !mobileFilterButtonRef.current.contains(target)) {
74684
74732
  setIsFilterOpen(false);
74733
+ setIsLinesDropdownOpen(false);
74734
+ } else if (linesDropdownRef.current && !linesDropdownRef.current.contains(target)) {
74735
+ setIsLinesDropdownOpen(false);
74685
74736
  }
74686
74737
  };
74687
74738
  document.addEventListener("mousedown", handleClickOutside);
@@ -74720,11 +74771,11 @@ var OperationsOverviewHeader = React141__default.memo(({
74720
74771
  {
74721
74772
  ref: mobileFilterButtonRef,
74722
74773
  onClick: handleFilterToggle,
74723
- className: `p-2 rounded-full transition-colors relative ${isFilterOpen || trendMode !== "all" ? "bg-blue-50" : "active:bg-gray-100"}`,
74774
+ className: `p-2 rounded-full transition-colors relative ${isFilterOpen || activeFilterCount > 0 ? "bg-blue-50" : "active:bg-gray-100"}`,
74724
74775
  "aria-label": "Open filters",
74725
74776
  children: [
74726
- /* @__PURE__ */ jsx(Filter, { className: `w-5 h-5 ${trendMode !== "all" ? "text-blue-600" : "text-gray-700"}` }),
74727
- trendMode !== "all" ? /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex items-center justify-center w-4 h-4 bg-blue-600 text-white text-[10px] rounded-full font-bold", children: "1" }) : null
74777
+ /* @__PURE__ */ jsx(Filter, { className: `w-5 h-5 ${activeFilterCount > 0 ? "text-blue-600" : "text-gray-700"}` }),
74778
+ activeFilterCount > 0 ? /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex items-center justify-center w-4 h-4 bg-blue-600 text-white text-[10px] rounded-full font-bold", children: activeFilterCount }) : null
74728
74779
  ]
74729
74780
  }
74730
74781
  )
@@ -74752,10 +74803,10 @@ var OperationsOverviewHeader = React141__default.memo(({
74752
74803
  {
74753
74804
  ref: filterButtonRef,
74754
74805
  onClick: handleFilterToggle,
74755
- className: `flex items-center gap-2 px-3 py-1.5 rounded-lg border text-sm font-medium transition-all shadow-sm ${isFilterOpen || trendMode !== "all" ? "border-blue-500 bg-blue-50 text-blue-700 ring-1 ring-blue-500" : "border-slate-200 bg-white text-slate-700 hover:bg-slate-50"}`,
74806
+ className: `flex items-center gap-2 px-3 py-1.5 rounded-lg border text-sm font-medium transition-all shadow-sm ${isFilterOpen || activeFilterCount > 0 ? "border-blue-500 bg-blue-50 text-blue-700 ring-1 ring-blue-500" : "border-slate-200 bg-white text-slate-700 hover:bg-slate-50"}`,
74756
74807
  "aria-label": "Open filters",
74757
74808
  children: [
74758
- /* @__PURE__ */ jsx(Filter, { className: `w-[18px] h-[18px] ${trendMode !== "all" ? "text-blue-600" : "text-slate-500"}` }),
74809
+ /* @__PURE__ */ jsx(Filter, { className: `w-[18px] h-[18px] ${activeFilterCount > 0 ? "text-blue-600" : "text-slate-500"}` }),
74759
74810
  "Filters",
74760
74811
  /* @__PURE__ */ jsx(ChevronDown, { className: `w-4 h-4 ml-0.5 transition-transform duration-200 ${isFilterOpen ? "rotate-180" : ""}` })
74761
74812
  ]
@@ -74766,40 +74817,94 @@ var OperationsOverviewHeader = React141__default.memo(({
74766
74817
  isFilterOpen ? /* @__PURE__ */ jsxs("div", { ref: filterRef, className: "absolute right-3 sm:right-4 md:right-5 lg:right-6 top-full mt-2 w-72 bg-white rounded-xl shadow-xl border border-gray-100 p-4 z-50", children: [
74767
74818
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
74768
74819
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900", children: "Filter View" }),
74769
- trendMode !== "all" ? /* @__PURE__ */ jsx(
74820
+ activeFilterCount > 0 ? /* @__PURE__ */ jsx(
74770
74821
  "button",
74771
74822
  {
74772
- onClick: () => {
74773
- onTrendModeChange("all");
74774
- setIsFilterOpen(false);
74775
- },
74823
+ onClick: handleClearAllFilters,
74776
74824
  className: "text-xs text-red-600 hover:text-red-700 font-medium",
74777
74825
  children: "Clear all"
74778
74826
  }
74779
74827
  ) : null
74780
74828
  ] }),
74781
- /* @__PURE__ */ jsx("div", { className: "space-y-3", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
74782
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Shift" }),
74783
- /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs(
74784
- "select",
74785
- {
74786
- value: trendMode,
74787
- onChange: handleTrendModeChange,
74788
- className: "w-full appearance-none pl-3 pr-8 py-2 text-sm bg-gray-50 border border-gray-200 hover:border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all cursor-pointer",
74789
- style: {
74790
- backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
74791
- backgroundPosition: "right 0.75rem center",
74792
- backgroundRepeat: "no-repeat",
74793
- backgroundSize: "1.2em 1.2em"
74794
- },
74795
- children: [
74796
- /* @__PURE__ */ jsx("option", { value: "all", children: "All Shifts" }),
74797
- /* @__PURE__ */ jsx("option", { value: "day", children: "Day Shift" }),
74798
- /* @__PURE__ */ jsx("option", { value: "night", children: "Night Shift" })
74799
- ]
74800
- }
74801
- ) })
74802
- ] }) })
74829
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
74830
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
74831
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Shift" }),
74832
+ /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs(
74833
+ "select",
74834
+ {
74835
+ value: trendMode,
74836
+ onChange: handleTrendModeChange,
74837
+ className: "w-full appearance-none pl-3 pr-8 py-2 text-sm bg-gray-50 border border-gray-200 hover:border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all cursor-pointer",
74838
+ style: {
74839
+ backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`,
74840
+ backgroundPosition: "right 0.75rem center",
74841
+ backgroundRepeat: "no-repeat",
74842
+ backgroundSize: "1.2em 1.2em"
74843
+ },
74844
+ children: [
74845
+ /* @__PURE__ */ jsx("option", { value: "all", children: "All Shifts" }),
74846
+ /* @__PURE__ */ jsx("option", { value: "day", children: "Day Shift" }),
74847
+ /* @__PURE__ */ jsx("option", { value: "night", children: "Night Shift" })
74848
+ ]
74849
+ }
74850
+ ) })
74851
+ ] }),
74852
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", ref: linesDropdownRef, children: [
74853
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide ml-1", children: "Lines" }),
74854
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
74855
+ /* @__PURE__ */ jsxs(
74856
+ "button",
74857
+ {
74858
+ type: "button",
74859
+ onClick: () => setIsLinesDropdownOpen((prev) => !prev),
74860
+ className: "w-full flex items-center justify-between pl-3 pr-3 py-2 text-sm bg-gray-50 border border-gray-200 hover:border-gray-300 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all cursor-pointer text-left",
74861
+ children: [
74862
+ /* @__PURE__ */ jsx("span", { className: "truncate pr-2", children: isAllLinesSelected ? "All Lines" : selectedLineIds.length === 1 ? lineOptions.find((l) => l.id === selectedLineIds[0])?.name || "1 Line Selected" : `${selectedLineIds.length} Lines Selected` }),
74863
+ /* @__PURE__ */ jsx(ChevronDown, { className: `w-4 h-4 text-gray-500 flex-shrink-0 transition-transform ${isLinesDropdownOpen ? "rotate-180" : ""}` })
74864
+ ]
74865
+ }
74866
+ ),
74867
+ isLinesDropdownOpen ? /* @__PURE__ */ jsxs("div", { className: "absolute top-full left-0 right-0 mt-1 max-h-48 overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg p-1.5 space-y-0.5 z-[60]", children: [
74868
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-gray-900 hover:bg-gray-50 cursor-pointer", children: [
74869
+ /* @__PURE__ */ jsx(
74870
+ "input",
74871
+ {
74872
+ type: "checkbox",
74873
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
74874
+ checked: isAllLinesSelected,
74875
+ onChange: handleAllLinesToggle
74876
+ }
74877
+ ),
74878
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "All Lines" })
74879
+ ] }),
74880
+ lineOptions.map((line) => {
74881
+ const isChecked = selectedLineIdSet.has(line.id);
74882
+ const disableUncheck = isChecked && selectedLineIds.length <= 1;
74883
+ return /* @__PURE__ */ jsxs(
74884
+ "label",
74885
+ {
74886
+ className: `flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm cursor-pointer transition-colors ${disableUncheck ? "text-gray-400 hover:bg-transparent" : "text-gray-800 hover:bg-gray-50"}`,
74887
+ children: [
74888
+ /* @__PURE__ */ jsx(
74889
+ "input",
74890
+ {
74891
+ type: "checkbox",
74892
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500 disabled:opacity-40",
74893
+ checked: isChecked,
74894
+ disabled: disableUncheck,
74895
+ onChange: () => handleLineToggle(line.id)
74896
+ }
74897
+ ),
74898
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: line.name })
74899
+ ]
74900
+ },
74901
+ line.id
74902
+ );
74903
+ })
74904
+ ] }) : null
74905
+ ] })
74906
+ ] })
74907
+ ] })
74803
74908
  ] }) : null
74804
74909
  ] }) });
74805
74910
  });
@@ -75698,6 +75803,7 @@ var PlantHeadView = () => {
75698
75803
  const [dateRange, setDateRange] = React141__default.useState(() => getCurrentWeekToDateRange(appTimezone));
75699
75804
  const [usesThisWeekComparison, setUsesThisWeekComparison] = React141__default.useState(true);
75700
75805
  const [trendMode, setTrendMode] = React141__default.useState("all");
75806
+ const [selectedLineIds, setSelectedLineIds] = React141__default.useState([]);
75701
75807
  React141__default.useEffect(() => {
75702
75808
  trackCorePageView("Operations Overview", {
75703
75809
  dashboard_surface: "operations_overview"
@@ -75716,9 +75822,27 @@ var PlantHeadView = () => {
75716
75822
  () => normalizedLineIds.join(","),
75717
75823
  [normalizedLineIds]
75718
75824
  );
75825
+ const lineOptions = React141__default.useMemo(
75826
+ () => normalizedLineIds.map((lineId) => ({
75827
+ id: lineId,
75828
+ name: getLineDisplayName(entityConfig, lineId)
75829
+ })),
75830
+ [entityConfig, normalizedLineIds]
75831
+ );
75832
+ React141__default.useEffect(() => {
75833
+ setSelectedLineIds((previous) => {
75834
+ if (normalizedLineIds.length === 0) return [];
75835
+ const previousSet = new Set(previous);
75836
+ const next = normalizedLineIds.filter((lineId) => previousSet.has(lineId));
75837
+ if (next.length === 0) {
75838
+ return normalizedLineIds;
75839
+ }
75840
+ return next;
75841
+ });
75842
+ }, [lineIdsKey, normalizedLineIds]);
75719
75843
  const scopedLineIds = React141__default.useMemo(
75720
- () => lineIdsKey ? lineIdsKey.split(",") : [],
75721
- [lineIdsKey]
75844
+ () => selectedLineIds.length > 0 ? selectedLineIds : normalizedLineIds,
75845
+ [normalizedLineIds, selectedLineIds]
75722
75846
  );
75723
75847
  const initializedTimezoneRef = React141__default.useRef(appTimezone);
75724
75848
  React141__default.useEffect(() => {
@@ -75746,6 +75870,16 @@ var PlantHeadView = () => {
75746
75870
  const handleTrendModeChange = React141__default.useCallback((mode) => {
75747
75871
  setTrendMode(mode);
75748
75872
  }, []);
75873
+ const handleSelectedLineIdsChange = React141__default.useCallback((lineIds) => {
75874
+ if (normalizedLineIds.length === 0) {
75875
+ setSelectedLineIds([]);
75876
+ return;
75877
+ }
75878
+ const uniqueSelected = Array.from(new Set(lineIds));
75879
+ const selectedSet = new Set(uniqueSelected);
75880
+ const next = normalizedLineIds.filter((lineId) => selectedSet.has(lineId));
75881
+ setSelectedLineIds(next.length > 0 ? next : normalizedLineIds);
75882
+ }, [normalizedLineIds]);
75749
75883
  const buildLineMonthlyHistoryUrl = React141__default.useCallback((lineId) => {
75750
75884
  const rangeStartDate = parseDateKeyToDate(dateRange.startKey);
75751
75885
  const params = new URLSearchParams();
@@ -75812,10 +75946,13 @@ var PlantHeadView = () => {
75812
75946
  {
75813
75947
  dateRange,
75814
75948
  trendMode,
75949
+ lineOptions,
75950
+ selectedLineIds: scopedLineIds,
75815
75951
  appTimezone,
75816
75952
  mobileMenuContext,
75817
75953
  onDateRangeChange: handleDateRangeChange,
75818
- onTrendModeChange: handleTrendModeChange
75954
+ onTrendModeChange: handleTrendModeChange,
75955
+ onSelectedLineIdsChange: handleSelectedLineIdsChange
75819
75956
  }
75820
75957
  ),
75821
75958
  /* @__PURE__ */ jsxs("div", { className: "p-4 sm:p-6 pb-6 max-w-[1800px] mx-auto w-full flex-1 min-h-0 overflow-y-auto flex flex-col gap-5", children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.11.8",
3
+ "version": "6.11.9",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",