refactor(simple-counter): move habit management from config page to habits page

Add delete button to habit settings dialog with confirmation. Add
collapsible disabled habits section to the habits page with enable,
edit, and delete actions. Remove simple counter section from global
config page and delete the unused SimpleCounterCfgComponent.
This commit is contained in:
Johannes Millan 2026-02-13 15:30:09 +01:00
parent 84062bde2b
commit 41027f3099
12 changed files with 174 additions and 31 deletions

View file

@ -1,5 +1,4 @@
import { FileImexComponent } from '../../imex/file-imex/file-imex.component';
import { SimpleCounterCfgComponent } from '../simple-counter/simple-counter-cfg/simple-counter-cfg.component';
import { CustomCfgSection } from './global-config.model';
import { ClickUpAdditionalCfgComponent } from '../issue/providers/clickup/clickup-view-components/clickup-cfg/clickup-additional-cfg.component';
import { ClipboardImagesCfgComponent } from './clipboard-images-cfg/clipboard-images-cfg.component';
@ -12,9 +11,6 @@ export const customConfigFormSectionComponent = (
case 'FILE_IMPORT_EXPORT':
return FileImexComponent;
case 'SIMPLE_COUNTER_CFG':
return SimpleCounterCfgComponent;
case 'CLICKUP_CFG':
return ClickUpAdditionalCfgComponent;

View file

@ -12,7 +12,6 @@ import { FormlyFieldConfig } from '@ngx-formly/core';
export const SIMPLE_COUNTER_FORM: ConfigFormSection<SimpleCounterConfig> = {
title: T.F.SIMPLE_COUNTER.FORM.TITLE,
key: 'EMPTY',
customSection: 'SIMPLE_COUNTER_CFG',
help: T.F.SIMPLE_COUNTER.FORM.HELP,
items: [
{

View file

@ -7,7 +7,6 @@ import { TAKE_A_BREAK_FORM_CFG } from './form-cfgs/take-a-break-form.const';
import { IMEX_FORM } from './form-cfgs/imex-form.const';
import { LANGUAGE_SELECTION_FORM_FORM } from './form-cfgs/language-selection-form.const';
import { EVALUATION_SETTINGS_FORM_CFG } from './form-cfgs/evaluation-settings-form.const';
import { SIMPLE_COUNTER_FORM } from './form-cfgs/simple-counter-form.const';
import { TIME_TRACKING_FORM_CFG } from './form-cfgs/time-tracking-form.const';
import { IS_ELECTRON } from '../../app.constants';
import { IS_ANDROID_WEB_VIEW } from '../../util/is-android-web-view';
@ -57,7 +56,6 @@ export const GLOBAL_PRODUCTIVITY_FORM_CONFIG: ConfigFormConfig = [
FOCUS_MODE_FORM_CFG,
TAKE_A_BREAK_FORM_CFG,
EVALUATION_SETTINGS_FORM_CFG,
SIMPLE_COUNTER_FORM,
...(!window.ea?.isSnap() && !!window.speechSynthesis ? [VOICE_REMINDER_FORM] : []),
].filter(filterGlobalConfigForm);

View file

@ -280,7 +280,6 @@ export interface LimitedFormlyFieldConfig<FormModel> extends Omit<
export type CustomCfgSection =
| 'FILE_IMPORT_EXPORT'
| 'JIRA_CFG'
| 'SIMPLE_COUNTER_CFG'
| 'OPENPROJECT_CFG'
| 'CLICKUP_CFG'
| 'CLIPBOARD_IMAGES_CFG';

View file

@ -13,7 +13,19 @@
</formly-form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<mat-dialog-actions>
@if (dialogData.simpleCounter.id) {
<button
mat-button
color="warn"
type="button"
(click)="delete()"
>
<mat-icon>delete</mat-icon>
{{ T.G.DELETE | translate }}
</button>
}
<span style="flex: 1"></span>
<button
mat-button
type="button"

View file

@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialog,
MatDialogActions,
MatDialogContent,
MatDialogRef,
@ -22,6 +23,7 @@ import { MatIcon } from '@angular/material/icon';
import { TranslatePipe } from '@ngx-translate/core';
import { EMPTY_SIMPLE_COUNTER } from '../simple-counter.const';
import { SimpleCounterService } from '../simple-counter.service';
import { DialogConfirmComponent } from '../../../ui/dialog-confirm/dialog-confirm.component';
@Component({
selector: 'dialog-simple-counter-edit-settings',
@ -44,6 +46,7 @@ export class DialogSimpleCounterEditSettingsComponent {
MatDialogRef<DialogSimpleCounterEditSettingsComponent>,
);
private readonly _simpleCounterService = inject(SimpleCounterService);
private readonly _matDialog = inject(MatDialog);
readonly dialogData = inject<{ simpleCounter: SimpleCounterCopy }>(MAT_DIALOG_DATA);
readonly T = T;
@ -87,6 +90,26 @@ export class DialogSimpleCounterEditSettingsComponent {
this._dialogRef.close();
}
delete(): void {
const id = this.dialogData.simpleCounter.id;
if (!id) return;
this._matDialog
.open(DialogConfirmComponent, {
restoreFocus: true,
data: {
message: T.F.SIMPLE_COUNTER.D_CONFIRM_REMOVE.MSG,
okTxt: T.F.SIMPLE_COUNTER.D_CONFIRM_REMOVE.OK,
},
})
.afterClosed()
.subscribe((confirmed: boolean) => {
if (confirmed) {
this._simpleCounterService.deleteSimpleCounter(id);
this._dialogRef.close();
}
});
}
isDirty(): boolean {
return (
JSON.stringify(this._normalizeSettings(this._initialModel)) !==

View file

@ -27,13 +27,6 @@
>
{{ T.F.SIMPLE_COUNTER.HABIT_TRACKER.TODAY | translate }}
</button>
<button
mat-icon-button
(click)="openManageHabits()"
[matTooltip]="T.F.SIMPLE_COUNTER.HABIT_TRACKER.MANAGE_HABITS | translate"
>
<mat-icon>settings</mat-icon>
</button>
</div>
</div>
@ -140,4 +133,59 @@
}}</span>
</button>
</div>
@if (disabledSimpleCounters().length > 0) {
<div class="disabled-section">
<button
class="disabled-section-header"
(click)="showDisabled.set(!showDisabled())"
>
<mat-icon>{{ showDisabled() ? 'expand_less' : 'expand_more' }}</mat-icon>
<span
>{{ T.F.SIMPLE_COUNTER.HABIT_TRACKER.DISABLED_HABITS | translate }} ({{
disabledSimpleCounters().length
}})</span
>
</button>
@if (showDisabled()) {
<div class="disabled-list">
@for (counter of disabledSimpleCounters(); track counter.id) {
<div class="disabled-item">
<div class="disabled-item-info">
@if (counter.icon && counter.icon.startsWith(':')) {
<mat-icon [svgIcon]="counter.icon"></mat-icon>
} @else if (counter.icon) {
<mat-icon>{{ counter.icon }}</mat-icon>
}
<span>{{ counter.title }}</span>
</div>
<div class="disabled-item-actions">
<button
mat-icon-button
(click)="enableHabit(counter.id)"
[matTooltip]="T.F.SIMPLE_COUNTER.FORM.L_IS_ENABLED | translate"
>
<mat-icon>visibility</mat-icon>
</button>
<button
mat-icon-button
(click)="openEditSettings(counter)"
[matTooltip]="T.G.EDIT | translate"
>
<mat-icon>edit</mat-icon>
</button>
<button
mat-icon-button
(click)="deleteHabit(counter)"
[matTooltip]="T.G.DELETE | translate"
>
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
}
</div>
}
</div>
}
</div>

View file

@ -260,6 +260,53 @@
}
}
.disabled-section {
margin-top: 8px;
padding: 0 4px;
}
.disabled-section-header {
display: flex;
align-items: center;
gap: 4px;
background: none;
border: none;
color: inherit;
cursor: pointer;
font-size: 13px;
opacity: 0.6;
padding: 8px 0;
&:hover {
opacity: 1;
}
}
.disabled-list {
display: flex;
flex-direction: column;
}
.disabled-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 0;
}
.disabled-item-info {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
opacity: 0.7;
}
.disabled-item-actions {
display: flex;
gap: 4px;
}
.day-cell {
flex: 1 1 0px;
min-width: 30px;

View file

@ -6,7 +6,6 @@ import {
input,
signal,
} from '@angular/core';
import { Router } from '@angular/router';
import { CommonModule } from '@angular/common';
import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
import { SimpleCounter, SimpleCounterType } from '../simple-counter.model';
@ -19,6 +18,7 @@ import { MatIconModule } from '@angular/material/icon';
import { MatDialog } from '@angular/material/dialog';
import { DialogSimpleCounterEditComponent } from '../dialog-simple-counter-edit/dialog-simple-counter-edit.component';
import { DialogSimpleCounterEditSettingsComponent } from '../dialog-simple-counter-edit-settings/dialog-simple-counter-edit-settings.component';
import { DialogConfirmComponent } from '../../../ui/dialog-confirm/dialog-confirm.component';
import { EMPTY_SIMPLE_COUNTER } from '../simple-counter.const';
import { MatTooltipModule } from '@angular/material/tooltip';
import { moveItemInArray } from '../../../util/move-item-in-array';
@ -43,11 +43,13 @@ import { IS_TOUCH_PRIMARY } from '../../../util/is-mouse-primary';
})
export class HabitTrackerComponent {
simpleCounters = input.required<SimpleCounter[]>();
disabledSimpleCounters = input<SimpleCounter[]>([]);
private _simpleCounterService = inject(SimpleCounterService);
private _dateService = inject(DateService);
private _matDialog = inject(MatDialog);
private _router = inject(Router);
showDisabled = signal(false);
T = T;
SimpleCounterType = SimpleCounterType;
@ -245,9 +247,24 @@ export class HabitTrackerComponent {
});
}
openManageHabits(): void {
this._router.navigate(['/config'], {
queryParams: { tab: 3, section: 'SIMPLE_COUNTER_CFG' },
});
enableHabit(id: string): void {
this._simpleCounterService.updateSimpleCounter(id, { isEnabled: true });
}
deleteHabit(counter: SimpleCounter): void {
this._matDialog
.open(DialogConfirmComponent, {
restoreFocus: true,
data: {
message: T.F.SIMPLE_COUNTER.D_CONFIRM_REMOVE.MSG,
okTxt: T.F.SIMPLE_COUNTER.D_CONFIRM_REMOVE.OK,
},
})
.afterClosed()
.subscribe((confirmed: boolean) => {
if (confirmed) {
this._simpleCounterService.deleteSimpleCounter(counter.id);
}
});
}
}

View file

@ -2,16 +2,18 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SimpleCounterService } from '../../features/simple-counter/simple-counter.service';
import { HabitTrackerComponent } from '../../features/simple-counter/habit-tracker/habit-tracker.component';
import { T } from '../../t.const';
import { TranslateModule } from '@ngx-translate/core';
import { map } from 'rxjs/operators';
@Component({
selector: 'habit-page',
standalone: true,
imports: [CommonModule, HabitTrackerComponent, TranslateModule],
imports: [CommonModule, HabitTrackerComponent],
template: `
<div class="page-wrapper">
<habit-tracker [simpleCounters]="(simpleCounters$ | async) || []"></habit-tracker>
<habit-tracker
[simpleCounters]="(simpleCounters$ | async) || []"
[disabledSimpleCounters]="(disabledSimpleCounters$ | async) || []"
></habit-tracker>
</div>
`,
styles: [
@ -26,7 +28,9 @@ import { TranslateModule } from '@ngx-translate/core';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HabitPageComponent {
simpleCounterService = inject(SimpleCounterService);
simpleCounters$ = this.simpleCounterService.enabledSimpleCounters$;
T = T;
private _simpleCounterService = inject(SimpleCounterService);
simpleCounters$ = this._simpleCounterService.enabledSimpleCounters$;
disabledSimpleCounters$ = this._simpleCounterService.simpleCounters$.pipe(
map((counters) => counters.filter((c) => !c.isEnabled)),
);
}

View file

@ -941,7 +941,7 @@ const T = {
NEXT_WEEK: 'F.SIMPLE_COUNTER.HABIT_TRACKER.NEXT_WEEK',
TODAY: 'F.SIMPLE_COUNTER.HABIT_TRACKER.TODAY',
ADD_HABIT: 'F.SIMPLE_COUNTER.HABIT_TRACKER.ADD_HABIT',
MANAGE_HABITS: 'F.SIMPLE_COUNTER.HABIT_TRACKER.MANAGE_HABITS',
DISABLED_HABITS: 'F.SIMPLE_COUNTER.HABIT_TRACKER.DISABLED_HABITS',
},
S: {
GOAL_REACHED_1: 'F.SIMPLE_COUNTER.S.GOAL_REACHED_1',

View file

@ -915,7 +915,7 @@
"NEXT_WEEK": "Next week",
"TODAY": "Today",
"ADD_HABIT": "Add a new habit",
"MANAGE_HABITS": "Manage habits"
"DISABLED_HABITS": "Disabled habits"
},
"S": {
"GOAL_REACHED_1": "You reached your goal for today!",