@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.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/solveSchedule.ts
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 solveSchedule(baseURL, payload) {
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 response = await axiosInstance.post(`${cleanBaseUrl}/api/schedule/solve`, payload);
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
- onSolution
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
- const solvePromise = solveSchedule(baseURL, payload);
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 response = await solvePromise;
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
- if (!response.explanation.isFeasible) {
2000
- const result = mapViolationsToResult(
2001
- (_a = response.explanation.violations) != null ? _a : [],
2002
- response.explanation.summary
2003
- );
2004
- const selectedLocations = jurisdictions.flatMap((j) => j.locations).filter(
2005
- (l) => selectedLocationIds.includes(l.id)
2006
- );
2007
- const recommendationPayload = generateRecommendationsURLAndHeaders ? {
2008
- context: {
2009
- selectedLocationIds,
2010
- selectedConstraintIds,
2011
- constraintValues,
2012
- selectedOptimizationIds,
2013
- optimizationValues,
2014
- timezone: (_b = selectedLocations[0]) == null ? void 0 : _b.timezone
2015
- },
2016
- locations: selectedLocations,
2017
- employees,
2018
- openShifts: openShifts.filter((s) => !s.employeeId),
2019
- assignedShifts: openShifts.filter((s) => Boolean(s.employeeId)),
2020
- timeOffs: timeOffs != null ? timeOffs : [],
2021
- explanation: response.explanation
2022
- } : void 0;
2023
- setLastFailure({
2024
- ...result,
2025
- recommendedFixes: [],
2026
- recommendationPayload
2027
- });
2028
- setScreen("failed");
2029
- } else {
2030
- setCompletedStepIndex(1);
2031
- setActiveStepIndex(OPTIMIZING_STEP_INDEX);
2032
- afterSuccessSteps(runId, response);
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");