feat: routines module; super admin subscription badge, 7-day trial and lifetime plan
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user