공지사항 기능 추가.

This commit is contained in:
leejinho 2019-12-03 14:32:50 +09:00
parent ec60e33af1
commit 5ac76edadd
22 changed files with 563 additions and 6 deletions

View File

@ -0,0 +1,53 @@
import {
APIRequest,
MessageAPIResponse,
APIDecoder,
APIJsonEncoder
} from '@ucap-webmessenger/api';
import { NoticeList } from '../models/notice-list';
export interface RetrieveNoticeRequest extends APIRequest {
userSeq: number;
companyCode: string;
pageSize: number;
pageCount: number;
}
export interface RetrieveNoticeResponse extends MessageAPIResponse {
pageSize: number;
pageCount: number;
totalCount: number;
noticeList: NoticeList[];
}
export const encodeRetrieveNotice: APIJsonEncoder<RetrieveNoticeRequest> = (
req: RetrieveNoticeRequest
) => {
return JSON.stringify(req);
};
export const decodeRetrieveNotice: APIDecoder<RetrieveNoticeResponse> = (
res: any
) => {
const noticeList: NoticeList[] = [];
if (!!res.noticeList && res.noticeList.length > 0) {
for (const notice of res.noticeList) {
noticeList.push({
...notice,
topYn: notice.topYn === 'Y' ? true : false
} as NoticeList);
}
}
return {
responseCode: res.responseCode,
responseMsg: res.responseMsg,
pageCount: res.pageCount,
pageSize: res.pageSize,
totalCount: res.totalCount,
noticeList
} as RetrieveNoticeResponse;
};

View File

@ -44,4 +44,7 @@ export interface Urls {
/** 읽지 않은 메시지 개수 조회 */
retrieveUnreadCount: string;
/** 공지 조회 */
retrieveNoticeList: string;
}

View File

@ -0,0 +1,12 @@
export interface NoticeList {
/** 공지 번호 */
noticeSeq: number;
/** 공지 제목 */
title: string;
/** 공지 내용 */
content: string;
/** 상단 표시 여부 */
topYn: boolean;
/** 공지 등록일자 */
regDate: string;
}

View File

@ -91,6 +91,12 @@ import {
encodeUnreadCount,
decodeUnreadCount
} from '../apis/unread-count';
import {
RetrieveNoticeRequest,
RetrieveNoticeResponse,
encodeRetrieveNotice,
decodeRetrieveNotice
} from '../apis/notice';
@Injectable({
providedIn: 'root'
@ -309,4 +315,15 @@ export class MessageApiService {
})
.pipe(map(res => decodeUnreadCount(res)));
}
/** NoticeList */
public retrieveNotice(
req: RetrieveNoticeRequest
): Observable<RetrieveNoticeResponse> {
return this.httpClient
.post<any>(this.urls.retrieveNoticeList, encodeRetrieveNotice(req), {
headers: this.headers
})
.pipe(map(res => decodeRetrieveNotice(res)));
}
}

View File

@ -16,6 +16,7 @@ export * from './lib/ucap-message-api.module';
export * from './lib/models/detail-receiver';
export * from './lib/models/detail-content';
export * from './lib/models/message-list';
export * from './lib/models/notice-list';
export * from './lib/types/category.type';
export * from './lib/types/content.type';

View File

@ -28,5 +28,7 @@
<!-- [E] About Chat room -->
<!-- [S] About Common -->
<app-layout-chat-right-drawer-notice *ngSwitchCase="RightDrawer.Notice">
</app-layout-chat-right-drawer-notice>
<!-- [E] About Common -->
</ng-container>

View File

