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

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.cjs CHANGED
@@ -29,22 +29,38 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
30
 
31
31
  // src/index.ts
32
- var src_exports = {};
33
- __export(src_exports, {
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
34
  AutoSchedulerModal: () => AutoSchedulerModal,
35
35
  AutoSchedulerModalWithProvider: () => AutoSchedulerModalWithProvider,
36
+ InfeasibleScheduleError: () => InfeasibleScheduleError,
37
+ MAXIMIZE_CONVERSION_RATE_RULE_ID: () => MAXIMIZE_CONVERSION_RATE_RULE_ID,
36
38
  RequestError: () => RequestError,
39
+ TRAFFIC_FORECAST_INCENTIVE_RULE_ID: () => TRAFFIC_FORECAST_INCENTIVE_RULE_ID,
37
40
  ViolatedConstraintsPanel: () => ViolatedConstraintsPanel,
38
41
  ViolatedConstraintsPanelWithProvider: () => ViolatedConstraintsPanelWithProvider,
39
42
  buildRulesPayload: () => buildRulesPayload,
40
43
  buildSchedulePayload: () => buildSchedulePayload,
41
- getFriendlyRuleName: () => getFriendlyRuleName
44
+ defaultSelectedScheduleDates: () => defaultSelectedScheduleDates,
45
+ getFriendlyRuleName: () => getFriendlyRuleName,
46
+ getSequentialWeekDatesFromWeekStart: () => getSequentialWeekDatesFromWeekStart,
47
+ weekdayTwoLetterFromYmd: () => weekdayTwoLetterFromYmd
42
48
  });
43
- module.exports = __toCommonJS(src_exports);
49
+ module.exports = __toCommonJS(index_exports);
44
50
 
45
51
  // src/AutoSchedulerModal.tsx
46
52
  var import_react3 = require("react");
47
53
  var import_react4 = require("@chakra-ui/react");
54
+ var import_web_ui = require("@easyteam/web-ui");
55
+
56
+ // src/types.ts
57
+ var InfeasibleScheduleError = class extends Error {
58
+ constructor(failure) {
59
+ super("Schedule could not be created due to constraint violations");
60
+ this.name = "InfeasibleScheduleError";
61
+ this.failure = failure;
62
+ }
63
+ };
48
64
 
49
65
  // src/icons/AutoSchedulerIcons.tsx
50
66
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -181,19 +197,15 @@ function CheckIcon(props) {
181
197
  "svg",
182
198
  {
183
199
  "aria-hidden": "true",
184
- viewBox: "0 0 20 20",
185
- fill: "none",
186
- width: "20",
187
- height: "20",
200
+ viewBox: "0 0 24 24",
201
+ width: "24",
202
+ height: "24",
188
203
  ...props,
189
204
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
190
205
  "path",
191
206
  {
192
- d: "M5.5 10.5l3 3 6-6",
193
- stroke: "currentColor",
194
- strokeWidth: "1.6",
195
- strokeLinecap: "round",
196
- strokeLinejoin: "round"
207
+ fill: "currentColor",
208
+ d: "M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"
197
209
  }
198
210
  )
199
211
  }
@@ -715,7 +727,7 @@ var defaultStyles = {
715
727
  },
716
728
  stepIconDone: {
717
729
  border: "none",
718
- bg: "#008060",
730
+ bg: "#5338ff",
719
731
  color: "white",
720
732
  borderRadius: "10px"
721
733
  },
@@ -1239,7 +1251,7 @@ var buildStyles = (defaultStyles2, overrides) => ({
1239
1251
  }
1240
1252
  });
1241
1253
 
