@servicetitan/dte-pdf-editor 1.96.0 → 1.96.1
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/README.md +3 -0
- package/dist/components/field-config-panel/advanced-settings.d.ts +1 -1
- package/dist/components/field-config-panel/advanced-settings.js +1 -1
- package/dist/components/field-config-panel/e-sign-options-modal.d.ts +1 -1
- package/dist/components/field-config-panel/e-sign-options-modal.js +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts +1 -1
- package/dist/components/field-config-panel/formula-generator.d.ts +1 -1
- package/dist/components/field-config-panel/formula-modal.d.ts +1 -1
- package/dist/components/field-config-panel/formula-workspace.d.ts +1 -1
- package/dist/interface/types.d.ts +1 -1
- package/dist/vendor/dte-shared/business-calendar-validation.utils.d.ts +22 -0
- package/dist/vendor/dte-shared/business-calendar-validation.utils.js +76 -0
- package/dist/vendor/dte-shared/components/business-calendar-section.d.ts +15 -0
- package/dist/vendor/dte-shared/components/business-calendar-section.js +13 -0
- package/dist/vendor/dte-shared/components/schedule-drawer.d.ts +16 -0
- package/dist/vendor/dte-shared/components/schedule-drawer.js +112 -0
- package/dist/vendor/dte-shared/components/schedule-picker.d.ts +8 -0
- package/dist/vendor/dte-shared/components/schedule-picker.js +88 -0
- package/dist/vendor/dte-shared/constants.d.ts +10 -0
- package/dist/vendor/dte-shared/constants.js +10 -0
- package/dist/vendor/dte-shared/index.d.ts +6 -0
- package/dist/vendor/dte-shared/index.js +5 -0
- package/dist/vendor/dte-shared/styles/schedule.css +19 -0
- package/dist/vendor/dte-shared/types.d.ts +67 -0
- package/dist/vendor/dte-shared/types.js +1 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -31,6 +31,9 @@ formulas.
|
|
|
31
31
|
|
|
32
32
|
### Recent changes
|
|
33
33
|
|
|
34
|
+
- **fix/shared-incloud-on-BYOD-build** (installable package) — the package no longer depends on the unpublished
|
|
35
|
+
`@servicetitan/dte-shared`; its code is bundled in, so `npm install` works. See
|
|
36
|
+
[RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
|
34
37
|
- **feat/DTE-4695/custom-weekend-holiday-exclusion** (business-day calendars) — a per-field "Exclude weekends &
|
|
35
38
|
holidays" toggle, schedule picker and "Create New Schedule" drawer in the Formula Builder (`scheduleBindings`,
|
|
36
39
|
`holidayCatalog`, `schedules`, `onScheduleBindingsChange`, `onCreateSchedule`); date calculated fields take a
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ScheduleEditorContext } from '
|
|
1
|
+
import { type ScheduleEditorContext } from '../../vendor/dte-shared/index';
|
|
2
2
|
import { Dispatch, FC, SetStateAction } from 'react';
|
|
3
3
|
import { type CalculatedFieldFormat } from '../../interface/types';
|
|
4
4
|
interface AdvancedSettingsProps {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Flex, SegmentedControl, Switch, Text } from '@servicetitan/anvil2';
|
|
3
|
-
import { BusinessCalendarSection } from '
|
|
3
|
+
import { BusinessCalendarSection } from '../../vendor/dte-shared/index';
|
|
4
4
|
import { DEFAULT_DATE_FORMAT, ROUNDING_OPTIONS } from '../../constants';
|
|
5
5
|
const DateFormatSettings = ({ businessCalendar, format, setFormat }) => {
|
|
6
6
|
var _a;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ScheduleEditorContext } from '
|
|
1
|
+
import { type ScheduleEditorContext } from '../../vendor/dte-shared/index';
|
|
2
2
|
import { FC } from 'react';
|
|
3
3
|
import { ESignDateSignedOffsetSettings, FormFieldsByFormIdI, FormInfo, PdfField, SchemaObject } from '../../interface/types';
|
|
4
4
|
interface ESignOptionsModalProps {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, Flex, NumberField } from '@servicetitan/anvil2';
|
|
3
3
|
import AddIcon from '@servicetitan/anvil2/assets/icons/material/round/add_circle.svg';
|
|
4
|
-
import { BusinessCalendarSection } from '
|
|
4
|
+
import { BusinessCalendarSection } from '../../vendor/dte-shared/index';
|
|
5
5
|
import { Fragment, useState } from 'react';
|
|
6
6
|
import { createClearedESignOffsetSettings } from '../../utils';
|
|
7
7
|
import { ESignDateRulesSummary } from './e-sign-date-rules-summary';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ScheduleEditorContext } from '
|
|
1
|
+
import { type ScheduleEditorContext } from '../../vendor/dte-shared/index';
|
|
2
2
|
import { FC } from 'react';
|
|
3
3
|
import { type FormFieldsByFormIdI, type FormInfo, type PdfField, type RecipientInfo, type SchemaObject } from '../../interface/types';
|
|
4
4
|
import { type DateCalcOptions } from '../../utils';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ScheduleEditorContext } from '
|
|
1
|
+
import { type ScheduleEditorContext } from '../../vendor/dte-shared/index';
|
|
2
2
|
import { FC } from 'react';
|
|
3
3
|
import { type CalculatedFieldFormat, type FormFieldsByFormIdI, type FormInfo, type PdfField, type SchemaObject, type StructuredFormula } from '../../interface/types';
|
|
4
4
|
import { type DateCalcOptions } from '../../utils';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ScheduleEditorContext } from '
|
|
1
|
+
import { type ScheduleEditorContext } from '../../vendor/dte-shared/index';
|
|
2
2
|
import { FC } from 'react';
|
|
3
3
|
import { type CalculatedFieldFormat, type DataModelFieldGroup, type FieldTypeOption, type FormFieldsByFormIdI, type FormInfo, type StructuredFormula } from '../../interface/types';
|
|
4
4
|
import { type DateCalcOptions } from '../../utils';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ScheduleEditorContext } from '
|
|
1
|
+
import { type ScheduleEditorContext } from '../../vendor/dte-shared/index';
|
|
2
2
|
import { Dispatch, FC, KeyboardEvent, MouseEvent, ReactNode, RefObject, SetStateAction } from 'react';
|
|
3
3
|
import { type CalculatedFieldFormat } from '../../interface/types';
|
|
4
4
|
interface FormulaWorkspaceProps {
|
|
@@ -255,7 +255,7 @@ export interface BusinessCalendar {
|
|
|
255
255
|
* working: the schedule editor UI (`ScheduleDrawer`/`SchedulePicker`) is shared with
|
|
256
256
|
* `@servicetitan/dte-unlayer` and lives there now, these types are its domain model.
|
|
257
257
|
*/
|
|
258
|
-
export type { BusinessCalendarRules, BusinessDaySchedule, HolidayCatalog, KnownHoliday, SaveScheduleRequest, WeekendDay, } from '
|
|
258
|
+
export type { BusinessCalendarRules, BusinessDaySchedule, HolidayCatalog, KnownHoliday, SaveScheduleRequest, WeekendDay, } from '../vendor/dte-shared/index';
|
|
259
259
|
/**
|
|
260
260
|
* A date calculated field whose custom business-day exclusion toggle is on, and the schedule it
|
|
261
261
|
* uses (DTE's bindings, full-replace via `PUT api/holidays/templates/{id}/bindings`). A field with
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Whether `value` is a valid `MM-DD` custom date (`02-29` included, as a recurring rule). */
|
|
2
|
+
export declare function isValidCustomDate(value: string): boolean;
|
|
3
|
+
/** A month/day pair as `DateFieldYearless` represents it (either part `null` while incomplete). */
|
|
4
|
+
export interface YearlessDateValue {
|
|
5
|
+
month: number | null;
|
|
6
|
+
day: number | null;
|
|
7
|
+
}
|
|
8
|
+
/** Converts a complete `{ month, day }` to `MM-DD`, or null while the field is incomplete/invalid. */
|
|
9
|
+
export declare function yearlessDateToCustomDate(value: YearlessDateValue | null): string | null;
|
|
10
|
+
/** Converts a stored `MM-DD` custom date back to `DateFieldYearless`'s `{ month, day }` value. */
|
|
11
|
+
export declare function customDateToYearlessDate(value: string): YearlessDateValue | null;
|
|
12
|
+
/** Formats a stored `MM-DD` custom date for display, e.g. `03-03` → `Mar - 03`. */
|
|
13
|
+
export declare function formatCustomDateLabel(value: string): string;
|
|
14
|
+
/** Whether `name` is the reserved default-schedule name, ignoring case and surrounding whitespace. */
|
|
15
|
+
export declare function isReservedScheduleName(name: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Client-side schedule name checks mirroring DTE's `SaveDateScheduleRequest.Name` contract: a
|
|
18
|
+
* required, non-reserved name up to 255 characters, unique per tenant ignoring case. `existingNames`
|
|
19
|
+
* should exclude the schedule being edited, if any. The server is still authoritative — a duplicate
|
|
20
|
+
* it rejects despite passing this check is a 409, not a bug here.
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateScheduleName(name: string, existingNames: string[]): string | undefined;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CUSTOM_DATE_MONTH_LENGTHS, CUSTOM_DATE_PATTERN, MAX_SCHEDULE_NAME_LENGTH, RESERVED_SCHEDULE_NAME, } from './constants';
|
|
2
|
+
/** Whether `value` is a valid `MM-DD` custom date (`02-29` included, as a recurring rule). */
|
|
3
|
+
export function isValidCustomDate(value) {
|
|
4
|
+
const match = CUSTOM_DATE_PATTERN.exec(value);
|
|
5
|
+
if (!match) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const month = Number(match[1]);
|
|
9
|
+
const day = Number(match[2]);
|
|
10
|
+
return day <= CUSTOM_DATE_MONTH_LENGTHS[month - 1];
|
|
11
|
+
}
|
|
12
|
+
/** Converts a complete `{ month, day }` to `MM-DD`, or null while the field is incomplete/invalid. */
|
|
13
|
+
export function yearlessDateToCustomDate(value) {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const month = (_a = value === null || value === void 0 ? void 0 : value.month) !== null && _a !== void 0 ? _a : null;
|
|
16
|
+
const day = (_b = value === null || value === void 0 ? void 0 : value.day) !== null && _b !== void 0 ? _b : null;
|
|
17
|
+
if (month === null || day === null) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const candidate = `${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
21
|
+
return isValidCustomDate(candidate) ? candidate : null;
|
|
22
|
+
}
|
|
23
|
+
/** Converts a stored `MM-DD` custom date back to `DateFieldYearless`'s `{ month, day }` value. */
|
|
24
|
+
export function customDateToYearlessDate(value) {
|
|
25
|
+
const match = CUSTOM_DATE_PATTERN.exec(value);
|
|
26
|
+
return match ? { month: Number(match[1]), day: Number(match[2]) } : null;
|
|
27
|
+
}
|
|
28
|
+
const CUSTOM_DATE_MONTH_ABBREVIATIONS = [
|
|
29
|
+
'Jan',
|
|
30
|
+
'Feb',
|
|
31
|
+
'Mar',
|
|
32
|
+
'Apr',
|
|
33
|
+
'May',
|
|
34
|
+
'Jun',
|
|
35
|
+
'Jul',
|
|
36
|
+
'Aug',
|
|
37
|
+
'Sep',
|
|
38
|
+
'Oct',
|
|
39
|
+
'Nov',
|
|
40
|
+
'Dec',
|
|
41
|
+
];
|
|
42
|
+
/** Formats a stored `MM-DD` custom date for display, e.g. `03-03` → `Mar - 03`. */
|
|
43
|
+
export function formatCustomDateLabel(value) {
|
|
44
|
+
const match = CUSTOM_DATE_PATTERN.exec(value);
|
|
45
|
+
if (!match) {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
const [, month, day] = match;
|
|
49
|
+
return `${CUSTOM_DATE_MONTH_ABBREVIATIONS[Number(month) - 1]} - ${day}`;
|
|
50
|
+
}
|
|
51
|
+
/** Whether `name` is the reserved default-schedule name, ignoring case and surrounding whitespace. */
|
|
52
|
+
export function isReservedScheduleName(name) {
|
|
53
|
+
return name.trim().toLowerCase() === RESERVED_SCHEDULE_NAME.toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Client-side schedule name checks mirroring DTE's `SaveDateScheduleRequest.Name` contract: a
|
|
57
|
+
* required, non-reserved name up to 255 characters, unique per tenant ignoring case. `existingNames`
|
|
58
|
+
* should exclude the schedule being edited, if any. The server is still authoritative — a duplicate
|
|
59
|
+
* it rejects despite passing this check is a 409, not a bug here.
|
|
60
|
+
*/
|
|
61
|
+
export function validateScheduleName(name, existingNames) {
|
|
62
|
+
const trimmed = name.trim();
|
|
63
|
+
if (!trimmed) {
|
|
64
|
+
return 'Schedule name is required';
|
|
65
|
+
}
|
|
66
|
+
if (trimmed.length > MAX_SCHEDULE_NAME_LENGTH) {
|
|
67
|
+
return `Schedule name must be ${MAX_SCHEDULE_NAME_LENGTH} characters or fewer`;
|
|
68
|
+
}
|
|
69
|
+
if (isReservedScheduleName(trimmed)) {
|
|
70
|
+
return `"${RESERVED_SCHEDULE_NAME}" is reserved for the default schedule`;
|
|
71
|
+
}
|
|
72
|
+
if (existingNames.some(existing => existing.trim().toLowerCase() === trimmed.toLowerCase())) {
|
|
73
|
+
return 'A schedule with this name already exists';
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import '../styles/schedule.css';
|
|
3
|
+
import { type ScheduleEditorContext } from '../types';
|
|
4
|
+
interface BusinessCalendarSectionProps {
|
|
5
|
+
businessCalendar: ScheduleEditorContext;
|
|
6
|
+
/** @default "Exclude weekends & holidays from the calculation" */
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A business-day exclusion toggle, shown for a date calculated field (Formula Builder's Advanced
|
|
11
|
+
* Settings, date result type only) and an e-sign `dateSignedOffset` field's day-offset settings.
|
|
12
|
+
* Off is plain calendar-day arithmetic; on reveals the schedule picker.
|
|
13
|
+
*/
|
|
14
|
+
export declare const BusinessCalendarSection: FC<BusinessCalendarSectionProps>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Flex, Switch } from '@servicetitan/anvil2';
|
|
3
|
+
import '../styles/schedule.css';
|
|
4
|
+
import { SchedulePicker } from './schedule-picker';
|
|
5
|
+
/**
|
|
6
|
+
* A business-day exclusion toggle, shown for a date calculated field (Formula Builder's Advanced
|
|
7
|
+
* Settings, date result type only) and an e-sign `dateSignedOffset` field's day-offset settings.
|
|
8
|
+
* Off is plain calendar-day arithmetic; on reveals the schedule picker.
|
|
9
|
+
*/
|
|
10
|
+
export const BusinessCalendarSection = ({ businessCalendar, label = 'Exclude weekends & holidays from the calculation', }) => {
|
|
11
|
+
const { enabled, onToggle } = businessCalendar;
|
|
12
|
+
return (_jsx("div", { className: "dte-business-calendar-section", children: _jsxs(Flex, { direction: "column", gap: "4", children: [_jsx(Switch, { checked: enabled, onChange: (_e, state) => onToggle(state.checked), label: label, value: "business-calendar-exclusion" }), enabled && _jsx(SchedulePicker, { businessCalendar: businessCalendar })] }) }));
|
|
13
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { type BusinessDaySchedule, type ScheduleEditorContext } from '../types';
|
|
3
|
+
interface ScheduleDrawerProps {
|
|
4
|
+
businessCalendar: ScheduleEditorContext;
|
|
5
|
+
/** When set, edits this schedule in place (PUT) instead of creating a new one (POST). */
|
|
6
|
+
editingSchedule?: BusinessDaySchedule;
|
|
7
|
+
onClose(): void;
|
|
8
|
+
onSaved(schedule: BusinessDaySchedule): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* "Create New Schedule" / "Edit Schedule" drawer. Creating starts from the default schedule's
|
|
12
|
+
* rules (DEFAULT_SCHEDULE_WEEKEND_DAYS, every catalog holiday); editing starts from
|
|
13
|
+
* `editingSchedule`'s own rules and replaces it in place on save.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ScheduleDrawer: FC<ScheduleDrawerProps>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Alert, Button, ButtonToggle, DateFieldYearless, Drawer, Flex, MultiSelectFieldSync, Text, TextField, } from '@servicetitan/anvil2';
|
|
3
|
+
import IconAddCircle from '@servicetitan/anvil2/assets/icons/material/round/add_circle.svg';
|
|
4
|
+
import DeleteIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';
|
|
5
|
+
import { useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { customDateToYearlessDate, validateScheduleName, yearlessDateToCustomDate, } from '../business-calendar-validation.utils';
|
|
7
|
+
import { DEFAULT_SCHEDULE_WEEKEND_DAYS } from '../constants';
|
|
8
|
+
const EMPTY_DATE = { month: null, day: null };
|
|
9
|
+
const isEmptyDate = (value) => value.month === null && value.day === null;
|
|
10
|
+
/**
|
|
11
|
+
* "Create New Schedule" / "Edit Schedule" drawer. Creating starts from the default schedule's
|
|
12
|
+
* rules (DEFAULT_SCHEDULE_WEEKEND_DAYS, every catalog holiday); editing starts from
|
|
13
|
+
* `editingSchedule`'s own rules and replaces it in place on save.
|
|
14
|
+
*/
|
|
15
|
+
export const ScheduleDrawer = ({ businessCalendar, editingSchedule, onClose, onSaved, }) => {
|
|
16
|
+
var _a;
|
|
17
|
+
const { holidayCatalog, holidayCatalogError, holidayCatalogLoading, onCreateSchedule, onUpdateSchedule, schedules, } = businessCalendar;
|
|
18
|
+
const [name, setName] = useState((_a = editingSchedule === null || editingSchedule === void 0 ? void 0 : editingSchedule.name) !== null && _a !== void 0 ? _a : '');
|
|
19
|
+
const [weekendDays, setWeekendDays] = useState(() => { var _a; return new Set((_a = editingSchedule === null || editingSchedule === void 0 ? void 0 : editingSchedule.calendar.weekendDays) !== null && _a !== void 0 ? _a : DEFAULT_SCHEDULE_WEEKEND_DAYS); });
|
|
20
|
+
const [knownHolidays, setKnownHolidays] = useState(() => {
|
|
21
|
+
var _a, _b;
|
|
22
|
+
return new Set((_b = (_a = editingSchedule === null || editingSchedule === void 0 ? void 0 : editingSchedule.calendar.knownHolidays) !== null && _a !== void 0 ? _a : holidayCatalog === null || holidayCatalog === void 0 ? void 0 : holidayCatalog.holidays.map(h => h.code)) !== null && _b !== void 0 ? _b : []);
|
|
23
|
+
});
|
|
24
|
+
const [customDates, setCustomDates] = useState(() => {
|
|
25
|
+
var _a;
|
|
26
|
+
return ((_a = editingSchedule === null || editingSchedule === void 0 ? void 0 : editingSchedule.calendar.customDates) !== null && _a !== void 0 ? _a : []).map((date, index) => {
|
|
27
|
+
var _a;
|
|
28
|
+
return ({
|
|
29
|
+
id: index,
|
|
30
|
+
value: (_a = customDateToYearlessDate(date)) !== null && _a !== void 0 ? _a : EMPTY_DATE,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
const nextRowId = useRef(customDates.length);
|
|
35
|
+
const [nameTouched, setNameTouched] = useState(false);
|
|
36
|
+
const [submitError, setSubmitError] = useState();
|
|
37
|
+
const [submitting, setSubmitting] = useState(false);
|
|
38
|
+
const holidayOptions = useMemo(() => {
|
|
39
|
+
var _a;
|
|
40
|
+
return ((_a = holidayCatalog === null || holidayCatalog === void 0 ? void 0 : holidayCatalog.holidays) !== null && _a !== void 0 ? _a : []).map(h => ({
|
|
41
|
+
id: h.code,
|
|
42
|
+
label: `${h.displayName} · ${h.rule}`,
|
|
43
|
+
}));
|
|
44
|
+
}, [holidayCatalog]);
|
|
45
|
+
const selectedHolidayOptions = useMemo(() => holidayOptions.filter(o => knownHolidays.has(String(o.id))), [holidayOptions, knownHolidays]);
|
|
46
|
+
const existingNames = useMemo(() => (schedules !== null && schedules !== void 0 ? schedules : [])
|
|
47
|
+
.filter(s => !s.isDefault && s.id !== (editingSchedule === null || editingSchedule === void 0 ? void 0 : editingSchedule.id))
|
|
48
|
+
.map(s => s.name), [schedules, editingSchedule]);
|
|
49
|
+
const nameError = validateScheduleName(name, existingNames);
|
|
50
|
+
const toggleWeekendDay = (day) => {
|
|
51
|
+
setWeekendDays(prev => {
|
|
52
|
+
const next = new Set(prev);
|
|
53
|
+
if (next.has(day)) {
|
|
54
|
+
next.delete(day);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
next.add(day);
|
|
58
|
+
}
|
|
59
|
+
return next;
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const handleAddDate = () => setCustomDates(prev => [...prev, { id: nextRowId.current++, value: EMPTY_DATE }]);
|
|
63
|
+
const handleRemoveDate = (id) => setCustomDates(prev => prev.filter(row => row.id !== id));
|
|
64
|
+
const handleCustomDateChange = (id, value) => setCustomDates(prev => prev.map(row => (row.id === id ? { ...row, value: value !== null && value !== void 0 ? value : EMPTY_DATE } : row)));
|
|
65
|
+
/** Resolved `MM-DD` per non-empty row, or `null` for an incomplete/invalid one (blocks submit). */
|
|
66
|
+
const resolvedCustomDates = customDates.map(row => isEmptyDate(row.value) ? '' : yearlessDateToCustomDate(row.value));
|
|
67
|
+
const hasInvalidCustomDate = resolvedCustomDates.some((d, i) => d === null && !isEmptyDate(customDates[i].value));
|
|
68
|
+
/*
|
|
69
|
+
* Editing is only offered for a saved tenant schedule, which always has an id (only the
|
|
70
|
+
* read-only default schedule — never editable — lacks one).
|
|
71
|
+
*/
|
|
72
|
+
const editingId = editingSchedule === null || editingSchedule === void 0 ? void 0 : editingSchedule.id;
|
|
73
|
+
const isEditing = editingId !== undefined;
|
|
74
|
+
const canSave = isEditing ? !!onUpdateSchedule : !!onCreateSchedule;
|
|
75
|
+
const canSubmit = !nameError && !hasInvalidCustomDate && !submitting && canSave;
|
|
76
|
+
const handleSubmit = async () => {
|
|
77
|
+
setNameTouched(true);
|
|
78
|
+
if (nameError || hasInvalidCustomDate || !canSave) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const request = {
|
|
82
|
+
name: name.trim(),
|
|
83
|
+
weekendDays: Array.from(weekendDays),
|
|
84
|
+
knownHolidays: Array.from(knownHolidays),
|
|
85
|
+
customDates: Array.from(new Set(resolvedCustomDates.filter((d) => !!d))),
|
|
86
|
+
};
|
|
87
|
+
setSubmitting(true);
|
|
88
|
+
setSubmitError(undefined);
|
|
89
|
+
try {
|
|
90
|
+
if (isEditing && onUpdateSchedule) {
|
|
91
|
+
onSaved(await onUpdateSchedule(editingId, request));
|
|
92
|
+
}
|
|
93
|
+
else if (!isEditing && onCreateSchedule) {
|
|
94
|
+
onSaved(await onCreateSchedule(request));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
setSubmitError(e instanceof Error
|
|
99
|
+
? e.message
|
|
100
|
+
: `Failed to ${isEditing ? 'save' : 'create'} schedule`);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
setSubmitting(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return (_jsxs(Drawer, { open: true, onClose: onClose, size: "medium", children: [_jsx(Drawer.Header, { children: isEditing ? 'Edit Schedule' : 'Create New Schedule' }), _jsx(Drawer.Content, { children: _jsxs(Flex, { direction: "column", gap: "4", children: [_jsx(TextField, { required: true, label: "Schedule Name", value: name, onChange: e => setName(e.target.value), onBlur: () => setNameTouched(true), error: nameTouched ? nameError : undefined }), _jsxs(Flex, { direction: "column", gap: "2", children: [_jsx(Text, { variant: "eyebrow", size: "small", children: "Exclude Weekends" }), _jsxs(Flex, { gap: "3", children: [_jsx(ButtonToggle, { checked: weekendDays.has(6), onClick: () => toggleWeekendDay(6), children: "Saturday" }), _jsx(ButtonToggle, { checked: weekendDays.has(0), onClick: () => toggleWeekendDay(0), children: "Sunday" })] })] }), _jsxs(Flex, { direction: "column", gap: "2", children: [_jsx(Text, { variant: "eyebrow", size: "small", children: "Exclude Holidays" }), holidayCatalogLoading && _jsx(Text, { variant: "body", children: "Loading holidays\u2026" }), holidayCatalogError && (_jsx(Alert, { status: "danger", title: holidayCatalogError })), _jsx(MultiSelectFieldSync, { label: holidayCatalog
|
|
107
|
+
? `${holidayCatalog.jurisdiction} Holidays`
|
|
108
|
+
: 'Holidays', disabled: holidayCatalogLoading, options: holidayOptions, value: selectedHolidayOptions, onSelectedOptionsChange: options => setKnownHolidays(new Set(options.map(o => String(o.id)))), description: "Select the holidays you want to exclude", filter: { keys: ['label'] } })] }), _jsxs(Flex, { direction: "column", gap: "2", children: [_jsx(Text, { variant: "eyebrow", size: "small", children: "Custom Dates" }), customDates.map((row, index) => {
|
|
109
|
+
const invalid = resolvedCustomDates[index] === null && !isEmptyDate(row.value);
|
|
110
|
+
return (_jsxs(Flex, { gap: "2", children: [_jsx(DateFieldYearless, { label: `Custom date ${index + 1}`, hideLabel: true, value: row.value, error: invalid ? 'Enter a valid date' : undefined, onChange: change => handleCustomDateChange(row.id, change.value), flex: 1 }), _jsx(Button, { icon: DeleteIcon, appearance: "ghost", "aria-label": "Remove date", onClick: () => handleRemoveDate(row.id) })] }, row.id));
|
|
111
|
+
}), _jsx(Button, { appearance: "ghost", icon: IconAddCircle, onClick: handleAddDate, children: "Add Date" })] }), submitError && _jsx(Alert, { status: "danger", title: submitError })] }) }), _jsxs(Drawer.Footer, { sticky: true, children: [_jsx(Drawer.CancelButton, { appearance: "secondary", children: "Cancel" }), _jsx(Button, { appearance: "primary", onClick: handleSubmit, loading: submitting, disabled: !canSubmit, children: isEditing ? 'Save' : 'Create' })] })] }));
|
|
112
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { type ScheduleEditorContext } from '../types';
|
|
3
|
+
interface SchedulePickerProps {
|
|
4
|
+
businessCalendar: ScheduleEditorContext;
|
|
5
|
+
}
|
|
6
|
+
/** "Select Schedule or create new" picker plus a collapsible preview of the selected schedule's rules. */
|
|
7
|
+
export declare const SchedulePicker: FC<SchedulePickerProps>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Alert, Button, Chip, Flex, SelectFieldSync, Text } from '@servicetitan/anvil2';
|
|
3
|
+
import DeleteIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';
|
|
4
|
+
import EditIcon from '@servicetitan/anvil2/assets/icons/material/round/edit.svg';
|
|
5
|
+
import IconExpandLess from '@servicetitan/anvil2/assets/icons/material/round/expand_less.svg';
|
|
6
|
+
import IconExpandMore from '@servicetitan/anvil2/assets/icons/material/round/expand_more.svg';
|
|
7
|
+
import { Fragment, useMemo, useState } from 'react';
|
|
8
|
+
import { formatCustomDateLabel } from '../business-calendar-validation.utils';
|
|
9
|
+
import { ScheduleDrawer } from './schedule-drawer';
|
|
10
|
+
/** `BusinessCalendarRules.weekendDays` day numbers, as `Date.getUTCDay()` numbers them. */
|
|
11
|
+
const WEEKEND_DAY_LABELS = { 0: 'Sundays', 6: 'Saturdays' };
|
|
12
|
+
const DEFAULT_OPTION_ID = 'default';
|
|
13
|
+
/** "Select Schedule or create new" picker plus a collapsible preview of the selected schedule's rules. */
|
|
14
|
+
export const SchedulePicker = ({ businessCalendar }) => {
|
|
15
|
+
var _a;
|
|
16
|
+
const { holidayCatalog, holidayCatalogError, holidayCatalogLoading, onDeleteSchedule, onSelectSchedule, schedules, schedulesError, schedulesLoading, selectedScheduleId, } = businessCalendar;
|
|
17
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
18
|
+
const [editingSchedule, setEditingSchedule] = useState();
|
|
19
|
+
const [previewOpen, setPreviewOpen] = useState(true);
|
|
20
|
+
const [deleting, setDeleting] = useState(false);
|
|
21
|
+
const [deleteError, setDeleteError] = useState();
|
|
22
|
+
const options = useMemo(() => (schedules !== null && schedules !== void 0 ? schedules : []).map(s => { var _a; return ({ id: (_a = s.id) !== null && _a !== void 0 ? _a : DEFAULT_OPTION_ID, label: s.name }); }), [schedules]);
|
|
23
|
+
const selectedOptionId = selectedScheduleId !== null && selectedScheduleId !== void 0 ? selectedScheduleId : DEFAULT_OPTION_ID;
|
|
24
|
+
const selectedOption = (_a = options.find(o => o.id === selectedOptionId)) !== null && _a !== void 0 ? _a : null;
|
|
25
|
+
const selectedSchedule = (schedules !== null && schedules !== void 0 ? schedules : []).find(s => { var _a; return ((_a = s.id) !== null && _a !== void 0 ? _a : DEFAULT_OPTION_ID) === selectedOptionId; });
|
|
26
|
+
const holidayByCode = useMemo(() => {
|
|
27
|
+
var _a;
|
|
28
|
+
const map = new Map();
|
|
29
|
+
for (const h of (_a = holidayCatalog === null || holidayCatalog === void 0 ? void 0 : holidayCatalog.holidays) !== null && _a !== void 0 ? _a : []) {
|
|
30
|
+
map.set(h.code, h);
|
|
31
|
+
}
|
|
32
|
+
return map;
|
|
33
|
+
}, [holidayCatalog]);
|
|
34
|
+
const handleSelectedOptionChange = (option) => {
|
|
35
|
+
if (!option) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
onSelectSchedule(option.id === DEFAULT_OPTION_ID ? undefined : option.id);
|
|
39
|
+
};
|
|
40
|
+
const handleSaved = (schedule) => {
|
|
41
|
+
onSelectSchedule(schedule.id);
|
|
42
|
+
setDrawerOpen(false);
|
|
43
|
+
setEditingSchedule(undefined);
|
|
44
|
+
};
|
|
45
|
+
const handleOpenCreate = () => {
|
|
46
|
+
setEditingSchedule(undefined);
|
|
47
|
+
setDrawerOpen(true);
|
|
48
|
+
};
|
|
49
|
+
const handleOpenEdit = () => {
|
|
50
|
+
if (!selectedSchedule) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setEditingSchedule(selectedSchedule);
|
|
54
|
+
setDrawerOpen(true);
|
|
55
|
+
};
|
|
56
|
+
const handleCloseDrawer = () => {
|
|
57
|
+
setDrawerOpen(false);
|
|
58
|
+
setEditingSchedule(undefined);
|
|
59
|
+
};
|
|
60
|
+
const handleDelete = async () => {
|
|
61
|
+
if (!(selectedSchedule === null || selectedSchedule === void 0 ? void 0 : selectedSchedule.id) || !onDeleteSchedule) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setDeleting(true);
|
|
65
|
+
setDeleteError(undefined);
|
|
66
|
+
try {
|
|
67
|
+
await onDeleteSchedule(selectedSchedule.id);
|
|
68
|
+
onSelectSchedule(undefined);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
setDeleteError(e instanceof Error ? e.message : 'Failed to delete schedule');
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
setDeleting(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return (_jsxs(Fragment, { children: [_jsx(SelectFieldSync, { label: "Select Schedule or create new", options: options, value: selectedOption, disableClearButton: true, disabled: schedulesLoading, onSelectedOptionChange: handleSelectedOptionChange, onAddNewItem: handleOpenCreate, addItemLabel: "Create new schedule", filter: { keys: ['label'] } }), schedulesLoading && _jsx(Text, { variant: "body", children: "Loading schedules\u2026" }), schedulesError && _jsx(Alert, { status: "danger", title: schedulesError }), selectedSchedule && (_jsxs("div", { className: "dte-business-calendar-preview", children: [_jsxs(Flex, { alignItems: "center", justifyContent: "space-between", gap: "2", className: "dte-business-calendar-preview-header", children: [_jsxs(Flex, { alignItems: "center", gap: "2", onClick: () => setPreviewOpen(prev => !prev), children: [_jsx(Button, { appearance: "ghost", size: "small", icon: previewOpen ? IconExpandLess : IconExpandMore, "aria-label": previewOpen
|
|
78
|
+
? 'Collapse schedule preview'
|
|
79
|
+
: 'Expand schedule preview' }), _jsx(Text, { variant: "body", children: _jsx("strong", { children: selectedSchedule.name }) })] }), !selectedSchedule.isDefault && (_jsxs(Flex, { alignItems: "center", gap: "2", children: [_jsx(Button, { icon: EditIcon, size: "small", appearance: "ghost", onClick: handleOpenEdit, disabled: deleting, children: "Edit" }), _jsx(Button, { icon: DeleteIcon, size: "small", appearance: "ghost", "aria-label": "Delete schedule", onClick: handleDelete, loading: deleting, disabled: !onDeleteSchedule })] }))] }), deleteError && _jsx(Alert, { status: "danger", title: deleteError }), previewOpen && (_jsxs(Flex, { direction: "column", gap: "3", className: "dte-business-calendar-preview-content", children: [_jsxs(Flex, { direction: "column", gap: "2", children: [_jsx(Text, { variant: "eyebrow", size: "small", children: "Excluded Weekends" }), _jsx(Flex, { gap: "2", style: { flexWrap: 'wrap' }, children: selectedSchedule.calendar.weekendDays.map(day => {
|
|
80
|
+
var _a;
|
|
81
|
+
return (_jsx(Chip, { label: (_a = WEEKEND_DAY_LABELS[day]) !== null && _a !== void 0 ? _a : String(day), size: "medium" }, day));
|
|
82
|
+
}) })] }), _jsxs(Flex, { direction: "column", gap: "2", children: [_jsx(Text, { variant: "eyebrow", size: "small", children: "Excluded Holidays" }), holidayCatalogLoading && (_jsx(Text, { variant: "body", children: "Loading holidays\u2026" })), holidayCatalogError && (_jsx(Alert, { status: "danger", title: holidayCatalogError })), _jsx(Flex, { gap: "2", style: { flexWrap: 'wrap' }, children: selectedSchedule.calendar.knownHolidays.map(code => {
|
|
83
|
+
const holiday = holidayByCode.get(code);
|
|
84
|
+
return (_jsx(Chip, { label: holiday
|
|
85
|
+
? `${holiday.displayName} · ${holiday.rule}`
|
|
86
|
+
: code, size: "medium" }, code));
|
|
87
|
+
}) })] }), selectedSchedule.calendar.customDates.length > 0 && (_jsxs(Flex, { direction: "column", gap: "2", children: [_jsx(Text, { variant: "eyebrow", size: "small", children: "Custom Dates" }), _jsx(Flex, { gap: "2", style: { flexWrap: 'wrap' }, children: selectedSchedule.calendar.customDates.map(date => (_jsx(Chip, { label: formatCustomDateLabel(date), size: "medium" }, date))) })] }))] }))] })), drawerOpen && (_jsx(ScheduleDrawer, { businessCalendar: businessCalendar, editingSchedule: editingSchedule, onClose: handleCloseDrawer, onSaved: handleSaved }))] }));
|
|
88
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** DTE's `SaveDateScheduleRequest.Name` limit. */
|
|
2
|
+
export declare const MAX_SCHEDULE_NAME_LENGTH = 255;
|
|
3
|
+
/** The read-only default schedule's name; a tenant schedule may not reuse it (case-insensitive). */
|
|
4
|
+
export declare const RESERVED_SCHEDULE_NAME = "Default";
|
|
5
|
+
/** `MM-DD`, 01-12 / 01-31, as DTE's `CalendarSpecModel.customDates` (`02-29` is a valid leap day). */
|
|
6
|
+
export declare const CUSTOM_DATE_PATTERN: RegExp;
|
|
7
|
+
/** Days per month for an `MM-DD` custom date, Feb including the leap day. */
|
|
8
|
+
export declare const CUSTOM_DATE_MONTH_LENGTHS: number[];
|
|
9
|
+
/** The default schedule's weekend: Sunday and Saturday, DTE's `Date.getUTCDay()` numbering. */
|
|
10
|
+
export declare const DEFAULT_SCHEDULE_WEEKEND_DAYS: [0, 6];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** DTE's `SaveDateScheduleRequest.Name` limit. */
|
|
2
|
+
export const MAX_SCHEDULE_NAME_LENGTH = 255;
|
|
3
|
+
/** The read-only default schedule's name; a tenant schedule may not reuse it (case-insensitive). */
|
|
4
|
+
export const RESERVED_SCHEDULE_NAME = 'Default';
|
|
5
|
+
/** `MM-DD`, 01-12 / 01-31, as DTE's `CalendarSpecModel.customDates` (`02-29` is a valid leap day). */
|
|
6
|
+
export const CUSTOM_DATE_PATTERN = /^(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
|
|
7
|
+
/** Days per month for an `MM-DD` custom date, Feb including the leap day. */
|
|
8
|
+
export const CUSTOM_DATE_MONTH_LENGTHS = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
9
|
+
/** The default schedule's weekend: Sunday and Saturday, DTE's `Date.getUTCDay()` numbering. */
|
|
10
|
+
export const DEFAULT_SCHEDULE_WEEKEND_DAYS = [0, 6];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { BusinessCalendarSection } from './components/business-calendar-section';
|
|
2
|
+
export { SchedulePicker } from './components/schedule-picker';
|
|
3
|
+
export { ScheduleDrawer } from './components/schedule-drawer';
|
|
4
|
+
export type * from './types';
|
|
5
|
+
export * from './constants';
|
|
6
|
+
export * from './business-calendar-validation.utils';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { BusinessCalendarSection } from './components/business-calendar-section';
|
|
2
|
+
export { SchedulePicker } from './components/schedule-picker';
|
|
3
|
+
export { ScheduleDrawer } from './components/schedule-drawer';
|
|
4
|
+
export * from './constants';
|
|
5
|
+
export * from './business-calendar-validation.utils';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.dte-business-calendar-section {
|
|
2
|
+
margin-top: var(--spacing-2);
|
|
3
|
+
padding-top: var(--spacing-2);
|
|
4
|
+
border-top: 1px solid var(--border-color);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.dte-business-calendar-preview {
|
|
8
|
+
padding: var(--spacing-2);
|
|
9
|
+
border: 1px solid var(--border-color);
|
|
10
|
+
border-radius: 8px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.dte-business-calendar-preview-header {
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dte-business-calendar-preview-content {
|
|
18
|
+
margin-top: var(--spacing-2);
|
|
19
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** A weekend day excluded by a business-day schedule, as `Date.getUTCDay()` numbers them. */
|
|
2
|
+
export type WeekendDay = 0 | 6;
|
|
3
|
+
/** A known holiday a tenant can exclude, from DTE's catalog (`GET api/holidays/catalog`). */
|
|
4
|
+
export interface KnownHoliday {
|
|
5
|
+
/** Stable code stored in a schedule's `knownHolidays`, e.g. `JULY4`. */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Name to show, e.g. "Independence Day". */
|
|
8
|
+
displayName: string;
|
|
9
|
+
/** When it falls, e.g. "July 4" or "4th Thursday of November". */
|
|
10
|
+
rule: string;
|
|
11
|
+
}
|
|
12
|
+
/** The known holidays a tenant can exclude, for the schedule editor's checklist. */
|
|
13
|
+
export interface HolidayCatalog {
|
|
14
|
+
/** The jurisdiction the holidays belong to, e.g. "US" or a US state code. */
|
|
15
|
+
jurisdiction: string;
|
|
16
|
+
holidays: KnownHoliday[];
|
|
17
|
+
}
|
|
18
|
+
/** The rules of a business-day schedule: which days a date calculation skips. */
|
|
19
|
+
export interface BusinessCalendarRules {
|
|
20
|
+
weekendDays: WeekendDay[];
|
|
21
|
+
/** Codes of excluded holidays from the jurisdiction's catalog (`KnownHoliday.code`). */
|
|
22
|
+
knownHolidays: string[];
|
|
23
|
+
/** Extra excluded days as `MM-DD`, repeating every year, e.g. `12-24`. */
|
|
24
|
+
customDates: string[];
|
|
25
|
+
}
|
|
26
|
+
/** A schedule a tenant can pick for a business-day calculation (DTE's `GET api/holidays/schedules`). */
|
|
27
|
+
export interface BusinessDaySchedule {
|
|
28
|
+
/** Identifier of a tenant schedule; undefined for the read-only default schedule. */
|
|
29
|
+
id?: number;
|
|
30
|
+
name: string;
|
|
31
|
+
/** Whether this is the read-only default schedule of the tenant's jurisdiction. */
|
|
32
|
+
isDefault: boolean;
|
|
33
|
+
calendar: BusinessCalendarRules;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Request to create a tenant business-day schedule (DTE's `POST api/holidays/schedules`). Always
|
|
37
|
+
* creates a new schedule — editing a selected one does not overwrite it.
|
|
38
|
+
*/
|
|
39
|
+
export interface SaveScheduleRequest extends BusinessCalendarRules {
|
|
40
|
+
/** Display name, unique per tenant ignoring case, max 255 characters, and not "Default". */
|
|
41
|
+
name: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Prop bundle for the business-calendar toggle/picker/drawer, built by a host package (currently
|
|
45
|
+
* `@servicetitan/dte-pdf-editor` and `@servicetitan/dte-unlayer`) from whatever it uses to track
|
|
46
|
+
* which of its own fields have the toggle on and which schedule each uses — this package has no
|
|
47
|
+
* opinion on that part, only on the schedule CRUD and the UI built from it.
|
|
48
|
+
*/
|
|
49
|
+
export interface ScheduleEditorContext {
|
|
50
|
+
/** Whether the current field's custom business-day exclusion toggle is on. */
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
/** The current field's bound schedule id, or undefined for the default schedule. */
|
|
53
|
+
selectedScheduleId?: number;
|
|
54
|
+
holidayCatalog?: HolidayCatalog;
|
|
55
|
+
holidayCatalogLoading?: boolean;
|
|
56
|
+
holidayCatalogError?: string;
|
|
57
|
+
schedules?: BusinessDaySchedule[];
|
|
58
|
+
schedulesLoading?: boolean;
|
|
59
|
+
schedulesError?: string;
|
|
60
|
+
onToggle(enabled: boolean): void;
|
|
61
|
+
onSelectSchedule(scheduleId: number | undefined): void;
|
|
62
|
+
onCreateSchedule?(request: SaveScheduleRequest): Promise<BusinessDaySchedule>;
|
|
63
|
+
/** Replaces a tenant schedule's name and rules in place (DTE's `PUT api/holidays/schedules/{id}`). */
|
|
64
|
+
onUpdateSchedule?(id: number, request: SaveScheduleRequest): Promise<BusinessDaySchedule>;
|
|
65
|
+
/** Permanently deletes a tenant schedule; fields bound to it fall back to the default schedule. */
|
|
66
|
+
onDeleteSchedule?(id: number): Promise<void>;
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/dte-pdf-editor",
|
|
3
|
-
"version": "1.96.
|
|
3
|
+
"version": "1.96.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"typings": "./dist/index.d.ts",
|
|
@@ -13,17 +13,19 @@
|
|
|
13
13
|
"registry": "https://registry.npmjs.org"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "tsc --build && startup prepare-package"
|
|
16
|
+
"build": "npm run build --prefix ../shared && tsc --build && startup prepare-package && node scripts/bundle-shared.mjs"
|
|
17
17
|
},
|
|
18
18
|
"cli": {
|
|
19
19
|
"webpack": false
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@servicetitan/dte-shared": "^0.0.1",
|
|
23
22
|
"pdfjs-dist": "^5.4.530",
|
|
24
23
|
"react-pdf": "^10.3.0",
|
|
25
24
|
"uuid": "^9.0.0"
|
|
26
25
|
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@servicetitan/dte-shared": "^0.0.1"
|
|
28
|
+
},
|
|
27
29
|
"peerDependencies": {
|
|
28
30
|
"@servicetitan/anvil2": "^3.7.1",
|
|
29
31
|
"react": "~18.3.1",
|