예약쪽지 수정 기능 구현.
This commit is contained in:
parent
bc037c6d52
commit
967d25c155
|
@ -2,7 +2,10 @@ import {
|
||||||
APIRequest,
|
APIRequest,
|
||||||
MessageAPIResponse,
|
MessageAPIResponse,
|
||||||
APIJsonEncoder,
|
APIJsonEncoder,
|
||||||
APIDecoder
|
APIDecoder,
|
||||||
|
FileUploadItem,
|
||||||
|
ParameterUtil,
|
||||||
|
APIFormDataEncoder
|
||||||
} from '@ucap-webmessenger/api';
|
} from '@ucap-webmessenger/api';
|
||||||
import { DeviceType } from '@ucap-webmessenger/core';
|
import { DeviceType } from '@ucap-webmessenger/core';
|
||||||
import { CategoryType } from '../types/category.type';
|
import { CategoryType } from '../types/category.type';
|
||||||
|
@ -17,19 +20,69 @@ export interface EditReservationRequest extends APIRequest {
|
||||||
title: string;
|
title: string;
|
||||||
titleYn: boolean;
|
titleYn: boolean;
|
||||||
listOrder: ContentType[];
|
listOrder: ContentType[];
|
||||||
|
resSeqList: number[];
|
||||||
reservationTime: string;
|
reservationTime: string;
|
||||||
smsYn: boolean;
|
smsYn?: boolean;
|
||||||
|
|
||||||
textContent: { text: string }[];
|
textContent: { text: string }[];
|
||||||
recvUserList: { userSeq: number; userName: string }[];
|
recvUserList: { userSeq: number; userName: string }[];
|
||||||
|
|
||||||
|
files?: File[];
|
||||||
|
fileUploadItem?: FileUploadItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditReservationResponse extends MessageAPIResponse {}
|
export interface EditReservationResponse extends MessageAPIResponse {}
|
||||||
|
|
||||||
export const encodeEditReservation: APIJsonEncoder<EditReservationRequest> = (
|
const editReservationEncodeMap = {
|
||||||
|
userSeq: 'userSeq',
|
||||||
|
deviceType: 'deviceType',
|
||||||
|
tokenKey: 'tokenKey',
|
||||||
|
msgId: 'msgId',
|
||||||
|
category: 'category',
|
||||||
|
title: 'title',
|
||||||
|
titleYn: 'titleYn',
|
||||||
|
listOrder: 'listOrder',
|
||||||
|
resSeqList: 'resSeqList',
|
||||||
|
reservationTime: 'reservationTime',
|
||||||
|
smsYn: 'smsYn',
|
||||||
|
textContent: 'textContent',
|
||||||
|
recvUserList: 'recvUserList',
|
||||||
|
files: 'files'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const encodeEditReservation: APIFormDataEncoder<EditReservationRequest> = (
|
||||||
req: EditReservationRequest
|
req: EditReservationRequest
|
||||||
) => {
|
) => {
|
||||||
return JSON.stringify(req);
|
const extraParams: any = {};
|
||||||
|
|
||||||
|
extraParams.userSeq = String(req.userSeq);
|
||||||
|
if (!!req.titleYn) {
|
||||||
|
extraParams.titleYn = req.titleYn ? 'Y' : 'N';
|
||||||
|
}
|
||||||
|
if (!!req.smsYn) {
|
||||||
|
extraParams.smsYn = req.smsYn ? 'Y' : 'N';
|
||||||
|
}
|
||||||
|
if (!!req.listOrder) {
|
||||||
|
let s = '';
|
||||||
|
req.listOrder.forEach(v => {
|
||||||
|
s = s + String(v);
|
||||||
|
});
|
||||||
|
extraParams.listOrder = s;
|
||||||
|
}
|
||||||
|
if (!!req.resSeqList) {
|
||||||
|
extraParams.resSeqList = req.resSeqList.map(v => v.toString()).join(',');
|
||||||
|
}
|
||||||
|
if (!!req.textContent) {
|
||||||
|
extraParams.textContent = JSON.stringify(req.textContent);
|
||||||
|
}
|
||||||
|
if (!!req.recvUserList) {
|
||||||
|
extraParams.recvUserList = JSON.stringify(req.recvUserList);
|
||||||
|
}
|
||||||
|
return ParameterUtil.encodeFormData(
|
||||||
|
editReservationEncodeMap,
|
||||||
|
req,
|
||||||
|
extraParams
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decodeEditReservation: APIDecoder<EditReservationResponse> = (
|
export const decodeEditReservation: APIDecoder<EditReservationResponse> = (
|
||||||
|
|
|
@ -188,6 +188,34 @@ export class MessageApiService {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/** edit reservation */
|
||||||
|
public editReservationMessageEx(
|
||||||
|
req: EditReservationRequest
|
||||||
|
): Observable<EditReservationResponse> {
|
||||||
|
const httpReq = new HttpRequest(
|
||||||
|
'POST',
|
||||||
|
this.urls.editReservationMessageEx,
|
||||||
|
encodeEditReservation(req),
|
||||||
|
{ reportProgress: true, responseType: 'text' as 'json' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const progress = req.fileUploadItem.uploadStart();
|
||||||
|
|
||||||
|
return this.httpClient.request(httpReq).pipe(
|
||||||
|
filter(event => {
|
||||||
|
if (event instanceof HttpResponse) {
|
||||||
|
return true;
|
||||||
|
} else if (HttpEventType.UploadProgress === event.type) {
|
||||||
|
progress.next(Math.round((100 * event.loaded) / event.total));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
map((event: HttpResponse<any>) => {
|
||||||
|
req.fileUploadItem.uploadComplete();
|
||||||
|
return decodeSend(event.body);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** detail */
|
/** detail */
|
||||||
public detailMessage(req: DetailRequest): Observable<DetailResponse> {
|
public detailMessage(req: DetailRequest): Observable<DetailResponse> {
|
||||||
|
@ -299,21 +327,6 @@ export class MessageApiService {
|
||||||
.pipe(map(res => decodeEditMy(res)));
|
.pipe(map(res => decodeEditMy(res)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** edit reservation */
|
|
||||||
public editReservationMessageEx(
|
|
||||||
req: EditReservationRequest
|
|
||||||
): Observable<EditReservationResponse> {
|
|
||||||
return this.httpClient
|
|
||||||
.post<any>(
|
|
||||||
this.urls.editReservationMessageEx,
|
|
||||||
encodeEditReservation(req),
|
|
||||||
{
|
|
||||||
headers: this.headers
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.pipe(map(res => decodeEditReservation(res)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** send-copy(forward) */
|
/** send-copy(forward) */
|
||||||
public sendCopyMessage(req: SendCopyRequest): Observable<SendCopyResponse> {
|
public sendCopyMessage(req: SendCopyRequest): Observable<SendCopyResponse> {
|
||||||
return this.httpClient
|
return this.httpClient
|
||||||
|
|
|
@ -199,6 +199,7 @@
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
>
|
>
|
||||||
<app-layout-chat-left-sidenav-message
|
<app-layout-chat-left-sidenav-message
|
||||||
|
#messageBoxComponent
|
||||||
[isVisible]="currentTabLable === MainMenu.Message"
|
[isVisible]="currentTabLable === MainMenu.Message"
|
||||||
(doRefreshUnReadCount)="getMessageUnreadCount()"
|
(doRefreshUnReadCount)="getMessageUnreadCount()"
|
||||||
></app-layout-chat-left-sidenav-message>
|
></app-layout-chat-left-sidenav-message>
|
||||||
|
|
|
@ -7,7 +7,8 @@ import {
|
||||||
ViewChildren,
|
ViewChildren,
|
||||||
QueryList,
|
QueryList,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
OnDestroy
|
OnDestroy,
|
||||||
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NGXLogger } from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
import { ucapAnimations, DialogService } from '@ucap-webmessenger/ui';
|
import { ucapAnimations, DialogService } from '@ucap-webmessenger/ui';
|
||||||
|
@ -34,7 +35,7 @@ import { SessionStorageService } from '@ucap-webmessenger/web-storage';
|
||||||
import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type';
|
import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type';
|
||||||
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
|
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
|
||||||
import { KEY_VER_INFO } from '@app/types/ver-info.type';
|
import { KEY_VER_INFO } from '@app/types/ver-info.type';
|
||||||
import { MessageApiService } from '@ucap-webmessenger/api-message';
|
import { MessageApiService, MessageType } from '@ucap-webmessenger/api-message';
|
||||||
import { DeviceType } from '@ucap-webmessenger/core';
|
import { DeviceType } from '@ucap-webmessenger/core';
|
||||||
import { UnreadCountRequest } from 'projects/ucap-webmessenger-api-message/src/lib/apis/unread-count';
|
import { UnreadCountRequest } from 'projects/ucap-webmessenger-api-message/src/lib/apis/unread-count';
|
||||||
import { map, catchError, tap } from 'rxjs/operators';
|
import { map, catchError, tap } from 'rxjs/operators';
|
||||||
|
@ -45,6 +46,7 @@ import {
|
||||||
MessageWriteDialogData
|
MessageWriteDialogData
|
||||||
} from '../dialogs/message/message-write.dialog.component';
|
} from '../dialogs/message/message-write.dialog.component';
|
||||||
import { EnvironmentsInfo, KEY_ENVIRONMENTS_INFO } from '@app/types';
|
import { EnvironmentsInfo, KEY_ENVIRONMENTS_INFO } from '@app/types';
|
||||||
|
import { MessageBoxComponent } from './left-sidenav/message.component';
|
||||||
|
|
||||||
export enum MainMenu {
|
export enum MainMenu {
|
||||||
Group = 'GROUP',
|
Group = 'GROUP',
|
||||||
|
@ -70,6 +72,9 @@ export class LeftSideComponent implements OnInit, OnDestroy {
|
||||||
@ViewChildren('tabs') tabs: QueryList<ElementRef<HTMLDivElement>>;
|
@ViewChildren('tabs') tabs: QueryList<ElementRef<HTMLDivElement>>;
|
||||||
currentTabLable: string;
|
currentTabLable: string;
|
||||||
|
|
||||||
|
@ViewChild('messageBoxComponent', { static: false })
|
||||||
|
messageBoxComponent: MessageBoxComponent;
|
||||||
|
|
||||||
badgeChatUnReadCount: number;
|
badgeChatUnReadCount: number;
|
||||||
badgeChatUnReadCountSubscription: Subscription;
|
badgeChatUnReadCountSubscription: Subscription;
|
||||||
badgeMessageUnReadCount: number;
|
badgeMessageUnReadCount: number;
|
||||||
|
@ -229,20 +234,24 @@ export class LeftSideComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (!!result && !!result.choice && result.choice) {
|
if (!!result && !!result.sendFlag && result.sendFlag) {
|
||||||
// if (!!result.selectedUserList && result.selectedUserList.length > 0) {
|
if (
|
||||||
// const userSeqs: number[] = [];
|
this.currentTabLable === MainMenu.Message &&
|
||||||
// result.selectedUserList.map(user => userSeqs.push(user.seq));
|
!!this.messageBoxComponent
|
||||||
|
) {
|
||||||
// if (type === 'NORMAL') {
|
const type = result.sendType || MessageType.Send;
|
||||||
// this.store.dispatch(ChatStore.openRoom({ userSeqList: userSeqs }));
|
switch (type) {
|
||||||
// } else if (type === 'TIMER') {
|
case MessageType.Send:
|
||||||
// this.store.dispatch(
|
this.messageBoxComponent.onSelectedIndexChange(1);
|
||||||
// ChatStore.openRoom({ userSeqList: userSeqs, isTimeRoom: true })
|
this.messageBoxComponent.onSelectedIndexTab(1);
|
||||||
// );
|
break;
|
||||||
// }
|
case MessageType.Reservation:
|
||||||
// }
|
this.messageBoxComponent.onSelectedIndexChange(2);
|
||||||
// }
|
this.messageBoxComponent.onSelectedIndexTab(2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickOpenProfile(userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN) {
|
onClickOpenProfile(userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN) {
|
||||||
|
|
|
@ -154,6 +154,9 @@ export class MessageBoxComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectedIndexTab(value: number) {
|
||||||
|
this.tabs.selectedIndex = value;
|
||||||
|
}
|
||||||
onSelectedIndexChange(value: number) {
|
onSelectedIndexChange(value: number) {
|
||||||
this.currentTabIndex = value;
|
this.currentTabIndex = value;
|
||||||
let type: MessageType;
|
let type: MessageType;
|
||||||
|
@ -377,6 +380,10 @@ export class MessageBoxComponent
|
||||||
// 단건 발송취소(예약)
|
// 단건 발송취소(예약)
|
||||||
this.doMessageCancelReservation(result.messageInfo);
|
this.doMessageCancelReservation(result.messageInfo);
|
||||||
break;
|
break;
|
||||||
|
case 'UPDATE':
|
||||||
|
// 예약 수정
|
||||||
|
this.getRetrieveMessage(MessageType.Reservation, 0);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -42,11 +42,27 @@
|
||||||
<span>{{ getSendReceiverNames() }}</span>
|
<span>{{ getSendReceiverNames() }}</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="label">받은시간</span>
|
<span
|
||||||
<span>{{
|
*ngIf="messageInfo.type === MessageType.Receive"
|
||||||
|
class="label"
|
||||||
|
>받은시간</span
|
||||||
|
>
|
||||||
|
<span *ngIf="messageInfo.type === MessageType.Send" class="label"
|
||||||
|
>보낸시간</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
*ngIf="messageInfo.type === MessageType.Reservation"
|
||||||
|
class="label"
|
||||||
|
>발송예정시간</span
|
||||||
|
>
|
||||||
|
<span *ngIf="messageInfo.type !== MessageType.Reservation">{{
|
||||||
messageInfo.regDate
|
messageInfo.regDate
|
||||||
| dateToStringFormat: 'YYYY.MM.dd (KS) a/p HH:mm'
|
| dateToStringFormat: 'YYYY.MM.dd (KS) a/p HH:mm'
|
||||||
}}</span>
|
}}</span>
|
||||||
|
<span *ngIf="messageInfo.type === MessageType.Reservation">{{
|
||||||
|
messageInfo.reservationTime
|
||||||
|
| dateToStringFormat: 'YYYY.MM.dd (KS) a/p HH:mm'
|
||||||
|
}}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -180,6 +180,32 @@ export class MessageDetailDialogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
getBaseImage(): void {
|
||||||
|
this.contents.forEach(content => {
|
||||||
|
if (content.resType === ContentType.Image) {
|
||||||
|
this.messageApiService
|
||||||
|
.retrieveResourceFile({
|
||||||
|
userSeq: this.data.loginRes.userSeq,
|
||||||
|
deviceType: DeviceType.PC,
|
||||||
|
tokenKey: this.data.loginRes.tokenString,
|
||||||
|
type: this.messageInfo.type,
|
||||||
|
msgId: this.messageInfo.msgId,
|
||||||
|
resUrl: content.resUrl
|
||||||
|
} as RetrieveResourceFileRequest)
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
map(async rawBlob => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(rawBlob);
|
||||||
|
reader.onloadend = () => {
|
||||||
|
content.imageSrc = reader.result;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * @deprecated
|
// * @deprecated
|
||||||
|
@ -442,7 +468,7 @@ export class MessageDetailDialogComponent implements OnInit {
|
||||||
break;
|
break;
|
||||||
case 'MESSAGE_UPDATE':
|
case 'MESSAGE_UPDATE':
|
||||||
{
|
{
|
||||||
this.dialogService.open<
|
const result = await this.dialogService.open<
|
||||||
MessageWriteDialogComponent,
|
MessageWriteDialogComponent,
|
||||||
MessageWriteDialogData,
|
MessageWriteDialogData,
|
||||||
MessageWriteDialogResult
|
MessageWriteDialogResult
|
||||||
|
@ -454,14 +480,38 @@ export class MessageDetailDialogComponent implements OnInit {
|
||||||
data: {
|
data: {
|
||||||
loginRes: this.data.loginRes,
|
loginRes: this.data.loginRes,
|
||||||
environmentsInfo: this.data.environmentsInfo,
|
environmentsInfo: this.data.environmentsInfo,
|
||||||
detail: this.data.detail
|
detail: this.data.detail,
|
||||||
|
detailContents: this.convertContentsToEdit()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!!result && !!result.sendFlag && result.sendFlag) {
|
||||||
|
this.dialogRef.close({
|
||||||
|
returnType: 'UPDATE'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 기존 내용을 editor 에 맞게 컨버팅 한다. */
|
||||||
|
private convertContentsToEdit(): string {
|
||||||
|
// const contents: DetailContent[] = this.data.detail.contents;
|
||||||
|
const contents = this.contents;
|
||||||
|
let html = '';
|
||||||
|
contents.forEach(content => {
|
||||||
|
if (content.resType === ContentType.Text) {
|
||||||
|
html += content.resContent.replace(/\n/g, '<br/>');
|
||||||
|
} else if (content.resType === ContentType.Image) {
|
||||||
|
html += `<img id="${content.resSeq}" src="${content.imageSrc}" >`;
|
||||||
|
} else if (content.resType === ContentType.AttachFile) {
|
||||||
|
}
|
||||||
|
html += '<br/>';
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
async cancelSendMessageForUsers() {
|
async cancelSendMessageForUsers() {
|
||||||
if (
|
if (
|
||||||
!!this.unReadUsers &&
|
!!this.unReadUsers &&
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<mat-card class="confirm-card mat-elevation-z">
|
<mat-card class="confirm-card mat-elevation-z">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>
|
<mat-card-title> 쪽지 {{ isModify ? '수정' : '보내기' }} </mat-card-title>
|
||||||
쪽지 보내기
|
|
||||||
</mat-card-title>
|
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<ucap-message-write
|
<ucap-message-write
|
||||||
#messageWrite
|
#messageWrite
|
||||||
|
[isModify]="isModify"
|
||||||
[detail]="data.detail"
|
[detail]="data.detail"
|
||||||
|
[detailContents]="data.detailContents"
|
||||||
[curReceiverList]="data.receiverList"
|
[curReceiverList]="data.receiverList"
|
||||||
(send)="onSend($event)"
|
(send)="onSend($event)"
|
||||||
(selectReceiver)="onSelectReceiver($event)"
|
(selectReceiver)="onSelectReceiver($event)"
|
||||||
|
|
|
@ -7,7 +7,8 @@ import {
|
||||||
DetailResponse,
|
DetailResponse,
|
||||||
DetailContent,
|
DetailContent,
|
||||||
MessageApiService,
|
MessageApiService,
|
||||||
DetailReceiver
|
DetailReceiver,
|
||||||
|
MessageType
|
||||||
} from '@ucap-webmessenger/api-message';
|
} from '@ucap-webmessenger/api-message';
|
||||||
|
|
||||||
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
|
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
|
||||||
|
@ -15,7 +16,8 @@ import { NGXLogger } from 'ngx-logger';
|
||||||
import { MessageStatusCode } from '@ucap-webmessenger/api';
|
import { MessageStatusCode } from '@ucap-webmessenger/api';
|
||||||
import {
|
import {
|
||||||
WriteComponent as UCapMessageWriteComponent,
|
WriteComponent as UCapMessageWriteComponent,
|
||||||
Message
|
Message,
|
||||||
|
MessageModify
|
||||||
} from '@ucap-webmessenger/ui-message';
|
} from '@ucap-webmessenger/ui-message';
|
||||||
import { UserInfo } from '@ucap-webmessenger/protocol-sync';
|
import { UserInfo } from '@ucap-webmessenger/protocol-sync';
|
||||||
import {
|
import {
|
||||||
|
@ -30,11 +32,15 @@ export interface MessageWriteDialogData {
|
||||||
loginRes: LoginResponse;
|
loginRes: LoginResponse;
|
||||||
environmentsInfo: EnvironmentsInfo;
|
environmentsInfo: EnvironmentsInfo;
|
||||||
detail?: DetailResponse;
|
detail?: DetailResponse;
|
||||||
|
detailContents?: string;
|
||||||
receiverList?: UserInfo[];
|
receiverList?: UserInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: no-empty-interface
|
// tslint:disable-next-line: no-empty-interface
|
||||||
export interface MessageWriteDialogResult {}
|
export interface MessageWriteDialogResult {
|
||||||
|
sendFlag: boolean;
|
||||||
|
sendType?: MessageType;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DownloadQueueForMessage extends DetailContent {
|
export interface DownloadQueueForMessage extends DetailContent {
|
||||||
downloadType: string;
|
downloadType: string;
|
||||||
|
@ -49,6 +55,8 @@ export class MessageWriteDialogComponent implements OnInit {
|
||||||
@ViewChild('messageWrite', { static: true })
|
@ViewChild('messageWrite', { static: true })
|
||||||
messageWrite: UCapMessageWriteComponent;
|
messageWrite: UCapMessageWriteComponent;
|
||||||
|
|
||||||
|
isModify = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<
|
public dialogRef: MatDialogRef<
|
||||||
MessageWriteDialogData,
|
MessageWriteDialogData,
|
||||||
|
@ -61,7 +69,11 @@ export class MessageWriteDialogComponent implements OnInit {
|
||||||
private dialogService: DialogService
|
private dialogService: DialogService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {
|
||||||
|
if (!!this.data.detail && !!this.data.detail.msgInfo) {
|
||||||
|
this.isModify = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onSelectReceiver(receiverList: UserInfo[]) {
|
async onSelectReceiver(receiverList: UserInfo[]) {
|
||||||
const result = await this.dialogService.open<
|
const result = await this.dialogService.open<
|
||||||
|
@ -87,7 +99,14 @@ export class MessageWriteDialogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSend(message: Message) {
|
onSend(message: Message | MessageModify) {
|
||||||
|
if (this.isModify) {
|
||||||
|
this.onModifySend(message as MessageModify);
|
||||||
|
} else {
|
||||||
|
this.onNewSend(message as Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onNewSend(message: Message) {
|
||||||
this.messageApiService
|
this.messageApiService
|
||||||
.sendMessage({
|
.sendMessage({
|
||||||
...message,
|
...message,
|
||||||
|
@ -112,7 +131,7 @@ export class MessageWriteDialogComponent implements OnInit {
|
||||||
verticalPosition: 'bottom'
|
verticalPosition: 'bottom'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dialogRef.close({});
|
this.dialogRef.close({ sendFlag: true, sendType: message.type });
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
this.snackBarService.open(`쪽지를 전송에 실패 하였습니다.`, '', {
|
this.snackBarService.open(`쪽지를 전송에 실패 하였습니다.`, '', {
|
||||||
|
@ -123,9 +142,37 @@ export class MessageWriteDialogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
onModifySend(message: MessageModify) {
|
||||||
|
this.messageApiService
|
||||||
|
.editReservationMessageEx({
|
||||||
|
...message,
|
||||||
|
userSeq: this.data.loginRes.userInfo.seq,
|
||||||
|
deviceType: this.data.environmentsInfo.deviceType,
|
||||||
|
tokenKey: this.data.loginRes.tokenString,
|
||||||
|
titleYn: '' !== message.title ? true : false
|
||||||
|
})
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe(
|
||||||
|
res => {
|
||||||
|
this.snackBarService.open('쪽지를 수정하였습니다.', '', {
|
||||||
|
duration: 3000,
|
||||||
|
verticalPosition: 'bottom'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialogRef.close({ sendFlag: true, sendType: message.type });
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.snackBarService.open(`쪽지 수정에 실패 하였습니다.`, '', {
|
||||||
|
duration: 3000,
|
||||||
|
verticalPosition: 'bottom'
|
||||||
|
});
|
||||||
|
// this.dialogRef.close({});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onCancel() {
|
onCancel() {
|
||||||
this.dialogRef.close({});
|
this.dialogRef.close({ sendFlag: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
getBtnValid() {
|
getBtnValid() {
|
||||||
|
|
|
@ -14,6 +14,16 @@
|
||||||
></div>
|
></div>
|
||||||
<input type="file" #fileInput style="display: none" multiple />
|
<input type="file" #fileInput style="display: none" multiple />
|
||||||
<mat-list>
|
<mat-list>
|
||||||
|
<mat-list-item *ngFor="let oldAttachment of oldAttachmentList">
|
||||||
|
{{ oldAttachment.resContent }}
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
aria-label="이미지삭제"
|
||||||
|
(click)="onClickDeleteOldAttachment(oldAttachment)"
|
||||||
|
>
|
||||||
|
<span class="mdi mdi-delete"></span>
|
||||||
|
</button>
|
||||||
|
</mat-list-item>
|
||||||
<mat-list-item *ngFor="let attachment of attachmentList">
|
<mat-list-item *ngFor="let attachment of attachmentList">
|
||||||
{{ attachment.name }}
|
{{ attachment.name }}
|
||||||
</mat-list-item>
|
</mat-list-item>
|
||||||
|
@ -80,6 +90,7 @@
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<ucap-split-button
|
<ucap-split-button
|
||||||
|
*ngIf="!isModify"
|
||||||
[menu]="appMenu"
|
[menu]="appMenu"
|
||||||
(buttonClick)="onClickSend()"
|
(buttonClick)="onClickSend()"
|
||||||
[disabled]="
|
[disabled]="
|
||||||
|
@ -91,6 +102,21 @@
|
||||||
"
|
"
|
||||||
>보내기</ucap-split-button
|
>보내기</ucap-split-button
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
*ngIf="isModify"
|
||||||
|
mat-stroked-button
|
||||||
|
(click)="onClickSendSchedule()"
|
||||||
|
[disabled]="
|
||||||
|
messageWriteForm.invalid ||
|
||||||
|
!receiverList ||
|
||||||
|
0 === receiverList.length ||
|
||||||
|
0 === contentLength ||
|
||||||
|
1000 < contentLength
|
||||||
|
"
|
||||||
|
class="mat-primary"
|
||||||
|
>
|
||||||
|
예약 보내기
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -24,7 +24,8 @@ import {
|
||||||
CategoryType,
|
CategoryType,
|
||||||
MessageType,
|
MessageType,
|
||||||
DetailResponse,
|
DetailResponse,
|
||||||
DetailReceiver
|
DetailReceiver,
|
||||||
|
DetailContent
|
||||||
} from '@ucap-webmessenger/api-message';
|
} from '@ucap-webmessenger/api-message';
|
||||||
import { FileUploadItem } from '@ucap-webmessenger/api';
|
import { FileUploadItem } from '@ucap-webmessenger/api';
|
||||||
import { UserInfo } from '@ucap-webmessenger/protocol-sync';
|
import { UserInfo } from '@ucap-webmessenger/protocol-sync';
|
||||||
|
@ -35,12 +36,14 @@ import {
|
||||||
} from '../dialogs/schedule-send.dialog.component';
|
} from '../dialogs/schedule-send.dialog.component';
|
||||||
import { RoleCode } from '@ucap-webmessenger/protocol-authentication';
|
import { RoleCode } from '@ucap-webmessenger/protocol-authentication';
|
||||||
import { EmployeeType } from '@ucap-webmessenger/protocol-room';
|
import { EmployeeType } from '@ucap-webmessenger/protocol-room';
|
||||||
|
import { contentTracing } from 'electron';
|
||||||
|
|
||||||
const ATTR_FILE = 'UCAP_ATTR_FILE';
|
const ATTR_FILE = 'UCAP_ATTR_FILE';
|
||||||
|
|
||||||
interface Content {
|
interface Content {
|
||||||
contentType: ContentType;
|
contentType: ContentType;
|
||||||
content: string | File;
|
content: string | File;
|
||||||
|
resSeq: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
|
@ -55,6 +58,11 @@ export interface Message {
|
||||||
reservationTime?: string;
|
reservationTime?: string;
|
||||||
smsYn?: boolean;
|
smsYn?: boolean;
|
||||||
}
|
}
|
||||||
|
export interface MessageModify extends Message {
|
||||||
|
resSeqList: number[];
|
||||||
|
reservationTime: string;
|
||||||
|
msgId: number;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ucap-message-write',
|
selector: 'ucap-message-write',
|
||||||
|
@ -67,9 +75,13 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
curReceiverList: UserInfo[] = [];
|
curReceiverList: UserInfo[] = [];
|
||||||
@Input()
|
@Input()
|
||||||
detail?: DetailResponse;
|
detail?: DetailResponse;
|
||||||
|
@Input()
|
||||||
|
detailContents?: string;
|
||||||
|
@Input()
|
||||||
|
isModify = false;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
send = new EventEmitter<Message>();
|
send = new EventEmitter<Message | MessageModify>();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
cancel = new EventEmitter<void>();
|
cancel = new EventEmitter<void>();
|
||||||
|
@ -84,13 +96,12 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
fileInput: ElementRef<HTMLInputElement>;
|
fileInput: ElementRef<HTMLInputElement>;
|
||||||
|
|
||||||
messageWriteForm: FormGroup;
|
messageWriteForm: FormGroup;
|
||||||
|
oldAttachmentList: DetailContent[] = [];
|
||||||
attachmentList: File[];
|
attachmentList: File[];
|
||||||
fileUploadItem: FileUploadItem;
|
fileUploadItem: FileUploadItem;
|
||||||
receiverList: UserInfo[] = [];
|
receiverList: UserInfo[] = [];
|
||||||
contentLength = 0;
|
contentLength = 0;
|
||||||
|
|
||||||
isModify = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
|
@ -111,17 +122,21 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!this.detail && !!this.detail.msgInfo) {
|
if (this.isModify) {
|
||||||
this.isModify = true;
|
|
||||||
|
|
||||||
if (!!this.detail.msgInfo.title) {
|
if (!!this.detail.msgInfo.title) {
|
||||||
this.messageWriteForm.setValue({ title: this.detail.msgInfo.title });
|
this.messageWriteForm.setValue({ title: this.detail.msgInfo.title });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!this.detail.contents && this.detail.contents.length > 0) {
|
if (!!this.detailContents) {
|
||||||
// 내용이 있다.
|
this.editor.nativeElement.innerHTML = this.detailContents;
|
||||||
this.detail.contents.map(cont => console.log(cont));
|
this.onInputEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.detail.contents.forEach(content => {
|
||||||
|
if (content.resType === ContentType.AttachFile) {
|
||||||
|
this.oldAttachmentList.push(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +144,12 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
|
|
||||||
ngAfterViewInit(): void {}
|
ngAfterViewInit(): void {}
|
||||||
|
|
||||||
|
onClickDeleteOldAttachment(oldAttachment: DetailContent) {
|
||||||
|
this.oldAttachmentList = this.oldAttachmentList.filter(
|
||||||
|
detailContent => detailContent.resSeq !== oldAttachment.resSeq
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onClickImage() {
|
onClickImage() {
|
||||||
this.fileInput.nativeElement.click();
|
this.fileInput.nativeElement.click();
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -206,6 +227,10 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClickSendSchedule() {
|
async onClickSendSchedule() {
|
||||||
|
const reservationDate = this.isModify
|
||||||
|
? moment(this.detail.msgInfo.reservationTime)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const result = await this.dialogService.open<
|
const result = await this.dialogService.open<
|
||||||
ScheduleSendDialogComponent,
|
ScheduleSendDialogComponent,
|
||||||
ScheduleSendDialogData,
|
ScheduleSendDialogData,
|
||||||
|
@ -214,7 +239,9 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
width: '600px',
|
width: '600px',
|
||||||
height: '600px',
|
height: '600px',
|
||||||
disableClose: true,
|
disableClose: true,
|
||||||
data: {}
|
data: {
|
||||||
|
reservationDate
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!result && !!result.scheduleSendDate) {
|
if (!!result && !!result.scheduleSendDate) {
|
||||||
|
@ -247,7 +274,7 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { listOrder, textContent, files } = result;
|
const { listOrder, textContent, files, resSeqList } = result;
|
||||||
|
|
||||||
if (!listOrder || 0 === listOrder.length) {
|
if (!listOrder || 0 === listOrder.length) {
|
||||||
return;
|
return;
|
||||||
|
@ -265,19 +292,39 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
|
|
||||||
this.fileUploadItem = FileUploadItem.from();
|
this.fileUploadItem = FileUploadItem.from();
|
||||||
|
|
||||||
this.send.emit({
|
if (!this.isModify) {
|
||||||
category: CategoryType.General,
|
const message: Message = {
|
||||||
type: !!reservationDate ? MessageType.Reservation : MessageType.Send,
|
category: CategoryType.General,
|
||||||
title,
|
type: !!reservationDate ? MessageType.Reservation : MessageType.Send,
|
||||||
listOrder,
|
title,
|
||||||
textContent,
|
listOrder,
|
||||||
recvUserList,
|
textContent,
|
||||||
reservationTime: !!reservationDate
|
recvUserList,
|
||||||
? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00`
|
reservationTime: !!reservationDate
|
||||||
: undefined,
|
? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00`
|
||||||
files,
|
: undefined,
|
||||||
fileUploadItem: this.fileUploadItem
|
files,
|
||||||
});
|
fileUploadItem: this.fileUploadItem
|
||||||
|
};
|
||||||
|
this.send.emit(message);
|
||||||
|
} else {
|
||||||
|
const message: MessageModify = {
|
||||||
|
category: CategoryType.General,
|
||||||
|
type: !!reservationDate ? MessageType.Reservation : MessageType.Send,
|
||||||
|
msgId: this.detail.msgInfo.msgId,
|
||||||
|
title,
|
||||||
|
listOrder,
|
||||||
|
resSeqList,
|
||||||
|
textContent,
|
||||||
|
recvUserList,
|
||||||
|
reservationTime: !!reservationDate
|
||||||
|
? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00`
|
||||||
|
: undefined,
|
||||||
|
files,
|
||||||
|
fileUploadItem: this.fileUploadItem
|
||||||
|
};
|
||||||
|
this.send.emit(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseContent():
|
private parseContent():
|
||||||
|
@ -285,6 +332,7 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
listOrder: ContentType[];
|
listOrder: ContentType[];
|
||||||
textContent: { text: string }[];
|
textContent: { text: string }[];
|
||||||
files: File[];
|
files: File[];
|
||||||
|
resSeqList: number[];
|
||||||
}
|
}
|
||||||
| undefined {
|
| undefined {
|
||||||
const contentList: Content[] = this.generateContent();
|
const contentList: Content[] = this.generateContent();
|
||||||
|
@ -295,20 +343,26 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
const listOrder: ContentType[] = [];
|
const listOrder: ContentType[] = [];
|
||||||
const textContent: { text: string }[] = [];
|
const textContent: { text: string }[] = [];
|
||||||
const files: File[] = [];
|
const files: File[] = [];
|
||||||
|
const resSeqList: number[] = [];
|
||||||
|
|
||||||
contentList.forEach(v => {
|
contentList.forEach(v => {
|
||||||
listOrder.push(v.contentType);
|
listOrder.push(v.contentType);
|
||||||
|
resSeqList.push(v.resSeq);
|
||||||
switch (v.contentType) {
|
switch (v.contentType) {
|
||||||
case ContentType.Text:
|
case ContentType.Text:
|
||||||
let content = v.content as string;
|
let content = v.content as string;
|
||||||
if ('\n' === content.charAt(content.length - 1)) {
|
if ('\n' === content.charAt(content.length - 1)) {
|
||||||
content = content.substring(0, content.length - 2);
|
content = content.substring(0, content.length - 1);
|
||||||
}
|
}
|
||||||
textContent.push({ text: content });
|
textContent.push({ text: content });
|
||||||
break;
|
break;
|
||||||
case ContentType.Image:
|
case ContentType.Image:
|
||||||
case ContentType.AttachFile:
|
case ContentType.AttachFile:
|
||||||
files.push(v.content as File);
|
{
|
||||||
|
if (v.resSeq === 0) {
|
||||||
|
files.push(v.content as File);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -318,7 +372,8 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
return {
|
return {
|
||||||
listOrder,
|
listOrder,
|
||||||
textContent,
|
textContent,
|
||||||
files
|
files,
|
||||||
|
resSeqList
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,11 +384,22 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
this.parseNode(contentList, v);
|
this.parseNode(contentList, v);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!!this.oldAttachmentList && 0 < this.oldAttachmentList.length) {
|
||||||
|
this.oldAttachmentList.forEach(v => {
|
||||||
|
contentList.push({
|
||||||
|
contentType: ContentType.AttachFile,
|
||||||
|
content: v.resContent,
|
||||||
|
resSeq: v.resSeq
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!!this.attachmentList && 0 < this.attachmentList.length) {
|
if (!!this.attachmentList && 0 < this.attachmentList.length) {
|
||||||
this.attachmentList.forEach(v => {
|
this.attachmentList.forEach(v => {
|
||||||
contentList.push({
|
contentList.push({
|
||||||
contentType: ContentType.AttachFile,
|
contentType: ContentType.AttachFile,
|
||||||
content: v
|
content: v,
|
||||||
|
resSeq: 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -362,7 +428,12 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
} else {
|
} else {
|
||||||
if ('IMG' === node.nodeName) {
|
if ('IMG' === node.nodeName) {
|
||||||
const img: HTMLImageElement = node as HTMLImageElement;
|
const img: HTMLImageElement = node as HTMLImageElement;
|
||||||
this.appendNode(contentList, ContentType.Image, img[ATTR_FILE]);
|
this.appendNode(
|
||||||
|
contentList,
|
||||||
|
ContentType.Image,
|
||||||
|
img[ATTR_FILE],
|
||||||
|
!!img.getAttribute('id') ? Number(img.getAttribute('id')) : 0
|
||||||
|
);
|
||||||
} else if ('BR' === node.nodeName) {
|
} else if ('BR' === node.nodeName) {
|
||||||
this.appendNode(contentList, ContentType.Text, `\n`);
|
this.appendNode(contentList, ContentType.Text, `\n`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -382,7 +453,8 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
private appendNode(
|
private appendNode(
|
||||||
contentList: Content[],
|
contentList: Content[],
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
content: string | File
|
content: string | File,
|
||||||
|
resSeq: number = 0
|
||||||
) {
|
) {
|
||||||
const prevContent = contentList[contentList.length - 1];
|
const prevContent = contentList[contentList.length - 1];
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
|
@ -392,12 +464,13 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
} else {
|
} else {
|
||||||
contentList.push({
|
contentList.push({
|
||||||
contentType: ContentType.Text,
|
contentType: ContentType.Text,
|
||||||
content
|
content,
|
||||||
|
resSeq
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
contentList.push({ contentType, content });
|
contentList.push({ contentType, content, resSeq });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
// tslint:disable-next-line: no-empty-interface
|
// tslint:disable-next-line: no-empty-interface
|
||||||
export interface ScheduleSendDialogData {}
|
export interface ScheduleSendDialogData {
|
||||||
|
reservationDate?: moment.Moment;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ScheduleSendDialogResult {
|
export interface ScheduleSendDialogResult {
|
||||||
scheduleSendDate?: moment.Moment;
|
scheduleSendDate?: moment.Moment;
|
||||||
|
@ -48,7 +50,11 @@ export class ScheduleSendDialogComponent implements OnInit {
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private logger: NGXLogger
|
private logger: NGXLogger
|
||||||
) {
|
) {
|
||||||
this.selectedDate = moment().add(1, 'hours');
|
if (!!this.data.reservationDate) {
|
||||||
|
this.selectedDate = this.data.reservationDate;
|
||||||
|
} else {
|
||||||
|
this.selectedDate = moment().add(1, 'hours');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user