1242
- // src/buildSchedulePayload.ts
1254
+ // src/utils/ruleIdMapping.ts
1243
1255
  var UI_TO_BACKEND_RULE_ID = {
1244
1256
  max_weekly_hours: "max-weekly-hours",
1245
1257
  max_daily_hours: "max-daily-hours",
@@ -1257,6 +1269,11 @@ var BACKEND_TO_UI_RULE_ID = Object.fromEntries(
1257
1269
  function toBackendRuleId(uiOptionId) {
1258
1270
  return UI_TO_BACKEND_RULE_ID[uiOptionId];
1259
1271
  }
1272
+
1273
+ // src/buildSchedulePayload.ts
1274
+ var MAXIMIZE_CONVERSION_RATE_RULE_ID = "maximize-conversion-rate";
1275
+ var TRAFFIC_FORECAST_INCENTIVE_RULE_ID = "traffic-forecast-incentive";
1276
+ var SALES_OPTIMIZATION_UI_IDS = /* @__PURE__ */ new Set(["maximize_sales", "cost_to_sales_ratio"]);
1260
1277
  function buildParametersForRule(backendRuleId, constraintValues, _optimizationValues) {
1261
1278
  var _a, _b, _c, _d;
1262
1279
  const params = {};
@@ -1327,11 +1344,24 @@ function hasAnyParameterOverrides(selectedConstraintIds, constraintValues, selec
1327
1344
  }
1328
1345
  return false;
1329
1346
  }
1330
- function buildRulesPayload(selectedConstraintIds, constraintValues, selectedOptimizationIds, optimizationValues) {
1347
+ function appendSiblingRuleId(ruleIds, siblingId) {
1348
+ if (!ruleIds.includes(siblingId)) {
1349
+ ruleIds.push(siblingId);
1350
+ }
1351
+ }
1352
+ function buildRulesPayload(selectedConstraintIds, constraintValues, selectedOptimizationIds, optimizationValues, siblings = {}) {
1331
1353
  const allSelectedRuleIds = [
1332
1354
  ...selectedConstraintIds.map(toBackendRuleId),
1333
1355
  ...selectedOptimizationIds.map(toBackendRuleId)
1334
1356
  ].filter((id) => Boolean(id));
1357
+ const salesOptSelected = selectedOptimizationIds.some((id) => SALES_OPTIMIZATION_UI_IDS.has(id));
1358
+ const maximizeSalesSelected = selectedOptimizationIds.includes("maximize_sales");
1359
+ if (siblings.includeConversionRate && salesOptSelected) {
1360
+ appendSiblingRuleId(allSelectedRuleIds, MAXIMIZE_CONVERSION_RATE_RULE_ID);
1361
+ }
1362
+ if (siblings.includeTrafficIncentive && maximizeSalesSelected) {
1363
+ appendSiblingRuleId(allSelectedRuleIds, TRAFFIC_FORECAST_INCENTIVE_RULE_ID);
1364
+ }
1335
1365
  if (allSelectedRuleIds.length === 0) {
1336
1366
  return {};
1337
1367
  }
@@ -1400,9 +1430,25 @@ function toApiLocation(location) {
1400
1430
  id: String(location.id),
1401
1431
  timezone: (_a = location.timezone) != null ? _a : void 0,
1402
1432
  jurisdiction: (_b = location.jurisdiction) != null ? _b : void 0,
1403
- tags: "tags" in location ? location.tags : void 0
1433
+ tags: "tags" in location ? location.tags : void 0,
1434
+ properties: location.properties ? location.properties : void 0
1404
1435
  };
1405
1436
  }
1437
+ function employeeHasConversionRate(employee) {
1438
+ var _a;
1439
+ const value = (_a = employee.properties) == null ? void 0 : _a.conversionRate;
1440
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
1441
+ }
1442
+ function employeeTouchesSelectedLocations(employee, selectedLocationIds) {
1443
+ var _a;
1444
+ const ids = (_a = employee.locationIds) != null ? _a : [];
1445
+ return ids.some((id) => selectedLocationIds.includes(String(id)));
1446
+ }
1447
+ function hasConversionRateInSelectedLocations(employees, selectedLocationIds) {
1448
+ return employees.some(
1449
+ (employee) => employeeTouchesSelectedLocations(employee, selectedLocationIds) && employeeHasConversionRate(employee)
1450
+ );
1451
+ }
1406
1452
  function buildSchedulePayload(input) {
1407
1453
  const {
1408
1454
  employees,
@@ -1413,7 +1459,11 @@ function buildSchedulePayload(input) {
1413
1459
  selectedConstraintIds,
1414
1460
  constraintValues,
1415
1461
  selectedOptimizationIds,
1416
- optimizationValues
1462
+ optimizationValues,
1463
+ trafficForecast,
1464
+ constraintDefinitionIds,
1465
+ constraintDefinitions,
1466
+ organizationId
1417
1467
  } = input;
1418
1468
  const locationsMap = /* @__PURE__ */ new Map();
1419
1469
  for (const j of jurisdictions) {
@@ -1422,19 +1472,45 @@ function buildSchedulePayload(input) {
1422
1472
  }
1423
1473
  }
1424
1474
  const selectedLocations = selectedLocationIds.map((id) => locationsMap.get(id)).filter((loc) => Boolean(loc));
1475
+ const scopedOpenShifts = openShifts.filter(
1476
+ (shift) => {
1477
+ var _a;
1478
+ return selectedLocationIds.includes(String((_a = shift.location) == null ? void 0 : _a.id));
1479
+ }
1480
+ );
1481
+ const includeConversionRate = hasConversionRateInSelectedLocations(
1482
+ employees,
1483
+ selectedLocationIds
1484
+ );
1485
+ const scopedTrafficForecast = (trafficForecast != null ? trafficForecast : []).filter(
1486
+ (entry) => selectedLocationIds.includes(String(entry.locationId))
1487
+ );
1488
+ const includeTrafficIncentive = scopedTrafficForecast.length > 0;
1425
1489
  const rules = buildRulesPayload(
1426
1490
  selectedConstraintIds,
1427
1491
  constraintValues,
1428
1492
  selectedOptimizationIds,
1429
- optimizationValues
1493
+ optimizationValues,
1494
+ { includeConversionRate, includeTrafficIncentive }
1430
1495
  );
1431
1496
  const includeEmployeePreferences = selectedOptimizationIds.includes("staff_preferences");
1497
+ const hasIds = Boolean(constraintDefinitionIds && constraintDefinitionIds.length > 0);
1498
+ const hasDefs = Boolean(constraintDefinitions && constraintDefinitions.length > 0);
1499
+ const hasRules = hasIds || hasDefs;
1500
+ if (hasIds && !organizationId) {
1501
+ throw new Error("organizationId is required when constraintDefinitionIds are provided");
1502
+ }
1503
+ const trafficRuleActive = includeTrafficIncentive && selectedOptimizationIds.includes("maximize_sales");
1432
1504
  return {
1433
1505
  employees: employees.map(toApiEmployee),
1434
- shifts: openShifts.map((shift) => toApiShift(shift, includeEmployeePreferences)),
1506
+ shifts: scopedOpenShifts.map((shift) => toApiShift(shift, includeEmployeePreferences)),
1435
1507
  locations: selectedLocations.map(toApiLocation),
1436
1508
  ...timeOffs && timeOffs.length > 0 ? { timeOffs } : {},
1437
- ...rules
1509
+ ...rules,
1510
+ ...trafficRuleActive ? { trafficForecast: scopedTrafficForecast } : {},
1511
+ ...hasIds ? { constraintDefinitionIds } : {},
1512
+ ...hasDefs ? { constraintDefinitions } : {},
1513
+ ...hasRules && organizationId ? { organizationId } : {}
1438
1514
  };
1439
1515
  }
1440
1516
 
@@ -1457,7 +1533,7 @@ async function submitSolveJob(baseURL, payload, options) {
1457
1533
  headers.Authorization = options.authorization;
1458
1534
  }
1459
1535
  const response = await axiosInstance.post(
1460
- `${cleanBaseUrl}/api/schedule/solve-async`,
1536
+ withHorizonQuery(`${cleanBaseUrl}/api/schedule/solve-async`, options),
1461
1537
  payload,
1462
1538
  { headers: Object.keys(headers).length > 0 ? headers : void 0 }
1463
1539
  );
@@ -1499,8 +1575,34 @@ async function getJobSolution(baseURL, jobId, options) {
1499
1575
  );
1500
1576
  return response.data;
1501
1577
  }
1578
+ function withHorizonQuery(url, options) {
1579
+ if (!(options == null ? void 0 : options.horizonStartDate) || !(options == null ? void 0 : options.horizonEndDate)) {
1580
+ return url;
1581
+ }
1582
+ const params = new URLSearchParams({
1583
+ dateRangeStart: options.horizonStartDate,
1584
+ dateRangeEnd: options.horizonEndDate
1585
+ });
1586
+ return `${url}?${params.toString()}`;
1587
+ }
1502
1588
 
1503
1589
  // src/utils/violationMessages.ts
1590
+ var INTERNAL_VIOLATION_PATTERNS = [
1591
+ /\w+Rule\{/,
1592
+ /\[I@/,
1593
+ /Employee\{id=/,
1594
+ /Accumulator violation \(Rule:/,
1595
+ /^Schedule has \d+ hard, \d+ medium, and \d+ soft constraint violation/,
1596
+ /ConstraintMatch\{/,
1597
+ /MatchAnalysis\{/,
1598
+ /Indictment\{/,
1599
+ /Hard constraint violation \(count:/
1600
+ ];
1601
+ function isInternalViolationText(text) {
1602
+ const trimmed = text == null ? void 0 : text.trim();
1603
+ if (!trimmed) return false;
1604
+ return INTERNAL_VIOLATION_PATTERNS.some((pattern) => pattern.test(trimmed));
1605
+ }
1504
1606
  var SYSTEM_CONSTRAINT_MESSAGES = {
1505
1607
  "system-unassigned-Hard": {
1506
1608
  title: "Not enough eligible staff to cover all shifts",
@@ -1520,15 +1622,19 @@ var SYSTEM_CONSTRAINT_MESSAGES = {
1520
1622
  },
1521
1623
  "system-employee-unavailable-time-Hard": {
1522
1624
  title: "Unavailable time",
1523
- detail: "Unavailable time"
1625
+ detail: "Some open shifts fall outside the available time windows for the remaining eligible team members."
1524
1626
  },
1525
1627
  "system-employee-available-time-Hard": {
1526
1628
  title: "Outside available time",
1527
- detail: "Outside available time"
1629
+ detail: "Some open shifts fall outside the available time windows for the remaining eligible team members."
1528
1630
  },
1529
1631
  "system-employee-time-off-Hard": {
1530
1632
  title: "Time off prevents assignment",
1531
1633
  detail: "Some open shifts overlap with approved time off for the remaining eligible team members."
1634
+ },
1635
+ "system-no-overlapping-shifts-Hard": {
1636
+ title: "Overlapping shifts",
1637
+ detail: "Some team members would be assigned to overlapping shifts if all open shifts are assigned."
1532
1638
  }
1533
1639
  };
1534
1640
  var RULE_ID_MESSAGES = {
@@ -1544,119 +1650,259 @@ var RULE_ID_MESSAGES = {
1544
1650
  title: "Minimum rest between shifts",
1545
1651
  detail: "Some team members would not receive the required rest time if all open shifts are assigned."
1546
1652
  },
1547
- "min-days-off-per-week": {
1653
+ "max-work-days-per-week": {
1548
1654
  title: "Maximum work days per week exceeded",
1549
1655
  detail: "Some team members would exceed the allowed work days this week if all open shifts are assigned."
1550
1656
  },
1657
+ "min-days-off-per-week": {
1658
+ title: "Minimum days off not met",
1659
+ detail: "Some team members would not receive the required days off this week if all open shifts are assigned."
1660
+ },
1551
1661
  "fair-hour-distribution": {
1552
1662
  title: "Work distribution not balanced",
1553
- detail: "Work distribution not balanced"
1663
+ detail: "Some team members would receive an uneven share of shifts if all open shifts are assigned."
1554
1664
  },
1555
1665
  "overtime-balance": {
1556
1666
  title: "Overtime balance rule violated",
1557
- detail: "Overtime balance rule violated"
1667
+ detail: "Some team members would exceed overtime limits if all open shifts are assigned."
1668
+ },
1669
+ "labor-cost-optimization": {
1670
+ title: "Labor cost optimization conflict",
1671
+ detail: "Some open shifts cannot be assigned while meeting labor cost optimization priorities."
1672
+ },
1673
+ "maximize-sales-performance": {
1674
+ title: "Sales optimization conflict",
1675
+ detail: "Some open shifts cannot be assigned while meeting sales optimization priorities."
1676
+ },
1677
+ "staff-open-shift-preferences": {
1678
+ title: "Staff preferences conflict",
1679
+ detail: "Some open shifts cannot be assigned while respecting all staff shift preferences."
1680
+ },
1681
+ "split-shift-penalty": {
1682
+ title: "Split shift rule violated",
1683
+ detail: "Some team members would receive split shifts that violate scheduling preferences."
1684
+ },
1685
+ "shift-continuity-penalty": {
1686
+ title: "Shift continuity rule violated",
1687
+ detail: "Some team members would receive shift patterns that violate continuity preferences."
1558
1688
  },
1559
1689
  "manager-cashier-ratio": {
1560
1690
  title: "Staffing ratio violated",
1561
- detail: "Staffing ratio violated"
1691
+ detail: "Some open shifts cannot be assigned while meeting required staffing ratios."
1562
1692
  },
1563
1693
  "min-staffing": {
1564
1694
  title: "Minimum staffing not met",
1565
- detail: "Minimum staffing not met"
1695
+ detail: "Some time periods would not meet minimum staffing requirements if all open shifts are assigned."
1566
1696
  },
1567
1697
  "shift-duration-limits": {
1568
1698
  title: "Shift duration outside allowed range",
1569
- detail: "Shift duration outside allowed range"
1699
+ detail: "Some open shifts fall outside the allowed shift duration range."
1570
1700
  },
1571
1701
  "max-consecutive-days": {
1572
1702
  title: "Shift sequence rule violated",
1573
- detail: "Shift sequence rule violated"
1703
+ detail: "Some team members would violate consecutive shift rules if all open shifts are assigned."
1574
1704
  },
1575
1705
  "some-transactional-rule": {
1576
1706
  title: "Policy rule violated",
1577
- detail: "Policy rule violated"
1707
+ detail: "Some open shifts cannot be assigned while meeting all policy requirements."
1708
+ }
1709
+ };
1710
+ var CONSTRAINT_NAME_MESSAGES = {
1711
+ "AccumulatorDays-Hard": {
1712
+ title: "Maximum work days per week exceeded",
1713
+ detail: "Some team members would exceed the allowed work days this week if all open shifts are assigned."
1714
+ },
1715
+ "Accumulator-Hard": {
1716
+ title: "Hours limit exceeded",
1717
+ detail: "Some team members would exceed hour limits if all open shifts are assigned."
1718
+ },
1719
+ "Spacer-Hard": {
1720
+ title: "Rest period not met",
1721
+ detail: "Some team members would not receive the required rest time if all open shifts are assigned."
1722
+ },
1723
+ "SpacerPeriodic-Hard": {
1724
+ title: "Required rest period not met",
1725
+ detail: "Some team members would not receive the required rest period if all open shifts are assigned."
1726
+ },
1727
+ "Composition-Hard": {
1728
+ title: "Staffing ratio violated",
1729
+ detail: "Some open shifts cannot be assigned while meeting required staffing ratios."
1730
+ },
1731
+ "Transactional-Hard": {
1732
+ title: "Policy rule violated",
1733
+ detail: "Some open shifts cannot be assigned while meeting all policy requirements."
1734
+ },
1735
+ "Balancer-Hard": {
1736
+ title: "Work distribution not balanced",
1737
+ detail: "Some team members would receive an uneven share of shifts if all open shifts are assigned."
1738
+ },
1739
+ "Coverage-Hard": {
1740
+ title: "Minimum staffing not met",
1741
+ detail: "Some time periods would not meet minimum staffing requirements if all open shifts are assigned."
1742
+ },
1743
+ "Sizing-Hard": {
1744
+ title: "Shift duration outside allowed range",
1745
+ detail: "Some open shifts fall outside the allowed shift duration range."
1746
+ },
1747
+ "Sequencer-Hard": {
1748
+ title: "Shift sequence rule violated",
1749
+ detail: "Some team members would violate consecutive shift rules if all open shifts are assigned."
1750
+ },
1751
+ "OptimizerMinimize-Hard": {
1752
+ title: "Optimization rule violated",
1753
+ detail: "Some open shifts cannot be assigned while meeting all optimization priorities."
1754
+ },
1755
+ "OptimizerMaximize-Hard": {
1756
+ title: "Optimization rule violated",
1757
+ detail: "Some open shifts cannot be assigned while meeting all optimization priorities."
1578
1758
  }
1579
1759
  };
1580
1760
  var ARCHETYPE_FALLBACK_MESSAGES = {
1581
- "Accumulator violation (HARD)": {
1761
+ AccumulatorDays: {
1762
+ title: "Maximum work days per week exceeded",
1763
+ detail: "Some team members would exceed the allowed work days this week if all open shifts are assigned."
1764
+ },
1765
+ Accumulator: {
1582
1766
  title: "Hours limit exceeded",
1583
- detail: "Hours limit exceeded"
1767
+ detail: "Some team members would exceed hour limits if all open shifts are assigned."
1584
1768
  },
1585
- "Spacer violation (HARD)": {
1769
+ Spacer: {
1586
1770
  title: "Rest period not met",
1587
- detail: "Rest period not met"
1771
+ detail: "Some team members would not receive the required rest time if all open shifts are assigned."
1588
1772
  },
1589
- "Spacer periodic violation (HARD)": {
1773
+ SpacerPeriodic: {
1590
1774
  title: "Required rest period not met",
1591
- detail: "Required rest period not met"
1775
+ detail: "Some team members would not receive the required rest period if all open shifts are assigned."
1592
1776
  },
1593
- "Composition violation (HARD)": {
1777
+ Composition: {
1594
1778
  title: "Staffing ratio violated",
1595
- detail: "Staffing ratio violated"
1779
+ detail: "Some open shifts cannot be assigned while meeting required staffing ratios."
1596
1780
  },
1597
- "Transactional violation (HARD)": {
1781
+ Transactional: {
1598
1782
  title: "Policy rule violated",
1599
- detail: "Policy rule violated"
1783
+ detail: "Some open shifts cannot be assigned while meeting all policy requirements."
1600
1784
  },
1601
- "Balancer violation (HARD)": {
1785
+ Balancer: {
1602
1786
  title: "Work distribution not balanced",
1603
- detail: "Work distribution not balanced"
1787
+ detail: "Some team members would receive an uneven share of shifts if all open shifts are assigned."
1604
1788
  },
1605
- "Coverage violation (HARD)": {
1789
+ Coverage: {
1606
1790
  title: "Minimum staffing not met",
1607
- detail: "Minimum staffing not met"
1791
+ detail: "Some time periods would not meet minimum staffing requirements if all open shifts are assigned."
1608
1792
  },
1609
- "Sizing violation (HARD)": {
1793
+ Sizing: {
1610
1794
  title: "Shift duration outside allowed range",
1611
- detail: "Shift duration outside allowed range"
1795
+ detail: "Some open shifts fall outside the allowed shift duration range."
1612
1796
  },
1613
- "Sequencer violation (HARD)": {
1797
+ Sequencer: {
1614
1798
  title: "Shift sequence rule violated",
1615
- detail: "Shift sequence rule violated"
1799
+ detail: "Some team members would violate consecutive shift rules if all open shifts are assigned."
1616
1800
  },
1617
- "Overtime balance violation (HARD)": {
1618
- title: "Overtime balance rule violated",
1619
- detail: "Overtime balance rule violated"
1801
+ OptimizerMinimize: {
1802
+ title: "Optimization rule violated",
1803
+ detail: "Some open shifts cannot be assigned while meeting all optimization priorities."
1804
+ },
1805
+ OptimizerMaximize: {
1806
+ title: "Optimization rule violated",
1807
+ detail: "Some open shifts cannot be assigned while meeting all optimization priorities."
1620
1808
  }
1621
1809
  };
1622
1810
  var FALLBACK_MESSAGE = {
1623
1811
  title: "Rule violated",
1624
- detail: "Rule violated"
1812
+ detail: "Please adjust constraints and try again."
1625
1813
  };
1626
- function getSystemConstraintMessage(constraintName, description) {
1627
- if (constraintName && constraintName in SYSTEM_CONSTRAINT_MESSAGES) {
1628
- return SYSTEM_CONSTRAINT_MESSAGES[constraintName];
1814
+ function parseBackendConstraintName(constraintName) {
1815
+ if (!(constraintName == null ? void 0 : constraintName.trim())) return {};
1816
+ let name = constraintName.trim();
1817
+ for (const suffix of ["Hard", "Medium", "Soft"]) {
1818
+ if (name.endsWith(`-${suffix}`)) {
1819
+ name = name.slice(0, -(suffix.length + 1));
1820
+ break;
1821
+ }
1822
+ }
1823
+ if (name.startsWith("system-")) {
1824
+ return {};
1825
+ }
1826
+ const lastDash = name.lastIndexOf("-");
1827
+ if (lastDash > 0) {
1828
+ return {
1829
+ ruleId: name.slice(0, lastDash),
1830
+ archetype: name.slice(lastDash + 1)
1831
+ };
1832
+ }
1833
+ return { archetype: name };
1834
+ }
1835
+ function resolveRuleIdMessage(ruleId) {
1836
+ if (!(ruleId == null ? void 0 : ruleId.trim())) return void 0;
1837
+ return RULE_ID_MESSAGES[ruleId.trim()];
1838
+ }
1839
+ function stripSeveritySuffix(name) {
1840
+ return name.replace(/-(Hard|Medium|Soft)$/, "");
1841
+ }
1842
+ function resolveConstraintNameMessage(constraintName) {
1843
+ if (!(constraintName == null ? void 0 : constraintName.trim())) return void 0;
1844
+ const trimmed = constraintName.trim();
1845
+ const exact = CONSTRAINT_NAME_MESSAGES[trimmed];
1846
+ if (exact) return exact;
1847
+ const severityStripped = stripSeveritySuffix(trimmed);
1848
+ const directRuleId = resolveRuleIdMessage(severityStripped);
1849
+ if (directRuleId) return directRuleId;
1850
+ const parsed = parseBackendConstraintName(trimmed);
1851
+ if (parsed.ruleId) {
1852
+ const fromEmbeddedRuleId = resolveRuleIdMessage(parsed.ruleId);
1853
+ if (fromEmbeddedRuleId) return fromEmbeddedRuleId;
1629
1854
  }
1630
- if (description == null ? void 0 : description.trim()) {
1631
- return { title: description, detail: description };
1855
+ if (parsed.archetype && parsed.archetype in ARCHETYPE_FALLBACK_MESSAGES) {
1856
+ return ARCHETYPE_FALLBACK_MESSAGES[parsed.archetype];
1632
1857
  }
1633
- return FALLBACK_MESSAGE;
1858
+ return void 0;
1634
1859
  }
1635
- function getRuleBasedViolationMessage(ruleId, constraintName, description) {
1636
- if (ruleId && ruleId in RULE_ID_MESSAGES) {
1637
- return RULE_ID_MESSAGES[ruleId];
1860
+ function safeDescriptionMessage(description) {
1861
+ if (!(description == null ? void 0 : description.trim()) || isInternalViolationText(description)) {
1862
+ return void 0;
1638
1863
  }
1639
- if (constraintName && constraintName in ARCHETYPE_FALLBACK_MESSAGES) {
1640
- return ARCHETYPE_FALLBACK_MESSAGES[constraintName];
1864
+ return { title: description.trim(), detail: description.trim() };
1865
+ }
1866
+ function getSystemConstraintMessage(constraintName, description) {
1867
+ var _a;
1868
+ if (constraintName && constraintName in SYSTEM_CONSTRAINT_MESSAGES) {
1869
+ return SYSTEM_CONSTRAINT_MESSAGES[constraintName];
1641
1870
  }
1642
- if (description == null ? void 0 : description.trim()) {
1643
- return { title: description, detail: description };
1871
+ const fromConstraintName = resolveConstraintNameMessage(constraintName);
1872
+ if (fromConstraintName) return fromConstraintName;
1873
+ return (_a = safeDescriptionMessage(description)) != null ? _a : FALLBACK_MESSAGE;
1874
+ }
1875
+ function getRuleBasedViolationMessage(ruleId, constraintName, description, archetype) {
1876
+ var _a;
1877
+ const fromRuleId = resolveRuleIdMessage(ruleId);
1878
+ if (fromRuleId) return fromRuleId;
1879
+ const fromConstraintName = resolveConstraintNameMessage(constraintName);
1880
+ if (fromConstraintName) return fromConstraintName;
1881
+ if ((archetype == null ? void 0 : archetype.trim()) && archetype.trim() in ARCHETYPE_FALLBACK_MESSAGES) {
1882
+ return ARCHETYPE_FALLBACK_MESSAGES[archetype.trim()];
1644
1883
  }
1645
- return FALLBACK_MESSAGE;
1884
+ return (_a = safeDescriptionMessage(description)) != null ? _a : FALLBACK_MESSAGE;
1646
1885
  }
1647
1886
 
1648
1887
  // src/utils/mapViolationsToResult.ts
1649
- function mapViolationsToResult(violations, summary) {
1888
+ function resolveCustomViolationMessage(ruleId, customMessages) {
1889
+ if (!customMessages) return void 0;
1890
+ const direct = customMessages[ruleId];
1891
+ if (direct) return direct;
1892
+ const uiRuleId = BACKEND_TO_UI_RULE_ID[ruleId];
1893
+ if (uiRuleId) {
1894
+ return customMessages[uiRuleId];
1895
+ }
1896
+ return void 0;
1897
+ }
1898
+ function mapViolationsToResult(violations, summary, customMessages) {
1899
+ var _a;
1650
1900
  const hardViolations = violations.filter((v) => v.isHardViolation === true);
1651
1901
  const seenTitles = /* @__PURE__ */ new Set();
1652
1902
  const violatedConstraints = [];
1653
1903
  for (const v of hardViolations) {
1654
1904
  const hasRuleId = v.ruleId != null && v.ruleId !== void 0 && String(v.ruleId).trim() !== "";
1655
- const msg = hasRuleId ? getRuleBasedViolationMessage(
1656
- v.ruleId,
1657
- v.constraintName,
1658
- v.description
1659
- ) : getSystemConstraintMessage(v.constraintName, v.description);
1905
+ const msg = hasRuleId ? (_a = resolveCustomViolationMessage(v.ruleId, customMessages)) != null ? _a : getRuleBasedViolationMessage(v.ruleId, v.constraintName, v.description, v.archetype) : getSystemConstraintMessage(v.constraintName, v.description);
1660
1906
  if (!seenTitles.has(msg.title)) {
1661
1907
  seenTitles.add(msg.title);
1662
1908
  violatedConstraints.push(msg);
@@ -1666,18 +1912,12 @@ function mapViolationsToResult(violations, summary) {
1666
1912
  violatedConstraints,
1667
1913
  recommendedFixes: []
1668
1914
  };
1669
- if (violatedConstraints.length === 0 && (summary == null ? void 0 : summary.trim())) {
1670
- result.violatedConstraints = [
1671
- {
1672
- title: "Schedule could not be created",
1673
- detail: summary
1674
- }
1675
- ];
1676
- } else if (violatedConstraints.length === 0) {
1915
+ if (violatedConstraints.length === 0) {
1916
+ const safeSummary = (summary == null ? void 0 : summary.trim()) && !isInternalViolationText(summary) ? summary.trim() : void 0;
1677
1917
  result.violatedConstraints = [
1678
1918
  {
1679
1919
  title: "Schedule could not be created",
1680
- detail: "Please adjust constraints and try again."
1920
+ detail: safeSummary != null ? safeSummary : "Please adjust constraints and try again."
1681
1921
  }
1682
1922
  ];
1683
1923
  }
@@ -1724,6 +1964,7 @@ function ViolatedConstraintsPanel({
1724
1964
  return;
1725
1965
  }
1726
1966
  if (recommendations !== null) {
1967
+ setShowRecommendations(true);
1727
1968
  return;
1728
1969
  }
1729
1970
  if (!canFetchRecommendations) {
@@ -1844,13 +2085,78 @@ function ViolatedConstraintsPanel({
1844
2085
  ] });
1845
2086
  }
1846
2087
 
2088
+ // src/weekDates.ts
2089
+ function parseCalendarYmd(ymd) {
2090
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(ymd).trim());
2091
+ if (!m) {
2092
+ return /* @__PURE__ */ new Date(NaN);
2093
+ }
2094
+ return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
2095
+ }
2096
+ function formatCalendarYmd(d) {
2097
+ const y = d.getFullYear();
2098
+ const mo = String(d.getMonth() + 1).padStart(2, "0");
2099
+ const da = String(d.getDate()).padStart(2, "0");
2100
+ return `${y}-${mo}-${da}`;
2101
+ }
2102
+ function getSequentialWeekDatesFromWeekStart(weekStartYmd) {
2103
+ const base = parseCalendarYmd(weekStartYmd);
2104
+ if (Number.isNaN(base.getTime())) return [];
2105
+ const out = [];
2106
+ for (let i = 0; i < 7; i++) {
2107
+ const d = new Date(base.getFullYear(), base.getMonth(), base.getDate() + i);
2108
+ out.push(formatCalendarYmd(d));
2109
+ }
2110
+ return out;
2111
+ }
2112
+ function weekdayTwoLetterFromYmd(ymd) {
2113
+ var _a;
2114
+ const labels = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
2115
+ const d = parseCalendarYmd(ymd);
2116
+ if (Number.isNaN(d.getTime())) return "?";
2117
+ return (_a = labels[d.getDay()]) != null ? _a : "?";
2118
+ }
2119
+ function defaultSelectedScheduleDates(orderedWeekYmds, todayYmd) {
2120
+ return orderedWeekYmds.filter((d) => d >= todayYmd);
2121
+ }
2122
+ function todayCalendarYmdInTimezone(timezone) {
2123
+ const fallback = formatCalendarYmd(/* @__PURE__ */ new Date());
2124
+ const tz = timezone == null ? void 0 : timezone.trim();
2125
+ if (!tz) return fallback;
2126
+ try {
2127
+ const parts = new Intl.DateTimeFormat("en-CA", {
2128
+ timeZone: tz,
2129
+ year: "numeric",
2130
+ month: "2-digit",
2131
+ day: "2-digit"
2132
+ }).formatToParts(/* @__PURE__ */ new Date());
2133
+ const valueByType = new Map(parts.map((part) => [part.type, part.value]));
2134
+ const year = valueByType.get("year");
2135
+ const month = valueByType.get("month");
2136
+ const day = valueByType.get("day");
2137
+ return year && month && day ? `${year}-${month}-${day}` : fallback;
2138
+ } catch {
2139
+ return fallback;
2140
+ }
2141
+ }
2142
+
1847
2143
  // src/AutoSchedulerModal.tsx
1848
2144
  var import_jsx_runtime3 = require("react/jsx-runtime");
2145
+ function resolveOptimizationFooter(footer, key) {
2146
+ if (!footer) return null;
2147
+ const node = typeof footer === "function" ? footer() : footer;
2148
+ if ((0, import_react3.isValidElement)(node)) return (0, import_react3.cloneElement)(node, { key });
2149
+ return node;
2150
+ }
2151
+ var DRAWER_WIDTH_MAP = { sm: "448px", md: "578px", lg: "600px" };
1849
2152
  function AutoSchedulerModal({
2153
+ displayMode,
2154
+ drawerSize = "lg",
1850
2155
  baseURL,
1851
2156
  isOpen,
1852
2157
  title = "Auto-scheduling",
1853
- bannerText,
2158
+ bannerTitle = "Create a smarter retail schedule in seconds.",
2159
+ bannerText = "AI assigns open shifts using your rules, availability, and optimization priorities while keeping existing\n assigned shifts in place.",
1854
2160
  jurisdictions,
1855
2161
  openShifts,
1856
2162
  employees,
@@ -1869,22 +2175,88 @@ function AutoSchedulerModal({
1869
2175
  onSolution,
1870
2176
  getStoredConfigForLocations,
1871
2177
  persistConfigForLocations,
1872
- errorToast
2178
+ errorToast,
2179
+ organizationId,
2180
+ constraintDefinitionIds,
2181
+ constraintDefinitions,
2182
+ hardConstraintOptions,
2183
+ softConstraintGroups,
2184
+ optimizationFooter,
2185
+ trafficForecast,
2186
+ builtinConstraintOptions,
2187
+ subLocations,
2188
+ showSubLocations,
2189
+ teams,
2190
+ showTeams,
2191
+ onSelectionChange,
2192
+ onGenerate,
2193
+ onGenerateSolution,
2194
+ solveStatus,
2195
+ scheduleWeekStartYmd,
2196
+ todayCalendarYmd,
2197
+ evaluationTimezone
1873
2198
  }) {
1874
2199
  const baseTheme = (0, import_react4.useTheme)();
1875
- const { chakraOverride, styleOverrides } = (0, import_react3.useMemo)(
1876
- () => resolveThemeParts(theme),
1877
- [theme]
1878
- );
2200
+ const { chakraOverride, styleOverrides } = (0, import_react3.useMemo)(() => resolveThemeParts(theme), [theme]);
1879
2201
  const mergedTheme = (0, import_react3.useMemo)(
1880
2202
  () => mergeThemeWithBase(baseTheme, chakraOverride),
1881
2203
  [baseTheme, chakraOverride]
1882
2204
  );
1883
2205
  const styles = (0, import_react3.useMemo)(() => buildStyles(defaultStyles, styleOverrides), [styleOverrides]);
2206
+ const effectiveHardConstraintOptions = (0, import_react3.useMemo)(() => {
2207
+ const base = hardConstraintOptions != null ? hardConstraintOptions : HARD_CONSTRAINT_OPTIONS;
2208
+ return base.map((opt) => {
2209
+ var _a, _b, _c, _d;
2210
+ return {
2211
+ ...opt,
2212
+ defaultValue: (_a = opt.defaultValue) != null ? _a : HARD_CONSTRAINT_DEFAULTS[opt.id],
2213
+ suffix: (_b = opt.suffix) != null ? _b : opt.id === "max_work_days_per_week" ? "days" : "hours",
2214
+ min: (_c = opt.min) != null ? _c : opt.id === "max_work_days_per_week" ? 1 : void 0,
2215
+ max: (_d = opt.max) != null ? _d : opt.id === "max_work_days_per_week" ? 6 : void 0
2216
+ };
2217
+ });
2218
+ }, [hardConstraintOptions]);
2219
+ const effectiveSoftConstraintGroups = (0, import_react3.useMemo)(
2220
+ () => softConstraintGroups != null ? softConstraintGroups : OPTIMIZATION_OPTION_GROUPS,
2221
+ [softConstraintGroups]
2222
+ );
2223
+ const effectiveBuiltinOptions = (0, import_react3.useMemo)(
2224
+ () => builtinConstraintOptions != null ? builtinConstraintOptions : [],
2225
+ [builtinConstraintOptions]
2226
+ );
2227
+ const defaultSelectedBuiltinIds = (0, import_react3.useMemo)(
2228
+ () => effectiveBuiltinOptions.filter((option) => option.defaultSelected).map((option) => option.id),
2229
+ [effectiveBuiltinOptions]
2230
+ );
2231
+ const dynamicViolationMessages = (0, import_react3.useMemo)(() => {
2232
+ const map = {};
2233
+ for (const opt of effectiveHardConstraintOptions) {
2234
+ const msg = { title: opt.name, detail: opt.description || opt.name };
2235
+ map[opt.id] = msg;
2236
+ const backendRuleId = toBackendRuleId(opt.id);
2237
+ if (backendRuleId) {
2238
+ map[backendRuleId] = msg;
2239
+ }
2240
+ }
2241
+ for (const group of effectiveSoftConstraintGroups) {
2242
+ for (const opt of group.options) {
2243
+ const msg = { title: opt.title, detail: opt.subtitle || opt.title };
2244
+ map[opt.id] = msg;
2245
+ const backendRuleId = toBackendRuleId(opt.id);
2246
+ if (backendRuleId) {
2247
+ map[backendRuleId] = msg;
2248
+ }
2249
+ }
2250
+ }
2251
+ return map;
2252
+ }, [effectiveHardConstraintOptions, effectiveSoftConstraintGroups]);
2253
+ const [selectedSubLocationIds, setSelectedSubLocationIds] = (0, import_react3.useState)([]);
2254
+ const [selectedTeamIds, setSelectedTeamIds] = (0, import_react3.useState)([]);
1884
2255
  const [selectedConstraintIds, setSelectedConstraintIds] = (0, import_react3.useState)([]);
1885
2256
  const [constraintValues, setConstraintValues] = (0, import_react3.useState)({});
1886
2257
  const [selectedOptimizationIds, setSelectedOptimizationIds] = (0, import_react3.useState)([]);
1887
2258
  const [optimizationValues, setOptimizationValues] = (0, import_react3.useState)({});
2259
+ const [selectedScheduleDates, setSelectedScheduleDates] = (0, import_react3.useState)([]);
1888
2260
  const [screen, setScreen] = (0, import_react3.useState)("configure");
1889
2261
  const [activeStepIndex, setActiveStepIndex] = (0, import_react3.useState)(0);
1890
2262
  const [completedStepIndex, setCompletedStepIndex] = (0, import_react3.useState)(-1);
@@ -1893,12 +2265,19 @@ function AutoSchedulerModal({
1893
2265
  const timersRef = (0, import_react3.useRef)([]);
1894
2266
  const pollIntervalRef = (0, import_react3.useRef)(null);
1895
2267
  const runIdRef = (0, import_react3.useRef)(0);
2268
+ const weekDatesOrdered = (0, import_react3.useMemo)(() => {
2269
+ if (!scheduleWeekStartYmd) return [];
2270
+ return getSequentialWeekDatesFromWeekStart(scheduleWeekStartYmd);
2271
+ }, [scheduleWeekStartYmd]);
2272
+ const toggleScheduleDate = (ymd) => {
2273
+ setSelectedScheduleDates(
2274
+ (prev) => prev.includes(ymd) ? prev.filter((d) => d !== ymd) : [...prev, ymd].sort()
2275
+ );
2276
+ };
1896
2277
  const selectedByJurisdiction = (0, import_react3.useMemo)(
1897
2278
  () => jurisdictions.map((jurisdiction) => ({
1898
2279
  ...jurisdiction,
1899
- selectedLocations: jurisdiction.locations.filter(
1900
- (location) => selectedLocationIds.includes(location.id)
1901
- )
2280
+ selectedLocations: jurisdiction.locations.filter((location) => selectedLocationIds.includes(location.id))
1902
2281
  })).filter((jurisdiction) => jurisdiction.selectedLocations.length > 0),
1903
2282
  [jurisdictions, selectedLocationIds]
1904
2283
  );
@@ -1907,43 +2286,84 @@ function AutoSchedulerModal({
1907
2286
  0
1908
2287
  );
1909
2288
  const locationIdsWithOpenShifts = (0, import_react3.useMemo)(() => {
1910
- return new Set(
1911
- openShifts.filter((shift) => !shift.employeeId).map((shift) => shift.location.id)
1912
- );
2289
+ return new Set(openShifts.filter((shift) => !shift.employeeId).map((shift) => shift.location.id));
1913
2290
  }, [openShifts]);
2291
+ const filteredSubLocations = (0, import_react3.useMemo)(
2292
+ () => {
2293
+ var _a;
2294
+ return (_a = subLocations == null ? void 0 : subLocations.filter((sl) => selectedLocationIds.includes(sl.parentId))) != null ? _a : [];
2295
+ },
2296
+ [subLocations, selectedLocationIds]
2297
+ );
2298
+ const selectedLocationTimezone = (0, import_react3.useMemo)(() => {
2299
+ var _a, _b;
2300
+ const allLocations = jurisdictions.flatMap((jurisdiction) => jurisdiction.locations);
2301
+ for (const id of selectedLocationIds) {
2302
+ const timezone = (_b = (_a = allLocations.find((location) => location.id === id)) == null ? void 0 : _a.timezone) == null ? void 0 : _b.trim();
2303
+ if (timezone) return timezone;
2304
+ }
2305
+ return (evaluationTimezone == null ? void 0 : evaluationTimezone.trim()) || "UTC";
2306
+ }, [evaluationTimezone, jurisdictions, selectedLocationIds]);
2307
+ const effectiveTodayCalendarYmd = (0, import_react3.useMemo)(() => {
2308
+ return todayCalendarYmd != null ? todayCalendarYmd : todayCalendarYmdInTimezone(selectedLocationTimezone);
2309
+ }, [selectedLocationTimezone, todayCalendarYmd]);
1914
2310
  const selectedConstraints = (0, import_react3.useMemo)(
1915
- () => HARD_CONSTRAINT_OPTIONS.filter((option) => selectedConstraintIds.includes(option.id)),
1916
- [selectedConstraintIds]
2311
+ () => effectiveHardConstraintOptions.filter((option) => selectedConstraintIds.includes(option.id)),
2312
+ [effectiveHardConstraintOptions, selectedConstraintIds]
2313
+ );
2314
+ const selectedConstraintDisplayOptions = (0, import_react3.useMemo)(
2315
+ () => effectiveHardConstraintOptions.filter(
2316
+ (option) => selectedConstraintIds.includes(option.id) || option.readOnly && option.defaultSelected
2317
+ ),
2318
+ [effectiveHardConstraintOptions, selectedConstraintIds]
1917
2319
  );
1918
2320
  const optimizationOptions = (0, import_react3.useMemo)(
1919
- () => OPTIMIZATION_OPTION_GROUPS.flatMap(
2321
+ () => effectiveSoftConstraintGroups.flatMap(
1920
2322
  (group) => group.options.map((option) => ({
1921
2323
  ...option,
1922
2324
  groupId: group.id,
1923
2325
  groupName: group.name
1924
2326
  }))
1925
2327
  ),
1926
- []
2328
+ [effectiveSoftConstraintGroups]
1927
2329
  );
1928
2330
  const selectedOptimizationOptions = (0, import_react3.useMemo)(
2331
+ () => optimizationOptions.filter((option) => selectedOptimizationIds.includes(option.id)),
2332
+ [optimizationOptions, selectedOptimizationIds]
2333
+ );
2334
+ const selectedOptimizationDisplayOptions = (0, import_react3.useMemo)(
1929
2335
  () => optimizationOptions.filter(
1930
- (option) => selectedOptimizationIds.includes(option.id)
2336
+ (option) => selectedOptimizationIds.includes(option.id) || option.readOnly && option.defaultSelected
1931
2337
  ),
1932
2338
  [optimizationOptions, selectedOptimizationIds]
1933
2339
  );
2340
+ const effectiveSelectedConstraintIds = (0, import_react3.useMemo)(
2341
+ () => Array.from(
2342
+ /* @__PURE__ */ new Set([
2343
+ ...selectedConstraintIds,
2344
+ ...effectiveHardConstraintOptions.filter((option) => option.readOnly && option.defaultSelected).map((option) => option.id)
2345
+ ])
2346
+ ),
2347
+ [selectedConstraintIds, effectiveHardConstraintOptions]
2348
+ );
2349
+ const effectiveSelectedOptimizationIds = (0, import_react3.useMemo)(
2350
+ () => Array.from(
2351
+ /* @__PURE__ */ new Set([
2352
+ ...selectedOptimizationIds,
2353
+ ...optimizationOptions.filter((option) => option.readOnly && option.defaultSelected).map((option) => option.id)
2354
+ ])
2355
+ ),
2356
+ [selectedOptimizationIds, optimizationOptions]
2357
+ );
1934
2358
  const hasEmptyInputs = (0, import_react3.useMemo)(() => {
1935
- const hasEmptyConstraint = selectedConstraints.some(
1936
- (opt) => {
1937
- var _a;
1938
- return !((_a = constraintValues[opt.id]) == null ? void 0 : _a.trim());
1939
- }
1940
- );
1941
- const hasEmptyWeight = selectedOptimizationOptions.length >= 2 && selectedOptimizationOptions.some(
1942
- (opt) => {
1943
- var _a;
1944
- return !((_a = optimizationValues[`${opt.id}_weightPoints`]) == null ? void 0 : _a.trim());
1945
- }
1946
- );
2359
+ const hasEmptyConstraint = selectedConstraints.filter((opt) => opt.defaultValue != null).some((opt) => {
2360
+ var _a;
2361
+ return !((_a = constraintValues[opt.id]) == null ? void 0 : _a.trim());
2362
+ });
2363
+ const hasEmptyWeight = selectedOptimizationOptions.length >= 2 && selectedOptimizationOptions.some((opt) => {
2364
+ var _a;
2365
+ return !((_a = optimizationValues[`${opt.id}_weightPoints`]) == null ? void 0 : _a.trim());
2366
+ });
1947
2367
  return hasEmptyConstraint || hasEmptyWeight;
1948
2368
  }, [selectedConstraints, constraintValues, selectedOptimizationOptions, optimizationValues]);
1949
2369
  const allocationSummary = (0, import_react3.useMemo)(() => {
@@ -1977,13 +2397,25 @@ function AutoSchedulerModal({
1977
2397
  setScreen("configure");
1978
2398
  setLastFailure(null);
1979
2399
  setSelectedLocationIds([]);
2400
+ setSelectedSubLocationIds([]);
2401
+ setSelectedTeamIds([]);
1980
2402
  setSelectedOptimizationIds([]);
1981
2403
  setSelectedConstraintIds([]);
1982
2404
  setConstraintValues({});
1983
2405
  setOptimizationValues({});
2406
+ setSelectedScheduleDates([]);
1984
2407
  resetProgress();
1985
2408
  }
1986
2409
  }, [isOpen]);
2410
+ (0, import_react3.useEffect)(() => {
2411
+ if (!isOpen || !scheduleWeekStartYmd) return;
2412
+ if (weekDatesOrdered.length === 0) {
2413
+ setSelectedScheduleDates([]);
2414
+ return;
2415
+ }
2416
+ const today = effectiveTodayCalendarYmd != null ? effectiveTodayCalendarYmd : weekDatesOrdered[0];
2417
+ setSelectedScheduleDates(defaultSelectedScheduleDates(weekDatesOrdered, today));
2418
+ }, [isOpen, scheduleWeekStartYmd, weekDatesOrdered, effectiveTodayCalendarYmd]);
1987
2419
  (0, import_react3.useEffect)(() => {
1988
2420
  if (isOpen && initialConfig) {
1989
2421
  setSelectedLocationIds(initialConfig.selectedLocationIds);
@@ -2016,11 +2448,12 @@ function AutoSchedulerModal({
2016
2448
  }, [selectedLocationIds, initialConfig, getStoredConfigForLocations]);
2017
2449
  (0, import_react3.useEffect)(() => () => clearTimers(), []);
2018
2450
  (0, import_react3.useEffect)(() => {
2451
+ if (onGenerate) return;
2019
2452
  setSelectedLocationIds((previous) => {
2020
2453
  const filtered = previous.filter((id) => locationIdsWithOpenShifts.has(id));
2021
2454
  return filtered.length === previous.length ? previous : filtered;
2022
2455
  });
2023
- }, [locationIdsWithOpenShifts]);
2456
+ }, [locationIdsWithOpenShifts, onGenerate]);
2024
2457
  const stepStatusForIndex = (index) => {
2025
2458
  if (index <= completedStepIndex) {
2026
2459
  return "done";
@@ -2048,144 +2481,249 @@ function AutoSchedulerModal({
2048
2481
  timersRef.current.push(timerId);
2049
2482
  };
2050
2483
  const afterSuccessSteps = (runId, response) => {
2051
- schedule(STEP_INTERVAL_MS, () => {
2052
- if (runIdRef.current !== runId) return;
2053
- setCompletedStepIndex(2);
2054
- setActiveStepIndex(3);
2055
- }, runId);
2056
- schedule(STEP_INTERVAL_MS * 2, () => {
2057
- if (runIdRef.current !== runId) return;
2058
- setCompletedStepIndex(3);
2059
- setActiveStepIndex(4);
2060
- }, runId);
2061
- schedule(STEP_INTERVAL_MS * 3, async () => {
2062
- if (runIdRef.current !== runId) return;
2063
- try {
2064
- await onSolution(response.solution);
2065
- setCompletedStepIndex(4);
2484
+ schedule(
2485
+ STEP_INTERVAL_MS,
2486
+ () => {
2487
+ if (runIdRef.current !== runId) return;
2488
+ setCompletedStepIndex(2);
2489
+ setActiveStepIndex(3);
2490
+ },
2491
+ runId
2492
+ );
2493
+ schedule(
2494
+ STEP_INTERVAL_MS * 2,
2495
+ () => {
2496
+ if (runIdRef.current !== runId) return;
2497
+ setCompletedStepIndex(3);
2066
2498
  setActiveStepIndex(4);
2067
- } catch (err) {
2068
- console.error(err);
2069
- const defaultErrorMessage = "Failed to create schedule shifts";
2070
- const detailedErrorMessage = err instanceof Error ? err.message || defaultErrorMessage : defaultErrorMessage;
2071
- setLastFailure({
2072
- violatedConstraints: [
2073
- {
2074
- title: "Something went wrong",
2075
- detail: detailedErrorMessage
2076
- }
2077
- ],
2078
- recommendedFixes: []
2079
- });
2080
- setScreen("failed");
2081
- }
2082
- }, runId);
2499
+ },
2500
+ runId
2501
+ );
2502
+ schedule(
2503
+ STEP_INTERVAL_MS * 3,
2504
+ async () => {
2505
+ if (runIdRef.current !== runId) return;
2506
+ try {
2507
+ await (onSolution == null ? void 0 : onSolution(response.solution));
2508
+ setCompletedStepIndex(4);
2509
+ setActiveStepIndex(4);
2510
+ } catch (err) {
2511
+ console.error(err);
2512
+ const defaultErrorMessage = "Failed to create schedule shifts";
2513
+ const detailedErrorMessage = err instanceof Error ? err.message || defaultErrorMessage : defaultErrorMessage;
2514
+ setLastFailure({
2515
+ violatedConstraints: [
2516
+ {
2517
+ title: "Something went wrong",
2518
+ detail: detailedErrorMessage
2519
+ }
2520
+ ],
2521
+ recommendedFixes: []
2522
+ });
2523
+ setScreen("failed");
2524
+ }
2525
+ },
2526
+ runId
2527
+ );
2083
2528
  };
2084
2529
  const handleCreateSchedule = () => {
2085
2530
  if (selectedCount === 0) {
2086
2531
  return;
2087
2532
  }
2533
+ if (scheduleWeekStartYmd && selectedScheduleDates.length === 0) {
2534
+ return;
2535
+ }
2536
+ if (onGenerate) {
2537
+ const runId2 = resetBeforeSubmit();
2538
+ schedule(
2539
+ STEP_INTERVAL_MS,
2540
+ async () => {
2541
+ if (runIdRef.current !== runId2) return;
2542
+ setCompletedStepIndex(0);
2543
+ setActiveStepIndex(1);
2544
+ try {
2545
+ const generateResult = await onGenerate({
2546
+ selectedLocationIds,
2547
+ selectedSubLocationIds,
2548
+ selectedTeamIds,
2549
+ schedulerTimezone: selectedLocationTimezone,
2550
+ selectedScheduleDates,
2551
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2552
+ constraintValues,
2553
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2554
+ optimizationValues,
2555
+ selectedBuiltinIds: defaultSelectedBuiltinIds
2556
+ });
2557
+ if (runIdRef.current !== runId2) return;
2558
+ setCompletedStepIndex(1);
2559
+ setActiveStepIndex(2);
2560
+ schedule(
2561
+ STEP_INTERVAL_MS,
2562
+ () => {
2563
+ if (runIdRef.current !== runId2) return;
2564
+ setCompletedStepIndex(2);
2565
+ setActiveStepIndex(3);
2566
+ },
2567
+ runId2
2568
+ );
2569
+ schedule(
2570
+ STEP_INTERVAL_MS * 2,
2571
+ () => {
2572
+ if (runIdRef.current !== runId2) return;
2573
+ setCompletedStepIndex(3);
2574
+ setActiveStepIndex(4);
2575
+ },
2576
+ runId2
2577
+ );
2578
+ schedule(
2579
+ STEP_INTERVAL_MS * 3,
2580
+ () => {
2581
+ if (runIdRef.current !== runId2) return;
2582
+ setCompletedStepIndex(4);
2583
+ setActiveStepIndex(4);
2584
+ onGenerateSolution == null ? void 0 : onGenerateSolution(generateResult);
2585
+ onClose();
2586
+ },
2587
+ runId2
2588
+ );
2589
+ } catch (err) {
2590
+ if (runIdRef.current !== runId2) return;
2591
+ if (err instanceof InfeasibleScheduleError) {
2592
+ setLastFailure(err.failure);
2593
+ } else {
2594
+ const msg = err instanceof Error ? err.message : "Unknown error";
2595
+ setLastFailure({
2596
+ violatedConstraints: [{ title: "Error", detail: msg }],
2597
+ recommendedFixes: []
2598
+ });
2599
+ }
2600
+ setScreen("failed");
2601
+ }
2602
+ },
2603
+ runId2
2604
+ );
2605
+ return;
2606
+ }
2088
2607
  const payload = buildSchedulePayload({
2089
2608
  employees,
2090
2609
  openShifts,
2091
2610
  timeOffs,
2092
2611
  selectedLocationIds,
2093
2612
  jurisdictions,
2094
- selectedConstraintIds,
2613
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2095
2614
  constraintValues,
2096
- selectedOptimizationIds,
2097
- optimizationValues
2615
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2616
+ optimizationValues,
2617
+ trafficForecast,
2618
+ constraintDefinitionIds,
2619
+ constraintDefinitions,
2620
+ organizationId
2098
2621
  });
2099
2622
  persistConfigForLocations == null ? void 0 : persistConfigForLocations(selectedLocationIds, {
2100
2623
  selectedLocationIds,
2101
- selectedConstraintIds,
2624
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2102
2625
  constraintValues,
2103
- selectedOptimizationIds,
2104
- optimizationValues
2626
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2627
+ optimizationValues,
2628
+ selectedBuiltinIds: defaultSelectedBuiltinIds
2105
2629
  });
2106
2630
  const runId = resetBeforeSubmit();
2107
- schedule(STEP_INTERVAL_MS, async () => {
2108
- setCompletedStepIndex(0);
2109
- setActiveStepIndex(1);
2110
- try {
2111
- const token = await getToken(getTokenURLAndHeaders.url, getTokenURLAndHeaders.headers);
2112
- if (!token) {
2113
- throw new Error("Failed to get auto-scheduler token");
2114
- }
2115
- const auth = { authorization: `Bearer ${token}` };
2116
- const { jobId } = await submitSolveJob(baseURL, payload, auth);
2117
- if (runIdRef.current !== runId) {
2118
- return;
2119
- }
2120
- setCompletedStepIndex(1);
2121
- setActiveStepIndex(OPTIMIZING_STEP_INDEX);
2122
- const pollForResult = async () => {
2123
- if (runIdRef.current !== runId) return;
2124
- try {
2125
- const statusRes = await getJobStatus(baseURL, jobId, auth);
2631
+ schedule(
2632
+ STEP_INTERVAL_MS,
2633
+ async () => {
2634
+ setCompletedStepIndex(0);
2635
+ setActiveStepIndex(1);
2636
+ try {
2637
+ const token = await getToken(getTokenURLAndHeaders.url, getTokenURLAndHeaders.headers);
2638
+ if (!token) {
2639
+ throw new Error("Failed to get auto-scheduler token");
2640
+ }
2641
+ const selectedDates = [...selectedScheduleDates].sort();
2642
+ const horizonOptions = selectedDates.length > 0 ? {
2643
+ horizonStartDate: selectedDates[0],
2644
+ horizonEndDate: selectedDates[selectedDates.length - 1]
2645
+ } : {};
2646
+ const auth = { authorization: `Bearer ${token}`, ...horizonOptions };
2647
+ const { jobId } = await submitSolveJob(baseURL, payload, auth);
2648
+ if (runIdRef.current !== runId) {
2649
+ return;
2650
+ }
2651
+ setCompletedStepIndex(1);
2652
+ setActiveStepIndex(OPTIMIZING_STEP_INDEX);
2653
+ const pollForResult = async () => {
2126
2654
  if (runIdRef.current !== runId) return;
2127
- if (statusRes.status === "NOT_SOLVING" || statusRes.status === "TERMINATED_EARLY") {
2655
+ try {
2656
+ const statusRes = await getJobStatus(baseURL, jobId, auth);
2657
+ if (runIdRef.current !== runId) return;
2658
+ if (statusRes.status === "NOT_SOLVING" || statusRes.status === "TERMINATED_EARLY") {
2659
+ if (pollIntervalRef.current) {
2660
+ clearInterval(pollIntervalRef.current);
2661
+ pollIntervalRef.current = null;
2662
+ }
2663
+ const response = await getJobResult(baseURL, jobId, auth);
2664
+ if (runIdRef.current !== runId) return;
2665
+ let responseForHandling = response;
2666
+ if (response.explanation.isFeasible) {
2667
+ const solution = await getJobSolution(baseURL, jobId, auth);
2668
+ if (runIdRef.current !== runId) return;
2669
+ responseForHandling = { ...response, solution };
2670
+ }
2671
+ handleSolveResult(runId, responseForHandling);
2672
+ return;
2673
+ }
2674
+ } catch (err) {
2675
+ if (runIdRef.current !== runId) return;
2128
2676
  if (pollIntervalRef.current) {
2129
2677
  clearInterval(pollIntervalRef.current);
2130
2678
  pollIntervalRef.current = null;
2131
2679
  }
2132
- const response = await getJobResult(baseURL, jobId, auth);
2133
- if (runIdRef.current !== runId) return;
2134
- let responseForHandling = response;
2135
- if (response.explanation.isFeasible) {
2136
- const solution = await getJobSolution(baseURL, jobId, auth);
2137
- if (runIdRef.current !== runId) return;
2138
- responseForHandling = { ...response, solution };
2139
- }
2140
- handleSolveResult(runId, responseForHandling);
2141
- return;
2142
- }
2143
- } catch (err) {
2144
- if (runIdRef.current !== runId) return;
2145
- if (pollIntervalRef.current) {
2146
- clearInterval(pollIntervalRef.current);
2147
- pollIntervalRef.current = null;
2680
+ console.error(err);
2681
+ setLastFailure(NETWORK_ERROR_FAILURE);
2682
+ setScreen("failed");
2148
2683
  }
2149
- console.error(err);
2150
- setLastFailure(NETWORK_ERROR_FAILURE);
2151
- setScreen("failed");
2684
+ };
2685
+ pollForResult();
2686
+ pollIntervalRef.current = setInterval(pollForResult, POLL_INTERVAL_MS);
2687
+ } catch (err) {
2688
+ if (runIdRef.current !== runId) {
2689
+ return;
2152
2690
  }
2153
- };
2154
- pollForResult();
2155
- pollIntervalRef.current = setInterval(pollForResult, POLL_INTERVAL_MS);
2156
- } catch (err) {
2157
- if (runIdRef.current !== runId) {
2158
- return;
2691
+ console.error(err);
2692
+ setLastFailure(NETWORK_ERROR_FAILURE);
2693
+ setScreen("failed");
2159
2694
  }
2160
- console.error(err);
2161
- setLastFailure(NETWORK_ERROR_FAILURE);
2162
- setScreen("failed");
2163
- }
2164
- }, runId);
2695
+ },
2696
+ runId
2697
+ );
2165
2698
  };
2166
2699
  const handleSolveResult = (runId, response) => {
2167
- var _a, _b;
2700
+ var _a;
2168
2701
  if (!response.explanation.isFeasible) {
2169
- const result = mapViolationsToResult(
2170
- (_a = response.explanation.violations) != null ? _a : [],
2171
- response.explanation.summary
2172
- );
2173
- const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter(
2174
- (l) => selectedLocationIds.includes(l.id)
2175
- );
2702
+ const result = mapViolationsToResult((_a = response.explanation.violations) != null ? _a : [], response.explanation.summary, dynamicViolationMessages);
2703
+ const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter((l) => selectedLocationIds.includes(l.id));
2176
2704
  const recommendationPayload = generateRecommendationsURLAndHeaders ? {
2177
2705
  context: {
2178
2706
  selectedLocationIds,
2179
- selectedConstraintIds,
2707
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2180
2708
  constraintValues,
2181
- selectedOptimizationIds,
2709
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
2182
2710
  optimizationValues,
2183
- timezone: (_b = selectedLocations[0]) == null ? void 0 : _b.timezone
2711
+ timezone: selectedLocationTimezone
2184
2712
  },
2185
2713
  locations: selectedLocations,
2186
2714
  employees,
2187
- openShifts: openShifts.filter((s) => !s.employeeId),
2188
- assignedShifts: openShifts.filter((s) => Boolean(s.employeeId)),
2715
+ openShifts: openShifts.filter(
2716
+ (s) => {
2717
+ var _a2;
2718
+ return !s.employeeId && selectedLocationIds.includes(String((_a2 = s.location) == null ? void 0 : _a2.id));
2719
+ }
2720
+ ),
2721
+ assignedShifts: openShifts.filter(
2722
+ (s) => {
2723
+ var _a2;
2724
+ return Boolean(s.employeeId) && selectedLocationIds.includes(String((_a2 = s.location) == null ? void 0 : _a2.id));
2725
+ }
2726
+ ),
2189
2727
  timeOffs: timeOffs != null ? timeOffs : [],
2190
2728
  explanation: response.explanation
2191
2729
  } : void 0;
@@ -2204,17 +2742,34 @@ function AutoSchedulerModal({
2204
2742
  setScreen("configure");
2205
2743
  };
2206
2744
  const handleToggleLocation = (locationId) => {
2207
- if (!locationIdsWithOpenShifts.has(locationId)) {
2745
+ if (!onGenerate && !locationIdsWithOpenShifts.has(locationId)) {
2208
2746
  return;
2209
2747
  }
2210
- if (selectedLocationIds.includes(locationId)) {
2211
- setSelectedLocationIds(selectedLocationIds.filter((id) => id !== locationId));
2212
- return;
2748
+ const newLocationIds = selectedLocationIds.includes(locationId) ? selectedLocationIds.filter((id) => id !== locationId) : [...selectedLocationIds, locationId];
2749
+ setSelectedLocationIds(newLocationIds);
2750
+ const filteredSubs = selectedSubLocationIds.filter((subId) => {
2751
+ const sub = subLocations == null ? void 0 : subLocations.find((s) => s.id === subId);
2752
+ return sub ? newLocationIds.includes(sub.parentId) : false;
2753
+ });
2754
+ if (filteredSubs.length !== selectedSubLocationIds.length) {
2755
+ setSelectedSubLocationIds(filteredSubs);
2213
2756
  }
2214
- setSelectedLocationIds([...selectedLocationIds, locationId]);
2757
+ onSelectionChange == null ? void 0 : onSelectionChange(
2758
+ newLocationIds,
2759
+ filteredSubs.length !== selectedSubLocationIds.length ? filteredSubs : selectedSubLocationIds
2760
+ );
2761
+ };
2762
+ const handleToggleSubLocation = (subLocationId) => {
2763
+ const next = selectedSubLocationIds.includes(subLocationId) ? selectedSubLocationIds.filter((id) => id !== subLocationId) : [...selectedSubLocationIds, subLocationId];
2764
+ setSelectedSubLocationIds(next);
2765
+ onSelectionChange == null ? void 0 : onSelectionChange(selectedLocationIds, next);
2766
+ };
2767
+ const handleToggleTeam = (teamId) => {
2768
+ setSelectedTeamIds((prev) => prev.includes(teamId) ? prev.filter((id) => id !== teamId) : [...prev, teamId]);
2215
2769
  };
2216
2770
  const handleToggleConstraint = (constraintId) => {
2217
2771
  setSelectedConstraintIds((prev) => {
2772
+ var _a;
2218
2773
  if (prev.includes(constraintId)) {
2219
2774
  setConstraintValues((values) => {
2220
2775
  const next = { ...values };
@@ -2223,10 +2778,11 @@ function AutoSchedulerModal({
2223
2778
  });
2224
2779
  return prev.filter((id) => id !== constraintId);
2225
2780
  }
2226
- if (HARD_CONSTRAINT_DEFAULTS[constraintId]) {
2781
+ const defaultVal = (_a = effectiveHardConstraintOptions.find((o) => o.id === constraintId)) == null ? void 0 : _a.defaultValue;
2782
+ if (defaultVal) {
2227
2783
  setConstraintValues((values) => ({
2228
2784
  ...values,
2229
- [constraintId]: HARD_CONSTRAINT_DEFAULTS[constraintId]
2785
+ [constraintId]: defaultVal
2230
2786
  }));
2231
2787
  }
2232
2788
  return [...prev, constraintId];
@@ -2278,12 +2834,12 @@ function AutoSchedulerModal({
2278
2834
  const configurationContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "20px", children: [
2279
2835
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { sx: styles.banner, children: [
2280
2836
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { sx: styles.bannerIcon, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MagicIcon, {}) }),
2281
- bannerText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerText, children: bannerText }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "4px", flex: 1, children: [
2282
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerTitle, children: "Create a smarter retail schedule in seconds." }),
2283
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerText, children: "AI assigns open shifts using your rules, availability, and optimization priorities while keeping existing assigned shifts in place." })
2837
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "4px", flex: 1, children: [
2838
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerTitle, children: bannerTitle }),
2839
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerText, children: bannerText })
2284
2840
  ] })
2285
2841
  ] }),
2286
- openShifts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2842
+ !onGenerate && openShifts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2287
2843
  import_react4.Box,
2288
2844
  {
2289
2845
  sx: {
@@ -2326,30 +2882,33 @@ function AutoSchedulerModal({
2326
2882
  ] }) }) }),
2327
2883
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverContent, { sx: styles.popoverContent, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverBody, { p: 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "4px", children: jurisdictions.map((jurisdiction) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { children: [
2328
2884
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.groupHeader, children: jurisdiction.title }),
2329
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: jurisdiction.locations.map((location) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2330
- import_react4.HStack,
2331
- {
2332
- as: "label",
2333
- sx: {
2334
- ...styles.optionRow,
2335
- cursor: locationIdsWithOpenShifts.has(location.id) ? "pointer" : "not-allowed",
2336
- opacity: locationIdsWithOpenShifts.has(location.id) ? 1 : 0.55
2885
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: jurisdiction.locations.map((location) => {
2886
+ const isEnabled = onGenerate || locationIdsWithOpenShifts.has(location.id);
2887
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2888
+ import_react4.HStack,
2889
+ {
2890
+ as: "label",
2891
+ sx: {
2892
+ ...styles.optionRow,
2893
+ cursor: isEnabled ? "pointer" : "not-allowed",
2894
+ opacity: isEnabled ? 1 : 0.55
2895
+ },
2896
+ children: [
2897
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2898
+ import_react4.Checkbox,
2899
+ {
2900
+ id: `location-${location.id}`,
2901
+ isChecked: selectedLocationIds.includes(location.id),
2902
+ isDisabled: !isEnabled,
2903
+ onChange: () => handleToggleLocation(location.id)
2904
+ }
2905
+ ),
2906
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optionLabel, children: location.name })
2907
+ ]
2337
2908
  },
2338
- children: [
2339
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2340
- import_react4.Checkbox,
2341
- {
2342
- id: `location-${location.id}`,
2343
- isChecked: selectedLocationIds.includes(location.id),
2344
- isDisabled: !locationIdsWithOpenShifts.has(location.id),
2345
- onChange: () => handleToggleLocation(location.id)
2346
- }
2347
- ),
2348
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optionLabel, children: location.name })
2349
- ]
2350
- },
2351
- location.id
2352
- )) })
2909
+ location.id
2910
+ );
2911
+ }) })
2353
2912
  ] }, jurisdiction.id)) }) }) })
2354
2913
  ] }),
2355
2914
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "8px", children: selectedByJurisdiction.map((jurisdiction) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { sx: styles.selectedBanner, children: [
@@ -2363,11 +2922,128 @@ function AutoSchedulerModal({
2363
2922
  ] })
2364
2923
  ] }, jurisdiction.id)) })
2365
2924
  ] }),
2925
+ showSubLocations && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "8px", sx: styles.locationSection, children: [
2926
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "4px", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.locationLabelRow, children: [
2927
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.locationLabel, children: "Sub-location" }),
2928
+ selectedSubLocationIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.locationBadge, children: selectedSubLocationIds.length })
2929
+ ] }) }),
2930
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
2931
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2932
+ import_react4.Button,
2933
+ {
2934
+ variant: "unstyled",
2935
+ isDisabled: selectedLocationIds.length === 0 || filteredSubLocations.length === 0,
2936
+ sx: {
2937
+ ...styles.locationTrigger,
2938
+ ...selectedLocationIds.length === 0 || filteredSubLocations.length === 0 ? { opacity: 0.5, cursor: "not-allowed" } : {}
2939
+ },
2940
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { justify: "space-between", w: "full", children: [
2941
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2942
+ import_react4.Text,
2943
+ {
2944
+ sx: {
2945
+ ...styles.locationTriggerText,
2946
+ ...selectedSubLocationIds.length > 0 ? { color: "#303030" } : {}
2947
+ },
2948
+ noOfLines: 1,
2949
+ children: selectedSubLocationIds.length > 0 ? filteredSubLocations.filter((s) => selectedSubLocationIds.includes(s.id)).map((s) => s.name).join(", ") : "Select sub-locations"
2950
+ }
2951
+ ),
2952
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
2953
+ ] })
2954
+ }
2955
+ ) }),
2956
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverContent, { sx: styles.popoverContent, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverBody, { p: 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: filteredSubLocations.map((sub) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { as: "label", sx: { ...styles.optionRow, cursor: "pointer" }, children: [
2957
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2958
+ import_react4.Checkbox,
2959
+ {
2960
+ id: `sublocation-${sub.id}`,
2961
+ isChecked: selectedSubLocationIds.includes(sub.id),
2962
+ onChange: () => handleToggleSubLocation(sub.id)
2963
+ }
2964
+ ),
2965
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optionLabel, children: sub.name })
2966
+ ] }, sub.id)) }) }) })
2967
+ ] })
2968
+ ] }),
2969
+ showTeams && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "8px", sx: styles.locationSection, children: [
2970
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "4px", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.locationLabelRow, children: [
2971
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.locationLabel, children: "Teams" }),
2972
+ selectedTeamIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.locationBadge, children: selectedTeamIds.length })
2973
+ ] }) }),
2974
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
2975
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2976
+ import_react4.Button,
2977
+ {
2978
+ variant: "unstyled",
2979
+ isDisabled: !teams || teams.length === 0,
2980
+ sx: {
2981
+ ...styles.locationTrigger,
2982
+ ...!teams || teams.length === 0 ? { opacity: 0.5, cursor: "not-allowed" } : {}
2983
+ },
2984
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { justify: "space-between", w: "full", children: [
2985
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2986
+ import_react4.Text,
2987
+ {
2988
+ sx: {
2989
+ ...styles.locationTriggerText,
2990
+ ...selectedTeamIds.length > 0 ? { color: "#303030" } : {}
2991
+ },
2992
+ noOfLines: 1,
2993
+ children: selectedTeamIds.length > 0 ? (teams != null ? teams : []).filter((t) => selectedTeamIds.includes(t.id)).map((t) => t.name).join(", ") : "Select teams"
2994
+ }
2995
+ ),
2996
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
2997
+ ] })
2998
+ }
2999
+ ) }),
3000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverContent, { sx: styles.popoverContent, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverBody, { p: 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: (teams != null ? teams : []).map((team) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { as: "label", sx: { ...styles.optionRow, cursor: "pointer" }, children: [
3001
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3002
+ import_react4.Checkbox,
3003
+ {
3004
+ id: `team-${team.id}`,
3005
+ isChecked: selectedTeamIds.includes(team.id),
3006
+ onChange: () => handleToggleTeam(team.id)
3007
+ }
3008
+ ),
3009
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optionLabel, children: team.name })
3010
+ ] }, team.id)) }) }) })
3011
+ ] })
3012
+ ] }),
3013
+ scheduleWeekStartYmd ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "8px", sx: styles.locationSection, children: [
3014
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: { ...styles.locationLabel, fontWeight: 600 }, children: "Generate schedule for:" }),
3015
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.HStack, { spacing: 2, flexWrap: "wrap", children: weekDatesOrdered.map((ymd) => {
3016
+ const selected = selectedScheduleDates.includes(ymd);
3017
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3018
+ import_react4.Button,
3019
+ {
3020
+ type: "button",
3021
+ variant: "unstyled",
3022
+ onClick: () => toggleScheduleDate(ymd),
3023
+ borderRadius: "full",
3024
+ minW: "40px",
3025
+ h: "40px",
3026
+ px: 0,
3027
+ fontSize: "sm",
3028
+ fontWeight: 600,
3029
+ borderWidth: selected ? "2px" : "1px",
3030
+ borderStyle: "solid",
3031
+ borderColor: selected ? "#5b44ff" : "#e2e8f0",
3032
+ bg: selected ? "#ede9fe" : "#ffffff",
3033
+ color: selected ? "#5b44ff" : "#303030",
3034
+ "aria-pressed": selected,
3035
+ "aria-label": `${weekdayTwoLetterFromYmd(ymd)} ${ymd}`,
3036
+ children: weekdayTwoLetterFromYmd(ymd)
3037
+ },
3038
+ ymd
3039
+ );
3040
+ }) })
3041
+ ] }) : null,
2366
3042
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "16px", sx: styles.hardConstraintsSection, children: [
2367
3043
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "4px", children: [
2368
3044
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.hardConstraintsLabelRow, children: [
2369
3045
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsLabel, children: "Required rules" }),
2370
- selectedConstraintIds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.hardConstraintsBadge, children: selectedConstraintIds.length }) : null
3046
+ selectedConstraintDisplayOptions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.hardConstraintsBadge, children: selectedConstraintDisplayOptions.length }) : null
2371
3047
  ] }),
