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'); }