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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  // src/AutoSchedulerModal.tsx
4
- import { useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
4
+ import { cloneElement, isValidElement, useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
5
5
  import {
6
6
  Badge,
7
7
  Box as Box2,
@@ -24,11 +24,22 @@ import {
24
24
  PopoverBody,
25
25
  PopoverContent,
26
26
  PopoverTrigger,
27
+ Portal,
27
28
  Spinner as Spinner2,
28
29
  Stack as Stack2,
29
30
  Text as Text2,
30
31
  useTheme
31
32
  } from "@chakra-ui/react";
33
+ import { Drawer as EtDrawer } from "@easyteam/web-ui";
34
+
35
+ // src/types.ts
36
+ var InfeasibleScheduleError = class extends Error {
37
+ constructor(failure) {
38
+ super("Schedule could not be created due to constraint violations");
39
+ this.name = "InfeasibleScheduleError";
40
+ this.failure = failure;
41
+ }
42
+ };
32
43
 
33
44
  // src/icons/AutoSchedulerIcons.tsx
34
45
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -165,19 +176,15 @@ function CheckIcon(props) {
165
176
  "svg",
166
177
  {
167
178
  "aria-hidden": "true",
168
- viewBox: "0 0 20 20",
169
- fill: "none",
170
- width: "20",
171
- height: "20",
179
+ viewBox: "0 0 24 24",
180
+ width: "24",
181
+ height: "24",
172
182
  ...props,
173
183
  children: /* @__PURE__ */ jsx(
174
184
  "path",
175
185
  {
176
- d: "M5.5 10.5l3 3 6-6",
177
- stroke: "currentColor",
178
- strokeWidth: "1.6",
179
- strokeLinecap: "round",
180
- strokeLinejoin: "round"
186
+ fill: "currentColor",
187
+ d: "M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"
181
188
  }
182
189
  )
183
190
  }
@@ -699,7 +706,7 @@ var defaultStyles = {
699
706
  },
700
707
  stepIconDone: {
701
708
  border: "none",
702
- bg: "#008060",
709
+ bg: "#5338ff",
703
710
  color: "white",
704
711
  borderRadius: "10px"
705
712
  },
@@ -1235,6 +1242,9 @@ var UI_TO_BACKEND_RULE_ID = {
1235
1242
  maximize_sales: "maximize-sales-performance",
1236
1243
  staff_preferences: "staff-open-shift-preferences"
1237
1244
  };
1245
+ var MAXIMIZE_CONVERSION_RATE_RULE_ID = "maximize-conversion-rate";
1246
+ var TRAFFIC_FORECAST_INCENTIVE_RULE_ID = "traffic-forecast-incentive";
1247
+ var SALES_OPTIMIZATION_UI_IDS = /* @__PURE__ */ new Set(["maximize_sales", "cost_to_sales_ratio"]);
1238
1248
  var BACKEND_TO_UI_RULE_ID = Object.fromEntries(
1239
1249
  Object.entries(UI_TO_BACKEND_RULE_ID).map(([ui, backend]) => [backend, ui])
1240
1250
  );
@@ -1311,11 +1321,24 @@ function hasAnyParameterOverrides(selectedConstraintIds, constraintValues, selec
1311
1321
  }
1312
1322
  return false;
1313
1323
  }
1314
- function buildRulesPayload(selectedConstraintIds, constraintValues, selectedOptimizationIds, optimizationValues) {
1324
+ function appendSiblingRuleId(ruleIds, siblingId) {
1325
+ if (!ruleIds.includes(siblingId)) {
1326
+ ruleIds.push(siblingId);
1327
+ }
1328
+ }
1329
+ function buildRulesPayload(selectedConstraintIds, constraintValues, selectedOptimizationIds, optimizationValues, siblings = {}) {
1315
1330
  const allSelectedRuleIds = [
1316
1331
  ...selectedConstraintIds.map(toBackendRuleId),
1317
1332
  ...selectedOptimizationIds.map(toBackendRuleId)
1318
1333
  ].filter((id) => Boolean(id));
1334
+ const salesOptSelected = selectedOptimizationIds.some((id) => SALES_OPTIMIZATION_UI_IDS.has(id));
1335
+ const maximizeSalesSelected = selectedOptimizationIds.includes("maximize_sales");
1336
+ if (siblings.includeConversionRate && salesOptSelected) {
1337
+ appendSiblingRuleId(allSelectedRuleIds, MAXIMIZE_CONVERSION_RATE_RULE_ID);
1338
+ }
1339
+ if (siblings.includeTrafficIncentive && maximizeSalesSelected) {
1340
+ appendSiblingRuleId(allSelectedRuleIds, TRAFFIC_FORECAST_INCENTIVE_RULE_ID);
1341
+ }
1319
1342
  if (allSelectedRuleIds.length === 0) {
1320
1343
  return {};
1321
1344
  }
@@ -1384,9 +1407,25 @@ function toApiLocation(location) {
1384
1407
  id: String(location.id),
1385
1408
  timezone: (_a = location.timezone) != null ? _a : void 0,
1386
1409
  jurisdiction: (_b = location.jurisdiction) != null ? _b : void 0,
1387
- tags: "tags" in location ? location.tags : void 0
1410
+ tags: "tags" in location ? location.tags : void 0,
1411
+ properties: location.properties ? location.properties : void 0
1388
1412
  };
1389
1413
  }
1414
+ function employeeHasConversionRate(employee) {
1415
+ var _a;
1416
+ const value = (_a = employee.properties) == null ? void 0 : _a.conversionRate;
1417
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
1418
+ }
1419
+ function employeeTouchesSelectedLocations(employee, selectedLocationIds) {
1420
+ var _a;
1421
+ const ids = (_a = employee.locationIds) != null ? _a : [];
1422
+ return ids.some((id) => selectedLocationIds.includes(String(id)));
1423
+ }
1424
+ function hasConversionRateInSelectedLocations(employees, selectedLocationIds) {
1425
+ return employees.some(
1426
+ (employee) => employeeTouchesSelectedLocations(employee, selectedLocationIds) && employeeHasConversionRate(employee)
1427
+ );
1428
+ }
1390
1429
  function buildSchedulePayload(input) {
1391
1430
  const {
1392
1431
  employees,
@@ -1397,7 +1436,11 @@ function buildSchedulePayload(input) {
1397
1436
  selectedConstraintIds,
1398
1437
  constraintValues,
1399
1438
  selectedOptimizationIds,
1400
- optimizationValues
1439
+ optimizationValues,
1440
+ trafficForecast,
1441
+ constraintDefinitionIds,
1442
+ constraintDefinitions,
1443
+ organizationId
1401
1444
  } = input;
1402
1445
  const locationsMap = /* @__PURE__ */ new Map();
1403
1446
  for (const j of jurisdictions) {
@@ -1406,19 +1449,45 @@ function buildSchedulePayload(input) {
1406
1449
  }
1407
1450
  }
1408
1451
  const selectedLocations = selectedLocationIds.map((id) => locationsMap.get(id)).filter((loc) => Boolean(loc));
1452
+ const scopedOpenShifts = openShifts.filter(
1453
+ (shift) => {
1454
+ var _a;
1455
+ return selectedLocationIds.includes(String((_a = shift.location) == null ? void 0 : _a.id));
1456
+ }
1457
+ );
1458
+ const includeConversionRate = hasConversionRateInSelectedLocations(
1459
+ employees,
1460
+ selectedLocationIds
1461
+ );
1462
+ const scopedTrafficForecast = (trafficForecast != null ? trafficForecast : []).filter(
1463
+ (entry) => selectedLocationIds.includes(String(entry.locationId))
1464
+ );
1465
+ const includeTrafficIncentive = scopedTrafficForecast.length > 0;
1409
1466
  const rules = buildRulesPayload(
1410
1467
  selectedConstraintIds,
1411
1468
  constraintValues,
1412
1469
  selectedOptimizationIds,
1413
- optimizationValues
1470
+ optimizationValues,
1471
+ { includeConversionRate, includeTrafficIncentive }
1414
1472
  );
1415
1473
  const includeEmployeePreferences = selectedOptimizationIds.includes("staff_preferences");
1474
+ const hasIds = Boolean(constraintDefinitionIds && constraintDefinitionIds.length > 0);
1475
+ const hasDefs = Boolean(constraintDefinitions && constraintDefinitions.length > 0);
1476
+ const hasRules = hasIds || hasDefs;
1477
+ if (hasIds && !organizationId) {
1478
+ throw new Error("organizationId is required when constraintDefinitionIds are provided");
1479
+ }
1480
+ const trafficRuleActive = includeTrafficIncentive && selectedOptimizationIds.includes("maximize_sales");
1416
1481
  return {
1417
1482
  employees: employees.map(toApiEmployee),
1418
- shifts: openShifts.map((shift) => toApiShift(shift, includeEmployeePreferences)),
1483
+ shifts: scopedOpenShifts.map((shift) => toApiShift(shift, includeEmployeePreferences)),
1419
1484
  locations: selectedLocations.map(toApiLocation),
1420
1485
  ...timeOffs && timeOffs.length > 0 ? { timeOffs } : {},
1421
- ...rules
1486
+ ...rules,
1487
+ ...trafficRuleActive ? { trafficForecast: scopedTrafficForecast } : {},
1488
+ ...hasIds ? { constraintDefinitionIds } : {},
1489
+ ...hasDefs ? { constraintDefinitions } : {},
1490
+ ...hasRules && organizationId ? { organizationId } : {}
1422
1491
  };
1423
1492
  }
1424
1493
 
@@ -1441,7 +1510,7 @@ async function submitSolveJob(baseURL, payload, options) {
1441
1510
  headers.Authorization = options.authorization;
1442
1511
  }
1443
1512
  const response = await axiosInstance.post(
1444
- `${cleanBaseUrl}/api/schedule/solve-async`,
1513
+ withHorizonQuery(`${cleanBaseUrl}/api/schedule/solve-async`, options),
1445
1514
  payload,
1446
1515
  { headers: Object.keys(headers).length > 0 ? headers : void 0 }
1447
1516
  );
@@ -1483,6 +1552,16 @@ async function getJobSolution(baseURL, jobId, options) {
1483
1552
  );
1484
1553
  return response.data;
1485
1554
  }
1555
+ function withHorizonQuery(url, options) {
1556
+ if (!(options == null ? void 0 : options.horizonStartDate) || !(options == null ? void 0 : options.horizonEndDate)) {
1557
+ return url;
1558
+ }
1559
+ const params = new URLSearchParams({
1560
+ dateRangeStart: options.horizonStartDate,
1561
+ dateRangeEnd: options.horizonEndDate
1562
+ });
1563
+ return `${url}?${params.toString()}`;
1564
+ }
1486
1565
 
1487
1566
  // src/utils/violationMessages.ts
1488
1567
  var SYSTEM_CONSTRAINT_MESSAGES = {
@@ -1630,17 +1709,14 @@ function getRuleBasedViolationMessage(ruleId, constraintName, description) {
1630
1709
  }
1631
1710
 
1632
1711
  // src/utils/mapViolationsToResult.ts
1633
- function mapViolationsToResult(violations, summary) {
1712
+ function mapViolationsToResult(violations, summary, customMessages) {
1713
+ var _a;
1634
1714
  const hardViolations = violations.filter((v) => v.isHardViolation === true);
1635
1715
  const seenTitles = /* @__PURE__ */ new Set();
1636
1716
  const violatedConstraints = [];
1637
1717
  for (const v of hardViolations) {
1638
1718
  const hasRuleId = v.ruleId != null && v.ruleId !== void 0 && String(v.ruleId).trim() !== "";
1639
- const msg = hasRuleId ? getRuleBasedViolationMessage(
1640
- v.ruleId,
1641
- v.constraintName,
1642
- v.description
1643
- ) : getSystemConstraintMessage(v.constraintName, v.description);
1719
+ const msg = hasRuleId ? (_a = customMessages == null ? void 0 : customMessages[v.ruleId]) != null ? _a : getRuleBasedViolationMessage(v.ruleId, v.constraintName, v.description) : getSystemConstraintMessage(v.constraintName, v.description);
1644
1720
  if (!seenTitles.has(msg.title)) {
1645
1721
  seenTitles.add(msg.title);
1646
1722
  violatedConstraints.push(msg);
@@ -1716,6 +1792,7 @@ function ViolatedConstraintsPanel({
1716
1792
  return;
1717
1793
  }
1718
1794
  if (recommendations !== null) {
1795
+ setShowRecommendations(true);
1719
1796
  return;
1720
1797
  }
1721
1798
  if (!canFetchRecommendations) {
@@ -1836,13 +1913,78 @@ function ViolatedConstraintsPanel({
1836
1913
  ] });
1837
1914
  }
1838
1915
 
1916
+ // src/weekDates.ts
1917
+ function parseCalendarYmd(ymd) {
1918
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(ymd).trim());
1919
+ if (!m) {
1920
+ return /* @__PURE__ */ new Date(NaN);
1921
+ }
1922
+ return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
1923
+ }
1924
+ function formatCalendarYmd(d) {
1925
+ const y = d.getFullYear();
1926
+ const mo = String(d.getMonth() + 1).padStart(2, "0");
1927
+ const da = String(d.getDate()).padStart(2, "0");
1928
+ return `${y}-${mo}-${da}`;
1929
+ }
1930
+ function getSequentialWeekDatesFromWeekStart(weekStartYmd) {
1931
+ const base = parseCalendarYmd(weekStartYmd);
1932
+ if (Number.isNaN(base.getTime())) return [];
1933
+ const out = [];
1934
+ for (let i = 0; i < 7; i++) {
1935
+ const d = new Date(base.getFullYear(), base.getMonth(), base.getDate() + i);
1936
+ out.push(formatCalendarYmd(d));
1937
+ }
1938
+ return out;
1939
+ }
1940
+ function weekdayTwoLetterFromYmd(ymd) {
1941
+ var _a;
1942
+ const labels = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
1943
+ const d = parseCalendarYmd(ymd);
1944
+ if (Number.isNaN(d.getTime())) return "?";
1945
+ return (_a = labels[d.getDay()]) != null ? _a : "?";
1946
+ }
1947
+ function defaultSelectedScheduleDates(orderedWeekYmds, todayYmd) {
1948
+ return orderedWeekYmds.filter((d) => d >= todayYmd);
1949
+ }
1950
+ function todayCalendarYmdInTimezone(timezone) {
1951
+ const fallback = formatCalendarYmd(/* @__PURE__ */ new Date());
1952
+ const tz = timezone == null ? void 0 : timezone.trim();
1953
+ if (!tz) return fallback;
1954
+ try {
1955
+ const parts = new Intl.DateTimeFormat("en-CA", {
1956
+ timeZone: tz,
1957
+ year: "numeric",
1958
+ month: "2-digit",
1959
+ day: "2-digit"
1960
+ }).formatToParts(/* @__PURE__ */ new Date());
1961
+ const valueByType = new Map(parts.map((part) => [part.type, part.value]));
1962
+ const year = valueByType.get("year");
1963
+ const month = valueByType.get("month");
1964
+ const day = valueByType.get("day");
1965
+ return year && month && day ? `${year}-${month}-${day}` : fallback;
1966
+ } catch {
1967
+ return fallback;
1968
+ }
1969
+ }
1970
+
1839
1971
  // src/AutoSchedulerModal.tsx
1840
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1972
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1973
+ function resolveOptimizationFooter(footer, key) {
1974
+ if (!footer) return null;
1975
+ const node = typeof footer === "function" ? footer() : footer;
1976
+ if (isValidElement(node)) return cloneElement(node, { key });
1977
+ return node;
1978
+ }
1979
+ var DRAWER_WIDTH_MAP = { sm: "448px", md: "578px", lg: "600px" };
1841
1980
  function AutoSchedulerModal({
1981
+ displayMode,
1982
+ drawerSize = "lg",
1842
1983
  baseURL,
1843
1984
  isOpen,
1844
1985
  title = "Auto-scheduling",
1845
- bannerText,
1986
+ bannerTitle = "Create a smarter retail schedule in seconds.",
1987
+ bannerText = "AI assigns open shifts using your rules, availability, and optimization priorities while keeping existing\n assigned shifts in place.",
1846
1988
  jurisdictions,
1847
1989
  openShifts,
1848
1990
  employees,
@@ -1861,22 +2003,78 @@ function AutoSchedulerModal({
1861
2003
  onSolution,
1862
2004
  getStoredConfigForLocations,
1863
2005
  persistConfigForLocations,
1864
- errorToast
2006
+ errorToast,
2007
+ organizationId,
2008
+ constraintDefinitionIds,
2009
+ constraintDefinitions,
2010
+ hardConstraintOptions,
2011
+ softConstraintGroups,
2012
+ optimizationFooter,
2013
+ trafficForecast,
2014
+ builtinConstraintOptions,
2015
+ subLocations,
2016
+ showSubLocations,
2017
+ teams,
2018
+ showTeams,
2019
+ onSelectionChange,
2020
+ onGenerate,
2021
+ onGenerateSolution,
2022
+ solveStatus,
2023
+ scheduleWeekStartYmd,
2024
+ todayCalendarYmd,
2025
+ evaluationTimezone
1865
2026
  }) {
1866
2027
  const baseTheme = useTheme();
1867
- const { chakraOverride, styleOverrides } = useMemo2(
1868
- () => resolveThemeParts(theme),
1869
- [theme]
1870
- );
2028
+ const { chakraOverride, styleOverrides } = useMemo2(() => resolveThemeParts(theme), [theme]);
1871
2029
  const mergedTheme = useMemo2(
1872
2030
  () => mergeThemeWithBase(baseTheme, chakraOverride),
1873
2031
  [baseTheme, chakraOverride]
1874
2032
  );
1875
2033
  const styles = useMemo2(() => buildStyles(defaultStyles, styleOverrides), [styleOverrides]);
2034
+ const effectiveHardConstraintOptions = useMemo2(() => {
2035
+ const base = hardConstraintOptions != null ? hardConstraintOptions : HARD_CONSTRAINT_OPTIONS;
2036
+ return base.map((opt) => {
2037
+ var _a, _b, _c, _d;
2038
+ return {
2039
+ ...opt,
2040
+ defaultValue: (_a = opt.defaultValue) != null ? _a : HARD_CONSTRAINT_DEFAULTS[opt.id],
2041
+ suffix: (_b = opt.suffix) != null ? _b : opt.id === "max_work_days_per_week" ? "days" : "hours",
2042
+ min: (_c = opt.min) != null ? _c : opt.id === "max_work_days_per_week" ? 1 : void 0,
2043
+ max: (_d = opt.max) != null ? _d : opt.id === "max_work_days_per_week" ? 6 : void 0
2044
+ };
2045
+ });
2046
+ }, [hardConstraintOptions]);
2047
+ const effectiveSoftConstraintGroups = useMemo2(
2048
+ () => softConstraintGroups != null ? softConstraintGroups : OPTIMIZATION_OPTION_GROUPS,
2049
+ [softConstraintGroups]
2050
+ );
2051
+ const effectiveBuiltinOptions = useMemo2(
2052
+ () => builtinConstraintOptions != null ? builtinConstraintOptions : [],
2053
+ [builtinConstraintOptions]
2054
+ );
2055
+ const defaultSelectedBuiltinIds = useMemo2(
2056
+ () => effectiveBuiltinOptions.filter((option) => option.defaultSelected).map((option) => option.id),
2057
+ [effectiveBuiltinOptions]
2058
+ );
2059
+ const dynamicViolationMessages = useMemo2(() => {
2060
+ const map = {};
2061
+ for (const opt of effectiveHardConstraintOptions) {
2062
+ map[opt.id] = { title: opt.name, detail: opt.description || opt.name };
2063
+ }
2064
+ for (const group of effectiveSoftConstraintGroups) {
2065
+ for (const opt of group.options) {
2066
+ map[opt.id] = { title: opt.title, detail: opt.subtitle || opt.title };
2067
+ }
2068
+ }
2069
+ return map;
2070
+ }, [effectiveHardConstraintOptions, effectiveSoftConstraintGroups]);
2071
+ const [selectedSubLocationIds, setSelectedSubLocationIds] = useState2([]);
2072
+ const [selectedTeamIds, setSelectedTeamIds] = useState2([]);
1876
2073
  const [selectedConstraintIds, setSelectedConstraintIds] = useState2([]);
1877
2074
  const [constraintValues, setConstraintValues] = useState2({});
1878
2075
  const [selectedOptimizationIds, setSelectedOptimizationIds] = useState2([]);
1879
2076
  const [optimizationValues, setOptimizationValues] = useState2({});
2077
+ const [selectedScheduleDates, setSelectedScheduleDates] = useState2([]);
1880
2078
  const [screen, setScreen] = useState2("configure");
1881
2079
  const [activeStepIndex, setActiveStepIndex] = useState2(0);
1882
2080
  const [completedStepIndex, setCompletedStepIndex] = useState2(-1);
@@ -1885,12 +2083,19 @@ function AutoSchedulerModal({
1885
2083
  const timersRef = useRef([]);
1886
2084
  const pollIntervalRef = useRef(null);
1887
2085
  const runIdRef = useRef(0);
2086
+ const weekDatesOrdered = useMemo2(() => {
2087
+ if (!scheduleWeekStartYmd) return [];
2088
+ return getSequentialWeekDatesFromWeekStart(scheduleWeekStartYmd);
2089
+ }, [scheduleWeekStartYmd]);
2090
+ const toggleScheduleDate = (ymd) => {
2091
+ setSelectedScheduleDates(
2092
+ (prev) => prev.includes(ymd) ? prev.filter((d) => d !== ymd) : [...prev, ymd].sort()
2093
+ );
2094
+ };
1888
2095
  const selectedByJurisdiction = useMemo2(
1889
2096
  () => jurisdictions.map((jurisdiction) => ({
1890
2097
  ...jurisdiction,
1891
- selectedLocations: jurisdiction.locations.filter(
1892
- (location) => selectedLocationIds.includes(location.id)
1893
- )
2098
+ selectedLocations: jurisdiction.locations.filter((location) => selectedLocationIds.includes(location.id))
1894
2099
  })).filter((jurisdiction) => jurisdiction.selectedLocations.length > 0),
1895
2100
  [jurisdictions, selectedLocationIds]
1896
2101
  );
@@ -1899,43 +2104,84 @@ function AutoSchedulerModal({
1899
2104
  0
1900
2105
  );
1901
2106
  const locationIdsWithOpenShifts = useMemo2(() => {
1902
- return new Set(
1903
- openShifts.filter((shift) => !shift.employeeId).map((shift) => shift.location.id)
1904
- );
2107
+ return new Set(openShifts.filter((shift) => !shift.employeeId).map((shift) => shift.location.id));
1905
2108
  }, [openShifts]);
2109
+ const filteredSubLocations = useMemo2(
2110
+ () => {
2111
+ var _a;
2112
+ return (_a = subLocations == null ? void 0 : subLocations.filter((sl) => selectedLocationIds.includes(sl.parentId))) != null ? _a : [];
2113
+ },
2114
+ [subLocations, selectedLocationIds]
2115
+ );
2116
+ const selectedLocationTimezone = useMemo2(() => {
2117
+ var _a, _b;
2118
+ const allLocations = jurisdictions.flatMap((jurisdiction) => jurisdiction.locations);
2119
+ for (const id of selectedLocationIds) {
2120
+ const timezone = (_b = (_a = allLocations.find((location) => location.id === id)) == null ? void 0 : _a.timezone) == null ? void 0 : _b.trim();
2121
+ if (timezone) return timezone;
2122
+ }
2123
+ return (evaluationTimezone == null ? void 0 : evaluationTimezone.trim()) || "UTC";
2124
+ }, [evaluationTimezone, jurisdictions, selectedLocationIds]);
2125
+ const effectiveTodayCalendarYmd = useMemo2(() => {
2126
+ return todayCalendarYmd != null ? todayCalendarYmd : todayCalendarYmdInTimezone(selectedLocationTimezone);
2127
+ }, [selectedLocationTimezone, todayCalendarYmd]);
1906
2128
  const selectedConstraints = useMemo2(
1907
- () => HARD_CONSTRAINT_OPTIONS.filter((option) => selectedConstraintIds.includes(option.id)),
1908
- [selectedConstraintIds]
2129
+ () => effectiveHardConstraintOptions.filter((option) => selectedConstraintIds.includes(option.id)),
2130
+ [effectiveHardConstraintOptions, selectedConstraintIds]
2131
+ );
2132
+ const selectedConstraintDisplayOptions = useMemo2(
2133
+ () => effectiveHardConstraintOptions.filter(
2134
+ (option) => selectedConstraintIds.includes(option.id) || option.readOnly && option.defaultSelected
2135
+ ),
2136
+ [effectiveHardConstraintOptions, selectedConstraintIds]
1909
2137
  );
1910
2138
  const optimizationOptions = useMemo2(
1911
- () => OPTIMIZATION_OPTION_GROUPS.flatMap(
2139
+ () => effectiveSoftConstraintGroups.flatMap(
1912
2140
  (group) => group.options.map((option) => ({
1913
2141
  ...option,
1914
2142
  groupId: group.id,
1915
2143
  groupName: group.name
1916
2144
  }))
1917
2145
  ),
1918
- []
2146
+ [effectiveSoftConstraintGroups]
1919
2147
  );
1920
2148
  const selectedOptimizationOptions = useMemo2(
2149
+ () => optimizationOptions.filter((option) => selectedOptimizationIds.includes(option.id)),
2150
+ [optimizationOptions, selectedOptimizationIds]
2151
+ );
2152
+ const selectedOptimizationDisplayOptions = useMemo2(
1921
2153
  () => optimizationOptions.filter(
1922
- (option) => selectedOptimizationIds.includes(option.id)
2154
+ (option) => selectedOptimizationIds.includes(option.id) || option.readOnly && option.defaultSelected
1923
2155
  ),
1924
2156
  [optimizationOptions, selectedOptimizationIds]
1925
2157
  );
2158
+ const effectiveSelectedConstraintIds = useMemo2(
2159
+ () => Array.from(
2160
+ /* @__PURE__ */ new Set([
2161
+ ...selectedConstraintIds,
2162
+ ...effectiveHardConstraintOptions.filter((option) => option.readOnly && option.defaultSelected).map((option) => option.id)
2163
+ ])
2164
+ ),
2165
+ [selectedConstraintIds, effectiveHardConstraintOptions]
2166
+ );
2167
+ const effectiveSelectedOptimizationIds = useMemo2(
2168
+ () => Array.from(
2169
+ /* @__PURE__ */ new Set([
2170
+ ...selectedOptimizationIds,
2171
+ ...optimizationOptions.filter((option) => option.readOnly && option.defaultSelected).map((option) => option.id)
2172
+ ])
2173
+ ),
2174
+ [selectedOptimizationIds, optimizationOptions]
2175
+ );
1926
2176
  const hasEmptyInputs = useMemo2(() => {
1927
- const hasEmptyConstraint = selectedConstraints.some(
1928
- (opt) => {
1929
- var _a;
1930
- return !((_a = constraintValues[opt.id]) == null ? void 0 : _a.trim());
1931
- }
1932
- );
1933
- const hasEmptyWeight = selectedOptimizationOptions.length >= 2 && selectedOptimizationOptions.some(
1934
- (opt) => {
1935
- var _a;
1936
- return !((_a = optimizationValues[`${opt.id}_weightPoints`]) == null ? void 0 : _a.trim());
1937
- }
1938
- );
2177
+ const hasEmptyConstraint = selectedConstraints.filter((opt) => opt.defaultValue != null).some((opt) => {
2178
+ var _a;
2179
+ return !((_a = constraintValues[opt.id]) == null ? void 0 : _a.trim());
2180
+ });
2181
+ const hasEmptyWeight = selectedOptimizationOptions.length >= 2 && selectedOptimizationOptions.some((opt) => {
2182
+ var _a;
2183
+ return !((_a = optimizationValues[`${opt.id}_weightPoints`]) == null ? void 0 : _a.trim());
2184
+ });
1939
2185
  return hasEmptyConstraint || hasEmptyWeight;
1940
2186
  }, [selectedConstraints, constraintValues, selectedOptimizationOptions, optimizationValues]);
1941
2187
  const allocationSummary = useMemo2(() => {
@@ -1969,13 +2215,25 @@ function AutoSchedulerModal({
1969
2215
  setScreen("configure");
1970
2216
  setLastFailure(null);
1971
2217
  setSelectedLocationIds([]);
2218
+ setSelectedSubLocationIds([]);
2219
+ setSelectedTeamIds([]);
1972
2220
  setSelectedOptimizationIds([]);
1973
2221
  setSelectedConstraintIds([]);
1974
2222
  setConstraintValues({});
1975
2223
  setOptimizationValues({});
2224
+ setSelectedScheduleDates([]);
1976
2225
  resetProgress();
1977
2226
  }
1978
2227
  }, [isOpen]);
2228
+ useEffect(() => {
2229
+ if (!isOpen || !scheduleWeekStartYmd) return;
2230
+ if (weekDatesOrdered.length === 0) {
2231
+ setSelectedScheduleDates([]);
2232
+ return;
2233
+ }
2234
+ const today = effectiveTodayCalendarYmd != null ? effectiveTodayCalendarYmd : weekDatesOrdered[0];
2235
+ setSelectedScheduleDates(defaultSelectedScheduleDates(weekDatesOrdered, today));
2236
+ }, [isOpen, scheduleWeekStartYmd, weekDatesOrdered, effectiveTodayCalendarYmd]);
1979
2237
  useEffect(() => {
1980
2238
  if (isOpen && initialConfig) {
1981
2239
  setSelectedLocationIds(initialConfig.selectedLocationIds);
@@ -2008,11 +2266,12 @@ function AutoSchedulerModal({
2008
2266
  }, [selectedLocationIds, initialConfig, getStoredConfigForLocations]);
2009
2267
  useEffect(() => () => clearTimers(), []);
2010
2268
  useEffect(() => {
2269
+ if (onGenerate) return;
2011
2270
  setSelectedLocationIds((previous) => {
2012
2271
  const filtered = previous.filter((id) => locationIdsWithOpenShifts.has(id));
2013
2272
  return filtered.length === previous.length ? previous : filtered;
2014
2273
  });
2015
- }, [locationIdsWithOpenShifts]);
2274
+ }, [locationIdsWithOpenShifts, onGenerate]);
2016
2275
  const stepStatusForIndex = (index) => {
2017
2276
  if (index <= completedStepIndex) {
2018
2277
  return "done";
@@ -2040,144 +2299,249 @@ function AutoSchedulerModal({
2040
2299
  timersRef.current.push(timerId);
2041
2300
  };
2042
2301
  const afterSuccessSteps = (runId, response) => {
2043
- schedule(STEP_INTERVAL_MS, () => {
2044
- if (runIdRef.current !== runId) return;
2045
- setCompletedStepIndex(2);
2046
- setActiveStepIndex(3);
2047
- }, runId);
2048
- schedule(STEP_INTERVAL_MS * 2, () => {
2049
- if (runIdRef.current !== runId) return;
2050
- setCompletedStepIndex(3);
2051
- setActiveStepIndex(4);
2052
- }, runId);
2053
- schedule(STEP_INTERVAL_MS * 3, async () => {
2054
- if (runIdRef.current !== runId) return;
2055
- try {
2056
- await onSolution(response.solution);
2057
- setCompletedStepIndex(4);
2302
+ schedule(
2303
+ STEP_INTERVAL_MS,
2304
+ () => {
2305
+ if (runIdRef.current !== runId) return;
2306
+ setCompletedStepIndex(2);
2307
+ setActiveStepIndex(3);
2308
+ },
2309
+ runId
2310
+ );
2311
+ schedule(
2312
+ STEP_INTERVAL_MS * 2,
2313
+ () => {
2314
+ if (runIdRef.current !== runId) return;
2315
+ setCompletedStepIndex(3);
2058
2316
  setActiveStepIndex(4);
2059
- } catch (err) {
2060
- console.error(err);
2061
- const defaultErrorMessage = "Failed to create schedule shifts";
2062
- const detailedErrorMessage = err instanceof Error ? err.message || defaultErrorMessage : defaultErrorMessage;
2063
- setLastFailure({
2064
- violatedConstraints: [
2065
- {
2066
- title: "Something went wrong",
2067
- detail: detailedErrorMessage
2068
- }
2069
- ],
2070
- recommendedFixes: []
2071
- });
2072
- setScreen("failed");
2073
- }
2074
- }, runId);
2317
+ },
2318
+ runId
2319
+ );
2320
+ schedule(
2321
+ STEP_INTERVAL_MS * 3,
2322
+ async () => {
2323
+ if (runIdRef.current !== runId) return;
2324
+ try {
2325
+ await (onSolution == null ? void 0 : onSolution(response.solution));
2326
+ setCompletedStepIndex(4);
2327
+ setActiveStepIndex(4);
2328
+ } catch (err) {
2329
+ console.error(err);
2330
+ const defaultErrorMessage = "Failed to create schedule shifts";
2331
+ const detailedErrorMessage = err instanceof Error ? err.message || defaultErrorMessage : defaultErrorMessage;
2332
+ setLastFailure({
2333
+ violatedConstraints: [
2334
+ {
2335
+ title: "Something went wrong",
2336
+ detail: detailedErrorMessage
2337
+ }
2338
+ ],
2339
+ recommendedFixes: []
2340
+ });
2341
+ setScreen("failed");
2342
+ }
2343
+ },
2344
+ runId
2345
+ );
2075
2346
  };
2076
2347
  const handleCreateSchedule = () => {
2077
2348
  if (selectedCount === 0) {
2078
2349
  return;
2079
2350
  }
2351
+ if (scheduleWeekStartYmd && selectedScheduleDates.length === 0) {
2352
+ return;
2353
+ }
2354
+ if (onGenerate) {
2355
+ const runId2 = resetBeforeSubmit();
2356
+ schedule(
2357
+ STEP_INTERVAL_MS,
2358
+ async () => {
2359
+ if (runIdRef.current !== runId2) return;
2360
+ setCompletedStepIndex(0);
2361
+ setActiveStepIndex(1);
2362
+ try {
2363
+ const generateResult = await onGenerate({
2364
+ selectedLocationIds,
2365
+ selectedSubLocationIds,
2366
+ selectedTeamIds,
2367
+ schedulerTimezone: selectedLocationTimezone,
2368
+ selectedScheduleDates,
2369
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2370
+ constraintValues,
2371
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2372
+ optimizationValues,
2373
+ selectedBuiltinIds: defaultSelectedBuiltinIds
2374
+ });
2375
+ if (runIdRef.current !== runId2) return;
2376
+ setCompletedStepIndex(1);
2377
+ setActiveStepIndex(2);
2378
+ schedule(
2379
+ STEP_INTERVAL_MS,
2380
+ () => {
2381
+ if (runIdRef.current !== runId2) return;
2382
+ setCompletedStepIndex(2);
2383
+ setActiveStepIndex(3);
2384
+ },
2385
+ runId2
2386
+ );
2387
+ schedule(
2388
+ STEP_INTERVAL_MS * 2,
2389
+ () => {
2390
+ if (runIdRef.current !== runId2) return;
2391
+ setCompletedStepIndex(3);
2392
+ setActiveStepIndex(4);
2393
+ },
2394
+ runId2
2395
+ );
2396
+ schedule(
2397
+ STEP_INTERVAL_MS * 3,
2398
+ () => {
2399
+ if (runIdRef.current !== runId2) return;
2400
+ setCompletedStepIndex(4);
2401
+ setActiveStepIndex(4);
2402
+ onGenerateSolution == null ? void 0 : onGenerateSolution(generateResult);
2403
+ onClose();
2404
+ },
2405
+ runId2
2406
+ );
2407
+ } catch (err) {
2408
+ if (runIdRef.current !== runId2) return;
2409
+ if (err instanceof InfeasibleScheduleError) {
2410
+ setLastFailure(err.failure);
2411
+ } else {
2412
+ const msg = err instanceof Error ? err.message : "Unknown error";
2413
+ setLastFailure({
2414
+ violatedConstraints: [{ title: "Error", detail: msg }],
2415
+ recommendedFixes: []
2416
+ });
2417
+ }
2418
+ setScreen("failed");
2419
+ }
2420
+ },
2421
+ runId2
2422
+ );
2423
+ return;
2424
+ }
2080
2425
  const payload = buildSchedulePayload({
2081
2426
  employees,
2082
2427
  openShifts,
2083
2428
  timeOffs,
2084
2429
  selectedLocationIds,
2085
2430
  jurisdictions,
2086
- selectedConstraintIds,
2431
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2087
2432
  constraintValues,
2088
- selectedOptimizationIds,
2089
- optimizationValues
2433
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2434
+ optimizationValues,
2435
+ trafficForecast,
2436
+ constraintDefinitionIds,
2437
+ constraintDefinitions,
2438
+ organizationId
2090
2439
  });
2091
2440
  persistConfigForLocations == null ? void 0 : persistConfigForLocations(selectedLocationIds, {
2092
2441
  selectedLocationIds,
2093
- selectedConstraintIds,
2442
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2094
2443
  constraintValues,
2095
- selectedOptimizationIds,
2096
- optimizationValues
2444
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2445
+ optimizationValues,
2446
+ selectedBuiltinIds: defaultSelectedBuiltinIds
2097
2447
  });
2098
2448
  const runId = resetBeforeSubmit();
2099
- schedule(STEP_INTERVAL_MS, async () => {
2100
- setCompletedStepIndex(0);
2101
- setActiveStepIndex(1);
2102
- try {
2103
- const token = await getToken(getTokenURLAndHeaders.url, getTokenURLAndHeaders.headers);
2104
- if (!token) {
2105
- throw new Error("Failed to get auto-scheduler token");
2106
- }
2107
- const auth = { authorization: `Bearer ${token}` };
2108
- const { jobId } = await submitSolveJob(baseURL, payload, auth);
2109
- if (runIdRef.current !== runId) {
2110
- return;
2111
- }
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);
2449
+ schedule(
2450
+ STEP_INTERVAL_MS,
2451
+ async () => {
2452
+ setCompletedStepIndex(0);
2453
+ setActiveStepIndex(1);
2454
+ try {
2455
+ const token = await getToken(getTokenURLAndHeaders.url, getTokenURLAndHeaders.headers);
2456
+ if (!token) {
2457
+ throw new Error("Failed to get auto-scheduler token");
2458
+ }
2459
+ const selectedDates = [...selectedScheduleDates].sort();
2460
+ const horizonOptions = selectedDates.length > 0 ? {
2461
+ horizonStartDate: selectedDates[0],
2462
+ horizonEndDate: selectedDates[selectedDates.length - 1]
2463
+ } : {};
2464
+ const auth = { authorization: `Bearer ${token}`, ...horizonOptions };
2465
+ const { jobId } = await submitSolveJob(baseURL, payload, auth);
2466
+ if (runIdRef.current !== runId) {
2467
+ return;
2468
+ }
2469
+ setCompletedStepIndex(1);
2470
+ setActiveStepIndex(OPTIMIZING_STEP_INDEX);
2471
+ const pollForResult = async () => {
2118
2472
  if (runIdRef.current !== runId) return;
2119
- if (statusRes.status === "NOT_SOLVING" || statusRes.status === "TERMINATED_EARLY") {
2473
+ try {
2474
+ const statusRes = await getJobStatus(baseURL, jobId, auth);
2475
+ if (runIdRef.current !== runId) return;
2476
+ if (statusRes.status === "NOT_SOLVING" || statusRes.status === "TERMINATED_EARLY") {
2477
+ if (pollIntervalRef.current) {
2478
+ clearInterval(pollIntervalRef.current);
2479
+ pollIntervalRef.current = null;
2480
+ }
2481
+ const response = await getJobResult(baseURL, jobId, auth);
2482
+ if (runIdRef.current !== runId) return;
2483
+ let responseForHandling = response;
2484
+ if (response.explanation.isFeasible) {
2485
+ const solution = await getJobSolution(baseURL, jobId, auth);
2486
+ if (runIdRef.current !== runId) return;
2487
+ responseForHandling = { ...response, solution };
2488
+ }
2489
+ handleSolveResult(runId, responseForHandling);
2490
+ return;
2491
+ }
2492
+ } catch (err) {
2493
+ if (runIdRef.current !== runId) return;
2120
2494
  if (pollIntervalRef.current) {
2121
2495
  clearInterval(pollIntervalRef.current);
2122
2496
  pollIntervalRef.current = null;
2123
2497
  }
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;
2498
+ console.error(err);
2499
+ setLastFailure(NETWORK_ERROR_FAILURE);
2500
+ setScreen("failed");
2140
2501
  }
2141
- console.error(err);
2142
- setLastFailure(NETWORK_ERROR_FAILURE);
2143
- setScreen("failed");
2502
+ };
2503
+ pollForResult();
2504
+ pollIntervalRef.current = setInterval(pollForResult, POLL_INTERVAL_MS);
2505
+ } catch (err) {
2506
+ if (runIdRef.current !== runId) {
2507
+ return;
2144
2508
  }
2145
- };
2146
- pollForResult();
2147
- pollIntervalRef.current = setInterval(pollForResult, POLL_INTERVAL_MS);
2148
- } catch (err) {
2149
- if (runIdRef.current !== runId) {
2150
- return;
2509
+ console.error(err);
2510
+ setLastFailure(NETWORK_ERROR_FAILURE);
2511
+ setScreen("failed");
2151
2512
  }
2152
- console.error(err);
2153
- setLastFailure(NETWORK_ERROR_FAILURE);
2154
- setScreen("failed");
2155
- }
2156
- }, runId);
2513
+ },
2514
+ runId
2515
+ );
2157
2516
  };
2158
2517
  const handleSolveResult = (runId, response) => {
2159
- var _a, _b;
2518
+ var _a;
2160
2519
  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
- );
2520
+ const result = mapViolationsToResult((_a = response.explanation.violations) != null ? _a : [], response.explanation.summary, dynamicViolationMessages);
2521
+ const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter((l) => selectedLocationIds.includes(l.id));
2168
2522
  const recommendationPayload = generateRecommendationsURLAndHeaders ? {
2169
2523
  context: {
2170
2524
  selectedLocationIds,
2171
- selectedConstraintIds,
2525
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2172
2526
  constraintValues,
2173
- selectedOptimizationIds,
2527
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2174
2528
  optimizationValues,
2175
- timezone: (_b = selectedLocations[0]) == null ? void 0 : _b.timezone
2529
+ timezone: selectedLocationTimezone
2176
2530
  },
2177
2531
  locations: selectedLocations,
2178
2532
  employees,
2179
- openShifts: openShifts.filter((s) => !s.employeeId),
2180
- assignedShifts: openShifts.filter((s) => Boolean(s.employeeId)),
2533
+ openShifts: openShifts.filter(
2534
+ (s) => {
2535
+ var _a2;
2536
+ return !s.employeeId && selectedLocationIds.includes(String((_a2 = s.location) == null ? void 0 : _a2.id));
2537
+ }
2538
+ ),
2539
+ assignedShifts: openShifts.filter(
2540
+ (s) => {
2541
+ var _a2;
2542
+ return Boolean(s.employeeId) && selectedLocationIds.includes(String((_a2 = s.location) == null ? void 0 : _a2.id));
2543
+ }
2544
+ ),
2181
2545
  timeOffs: timeOffs != null ? timeOffs : [],
2182
2546
  explanation: response.explanation
2183
2547
  } : void 0;
@@ -2196,17 +2560,34 @@ function AutoSchedulerModal({
2196
2560
  setScreen("configure");
2197
2561
  };
2198
2562
  const handleToggleLocation = (locationId) => {
2199
- if (!locationIdsWithOpenShifts.has(locationId)) {
2563
+ if (!onGenerate && !locationIdsWithOpenShifts.has(locationId)) {
2200
2564
  return;
2201
2565
  }
2202
- if (selectedLocationIds.includes(locationId)) {
2203
- setSelectedLocationIds(selectedLocationIds.filter((id) => id !== locationId));
2204
- return;
2566
+ const newLocationIds = selectedLocationIds.includes(locationId) ? selectedLocationIds.filter((id) => id !== locationId) : [...selectedLocationIds, locationId];
2567
+ setSelectedLocationIds(newLocationIds);
2568
+ const filteredSubs = selectedSubLocationIds.filter((subId) => {
2569
+ const sub = subLocations == null ? void 0 : subLocations.find((s) => s.id === subId);
2570
+ return sub ? newLocationIds.includes(sub.parentId) : false;
2571
+ });
2572
+ if (filteredSubs.length !== selectedSubLocationIds.length) {
2573
+ setSelectedSubLocationIds(filteredSubs);
2205
2574
  }
2206
- setSelectedLocationIds([...selectedLocationIds, locationId]);
2575
+ onSelectionChange == null ? void 0 : onSelectionChange(
2576
+ newLocationIds,
2577
+ filteredSubs.length !== selectedSubLocationIds.length ? filteredSubs : selectedSubLocationIds
2578
+ );
2579
+ };
2580
+ const handleToggleSubLocation = (subLocationId) => {
2581
+ const next = selectedSubLocationIds.includes(subLocationId) ? selectedSubLocationIds.filter((id) => id !== subLocationId) : [...selectedSubLocationIds, subLocationId];
2582
+ setSelectedSubLocationIds(next);
2583
+ onSelectionChange == null ? void 0 : onSelectionChange(selectedLocationIds, next);
2584
+ };
2585
+ const handleToggleTeam = (teamId) => {
2586
+ setSelectedTeamIds((prev) => prev.includes(teamId) ? prev.filter((id) => id !== teamId) : [...prev, teamId]);
2207
2587
  };
2208
2588
  const handleToggleConstraint = (constraintId) => {
2209
2589
  setSelectedConstraintIds((prev) => {
2590
+ var _a;
2210
2591
  if (prev.includes(constraintId)) {
2211
2592
  setConstraintValues((values) => {
2212
2593
  const next = { ...values };
@@ -2215,10 +2596,11 @@ function AutoSchedulerModal({
2215
2596
  });
2216
2597
  return prev.filter((id) => id !== constraintId);
2217
2598
  }
2218
- if (HARD_CONSTRAINT_DEFAULTS[constraintId]) {
2599
+ const defaultVal = (_a = effectiveHardConstraintOptions.find((o) => o.id === constraintId)) == null ? void 0 : _a.defaultValue;
2600
+ if (defaultVal) {
2219
2601
  setConstraintValues((values) => ({
2220
2602
  ...values,
2221
- [constraintId]: HARD_CONSTRAINT_DEFAULTS[constraintId]
2603
+ [constraintId]: defaultVal
2222
2604
  }));
2223
2605
  }
2224
2606
  return [...prev, constraintId];
@@ -2270,12 +2652,12 @@ function AutoSchedulerModal({
2270
2652
  const configurationContent = /* @__PURE__ */ jsxs3(Stack2, { spacing: "20px", children: [
2271
2653
  /* @__PURE__ */ jsxs3(Box2, { sx: styles.banner, children: [
2272
2654
  /* @__PURE__ */ jsx3(Box2, { sx: styles.bannerIcon, children: /* @__PURE__ */ jsx3(MagicIcon, {}) }),
2273
- bannerText ? /* @__PURE__ */ jsx3(Text2, { sx: styles.bannerText, children: bannerText }) : /* @__PURE__ */ jsxs3(Stack2, { spacing: "4px", flex: 1, children: [
2274
- /* @__PURE__ */ jsx3(Text2, { sx: styles.bannerTitle, children: "Create a smarter retail schedule in seconds." }),
2275
- /* @__PURE__ */ jsx3(Text2, { sx: styles.bannerText, children: "AI assigns open shifts using your rules, availability, and optimization priorities while keeping existing assigned shifts in place." })
2655
+ /* @__PURE__ */ jsxs3(Stack2, { spacing: "4px", flex: 1, children: [
2656
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.bannerTitle, children: bannerTitle }),
2657
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.bannerText, children: bannerText })
2276
2658
  ] })
2277
2659
  ] }),
2278
- openShifts.length === 0 ? /* @__PURE__ */ jsxs3(
2660
+ !onGenerate && openShifts.length === 0 ? /* @__PURE__ */ jsxs3(
2279
2661
  Box2,
2280
2662
  {
2281
2663
  sx: {
@@ -2318,30 +2700,33 @@ function AutoSchedulerModal({
2318
2700
  ] }) }) }),
2319
2701
  /* @__PURE__ */ jsx3(PopoverContent, { sx: styles.popoverContent, children: /* @__PURE__ */ jsx3(PopoverBody, { p: 0, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "4px", children: jurisdictions.map((jurisdiction) => /* @__PURE__ */ jsxs3(Box2, { children: [
2320
2702
  /* @__PURE__ */ jsx3(Text2, { sx: styles.groupHeader, children: jurisdiction.title }),
2321
- /* @__PURE__ */ jsx3(Stack2, { spacing: "2px", children: jurisdiction.locations.map((location) => /* @__PURE__ */ jsxs3(
2322
- HStack2,
2323
- {
2324
- as: "label",
2325
- sx: {
2326
- ...styles.optionRow,
2327
- cursor: locationIdsWithOpenShifts.has(location.id) ? "pointer" : "not-allowed",
2328
- opacity: locationIdsWithOpenShifts.has(location.id) ? 1 : 0.55
2703
+ /* @__PURE__ */ jsx3(Stack2, { spacing: "2px", children: jurisdiction.locations.map((location) => {
2704
+ const isEnabled = onGenerate || locationIdsWithOpenShifts.has(location.id);
2705
+ return /* @__PURE__ */ jsxs3(
2706
+ HStack2,
2707
+ {
2708
+ as: "label",
2709
+ sx: {
2710
+ ...styles.optionRow,
2711
+ cursor: isEnabled ? "pointer" : "not-allowed",
2712
+ opacity: isEnabled ? 1 : 0.55
2713
+ },
2714
+ children: [
2715
+ /* @__PURE__ */ jsx3(
2716
+ Checkbox,
2717
+ {
2718
+ id: `location-${location.id}`,
2719
+ isChecked: selectedLocationIds.includes(location.id),
2720
+ isDisabled: !isEnabled,
2721
+ onChange: () => handleToggleLocation(location.id)
2722
+ }
2723
+ ),
2724
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.optionLabel, children: location.name })
2725
+ ]
2329
2726
  },
2330
- children: [
2331
- /* @__PURE__ */ jsx3(
2332
- Checkbox,
2333
- {
2334
- id: `location-${location.id}`,
2335
- isChecked: selectedLocationIds.includes(location.id),
2336
- isDisabled: !locationIdsWithOpenShifts.has(location.id),
2337
- onChange: () => handleToggleLocation(location.id)
2338
- }
2339
- ),
2340
- /* @__PURE__ */ jsx3(Text2, { sx: styles.optionLabel, children: location.name })
2341
- ]
2342
- },
2343
- location.id
2344
- )) })
2727
+ location.id
2728
+ );
2729
+ }) })
2345
2730
  ] }, jurisdiction.id)) }) }) })
2346
2731
  ] }),
2347
2732
  /* @__PURE__ */ jsx3(Stack2, { spacing: "8px", children: selectedByJurisdiction.map((jurisdiction) => /* @__PURE__ */ jsxs3(Box2, { sx: styles.selectedBanner, children: [
@@ -2355,11 +2740,128 @@ function AutoSchedulerModal({
2355
2740
  ] })
2356
2741
  ] }, jurisdiction.id)) })
2357
2742
  ] }),
2743
+ showSubLocations && /* @__PURE__ */ jsxs3(Stack2, { spacing: "8px", sx: styles.locationSection, children: [
2744
+ /* @__PURE__ */ jsx3(Stack2, { spacing: "4px", children: /* @__PURE__ */ jsxs3(HStack2, { sx: styles.locationLabelRow, children: [
2745
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.locationLabel, children: "Sub-location" }),
2746
+ selectedSubLocationIds.length > 0 && /* @__PURE__ */ jsx3(Badge, { sx: styles.locationBadge, children: selectedSubLocationIds.length })
2747
+ ] }) }),
2748
+ /* @__PURE__ */ jsxs3(Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
2749
+ /* @__PURE__ */ jsx3(PopoverTrigger, { children: /* @__PURE__ */ jsx3(
2750
+ Button2,
2751
+ {
2752
+ variant: "unstyled",
2753
+ isDisabled: selectedLocationIds.length === 0 || filteredSubLocations.length === 0,
2754
+ sx: {
2755
+ ...styles.locationTrigger,
2756
+ ...selectedLocationIds.length === 0 || filteredSubLocations.length === 0 ? { opacity: 0.5, cursor: "not-allowed" } : {}
2757
+ },
2758
+ children: /* @__PURE__ */ jsxs3(HStack2, { justify: "space-between", w: "full", children: [
2759
+ /* @__PURE__ */ jsx3(
2760
+ Text2,
2761
+ {
2762
+ sx: {
2763
+ ...styles.locationTriggerText,
2764
+ ...selectedSubLocationIds.length > 0 ? { color: "#303030" } : {}
2765
+ },
2766
+ noOfLines: 1,
2767
+ children: selectedSubLocationIds.length > 0 ? filteredSubLocations.filter((s) => selectedSubLocationIds.includes(s.id)).map((s) => s.name).join(", ") : "Select sub-locations"
2768
+ }
2769
+ ),
2770
+ /* @__PURE__ */ jsx3(Box2, { color: "#5c5c5c", children: /* @__PURE__ */ jsx3(ChevronDownIcon, {}) })
2771
+ ] })
2772
+ }
2773
+ ) }),
2774
+ /* @__PURE__ */ jsx3(PopoverContent, { sx: styles.popoverContent, children: /* @__PURE__ */ jsx3(PopoverBody, { p: 0, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "2px", children: filteredSubLocations.map((sub) => /* @__PURE__ */ jsxs3(HStack2, { as: "label", sx: { ...styles.optionRow, cursor: "pointer" }, children: [
2775
+ /* @__PURE__ */ jsx3(
2776
+ Checkbox,
2777
+ {
2778
+ id: `sublocation-${sub.id}`,
2779
+ isChecked: selectedSubLocationIds.includes(sub.id),
2780
+ onChange: () => handleToggleSubLocation(sub.id)
2781
+ }
2782
+ ),
2783
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.optionLabel, children: sub.name })
2784
+ ] }, sub.id)) }) }) })
2785
+ ] })
2786
+ ] }),
2787
+ showTeams && /* @__PURE__ */ jsxs3(Stack2, { spacing: "8px", sx: styles.locationSection, children: [
2788
+ /* @__PURE__ */ jsx3(Stack2, { spacing: "4px", children: /* @__PURE__ */ jsxs3(HStack2, { sx: styles.locationLabelRow, children: [
2789
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.locationLabel, children: "Teams" }),
2790
+ selectedTeamIds.length > 0 && /* @__PURE__ */ jsx3(Badge, { sx: styles.locationBadge, children: selectedTeamIds.length })
2791
+ ] }) }),
2792
+ /* @__PURE__ */ jsxs3(Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
2793
+ /* @__PURE__ */ jsx3(PopoverTrigger, { children: /* @__PURE__ */ jsx3(
2794
+ Button2,
2795
+ {
2796
+ variant: "unstyled",
2797
+ isDisabled: !teams || teams.length === 0,
2798
+ sx: {
2799
+ ...styles.locationTrigger,
2800
+ ...!teams || teams.length === 0 ? { opacity: 0.5, cursor: "not-allowed" } : {}
2801
+ },
2802
+ children: /* @__PURE__ */ jsxs3(HStack2, { justify: "space-between", w: "full", children: [
2803
+ /* @__PURE__ */ jsx3(
2804
+ Text2,
2805
+ {
2806
+ sx: {
2807
+ ...styles.locationTriggerText,
2808
+ ...selectedTeamIds.length > 0 ? { color: "#303030" } : {}
2809
+ },
2810
+ noOfLines: 1,
2811
+ children: selectedTeamIds.length > 0 ? (teams != null ? teams : []).filter((t) => selectedTeamIds.includes(t.id)).map((t) => t.name).join(", ") : "Select teams"
2812
+ }
2813
+ ),
2814
+ /* @__PURE__ */ jsx3(Box2, { color: "#5c5c5c", children: /* @__PURE__ */ jsx3(ChevronDownIcon, {}) })
2815
+ ] })
2816
+ }
2817
+ ) }),
2818
+ /* @__PURE__ */ jsx3(PopoverContent, { sx: styles.popoverContent, children: /* @__PURE__ */ jsx3(PopoverBody, { p: 0, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "2px", children: (teams != null ? teams : []).map((team) => /* @__PURE__ */ jsxs3(HStack2, { as: "label", sx: { ...styles.optionRow, cursor: "pointer" }, children: [
2819
+ /* @__PURE__ */ jsx3(
2820
+ Checkbox,
2821
+ {
2822
+ id: `team-${team.id}`,
2823
+ isChecked: selectedTeamIds.includes(team.id),
2824
+ onChange: () => handleToggleTeam(team.id)
2825
+ }
2826
+ ),
2827
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.optionLabel, children: team.name })
2828
+ ] }, team.id)) }) }) })
2829
+ ] })
2830
+ ] }),
2831
+ scheduleWeekStartYmd ? /* @__PURE__ */ jsxs3(Stack2, { spacing: "8px", sx: styles.locationSection, children: [
2832
+ /* @__PURE__ */ jsx3(Text2, { sx: { ...styles.locationLabel, fontWeight: 600 }, children: "Generate schedule for:" }),
2833
+ /* @__PURE__ */ jsx3(HStack2, { spacing: 2, flexWrap: "wrap", children: weekDatesOrdered.map((ymd) => {
2834
+ const selected = selectedScheduleDates.includes(ymd);
2835
+ return /* @__PURE__ */ jsx3(
2836
+ Button2,
2837
+ {
2838
+ type: "button",
2839
+ variant: "unstyled",
2840
+ onClick: () => toggleScheduleDate(ymd),
2841
+ borderRadius: "full",
2842
+ minW: "40px",
2843
+ h: "40px",
2844
+ px: 0,
2845
+ fontSize: "sm",
2846
+ fontWeight: 600,
2847
+ borderWidth: selected ? "2px" : "1px",
2848
+ borderStyle: "solid",
2849
+ borderColor: selected ? "#5b44ff" : "#e2e8f0",
2850
+ bg: selected ? "#ede9fe" : "#ffffff",
2851
+ color: selected ? "#5b44ff" : "#303030",
2852
+ "aria-pressed": selected,
2853
+ "aria-label": `${weekdayTwoLetterFromYmd(ymd)} ${ymd}`,
2854
+ children: weekdayTwoLetterFromYmd(ymd)
2855
+ },
2856
+ ymd
2857
+ );
2858
+ }) })
2859
+ ] }) : null,
2358
2860
  /* @__PURE__ */ jsxs3(Stack2, { spacing: "16px", sx: styles.hardConstraintsSection, children: [
2359
2861
  /* @__PURE__ */ jsxs3(Stack2, { spacing: "4px", children: [
2360
2862
  /* @__PURE__ */ jsxs3(HStack2, { sx: styles.hardConstraintsLabelRow, children: [
2361
2863
  /* @__PURE__ */ jsx3(Text2, { sx: styles.hardConstraintsLabel, children: "Required rules" }),
2362
- selectedConstraintIds.length > 0 ? /* @__PURE__ */ jsx3(Badge, { sx: styles.hardConstraintsBadge, children: selectedConstraintIds.length }) : null
2864
+ selectedConstraintDisplayOptions.length > 0 ? /* @__PURE__ */ jsx3(Badge, { sx: styles.hardConstraintsBadge, children: selectedConstraintDisplayOptions.length }) : null
2363
2865
  ] }),
2364
2866
  /* @__PURE__ */ jsx3(Text2, { sx: styles.hardConstraintsSubtitle, children: "Assigned shifts will be required to meet the following rules:" })
2365
2867
  ] }),
@@ -2371,35 +2873,30 @@ function AutoSchedulerModal({
2371
2873
  {
2372
2874
  sx: {
2373
2875
  ...styles.hardConstraintsTriggerText,
2374
- ...selectedConstraintIds.length > 0 ? { color: "#303030" } : {}
2876
+ ...selectedConstraintDisplayOptions.length > 0 ? { color: "#303030" } : {}
2375
2877
  },
2376
2878
  noOfLines: 1,
2377
- children: selectedConstraintIds.length > 0 ? HARD_CONSTRAINT_OPTIONS.filter((o) => selectedConstraintIds.includes(o.id)).map((o) => o.name).join(", ") : "Select rules"
2879
+ children: selectedConstraintDisplayOptions.length > 0 ? selectedConstraintDisplayOptions.map((o) => o.name).join(", ") : "Select rules"
2378
2880
  }
2379
2881
  ),
2380
2882
  /* @__PURE__ */ jsx3(Box2, { color: "#5c5c5c", children: /* @__PURE__ */ jsx3(ChevronDownIcon, {}) })
2381
2883
  ] }) }) }),
2382
- /* @__PURE__ */ jsx3(PopoverContent, { sx: styles.hardConstraintsPopoverContent, children: /* @__PURE__ */ jsx3(PopoverBody, { p: 0, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "4px", children: HARD_CONSTRAINT_OPTIONS.map((option) => /* @__PURE__ */ jsxs3(
2383
- HStack2,
2384
- {
2385
- as: "label",
2386
- sx: { ...styles.hardConstraintsOptionRow, cursor: "pointer" },
2387
- children: [
2388
- /* @__PURE__ */ jsx3(
2389
- Checkbox,
2390
- {
2391
- id: `constraint-${option.id}`,
2392
- isChecked: selectedConstraintIds.includes(option.id),
2393
- onChange: () => handleToggleConstraint(option.id)
2394
- }
2395
- ),
2396
- /* @__PURE__ */ jsx3(Text2, { sx: styles.hardConstraintsOptionLabel, children: option.name })
2397
- ]
2398
- },
2399
- option.id
2400
- )) }) }) })
2884
+ /* @__PURE__ */ jsx3(PopoverContent, { sx: styles.hardConstraintsPopoverContent, children: /* @__PURE__ */ jsx3(PopoverBody, { p: 0, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "4px", children: effectiveHardConstraintOptions.map((option) => /* @__PURE__ */ jsxs3(HStack2, { as: "label", sx: { ...styles.hardConstraintsOptionRow, cursor: "pointer" }, children: [
2885
+ /* @__PURE__ */ jsx3(
2886
+ Checkbox,
2887
+ {
2888
+ id: `constraint-${option.id}`,
2889
+ isChecked: selectedConstraintIds.includes(option.id) || Boolean(option.readOnly && option.defaultSelected),
2890
+ isDisabled: option.readOnly,
2891
+ onChange: () => {
2892
+ if (!option.readOnly) handleToggleConstraint(option.id);
2893
+ }
2894
+ }
2895
+ ),
2896
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.hardConstraintsOptionLabel, children: option.name })
2897
+ ] }, option.id)) }) }) })
2401
2898
  ] }),