2372
3048
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsSubtitle, children: "Assigned shifts will be required to meet the following rules:" })
2373
3049
  ] }),
@@ -2379,35 +3055,30 @@ function AutoSchedulerModal({
2379
3055
  {
2380
3056
  sx: {
2381
3057
  ...styles.hardConstraintsTriggerText,
2382
- ...selectedConstraintIds.length > 0 ? { color: "#303030" } : {}
3058
+ ...selectedConstraintDisplayOptions.length > 0 ? { color: "#303030" } : {}
2383
3059
  },
2384
3060
  noOfLines: 1,
2385
- children: selectedConstraintIds.length > 0 ? HARD_CONSTRAINT_OPTIONS.filter((o) => selectedConstraintIds.includes(o.id)).map((o) => o.name).join(", ") : "Select rules"
3061
+ children: selectedConstraintDisplayOptions.length > 0 ? selectedConstraintDisplayOptions.map((o) => o.name).join(", ") : "Select rules"
2386
3062
  }
2387
3063
  ),
2388
3064
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
2389
3065
  ] }) }) }),
2390
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverContent, { sx: styles.hardConstraintsPopoverContent, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverBody, { p: 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "4px", children: HARD_CONSTRAINT_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2391
- import_react4.HStack,
2392
- {
2393
- as: "label",
2394
- sx: { ...styles.hardConstraintsOptionRow, cursor: "pointer" },
2395
- children: [
2396
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2397
- import_react4.Checkbox,
2398
- {
2399
- id: `constraint-${option.id}`,
2400
- isChecked: selectedConstraintIds.includes(option.id),
2401
- onChange: () => handleToggleConstraint(option.id)
2402
- }
2403
- ),
2404
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsOptionLabel, children: option.name })
2405
- ]
2406
- },
2407
- option.id
2408
- )) }) }) })
3066
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverContent, { sx: styles.hardConstraintsPopoverContent, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverBody, { p: 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "4px", children: effectiveHardConstraintOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { as: "label", sx: { ...styles.hardConstraintsOptionRow, cursor: "pointer" }, children: [
3067
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3068
+ import_react4.Checkbox,
3069
+ {
3070
+ id: `constraint-${option.id}`,
3071
+ isChecked: selectedConstraintIds.includes(option.id) || Boolean(option.readOnly && option.defaultSelected),
3072
+ isDisabled: option.readOnly,
3073
+ onChange: () => {
3074
+ if (!option.readOnly) handleToggleConstraint(option.id);
3075
+ }
3076
+ }
3077
+ ),
3078
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsOptionLabel, children: option.name })
3079
+ ] }, option.id)) }) }) })
2409
3080
  ] }),