@ -40,7 +40,10 @@
</div>
<ul>
<li class="name">{{ selectedFile.info.name }}</li>
<li><span class="text-accent-color">size :</span> {{ selectedFile.info.size | ucapBytes }}</li>
<li>
<span class="text-accent-color">size :</span>
{{ selectedFile.info.size | ucapBytes }}
</li>
<li>
<span class="text-accent-color">date :</span>
{{ selectedFile.info.sendDate | dateToStringFormat: 'YYYY.MM.DD' }}
@ -167,7 +170,10 @@
{{ element.info.size | ucapBytes }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-header-row
*matHeaderRowDef="displayedColumns; sticky: true"
></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"

View File

@ -1,9 +1,12 @@
import { FileBoxComponent } from './file-box.component';
import { AlbumBoxComponent } from './album-box.component';
import { RoomUserListComponent } from './room-user-list.component';
import { NoticeComponent } from './notice.component';
export const RIGHT_DRAWER_COMPONENTS = [
FileBoxComponent,
AlbumBoxComponent,
RoomUserListComponent
RoomUserListComponent,
NoticeComponent
];

View File

@ -0,0 +1,54 @@
<div fxLayout="column" class="rightDrawer-notice">
<div class="table-box">
<div style="position: relative;">
<div
*ngIf="isLoadingResults"
style="position: absolute; width: 100%; z-index: 101;"
>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
</div>
<perfect-scrollbar class="album-scrollbar">
<table mat-table [dataSource]="noticelist">
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef class="infos">
제목
</th>
<td mat-cell *matCellDef="let element" class="notice-info">
<div class="title">
<span class="mdi mdi-star important" *ngIf="element.topYn"></span>
{{ element.title }}
</div>
</td>
</ng-container>
<ng-container matColumnDef="regDate">
<th mat-header-cell *matHeaderCellDef>
게시일
</th>
<td mat-cell *matCellDef="let element" class="date">
<div class="date">
{{ element.regDate }}
</div>
</td>
</ng-container>
<tr
mat-header-row
*matHeaderRowDef="displayedColumns; sticky: true"
></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="onClickDetail(row)"
></tr>
</table>
</perfect-scrollbar>
</div>
<div fxFlex="1 1 111.5px" class="footer-fix">
<mat-paginator
[length]="totalCount"
[pageSize]="10"
[pageSizeOptions]="[10, 20, 30]"
showFirstLastButtons
></mat-paginator>
</div>
</div>

View File

@ -0,0 +1,105 @@
@mixin ellipsis($row) {
overflow: hidden;
text-overflow: ellipsis;
@if $row == 1 {
display: block;
white-space: nowrap;
word-wrap: normal;
} @else if $row >= 2 {
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
word-wrap: break-word;
}
}
.rightDrawer-notice {
width: 100%;
height: calc(100% - 60px);
.table-box {
height: calc(100% - 111.5px);
overflow: auto;
}
}
.mat-table {
width: 100%;
position: relative;
th.infos {
padding: 10px;
}
tr.mat-row {
height: 70px;
.notice-info {
padding: 16px;
display: grid;
height: 70px;
.title {
font-weight: 600;
margin-bottom: 2px;
width: 50%;
@include ellipsis(1);
.important {
color: red;
}
}
}
}
}
.mat-paginator-container {
display: flex;
flex-flow: column;
}
.mat-row:hover {
background: rgba(0, 0, 0, 0.04);
cursor: pointer;
}
.footer-fix {
position: absolute;
bottom: 0;
flex-direction: column;
box-sizing: border-box;
display: flex;
border-top: 1px solid #dddddd;
.btn-box {
height: 50px;
padding-bottom: 10px;
width: 100%;
background-color: #ffffff;
button {
margin: 5px;
}
}
}
.mat-paginator {
.mat-paginator-container {
justify-content: center;
}
.mat-paginator-navigation-first {
order: 1;
}
.mat-paginator-navigation-previous {
order: 2;
}
// override material paginator page switch
.mat-paginator-range-label {
order: 3;
}
.mat-paginator-navigation-next {
order: 4;
}
.mat-paginator-navigation-last {
order: 5;
}
}
.mat-form-field-appearance-legacy {
.mat-form-field-infix {
padding: 6px;
}
}

View File

@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoticeComponent } from './notice.component';
describe('NoticeComponent', () => {
let component: NoticeComponent;
let fixture: ComponentFixture<NoticeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NoticeComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NoticeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,112 @@
import {
Component,
OnInit,
OnDestroy,
ViewChild,
AfterViewInit
} from '@angular/core';
import { of, merge, Subscription } from 'rxjs';
import { Store } from '@ngrx/store';
import { map, catchError, startWith, switchMap } from 'rxjs/operators';
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
import { DialogService } from '@ucap-webmessenger/ui';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type';
import { MessageApiService, NoticeList } from '@ucap-webmessenger/api-message';
import { RetrieveNoticeRequest } from 'projects/ucap-webmessenger-api-message/src/lib/apis/notice';
import { NGXLogger } from 'ngx-logger';
import { MatPaginator } from '@angular/material';
import {
NoticeDetailDialogComponent,
NoticeDetailDialogData
} from '../../dialogs/notice/notice-detail.dialog.component';
@Component({
selector: 'app-layout-chat-right-drawer-notice',
templateUrl: './notice.component.html',
styleUrls: ['./notice.component.scss']
})
export class NoticeComponent implements OnInit, OnDestroy, AfterViewInit {
loginRes: LoginResponse;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
displayedColumns: string[] = ['title', 'regDate'];
isLoadingResults = true;
isRateLimitReached = false;
defaultPageSize = 10; // default
totalCount = 0;
currentPage = 0;
noticelist: NoticeList[] = [];
noticeListSubscription: Subscription;
constructor(
private store: Store<any>,
private sessionStorageService: SessionStorageService,
private dialogService: DialogService,
private messageApiService: MessageApiService,
private logger: NGXLogger
) {
this.loginRes = this.sessionStorageService.get<LoginResponse>(
KEY_LOGIN_RES_INFO
);
}
ngOnInit() {
// this.getRetrieveNotice(this.currentPage);
}
ngOnDestroy(): void {
if (!!this.noticeListSubscription) {
this.noticeListSubscription.unsubscribe();
}
}
ngAfterViewInit(): void {
this.noticeListSubscription = merge(this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.messageApiService.retrieveNotice({
userSeq: this.loginRes.userSeq,
companyCode: this.loginRes.companyCode,
pageSize: this.defaultPageSize,
pageCount: this.paginator.pageIndex
} as RetrieveNoticeRequest);
}),
map(data => {
// Flip flag to show that loading has finished.
this.isLoadingResults = false;
this.isRateLimitReached = false;
this.totalCount = data.totalCount;
this.currentPage = data.pageCount;
this.noticelist = data.noticeList;
return data.noticeList;
}),
catchError(() => {
this.isLoadingResults = false;
// Catch if the GitHub API has reached its rate limit. Return empty data.
this.isRateLimitReached = true;
return of([]);
})
)
.subscribe(data => (this.noticelist = data));
}
onClickDetail(notice: NoticeList) {
const result = this.dialogService.open<
NoticeDetailDialogComponent,
NoticeDetailDialogData
>(NoticeDetailDialogComponent, {
disableClose: false,
width: '550px',
data: {
notice
}
});
}
}

