diff --git a/src/app/commons/component/header/header.component.html b/src/app/commons/component/header/header.component.html index 4c92ef6..3c93cfc 100644 --- a/src/app/commons/component/header/header.component.html +++ b/src/app/commons/component/header/header.component.html @@ -7,7 +7,7 @@ - +
diff --git a/src/packages/notification/component/badge/notification.component.scss b/src/packages/notification/component/badge/notification.component.scss index 2594319..d4bd28f 100644 --- a/src/packages/notification/component/badge/notification.component.scss +++ b/src/packages/notification/component/badge/notification.component.scss @@ -1,5 +1,8 @@ $prefix: 'notification'; +.highlight { + background: #039be5 +} .badge { position: absolute; diff --git a/src/packages/notification/component/badge/notification.component.ts b/src/packages/notification/component/badge/notification.component.ts index fd1c9a7..9d785f0 100644 --- a/src/packages/notification/component/badge/notification.component.ts +++ b/src/packages/notification/component/badge/notification.component.ts @@ -1,34 +1,103 @@ -import { Component, OnInit, Input } from '@angular/core'; +import { Component, OnInit, Input, AfterContentInit } from '@angular/core'; import { Router } from '@angular/router'; +import { Store, select } from '@ngrx/store'; +import { RPCError } from 'packages/core/rpc/error'; +import { Notification } from '../../model'; +import * as ListStore from '../../store/list'; +import * as DetailStore from '../../store/detail'; +import { ReadAllByMemberSelector, ReadSelector } from '../../store'; +import { AuthSelector } from 'packages/member/store'; +import { Member } from '../../../member/model'; +import { PageParams, Page } from 'app/commons/model'; +import { MarkAsRead } from '../../store/detail'; @Component({ selector: 'of-notification-badge', templateUrl: './notification.component.html', styleUrls: ['./notification.component.scss'] }) -export class NotificationBadgeComponent implements OnInit { - - cssPrefix = 'toolbar-notification'; +export class NotificationBadgeComponent implements OnInit, AfterContentInit { + notification$ = this.listStore.pipe(select(ReadAllByMemberSelector.select('page'))); + mark$ = this.detailStore.pipe(select(ReadSelector.select('notification'))); isOpen = false; - @Input() notifications = []; + notifications: Notification[] = null; constructor( - private router: Router + private router: Router, + private listStore: Store, + private detailStore: Store ) { } ngOnInit() { + this.notification$.subscribe( + (page: Page) => { + if (page !== null) { + this.notifications = page.content; + } + }, + (error: RPCError) => { + console.log(error.message); + } + ); + this.mark$.subscribe( + (n: Notification) => { + if (n !== null && n.confirmDate !== null) { + this.getNotifications(0); + } + }, + (error: RPCError) => { + console.log(error.message); + } + ); } - select() { - + ngAfterContentInit() { + this.getNotifications(0); } - delete(notification) { + getNotifications(pageIndex: number) { + this.listStore.select(AuthSelector.select('member')).subscribe( + (member: Member) => { + const pageParams: PageParams = { + pageNo: '0', + countPerPage: '10', + sortCol: 'id', + sortDirection: 'descending' + }; + this.listStore.dispatch(new ListStore.ReadAllByMember({ member, pageParams })); + }, + (error) => { + console.log(error); + } + ); + } + + mark(notification: Notification, e: Event) { + this.detailStore.dispatch(new DetailStore.MarkAsRead(notification)); + e.stopPropagation(); + } + + handleClick(n: Notification) { + alert('Will redirect to ' + n.url); } handleMarkAllAsRead() { - console.log('Mark All'); + + this.listStore.select(AuthSelector.select('member')).subscribe( + (member: Member) => { + const pageParams: PageParams = { + pageNo: '0', + countPerPage: '10', + sortCol: 'id', + sortDirection: 'descending' + }; + this.listStore.dispatch(new ListStore.MarkAllAsRead({ member, pageParams })); + }, + (error) => { + console.log(error); + } + ); } handleViewAll() { diff --git a/src/packages/notification/component/notification/notification.component.html b/src/packages/notification/component/notification/notification.component.html index c2d1fe2..2e236d5 100644 --- a/src/packages/notification/component/notification/notification.component.html +++ b/src/packages/notification/component/notification/notification.component.html @@ -21,7 +21,7 @@ {{element.member.name}} - + diff --git a/src/packages/notification/component/notification/notification.component.scss b/src/packages/notification/component/notification/notification.component.scss index 2594319..ea8f92b 100644 --- a/src/packages/notification/component/notification/notification.component.scss +++ b/src/packages/notification/component/notification/notification.component.scss @@ -1,165 +1,3 @@ -$prefix: 'notification'; - - -.badge { - position: absolute; - top: 0; - left: 50%; - font-weight: 700; - line-height: 13px; - height: 13px; - padding: 5px; - border-radius: 26%; - width: 30%; - background-color: #f44336; - color: #fff; - border-color:#f44336 +.highlight { + background: #039be5 } - -.#{$prefix} { - &-container { - position: relative; - display: flex; - align-items: center; - } - - &-btn { - display: flex; - justify-content: center; - margin-right: 10px; - } -} -.dropdown { - background: white; - position: absolute; - top: 42px; - right: 28px; - min-width: 350px; - z-index: 2; - transform: translateY(0) scale(0); - transform-origin: top right; - visibility: hidden; - transition: transform .4s cubic-bezier(.25, .8, .25, 1), visibility .4s cubic-bezier(.25, .8, .25, 1); - - @media screen and (max-width: 599px) { - min-width: 50vw; - right: 5px; - transform: translateY(0); - visibility: hidden; - transition: transform .4s cubic-bezier(.25,.8,.25,1), visibility .4s cubic-bezier(.25,.8,.25,1); - } - - &.open { - transform: translateY(0) scale(1); - visibility: visible; - } - .card { - - .header { - background: #EEEEEE; - min-height: 54px; - padding-left: 16px; - padding-right: 8px; - color: #555; - display: flex; - justify-content: flex-start; - align-items: center; - align-content: center; - border-bottom: 1px solid #e0e0e0; - - .extra { - font-size: 12px; - color: #888; - } - } - } - .content { - overflow: hidden; - max-height: 256px; - - .notification { - min-height: 64px; - padding: 0 16px 0 14px; - position: relative; - color: #666; - cursor: pointer; - - .icon { - height: 28px; - width: 28px; - line-height: 28px; - font-size: 18px; - margin-right: 13px; - text-align: center; - border-radius: 50%; - background: #FFF; - color: #888; - border: 1px solid #EEE; - } - - .title { - font-weight: 500; - font-size: 14px; - } - - .time { - font-size: 12px; - } - - .close { - font-size: 18px; - width: 18px; - height: 18px; - line-height: 18px; - } - - &.primary { - .icon { - background: #ccc; - color: #ddd; - } - } - - &.accent { - .icon { - background: #aaa; - color: #bbb; - } - } - - &.warn { - .icon { - background: #eee; - color: #ddd; - } - } - - &.read { - color: #999; - - .name { - font-weight: normal; - } - } - } - } - - .footer { - min-height: 42px; - border-top: 1px solid #EEE; - - .action { - cursor: pointer; - color: #AAA; - text-align: center; - font-size: 13px; - } - } - - .divider { - width: calc(100% - 30px); - height: 1px; - background: #EEE; - margin: 0 16px 0 14px; - } -} \ No newline at end of file diff --git a/src/packages/notification/component/notification/notification.component.ts b/src/packages/notification/component/notification/notification.component.ts index 17afbeb..423bfcf 100644 --- a/src/packages/notification/component/notification/notification.component.ts +++ b/src/packages/notification/component/notification/notification.component.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router'; import { Store, select } from '@ngrx/store'; import { RPCError } from 'packages/core/rpc/error'; import { Notification } from '../../model'; -import * as NotificationStore from '../../store/notification'; +import * as NotificationStore from '../../store/list'; import { ReadAllByMemberSelector } from '../../store'; import { AuthSelector } from 'packages/member/store'; import { Member } from '../../../member/model'; @@ -65,7 +65,8 @@ export class NotificationComponent implements OnInit, AfterContentInit { } handleRowClick(n: Notification) { - this.router.navigate([n.url]); + alert('Will redirect to ' + n.url); + // this.router.navigate([n.url]); } handlePaging(e: PageEvent) { diff --git a/src/packages/notification/service/notification.service.ts b/src/packages/notification/service/notification.service.ts index 5dd4523..cfe535b 100644 --- a/src/packages/notification/service/notification.service.ts +++ b/src/packages/notification/service/notification.service.ts @@ -7,7 +7,7 @@ import { RPCClient } from 'packages/core/rpc/client/RPCClient'; import { Notification } from '../model'; import { Member } from '../../member/model'; -import { PageParams } from 'app/commons/model'; +import { PageParams, Page } from 'app/commons/model'; @Injectable() export class NotificationService { @@ -18,10 +18,16 @@ export class NotificationService { } - public readAllByMember(member: Member, pageParams: PageParams): Observable { - + public readAllByMember(member: Member, pageParams: PageParams): Observable { return this.rpcClient.call('NotificationService.readAllByMember', member, pageParams); } + public markAllAsRead(member: Member, pageParams: PageParams): Observable { + return this.rpcClient.call('NotificationService.markAllAsRead', member, pageParams); + } + + public markAsRead(notification: Notification): Observable { + return this.rpcClient.call('NotificationService.markAsRead', notification); + } } diff --git a/src/packages/notification/store/notification/index.ts b/src/packages/notification/store/detail/index.ts similarity index 100% rename from src/packages/notification/store/notification/index.ts rename to src/packages/notification/store/detail/index.ts diff --git a/src/packages/notification/store/detail/notification.action.ts b/src/packages/notification/store/detail/notification.action.ts new file mode 100644 index 0000000..c53dff0 --- /dev/null +++ b/src/packages/notification/store/detail/notification.action.ts @@ -0,0 +1,61 @@ +import { Action } from '@ngrx/store'; + +import { RPCError } from 'packages/core/rpc/error'; + +import { Member } from '../../../member/model'; +import { PageParams, Page } from 'app/commons/model'; +import { Notification } from '../../model'; + +export enum ActionType { + MarkAsRead = '[Notification.notification] MarkAsRead', + MarkAsReadSuccess = '[Notification.notification] MarkAsReadSuccess', + MarkAsReadFailure = '[Notification.notification] MarkAsReadFailure', + // ReadUnconfirmedCount = '[Notification.notification] ReadUnconfirmedCount', + // ReadUnconfirmedCountSuccess = '[Notification.notification] ReadUnconfirmedCountSuccess', + // ReadUnconfirmedCountFailure = '[Notification.notification] ReadUnconfirmedCountFailure', +} + +export class MarkAsRead implements Action { + readonly type = ActionType.MarkAsRead; + + constructor(public payload: Notification ) {} +} + +export class MarkAsReadSuccess implements Action { + readonly type = ActionType.MarkAsReadSuccess; + + constructor(public payload: Notification) {} +} + +export class MarkAsReadFailure implements Action { + readonly type = ActionType.MarkAsReadFailure; + + constructor(public payload: RPCError) {} +} + +// export class ReadUnconfirmedCount implements Action { +// readonly type = ActionType.ReadUnconfirmedCount; + +// constructor(public payload: Member) {} +// } + +// export class ReadUnconfirmedCountSuccess implements Action { +// readonly type = ActionType.ReadUnconfirmedCountSuccess; + +// constructor(public payload: number) {} +// } + +// export class ReadUnconfirmedCountFailure implements Action { +// readonly type = ActionType.ReadUnconfirmedCountFailure; + +// constructor(public payload: RPCError) {} +// } + +export type Actions = + | MarkAsRead + | MarkAsReadSuccess + | MarkAsReadFailure + // | ReadUnconfirmedCount + // | ReadUnconfirmedCountSuccess + // | ReadUnconfirmedCountFailure +; diff --git a/src/packages/notification/store/notification/notification.effect.spec.ts b/src/packages/notification/store/detail/notification.effect.spec.ts similarity index 100% rename from src/packages/notification/store/notification/notification.effect.spec.ts rename to src/packages/notification/store/detail/notification.effect.spec.ts diff --git a/src/packages/notification/store/detail/notification.effect.ts b/src/packages/notification/store/detail/notification.effect.ts new file mode 100644 index 0000000..9cf9be0 --- /dev/null +++ b/src/packages/notification/store/detail/notification.effect.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Action } from '@ngrx/store'; + +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; + +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/exhaustMap'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/take'; + +import { RPCError } from 'packages/core/rpc/error'; + +import { Notification } from '../../model'; +import { NotificationService } from '../../service/notification.service'; + +import { + MarkAsRead, + MarkAsReadSuccess, + MarkAsReadFailure, + ActionType, +} from './notification.action'; + +@Injectable() +export class Effects { + + constructor( + private actions$: Actions, + private notificationService: NotificationService, + private router: Router + ) { } + + + @Effect() + markAsRead$: Observable = this.actions$ + .ofType(ActionType.MarkAsRead) + .map((action: MarkAsRead) => action.payload) + .switchMap(payload => this.notificationService.markAsRead(payload)) + .map(notification => { + return new MarkAsReadSuccess(notification); + }) + .catch((error: RPCError) => { + console.log(error.message); + return of(new MarkAsReadFailure(error)); + }); +} diff --git a/src/packages/notification/store/detail/notification.reducer.ts b/src/packages/notification/store/detail/notification.reducer.ts new file mode 100644 index 0000000..f329612 --- /dev/null +++ b/src/packages/notification/store/detail/notification.reducer.ts @@ -0,0 +1,44 @@ +import { + Actions, + ActionType, +} from './notification.action'; + +import { + State, + initialState, +} from './notification.state'; + +export function reducer(state = initialState, action: Actions): State { + switch (action.type) { + case ActionType.MarkAsRead: { + return { + ...state, + error: null, + pending: true, + }; + } + + case ActionType.MarkAsReadSuccess: { + return { + ...state, + error: null, + pending: false, + notification: action.payload, + }; + } + + case ActionType.MarkAsReadFailure: { + return { + ...state, + error: action.payload, + pending: false, + notification: null, + }; + } + + + default: { + return state; + } + } +} diff --git a/src/packages/notification/store/detail/notification.state.ts b/src/packages/notification/store/detail/notification.state.ts new file mode 100644 index 0000000..057b3c8 --- /dev/null +++ b/src/packages/notification/store/detail/notification.state.ts @@ -0,0 +1,14 @@ +import { RPCError } from 'packages/core/rpc/error'; +import { Notification } from '../../model'; + +export interface State { + error: RPCError | null; + pending: boolean; + notification: Notification; +} + +export const initialState: State = { + error: null, + pending: false, + notification: null, +}; diff --git a/src/packages/notification/store/index.ts b/src/packages/notification/store/index.ts index b22f27e..157fe40 100644 --- a/src/packages/notification/store/index.ts +++ b/src/packages/notification/store/index.ts @@ -1,30 +1,38 @@ import { - createSelector, - createFeatureSelector, - ActionReducerMap, - } from '@ngrx/store'; + createSelector, + createFeatureSelector, + ActionReducerMap, +} from '@ngrx/store'; - import { StateSelector } from 'packages/core/ngrx/store'; +import { StateSelector } from 'packages/core/ngrx/store'; - import { MODULE } from '../notification.constant'; +import { MODULE } from '../notification.constant'; - import * as NotificationStore from './notification'; +import * as ListStore from './list'; +import * as DetailStore from './detail'; - export interface State { - notifications: NotificationStore.State; - } +export interface State { + notifications: ListStore.State; + notification: DetailStore.State; +} - export const REDUCERS = { - notifications: NotificationStore.reducer, - }; +export const REDUCERS = { + notifications: ListStore.reducer, + notification: DetailStore.reducer, +}; - export const EFFECTS = [ - NotificationStore.Effects, - ]; +export const EFFECTS = [ + ListStore.Effects, + DetailStore.Effects, +]; - export const selectNotificationState = createFeatureSelector(MODULE.name); +export const selectNotificationState = createFeatureSelector(MODULE.name); - export const ReadAllByMemberSelector = new StateSelector(createSelector( +export const ReadAllByMemberSelector = new StateSelector(createSelector( + selectNotificationState, + (state: State) => state.notifications +)); + export const ReadSelector = new StateSelector(createSelector( selectNotificationState, - (state: State) => state.notifications + (state: State) => state.notification )); diff --git a/src/packages/notification/store/list/index.ts b/src/packages/notification/store/list/index.ts new file mode 100644 index 0000000..c7467b0 --- /dev/null +++ b/src/packages/notification/store/list/index.ts @@ -0,0 +1,4 @@ +export * from './notification.action'; +export * from './notification.effect'; +export * from './notification.reducer'; +export * from './notification.state'; diff --git a/src/packages/notification/store/notification/notification.action.ts b/src/packages/notification/store/list/notification.action.ts similarity index 55% rename from src/packages/notification/store/notification/notification.action.ts rename to src/packages/notification/store/list/notification.action.ts index 5729986..6b3d2b6 100644 --- a/src/packages/notification/store/notification/notification.action.ts +++ b/src/packages/notification/store/list/notification.action.ts @@ -9,6 +9,9 @@ export enum ActionType { ReadAllByMember = '[Notification.notification] ReadAllByMember', ReadAllByMemberSuccess = '[Notification.notification] ReadAllByMemberSuccess', ReadAllByMemberFailure = '[Notification.notification] ReadAllByMemberFailure', + MarkAllAsRead = '[Notification.notification] MarkAllAsRead', + MarkAllAsReadSuccess = '[Notification.notification] MarkAllAsReadSuccess', + MarkAllAsReadFailure = '[Notification.notification] MarkAllAsReadFailure', } export class ReadAllByMember implements Action { @@ -20,7 +23,7 @@ export class ReadAllByMember implements Action { export class ReadAllByMemberSuccess implements Action { readonly type = ActionType.ReadAllByMemberSuccess; - constructor(public payload: any) {} + constructor(public payload: Page) {} } export class ReadAllByMemberFailure implements Action { @@ -29,8 +32,30 @@ export class ReadAllByMemberFailure implements Action { constructor(public payload: RPCError) {} } +export class MarkAllAsRead implements Action { + readonly type = ActionType.MarkAllAsRead; + + constructor(public payload: { member: Member, pageParams: PageParams }) {} +} + +export class MarkAllAsReadSuccess implements Action { + readonly type = ActionType.MarkAllAsReadSuccess; + + constructor(public payload: Page) {} +} + +export class MarkAllAsReadFailure implements Action { + readonly type = ActionType.MarkAllAsReadFailure; + + constructor(public payload: RPCError) {} +} + + export type Actions = | ReadAllByMember | ReadAllByMemberSuccess | ReadAllByMemberFailure + | MarkAllAsRead + | MarkAllAsReadSuccess + | MarkAllAsReadFailure ; diff --git a/src/packages/notification/store/list/notification.effect.spec.ts b/src/packages/notification/store/list/notification.effect.spec.ts new file mode 100644 index 0000000..4bbc6cf --- /dev/null +++ b/src/packages/notification/store/list/notification.effect.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { Effects } from './notification.effect'; + +describe('Notification.Effects', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [Effects] + }); + }); + + it('should be created', inject([Effects], (effects: Effects) => { + expect(effects).toBeTruthy(); + })); +}); diff --git a/src/packages/notification/store/notification/notification.effect.ts b/src/packages/notification/store/list/notification.effect.ts similarity index 70% rename from src/packages/notification/store/notification/notification.effect.ts rename to src/packages/notification/store/list/notification.effect.ts index 46f544e..2a8b240 100644 --- a/src/packages/notification/store/notification/notification.effect.ts +++ b/src/packages/notification/store/list/notification.effect.ts @@ -23,6 +23,9 @@ import { ReadAllByMemberSuccess, ReadAllByMemberFailure, ActionType, + MarkAllAsRead, + MarkAllAsReadSuccess, + MarkAllAsReadFailure, } from './notification.action'; @Injectable() @@ -36,14 +39,6 @@ export class Effects { @Effect() readAllByMember$: Observable = this.actions$ - // .ofType(ActionType.ReadAllByMember) - // .map((action: ReadAllByMember) => action.payload) - // .exhaustMap(member => - // this.notificationService - // .readAllByMember(member) - // .map(notificationList => new ReadAllByMemberSuccess(notificationList)) - // .catch(error => of(new ReadAllByMemberFailure(error))) - // ); .ofType(ActionType.ReadAllByMember) .map((action: ReadAllByMember) => action.payload) .switchMap(payload => this.notificationService.readAllByMember(payload.member, payload.pageParams)) @@ -54,4 +49,16 @@ export class Effects { return of(new ReadAllByMemberFailure(error)); }); + @Effect() + markAllAsRead$: Observable = this.actions$ + .ofType(ActionType.MarkAllAsRead) + .map((action: MarkAllAsRead) => action.payload) + .switchMap(payload => this.notificationService.markAllAsRead(payload.member, payload.pageParams)) + .map(page => { + return new MarkAllAsReadSuccess(page); + }) + .catch((error: RPCError) => { + console.log('errrrrrrrrrrrrrr : ' + error.message); + return of(new MarkAllAsReadFailure(error)); + }); } diff --git a/src/packages/notification/store/list/notification.reducer.ts b/src/packages/notification/store/list/notification.reducer.ts new file mode 100644 index 0000000..a804558 --- /dev/null +++ b/src/packages/notification/store/list/notification.reducer.ts @@ -0,0 +1,69 @@ +import { + Actions, + ActionType, +} from './notification.action'; + +import { + State, + initialState, +} from './notification.state'; + +export function reducer(state = initialState, action: Actions): State { + switch (action.type) { + case ActionType.ReadAllByMember: { + return { + ...state, + error: null, + pending: true, + }; + } + + case ActionType.ReadAllByMemberSuccess: { + return { + ...state, + error: null, + pending: false, + page: action.payload, + }; + } + + case ActionType.ReadAllByMemberFailure: { + return { + ...state, + error: action.payload, + pending: false, + page: null, + }; + } + + case ActionType.MarkAllAsRead: { + return { + ...state, + error: null, + pending: true, + }; + } + + case ActionType.MarkAllAsReadSuccess: { + return { + ...state, + error: null, + pending: false, + page: action.payload, + }; + } + + case ActionType.MarkAllAsReadFailure: { + return { + ...state, + error: action.payload, + pending: false, + page: null, + }; + } + + default: { + return state; + } + } +} diff --git a/src/packages/notification/store/notification/notification.state.ts b/src/packages/notification/store/list/notification.state.ts similarity index 70% rename from src/packages/notification/store/notification/notification.state.ts rename to src/packages/notification/store/list/notification.state.ts index 6e576ef..0b0c4e7 100644 --- a/src/packages/notification/store/notification/notification.state.ts +++ b/src/packages/notification/store/list/notification.state.ts @@ -1,18 +1,14 @@ import { RPCError } from 'packages/core/rpc/error'; - -import { Notification } from '../../model'; import { Page } from 'app/commons/model'; export interface State { error: RPCError | null; pending: boolean; page: Page; - // notifications: Notification[] | null; } export const initialState: State = { error: null, pending: false, page: null, - // notifications: null, }; diff --git a/src/packages/notification/store/notification/notification.reducer.ts b/src/packages/notification/store/notification/notification.reducer.ts deleted file mode 100644 index 56bd2dd..0000000 --- a/src/packages/notification/store/notification/notification.reducer.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - Actions, - ActionType, - } from './notification.action'; - - import { - State, - initialState, - } from './notification.state'; - - export function reducer(state = initialState, action: Actions): State { - switch (action.type) { - case ActionType.ReadAllByMember: { - return { - ...state, - error: null, - pending: true, - }; - } - - case ActionType.ReadAllByMemberSuccess: { - return { - ...state, - error: null, - pending: false, - page: action.payload, - }; - } - - case ActionType.ReadAllByMemberFailure: { - return { - ...state, - error: action.payload, - pending: false, - page: null, - }; - } - - default: { - return state; - } - } - }