From 11f8786af0ce6ce9be32bea489c8c11cef448fdf Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Mon, 26 Oct 2026 16:12:38 +0600 Subject: [PATCH 1/6] fix(dashboard): handle period filtering in lazy sections and default to today --- .../src/js/frontend/dashboard/lazy-section.ts | 18 +++++++++++++----- classes/DashboardSectionManager.php | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/assets/src/js/frontend/dashboard/lazy-section.ts b/assets/src/js/frontend/dashboard/lazy-section.ts index 24bcf1be81..5c215ea0e0 100644 --- a/assets/src/js/frontend/dashboard/lazy-section.ts +++ b/assets/src/js/frontend/dashboard/lazy-section.ts @@ -39,6 +39,7 @@ export interface LazySectionState { section: string; dateDependent: boolean; sortDependent: boolean; + period: string; startDate: string; endDate: string; sortType: string; @@ -87,6 +88,7 @@ export const lazySection = ({ section, dateDependent, sortDependent, + period: 'today', startDate: '', endDate: '', sortType: type, @@ -141,7 +143,7 @@ export const lazySection = ({ return; } - const currentKey = `${this.startDate}_${this.endDate}_${this.sortType}`; + const currentKey = `${this.period}_${this.startDate}_${this.endDate}_${this.sortType}`; if (this.lastFetchedKey !== currentKey || !this.query?.data) { this.lastFetchedKey = currentKey; this.fetchSection(); @@ -152,6 +154,7 @@ export const lazySection = ({ const params = new URLSearchParams(window.location.search); this.startDate = params.get('start_date') || ''; this.endDate = params.get('end_date') || ''; + this.period = this.startDate && this.endDate ? '' : params.get('period') || 'today'; this.sortType = params.get('top_performing_course') || params.get('type') || type; // Watch for content updates from QueryService and re-initialize Alpine child tree @@ -187,15 +190,19 @@ export const lazySection = ({ if (this.dateDependent) { this.dateChangeHandler = (e: Event) => { const detail = - e instanceof CustomEvent ? (e.detail as { startDate?: string; endDate?: string } | undefined) : undefined; + e instanceof CustomEvent + ? (e.detail as { period?: string; startDate?: string; endDate?: string } | undefined) + : undefined; const newStart = detail?.startDate || ''; const newEnd = detail?.endDate || ''; + const newPeriod = detail?.period || (newStart && newEnd ? '' : 'today'); - // Guard: Avoid refetch if the filter dates have not changed - if (this.startDate === newStart && this.endDate === newEnd) { + // Guard: Avoid refetch if neither the period nor the date range has changed + if (this.period === newPeriod && this.startDate === newStart && this.endDate === newEnd) { return; } + this.period = newPeriod; this.startDate = newStart; this.endDate = newEnd; @@ -238,7 +245,7 @@ export const lazySection = ({ }, fetchSection(this: LazySectionContext) { - const queryKey = ['dashboard-section', this.section, this.startDate, this.endDate, this.sortType]; + const queryKey = ['dashboard-section', this.section, this.period, this.startDate, this.endDate, this.sortType]; const queryService = window.TutorCore.query; if (queryService) { @@ -247,6 +254,7 @@ export const lazySection = ({ () => wpPost>('tutor_get_dashboard_section', { section: this.section, + period: this.period, start_date: this.startDate, end_date: this.endDate, top_performing_course: this.sortType, diff --git a/classes/DashboardSectionManager.php b/classes/DashboardSectionManager.php index 37bb757a8c..22efb7030e 100644 --- a/classes/DashboardSectionManager.php +++ b/classes/DashboardSectionManager.php @@ -225,6 +225,7 @@ public function ajax_get_dashboard_section() { $user_id = get_current_user_id(); $start_date = Input::post( 'start_date', '', Input::TYPE_STRING ); $end_date = Input::post( 'end_date', '', Input::TYPE_STRING ); + $period = Input::post( 'period', '', Input::TYPE_STRING ); $type = Input::post( 'top_performing_course', Input::post( 'type', 'revenue', Input::TYPE_STRING ), Input::TYPE_STRING ); $type = in_array( $type, array( 'revenue', 'student' ), true ) ? $type : 'revenue'; @@ -233,6 +234,7 @@ public function ajax_get_dashboard_section() { $params = array( 'user_id' => $user_id, + 'period' => $period, 'start_date' => $start_date, 'end_date' => $end_date, 'top_performing_course' => $type, From 361a4c78ed9645df7be9446670f88454d9aa9f58 Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Tue, 27 Oct 2026 00:06:54 +0600 Subject: [PATCH 2/6] fix(calendar): support default preset option and sync active preset on date filter change --- assets/core/ts/components/calendar.ts | 25 ++++++++++++++++++- components/DateFilter.php | 35 ++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/assets/core/ts/components/calendar.ts b/assets/core/ts/components/calendar.ts index af1b9602b3..0391bda345 100644 --- a/assets/core/ts/components/calendar.ts +++ b/assets/core/ts/components/calendar.ts @@ -98,6 +98,7 @@ export function calendar({ options, hidePopover }: { options: Options; hidePopov cleanupCalendarRootListeners: null as (() => void) | null, calendarClearHandler: null as (() => void) | null, popStateHandler: null as (() => void) | null, + dateFilterChangedHandler: null as ((e: Event) => void) | null, parseInputValue(inputValue: string): { selectedDates: string[]; selectedTime: string } { const normalizedValue = inputValue.trim(); @@ -382,8 +383,22 @@ export function calendar({ options, hidePopover }: { options: Options; hidePopov this.popStateHandler = () => this.updateActivePreset(); this.calendarClearHandler = () => this.clear(); + this.dateFilterChangedHandler = (e: Event) => { + const detail = (e as CustomEvent)?.detail; + if (!detail) return; + + const startDate = detail.startDate || ''; + const endDate = detail.endDate || ''; + const date = detail.date || ''; + + const selectedDates = startDate && endDate ? [startDate, endDate] : date ? [date] : []; + this.calendar?.set({ selectedDates }); + this.updateActivePreset(); + }; + window.addEventListener(TUTOR_CALENDAR_EVENTS.popstate, this.popStateHandler); window.addEventListener(TUTOR_CALENDAR_EVENTS.calendarClear, this.calendarClearHandler); + window.addEventListener(TUTOR_CUSTOM_EVENTS.DATE_FILTER_CHANGED, this.dateFilterChangedHandler); }); }, @@ -403,6 +418,11 @@ export function calendar({ options, hidePopover }: { options: Options; hidePopov window.removeEventListener(TUTOR_CALENDAR_EVENTS.calendarClear, this.calendarClearHandler); this.calendarClearHandler = null; } + + if (this.dateFilterChangedHandler) { + window.removeEventListener(TUTOR_CUSTOM_EVENTS.DATE_FILTER_CHANGED, this.dateFilterChangedHandler); + this.dateFilterChangedHandler = null; + } }, handleActionClick(e: Event) { @@ -628,7 +648,10 @@ export function calendar({ options, hidePopover }: { options: Options; hidePopov let activePreset: Preset | '' = ''; if (!startDate && !endDate) { - activePreset = PRESETS.ALL_TIME; + const defaultPreset = (options as Record)?.defaultPreset as Preset | undefined; + if (defaultPreset && (Object.values(PRESETS) as string[]).includes(defaultPreset)) { + activePreset = defaultPreset; + } } else if (startDate && endDate) { const presets = (Object.values(PRESETS) as Preset[]).filter((key) => key !== PRESETS.ALL_TIME); diff --git a/components/DateFilter.php b/components/DateFilter.php index e2b5c230ad..fa569d192e 100644 --- a/components/DateFilter.php +++ b/components/DateFilter.php @@ -163,6 +163,15 @@ class DateFilter extends BaseComponent { */ protected $clear_params = array(); + /** + * Default preset to activate when no date range is selected. + * + * @since 4.0.0 + * + * @var string|null + */ + protected $default_preset = null; + /** * Whether to operate in AJAX mode (dispatch event instead of full page reload). * @@ -296,6 +305,20 @@ public function clear_params( array $params ): self { return $this; } + /** + * Set default preset when no date range is selected. + * + * @since 4.2.1 + * + * @param string|null $preset Preset key (e.g. 'all-time'). + * + * @return self + */ + public function default_preset( ?string $preset = 'all-time' ): self { + $this->default_preset = $preset; + return $this; + } + /** * Render the component. * @@ -308,11 +331,14 @@ public function get(): string { $this->label = $this->calculate_label(); } + $default_preset = $this->default_preset ?? ( $this->hide_initial_label ? null : 'all-time' ); + // Default settings based on type. $calendar_options = array( - 'type' => 'default', - 'clearParams' => $this->clear_params, - 'ajaxMode' => $this->ajax_mode, + 'type' => 'default', + 'clearParams' => $this->clear_params, + 'ajaxMode' => $this->ajax_mode, + 'defaultPreset' => $default_preset, ); $button_classes = 'tutor-btn tutor-btn-outline'; @@ -330,6 +356,7 @@ public function get(): string { 'selectionDatesMode' => 'multiple-ranged', 'clearParams' => $this->clear_params, 'ajaxMode' => $this->ajax_mode, + 'defaultPreset' => $default_preset, ); $popover_classes .= ' tutor-range-calendar-popover'; } @@ -358,7 +385,7 @@ public function get(): string { ajax_mode ) : ?> @tutor:date-filter-changed.window=" hasSelection = Boolean( $event.detail.startDate || $event.detail.endDate || $event.detail.date ); - label = $event.detail.label || hide_initial_label ? '' : __( 'All Time', 'tutor' ) ) ); ?>; + label = hasSelection ? ( $event.detail.label || '' ) : hide_initial_label ? '' : __( 'All Time', 'tutor' ) ) ); ?>; " get_attributes_string(); //phpcs:ignore -- Sanitization is performed inside get_attributes_string. ?> From c18840d4bb2a1ae923fff08dc5cedf7a549f3cfc Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Tue, 27 Oct 2026 00:27:28 +0600 Subject: [PATCH 3/6] fix(calendar): support all-time preset selection and event period forwarding --- assets/core/ts/components/calendar.ts | 9 ++++++++- components/DateFilter.php | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/assets/core/ts/components/calendar.ts b/assets/core/ts/components/calendar.ts index 0391bda345..64c0646243 100644 --- a/assets/core/ts/components/calendar.ts +++ b/assets/core/ts/components/calendar.ts @@ -545,12 +545,15 @@ export function calendar({ options, hidePopover }: { options: Options; hidePopov presetTitle || (startDate && endDate ? (startDate === endDate ? startDate : `${startDate} - ${endDate}`) : date || ''); + const period = params.period ?? url.searchParams.get('period') ?? ''; + window.dispatchEvent( new CustomEvent(TUTOR_CUSTOM_EVENTS.DATE_FILTER_CHANGED, { detail: { startDate, endDate, date, + period, label: formattedLabel, preset: presetKey || '', presetTitle: presetTitle || '', @@ -631,6 +634,7 @@ export function calendar({ options, hidePopover }: { options: Options; hidePopov { [TUTOR_CALENDAR_QUERY_PARAMS.startDate]: null, [TUTOR_CALENDAR_QUERY_PARAMS.endDate]: null, + ...(preset === PRESETS.ALL_TIME ? { period: PRESETS.ALL_TIME } : {}), }, preset, presetTitle, @@ -648,8 +652,11 @@ export function calendar({ options, hidePopover }: { options: Options; hidePopov let activePreset: Preset | '' = ''; if (!startDate && !endDate) { + const urlPeriod = url.searchParams.get('period'); const defaultPreset = (options as Record)?.defaultPreset as Preset | undefined; - if (defaultPreset && (Object.values(PRESETS) as string[]).includes(defaultPreset)) { + if (urlPeriod === PRESETS.ALL_TIME) { + activePreset = PRESETS.ALL_TIME; + } else if (defaultPreset && (Object.values(PRESETS) as string[]).includes(defaultPreset)) { activePreset = defaultPreset; } } else if (startDate && endDate) { diff --git a/components/DateFilter.php b/components/DateFilter.php index fa569d192e..5a90f6cbd2 100644 --- a/components/DateFilter.php +++ b/components/DateFilter.php @@ -384,7 +384,7 @@ public function get(): string { }" ajax_mode ) : ?> @tutor:date-filter-changed.window=" - hasSelection = Boolean( $event.detail.startDate || $event.detail.endDate || $event.detail.date ); + hasSelection = Boolean( $event.detail.startDate || $event.detail.endDate || $event.detail.date || $event.detail.period === 'all-time' ); label = hasSelection ? ( $event.detail.label || '' ) : hide_initial_label ? '' : __( 'All Time', 'tutor' ) ) ); ?>; " @@ -433,7 +433,7 @@ protected function has_selection(): bool { if ( self::TYPE_SINGLE === $this->type ) { return ! empty( Input::get( 'date' ) ); } - return ! empty( Input::get( 'start_date' ) ) && ! empty( Input::get( 'end_date' ) ); + return ( ! empty( Input::get( 'start_date' ) ) && ! empty( Input::get( 'end_date' ) ) ) || 'all-time' === Input::get( 'period' ); } /** @@ -452,6 +452,11 @@ protected function calculate_label(): string { $start_date = Input::get( 'start_date' ); $end_date = Input::get( 'end_date' ); + $period = Input::get( 'period' ); + + if ( 'all-time' === $period ) { + return __( 'All Time', 'tutor' ); + } if ( ! $start_date || ! $end_date ) { return $this->hide_initial_label ? '' : __( 'All Time', 'tutor' ); From bea840d3a2dc24d11887979c2a6bfcfc96a9bc4b Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Tue, 27 Oct 2026 13:23:50 +0600 Subject: [PATCH 4/6] fix(dashboard): prevent layout shift on top performing courses sort filter --- .../scss/frontend/dashboard/pages/_instructor-dashboard.scss | 2 ++ components/Skeleton.php | 2 +- templates/dashboard/instructor/home.php | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/src/scss/frontend/dashboard/pages/_instructor-dashboard.scss b/assets/src/scss/frontend/dashboard/pages/_instructor-dashboard.scss index dc5f6a9f3a..80d17a9926 100644 --- a/assets/src/scss/frontend/dashboard/pages/_instructor-dashboard.scss +++ b/assets/src/scss/frontend/dashboard/pages/_instructor-dashboard.scss @@ -158,6 +158,7 @@ @include tutor-transition(background-color); width: 100%; padding: $tutor-spacing-4 $tutor-spacing-5; + min-height: 58px; &:hover { background-color: $tutor-surface-base; @@ -291,6 +292,7 @@ [data-section-id='top_performing_courses'] { view-transition-name: top-performing-courses; + min-height: 346px; } [data-section-id='upcoming_tasks_and_activity'] { diff --git a/components/Skeleton.php b/components/Skeleton.php index d4d93a83e5..1b2a9a137e 100644 --- a/components/Skeleton.php +++ b/components/Skeleton.php @@ -418,7 +418,7 @@ protected function render_top_courses(): void {
- +
count; $i++ ) : ?> diff --git a/templates/dashboard/instructor/home.php b/templates/dashboard/instructor/home.php index a0913b3696..5650e3cf17 100644 --- a/templates/dashboard/instructor/home.php +++ b/templates/dashboard/instructor/home.php @@ -204,7 +204,7 @@ class="tutor-popover-menu-item" })" > -
+
type( $section['skeleton'] ) From bdcca9f27b46b4126b29c8d89204072b403be767 Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Tue, 27 Oct 2026 15:34:51 +0600 Subject: [PATCH 5/6] fix(dashboard): show skeleton pre-js by removing x-cloak --- templates/dashboard/instructor/home.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/dashboard/instructor/home.php b/templates/dashboard/instructor/home.php index 5650e3cf17..a0913b3696 100644 --- a/templates/dashboard/instructor/home.php +++ b/templates/dashboard/instructor/home.php @@ -204,7 +204,7 @@ class="tutor-popover-menu-item" })" > -
+
type( $section['skeleton'] ) From 5f7e67ffbdbe2ce7b12daa010a452eec4f7b508d Mon Sep 17 00:00:00 2001 From: b-l-i-n-d Date: Tue, 27 Oct 2026 15:38:19 +0600 Subject: [PATCH 6/6] refactor: Update since tag --- components/DateFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DateFilter.php b/components/DateFilter.php index 5a90f6cbd2..7f0a8fe076 100644 --- a/components/DateFilter.php +++ b/components/DateFilter.php @@ -166,7 +166,7 @@ class DateFilter extends BaseComponent { /** * Default preset to activate when no date range is selected. * - * @since 4.0.0 + * @since 4.2.1 * * @var string|null */