Skip to content

Commit 0fd3e5d

Browse files
feat: TT-208 don't allow deleting activities (#669)
* feat: TT-208 add new column in activity-list * feat: TT-208 add switch status button in column * feat: TT-208 remove delete button & assign openmodal to switch * feat: TT-208 connect switch btn with ngrx flux * feat: TT-208 update ngrx delete flux * feat: TT-208 show active activities in entry & details fields * feat: TT-208 change ui-switch to button * fix: TT-208 display the required activities when clicking on time entry with inactive activity * feat: TT-208 add new column in activity-list * feat: TT-208 add switch status button in column * feat: TT-208 remove delete button & assign openmodal to switch * feat: TT-208 connect switch btn with ngrx flux * feat: TT-208 update ngrx delete flux * feat: TT-208 show active activities in entry & details fields * feat: TT-208 change ui-switch to button * fix: TT-208 display the required activities when clicking on time entry with inactive activity * feat: TT-208 rebase on latest master commit
1 parent e16c4c8 commit 0fd3e5d

18 files changed

+589
-228
lines changed

src/app/modules/activities-management/components/activity-list/activity-list.component.html

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,32 @@
33
<thead class="thead-blue">
44
<tr class="d-flex">
55
<th class="col-4 text-center">Activity ID</th>
6-
<th class="col-5 text-center">Activity</th>
7-
<th class="col-3 text-center">Options</th>
6+
<th class="col-4 text-center">Activity</th>
7+
<th class="col-2 text-center">Options</th>
8+
<th class="col-2 text-center">Operation</th>
89
</tr>
910
</thead>
10-
<app-loading-bar *ngIf="(isLoading$ | async)"></app-loading-bar>
11+
<app-loading-bar *ngIf="isLoading$ | async"></app-loading-bar>
1112
<tbody *ngIf="(isLoading$ | async) === false">
1213
<tr class="d-flex" *ngFor="let activity of activities">
1314
<td class="col-4 text-break">{{ activity.id }}</td>
14-
<td class="col-5 ">{{ activity.name }}</td>
15-
<td class="col-3 text-center">
16-
<button
17-
type="button"
18-
class="btn btn-sm btn-primary"
19-
(click)="updateActivity(activity.id)"
20-
>
15+
<td class="col-4">{{ activity.name }}</td>
16+
<td class="col-2 text-center">
17+
<button type="button" class="btn btn-sm btn-primary" (click)="updateActivity(activity.id)">
2118
<i class="fa fa-pencil fa-xs"></i>
2219
</button>
20+
</td>
21+
<td class="col-2 text-center">
2322
<button
24-
type="button"
25-
class="btn btn-sm btn-danger ml-2"
23+
class="btn btn-sm"
2624
data-toggle="modal"
2725
data-target="#deleteModal"
28-
(click)="openModal(activity)"
26+
[ngClass]="activity.btnColor"
27+
(click)="changeOperation(activity)"
2928
>
30-
<i class="fas fa-trash-alt fa-xs"></i>
29+
<i class="fa" [ngClass]="activity.btnIcon"></i>
30+
{{ activity.btnName }}
31+
<span *ngIf="activity._status">&nbsp;</span>
3132
</button>
3233
</td>
3334
</tr>
@@ -42,7 +43,7 @@
4243
tabindex="-1"
4344
role="dialog"
4445
aria-hidden="true"
45-
[title]="'Delete Activity'"
46+
[title]="'Archive Activity'"
4647
[body]="message"
4748
(closeModalEvent)="deleteActivity()"
4849
>

src/app/modules/activities-management/components/activity-list/activity-list.component.spec.ts

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
33

44
import { allActivities } from './../../store/activity-management.selectors';
55
import { ActivityState } from './../../store/activity-management.reducers';
6-
import { DeleteActivity, SetActivityToEdit } from './../../store/activity-management.actions';
6+
import { ArchiveActivity, SetActivityToEdit, UnarchiveActivity } from './../../store/activity-management.actions';
77
import { ActivityListComponent } from './activity-list.component';
88