2410
- selectedConstraints.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { sx: styles.hardConstraintsInputsContainer, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "16px", children: selectedConstraints.map((option, index) => {
3081
+ selectedConstraints.filter((o) => o.defaultValue != null).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { sx: styles.hardConstraintsInputsContainer, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "16px", children: selectedConstraints.filter((o) => o.defaultValue != null).map((option, index) => {
2411
3082
  var _a;
2412
3083
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { sx: styles.hardConstraintsInputItem, children: [
2413
3084
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Text, { sx: styles.hardConstraintsInputLabel, children: [
@@ -2425,13 +3096,13 @@ function AutoSchedulerModal({
2425
3096
  ...prev,
2426
3097
  [option.id]: event.target.value
2427
3098
  })),
2428
- min: option.id === "max_work_days_per_week" ? 1 : void 0,
2429
- max: option.id === "max_work_days_per_week" ? 6 : void 0,
2430
- step: option.id === "max_work_days_per_week" ? void 0 : "any",
3099
+ min: option.min,
3100
+ max: option.max,
3101
+ step: option.max != null ? void 0 : "any",
2431
3102
  sx: styles.hardConstraintsInputField
2432
3103
  }
2433
3104
  ),
2434
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: option.id === "max_work_days_per_week" ? "days" : "hours" })
3105
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: option.suffix })
2435
3106
  ] })