2402
- selectedConstraints.length > 0 ? /* @__PURE__ */ jsx3(Box2, { sx: styles.hardConstraintsInputsContainer, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "16px", children: selectedConstraints.map((option, index) => {
2899
+ selectedConstraints.filter((o) => o.defaultValue != null).length > 0 ? /* @__PURE__ */ jsx3(Box2, { sx: styles.hardConstraintsInputsContainer, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "16px", children: selectedConstraints.filter((o) => o.defaultValue != null).map((option, index) => {
2403
2900
  var _a;
2404
2901
  return /* @__PURE__ */ jsxs3(Stack2, { sx: styles.hardConstraintsInputItem, children: [
2405
2902
  /* @__PURE__ */ jsxs3(Text2, { sx: styles.hardConstraintsInputLabel, children: [
@@ -2417,13 +2914,13 @@ function AutoSchedulerModal({
2417
2914
  ...prev,
2418
2915
  [option.id]: event.target.value
2419
2916
  })),
2420
- min: option.id === "max_work_days_per_week" ? 1 : void 0,
2421
- max: option.id === "max_work_days_per_week" ? 6 : void 0,
2422
- step: option.id === "max_work_days_per_week" ? void 0 : "any",
2917
+ min: option.min,
2918
+ max: option.max,
2919
+ step: option.max != null ? void 0 : "any",
2423
2920
  sx: styles.hardConstraintsInputField
2424
2921
  }
2425
2922
  ),
2426
- /* @__PURE__ */ jsx3(InputRightAddon, { sx: styles.optimizationSuffix, children: option.id === "max_work_days_per_week" ? "days" : "hours" })
2923
+ /* @__PURE__ */ jsx3(InputRightAddon, { sx: styles.optimizationSuffix, children: option.suffix })
2427
2924
  ] })
2428
2925
  ] }, option.id);
2429
2926
  }) }) }) : null
