Commit 8b8e7269 authored by François Agneray's avatar François Agneray
Browse files

Add group list page

parent ff7561ef
<div class="row mb-3">
<div class="col-12">
<button (click)="openModalNew(templateForNew); $event.stopPropagation()" title="Add new dataset family" class="btn btn-outline-success float-right">
<div class="btn-toolbar mb-3" role="toolbar" aria-label="Toolbar with button groups">
<div class="btn-group mr-2" role="group" aria-label="First group">
<button (click)="openModalNew(templateForNew); $event.stopPropagation()" title="Add new dataset family" class="btn btn-outline-success">
<span class="fas fa-plus"></span> New dataset family
</button>
</div>
<div class="btn-group mr-2" role="group" aria-label="Second group">
<a routerLink="group" title="Handle groups" class="btn btn-outline-primary">
<span class="fas fa-users"></span> Handle groups
</a>
</div>
</div>
<div *ngFor="let datasetFamily of datasetFamilyList" class="card mb-3">
......
<div class="card">
<div class="card-header">
Groups
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Label</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let group of groupList">
<td class="align-middle">{{ group.id }}</td>
<td class="align-middle">{{ group.label }}</td>
<td class="align-middle">
<a title="Edit this group" routerLink="/edit-group/{{group.id}}" class="btn btn-outline-primary">
<span class="fas fa-edit"></span>
</a>
&nbsp;
<button title="Delete this group" (click)="openModal(template, group); $event.stopPropagation()" class="btn btn-outline-danger">
<span class="fas fa-trash-alt"></span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<ng-template #template>
<div class="modal-header">
<h4 class="modal-title pull-left">Confirm</h4>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this group : <strong>{{ groupForDel.label }}</strong> ?</p>
<p>
<button (click)="modalRef.hide()" class="btn btn-default">No</button>
&nbsp;
<button (click)="confirmDel()" class="btn btn-danger">Yes</button>
</p>
</div>
</ng-template>
import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, TemplateRef } from '@angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
import { Group } from '../../store/model';
@Component({
selector: 'app-group-list',
templateUrl: 'group-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class GroupListComponent {
@Input() groupList: Group[];
@Output() deleteGroup: EventEmitter<Group> = new EventEmitter();
modalRef: BsModalRef;
groupForDel: Group;
constructor(private modalService: BsModalService) { }
openModal(template: TemplateRef<any>, group: Group) {
this.groupForDel = group;
this.modalRef = this.modalService.show(template);
}
confirmDel() {
this.deleteGroup.emit(this.groupForDel);
this.modalRef.hide();
}
}
......@@ -9,6 +9,7 @@ import { FormDatasetFamilyComponent } from './dataset-family/form-dataset-famil
import { DatasetListComponent } from './dataset/dataset-list.component';
import { FormDatasetComponent } from './dataset/form-dataset.component';
import { attributeDummiesComponents } from './attribute';
import { GroupListComponent } from './group/group-list.component';
export const dummiesComponents = [
ProjectListComponent,
......@@ -21,5 +22,6 @@ export const dummiesComponents = [
FormDatasetFamilyComponent,
DatasetListComponent,
FormDatasetComponent,
attributeDummiesComponents
attributeDummiesComponents,
GroupListComponent
];
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a routerLink="/instance-list">
Instances
</a>
</li>
<li class="breadcrumb-item">
<a routerLink="/configure-instance/{{ instanceSelected | async }}">
Configure instance {{ instanceSelected | async }}
</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Groups</li>
</ol>
</nav>
<div *ngIf="groupListIsLoading | async" class="row justify-content-center mt-5">
<span class="fas fa-circle-notch fa-spin fa-3x"></span>
<span class="sr-only">Loading...</span>
</div>
<div *ngIf="groupListIsLoaded | async">
<app-group-list [groupList]="groupList | async" (deleteGroup)="deleteGroup($event)">
</app-group-list>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { Group } from '../../store/model';
import * as groupActions from '../../store/action/group.action';
import * as groupReducer from '../../store/reducer/group.reducer';
import * as groupSelector from '../../store/selector/group.selector';
import * as instanceSelector from '../../store/selector/instance.selector';
@Component({
selector: 'app-group',
templateUrl: 'group.component.html'
})
export class GroupComponent implements OnInit {
public instanceSelected: Observable<string>;
public groupListIsLoading: Observable<boolean>;
public groupListIsLoaded: Observable<boolean>;
public groupList: Observable<Group[]>;
constructor(private store: Store<groupReducer.State>) {
this.instanceSelected = store.select(instanceSelector.getInstanceSelected);
this.groupListIsLoading = store.select(groupSelector.getGroupListIsLoading);
this.groupListIsLoaded = store.select(groupSelector.getGroupListIsLoaded);
this.groupList = store.select(groupSelector.getGroupList);
}
ngOnInit() {
this.store.dispatch(new groupActions.LoadGroupListAction());
}
deleteGroup(group: Group) {
this.store.dispatch(new groupActions.DeleteGroupAction(group));
}
}
......@@ -15,6 +15,7 @@ import { ConfigureInstanceComponent } from './containers/instance/configure-inst
import { NewDatasetComponent } from './containers/dataset/new-dataset.component';
import { EditDatasetComponent } from './containers/dataset/edit-dataset.component';
import { AttributeComponent } from './containers/attribute/attribute.component';
import { GroupComponent } from './containers/group/group.component';
import { AuthGuard } from '../core/auth.guard';
const routes: Routes = [
......@@ -22,6 +23,7 @@ const routes: Routes = [
{ path: 'new-instance', canActivate: [AuthGuard], component: NewInstanceComponent },
{ path: 'edit-instance/:iname', canActivate: [AuthGuard], component: EditInstanceComponent },
{ path: 'configure-instance/:iname', canActivate: [AuthGuard], component: ConfigureInstanceComponent },
{ path: 'configure-instance/:iname/group', canActivate: [AuthGuard], component: GroupComponent },
{ path: 'configure-instance/:iname/new-dataset', canActivate: [AuthGuard], component: NewDatasetComponent },
{ path: 'configure-instance/:iname/edit-dataset/:dname', canActivate: [AuthGuard], component: EditDatasetComponent },
{ path: 'configure-instance/:iname/configure-dataset/:dname', canActivate: [AuthGuard], component: AttributeComponent },
......@@ -58,5 +60,6 @@ export const routedComponents = [
ConfigureInstanceComponent,
NewDatasetComponent,
EditDatasetComponent,
AttributeComponent
AttributeComponent,
GroupComponent
];
import { Action } from '@ngrx/store';
import { Group } from '../model';
export const LOAD_GROUP_LIST = '[Group] Load Group List';
export const LOAD_GROUP_LIST_SUCCESS = '[Group] Load Group List Success';
export const LOAD_GROUP_LIST_FAIL = '[Group] Load Group List Fail';
export const ADD_NEW_GROUP = '[Group] Add New Group';
export const ADD_NEW_GROUP_SUCCESS = '[Group] Add New Group Success';
export const ADD_NEW_GROUP_FAIL = '[Group] Add New Group Fail';
export const EDIT_GROUP = '[Group] Edit Group';
export const EDIT_GROUP_SUCCESS = '[Group] Edit Group Success';
export const EDIT_GROUP_FAIL = '[Group] Edit Group Fail';
export const DELETE_GROUP = '[Group] Delete Group';
export const DELETE_GROUP_SUCCESS = '[Group] Delete Group Success';
export const DELETE_GROUP_FAIL = '[Group] Delete Group Fail';
export class LoadGroupListAction implements Action {
type = LOAD_GROUP_LIST;
constructor(public payload: {} = null) { }
}
export class LoadGroupListSuccessAction implements Action {
type = LOAD_GROUP_LIST_SUCCESS;
constructor(public payload: Group[]) { }
}
export class LoadGroupListFailAction implements Action {
type = LOAD_GROUP_LIST_FAIL;
constructor(public payload: {} = null) { }
}
export class AddNewGroupAction implements Action {
type = ADD_NEW_GROUP;
constructor(public payload: Group) { }
}
export class AddNewGroupSuccessAction implements Action {
type = ADD_NEW_GROUP_SUCCESS;
constructor(public payload: Group) { }
}
export class AddNewGroupFailAction implements Action {
type = ADD_NEW_GROUP_FAIL;
constructor(public payload: {} = null) { }
}
export class EditGroupAction implements Action {
type = EDIT_GROUP;
constructor(public payload: Group) { }
}
export class EditGroupSuccessAction implements Action {
type = EDIT_GROUP_SUCCESS;
constructor(public payload: Group) { }
}
export class EditGroupFailAction implements Action {
type = EDIT_GROUP_FAIL;
constructor(public payload: {} = null) { }
}
export class DeleteGroupAction implements Action {
type = DELETE_GROUP;
constructor(public payload: Group) { }
}
export class DeleteGroupSuccessAction implements Action {
type = DELETE_GROUP_SUCCESS;
constructor(public payload: Group) { }
}
export class DeleteGroupFailAction implements Action {
type = DELETE_GROUP_FAIL;
constructor(public payload: {} = null) { }
}
export type Actions
= LoadGroupListAction
| LoadGroupListSuccessAction
| LoadGroupListFailAction
| AddNewGroupAction
| AddNewGroupSuccessAction
| AddNewGroupFailAction
| EditGroupAction
| EditGroupSuccessAction
| EditGroupFailAction
| DeleteGroupAction
| DeleteGroupSuccessAction
| DeleteGroupFailAction;
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';
import { withLatestFrom, switchMap, map, catchError, tap } from 'rxjs/operators';
import * as fromRouter from '../../../shared/utils';
import { Group } from '../model';
import * as groupActions from '../action/group.action';
import { GroupService } from '../service/group.service';
@Injectable()
export class GroupEffects {
constructor(
private actions$: Actions,
private store$: Store<{router: fromRouter.RouterReducerState}>,
private groupService: GroupService,
private router: Router,
private toastr: ToastrService
) { }
@Effect()
loadGroupListAction$ = this.actions$.pipe(
ofType(groupActions.LOAD_GROUP_LIST),
withLatestFrom(this.store$),
switchMap(([action, state]) => {
const instanceName = state.router.state.params.iname;
return this.groupService.retrieveGroupList(instanceName).pipe(
map((groupList: Group[]) =>
new groupActions.LoadGroupListSuccessAction(groupList)),
catchError(() => of(new groupActions.LoadGroupListFailAction()))
)
})
);
@Effect({ dispatch: false })
loadGroupListFailedAction$ = this.actions$.pipe(
ofType(groupActions.LOAD_GROUP_LIST_FAIL),
tap(_ => this.toastr.error('Loading Failed!', 'Group list loading failed'))
);
@Effect()
addNewGroupAction$ = this.actions$.pipe(
ofType(groupActions.ADD_NEW_GROUP),
withLatestFrom(this.store$),
switchMap(([action, state]) => {
const addNewGroupAction = action as groupActions.AddNewGroupAction;
const instanceName = state.router.state.params.iname;
return this.groupService.addGroup(instanceName, addNewGroupAction.payload).pipe(
map((newGroup: Group) => new groupActions.AddNewGroupSuccessAction(newGroup)),
catchError(() => of(new groupActions.AddNewGroupFailAction()))
)
})
);
@Effect({dispatch: false})
addNewGroupSuccessAction$ = this.actions$.pipe(
ofType(groupActions.ADD_NEW_GROUP_SUCCESS),
map(_ => {
this.router.navigate(['/project/group-list']);
this.toastr.success('Add group success!', 'The new group has been created!');
})
);
@Effect({dispatch: false})
addNewGroupFailedAction$ = this.actions$.pipe(
ofType(groupActions.ADD_NEW_GROUP_FAIL),
map(_ => this.toastr.error('Add group failed!', 'The new group could not be created into the database'))
);
@Effect()
editGroupAction$ = this.actions$.pipe(
ofType(groupActions.EDIT_GROUP),
switchMap(action => {
const editGroupAction = action as groupActions.EditGroupAction;
return this.groupService.editGroup(editGroupAction.payload).pipe(
map((group: Group) => new groupActions.EditGroupSuccessAction(group)),
catchError(() => of(new groupActions.EditGroupFailAction()))
)
})
);
@Effect({dispatch: false})
editGroupSuccessAction$ = this.actions$.pipe(
ofType(groupActions.EDIT_GROUP_SUCCESS),
map(_ => {
this.router.navigate(['/project/group-list']);
this.toastr.success('Edit group success!', 'The existing entity has been edited into the database')
})
);
@Effect({dispatch: false})
editGroupFailedAction$ = this.actions$.pipe(
ofType(groupActions.EDIT_GROUP_FAIL),
map(_ => this.toastr.error('Edit group failed!', 'The existing entity could not be edited into the database'))
);
@Effect()
deleteGroupAction$ = this.actions$.pipe(
ofType(groupActions.DELETE_GROUP),
switchMap(action => {
const deleteGroupAction = action as groupActions.DeleteGroupAction;
return this.groupService.deleteGroup(deleteGroupAction.payload.id).pipe(
map(_ => new groupActions.DeleteGroupSuccessAction(deleteGroupAction.payload)),
catchError(() => of(new groupActions.DeleteGroupFailAction()))
)
})
);
@Effect({dispatch: false})
deleteGroupSuccessAction$ = this.actions$.pipe(
ofType(groupActions.DELETE_GROUP_SUCCESS),
map(_ => {
this.toastr.success('Delete group success!', 'The group has been deleted!');
})
);
@Effect({dispatch: false})
deleteGroupFailedAction$ = this.actions$.pipe(
ofType(groupActions.DELETE_GROUP_FAIL),
map(_ => this.toastr.error('Delete group failed!', 'The group could not be deleted into the database'))
);
}
......@@ -7,6 +7,7 @@ import { AttributeEffects } from './attribute.effects';
import { CriteriaFamilyEffects } from './criteria-family.effects';
import { OutputFamilyEffects } from './output-family.effects';
import { OutputCategoryEffects } from './output-category.effects';
import { GroupEffects } from './group.effects';
export const metamodelEffects = [
DatabaseEffects,
......@@ -17,5 +18,6 @@ export const metamodelEffects = [
AttributeEffects,
CriteriaFamilyEffects,
OutputFamilyEffects,
OutputCategoryEffects
OutputCategoryEffects,
GroupEffects
];
export class Group {
id: number;
label: string;
instance_name: string;
datasets: string[];
}
......@@ -8,5 +8,6 @@ export * from './option.model';
export * from './criteria-family.model';
export * from './output-family.model';
export * from './output-category.model';
export * from './group.model';
export * from './displayable.model';
export * from './renderer';
\ No newline at end of file
import * as actions from '../action/group.action';
import { Group } from '../model';
export interface State {
groupListIsLoading: boolean;
groupListIsLoaded: boolean;
groupList: Group[];
}
const initialState: State = {
groupListIsLoading: false,
groupListIsLoaded: false,
groupList: []
};
export function reducer(state: State = initialState, action: actions.Actions): State {
switch (action.type) {
case actions.LOAD_GROUP_LIST:
return {
...state,
groupListIsLoading: true,
groupListIsLoaded: false
};
case actions.LOAD_GROUP_LIST_SUCCESS:
const groupList = action.payload as Group[];
return {
...state,
groupList,
groupListIsLoading: false,
groupListIsLoaded: true
};
case actions.LOAD_GROUP_LIST_FAIL:
return {
...state,
groupListIsLoading: false
};
case actions.ADD_NEW_GROUP_SUCCESS:
const newGroup = action.payload as Group;
return {
...state,
groupList: [...state.groupList, newGroup]
};
case actions.EDIT_GROUP_SUCCESS:
const editedGroup = action.payload as Group;
return {
...state,
groupList: [...state.groupList.filter(group => group.id !== editedGroup.id), editedGroup]
};
case actions.DELETE_GROUP_SUCCESS:
const deletedGroup = action.payload as Group;
return {
...state,
groupList: state.groupList.filter(group => group.id !== deletedGroup.id)
}
default:
return state;
}
}
export const getGroupListIsLoading = (state: State) => state.groupListIsLoading;
export const getGroupListIsLoaded = (state: State) => state.groupListIsLoaded;
export const getGroupList = (state: State) => state.groupList;
\ No newline at end of file
......@@ -9,6 +9,7 @@ import * as attribute from './attribute.reducer';
import * as criteriaFamily from './criteria-family.reducer';
import * as outputFamily from './output-family.reducer';
import * as outputCategory from './output-category.reducer';
import * as group from './group.reducer';
export interface State {
project: project.State;
......@@ -20,6 +21,7 @@ export interface State {
criteriaFamily: criteriaFamily.State;
outputFamily: outputFamily.State;
outputCategory: outputCategory.State;
group: group.State;
}
const reducers = {
......@@ -31,7 +33,8 @@ const reducers = {
attribute: attribute.reducer,
criteriaFamily: criteriaFamily.reducer,
outputFamily: outputFamily.reducer,
outputCategory: outputCategory.reducer
outputCategory: outputCategory.reducer,
group: group.reducer
};
const productionReducer = combineReducers(reducers);
......
import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as reducer from '../reducer';
import * as group from '../reducer/group.reducer';
import { RouterReducerState } from '../../../shared/utils';
const selectRouterState = createFeatureSelector<RouterReducerState>('router');
export const getGroupState = createSelector(
reducer.getMetamodelState,
(state: reducer.State) => state.group
);
export const getGroupListIsLoading = createSelector(
getGroupState,
group.getGroupListIsLoading
);