Files
gameno-dashboard/src/utils/classSchedule.js
T

156 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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');
}