@easyteam/auto-scheduler-modal-ui 0.1.0 → 0.1.2
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 +166 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +166 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -272,7 +272,15 @@ type AutoSchedulerModalProps = {
|
|
|
272
272
|
url: string;
|
|
273
273
|
headers?: Record<string, string>;
|
|
274
274
|
};
|
|
275
|
+
getTokenURLAndHeaders: {
|
|
276
|
+
url: string;
|
|
277
|
+
headers?: Record<string, string>;
|
|
278
|
+
};
|
|
275
279
|
onSolution: (solution: ScheduleSolution) => Promise<void>;
|
|
280
|
+
/** When provided, fetches stored config for a location set to prefill the form */
|
|
281
|
+
getStoredConfigForLocations?: (locationIds: string[]) => AutoSchedulerModalConfig | null;
|
|
282
|
+
/** When provided, persists config after successful schedule creation */
|
|
283
|
+
persistConfigForLocations?: (locationIds: string[], config: AutoSchedulerModalConfig) => void;
|
|
276
284
|
};
|
|
277
285
|
type Screen = "configure" | "creating" | "failed";
|
|
278
286
|
type StepStatus = "pending" | "active" | "done";
|
|
@@ -393,7 +401,7 @@ type ScheduleRecommendation = {
|
|
|
393
401
|
why_this_resolves_it: string;
|
|
394
402
|
};
|
|
395
403
|
|
|
396
|
-
declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, onSolution, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
|
|
404
|
+
declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, getTokenURLAndHeaders, onSolution, getStoredConfigForLocations, persistConfigForLocations, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
|
|
397
405
|
|
|
398
406
|
/**
|
|
399
407
|
* Self-contained AutoSchedulerModal that wraps itself in ChakraProvider.
|
package/dist/index.d.ts
CHANGED
|
@@ -272,7 +272,15 @@ type AutoSchedulerModalProps = {
|
|
|
272
272
|
url: string;
|
|
273
273
|
headers?: Record<string, string>;
|
|
274
274
|
};
|
|
275
|
+
getTokenURLAndHeaders: {
|
|
276
|
+
url: string;
|
|
277
|
+
headers?: Record<string, string>;
|
|
278
|
+
};
|
|
275
279
|
onSolution: (solution: ScheduleSolution) => Promise<void>;
|
|
280
|
+
/** When provided, fetches stored config for a location set to prefill the form */
|
|
281
|
+
getStoredConfigForLocations?: (locationIds: string[]) => AutoSchedulerModalConfig | null;
|
|
282
|
+
/** When provided, persists config after successful schedule creation */
|
|
283
|
+
persistConfigForLocations?: (locationIds: string[], config: AutoSchedulerModalConfig) => void;
|
|
276
284
|
};
|
|
277
285
|
type Screen = "configure" | "creating" | "failed";
|
|
278
286
|
type StepStatus = "pending" | "active" | "done";
|
|
@@ -393,7 +401,7 @@ type ScheduleRecommendation = {
|
|
|
393
401
|
why_this_resolves_it: string;
|
|
394
402
|
};
|
|
395
403
|
|
|
396
|
-
declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, onSolution, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
|
|
404
|
+
declare function AutoSchedulerModal({ baseURL, isOpen, title, bannerText, jurisdictions, openShifts, employees, timeOffs, locationPlaceholder, onClose, cancelLabel, primaryActionLabel, onPrimaryAction: _onPrimaryAction, onFixManually, initialConfig, theme, cssVarsRoot, generateRecommendationsURLAndHeaders, getTokenURLAndHeaders, onSolution, getStoredConfigForLocations, persistConfigForLocations, }: AutoSchedulerModalProps): react_jsx_runtime.JSX.Element;
|
|
397
405
|
|
|
398
406
|
/**
|
|
399
407
|
* Self-contained AutoSchedulerModal that wraps itself in ChakraProvider.
|
package/dist/index.js
CHANGED
|
@@ -889,6 +889,7 @@ var SCHEDULE_STEPS = [
|
|
|
889
889
|
{ title: "Finalizing schedule" }
|
|
890
890
|
];
|
|
891
891
|
var STEP_INTERVAL_MS = 1200;
|
|
892
|
+
var POLL_INTERVAL_MS = 2500;
|
|
892
893
|
var OPTIMIZING_STEP_INDEX = 2;
|
|
893
894
|
var NETWORK_ERROR_FAILURE = {
|
|
894
895
|
violatedConstraints: [
|
|
@@ -1421,14 +1422,65 @@ function buildSchedulePayload(input) {
|
|
|
1421
1422
|
};
|
|
1422
1423
|
}
|
|
1423
1424
|
|
|
1424
|
-
// src/api/
|
|
1425
|
+
// src/api/api.ts
|
|
1425
1426
|
import axios from "axios";
|
|
1426
1427
|
var axiosInstance = axios.create({
|
|
1427
1428
|
headers: { "Content-Type": "application/json" }
|
|
1428
1429
|
});
|
|
1429
|
-
async function
|
|
1430
|
+
async function getToken(fullUrl, headers) {
|
|
1431
|
+
var _a;
|
|
1432
|
+
const tokenRes = await axiosInstance.get(fullUrl, {
|
|
1433
|
+
...headers ? { headers } : {}
|
|
1434
|
+
});
|
|
1435
|
+
return (_a = tokenRes.data) == null ? void 0 : _a.token;
|
|
1436
|
+
}
|
|
1437
|
+
async function submitSolveJob(baseURL, payload, options) {
|
|
1438
|
+
const cleanBaseUrl = baseURL.replace(/\/$/, "");
|
|
1439
|
+
const headers = {};
|
|
1440
|
+
if (options == null ? void 0 : options.authorization) {
|
|
1441
|
+
headers.Authorization = options.authorization;
|
|
1442
|
+
}
|
|
1443
|
+
const response = await axiosInstance.post(
|
|
1444
|
+
`${cleanBaseUrl}/api/schedule/solve-async`,
|
|
1445
|
+
payload,
|
|
1446
|
+
{ headers: Object.keys(headers).length > 0 ? headers : void 0 }
|
|
1447
|
+
);
|
|
1448
|
+
return response.data;
|
|
1449
|
+
}
|
|
1450
|
+
async function getJobStatus(baseURL, jobId, options) {
|
|
1430
1451
|
const cleanBaseUrl = baseURL.replace(/\/$/, "");
|
|
1431
|
-
const
|
|
1452
|
+
const headers = {};
|
|
1453
|
+
if (options == null ? void 0 : options.authorization) {
|
|
1454
|
+
headers.Authorization = options.authorization;
|
|
1455
|
+
}
|
|
1456
|
+
const response = await axiosInstance.get(
|
|
1457
|
+
`${cleanBaseUrl}/api/schedule/status/${jobId}`,
|
|
1458
|
+
{ headers: Object.keys(headers).length > 0 ? headers : void 0 }
|
|
1459
|
+
);
|
|
1460
|
+
return response.data;
|
|
1461
|
+
}
|
|
1462
|
+
async function getJobResult(baseURL, jobId, options) {
|
|
1463
|
+
const cleanBaseUrl = baseURL.replace(/\/$/, "");
|
|
1464
|
+
const headers = {};
|
|
1465
|
+
if (options == null ? void 0 : options.authorization) {
|
|
1466
|
+
headers.Authorization = options.authorization;
|
|
1467
|
+
}
|
|
1468
|
+
const response = await axiosInstance.get(
|
|
1469
|
+
`${cleanBaseUrl}/api/schedule/result/${jobId}`,
|
|
1470
|
+
{ headers: Object.keys(headers).length > 0 ? headers : void 0 }
|
|
1471
|
+
);
|
|
1472
|
+
return response.data;
|
|
1473
|
+
}
|
|
1474
|
+
async function getJobSolution(baseURL, jobId, options) {
|
|
1475
|
+
const cleanBaseUrl = baseURL.replace(/\/$/, "");
|
|
1476
|
+
const headers = {};
|
|
1477
|
+
if (options == null ? void 0 : options.authorization) {
|
|
1478
|
+
headers.Authorization = options.authorization;
|
|
1479
|
+
}
|
|
1480
|
+
const response = await axiosInstance.get(
|
|
1481
|
+
`${cleanBaseUrl}/api/schedule/solution/${jobId}`,
|
|
1482
|
+
{ headers: Object.keys(headers).length > 0 ? headers : void 0 }
|
|
1483
|
+
);
|
|
1432
1484
|
return response.data;
|
|
1433
1485
|
}
|
|
1434
1486
|
|
|
@@ -1791,7 +1843,10 @@ function AutoSchedulerModal({
|
|
|
1791
1843
|
theme,
|
|
1792
1844
|
cssVarsRoot,
|
|
1793
1845
|
generateRecommendationsURLAndHeaders,
|
|
1794
|
-
|
|
1846
|
+
getTokenURLAndHeaders,
|
|
1847
|
+
onSolution,
|
|
1848
|
+
getStoredConfigForLocations,
|
|
1849
|
+
persistConfigForLocations
|
|
1795
1850
|
}) {
|
|
1796
1851
|
const baseTheme = useTheme();
|
|
1797
1852
|
const { chakraOverride, styleOverrides } = useMemo2(
|
|
@@ -1813,6 +1868,7 @@ function AutoSchedulerModal({
|
|
|
1813
1868
|
const [lastFailure, setLastFailure] = useState2(null);
|
|
1814
1869
|
const [selectedLocationIds, setSelectedLocationIds] = useState2([]);
|
|
1815
1870
|
const timersRef = useRef([]);
|
|
1871
|
+
const pollIntervalRef = useRef(null);
|
|
1816
1872
|
const runIdRef = useRef(0);
|
|
1817
1873
|
const selectedByJurisdiction = useMemo2(
|
|
1818
1874
|
() => jurisdictions.map((jurisdiction) => ({
|
|
@@ -1878,6 +1934,10 @@ function AutoSchedulerModal({
|
|
|
1878
1934
|
const clearTimers = () => {
|
|
1879
1935
|
timersRef.current.forEach((timerId) => clearTimeout(timerId));
|
|
1880
1936
|
timersRef.current = [];
|
|
1937
|
+
if (pollIntervalRef.current) {
|
|
1938
|
+
clearInterval(pollIntervalRef.current);
|
|
1939
|
+
pollIntervalRef.current = null;
|
|
1940
|
+
}
|
|
1881
1941
|
};
|
|
1882
1942
|
const resetProgress = () => {
|
|
1883
1943
|
setActiveStepIndex(0);
|
|
@@ -1909,6 +1969,23 @@ function AutoSchedulerModal({
|
|
|
1909
1969
|
setScreen("configure");
|
|
1910
1970
|
}
|
|
1911
1971
|
}, [isOpen, initialConfig]);
|
|
1972
|
+
useEffect(() => {
|
|
1973
|
+
if (selectedLocationIds.length === 0 || initialConfig || !getStoredConfigForLocations) {
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
const stored = getStoredConfigForLocations(selectedLocationIds);
|
|
1977
|
+
if (stored) {
|
|
1978
|
+
setSelectedConstraintIds(stored.selectedConstraintIds);
|
|
1979
|
+
setConstraintValues(stored.constraintValues);
|
|
1980
|
+
setSelectedOptimizationIds(stored.selectedOptimizationIds);
|
|
1981
|
+
setOptimizationValues(stored.optimizationValues);
|
|
1982
|
+
} else {
|
|
1983
|
+
setSelectedConstraintIds([]);
|
|
1984
|
+
setConstraintValues({});
|
|
1985
|
+
setSelectedOptimizationIds([]);
|
|
1986
|
+
setOptimizationValues({});
|
|
1987
|
+
}
|
|
1988
|
+
}, [selectedLocationIds, initialConfig, getStoredConfigForLocations]);
|
|
1912
1989
|
useEffect(() => () => clearTimers(), []);
|
|
1913
1990
|
const stepStatusForIndex = (index) => {
|
|
1914
1991
|
if (index <= completedStepIndex) {
|
|
@@ -1985,52 +2062,63 @@ function AutoSchedulerModal({
|
|
|
1985
2062
|
selectedOptimizationIds,
|
|
1986
2063
|
optimizationValues
|
|
1987
2064
|
});
|
|
1988
|
-
|
|
2065
|
+
persistConfigForLocations == null ? void 0 : persistConfigForLocations(selectedLocationIds, {
|
|
2066
|
+
selectedLocationIds,
|
|
2067
|
+
selectedConstraintIds,
|
|
2068
|
+
constraintValues,
|
|
2069
|
+
selectedOptimizationIds,
|
|
2070
|
+
optimizationValues
|
|
2071
|
+
});
|
|
1989
2072
|
const runId = resetBeforeSubmit();
|
|
1990
2073
|
schedule(STEP_INTERVAL_MS, async () => {
|
|
1991
|
-
var _a, _b;
|
|
1992
2074
|
setCompletedStepIndex(0);
|
|
1993
2075
|
setActiveStepIndex(1);
|
|
1994
2076
|
try {
|
|
1995
|
-
const
|
|
2077
|
+
const token = await getToken(getTokenURLAndHeaders.url, getTokenURLAndHeaders.headers);
|
|
2078
|
+
if (!token) {
|
|
2079
|
+
throw new Error("Failed to get auto-scheduler token");
|
|
2080
|
+
}
|
|
2081
|
+
const auth = { authorization: `Bearer ${token}` };
|
|
2082
|
+
const { jobId } = await submitSolveJob(baseURL, payload, auth);
|
|
1996
2083
|
if (runIdRef.current !== runId) {
|
|
1997
2084
|
return;
|
|
1998
2085
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2086
|
+
setCompletedStepIndex(1);
|
|
2087
|
+
setActiveStepIndex(OPTIMIZING_STEP_INDEX);
|
|
2088
|
+
const pollForResult = async () => {
|
|
2089
|
+
if (runIdRef.current !== runId) return;
|
|
2090
|
+
try {
|
|
2091
|
+
const statusRes = await getJobStatus(baseURL, jobId, auth);
|
|
2092
|
+
if (runIdRef.current !== runId) return;
|
|
2093
|
+
if (statusRes.status === "NOT_SOLVING" || statusRes.status === "TERMINATED_EARLY") {
|
|
2094
|
+
if (pollIntervalRef.current) {
|
|
2095
|
+
clearInterval(pollIntervalRef.current);
|
|
2096
|
+
pollIntervalRef.current = null;
|
|
2097
|
+
}
|
|
2098
|
+
const response = await getJobResult(baseURL, jobId, auth);
|
|
2099
|
+
if (runIdRef.current !== runId) return;
|
|
2100
|
+
let responseForHandling = response;
|
|
2101
|
+
if (response.explanation.isFeasible) {
|
|
2102
|
+
const solution = await getJobSolution(baseURL, jobId, auth);
|
|
2103
|
+
if (runIdRef.current !== runId) return;
|
|
2104
|
+
responseForHandling = { ...response, solution };
|
|
2105
|
+
}
|
|
2106
|
+
handleSolveResult(runId, responseForHandling);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
} catch (err) {
|
|
2110
|
+
if (runIdRef.current !== runId) return;
|
|
2111
|
+
if (pollIntervalRef.current) {
|
|
2112
|
+
clearInterval(pollIntervalRef.current);
|
|
2113
|
+
pollIntervalRef.current = null;
|
|
2114
|
+
}
|
|
2115
|
+
console.error(err);
|
|
2116
|
+
setLastFailure(NETWORK_ERROR_FAILURE);
|
|
2117
|
+
setScreen("failed");
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
pollForResult();
|
|
2121
|
+
pollIntervalRef.current = setInterval(pollForResult, POLL_INTERVAL_MS);
|
|
2034
2122
|
} catch (err) {
|
|
2035
2123
|
if (runIdRef.current !== runId) {
|
|
2036
2124
|
return;
|
|
@@ -2041,6 +2129,42 @@ function AutoSchedulerModal({
|
|
|
2041
2129
|
}
|
|
2042
2130
|
}, runId);
|
|
2043
2131
|
};
|
|
2132
|
+
const handleSolveResult = (runId, response) => {
|
|
2133
|
+
var _a, _b;
|
|
2134
|
+
if (!response.explanation.isFeasible) {
|
|
2135
|
+
const result = mapViolationsToResult(
|
|
2136
|
+
(_a = response.explanation.violations) != null ? _a : [],
|
|
2137
|
+
response.explanation.summary
|
|
2138
|
+
);
|
|
2139
|
+
const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter(
|
|
2140
|
+
(l) => selectedLocationIds.includes(l.id)
|
|
2141
|
+
);
|
|
2142
|
+
const recommendationPayload = generateRecommendationsURLAndHeaders ? {
|
|
2143
|
+
context: {
|
|
2144
|
+
selectedLocationIds,
|
|
2145
|
+
selectedConstraintIds,
|
|
2146
|
+
constraintValues,
|
|
2147
|
+
selectedOptimizationIds,
|
|
2148
|
+
optimizationValues,
|
|
2149
|
+
timezone: (_b = selectedLocations[0]) == null ? void 0 : _b.timezone
|
|
2150
|
+
},
|
|
2151
|
+
locations: selectedLocations,
|
|
2152
|
+
employees,
|
|
2153
|
+
openShifts: openShifts.filter((s) => !s.employeeId),
|
|
2154
|
+
assignedShifts: openShifts.filter((s) => Boolean(s.employeeId)),
|
|
2155
|
+
timeOffs: timeOffs != null ? timeOffs : [],
|
|
2156
|
+
explanation: response.explanation
|
|
2157
|
+
} : void 0;
|
|
2158
|
+
setLastFailure({
|
|
2159
|
+
...result,
|
|
2160
|
+
recommendedFixes: [],
|
|
2161
|
+
recommendationPayload
|
|
2162
|
+
});
|
|
2163
|
+
setScreen("failed");
|
|
2164
|
+
} else {
|
|
2165
|
+
afterSuccessSteps(runId, response);
|
|
2166
|
+
}
|
|
2167
|
+
};
|
|
2044
2168
|
const handleAdjustConstraints = () => {
|
|
2045
2169
|
clearTimers();
|
|
2046
2170
|
setScreen("configure");
|