2436
3107
  ] }, option.id);
2437
3108
  }) }) }) : null
@@ -2440,89 +3111,100 @@ function AutoSchedulerModal({
2440
3111
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "4px", children: [
2441
3112
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.optimizationLabelRow, children: [
2442
3113
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationLabel, children: "Optimization" }),
2443
- selectedOptimizationIds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.optimizationBadge, children: selectedOptimizationIds.length }) : null
3114
+ selectedOptimizationDisplayOptions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.optimizationBadge, children: selectedOptimizationDisplayOptions.length }) : null
2444
3115
  ] }),
2445
3116
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationSubtitle, children: "Your schedule will be optimized based on the following priorities:" })
2446
3117
  ] }),
2447
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
3118
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "top-start", strategy: "fixed", matchWidth: true, isLazy: true, gutter: 4, children: [
2448
3119
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Button, { variant: "unstyled", sx: styles.optimizationTrigger, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { justify: "space-between", w: "full", children: [
2449
3120
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2450
3121
  import_react4.Text,
2451
3122
  {
2452
3123
  sx: {
2453
3124
  ...styles.optimizationTriggerText,
2454
- ...selectedOptimizationIds.length > 0 ? { color: "#303030" } : {}
3125
+ ...selectedOptimizationDisplayOptions.length > 0 ? { color: "#303030" } : {}
2455
3126
  },
2456
3127
  noOfLines: 1,
2457
- children: selectedOptimizationIds.length > 0 ? optimizationOptions.filter((o) => selectedOptimizationIds.includes(o.id)).map((o) => o.title).join(", ") : "Select optimization priorities"
3128
+ children: selectedOptimizationDisplayOptions.length > 0 ? selectedOptimizationDisplayOptions.map((o) => o.title).join(", ") : "Select optimization priorities"
2458
3129
  }
2459
3130
  ),
2460
3131
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
2461
3132
  ] }) }) }),