@@ -2432,89 +2929,100 @@ function AutoSchedulerModal({
2432
2929
  /* @__PURE__ */ jsxs3(Stack2, { spacing: "4px", children: [
2433
2930
  /* @__PURE__ */ jsxs3(HStack2, { sx: styles.optimizationLabelRow, children: [
2434
2931
  /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationLabel, children: "Optimization" }),
2435
- selectedOptimizationIds.length > 0 ? /* @__PURE__ */ jsx3(Badge, { sx: styles.optimizationBadge, children: selectedOptimizationIds.length }) : null
2932
+ selectedOptimizationDisplayOptions.length > 0 ? /* @__PURE__ */ jsx3(Badge, { sx: styles.optimizationBadge, children: selectedOptimizationDisplayOptions.length }) : null
2436
2933
  ] }),
2437
2934
  /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationSubtitle, children: "Your schedule will be optimized based on the following priorities:" })
2438
2935
  ] }),
2439
- /* @__PURE__ */ jsxs3(Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
2936
+ /* @__PURE__ */ jsxs3(Popover, { placement: "top-start", strategy: "fixed", matchWidth: true, isLazy: true, gutter: 4, children: [
2440
2937
  /* @__PURE__ */ jsx3(PopoverTrigger, { children: /* @__PURE__ */ jsx3(Button2, { variant: "unstyled", sx: styles.optimizationTrigger, children: /* @__PURE__ */ jsxs3(HStack2, { justify: "space-between", w: "full", children: [
2441
2938
  /* @__PURE__ */ jsx3(
2442
2939
  Text2,
2443
2940
  {
2444
2941
  sx: {
2445
2942
  ...styles.optimizationTriggerText,
2446
- ...selectedOptimizationIds.length > 0 ? { color: "#303030" } : {}
2943
+ ...selectedOptimizationDisplayOptions.length > 0 ? { color: "#303030" } : {}
2447
2944
  },
2448
2945
  noOfLines: 1,
2449
- children: selectedOptimizationIds.length > 0 ? optimizationOptions.filter((o) => selectedOptimizationIds.includes(o.id)).map((o) => o.title).join(", ") : "Select optimization priorities"
2946
+ children: selectedOptimizationDisplayOptions.length > 0 ? selectedOptimizationDisplayOptions.map((o) => o.title).join(", ") : "Select optimization priorities"
2450
2947
  }
2451
2948
  ),
2452
2949
  /* @__PURE__ */ jsx3(Box2, { color: "#5c5c5c", children: /* @__PURE__ */ jsx3(ChevronDownIcon, {}) })
2453
2950
  ] }) }) }),
2454
- /* @__PURE__ */ jsx3(PopoverContent, { sx: styles.optimizationPopoverContent, children: /* @__PURE__ */ jsx3(PopoverBody, { p: 0, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "4px", children: OPTIMIZATION_OPTION_GROUPS.map((group) => /* @__PURE__ */ jsxs3(Box2, { children: [
2455
- /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationGroupHeader, children: group.name }),
2456
- /* @__PURE__ */ jsx3(Stack2, { spacing: "2px", children: group.options.map((option) => /* @__PURE__ */ jsxs3(
2457
- Stack2,
2458
- {
2459
- as: "label",
2460
- spacing: "2px",
2461
- sx: { ...styles.optimizationOptionRow, cursor: "pointer" },
2462
- children: [
2463
- /* @__PURE__ */ jsxs3(HStack2, { spacing: "8px", children: [
2464
- /* @__PURE__ */ jsx3(
2465
- Checkbox,
2466
- {
2467
- id: `optimization-${option.id}`,
2468
- isChecked: selectedOptimizationIds.includes(option.id),
2469
- isDisabled: !selectedOptimizationIds.includes(option.id) && selectedOptimizationIds.length >= 3,
2470
- onChange: () => handleToggleOptimization(option.id)
2471
- }
2472
- ),
2473
- /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationOptionLabel, children: option.title })
2474
- ] }),
2475
- option.subtitle ? /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationOptionHelp, children: option.subtitle }) : null
2476
- ]
2951
+ /* @__PURE__ */ jsx3(Portal, { appendToParentPortal: false, children: /* @__PURE__ */ jsx3(
2952
+ PopoverContent,
2953
+ {
2954
+ sx: {
2955
+ ...styles.optimizationPopoverContent,
2956
+ maxH: "min(560px, calc(100vh - 96px))",
2957
+ overflowY: "auto",
2958
+ zIndex: 1500,
2959
+ scrollbarWidth: "none",
2960
+ msOverflowStyle: "none",
2961
+ "&::-webkit-scrollbar": { display: "none" }
2477
2962
  },
2478
- option.id
2479
- )) })
2480
- ] }, group.id)) }) }) })
2963
+ children: /* @__PURE__ */ jsx3(PopoverBody, { p: 0, children: /* @__PURE__ */ jsx3(Stack2, { spacing: "4px", children: effectiveSoftConstraintGroups.map((group) => /* @__PURE__ */ jsxs3(Box2, { children: [
2964
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationGroupHeader, children: group.name }),
2965
+ /* @__PURE__ */ jsx3(Stack2, { spacing: "2px", children: group.options.map((option) => /* @__PURE__ */ jsxs3(Box2, { children: [
2966
+ /* @__PURE__ */ jsxs3(
2967
+ Stack2,
2968
+ {
2969
+ as: "label",
2970
+ spacing: "2px",
2971
+ sx: { ...styles.optimizationOptionRow, cursor: "pointer" },
2972
+ children: [
2973
+ /* @__PURE__ */ jsxs3(HStack2, { spacing: "8px", children: [
2974
+ /* @__PURE__ */ jsx3(
2975
+ Checkbox,
2976
+ {
2977
+ id: `optimization-${option.id}`,
2978
+ isChecked: selectedOptimizationIds.includes(option.id) || Boolean(option.readOnly && option.defaultSelected),
2979
+ isDisabled: option.readOnly || !selectedOptimizationIds.includes(option.id) && selectedOptimizationIds.length >= 3,
2980
+ onChange: () => {
2981
+ if (!option.readOnly) handleToggleOptimization(option.id);
2982
+ }
2983
+ }
2984
+ ),
2985
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationOptionLabel, children: option.title })
2986
+ ] }),
2987
+ option.subtitle ? /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationOptionHelp, children: option.subtitle }) : null
2988
+ ]
2989
+ }
2990
+ ),
2991
+ optimizationFooter && (option.id === "cost_to_sales_ratio" || option.id === "maximize_sales") ? /* @__PURE__ */ jsx3(Box2, { px: "12px", pt: "4px", pb: "6px", children: resolveOptimizationFooter(optimizationFooter, option.id) }) : null
2992
+ ] }, option.id)) })
2993
+ ] }, group.id)) }) })
2994
+ }
2995
+ ) })
2481
2996
  ] }),
