102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
import type { RoutineRecurrence } from '@mento/shared';
|
|
import { ROUTINE_WEEKDAY_LABELS } from '@mento/shared';
|
|
import {
|
|
addDays,
|
|
dayDiff,
|
|
jalaliDayOfMonth,
|
|
persianWeekday,
|
|
toFaDigits,
|
|
weekStartKey,
|
|
} from './routine-dates';
|
|
|
|
export type RecurrenceRule = {
|
|
recurrence: RoutineRecurrence;
|
|
startDate: string;
|
|
endDate: string | null;
|
|
weekdays: number[];
|
|
intervalDays: number | null;
|
|
monthDay: number | null;
|
|
specificDates: string[];
|
|
};
|
|
|
|
function weeklyDays(rule: RecurrenceRule): number[] {
|
|
return rule.weekdays.length ? rule.weekdays : [persianWeekday(rule.startDate)];
|
|
}
|
|
|
|
export function occursOn(rule: RecurrenceRule, day: string): boolean {
|
|
if (day < rule.startDate) return false;
|
|
if (rule.endDate && day > rule.endDate) return false;
|
|
|
|
switch (rule.recurrence) {
|
|
case 'daily':
|
|
return true;
|
|
case 'weekdays':
|
|
case 'weekly':
|
|
return weeklyDays(rule).includes(persianWeekday(day));
|
|
case 'biweekly': {
|
|
if (!weeklyDays(rule).includes(persianWeekday(day))) return false;
|
|
const weeks = Math.floor(
|
|
dayDiff(weekStartKey(rule.startDate), weekStartKey(day)) / 7,
|
|
);
|
|
return weeks % 2 === 0;
|
|
}
|
|
case 'monthly': {
|
|
const target = rule.monthDay ?? jalaliDayOfMonth(rule.startDate);
|
|
const current = jalaliDayOfMonth(day);
|
|
if (current === target) return true;
|
|
// ماههایی که روز هدف را ندارند (مثلاً ۳۱ در مهر): آخرین روز ماه
|
|
const isLastDay = jalaliDayOfMonth(addDays(day, 1)) === 1;
|
|
return isLastDay && target > current;
|
|
}
|
|
case 'every_n_days': {
|
|
const n = Math.max(1, rule.intervalDays ?? 1);
|
|
return dayDiff(rule.startDate, day) % n === 0;
|
|
}
|
|
case 'specific_dates':
|
|
return rule.specificDates.includes(day);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function joinFa(items: string[]): string {
|
|
if (items.length <= 1) return items.join('');
|
|
return `${items.slice(0, -1).join('، ')} و ${items[items.length - 1]}`;
|
|
}
|
|
|
|
function describeWeekdays(days: number[]): string {
|
|
const sorted = [...new Set(days)].sort((a, b) => a - b);
|
|
if (sorted.length === 7) return 'هر روز';
|
|
const contiguous =
|
|
sorted.length >= 3 &&
|
|
sorted.every((d, i) => i === 0 || d === sorted[i - 1]! + 1);
|
|
if (contiguous) {
|
|
return `${ROUTINE_WEEKDAY_LABELS[sorted[0]!]} تا ${ROUTINE_WEEKDAY_LABELS[sorted[sorted.length - 1]!]}`;
|
|
}
|
|
return `هر ${joinFa(sorted.map((d) => ROUTINE_WEEKDAY_LABELS[d]!))}`;
|
|
}
|
|
|
|
export function describeRecurrence(rule: RecurrenceRule): string {
|
|
switch (rule.recurrence) {
|
|
case 'daily':
|
|
return 'هر روز';
|
|
case 'weekdays':
|
|
case 'weekly':
|
|
return describeWeekdays(weeklyDays(rule));
|
|
case 'biweekly':
|
|
return `هر دو هفته، ${joinFa(weeklyDays(rule).map((d) => ROUTINE_WEEKDAY_LABELS[d]!))}`;
|
|
case 'monthly': {
|
|
const day = rule.monthDay ?? jalaliDayOfMonth(rule.startDate);
|
|
return day === 1 ? 'اول هر ماه' : `روز ${toFaDigits(day)} هر ماه`;
|
|
}
|
|
case 'every_n_days': {
|
|
const n = Math.max(1, rule.intervalDays ?? 1);
|
|
return n === 1 ? 'هر روز' : `هر ${toFaDigits(n)} روز یکبار`;
|
|
}
|
|
case 'specific_dates':
|
|
return `${toFaDigits(rule.specificDates.length)} تاریخ مشخص`;
|
|
default:
|
|
return '';
|
|
}
|
|
}
|