From 0201e81571c6bb835d78492a8a09b08e3d17e066 Mon Sep 17 00:00:00 2001 From: Kavehhn174 Date: Fri, 21 Aug 2026 23:26:34 +0330 Subject: [PATCH] fix(dashboard): resolve loop and datepicker issues in class and session edit forms --- .../classes/ClassSessionsSection.vue | 32 +++++++----- src/router/guards.js | 4 +- src/views/classes/ClassFormView.vue | 48 ++++++++--------- src/views/sessions/SessionFormView.vue | 52 +++++++++---------- 4 files changed, 67 insertions(+), 69 deletions(-) diff --git a/src/components/classes/ClassSessionsSection.vue b/src/components/classes/ClassSessionsSection.vue index 858fb90..a386504 100644 --- a/src/components/classes/ClassSessionsSection.vue +++ b/src/components/classes/ClassSessionsSection.vue @@ -299,11 +299,9 @@ const props = defineProps({ endTime: { type: String, default: '' } }); -const { toPersianDigits, toLatinDigits, formatJalali, getTodayJalali } = usePersianDate(); +const { toPersianDigits, toLatinDigits, toGregorianIso, formatJalali, getTodayJalali } = usePersianDate(); const { showSuccess, showError } = useToast(); -const todayJalali = () => moment().locale('fa').format('jYYYY/jMM/jDD'); - const sessions = ref([]); const selectedSessions = ref([]); const loadingSessions = ref(false); @@ -315,7 +313,7 @@ const bulkDeleteMode = ref(false); const sessionToDelete = ref(null); const bulkStatus = ref(null); const generatedSessions = ref([]); -const manualSessions = ref([{ date: todayJalali(), startTime: '19:00', endTime: '20:30', topic: '' }]); +const manualSessions = ref([{ date: getTodayJalali(), startTime: '19:00', endTime: '20:30', topic: '' }]); const notifyDialogVisible = ref(false); const selectedSessionForNotify = ref(null); @@ -409,7 +407,7 @@ const weekdays = [ const autoForm = reactive({ sessionCount: props.defaultSessionCount || 12, - startDate: todayJalali(), + startDate: getTodayJalali(), startTime: props.startTime || '19:00', endTime: props.endTime || '20:30', selectedDays: props.days?.length ? [...props.days] : [6, 2] @@ -428,9 +426,7 @@ watch( const toSessionDayIso = (value) => { if (!value) return null; - const latin = toLatinDigits(String(value)); - const m = moment(latin, ['jYYYY/jMM/jDD', 'YYYY/MM/DD', moment.ISO_8601], true); - return m.isValid() ? m.toDate().toISOString() : null; + return toGregorianIso(value) || null; }; const fetchSessions = async () => { @@ -509,7 +505,7 @@ const applyBulkStatus = async () => { const resetCreationForms = () => { generatedSessions.value = []; - manualSessions.value = [{ date: todayJalali(), startTime: '19:00', endTime: '20:30', topic: '' }]; + manualSessions.value = [{ date: getTodayJalali(), startTime: '19:00', endTime: '20:30', topic: '' }]; }; const generateSessionsPreview = () => { @@ -524,21 +520,29 @@ const generateSessionsPreview = () => { const list = []; const latinStartDate = toLatinDigits(autoForm.startDate); - let currentMoment = moment(latinStartDate, 'jYYYY/jMM/jDD'); - let count = 0; - const targetCount = autoForm.sessionCount || 12; + let currentMoment = moment(latinStartDate, ['jYYYY/jMM/jDD', 'YYYY-MM-DD', 'YYYY/MM/DD', moment.ISO_8601]); + if (!currentMoment.isValid()) { + showError('تاریخ شروع نامعتبر است'); + return; + } - while (count < targetCount) { + let count = 0; + let safetyLimit = 365 * 5; + const targetCount = Math.max(1, parseInt(autoForm.sessionCount, 10) || 12); + + while (count < targetCount && safetyLimit > 0) { + safetyLimit--; if (autoForm.selectedDays.includes(currentMoment.day())) { count++; list.push({ sessionNumber: count, - date: currentMoment.locale('fa').format('YYYY/MM/DD'), + date: toLatinDigits(currentMoment.locale('fa').format('jYYYY/jMM/jDD')), startTime: autoForm.startTime, endTime: autoForm.endTime, topic: `جلسه ${count}` }); } + if (count >= targetCount) break; currentMoment.add(1, 'day'); } generatedSessions.value = list; diff --git a/src/router/guards.js b/src/router/guards.js index 7b28160..7d9d467 100644 --- a/src/router/guards.js +++ b/src/router/guards.js @@ -31,8 +31,8 @@ export function setupGuards(router) { return next({ name: 'Login', query: { redirect: to.fullPath } }); } - // 3. Rehydrate permission store if token exists but permissions store is unpopulated - if (!permissionStore.isLoaded || permissionStore.permissions.length === 0) { + // 3. Rehydrate permission store if token exists but permissions store is not loaded + if (!permissionStore.isLoaded) { try { await permissionStore.fetchPermissions(); } catch (err) { diff --git a/src/views/classes/ClassFormView.vue b/src/views/classes/ClassFormView.vue index 49ca007..2abc370 100644 --- a/src/views/classes/ClassFormView.vue +++ b/src/views/classes/ClassFormView.vue @@ -324,7 +324,7 @@ const route = useRoute(); const router = useRouter(); const { showSuccess, showError } = useToast(); const { hasPermission } = usePermission(); -const { toPersianDigits, toLatinDigits, getTodayJalali } = usePersianDate(); +const { toPersianDigits, toLatinDigits, toJalaliPickerValue, toGregorianIso, getTodayJalali } = usePersianDate(); const classId = route.params.id; const queryCourseId = route.query.courseId ? String(route.query.courseId) : null; @@ -424,19 +424,6 @@ const goBack = () => { router.push('/classes'); }; -const toGregorianIso = (jalaliValue) => { - if (!jalaliValue) return undefined; - if (jalaliValue instanceof Date) return jalaliValue.toISOString(); - const latin = toLatinDigits(String(jalaliValue)); - const m = moment(latin, 'jYYYY/jMM/jDD'); - return m.isValid() ? m.toDate().toISOString() : undefined; -}; - -const toJalaliDisplay = (value) => { - if (!value) return ''; - return moment(value).locale('fa').format('jYYYY/jMM/jDD'); -}; - const applyCourseDefaults = (course) => { if (!course) return; if (!form.professor && (course.professor?._id || course.professor)) { @@ -453,23 +440,32 @@ const applyCourseDefaults = (course) => { const fetchData = async () => { try { - const [cRes, pRes, uRes] = await Promise.all([ + const [cResult, pResult, uResult] = await Promise.allSettled([ courseApi.getAll({ limit: 100 }), professorApi.getAll({ limit: 100 }), userApi.getAll({ limit: 200 }) ]); - const cData = cRes.data || cRes; - courses.value = Array.isArray(cData) ? cData : (cData.items || cData.courses || cData.data || []); + if (cResult.status === 'fulfilled') { + const cRes = cResult.value; + const cData = cRes.data || cRes; + courses.value = Array.isArray(cData) ? cData : (cData.items || cData.courses || cData.data || []); + } - const pData = pRes.data || pRes; - professors.value = (Array.isArray(pData) ? pData : (pData.items || pData.professors || pData.data || [])).map((p) => ({ - ...p, - name: `${p.name || ''} ${p.surname || ''}`.trim() - })); + if (pResult.status === 'fulfilled') { + const pRes = pResult.value; + const pData = pRes.data || pRes; + professors.value = (Array.isArray(pData) ? pData : (pData.items || pData.professors || pData.data || [])).map((p) => ({ + ...p, + name: `${p.name || ''} ${p.surname || ''}`.trim() + })); + } - const uData = uRes.data || uRes; - allUsers.value = Array.isArray(uData) ? uData : (uData.items || uData.users || uData.data || []); + if (uResult.status === 'fulfilled') { + const uRes = uResult.value; + const uData = uRes.data || uRes; + allUsers.value = Array.isArray(uData) ? uData : (uData.items || uData.users || uData.data || []); + } if (classId) { const res = await classApi.getOne(classId); @@ -484,8 +480,8 @@ const fetchData = async () => { discount: data.discount || 0, freeSpots: data.freeSpots ?? null, showOnFrontend: data.showOnFrontend !== false, - startDate: toJalaliDisplay(data.startDate), - endDate: toJalaliDisplay(data.endDate), + startDate: toJalaliPickerValue(data.startDate), + endDate: toJalaliPickerValue(data.endDate), days: Array.isArray(data.days) ? data.days.map(Number).filter((day) => day >= 0 && day <= 6) : [], startTime: data.startTime || '', endTime: data.endTime || '', diff --git a/src/views/sessions/SessionFormView.vue b/src/views/sessions/SessionFormView.vue index 9ace9c5..09b34ea 100644 --- a/src/views/sessions/SessionFormView.vue +++ b/src/views/sessions/SessionFormView.vue @@ -112,7 +112,7 @@ import Button from 'primevue/button'; const route = useRoute(); const router = useRouter(); const { showSuccess, showError } = useToast(); -const { toLatinDigits, getTodayJalali } = usePersianDate(); +const { toLatinDigits, toJalaliPickerValue, toGregorianIso, getTodayJalali } = usePersianDate(); const sessionId = route.params.id; const isEditMode = computed(() => !!sessionId); @@ -132,7 +132,7 @@ const form = reactive({ course: null, class: null, professor: null, - day: moment().locale('fa').format('jYYYY/jMM/jDD'), + day: getTodayJalali(), startTime: '19:00', endTime: '20:30', status: 'scheduled', @@ -142,27 +142,19 @@ const form = reactive({ adminNotes: [] }); -const toGregorianIso = (jalaliValue) => { - if (!jalaliValue) return null; - if (jalaliValue instanceof Date) return jalaliValue.toISOString(); - const latin = toLatinDigits(String(jalaliValue)); - const m = moment(latin, 'jYYYY/jMM/jDD'); - return m.isValid() ? m.toDate().toISOString() : null; -}; - -const toJalaliDisplay = (value) => { - if (!value) return ''; - return moment(value).locale('fa').format('jYYYY/jMM/jDD'); -}; - const loadClasses = async (courseId) => { if (!courseId) { classes.value = []; return; } - const res = await classApi.getAll({ limit: 100, courseId }); - const data = res.data || res; - classes.value = Array.isArray(data) ? data : (data.items || data.data || []); + try { + const res = await classApi.getAll({ limit: 100, courseId }); + const data = res.data || res; + classes.value = Array.isArray(data) ? data : (data.items || data.data || []); + } catch (err) { + console.warn('Failed to load classes for course:', err); + classes.value = []; + } }; const onCourseChange = async () => { @@ -172,19 +164,25 @@ const onCourseChange = async () => { const fetchData = async () => { try { - const [cRes, pRes] = await Promise.all([ + const [cResult, pResult] = await Promise.allSettled([ courseApi.getAll({ limit: 100 }), professorApi.getAll({ limit: 100 }) ]); - const cData = cRes.data || cRes; - courses.value = Array.isArray(cData) ? cData : (cData.items || cData.courses || cData.data || []); + if (cResult.status === 'fulfilled') { + const cRes = cResult.value; + const cData = cRes.data || cRes; + courses.value = Array.isArray(cData) ? cData : (cData.items || cData.courses || cData.data || []); + } - const pData = pRes.data || pRes; - professors.value = (Array.isArray(pData) ? pData : (pData.items || pData.professors || pData.data || [])).map((p) => ({ - ...p, - name: `${p.name || ''} ${p.surname || ''}`.trim() - })); + if (pResult.status === 'fulfilled') { + const pRes = pResult.value; + const pData = pRes.data || pRes; + professors.value = (Array.isArray(pData) ? pData : (pData.items || pData.professors || pData.data || [])).map((p) => ({ + ...p, + name: `${p.name || ''} ${p.surname || ''}`.trim() + })); + } if (sessionId) { const res = await sessionApi.getOne(sessionId); @@ -195,7 +193,7 @@ const fetchData = async () => { course: courseId, class: data.class?._id || data.class || null, professor: data.professor?._id || data.professor || null, - day: toJalaliDisplay(data.day || data.date), + day: toJalaliPickerValue(data.day || data.date) || getTodayJalali(), startTime: data.startTime || '19:00', endTime: data.endTime || '20:30', status: data.status || 'scheduled',