@easyteam/auto-scheduler-modal-ui 0.1.1 → 0.1.3

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.d.cts CHANGED
@@ -118,6 +118,8 @@ type OpenShift = {
118
118
  };
119
119
  requiredPosition: string;
120
120
  requiredCount: number;
121
+ isDraft?: boolean;
122
+ isPublished?: boolean;
121
123
  employeePreferences: Array<{
122
124
  employeeId: string;
123
125
  level: "PREFERRED" | "AVAILABLE" | "UNAVAILABLE" | string;
@@ -281,6 +283,7 @@ type AutoSchedulerModalProps = {
281
283
  getStoredConfigForLocations?: (locationIds: string[]) => AutoSchedulerModalConfig | null;
282
284
  /** When provided, persists config after successful schedule creation */
283
285
  persistConfigForLocations?: (locationIds: string[], config: AutoSchedulerModalConfig) => void;
286
+ errorToast?: (msg: string) => void;
284
287
  };
285
288
  type Screen = "configure" | "creating" | "failed";
286
289
  type StepStatus = "pending" | "active" | "done";
@@ -374,6 +377,7 @@ type ViolatedConstraintsPanelProps = {
374
377
  };
375
378
  styles?: Record<string, Record<string, unknown>>;
376
379
  styleOverrides?: ViolatedConstraintsPanelStyleOverrides;
380
+ errorToast?: (msg: string) => void;
377
381
  };
