@optifye/dashboard-core 6.12.20 → 6.12.22

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
@@ -661,6 +661,9 @@ body {
661
661
  .z-\[100\] {
662
662
  z-index: 100;
663
663
  }
664
+ .z-\[110\] {
665
+ z-index: 110;
666
+ }
664
667
  .z-\[60\] {
665
668
  z-index: 60;
666
669
  }
package/dist/index.js CHANGED
@@ -19916,6 +19916,9 @@ function useNavigation(customNavigate) {
19916
19916
  await router$1.push(path, void 0, { shallow: options?.shallow ?? false });
19917
19917
  }
19918
19918
  } catch (error) {
19919
+ if (error?.cancelled || error?.message?.includes("Route change aborted")) {
19920
+ return;
19921
+ }
19919
19922
  if (error.message?.includes("Page not compiled") && retryCount < MAX_RETRIES) {
19920
19923
  setTimeout(() => attemptNavigation(retryCount + 1), RETRY_DELAY);
19921
19924
  } else if (retryCount >= MAX_RETRIES) {
@@ -39816,7 +39819,7 @@ var BreakNotificationPopup = ({
39816
39819
  return null;
39817
39820
  }
39818
39821
  const currentBreak = visibleBreaks[currentIndex];
39819
- return /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `fixed right-4 top-24 z-50 max-w-xs w-full ${className}`, children: [
39822
+ return /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `fixed right-4 top-24 z-[110] max-w-xs w-full ${className}`, children: [
39820
39823
  visibleBreaks.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39821
39824
  /* @__PURE__ */ jsxRuntime.jsx(
39822
39825
  "div",
@@ -72347,6 +72350,46 @@ var ClipsCostView = () => {
72347
72350
  ] });
72348
72351
  };
72349
72352
  var ClipsCostView_default = ClipsCostView;
72353
+ var DEFAULT_UNSAVED_CHANGES_MESSAGE = "You have unsaved changes. Leave this page and lose them?";
72354
+ var useUnsavedChangesPrompt = (shouldBlock, message = DEFAULT_UNSAVED_CHANGES_MESSAGE) => {
72355
+ const router$1 = router.useRouter();
72356
+ React144.useEffect(() => {
72357
+ if (!shouldBlock || typeof window === "undefined") {
72358
+ return;
72359
+ }
72360
+ const handleBeforeUnload = (event) => {
72361
+ event.preventDefault();
72362
+ event.returnValue = message;
72363
+ return message;
72364
+ };
72365
+ window.addEventListener("beforeunload", handleBeforeUnload);
72366
+ return () => {
72367
+ window.removeEventListener("beforeunload", handleBeforeUnload);
72368
+ };
72369
+ }, [message, shouldBlock]);
72370
+ React144.useEffect(() => {
72371
+ if (!shouldBlock || !router$1?.events || typeof window === "undefined") {
72372
+ return;
72373
+ }
72374
+ const handleRouteChangeStart = (url) => {
72375
+ const currentUrl = router$1.asPath || window.location.pathname;
72376
+ if (url === currentUrl) {
72377
+ return;
72378
+ }
72379
+ if (window.confirm(message)) {
72380
+ return;
72381
+ }
72382
+ const routeChangeError = new Error("Route change aborted due to unsaved changes");
72383
+ routeChangeError.cancelled = true;
72384
+ router$1.events.emit("routeChangeError", routeChangeError, url, { shallow: false });
72385
+ throw routeChangeError;
72386
+ };
72387
+ router$1.events.on("routeChangeStart", handleRouteChangeStart);
72388
+ return () => {
72389
+ router$1.events.off("routeChangeStart", handleRouteChangeStart);
72390
+ };
72391
+ }, [message, router$1, shouldBlock]);
72392
+ };
72350
72393
  var calculateShiftHours = (startTime, endTime, breaks = []) => {
72351
72394
  if (!startTime || !endTime) return 8;
72352
72395
  const [startHour, startMinute] = startTime.split(":").map(Number);
@@ -72676,6 +72719,11 @@ var ShiftsView = ({
72676
72719
  className = ""
72677
72720
  }) => {
72678
72721
  const supabase = useSupabase();
72722
+ const lineIdsKey = React144.useMemo(() => lineIds.join("|"), [lineIds]);
72723
+ const stableLineIds = React144.useMemo(
72724
+ () => lineIdsKey.split("|").filter(Boolean),
72725
+ [lineIdsKey]
72726
+ );
72679
72727
  const { lines: dbLines } = useLines();
72680
72728
  const mergedLineNames = React144.useMemo(() => {
72681
72729
  const merged = { ...lineNames };
@@ -72689,14 +72737,14 @@ var ShiftsView = ({
72689
72737
  React144.useEffect(() => {
72690
72738
  console.log("[ShiftsView] Component mounted/re-rendered", {
72691
72739
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
72692
- lineIds: lineIds.length
72740
+ lineIds: stableLineIds.length
72693
72741
  });
72694
72742
  return () => {
72695
72743
  console.log("[ShiftsView] Component unmounting");
72696
72744
  };
72697
- }, []);
72745
+ }, [stableLineIds.length]);
72698
72746
  const [lineConfigs, setLineConfigs] = React144.useState(
72699
- () => lineIds.map((id3) => ({
72747
+ () => stableLineIds.map((id3) => ({
72700
72748
  id: id3,
72701
72749
  name: lineNames[id3] || `Line ${id3.substring(0, 4)}`,
72702
72750
  timezone: "Asia/Kolkata",
@@ -72710,8 +72758,18 @@ var ShiftsView = ({
72710
72758
  const [loading, setLoading] = React144.useState(true);
72711
72759
  const [error, setError] = React144.useState(null);
72712
72760
  const [saveStatusMessages, setSaveStatusMessages] = React144.useState(
72713
- () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
72761
+ () => stableLineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
72714
72762
  );
72763
+ const [dirtyLineIds, setDirtyLineIds] = React144.useState(/* @__PURE__ */ new Set());
72764
+ useUnsavedChangesPrompt(dirtyLineIds.size > 0);
72765
+ const markLineDirty = React144.useCallback((lineId) => {
72766
+ setDirtyLineIds((prev) => {
72767
+ if (prev.has(lineId)) return prev;
72768
+ const next = new Set(prev);
72769
+ next.add(lineId);
72770
+ return next;
72771
+ });
72772
+ }, []);
72715
72773
  const showToast = React144.useCallback((type, message) => {
72716
72774
  if (onToast) {
72717
72775
  try {
@@ -72731,7 +72789,7 @@ var ShiftsView = ({
72731
72789
  try {
72732
72790
  setLoading(true);
72733
72791
  setError(null);
72734
- const { data: enabledLines, error: linesError } = await supabase.from("lines").select("id, enable").in("id", lineIds).eq("enable", true);
72792
+ const { data: enabledLines, error: linesError } = await supabase.from("lines").select("id, enable").in("id", stableLineIds).eq("enable", true);
72735
72793
  if (linesError) {
72736
72794
  console.error("Error fetching enabled lines:", linesError);
72737
72795
  showToast("error", "Error loading lines");
@@ -72763,18 +72821,21 @@ var ShiftsView = ({
72763
72821
  return acc;
72764
72822
  }, {});
72765
72823
  setLineConfigs((prev) => {
72766
- const enabledConfigs = prev.filter((config) => enabledLineIds.includes(config.id));
72767
- return enabledConfigs.map((config) => {
72768
- const rows = shiftsByLine[config.id] || [];
72824
+ const previousByLineId = new Map(prev.map((config) => [config.id, config]));
72825
+ return enabledLineIds.map((lineId) => {
72826
+ const existingConfig = previousByLineId.get(lineId);
72827
+ const rows = shiftsByLine[lineId] || [];
72769
72828
  const builtConfig = buildShiftConfigFromOperatingHoursRows(rows);
72770
- const lineName = lineNameMap[config.id] || mergedLineNames[config.id] || `Line ${config.id.substring(0, 4)}`;
72829
+ const lineName = lineNameMap[lineId] || mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}`;
72771
72830
  const sortedShifts = [...builtConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
72772
72831
  return {
72773
- ...config,
72832
+ id: lineId,
72774
72833
  name: lineName,
72834
+ timezone: builtConfig.timezone || existingConfig?.timezone || "Asia/Kolkata",
72775
72835
  shifts: sortedShifts,
72776
- timezone: builtConfig.timezone || config.timezone,
72777
- isOpen: config.isOpen ?? getStoredLineState(config.id)
72836
+ isSaving: existingConfig?.isSaving ?? false,
72837
+ saveSuccess: existingConfig?.saveSuccess ?? false,
72838
+ isOpen: existingConfig?.isOpen ?? getStoredLineState(lineId)
72778
72839
  };
72779
72840
  });
72780
72841
  });
@@ -72787,7 +72848,7 @@ var ShiftsView = ({
72787
72848
  }
72788
72849
  };
72789
72850
  fetchShiftConfigs();
72790
- }, [lineIds, showToast]);
72851
+ }, [stableLineIds, showToast]);
72791
72852
  React144.useCallback((lineId) => {
72792
72853
  setLineConfigs((prev) => {
72793
72854
  const typedPrev = prev;
@@ -72799,6 +72860,7 @@ var ShiftsView = ({
72799
72860
  });
72800
72861
  }, []);
72801
72862
  const updateShiftTime = React144.useCallback((lineId, shiftIndex, field, value) => {
72863
+ markLineDirty(lineId);
72802
72864
  setLineConfigs((prev) => prev.map((config) => {
72803
72865
  if (config.id === lineId && config.shifts) {
72804
72866
  const newShifts = [...config.shifts];
@@ -72809,8 +72871,9 @@ var ShiftsView = ({
72809
72871
  }
72810
72872
  return config;
72811
72873
  }));
72812
- }, []);
72874
+ }, [markLineDirty]);
72813
72875
  const addShiftBreak = React144.useCallback((lineId, shiftIndex) => {
72876
+ markLineDirty(lineId);
72814
72877
  setLineConfigs((prev) => prev.map((config) => {
72815
72878
  if (config.id === lineId && config.shifts) {
72816
72879
  const newShifts = [...config.shifts];
@@ -72831,8 +72894,9 @@ var ShiftsView = ({
72831
72894
  }
72832
72895
  return config;
72833
72896
  }));
72834
- }, []);
72897
+ }, [markLineDirty]);
72835
72898
  const updateShiftBreak = React144.useCallback((lineId, shiftIndex, breakIndex, field, value) => {
72899
+ markLineDirty(lineId);
72836
72900
  setLineConfigs((prev) => prev.map((config) => {
72837
72901
  if (config.id === lineId && config.shifts) {
72838
72902
  const newShifts = [...config.shifts];
@@ -72858,8 +72922,9 @@ var ShiftsView = ({
72858
72922
  }
72859
72923
  return config;
72860
72924
  }));
72861
- }, []);
72925
+ }, [markLineDirty]);
72862
72926
  const removeShiftBreak = React144.useCallback((lineId, shiftIndex, breakIndex) => {
72927
+ markLineDirty(lineId);
72863
72928
  setLineConfigs((prev) => prev.map((config) => {
72864
72929
  if (config.id === lineId && config.shifts) {
72865
72930
  const newShifts = [...config.shifts];
@@ -72874,7 +72939,7 @@ var ShiftsView = ({
72874
72939
  }
72875
72940
  return config;
72876
72941
  }));
72877
- }, []);
72942
+ }, [markLineDirty]);
72878
72943
  const getOperationalDateForLine = React144.useCallback((lineConfig) => {
72879
72944
  const sortedShifts = [...lineConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
72880
72945
  const dayBoundaryStart = sortedShifts[0]?.startTime || "06:00";
@@ -73159,6 +73224,11 @@ var ShiftsView = ({
73159
73224
  setLineConfigs((prev) => prev.map(
73160
73225
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
73161
73226
  ));
73227
+ setDirtyLineIds((prev) => {
73228
+ const next = new Set(prev);
73229
+ next.delete(lineId);
73230
+ return next;
73231
+ });
73162
73232
  let successMessage = "Shift configurations saved successfully.";
73163
73233
  if (changedShiftCount > 0 && recalculatedTargetsCount > 0) {
73164
73234
  successMessage = `Shift configurations saved. Targets updated successfully for ${recalculatedTargetsCount} workspace${recalculatedTargetsCount === 1 ? "" : "s"}.`;
@@ -74243,8 +74313,13 @@ var TargetsView = ({
74243
74313
  onSaveChanges
74244
74314
  }) => {
74245
74315
  const timezone = useAppTimezone();
74316
+ const lineIdsKey = React144.useMemo(() => lineIds.join("|"), [lineIds]);
74317
+ const stableLineIds = React144.useMemo(
74318
+ () => lineIdsKey.split("|").filter(Boolean),
74319
+ [lineIdsKey]
74320
+ );
74246
74321
  React144.useMemo(() => {
74247
- return lineIds.reduce((acc, lineId) => ({
74322
+ return stableLineIds.reduce((acc, lineId) => ({
74248
74323
  ...acc,
74249
74324
  [lineId]: {
74250
74325
  productId: "",
@@ -74258,9 +74333,9 @@ var TargetsView = ({
74258
74333
  // Initialize factoryId
74259
74334
  }
74260
74335
  }), {});
74261
- }, [lineIds]);
74336
+ }, [stableLineIds]);
74262
74337
  const [dropdownStates, setDropdownStates] = React144.useState(() => {
74263
- return lineIds.reduce((acc, lineId) => ({
74338
+ return stableLineIds.reduce((acc, lineId) => ({
74264
74339
  ...acc,
74265
74340
  [lineId]: getStoredLineState2(lineId)
74266
74341
  }), {});
@@ -74268,10 +74343,10 @@ var TargetsView = ({
74268
74343
  const [allShiftsData, setAllShiftsData] = React144.useState({});
74269
74344
  const [actionIds, setActionIds] = React144.useState(null);
74270
74345
  const [savingLines, setSavingLines] = React144.useState(
74271
- () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74346
+ () => stableLineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74272
74347
  );
74273
74348
  const [saveSuccess, setSaveSuccess] = React144.useState(
74274
- () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74349
+ () => stableLineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74275
74350
  );
74276
74351
  const [isLoading, setIsLoading] = React144.useState(true);
74277
74352
  const [isConfigureLegendOpen, setIsConfigureLegendOpen] = React144.useState(false);
@@ -74281,6 +74356,8 @@ var TargetsView = ({
74281
74356
  const canSaveTargets = useCanSaveTargets();
74282
74357
  const [dbValues, setDbValues] = React144.useState({ 0: {}, 1: {} });
74283
74358
  const [userEditedFields, setUserEditedFields] = React144.useState(/* @__PURE__ */ new Set());
74359
+ const hasUnsavedChanges = userEditedFields.size > 0;
74360
+ useUnsavedChangesPrompt(hasUnsavedChanges);
74284
74361
  const lineWorkspaces = allShiftsData[selectedShift] || {};
74285
74362
  const setLineWorkspaces = React144.useCallback((updater) => {
74286
74363
  setAllShiftsData((prev) => ({
@@ -74297,13 +74374,13 @@ var TargetsView = ({
74297
74374
  dbLines.forEach((line) => {
74298
74375
  merged[line.id] = line.line_name;
74299
74376
  });
74300
- lineIds.forEach((lineId) => {
74377
+ stableLineIds.forEach((lineId) => {
74301
74378
  if (!merged[lineId]) {
74302
74379
  merged[lineId] = `Line ${lineId.substring(0, 4)}`;
74303
74380
  }
74304
74381
  });
74305
74382
  return merged;
74306
- }, [lineNames, dbLines, lineIds]);
74383
+ }, [lineNames, dbLines, stableLineIds]);
74307
74384
  const { legend, updateLegend } = useEfficiencyLegend(companyId);
74308
74385
  const { skus, isLoading: skusLoading } = useSKUs(companyId);
74309
74386
  const { skus: skuCatalog, isLoading: skuCatalogLoading } = useSKUs(companyId, {
@@ -74362,22 +74439,22 @@ var TargetsView = ({
74362
74439
  React144.useEffect(() => {
74363
74440
  console.log("[TargetsView] Component mounted/re-rendered", {
74364
74441
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
74365
- lineIds: lineIds.length,
74442
+ lineIds: stableLineIds.length,
74366
74443
  effectiveUserId
74367
74444
  });
74368
74445
  return () => {
74369
74446
  console.log("[TargetsView] Component unmounting");
74370
74447
  };
74371
- }, []);
74448
+ }, [effectiveUserId, stableLineIds.length]);
74372
74449
  React144.useEffect(() => {
74373
74450
  const fetchInitialData = async () => {
74374
- if (lineIds.length === 0 || !companyId) return;
74451
+ if (stableLineIds.length === 0 || !companyId) return;
74375
74452
  if (skuCatalogLoading) return;
74376
74453
  console.log("[TargetsView] Starting optimized fetchInitialData with bulk endpoint");
74377
74454
  setIsLoading(true);
74378
74455
  try {
74379
74456
  const currentDate = getOperationalDate(timezone);
74380
- const { data: shiftsData, error: shiftsError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name").in("line_id", lineIds);
74457
+ const { data: shiftsData, error: shiftsError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name").in("line_id", stableLineIds);
74381
74458
  if (shiftsError) {
74382
74459
  console.warn("[TargetsView] Error fetching shift definitions, falling back to defaults", shiftsError);
74383
74460
  }
@@ -74395,7 +74472,7 @@ var TargetsView = ({
74395
74472
  setSelectedShift(defaultShiftId);
74396
74473
  const bulkResponse = await workspaceService.fetchBulkTargets({
74397
74474
  companyId,
74398
- lineIds,
74475
+ lineIds: stableLineIds,
74399
74476
  date: currentDate,
74400
74477
  shifts: effectiveShiftOptions.map((s) => s.id),
74401
74478
  // Fetch all configured shifts
@@ -74540,7 +74617,7 @@ var TargetsView = ({
74540
74617
  });
74541
74618
  });
74542
74619
  effectiveShiftOptions.forEach(({ id: shiftId }) => {
74543
- lineIds.forEach((lineId) => {
74620
+ stableLineIds.forEach((lineId) => {
74544
74621
  if (!newAllShiftsData[shiftId][lineId]) {
74545
74622
  const lineData = data.lines[lineId];
74546
74623
  const shift0Data = newAllShiftsData[0]?.[lineId];
@@ -74610,7 +74687,7 @@ var TargetsView = ({
74610
74687
  });
74611
74688
  effectiveShiftOptions.forEach(({ id: shiftId }) => {
74612
74689
  const orderedLineWorkspaces = {};
74613
- lineIds.forEach((lineId) => {
74690
+ stableLineIds.forEach((lineId) => {
74614
74691
  if (newAllShiftsData[shiftId][lineId]) {
74615
74692
  orderedLineWorkspaces[lineId] = newAllShiftsData[shiftId][lineId];
74616
74693
  }
@@ -74632,7 +74709,7 @@ var TargetsView = ({
74632
74709
  }
74633
74710
  };
74634
74711
  fetchInitialData();
74635
- }, [lineIds, companyId, timezone, skuEnabled, skuCatalogLoading]);
74712
+ }, [stableLineIds, companyId, timezone, skuEnabled, skuCatalogLoading]);
74636
74713
  const toggleLineDropdown = React144.useCallback((lineId) => {
74637
74714
  setDropdownStates((prev) => {
74638
74715
  const newIsOpen = !prev[lineId];
@@ -74807,6 +74884,7 @@ var TargetsView = ({
74807
74884
  };
74808
74885
  const handleActionTypeChange = React144.useCallback((lineId, workspaceId, newActionType) => {
74809
74886
  if (!actionIds) return;
74887
+ setUserEditedFields((prev) => new Set(prev).add(`${lineId}-${workspaceId}-actionType`));
74810
74888
  setLineWorkspaces((prev) => {
74811
74889
  const currentLineData = prev?.[lineId];
74812
74890
  if (!currentLineData?.workspaces) {
@@ -75004,10 +75082,10 @@ var TargetsView = ({
75004
75082
  console.log(`[handleSaveLine] Updated dbValues for line ${lineId}, shift ${selectedShift}`);
75005
75083
  setUserEditedFields((prev) => {
75006
75084
  const newSet = new Set(prev);
75007
- lineDataToSave.workspaces.forEach((ws) => {
75008
- newSet.delete(`${lineId}-${ws.id}-targetPPH`);
75009
- newSet.delete(`${lineId}-${ws.id}-targetCycleTime`);
75010
- newSet.delete(`${lineId}-${ws.id}-targetDayOutput`);
75085
+ Array.from(newSet).forEach((fieldKey) => {
75086
+ if (fieldKey.startsWith(`${lineId}-`)) {
75087
+ newSet.delete(fieldKey);
75088
+ }
75011
75089
  });
75012
75090
  return newSet;
75013
75091
  });
package/dist/index.mjs CHANGED
@@ -19887,6 +19887,9 @@ function useNavigation(customNavigate) {
19887
19887
  await router.push(path, void 0, { shallow: options?.shallow ?? false });
19888
19888
  }
19889
19889
  } catch (error) {
19890
+ if (error?.cancelled || error?.message?.includes("Route change aborted")) {
19891
+ return;
19892
+ }
19890
19893
  if (error.message?.includes("Page not compiled") && retryCount < MAX_RETRIES) {
19891
19894
  setTimeout(() => attemptNavigation(retryCount + 1), RETRY_DELAY);
19892
19895
  } else if (retryCount >= MAX_RETRIES) {
@@ -39787,7 +39790,7 @@ var BreakNotificationPopup = ({
39787
39790
  return null;
39788
39791
  }
39789
39792
  const currentBreak = visibleBreaks[currentIndex];
39790
- return /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsxs("div", { className: `fixed right-4 top-24 z-50 max-w-xs w-full ${className}`, children: [
39793
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: /* @__PURE__ */ jsxs("div", { className: `fixed right-4 top-24 z-[110] max-w-xs w-full ${className}`, children: [
39791
39794
  visibleBreaks.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
39792
39795
  /* @__PURE__ */ jsx(
39793
39796
  "div",
@@ -72318,6 +72321,46 @@ var ClipsCostView = () => {
72318
72321
  ] });
72319
72322
  };
72320
72323
  var ClipsCostView_default = ClipsCostView;
72324
+ var DEFAULT_UNSAVED_CHANGES_MESSAGE = "You have unsaved changes. Leave this page and lose them?";
72325
+ var useUnsavedChangesPrompt = (shouldBlock, message = DEFAULT_UNSAVED_CHANGES_MESSAGE) => {
72326
+ const router = useRouter();
72327
+ useEffect(() => {
72328
+ if (!shouldBlock || typeof window === "undefined") {
72329
+ return;
72330
+ }
72331
+ const handleBeforeUnload = (event) => {
72332
+ event.preventDefault();
72333
+ event.returnValue = message;
72334
+ return message;
72335
+ };
72336
+ window.addEventListener("beforeunload", handleBeforeUnload);
72337
+ return () => {
72338
+ window.removeEventListener("beforeunload", handleBeforeUnload);
72339
+ };
72340
+ }, [message, shouldBlock]);
72341
+ useEffect(() => {
72342
+ if (!shouldBlock || !router?.events || typeof window === "undefined") {
72343
+ return;
72344
+ }
72345
+ const handleRouteChangeStart = (url) => {
72346
+ const currentUrl = router.asPath || window.location.pathname;
72347
+ if (url === currentUrl) {
72348
+ return;
72349
+ }
72350
+ if (window.confirm(message)) {
72351
+ return;
72352
+ }
72353
+ const routeChangeError = new Error("Route change aborted due to unsaved changes");
72354
+ routeChangeError.cancelled = true;
72355
+ router.events.emit("routeChangeError", routeChangeError, url, { shallow: false });
72356
+ throw routeChangeError;
72357
+ };
72358
+ router.events.on("routeChangeStart", handleRouteChangeStart);
72359
+ return () => {
72360
+ router.events.off("routeChangeStart", handleRouteChangeStart);
72361
+ };
72362
+ }, [message, router, shouldBlock]);
72363
+ };
72321
72364
  var calculateShiftHours = (startTime, endTime, breaks = []) => {
72322
72365
  if (!startTime || !endTime) return 8;
72323
72366
  const [startHour, startMinute] = startTime.split(":").map(Number);
@@ -72647,6 +72690,11 @@ var ShiftsView = ({
72647
72690
  className = ""
72648
72691
  }) => {
72649
72692
  const supabase = useSupabase();
72693
+ const lineIdsKey = useMemo(() => lineIds.join("|"), [lineIds]);
72694
+ const stableLineIds = useMemo(
72695
+ () => lineIdsKey.split("|").filter(Boolean),
72696
+ [lineIdsKey]
72697
+ );
72650
72698
  const { lines: dbLines } = useLines();
72651
72699
  const mergedLineNames = useMemo(() => {
72652
72700
  const merged = { ...lineNames };
@@ -72660,14 +72708,14 @@ var ShiftsView = ({
72660
72708
  useEffect(() => {
72661
72709
  console.log("[ShiftsView] Component mounted/re-rendered", {
72662
72710
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
72663
- lineIds: lineIds.length
72711
+ lineIds: stableLineIds.length
72664
72712
  });
72665
72713
  return () => {
72666
72714
  console.log("[ShiftsView] Component unmounting");
72667
72715
  };
72668
- }, []);
72716
+ }, [stableLineIds.length]);
72669
72717
  const [lineConfigs, setLineConfigs] = useState(
72670
- () => lineIds.map((id3) => ({
72718
+ () => stableLineIds.map((id3) => ({
72671
72719
  id: id3,
72672
72720
  name: lineNames[id3] || `Line ${id3.substring(0, 4)}`,
72673
72721
  timezone: "Asia/Kolkata",
@@ -72681,8 +72729,18 @@ var ShiftsView = ({
72681
72729
  const [loading, setLoading] = useState(true);
72682
72730
  const [error, setError] = useState(null);
72683
72731
  const [saveStatusMessages, setSaveStatusMessages] = useState(
72684
- () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
72732
+ () => stableLineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
72685
72733
  );
72734
+ const [dirtyLineIds, setDirtyLineIds] = useState(/* @__PURE__ */ new Set());
72735
+ useUnsavedChangesPrompt(dirtyLineIds.size > 0);
72736
+ const markLineDirty = useCallback((lineId) => {
72737
+ setDirtyLineIds((prev) => {
72738
+ if (prev.has(lineId)) return prev;
72739
+ const next = new Set(prev);
72740
+ next.add(lineId);
72741
+ return next;
72742
+ });
72743
+ }, []);
72686
72744
  const showToast = useCallback((type, message) => {
72687
72745
  if (onToast) {
72688
72746
  try {
@@ -72702,7 +72760,7 @@ var ShiftsView = ({
72702
72760
  try {
72703
72761
  setLoading(true);
72704
72762
  setError(null);
72705
- const { data: enabledLines, error: linesError } = await supabase.from("lines").select("id, enable").in("id", lineIds).eq("enable", true);
72763
+ const { data: enabledLines, error: linesError } = await supabase.from("lines").select("id, enable").in("id", stableLineIds).eq("enable", true);
72706
72764
  if (linesError) {
72707
72765
  console.error("Error fetching enabled lines:", linesError);
72708
72766
  showToast("error", "Error loading lines");
@@ -72734,18 +72792,21 @@ var ShiftsView = ({
72734
72792
  return acc;
72735
72793
  }, {});
72736
72794
  setLineConfigs((prev) => {
72737
- const enabledConfigs = prev.filter((config) => enabledLineIds.includes(config.id));
72738
- return enabledConfigs.map((config) => {
72739
- const rows = shiftsByLine[config.id] || [];
72795
+ const previousByLineId = new Map(prev.map((config) => [config.id, config]));
72796
+ return enabledLineIds.map((lineId) => {
72797
+ const existingConfig = previousByLineId.get(lineId);
72798
+ const rows = shiftsByLine[lineId] || [];
72740
72799
  const builtConfig = buildShiftConfigFromOperatingHoursRows(rows);
72741
- const lineName = lineNameMap[config.id] || mergedLineNames[config.id] || `Line ${config.id.substring(0, 4)}`;
72800
+ const lineName = lineNameMap[lineId] || mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}`;
72742
72801
  const sortedShifts = [...builtConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
72743
72802
  return {
72744
- ...config,
72803
+ id: lineId,
72745
72804
  name: lineName,
72805
+ timezone: builtConfig.timezone || existingConfig?.timezone || "Asia/Kolkata",
72746
72806
  shifts: sortedShifts,
72747
- timezone: builtConfig.timezone || config.timezone,
72748
- isOpen: config.isOpen ?? getStoredLineState(config.id)
72807
+ isSaving: existingConfig?.isSaving ?? false,
72808
+ saveSuccess: existingConfig?.saveSuccess ?? false,
72809
+ isOpen: existingConfig?.isOpen ?? getStoredLineState(lineId)
72749
72810
  };
72750
72811
  });
72751
72812
  });
@@ -72758,7 +72819,7 @@ var ShiftsView = ({
72758
72819
  }
72759
72820
  };
72760
72821
  fetchShiftConfigs();
72761
- }, [lineIds, showToast]);
72822
+ }, [stableLineIds, showToast]);
72762
72823
  useCallback((lineId) => {
72763
72824
  setLineConfigs((prev) => {
72764
72825
  const typedPrev = prev;
@@ -72770,6 +72831,7 @@ var ShiftsView = ({
72770
72831
  });
72771
72832
  }, []);
72772
72833
  const updateShiftTime = useCallback((lineId, shiftIndex, field, value) => {
72834
+ markLineDirty(lineId);
72773
72835
  setLineConfigs((prev) => prev.map((config) => {
72774
72836
  if (config.id === lineId && config.shifts) {
72775
72837
  const newShifts = [...config.shifts];
@@ -72780,8 +72842,9 @@ var ShiftsView = ({
72780
72842
  }
72781
72843
  return config;
72782
72844
  }));
72783
- }, []);
72845
+ }, [markLineDirty]);
72784
72846
  const addShiftBreak = useCallback((lineId, shiftIndex) => {
72847
+ markLineDirty(lineId);
72785
72848
  setLineConfigs((prev) => prev.map((config) => {
72786
72849
  if (config.id === lineId && config.shifts) {
72787
72850
  const newShifts = [...config.shifts];
@@ -72802,8 +72865,9 @@ var ShiftsView = ({
72802
72865
  }
72803
72866
  return config;
72804
72867
  }));
72805
- }, []);
72868
+ }, [markLineDirty]);
72806
72869
  const updateShiftBreak = useCallback((lineId, shiftIndex, breakIndex, field, value) => {
72870
+ markLineDirty(lineId);
72807
72871
  setLineConfigs((prev) => prev.map((config) => {
72808
72872
  if (config.id === lineId && config.shifts) {
72809
72873
  const newShifts = [...config.shifts];
@@ -72829,8 +72893,9 @@ var ShiftsView = ({
72829
72893
  }
72830
72894
  return config;
72831
72895
  }));
72832
- }, []);
72896
+ }, [markLineDirty]);
72833
72897
  const removeShiftBreak = useCallback((lineId, shiftIndex, breakIndex) => {
72898
+ markLineDirty(lineId);
72834
72899
  setLineConfigs((prev) => prev.map((config) => {
72835
72900
  if (config.id === lineId && config.shifts) {
72836
72901
  const newShifts = [...config.shifts];
@@ -72845,7 +72910,7 @@ var ShiftsView = ({
72845
72910
  }
72846
72911
  return config;
72847
72912
  }));
72848
- }, []);
72913
+ }, [markLineDirty]);
72849
72914
  const getOperationalDateForLine = useCallback((lineConfig) => {
72850
72915
  const sortedShifts = [...lineConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
72851
72916
  const dayBoundaryStart = sortedShifts[0]?.startTime || "06:00";
@@ -73130,6 +73195,11 @@ var ShiftsView = ({
73130
73195
  setLineConfigs((prev) => prev.map(
73131
73196
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
73132
73197
  ));
73198
+ setDirtyLineIds((prev) => {
73199
+ const next = new Set(prev);
73200
+ next.delete(lineId);
73201
+ return next;
73202
+ });
73133
73203
  let successMessage = "Shift configurations saved successfully.";
73134
73204
  if (changedShiftCount > 0 && recalculatedTargetsCount > 0) {
73135
73205
  successMessage = `Shift configurations saved. Targets updated successfully for ${recalculatedTargetsCount} workspace${recalculatedTargetsCount === 1 ? "" : "s"}.`;
@@ -74214,8 +74284,13 @@ var TargetsView = ({
74214
74284
  onSaveChanges
74215
74285
  }) => {
74216
74286
  const timezone = useAppTimezone();
74287
+ const lineIdsKey = useMemo(() => lineIds.join("|"), [lineIds]);
74288
+ const stableLineIds = useMemo(
74289
+ () => lineIdsKey.split("|").filter(Boolean),
74290
+ [lineIdsKey]
74291
+ );
74217
74292
  useMemo(() => {
74218
- return lineIds.reduce((acc, lineId) => ({
74293
+ return stableLineIds.reduce((acc, lineId) => ({
74219
74294
  ...acc,
74220
74295
  [lineId]: {
74221
74296
  productId: "",
@@ -74229,9 +74304,9 @@ var TargetsView = ({
74229
74304
  // Initialize factoryId
74230
74305
  }
74231
74306
  }), {});
74232
- }, [lineIds]);
74307
+ }, [stableLineIds]);
74233
74308
  const [dropdownStates, setDropdownStates] = useState(() => {
74234
- return lineIds.reduce((acc, lineId) => ({
74309
+ return stableLineIds.reduce((acc, lineId) => ({
74235
74310
  ...acc,
74236
74311
  [lineId]: getStoredLineState2(lineId)
74237
74312
  }), {});
@@ -74239,10 +74314,10 @@ var TargetsView = ({
74239
74314
  const [allShiftsData, setAllShiftsData] = useState({});
74240
74315
  const [actionIds, setActionIds] = useState(null);
74241
74316
  const [savingLines, setSavingLines] = useState(
74242
- () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74317
+ () => stableLineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74243
74318
  );
74244
74319
  const [saveSuccess, setSaveSuccess] = useState(
74245
- () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74320
+ () => stableLineIds.reduce((acc, id3) => ({ ...acc, [id3]: false }), {})
74246
74321
  );
74247
74322
  const [isLoading, setIsLoading] = useState(true);
74248
74323
  const [isConfigureLegendOpen, setIsConfigureLegendOpen] = useState(false);
@@ -74252,6 +74327,8 @@ var TargetsView = ({
74252
74327
  const canSaveTargets = useCanSaveTargets();
74253
74328
  const [dbValues, setDbValues] = useState({ 0: {}, 1: {} });
74254
74329
  const [userEditedFields, setUserEditedFields] = useState(/* @__PURE__ */ new Set());
74330
+ const hasUnsavedChanges = userEditedFields.size > 0;
74331
+ useUnsavedChangesPrompt(hasUnsavedChanges);
74255
74332
  const lineWorkspaces = allShiftsData[selectedShift] || {};
74256
74333
  const setLineWorkspaces = useCallback((updater) => {
74257
74334
  setAllShiftsData((prev) => ({
@@ -74268,13 +74345,13 @@ var TargetsView = ({
74268
74345
  dbLines.forEach((line) => {
74269
74346
  merged[line.id] = line.line_name;
74270
74347
  });
74271
- lineIds.forEach((lineId) => {
74348
+ stableLineIds.forEach((lineId) => {
74272
74349
  if (!merged[lineId]) {
74273
74350
  merged[lineId] = `Line ${lineId.substring(0, 4)}`;
74274
74351
  }
74275
74352
  });
74276
74353
  return merged;
74277
- }, [lineNames, dbLines, lineIds]);
74354
+ }, [lineNames, dbLines, stableLineIds]);
74278
74355
  const { legend, updateLegend } = useEfficiencyLegend(companyId);
74279
74356
  const { skus, isLoading: skusLoading } = useSKUs(companyId);
74280
74357
  const { skus: skuCatalog, isLoading: skuCatalogLoading } = useSKUs(companyId, {
@@ -74333,22 +74410,22 @@ var TargetsView = ({
74333
74410
  useEffect(() => {
74334
74411
  console.log("[TargetsView] Component mounted/re-rendered", {
74335
74412
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
74336
- lineIds: lineIds.length,
74413
+ lineIds: stableLineIds.length,
74337
74414
  effectiveUserId
74338
74415
  });
74339
74416
  return () => {
74340
74417
  console.log("[TargetsView] Component unmounting");
74341
74418
  };
74342
- }, []);
74419
+ }, [effectiveUserId, stableLineIds.length]);
74343
74420
  useEffect(() => {
74344
74421
  const fetchInitialData = async () => {
74345
- if (lineIds.length === 0 || !companyId) return;
74422
+ if (stableLineIds.length === 0 || !companyId) return;
74346
74423
  if (skuCatalogLoading) return;
74347
74424
  console.log("[TargetsView] Starting optimized fetchInitialData with bulk endpoint");
74348
74425
  setIsLoading(true);
74349
74426
  try {
74350
74427
  const currentDate = getOperationalDate(timezone);
74351
- const { data: shiftsData, error: shiftsError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name").in("line_id", lineIds);
74428
+ const { data: shiftsData, error: shiftsError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name").in("line_id", stableLineIds);
74352
74429
  if (shiftsError) {
74353
74430
  console.warn("[TargetsView] Error fetching shift definitions, falling back to defaults", shiftsError);
74354
74431
  }
@@ -74366,7 +74443,7 @@ var TargetsView = ({
74366
74443
  setSelectedShift(defaultShiftId);
74367
74444
  const bulkResponse = await workspaceService.fetchBulkTargets({
74368
74445
  companyId,
74369
- lineIds,
74446
+ lineIds: stableLineIds,
74370
74447
  date: currentDate,
74371
74448
  shifts: effectiveShiftOptions.map((s) => s.id),
74372
74449
  // Fetch all configured shifts
@@ -74511,7 +74588,7 @@ var TargetsView = ({
74511
74588
  });
74512
74589
  });
74513
74590
  effectiveShiftOptions.forEach(({ id: shiftId }) => {
74514
- lineIds.forEach((lineId) => {
74591
+ stableLineIds.forEach((lineId) => {
74515
74592
  if (!newAllShiftsData[shiftId][lineId]) {
74516
74593
  const lineData = data.lines[lineId];
74517
74594
  const shift0Data = newAllShiftsData[0]?.[lineId];
@@ -74581,7 +74658,7 @@ var TargetsView = ({
74581
74658
  });
74582
74659
  effectiveShiftOptions.forEach(({ id: shiftId }) => {
74583
74660
  const orderedLineWorkspaces = {};
74584
- lineIds.forEach((lineId) => {
74661
+ stableLineIds.forEach((lineId) => {
74585
74662
  if (newAllShiftsData[shiftId][lineId]) {
74586
74663
  orderedLineWorkspaces[lineId] = newAllShiftsData[shiftId][lineId];
74587
74664
  }
@@ -74603,7 +74680,7 @@ var TargetsView = ({
74603
74680
  }
74604
74681
  };
74605
74682
  fetchInitialData();
74606
- }, [lineIds, companyId, timezone, skuEnabled, skuCatalogLoading]);
74683
+ }, [stableLineIds, companyId, timezone, skuEnabled, skuCatalogLoading]);
74607
74684
  const toggleLineDropdown = useCallback((lineId) => {
74608
74685
  setDropdownStates((prev) => {
74609
74686
  const newIsOpen = !prev[lineId];
@@ -74778,6 +74855,7 @@ var TargetsView = ({
74778
74855
  };
74779
74856
  const handleActionTypeChange = useCallback((lineId, workspaceId, newActionType) => {
74780
74857
  if (!actionIds) return;
74858
+ setUserEditedFields((prev) => new Set(prev).add(`${lineId}-${workspaceId}-actionType`));
74781
74859
  setLineWorkspaces((prev) => {
74782
74860
  const currentLineData = prev?.[lineId];
74783
74861
  if (!currentLineData?.workspaces) {
@@ -74975,10 +75053,10 @@ var TargetsView = ({
74975
75053
  console.log(`[handleSaveLine] Updated dbValues for line ${lineId}, shift ${selectedShift}`);
74976
75054
  setUserEditedFields((prev) => {
74977
75055
  const newSet = new Set(prev);
74978
- lineDataToSave.workspaces.forEach((ws) => {
74979
- newSet.delete(`${lineId}-${ws.id}-targetPPH`);
74980
- newSet.delete(`${lineId}-${ws.id}-targetCycleTime`);
74981
- newSet.delete(`${lineId}-${ws.id}-targetDayOutput`);
75056
+ Array.from(newSet).forEach((fieldKey) => {
75057
+ if (fieldKey.startsWith(`${lineId}-`)) {
75058
+ newSet.delete(fieldKey);
75059
+ }
74982
75060
  });
74983
75061
  return newSet;
74984
75062
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.12.20",
3
+ "version": "6.12.22",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",