@tmlmobilidade/dates 20260408.1128.43 → 20260409.1552.16
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 -4
- package/dist/calendar/rules/formatting/common.d.ts +12 -0
- package/dist/calendar/rules/formatting/common.js +27 -1
- package/dist/calendar/rules/formatting/rule-summary.js +39 -20
- package/dist/calendar/rules/preview/affectedDates.js +12 -0
- package/package.json +1 -1
|
@@ -53,6 +53,9 @@ export function computeActiveRules(date, rules, periods, holidays, options) {
|
|
|
53
53
|
return event.dates.includes(date);
|
|
54
54
|
});
|
|
55
55
|
const key = calendarKey(Dates.fromOperationalDate(date, 'Europe/Lisbon'));
|
|
56
|
+
// Filter out manual rules whose months list doesn't include the current month
|
|
57
|
+
const month = Number(key.slice(5, 7));
|
|
58
|
+
const monthFilteredManualRules = filteredManualRules.filter(rule => !rule.months?.length || rule.months.includes(month));
|
|
56
59
|
const replacement = findReplacementForDate(date, replacementRules);
|
|
57
60
|
// 1) Base timepoints
|
|
58
61
|
let timepoints;
|
|
@@ -64,11 +67,11 @@ export function computeActiveRules(date, rules, periods, holidays, options) {
|
|
|
64
67
|
? { ...replacement, weekdays: [calendarWeekday(key, holidays)] }
|
|
65
68
|
: replacement;
|
|
66
69
|
// Replacement mode: match manuals by intersection (Option A)
|
|
67
|
-
const base = collectReplacementManualIncludes(effectiveReplacement,
|
|
70
|
+
const base = collectReplacementManualIncludes(effectiveReplacement, monthFilteredManualRules);
|
|
68
71
|
timepoints = base.timepoints;
|
|
69
72
|
appliedRuleIds = base.appliedRuleIds;
|
|
70
73
|
// Apply manual excludes *also* by intersection with replacement targets
|
|
71
|
-
applyReplacementManualExcludes(timepoints, appliedRuleIds, effectiveReplacement,
|
|
74
|
+
applyReplacementManualExcludes(timepoints, appliedRuleIds, effectiveReplacement, monthFilteredManualRules);
|
|
72
75
|
}
|
|
73
76
|
else {
|
|
74
77
|
// Normal mode: day resolves to a single weekday + single yearPeriodId
|
|
@@ -76,10 +79,10 @@ export function computeActiveRules(date, rules, periods, holidays, options) {
|
|
|
76
79
|
weekday: calendarWeekday(key, holidays),
|
|
77
80
|
yearPeriodId: getActivePeriodId(date, periods),
|
|
78
81
|
};
|
|
79
|
-
const base = collectManualIncludes(
|
|
82
|
+
const base = collectManualIncludes(monthFilteredManualRules, ctx);
|
|
80
83
|
timepoints = base.timepoints;
|
|
81
84
|
appliedRuleIds = base.appliedRuleIds;
|
|
82
|
-
applyManualExcludes(timepoints, appliedRuleIds,
|
|
85
|
+
applyManualExcludes(timepoints, appliedRuleIds, monthFilteredManualRules, ctx);
|
|
83
86
|
}
|
|
84
87
|
// 2) Event restrictions always apply by date
|
|
85
88
|
applyEventRestrictions(date, timepoints, appliedRuleIds, restrictionRules);
|
|
@@ -35,3 +35,15 @@ export declare function buildWeekdaysPart(rule: EventReplacementRule | ManualRul
|
|
|
35
35
|
export declare function buildDayPeriodsPart(parameter: StopsParameterOverride, cfg: {
|
|
36
36
|
mode: 'long' | 'short';
|
|
37
37
|
}): string;
|
|
38
|
+
/**
|
|
39
|
+
* Builds the months portion of a rule summary.
|
|
40
|
+
*
|
|
41
|
+
* Handles smart formatting:
|
|
42
|
+
* - No months or all 12 selected: "Todos os meses" / "em todos os meses"
|
|
43
|
+
* - Single month: Shows month label
|
|
44
|
+
* - Multiple months: "N meses" (short) or lists them (long)
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildMonthsPart(rule: Pick<ManualRule, 'months'>, cfg?: {
|
|
47
|
+
mode: 'long' | 'short';
|
|
48
|
+
omitIfAll?: boolean;
|
|
49
|
+
}): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DAY_PERIOD_LABELS, WEEKDAY_OPTIONS } from '@tmlmobilidade/types';
|
|
1
|
+
import { DAY_PERIOD_LABELS, MONTH_OPTIONS, WEEKDAY_OPTIONS } from '@tmlmobilidade/types';
|
|
2
2
|
/**
|
|
3
3
|
* Builds the period portion of a rule summary.
|
|
4
4
|
*
|
|
@@ -93,3 +93,29 @@ export function buildDayPeriodsPart(parameter, cfg) {
|
|
|
93
93
|
return `Durante ${labels[0]}`;
|
|
94
94
|
return `Durante ${labels.join(' e ')}`;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Builds the months portion of a rule summary.
|
|
98
|
+
*
|
|
99
|
+
* Handles smart formatting:
|
|
100
|
+
* - No months or all 12 selected: "Todos os meses" / "em todos os meses"
|
|
101
|
+
* - Single month: Shows month label
|
|
102
|
+
* - Multiple months: "N meses" (short) or lists them (long)
|
|
103
|
+
*/
|
|
104
|
+
export function buildMonthsPart(rule, cfg = { mode: 'long' }) {
|
|
105
|
+
if (!rule.months?.length || rule.months.length === 12) {
|
|
106
|
+
if (cfg.omitIfAll)
|
|
107
|
+
return '';
|
|
108
|
+
return cfg.mode === 'short' ? 'Todos os meses' : 'em todos os meses';
|
|
109
|
+
}
|
|
110
|
+
const labels = MONTH_OPTIONS
|
|
111
|
+
.filter(o => rule.months.includes(o.value))
|
|
112
|
+
.map(o => o.label);
|
|
113
|
+
if (cfg.mode === 'short') {
|
|
114
|
+
return labels.length === 1 ? labels[0] : `${labels.length} meses`;
|
|
115
|
+
}
|
|
116
|
+
if (labels.length === 1)
|
|
117
|
+
return labels[0];
|
|
118
|
+
if (labels.length === 2)
|
|
119
|
+
return `${labels[0]} e ${labels[1]}`;
|
|
120
|
+
return labels.join(', ');
|
|
121
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Dates } from '../../../dates.js';
|
|
2
2
|
import { FORMATS } from '../../../format.js';
|
|
3
|
-
import { WEEKDAY_OPTIONS, } from '@tmlmobilidade/types';
|
|
4
|
-
import { buildWeekdaysPart, buildYearPeriodsPart } from './common.js';
|
|
3
|
+
import { MONTH_OPTIONS, WEEKDAY_OPTIONS, } from '@tmlmobilidade/types';
|
|
4
|
+
import { buildMonthsPart, buildWeekdaysPart, buildYearPeriodsPart } from './common.js';
|
|
5
5
|
/**
|
|
6
6
|
* Builds human-readable summaries of a scheduling rule.
|
|
7
7
|
*
|
|
@@ -69,6 +69,9 @@ function buildRuleSummaryShort(rule, options) {
|
|
|
69
69
|
const periodPart = buildYearPeriodsPart(rule, options, { mode: 'short', omitIfAll: true });
|
|
70
70
|
if (periodPart)
|
|
71
71
|
parts.push(periodPart);
|
|
72
|
+
const monthsPart = buildMonthsPart(rule, { mode: 'short', omitIfAll: true });
|
|
73
|
+
if (monthsPart)
|
|
74
|
+
parts.push(monthsPart);
|
|
72
75
|
const weekdayPart = buildWeekdaysPart(rule, { mode: 'short', omitIfAll: true });
|
|
73
76
|
if (weekdayPart)
|
|
74
77
|
parts.push(weekdayPart);
|
|
@@ -79,6 +82,9 @@ function buildRuleSummaryShort(rule, options) {
|
|
|
79
82
|
const periodPart = buildYearPeriodsPart(rule, options, { mode: 'short' });
|
|
80
83
|
if (periodPart)
|
|
81
84
|
parts.push(periodPart);
|
|
85
|
+
const monthsPart = buildMonthsPart(rule, { mode: 'short', omitIfAll: true });
|
|
86
|
+
if (monthsPart)
|
|
87
|
+
parts.push(monthsPart);
|
|
82
88
|
const weekdayPart = buildWeekdaysPart(rule, { mode: 'short' });
|
|
83
89
|
if (weekdayPart)
|
|
84
90
|
parts.push(weekdayPart);
|
|
@@ -104,6 +110,9 @@ function buildRuleSummaryLong(rule, options) {
|
|
|
104
110
|
const periodPart = buildYearPeriodsPart(rule, options, { mode: 'long', omitIfAll: true });
|
|
105
111
|
if (periodPart)
|
|
106
112
|
parts.push(periodPart);
|
|
113
|
+
const monthsPart = buildMonthsPart(rule, { mode: 'long', omitIfAll: true });
|
|
114
|
+
if (monthsPart)
|
|
115
|
+
parts.push(monthsPart);
|
|
107
116
|
const weekdayPart = buildWeekdaysPart(rule, { mode: 'long', omitIfAll: true });
|
|
108
117
|
if (weekdayPart)
|
|
109
118
|
parts.push(weekdayPart);
|
|
@@ -114,6 +123,9 @@ function buildRuleSummaryLong(rule, options) {
|
|
|
114
123
|
const periodPart = buildYearPeriodsPart(rule, options, { mode: 'long' });
|
|
115
124
|
if (periodPart)
|
|
116
125
|
parts.push(periodPart);
|
|
126
|
+
const monthsPart = buildMonthsPart(rule, { mode: 'long', omitIfAll: true });
|
|
127
|
+
if (monthsPart)
|
|
128
|
+
parts.push(monthsPart);
|
|
117
129
|
const weekdayPart = buildWeekdaysPart(rule, { mode: 'long' });
|
|
118
130
|
if (weekdayPart)
|
|
119
131
|
parts.push(weekdayPart);
|
|
@@ -235,6 +247,14 @@ function mapWeekdaysToGtfsAbbreviation(weekdays) {
|
|
|
235
247
|
};
|
|
236
248
|
return sorted.map(day => map[day] || String(day)).join('-');
|
|
237
249
|
}
|
|
250
|
+
function mapMonthsToGtfsAbbreviation(months) {
|
|
251
|
+
if (!months?.length)
|
|
252
|
+
return null;
|
|
253
|
+
const sorted = [...months].sort((a, b) => a - b);
|
|
254
|
+
return sorted
|
|
255
|
+
.map(m => MONTH_OPTIONS.find(o => o.value === m)?.label.toUpperCase() ?? String(m))
|
|
256
|
+
.join('-');
|
|
257
|
+
}
|
|
238
258
|
/**
|
|
239
259
|
* GTFS-oriented rule token:
|
|
240
260
|
* - FER_DU
|
|
@@ -253,27 +273,26 @@ export function buildRuleSummaryGtfs(rule, options) {
|
|
|
253
273
|
const weekdays = rule.weekdays ?? [];
|
|
254
274
|
const periodPart = mapPeriodsToGtfsAbbreviation(periodIds);
|
|
255
275
|
const weekdayPart = mapWeekdaysToGtfsAbbreviation(weekdays);
|
|
276
|
+
const monthsPart = mapMonthsToGtfsAbbreviation(rule.months);
|
|
256
277
|
if (rule.kind === 'manual' && rule.event_id) {
|
|
257
278
|
const title = getEventForManualRule(rule, options?.events)?.title ?? rule.name ?? rule._id;
|
|
258
|
-
|
|
279
|
+
const tokenParts = [];
|
|
280
|
+
if (periodPart !== 'ALL')
|
|
281
|
+
tokenParts.push(periodPart);
|
|
282
|
+
if (monthsPart)
|
|
283
|
+
tokenParts.push(monthsPart);
|
|
284
|
+
if (weekdayPart !== 'ALL')
|
|
285
|
+
tokenParts.push(weekdayPart);
|
|
286
|
+
if (!tokenParts.length)
|
|
259
287
|
return title;
|
|
260
|
-
}
|
|
261
|
-
if (periodPart === 'ALL') {
|
|
262
|
-
return `${title}_${weekdayPart}`;
|
|
263
|
-
}
|
|
264
|
-
if (weekdayPart === 'ALL') {
|
|
265
|
-
return `${title}_${periodPart}`;
|
|
266
|
-
}
|
|
267
|
-
return `${title}_${periodPart}_${weekdayPart}`;
|
|
288
|
+
return `${title}_${tokenParts.join('_')}`;
|
|
268
289
|
}
|
|
269
|
-
|
|
290
|
+
const tokenParts = [periodPart];
|
|
291
|
+
if (monthsPart)
|
|
292
|
+
tokenParts.push(monthsPart);
|
|
293
|
+
if (weekdayPart !== 'ALL')
|
|
294
|
+
tokenParts.push(weekdayPart);
|
|
295
|
+
if (tokenParts.length === 1 && tokenParts[0] === 'ALL')
|
|
270
296
|
return 'ALL';
|
|
271
|
-
|
|
272
|
-
if (periodPart === 'ALL') {
|
|
273
|
-
return `ALL_${weekdayPart}`;
|
|
274
|
-
}
|
|
275
|
-
if (weekdayPart === 'ALL') {
|
|
276
|
-
return periodPart;
|
|
277
|
-
}
|
|
278
|
-
return `${periodPart}_${weekdayPart}`;
|
|
297
|
+
return tokenParts.join('_');
|
|
279
298
|
}
|
|
@@ -25,6 +25,12 @@ function ruleAppliesToCivilKey(rule, key, ctx) {
|
|
|
25
25
|
return false;
|
|
26
26
|
if (!rule.weekdays.includes(weekday))
|
|
27
27
|
return false;
|
|
28
|
+
// 3) Months filter (optional)
|
|
29
|
+
if (rule.months?.length) {
|
|
30
|
+
const month = Number(key.slice(5, 7));
|
|
31
|
+
if (!rule.months.includes(month))
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
28
34
|
return true;
|
|
29
35
|
}
|
|
30
36
|
// USED IN RuleCreateEditView
|
|
@@ -64,6 +70,12 @@ export function getManualRuleAffectedDates(rule, ctx) {
|
|
|
64
70
|
if (!isInPeriod(rule, key, ctx))
|
|
65
71
|
continue;
|
|
66
72
|
}
|
|
73
|
+
// If months are specified, narrow event dates to matching months only
|
|
74
|
+
if (rule.months?.length) {
|
|
75
|
+
const month = Number(key.slice(5, 7));
|
|
76
|
+
if (!rule.months.includes(month))
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
67
79
|
affected.push(key);
|
|
68
80
|
}
|
|
69
81
|
}
|