99
describe('ActivityListComponent', () => {
@@ -13,11 +13,24 @@ describe('ActivityListComponent', () => {
1313
let mockActivitiesSelector;
1414

1515
const state = {
16-
data: [{ id: 'id', name: 'name', description: 'description' }],
16+
data: [{ id: '1', name: 'name', description: 'description', status: 'inactive' }],
1717
isLoading: false,
1818
message: '',
1919
activityIdToEdit: '',
2020
};
21+
const operationBtnProps = [{
22+
key: 'active',
23+
_status: false,
24+
btnColor: 'btn-danger',
25+
btnIcon: 'fa-arrow-circle-down',
26+
btnName: 'Archive',
27+
}, {
28+
key: 'inactive',
29+
_status: true,
30+
btnColor: 'btn-primary',
31+
btnIcon: 'fa-arrow-circle-up',
32+
btnName: 'Active',
33+
}];
2134

2235
beforeEach(waitForAsync(() => {
2336
TestBed.configureTestingModule({
@@ -48,12 +61,27 @@ describe('ActivityListComponent', () => {
4861
expect(store.dispatch).toHaveBeenCalled();
4962
});
5063

64+
it('onInit, activities field is populated with data from store', () => {
65+
component.ngOnInit();
66+
67+
const expectedData = state.data.map(item => {
68+
const props = operationBtnProps.find(prop => prop.key === item.status);
69+
return { ...item, ...props };
70+
});
71+
72+
expect(component.activities).toEqual(expectedData);
73+
});
74+
75+
afterEach(() => {
76+
fixture.destroy();
77+
});
78+
5179
it('deleteActivity, dispatchs DeleteActivity action', () => {
5280
spyOn(store, 'dispatch');
53-
component.idToDelete = 'id';
81+
component.idToModify = 'id';
5482
component.deleteActivity();
5583

56-
expect(store.dispatch).toHaveBeenCalledWith(new DeleteActivity('id'));
84+
expect(store.dispatch).toHaveBeenCalledWith(new ArchiveActivity('id'));
5785
});
5886

5987
it('updateActivity, dispatchs SetActivityToEdit action', () => {
@@ -64,13 +92,66 @@ describe('ActivityListComponent', () => {
6492
expect(store.dispatch).toHaveBeenCalledWith(new SetActivityToEdit('id'));
6593
});
6694

67-
it('onInit, activities field is populated with data from store', () => {
68-
component.ngOnInit();
95+
it('unarchiveActivity, dispatchs UnarchiveActivity action', () => {
96+
spyOn(store, 'dispatch');
97+
component.idToModify = 'id';
98+
component.unarchiveActivity();
6999

70-
expect(component.activities).toBe(state.data);
100+
expect(store.dispatch).toHaveBeenCalledWith(new UnarchiveActivity('id'));
71101
});
72102

73-
afterEach(() => {
74-
fixture.destroy();
103+
it('openModal should set on true and display \"Are you sure you want to archive activity\"', () => {
104+
const message = 'Are you sure you want to archive activity name?';
105+
const itemData = {
106+
id: '1',
107+
name: 'name',
108+
description: 'description',
109+
status: 'active',
110+
key: 'active',
111+
_status: false,
112+
btnColor: 'btn-danger',
113+
btnIcon: 'fa-arrow-circle-down',
114+
btnName: 'Archive',
115+
};
116+
117+
component.openModal(itemData);
118+
expect(component.showModal).toBeTrue();
119+
expect(component.message).toBe(message);
120+
});
121+
122+
it('changeOperation should call unarchiveActivity() on item._status = true', () => {
123+
const itemData = {
124+
id: '1',
125+
name: 'name',
126+
description: 'description',
127+
status: 'inactive',
128+
key: 'inactive',
129+
_status: true,
130+
btnColor: 'btn-primary',
131+
btnIcon: 'fa-arrow-circle-up',
132+
btnName: 'Active',
133+
};
134+
135+
spyOn(component, 'unarchiveActivity');
136+
component.changeOperation(itemData);
137+
expect(component.unarchiveActivity).toHaveBeenCalled();
138+
});
139+
140+
it('changeOperation should call openModal() on item._status = false', () => {
141+
const itemData = {
142+
id: '1',
143+
name: 'name',
144+
description: 'description',
145+
status: 'active',
146+
key: 'active',
147+
_status: false,
148+
btnColor: 'btn-danger',
149+
btnIcon: 'fa-arrow-circle-down',
150+
btnName: 'Archive',
151+
};
152+
153+
spyOn(component, 'openModal');
154+
component.changeOperation(itemData);
155+
expect(component.openModal).toHaveBeenCalled();
75156
});
76157
});
Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,81 @@
11
import { Component, OnInit } from '@angular/core';
22
import { select, Store } from '@ngrx/store';
33
import { Observable } from 'rxjs';
4-
import { delay } from 'rxjs/operators';
4+
import { delay, map } from 'rxjs/operators';
55
import { getIsLoading } from 'src/app/modules/activities-management/store/activity-management.selectors';
6-
import { Activity } from '../../../shared/models';
6+
import { Activity, ActivityFront } from '../../../shared/models';
77
import { allActivities } from '../../store';
8-
import { DeleteActivity, LoadActivities, SetActivityToEdit } from './../../store/activity-management.actions';
8+
import { ArchiveActivity, LoadActivities, SetActivityToEdit, UnarchiveActivity } from './../../store/activity-management.actions';
99
import { ActivityState } from './../../store/activity-management.reducers';
1010

11-
1211
@Component({
1312
selector: 'app-activity-list',
1413
templateUrl: './activity-list.component.html',
1514
styleUrls: ['./activity-list.component.scss'],
1615
})
1716
export class ActivityListComponent implements OnInit {
18-
activities: Activity[] = [];
17+
constructor(private store: Store<ActivityState>) {
18+
this.isLoading$ = store.pipe(delay(0), select(getIsLoading));
19+
}
20+
activities: ActivityFront[] = [];
1921
showModal = false;
2022
activityToDelete: Activity;
2123
message: string;
22-
idToDelete: string;
24+
idToModify: string;
2325
isLoading$: Observable<boolean>;
24-
constructor(private store: Store<ActivityState>) {
25-
this.isLoading$ = store.pipe(delay(0), select(getIsLoading));
26-
}
2726

2827
ngOnInit() {
28+
const operationBtnProps = [{
29+
key: 'active',
30+
_status: false,
31+
btnColor: 'btn-danger',
32+
btnIcon: 'fa-arrow-circle-down',
33+
btnName: 'Archive',
34+
}, {
35+
key: 'inactive',
36+
_status: true,
37+
btnColor: 'btn-primary',
38+
btnIcon: 'fa-arrow-circle-up',
39+
btnName: 'Active',
40+
}];
41+
2942
this.store.dispatch(new LoadActivities());
30-
const activities$ = this.store.pipe(select(allActivities));
43+
const activities$ = this.store.pipe(
44+
select(allActivities),
45+
map((activity: Activity[]) => {
46+
return activity.map(item => {
47+
const addProps = operationBtnProps.find(prop => (prop.key === item.status));
48+
return { ...item, ...addProps };
49+
});
50+
}),
51+
);
3152

3253
activities$.subscribe((response) => {
3354
this.activities = response;
3455
});
3556
}
3657

37-
deleteActivity() {
38-
this.store.dispatch(new DeleteActivity(this.idToDelete));
39-
this.showModal = true;
58+
deleteActivity(): void {
59+
this.store.dispatch(new ArchiveActivity(this.idToModify));
60+
this.showModal = false;
4061
}
4162

42-
updateActivity(activityId: string) {
63+
updateActivity(activityId: string): void {
4364
this.store.dispatch(new SetActivityToEdit(activityId));
4465
}
4566

46-
openModal(item: Activity) {
47-
this.idToDelete = item.id;
48-
this.message = `Are you sure you want to delete ${item.name}?`;
67+
unarchiveActivity(): void {
68+
this.store.dispatch(new UnarchiveActivity(this.idToModify));
69+
this.showModal = false;
70+
}
71+
72+
openModal(item: Activity): void {
73+
this.message = `Are you sure you want to archive activity ${item.name}?`;
4974
this.showModal = true;
5075
}
76+
77+
changeOperation(item: ActivityFront): void {
78+
this.idToModify = item.id;
79+
!item._status ? this.openModal(item) : this.unarchiveActivity();
80+
}
5181
}

src/app/modules/activities-management/store/activity-management.actions.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ describe('LoadActivitiesSuccess', () => {
2525
expect(createActivityFail.type).toEqual(actions.ActivityManagementActionTypes.CREATE_ACTIVITY_FAIL);
2626
});
2727

28+
it('ArchiveActivity type is ActivityManagementActionTypes.ARCHIVE_ACTIVITY', () => {
29+
const archiveActivity = new actions.ArchiveActivity('id_test');
30+
expect(archiveActivity.type).toEqual(actions.ActivityManagementActionTypes.ARCHIVE_ACTIVITY);
31+
});
32+
33+
it('ArchiveActivitySuccess type is ActivityManagementActionTypes.ARCHIVE_ACTIVITY_SUCCESS', () => {
34+
const archiveActivitySuccess = new actions.ArchiveActivitySuccess({
35+
id: 'id_test',
36+
status: 'inactive'
37+
});
38+
expect(archiveActivitySuccess.type).toEqual(actions.ActivityManagementActionTypes.ARCHIVE_ACTIVITY_SUCCESS);
39+
});
40+
41+
it('ArchiveActivityFail type is ActivityManagementActionTypes.ARCHIVE_ACTIVITY_FAIL', () => {
42+
const archiveActivityFail = new actions.ArchiveActivityFail('error');
43+
expect(archiveActivityFail.type).toEqual(actions.ActivityManagementActionTypes.ARCHIVE_ACTIVITY_FAIL);
44+
});
45+
2846
it('UpdateActivitySuccess type is ActivityManagementActionTypes.UPDATE_ACTIVITY_SUCCESS', () => {
2947
const updateActivitySuccess = new actions.UpdateActivitySuccess({
3048
id: '1',
@@ -39,6 +57,24 @@ describe('LoadActivitiesSuccess', () => {
3957
expect(updateActivityFail.type).toEqual(actions.ActivityManagementActionTypes.UPDATE_ACTIVITY_FAIL);
4058
});
4159

60+
it('UnarchiveActivity type is ActivityManagementActionTypes.UNARCHIVE_ACTIVITY', () => {
61+
const unarchiveActivity = new actions.UnarchiveActivity('id_test');
62+
expect(unarchiveActivity.type).toEqual(actions.ActivityManagementActionTypes.UNARCHIVE_ACTIVITY);
63+
});
64+
65+
it('UnarchiveActivitySuccess type is ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_SUCCESS', () => {
66+
const unarchiveActivitySuccess = new actions.UnarchiveActivitySuccess({
67+
id: 'id_test',
68+
status: 'active'
69+
});
70+
expect(unarchiveActivitySuccess.type).toEqual(actions.ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_SUCCESS);
71+
});
72+
73+
it('UnarchiveActivityFail type is ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_FAIL', () => {
74+
const unarchiveActivityFail = new actions.UnarchiveActivityFail('error');
75+
expect(unarchiveActivityFail.type).toEqual(actions.ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_FAIL);
76+
});
77+
4278
it('SetActivityToEdit type is ActivityManagementActionTypes.SET_ACTIVITY_ID_TO_EDIT', () => {
4379
const setActivityToEdit = new actions.SetActivityToEdit('123');
4480
expect(setActivityToEdit.type).toEqual(actions.ActivityManagementActionTypes.SET_ACTIVITY_ID_TO_EDIT);

0 commit comments

Comments
 (0)