378
382
  type SolveApiResponse = {
379
383
  problemId: string;
@@ -401,7 +405,7 @@ type ScheduleRecommendation = {
401
405
  why_this_resolves_it: string;
402
406
  };
403
407
 
404
- declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, getTokenURLAndHeaders, onSolution, getStoredConfigForLocations, persistConfigForLocations, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
408
+ declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, getTokenURLAndHeaders, onSolution, getStoredConfigForLocations, persistConfigForLocations, errorToast, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
405
409
 
406
410
  /**
407
411
  * Self-contained AutoSchedulerModal that wraps itself in ChakraProvider.
@@ -410,7 +414,7 @@ declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisd
410
414
  */
411
415
  declare function AutoSchedulerModalWithProvider(props: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
412
416
 
413
- declare function ViolatedConstraintsPanel({ violatedConstraints, title, subtitle, showSecondaryButton, secondaryButtonTitle, onSecondaryButtonClick, generateRecommendationsURLAndHeaders, styles: stylesProp, styleOverrides, }: ViolatedConstraintsPanelProps): react_jsx_runtime.JSX.Element;
417
+ declare function ViolatedConstraintsPanel({ violatedConstraints, title, subtitle, showSecondaryButton, secondaryButtonTitle, onSecondaryButtonClick, generateRecommendationsURLAndHeaders, styles: stylesProp, styleOverrides, errorToast, }: ViolatedConstraintsPanelProps): react_jsx_runtime.JSX.Element;
414
418
 
415
419
  declare function ViolatedConstraintsPanelWithProvider(props: ViolatedConstraintsPanelProps): react_jsx_runtime.JSX.Element;
416
420
 
package/dist/index.d.ts CHANGED
@@ -118,6 +118,8 @@ type OpenShift = {
118
118
  };
119
119
  requiredPosition: string;
120
120
  requiredCount: number;
121
+ isDraft?: boolean;
122
+ isPublished?: boolean;
121
123
  employeePreferences: Array<{
122
124
  employeeId: string;
123
125
  level: "PREFERRED" | "AVAILABLE" | "UNAVAILABLE" | string;
@@ -281,6 +283,7 @@ type AutoSchedulerModalProps = {
281
283
  getStoredConfigForLocations?: (locationIds: string[]) => AutoSchedulerModalConfig | null;
282
284
  /** When provided, persists config after successful schedule creation */
283
285
  persistConfigForLocations?: (locationIds: string[], config: AutoSchedulerModalConfig) => void;
286
+ errorToast?: (msg: string) => void;
284
287
  };
285
288
  type Screen = "configure" | "creating" | "failed";
286
289
  type StepStatus = "pending" | "active" | "done";
@@ -374,6 +377,7 @@ type ViolatedConstraintsPanelProps = {
374
377
  };
375
378
  styles?: Record<string, Record<string, unknown>>;
376
379
  styleOverrides?: ViolatedConstraintsPanelStyleOverrides;
380
+ errorToast?: (msg: string) => void;
377
381
  };
378
382
  type SolveApiResponse = {
379
383
  problemId: string;
@@ -401,7 +405,7 @@ type ScheduleRecommendation = {
401
405
  why_this_resolves_it: string;
402
406
  };
403
407
 
404
- declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, getTokenURLAndHeaders, onSolution, getStoredConfigForLocations, persistConfigForLocations, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
408
+ declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, getTokenURLAndHeaders, onSolution, getStoredConfigForLocations, persistConfigForLocations, errorToast, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
405
409
 
406
410
  /**
407
411
  * Self-contained AutoSchedulerModal that wraps itself in ChakraProvider.
@@ -410,7 +414,7 @@ declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisd
410
414
  */
411
415
  declare function AutoSchedulerModalWithProvider(props: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
412
416
 
413
- declare function ViolatedConstraintsPanel({ violatedConstraints, title, subtitle, showSecondaryButton, secondaryButtonTitle, onSecondaryButtonClick, generateRecommendationsURLAndHeaders, styles: stylesProp, styleOverrides, }: ViolatedConstraintsPanelProps): react_jsx_runtime.JSX.Element;
417
+ declare function ViolatedConstraintsPanel({ violatedConstraints, title, subtitle, showSecondaryButton, secondaryButtonTitle, onSecondaryButtonClick, generateRecommendationsURLAndHeaders, styles: stylesProp, styleOverrides, errorToast, }: ViolatedConstraintsPanelProps): react_jsx_runtime.JSX.Element;
414
418
 
415
419
  declare function ViolatedConstraintsPanelWithProvider(props: ViolatedConstraintsPanelProps): react_jsx_runtime.JSX.Element;
416
420
 
package/dist/index.js CHANGED
@@ -889,6 +889,7 @@ var SCHEDULE_STEPS = [
889
889
  { title: "Finalizing schedule" }
890
890
  ];
891
891
  var STEP_INTERVAL_MS = 1200;
892
+ var POLL_INTERVAL_MS = 2500;
892
893
  var OPTIMIZING_STEP_INDEX = 2;
893
894
  var NETWORK_ERROR_FAILURE = {
894
895
  violatedConstraints: [
@@ -1344,7 +1345,7 @@ function buildRulesPayload(selectedConstraintIds, constraintValues, selectedOpti
1344
1345
  return { ruleIds: allSelectedRuleIds, ruleOverrides };
1345
1346
  }
1346
1347
  function toApiShift(shift, includeEmployeePreferences) {
1347
- var _a, _b;
1348
+ var _a;
1348
1349
  const startDateTime = "startDateTime" in shift ? shift.startDateTime : shift.startTime;
1349
1350
  const endDateTime = "endDateTime" in shift ? shift.endDateTime : shift.endTime;
1350
1351
  return {
@@ -1352,8 +1353,8 @@ function toApiShift(shift, includeEmployeePreferences) {
1352
1353
  startDateTime,
1353
1354
  endDateTime,
1354
1355
  ...shift.employeeId ? { employeeId: String(shift.employeeId) } : {},
1355
- requiredPosition: (_a = shift.requiredPosition) != null ? _a : void 0,
1356
- requiredCount: (_b = shift.requiredCount) != null ? _b : 1,
1356
+ requiredPosition: shift.requiredPosition || void 0,
1357
+ requiredCount: (_a = shift.requiredCount) != null ? _a : 1,
1357
1358
  location: shift.location ? {
1358
1359
  id: String(shift.location.id),
1359
1360
  timezone: "timezone" in shift.location ? shift.location.timezone : void 0,
@@ -1433,19 +1434,55 @@ async function getToken(fullUrl, headers) {
1433
1434
  });
1434
1435
  return (_a = tokenRes.data) == null ? void 0 : _a.token;
1435
1436
  }
1436
- async function solveSchedule(baseURL, payload, options) {
1437
+ async function submitSolveJob(baseURL, payload, options) {
1437
1438
  const cleanBaseUrl = baseURL.replace(/\/$/, "");
1438
1439
  const headers = {};
1439
1440
  if (options == null ? void 0 : options.authorization) {
1440
1441
  headers.Authorization = options.authorization;
1441
1442
  }
1442
1443
  const response = await axiosInstance.post(
1443
- `${cleanBaseUrl}/api/schedule/solve`,
1444
+ `${cleanBaseUrl}/api/schedule/solve-async`,
1444
1445
  payload,
1445
1446
  { headers: Object.keys(headers).length > 0 ? headers : void 0 }
1446
1447
  );
1447
1448
  return response.data;
1448
1449
  }
1450
+ async function getJobStatus(baseURL, jobId, options) {
1451
+ const cleanBaseUrl = baseURL.replace(/\/$/, "");
1452
+ const headers = {};
1453
+ if (options == null ? void 0 : options.authorization) {
1454
+ headers.Authorization = options.authorization;
1455
+ }
1456
+ const response = await axiosInstance.get(
1457
+ `${cleanBaseUrl}/api/schedule/status/${jobId}`,
1458
+ { headers: Object.keys(headers).length > 0 ? headers : void 0 }
1459
+ );
1460
+ return response.data;
1461
+ }
1462
+ async function getJobResult(baseURL, jobId, options) {
1463
+ const cleanBaseUrl = baseURL.replace(/\/$/, "");
1464
+ const headers = {};
1465
+ if (options == null ? void 0 : options.authorization) {
1466
+ headers.Authorization = options.authorization;
1467
+ }
1468
+ const response = await axiosInstance.get(
1469
+ `${cleanBaseUrl}/api/schedule/result/${jobId}`,
1470
+ { headers: Object.keys(headers).length > 0 ? headers : void 0 }
1471
+ );
1472
+ return response.data;
1473
+ }
1474
+ async function getJobSolution(baseURL, jobId, options) {
1475
+ const cleanBaseUrl = baseURL.replace(/\/$/, "");
1476
+ const headers = {};
1477
+ if (options == null ? void 0 : options.authorization) {
1478
+ headers.Authorization = options.authorization;
1479
+ }
1480
+ const response = await axiosInstance.get(
1481
+ `${cleanBaseUrl}/api/schedule/solution/${jobId}`,
1482
+ { headers: Object.keys(headers).length > 0 ? headers : void 0 }
1483
+ );
1484
+ return response.data;
1485
+ }
1449
1486
 
1450
1487
  // src/utils/violationMessages.ts
1451
1488
  var SYSTEM_CONSTRAINT_MESSAGES = {
@@ -1655,12 +1692,15 @@ function ViolatedConstraintsPanel({
1655
1692
  onSecondaryButtonClick,
1656
1693
  generateRecommendationsURLAndHeaders,
1657
1694
  styles: stylesProp,
1658
- styleOverrides
1695
+ styleOverrides,
1696
+ errorToast
1659
1697
  }) {
1660
1698
  const [showRecommendations, setShowRecommendations] = useState(false);
1661
1699
  const [isExpanded, setIsExpanded] = useState(true);
1662
1700
  const [recommendations, setRecommendations] = useState(null);
1663
1701
  const [isLoadingRecommendations, setIsLoadingRecommendations] = useState(false);
1702
+ const [isDisabledRecommendations, setIsDisabledRecommendations] = useState(false);
1703
+ const [recommendationsButtonText, setRecommendationsButtonText] = useState("Show recommendations");
1664
1704
  const styles = useMemo(() => {
1665
1705
  if (stylesProp) return stylesProp;
1666
1706
  return buildStyles(defaultStyles, styleOverrides);
@@ -1671,12 +1711,10 @@ function ViolatedConstraintsPanel({
1671
1711
  const displayRecommendations = recommendations !== null ? recommendations : violatedConstraints.recommendedFixes;
1672
1712
  const hasRecommendations = displayRecommendations.length > 0;
1673
1713
  const handleToggleRecommendations = useCallback(async () => {
1674
- var _a, _b;
1675
1714
  if (showRecommendations) {
1676
1715
  setShowRecommendations(false);
1677
1716
  return;
1678
1717
  }
1679
- setShowRecommendations(true);
1680
1718
  if (recommendations !== null) {
1681
1719
  return;
1682
1720
  }
@@ -1696,9 +1734,21 @@ function ViolatedConstraintsPanel({
1696
1734
  }
1697
1735
  }
1698
1736
  );
1699
- const data = (_b = (_a = recResponse.data) == null ? void 0 : _a.data) != null ? _b : recResponse.data;
1700
- if (data) {
1701
- setRecommendations(data.map((r) => r.recommendation));
1737
+ const { success, data, msg } = recResponse.data;
1738
+ const { isExpired, recommendations: recommendations2 } = data != null ? data : {};
1739
+ if (!success) {
1740
+ const errorMsg = msg || "Failed to fetch recommendations";
1741
+ errorToast == null ? void 0 : errorToast(errorMsg);
1742
+ if (isExpired) {
1743
+ setIsDisabledRecommendations(true);
1744
+ setRecommendationsButtonText("Daily limit reached");
1745
+ }
1746
+ return;
1747
+ }
1748
+ setShowRecommendations(true);
1749
+ const fetchedRecommendations = recommendations2;
1750
+ if (fetchedRecommendations) {
1751
+ setRecommendations(fetchedRecommendations.map((r) => r.recommendation));
1702
1752
  } else {
1703
1753
  setRecommendations([]);
1704
1754
  }
@@ -1713,7 +1763,8 @@ function ViolatedConstraintsPanel({
1713
1763
  recommendations,
1714
1764
  canFetchRecommendations,
1715
1765
  violatedConstraints.recommendationPayload,
1716
- generateRecommendationsURLAndHeaders
1766
+ generateRecommendationsURLAndHeaders,
1767
+ errorToast
1717
1768
  ]);
1718
1769
  return /* @__PURE__ */ jsxs2(Box, { sx: styles.violationPanel, children: [
1719
1770
  /* @__PURE__ */ jsxs2(HStack, { sx: styles.violationHeaderRow, children: [
@@ -1765,9 +1816,9 @@ function ViolatedConstraintsPanel({
1765
1816
  variant: "outline",
1766
1817
  size: "xs",
1767
1818
  onClick: handleToggleRecommendations,
1768
- isDisabled: isLoadingRecommendations,
1819
+ isDisabled: isLoadingRecommendations || isDisabledRecommendations,
1769
1820
  sx: { ...styles.recommendationsToggle, minWidth: "140px" },
1770
- children: isLoadingRecommendations ? /* @__PURE__ */ jsx2(Spinner, { size: "sm", thickness: "2px" }) : showRecommendations ? "Hide recommendations" : "Show recommendations"
1821
+ children: isLoadingRecommendations ? /* @__PURE__ */ jsx2(Spinner, { size: "sm", thickness: "2px" }) : showRecommendations ? "Hide recommendations" : recommendationsButtonText
1771
1822
  }
1772
1823
  ) : null,
1773
1824
  showSecondaryButton && secondaryButtonTitle && onSecondaryButtonClick ? /* @__PURE__ */ jsx2(
@@ -1809,7 +1860,8 @@ function AutoSchedulerModal({
1809
1860
  getTokenURLAndHeaders,
1810
1861
  onSolution,
1811
1862
  getStoredConfigForLocations,
1812
- persistConfigForLocations
1863
+ persistConfigForLocations,
1864
+ errorToast
1813
1865
  }) {
1814
1866
  const baseTheme = useTheme();
1815
1867
  const { chakraOverride, styleOverrides } = useMemo2(
@@ -1831,6 +1883,7 @@ function AutoSchedulerModal({
1831
1883
  const [lastFailure, setLastFailure] = useState2(null);
1832
1884
  const [selectedLocationIds, setSelectedLocationIds] = useState2([]);
1833
1885
  const timersRef = useRef([]);
1886
+ const pollIntervalRef = useRef(null);
1834
1887
  const runIdRef = useRef(0);
1835
1888
  const selectedByJurisdiction = useMemo2(
1836
1889
  () => jurisdictions.map((jurisdiction) => ({
@@ -1845,6 +1898,11 @@ function AutoSchedulerModal({
1845
1898
  (total, jurisdiction) => total + jurisdiction.selectedLocations.length,
1846
1899
  0
1847
1900
  );
1901
+ const locationIdsWithOpenShifts = useMemo2(() => {
1902
+ return new Set(
1903
+ openShifts.filter((shift) => !shift.employeeId).map((shift) => shift.location.id)
1904
+ );
1905
+ }, [openShifts]);
1848
1906
  const selectedConstraints = useMemo2(
1849
1907
  () => HARD_CONSTRAINT_OPTIONS.filter((option) => selectedConstraintIds.includes(option.id)),
1850
1908
  [selectedConstraintIds]
@@ -1896,6 +1954,10 @@ function AutoSchedulerModal({
1896
1954
  const clearTimers = () => {
1897
1955
  timersRef.current.forEach((timerId) => clearTimeout(timerId));
1898
1956
  timersRef.current = [];
1957
+ if (pollIntervalRef.current) {
1958
+ clearInterval(pollIntervalRef.current);
1959
+ pollIntervalRef.current = null;
1960
+ }
1899
1961
  };
1900
1962
  const resetProgress = () => {
1901
1963
  setActiveStepIndex(0);
@@ -1945,6 +2007,12 @@ function AutoSchedulerModal({
1945
2007
  }
1946
2008
  }, [selectedLocationIds, initialConfig, getStoredConfigForLocations]);
1947
2009
  useEffect(() => () => clearTimers(), []);
2010
+ useEffect(() => {
2011
+ setSelectedLocationIds((previous) => {
2012
+ const filtered = previous.filter((id) => locationIdsWithOpenShifts.has(id));
2013
+ return filtered.length === previous.length ? previous : filtered;
2014
+ });
2015
+ }, [locationIdsWithOpenShifts]);
1948
2016
  const stepStatusForIndex = (index) => {
1949
2017
  if (index <= completedStepIndex) {
1950
2018
  return "done";
@@ -2029,7 +2097,6 @@ function AutoSchedulerModal({
2029
2097
  });
2030
2098
  const runId = resetBeforeSubmit();
2031
2099
  schedule(STEP_INTERVAL_MS, async () => {
2032
- var _a, _b;
2033
2100
  setCompletedStepIndex(0);
2034
2101
  setActiveStepIndex(1);
2035
2102
  try {
@@ -2037,51 +2104,47 @@ function AutoSchedulerModal({
2037
2104
  if (!token) {
2038
2105
  throw new Error("Failed to get auto-scheduler token");
2039
2106
  }
2040
- const response = await solveSchedule(
2041
- baseURL,
2042
- payload,
2043
- {
2044
- authorization: `Bearer ${token}`
2045
- }
2046
- );
2107
+ const auth = { authorization: `Bearer ${token}` };
2108
+ const { jobId } = await submitSolveJob(baseURL, payload, auth);
2047
2109
  if (runIdRef.current !== runId) {
2048
2110
  return;
2049
2111
  }
2050
- if (!response.explanation.isFeasible) {
2051
- const result = mapViolationsToResult(
2052
- (_a = response.explanation.violations) != null ? _a : [],
2053
- response.explanation.summary
2054
- );
2055
- const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter(
2056
- (l) => selectedLocationIds.includes(l.id)
2057
- );
2058
- const recommendationPayload = generateRecommendationsURLAndHeaders ? {
2059
- context: {
2060
- selectedLocationIds,
2061
- selectedConstraintIds,
2062
- constraintValues,
2063
- selectedOptimizationIds,
2064
- optimizationValues,
2065
- timezone: (_b = selectedLocations[0]) == null ? void 0 : _b.timezone
2066
- },
2067
- locations: selectedLocations,
2068
- employees,
2069
- openShifts: openShifts.filter((s) => !s.employeeId),
2070
- assignedShifts: openShifts.filter((s) => Boolean(s.employeeId)),
2071
- timeOffs: timeOffs != null ? timeOffs : [],
2072
- explanation: response.explanation
2073
- } : void 0;
2074
- setLastFailure({
2075
- ...result,
2076
- recommendedFixes: [],
2077
- recommendationPayload
2078
- });
2079
- setScreen("failed");
2080
- } else {
2081
- setCompletedStepIndex(1);
2082
- setActiveStepIndex(OPTIMIZING_STEP_INDEX);
2083
- afterSuccessSteps(runId, response);
2084
- }
2112
+ setCompletedStepIndex(1);
2113
+ setActiveStepIndex(OPTIMIZING_STEP_INDEX);
2114
+ const pollForResult = async () => {
2115
+ if (runIdRef.current !== runId) return;
2116
+ try {
2117
+ const statusRes = await getJobStatus(baseURL, jobId, auth);
2118
+ if (runIdRef.current !== runId) return;
2119
+ if (statusRes.status === "NOT_SOLVING" || statusRes.status === "TERMINATED_EARLY") {
2120
+ if (pollIntervalRef.current) {
2121
+ clearInterval(pollIntervalRef.current);
2122
+ pollIntervalRef.current = null;
2123
+ }
2124
+ const response = await getJobResult(baseURL, jobId, auth);
2125
+ if (runIdRef.current !== runId) return;
2126
+ let responseForHandling = response;
2127
+ if (response.explanation.isFeasible) {
2128
+ const solution = await getJobSolution(baseURL, jobId, auth);
2129
+ if (runIdRef.current !== runId) return;
2130
+ responseForHandling = { ...response, solution };
2131
+ }
2132
+ handleSolveResult(runId, responseForHandling);
2133
+ return;
2134
+ }
2135
+ } catch (err) {
2136
+ if (runIdRef.current !== runId) return;
2137
+ if (pollIntervalRef.current) {
2138
+ clearInterval(pollIntervalRef.current);
2139
+ pollIntervalRef.current = null;
2140
+ }
2141
+ console.error(err);
2142
+ setLastFailure(NETWORK_ERROR_FAILURE);
2143
+ setScreen("failed");
2144
+ }
2145
+ };
2146
+ pollForResult();
2147
+ pollIntervalRef.current = setInterval(pollForResult, POLL_INTERVAL_MS);
2085
2148
  } catch (err) {
2086
2149
  if (runIdRef.current !== runId) {
2087
2150
  return;
@@ -2092,11 +2155,50 @@ function AutoSchedulerModal({
2092
2155
  }
2093
2156
  }, runId);
2094
2157
  };
2158
+ const handleSolveResult = (runId, response) => {
2159
+ var _a, _b;
2160
+ if (!response.explanation.isFeasible) {
2161
+ const result = mapViolationsToResult(
2162
+ (_a = response.explanation.violations) != null ? _a : [],
2163
+ response.explanation.summary
2164
+ );
2165
+ const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter(
2166
+ (l) => selectedLocationIds.includes(l.id)
2167
+ );
2168
+ const recommendationPayload = generateRecommendationsURLAndHeaders ? {
2169
+ context: {
2170
+ selectedLocationIds,
2171
+ selectedConstraintIds,
2172
+ constraintValues,
2173
+ selectedOptimizationIds,
2174
+ optimizationValues,
2175
+ timezone: (_b = selectedLocations[0]) == null ? void 0 : _b.timezone
2176
+ },
2177
+ locations: selectedLocations,
2178
+ employees,
2179
+ openShifts: openShifts.filter((s) => !s.employeeId),
2180
+ assignedShifts: openShifts.filter((s) => Boolean(s.employeeId)),
2181
+ timeOffs: timeOffs != null ? timeOffs : [],
2182
+ explanation: response.explanation
2183
+ } : void 0;
2184
+ setLastFailure({
2185
+ ...result,
2186
+ recommendedFixes: [],
2187
+ recommendationPayload
2188
+ });
2189
+ setScreen("failed");
2190
+ } else {
2191
+ afterSuccessSteps(runId, response);
2192
+ }
2193
+ };
2095
2194
  const handleAdjustConstraints = () => {
2096
2195
  clearTimers();
2097
2196
  setScreen("configure");
2098
2197
  };
2099
2198
  const handleToggleLocation = (locationId) => {
2199
+ if (!locationIdsWithOpenShifts.has(locationId)) {
2200
+ return;
2201
+ }
2100
2202
  if (selectedLocationIds.includes(locationId)) {
2101
2203
  setSelectedLocationIds(selectedLocationIds.filter((id) => id !== locationId));
2102
2204
  return;
@@ -2160,6 +2262,7 @@ function AutoSchedulerModal({
2160
2262
  violatedConstraints: lastFailure,
2161
2263
  title: violationTitle,
2162
2264
  subtitle: "",
2265
+ errorToast,
2163
2266
  generateRecommendationsURLAndHeaders,
2164
2267
  styles
2165
2268
  }
@@ -2219,13 +2322,18 @@ function AutoSchedulerModal({
2219
2322
  HStack2,
2220
2323
  {
2221
2324
  as: "label",
2222
- sx: { ...styles.optionRow, cursor: "pointer" },
2325
+ sx: {
2326
+ ...styles.optionRow,
2327
+ cursor: locationIdsWithOpenShifts.has(location.id) ? "pointer" : "not-allowed",
2328
+ opacity: locationIdsWithOpenShifts.has(location.id) ? 1 : 0.55
2329
+ },
2223
2330
  children: [
2224
2331
  /* @__PURE__ */ jsx3(
2225
2332
  Checkbox,
2226
2333
  {
2227
2334
  id: `location-${location.id}`,
2228
2335
  isChecked: selectedLocationIds.includes(location.id),
2336
+ isDisabled: !locationIdsWithOpenShifts.has(location.id),
2229
2337
  onChange: () => handleToggleLocation(location.id)
2230
2338
  }
2231
2339
  ),