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'; + + +
+ + + +
} @@ -84,21 +108,56 @@ import { WorkContextService } from '../../../features/work-context/work-context. } } + .page-title-actions { + display: flex; + align-items: center; + gap: var(--s-quarter); + margin-left: calc(-1 * var(--s)); + margin-right: var(--s2); + } + .project-settings-btn { - display: none; - @media (min-width: 600px) { + opacity: 1; + + /*display: none;*/ + /*@media (min-width: 600px) {*/ + /* display: block;*/ + /* transition: var(--transition-standard);*/ + /* opacity: 0;*/ + /* position: relative;*/ + /* z-index: 1;*/ + /*}*/ + + /*&:hover,*/ + /*.page-title:hover + .page-title-actions &,*/ + /*.page-title-actions:hover & {*/ + /* opacity: 1;*/ + /*}*/ + } + + .task-filter-btn { + position: relative; + transition: all 0.2s ease; + overflow: visible !important; + + .mat-icon { + transition: transform 0.2s ease; display: block; - transition: var(--transition-standard); - opacity: 0; - margin-right: var(--s2); - margin-left: calc(-1 * var(--s)); - position: relative; - z-index: 1; } - &:hover, - .page-title:hover + & { - opacity: 1; + &.isCustomized { + color: var(--c-accent); + box-shadow: none; + } + + &:hover:not(.isCustomized):not(:disabled) { + background-color: var(--hover-color, rgba(0, 0, 0, 0.04)); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + background: transparent !important; } } `, @@ -109,6 +168,8 @@ export class PageTitleComponent { private _breakpointObserver = inject(BreakpointObserver); private _router = inject(Router); private _workContextService = inject(WorkContextService); + readonly taskViewCustomizerService = inject(TaskViewCustomizerService); + private readonly _configService = inject(GlobalConfigService); readonly T = T; @@ -140,6 +201,13 @@ export class PageTitleComponent { ); isBoardsSection = toSignal(this._isBoardsSection$, { initialValue: false }); + private _isWorkViewPage$ = this._router.events.pipe( + filter((event): event is NavigationEnd => event instanceof NavigationEnd), + map((event) => !!event.urlAfterRedirects.match(/tasks$/)), + startWith(!!this._router.url.match(/tasks$/)), + ); + isWorkViewPage = toSignal(this._isWorkViewPage$, { initialValue: false }); + // Override title for special routes displayTitle = computed(() => { if (this.isScheduleSection()) { @@ -158,4 +226,8 @@ export class PageTitleComponent { isXxxs = toSignal(this._isXxxs$.pipe(map((result) => result.matches)), { initialValue: false, }); + + get kb(): KeyboardConfig { + return (this._configService.cfg()?.keyboard as KeyboardConfig) || {}; + } } diff --git a/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html b/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html index c897a34289..64c4762af9 100644 --- a/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html +++ b/src/app/core-ui/mobile-bottom-nav/mobile-bottom-nav.component.html @@ -62,17 +62,6 @@ } - - + + + + + + + + + + + -
+ + + + + @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; + } +}