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