@tmlmobilidade/dates 20260324.1728.13 → 20260324.1820.40
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/calendar/rules/calculation/index.js +7 -2
- package/dist/calendar/rules/calculation/matchers.js +10 -0
- package/dist/calendar/rules/formatting/rule-summary.js +37 -9
- package/dist/calendar/rules/preview/affectedDates.js +6 -0
- package/dist/calendar/rules/preview/computeRuleTimePoints.js +4 -1
- package/dist/calendar/utils/index.js +5 -2
- package/package.json +1 -1
|
@@ -58,12 +58,17 @@ export function computeActiveRules(date, rules, periods, options) {
|
|
|
58
58
|
let timepoints;
|
|
59
59
|
let appliedRuleIds;
|
|
60
60
|
if (replacement) {
|
|
61
|
+
// When same_weekday is true, each event date functions as its own actual weekday
|
|
62
|
+
// within the replacement's target periods, rather than all dates acting as one fixed weekday.
|
|
63
|
+
const effectiveReplacement = replacement.same_weekday
|
|
64
|
+
? { ...replacement, weekdays: [calendarWeekday(key)] }
|
|
65
|
+
: replacement;
|
|
61
66
|
// Replacement mode: match manuals by intersection (Option A)
|
|
62
|
-
const base = collectReplacementManualIncludes(
|
|
67
|
+
const base = collectReplacementManualIncludes(effectiveReplacement, filteredManualRules);
|
|
63
68
|
timepoints = base.timepoints;
|
|
64
69
|
appliedRuleIds = base.appliedRuleIds;
|
|
65
70
|
// Apply manual excludes *also* by intersection with replacement targets
|
|
66
|
-
applyReplacementManualExcludes(timepoints, appliedRuleIds,
|
|
71
|
+
applyReplacementManualExcludes(timepoints, appliedRuleIds, effectiveReplacement, filteredManualRules);
|
|
67
72
|
}
|
|
68
73
|
else {
|
|
69
74
|
// Normal mode: day resolves to a single weekday + single yearPeriodId
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
export function manualRuleMatchesContext(rule, ctx) {
|
|
19
|
+
if (rule.event_id) {
|
|
20
|
+
// Event-exception rules: date matching already done by filteredManualRules in computeActiveRules.
|
|
21
|
+
// year_period_ids is cleared for these rules, so skip that check.
|
|
22
|
+
// If weekdays is non-empty, the user narrowed the event to specific days — check it.
|
|
23
|
+
// If weekdays is empty, all days of the event are included.
|
|
24
|
+
if (rule.weekdays.length > 0) {
|
|
25
|
+
return rule.weekdays.includes(ctx.weekday);
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
19
29
|
// Strict by design: rule must include the weekday and the yearPeriodId.
|
|
20
30
|
return rule.weekdays.includes(ctx.weekday) && rule.year_period_ids.includes(ctx.yearPeriodId);
|
|
21
31
|
}
|
|
@@ -62,7 +62,11 @@ function buildRuleSummaryShort(rule, options) {
|
|
|
62
62
|
return rule.event?.title ?? '';
|
|
63
63
|
}
|
|
64
64
|
if (rule.kind === 'manual' && rule.event_id) {
|
|
65
|
-
|
|
65
|
+
const title = getEventForManualRule(rule, options?.events)?.title ?? '';
|
|
66
|
+
if (!rule.weekdays?.length)
|
|
67
|
+
return title;
|
|
68
|
+
const weekdayPart = buildWeekdaysPart(rule, { mode: 'short' });
|
|
69
|
+
return [title, weekdayPart].filter(Boolean).join(' · ');
|
|
66
70
|
}
|
|
67
71
|
// manual
|
|
68
72
|
const parts = [];
|
|
@@ -85,7 +89,11 @@ function buildRuleSummaryLong(rule, options) {
|
|
|
85
89
|
return rule.event?.title ?? '';
|
|
86
90
|
}
|
|
87
91
|
if (rule.kind === 'manual' && rule.event_id) {
|
|
88
|
-
|
|
92
|
+
const title = getEventForManualRule(rule, options?.events)?.title ?? '';
|
|
93
|
+
if (!rule.weekdays?.length)
|
|
94
|
+
return title;
|
|
95
|
+
const weekdayPart = buildWeekdaysPart(rule, { mode: 'long' });
|
|
96
|
+
return [title, weekdayPart].filter(Boolean).join(', ');
|
|
89
97
|
}
|
|
90
98
|
// manual
|
|
91
99
|
const parts = [];
|
|
@@ -108,6 +116,13 @@ function formatDateWithWeekday(date) {
|
|
|
108
116
|
const weekdayShort = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'][weekday];
|
|
109
117
|
return `${formattedDate} (${weekdayShort})`;
|
|
110
118
|
}
|
|
119
|
+
function truncateDates(dates, max = 5) {
|
|
120
|
+
if (dates.length <= max)
|
|
121
|
+
return dates.join(', ');
|
|
122
|
+
const visible = dates.slice(0, max);
|
|
123
|
+
const remaining = dates.length - max;
|
|
124
|
+
return `${visible.join(', ')} e mais ${remaining}`;
|
|
125
|
+
}
|
|
111
126
|
/**
|
|
112
127
|
* Builds detailed tooltip text for event rules.
|
|
113
128
|
*
|
|
@@ -117,7 +132,8 @@ function formatDateWithWeekday(date) {
|
|
|
117
132
|
*/
|
|
118
133
|
function buildRuleSummaryTooltip(rule, options) {
|
|
119
134
|
if (isEventRestriction(rule)) {
|
|
120
|
-
const
|
|
135
|
+
const formattedDates = (rule.dates ?? []).map(formatDateWithWeekday);
|
|
136
|
+
const dates = truncateDates(formattedDates);
|
|
121
137
|
const datesText = (rule.dates?.length ?? 0) > 1 ? `nos dias ${dates}` : `no dia ${dates}`;
|
|
122
138
|
const timeWindow = (rule?.start_time && rule?.end_time)
|
|
123
139
|
? `entre as ${rule.start_time}h e ${rule.end_time}h`
|
|
@@ -125,24 +141,36 @@ function buildRuleSummaryTooltip(rule, options) {
|
|
|
125
141
|
return `Oferta excluída ${datesText}, ${timeWindow}`.trim();
|
|
126
142
|
}
|
|
127
143
|
if (isEventReplacement(rule)) {
|
|
128
|
-
const
|
|
144
|
+
const formattedDates = [...(rule.dates ?? [])].sort().map(formatDateWithWeekday);
|
|
145
|
+
const dates = truncateDates(formattedDates);
|
|
129
146
|
const datesText = (rule.dates?.length ?? 0) > 1 ? `nos dias ${dates}` : `no dia ${dates}`;
|
|
147
|
+
const periods = rule.year_period_ids
|
|
148
|
+
?.map(id => options?.periods?.find(p => p._id === id)?.name || id)
|
|
149
|
+
.join(', ') || '';
|
|
150
|
+
if (rule.same_weekday) {
|
|
151
|
+
const parts = ['mesmo dia da semana', periods].filter(Boolean);
|
|
152
|
+
return `Funcionará como ${parts.join(' · ')}, ${datesText}`;
|
|
153
|
+
}
|
|
130
154
|
const weekdays = rule.weekdays
|
|
131
155
|
?.map(wd => WEEKDAY_OPTIONS.find(opt => opt.value === wd)?.label)
|
|
132
156
|
.filter(Boolean)
|
|
133
157
|
.join(', ') ?? '';
|
|
134
|
-
const periods = rule.year_period_ids
|
|
135
|
-
?.map(id => options?.periods?.find(p => p._id === id)?.name || id)
|
|
136
|
-
.join(', ') || '';
|
|
137
158
|
const parts = [weekdays, periods].filter(Boolean);
|
|
138
159
|
return `Funcionará como ${parts.join(' · ')}, ${datesText}`;
|
|
139
160
|
}
|
|
140
161
|
if (rule.kind === 'manual' && rule.event_id) {
|
|
141
162
|
const event = getEventForManualRule(rule, options?.events);
|
|
142
|
-
const
|
|
163
|
+
const filteredDates = (event?.dates ?? []).filter((date) => {
|
|
164
|
+
if (!rule.weekdays?.length)
|
|
165
|
+
return true;
|
|
166
|
+
const jsDay = Dates.fromOperationalDate(date, 'Europe/Lisbon').js_date.getDay();
|
|
167
|
+
const isoWeekday = (jsDay === 0 ? 7 : jsDay);
|
|
168
|
+
return rule.weekdays.includes(isoWeekday);
|
|
169
|
+
});
|
|
170
|
+
const dates = truncateDates(filteredDates.map(formatDateWithWeekday));
|
|
143
171
|
if (!dates)
|
|
144
172
|
return '';
|
|
145
|
-
const datesText =
|
|
173
|
+
const datesText = filteredDates.length > 1 ? `nos dias ${dates}` : `no dia ${dates}`;
|
|
146
174
|
return `Aplicável ${datesText}`;
|
|
147
175
|
}
|
|
148
176
|
return '';
|
|
@@ -53,6 +53,12 @@ export function getManualRuleAffectedDates(rule, ctx) {
|
|
|
53
53
|
for (const opDate of event.dates) {
|
|
54
54
|
const key = calendarKey(Dates.fromOperationalDate(opDate, 'Europe/Lisbon'));
|
|
55
55
|
if (key >= from && key <= to) {
|
|
56
|
+
// If weekdays are specified, narrow event dates to matching weekdays only
|
|
57
|
+
if (rule.weekdays?.length) {
|
|
58
|
+
const weekday = calendarWeekday(key);
|
|
59
|
+
if (!rule.weekdays.includes(weekday))
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
56
62
|
affected.push(key);
|
|
57
63
|
}
|
|
58
64
|
}
|
|
@@ -34,7 +34,10 @@ export function computeRuleTimePoints(rule, allRules, periods, options) {
|
|
|
34
34
|
}
|
|
35
35
|
// For replacement rules: return timepoints from the TARGET weekdays
|
|
36
36
|
if (rule.kind === 'event_replacement') {
|
|
37
|
-
|
|
37
|
+
// When same_weekday is true, each event date maps to its own actual weekday
|
|
38
|
+
const targetWeekdays = rule.same_weekday
|
|
39
|
+
? affectedWeekdays
|
|
40
|
+
: new Set(rule.weekdays || []);
|
|
38
41
|
const targetPeriods = new Set(rule.year_period_ids || []);
|
|
39
42
|
// Find manual include rules that match the target pattern
|
|
40
43
|
const timepoints = new Set();
|
|
@@ -99,7 +99,7 @@ export function isKeyInRange(key, start, end) {
|
|
|
99
99
|
export function generateMonthGrid(year, month, fixedWeeks = false, tz = DEFAULT_CAL_TZ) {
|
|
100
100
|
// Create the first day of the target month at noon in tz to avoid DST edge cases
|
|
101
101
|
const firstDayOfMonth = Dates.fromFormat(`${year}-${String(month).padStart(2, '0')}-01 12:00`, 'yyyy-MM-dd HH:mm', tz);
|
|
102
|
-
const lastDayOfMonth = firstDayOfMonth.endOf('month');
|
|
102
|
+
const lastDayOfMonth = firstDayOfMonth.endOf('month').set({ hour: 12, millisecond: 0, minute: 0, second: 0 });
|
|
103
103
|
// Today (civil)
|
|
104
104
|
const todayKey = calendarKey(Dates.now(tz), tz);
|
|
105
105
|
// ISO weekday for first day (1=Mon..7=Sun)
|
|
@@ -136,9 +136,12 @@ export function generateMonthGrid(year, month, fixedWeeks = false, tz = DEFAULT_
|
|
|
136
136
|
pushDay(firstDayOfMonth.plus({ days: d - 1 }), true);
|
|
137
137
|
}
|
|
138
138
|
// Next month overflow
|
|
139
|
+
// Use firstDayOfMonth (noon, tz-stable) as anchor instead of lastDayOfMonth (23:59 UTC),
|
|
140
|
+
// which crosses midnight into the next civil day when converted to a UTC+ timezone (e.g.
|
|
141
|
+
// Lisbon entering DST on March 29: 23:59 UTC = 00:59+01:00 = next day).
|
|
139
142
|
const daysFromNextMonth = fixedWeeks ? 42 - days.length : naturalDaysFromNextMonth;
|
|
140
143
|
for (let i = 1; i <= daysFromNextMonth; i++) {
|
|
141
|
-
pushDay(
|
|
144
|
+
pushDay(firstDayOfMonth.plus({ days: daysInMonth + i - 1 }), false);
|
|
142
145
|
}
|
|
143
146
|
// Weeks (rows)
|
|
144
147
|
const weeks = [];
|