diff --git a/src/assets/styles/main.scss b/src/assets/styles/main.scss index 9958f84..45edd04 100644 --- a/src/assets/styles/main.scss +++ b/src/assets/styles/main.scss @@ -119,26 +119,85 @@ a { .vpd-controls { color: var(--text-primary) !important; + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 10px; - button, span, i, svg, .vpd-month-label { + .vpd-next, + .vpd-prev, + button { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + width: 32px !important; + height: 32px !important; + border-radius: 8px !important; + background-color: var(--surface-ground) !important; + border: 1px solid var(--border-color) !important; color: var(--text-primary) !important; - fill: var(--text-primary) !important; + cursor: pointer; + transition: all 0.2s ease; + + svg { + width: 14px !important; + height: 14px !important; + fill: var(--text-primary) !important; + + path { + fill: var(--text-primary) !important; + } + } + + &:hover:not([disabled="true"]) { + background-color: var(--primary-color) !important; + border-color: var(--primary-color) !important; + color: #ffffff !important; + + svg, + svg path { + fill: #ffffff !important; + } + } + + &[disabled="true"] { + opacity: 0.3 !important; + cursor: not-allowed !important; + } } - button:hover { - background-color: var(--surface-hover) !important; - border-radius: 50%; - } + .vpd-month-label { + span { + color: var(--text-primary) !important; + font-weight: 600 !important; + padding: 4px 10px !important; + border-radius: 6px !important; + border: 1px solid var(--border-color) !important; + background-color: var(--surface-ground) !important; - .vpd-month-label span { - border-color: var(--border-color) !important; - - &:hover { - background-color: var(--surface-hover) !important; + &:hover { + background-color: var(--surface-hover) !important; + border-color: var(--primary-color) !important; + } } } } + .vpd-up-arrow-btn, + .vpd-down-arrow-btn { + svg, + svg path { + fill: var(--text-primary) !important; + } + &:hover { + svg, + svg path { + fill: var(--primary-color) !important; + } + } + } + + .vpd-week { .vpd-weekday { color: var(--text-muted) !important; @@ -272,3 +331,20 @@ a { } } +/* ========================================== + Responsive Tabs & TabList Horizontal Scroll + ========================================== */ +.p-tablist { + max-width: 100% !important; + overflow-x: auto !important; + -webkit-overflow-scrolling: touch !important; + scrollbar-width: thin; +} + +.p-tablist-tab-list { + flex-wrap: nowrap !important; + white-space: nowrap !important; + min-width: max-content !important; +} + + diff --git a/src/components/classes/ClassSessionsSection.vue b/src/components/classes/ClassSessionsSection.vue index d56e9d9..bac133e 100644 --- a/src/components/classes/ClassSessionsSection.vue +++ b/src/components/classes/ClassSessionsSection.vue @@ -130,7 +130,7 @@
- +
@@ -194,7 +194,7 @@
#{{ toPersianDigits(index + 1) }}
- +
diff --git a/src/composables/usePersianDate.js b/src/composables/usePersianDate.js index a4c890e..ede3491 100644 --- a/src/composables/usePersianDate.js +++ b/src/composables/usePersianDate.js @@ -55,6 +55,10 @@ export function usePersianDate() { const jalaliToIso = (jalaliStr) => toGregorianIso(jalaliStr) || null; + const getTodayJalali = (formatStr = 'jYYYY/jMM/jDD') => { + return moment().locale('fa').format(formatStr); + }; + return { toPersianDigits, toLatinDigits, @@ -62,6 +66,8 @@ export function usePersianDate() { formatJalaliWithTime, toJalaliPickerValue, toGregorianIso, - jalaliToIso + jalaliToIso, + getTodayJalali }; } + diff --git a/src/utils/classSchedule.js b/src/utils/classSchedule.js index 177608f..f106d1e 100644 --- a/src/utils/classSchedule.js +++ b/src/utils/classSchedule.js @@ -82,3 +82,38 @@ export function calculateClassEndDate(startDate, days = [], numberOfSessions = 0 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 until the 5th session. + * @param {Object} cls - Class object + * @returns {string} Jalali formatted date string, or today's date if not calculable + */ +export function calculateClassMidDate(cls) { + 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 = moment(cls.startDate); + const endM = moment(cls.endDate); + if (startM.isValid() && 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 = moment(cls.startDate); + if (startM.isValid()) return startM.locale('fa').format('jYYYY/jMM/jDD'); + } + + return moment().locale('fa').format('jYYYY/jMM/jDD'); +} + diff --git a/src/utils/classSchedule.test.js b/src/utils/classSchedule.test.js index efd2f8d..275bd85 100644 --- a/src/utils/classSchedule.test.js +++ b/src/utils/classSchedule.test.js @@ -1,6 +1,6 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; -import { formatClassDays, formatClassTime, formatClassSchedule, calculateClassEndDate } from './classSchedule.js'; +import { formatClassDays, formatClassTime, formatClassSchedule, calculateClassEndDate, calculateClassMidDate } from './classSchedule.js'; describe('class schedule display', () => { it('formats weekdays in Iran week order', () => { @@ -28,4 +28,21 @@ describe('class schedule display', () => { const endDate = calculateClassEndDate('1403/01/01', [6, 2], 2); assert.equal(endDate, '1403/01/07'); }); + + it('calculates class mid date (halfway through class sessions)', () => { + // For 10 sessions on [6, 2] starting 1403/01/01: + // Half is 5th session + const midDate = calculateClassMidDate({ + startDate: '1403/01/01', + days: [6, 2], + numberOfSessions: 10 + }); + // Session 1: 1403/01/04 (Sat) + // Session 2: 1403/01/07 (Tue) + // Session 3: 1403/01/11 (Sat) + // Session 4: 1403/01/14 (Tue) + // Session 5: 1403/01/18 (Sat) + assert.equal(midDate, '1403/01/18'); + }); }); + diff --git a/src/views/classes/ClassFormView.vue b/src/views/classes/ClassFormView.vue index 7615199..49ca007 100644 --- a/src/views/classes/ClassFormView.vue +++ b/src/views/classes/ClassFormView.vue @@ -117,7 +117,7 @@
- +
@@ -324,7 +324,7 @@ const route = useRoute(); const router = useRouter(); const { showSuccess, showError } = useToast(); const { hasPermission } = usePermission(); -const { toPersianDigits, toLatinDigits } = usePersianDate(); +const { toPersianDigits, toLatinDigits, getTodayJalali } = usePersianDate(); const classId = route.params.id; const queryCourseId = route.query.courseId ? String(route.query.courseId) : null; @@ -354,9 +354,10 @@ const form = reactive({ discount: 0, freeSpots: null, showOnFrontend: true, - startDate: '', + startDate: isEditMode.value ? '' : getTodayJalali(), endDate: '', days: [], + startTime: '', endTime: '', numberOfSessions: null, diff --git a/src/views/expenses/ExpenseListView.vue b/src/views/expenses/ExpenseListView.vue index b8ddb69..fb7cd19 100644 --- a/src/views/expenses/ExpenseListView.vue +++ b/src/views/expenses/ExpenseListView.vue @@ -84,7 +84,7 @@
- +
@@ -126,7 +126,8 @@ import InputNumber from 'primevue/inputnumber'; import Textarea from 'primevue/textarea'; import DatePicker from 'vue3-persian-datetime-picker'; -const { toPersianDigits, formatJalali, toGregorianIso, toJalaliPickerValue } = usePersianDate(); +const { toPersianDigits, formatJalali, toGregorianIso, toJalaliPickerValue, getTodayJalali } = usePersianDate(); + const { showSuccess, showError } = useToast(); const { diff --git a/src/views/financialReports/FinancialReportsView.vue b/src/views/financialReports/FinancialReportsView.vue index 090affe..a16f99b 100644 --- a/src/views/financialReports/FinancialReportsView.vue +++ b/src/views/financialReports/FinancialReportsView.vue @@ -171,11 +171,11 @@
- +
- +
- +
- +
@@ -185,6 +185,7 @@ placeholder="یادداشت داخلی درباره این تراکنش…" />
+