2462
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverContent, { sx: styles.optimizationPopoverContent, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverBody, { p: 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "4px", children: OPTIMIZATION_OPTION_GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { children: [
2463
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationGroupHeader, children: group.name }),
2464
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: group.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2465
- import_react4.Stack,
2466
- {
2467
- as: "label",
2468
- spacing: "2px",
2469
- sx: { ...styles.optimizationOptionRow, cursor: "pointer" },
2470
- children: [
2471
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { spacing: "8px", children: [
2472
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2473
- import_react4.Checkbox,
2474
- {
2475
- id: `optimization-${option.id}`,
2476
- isChecked: selectedOptimizationIds.includes(option.id),
2477
- isDisabled: !selectedOptimizationIds.includes(option.id) && selectedOptimizationIds.length >= 3,
2478
- onChange: () => handleToggleOptimization(option.id)
2479
- }
2480
- ),
2481
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionLabel, children: option.title })
2482
- ] }),
2483
- option.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionHelp, children: option.subtitle }) : null
2484
- ]
3133
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Portal, { appendToParentPortal: false, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3134
+ import_react4.PopoverContent,
3135
+ {
3136
+ sx: {
3137
+ ...styles.optimizationPopoverContent,
3138
+ maxH: "min(560px, calc(100vh - 96px))",
3139
+ overflowY: "auto",
3140
+ zIndex: 1500,
3141
+ scrollbarWidth: "none",
3142
+ msOverflowStyle: "none",
3143
+ "&::-webkit-scrollbar": { display: "none" }
2485
3144
  },
2486
- option.id
2487
- )) })
2488
- ] }, group.id)) }) }) })
3145
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverBody, { p: 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "4px", children: effectiveSoftConstraintGroups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { children: [
3146
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationGroupHeader, children: group.name }),
3147
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: group.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { children: [
3148
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3149
+ import_react4.Stack,
3150
+ {
3151
+ as: "label",
3152
+ spacing: "2px",
3153
+ sx: { ...styles.optimizationOptionRow, cursor: "pointer" },
3154
+ children: [
3155
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { spacing: "8px", children: [
3156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3157
+ import_react4.Checkbox,
3158
+ {
3159
+ id: `optimization-${option.id}`,
3160
+ isChecked: selectedOptimizationIds.includes(option.id) || Boolean(option.readOnly && option.defaultSelected),
3161
+ isDisabled: option.readOnly || !selectedOptimizationIds.includes(option.id) && selectedOptimizationIds.length >= 3,
3162
+ onChange: () => {
3163
+ if (!option.readOnly) handleToggleOptimization(option.id);
3164
+ }
3165
+ }
3166
+ ),
3167
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionLabel, children: option.title })
3168
+ ] }),
3169
+ option.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionHelp, children: option.subtitle }) : null
3170
+ ]
3171
+ }
3172
+ ),
3173
+ optimizationFooter && (option.id === "cost_to_sales_ratio" || option.id === "maximize_sales") ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { px: "12px", pt: "4px", pb: "6px", children: resolveOptimizationFooter(optimizationFooter, option.id) }) : null
3174
+ ] }, option.id)) })
3175
+ ] }, group.id)) }) })
3176
+ }
3177
+ ) })
2489
3178
  ] }),
