Skip to content

Commit

Permalink
Feature/34/reminder types (#37)
Browse files Browse the repository at this point in the history
* #34: adds reminder type config and display

* #34: adds filtering option to reminder drawer

* #34: stores and applies reminder filter to local storage

* #34: adds info text to total reminder count

---------

Co-authored-by: Dirk Peter <[email protected]>
  • Loading branch information
dirk-peter-c8y and Dirk Peter authored Aug 27, 2024
1 parent bbe2e55 commit 3d23f59
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 16 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ _TBD_

---

## Reminder Type Config

| Key | Value | Info |
| -------- | -------------- | ---- |
| categroy | `c8y.reminder` | |
| key | `types` | |

```json
[
{
"id": "1",
"name": "Type 1"
},
{
"id": "2",
"name": "My Preferred Type"
},
{
"id": "a_123",
"name": "A123"
}
]
```

---

## Useful links

### 📘 Explore the Knowledge Base
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ <h2 translate>Reminder</h2>
</ul>

<div class="body">
<!-- filter: reminder type -->
<div *ngIf="types.length">
<label for="reminder-type-filter" translate>Filter by Reminder Type</label>
<div class="c8y-select-wrapper">
<select
id="reminder-type-filter"
name="reminder-type"
class="form-control"
(change)="filterReminders()"
[(ngModel)]="typeFilter"
>
<option value="">{{ 'Not filtered' | translate }}</option>
<option *ngFor="let type of types" [ngValue]="type.id">{{ type.name }}</option>
</select>
</div>
</div>

<!-- list of reminders -->
<ul class="reminder-group-list">
<li *ngFor="let group of reminderGroups; let i = index" class="reminder-group-item">
<header>
Expand All @@ -30,6 +48,13 @@ <h2 translate>Reminder</h2>
[class.text-danger]="group.status === reminderGroupStatus.due && group.count > 0"
>
{{ group.count || 0 }}
<small
*ngIf="group.total"
title="{{ 'Number of reminders if not filtered' | translate }}"
class="text-muted"
>
/ {{ group.total }}
</small>
</span>
<em>{{ group.status | translate }}</em>
<i [c8yIcon]="'expand-arrow'" [class.expanded]="groupIsExpanded[i]"></i>
Expand Down Expand Up @@ -136,6 +161,12 @@ <h2 translate>Reminder</h2>
</p>

<footer>
<!-- type -->
<button *ngIf="types.length && reminder.reminderType" type="button" class="btn btn-clean" (click)="setTypeFilter(reminder.reminderType)">
<i class="m-r-4" [c8yIcon]="'tag'"></i>
<c8y-reminder-type [id]="reminder.reminderType"></c8y-reminder-type>
</button>
<!-- asset reference -->
<ng-container *ngIf="reminder.isGroup; else isDevice">
<a [routerLink]="['/group', reminder.source.id]">
<i [c8yIcon]="'c8y-group-open'"></i>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@ aside {
display: flex;
flex-direction: column;

> div,
> small {
margin: 16px 0;
display: block;
padding: 0 16px;
}

> small {
display: block;
text-align: center;
}
}
Expand Down Expand Up @@ -157,6 +161,8 @@ aside {

footer {
margin: 4px 0 0;
display: flex;
flex-direction: column;
}

&.text-muted::before {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Component, OnDestroy } from '@angular/core';
import { AlertService, HeaderService } from '@c8y/ngx-components';
import { has } from 'lodash';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BehaviorSubject, Subscription } from 'rxjs';
import {
Reminder,
ReminderGroup,
ReminderGroupFilter,
ReminderGroupStatus,
ReminderStatus,
ReminderType,
REMINDER_DRAWER_OPEN_CLASS,
REMINDER_MAIN_HEADER_CLASS,
REMINDER_TYPE_FRAGMENT,
} from '../../reminder.model';
import { ReminderService } from '../../services/reminder.service';
import { ReminderModalComponent } from '../reminder-modal/reminder-modal.component';
Expand All @@ -23,11 +27,13 @@ export class ReminderDrawerComponent implements OnDestroy {
reminders: Reminder[] = [];
reminderGroups: ReminderGroup[] = [];
lastUpdate?: Date;
types: ReminderType[] = [];

// for template
reminderStatus = ReminderStatus;
reminderGroupStatus = ReminderGroupStatus;
groupIsExpanded: boolean[] = [true, true, false];
typeFilter = '';

get open(): boolean {
return this._open;
Expand All @@ -48,7 +54,9 @@ export class ReminderDrawerComponent implements OnDestroy {
private reminderService: ReminderService,
private alertService: AlertService,
private modalService: BsModalService
) {
) {
this.initFilter();

// check if the actual drawer was opened
this.subscriptions.add(
this.headerService.rightDrawerOpen$.subscribe((open) => {
Expand All @@ -61,6 +69,7 @@ export class ReminderDrawerComponent implements OnDestroy {
})
);

// get live updates on reminders from service
this.subscriptions.add(
this.reminderService.reminders$.subscribe((reminders) =>
this.digestReminders(reminders)
Expand Down Expand Up @@ -103,6 +112,21 @@ export class ReminderDrawerComponent implements OnDestroy {
}
}

setTypeFilter(type: ReminderType['id']): void {
if (!this.types.length) return;

this.typeFilter = type;
this.filterReminders();
}

filterReminders(): void {
this.reminderGroups = this.reminderService.groupReminders(
this.reminders,
this.buildFilter()
);
this.reminderService.storeFilterConfig();
}

private toggleRightDrawer(open: boolean): void {
const drawer = document.getElementsByClassName(
REMINDER_MAIN_HEADER_CLASS
Expand All @@ -122,10 +146,35 @@ export class ReminderDrawerComponent implements OnDestroy {
}

private digestReminders(reminders: Reminder[]): void {
// TODO allow filtering in UI?
// - filter by type, group / device?
this.reminders = reminders;
this.reminderGroups = this.reminderService.groupReminders(reminders);
this.lastUpdate = new Date();
this.reminderGroups = this.reminderService.groupReminders(
reminders,
this.buildFilter()
);
}

private buildFilter(): ReminderGroupFilter {
const filters: ReminderGroupFilter = {};

// populate filters
if (this.typeFilter !== '')
filters[REMINDER_TYPE_FRAGMENT] = this.typeFilter;

return Object.keys(filters).length > 0 ? filters : null;
}

private initFilter(): void {
this.types = this.reminderService.types;

if (!this.types.length) {
this.reminderService.resetFilterConfig();
return;
}

const filters = this.reminderService.filters;

if (has(filters, REMINDER_TYPE_FRAGMENT))
this.typeFilter = filters[REMINDER_TYPE_FRAGMENT];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,27 @@ import { BsModalRef } from 'ngx-bootstrap/modal';
import {
Reminder,
ReminderStatus,
ReminderType,
REMINDER_TEXT_LENGTH,
REMINDER_TYPE,
REMINDER_TYPE_FRAGMENT,
} from '../../reminder.model';
import { ReminderService } from '../../services';

interface FormlySelectOptions {
label: string;
value: string;
group?: string;
}

@Component({
selector: 'c8y-reminder-modal',
templateUrl: './reminder-modal.component.html',
})
export class ReminderModalComponent implements OnInit {
isLoading = false;
asset!: Partial<IManagedObject>;
typeOptions!: FormlySelectOptions[];
isLoading = false;
form = new FormGroup({});

reminder: Partial<Reminder> = {
Expand Down Expand Up @@ -70,8 +80,11 @@ export class ReminderModalComponent implements OnInit {
private eventService: EventService,
private alertService: AlertService,
private activatedRoute: ActivatedRoute,
private translateService: TranslateService
) {}
private translateService: TranslateService,
private reminderService: ReminderService
) {
this.setTypeField();
}

ngOnInit(): void {
const asset = this.getAssetFromRoute(this.activatedRoute.snapshot);
Expand All @@ -94,6 +107,7 @@ export class ReminderModalComponent implements OnInit {
const reminder: IEvent = {
source: this.reminder.source,
type: REMINDER_TYPE,
reminderType: this.reminder.reminderType || null,
time: moment(this.reminder.time).seconds(0).toISOString(),
text: this.reminder.text,
status: ReminderStatus.active,
Expand Down Expand Up @@ -159,4 +173,23 @@ export class ReminderModalComponent implements OnInit {

return undefined;
}

private setTypeField(): void {
this.typeOptions = this.reminderService.types.map((type: ReminderType) => ({
label: type.name,
value: type.id,
}));

if (!this.typeOptions.length) return;

this.fields.push({
key: REMINDER_TYPE_FRAGMENT,
type: 'select',
props: {
label: this.translateService.instant('Reminder type (optional)'),
hidden: this.typeOptions?.length > 0,
options: this.typeOptions,
},
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ type?.name || 'Unknown' }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
display: inline-block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Component, Input } from '@angular/core';
import { Reminder, ReminderType } from '../../reminder.model';
import { ReminderService } from '../../services';

@Component({
selector: 'c8y-reminder-type',
templateUrl: './reminder-type.component.html',
styleUrl: './reminder-type.component.less',
})
export class ReminderTypeComponent {
@Input() set reminder(reminder: Reminder) {
this.setType(reminder.reminderType);
}

@Input() set id(reminderTypeID: ReminderType['id']) {
this.setType(reminderTypeID);
}

type: ReminderType;

constructor(private reminderService: ReminderService) {}

private setType(id: ReminderType['id']) {
this.type = {
id,
name: this.reminderService.getReminderTypeName(id),
};
}
}
2 changes: 2 additions & 0 deletions src/app/reminder-plugin/reminder-plugin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import {
ReminderModalComponent,
TimeFieldType,
} from './components';
import { ReminderTypeComponent } from './components/reminder-type/reminder-type.component';
import { DomService, ReminderService } from './services';

@NgModule({
declarations: [
ReminderIndicatorComponent,
ReminderDrawerComponent,
ReminderModalComponent,
ReminderTypeComponent,
AssetFieldType,
TimeFieldType,
],
Expand Down
17 changes: 16 additions & 1 deletion src/app/reminder-plugin/reminder.model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { IEvent } from '@c8y/client';
import { IEvent, ITenantOption } from '@c8y/client';

export const REMINDER_TYPE = 'c8y_Reminder';
export const REMINDER_TYPE_FRAGMENT = 'reminderType';
export const REMINDER_INITIAL_QUERY_SIZE = 100;
export const REMINDER_DRAWER_OPEN_CLASS = 'drawerOpen';
export const REMINDER_MAIN_HEADER_CLASS = 'app-main-header';
export const REMINDER_MAX_COUNTER = 10;
export const REMINDER_TEXT_LENGTH = 100;
export const REMINDER_TENENAT_OPTION_CATEGORY: ITenantOption['category'] = 'c8y.reminder';
export const REMINDER_TENENAT_OPTION_TYPE_KEY: ITenantOption['key'] = 'types';
export const REMINDER_LOCAL_STORAGE_FILTER = 'c8y_rpFilter';

export const ReminderGroupStatus = {
due: 'DUE',
Expand All @@ -26,10 +30,21 @@ export interface Reminder extends IEvent {
isGroup?: object;
diff?: number;
isCleared?: object;
reminderType?: ReminderType['id'];
}

export interface ReminderGroup {
status: ReminderGroupStatus;
reminders: Reminder[];
count: number;
total?: number;
}

export interface ReminderType {
id: string;
name: string;
}

export interface ReminderGroupFilter {
[key: string]: string;
}
Loading

0 comments on commit 3d23f59

Please sign in to comment.