156 lines
5.6 KiB
JavaScript
156 lines
5.6 KiB
JavaScript
import moment from 'jalali-moment';
|
||
|
||
export const WEEKDAYS = [
|
||
{ label: 'شنبه', value: 6 },
|
||
{ label: 'یکشنبه', value: 0 },
|
||
{ label: 'دوشنبه', value: 1 },
|
||
{ label: 'سهشنبه', value: 2 },
|
||
{ label: 'چهارشنبه', value: 3 },
|
||
{ label: 'پنجشنبه', value: 4 },
|
||
{ label: 'جمعه', value: 5 }
|
||
];
|
||
|
||
const IRAN_WEEK_ORDER = [6, 0, 1, 2, 3, 4, 5];
|
||
const LABEL_BY_VALUE = Object.fromEntries(WEEKDAYS.map((day) => [day.value, day.label]));
|
||
|
||
const joinFaList = (items) => {
|
||
if (!items.length) return '';
|
||
if (items.length === 1) return items[0];
|
||
if (items.length === 2) return `${items[0]} و ${items[1]}`;
|
||
return `${items.slice(0, -1).join('، ')} و ${items[items.length - 1]}`;
|
||
};
|
||
|
||
export function formatClassDays(days = []) {
|
||
const unique = [...new Set((days || []).map(Number).filter((day) => day >= 0 && day <= 6))];
|
||
unique.sort((a, b) => IRAN_WEEK_ORDER.indexOf(a) - IRAN_WEEK_ORDER.indexOf(b));
|
||
return joinFaList(unique.map((day) => LABEL_BY_VALUE[day]).filter(Boolean));
|
||
}
|
||
|
||
export function formatClassTime(startTime, endTime) {
|
||
const start = String(startTime || '').trim();
|
||
const end = String(endTime || '').trim();
|
||
if (!start && !end) return '';
|
||
if (!end) return start;
|
||
if (!start) return end;
|
||
return `${start} – ${end}`;
|
||
}
|
||
|
||
export function formatClassSchedule(cls) {
|
||
const days = formatClassDays(cls?.days);
|
||
const time = formatClassTime(cls?.startTime, cls?.endTime);
|
||
if (days && time) return `${days} | ${time}`;
|
||
return days || time || '';
|
||
}
|
||
|
||
/**
|
||
* Parses any date format (Jalali string, ISO string, Date object, Persian/Latin digits) into a valid moment object
|
||
*/
|
||
export function parseDateSmart(input) {
|
||
if (!input) return null;
|
||
if (input instanceof Date) {
|
||
const m = moment(input);
|
||
return m.isValid() ? m : null;
|
||
}
|
||
const latin = String(input)
|
||
.replace(/[۰-۹]/g, (d) => '۰۱۲۳۴۵۶۷۸۹'.indexOf(d))
|
||
.replace(/[٠-٩]/g, (d) => '٠١٢٣٤٥٦٧٨٩'.indexOf(d))
|
||
.trim();
|
||
const isJalaliPattern = /^(1[34]\d{2})[/-](\d{1,2})[/-](\d{1,2})/.test(latin);
|
||
if (isJalaliPattern) {
|
||
const m = moment(latin, ['jYYYY/jMM/jDD', 'jYYYY-jMM-jDD', 'jYYYY/jM/jD', 'jYYYY-jM-jD']);
|
||
if (m.isValid()) return m;
|
||
}
|
||
const m = moment(latin);
|
||
if (m.isValid()) return m;
|
||
const mJalali = moment(latin, ['jYYYY/jMM/jDD', 'jYYYY-jMM-jDD']);
|
||
return mJalali.isValid() ? mJalali : null;
|
||
}
|
||
|
||
/**
|
||
* Calculates the class end date in Jalali (jYYYY/jMM/jDD) based on start date, selected days, and number of sessions.
|
||
* @param {string|Date} startDate - Jalali string (e.g. 1403/01/01) or ISO string / Date
|
||
* @param {number[]} days - Array of weekday indexes (0=Sun, 1=Mon, ..., 6=Sat)
|
||
* @param {number} numberOfSessions - Total number of sessions
|
||
* @returns {string} Jalali formatted date string, or empty string if not calculable
|
||
*/
|
||
export function calculateClassEndDate(startDate, days = [], numberOfSessions = 0) {
|
||
const countNeeded = parseInt(numberOfSessions, 10);
|
||
if (!startDate || isNaN(countNeeded) || countNeeded < 1) return '';
|
||
|
||
const validDays = Array.isArray(days)
|
||
? [...new Set(days.map(Number).filter((d) => d >= 0 && d <= 6))]
|
||
: [];
|
||
if (!validDays.length) return '';
|
||
|
||
let current = parseDateSmart(startDate);
|
||
if (!current || !current.isValid()) return '';
|
||
|
||
let count = 0;
|
||
let lastMatchingDate = null;
|
||
let safetyLimit = 365 * 5;
|
||
|
||
while (count < countNeeded && safetyLimit > 0) {
|
||
safetyLimit--;
|
||
if (validDays.includes(current.day())) {
|
||
count++;
|
||
lastMatchingDate = current.clone();
|
||
}
|
||
if (count >= countNeeded) break;
|
||
current.add(1, 'day');
|
||
}
|
||
|
||
return lastMatchingDate ? lastMatchingDate.locale('fa').format('jYYYY/jMM/jDD') : '';
|
||
}
|
||
|
||
/**
|
||
* Calculates the halfway due date for a class in Jalali (jYYYY/jMM/jDD).
|
||
* If class has 10 sessions, due date is calculated as the date of the 5th session.
|
||
* @param {Object} cls - Class object
|
||
* @param {Array} [sessions] - Optional array of session objects
|
||
* @returns {string} Jalali formatted date string, or today's date if not calculable
|
||
*/
|
||
export function calculateClassMidDate(cls, sessions = []) {
|
||
if (Array.isArray(sessions) && sessions.length > 0) {
|
||
const validSessions = sessions
|
||
.filter((s) => s && (s.day || s.date))
|
||
.map((s) => ({
|
||
...s,
|
||
momentDate: parseDateSmart(s.day || s.date)
|
||
}))
|
||
.filter((s) => s.momentDate && s.momentDate.isValid());
|
||
|
||
if (validSessions.length > 0) {
|
||
validSessions.sort((a, b) => a.momentDate.valueOf() - b.momentDate.valueOf());
|
||
const midIndex = Math.ceil(validSessions.length / 2) - 1;
|
||
return validSessions[midIndex].momentDate.locale('fa').format('jYYYY/jMM/jDD');
|
||
}
|
||
}
|
||
|
||
if (!cls) return moment().locale('fa').format('jYYYY/jMM/jDD');
|
||
|
||
const totalSessions = parseInt(cls.numberOfSessions || cls.course?.sectionCount || 0, 10);
|
||
const midSessions = totalSessions > 0 ? Math.ceil(totalSessions / 2) : 0;
|
||
|
||
if (cls.startDate && Array.isArray(cls.days) && cls.days.length > 0 && midSessions >= 1) {
|
||
const computed = calculateClassEndDate(cls.startDate, cls.days, midSessions);
|
||
if (computed) return computed;
|
||
}
|
||
|
||
if (cls.startDate && cls.endDate) {
|
||
const startM = parseDateSmart(cls.startDate);
|
||
const endM = parseDateSmart(cls.endDate);
|
||
if (startM && startM.isValid() && endM && endM.isValid()) {
|
||
const midTime = startM.valueOf() + (endM.valueOf() - startM.valueOf()) / 2;
|
||
return moment(midTime).locale('fa').format('jYYYY/jMM/jDD');
|
||
}
|
||
}
|
||
|
||
if (cls.startDate) {
|
||
const startM = parseDateSmart(cls.startDate);
|
||
if (startM && startM.isValid()) return startM.locale('fa').format('jYYYY/jMM/jDD');
|
||
}
|
||
|
||
return moment().locale('fa').format('jYYYY/jMM/jDD');
|
||
}
|
||
|