2490
3179
  selectedOptimizationOptions.length >= 2 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { sx: styles.optimizationInputsContainer, children: [
2491
3180
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "16px", children: selectedOptimizationOptions.map((option, index) => {
2492
3181
  var _a;
2493
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2494
- import_react4.Stack,
2495
- {
2496
- sx: styles.optimizationInputItem,
2497
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { sx: styles.hardConstraintsInputItem, children: [
2498
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Text, { sx: styles.hardConstraintsInputLabel, children: [
2499
- index + 1,
2500
- ". ",
2501
- option.title
2502
- ] }),
2503
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.InputGroup, { children: [
2504
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2505
- import_react4.Input,
2506
- {
2507
- type: "text",
2508
- value: (_a = optimizationValues[`${option.id}_weightPoints`]) != null ? _a : "1",
2509
- onChange: (e) => {
2510
- const val = e.target.value;
2511
- if (val !== "" && (!/^\d+$/.test(val) || Number(val) < 1)) return;
2512
- setOptimizationValues((prev) => ({
2513
- ...prev,
2514
- [`${option.id}_weightPoints`]: val
2515
- }));
2516
- },
2517
- sx: styles.hardConstraintsInputField
2518
- }
2519
- ),
2520
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: "points" })
2521
- ] })
2522
- ] })
2523
- },
2524
- option.id
2525
- );
3182
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { sx: styles.optimizationInputItem, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { sx: styles.hardConstraintsInputItem, children: [
3183
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Text, { sx: styles.hardConstraintsInputLabel, children: [
3184
+ index + 1,
3185
+ ". ",
3186
+ option.title
3187
+ ] }),
3188
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.InputGroup, { children: [
3189
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3190
+ import_react4.Input,
3191
+ {
3192
+ type: "text",
3193
+ value: (_a = optimizationValues[`${option.id}_weightPoints`]) != null ? _a : "1",
3194
+ onChange: (e) => {
3195
+ const val = e.target.value;
3196
+ if (val !== "" && (!/^\d+$/.test(val) || Number(val) < 1)) return;
3197
+ setOptimizationValues((prev) => ({
3198
+ ...prev,
3199
+ [`${option.id}_weightPoints`]: val
3200
+ }));
3201
+ },
3202
+ sx: styles.hardConstraintsInputField
3203
+ }
3204
+ ),
3205
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: "points" })
3206
+ ] })
3207
+ ] }) }, option.id);
2526
3208
  }) }),
