@easyteam/auto-scheduler-modal-ui 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +917 -316
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +207 -8
- package/dist/index.d.ts +207 -8
- package/dist/index.js +910 -314
- package/dist/index.js.map +1 -1
- package/package.json +27 -25
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
|
|
33
|
-
__export(
|
|
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
|
-
|
|
44
|
+
defaultSelectedScheduleDates: () => defaultSelectedScheduleDates,
|
|
45
|
+
getFriendlyRuleName: () => getFriendlyRuleName,
|
|
46
|
+
getSequentialWeekDatesFromWeekStart: () => getSequentialWeekDatesFromWeekStart,
|
|
47
|
+
weekdayTwoLetterFromYmd: () => weekdayTwoLetterFromYmd
|
|
42
48
|
});
|
|
43
|
-
module.exports = __toCommonJS(
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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: "#
|
|
730
|
+
bg: "#5338ff",
|
|
719
731
|
color: "white",
|
|
720
732
|
borderRadius: "10px"
|
|
721
733
|
},
|
|
@@ -1251,6 +1263,9 @@ var UI_TO_BACKEND_RULE_ID = {
|
|
|
1251
1263
|
maximize_sales: "maximize-sales-performance",
|
|
1252
1264
|
staff_preferences: "staff-open-shift-preferences"
|
|
1253
1265
|
};
|
|
1266
|
+
var MAXIMIZE_CONVERSION_RATE_RULE_ID = "maximize-conversion-rate";
|
|
1267
|
+
var TRAFFIC_FORECAST_INCENTIVE_RULE_ID = "traffic-forecast-incentive";
|
|
1268
|
+
var SALES_OPTIMIZATION_UI_IDS = /* @__PURE__ */ new Set(["maximize_sales", "cost_to_sales_ratio"]);
|
|
1254
1269
|
var BACKEND_TO_UI_RULE_ID = Object.fromEntries(
|
|
1255
1270
|
Object.entries(UI_TO_BACKEND_RULE_ID).map(([ui, backend]) => [backend, ui])
|
|
1256
1271
|
);
|
|
@@ -1327,11 +1342,24 @@ function hasAnyParameterOverrides(selectedConstraintIds, constraintValues, selec
|
|
|
1327
1342
|
}
|
|
1328
1343
|
return false;
|
|
1329
1344
|
}
|
|
1330
|
-
function
|
|
1345
|
+
function appendSiblingRuleId(ruleIds, siblingId) {
|
|
1346
|
+
if (!ruleIds.includes(siblingId)) {
|
|
1347
|
+
ruleIds.push(siblingId);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function buildRulesPayload(selectedConstraintIds, constraintValues, selectedOptimizationIds, optimizationValues, siblings = {}) {
|
|
1331
1351
|
const allSelectedRuleIds = [
|
|
1332
1352
|
...selectedConstraintIds.map(toBackendRuleId),
|
|
1333
1353
|
...selectedOptimizationIds.map(toBackendRuleId)
|
|
1334
1354
|
].filter((id) => Boolean(id));
|
|
1355
|
+
const salesOptSelected = selectedOptimizationIds.some((id) => SALES_OPTIMIZATION_UI_IDS.has(id));
|
|
1356
|
+
const maximizeSalesSelected = selectedOptimizationIds.includes("maximize_sales");
|
|
1357
|
+
if (siblings.includeConversionRate && salesOptSelected) {
|
|
1358
|
+
appendSiblingRuleId(allSelectedRuleIds, MAXIMIZE_CONVERSION_RATE_RULE_ID);
|
|
1359
|
+
}
|
|
1360
|
+
if (siblings.includeTrafficIncentive && maximizeSalesSelected) {
|
|
1361
|
+
appendSiblingRuleId(allSelectedRuleIds, TRAFFIC_FORECAST_INCENTIVE_RULE_ID);
|
|
1362
|
+
}
|
|
1335
1363
|
if (allSelectedRuleIds.length === 0) {
|
|
1336
1364
|
return {};
|
|
1337
1365
|
}
|
|
@@ -1361,7 +1389,7 @@ function buildRulesPayload(selectedConstraintIds, constraintValues, selectedOpti
|
|
|
1361
1389
|
return { ruleIds: allSelectedRuleIds, ruleOverrides };
|
|
1362
1390
|
}
|
|
1363
1391
|
function toApiShift(shift, includeEmployeePreferences) {
|
|
1364
|
-
var _a
|
|
1392
|
+
var _a;
|
|
1365
1393
|
const startDateTime = "startDateTime" in shift ? shift.startDateTime : shift.startTime;
|
|
1366
1394
|
const endDateTime = "endDateTime" in shift ? shift.endDateTime : shift.endTime;
|
|
1367
1395
|
return {
|
|
@@ -1369,8 +1397,8 @@ function toApiShift(shift, includeEmployeePreferences) {
|
|
|
1369
1397
|
startDateTime,
|
|
1370
1398
|
endDateTime,
|
|
1371
1399
|
...shift.employeeId ? { employeeId: String(shift.employeeId) } : {},
|
|
1372
|
-
requiredPosition:
|
|
1373
|
-
requiredCount: (
|
|
1400
|
+
requiredPosition: shift.requiredPosition || void 0,
|
|
1401
|
+
requiredCount: (_a = shift.requiredCount) != null ? _a : 1,
|
|
1374
1402
|
location: shift.location ? {
|
|
1375
1403
|
id: String(shift.location.id),
|
|
1376
1404
|
timezone: "timezone" in shift.location ? shift.location.timezone : void 0,
|
|
@@ -1400,9 +1428,25 @@ function toApiLocation(location) {
|
|
|
1400
1428
|
id: String(location.id),
|
|
1401
1429
|
timezone: (_a = location.timezone) != null ? _a : void 0,
|
|
1402
1430
|
jurisdiction: (_b = location.jurisdiction) != null ? _b : void 0,
|
|
1403
|
-
tags: "tags" in location ? location.tags : void 0
|
|
1431
|
+
tags: "tags" in location ? location.tags : void 0,
|
|
1432
|
+
properties: location.properties ? location.properties : void 0
|
|
1404
1433
|
};
|
|
1405
1434
|
}
|
|
1435
|
+
function employeeHasConversionRate(employee) {
|
|
1436
|
+
var _a;
|
|
1437
|
+
const value = (_a = employee.properties) == null ? void 0 : _a.conversionRate;
|
|
1438
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
1439
|
+
}
|
|
1440
|
+
function employeeTouchesSelectedLocations(employee, selectedLocationIds) {
|
|
1441
|
+
var _a;
|
|
1442
|
+
const ids = (_a = employee.locationIds) != null ? _a : [];
|
|
1443
|
+
return ids.some((id) => selectedLocationIds.includes(String(id)));
|
|
1444
|
+
}
|
|
1445
|
+
function hasConversionRateInSelectedLocations(employees, selectedLocationIds) {
|
|
1446
|
+
return employees.some(
|
|
1447
|
+
(employee) => employeeTouchesSelectedLocations(employee, selectedLocationIds) && employeeHasConversionRate(employee)
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1406
1450
|
function buildSchedulePayload(input) {
|
|
1407
1451
|
const {
|
|
1408
1452
|
employees,
|
|
@@ -1413,7 +1457,11 @@ function buildSchedulePayload(input) {
|
|
|
1413
1457
|
selectedConstraintIds,
|
|
1414
1458
|
constraintValues,
|
|
1415
1459
|
selectedOptimizationIds,
|
|
1416
|
-
optimizationValues
|
|
1460
|
+
optimizationValues,
|
|
1461
|
+
trafficForecast,
|
|
1462
|
+
constraintDefinitionIds,
|
|
1463
|
+
constraintDefinitions,
|
|
1464
|
+
organizationId
|
|
1417
1465
|
} = input;
|
|
1418
1466
|
const locationsMap = /* @__PURE__ */ new Map();
|
|
1419
1467
|
for (const j of jurisdictions) {
|
|
@@ -1422,19 +1470,45 @@ function buildSchedulePayload(input) {
|
|
|
1422
1470
|
}
|
|
1423
1471
|
}
|
|
1424
1472
|
const selectedLocations = selectedLocationIds.map((id) => locationsMap.get(id)).filter((loc) => Boolean(loc));
|
|
1473
|
+
const scopedOpenShifts = openShifts.filter(
|
|
1474
|
+
(shift) => {
|
|
1475
|
+
var _a;
|
|
1476
|
+
return selectedLocationIds.includes(String((_a = shift.location) == null ? void 0 : _a.id));
|
|
1477
|
+
}
|
|
1478
|
+
);
|
|
1479
|
+
const includeConversionRate = hasConversionRateInSelectedLocations(
|
|
1480
|
+
employees,
|
|
1481
|
+
selectedLocationIds
|
|
1482
|
+
);
|
|
1483
|
+
const scopedTrafficForecast = (trafficForecast != null ? trafficForecast : []).filter(
|
|
1484
|
+
(entry) => selectedLocationIds.includes(String(entry.locationId))
|
|
1485
|
+
);
|
|
1486
|
+
const includeTrafficIncentive = scopedTrafficForecast.length > 0;
|
|
1425
1487
|
const rules = buildRulesPayload(
|
|
1426
1488
|
selectedConstraintIds,
|
|
1427
1489
|
constraintValues,
|
|
1428
1490
|
selectedOptimizationIds,
|
|
1429
|
-
optimizationValues
|
|
1491
|
+
optimizationValues,
|
|
1492
|
+
{ includeConversionRate, includeTrafficIncentive }
|
|
1430
1493
|
);
|
|
1431
1494
|
const includeEmployeePreferences = selectedOptimizationIds.includes("staff_preferences");
|
|
1495
|
+
const hasIds = Boolean(constraintDefinitionIds && constraintDefinitionIds.length > 0);
|
|
1496
|
+
const hasDefs = Boolean(constraintDefinitions && constraintDefinitions.length > 0);
|
|
1497
|
+
const hasRules = hasIds || hasDefs;
|
|
1498
|
+
if (hasIds && !organizationId) {
|
|
1499
|
+
throw new Error("organizationId is required when constraintDefinitionIds are provided");
|
|
1500
|
+
}
|
|
1501
|
+
const trafficRuleActive = includeTrafficIncentive && selectedOptimizationIds.includes("maximize_sales");
|
|
1432
1502
|
return {
|
|
1433
1503
|
employees: employees.map(toApiEmployee),
|
|
1434
|
-
shifts:
|
|
1504
|
+
shifts: scopedOpenShifts.map((shift) => toApiShift(shift, includeEmployeePreferences)),
|
|
1435
1505
|
locations: selectedLocations.map(toApiLocation),
|
|
1436
1506
|
...timeOffs && timeOffs.length > 0 ? { timeOffs } : {},
|
|
1437
|
-
...rules
|
|
1507
|
+
...rules,
|
|
1508
|
+
...trafficRuleActive ? { trafficForecast: scopedTrafficForecast } : {},
|
|
1509
|
+
...hasIds ? { constraintDefinitionIds } : {},
|
|
1510
|
+
...hasDefs ? { constraintDefinitions } : {},
|
|
1511
|
+
...hasRules && organizationId ? { organizationId } : {}
|
|
1438
1512
|
};
|
|
1439
1513
|
}
|
|
1440
1514
|
|
|
@@ -1457,7 +1531,7 @@ async function submitSolveJob(baseURL, payload, options) {
|
|
|
1457
1531
|
headers.Authorization = options.authorization;
|
|
1458
1532
|
}
|
|
1459
1533
|
const response = await axiosInstance.post(
|
|
1460
|
-
`${cleanBaseUrl}/api/schedule/solve-async`,
|
|
1534
|
+
withHorizonQuery(`${cleanBaseUrl}/api/schedule/solve-async`, options),
|
|
1461
1535
|
payload,
|
|
1462
1536
|
{ headers: Object.keys(headers).length > 0 ? headers : void 0 }
|
|
1463
1537
|
);
|
|
@@ -1499,6 +1573,16 @@ async function getJobSolution(baseURL, jobId, options) {
|
|
|
1499
1573
|
);
|
|
1500
1574
|
return response.data;
|
|
1501
1575
|
}
|
|
1576
|
+
function withHorizonQuery(url, options) {
|
|
1577
|
+
if (!(options == null ? void 0 : options.horizonStartDate) || !(options == null ? void 0 : options.horizonEndDate)) {
|
|
1578
|
+
return url;
|
|
1579
|
+
}
|
|
1580
|
+
const params = new URLSearchParams({
|
|
1581
|
+
dateRangeStart: options.horizonStartDate,
|
|
1582
|
+
dateRangeEnd: options.horizonEndDate
|
|
1583
|
+
});
|
|
1584
|
+
return `${url}?${params.toString()}`;
|
|
1585
|
+
}
|
|
1502
1586
|
|
|
1503
1587
|
// src/utils/violationMessages.ts
|
|
1504
1588
|
var SYSTEM_CONSTRAINT_MESSAGES = {
|
|
@@ -1646,17 +1730,14 @@ function getRuleBasedViolationMessage(ruleId, constraintName, description) {
|
|
|
1646
1730
|
}
|
|
1647
1731
|
|
|
1648
1732
|
// src/utils/mapViolationsToResult.ts
|
|
1649
|
-
function mapViolationsToResult(violations, summary) {
|
|
1733
|
+
function mapViolationsToResult(violations, summary, customMessages) {
|
|
1734
|
+
var _a;
|
|
1650
1735
|
const hardViolations = violations.filter((v) => v.isHardViolation === true);
|
|
1651
1736
|
const seenTitles = /* @__PURE__ */ new Set();
|
|
1652
1737
|
const violatedConstraints = [];
|
|
1653
1738
|
for (const v of hardViolations) {
|
|
1654
1739
|
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);
|
|
1740
|
+
const msg = hasRuleId ? (_a = customMessages == null ? void 0 : customMessages[v.ruleId]) != null ? _a : getRuleBasedViolationMessage(v.ruleId, v.constraintName, v.description) : getSystemConstraintMessage(v.constraintName, v.description);
|
|
1660
1741
|
if (!seenTitles.has(msg.title)) {
|
|
1661
1742
|
seenTitles.add(msg.title);
|
|
1662
1743
|
violatedConstraints.push(msg);
|
|
@@ -1700,12 +1781,15 @@ function ViolatedConstraintsPanel({
|
|
|
1700
1781
|
onSecondaryButtonClick,
|
|
1701
1782
|
generateRecommendationsURLAndHeaders,
|
|
1702
1783
|
styles: stylesProp,
|
|
1703
|
-
styleOverrides
|
|
1784
|
+
styleOverrides,
|
|
1785
|
+
errorToast
|
|
1704
1786
|
}) {
|
|
1705
1787
|
const [showRecommendations, setShowRecommendations] = (0, import_react.useState)(false);
|
|
1706
1788
|
const [isExpanded, setIsExpanded] = (0, import_react.useState)(true);
|
|
1707
1789
|
const [recommendations, setRecommendations] = (0, import_react.useState)(null);
|
|
1708
1790
|
const [isLoadingRecommendations, setIsLoadingRecommendations] = (0, import_react.useState)(false);
|
|
1791
|
+
const [isDisabledRecommendations, setIsDisabledRecommendations] = (0, import_react.useState)(false);
|
|
1792
|
+
const [recommendationsButtonText, setRecommendationsButtonText] = (0, import_react.useState)("Show recommendations");
|
|
1709
1793
|
const styles = (0, import_react.useMemo)(() => {
|
|
1710
1794
|
if (stylesProp) return stylesProp;
|
|
1711
1795
|
return buildStyles(defaultStyles, styleOverrides);
|
|
@@ -1716,13 +1800,12 @@ function ViolatedConstraintsPanel({
|
|
|
1716
1800
|
const displayRecommendations = recommendations !== null ? recommendations : violatedConstraints.recommendedFixes;
|
|
1717
1801
|
const hasRecommendations = displayRecommendations.length > 0;
|
|
1718
1802
|
const handleToggleRecommendations = (0, import_react.useCallback)(async () => {
|
|
1719
|
-
var _a, _b;
|
|
1720
1803
|
if (showRecommendations) {
|
|
1721
1804
|
setShowRecommendations(false);
|
|
1722
1805
|
return;
|
|
1723
1806
|
}
|
|
1724
|
-
setShowRecommendations(true);
|
|
1725
1807
|
if (recommendations !== null) {
|
|
1808
|
+
setShowRecommendations(true);
|
|
1726
1809
|
return;
|
|
1727
1810
|
}
|
|
1728
1811
|
if (!canFetchRecommendations) {
|
|
@@ -1741,9 +1824,21 @@ function ViolatedConstraintsPanel({
|
|
|
1741
1824
|
}
|
|
1742
1825
|
}
|
|
1743
1826
|
);
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1827
|
+
const { success, data, msg } = recResponse.data;
|
|
1828
|
+
const { isExpired, recommendations: recommendations2 } = data != null ? data : {};
|
|
1829
|
+
if (!success) {
|
|
1830
|
+
const errorMsg = msg || "Failed to fetch recommendations";
|
|
1831
|
+
errorToast == null ? void 0 : errorToast(errorMsg);
|
|
1832
|
+
if (isExpired) {
|
|
1833
|
+
setIsDisabledRecommendations(true);
|
|
1834
|
+
setRecommendationsButtonText("Daily limit reached");
|
|
1835
|
+
}
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
setShowRecommendations(true);
|
|
1839
|
+
const fetchedRecommendations = recommendations2;
|
|
1840
|
+
if (fetchedRecommendations) {
|
|
1841
|
+
setRecommendations(fetchedRecommendations.map((r) => r.recommendation));
|
|
1747
1842
|
} else {
|
|
1748
1843
|
setRecommendations([]);
|
|
1749
1844
|
}
|
|
@@ -1758,7 +1853,8 @@ function ViolatedConstraintsPanel({
|
|
|
1758
1853
|
recommendations,
|
|
1759
1854
|
canFetchRecommendations,
|
|
1760
1855
|
violatedConstraints.recommendationPayload,
|
|
1761
|
-
generateRecommendationsURLAndHeaders
|
|
1856
|
+
generateRecommendationsURLAndHeaders,
|
|
1857
|
+
errorToast
|
|
1762
1858
|
]);
|
|
1763
1859
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react2.Box, { sx: styles.violationPanel, children: [
|
|
1764
1860
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react2.HStack, { sx: styles.violationHeaderRow, children: [
|
|
@@ -1810,9 +1906,9 @@ function ViolatedConstraintsPanel({
|
|
|
1810
1906
|
variant: "outline",
|
|
1811
1907
|
size: "xs",
|
|
1812
1908
|
onClick: handleToggleRecommendations,
|
|
1813
|
-
isDisabled: isLoadingRecommendations,
|
|
1909
|
+
isDisabled: isLoadingRecommendations || isDisabledRecommendations,
|
|
1814
1910
|
sx: { ...styles.recommendationsToggle, minWidth: "140px" },
|
|
1815
|
-
children: isLoadingRecommendations ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Spinner, { size: "sm", thickness: "2px" }) : showRecommendations ? "Hide recommendations" :
|
|
1911
|
+
children: isLoadingRecommendations ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Spinner, { size: "sm", thickness: "2px" }) : showRecommendations ? "Hide recommendations" : recommendationsButtonText
|
|
1816
1912
|
}
|
|
1817
1913
|
) : null,
|
|
1818
1914
|
showSecondaryButton && secondaryButtonTitle && onSecondaryButtonClick ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -1830,13 +1926,78 @@ function ViolatedConstraintsPanel({
|
|
|
1830
1926
|
] });
|
|
1831
1927
|
}
|
|
1832
1928
|
|
|
1929
|
+
// src/weekDates.ts
|
|
1930
|
+
function parseCalendarYmd(ymd) {
|
|
1931
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(ymd).trim());
|
|
1932
|
+
if (!m) {
|
|
1933
|
+
return /* @__PURE__ */ new Date(NaN);
|
|
1934
|
+
}
|
|
1935
|
+
return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
1936
|
+
}
|
|
1937
|
+
function formatCalendarYmd(d) {
|
|
1938
|
+
const y = d.getFullYear();
|
|
1939
|
+
const mo = String(d.getMonth() + 1).padStart(2, "0");
|
|
1940
|
+
const da = String(d.getDate()).padStart(2, "0");
|
|
1941
|
+
return `${y}-${mo}-${da}`;
|
|
1942
|
+
}
|
|
1943
|
+
function getSequentialWeekDatesFromWeekStart(weekStartYmd) {
|
|
1944
|
+
const base = parseCalendarYmd(weekStartYmd);
|
|
1945
|
+
if (Number.isNaN(base.getTime())) return [];
|
|
1946
|
+
const out = [];
|
|
1947
|
+
for (let i = 0; i < 7; i++) {
|
|
1948
|
+
const d = new Date(base.getFullYear(), base.getMonth(), base.getDate() + i);
|
|
1949
|
+
out.push(formatCalendarYmd(d));
|
|
1950
|
+
}
|
|
1951
|
+
return out;
|
|
1952
|
+
}
|
|
1953
|
+
function weekdayTwoLetterFromYmd(ymd) {
|
|
1954
|
+
var _a;
|
|
1955
|
+
const labels = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
1956
|
+
const d = parseCalendarYmd(ymd);
|
|
1957
|
+
if (Number.isNaN(d.getTime())) return "?";
|
|
1958
|
+
return (_a = labels[d.getDay()]) != null ? _a : "?";
|
|
1959
|
+
}
|
|
1960
|
+
function defaultSelectedScheduleDates(orderedWeekYmds, todayYmd) {
|
|
1961
|
+
return orderedWeekYmds.filter((d) => d >= todayYmd);
|
|
1962
|
+
}
|
|
1963
|
+
function todayCalendarYmdInTimezone(timezone) {
|
|
1964
|
+
const fallback = formatCalendarYmd(/* @__PURE__ */ new Date());
|
|
1965
|
+
const tz = timezone == null ? void 0 : timezone.trim();
|
|
1966
|
+
if (!tz) return fallback;
|
|
1967
|
+
try {
|
|
1968
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
1969
|
+
timeZone: tz,
|
|
1970
|
+
year: "numeric",
|
|
1971
|
+
month: "2-digit",
|
|
1972
|
+
day: "2-digit"
|
|
1973
|
+
}).formatToParts(/* @__PURE__ */ new Date());
|
|
1974
|
+
const valueByType = new Map(parts.map((part) => [part.type, part.value]));
|
|
1975
|
+
const year = valueByType.get("year");
|
|
1976
|
+
const month = valueByType.get("month");
|
|
1977
|
+
const day = valueByType.get("day");
|
|
1978
|
+
return year && month && day ? `${year}-${month}-${day}` : fallback;
|
|
1979
|
+
} catch {
|
|
1980
|
+
return fallback;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1833
1984
|
// src/AutoSchedulerModal.tsx
|
|
1834
1985
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1986
|
+
function resolveOptimizationFooter(footer, key) {
|
|
1987
|
+
if (!footer) return null;
|
|
1988
|
+
const node = typeof footer === "function" ? footer() : footer;
|
|
1989
|
+
if ((0, import_react3.isValidElement)(node)) return (0, import_react3.cloneElement)(node, { key });
|
|
1990
|
+
return node;
|
|
1991
|
+
}
|
|
1992
|
+
var DRAWER_WIDTH_MAP = { sm: "448px", md: "578px", lg: "600px" };
|
|
1835
1993
|
function AutoSchedulerModal({
|
|
1994
|
+
displayMode,
|
|
1995
|
+
drawerSize = "lg",
|
|
1836
1996
|
baseURL,
|
|
1837
1997
|
isOpen,
|
|
1838
1998
|
title = "Auto-scheduling",
|
|
1839
|
-
|
|
1999
|
+
bannerTitle = "Create a smarter retail schedule in seconds.",
|
|
2000
|
+
bannerText = "AI assigns open shifts using your rules, availability, and optimization priorities while keeping existing\n assigned shifts in place.",
|
|
1840
2001
|
jurisdictions,
|
|
1841
2002
|
openShifts,
|
|
1842
2003
|
employees,
|
|
@@ -1854,22 +2015,79 @@ function AutoSchedulerModal({
|
|
|
1854
2015
|
getTokenURLAndHeaders,
|
|
1855
2016
|
onSolution,
|
|
1856
2017
|
getStoredConfigForLocations,
|
|
1857
|
-
persistConfigForLocations
|
|
2018
|
+
persistConfigForLocations,
|
|
2019
|
+
errorToast,
|
|
2020
|
+
organizationId,
|
|
2021
|
+
constraintDefinitionIds,
|
|
2022
|
+
constraintDefinitions,
|
|
2023
|
+
hardConstraintOptions,
|
|
2024
|
+
softConstraintGroups,
|
|
2025
|
+
optimizationFooter,
|
|
2026
|
+
trafficForecast,
|
|
2027
|
+
builtinConstraintOptions,
|
|
2028
|
+
subLocations,
|
|
2029
|
+
showSubLocations,
|
|
2030
|
+
teams,
|
|
2031
|
+
showTeams,
|
|
2032
|
+
onSelectionChange,
|
|
2033
|
+
onGenerate,
|
|
2034
|
+
onGenerateSolution,
|
|
2035
|
+
solveStatus,
|
|
2036
|
+
scheduleWeekStartYmd,
|
|
2037
|
+
todayCalendarYmd,
|
|
2038
|
+
evaluationTimezone
|
|
1858
2039
|
}) {
|
|
1859
2040
|
const baseTheme = (0, import_react4.useTheme)();
|
|
1860
|
-
const { chakraOverride, styleOverrides } = (0, import_react3.useMemo)(
|
|
1861
|
-
() => resolveThemeParts(theme),
|
|
1862
|
-
[theme]
|
|
1863
|
-
);
|
|
2041
|
+
const { chakraOverride, styleOverrides } = (0, import_react3.useMemo)(() => resolveThemeParts(theme), [theme]);
|
|
1864
2042
|
const mergedTheme = (0, import_react3.useMemo)(
|
|
1865
2043
|
() => mergeThemeWithBase(baseTheme, chakraOverride),
|
|
1866
2044
|
[baseTheme, chakraOverride]
|
|
1867
2045
|
);
|
|
1868
2046
|
const styles = (0, import_react3.useMemo)(() => buildStyles(defaultStyles, styleOverrides), [styleOverrides]);
|
|
2047
|
+
const effectiveHardConstraintOptions = (0, import_react3.useMemo)(() => {
|
|
2048
|
+
const base = hardConstraintOptions != null ? hardConstraintOptions : HARD_CONSTRAINT_OPTIONS;
|
|
2049
|
+
return base.map((opt) => {
|
|
2050
|
+
var _a, _b, _c, _d;
|
|
2051
|
+
return {
|
|
2052
|
+
...opt,
|
|
2053
|
+
defaultValue: (_a = opt.defaultValue) != null ? _a : HARD_CONSTRAINT_DEFAULTS[opt.id],
|
|
2054
|
+
suffix: (_b = opt.suffix) != null ? _b : opt.id === "max_work_days_per_week" ? "days" : "hours",
|
|
2055
|
+
min: (_c = opt.min) != null ? _c : opt.id === "max_work_days_per_week" ? 1 : void 0,
|
|
2056
|
+
max: (_d = opt.max) != null ? _d : opt.id === "max_work_days_per_week" ? 6 : void 0
|
|
2057
|
+
};
|
|
2058
|
+
});
|
|
2059
|
+
}, [hardConstraintOptions]);
|
|
2060
|
+
const effectiveSoftConstraintGroups = (0, import_react3.useMemo)(
|
|
2061
|
+
() => softConstraintGroups != null ? softConstraintGroups : OPTIMIZATION_OPTION_GROUPS,
|
|
2062
|
+
[softConstraintGroups]
|
|
2063
|
+
);
|
|
2064
|
+
const effectiveBuiltinOptions = (0, import_react3.useMemo)(
|
|
2065
|
+
() => builtinConstraintOptions != null ? builtinConstraintOptions : [],
|
|
2066
|
+
[builtinConstraintOptions]
|
|
2067
|
+
);
|
|
2068
|
+
const defaultSelectedBuiltinIds = (0, import_react3.useMemo)(
|
|
2069
|
+
() => effectiveBuiltinOptions.filter((option) => option.defaultSelected).map((option) => option.id),
|
|
2070
|
+
[effectiveBuiltinOptions]
|
|
2071
|
+
);
|
|
2072
|
+
const dynamicViolationMessages = (0, import_react3.useMemo)(() => {
|
|
2073
|
+
const map = {};
|
|
2074
|
+
for (const opt of effectiveHardConstraintOptions) {
|
|
2075
|
+
map[opt.id] = { title: opt.name, detail: opt.description || opt.name };
|
|
2076
|
+
}
|
|
2077
|
+
for (const group of effectiveSoftConstraintGroups) {
|
|
2078
|
+
for (const opt of group.options) {
|
|
2079
|
+
map[opt.id] = { title: opt.title, detail: opt.subtitle || opt.title };
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
return map;
|
|
2083
|
+
}, [effectiveHardConstraintOptions, effectiveSoftConstraintGroups]);
|
|
2084
|
+
const [selectedSubLocationIds, setSelectedSubLocationIds] = (0, import_react3.useState)([]);
|
|
2085
|
+
const [selectedTeamIds, setSelectedTeamIds] = (0, import_react3.useState)([]);
|
|
1869
2086
|
const [selectedConstraintIds, setSelectedConstraintIds] = (0, import_react3.useState)([]);
|
|
1870
2087
|
const [constraintValues, setConstraintValues] = (0, import_react3.useState)({});
|
|
1871
2088
|
const [selectedOptimizationIds, setSelectedOptimizationIds] = (0, import_react3.useState)([]);
|
|
1872
2089
|
const [optimizationValues, setOptimizationValues] = (0, import_react3.useState)({});
|
|
2090
|
+
const [selectedScheduleDates, setSelectedScheduleDates] = (0, import_react3.useState)([]);
|
|
1873
2091
|
const [screen, setScreen] = (0, import_react3.useState)("configure");
|
|
1874
2092
|
const [activeStepIndex, setActiveStepIndex] = (0, import_react3.useState)(0);
|
|
1875
2093
|
const [completedStepIndex, setCompletedStepIndex] = (0, import_react3.useState)(-1);
|
|
@@ -1878,12 +2096,19 @@ function AutoSchedulerModal({
|
|
|
1878
2096
|
const timersRef = (0, import_react3.useRef)([]);
|
|
1879
2097
|
const pollIntervalRef = (0, import_react3.useRef)(null);
|
|
1880
2098
|
const runIdRef = (0, import_react3.useRef)(0);
|
|
2099
|
+
const weekDatesOrdered = (0, import_react3.useMemo)(() => {
|
|
2100
|
+
if (!scheduleWeekStartYmd) return [];
|
|
2101
|
+
return getSequentialWeekDatesFromWeekStart(scheduleWeekStartYmd);
|
|
2102
|
+
}, [scheduleWeekStartYmd]);
|
|
2103
|
+
const toggleScheduleDate = (ymd) => {
|
|
2104
|
+
setSelectedScheduleDates(
|
|
2105
|
+
(prev) => prev.includes(ymd) ? prev.filter((d) => d !== ymd) : [...prev, ymd].sort()
|
|
2106
|
+
);
|
|
2107
|
+
};
|
|
1881
2108
|
const selectedByJurisdiction = (0, import_react3.useMemo)(
|
|
1882
2109
|
() => jurisdictions.map((jurisdiction) => ({
|
|
1883
2110
|
...jurisdiction,
|
|
1884
|
-
selectedLocations: jurisdiction.locations.filter(
|
|
1885
|
-
(location) => selectedLocationIds.includes(location.id)
|
|
1886
|
-
)
|
|
2111
|
+
selectedLocations: jurisdiction.locations.filter((location) => selectedLocationIds.includes(location.id))
|
|
1887
2112
|
})).filter((jurisdiction) => jurisdiction.selectedLocations.length > 0),
|
|
1888
2113
|
[jurisdictions, selectedLocationIds]
|
|
1889
2114
|
);
|
|
@@ -1891,39 +2116,85 @@ function AutoSchedulerModal({
|
|
|
1891
2116
|
(total, jurisdiction) => total + jurisdiction.selectedLocations.length,
|
|
1892
2117
|
0
|
|
1893
2118
|
);
|
|
2119
|
+
const locationIdsWithOpenShifts = (0, import_react3.useMemo)(() => {
|
|
2120
|
+
return new Set(openShifts.filter((shift) => !shift.employeeId).map((shift) => shift.location.id));
|
|
2121
|
+
}, [openShifts]);
|
|
2122
|
+
const filteredSubLocations = (0, import_react3.useMemo)(
|
|
2123
|
+
() => {
|
|
2124
|
+
var _a;
|
|
2125
|
+
return (_a = subLocations == null ? void 0 : subLocations.filter((sl) => selectedLocationIds.includes(sl.parentId))) != null ? _a : [];
|
|
2126
|
+
},
|
|
2127
|
+
[subLocations, selectedLocationIds]
|
|
2128
|
+
);
|
|
2129
|
+
const selectedLocationTimezone = (0, import_react3.useMemo)(() => {
|
|
2130
|
+
var _a, _b;
|
|
2131
|
+
const allLocations = jurisdictions.flatMap((jurisdiction) => jurisdiction.locations);
|
|
2132
|
+
for (const id of selectedLocationIds) {
|
|
2133
|
+
const timezone = (_b = (_a = allLocations.find((location) => location.id === id)) == null ? void 0 : _a.timezone) == null ? void 0 : _b.trim();
|
|
2134
|
+
if (timezone) return timezone;
|
|
2135
|
+
}
|
|
2136
|
+
return (evaluationTimezone == null ? void 0 : evaluationTimezone.trim()) || "UTC";
|
|
2137
|
+
}, [evaluationTimezone, jurisdictions, selectedLocationIds]);
|
|
2138
|
+
const effectiveTodayCalendarYmd = (0, import_react3.useMemo)(() => {
|
|
2139
|
+
return todayCalendarYmd != null ? todayCalendarYmd : todayCalendarYmdInTimezone(selectedLocationTimezone);
|
|
2140
|
+
}, [selectedLocationTimezone, todayCalendarYmd]);
|
|
1894
2141
|
const selectedConstraints = (0, import_react3.useMemo)(
|
|
1895
|
-
() =>
|
|
1896
|
-
[selectedConstraintIds]
|
|
2142
|
+
() => effectiveHardConstraintOptions.filter((option) => selectedConstraintIds.includes(option.id)),
|
|
2143
|
+
[effectiveHardConstraintOptions, selectedConstraintIds]
|
|
2144
|
+
);
|
|
2145
|
+
const selectedConstraintDisplayOptions = (0, import_react3.useMemo)(
|
|
2146
|
+
() => effectiveHardConstraintOptions.filter(
|
|
2147
|
+
(option) => selectedConstraintIds.includes(option.id) || option.readOnly && option.defaultSelected
|
|
2148
|
+
),
|
|
2149
|
+
[effectiveHardConstraintOptions, selectedConstraintIds]
|
|
1897
2150
|
);
|
|
1898
2151
|
const optimizationOptions = (0, import_react3.useMemo)(
|
|
1899
|
-
() =>
|
|
2152
|
+
() => effectiveSoftConstraintGroups.flatMap(
|
|
1900
2153
|
(group) => group.options.map((option) => ({
|
|
1901
2154
|
...option,
|
|
1902
2155
|
groupId: group.id,
|
|
1903
2156
|
groupName: group.name
|
|
1904
2157
|
}))
|
|
1905
2158
|
),
|
|
1906
|
-
[]
|
|
2159
|
+
[effectiveSoftConstraintGroups]
|
|
1907
2160
|
);
|
|
1908
2161
|
const selectedOptimizationOptions = (0, import_react3.useMemo)(
|
|
2162
|
+
() => optimizationOptions.filter((option) => selectedOptimizationIds.includes(option.id)),
|
|
2163
|
+
[optimizationOptions, selectedOptimizationIds]
|
|
2164
|
+
);
|
|
2165
|
+
const selectedOptimizationDisplayOptions = (0, import_react3.useMemo)(
|
|
1909
2166
|
() => optimizationOptions.filter(
|
|
1910
|
-
(option) => selectedOptimizationIds.includes(option.id)
|
|
2167
|
+
(option) => selectedOptimizationIds.includes(option.id) || option.readOnly && option.defaultSelected
|
|
1911
2168
|
),
|
|
1912
2169
|
[optimizationOptions, selectedOptimizationIds]
|
|
1913
2170
|
);
|
|
2171
|
+
const effectiveSelectedConstraintIds = (0, import_react3.useMemo)(
|
|
2172
|
+
() => Array.from(
|
|
2173
|
+
/* @__PURE__ */ new Set([
|
|
2174
|
+
...selectedConstraintIds,
|
|
2175
|
+
...effectiveHardConstraintOptions.filter((option) => option.readOnly && option.defaultSelected).map((option) => option.id)
|
|
2176
|
+
])
|
|
2177
|
+
),
|
|
2178
|
+
[selectedConstraintIds, effectiveHardConstraintOptions]
|
|
2179
|
+
);
|
|
2180
|
+
const effectiveSelectedOptimizationIds = (0, import_react3.useMemo)(
|
|
2181
|
+
() => Array.from(
|
|
2182
|
+
/* @__PURE__ */ new Set([
|
|
2183
|
+
...selectedOptimizationIds,
|
|
2184
|
+
...optimizationOptions.filter((option) => option.readOnly && option.defaultSelected).map((option) => option.id)
|
|
2185
|
+
])
|
|
2186
|
+
),
|
|
2187
|
+
[selectedOptimizationIds, optimizationOptions]
|
|
2188
|
+
);
|
|
1914
2189
|
const hasEmptyInputs = (0, import_react3.useMemo)(() => {
|
|
1915
|
-
const hasEmptyConstraint = selectedConstraints.some(
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
var _a;
|
|
1924
|
-
return !((_a = optimizationValues[`${opt.id}_weightPoints`]) == null ? void 0 : _a.trim());
|
|
1925
|
-
}
|
|
1926
|
-
);
|
|
2190
|
+
const hasEmptyConstraint = selectedConstraints.filter((opt) => opt.defaultValue != null).some((opt) => {
|
|
2191
|
+
var _a;
|
|
2192
|
+
return !((_a = constraintValues[opt.id]) == null ? void 0 : _a.trim());
|
|
2193
|
+
});
|
|
2194
|
+
const hasEmptyWeight = selectedOptimizationOptions.length >= 2 && selectedOptimizationOptions.some((opt) => {
|
|
2195
|
+
var _a;
|
|
2196
|
+
return !((_a = optimizationValues[`${opt.id}_weightPoints`]) == null ? void 0 : _a.trim());
|
|
2197
|
+
});
|
|
1927
2198
|
return hasEmptyConstraint || hasEmptyWeight;
|
|
1928
2199
|
}, [selectedConstraints, constraintValues, selectedOptimizationOptions, optimizationValues]);
|
|
1929
2200
|
const allocationSummary = (0, import_react3.useMemo)(() => {
|
|
@@ -1957,13 +2228,25 @@ function AutoSchedulerModal({
|
|
|
1957
2228
|
setScreen("configure");
|
|
1958
2229
|
setLastFailure(null);
|
|
1959
2230
|
setSelectedLocationIds([]);
|
|
2231
|
+
setSelectedSubLocationIds([]);
|
|
2232
|
+
setSelectedTeamIds([]);
|
|
1960
2233
|
setSelectedOptimizationIds([]);
|
|
1961
2234
|
setSelectedConstraintIds([]);
|
|
1962
2235
|
setConstraintValues({});
|
|
1963
2236
|
setOptimizationValues({});
|
|
2237
|
+
setSelectedScheduleDates([]);
|
|
1964
2238
|
resetProgress();
|
|
1965
2239
|
}
|
|
1966
2240
|
}, [isOpen]);
|
|
2241
|
+
(0, import_react3.useEffect)(() => {
|
|
2242
|
+
if (!isOpen || !scheduleWeekStartYmd) return;
|
|
2243
|
+
if (weekDatesOrdered.length === 0) {
|
|
2244
|
+
setSelectedScheduleDates([]);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
const today = effectiveTodayCalendarYmd != null ? effectiveTodayCalendarYmd : weekDatesOrdered[0];
|
|
2248
|
+
setSelectedScheduleDates(defaultSelectedScheduleDates(weekDatesOrdered, today));
|
|
2249
|
+
}, [isOpen, scheduleWeekStartYmd, weekDatesOrdered, effectiveTodayCalendarYmd]);
|
|
1967
2250
|
(0, import_react3.useEffect)(() => {
|
|
1968
2251
|
if (isOpen && initialConfig) {
|
|
1969
2252
|
setSelectedLocationIds(initialConfig.selectedLocationIds);
|
|
@@ -1995,6 +2278,13 @@ function AutoSchedulerModal({
|
|
|
1995
2278
|
}
|
|
1996
2279
|
}, [selectedLocationIds, initialConfig, getStoredConfigForLocations]);
|
|
1997
2280
|
(0, import_react3.useEffect)(() => () => clearTimers(), []);
|
|
2281
|
+
(0, import_react3.useEffect)(() => {
|
|
2282
|
+
if (onGenerate) return;
|
|
2283
|
+
setSelectedLocationIds((previous) => {
|
|
2284
|
+
const filtered = previous.filter((id) => locationIdsWithOpenShifts.has(id));
|
|
2285
|
+
return filtered.length === previous.length ? previous : filtered;
|
|
2286
|
+
});
|
|
2287
|
+
}, [locationIdsWithOpenShifts, onGenerate]);
|
|
1998
2288
|
const stepStatusForIndex = (index) => {
|
|
1999
2289
|
if (index <= completedStepIndex) {
|
|
2000
2290
|
return "done";
|
|
@@ -2022,144 +2312,249 @@ function AutoSchedulerModal({
|
|
|
2022
2312
|
timersRef.current.push(timerId);
|
|
2023
2313
|
};
|
|
2024
2314
|
const afterSuccessSteps = (runId, response) => {
|
|
2025
|
-
schedule(
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
setCompletedStepIndex(4);
|
|
2315
|
+
schedule(
|
|
2316
|
+
STEP_INTERVAL_MS,
|
|
2317
|
+
() => {
|
|
2318
|
+
if (runIdRef.current !== runId) return;
|
|
2319
|
+
setCompletedStepIndex(2);
|
|
2320
|
+
setActiveStepIndex(3);
|
|
2321
|
+
},
|
|
2322
|
+
runId
|
|
2323
|
+
);
|
|
2324
|
+
schedule(
|
|
2325
|
+
STEP_INTERVAL_MS * 2,
|
|
2326
|
+
() => {
|
|
2327
|
+
if (runIdRef.current !== runId) return;
|
|
2328
|
+
setCompletedStepIndex(3);
|
|
2040
2329
|
setActiveStepIndex(4);
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2330
|
+
},
|
|
2331
|
+
runId
|
|
2332
|
+
);
|
|
2333
|
+
schedule(
|
|
2334
|
+
STEP_INTERVAL_MS * 3,
|
|
2335
|
+
async () => {
|
|
2336
|
+
if (runIdRef.current !== runId) return;
|
|
2337
|
+
try {
|
|
2338
|
+
await (onSolution == null ? void 0 : onSolution(response.solution));
|
|
2339
|
+
setCompletedStepIndex(4);
|
|
2340
|
+
setActiveStepIndex(4);
|
|
2341
|
+
} catch (err) {
|
|
2342
|
+
console.error(err);
|
|
2343
|
+
const defaultErrorMessage = "Failed to create schedule shifts";
|
|
2344
|
+
const detailedErrorMessage = err instanceof Error ? err.message || defaultErrorMessage : defaultErrorMessage;
|
|
2345
|
+
setLastFailure({
|
|
2346
|
+
violatedConstraints: [
|
|
2347
|
+
{
|
|
2348
|
+
title: "Something went wrong",
|
|
2349
|
+
detail: detailedErrorMessage
|
|
2350
|
+
}
|
|
2351
|
+
],
|
|
2352
|
+
recommendedFixes: []
|
|
2353
|
+
});
|
|
2354
|
+
setScreen("failed");
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
runId
|
|
2358
|
+
);
|
|
2057
2359
|
};
|
|
2058
2360
|
const handleCreateSchedule = () => {
|
|
2059
2361
|
if (selectedCount === 0) {
|
|
2060
2362
|
return;
|
|
2061
2363
|
}
|
|
2364
|
+
if (scheduleWeekStartYmd && selectedScheduleDates.length === 0) {
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
if (onGenerate) {
|
|
2368
|
+
const runId2 = resetBeforeSubmit();
|
|
2369
|
+
schedule(
|
|
2370
|
+
STEP_INTERVAL_MS,
|
|
2371
|
+
async () => {
|
|
2372
|
+
if (runIdRef.current !== runId2) return;
|
|
2373
|
+
setCompletedStepIndex(0);
|
|
2374
|
+
setActiveStepIndex(1);
|
|
2375
|
+
try {
|
|
2376
|
+
const generateResult = await onGenerate({
|
|
2377
|
+
selectedLocationIds,
|
|
2378
|
+
selectedSubLocationIds,
|
|
2379
|
+
selectedTeamIds,
|
|
2380
|
+
schedulerTimezone: selectedLocationTimezone,
|
|
2381
|
+
selectedScheduleDates,
|
|
2382
|
+
selectedConstraintIds: effectiveSelectedConstraintIds,
|
|
2383
|
+
constraintValues,
|
|
2384
|
+
selectedOptimizationIds: effectiveSelectedOptimizationIds,
|
|
2385
|
+
optimizationValues,
|
|
2386
|
+
selectedBuiltinIds: defaultSelectedBuiltinIds
|
|
2387
|
+
});
|
|
2388
|
+
if (runIdRef.current !== runId2) return;
|
|
2389
|
+
setCompletedStepIndex(1);
|
|
2390
|
+
setActiveStepIndex(2);
|
|
2391
|
+
schedule(
|
|
2392
|
+
STEP_INTERVAL_MS,
|
|
2393
|
+
() => {
|
|
2394
|
+
if (runIdRef.current !== runId2) return;
|
|
2395
|
+
setCompletedStepIndex(2);
|
|
2396
|
+
setActiveStepIndex(3);
|
|
2397
|
+
},
|
|
2398
|
+
runId2
|
|
2399
|
+
);
|
|
2400
|
+
schedule(
|
|
2401
|
+
STEP_INTERVAL_MS * 2,
|
|
2402
|
+
() => {
|
|
2403
|
+
if (runIdRef.current !== runId2) return;
|
|
2404
|
+
setCompletedStepIndex(3);
|
|
2405
|
+
setActiveStepIndex(4);
|
|
2406
|
+
},
|
|
2407
|
+
runId2
|
|
2408
|
+
);
|
|
2409
|
+
schedule(
|
|
2410
|
+
STEP_INTERVAL_MS * 3,
|
|
2411
|
+
() => {
|
|
2412
|
+
if (runIdRef.current !== runId2) return;
|
|
2413
|
+
setCompletedStepIndex(4);
|
|
2414
|
+
setActiveStepIndex(4);
|
|
2415
|
+
onGenerateSolution == null ? void 0 : onGenerateSolution(generateResult);
|
|
2416
|
+
onClose();
|
|
2417
|
+
},
|
|
2418
|
+
runId2
|
|
2419
|
+
);
|
|
2420
|
+
} catch (err) {
|
|
2421
|
+
if (runIdRef.current !== runId2) return;
|
|
2422
|
+
if (err instanceof InfeasibleScheduleError) {
|
|
2423
|
+
setLastFailure(err.failure);
|
|
2424
|
+
} else {
|
|
2425
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
2426
|
+
setLastFailure({
|
|
2427
|
+
violatedConstraints: [{ title: "Error", detail: msg }],
|
|
2428
|
+
recommendedFixes: []
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
setScreen("failed");
|
|
2432
|
+
}
|
|
2433
|
+
},
|
|
2434
|
+
runId2
|
|
2435
|
+
);
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2062
2438
|
const payload = buildSchedulePayload({
|
|
2063
2439
|
employees,
|
|
2064
2440
|
openShifts,
|
|
2065
2441
|
timeOffs,
|
|
2066
2442
|
selectedLocationIds,
|
|
2067
2443
|
jurisdictions,
|
|
2068
|
-
selectedConstraintIds,
|
|
2444
|
+
selectedConstraintIds: effectiveSelectedConstraintIds,
|
|
2069
2445
|
constraintValues,
|
|
2070
|
-
selectedOptimizationIds,
|
|
2071
|
-
optimizationValues
|
|
2446
|
+
selectedOptimizationIds: effectiveSelectedOptimizationIds,
|
|
2447
|
+
optimizationValues,
|
|
2448
|
+
trafficForecast,
|
|
2449
|
+
constraintDefinitionIds,
|
|
2450
|
+
constraintDefinitions,
|
|
2451
|
+
organizationId
|
|
2072
2452
|
});
|
|
2073
2453
|
persistConfigForLocations == null ? void 0 : persistConfigForLocations(selectedLocationIds, {
|
|
2074
2454
|
selectedLocationIds,
|
|
2075
|
-
selectedConstraintIds,
|
|
2455
|
+
selectedConstraintIds: effectiveSelectedConstraintIds,
|
|
2076
2456
|
constraintValues,
|
|
2077
|
-
selectedOptimizationIds,
|
|
2078
|
-
optimizationValues
|
|
2457
|
+
selectedOptimizationIds: effectiveSelectedOptimizationIds,
|
|
2458
|
+
optimizationValues,
|
|
2459
|
+
selectedBuiltinIds: defaultSelectedBuiltinIds
|
|
2079
2460
|
});
|
|
2080
2461
|
const runId = resetBeforeSubmit();
|
|
2081
|
-
schedule(
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2462
|
+
schedule(
|
|
2463
|
+
STEP_INTERVAL_MS,
|
|
2464
|
+
async () => {
|
|
2465
|
+
setCompletedStepIndex(0);
|
|
2466
|
+
setActiveStepIndex(1);
|
|
2467
|
+
try {
|
|
2468
|
+
const token = await getToken(getTokenURLAndHeaders.url, getTokenURLAndHeaders.headers);
|
|
2469
|
+
if (!token) {
|
|
2470
|
+
throw new Error("Failed to get auto-scheduler token");
|
|
2471
|
+
}
|
|
2472
|
+
const selectedDates = [...selectedScheduleDates].sort();
|
|
2473
|
+
const horizonOptions = selectedDates.length > 0 ? {
|
|
2474
|
+
horizonStartDate: selectedDates[0],
|
|
2475
|
+
horizonEndDate: selectedDates[selectedDates.length - 1]
|
|
2476
|
+
} : {};
|
|
2477
|
+
const auth = { authorization: `Bearer ${token}`, ...horizonOptions };
|
|
2478
|
+
const { jobId } = await submitSolveJob(baseURL, payload, auth);
|
|
2479
|
+
if (runIdRef.current !== runId) {
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
setCompletedStepIndex(1);
|
|
2483
|
+
setActiveStepIndex(OPTIMIZING_STEP_INDEX);
|
|
2484
|
+
const pollForResult = async () => {
|
|
2100
2485
|
if (runIdRef.current !== runId) return;
|
|
2101
|
-
|
|
2486
|
+
try {
|
|
2487
|
+
const statusRes = await getJobStatus(baseURL, jobId, auth);
|
|
2488
|
+
if (runIdRef.current !== runId) return;
|
|
2489
|
+
if (statusRes.status === "NOT_SOLVING" || statusRes.status === "TERMINATED_EARLY") {
|
|
2490
|
+
if (pollIntervalRef.current) {
|
|
2491
|
+
clearInterval(pollIntervalRef.current);
|
|
2492
|
+
pollIntervalRef.current = null;
|
|
2493
|
+
}
|
|
2494
|
+
const response = await getJobResult(baseURL, jobId, auth);
|
|
2495
|
+
if (runIdRef.current !== runId) return;
|
|
2496
|
+
let responseForHandling = response;
|
|
2497
|
+
if (response.explanation.isFeasible) {
|
|
2498
|
+
const solution = await getJobSolution(baseURL, jobId, auth);
|
|
2499
|
+
if (runIdRef.current !== runId) return;
|
|
2500
|
+
responseForHandling = { ...response, solution };
|
|
2501
|
+
}
|
|
2502
|
+
handleSolveResult(runId, responseForHandling);
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
} catch (err) {
|
|
2506
|
+
if (runIdRef.current !== runId) return;
|
|
2102
2507
|
if (pollIntervalRef.current) {
|
|
2103
2508
|
clearInterval(pollIntervalRef.current);
|
|
2104
2509
|
pollIntervalRef.current = null;
|
|
2105
2510
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
if (response.explanation.isFeasible) {
|
|
2110
|
-
const solution = await getJobSolution(baseURL, jobId, auth);
|
|
2111
|
-
if (runIdRef.current !== runId) return;
|
|
2112
|
-
responseForHandling = { ...response, solution };
|
|
2113
|
-
}
|
|
2114
|
-
handleSolveResult(runId, responseForHandling);
|
|
2115
|
-
return;
|
|
2116
|
-
}
|
|
2117
|
-
} catch (err) {
|
|
2118
|
-
if (runIdRef.current !== runId) return;
|
|
2119
|
-
if (pollIntervalRef.current) {
|
|
2120
|
-
clearInterval(pollIntervalRef.current);
|
|
2121
|
-
pollIntervalRef.current = null;
|
|
2511
|
+
console.error(err);
|
|
2512
|
+
setLastFailure(NETWORK_ERROR_FAILURE);
|
|
2513
|
+
setScreen("failed");
|
|
2122
2514
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2515
|
+
};
|
|
2516
|
+
pollForResult();
|
|
2517
|
+
pollIntervalRef.current = setInterval(pollForResult, POLL_INTERVAL_MS);
|
|
2518
|
+
} catch (err) {
|
|
2519
|
+
if (runIdRef.current !== runId) {
|
|
2520
|
+
return;
|
|
2126
2521
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
} catch (err) {
|
|
2131
|
-
if (runIdRef.current !== runId) {
|
|
2132
|
-
return;
|
|
2522
|
+
console.error(err);
|
|
2523
|
+
setLastFailure(NETWORK_ERROR_FAILURE);
|
|
2524
|
+
setScreen("failed");
|
|
2133
2525
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
}
|
|
2138
|
-
}, runId);
|
|
2526
|
+
},
|
|
2527
|
+
runId
|
|
2528
|
+
);
|
|
2139
2529
|
};
|
|
2140
2530
|
const handleSolveResult = (runId, response) => {
|
|
2141
|
-
var _a
|
|
2531
|
+
var _a;
|
|
2142
2532
|
if (!response.explanation.isFeasible) {
|
|
2143
|
-
const result = mapViolationsToResult(
|
|
2144
|
-
|
|
2145
|
-
response.explanation.summary
|
|
2146
|
-
);
|
|
2147
|
-
const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter(
|
|
2148
|
-
(l) => selectedLocationIds.includes(l.id)
|
|
2149
|
-
);
|
|
2533
|
+
const result = mapViolationsToResult((_a = response.explanation.violations) != null ? _a : [], response.explanation.summary, dynamicViolationMessages);
|
|
2534
|
+
const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter((l) => selectedLocationIds.includes(l.id));
|
|
2150
2535
|
const recommendationPayload = generateRecommendationsURLAndHeaders ? {
|
|
2151
2536
|
context: {
|
|
2152
2537
|
selectedLocationIds,
|
|
2153
|
-
selectedConstraintIds,
|
|
2538
|
+
selectedConstraintIds: effectiveSelectedConstraintIds,
|
|
2154
2539
|
constraintValues,
|
|
2155
|
-
selectedOptimizationIds,
|
|
2540
|
+
selectedOptimizationIds: effectiveSelectedOptimizationIds,
|
|
2156
2541
|
optimizationValues,
|
|
2157
|
-
timezone:
|
|
2542
|
+
timezone: selectedLocationTimezone
|
|
2158
2543
|
},
|
|
2159
2544
|
locations: selectedLocations,
|
|
2160
2545
|
employees,
|
|
2161
|
-
openShifts: openShifts.filter(
|
|
2162
|
-
|
|
2546
|
+
openShifts: openShifts.filter(
|
|
2547
|
+
(s) => {
|
|
2548
|
+
var _a2;
|
|
2549
|
+
return !s.employeeId && selectedLocationIds.includes(String((_a2 = s.location) == null ? void 0 : _a2.id));
|
|
2550
|
+
}
|
|
2551
|
+
),
|
|
2552
|
+
assignedShifts: openShifts.filter(
|
|
2553
|
+
(s) => {
|
|
2554
|
+
var _a2;
|
|
2555
|
+
return Boolean(s.employeeId) && selectedLocationIds.includes(String((_a2 = s.location) == null ? void 0 : _a2.id));
|
|
2556
|
+
}
|
|
2557
|
+
),
|
|
2163
2558
|
timeOffs: timeOffs != null ? timeOffs : [],
|
|
2164
2559
|
explanation: response.explanation
|
|
2165
2560
|
} : void 0;
|
|
@@ -2178,14 +2573,34 @@ function AutoSchedulerModal({
|
|
|
2178
2573
|
setScreen("configure");
|
|
2179
2574
|
};
|
|
2180
2575
|
const handleToggleLocation = (locationId) => {
|
|
2181
|
-
if (
|
|
2182
|
-
setSelectedLocationIds(selectedLocationIds.filter((id) => id !== locationId));
|
|
2576
|
+
if (!onGenerate && !locationIdsWithOpenShifts.has(locationId)) {
|
|
2183
2577
|
return;
|
|
2184
2578
|
}
|
|
2185
|
-
|
|
2579
|
+
const newLocationIds = selectedLocationIds.includes(locationId) ? selectedLocationIds.filter((id) => id !== locationId) : [...selectedLocationIds, locationId];
|
|
2580
|
+
setSelectedLocationIds(newLocationIds);
|
|
2581
|
+
const filteredSubs = selectedSubLocationIds.filter((subId) => {
|
|
2582
|
+
const sub = subLocations == null ? void 0 : subLocations.find((s) => s.id === subId);
|
|
2583
|
+
return sub ? newLocationIds.includes(sub.parentId) : false;
|
|
2584
|
+
});
|
|
2585
|
+
if (filteredSubs.length !== selectedSubLocationIds.length) {
|
|
2586
|
+
setSelectedSubLocationIds(filteredSubs);
|
|
2587
|
+
}
|
|
2588
|
+
onSelectionChange == null ? void 0 : onSelectionChange(
|
|
2589
|
+
newLocationIds,
|
|
2590
|
+
filteredSubs.length !== selectedSubLocationIds.length ? filteredSubs : selectedSubLocationIds
|
|
2591
|
+
);
|
|
2592
|
+
};
|
|
2593
|
+
const handleToggleSubLocation = (subLocationId) => {
|
|
2594
|
+
const next = selectedSubLocationIds.includes(subLocationId) ? selectedSubLocationIds.filter((id) => id !== subLocationId) : [...selectedSubLocationIds, subLocationId];
|
|
2595
|
+
setSelectedSubLocationIds(next);
|
|
2596
|
+
onSelectionChange == null ? void 0 : onSelectionChange(selectedLocationIds, next);
|
|
2597
|
+
};
|
|
2598
|
+
const handleToggleTeam = (teamId) => {
|
|
2599
|
+
setSelectedTeamIds((prev) => prev.includes(teamId) ? prev.filter((id) => id !== teamId) : [...prev, teamId]);
|
|
2186
2600
|
};
|
|
2187
2601
|
const handleToggleConstraint = (constraintId) => {
|
|
2188
2602
|
setSelectedConstraintIds((prev) => {
|
|
2603
|
+
var _a;
|
|
2189
2604
|
if (prev.includes(constraintId)) {
|
|
2190
2605
|
setConstraintValues((values) => {
|
|
2191
2606
|
const next = { ...values };
|
|
@@ -2194,10 +2609,11 @@ function AutoSchedulerModal({
|
|
|
2194
2609
|
});
|
|
2195
2610
|
return prev.filter((id) => id !== constraintId);
|
|
2196
2611
|
}
|
|
2197
|
-
|
|
2612
|
+
const defaultVal = (_a = effectiveHardConstraintOptions.find((o) => o.id === constraintId)) == null ? void 0 : _a.defaultValue;
|
|
2613
|
+
if (defaultVal) {
|
|
2198
2614
|
setConstraintValues((values) => ({
|
|
2199
2615
|
...values,
|
|
2200
|
-
[constraintId]:
|
|
2616
|
+
[constraintId]: defaultVal
|
|
2201
2617
|
}));
|
|
2202
2618
|
}
|
|
2203
2619
|
return [...prev, constraintId];
|
|
@@ -2241,6 +2657,7 @@ function AutoSchedulerModal({
|
|
|
2241
2657
|
violatedConstraints: lastFailure,
|
|
2242
2658
|
title: violationTitle,
|
|
2243
2659
|
subtitle: "",
|
|
2660
|
+
errorToast,
|
|
2244
2661
|
generateRecommendationsURLAndHeaders,
|
|
2245
2662
|
styles
|
|
2246
2663
|
}
|
|
@@ -2248,12 +2665,12 @@ function AutoSchedulerModal({
|
|
|
2248
2665
|
const configurationContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "20px", children: [
|
|
2249
2666
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { sx: styles.banner, children: [
|
|
2250
2667
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { sx: styles.bannerIcon, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MagicIcon, {}) }),
|
|
2251
|
-
|
|
2252
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerTitle, children:
|
|
2253
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerText, children:
|
|
2668
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "4px", flex: 1, children: [
|
|
2669
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerTitle, children: bannerTitle }),
|
|
2670
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.bannerText, children: bannerText })
|
|
2254
2671
|
] })
|
|
2255
2672
|
] }),
|
|
2256
|
-
openShifts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2673
|
+
!onGenerate && openShifts.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2257
2674
|
import_react4.Box,
|
|
2258
2675
|
{
|
|
2259
2676
|
sx: {
|
|
@@ -2296,25 +2713,33 @@ function AutoSchedulerModal({
|
|
|
2296
2713
|
] }) }) }),
|
|
2297
2714
|
/* @__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: [
|
|
2298
2715
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.groupHeader, children: jurisdiction.title }),
|
|
2299
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: jurisdiction.locations.map((location) =>
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2716
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "2px", children: jurisdiction.locations.map((location) => {
|
|
2717
|
+
const isEnabled = onGenerate || locationIdsWithOpenShifts.has(location.id);
|
|
2718
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2719
|
+
import_react4.HStack,
|
|
2720
|
+
{
|
|
2721
|
+
as: "label",
|
|
2722
|
+
sx: {
|
|
2723
|
+
...styles.optionRow,
|
|
2724
|
+
cursor: isEnabled ? "pointer" : "not-allowed",
|
|
2725
|
+
opacity: isEnabled ? 1 : 0.55
|
|
2726
|
+
},
|
|
2727
|
+
children: [
|
|
2728
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2729
|
+
import_react4.Checkbox,
|
|
2730
|
+
{
|
|
2731
|
+
id: `location-${location.id}`,
|
|
2732
|
+
isChecked: selectedLocationIds.includes(location.id),
|
|
2733
|
+
isDisabled: !isEnabled,
|
|
2734
|
+
onChange: () => handleToggleLocation(location.id)
|
|
2735
|
+
}
|
|
2736
|
+
),
|
|
2737
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optionLabel, children: location.name })
|
|
2738
|
+
]
|
|
2739
|
+
},
|
|
2740
|
+
location.id
|
|
2741
|
+
);
|
|
2742
|
+
}) })
|
|
2318
2743
|
] }, jurisdiction.id)) }) }) })
|
|
2319
2744
|
] }),
|
|
2320
2745
|
/* @__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: [
|
|
@@ -2328,11 +2753,128 @@ function AutoSchedulerModal({
|
|
|
2328
2753
|
] })
|
|
2329
2754
|
] }, jurisdiction.id)) })
|
|
2330
2755
|
] }),
|
|
2756
|
+
showSubLocations && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "8px", sx: styles.locationSection, children: [
|
|
2757
|
+
/* @__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: [
|
|
2758
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.locationLabel, children: "Sub-location" }),
|
|
2759
|
+
selectedSubLocationIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.locationBadge, children: selectedSubLocationIds.length })
|
|
2760
|
+
] }) }),
|
|
2761
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
|
|
2762
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2763
|
+
import_react4.Button,
|
|
2764
|
+
{
|
|
2765
|
+
variant: "unstyled",
|
|
2766
|
+
isDisabled: selectedLocationIds.length === 0 || filteredSubLocations.length === 0,
|
|
2767
|
+
sx: {
|
|
2768
|
+
...styles.locationTrigger,
|
|
2769
|
+
...selectedLocationIds.length === 0 || filteredSubLocations.length === 0 ? { opacity: 0.5, cursor: "not-allowed" } : {}
|
|
2770
|
+
},
|
|
2771
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { justify: "space-between", w: "full", children: [
|
|
2772
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2773
|
+
import_react4.Text,
|
|
2774
|
+
{
|
|
2775
|
+
sx: {
|
|
2776
|
+
...styles.locationTriggerText,
|
|
2777
|
+
...selectedSubLocationIds.length > 0 ? { color: "#303030" } : {}
|
|
2778
|
+
},
|
|
2779
|
+
noOfLines: 1,
|
|
2780
|
+
children: selectedSubLocationIds.length > 0 ? filteredSubLocations.filter((s) => selectedSubLocationIds.includes(s.id)).map((s) => s.name).join(", ") : "Select sub-locations"
|
|
2781
|
+
}
|
|
2782
|
+
),
|
|
2783
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
|
|
2784
|
+
] })
|
|
2785
|
+
}
|
|
2786
|
+
) }),
|
|
2787
|
+
/* @__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: [
|
|
2788
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2789
|
+
import_react4.Checkbox,
|
|
2790
|
+
{
|
|
2791
|
+
id: `sublocation-${sub.id}`,
|
|
2792
|
+
isChecked: selectedSubLocationIds.includes(sub.id),
|
|
2793
|
+
onChange: () => handleToggleSubLocation(sub.id)
|
|
2794
|
+
}
|
|
2795
|
+
),
|
|
2796
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optionLabel, children: sub.name })
|
|
2797
|
+
] }, sub.id)) }) }) })
|
|
2798
|
+
] })
|
|
2799
|
+
] }),
|
|
2800
|
+
showTeams && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "8px", sx: styles.locationSection, children: [
|
|
2801
|
+
/* @__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: [
|
|
2802
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.locationLabel, children: "Teams" }),
|
|
2803
|
+
selectedTeamIds.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.locationBadge, children: selectedTeamIds.length })
|
|
2804
|
+
] }) }),
|
|
2805
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "bottom-start", matchWidth: true, isLazy: true, children: [
|
|
2806
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.PopoverTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2807
|
+
import_react4.Button,
|
|
2808
|
+
{
|
|
2809
|
+
variant: "unstyled",
|
|
2810
|
+
isDisabled: !teams || teams.length === 0,
|
|
2811
|
+
sx: {
|
|
2812
|
+
...styles.locationTrigger,
|
|
2813
|
+
...!teams || teams.length === 0 ? { opacity: 0.5, cursor: "not-allowed" } : {}
|
|
2814
|
+
},
|
|
2815
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { justify: "space-between", w: "full", children: [
|
|
2816
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2817
|
+
import_react4.Text,
|
|
2818
|
+
{
|
|
2819
|
+
sx: {
|
|
2820
|
+
...styles.locationTriggerText,
|
|
2821
|
+
...selectedTeamIds.length > 0 ? { color: "#303030" } : {}
|
|
2822
|
+
},
|
|
2823
|
+
noOfLines: 1,
|
|
2824
|
+
children: selectedTeamIds.length > 0 ? (teams != null ? teams : []).filter((t) => selectedTeamIds.includes(t.id)).map((t) => t.name).join(", ") : "Select teams"
|
|
2825
|
+
}
|
|
2826
|
+
),
|
|
2827
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
|
|
2828
|
+
] })
|
|
2829
|
+
}
|
|
2830
|
+
) }),
|
|
2831
|
+
/* @__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: [
|
|
2832
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2833
|
+
import_react4.Checkbox,
|
|
2834
|
+
{
|
|
2835
|
+
id: `team-${team.id}`,
|
|
2836
|
+
isChecked: selectedTeamIds.includes(team.id),
|
|
2837
|
+
onChange: () => handleToggleTeam(team.id)
|
|
2838
|
+
}
|
|
2839
|
+
),
|
|
2840
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optionLabel, children: team.name })
|
|
2841
|
+
] }, team.id)) }) }) })
|
|
2842
|
+
] })
|
|
2843
|
+
] }),
|
|
2844
|
+
scheduleWeekStartYmd ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "8px", sx: styles.locationSection, children: [
|
|
2845
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: { ...styles.locationLabel, fontWeight: 600 }, children: "Generate schedule for:" }),
|
|
2846
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.HStack, { spacing: 2, flexWrap: "wrap", children: weekDatesOrdered.map((ymd) => {
|
|
2847
|
+
const selected = selectedScheduleDates.includes(ymd);
|
|
2848
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2849
|
+
import_react4.Button,
|
|
2850
|
+
{
|
|
2851
|
+
type: "button",
|
|
2852
|
+
variant: "unstyled",
|
|
2853
|
+
onClick: () => toggleScheduleDate(ymd),
|
|
2854
|
+
borderRadius: "full",
|
|
2855
|
+
minW: "40px",
|
|
2856
|
+
h: "40px",
|
|
2857
|
+
px: 0,
|
|
2858
|
+
fontSize: "sm",
|
|
2859
|
+
fontWeight: 600,
|
|
2860
|
+
borderWidth: selected ? "2px" : "1px",
|
|
2861
|
+
borderStyle: "solid",
|
|
2862
|
+
borderColor: selected ? "#5b44ff" : "#e2e8f0",
|
|
2863
|
+
bg: selected ? "#ede9fe" : "#ffffff",
|
|
2864
|
+
color: selected ? "#5b44ff" : "#303030",
|
|
2865
|
+
"aria-pressed": selected,
|
|
2866
|
+
"aria-label": `${weekdayTwoLetterFromYmd(ymd)} ${ymd}`,
|
|
2867
|
+
children: weekdayTwoLetterFromYmd(ymd)
|
|
2868
|
+
},
|
|
2869
|
+
ymd
|
|
2870
|
+
);
|
|
2871
|
+
}) })
|
|
2872
|
+
] }) : null,
|
|
2331
2873
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "16px", sx: styles.hardConstraintsSection, children: [
|
|
2332
2874
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "4px", children: [
|
|
2333
2875
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.hardConstraintsLabelRow, children: [
|
|
2334
2876
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsLabel, children: "Required rules" }),
|
|
2335
|
-
|
|
2877
|
+
selectedConstraintDisplayOptions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.hardConstraintsBadge, children: selectedConstraintDisplayOptions.length }) : null
|
|
2336
2878
|
] }),
|
|
2337
2879
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsSubtitle, children: "Assigned shifts will be required to meet the following rules:" })
|
|
2338
2880
|
] }),
|
|
@@ -2344,35 +2886,30 @@ function AutoSchedulerModal({
|
|
|
2344
2886
|
{
|
|
2345
2887
|
sx: {
|
|
2346
2888
|
...styles.hardConstraintsTriggerText,
|
|
2347
|
-
...
|
|
2889
|
+
...selectedConstraintDisplayOptions.length > 0 ? { color: "#303030" } : {}
|
|
2348
2890
|
},
|
|
2349
2891
|
noOfLines: 1,
|
|
2350
|
-
children:
|
|
2892
|
+
children: selectedConstraintDisplayOptions.length > 0 ? selectedConstraintDisplayOptions.map((o) => o.name).join(", ") : "Select rules"
|
|
2351
2893
|
}
|
|
2352
2894
|
),
|
|
2353
2895
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
|
|
2354
2896
|
] }) }) }),
|
|
2355
|
-
/* @__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:
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsOptionLabel, children: option.name })
|
|
2370
|
-
]
|
|
2371
|
-
},
|
|
2372
|
-
option.id
|
|
2373
|
-
)) }) }) })
|
|
2897
|
+
/* @__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: [
|
|
2898
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2899
|
+
import_react4.Checkbox,
|
|
2900
|
+
{
|
|
2901
|
+
id: `constraint-${option.id}`,
|
|
2902
|
+
isChecked: selectedConstraintIds.includes(option.id) || Boolean(option.readOnly && option.defaultSelected),
|
|
2903
|
+
isDisabled: option.readOnly,
|
|
2904
|
+
onChange: () => {
|
|
2905
|
+
if (!option.readOnly) handleToggleConstraint(option.id);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
),
|
|
2909
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.hardConstraintsOptionLabel, children: option.name })
|
|
2910
|
+
] }, option.id)) }) }) })
|
|
2374
2911
|
] }),
|
|
2375
|
-
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) => {
|
|
2912
|
+
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) => {
|
|
2376
2913
|
var _a;
|
|
2377
2914
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { sx: styles.hardConstraintsInputItem, children: [
|
|
2378
2915
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Text, { sx: styles.hardConstraintsInputLabel, children: [
|
|
@@ -2390,13 +2927,13 @@ function AutoSchedulerModal({
|
|
|
2390
2927
|
...prev,
|
|
2391
2928
|
[option.id]: event.target.value
|
|
2392
2929
|
})),
|
|
2393
|
-
min: option.
|
|
2394
|
-
max: option.
|
|
2395
|
-
step: option.
|
|
2930
|
+
min: option.min,
|
|
2931
|
+
max: option.max,
|
|
2932
|
+
step: option.max != null ? void 0 : "any",
|
|
2396
2933
|
sx: styles.hardConstraintsInputField
|
|
2397
2934
|
}
|
|
2398
2935
|
),
|
|
2399
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: option.
|
|
2936
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: option.suffix })
|
|
2400
2937
|
] })
|
|
2401
2938
|
] }, option.id);
|
|
2402
2939
|
}) }) }) : null
|
|
@@ -2405,89 +2942,100 @@ function AutoSchedulerModal({
|
|
|
2405
2942
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "4px", children: [
|
|
2406
2943
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.optimizationLabelRow, children: [
|
|
2407
2944
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationLabel, children: "Optimization" }),
|
|
2408
|
-
|
|
2945
|
+
selectedOptimizationDisplayOptions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Badge, { sx: styles.optimizationBadge, children: selectedOptimizationDisplayOptions.length }) : null
|
|
2409
2946
|
] }),
|
|
2410
2947
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationSubtitle, children: "Your schedule will be optimized based on the following priorities:" })
|
|
2411
2948
|
] }),
|
|
2412
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "
|
|
2949
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Popover, { placement: "top-start", strategy: "fixed", matchWidth: true, isLazy: true, gutter: 4, children: [
|
|
2413
2950
|
/* @__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: [
|
|
2414
2951
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2415
2952
|
import_react4.Text,
|
|
2416
2953
|
{
|
|
2417
2954
|
sx: {
|
|
2418
2955
|
...styles.optimizationTriggerText,
|
|
2419
|
-
...
|
|
2956
|
+
...selectedOptimizationDisplayOptions.length > 0 ? { color: "#303030" } : {}
|
|
2420
2957
|
},
|
|
2421
2958
|
noOfLines: 1,
|
|
2422
|
-
children:
|
|
2959
|
+
children: selectedOptimizationDisplayOptions.length > 0 ? selectedOptimizationDisplayOptions.map((o) => o.title).join(", ") : "Select optimization priorities"
|
|
2423
2960
|
}
|
|
2424
2961
|
),
|
|
2425
2962
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Box, { color: "#5c5c5c", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChevronDownIcon, {}) })
|
|
2426
2963
|
] }) }) }),
|
|
2427
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
import_react4.Checkbox,
|
|
2439
|
-
{
|
|
2440
|
-
id: `optimization-${option.id}`,
|
|
2441
|
-
isChecked: selectedOptimizationIds.includes(option.id),
|
|
2442
|
-
isDisabled: !selectedOptimizationIds.includes(option.id) && selectedOptimizationIds.length >= 3,
|
|
2443
|
-
onChange: () => handleToggleOptimization(option.id)
|
|
2444
|
-
}
|
|
2445
|
-
),
|
|
2446
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionLabel, children: option.title })
|
|
2447
|
-
] }),
|
|
2448
|
-
option.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionHelp, children: option.subtitle }) : null
|
|
2449
|
-
]
|
|
2964
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Portal, { appendToParentPortal: false, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2965
|
+
import_react4.PopoverContent,
|
|
2966
|
+
{
|
|
2967
|
+
sx: {
|
|
2968
|
+
...styles.optimizationPopoverContent,
|
|
2969
|
+
maxH: "min(560px, calc(100vh - 96px))",
|
|
2970
|
+
overflowY: "auto",
|
|
2971
|
+
zIndex: 1500,
|
|
2972
|
+
scrollbarWidth: "none",
|
|
2973
|
+
msOverflowStyle: "none",
|
|
2974
|
+
"&::-webkit-scrollbar": { display: "none" }
|
|
2450
2975
|
},
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2976
|
+
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: [
|
|
2977
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationGroupHeader, children: group.name }),
|
|
2978
|
+
/* @__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: [
|
|
2979
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2980
|
+
import_react4.Stack,
|
|
2981
|
+
{
|
|
2982
|
+
as: "label",
|
|
2983
|
+
spacing: "2px",
|
|
2984
|
+
sx: { ...styles.optimizationOptionRow, cursor: "pointer" },
|
|
2985
|
+
children: [
|
|
2986
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { spacing: "8px", children: [
|
|
2987
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2988
|
+
import_react4.Checkbox,
|
|
2989
|
+
{
|
|
2990
|
+
id: `optimization-${option.id}`,
|
|
2991
|
+
isChecked: selectedOptimizationIds.includes(option.id) || Boolean(option.readOnly && option.defaultSelected),
|
|
2992
|
+
isDisabled: option.readOnly || !selectedOptimizationIds.includes(option.id) && selectedOptimizationIds.length >= 3,
|
|
2993
|
+
onChange: () => {
|
|
2994
|
+
if (!option.readOnly) handleToggleOptimization(option.id);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
),
|
|
2998
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionLabel, children: option.title })
|
|
2999
|
+
] }),
|
|
3000
|
+
option.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationOptionHelp, children: option.subtitle }) : null
|
|
3001
|
+
]
|
|
3002
|
+
}
|
|
3003
|
+
),
|
|
3004
|
+
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
|
|
3005
|
+
] }, option.id)) })
|
|
3006
|
+
] }, group.id)) }) })
|
|
3007
|
+
}
|
|
3008
|
+
) })
|
|
2454
3009
|
] }),
|
|
2455
3010
|
selectedOptimizationOptions.length >= 2 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { sx: styles.optimizationInputsContainer, children: [
|
|
2456
3011
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "16px", children: selectedOptimizationOptions.map((option, index) => {
|
|
2457
3012
|
var _a;
|
|
2458
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2459
|
-
import_react4.
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
),
|
|
2485
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: "points" })
|
|
2486
|
-
] })
|
|
2487
|
-
] })
|
|
2488
|
-
},
|
|
2489
|
-
option.id
|
|
2490
|
-
);
|
|
3013
|
+
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: [
|
|
3014
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Text, { sx: styles.hardConstraintsInputLabel, children: [
|
|
3015
|
+
index + 1,
|
|
3016
|
+
". ",
|
|
3017
|
+
option.title
|
|
3018
|
+
] }),
|
|
3019
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.InputGroup, { children: [
|
|
3020
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3021
|
+
import_react4.Input,
|
|
3022
|
+
{
|
|
3023
|
+
type: "text",
|
|
3024
|
+
value: (_a = optimizationValues[`${option.id}_weightPoints`]) != null ? _a : "1",
|
|
3025
|
+
onChange: (e) => {
|
|
3026
|
+
const val = e.target.value;
|
|
3027
|
+
if (val !== "" && (!/^\d+$/.test(val) || Number(val) < 1)) return;
|
|
3028
|
+
setOptimizationValues((prev) => ({
|
|
3029
|
+
...prev,
|
|
3030
|
+
[`${option.id}_weightPoints`]: val
|
|
3031
|
+
}));
|
|
3032
|
+
},
|
|
3033
|
+
sx: styles.hardConstraintsInputField
|
|
3034
|
+
}
|
|
3035
|
+
),
|
|
3036
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.InputRightAddon, { sx: styles.optimizationSuffix, children: "points" })
|
|
3037
|
+
] })
|
|
3038
|
+
] }) }, option.id);
|
|
2491
3039
|
}) }),
|
|
2492
3040
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Divider, { sx: { ...styles.optimizationDivider, my: "16px" } }),
|
|
2493
3041
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.optimizationSummary, children: allocationSummary })
|
|
@@ -2496,7 +3044,9 @@ function AutoSchedulerModal({
|
|
|
2496
3044
|
] });
|
|
2497
3045
|
const stepsContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Stack, { spacing: "16px", sx: styles.stepsContainer, children: [
|
|
2498
3046
|
showFailed ? violationPanel : null,
|
|
2499
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "14px", sx: styles.stepsList, children: SCHEDULE_STEPS.map((
|
|
3047
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Stack, { spacing: "14px", sx: styles.stepsList, children: SCHEDULE_STEPS.map((rawStep, index) => {
|
|
3048
|
+
const isQueueWait = index === 1 && solveStatus === "QUEUED";
|
|
3049
|
+
const step = isQueueWait ? { title: "Waiting in queue\u2026", subtitle: void 0 } : rawStep;
|
|
2500
3050
|
const status = stepStatusForIndex(index);
|
|
2501
3051
|
const isFailedStep = showFailed && index === activeStepIndex;
|
|
2502
3052
|
const iconStyles = {
|
|
@@ -2514,26 +3064,26 @@ function AutoSchedulerModal({
|
|
|
2514
3064
|
...isFailedStep ? styles.stepTitleFailed : null
|
|
2515
3065
|
};
|
|
2516
3066
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.HStack, { sx: styles.stepRow, children: [
|
|
2517
|
-
/* @__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:
|
|
3067
|
+
/* @__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 }),
|
|
2518
3068
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.Box, { children: [
|
|
2519
3069
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: titleStyles, children: step.title }),
|
|
2520
3070
|
status === "active" && step.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.stepSubtitle, children: step.subtitle }) : null
|
|
2521
3071
|
] })
|
|
2522
|
-
] },
|
|
3072
|
+
] }, rawStep.title);
|
|
2523
3073
|
}) })
|
|
2524
3074
|
] });
|
|
2525
|
-
const
|
|
3075
|
+
const footerButtons = showConfiguration ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2526
3076
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Button, { variant: "outline", onClick: onClose, sx: styles.cancelButton, children: cancelLabel }),
|
|
2527
3077
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2528
3078
|
import_react4.Button,
|
|
2529
3079
|
{
|
|
2530
3080
|
onClick: handleCreateSchedule,
|
|
2531
|
-
isDisabled: selectedCount === 0 || openShifts.length === 0 || hasEmptyInputs,
|
|
3081
|
+
isDisabled: selectedCount === 0 || !onGenerate && openShifts.length === 0 || hasEmptyInputs || Boolean(scheduleWeekStartYmd) && selectedScheduleDates.length === 0,
|
|
2532
3082
|
sx: styles.primaryButton,
|
|
2533
3083
|
children: primaryActionLabel
|
|
2534
3084
|
}
|
|
2535
3085
|
)
|
|
2536
|
-
] }) : showFailed ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3086
|
+
] }) : showFailed ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2537
3087
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2538
3088
|
import_react4.Button,
|
|
2539
3089
|
{
|
|
@@ -2542,10 +3092,11 @@ function AutoSchedulerModal({
|
|
|
2542
3092
|
if (lastFailure) {
|
|
2543
3093
|
onFixManually == null ? void 0 : onFixManually(lastFailure, {
|
|
2544
3094
|
selectedLocationIds,
|
|
2545
|
-
selectedConstraintIds,
|
|
3095
|
+
selectedConstraintIds: effectiveSelectedConstraintIds,
|
|
2546
3096
|
constraintValues,
|
|
2547
|
-
selectedOptimizationIds,
|
|
2548
|
-
optimizationValues
|
|
3097
|
+
selectedOptimizationIds: effectiveSelectedOptimizationIds,
|
|
3098
|
+
optimizationValues,
|
|
3099
|
+
selectedBuiltinIds: defaultSelectedBuiltinIds
|
|
2549
3100
|
});
|
|
2550
3101
|
}
|
|
2551
3102
|
onClose();
|
|
@@ -2556,31 +3107,75 @@ function AutoSchedulerModal({
|
|
|
2556
3107
|
),
|
|
2557
3108
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Button, { onClick: handleAdjustConstraints, sx: styles.failedPrimaryButton, children: "Adjust constraints" })
|
|
2558
3109
|
] }) : null;
|
|
2559
|
-
const
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
3110
|
+
const bodyStyles = showConfiguration ? styles.body : { ...styles.body, ...styles.creatingBody };
|
|
3111
|
+
const bodyContent = showConfiguration ? configurationContent : stepsContent;
|
|
3112
|
+
const modalContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3113
|
+
import_react4.Modal,
|
|
3114
|
+
{
|
|
3115
|
+
isOpen,
|
|
3116
|
+
onClose,
|
|
3117
|
+
isCentered: true,
|
|
3118
|
+
motionPreset: "scale",
|
|
3119
|
+
closeOnOverlayClick: false,
|
|
3120
|
+
closeOnEsc: false,
|
|
3121
|
+
trapFocus: false,
|
|
3122
|
+
children: [
|
|
3123
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalOverlay, { sx: styles.overlay }),
|
|
3124
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react4.ModalContent, { sx: styles.content, children: [
|
|
3125
|
+
/* @__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: [
|
|
3126
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Text, { sx: styles.title, children: modalTitle }),
|
|
3127
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3128
|
+
import_react4.IconButton,
|
|
3129
|
+
{
|
|
3130
|
+
"aria-label": "Close modal",
|
|
3131
|
+
variant: "ghost",
|
|
3132
|
+
size: "sm",
|
|
3133
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}),
|
|
3134
|
+
onClick: onClose,
|
|
3135
|
+
sx: styles.closeButton
|
|
3136
|
+
}
|
|
3137
|
+
)
|
|
3138
|
+
] }) }),
|
|
3139
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalBody, { sx: bodyStyles, children: bodyContent }),
|
|
3140
|
+
footerButtons && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ModalFooter, { sx: showFailed ? styles.failedFooter : styles.footer, children: footerButtons })
|
|
3141
|
+
] })
|
|
3142
|
+
]
|
|
3143
|
+
}
|
|
3144
|
+
);
|
|
3145
|
+
const drawerFooterNode = footerButtons ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3146
|
+
import_react4.HStack,
|
|
3147
|
+
{
|
|
3148
|
+
align: "center",
|
|
3149
|
+
justify: "flex-end",
|
|
3150
|
+
px: 6,
|
|
3151
|
+
h: "68px",
|
|
3152
|
+
bg: "gray.50",
|
|
3153
|
+
borderTop: "1px solid",
|
|
3154
|
+
borderColor: "gray.200",
|
|
3155
|
+
gap: 2,
|
|
3156
|
+
flexShrink: 0,
|
|
3157
|
+
w: "full",
|
|
3158
|
+
children: footerButtons
|
|
3159
|
+
}
|
|
3160
|
+
) : void 0;
|
|
3161
|
+
const drawerContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3162
|
+
import_web_ui.Drawer,
|
|
3163
|
+
{
|
|
3164
|
+
isOpen,
|
|
3165
|
+
onClose,
|
|
3166
|
+
title: modalTitle,
|
|
3167
|
+
closeOnOverlayClick: false,
|
|
3168
|
+
maxW: DRAWER_WIDTH_MAP[drawerSize],
|
|
3169
|
+
bodySx: bodyStyles,
|
|
3170
|
+
footer: drawerFooterNode,
|
|
3171
|
+
children: bodyContent
|
|
3172
|
+
}
|
|
3173
|
+
);
|
|
3174
|
+
const content = displayMode === "drawer" ? drawerContent : modalContent;
|
|
2580
3175
|
if (theme || cssVarsRoot) {
|
|
2581
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ChakraProvider, { theme: mergedTheme, cssVarsRoot, children:
|
|
3176
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.ChakraProvider, { theme: mergedTheme, cssVarsRoot, children: content });
|
|
2582
3177
|
}
|
|
2583
|
-
return
|
|
3178
|
+
return content;
|
|
2584
3179
|
}
|
|
2585
3180
|
|
|
2586
3181
|
// src/AutoSchedulerModalWithProvider.tsx
|
|
@@ -2657,11 +3252,17 @@ var RequestError = class extends Error {
|
|
|
2657
3252
|
0 && (module.exports = {
|
|
2658
3253
|
AutoSchedulerModal,
|
|
2659
3254
|
AutoSchedulerModalWithProvider,
|
|
3255
|
+
InfeasibleScheduleError,
|
|
3256
|
+
MAXIMIZE_CONVERSION_RATE_RULE_ID,
|
|
2660
3257
|
RequestError,
|
|
3258
|
+
TRAFFIC_FORECAST_INCENTIVE_RULE_ID,
|
|
2661
3259
|
ViolatedConstraintsPanel,
|
|
2662
3260
|
ViolatedConstraintsPanelWithProvider,
|
|
2663
3261
|
buildRulesPayload,
|
|
2664
3262
|
buildSchedulePayload,
|
|
2665
|
-
|
|
3263
|
+
defaultSelectedScheduleDates,
|
|
3264
|
+
getFriendlyRuleName,
|
|
3265
|
+
getSequentialWeekDatesFromWeekStart,
|
|
3266
|
+
weekdayTwoLetterFromYmd
|
|
2666
3267
|
});
|
|
2667
3268
|
//# sourceMappingURL=index.cjs.map
|