fix(dashboard): resolve loop and datepicker issues in class and session edit forms
This commit is contained in:
@@ -299,11 +299,9 @@ const props = defineProps({
|
|||||||
endTime: { type: String, default: '' }
|
endTime: { type: String, default: '' }
|
||||||
});
|
});
|
||||||
|
|
||||||
const { toPersianDigits, toLatinDigits, formatJalali, getTodayJalali } = usePersianDate();
|
const { toPersianDigits, toLatinDigits, toGregorianIso, formatJalali, getTodayJalali } = usePersianDate();
|
||||||
const { showSuccess, showError } = useToast();
|
const { showSuccess, showError } = useToast();
|
||||||
|
|
||||||
const todayJalali = () => moment().locale('fa').format('jYYYY/jMM/jDD');
|
|
||||||
|
|
||||||
const sessions = ref([]);
|
const sessions = ref([]);
|
||||||
const selectedSessions = ref([]);
|
const selectedSessions = ref([]);
|
||||||
const loadingSessions = ref(false);
|
const loadingSessions = ref(false);
|
||||||
@@ -315,7 +313,7 @@ const bulkDeleteMode = ref(false);
|
|||||||
const sessionToDelete = ref(null);
|
const sessionToDelete = ref(null);
|
||||||
const bulkStatus = ref(null);
|
const bulkStatus = ref(null);
|
||||||
const generatedSessions = ref([]);
|
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 notifyDialogVisible = ref(false);
|
||||||
const selectedSessionForNotify = ref(null);
|
const selectedSessionForNotify = ref(null);
|
||||||
@@ -409,7 +407,7 @@ const weekdays = [
|
|||||||
|
|
||||||
const autoForm = reactive({
|
const autoForm = reactive({
|
||||||
sessionCount: props.defaultSessionCount || 12,
|
sessionCount: props.defaultSessionCount || 12,
|
||||||
startDate: todayJalali(),
|
startDate: getTodayJalali(),
|
||||||
startTime: props.startTime || '19:00',
|
startTime: props.startTime || '19:00',
|
||||||
endTime: props.endTime || '20:30',
|
endTime: props.endTime || '20:30',
|
||||||
selectedDays: props.days?.length ? [...props.days] : [6, 2]
|
selectedDays: props.days?.length ? [...props.days] : [6, 2]
|
||||||
@@ -428,9 +426,7 @@ watch(
|
|||||||
|
|
||||||
const toSessionDayIso = (value) => {
|
const toSessionDayIso = (value) => {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
const latin = toLatinDigits(String(value));
|
return toGregorianIso(value) || null;
|
||||||
const m = moment(latin, ['jYYYY/jMM/jDD', 'YYYY/MM/DD', moment.ISO_8601], true);
|
|
||||||
return m.isValid() ? m.toDate().toISOString() : null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchSessions = async () => {
|
const fetchSessions = async () => {
|
||||||
@@ -509,7 +505,7 @@ const applyBulkStatus = async () => {
|
|||||||
|
|
||||||
const resetCreationForms = () => {
|
const resetCreationForms = () => {
|
||||||
generatedSessions.value = [];
|
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 = () => {
|
const generateSessionsPreview = () => {
|
||||||
@@ -524,21 +520,29 @@ const generateSessionsPreview = () => {
|
|||||||
|
|
||||||
const list = [];
|
const list = [];
|
||||||
const latinStartDate = toLatinDigits(autoForm.startDate);
|
const latinStartDate = toLatinDigits(autoForm.startDate);
|
||||||
let currentMoment = moment(latinStartDate, 'jYYYY/jMM/jDD');
|
let currentMoment = moment(latinStartDate, ['jYYYY/jMM/jDD', 'YYYY-MM-DD', 'YYYY/MM/DD', moment.ISO_8601]);
|
||||||
let count = 0;
|
if (!currentMoment.isValid()) {
|
||||||
const targetCount = autoForm.sessionCount || 12;
|
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())) {
|
if (autoForm.selectedDays.includes(currentMoment.day())) {
|
||||||
count++;
|
count++;
|
||||||
list.push({
|
list.push({
|
||||||
sessionNumber: count,
|
sessionNumber: count,
|
||||||
date: currentMoment.locale('fa').format('YYYY/MM/DD'),
|
date: toLatinDigits(currentMoment.locale('fa').format('jYYYY/jMM/jDD')),
|
||||||
startTime: autoForm.startTime,
|
startTime: autoForm.startTime,
|
||||||
endTime: autoForm.endTime,
|
endTime: autoForm.endTime,
|
||||||
topic: `جلسه ${count}`
|
topic: `جلسه ${count}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (count >= targetCount) break;
|
||||||
currentMoment.add(1, 'day');
|
currentMoment.add(1, 'day');
|
||||||
}
|
}
|
||||||
generatedSessions.value = list;
|
generatedSessions.value = list;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ export function setupGuards(router) {
|
|||||||
return next({ name: 'Login', query: { redirect: to.fullPath } });
|
return next({ name: 'Login', query: { redirect: to.fullPath } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Rehydrate permission store if token exists but permissions store is unpopulated
|
// 3. Rehydrate permission store if token exists but permissions store is not loaded
|
||||||
if (!permissionStore.isLoaded || permissionStore.permissions.length === 0) {
|
if (!permissionStore.isLoaded) {
|
||||||
try {
|
try {
|
||||||
await permissionStore.fetchPermissions();
|
await permissionStore.fetchPermissions();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ const route = useRoute();
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { showSuccess, showError } = useToast();
|
const { showSuccess, showError } = useToast();
|
||||||
const { hasPermission } = usePermission();
|
const { hasPermission } = usePermission();
|
||||||
const { toPersianDigits, toLatinDigits, getTodayJalali } = usePersianDate();
|
const { toPersianDigits, toLatinDigits, toJalaliPickerValue, toGregorianIso, getTodayJalali } = usePersianDate();
|
||||||
|
|
||||||
const classId = route.params.id;
|
const classId = route.params.id;
|
||||||
const queryCourseId = route.query.courseId ? String(route.query.courseId) : null;
|
const queryCourseId = route.query.courseId ? String(route.query.courseId) : null;
|
||||||
@@ -424,19 +424,6 @@ const goBack = () => {
|
|||||||
router.push('/classes');
|
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) => {
|
const applyCourseDefaults = (course) => {
|
||||||
if (!course) return;
|
if (!course) return;
|
||||||
if (!form.professor && (course.professor?._id || course.professor)) {
|
if (!form.professor && (course.professor?._id || course.professor)) {
|
||||||
@@ -453,23 +440,32 @@ const applyCourseDefaults = (course) => {
|
|||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const [cRes, pRes, uRes] = await Promise.all([
|
const [cResult, pResult, uResult] = await Promise.allSettled([
|
||||||
courseApi.getAll({ limit: 100 }),
|
courseApi.getAll({ limit: 100 }),
|
||||||
professorApi.getAll({ limit: 100 }),
|
professorApi.getAll({ limit: 100 }),
|
||||||
userApi.getAll({ limit: 200 })
|
userApi.getAll({ limit: 200 })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const cData = cRes.data || cRes;
|
if (cResult.status === 'fulfilled') {
|
||||||
courses.value = Array.isArray(cData) ? cData : (cData.items || cData.courses || cData.data || []);
|
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;
|
if (pResult.status === 'fulfilled') {
|
||||||
professors.value = (Array.isArray(pData) ? pData : (pData.items || pData.professors || pData.data || [])).map((p) => ({
|
const pRes = pResult.value;
|
||||||
...p,
|
const pData = pRes.data || pRes;
|
||||||
name: `${p.name || ''} ${p.surname || ''}`.trim()
|
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;
|
if (uResult.status === 'fulfilled') {
|
||||||
allUsers.value = Array.isArray(uData) ? uData : (uData.items || uData.users || uData.data || []);
|
const uRes = uResult.value;
|
||||||
|
const uData = uRes.data || uRes;
|
||||||
|
allUsers.value = Array.isArray(uData) ? uData : (uData.items || uData.users || uData.data || []);
|
||||||
|
}
|
||||||
|
|
||||||
if (classId) {
|
if (classId) {
|
||||||
const res = await classApi.getOne(classId);
|
const res = await classApi.getOne(classId);
|
||||||
@@ -484,8 +480,8 @@ const fetchData = async () => {
|
|||||||
discount: data.discount || 0,
|
discount: data.discount || 0,
|
||||||
freeSpots: data.freeSpots ?? null,
|
freeSpots: data.freeSpots ?? null,
|
||||||
showOnFrontend: data.showOnFrontend !== false,
|
showOnFrontend: data.showOnFrontend !== false,
|
||||||
startDate: toJalaliDisplay(data.startDate),
|
startDate: toJalaliPickerValue(data.startDate),
|
||||||
endDate: toJalaliDisplay(data.endDate),
|
endDate: toJalaliPickerValue(data.endDate),
|
||||||
days: Array.isArray(data.days) ? data.days.map(Number).filter((day) => day >= 0 && day <= 6) : [],
|
days: Array.isArray(data.days) ? data.days.map(Number).filter((day) => day >= 0 && day <= 6) : [],
|
||||||
startTime: data.startTime || '',
|
startTime: data.startTime || '',
|
||||||
endTime: data.endTime || '',
|
endTime: data.endTime || '',
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ import Button from 'primevue/button';
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { showSuccess, showError } = useToast();
|
const { showSuccess, showError } = useToast();
|
||||||
const { toLatinDigits, getTodayJalali } = usePersianDate();
|
const { toLatinDigits, toJalaliPickerValue, toGregorianIso, getTodayJalali } = usePersianDate();
|
||||||
|
|
||||||
const sessionId = route.params.id;
|
const sessionId = route.params.id;
|
||||||
const isEditMode = computed(() => !!sessionId);
|
const isEditMode = computed(() => !!sessionId);
|
||||||
@@ -132,7 +132,7 @@ const form = reactive({
|
|||||||
course: null,
|
course: null,
|
||||||
class: null,
|
class: null,
|
||||||
professor: null,
|
professor: null,
|
||||||
day: moment().locale('fa').format('jYYYY/jMM/jDD'),
|
day: getTodayJalali(),
|
||||||
startTime: '19:00',
|
startTime: '19:00',
|
||||||
endTime: '20:30',
|
endTime: '20:30',
|
||||||
status: 'scheduled',
|
status: 'scheduled',
|
||||||
@@ -142,27 +142,19 @@ const form = reactive({
|
|||||||
adminNotes: []
|
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) => {
|
const loadClasses = async (courseId) => {
|
||||||
if (!courseId) {
|
if (!courseId) {
|
||||||
classes.value = [];
|
classes.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await classApi.getAll({ limit: 100, courseId });
|
try {
|
||||||
const data = res.data || res;
|
const res = await classApi.getAll({ limit: 100, courseId });
|
||||||
classes.value = Array.isArray(data) ? data : (data.items || data.data || []);
|
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 () => {
|
const onCourseChange = async () => {
|
||||||
@@ -172,19 +164,25 @@ const onCourseChange = async () => {
|
|||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const [cRes, pRes] = await Promise.all([
|
const [cResult, pResult] = await Promise.allSettled([
|
||||||
courseApi.getAll({ limit: 100 }),
|
courseApi.getAll({ limit: 100 }),
|
||||||
professorApi.getAll({ limit: 100 })
|
professorApi.getAll({ limit: 100 })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const cData = cRes.data || cRes;
|
if (cResult.status === 'fulfilled') {
|
||||||
courses.value = Array.isArray(cData) ? cData : (cData.items || cData.courses || cData.data || []);
|
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;
|
if (pResult.status === 'fulfilled') {
|
||||||
professors.value = (Array.isArray(pData) ? pData : (pData.items || pData.professors || pData.data || [])).map((p) => ({
|
const pRes = pResult.value;
|
||||||
...p,
|
const pData = pRes.data || pRes;
|
||||||
name: `${p.name || ''} ${p.surname || ''}`.trim()
|
professors.value = (Array.isArray(pData) ? pData : (pData.items || pData.professors || pData.data || [])).map((p) => ({
|
||||||
}));
|
...p,
|
||||||
|
name: `${p.name || ''} ${p.surname || ''}`.trim()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
const res = await sessionApi.getOne(sessionId);
|
const res = await sessionApi.getOne(sessionId);
|
||||||
@@ -195,7 +193,7 @@ const fetchData = async () => {
|
|||||||
course: courseId,
|
course: courseId,
|
||||||
class: data.class?._id || data.class || null,
|
class: data.class?._id || data.class || null,
|
||||||
professor: data.professor?._id || data.professor || 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',
|
startTime: data.startTime || '19:00',
|
||||||
endTime: data.endTime || '20:30',
|
endTime: data.endTime || '20:30',
|
||||||
status: data.status || 'scheduled',
|
status: data.status || 'scheduled',
|
||||||
|
|||||||
Reference in New Issue
Block a user