View File

@ -2,6 +2,7 @@ import { DIALOGS as ACCOUNT_DIALOGS } from './account';
import { DIALOGS as CHAT_DIALOGS } from './chat';
import { DIALOGS as GROUP_DIALOGS } from './group';
import { DIALOGS as MESSAGE_DIALOGS } from './message';
import { DIALOGS as NOTICE_DIALOGS } from './notice';
import { DIALOGS as PROFILE_DIALOGS } from './profile';
import { DIALOGS as SETTINGS_DIALOGS } from './settings';
@ -10,6 +11,7 @@ export const DIALOGS = [
...CHAT_DIALOGS,
...GROUP_DIALOGS,
...MESSAGE_DIALOGS,
...NOTICE_DIALOGS,
...PROFILE_DIALOGS,
...SETTINGS_DIALOGS
];

View File

@ -0,0 +1,3 @@
import { NoticeDetailDialogComponent } from './notice-detail.dialog.component';
export const DIALOGS = [NoticeDetailDialogComponent];

View File

@ -0,0 +1,21 @@
<mat-card class="confirm-card mat-elevation-z">
<mat-card-header>
<mat-card-title class="title">
<span *ngIf="data.notice.topYn"> [중요] </span>
{{ data.notice.title }}
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div>
{{ data.notice.regDate }}
</div>
<perfect-scrollbar>
<p [innerHTML]="data.notice.content | linky" class="contnets"></p>
</perfect-scrollbar>
</mat-card-content>
<mat-card-actions class="button-farm flex-row">
<button mat-stroked-button (click)="onClickConfirm()" class="mat-primary">
confirm
</button>
</mat-card-actions>
</mat-card>

