diff --git a/src/app/core-ui/main-header/desktop-panel-buttons/desktop-panel-buttons.component.ts b/src/app/core-ui/main-header/desktop-panel-buttons/desktop-panel-buttons.component.ts
index a7cddb8790..3e23e33e17 100644
--- a/src/app/core-ui/main-header/desktop-panel-buttons/desktop-panel-buttons.component.ts
+++ b/src/app/core-ui/main-header/desktop-panel-buttons/desktop-panel-buttons.component.ts
@@ -4,9 +4,9 @@ import { MatIcon } from '@angular/material/icon';
import { MatTooltip } from '@angular/material/tooltip';
import { TranslatePipe } from '@ngx-translate/core';
import { LayoutService } from '../../layout/layout.service';
-import { TaskViewCustomizerService } from '../../../features/task-view-customizer/task-view-customizer.service';
import { T } from '../../../t.const';
import { KeyboardConfig } from '../../../features/config/keyboard-config.model';
+import { TaskViewCustomizerService } from '../../../features/task-view-customizer/task-view-customizer.service';
@Component({
selector: 'desktop-panel-buttons',
diff --git a/src/app/core-ui/main-header/main-header.component.ts b/src/app/core-ui/main-header/main-header.component.ts
index b575608368..a0aa973951 100644
--- a/src/app/core-ui/main-header/main-header.component.ts
+++ b/src/app/core-ui/main-header/main-header.component.ts
@@ -145,12 +145,12 @@ export class MainHeaderComponent implements OnDestroy {
enabledSimpleCounters = toSignal(this.simpleCounterService.enabledSimpleCounters$, {
initialValue: [],
});
- isShowTaskViewCustomizerPanel = computed(() =>
- this.layoutService.isShowTaskViewCustomizerPanel(),
- );
isShowIssuePanel = computed(() => this.layoutService.isShowIssuePanel());
isShowNotes = computed(() => this.layoutService.isShowNotes());
isShowScheduleDayPanel = computed(() => this.layoutService.isShowScheduleDayPanel());
+ isShowTaskViewCustomizerPanel = computed(() =>
+ this.layoutService.isShowTaskViewCustomizerPanel(),
+ );
syncIsEnabledAndReady = toSignal(this.syncWrapperService.isEnabledAndReady$);
syncState = toSignal(this.syncWrapperService.syncState$);
isSyncInProgress = toSignal(this.syncWrapperService.isSyncInProgress$);
diff --git a/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts b/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts
index 37d944e759..906dccc313 100644
--- a/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts
+++ b/src/app/core-ui/main-header/mobile-side-panel-menu/mobile-side-panel-menu.component.ts
@@ -3,9 +3,11 @@ import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
+import { MatMenuModule } from '@angular/material/menu';
import { TranslateModule } from '@ngx-translate/core';
import { LayoutService } from '../../layout/layout.service';
import { TaskViewCustomizerService } from '../../../features/task-view-customizer/task-view-customizer.service';
+import { TaskViewCustomizerPanelComponent } from '../../../features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component';
import { T } from '../../../t.const';
import { KeyboardConfig } from '../../../features/config/keyboard-config.model';
import { GlobalConfigService } from '../../../features/config/global-config.service';
@@ -31,8 +33,10 @@ import { BreakpointObserver } from '@angular/cdk/layout';
MatIconModule,
MatButtonModule,
MatTooltipModule,
+ MatMenuModule,
TranslateModule,
PluginIconComponent,
+ TaskViewCustomizerPanelComponent,
],
template: `
@@ -68,15 +72,16 @@ import { BreakpointObserver } from '@angular/cdk/layout';
+
+
}
-
- filter_list
- {{ T.BN.SHOW_TASK_VIEW_CUSTOMIZER_PANEL | translate }}
-
-
();
@@ -88,20 +85,9 @@ export class MobileBottomNavComponent {
{ initialValue: true },
);
- readonly isWorkViewPage = toSignal(
- this._router.events.pipe(
- filter((event): event is NavigationEnd => event instanceof NavigationEnd),
- map((event) => !!event.urlAfterRedirects.match(/tasks$/)),
- startWith(!!this._router.url.match(/tasks$/)),
- ),
- { initialValue: !!this._router.url.match(/tasks$/) },
- );
-
// Panel state signals from layout service
readonly isShowNotes = this._layoutService.isShowNotes;
readonly isShowIssuePanel = this._layoutService.isShowIssuePanel;
- readonly isShowTaskViewCustomizerPanel =
- this._layoutService.isShowTaskViewCustomizerPanel;
// Navigation methods
showAddTaskBar(): void {
@@ -126,10 +112,6 @@ export class MobileBottomNavComponent {
}
}
- toggleTaskViewCustomizer(): void {
- this._layoutService.toggleTaskViewCustomizerPanel();
- }
-
toggleIssuePanel(): void {
this._layoutService.toggleAddTaskPanel();
}
diff --git a/src/app/features/right-panel/right-panel-content.component.html b/src/app/features/right-panel/right-panel-content.component.html
index 91b481f3de..330d2e4afb 100644
--- a/src/app/features/right-panel/right-panel-content.component.html
+++ b/src/app/features/right-panel/right-panel-content.component.html
@@ -2,14 +2,6 @@
} @else if (panelContent() === 'NOTES') {
-} @else if (panelContent() === 'TASK_VIEW_CUSTOMIZER_PANEL') {
-
-
-
-
} @else if (panelContent() === 'PLUGIN') {
@for (key of pluginPanelKeys(); track key) {
diff --git a/src/app/features/right-panel/right-panel-content.component.ts b/src/app/features/right-panel/right-panel-content.component.ts
index be24c1b9ed..9a86cfd636 100644
--- a/src/app/features/right-panel/right-panel-content.component.ts
+++ b/src/app/features/right-panel/right-panel-content.component.ts
@@ -18,7 +18,6 @@ import { taskDetailPanelTaskChangeAnimation } from '../tasks/task-detail-panel/t
import { IssuePanelComponent } from '../issue-panel/issue-panel.component';
import { NotesComponent } from '../note/notes/notes.component';
import { TaskDetailPanelComponent } from '../tasks/task-detail-panel/task-detail-panel.component';
-import { TaskViewCustomizerPanelComponent } from '../task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component';
import { PluginService } from '../../plugins/plugin.service';
import { PluginPanelContainerComponent } from '../../plugins/ui/plugin-panel-container/plugin-panel-container.component';
import { ScheduleDayPanelComponent } from '../schedule/schedule-day-panel/schedule-day-panel.component';
@@ -56,7 +55,6 @@ export type RightPanelContentPanelType = PanelContentType;
IssuePanelComponent,
NotesComponent,
TaskDetailPanelComponent,
- TaskViewCustomizerPanelComponent,
PluginPanelContainerComponent,
ScheduleDayPanelComponent,
],
diff --git a/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.html b/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.html
index 70dc262db5..47a00f174a 100644
--- a/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.html
+++ b/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.html
@@ -1,113 +1,288 @@
-
-
{{ T.F.TASK_VIEW.CUSTOMIZER.TITLE | translate }}
-
-
-
-
- @for (opt of sortOptions; track opt.value) {
- {{ opt.label | translate }}
- }
-
-
-
-
-
-
-
-
- @for (opt of groupOptions; track opt.value) {
- {{ opt.label | translate }}
- }
-
-
-
-
-
-
-
-
- @for (opt of filterOptions; track opt.value) {
- {{ opt.label | translate }}
- }
-
-
-
- @if (customizerService.selectedFilter() === 'tag') {
-
-
-
- }
-
- @if (customizerService.selectedFilter() === 'project') {
-
-
-
- }
-
- @if (customizerService.selectedFilter() === 'scheduledDate') {
-
-
- @for (option of scheduledPresets; track option.value) {
- {{ option.label | translate }}
- }
-
-
- }
-
- @if (customizerService.selectedFilter() === 'estimatedTime') {
-
-
- @for (option of timePresets; track option.value) {
- {{ option.label | translate }}
- }
-
-
- }
-
- @if (customizerService.selectedFilter() === 'timeSpent') {
-
-
- @for (option of timePresets; track option.value) {
- {{ option.label | translate }}
- }
-
-
- }
-
-
+
+
+
+
+
+ @for (opt of sortOptions; track opt.value) {
+
+
+
+ }
+
+
+
+
+ @for (opt of groupOptions; track opt.value) {
+
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @for (option of scheduledPresets; track option.value) {
+
+
+
+ }
+
+
+
+
+ @for (option of timePresets; track option.value) {
+
+
+
+ }
+
+
+
+
+ @for (option of timePresets; track option.value) {
+
+
+
+ }
+
diff --git a/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.scss b/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.scss
index c6cc0d9150..092c645048 100644
--- a/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.scss
+++ b/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.scss
@@ -1,33 +1,11 @@
-.customizer-panel {
- // Align layout with other right-panel contents (e.g. issue-panel)
- // Fill the container and let the parent handle scrolling
- position: absolute;
- inset: 8px 16px;
- padding: 8px 0;
- display: flex;
- flex-direction: column;
- height: 100%;
+// Menu styles are in global styles: src/styles/components/_customizer-menu.scss
+// This is necessary because mat-menu renders in a global overlay container
- h3 {
- text-align: center;
- margin-bottom: 20px;
- }
+.menu-input-wrapper {
+ padding: 8px 16px;
+ min-width: 250px;
- .form-group {
- margin-bottom: 15px;
- display: flex;
- flex-direction: column;
-
- label {
- margin-bottom: 5px;
- }
-
- mat-form-field {
- width: 100%;
- }
-
- mat-slide-toggle {
- align-self: flex-start;
- }
+ .menu-input {
+ width: 100%;
}
}
diff --git a/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.ts b/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.ts
index af1594eef4..13d42590fc 100644
--- a/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.ts
+++ b/src/app/features/task-view-customizer/task-view-customizer-panel/task-view-customizer-panel.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core';
import { OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@@ -7,6 +7,9 @@ import { MatSelectModule } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatMenuModule, MatMenu } from '@angular/material/menu';
+import { MatIconModule } from '@angular/material/icon';
+import { MatDividerModule } from '@angular/material/divider';
import { TaskViewCustomizerService } from '../task-view-customizer.service';
import { TranslatePipe } from '@ngx-translate/core';
import { T } from 'src/app/t.const';
@@ -17,6 +20,7 @@ import { T } from 'src/app/t.const';
styleUrls: ['./task-view-customizer-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
+ exportAs: 'customizerMenu',
imports: [
CommonModule,
FormsModule,
@@ -25,12 +29,18 @@ import { T } from 'src/app/t.const';
MatInputModule,
MatButtonModule,
MatSlideToggleModule,
+ MatMenuModule,
+ MatIconModule,
+ MatDividerModule,
TranslatePipe,
],
})
export class TaskViewCustomizerPanelComponent implements OnInit {
customizerService = inject(TaskViewCustomizerService);
+ @ViewChild('customizerMenu', { static: false })
+ menu!: MatMenu;
+
T = T;
selectedSort: string = 'default';
selectedGroup: string = 'default';
@@ -89,6 +99,37 @@ export class TaskViewCustomizerPanelComponent implements OnInit {
this.filterInputValue = this.customizerService.filterInputValue();
}
+ getSortLabel(value: string): string {
+ const option = this.sortOptions.find((opt) => opt.value === value);
+ return option ? option.label : '';
+ }
+
+ getGroupLabel(value: string): string {
+ const option = this.groupOptions.find((opt) => opt.value === value);
+ return option ? option.label : '';
+ }
+
+ getFilterLabel(value: string): string {
+ const option = this.filterOptions.find((opt) => opt.value === value);
+ return option ? option.label : '';
+ }
+
+ onFilterSelect(filterType: string): void {
+ this.customizerService.setFilter(filterType);
+ }
+
+ onFilterInputChange(filterType: string, value: string): void {
+ if (this.customizerService.selectedFilter() !== filterType) {
+ this.customizerService.setFilter(filterType);
+ }
+ this.customizerService.setFilterInputValue(value);
+ }
+
+ onFilterWithValue(filterType: string, value: string): void {
+ this.customizerService.setFilter(filterType);
+ this.customizerService.setFilterInputValue(value);
+ }
+
onResetAll(): void {
this.customizerService.resetAll();
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 501ded5c80..a666e264a4 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -1465,8 +1465,8 @@
},
"TASK_VIEW": {
"CUSTOMIZER": {
- "ENTER_PROJECT": "Enter project",
- "ENTER_TAG": "Enter tag",
+ "ENTER_PROJECT": "Filter Projects",
+ "ENTER_TAG": "Filter Tag",
"ESTIMATED_TIME": "Estimated Time",
"FILTER_BY": "Filter By",
"FILTER_DEFAULT": "No Filter",
diff --git a/src/styles/components/_components.scss b/src/styles/components/_components.scss
index d4d19e7ea5..e766e64843 100644
--- a/src/styles/components/_components.scss
+++ b/src/styles/components/_components.scss
@@ -16,5 +16,6 @@
@use './planner-shared';
@use './mentions';
@use './bottom-panel';
+@use './customizer-menu';
//@import '../../app/ui/custom-datetime-picker/sass/picker';
diff --git a/src/styles/components/_customizer-menu.scss b/src/styles/components/_customizer-menu.scss
new file mode 100644
index 0000000000..1c5e9a753f
--- /dev/null
+++ b/src/styles/components/_customizer-menu.scss
@@ -0,0 +1,41 @@
+// TASK VIEW CUSTOMIZER MENU
+// Styles for the task view customizer menu component
+// Menu renders in global overlay, so styles must be global
+
+.menu-item-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ gap: 16px;
+
+ > span:first-child {
+ flex: 1;
+ }
+
+ .check-icon {
+ flex-shrink: 0;
+ margin-left: auto;
+ margin-right: 0 !important;
+ }
+}
+
+.customizer-menu {
+ .mat-mdc-menu-item {
+ &.active {
+ background-color: rgba(0, 0, 0, 0.08);
+ font-weight: 500;
+ }
+ }
+
+ .current-value {
+ padding-left: 16px;
+ font-size: 0.875em;
+ opacity: 0.7;
+ font-style: italic;
+ }
+
+ mat-icon:first-child:not(.check-icon) {
+ margin-right: 8px;
+ }
+}