2482
2997
  selectedOptimizationOptions.length >= 2 ? /* @__PURE__ */ jsxs3(Box2, { sx: styles.optimizationInputsContainer, children: [
2483
2998
  /* @__PURE__ */ jsx3(Stack2, { spacing: "16px", children: selectedOptimizationOptions.map((option, index) => {
2484
2999
  var _a;
2485
- return /* @__PURE__ */ jsx3(
2486
- Stack2,
2487
- {
2488
- sx: styles.optimizationInputItem,
2489
- children: /* @__PURE__ */ jsxs3(Stack2, { sx: styles.hardConstraintsInputItem, children: [
2490
- /* @__PURE__ */ jsxs3(Text2, { sx: styles.hardConstraintsInputLabel, children: [
2491
- index + 1,
2492
- ". ",
2493
- option.title
2494
- ] }),
2495
- /* @__PURE__ */ jsxs3(InputGroup, { children: [
2496
- /* @__PURE__ */ jsx3(
2497
- Input,
2498
- {
2499
- type: "text",
2500
- value: (_a = optimizationValues[`${option.id}_weightPoints`]) != null ? _a : "1",
2501
- onChange: (e) => {
2502
- const val = e.target.value;
2503
- if (val !== "" && (!/^\d+$/.test(val) || Number(val) < 1)) return;
2504
- setOptimizationValues((prev) => ({
2505
- ...prev,
2506
- [`${option.id}_weightPoints`]: val
2507
- }));
2508
- },
2509
- sx: styles.hardConstraintsInputField
2510
- }
2511
- ),
2512
- /* @__PURE__ */ jsx3(InputRightAddon, { sx: styles.optimizationSuffix, children: "points" })
2513
- ] })
2514
- ] })
2515
- },
2516
- option.id
2517
- );
3000
+ return /* @__PURE__ */ jsx3(Stack2, { sx: styles.optimizationInputItem, children: /* @__PURE__ */ jsxs3(Stack2, { sx: styles.hardConstraintsInputItem, children: [
3001
+ /* @__PURE__ */ jsxs3(Text2, { sx: styles.hardConstraintsInputLabel, children: [
3002
+ index + 1,
3003
+ ". ",
3004
+ option.title
3005
+ ] }),
3006
+ /* @__PURE__ */ jsxs3(InputGroup, { children: [
3007
+ /* @__PURE__ */ jsx3(
3008
+ Input,
3009
+ {
3010
+ type: "text",
3011
+ value: (_a = optimizationValues[`${option.id}_weightPoints`]) != null ? _a : "1",
3012
+ onChange: (e) => {
3013
+ const val = e.target.value;
3014
+ if (val !== "" && (!/^\d+$/.test(val) || Number(val) < 1)) return;
3015
+ setOptimizationValues((prev) => ({
3016
+ ...prev,
3017
+ [`${option.id}_weightPoints`]: val
3018
+ }));
3019
+ },
3020
+ sx: styles.hardConstraintsInputField
3021
+ }
3022
+ ),
3023
+ /* @__PURE__ */ jsx3(InputRightAddon, { sx: styles.optimizationSuffix, children: "points" })
3024
+ ] })
3025
+ ] }) }, option.id);
2518
3026
  }) }),
2519
3027
  /* @__PURE__ */ jsx3(Divider, { sx: { ...styles.optimizationDivider, my: "16px" } }),
2520
3028
  /* @__PURE__ */ jsx3(Text2, { sx: styles.optimizationSummary, children: allocationSummary })
@@ -2523,7 +3031,9 @@ function AutoSchedulerModal({
2523
3031
  ] });
2524
3032
  const stepsContent = /* @__PURE__ */ jsxs3(Stack2, { spacing: "16px", sx: styles.stepsContainer, children: [
2525
3033
  showFailed ? violationPanel : null,
2526
- /* @__PURE__ */ jsx3(Stack2, { spacing: "14px", sx: styles.stepsList, children: SCHEDULE_STEPS.map((step, index) => {
3034
+ /* @__PURE__ */ jsx3(Stack2, { spacing: "14px", sx: styles.stepsList, children: SCHEDULE_STEPS.map((rawStep, index) => {
3035
+ const isQueueWait = index === 1 && solveStatus === "QUEUED";
3036
+ const step = isQueueWait ? { title: "Waiting in queue\u2026", subtitle: void 0 } : rawStep;
2527
3037
  const status = stepStatusForIndex(index);
2528
3038
  const isFailedStep = showFailed && index === activeStepIndex;
2529
3039
  const iconStyles = {
@@ -2541,26 +3051,26 @@ function AutoSchedulerModal({
2541
3051
  ...isFailedStep ? styles.stepTitleFailed : null
2542
3052
  };
2543
3053
  return /* @__PURE__ */ jsxs3(HStack2, { sx: styles.stepRow, children: [
2544
- /* @__PURE__ */ jsx3(Box2, { sx: iconStyles, children: isFailedStep ? /* @__PURE__ */ jsx3(FailedIcon, { width: 12, height: 12 }) : status === "active" ? /* @__PURE__ */ jsx3(Spinner2, { size: "sm", thickness: "2px", speed: "0.7s" }) : status === "done" ? /* @__PURE__ */ jsx3(CheckIcon, { width: 12, height: 12 }) : null }),
3054
+ /* @__PURE__ */ jsx3(Box2, { sx: iconStyles, children: isFailedStep ? /* @__PURE__ */ jsx3(FailedIcon, { width: 12, height: 12 }) : status === "active" ? /* @__PURE__ */ jsx3(Spinner2, { size: "sm", thickness: "2px", speed: "0.7s" }) : status === "done" ? /* @__PURE__ */ jsx3(CheckIcon, { width: 10, height: 10 }) : null }),
2545
3055
  /* @__PURE__ */ jsxs3(Box2, { children: [
2546
3056
  /* @__PURE__ */ jsx3(Text2, { sx: titleStyles, children: step.title }),
2547
3057
  status === "active" && step.subtitle ? /* @__PURE__ */ jsx3(Text2, { sx: styles.stepSubtitle, children: step.subtitle }) : null
2548
3058
  ] })
2549
- ] }, step.title);
3059
+ ] }, rawStep.title);
2550
3060
  }) })
2551
3061
  ] });
2552
- const modalFooter = showConfiguration ? /* @__PURE__ */ jsxs3(ModalFooter, { sx: styles.footer, children: [
3062
+ const footerButtons = showConfiguration ? /* @__PURE__ */ jsxs3(Fragment, { children: [
2553
3063
  /* @__PURE__ */ jsx3(Button2, { variant: "outline", onClick: onClose, sx: styles.cancelButton, children: cancelLabel }),
2554
3064
  /* @__PURE__ */ jsx3(
2555
3065
  Button2,
2556
3066
  {
2557
3067
  onClick: handleCreateSchedule,
2558
- isDisabled: selectedCount === 0 || openShifts.length === 0 || hasEmptyInputs,
3068
+ isDisabled: selectedCount === 0 || !onGenerate && openShifts.length === 0 || hasEmptyInputs || Boolean(scheduleWeekStartYmd) && selectedScheduleDates.length === 0,
2559
3069
  sx: styles.primaryButton,
2560
3070
  children: primaryActionLabel
2561
3071
  }
2562
3072
  )
2563
- ] }) : showFailed ? /* @__PURE__ */ jsxs3(ModalFooter, { sx: styles.failedFooter, children: [
3073
+ ] }) : showFailed ? /* @__PURE__ */ jsxs3(Fragment, { children: [
2564
3074
  /* @__PURE__ */ jsx3(
2565
3075
  Button2,
2566
3076
  {
@@ -2569,10 +3079,11 @@ function AutoSchedulerModal({
2569
3079
  if (lastFailure) {
2570
3080
  onFixManually == null ? void 0 : onFixManually(lastFailure, {
2571
3081
  selectedLocationIds,
2572
- selectedConstraintIds,
3082
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2573
3083
  constraintValues,
2574
- selectedOptimizationIds,
2575
- optimizationValues
3084
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
3085
+ optimizationValues,
3086
+ selectedBuiltinIds: defaultSelectedBuiltinIds
2576
3087
  });
2577
3088
  }
2578
3089
  onClose();
@@ -2583,31 +3094,75 @@ function AutoSchedulerModal({
2583
3094
  ),
2584
3095
  /* @__PURE__ */ jsx3(Button2, { onClick: handleAdjustConstraints, sx: styles.failedPrimaryButton, children: "Adjust constraints" })
2585
3096
  ] }) : null;
2586
- const modalContent = /* @__PURE__ */ jsxs3(Modal, { isOpen, onClose, isCentered: true, motionPreset: "scale", closeOnOverlayClick: false, closeOnEsc: false, children: [
2587
- /* @__PURE__ */ jsx3(ModalOverlay, { sx: styles.overlay }),
2588
- /* @__PURE__ */ jsxs3(ModalContent, { sx: styles.content, children: [
2589
- /* @__PURE__ */ jsx3(ModalHeader, { sx: styles.header, children: /* @__PURE__ */ jsxs3(HStack2, { justify: "space-between", w: "full", children: [
2590
- /* @__PURE__ */ jsx3(Text2, { sx: styles.title, children: modalTitle }),
2591
- /* @__PURE__ */ jsx3(
2592
- IconButton2,
2593
- {
2594
- "aria-label": "Close modal",
2595
- variant: "ghost",
2596
- size: "sm",
2597
- icon: /* @__PURE__ */ jsx3(CloseIcon, {}),
2598
- onClick: onClose,
2599
- sx: styles.closeButton
2600
- }
2601
- )
2602
- ] }) }),
2603
- /* @__PURE__ */ jsx3(ModalBody, { sx: showConfiguration ? styles.body : { ...styles.body, ...styles.creatingBody }, children: showConfiguration ? configurationContent : stepsContent }),
2604
- modalFooter
2605
- ] })
2606
- ] });
3097
+ const bodyStyles = showConfiguration ? styles.body : { ...styles.body, ...styles.creatingBody };
3098
+ const bodyContent = showConfiguration ? configurationContent : stepsContent;
3099
+ const modalContent = /* @__PURE__ */ jsxs3(
3100
+ Modal,
3101
+ {
3102
+ isOpen,
3103
+ onClose,
3104
+ isCentered: true,
3105
+ motionPreset: "scale",
3106
+ closeOnOverlayClick: false,
3107
+ closeOnEsc: false,
3108
+ trapFocus: false,
3109
+ children: [
3110
+ /* @__PURE__ */ jsx3(ModalOverlay, { sx: styles.overlay }),
3111
+ /* @__PURE__ */ jsxs3(ModalContent, { sx: styles.content, children: [
3112
+ /* @__PURE__ */ jsx3(ModalHeader, { sx: styles.header, children: /* @__PURE__ */ jsxs3(HStack2, { justify: "space-between", w: "full", children: [
3113
+ /* @__PURE__ */ jsx3(Text2, { sx: styles.title, children: modalTitle }),
3114
+ /* @__PURE__ */ jsx3(
3115
+ IconButton2,
3116
+ {
3117
+ "aria-label": "Close modal",
3118
+ variant: "ghost",
3119
+ size: "sm",
3120
+ icon: /* @__PURE__ */ jsx3(CloseIcon, {}),
3121
+ onClick: onClose,
3122
+ sx: styles.closeButton
3123
+ }
3124
+ )
3125
+ ] }) }),
3126
+ /* @__PURE__ */ jsx3(ModalBody, { sx: bodyStyles, children: bodyContent }),
3127
+ footerButtons && /* @__PURE__ */ jsx3(ModalFooter, { sx: showFailed ? styles.failedFooter : styles.footer, children: footerButtons })
3128
+ ] })
3129
+ ]
3130
+ }
3131
+ );
3132
+ const drawerFooterNode = footerButtons ? /* @__PURE__ */ jsx3(
3133
+ HStack2,
3134
+ {
3135
+ align: "center",
3136
+ justify: "flex-end",
3137
+ px: 6,
3138
+ h: "68px",
3139
+ bg: "gray.50",
3140
+ borderTop: "1px solid",
3141
+ borderColor: "gray.200",
3142
+ gap: 2,
3143
+ flexShrink: 0,
3144
+ w: "full",
3145
+ children: footerButtons
3146
+ }
3147
+ ) : void 0;
3148
+ const drawerContent = /* @__PURE__ */ jsx3(
3149
+ EtDrawer,
3150
+ {
3151
+ isOpen,
3152
+ onClose,
3153
+ title: modalTitle,
3154
+ closeOnOverlayClick: false,
3155
+ maxW: DRAWER_WIDTH_MAP[drawerSize],
3156
+ bodySx: bodyStyles,
3157
+ footer: drawerFooterNode,
3158
+ children: bodyContent
3159
+ }
3160
+ );
3161
+ const content = displayMode === "drawer" ? drawerContent : modalContent;
2607
3162
  if (theme || cssVarsRoot) {
2608
- return /* @__PURE__ */ jsx3(ChakraProvider, { theme: mergedTheme, cssVarsRoot, children: modalContent });
3163
+ return /* @__PURE__ */ jsx3(ChakraProvider, { theme: mergedTheme, cssVarsRoot, children: content });
2609
3164
  }
2610
- return modalContent;
3165
+ return content;
2611
3166
  }
2612
3167
 
2613
3168
  // src/AutoSchedulerModalWithProvider.tsx
@@ -2683,11 +3238,17 @@ var RequestError = class extends Error {
2683
3238
  export {
2684
3239
  AutoSchedulerModal,
2685
3240
  AutoSchedulerModalWithProvider,
3241
+ InfeasibleScheduleError,
3242
+ MAXIMIZE_CONVERSION_RATE_RULE_ID,
2686
3243
  RequestError,
3244
+ TRAFFIC_FORECAST_INCENTIVE_RULE_ID,
2687
3245
  ViolatedConstraintsPanel,
2688
3246
  ViolatedConstraintsPanelWithProvider,
2689
3247
  buildRulesPayload,
2690
3248
  buildSchedulePayload,
2691
- getFriendlyRuleName
3249
+ defaultSelectedScheduleDates,
3250
+ getFriendlyRuleName,
3251
+ getSequentialWeekDatesFromWeekStart,
3252
+ weekdayTwoLetterFromYmd
2692
3253
  };
2693
3254
  //# sourceMappingURL=index.js.map