View File

@ -0,0 +1,45 @@
@mixin ellipsis($row) {
overflow: hidden;
text-overflow: ellipsis;
@if $row == 1 {
display: block;
white-space: nowrap;
word-wrap: normal;
} @else if $row >= 2 {
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
word-wrap: break-word;
}
}
::ng-deep .mat-card-header-text {
margin: 0;
.title {
width: 480px;
@include ellipsis(1);
}
}
.confirm-card {
min-width: 500px;
.mat-card-header {
margin-bottom: 20px;
.mat-card-header-text {
.mat-card-title {
margin: 0 -16px;
}
}
}
.button-farm {
text-align: right;
.mat-primary {
margin-left: 4px;
}
}
}
.contnets {
max-height: 500px;
min-height: 400px;
word-break: break-word;
}

View File

@ -0,0 +1,27 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { NoticeDetailDialogComponent } from './notice-detail.dialog.component';
describe('ProfileDialogComponent', () => {
let component: NoticeDetailDialogComponent;
let fixture: ComponentFixture<NoticeDetailDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NoticeDetailDialogComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NoticeDetailDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,30 @@
import { Component, OnInit, Inject, ViewChild, OnDestroy } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { NoticeList } from '@ucap-webmessenger/api-message';
export interface NoticeDetailDialogData {
notice: NoticeList;
}
export interface NoticeDetailDialogResult {}
@Component({
selector: 'app-notice-detail.dialog',
templateUrl: './notice-detail.dialog.component.html',
styleUrls: ['./notice-detail.dialog.component.scss']
})
export class NoticeDetailDialogComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<
NoticeDetailDialogData,
NoticeDetailDialogResult
>,
@Inject(MAT_DIALOG_DATA) public data: NoticeDetailDialogData
) {}
ngOnInit() {}
onClickConfirm(): void {
this.dialogRef.close();
}
}

View File

@ -8,6 +8,28 @@
<button
mat-icon-button
class="button app-layout-native-title-bar-setting"
matTooltip="공지사항"
(click)="onClickNotice()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
alt="공지사항"
>
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
></path>
</svg>
</button>
<button
mat-icon-button
class="button app-layout-native-title-bar-setting"

View File

@ -8,11 +8,13 @@ import { Observable, Subscription } from 'rxjs';
import { Store, select } from '@ngrx/store';
import * as AppStore from '@app/store';
import * as ChatStore from '@app/store/messenger/chat';
import * as AuthenticationStore from '@app/store/account/authentication';
import * as SettingsStore from '@app/store/messenger/settings';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { tap } from 'rxjs/operators';
import { RightDrawer } from '@app/types';
@Component({
selector: 'app-layout-native-top-bar',
@ -65,4 +67,12 @@ export class TopBarComponent implements OnInit, OnDestroy {
onClickLogout(): void {
this.store.dispatch(AuthenticationStore.logoutConfirmation());
}
onClickNotice(): void {
this.store.dispatch(
ChatStore.selectedRightDrawer({
req: RightDrawer.Notice
})
);
}
}

View File

@ -4,5 +4,8 @@ export enum RightDrawer {
/** 대화방 > 파일함 */
FileBox = 'FILE_BOX',
/** 대화방 > 대화참여자목록 */
RoomUser = 'ROOM_USER'
RoomUser = 'ROOM_USER',
/** 공지사항 */
Notice = 'NOTICE'
}

View File

@ -102,7 +102,9 @@ export const messageApiUrls: MessageApiUrls = {
sendCopyMessage: '/uCapMsg/msg/sendCopyMessage.do',
retrieveUnreadCount: '/uCapMsg/msg/retrieveUnreadCount.do'
retrieveUnreadCount: '/uCapMsg/msg/retrieveUnreadCount.do',
retrieveNoticeList: '/uCapMsg/notice/retrieveNoticeList.do'
};
export const piUrls: PiUrls = {
login2: '/uCapPi/login2',