@open-mercato/core 0.6.4-develop.4106.1.12c205a613 → 0.6.4-develop.4113.1.5e87922616
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/currencies/services/rateFetchingService.js +30 -11
- package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +16 -2
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/lib/field-definitions.js +35 -21
- package/dist/modules/entities/lib/field-definitions.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +9 -3
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +39 -26
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/planner/components/availabilityRulesEditorState.js +8 -0
- package/dist/modules/planner/components/availabilityRulesEditorState.js.map +7 -0
- package/package.json +7 -7
- package/src/modules/currencies/services/rateFetchingService.ts +44 -13
- package/src/modules/entities/api/definitions.batch.ts +19 -2
- package/src/modules/entities/lib/field-definitions.ts +42 -21
- package/src/modules/perspectives/services/perspectiveService.ts +12 -3
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +40 -26
- package/src/modules/planner/components/availabilityRulesEditorState.ts +11 -0
|
@@ -29,6 +29,7 @@ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
|
29
29
|
import { parseAvailabilityRuleWindow } from "@open-mercato/core/modules/planner/lib/availabilitySchedule";
|
|
30
30
|
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
31
31
|
import { Calendar, Clock, List, PencilLine, Plus, Trash2 } from "lucide-react";
|
|
32
|
+
import { resolveRuleSetSelectValue } from "./availabilityRulesEditorState.js";
|
|
32
33
|
const DAY_LABELS = [
|
|
33
34
|
{ code: "SU", short: "S", nameKey: "schedule.weekday.sunday", fallback: "Sunday" },
|
|
34
35
|
{ code: "MO", short: "M", nameKey: "schedule.weekday.monday", fallback: "Monday" },
|
|
@@ -313,6 +314,7 @@ function AvailabilityRulesEditor({
|
|
|
313
314
|
const [createRuleSetOpen, setCreateRuleSetOpen] = React.useState(false);
|
|
314
315
|
const [isWeeklyAutoSaving, setIsWeeklyAutoSaving] = React.useState(false);
|
|
315
316
|
const [customOverridesEnabled, setCustomOverridesEnabled] = React.useState(false);
|
|
317
|
+
const [selectedRulesetId, setSelectedRulesetId] = React.useState(rulesetId ?? null);
|
|
316
318
|
const autoSaveTimerRef = React.useRef(null);
|
|
317
319
|
const lastSavedWeeklyKeyRef = React.useRef(null);
|
|
318
320
|
const weeklySaveStateRef = React.useRef({ inFlight: false, queued: false });
|
|
@@ -321,7 +323,11 @@ function AvailabilityRulesEditor({
|
|
|
321
323
|
const viewModeRef = React.useRef(viewMode);
|
|
322
324
|
const saveWeeklyRef = React.useRef(null);
|
|
323
325
|
const timezoneOptions = React.useMemo(() => getTimezoneOptions(), []);
|
|
324
|
-
|
|
326
|
+
React.useEffect(() => {
|
|
327
|
+
setSelectedRulesetId(rulesetId ?? null);
|
|
328
|
+
}, [rulesetId]);
|
|
329
|
+
const effectiveRulesetId = selectedRulesetId;
|
|
330
|
+
const usingRuleSet = Boolean(effectiveRulesetId) && availabilityRules.length === 0 && !customOverridesEnabled;
|
|
325
331
|
const activeRules = usingRuleSet ? rulesetRules : availabilityRules;
|
|
326
332
|
const scheduleRules = React.useMemo(() => {
|
|
327
333
|
const dateBlockers = /* @__PURE__ */ new Set();
|
|
@@ -455,7 +461,7 @@ function AvailabilityRulesEditor({
|
|
|
455
461
|
}
|
|
456
462
|
}, [labelPrefix, subjectId, subjectType, t]);
|
|
457
463
|
const refreshRuleSetRules = React.useCallback(async () => {
|
|
458
|
-
if (!
|
|
464
|
+
if (!effectiveRulesetId) {
|
|
459
465
|
setRulesetRules([]);
|
|
460
466
|
return;
|
|
461
467
|
}
|
|
@@ -464,7 +470,7 @@ function AvailabilityRulesEditor({
|
|
|
464
470
|
page: "1",
|
|
465
471
|
pageSize: "100",
|
|
466
472
|
subjectType: "ruleset",
|
|
467
|
-
subjectIds:
|
|
473
|
+
subjectIds: effectiveRulesetId
|
|
468
474
|
});
|
|
469
475
|
const call = await apiCall(`/api/planner/availability?${params.toString()}`);
|
|
470
476
|
const items = Array.isArray(call.result?.items) ? call.result.items : [];
|
|
@@ -472,7 +478,7 @@ function AvailabilityRulesEditor({
|
|
|
472
478
|
} catch {
|
|
473
479
|
setRulesetRules([]);
|
|
474
480
|
}
|
|
475
|
-
}, [
|
|
481
|
+
}, [effectiveRulesetId]);
|
|
476
482
|
const refreshRuleSets = React.useCallback(async () => {
|
|
477
483
|
if (!onRulesetChange) return;
|
|
478
484
|
setRuleSetsLoading(true);
|
|
@@ -550,20 +556,20 @@ function AvailabilityRulesEditor({
|
|
|
550
556
|
}, [activeRules, initialTimezone, timezoneDirty]);
|
|
551
557
|
React.useEffect(() => {
|
|
552
558
|
if (timezoneDirty) return;
|
|
553
|
-
if (!usingRuleSet || !
|
|
554
|
-
const ruleset = ruleSets.find((entry) => entry.id ===
|
|
559
|
+
if (!usingRuleSet || !effectiveRulesetId) return;
|
|
560
|
+
const ruleset = ruleSets.find((entry) => entry.id === effectiveRulesetId);
|
|
555
561
|
if (ruleset?.timezone) setTimezone(ruleset.timezone);
|
|
556
|
-
}, [
|
|
562
|
+
}, [effectiveRulesetId, ruleSets, timezoneDirty, usingRuleSet]);
|
|
557
563
|
React.useEffect(() => {
|
|
558
564
|
if (availabilityRules.length > 0) {
|
|
559
565
|
setCustomOverridesEnabled(true);
|
|
560
566
|
}
|
|
561
567
|
}, [availabilityRules.length]);
|
|
562
568
|
React.useEffect(() => {
|
|
563
|
-
if (!
|
|
569
|
+
if (!effectiveRulesetId) {
|
|
564
570
|
setCustomOverridesEnabled(false);
|
|
565
571
|
}
|
|
566
|
-
}, [
|
|
572
|
+
}, [effectiveRulesetId]);
|
|
567
573
|
const refreshBookedEvents = React.useCallback(async () => {
|
|
568
574
|
if (!loadBookedEvents) return;
|
|
569
575
|
setBookedLoading(true);
|
|
@@ -647,7 +653,7 @@ function AvailabilityRulesEditor({
|
|
|
647
653
|
const saveWeeklyHours = React.useCallback(async (options) => {
|
|
648
654
|
if (isReadOnly) return;
|
|
649
655
|
const subjectForRules = usingRuleSet ? "ruleset" : subjectType;
|
|
650
|
-
const subjectIdForRules = usingRuleSet ?
|
|
656
|
+
const subjectIdForRules = usingRuleSet ? effectiveRulesetId ?? "" : subjectId;
|
|
651
657
|
if (!subjectIdForRules) return;
|
|
652
658
|
if (weeklyHasErrors) return;
|
|
653
659
|
const shouldSkipRefresh = Boolean(options?.skipRefresh);
|
|
@@ -684,7 +690,7 @@ function AvailabilityRulesEditor({
|
|
|
684
690
|
listLabels.saveWeeklySuccess,
|
|
685
691
|
refreshAvailability,
|
|
686
692
|
refreshRuleSetRules,
|
|
687
|
-
|
|
693
|
+
effectiveRulesetId,
|
|
688
694
|
subjectId,
|
|
689
695
|
subjectType,
|
|
690
696
|
timezone,
|
|
@@ -734,7 +740,7 @@ function AvailabilityRulesEditor({
|
|
|
734
740
|
if (isReadOnly) return;
|
|
735
741
|
if (timezoneSaveInFlightRef.current) return;
|
|
736
742
|
const trimmedTimezone = nextTimezone.trim() || "UTC";
|
|
737
|
-
const rulesetTimezoneId = subjectType === "ruleset" ? subjectId :
|
|
743
|
+
const rulesetTimezoneId = subjectType === "ruleset" ? subjectId : effectiveRulesetId;
|
|
738
744
|
const rulesToUpdate = activeRules.filter((rule) => rule.timezone !== trimmedTimezone);
|
|
739
745
|
if (!rulesToUpdate.length && !rulesetTimezoneId) {
|
|
740
746
|
setTimezoneDirty(false);
|
|
@@ -773,7 +779,7 @@ function AvailabilityRulesEditor({
|
|
|
773
779
|
listLabels.timezoneSaveError,
|
|
774
780
|
refreshAvailability,
|
|
775
781
|
refreshRuleSetRules,
|
|
776
|
-
|
|
782
|
+
effectiveRulesetId,
|
|
777
783
|
subjectId,
|
|
778
784
|
subjectType,
|
|
779
785
|
isReadOnly
|
|
@@ -802,7 +808,7 @@ function AvailabilityRulesEditor({
|
|
|
802
808
|
}, [canManageUnavailability, isReadOnly]);
|
|
803
809
|
const handleCustomize = React.useCallback(async () => {
|
|
804
810
|
if (isReadOnly) return;
|
|
805
|
-
if (!
|
|
811
|
+
if (!effectiveRulesetId) return;
|
|
806
812
|
try {
|
|
807
813
|
const creations = rulesetRules.map((rule) => createCrud("planner/availability", {
|
|
808
814
|
subjectType,
|
|
@@ -820,10 +826,10 @@ function AvailabilityRulesEditor({
|
|
|
820
826
|
const message = error2 instanceof Error ? error2.message : listLabels.saveWeeklyError;
|
|
821
827
|
flash(message, "error");
|
|
822
828
|
}
|
|
823
|
-
}, [listLabels.saveWeeklyError, refreshAvailability,
|
|
829
|
+
}, [effectiveRulesetId, listLabels.saveWeeklyError, refreshAvailability, rulesetRules, subjectId, subjectType, isReadOnly]);
|
|
824
830
|
const handleResetToRuleSet = React.useCallback(async () => {
|
|
825
831
|
if (isReadOnly) return;
|
|
826
|
-
if (!
|
|
832
|
+
if (!effectiveRulesetId) return;
|
|
827
833
|
try {
|
|
828
834
|
await Promise.all(
|
|
829
835
|
availabilityRules.map((rule) => deleteCrud("planner/availability", rule.id, { errorMessage: listLabels.saveWeeklyError }))
|
|
@@ -834,11 +840,11 @@ function AvailabilityRulesEditor({
|
|
|
834
840
|
const message = error2 instanceof Error ? error2.message : listLabels.saveWeeklyError;
|
|
835
841
|
flash(message, "error");
|
|
836
842
|
}
|
|
837
|
-
}, [availabilityRules, listLabels.saveWeeklyError, refreshAvailability,
|
|
843
|
+
}, [availabilityRules, effectiveRulesetId, listLabels.saveWeeklyError, refreshAvailability, isReadOnly]);
|
|
838
844
|
const handleRuleSetChange = React.useCallback(async (nextId) => {
|
|
839
845
|
if (isReadOnly) return;
|
|
840
846
|
if (!onRulesetChange) return;
|
|
841
|
-
if (availabilityRules.length > 0 && nextId !==
|
|
847
|
+
if (availabilityRules.length > 0 && nextId !== effectiveRulesetId) {
|
|
842
848
|
const confirmed = await confirm({
|
|
843
849
|
title: listLabels.ruleSetConfirm,
|
|
844
850
|
variant: "default"
|
|
@@ -848,17 +854,23 @@ function AvailabilityRulesEditor({
|
|
|
848
854
|
availabilityRules.map((rule) => deleteCrud("planner/availability", rule.id, { errorMessage: listLabels.saveWeeklyError }))
|
|
849
855
|
);
|
|
850
856
|
}
|
|
857
|
+
setSelectedRulesetId(nextId);
|
|
851
858
|
setCustomOverridesEnabled(false);
|
|
852
|
-
|
|
853
|
-
|
|
859
|
+
try {
|
|
860
|
+
await onRulesetChange(nextId);
|
|
861
|
+
await refreshAvailability();
|
|
862
|
+
} catch (error2) {
|
|
863
|
+
setSelectedRulesetId(effectiveRulesetId);
|
|
864
|
+
throw error2;
|
|
865
|
+
}
|
|
854
866
|
}, [
|
|
855
867
|
availabilityRules,
|
|
856
868
|
confirm,
|
|
869
|
+
effectiveRulesetId,
|
|
857
870
|
listLabels.ruleSetConfirm,
|
|
858
871
|
listLabels.saveWeeklyError,
|
|
859
872
|
onRulesetChange,
|
|
860
873
|
refreshAvailability,
|
|
861
|
-
rulesetId,
|
|
862
874
|
isReadOnly
|
|
863
875
|
]);
|
|
864
876
|
const ruleSetFormSchema = React.useMemo(
|
|
@@ -927,6 +939,7 @@ function AvailabilityRulesEditor({
|
|
|
927
939
|
}
|
|
928
940
|
await refreshRuleSets();
|
|
929
941
|
if (onRulesetChange) {
|
|
942
|
+
setSelectedRulesetId(id);
|
|
930
943
|
await onRulesetChange(id);
|
|
931
944
|
await refreshAvailability();
|
|
932
945
|
}
|
|
@@ -1015,7 +1028,7 @@ function AvailabilityRulesEditor({
|
|
|
1015
1028
|
if (isReadOnly) return;
|
|
1016
1029
|
if (editorUnavailable && !canManageUnavailability) return;
|
|
1017
1030
|
const subjectForRules = usingRuleSet ? "ruleset" : subjectType;
|
|
1018
|
-
const subjectIdForRules = usingRuleSet ?
|
|
1031
|
+
const subjectIdForRules = usingRuleSet ? effectiveRulesetId ?? "" : subjectId;
|
|
1019
1032
|
if (!subjectIdForRules) return;
|
|
1020
1033
|
if (!editorUnavailable && editorWindowErrors.some(Boolean)) return;
|
|
1021
1034
|
const validWindows = editorWindows;
|
|
@@ -1091,7 +1104,7 @@ function AvailabilityRulesEditor({
|
|
|
1091
1104
|
refreshAvailability,
|
|
1092
1105
|
refreshRuleSetRules,
|
|
1093
1106
|
reasonEntriesById,
|
|
1094
|
-
|
|
1107
|
+
effectiveRulesetId,
|
|
1095
1108
|
subjectId,
|
|
1096
1109
|
subjectType,
|
|
1097
1110
|
timezone,
|
|
@@ -1163,7 +1176,7 @@ function AvailabilityRulesEditor({
|
|
|
1163
1176
|
ruleSetsLoading ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: listLabels.ruleSetLoading }) : /* @__PURE__ */ jsxs(
|
|
1164
1177
|
Select,
|
|
1165
1178
|
{
|
|
1166
|
-
value:
|
|
1179
|
+
value: resolveRuleSetSelectValue(ruleSets, effectiveRulesetId),
|
|
1167
1180
|
onValueChange: (value) => {
|
|
1168
1181
|
void handleRuleSetChange(value ? value : null);
|
|
1169
1182
|
},
|
|
@@ -1178,8 +1191,8 @@ function AvailabilityRulesEditor({
|
|
|
1178
1191
|
/* @__PURE__ */ jsx(Plus, { className: "size-4 mr-2", "aria-hidden": true }),
|
|
1179
1192
|
listLabels.ruleSetCreateLabel
|
|
1180
1193
|
] }),
|
|
1181
|
-
|
|
1182
|
-
|
|
1194
|
+
effectiveRulesetId && usingRuleSet ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleCustomize, disabled: isReadOnly, children: listLabels.ruleSetCustomize }) : null,
|
|
1195
|
+
effectiveRulesetId && !usingRuleSet ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleResetToRuleSet, disabled: isReadOnly, children: listLabels.ruleSetReset }) : null
|
|
1183
1196
|
] }) : null,
|
|
1184
1197
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1185
1198
|
/* @__PURE__ */ jsx("span", { children: listLabels.timezoneLabel }),
|