2527
3209
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Divider, { sx: { ...styles.optimizationDivider, my: "16px" } }),
2528
3210
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationSummary, children: allocationSummary })
@@ -2531,7 +3213,9 @@ function AutoSchedulerModal({
2531
3213
  ] });
2532
3214
  const stepsContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "16px", sx: styles.stepsContainer, children: [
2533
3215
  showFailed ? violationPanel : null,
2534
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "14px", sx: styles.stepsList, children: SCHEDULE_STEPS.map((step, index) => {
3216
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "14px", sx: styles.stepsList, children: SCHEDULE_STEPS.map((rawStep, index) => {
3217
+ const isQueueWait = index === 1 && solveStatus === "QUEUED";
3218
+ const step = isQueueWait ? { title: "Waiting in queue\u2026", subtitle: void 0 } : rawStep;
2535
3219
  const status = stepStatusForIndex(index);
2536
3220
  const isFailedStep = showFailed && index === activeStepIndex;
2537
3221
  const iconStyles = {
@@ -2549,26 +3233,26 @@ function AutoSchedulerModal({
2549
3233
  ...isFailedStep ? styles.stepTitleFailed : null
2550
3234
  };
2551
3235
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.stepRow, children: [
2552
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { sx: iconStyles, children: isFailedStep ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FailedIcon, { width: 12, height: 12 }) : status === "active" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Spinner, { size: "sm", thickness: "2px", speed: "0.7s" }) : status === "done" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { width: 12, height: 12 }) : null }),
3236
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { sx: iconStyles, children: isFailedStep ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FailedIcon, { width: 12, height: 12 }) : status === "active" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Spinner, { size: "sm", thickness: "2px", speed: "0.7s" }) : status === "done" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { width: 10, height: 10 }) : null }),
2553
3237
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { children: [
2554
3238
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: titleStyles, children: step.title }),
2555
3239
  status === "active" && step.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.stepSubtitle, children: step.subtitle }) : null
2556
3240
  ] })
2557
- ] }, step.title);
3241
+ ] }, rawStep.title);
2558
3242
  }) })
2559
3243
  ] });
2560
- const modalFooter = showConfiguration ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.ModalFooter, { sx: styles.footer, children: [
3244
+ const footerButtons = showConfiguration ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2561
3245
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Button, { variant: "outline", onClick: onClose, sx: styles.cancelButton, children: cancelLabel }),
2562
3246
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2563
3247
  import_react4.Button,
2564
3248
  {
2565
3249
  onClick: handleCreateSchedule,
2566
- isDisabled: selectedCount === 0 || openShifts.length === 0 || hasEmptyInputs,
3250
+ isDisabled: selectedCount === 0 || !onGenerate && openShifts.length === 0 || hasEmptyInputs || Boolean(scheduleWeekStartYmd) && selectedScheduleDates.length === 0,
2567
3251
  sx: styles.primaryButton,
2568
3252
  children: primaryActionLabel
2569
3253
  }
2570
3254
  )
2571
- ] }) : showFailed ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.ModalFooter, { sx: styles.failedFooter, children: [
3255
+ ] }) : showFailed ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2572
3256
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2573
3257
  import_react4.Button,
2574
3258
  {
@@ -2577,10 +3261,11 @@ function AutoSchedulerModal({
2577
3261
  if (lastFailure) {
2578
3262
  onFixManually == null ? void 0 : onFixManually(lastFailure, {
2579
3263
  selectedLocationIds,
2580
- selectedConstraintIds,
3264
+ selectedConstraintIds: effectiveSelectedConstraintIds,
2581
3265
  constraintValues,
2582
- selectedOptimizationIds,
2583
- optimizationValues
3266
+ selectedOptimizationIds: effectiveSelectedOptimizationIds,
3267
+ optimizationValues,
3268
+ selectedBuiltinIds: defaultSelectedBuiltinIds
2584
3269
  });
2585
3270
  }
2586
3271
  onClose();
@@ -2591,31 +3276,75 @@ function AutoSchedulerModal({
2591
3276
  ),
2592
3277
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Button, { onClick: handleAdjustConstraints, sx: styles.failedPrimaryButton, children: "Adjust constraints" })
2593
3278
  ] }) : null;
2594
- const modalContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Modal, { isOpen, onClose, isCentered: true, motionPreset: "scale", closeOnOverlayClick: false, closeOnEsc: false, children: [
2595
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalOverlay, { sx: styles.overlay }),
2596
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.ModalContent, { sx: styles.content, children: [
2597
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalHeader, { sx: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { justify: "space-between", w: "full", children: [
2598
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.title, children: modalTitle }),
2599
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2600
- import_react4.IconButton,
2601
- {
2602
- "aria-label": "Close modal",
2603
- variant: "ghost",
2604
- size: "sm",
2605
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}),
2606
- onClick: onClose,
2607
- sx: styles.closeButton
2608
- }
2609
- )
2610
- ] }) }),
2611
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalBody, { sx: showConfiguration ? styles.body : { ...styles.body, ...styles.creatingBody }, children: showConfiguration ? configurationContent : stepsContent }),
2612
- modalFooter
2613
- ] })
2614
- ] });
3279
+ const bodyStyles = showConfiguration ? styles.body : { ...styles.body, ...styles.creatingBody };
3280
+ const bodyContent = showConfiguration ? configurationContent : stepsContent;
3281
+ const modalContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3282
+ import_react4.Modal,
3283
+ {
3284
+ isOpen,
3285
+ onClose,
3286
+ isCentered: true,
3287
+ motionPreset: "scale",
3288
+ closeOnOverlayClick: false,
3289
+ closeOnEsc: false,
3290
+ trapFocus: false,
3291
+ children: [
3292
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalOverlay, { sx: styles.overlay }),
3293
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.ModalContent, { sx: styles.content, children: [
3294
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalHeader, { sx: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { justify: "space-between", w: "full", children: [
3295
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.title, children: modalTitle }),
3296
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3297
+ import_react4.IconButton,
3298
+ {
3299
+ "aria-label": "Close modal",
3300
+ variant: "ghost",
3301
+ size: "sm",
3302
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}),
3303
+ onClick: onClose,
3304
+ sx: styles.closeButton
3305
+ }
3306
+ )
3307
+ ] }) }),
3308
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalBody, { sx: bodyStyles, children: bodyContent }),
3309
+ footerButtons && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalFooter, { sx: showFailed ? styles.failedFooter : styles.footer, children: footerButtons })
3310
+ ] })
3311
+ ]
3312
+ }
3313
+ );
3314
+ const drawerFooterNode = footerButtons ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3315
+ import_react4.HStack,
3316
+ {
3317
+ align: "center",
3318
+ justify: "flex-end",
3319
+ px: 6,
3320
+ h: "68px",
3321
+ bg: "gray.50",
3322
+ borderTop: "1px solid",
3323
+ borderColor: "gray.200",
3324
+ gap: 2,
3325
+ flexShrink: 0,
3326
+ w: "full",
3327
+ children: footerButtons
3328
+ }
3329
+ ) : void 0;
3330
+ const drawerContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3331
+ import_web_ui.Drawer,
3332
+ {
3333
+ isOpen,
3334
+ onClose,
3335
+ title: modalTitle,
3336
+ closeOnOverlayClick: false,
3337
+ maxW: DRAWER_WIDTH_MAP[drawerSize],
3338
+ bodySx: bodyStyles,
3339
+ footer: drawerFooterNode,
3340
+ children: bodyContent
3341
+ }
3342
+ );
3343
+ const content = displayMode === "drawer" ? drawerContent : modalContent;
2615
3344
  if (theme || cssVarsRoot) {
2616
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ChakraProvider, { theme: mergedTheme, cssVarsRoot, children: modalContent });
3345
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ChakraProvider, { theme: mergedTheme, cssVarsRoot, children: content });
2617
3346
  }
2618
- return modalContent;
3347
+ return content;
2619
3348
  }
2620
3349
 
2621
3350
  // src/AutoSchedulerModalWithProvider.tsx
@@ -2692,11 +3421,17 @@ var RequestError = class extends Error {
2692
3421
  0 && (module.exports = {
2693
3422
  AutoSchedulerModal,
2694
3423
  AutoSchedulerModalWithProvider,
3424
+ InfeasibleScheduleError,
3425
+ MAXIMIZE_CONVERSION_RATE_RULE_ID,
2695
3426
  RequestError,
3427
+ TRAFFIC_FORECAST_INCENTIVE_RULE_ID,
2696
3428
  ViolatedConstraintsPanel,
2697
3429
  ViolatedConstraintsPanelWithProvider,
2698
3430
  buildRulesPayload,
2699
3431
  buildSchedulePayload,
2700
- getFriendlyRuleName
3432
+ defaultSelectedScheduleDates,
3433
+ getFriendlyRuleName,
3434
+ getSequentialWeekDatesFromWeekStart,
3435
+ weekdayTwoLetterFromYmd
2701
3436
  });
2702
3437
  //# sourceMappingURL=index.cjs.map