0527 sync

This commit is contained in:
Park Byung Eun 2020-05-28 21:52:40 +09:00
parent 23bbfc4b63
commit 92da6c71ce
101 changed files with 5166 additions and 1227 deletions

View File

@ -55,8 +55,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
"maximumWarning": "30kb",
"maximumError": "50kb"
}
]
},
@ -86,8 +86,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
"maximumWarning": "30kb",
"maximumError": "50kb"
}
]
},

View File

@ -32,12 +32,12 @@
"@ngrx/router-store": "^9.0.0",
"@ngrx/store": "^9.0.0",
"@ucap/api": "~0.0.2",
"@ucap/api-common": "~0.0.3",
"@ucap/api-common": "~0.0.5",
"@ucap/api-external": "~0.0.5",
"@ucap/api-message": "~0.0.3",
"@ucap/api-prompt": "~0.0.3",
"@ucap/api-public": "~0.0.4",
"@ucap/core": "~0.0.7",
"@ucap/core": "~0.0.10",
"@ucap/logger": "~0.0.12",
"@ucap/native": "~0.0.6",
"@ucap/native-browser": "~0.0.5",
@ -46,7 +46,7 @@
"@ucap/ng-api-message": "~0.0.1",
"@ucap/ng-api-prompt": "~0.0.1",
"@ucap/ng-api-public": "~0.0.1",
"@ucap/ng-core": "~0.0.1",
"@ucap/ng-core": "~0.0.7",
"@ucap/ng-logger": "~0.0.2",
"@ucap/ng-i18n": "~0.0.6",
"@ucap/ng-native": "~0.0.1",
@ -69,16 +69,16 @@
"@ucap/ng-protocol-sync": "~0.0.3",
"@ucap/ng-protocol-umg": "~0.0.3",
"@ucap/ng-store-authentication": "~0.0.11",
"@ucap/ng-store-chat": "~0.0.13",
"@ucap/ng-store-chat": "~0.0.16",
"@ucap/ng-store-group": "~0.0.14",
"@ucap/ng-store-organization": "~0.0.8",
"@ucap/ng-web-socket": "~0.0.2",
"@ucap/ng-web-storage": "~0.0.3",
"@ucap/ng-ui": "~0.0.19",
"@ucap/ng-ui-organization": "~0.0.55",
"@ucap/ng-ui-authentication": "~0.0.24",
"@ucap/ng-ui-organization": "~0.0.83",
"@ucap/ng-ui-authentication": "~0.0.25",
"@ucap/ng-ui-group": "~0.0.33",
"@ucap/ng-ui-chat": "~0.0.9",
"@ucap/ng-ui-chat": "~0.0.12",
"@ucap/ng-ui-material": "~0.0.4",
"@ucap/ng-ui-skin-default": "~0.0.1",
"@ucap/pi": "~0.0.5",
@ -93,12 +93,12 @@
"@ucap/protocol-option": "~0.0.7",
"@ucap/protocol-ping": "~0.0.6",
"@ucap/protocol-query": "~0.0.5",
"@ucap/protocol-room": "~0.0.5",
"@ucap/protocol-room": "~0.0.6",
"@ucap/protocol-service": "~0.0.4",
"@ucap/protocol-status": "~0.0.5",
"@ucap/protocol-sync": "~0.0.4",
"@ucap/protocol-umg": "~0.0.5",
"@ucap/ui-scss": "~0.0.4",
"@ucap/ui-scss": "~0.0.5",
"@ucap/web-socket": "~0.0.10",
"@ucap/web-storage": "~0.0.9",
"autolinker": "^3.13.0",

View File

@ -15,6 +15,7 @@ import { AppAuthenticationService } from './services/app-authentication.service'
import { AppNativeService } from './services/app-native.service';
import { AppService } from './services/app.service';
import { AppChatService } from './services/app-chat.service';
import { AppFileService } from './services/app-file.service';
const GUARDS = [AppAuthenticationGuard];
const RESOLVERS = [AppSessionResolver];
@ -22,6 +23,7 @@ const SERVICES = [
AppService,
AppAuthenticationService,
AppNativeService,
AppFileService,
AppChatService
];

View File

@ -22,11 +22,11 @@ $typography: mat-typography-config(
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$lgRed-app-primary: mat-palette($lg-red);
$lgRed-app-accent: mat-palette($lg-red, A200, A100, A400);
$lgRed-app-primary: mat-palette($ucap-color-primary);
$lgRed-app-accent: mat-palette($ucap-color-accent, 700);
// The warn palette is optional (defaults to red).
$lgRed-app-warn: mat-palette($lg-red);
$lgRed-app-warn: mat-palette($ucap-color-warn, 500);
// Create the theme object (a Sass map containing all of the palettes).
$lgRed-app-theme: mat-light-theme(

View File

@ -11,6 +11,7 @@ import { MatSidenav } from '@angular/material/sidenav';
import { LogService } from '@ucap/ng-logger';
import { AppSelector } from '@app/store/state';
import { AppChatService } from '@app/services/app-chat.service';
const NAVS = ['group', 'chat', 'organization', 'message'];
@ -37,6 +38,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
constructor(
private router: Router,
private store: Store<any>,
private appChatService: AppChatService,
private logService: LogService
) {}
@ -175,7 +177,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
break;
case 'CAHT_NEW_ADD':
{
this.logService.debug('CAHT_NEW_ADD');
this.appChatService.newOpenRoomDialog();
}
break;
case 'CHAT_NEW_TIMER_ADD':

View File

@ -17,6 +17,9 @@ import { UiModule } from '@ucap/ng-ui';
import { COMPONENTS } from './components';
import { DIALOGS } from './dialogs';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { MatSelectModule } from '@angular/material/select';
@NgModule({
imports: [
@ -30,13 +33,21 @@ import { DIALOGS } from './dialogs';
MatSidenavModule,
MatTabsModule,
MatToolbarModule,
MatSelectModule,
PerfectScrollbarModule,
I18nModule,
UiModule
],
exports: [...COMPONENTS, ...DIALOGS],
declarations: [...COMPONENTS, ...DIALOGS],
entryComponents: [...DIALOGS]
entryComponents: [...DIALOGS],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['chat', 'common']
}
]
})
export class AppLayoutsModule {}

View File

@ -7,9 +7,11 @@ $login-bg-h: 100/1080;
width: 100%;
height: 100%;
overflow: auto;
// box-sizing: border-box;
// display: flex;
// flex-direction: column;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: $bg-gray;
background-image: url(../../../../assets/images/bg/bg_login_circle_square01.svg),

View File

@ -15,6 +15,7 @@ import { AppChatRoutingPageModule } from './chat-routing.page.module';
import { UiModule } from '@ucap/ng-ui';
import { COMPONENTS } from './components';
import { UCAP_I18N_NAMESPACE, I18nModule } from '@ucap/ng-i18n';
@NgModule({
imports: [
@ -30,9 +31,16 @@ import { COMPONENTS } from './components';
AppChatSectionModule,
AppChatRoutingPageModule,
I18nModule,
UiModule
],
declarations: [...COMPONENTS],
entryComponents: []
entryComponents: [],
providers: [
{
provide: UCAP_I18N_NAMESPACE,
useValue: ['chat', 'common']
}
]
})
export class AppChatPageModule {}

View File

@ -1,6 +1,6 @@
<div fxFlexFill class="sidenav-container">
<div class="chat-header">
<h3>대화</h3>
<h3>{{ 'label.chat' | ucapI18n }}</h3>
<div class="chat-menu-btn">
<button
mat-icon-button

View File

@ -44,6 +44,9 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
// language setting
this.i18nService.setDefaultNamespace('chat');
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(RoomSelector.rooms))
.subscribe((rooms) => {
@ -67,8 +70,8 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
ConfirmDialogResult
>(ConfirmDialogComponent, {
data: {
title: this.i18nService.t('room.dialog.titleExitFromRoom'),
html: this.i18nService.t('room.dialog.confirmExitFromRoom')
title: this.i18nService.t('dialog.title.exitFromRoom'),
html: this.i18nService.t('dialog.confirmExitFromRoom')
}
});

View File

@ -21,7 +21,7 @@
</div>
</div>
<div class="extra-box" fxFlex="0 0 50px">
<app-organization-search-for-tenant (changed)="onKeyDownSearch($event)">
<app-organization-search-for-tenant [(searchData)]="companySearchData">
</app-organization-search-for-tenant>
</div>
@ -29,7 +29,7 @@
<app-sections-group-list
#sectionGroupList
fxFlexFill
[searchObj]="searchObj"
[searchData]="companySearchData"
[showType]="showType"
></app-sections-group-list>
</div>

View File

@ -1,5 +1,5 @@
import { Subscription, of } from 'rxjs';
import { take, map, catchError } from 'rxjs/operators';
import { of, Subject } from 'rxjs';
import { take, map, catchError, takeUntil } from 'rxjs/operators';
import {
Component,
@ -14,14 +14,14 @@ import { Store } from '@ngrx/store';
import { MatDialog } from '@angular/material/dialog';
import { ParamsUtil } from '@ucap/ng-core';
import { LogService } from '@ucap/ng-logger';
import { GroupActions } from '@ucap/ng-store-group';
import { SelectUserDialogType } from '@app/types';
import { I18nService } from '@ucap/ng-i18n';
import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component';
import { I18nService } from '@ucap/ng-i18n';
import { ListSectionComponent } from '@app/sections/group/components/list.section.component';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { QueryParams } from '@app/pages/organization/types/params.type';
@Component({
selector: 'app-pages-group-sidenav',
@ -32,14 +32,19 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
@ViewChild('sectionGroupList', { static: false })
sectionGroupList: ListSectionComponent;
searchObj: any = {
isShowSearch: false,
companyCode: '',
searchWord: ''
};
set companySearchData(searchData: SearchData) {
this._companySearchData = searchData;
}
get companySearchData() {
return this._companySearchData;
}
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
showType: string;
private ngOnDestroySubject: Subject<boolean>;
constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
@ -53,33 +58,14 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.showType = 'ALL';
}
ngOnDestroy(): void {}
onClickFab(event: MouseEvent) {}
onKeyDownSearch(params: {
isShowSearch: boolean;
companyCode: string;
searchWord: string;
}) {
this.searchObj = {
isShowSearch: params.isShowSearch,
companyCode: params.companyCode,
searchWord: params.searchWord
};
this.changeDetectorRef.detectChanges();
}
onClickCancel() {
this.searchObj = {
isShowSearch: false,
companyCode: '',
searchWord: ''
};
this.changeDetectorRef.detectChanges();
}
onClickGroupMenu(menuType: string) {
switch (menuType) {

View File

@ -1,5 +1,12 @@
<div fxFlexFill>
<app-sections-organization-member-list
[searchData]="_searchData"
></app-sections-organization-member-list>
<div class="index-page-container" fxLayout="column">
<!-- search start-->
<div fxFlex="0 0 50px">
<app-organization-search-for-tenant [(searchData)]="companySearchData">
</app-organization-search-for-tenant>
</div>
<!-- search end-->
<div class="member-list-body" fxFlex="1 1 auto">
<app-sections-organization-member-list [searchData]="deptSearchData">
</app-sections-organization-member-list>
</div>
</div>

View File

@ -0,0 +1,4 @@
.index-page-container {
width: 100%;
height: 100%;
}

View File

@ -2,10 +2,14 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { Store } from '@ngrx/store';
import { ParamsUtil } from '@ucap/ng-core';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { QueryParams } from '../types/params.type';
@Component({
@ -14,12 +18,17 @@ import { QueryParams } from '../types/params.type';
styleUrls: ['./index.page.component.scss']
})
export class IndexPageComponent implements OnInit, OnDestroy {
set companySearchData(searchData: SearchData) {
this._companySearchData = searchData;
this.onChangedCompanySearch();
}
get companySearchData() {
return this._companySearchData;
}
// tslint:disable-next-line: variable-name
_searchData: {
companyCode: string;
searchWord: string;
isSearch: boolean;
};
_companySearchData: SearchData;
deptSearchData: SearchData;
deptSeq: string;
@ -27,6 +36,7 @@ export class IndexPageComponent implements OnInit, OnDestroy {
constructor(
private store: Store<any>,
private router: Router,
private activatedRoute: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef
) {}
@ -37,14 +47,25 @@ export class IndexPageComponent implements OnInit, OnDestroy {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((params) => {
console.log('activatedRoute.queryParams');
if (!!params) {
const companyCode = params[QueryParams.DEPT_SEQ];
console.log('activatedRoute.queryParams', companyCode);
this._searchData = {
companyCode,
searchWord: '',
isSearch: false
const deptSeq = params[QueryParams.DEPT_SEQ];
const companyCode = params[QueryParams.COMPANY_CODE];
const searchWord = params[QueryParams.SEARCH_WORD];
const bySearch = ParamsUtil.getParameter<boolean>(
params,
QueryParams.BY_SEARCH,
false
);
this.deptSearchData = {
deptSeq: bySearch ? undefined : deptSeq,
companyCode: bySearch ? companyCode : undefined,
searchWord: bySearch ? searchWord : undefined,
bySearch
};
this._companySearchData = {
...this.deptSearchData
};
}
});
@ -55,4 +76,23 @@ export class IndexPageComponent implements OnInit, OnDestroy {
this.ngOnDestroySubject.complete();
}
}
onChangedCompanySearch() {
const queryParams: Params = {};
queryParams[QueryParams.COMPANY_CODE] = this._companySearchData.companyCode;
queryParams[QueryParams.SEARCH_WORD] = this._companySearchData.searchWord;
queryParams[QueryParams.BY_SEARCH] = String(true);
this.router.navigate(
[
'organization',
{
outlets: { content: 'index' }
}
],
{
queryParams
}
);
}
}

View File

@ -52,6 +52,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy {
onClickedTree(node: DeptInfo) {
const queryParams: Params = {};
queryParams[QueryParams.DEPT_SEQ] = String(node.seq);
queryParams[QueryParams.BY_SEARCH] = String(false);
this.router.navigate(
[

View File

@ -1,3 +1,6 @@
export enum QueryParams {
DEPT_SEQ = 'dept_seq'
DEPT_SEQ = 'dept_seq',
COMPANY_CODE = 'company_code',
SEARCH_WORD = 'search_word',
BY_SEARCH = 'by_search'
}

View File

@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
@ -20,6 +20,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MatTreeModule } from '@angular/material/tree';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatStepperModule } from '@angular/material/stepper';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
@ -27,12 +28,16 @@ import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { UiModule } from '@ucap/ng-ui';
import { ChatUiModule } from '@ucap/ng-ui-chat';
import { AppChatModule } from '@app/ucap/chat/chat.module';
import { AppLayoutsModule } from '@app/layouts/layouts.module';
import { AppGroupSectionModule } from '../group/group.section.module';
import { COMPONENTS } from './components';
import { DIALOGS } from './dialogs';
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
FlexLayoutModule,
@ -50,6 +55,7 @@ import { COMPONENTS } from './components';
MatRippleModule,
MatTreeModule,
MatTooltipModule,
MatStepperModule,
PerfectScrollbarModule,
ScrollingModule,
@ -57,12 +63,14 @@ import { COMPONENTS } from './components';
I18nModule,
UiModule,
AppLayoutsModule,
AppGroupSectionModule,
ChatUiModule,
AppChatModule
],
exports: [...COMPONENTS],
declarations: [...COMPONENTS],
entryComponents: [],
exports: [...COMPONENTS, ...DIALOGS],
declarations: [...COMPONENTS, ...DIALOGS],
entryComponents: [...DIALOGS],
providers: [
{
provide: UCAP_I18N_NAMESPACE,

View File

@ -1,16 +1,23 @@
<ng-container [ngSwitch]="selectorType">
<app-chat-selector-sticker *ngSwitchCase="'STICKER'">
<app-chat-selector-sticker
*ngSwitchCase="SelectorType.STICKER"
(selectedSticker)="onSelectedSticker($event)"
(closeSticker)="selectorType = SelectorType.EMPTY"
>
</app-chat-selector-sticker>
<app-chat-selector-translation
*ngSwitchCase="'TRANSLATION'"
*ngSwitchCase="SelectorType.TRANSLATION"
></app-chat-selector-translation>
<app-chat-selector-file-upload *ngSwitchCase="'FILEUPLOAD'">
<app-chat-selector-file-upload
#fileUploadSelector
*ngSwitchCase="SelectorType.FILEUPLOAD"
>
</app-chat-selector-file-upload>
<app-chat-selector-email-send
*ngSwitchCase="'EMAILSENDER'"
*ngSwitchCase="SelectorType.EMAILSENDER"
></app-chat-selector-email-send>
</ng-container>
@ -23,13 +30,22 @@
>
<textarea
matInput
#replyInput
#messageInput
placeholder=""
name="message"
[matTextareaAutosize]="true"
[matAutosizeMaxRows]="20"
(keydown)="onKeydown($event)"
></textarea>
</mat-form-field>
<input
type="file"
#fileInput
style="display: none;"
multiple
(change)="onChangeFileInput()"
/>
</div>
<div class="button-area">
<button
@ -37,8 +53,7 @@
aria-label="attachFile"
matTooltipPosition="above"
matTooltip="{{ 'label.attachFile' | ucapI18n }}"
matTool
(click)="onOpenSelector('FILEUPLOAD')"
(click)="clearSelector(); fileInput.click()"
>
첨부파일
</button>
@ -47,7 +62,7 @@
aria-label="attachImage"
matTooltipPosition="above"
matTooltip="{{ 'label.attachImage' | ucapI18n }}"
(click)="onOpenSelector('')"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
이미지
</button>
@ -56,7 +71,7 @@
aria-label="screenshot"
matTooltipPosition="above"
matTooltip="{{ 'label.screenshot' | ucapI18n }}"
(click)="onOpenSelector('')"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
캡쳐 화면 전송
</button>
@ -65,7 +80,7 @@
aria-label="imoticon"
matTooltipPosition="above"
matTooltip="{{ 'label.imoticon' | ucapI18n }}"
(click)="onOpenSelector('STICKER')"
(click)="onOpenSelector(SelectorType.STICKER)"
>
이모티콘
</button>
@ -74,7 +89,7 @@
aria-label="emailSend"
matTooltipPosition="above"
matTooltip="{{ 'label.emailSend' | ucapI18n }}"
(click)="onOpenSelector('EMAILSENDER')"
(click)="onOpenSelector(SelectorType.EMAILSENDER)"
>
대화내용 메일 전송
</button>
@ -83,7 +98,7 @@
aria-label="translation"
matTooltipPosition="above"
matTooltip="{{ 'label.translation' | ucapI18n }}"
(click)="onOpenSelector('TRANSLATION')"
(click)="onOpenSelector(SelectorType.TRANSLATION)"
>
대화내용 번역
</button>
@ -92,7 +107,7 @@
aria-label="gams"
matTooltipPosition="above"
matTooltip="{{ 'label.gams' | ucapI18n }}"
(click)="onOpenSelector('')"
(click)="onOpenSelector(SelectorType.EMPTY)"
>
+GAMS
</button>
@ -101,7 +116,7 @@
color="accent"
aria-label="send"
matTooltip="{{ 'label.send' | ucapI18n }}"
(click)="onOpenSelector('')"
(click)="send()"
>
<mat-icon>send</mat-icon>
</button>

View File

@ -1,5 +1,5 @@
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Subject, of, Observable, forkJoin } from 'rxjs';
import { takeUntil, map, catchError, take } from 'rxjs/operators';
import {
Component,
@ -7,12 +7,61 @@ import {
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input
Input,
ViewChild,
ElementRef
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import { RoomInfo } from '@ucap/protocol-room';
import { Dictionary } from '@ngrx/entity';
import { RoomInfo } from '@ucap/protocol-room';
import {
SendRequest as SendEventRequest,
EventType
} from '@ucap/protocol-event';
import { LoginResponse } from '@ucap/protocol-authentication';
import { ChattingActions } from '@ucap/ng-store-chat';
import {
LoginSelector,
ConfigurationSelector
} from '@ucap/ng-store-authentication';
import { StickerFilesInfo, KEY_STICKER_HISTORY } from '@ucap/ng-core';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
import { I18nService } from '@ucap/ng-i18n';
import { LogService } from '@ucap/ng-logger';
import { MatDialog } from '@angular/material/dialog';
import {
TranslationSaveResponse,
MassTalkSaveRequest,
FileTalkSaveResponse,
FileTalkSaveRequest
} from '@ucap/api-common';
import { environment } from '@environments';
import { LocalStorageService } from '@ucap/ng-web-storage';
import { CommonApiService } from '@ucap/ng-api-common';
import { LoginSession } from '@app/models/login-session';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { StatusCode, FileUploadItem } from '@ucap/api';
import { AppFileService } from '@app/services/app-file.service';
import { VersionInfo2Response } from '@ucap/api-public';
import { FileUploadSelectorComponent } from '@app/ucap/chat/components/file-upload.selector.component';
import { FileUtil } from '@ucap/core';
import { AppChatService } from '@app/services/app-chat.service';
export enum SelectorType {
EMPTY = '',
STICKER = 'STICKER',
TRANSLATION = 'TRANSLATION',
FILEUPLOAD = 'FILEUPLOAD',
EMAILSENDER = 'EMAILSENDER'
}
@Component({
selector: 'app-sections-chat-form',
templateUrl: './form.section.component.html',
@ -30,19 +79,70 @@ export class FormSectionComponent implements OnInit, OnDestroy {
// tslint:disable-next-line: variable-name
_roomId: string;
versionInfo2Res: VersionInfo2Response;
loginSession: LoginSession;
loginRes: LoginResponse;
currentRoomInfo: RoomInfo;
selectorType = '';
selectorType: SelectorType = SelectorType.EMPTY;
/** About Sticker */
selectedSticker: StickerFilesInfo;
/** About Translation */
translationSimpleview = false;
translationPreview = false;
destLocale = 'en'; // default English :: en
translationPreviewInfo: {
previewInfo: TranslationSaveResponse | null;
translationType: EventType.Translation | EventType.MassTranslation;
};
@ViewChild('messageInput', { static: false })
messageInput: ElementRef<HTMLTextAreaElement>;
@ViewChild('fileInput', { static: false })
fileInput: ElementRef<HTMLInputElement>;
@ViewChild('fileUploadSelector', { static: false })
fileUploadSelector: FileUploadSelectorComponent;
SelectorType = SelectorType;
private ngOnDestroySubject: Subject<boolean>;
constructor(
private appFileService: AppFileService,
private appChatService: AppChatService,
private store: Store<any>,
private i18nService: I18nService,
private dialog: MatDialog,
private localStorageService: LocalStorageService,
private logService: LogService,
private appAuthenticationService: AppAuthenticationService,
private commonApiService: CommonApiService,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
this.appAuthenticationService
.getLoginSession$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((loginSession) => (this.loginSession = loginSession));
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
@ -65,7 +165,176 @@ export class FormSectionComponent implements OnInit, OnDestroy {
}
}
onOpenSelector(type: string): void {
/** About Selector */
onOpenSelector(type: SelectorType): void {
this.selectorType = type;
this.changeDetectorRef.detectChanges();
}
clearSelector(): void {
this.selectorType = SelectorType.EMPTY;
this.selectedSticker = null;
this.changeDetectorRef.detectChanges();
}
/** Element Handling */
focus(clearField: boolean = true): void {
if (!!this.messageInput) {
if (!!clearField) {
this.messageInput.nativeElement.value = '';
this.clearSelector();
}
this.messageInput.nativeElement.focus();
}
}
onChangeFileInput(): void {
const self = this;
const fileList = this.fileInput.nativeElement.files;
this.appFileService
.validUploadFile(fileList, this.versionInfo2Res?.fileAllowSize)
.then((result) => {
if (!result) {
self.fileInput.nativeElement.value = '';
return;
} else {
// selector open
self.onOpenSelector(SelectorType.FILEUPLOAD);
// FileuploadItem Init. & FileSelector Init.
const fileUploadItems = FileUploadItem.fromFiles(fileList);
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onFileSelected(fileUploadItems);
}
self.fileInput.nativeElement.value = '';
// File Upload..
self.appChatService
.sendMessageOfAttachFile(
self.loginRes,
self.loginSession.deviceType,
self.currentRoomInfo.roomId,
fileUploadItems
)
.then((success) => {
if (!!success) {
self.clearSelector();
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onUploadComplete();
}
}
})
.catch((err) => {
alert(err);
if (!!self.fileUploadSelector) {
self.fileUploadSelector.onUploadComplete();
}
});
}
})
.catch((err) => {
self.fileInput.nativeElement.value = '';
self.logService.error(`validUploadFile ${err}`);
});
}
onKeydown(event: KeyboardEvent) {
if (event.key === 'PageUp' || event.key === 'PageDown') {
event.preventDefault();
return false;
} else if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
this.send();
}
}
onSelectedSticker(stickerInfo: StickerFilesInfo) {
this.selectedSticker = stickerInfo;
this.focus(false);
}
async send() {
const roomId = this.currentRoomInfo.roomId;
const userSeq = this.loginRes.userSeq;
let message = this.messageInput.nativeElement.value;
if (!!message || message.trim().length > 0) {
message = message.replace(/\t/g, ' ');
}
// Empty Check.
if (!this.selectedSticker) {
try {
if (!message || message.trim().length === 0) {
const result = await this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: this.i18nService.t('errors.label'),
message: this.i18nService.t('errors.inputChatMessage')
},
panelClass: ''
});
return;
}
} catch (e) {
this.logService.debug(e);
}
}
if (
this.selectorType === SelectorType.TRANSLATION &&
this.destLocale.trim().length > 0
) {
/** CASE : Translation */
// 번역할 대화 없이 스티커만 전송할 경우.
if (!message || message.trim().length === 0) {
this.appChatService.sendMessageOfSticker(
userSeq,
roomId,
this.selectedSticker,
message
);
this.clearSelector();
} else {
this.appChatService.sendMessageOfTranslate(
this.loginRes,
this.loginSession.deviceType,
this.destLocale,
roomId,
message,
this.selectedSticker
);
}
} else if (!!this.selectedSticker) {
/** CASE : Sticker */
this.appChatService.sendMessageOfSticker(
userSeq,
roomId,
this.selectedSticker,
message
);
this.clearSelector();
} else if (
message.trim().length > environment.productConfig.chat.masstextLength
) {
/** CASE : MASS TEXT */
this.appChatService.sendMessageOfMassText(
this.loginRes,
this.loginSession.deviceType,
roomId,
message
);
} else {
/** CASE : Normal Text */
this.appChatService.sendMessageOfNormal(userSeq, roomId, message);
}
this.focus();
}
}

View File

@ -144,6 +144,14 @@ export class ListSectionComponent implements OnInit, OnDestroy {
private ngZone: NgZone,
private logService: LogService
) {
// default image setting
this.defaultProfileImage = this.appChatService.defaultProfileImage;
this.defaultProfileImageMulti = this.appChatService.defaultProfileImage;
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
// language setting
this.translateService.setDefaultLang(this.i18nService.currentLng);
this.translateService.use(this.i18nService.currentLng);
@ -153,14 +161,6 @@ export class ListSectionComponent implements OnInit, OnDestroy {
);
this.i18nService.setDefaultNamespace('chat');
// default image setting
this.defaultProfileImage = this.appChatService.defaultProfileImage;
this.defaultProfileImageMulti = this.appChatService.defaultProfileImage;
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
@ -176,15 +176,20 @@ export class ListSectionComponent implements OnInit, OnDestroy {
this.loginRes = loginRes;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(RoomSelector.rooms))
.subscribe((rooms) => {
rooms = (rooms || []).filter((info) => info.isJoinRoom);
combineLatest([
this.store.pipe(select(RoomSelector.rooms)),
this.store.pipe(select(RoomSelector.standbyRooms))
])
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe(([rooms, standbyRooms]) => {
rooms = (rooms || []).filter((info) => {
return (
info.isJoinRoom &&
!standbyRooms.find((standbyRoom) => standbyRoom === info.roomId)
);
});
this.roomList = rooms;
// groupping.
this.initGroup();
this.changeDetectorRef.detectChanges();
});
@ -218,41 +223,6 @@ export class ListSectionComponent implements OnInit, OnDestroy {
}
}
initGroup() {
this.roomGroup = [];
this.roomList.forEach((roomInfo) => {
const date = roomInfo.finalEventDate;
let division = '';
try {
const value = this.dateService.get(date, 'LL');
if (value === 'Invalid date') {
division = date;
} else {
division = value;
}
} catch (error) {
division = date;
}
const index = this.roomGroup.findIndex(
(info) => info.division === division
);
if (index > -1) {
this.roomGroup[index] = {
...this.roomGroup[index],
roomList: [...this.roomGroup[index].roomList, roomInfo]
};
} else {
this.roomGroup.push({
division,
roomList: [roomInfo]
});
}
});
}
getRoomName(roomInfo: RoomInfo): string {
if (!roomInfo) {
return '';
@ -392,8 +362,8 @@ export class ListSectionComponent implements OnInit, OnDestroy {
ConfirmDialogResult
>(ConfirmDialogComponent, {
data: {
title: this.i18nService.t('room.dialog.titleExitFromRoom'),
html: this.i18nService.t('room.dialog.confirmExitFromRoom')
title: this.i18nService.t('dialog.title.exitFromRoom'),
html: this.i18nService.t('dialog.confirmExitFromRoom')
}
});

View File

@ -18,6 +18,7 @@
<app-chat-message-box
*ngFor="let event of eventList"
[message]="event"
[roomInfo]="currentRoomInfo"
[isMe]="event.senderSeq + '' === loginRes?.userSeq + ''"
[senderInfo]="getSenderInfo(event.senderSeq)"
[defaultProfileImage]="defaultProfileImage"

View File

@ -21,7 +21,8 @@ import { Chatting } from '@ucap/ng-store-chat/lib/store/chatting/state';
import { ChattingSelector, RoomSelector } from '@ucap/ng-store-chat';
import {
UserInfo as RoomUserInfo,
UserInfoShort as RoomUserInfoShort
UserInfoShort as RoomUserInfoShort,
RoomInfo
} from '@ucap/protocol-room';
import { LoginResponse } from '@ucap/protocol-authentication';
import {
@ -31,6 +32,7 @@ import {
import { AppChatService } from '@app/services/app-chat.service';
import { VersionInfo2Response } from '@ucap/api-public';
import moment from 'moment';
import { Dictionary } from '@ngrx/entity';
@Component({
selector: 'app-sections-chat-message',
@ -59,6 +61,7 @@ export class MessageSectionComponent implements OnInit, OnDestroy {
roomIdSubject = new Subject<string>();
currentRoomInfo: RoomInfo;
chatting$: Observable<Chatting>;
roomUsers: RoomUserInfoShort[] = [];
// eventList$: Observable<Info<EventJson>[]>;
@ -91,6 +94,19 @@ export class MessageSectionComponent implements OnInit, OnDestroy {
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(
(state: any) => state.chat.room.rooms.entities as Dictionary<RoomInfo>
)
)
.subscribe((rooms) => {
if (!!this.roomId) {
this.currentRoomInfo = rooms[this.roomId];
}
});
}
initializeRoomData() {

View File

@ -0,0 +1,86 @@
<div class="dialog-container">
<app-layouts-default-dialog
[disableClose]="false"
(closed)="onClosed($event)"
>
<div appLayoutsDefaultDialog="header">
{{ 'dialog.title.newChatRoom' | ucapI18n }}
</div>
<div class="dialog-body" appLayoutsDefaultDialog="body">
<mat-horizontal-stepper
[linear]="true"
#stepper
[selectedIndex]="currentStep"
>
<mat-step label="Select room type">
<div
class="normal-room"
[ngClass]="
isTimer !== undefined && isTimer === false ? 'checked' : ''
"
(click)="isTimer = false"
>
<span class="title">{{ 'dialog.normalRoom' | ucapI18n }}</span>
<img />
<div
class="description"
[innerHTML]="
'dialog.normalRoomDescription'
| ucapI18n: { maxCount: maxChatRoomUser }
"
></div>
</div>
<div
class="normal-room"
[ngClass]="
isTimer !== undefined && isTimer === true ? 'checked' : ''
"
(click)="isTimer = true"
>
<span class="title">{{ 'dialog.timerRoom' | ucapI18n }}</span>
<img />
<div
class="description"
[innerHTML]="
'dialog.timerRoomDescription'
| ucapI18n: { maxCount: maxChatRoomUser }
"
></div>
</div>
</mat-step>
<mat-step label="Select users">
<app-group-select-user
[isDialog]="true"
[checkable]="true"
(changeUserList)="onChangeUserList($event)"
></app-group-select-user>
</mat-step>
</mat-horizontal-stepper>
</div>
<div appLayoutsDefaultDialog="action">
<button mat-button (click)="onCancel(stepper)">
{{
(stepper.selectedIndex === 0
? 'dialog.button.cancel'
: 'dialog.button.previous'
) | ucapI18n
}}
</button>
<button
mat-button
*ngIf="stepper.selectedIndex === 0"
(click)="onConfirm(stepper)"
>
{{ 'dialog.button.selectRoomUser' | ucapI18n }}
</button>
<button
mat-button
*ngIf="stepper.selectedIndex === 1"
(click)="onOpenRoom(stepper)"
>
{{ 'dialog.button.openRoom' | ucapI18n }}
</button>
</div>
</app-layouts-default-dialog>
</div>

View File

@ -0,0 +1,9 @@
.dialog-container {
width: 100%;
height: 100%;
.dialog-body {
width: 100%;
height: 100%;
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { CreateDialogComponent } from './create.dialog.component';
describe('ucap::ui-organization::CreateChatDialogComponent', () => {
let component: CreateDialogComponent;
let fixture: ComponentFixture<CreateDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CreateDialogComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreateDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,132 @@
import { Subject } from 'rxjs';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
Input
} from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialog
} from '@angular/material/dialog';
import { UserInfo } from '@ucap/protocol-sync';
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
import { UserInfo as RoomUserInfo } from '@ucap/protocol-room';
import { MatStepper } from '@angular/material/stepper';
import { I18nService } from '@ucap/ng-i18n';
import { GroupActions } from '@ucap/ng-store-group';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
import { environment } from '@environments';
import { AppChatService } from '@app/services/app-chat.service';
export type UserInfoTypes =
| UserInfo
| UserInfoSS
| UserInfoF
| UserInfoDN
| RoomUserInfo;
export interface CreateDialogData {}
export interface CreateDialogResult {
userSeqs: string[];
isTimer: boolean | undefined;
}
@Component({
selector: 'app-dialog-chat-create',
templateUrl: './create.dialog.component.html',
styleUrls: ['./create.dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CreateDialogComponent implements OnInit, OnDestroy {
currentStep = 0;
maxChatRoomUser: number;
isTimer: boolean | undefined;
selectedUserList: UserInfoTypes[] = [];
constructor(
public dialogRef: MatDialogRef<CreateDialogData, CreateDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: CreateDialogData,
private i18nService: I18nService,
public dialog: MatDialog,
private changeDetectorRef: ChangeDetectorRef
) {
this.maxChatRoomUser = environment.productConfig.chat.maxChatRoomUser;
}
ngOnInit(): void {}
ngOnDestroy(): void {}
onClosed(event: MouseEvent): void {
this.dialogRef.close();
}
onCancel(stepper: MatStepper) {
if (stepper.selectedIndex > 0) {
stepper.previous();
return;
}
this.dialogRef.close();
}
onConfirm(stepper: MatStepper) {
// validation.
if (this.isTimer === undefined) {
this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: this.i18nService.t('errors.label'),
html: this.i18nService.t('errors.emptyOpenRoomType', {
maxCount: this.maxChatRoomUser
})
}
});
return;
}
stepper.next();
}
onOpenRoom(stepper: MatStepper) {
const userSeqs: string[] = [];
this.selectedUserList.map((user) => userSeqs.push(user.seq.toString()));
if (this.selectedUserList.length >= this.maxChatRoomUser) {
this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: this.i18nService.t('errors.label'),
html: this.i18nService.t('errors.maxCountOfRoomMemberWith', {
maxCount: this.maxChatRoomUser
})
}
});
return;
}
// Open Room.
this.dialogRef.close({ userSeqs, isTimer: this.isTimer });
}
onChangeUserList(selectedUserList: UserInfoTypes[]) {
this.selectedUserList = selectedUserList;
}
}

View File

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

View File

@ -3,11 +3,12 @@ import { SearchSectionComponent } from './search.section.component';
import { ProfileSectionComponent } from './profile.section.component';
import { InfoSectionComponent } from './info.section.component';
import { SelectUserSectionComponent } from './select-user.section.component';
import { SelectGroupSectionComponent } from './select-group.section.component';
export const COMPONENTS = [
ListSectionComponent,
SearchSectionComponent,
ProfileSectionComponent,
InfoSectionComponent,
SelectUserSectionComponent
SelectUserSectionComponent,
SelectGroupSectionComponent
];

View File

@ -1,5 +1,5 @@
<div
*ngIf="!!searchObj && !searchObj.isShowSearch"
*ngIf="!!searchData && !searchData.bySearch"
fxFlexFill
class="list-container"
>
@ -8,20 +8,13 @@
[showType]="showType"
(clicked)="onClickUser($event)"
(selectGroupMenu)="onSelectGroupMenu($event)"
(selectProfileMenu)="onSelectProfileMenu($event)"
(profileMenu)="onProfileMenu($event)"
></app-group-expansion>
</div>
<div *ngIf="!!searchObj && searchObj.isShowSearch" class="search-wrpper">
<perfect-scrollbar fxFlex="1 1 auto">
<app-group-profile-list-item
*ngFor="let userInfo of searchUserInfos"
[userInfo]="userInfo"
[checkable]="checkable"
defaultProfileImage="assets/images/img_nophoto_50.png"
(checkUser)="onCheckUser($event)"
(click)="onClickSearchUser($event, userInfo)"
[presence]="getStatusBulkInfo(userInfo) | async"
>
</app-group-profile-list-item>
</perfect-scrollbar>
<div *ngIf="!!searchData && searchData.bySearch" class="search-wrpper">
<app-group-profile-list
[searchData]="_searchData"
[checkable]="checkable"
></app-group-profile-list>
</div>

View File

@ -29,7 +29,7 @@ import { LogService } from '@ucap/ng-logger';
import { ExpansionComponent as AppExpansionComponent } from '@app/ucap/group/components/expansion.component';
import { SessionStorageService } from '@ucap/ng-web-storage';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { GroupActions } from '@ucap/ng-store-group';
import { GroupActions, BuddyActions } from '@ucap/ng-store-group';
import { I18nService } from '@ucap/ng-i18n';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
@ -52,7 +52,22 @@ import {
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
import {
ManageDialogComponent,
ManageDialogData,
ManageDialogResult
} from '../dialogs/manage.dialog.component';
import { PresenceActions, PresenceSelector } from '@ucap/ng-store-organization';
import { GroupUserDialaogType } from '@app/types';
import { EditInlineInputDialogComponent } from '../dialogs/edit-inline-input.dialog.component';
import {
EditUserDialogComponent,
EditUserDialogData,
EditUserDialogResult
} from '../dialogs/edit-user.dialog.component';
import { SearchData } from '@app/ucap/organization/models/search-data';
import { UserStore } from '@app/models/user-store';
export type UserInfoTypes =
| UserInfo
@ -81,24 +96,34 @@ export class GroupVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
})
export class ListSectionComponent implements OnInit, OnDestroy {
@Input()
set searchObj(obj: {
isShowSearch: boolean;
companyCode: string;
searchWord: string;
}) {
this._searchObj = obj;
if (obj.isShowSearch && obj.searchWord.localeCompare('') !== 0) {
this.onOrganizationTenantSearch(obj);
set searchData(searchData: SearchData) {
this._searchData = searchData;
if (!searchData) {
this._searchData = {
companyCode: this.userStore.companyCode
};
} else {
this._searchObj.isShowSearch = false;
this.searchUserInfos = [];
if (!this._searchData.companyCode) {
this._searchData.companyCode = this.userStore.companyCode;
}
if (
searchData.bySearch &&
searchData.searchWord.localeCompare('') !== 0
) {
this.onOrganizationTenantSearch(searchData);
} else {
this._searchData.isShowSearch = false;
this.searchUserInfos = [];
}
}
}
get searchObj() {
return this._searchObj;
get searchData() {
return this._searchData;
}
_searchObj: any;
// tslint:disable-next-line: variable-name
_searchData: any;
@Input()
checkable = false;
@ -120,6 +145,7 @@ export class ListSectionComponent implements OnInit, OnDestroy {
searchUserInfos: UserInfoSS[] = [];
private ngOnDestroySubject = new Subject<boolean>();
private userStore: UserStore;
constructor(
private router: Router,
@ -132,6 +158,7 @@ export class ListSectionComponent implements OnInit, OnDestroy {
private queryProtocolService: QueryProtocolService,
public dialog: MatDialog
) {
this.userStore = this.appAuthenticationService.getUserStore();
this.i18nService.setDefaultNamespace('group');
}
@ -151,19 +178,15 @@ export class ListSectionComponent implements OnInit, OnDestroy {
}
}
onOrganizationTenantSearch(obj: {
isShowSearch: boolean;
companyCode: string;
searchWord: string;
}) {
onOrganizationTenantSearch(searchData: SearchData) {
const searchUserInfos: UserInfoSS[] = [];
this.queryProtocolService
.deptUser({
divCd: 'GRP',
companyCode: this._searchObj.companyCode,
companyCode: searchData.companyCode,
searchRange: DeptSearchType.All,
search: this._searchObj.searchWord,
search: searchData.searchWord,
senderCompanyCode: this.loginRes.userInfo.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
})
@ -234,7 +257,11 @@ export class ListSectionComponent implements OnInit, OnDestroy {
onProfileMenu(event) {
console.log(event);
}
onSelectGroupMenu(params: { menuType: string; group: GroupDetailData }) {
onSelectGroupMenu(params: {
menuType: string;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
rect: any;
}) {
switch (params.menuType) {
case 'CHAT':
{
@ -266,10 +293,31 @@ export class ListSectionComponent implements OnInit, OnDestroy {
break;
case 'RENAME':
{
this.renameGroup(params);
}
break;
case 'MANAGE_MEMBER':
{
const dialogRef = this.dialog.open<
ManageDialogComponent,
ManageDialogData,
ManageDialogResult
>(ManageDialogComponent, {
width: '100%',
height: '100%',
data: {
title: '그룹 멤버 관리',
groupBuddyList: params.groupBuddyList
}
});
dialogRef
.afterClosed()
.pipe(take(1))
.subscribe((result) => {
if (!!result && !!result.type) {
this.manageGroup(result);
}
});
}
break;
case 'DELETE':
@ -289,7 +337,7 @@ export class ListSectionComponent implements OnInit, OnDestroy {
.pipe(take(1))
.subscribe((result) => {
if (!!result && !!result.choice) {
this.store.dispatch(GroupActions.del({ group: params.group }));
GroupActions.del({ group: params.groupBuddyList.group });
}
});
}
@ -298,41 +346,168 @@ export class ListSectionComponent implements OnInit, OnDestroy {
break;
}
}
// onEditGroupName(params: { editName: string; group: GroupDetailData }) {
// if (params.editName.localeCompare('') === 0) {
// const dialogRef = this.dialog.open<
// AlertDialogComponent,
// AlertDialogData,
// AlertDialogResult
// >(AlertDialogComponent, {
// data: {
// title: this.i18nService.t('moreMenu.error.label'),
// html: this.i18nService.t('moreMenu.error.requireName')
// }
// });
// dialogRef
// .afterClosed()
// .pipe(
// take(1),
// map((result) => {}),
// catchError((err) => {
// return of(err);
// })
// )
// .subscribe();
// return;
// }
// this.store.dispatch(
// GroupActions.update({
// req: {
// groupSeq: params.group.seq,
// groupName: params.editName,
// userSeqs: params.group.userSeqs
// }
// })
// );
// console.log(params.editName);
// }
onSelectProfileMenu(params: {
menuType: string;
userInfo: UserInfoF;
group: GroupDetailData;
rect: any;
}) {
switch (params.menuType) {
case 'REGISTER_FAVORITE':
this.store.dispatch(
BuddyActions.update({
req: {
seq: Number(params.userInfo.seq),
isFavorit: !params.userInfo.isFavorit
}
})
);
break;
case 'NICKNAME':
{
this.editNickname(params.userInfo, params.rect);
}
break;
case 'COPY_BUDDY':
this.editUserDialog('COPY_BUDDY', params.group, params.userInfo);
break;
case 'MOVE_BUDDY':
this.editUserDialog('MOVE_BUDDY', params.group, params.userInfo);
break;
case 'REMOVE_BUDDY':
{
this.removeBuddy(params.userInfo, params.group);
}
break;
}
}
private renameGroup(params: {
menuType: string;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
rect: any;
}) {
const paramGroup = params.groupBuddyList.group;
const dialogRef = this.dialog.open(EditInlineInputDialogComponent, {
width: params.rect.width,
height: params.rect.height,
panelClass: 'ucap-edit-group-name-dialog',
data: {
curValue: paramGroup.name,
placeholder: '그룹명을 입력하세요.',
left: params.rect.left,
top: params.rect.top
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (
!!result &&
result.choice &&
result.curValue.localeCompare(paramGroup.name) !== 0
) {
this.store.dispatch(
GroupActions.update({
req: {
groupSeq: paramGroup.seq,
groupName: result.curValue,
userSeqs: paramGroup.userSeqs
}
})
);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
private manageGroup(result: ManageDialogResult) {
let targetGroup: GroupDetailData;
let targetUserSeqs: string[];
if (result.type === GroupUserDialaogType.Add) {
targetGroup = result.group;
targetUserSeqs = [];
result.selelctUserList.forEach((userInfo) => {
targetUserSeqs.push(userInfo.seq + '');
});
this.store.dispatch(
GroupActions.updateMember({ targetGroup, targetUserSeqs })
);
} else if (result.type === GroupUserDialaogType.Copy) {
if (!!result.selectGroupList && result.selectGroupList.length > 0) {
result.selectGroupList.forEach((g) => {
targetGroup = g;
targetUserSeqs = [];
g.userSeqs.map((seq) => {
targetUserSeqs.push(seq);
});
if (targetUserSeqs.length === 0) {
result.selelctUserList.forEach((user) => {
targetUserSeqs.push(user.seq as any);
});
} else {
result.selelctUserList.forEach((user) => {
const find = targetUserSeqs.indexOf(user.seq as any);
if (find < 0) {
targetUserSeqs.push(user.seq as any);
}
});
}
this.store.dispatch(
GroupActions.updateMember({ targetGroup, targetUserSeqs })
);
});
}
} else if (result.type === GroupUserDialaogType.Move) {
const fromGroup = result.group;
let toGroup: GroupDetailData;
targetUserSeqs = [];
if (!!result.selectGroupList && result.selectGroupList.length > 0) {
result.selectGroupList.forEach((g) => {
toGroup = g;
targetUserSeqs = [];
result.selelctUserList.forEach((user) => {
targetUserSeqs.push(user.seq as any);
});
this.store.dispatch(
GroupActions.moveMember({
fromGroup,
toGroup,
targetUserSeq: targetUserSeqs
})
);
});
}
} else if (result.type === GroupUserDialaogType.Create) {
targetUserSeqs = [];
result.selelctUserList.forEach((userInfo) => {
targetUserSeqs.push(userInfo.seq + '');
});
this.store.dispatch(
GroupActions.create({
groupName: result.groupName,
targetUserSeqs
})
);
}
}
getStatusBulkInfo(buddy: UserInfoTypes) {
return this.store.pipe(
@ -342,4 +517,188 @@ export class ListSectionComponent implements OnInit, OnDestroy {
)
);
}
private editUserDialog(
type: string,
group: GroupDetailData,
userInfo: UserInfoTypes
) {
let title = '';
let dialogType: GroupUserDialaogType;
if (type === 'COPY_BUDDY') {
title = '멤버 복사';
dialogType = GroupUserDialaogType.Copy;
} else {
title = '멤버 이동';
dialogType = GroupUserDialaogType.Move;
}
const dialogRef = this.dialog.open<
EditUserDialogComponent,
EditUserDialogData,
EditUserDialogResult
>(EditUserDialogComponent, {
width: '100%',
height: '100%',
data: {
title,
type: dialogType,
group,
userInfo
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result: EditUserDialogResult) => {
let targetGroup: GroupDetailData;
let targetUserSeqs: string[];
if (result.type === GroupUserDialaogType.Add) {
targetGroup = result.group;
targetUserSeqs = [];
result.selelctUserList.forEach((u) => {
targetUserSeqs.push(u.seq + '');
});
this.store.dispatch(
GroupActions.updateMember({ targetGroup, targetUserSeqs })
);
} else if (result.type === GroupUserDialaogType.Copy) {
if (!!result.selectGroupList && result.selectGroupList.length > 0) {
result.selectGroupList.forEach((g) => {
targetGroup = g;
targetUserSeqs = [];
g.userSeqs.map((seq) => {
targetUserSeqs.push(seq);
});
if (targetUserSeqs.length === 0) {
result.selelctUserList.forEach((user) => {
targetUserSeqs.push(user.seq as any);
});
} else {
result.selelctUserList.forEach((user) => {
const find = targetUserSeqs.indexOf(user.seq as any);
if (find < 0) {
targetUserSeqs.push(user.seq as any);
}
});
}
this.store.dispatch(
GroupActions.updateMember({ targetGroup, targetUserSeqs })
);
});
}
} else if (result.type === GroupUserDialaogType.Move) {
const fromGroup = result.group;
let toGroup: GroupDetailData;
targetUserSeqs = [];
if (!!result.selectGroupList && result.selectGroupList.length > 0) {
result.selectGroupList.forEach((g) => {
toGroup = g;
targetUserSeqs = [];
result.selelctUserList.forEach((user) => {
targetUserSeqs.push(user.seq as any);
});
this.store.dispatch(
GroupActions.moveMember({
fromGroup,
toGroup,
targetUserSeq: targetUserSeqs
})
);
});
}
} else if (result.type === GroupUserDialaogType.Create) {
targetUserSeqs = [];
result.selelctUserList.forEach((u) => {
targetUserSeqs.push(u.seq + '');
});
this.store.dispatch(
GroupActions.create({
groupName: result.groupName,
targetUserSeqs
})
);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
private removeBuddy(userInfo: UserInfoF, group: GroupDetailData) {
const dialogRef = this.dialog.open<
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
>(ConfirmDialogComponent, {
data: {
title: '',
html: this.i18nService.t('label.confirmRemoveBuddy')
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (!!result && result.choice) {
const trgtUserSeq = group.userSeqs.filter(
(user) => user + '' !== userInfo.seq + ''
);
this.store.dispatch(
GroupActions.updateMember({
targetGroup: group,
targetUserSeqs: trgtUserSeq
})
);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
private editNickname(userInfo: UserInfoF, rect: any) {
const dialogRef = this.dialog.open(EditInlineInputDialogComponent, {
width: rect.width - 30 + '',
height: rect.height,
panelClass: 'ucap-edit-group-name-dialog',
data: {
curValue: userInfo.nickName,
placeholder: '닉네임을 설정하세요.',
left: rect.left + 70,
top: rect.top
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (
!!result &&
result.choice &&
result.curValue.localeCompare(userInfo.nickName) !== 0
) {
this.store.dispatch(
BuddyActions.nickname({
req: {
userSeq: Number(userInfo.seq),
nickname: result.curValue
}
})
);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
}

View File

@ -0,0 +1,70 @@
<div fxFlexFill>
<div fxFlex class="container">
<!-- search start-->
<div>
<app-organization-search-for-tenant
placeholder="이름 부서명, 전화번호, 이메일"
[(searchData)]="companySearchData"
(canceled)="onCanceled()"
>
</app-organization-search-for-tenant>
</div>
<!-- search end-->
<div *ngIf="!isSearch">
<form name="inputForm" [formGroup]="inputForm" novalidate>
<mat-form-field
hintLabel="금지단어[-,_]"
style="display: block; margin-bottom: 10px;"
>
<input
matInput
#input
maxlength="20"
placeholder="그룹명을 입력하세요."
formControlName="groupName"
(keyup)="onKeyupGroupName()"
/>
<mat-hint align="end">{{ input.value?.length || 0 }}/20</mat-hint>
<!-- <mat-error *ngIf="inputForm.get('groupName').hasError('groupNameBanned')"> -->
<!-- {{
'group.errors.bannedWords'
| translate: { bannedWords: appService.bannedGroupNames.join(',') }
}} -->
<!-- 금지단어[-,_] -->
<!-- </mat-error> -->
<!-- <mat-error *ngIf="inputForm.get('groupName').hasError('groupNameSamed')"> -->
<!-- {{ 'group.errors.sameNameExist' | translate }} -->
<!-- 이미 존재하는 그룹명입니다. -->
<!-- </mat-error> -->
</mat-form-field>
</form>
<div *ngIf="!!groupList && groupList.length > 0">
<span>기존 그룹 지정</span>
<div fxFlexFill class="group-list-container">
<div *ngFor="let group of groupList">
<div *ngIf="checkVisible(group)">
<span>{{ group.name }}</span>
<div>
<mat-checkbox
#checkbox
[checked]="groupChecked"
(change)="onCheckForGroup($event.source, group)"
></mat-checkbox>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="!!isSearch" class="search-wrpper" style="height: 400px;">
<app-group-profile-list
[searchData]="companySearchData"
[checkable]="checkable"
[isDialog]="isDialog"
(toggleCheck)="onToggleCheck($event)"
></app-group-profile-list>
</div>
</div>
</div>

View File

@ -0,0 +1,2 @@
.profile-container {
}

View File

@ -0,0 +1,32 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { SelectGroupSectionComponent } from './select-group.section.component';
describe('app::sections::group::SelectGroupSectionComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [SelectGroupSectionComponent]
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(SelectGroupSectionComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'ucap-lg-web'`, () => {
const fixture = TestBed.createComponent(SelectGroupSectionComponent);
const app = fixture.componentInstance;
});
it('should render title', () => {
const fixture = TestBed.createComponent(SelectGroupSectionComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain(
'ucap-lg-web app is running!'
);
});
});

View File

@ -0,0 +1,226 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input,
Output,
EventEmitter
} from '@angular/core';
import { Subject, of } from 'rxjs';
import { Store, select } from '@ngrx/store';
import { takeUntil, take, map, catchError } from 'rxjs/operators';
import {
LoginSelector,
AuthorizationSelector
} from '@ucap/ng-store-authentication';
import { LoginResponse } from '@ucap/protocol-authentication';
import { QueryProtocolService } from '@ucap/ng-protocol-query';
import {
UserInfoSS,
AuthResponse,
DeptSearchType,
UserInfoF,
UserInfoDN
} from '@ucap/protocol-query';
import { GroupSelector } from '@ucap/ng-store-group';
import { GroupDetailData, UserInfo } from '@ucap/protocol-sync';
import { PresenceActions } from '@ucap/ng-store-organization';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { UserInfo as RoomUserInfo } from '@ucap/protocol-room';
import { MatDialog } from '@angular/material/dialog';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
import { MatCheckbox } from '@angular/material/checkbox';
import { SearchData } from '@app/ucap/organization/models/search-data';
export type UserInfoTypes =
| UserInfo
| UserInfoSS
| UserInfoF
| UserInfoDN
| RoomUserInfo;
@Component({
selector: 'app-sections-select-group',
templateUrl: './select-group.section.component.html',
styleUrls: ['./select-group.section.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SelectGroupSectionComponent implements OnInit, OnDestroy {
@Input()
isMemberMove: boolean;
@Input()
isDialog = false;
@Input()
checkable = false;
@Input()
curGroup: GroupDetailData;
@Output()
changeUserList: EventEmitter<{
checked: boolean;
userInfo: UserInfoSS;
}> = new EventEmitter();
@Output()
changeGroupList: EventEmitter<GroupDetailData[]> = new EventEmitter();
@Output()
changeGroupName: EventEmitter<string> = new EventEmitter();
set companySearchData(searchData: SearchData) {
this._companySearchData = searchData;
this.onChangedCompanySearch();
}
get companySearchData() {
return this._companySearchData;
}
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
private ngOnDestroySubject = new Subject<boolean>();
constructor(
private store: Store<any>,
private queryProtocolService: QueryProtocolService,
private changeDetectorRef: ChangeDetectorRef,
private formBuilder: FormBuilder,
public dialog: MatDialog
) {}
loginRes: LoginResponse;
isSearch = false;
searchWord: string;
searchUserInfos: UserInfoSS[] = [];
groupList: GroupDetailData[];
selectedUserList: UserInfoTypes[] = [];
selectedGroupList: GroupDetailData[] = [];
inputForm: FormGroup;
groupChecked = false;
groupName: string;
ngOnInit(): void {
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(GroupSelector.groups))
.subscribe((groups) => {
this.groupList = groups;
});
this.inputForm = this.formBuilder.group({
groupName: [
this.groupName,
[
// Validators.required
// StringUtil.includes(, CharactorType.Special),
// this.checkBanWords(),
// this.checkSameName()
]
]
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.complete();
}
}
onKeyupGroupName() {
this.inputForm.get('groupName').markAsTouched();
this.changeGroupName.emit(this.inputForm.get('groupName').value);
}
onCheckForGroup(checbox: MatCheckbox, group: GroupDetailData) {
if (
this.isMemberMove &&
!!this.selectedGroupList &&
this.selectedGroupList.length > 0 &&
this.selectedGroupList[0].seq !== group.seq
) {
this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: '멤버이동',
html: '멤버이동은 그룹 여러개를 선택할 수 없습니다.'
}
});
checbox.checked = false;
return;
}
if (
this.selectedGroupList.filter((g) => g.seq === group.seq).length === 0
) {
this.selectedGroupList = [...this.selectedGroupList, group];
} else {
this.selectedGroupList = this.selectedGroupList.filter(
(g) => g.seq !== group.seq
);
}
this.changeGroupList.emit(this.selectedGroupList);
}
// onCheckForUser(params: { isChecked: boolean; userInfo: UserInfoTypes }) {
// if (
// this.selectedUserList.filter((user) => user.seq === params.userInfo.seq)
// .length === 0
// ) {
// this.selectedUserList = [...this.selectedUserList, params.userInfo];
// } else {
// this.selectedUserList = this.selectedUserList.filter(
// (item) => item.seq !== params.userInfo.seq
// );
// }
// this.changeUserList.emit(this.selectedUserList);
// }
onToggleCheck(data: { checked: boolean; userInfo: UserInfoSS }) {
this.changeUserList.emit(data);
}
onChangedCompanySearch() {
this.isSearch = true;
}
onCanceled() {
this.isSearch = false;
}
getCheckedUser(userInfo: UserInfoTypes) {
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
return (
this.selectedUserList.filter((item) => item.seq === userInfo.seq)
.length > 0
);
}
return false;
}
checkVisible(group: GroupDetailData): boolean {
if (!!this.curGroup && this.curGroup.seq === group.seq) {
return false;
}
return true;
}
}

View File

@ -4,7 +4,7 @@
<div>
<app-organization-search-for-tenant
placeholder="이름 부서명, 전화번호, 이메일"
(changed)="onChanged($event)"
[(searchData)]="companySearchData"
(canceled)="onCanceled()"
>
</app-organization-search-for-tenant>
@ -24,12 +24,13 @@
<div fxFlexFill>
<div class="organization-tree">
<app-group-expansion
style="height: 400px;"
fxFlexFill
[isDialog]="isDialog"
[selectedUserList]="selectedUserList"
[checkable]="true"
(checked)="onCheckUser($event)"
(checkGroup)="onCheckGroup($event)"
[checkable]="checkable"
(toggleCheckUser)="onToggleCheckUser($event)"
(toggleCheckGroup)="onToggleCheckGroup($event)"
></app-group-expansion>
</div>
</div>
@ -59,73 +60,14 @@
</mat-tab>
</mat-tab-group>
</div>
<div *ngIf="!!isSearch" class="search-wrpper">
<perfect-scrollbar fxFlex="1 1 auto">
<app-group-profile-list-item
*ngFor="let userInfo of searchUserInfos"
[userInfo]="userInfo"
[checkable]="checkable"
[isChecked]="getCheckedUser(userInfo)"
defaultProfileImage="assets/images/img_nophoto_50.png"
(checked)="onCheckUser($event)"
>
</app-group-profile-list-item>
</perfect-scrollbar>
<div *ngIf="!!isSearch" class="search-wrpper" style="height: 400px;">
<app-group-profile-list
[searchData]="companySearchData"
[selectedUser]="selectedUserList"
[checkable]="checkable"
[isDialog]="isDialog"
(toggleCheck)="onToggleCheckUser($event)"
></app-group-profile-list>
</div>
</div>
</div>
<div>
<ng-template [ngTemplateOutlet]="selectedUserListTemplate"></ng-template>
</div>
<ng-template #selectedUserListTemplate>
<div class="list-chip">
<mat-chip-list aria-label="User selection">
<mat-chip
*ngFor="let userInfo of selectedUserList"
[selected]="getChipsRemoveYn(userInfo)"
(removed)="onClickDeleteUser(userInfo)"
>
<!-- {{ userInfo | ucapTranslate: 'name' }} -->
{{ userInfo.name }}
<mat-icon matChipRemove *ngIf="getChipsRemoveYn(userInfo)"
>clear</mat-icon
>
</mat-chip>
</mat-chip-list>
</div>
<ng-container
*ngIf="
SelectUserDialogType.NewChat === SelectUserDialogType.NewChat;
then newchatcount;
else defaultcount
"
></ng-container>
<ng-template #newchatcount>
<span [ngClass]="selectedUserList.length >= 300 ? 'text-warn-color' : ''">
{{ selectedUserList.length }} / 300
<!-- {{ environment.productConfig.CommonSetting.maxChatRoomUser - 1 }} -->
<!-- {{ 'common.units.persons' | translate }} -->
</span>
<span
class="text-warn-color"
style="float: right;"
*ngIf="selectedUserList.length >= 300"
>
<!-- ({{
'chat.errors.maxCountOfRoomMemberWith'
| translate
: {
maxCount:
environment.productConfig.CommonSetting.maxChatRoomUser - 1
}
}}) -->
</span>
</ng-template>
<ng-template #defaultcount>
<span>
{{ selectedUserList.length }}
<!-- {{ 'common.units.persons' | translate }} -->
</span>
</ng-template>
</ng-template>

View File

@ -27,6 +27,7 @@ import {
import { UserInfo as RoomUserInfo } from '@ucap/protocol-room';
import { LoginResponse } from '@ucap/protocol-authentication';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { SearchData } from '@app/ucap/organization/models/search-data';
export type UserInfoTypes =
| UserInfo
@ -61,16 +62,22 @@ export class SelectUserSectionComponent implements OnInit, OnDestroy {
existUsers: UserInfoTypes[];
@Input()
set checkable(check: boolean) {
this._checkable = check;
}
get checkable(): boolean {
return this._checkable;
}
_checkable = false;
isSelectionOff = true;
@Input()
checkable = false;
@Output()
changeUserList = new EventEmitter<UserInfoTypes[]>();
toggleCheckUser: EventEmitter<{
checked: boolean;
userInfo: UserInfoSS;
}> = new EventEmitter();
@Output()
toggleCheckGroup: EventEmitter<{
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}> = new EventEmitter();
@Output()
cancel = new EventEmitter();
@ -78,11 +85,23 @@ export class SelectUserSectionComponent implements OnInit, OnDestroy {
@Output()
confirm = new EventEmitter();
set companySearchData(searchData: SearchData) {
this._companySearchData = searchData;
this.onChangedCompanySearch();
}
get companySearchData() {
return this._companySearchData;
}
// tslint:disable-next-line: variable-name
_companySearchData: SearchData;
isSearch = false;
searchWord: string;
private ngOnDestroySubject = new Subject<boolean>();
@Input()
selectedUserList: UserInfoTypes[] = [];
searchUserInfos: UserInfoSS[] = [];
@ -113,20 +132,15 @@ export class SelectUserSectionComponent implements OnInit, OnDestroy {
}
}
onCheckUser(params: { isChecked: boolean; userInfo: UserInfoTypes }) {
console.log(params);
if (
this.selectedUserList.filter((user) => user.seq === params.userInfo.seq)
.length === 0
) {
this.selectedUserList = [...this.selectedUserList, params.userInfo];
} else {
this.selectedUserList = this.selectedUserList.filter(
(item) => item.seq !== params.userInfo.seq
);
}
onToggleCheckGroup(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
this.toggleCheckGroup.emit(params);
}
this.changeUserList.emit(this.selectedUserList);
onToggleCheckUser(data: { checked: boolean; userInfo: UserInfoSS }) {
this.toggleCheckUser.emit(data);
}
getCheckedUser(userInfo: UserInfoTypes) {
@ -139,96 +153,8 @@ export class SelectUserSectionComponent implements OnInit, OnDestroy {
return false;
}
onCheckGroup(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
if (params.isChecked) {
params.groupBuddyList.buddyList.forEach((item) => {
if (
this.selectedUserList.filter((user) => user.seq === item.seq)
.length === 0
) {
this.selectedUserList = [...this.selectedUserList, item];
}
});
} else {
this.selectedUserList = this.selectedUserList.filter(
(item) =>
params.groupBuddyList.buddyList.filter((del) => del.seq === item.seq)
.length === 0
);
}
this.changeDetectorRef.markForCheck();
}
getSelectedUserList(): any[] {
return this.selectedUserList;
}
getChipsRemoveYn(userInfo: UserInfoTypes) {
if (!!this.existUsers && this.existUsers.length > 0) {
return !(
this.existUsers.filter((user) => user.seq === userInfo.seq).length > 0
);
} else {
return true;
}
}
onClickDeleteUser(userInfo: UserInfoTypes) {
this.selectedUserList = this.selectedUserList.filter(
(item) => item.seq !== userInfo.seq
);
this.changeDetectorRef.markForCheck();
}
onChanged(data: { companyCode: string; searchWord: string }) {
onChangedCompanySearch() {
this.isSearch = true;
this.searchWord = data.searchWord;
const searchUserInfos: UserInfoSS[] = [];
this.queryProtocolService
.deptUser({
divCd: 'GRP',
companyCode: data.companyCode,
searchRange: DeptSearchType.All,
search: data.searchWord,
senderCompanyCode: this.loginRes.userInfo.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
})
.pipe(
map((resObj) => {
const userInfos = resObj.userInfos;
searchUserInfos.push(...userInfos);
// 검색 결과 처리.
this.searchUserInfos = searchUserInfos.sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);
// this.searchProcessing = false;
// 검색 결과에 따른 프레즌스 조회.
const userSeqList: number[] = [];
this.searchUserInfos.map((user) =>
userSeqList.push(Number(user.seq))
);
this.changeDetectorRef.markForCheck();
if (userSeqList.length > 0) {
// this.store.dispatch(
// StatusStore.bulkInfo({
// divCd: 'groupSrch',
// userSeqs: userSeqList
// })
// );
}
}),
catchError((error) => {
// this.searchProcessing = false;
return of(error);
})
)
.subscribe();
}
onCanceled() {

View File

@ -43,12 +43,25 @@
</mat-step>
<mat-step label="Step 1">
<app-group-select-user
style="height: 500px;"
[isDialog]="true"
[checkable]="true"
(changeUserList)="onChangeUserList($event)"
[selectedUserList]="selectedUserList"
(toggleCheckUser)="onChangeUserList($event)"
(toggleCheckGroup)="onChangeGroupList($event)"
></app-group-select-user>
</mat-step>
</mat-horizontal-stepper>
<div>
<ucap-organization-profile-selection-01
[userInfoList]="selectedUserList"
[removableFor]="removableForSelection"
[colorFor]="colorForSelection"
(removed)="onRemovedProfileSelection($event)"
>
</ucap-organization-profile-selection-01>
</div>
</div>
<div appLayoutsDefaultDialog="action">
<button mat-button (click)="onCancel(stepper)">취소</button>

View File

@ -147,7 +147,63 @@ export class CreateDialogComponent implements OnInit, OnDestroy {
this.inputForm.get('groupName').markAsTouched();
}
onChangeUserList(selectedUserList: UserInfoTypes[]) {
this.selectedUserList = selectedUserList;
onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) {
const i = this.selectedUserList.findIndex(
(u) => u.seq === data.userInfo.seq
);
if (data.checked) {
if (-1 === i) {
this.selectedUserList = [...this.selectedUserList, data.userInfo];
}
} else {
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => u.seq !== data.userInfo.seq
);
}
}
}
onChangeGroupList(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
if (params.isChecked) {
params.groupBuddyList.buddyList.forEach((item) => {
if (
this.selectedUserList.filter((user) => user.seq === item.seq)
.length === 0
) {
this.selectedUserList = [...this.selectedUserList, item];
}
});
} else {
this.selectedUserList = this.selectedUserList.filter(
(item) =>
params.groupBuddyList.buddyList.filter((del) => del.seq === item.seq)
.length === 0
);
}
}
onRemovedProfileSelection(userInfo: UserInfo) {
const i = this.selectedUserList.findIndex(
(u) => (u.seq as any) === (userInfo.seq as any)
);
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => (u.seq as any) !== (userInfo.seq as any)
);
}
}
removableForSelection = (userInfo: UserInfo) => {
return true;
};
colorForSelection = (userInfo: UserInfo) => {
return 'accent';
};
}

View File

@ -0,0 +1,36 @@
<div class="dialog-container">
<app-layouts-default-dialog
[disableClose]="false"
(closed)="onClosed($event)"
>
<div appLayoutsDefaultDialog="header">
{{ data.title }}
</div>
<div class="dialog-body" appLayoutsDefaultDialog="body">
<app-sections-select-group
[isMemberMove]="data.type"
[isDialog]="true"
[checkable]="true"
[curGroup]="data.group"
(changeUserList)="onChangeUserList($event)"
(changeGroupList)="onChangeGroupList($event)"
(changeGroupName)="onChangeGroupName($event)"
></app-sections-select-group>
<div>
<ucap-organization-profile-selection-01
[userInfoList]="selectedUserList"
[removableFor]="removableForSelection"
[colorFor]="colorForSelection"
(removed)="onRemovedProfileSelection($event)"
>
</ucap-organization-profile-selection-01>
</div>
</div>
<div appLayoutsDefaultDialog="action">
<button mat-button (click)="onClosed($event)">취소</button>
<button mat-button (click)="onConfirm()">
완료
</button>
</div>
</app-layouts-default-dialog>
</div>

View File

@ -0,0 +1,9 @@
.dialog-container {
width: 100%;
height: 100%;
.dialog-body {
width: 100%;
height: 100%;
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { EditUserDialogComponent } from './edit-user.dialog.component';
describe('ucap::ui-organization::EditUserDialogComponent', () => {
let component: EditUserDialogComponent;
let fixture: ComponentFixture<EditUserDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [EditUserDialogComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditUserDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,149 @@
import { Subject } from 'rxjs';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
Input
} from '@angular/core';
import { Store } from '@ngrx/store';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialog
} from '@angular/material/dialog';
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
import { UserInfo as RoomUserInfo } from '@ucap/protocol-room';
import { I18nService } from '@ucap/ng-i18n';
import { SelectUserDialogType, GroupUserDialaogType } from '@app/types';
export type UserInfoTypes =
| UserInfo
| UserInfoSS
| UserInfoF
| UserInfoDN
| RoomUserInfo;
export interface EditUserDialogData {
title: string;
type: GroupUserDialaogType;
group: GroupDetailData;
userInfo: UserInfoTypes;
}
export interface EditUserDialogResult {
type: GroupUserDialaogType;
groupName: string;
group: GroupDetailData;
selelctUserList?: UserInfoTypes[];
selectGroupList?: GroupDetailData[];
}
@Component({
selector: 'app-dialog-edit-user',
templateUrl: './edit-user.dialog.component.html',
styleUrls: ['./edit-user.dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditUserDialogComponent implements OnInit, OnDestroy {
constructor(
public dialogRef: MatDialogRef<EditUserDialogData, EditUserDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: EditUserDialogData,
private changeDetectorRef: ChangeDetectorRef,
private store: Store<any>,
private i18nService: I18nService,
public dialog: MatDialog
) {}
private ngOnDestroySubject: Subject<void>;
currentType: GroupUserDialaogType;
selectedUserList: UserInfoTypes[] = [];
selectedGroupList: GroupDetailData[] = [];
groupName = '';
SelectUserDialogType = SelectUserDialogType;
ngOnInit(): void {
this.ngOnDestroySubject = new Subject();
this.selectedUserList.push(this.data.userInfo);
this.currentType = this.data.type;
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.complete();
}
}
onClosed(event: MouseEvent): void {
this.dialogRef.close();
}
onConfirm() {
if (!!this.groupName && this.groupName.trim().localeCompare('') !== 0) {
this.currentType = GroupUserDialaogType.Create;
} else {
this.groupName = undefined;
}
this.dialogRef.close({
type: this.currentType,
groupName: this.groupName,
group: this.data.group,
selelctUserList: this.selectedUserList,
selectGroupList: this.selectedGroupList
});
}
onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) {
const i = this.selectedUserList.findIndex(
(u) => u.seq === data.userInfo.seq
);
if (data.checked) {
if (-1 === i) {
this.selectedUserList = [...this.selectedUserList, data.userInfo];
}
} else {
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => u.seq !== data.userInfo.seq
);
}
}
}
onChangeGroupList(selectedGroupList: GroupDetailData[]) {
this.selectedGroupList = selectedGroupList;
}
onChangeGroupName(name: string) {
this.groupName = name;
}
onRemovedProfileSelection(userInfo: UserInfo) {
const i = this.selectedUserList.findIndex(
(u) => (u.seq as any) === (userInfo.seq as any)
);
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => (u.seq as any) !== (userInfo.seq as any)
);
}
}
removableForSelection = (userInfo: UserInfo) => {
return true;
};
colorForSelection = (userInfo: UserInfo) => {
return 'accent';
};
}

View File

@ -1,4 +1,11 @@
import { CreateDialogComponent } from './create.dialog.component';
import { EditInlineInputDialogComponent } from './edit-inline-input.dialog.component';
import { ManageDialogComponent } from './manage.dialog.component';
import { EditUserDialogComponent } from './edit-user.dialog.component';
export const DIALOGS = [CreateDialogComponent, EditInlineInputDialogComponent];
export const DIALOGS = [
CreateDialogComponent,
EditInlineInputDialogComponent,
ManageDialogComponent,
EditUserDialogComponent
];

View File

@ -12,28 +12,68 @@
#stepper
[selectedIndex]="currentStep"
>
<mat-step label="Step 1"> </mat-step>
<mat-step label="Step 1">
<app-group-select-user
[isDialog]="true"
[checkable]="true"
(changeUserList)="onChangeUserList($event)"
></app-group-select-user>
<div>
<div>
{{ data.groupBuddyList.group.name }}
</div>
<div>
<button
mat-button
(click)="onAdd(stepper)"
aria-label="대화상대 추가"
>
<mat-icon>add</mat-icon>
</button>
</div>
</div>
<div>
<app-group-profile-list-item-02
*ngFor="let userInfo of this.data.groupBuddyList.buddyList"
[userInfo]="userInfo"
[isDialog]="true"
[checked]="getCheckedUser(userInfo)"
[checkable]="true"
(changeCheckUser)="onToggleCheck($event)"
></app-group-profile-list-item-02>
</div>
</mat-step>
<mat-step label="Step 2">
<ng-template #dialogContainer></ng-template>
</mat-step>
</mat-horizontal-stepper>
<div>
<ucap-organization-profile-selection-01
[userInfoList]="selectedUserList"
[removableFor]="removableForSelection"
[colorFor]="colorForSelection"
(removed)="onRemovedProfileSelection($event)"
>
</ucap-organization-profile-selection-01>
</div>
</div>
<div appLayoutsDefaultDialog="action">
<button mat-button (click)="onDelete(stepper)">삭제</button>
<button mat-button (click)="onConfirm(stepper)">
완료
</button>
<button
*ngIf="currentStep < 1"
mat-button
(click)="onCompleteConfirm(stepper)"
>
그룹지정후 완료
</button>
<div *ngIf="currentStep === 0">
<button mat-button (click)="onDelete(stepper)">삭제</button>
<button
mat-button
(click)="onUpdateMember(stepper, GroupUserDialaogType.Copy)"
>
그룹복사
</button>
<button
mat-button
(click)="onUpdateMember(stepper, GroupUserDialaogType.Move)"
>
그룹이동
</button>
</div>
<div *ngIf="currentStep > 0">
<button mat-button (click)="onCnacel(stepper)">취소</button>
<button mat-button (click)="onConfirm(stepper)">
완료
</button>
</div>
</div>
</app-layouts-default-dialog>
</div>

View File

@ -1,4 +1,4 @@
import { Subject } from 'rxjs';
import { Subject, of } from 'rxjs';
import {
Component,
@ -7,7 +7,11 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
Input
Input,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
ComponentRef
} from '@angular/core';
import { Store } from '@ngrx/store';
@ -28,8 +32,15 @@ import { GroupActions } from '@ucap/ng-store-group';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
AlertDialogResult,
ConfirmDialogComponent,
ConfirmDialogResult,
ConfirmDialogData
} from '@ucap/ng-ui';
import { SelectUserSectionComponent } from '../components/select-user.section.component';
import { take, map, catchError } from 'rxjs/operators';
import { SelectGroupSectionComponent } from '../components/select-group.section.component';
import { SelectUserDialogType, GroupUserDialaogType } from '@app/types';
export type UserInfoTypes =
| UserInfo
@ -40,8 +51,15 @@ export type UserInfoTypes =
export interface ManageDialogData {
title: string;
groupBuddyList?: { group: GroupDetailData; buddyList: UserInfo[] };
}
export interface ManageDialogResult {
type: GroupUserDialaogType;
groupName: string;
group?: GroupDetailData;
selelctUserList?: UserInfoTypes[];
selectGroupList?: GroupDetailData[];
}
export interface ManageDialogResult {}
@Component({
selector: 'app-dialog-group-manage',
@ -57,16 +75,29 @@ export class ManageDialogComponent implements OnInit, OnDestroy {
private store: Store<any>,
private formBuilder: FormBuilder,
private i18nService: I18nService,
public dialog: MatDialog
public dialog: MatDialog,
private cfResolver: ComponentFactoryResolver
) {}
private ngOnDestroySubject: Subject<void>;
currentStep = 0;
@ViewChild('dialogContainer', { static: true, read: ViewContainerRef })
dialogContainer: ViewContainerRef;
componentRef: ComponentRef<any>;
private ngOnDestroySubject: Subject<void>;
currentType: GroupUserDialaogType;
SelectUserDialogType = SelectUserDialogType;
GroupUserDialaogType = GroupUserDialaogType;
currentStep = 0;
groupName = '';
delteUserList: UserInfoTypes[] = [];
selectedUserList: UserInfoTypes[] = [];
selectedGroupList: GroupDetailData[] = [];
ngOnInit(): void {
this.ngOnDestroySubject = new Subject();
// this.selectedUserList = this.data.groupBuddyList.buddyList;
}
ngOnDestroy(): void {
@ -80,14 +111,111 @@ export class ManageDialogComponent implements OnInit, OnDestroy {
}
onDelete(stepper: MatStepper) {
this.dialogRef.close();
if (
!!this.delteUserList &&
this.delteUserList.length > 0 &&
this.currentStep === 0
) {
let titleStr = '';
this.delteUserList.forEach((user, idx) => {
let userTitle = user.name + ' ' + user.grade;
if (idx < this.delteUserList.length) {
userTitle = userTitle + ', ';
}
titleStr = titleStr.concat('', userTitle);
});
const dialogRef = this.dialog.open<
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
>(ConfirmDialogComponent, {
data: {
title: '동료 삭제',
html: titleStr + '을 삭제하시겠습니까?'
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (!!result && result.choice) {
const trgtUserSeq: string[] = [];
this.delteUserList.forEach((userIfno) => {
const tempSeq = this.data.groupBuddyList.group.userSeqs.filter(
(seq) => seq === userIfno.seq
);
trgtUserSeq.push(tempSeq[0]);
});
console.log(trgtUserSeq);
this.store.dispatch(
GroupActions.updateMember({
targetGroup: this.data.groupBuddyList.group,
targetUserSeqs: trgtUserSeq
})
);
this.dialogRef.close();
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
}
onCopy(stepper: MatStepper) {
this.dialogRef.close();
onUpdateMember(stepper: MatStepper, type: GroupUserDialaogType) {
this.dialogContainer.clear();
this.currentType = type;
const isMemberMove = type === GroupUserDialaogType.Copy ? false : true;
// const title = type === GroupUserDialaogType.Copy ? '멤버 복사' : '멤버 이동';
const factory = this.cfResolver.resolveComponentFactory(
SelectGroupSectionComponent
);
this.componentRef = this.dialogContainer.createComponent(factory);
const cpInstance = this.componentRef.instance;
// cpInstance.title = title;
cpInstance.isMemberMove = isMemberMove;
cpInstance.isDialog = true;
cpInstance.checkable = true;
cpInstance.curGroup = this.data.groupBuddyList.group;
cpInstance.changeUserList.subscribe((val) => {
this.selectedUserList = val;
});
cpInstance.changeGroupList.subscribe((groupList) => {
this.selectedGroupList = groupList;
});
cpInstance.changeGroupName.subscribe((groupName) => {
this.groupName = groupName;
});
this.currentStep++;
stepper.next();
}
onMove(stepper: MatStepper) {}
onAdd(stepper: MatStepper) {
this.dialogContainer.clear();
this.currentType = GroupUserDialaogType.Add;
// this.selectedUserList = this.data.groupBuddyList.buddyList;
const factory = this.cfResolver.resolveComponentFactory(
SelectUserSectionComponent
);
this.componentRef = this.dialogContainer.createComponent(factory);
const cpInstance = this.componentRef.instance;
cpInstance.isDialog = true;
cpInstance.checkable = true;
cpInstance.selectedUserList = this.data.groupBuddyList.buddyList;
cpInstance.isSelectionOff = false;
cpInstance.changeUserList.subscribe((val) => {
this.selectedUserList = val;
});
this.currentStep++;
stepper.next();
}
@ -95,4 +223,119 @@ export class ManageDialogComponent implements OnInit, OnDestroy {
onChangeUserList(selectedUserList: UserInfoTypes[]) {
this.selectedUserList = selectedUserList;
}
onCnacel(stepper: MatStepper) {
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
this.selectedUserList = [];
}
this.currentStep--;
stepper.previous();
}
onConfirm(stepper: MatStepper) {
switch (this.currentType) {
case GroupUserDialaogType.Add:
{
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
this.doAction();
}
}
break;
case GroupUserDialaogType.Copy:
case GroupUserDialaogType.Move:
{
if (!!this.selectedUserList && this.selectedUserList.length === 0) {
this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
data: {
title: 'Error',
html: '선택된 유저가 없습니다.'
}
});
return;
}
this.doAction();
}
break;
}
}
doAction() {
this.dialogContainer.clear();
if (!!this.groupName && this.groupName.trim().localeCompare('') !== 0) {
this.currentType = GroupUserDialaogType.Create;
} else {
this.groupName = undefined;
}
this.dialogRef.close({
type: this.currentType,
groupName: this.groupName,
group: this.data.groupBuddyList.group,
selelctUserList: this.selectedUserList,
selectGroupList: this.selectedGroupList
});
}
/** 개별 체크여부 */
getCheckedUser(userInfo: UserInfoSS) {
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
return (
this.selectedUserList.filter(
(item) => (item.seq as any) === (userInfo.seq as any)
).length > 0
);
}
return false;
}
onToggleCheckForDelete(data: { checked: boolean; userInfo: UserInfoSS }) {
if (data.checked) {
this.delteUserList.push(data.userInfo);
} else {
const index = this.delteUserList.findIndex(
(userInfo) => userInfo.seq === data.userInfo.seq
);
if (index > -1) {
this.delteUserList.splice(index, 1);
}
}
this.onToggleCheck(data);
}
onToggleCheck(data: { checked: boolean; userInfo: UserInfoSS }) {
if (data.checked) {
this.selectedUserList.push(data.userInfo);
} else {
const index = this.selectedUserList.findIndex(
(userInfo) => userInfo.seq === data.userInfo.seq
);
if (index > -1) {
this.selectedUserList.splice(index, 1);
}
}
}
onRemovedProfileSelection(userInfo: UserInfo) {
const i = this.selectedUserList.findIndex(
(u) => (u.seq as any) === (userInfo.seq as any)
);
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => (u.seq as any) !== (userInfo.seq as any)
);
}
}
removableForSelection = (userInfo: UserInfo) => {
return true;
};
colorForSelection = (userInfo: UserInfo) => {
return 'accent';
};
}

View File

@ -43,8 +43,6 @@ import { DIALOGS } from './dialogs';
MatIconModule,
MatCardModule,
MatCheckboxModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatTabsModule,
MatChipsModule,

View File

@ -2,7 +2,9 @@
<div class="title">
{{ selectedDeptInfo | ucapOrganizationTranslate: 'name'
}}<strong>{{
!!searchData.isSearch ? searchUserList.length : departmentUserList.length
!!searchObj.isShowSearch
? searchUserList.length
: departmentUserList.length
}}</strong
>{{ 'common.units.persons' | ucapI18n }}
</div>
@ -27,9 +29,28 @@
</mat-checkbox>
</div>
<div class="table-body" fxFlexFill>
<app-organization-profile-list
[searchData]="searchData"
></app-organization-profile-list>
<cdk-virtual-scroll-viewport #cvsvList perfectScrollbar fxFlexFill>
<ucap-organization-profile-list-item-01
*cdkVirtualFor="
let userInfo of !!searchObj.isShowSearch
? searchUserList
: departmentUserList
"
[userInfo]="userInfo"
[checkable]="loginRes?.userSeq !== userInfo.seq"
[checked]="getCheckedUser(userInfo)"
(openProfile)="onOpenProfile($event)"
(changeCheck)="onToggleUser($event)"
>
<ucap-organization-profile-image-01
ucapOrganizationProfileListItem01="profileImage"
[userInfo]="userInfo"
[profileImageRoot]="versionInfo2Res?.profileRoot"
defaultProfileImage="assets/images/ico/img_nophoto.svg"
>
</ucap-organization-profile-image-01>
</ucap-organization-profile-list-item-01>
</cdk-virtual-scroll-viewport>
</div>
<div class="selected-users">
<mat-accordion class="organization-accordion">

View File

@ -59,7 +59,26 @@ export class DepartmentUserVirtualScrollStrategy extends FixedSizeVirtualScrollS
})
export class DetailTableComponent implements OnInit, OnDestroy {
@Input()
searchData: any;
set searchObj(obj: {
isShowSearch: boolean;
companyCode: string;
searchWord: string;
}) {
this._searchObj = obj;
if (obj.isShowSearch && obj.searchWord.localeCompare('') !== 0) {
this.onOrganizationTenantSearch(obj);
} else {
this._searchObj.isShowSearch = false;
this.searchUserList = [];
this.changeDetectorRef.detectChanges();
}
}
get searchObj() {
return this._searchObj;
}
// tslint:disable-next-line: variable-name
_searchObj: any;
@Input()
set deptSeq(deptSeq: string) {
@ -198,13 +217,59 @@ export class DetailTableComponent implements OnInit, OnDestroy {
}
}
onOrganizationTenantSearch(obj: {
isShowSearch: boolean;
companyCode: string;
searchWord: string;
}) {
this.departmentUserListProcessing = true;
this.queryProtocolService
.deptUser({
divCd: 'ORGS',
companyCode: this._searchObj.companyCode,
searchRange: DeptSearchType.All,
search: this._searchObj.searchWord,
senderCompanyCode: this.loginRes.userInfo.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
})
.pipe(
map((resObj) => {
// 검색 결과 처리.
this.searchUserList = resObj.userInfos.sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);
this.changeDetectorRef.detectChanges();
this.departmentUserListProcessing = false;
// 검색 결과에 따른 프레즌스 조회.
const userSeqList: string[] = [];
this.searchUserList.map((user) => userSeqList.push(user.seq));
if (userSeqList.length > 0) {
this.store.dispatch(
PresenceActions.bulkInfo({
divCd: 'orgSrch',
userSeqs: userSeqList
})
);
}
}),
catchError((error) => {
this.departmentUserListProcessing = false;
return of(error);
})
)
.subscribe();
}
/** 전체선택 체크여부 */
getCheckedAllUser() {
if (!this.loginRes) {
return false;
}
const compareList: UserInfoSS[] = !!this.searchData.isSearch
const compareList: UserInfoSS[] = !!this.searchObj.isShowSearch
? this.searchUserList
: this.departmentUserList;
if (
@ -233,7 +298,7 @@ export class DetailTableComponent implements OnInit, OnDestroy {
return false;
}
const trgtUserList = !!this.searchData.isSearch
const trgtUserList = !!this.searchObj.isShowSearch
? this.searchUserList
: this.departmentUserList;

View File

@ -1,34 +1,99 @@
<div class="member-list-container" fxLayout="column">
<!-- search start-->
<div fxFlex="0 0 50px">
<app-organization-search-for-tenant (changed)="onChangedSearch($event)">
</app-organization-search-for-tenant>
</div>
<!-- search end-->
<!-- content table start-->
<div class="member-list-body" fxFlex="1 1 auto" fxLayout="column">
<div class="list-header" fxFlex="0 0 48px" fxLayout="row">
<div class="list-header-title" fxFlex="1 1 auto">
<h5>아키텍처솔루션팀 <strong>20</strong></h5>
</div>
<div class="list-header-toolbox" fxFlex="0 0 100px">
이름
<mat-icon matSuffix class="selet-ico-obj" matRipple unbounded="true">
format_line_spacing
</mat-icon>
<mat-checkbox
class="subtitle-chk-box"
#checkboxAll
(click)="$event.stopPropagation()"
>
</mat-checkbox>
</div>
<div class="list-header" fxFlex="0 0 48px" fxLayout="row">
<div class="list-header-title" fxFlex="1 1 auto">
<h5>
<ng-container *ngIf="!!selectedDeptInfo">
{{ selectedDeptInfo | ucapOrganizationTranslate: 'name' }}
</ng-container>
<ng-container *ngIf="!!selectedCompanyInfo">
{{ selectedCompanyInfo.companyName }}
</ng-container>
<strong>{{ searchedProfileLength }}</strong
>명
</h5>
</div>
<div class="list-header-toolbox" fxFlex="0 0 100px">
이름
<button mat-icon-button aria-label="icon" (click)="onClickToggleSort()">
<mat-icon>format_line_spacing</mat-icon>
</button>
<mat-checkbox
class="subtitle-chk-box"
#checkboxAll
(change)="onChangeSelectAll($event)"
>
</mat-checkbox>
</div>
</div>
<div fxFlex="1 1 auto" fxLayout="column">
<div fxFlex="1 1 auto">
<app-organization-profile-list [searchData]="_searchData">
<app-organization-profile-list
#profileList
[searchData]="_searchData"
[selectedUser]="selectedUserInfos"
[sortOrder]="sortOrderForProfileList"
(searched)="onSearchedProfileList($event)"
(changedCheck)="onChangedCheckProfileList($event)"
>
</app-organization-profile-list>
</div>
<div
[style.display]="
selectedUserInfos && 0 < selectedUserInfos.length ? '' : 'none'
"
class="selected-users"
[fxFlex]="isExpanded ? '0 0 200px' : '0 0 40px'"
>
<mat-accordion class="organization-accordion">
<mat-expansion-panel
(opened)="onOpenedSelection()"
(closed)="onClosedSelection()"
>
<mat-expansion-panel-header
collapsedHeight="40px"
expandedHeight="40px"
class="organization-accordion-head"
>
<mat-panel-title class="select-user-title">
선택한 대화상대
<strong>{{
!!selectedUserInfos ? selectedUserInfos.length : 0
}}</strong>
</mat-panel-title>
<mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header>
<div class="selected-user-list">
<ucap-organization-profile-selection-01
[userInfoList]="selectedUserInfos"
[removableFor]="removableForSelection"
[colorFor]="colorForSelection"
(removed)="onRemovedProfileSelection($event)"
>
</ucap-organization-profile-selection-01>
</div>
<div class="btn-box">
<button mat-stroked-button class="mat-basic">
그룹추가
</button>
<button mat-flat-button class="bg-primary-darkest">
대화
</button>
<button mat-flat-button class="bg-primary-dark">
쪽지
</button>
<button mat-flat-button class="bg-primary-dark" disabled>
전화
</button>
<button mat-flat-button class="bg-primary-dark">
화상
</button>
</div>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
<!-- content table end-->
</div>

View File

@ -1,34 +1,59 @@
@import '~@ucap/lg-scss/mixins';
.member-list-container {
width: 100%;
height: 100%;
.member-list-body {
align-content: space-between;
padding: 0 30px;
background-color: white;
align-content: space-between;
padding: 0 30px;
background-color: white;
.list-header {
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #999999;
padding: 12px 0 13px;
.list-header {
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #999999;
padding: 12px 0 13px;
.list-header-title {
h5 {
font-size: 13px;
align-items: center;
font-weight: 600;
color: #333333;
flex-grow: 1;
strong {
color: #e42f66;
}
.list-header-title {
h5 {
font-size: 13px;
align-items: center;
font-weight: 600;
color: #333333;
flex-grow: 1;
strong {
color: #e42f66;
}
}
.list-header-toolbox {
position: absolute;
right: 0px;
}
.list-header-toolbox {
right: 0px;
}
}
.selected-users {
flex-grow: 0.8;
.organization-accordion-head {
background-color: #f1f2f6;
}
.select-user-title {
strong {
color: $lipstick;
margin-left: 8px;
}
}
.selected-user-list {
width: 150px;
}
.btn-box {
margin-top: 10px;
padding-right: 8px;
display: flex;
flex-direction: row;
align-content: center;
justify-content: space-between;
}
}
}

View File

@ -1,89 +1,83 @@
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
Component,
ViewChild,
OnInit,
ChangeDetectorRef,
Input,
OnDestroy,
ChangeDetectionStrategy
ChangeDetectionStrategy,
ViewChild
} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { SelectionModel } from '@angular/cdk/collections';
import { MatSort } from '@angular/material/sort';
import { Subject, of } from 'rxjs';
import { map, takeUntil, take, catchError } from 'rxjs/operators';
import {
DeptInfo,
DeptSearchType,
DeptUserRequest,
UserInfoSS,
AuthResponse
} from '@ucap/protocol-query';
import { select, Store } from '@ngrx/store';
import { SortOrder } from '@ucap/core';
import { LoginResponse } from '@ucap/protocol-authentication';
import { DeptInfo, UserInfoSS } from '@ucap/protocol-query';
import { UserInfo } from '@ucap/protocol-sync';
import { LoginSelector } from '@ucap/ng-store-authentication';
import {
DepartmentSelector,
PresenceActions
CompanySelector
} from '@ucap/ng-store-organization';
import { select, Store } from '@ngrx/store';
import { QueryProtocolService } from '@ucap/ng-protocol-query';
import {
LoginSelector,
AuthorizationSelector,
ConfigurationSelector
} from '@ucap/ng-store-authentication';
import { LoginResponse } from '@ucap/protocol-authentication';
import {
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY,
CdkVirtualScrollViewport
} from '@angular/cdk/scrolling';
import { VersionInfo2Response } from '@ucap/api-public';
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
export class MemberVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
constructor() {
super(184, 150, 200);
}
}
import { SearchData } from '@app/ucap/organization/models/search-data';
import { Company } from '@ucap/api-external';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { ProfileListComponent as AppProfileListComponent } from '@app/ucap/organization/components/profile-list.component';
@Component({
selector: 'app-sections-organization-member-list',
templateUrl: './member-list.component.html',
styleUrls: ['./member-list.component.scss'],
providers: [
{
provide: VIRTUAL_SCROLL_STRATEGY,
useClass: MemberVirtualScrollStrategy
}
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MemberListComponent implements OnInit, OnDestroy {
@Input()
set searchData(data: {
companyCode: string;
searchWord: string;
isSearch: boolean;
}) {
this._searchData = data;
set searchData(searchData: SearchData) {
this._searchData = searchData;
if (searchData.bySearch) {
this.setCompanyInfo(searchData.companyCode);
} else {
this.setDeptInfo(searchData.deptSeq);
}
}
@ViewChild('profileList', { static: false })
profileList: AppProfileListComponent;
// tslint:disable-next-line: variable-name
_searchData: {
companyCode: string;
searchWord: string;
isSearch: boolean;
_searchData: SearchData;
loginRes: LoginResponse;
selectedDeptInfo: DeptInfo;
selectedCompanyInfo: Company;
searchedProfileLength: number;
selectedUserInfos: UserInfoSS[] = [];
isExpanded = false;
sortOrderForProfileList: SortOrder = {
property: 'name',
ascending: true
};
private ngOnDestroySubject: Subject<void>;
constructor(
private store: Store<any>,
private queryProtocolService: QueryProtocolService,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
this.ngOnDestroySubject = new Subject();
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
}
ngOnDestroy(): void {
@ -92,11 +86,131 @@ export class MemberListComponent implements OnInit, OnDestroy {
}
}
onChangedSearch(data: {
isSearch: boolean;
companyCode: string;
searchWord: string;
}) {
this._searchData = data;
onChangedSearch(data: { deptSeq: string; searchWord: string }) {
this._searchData = {
bySearch: true,
...data
};
this.setDeptInfo(data.deptSeq);
}
onSearchedProfileList(userInfos: UserInfoSS[]) {
this.searchedProfileLength = !!userInfos ? userInfos.length : 0;
}
onChangedCheckProfileList(
datas: { checked: boolean; userInfo: UserInfoSS }[]
) {
if (!datas || 0 === datas.length) {
return;
}
const pushs: UserInfoSS[] = [];
const pops: UserInfoSS[] = [];
datas.forEach((d) => {
const i = this.selectedUserInfos.findIndex(
(u) => u.seq === d.userInfo.seq
);
if (d.checked) {
if (-1 === i) {
pushs.push(d.userInfo);
}
} else {
if (-1 < i) {
pops.push(d.userInfo);
}
}
});
if (0 < pushs.length) {
this.selectedUserInfos = [...this.selectedUserInfos, ...pushs];
}
if (0 < pops.length) {
this.selectedUserInfos = this.selectedUserInfos.filter(
(u) => -1 === pops.findIndex((p) => p.seq === u.seq)
);
}
}
onRemovedProfileSelection(userInfo: UserInfo) {
const i = this.selectedUserInfos.findIndex(
(u) => u.seq === String(userInfo.seq)
);
if (-1 < i) {
this.selectedUserInfos = this.selectedUserInfos.filter(
(u) => u.seq !== String(userInfo.seq)
);
}
}
removableForSelection = (userInfo: UserInfo) => {
return true;
};
colorForSelection = (userInfo: UserInfo) => {
return 'accent';
};
onOpenedSelection() {
this.isExpanded = true;
}
onClosedSelection() {
this.isExpanded = false;
}
onClickToggleSort() {
this.sortOrderForProfileList = {
...this.sortOrderForProfileList,
ascending: !this.sortOrderForProfileList.ascending
};
}
onChangeSelectAll(event: MatCheckboxChange) {
if (event.checked) {
this.profileList.checkAll();
} else {
this.selectedUserInfos = [];
}
}
private setCompanyInfo(companyCode: string) {
const destroySubject: Subject<void> = new Subject();
this.store
.pipe(takeUntil(destroySubject), select(CompanySelector.companyList))
.subscribe((companyList) => {
if (!companyList) {
return;
}
this.selectedCompanyInfo = companyList.find(
(c) => c.companyCode === companyCode
);
this.changeDetectorRef.markForCheck();
destroySubject.next();
destroySubject.complete();
});
}
private setDeptInfo(seq: string) {
const destroySubject: Subject<void> = new Subject();
this.store
.pipe(
takeUntil(destroySubject),
select(DepartmentSelector.departmentInfoList)
)
.subscribe((departmentInfoList) => {
if (!departmentInfoList) {
return;
}
this.selectedDeptInfo = departmentInfoList.find(
(d) => String(d.seq) === seq
);
this.changeDetectorRef.markForCheck();
destroySubject.next();
destroySubject.complete();
});
}
}

View File

@ -15,6 +15,7 @@ import { MatChipsModule } from '@angular/material/chips';
import { MatTableModule } from '@angular/material/table';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatSidenavModule } from '@angular/material/sidenav';
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
@ -42,6 +43,7 @@ import { COMPONENTS } from './components';
MatTableModule,
MatButtonModule,
MatExpansionModule,
MatSidenavModule,
PerfectScrollbarModule,

View File

@ -1,11 +1,11 @@
import { Observable } from 'rxjs';
import { take, concatMap, map } from 'rxjs/operators';
import { Observable, of, forkJoin } from 'rxjs';
import { take, concatMap, map, catchError } from 'rxjs/operators';
import { Injectable, Inject, ChangeDetectorRef } from '@angular/core';
import { Store } from '@ngrx/store';
import { LocaleCode } from '@ucap/core';
import { LocaleCode, DeviceType, FileUtil } from '@ucap/core';
import { PasswordUtil } from '@ucap/pi';
import { LoginResponse, SSOMode } from '@ucap/protocol-authentication';
@ -26,7 +26,12 @@ import { UserStore } from '@app/models/user-store';
import { AppKey } from '@app/types/app-key.type';
import { environment } from '@environments';
import { RoomInfo, RoomType } from '@ucap/protocol-room';
import {
RoomInfo,
RoomType,
OpenRequest,
Open3Request
} from '@ucap/protocol-room';
import { Dictionary } from '@ngrx/entity';
import {
RoomUserMap,
@ -38,6 +43,31 @@ import {
TranslateService
} from '@ucap/ng-ui-organization';
import { I18nService } from '@ucap/ng-i18n';
import { ChattingActions, RoomActions } from '@ucap/ng-store-chat';
import {
SendRequest as SendEventRequest,
EventType
} from '@ucap/protocol-event';
import {
MassTalkSaveRequest,
FileTalkSaveResponse,
FileTalkSaveRequest
} from '@ucap/api-common';
import { CommonApiService } from '@ucap/ng-api-common';
import { StatusCode, FileUploadItem } from '@ucap/api';
import { LogService } from '@ucap/ng-logger';
import { MatDialog } from '@angular/material/dialog';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
} from '@ucap/ng-ui';
import { StickerFilesInfo, KEY_STICKER_HISTORY } from '@ucap/ng-core';
import {
CreateDialogComponent,
CreateDialogData,
CreateDialogResult
} from '@app/sections/chat/dialogs/create.dialog.component';
@Injectable({
providedIn: 'root'
@ -46,8 +76,21 @@ export class AppChatService {
defaultProfileImage = 'assets/images/ico/img_nophoto.svg';
defaultProfileImageMulti = 'assets/images/ico/img_nophoto.svg';
constructor(private i18nService: I18nService) {}
constructor(
private i18nService: I18nService,
private dialog: MatDialog,
private localStorageService: LocalStorageService,
private store: Store<any>,
private commonApiService: CommonApiService,
private logService: LogService
) {
this.i18nService.setDefaultNamespace('chat');
}
/**
* .
* cf) , .
*/
getRoomName(
organizationTranslate: OrganizationTranslate,
loginRes: LoginResponse,
@ -109,6 +152,12 @@ export class AppChatService {
return roomName;
}
/**
* .
* cf) .
* , defaultProfileImage, defaultProfileImageMulti,
* defaultProfileImageMulti
*/
getRoomProfileImage(
roomInfo: RoomInfo,
loginRes: LoginResponse,
@ -164,6 +213,11 @@ export class AppChatService {
return roomImage;
}
/**
* .
* cf) roomUserShort, roomUser short , .
* roomUser detail .
*/
getRoomUserList(
loginRes: LoginResponse,
roomId: string,
@ -197,4 +251,431 @@ export class AppChatService {
users
};
}
/**
* in localstorage.
* cf) .
*/
setStickerHistory(sticker: StickerFilesInfo) {
const history = this.localStorageService.get<string[]>(KEY_STICKER_HISTORY);
if (!!history && history.length > 0) {
const stickers: string[] = [];
[sticker.index, ...history.filter((hist) => hist !== sticker.index)].map(
(s, i) => {
if (i < 10) {
stickers.push(s);
}
}
);
this.localStorageService.set<string[]>(KEY_STICKER_HISTORY, stickers);
} else {
this.localStorageService.set<string[]>(KEY_STICKER_HISTORY, [
sticker.index
]);
}
}
/**
* Event Send Protocol Service.
*/
protected sendEvent(
senderSeq: string,
roomId: string,
eventType: EventType,
sentMessage: string
) {
this.store.dispatch(
ChattingActions.send({
senderSeq,
req: {
roomId,
eventType,
sentMessage
} as SendEventRequest
})
);
}
/** Send Normal message */
sendMessageOfNormal(senderSeq: string, roomId: string, sentMessage: string) {
this.sendEvent(senderSeq, roomId, EventType.Character, sentMessage);
}
/** Send Masstext message */
sendMessageOfMassText(
loginRes: LoginResponse,
deviceType: DeviceType,
roomId: string,
sentMessage: string
) {
const req: MassTalkSaveRequest = {
userSeq: loginRes.userSeq,
deviceType,
token: loginRes.tokenString,
content: sentMessage.replace(/"/g, '\\"'),
roomId
};
this.commonApiService
.massTalkSave(req)
.pipe(
take(1),
map((res) => {
if (res.statusCode === StatusCode.Success) {
this.sendEvent(
loginRes.userSeq,
roomId,
EventType.MassText,
res.returnJson
);
} else {
this.logService.error(
`commonApiService] massTalkSave ${res?.errorMessage}`
);
}
}),
catchError((error) => of({ error }))
)
.subscribe();
}
async sendMessageOfSticker(
senderSeq: string,
roomId: string,
selectedSticker: StickerFilesInfo,
sentMessage: string
) {
// Validation
if (
!!sentMessage &&
sentMessage.trim().length > environment.productConfig.chat.masstextLength
) {
const result = await this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
panelClass: 'miniSize-dialog',
data: {
title: this.i18nService.t('errors.label'),
message: this.i18nService.t('errors.maxLengthOfMassText', {
maxLength: environment.productConfig.chat.masstextLength
})
}
});
return;
}
// Send
this.sendEvent(
senderSeq,
roomId,
EventType.Sticker,
JSON.stringify({
name: '스티커',
file: selectedSticker.index,
chat: !!sentMessage ? sentMessage.trim() : ''
})
);
// set sticker's history in localstorage
this.setStickerHistory(selectedSticker);
}
/** Send Translation message */
sendMessageOfTranslate(
loginRes: LoginResponse,
deviceType: DeviceType,
destLocale: string,
roomId: string,
sentMessage: string,
selectedSticker?: StickerFilesInfo
) {
// const destLocale = this.destLocale;
// const original = message;
// const roomSeq = this.roomInfoSubject.value.roomSeq;
// if (!!this.isTranslationProcess) {
// return;
// }
// this.isTranslationProcess = true;
// this.commonApiService
// .translationSave({
// userSeq: this.loginResSubject.value.userSeq,
// deviceType: this.environmentsInfo.deviceType,
// token: this.loginResSubject.value.tokenString,
// roomSeq,
// original,
// srcLocale: '',
// destLocale
// } as TranslationSaveRequest)
// .pipe(
// take(1),
// map((res) => {
// if (res.statusCode === StatusCode.Success) {
// let sentMessage = '';
// let eventType = EventType.Translation;
// let previewObject: TranslationEventJson | MassTranslationEventJson;
// if (res.translationSeq > 0) {
// // Mass Text Translation
// previewObject = res;
// sentMessage = res.returnJson;
// eventType = EventType.MassTranslation;
// } else {
// // Normal Text Translation
// previewObject = {
// locale: destLocale,
// original,
// translation: res.translation,
// stickername: '',
// stickerfile: !!this.selectedSticker
// ? this.selectedSticker.index
// : ''
// };
// sentMessage = JSON.stringify(previewObject);
// eventType = EventType.Translation;
// }
// if (!!this.translationPreview) {
// // preview
// this.translationPreviewInfo = {
// previewInfo: res,
// translationType: eventType
// };
// this.changeDetectorRef.detectChanges();
// } else {
// // direct send
// this.store.dispatch(
// EventStore.send({
// senderSeq: this.loginResSubject.value.userSeq,
// req: {
// roomSeq,
// eventType,
// sentMessage
// }
// })
// );
// if (!!this.translationPreviewInfo) {
// this.translationPreviewInfo = null;
// }
// }
// if (!!this.selectedSticker) {
// this.isShowStickerSelector = false;
// this.setStickerHistory(this.selectedSticker);
// this.selectedSticker = null;
// }
// } else {
// this.isTranslationProcess = false;
// this.dialogService.open<
// AlertDialogComponent,
// AlertDialogData,
// AlertDialogResult
// >(AlertDialogComponent, {
// panelClass: 'miniSize-dialog',
// data: {
// title: '',
// message: this.translateService.instant(
// 'chat.error.translateServerError'
// )
// }
// });
// this.logger.error('res', res);
// }
// }),
// catchError((error) => {
// this.isTranslationProcess = false;
// this.dialogService.open<
// AlertDialogComponent,
// AlertDialogData,
// AlertDialogResult
// >(AlertDialogComponent, {
// panelClass: 'miniSize-dialog',
// data: {
// title: '',
// message: this.translateService.instant(
// 'chat.error.translateServerError'
// )
// }
// });
// return of(this.logger.error('error', error));
// })
// )
// .subscribe(() => {
// this.isTranslationProcess = false;
// });
}
/** Send AttachFile message */
sendMessageOfAttachFile(
loginRes: LoginResponse,
deviceType: DeviceType,
roomId: string,
fileUploadItems: FileUploadItem[]
): Promise<boolean> {
const executor = async (
resolve: (value?: boolean | PromiseLike<boolean>) => void,
reject: (reason?: any) => void
) => {
const allObservables: Observable<FileTalkSaveResponse>[] = [];
for (const fileUploadItem of fileUploadItems) {
let thumbnail: File;
if (
-1 !==
[
'3gp',
'avi',
'm4v',
'mkv',
'mov',
'mp4',
'mpeg',
'mpg',
'rv',
'ts',
'webm',
'wmv'
].indexOf(FileUtil.getExtension(fileUploadItem.file.name))
) {
try {
thumbnail = await FileUtil.thumbnail(fileUploadItem.file);
} catch (err) {
this.logService.error('video thumbnail error.', err);
}
this.logService.debug('thumbnail', thumbnail);
}
const req: FileTalkSaveRequest = {
userSeq: loginRes.userSeq,
deviceType,
token: loginRes.tokenString,
roomId,
file: fileUploadItem.file,
fileName: fileUploadItem.file.name,
thumb: thumbnail,
fileUploadItem
};
allObservables.push(
this.commonApiService.fileTalkSave(req).pipe(
map((res) => {
if (!res) {
return;
}
if (StatusCode.Success === res.statusCode) {
return res;
} else {
throw res;
}
})
)
);
}
forkJoin(allObservables)
.pipe(take(1))
.subscribe(
(resList) => {
for (const res of resList) {
this.store.dispatch(
ChattingActions.send({
senderSeq: loginRes.userSeq,
req: {
roomId,
eventType: EventType.File,
sentMessage: JSON.stringify(res.returnJson)
} as SendEventRequest
})
);
}
},
(error) => {
this.logService.debug('onFileSelected error', error);
const msg = this.i18nService.t('common.file.errors.failToUpload');
alert(msg);
reject(msg);
},
() => {
resolve(true);
}
);
};
return new Promise<boolean>(executor);
}
/**
* Open Dialog for 'New Room Open'.
*/
newOpenRoomDialog(): void {
const dialogRef = this.dialog.open<
CreateDialogComponent,
CreateDialogData,
CreateDialogResult
>(CreateDialogComponent, {
width: '100%',
height: '100%',
data: {}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (!!result && !!result.userSeqs && result.userSeqs.length > 0) {
this.newOpenRoom(result.userSeqs, result.isTimer);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
newOpenRoom(
userSeqs: string[],
isTimerRoom: boolean,
loginRes?: LoginResponse
) {
if (!userSeqs || userSeqs.length === 0) {
return;
}
isTimerRoom = isTimerRoom || false;
if (!!isTimerRoom) {
/** Timer Room Open. */
const req: Open3Request = {
divCd: 'OPROOMT',
roomName: '',
isTimerRoom,
timerRoomInterval:
environment.productConfig.chat.timerRoomDefaultInterval,
userSeqs
};
this.store.dispatch(RoomActions.createTimer({ req }));
} else {
/** Normal Room Open */
let req: OpenRequest;
if (
userSeqs.length === 1 &&
!!loginRes &&
userSeqs[0] === loginRes.userSeq
) {
// MyTalk Open.
req = {
divCd: 'OPMYTALK',
userSeqs: [loginRes.talkWithMeBotSeq + '']
};
} else {
req = {
divCd: 'OPROOM',
userSeqs
};
}
this.store.dispatch(RoomActions.create({ req }));
}
}
}

View File

@ -0,0 +1,86 @@
import { Injectable, Inject } from '@angular/core';
import { FileUtil } from '@ucap/core';
import { I18nService, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
import { CommonApiService } from '@ucap/ng-api-common';
import { Store } from '@ngrx/store';
@Injectable({
providedIn: 'root'
})
export class AppFileService {
constructor(
private store: Store<any>,
private i18nService: I18nService,
private commonApiService: CommonApiService
) {
this.i18nService.setDefaultNamespace('common');
}
async validUploadFile(
fileList: FileList,
fileAllowSize: number = 50
): Promise<boolean> {
let valid = true;
// File size check.
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
if (file.size > fileAllowSize * 1024 * 1024) {
const msg = this.i18nService.t('common.file.errors.oversize', {
maxSize: fileAllowSize
});
alert(msg);
valid = false;
return valid;
}
}
// Extention check.
const checkExt = this.commonApiService.acceptableExtensionForFileTalk(
FileUtil.getExtensions(fileList)
);
if (!!checkExt) {
const msg = this.i18nService.t('common.file.errors.notSupporedType', {
supporedType: checkExt.join(',')
});
alert(msg);
// this.snackBarService.openFromComponent<
// AlertSnackbarComponent,
// AlertSnackbarData
// >(AlertSnackbarComponent, {
// duration: 1000,
// verticalPosition: 'bottom',
// horizontalPosition: 'center',
// data: {
// html: this.translateService.instant(
// 'common.file.errors.notSupporedType',
// {
// supporedType: checkExt.join(',')
// }
// )
// }
// });
valid = false;
return valid;
}
// Fake media file check.
const fakeMedia = await this.commonApiService.checkInvalidMediaMimeForFileTalkForFileList(
fileList
);
if (!!fakeMedia) {
const msg = this.i18nService.t('common.file.errors.notAcceptableMime', {
supporedType: fakeMedia.join(',')
});
alert(msg);
valid = false;
return valid;
}
return valid;
}
}

View File

@ -0,0 +1,6 @@
export enum GroupUserDialaogType {
Create = 'CREATE_GROUP',
Add = 'ADD_GROUP',
Copy = 'COPY_GROUP',
Move = 'MOVE_GROUP'
}

View File

@ -1,3 +1,4 @@
export * from './app-key.type';
export * from './select-user.dialog.type';
export * from './tokens';
export * from './group-user.dialog.type';

View File

@ -1,4 +1,4 @@
<div class="login-container">
<div class="login-container ucap-mat-input-container">
<ucap-authentication-login
[companyList]="companyList"
[fixedCompanyCode]="fixedCompanyCode"
@ -9,7 +9,7 @@
[loginTry]="loginTry"
(login)="onLogin($event)"
>
<div ucapAuthenticationLogin="header">
<div ucapAuthenticationLogin="header" class="header">
<div class="logo-img">
<img src="../../../assets/images/logo_140.png" alt="" />
</div>
@ -47,7 +47,7 @@
</div>
<div class="login-pass-info">
<ul>
<li>
<li matRipple>
<a href="">{{ 'login.labels.forgotPassword' | ucapI18n }}</a>
</li>
<li>
@ -58,7 +58,7 @@
</ul>
</div>
<div class="login-button-area">
<button type="button">
<button mat-flat-button>
{{ 'login.labels.notesOnUse' | ucapI18n }}
</button>
</div>

View File

@ -1,32 +1,55 @@
@import '~@ucap/lg-scss/mixins';
h1 {
@include font-family($font-light);
font-size: 24px;
text-align: center;
color: $txt-color01;
font-weight: 600;
line-height: 1.2;
@include screen(mid) {
font-size: 19px;
}
@include screen(xs) {
font-size: 14px;
}
}
.login-container {
width: 100%;
height: 100%;
overflow: auto;
padding: 0 0 45px;
width: 420px;
margin: auto;
text-align: center;
flex-basis: auto;
align-items: center;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
.header {
.logo-img {
display: block;
text-align: center;
img {
margin-bottom: 7px;
vertical-align: top;
@include screen(mid) {
width: 120px;
}
@include screen(xs) {
width: 100px;
margin-bottom: 6px;
}
}
}
h1 {
@include font-family($font-light);
font-size: 1.714em;
text-align: center;
color: $txt-color01;
font-weight: 600;
line-height: 1.2;
@include screen(mid) {
font-size: 1.357em;
}
@include screen(xs) {
font-size: 1em;
}
}
}
}
.login-chk-area {
margin-top: 6px;
font-size: 13px;
margin-top: 7px;
font-size: 0.929em;
text-align: left;
@include screen(xs) {
font-size: 12px;
font-size: 0.857em;
}
}
.login-pass-info {
@ -62,14 +85,14 @@ h1 {
}
a {
line-height: 24px;
font-size: 12px;
font-size: 0.857em;
color: $gray-re4a;
padding-left: 34px;
position: relative;
white-space: nowrap;
&::before {
font-family: 'material Icons';
font-size: 18px;
font-size: 1.286em;
text-align: center;
content: 'search';
color: $white;
@ -104,7 +127,7 @@ h1 {
height: 46px;
border-radius: 4px;
background-color: #e0e3e7;
font-size: 12px;
font-size: 0.857em;
color: $gray-re4a;
cursor: pointer;
@include screen(mid) {

View File

@ -7,8 +7,24 @@
</div>
<div appLayoutsSelector="body">
<div class="file-item" *ngFor="let item of fileList">
<span class="file-name">{{ item.name }}</span>
<div class="file-item" *ngFor="let fileUploadItem of fileUploadItems">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path
d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"
></path>
</svg>
<div class="file-upload-name">{{ fileUploadItem.file.name }}</div>
<button
mat-icon-button
matSuffix
@ -18,8 +34,13 @@
>
<mat-icon>close</mat-icon>
</button>
<div class="progress-area" style="width: 100%;" *ngIf="item.progress">
<mat-progress-bar mode="determinate" value="75"> </mat-progress-bar>
<div class="progress-area" style="width: 100%;">
<mat-progress-bar
mode="determinate"
[value]="fileUploadItem.uploadingProgress$ | async"
>
</mat-progress-bar>
<button
mat-icon-button
matSuffix
@ -34,11 +55,11 @@
</div>
<div appLayoutsSelector="footer">
<button mat-button aria-label="모두에게 보내기">
모두에게 보내기
<button mat-button aria-label="취소">
취소
</button>
<button mat-button aria-label="나에게 보내기">
나에게 보내기
<button mat-button aria-label="완료">
완료
</button>
</div>
</app-layout-selector>

View File

@ -1,35 +1,103 @@
import {
Component,
OnInit,
Input,
ElementRef,
AfterViewInit,
Inject
ChangeDetectorRef,
ChangeDetectionStrategy
} from '@angular/core';
import { FileUploadItem } from '@ucap/api';
@Component({
selector: 'app-chat-selector-file-upload',
templateUrl: './file-upload.selector.component.html',
styleUrls: ['./file-upload.selector.component.scss']
styleUrls: ['./file-upload.selector.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FileUploadSelectorComponent implements OnInit, AfterViewInit {
fileList: { name: string; progress: boolean }[];
fileUploadItems: FileUploadItem[];
uploadItems: DataTransferItem[];
constructor() {}
constructor(
private elementRef: ElementRef<HTMLElement>,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
this.fileList = [
{ name: 'UCAP 메신저 UX 가이드_V0.1_0309.pptx', progress: false },
{ name: 'UCAP 메신저 UX .1_0309.pptx', progress: false },
{ name: 'UCAP 메신저 가이드_V0.1_0309.pptx', progress: true },
{ name: 'UCAP 메신저 UX 가이드_V.1_0309.pptx', progress: true },
{ name: 'UCAP 메신저 UX 가이드_V0.1_0309.pptx', progress: false },
{ name: 'UCAP 메신저 UX 가이드_V0..pptx', progress: false },
{ name: 'UCAP 메신저 UX 가이드_V0.1_0309.pptx', progress: false },
{ name: ' 메신저 UX 가이드_V0.1_0309.pptx', progress: false },
{ name: 'UCAP 메신저 UX 가이드_V0.1_0309.pptx', progress: false }
];
}
ngOnInit() {}
ngAfterViewInit(): void {}
onDragEnter(items: DataTransferItemList): void {
if (!items || 0 === items.length) {
return;
}
const uploadItems: DataTransferItem[] = [];
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < items.length; i++) {
uploadItems.push(items[i]);
}
this.uploadItems = [...uploadItems];
this.changeStyleDisplay(true);
}
onDragLeave(): void {
this.changeStyleDisplay(false);
}
onDrop(fileUploadItems: FileUploadItem[]) {
if (!fileUploadItems || 0 === fileUploadItems.length) {
return;
}
this.fileUploadItems = fileUploadItems;
this.uploadItems = undefined;
}
onFileSelected(fileUploadItems: FileUploadItem[]): void {
if (!fileUploadItems || 0 === fileUploadItems.length) {
return;
}
this.fileUploadItems = fileUploadItems;
this.uploadItems = undefined;
this.changeStyleDisplay(true);
}
onUploadComplete(): void {
setTimeout(() => {
this.fileUploadItems = undefined;
this.changeStyleDisplay(false);
}, 1000);
}
isEventInElement(event: DragEvent): boolean {
const rect = this.elementRef.nativeElement.getBoundingClientRect();
// const rect: DOMRect = this.elementRef.nativeElement.getBoundingClientRect();
if (
event.pageX >= rect.left &&
event.pageX <= rect.left + rect.width &&
event.pageY >= rect.top &&
event.pageY <= rect.top + rect.height
) {
return true;
}
return false;
}
private changeStyleDisplay(show: boolean): void {
// if (show || (!!this.fileUploadItems && 0 < this.fileUploadItems.length)) {
// if (this.dropZoneIncludeParent) {
// this.elementRef.nativeElement.parentElement.style.display = '';
// } else {
// this.elementRef.nativeElement.style.display = '';
// }
// } else {
// if (this.dropZoneIncludeParent) {
// this.elementRef.nativeElement.parentElement.style.display = 'none';
// } else {
// this.elementRef.nativeElement.style.display = 'none';
// }
// }
this.changeDetectorRef.detectChanges();
}
}

View File

@ -8,7 +8,6 @@
highlight: highlight
}"
>
isMe : {{ isMe }}
<ucap-chat-message-box-read-here
id="message-box-readhere"
*ngIf="existReadToHere"
@ -19,14 +18,13 @@
</ucap-chat-message-box-date-splitter>
<div #mbChatRow class="chat-row">
<div *ngIf="isInformation; then information; else contents"></div>
<div *ngIf="isInformation(message); then information; else contents"></div>
<ng-template #information>
<ng-container class="bubble" [ngSwitch]="message.type">
Information...
<ucap-chat-message-box-information
*ngSwitchCase="EventType.Join"
[message]="message"
></ucap-chat-message-box-information>
-->
<ucap-chat-message-box-information
*ngSwitchCase="EventType.Exit"
@ -65,7 +63,7 @@
<img
ucapImage
[base]="profileImageRoot"
[path]="senderInfo.profileImageFile"
[path]="senderInfo?.profileImageFile"
[default]="defaultProfileImage"
/>
</div>
@ -91,33 +89,40 @@
<div class="contents">
<ng-container
class="bubble"
*ngIf="
messageType !== EventType.NotificationForTimerRoom &&
!isInformation
"
*ngIf="!isInformation(message)"
[ngSwitch]="message.type"
>
<ucap-chat-message-box-text
*ngSwitchCase="EventType.Character"
[message]="message"
(openLink)="onOpenLink($event)"
>
</ucap-chat-message-box-text>
<ucap-chat-message-box-file
*ngSwitchCase="EventType.File"
[message]="message"
[roomInfo]="roomInfo"
>
</ucap-chat-message-box-file>
<ucap-chat-message-box-sticker
*ngSwitchCase="EventType.Sticker"
[message]="message"
(openLink)="onOpenLink($event)"
>
<img
ucapUiChatStickerComponent="stickerImage"
[src]="getStickerImage(message)"
onerror="this.src='assets/sticker/sticker_default.png'"
/>
</ucap-chat-message-box-sticker>
<ucap-chat-message-box-mass
*ngSwitchCase="EventType.MassText"
[message]="message"
(openLink)="onOpenLink($event)"
(massDetail)="onOpenMassDetail($event)"
>
</ucap-chat-message-box-mass>
@ -136,11 +141,13 @@
<ucap-chat-message-box-translation
*ngSwitchCase="EventType.Translation"
[message]="message"
(openLink)="onOpenLink($event)"
></ucap-chat-message-box-translation>
<ucap-chat-message-box-mass-translation
*ngSwitchCase="EventType.MassTranslation"
[message]="message"
(openLink)="onOpenLink($event)"
>
</ucap-chat-message-box-mass-translation>

View File

@ -7,22 +7,52 @@ import {
AfterViewInit,
ElementRef,
ViewChild,
ChangeDetectorRef
ChangeDetectorRef,
OnDestroy
} from '@angular/core';
import { Info, EventJson, EventType, FileType } from '@ucap/protocol-event';
import { Store, select } from '@ngrx/store';
import { map, catchError, takeUntil, take } from 'rxjs/operators';
import { of, Subject } from 'rxjs';
import {
Info,
EventJson,
EventType,
FileType,
StickerEventJson
} from '@ucap/protocol-event';
import {
UserInfo as RoomUserInfo,
UserInfoShort as RoomUserInfoShort
UserInfoShort as RoomUserInfoShort,
RoomInfo
} from '@ucap/protocol-room';
import { LoginResponse } from '@ucap/protocol-authentication';
import { LoginSession } from '@ucap/core';
import { I18nService } from '@ucap/ng-i18n';
import { LocalStorageService } from '@ucap/ng-web-storage';
import { LogService } from '@ucap/ng-logger';
import { CommonApiService } from '@ucap/ng-api-common';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { MatDialog } from '@angular/material/dialog';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { MassTalkDownloadRequest } from '@ucap/api-common';
import { StatusCode } from '@ucap/api';
import { Dictionary } from '@ngrx/entity';
@Component({
selector: 'app-chat-message-box',
templateUrl: './message-box.component.html',
styleUrls: ['./message-box.component.scss']
})
export class MessageBoxComponent implements OnInit, AfterViewInit {
export class MessageBoxComponent implements OnInit, OnDestroy, AfterViewInit {
@Input()
message: Info<EventJson>;
@Input()
roomInfo: RoomInfo;
@Input()
isMe = false;
@ -38,23 +68,129 @@ export class MessageBoxComponent implements OnInit, AfterViewInit {
@Input()
unreadCount: number;
loginSession: LoginSession;
loginRes: LoginResponse;
EventType = EventType;
//////////////////////////////////
@Input()
messageType: string;
@Input()
isInformation = false;
@Input()
highlight = false;
@Input()
existReadToHere = false;
@Input()
fileType: FileType;
constructor() {}
private ngOnDestroySubject: Subject<boolean>;
constructor(
private store: Store<any>,
private i18nService: I18nService,
private dialog: MatDialog,
private localStorageService: LocalStorageService,
private logService: LogService,
private appAuthenticationService: AppAuthenticationService,
private commonApiService: CommonApiService,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {}
ngOnInit() {
this.ngOnDestroySubject = new Subject<boolean>();
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
this.appAuthenticationService
.getLoginSession$()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((loginSession) => (this.loginSession = loginSession));
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.complete();
}
}
ngAfterViewInit(): void {}
/**
* Event .
* @description event , .
* Edit with reducers.ts / sync / updateRoomForNewEventMessage
*/
isInformation(info: Info<EventJson>) {
if (
info.type === EventType.Join ||
info.type === EventType.Exit ||
info.type === EventType.ForcedExit ||
info.type === EventType.RenameRoom ||
info.type === EventType.NotificationForiOSCapture ||
info.type === EventType.NotificationForTimerRoom ||
info.type === EventType.GuideForRoomTimerChanged
) {
return true;
}
return false;
}
/**
* Get Sticker image.
*/
getStickerImage(message: Info<EventJson>): string {
if (!!message.sentMessageJson) {
return `assets/sticker/sticker_s_${
(message.sentMessageJson as StickerEventJson).file
}.png`;
} else {
return '';
}
}
/**
* Url link Open Event.
*/
onOpenLink(url: string): void {
console.log('onOpenLink', url);
}
/**
* Detail view > Mass text.
*/
onOpenMassDetail(eventMassSeq: number): void {
const req = {
userSeq: this.loginRes.userSeq,
deviceType: this.loginSession.deviceType,
eventMassSeq: Number(eventMassSeq),
token: this.loginRes.tokenString
} as MassTalkDownloadRequest;
this.commonApiService
.massTalkDownload(req)
.pipe(
take(1),
map((res) => {
if (res.statusCode === StatusCode.Success) {
console.log('massTalkDownload', res.content);
// const result = this.dialog.open<
// MassDetailComponent,
// MassDetailDialogData
// >(MassDetailComponent, {
// data: {
// title: this.i18nService.t('chat.detailView'),
// contents: res.content
// }
// });
} else {
this.logService.error(
`commonApiService] massTalkDownload ${res?.errorMessage}`
);
}
}),
catchError((error) => of({ error }))
)
.subscribe();
}
}

View File

@ -19,7 +19,10 @@
<ng-template ucapChatRoomExpansionHeader let-node>
<span class="header-buddy">
<span>{{ node.nodeType }} {{ node.nodeType | ucapDate: 'dddd' }}</span>
<span
>{{ node.nodeType | ucapDate: 'LL' }}
{{ node.nodeType | ucapDate: 'dddd' }}</span
>
<span *ngIf="isToday(node.nodeType)">
({{ 'room.today' | ucapI18n }})
</span>

View File

@ -151,10 +151,18 @@ export class RoomExpansionComponent implements OnInit, OnDestroy {
this.loginRes = loginRes;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(RoomSelector.rooms))
.subscribe((rooms) => {
rooms = (rooms || []).filter((info) => info.isJoinRoom);
combineLatest([
this.store.pipe(select(RoomSelector.rooms)),
this.store.pipe(select(RoomSelector.standbyRooms))
])
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe(([rooms, standbyRooms]) => {
rooms = (rooms || []).filter((info) => {
return (
info.isJoinRoom &&
!standbyRooms.find((standbyRoom) => standbyRoom === info.roomId)
);
});
this.roomList = rooms;
// groupping.
@ -201,7 +209,7 @@ export class RoomExpansionComponent implements OnInit, OnDestroy {
const date = roomInfo.finalEventDate;
let division = '';
try {
const value = this.dateService.get(date, 'LL');
const value = this.dateService.get(date, 'YYYYMMDD');
if (value === 'Invalid date') {
division = date;
@ -295,8 +303,8 @@ export class RoomExpansionComponent implements OnInit, OnDestroy {
ConfirmDialogResult
>(ConfirmDialogComponent, {
data: {
title: this.i18nService.t('room.dialog.titleExitFromRoom'),
html: this.i18nService.t('room.dialog.confirmExitFromRoom')
title: this.i18nService.t('dialog.title.exitFromRoom'),
html: this.i18nService.t('dialog.confirmExitFromRoom')
}
});

View File

@ -4,8 +4,19 @@ import {
Input,
ElementRef,
AfterViewInit,
Inject
Inject,
Output,
EventEmitter
} from '@angular/core';
import {
StickerInfo,
StickerFilesInfo,
StickerUtil,
StickerMap,
ActiveAndOrdering,
KEY_STICKER_HISTORY
} from '@ucap/ng-core';
import { LocalStorageService } from '@ucap/ng-web-storage';
@Component({
selector: 'app-chat-selector-sticker',
@ -13,6 +24,12 @@ import {
styleUrls: ['./sticker.selector.component.scss']
})
export class StickerSelectorComponent implements OnInit, AfterViewInit {
@Output()
selectedSticker = new EventEmitter<StickerFilesInfo>();
@Output()
closeSticker = new EventEmitter();
stickerHistory: string[] = [];
stickerBasePath = '../../../../assets/sticker/';
@ -22,10 +39,20 @@ export class StickerSelectorComponent implements OnInit, AfterViewInit {
currentTabIndex: number;
constructor() {}
customStickerMap: StickerInfo[] = [...StickerMap];
activeAndOrdering: string[] = [...ActiveAndOrdering];
constructor(private localStorageService: LocalStorageService) {}
ngOnInit() {
this.stickerInfoList = StickerUtil.getStickerInfoList();
this.stickerHistory = this.localStorageService.get<string[]>(
KEY_STICKER_HISTORY
);
this.stickerInfoList = StickerUtil.getStickerInfoList(
this.customStickerMap,
this.activeAndOrdering
);
this.currentTabIndex = 0;
}
@ -59,7 +86,7 @@ export class StickerSelectorComponent implements OnInit, AfterViewInit {
this.stickerHistory.forEach((sticker) => {
const arr: string[] = sticker.split('_');
if (arr.length === 2) {
const sInfo: StickerInfo[] = StickerMap.filter(
const sInfo: StickerInfo[] = this.customStickerMap.filter(
(s) => s.index === arr[0]
);
if (!!sInfo && sInfo.length > 0) {
@ -84,180 +111,10 @@ export class StickerSelectorComponent implements OnInit, AfterViewInit {
onClickSelectSticker(sticker: StickerFilesInfo) {
this.currentSticker = sticker;
this.selectedSticker.emit(sticker);
}
onClickClose() {}
}
export interface StickerInfo {
index: string;
title: string;
iconPath: string;
iconPathOn: string;
useYn: boolean;
fileInfos: StickerFilesInfo[];
}
export interface StickerFilesInfo {
index: string;
path: string;
}
export const StickerMap: StickerInfo[] = [
{
index: '00',
title: 'History',
iconPath: 'sticker_cate00.png',
iconPathOn: 'sticker_cate00_f.png',
useYn: true,
fileInfos: []
},
{
index: '01',
title: '꼼므',
iconPath: 'sticker_cate01.png',
iconPathOn: 'sticker_cate01_f.png',
useYn: true,
fileInfos: [
{ index: '01_01', path: 'sticker_s_01_01.png' },
{ index: '01_02', path: 'sticker_s_01_02.png' },
{ index: '01_03', path: 'sticker_s_01_03.png' },
{ index: '01_04', path: 'sticker_s_01_04.png' },
{ index: '01_05', path: 'sticker_s_01_05.png' },
{ index: '01_06', path: 'sticker_s_01_06.png' },
{ index: '01_07', path: 'sticker_s_01_07.png' },
{ index: '01_08', path: 'sticker_s_01_08.png' }
]
},
{
index: '02',
title: '까미',
iconPath: 'sticker_cate02.png',
iconPathOn: 'sticker_cate02_f.png',
useYn: true,
fileInfos: [
{ index: '02_02', path: 'sticker_s_02_02.png' },
{ index: '02_03', path: 'sticker_s_02_03.png' },
{ index: '02_04', path: 'sticker_s_02_04.png' },
{ index: '02_05', path: 'sticker_s_02_05.png' },
{ index: '02_06', path: 'sticker_s_02_06.png' },
{ index: '02_07', path: 'sticker_s_02_07.png' },
{ index: '02_08', path: 'sticker_s_02_08.png' }
]
},
{
index: '03',
title: '왈도',
iconPath: 'sticker_cate03.png',
iconPathOn: 'sticker_cate03_f.png',
useYn: true,
fileInfos: [
{ index: '03_01', path: 'sticker_s_03_01.png' },
{ index: '03_02', path: 'sticker_s_03_02.png' },
{ index: '03_03', path: 'sticker_s_03_03.png' },
{ index: '03_04', path: 'sticker_s_03_04.png' },
{ index: '03_05', path: 'sticker_s_03_05.png' },
{ index: '03_06', path: 'sticker_s_03_06.png' },
{ index: '03_07', path: 'sticker_s_03_07.png' },
{ index: '03_08', path: 'sticker_s_03_08.png' }
]
},
{
index: '04',
title: '웅쓰',
iconPath: 'sticker_cate04.png',
iconPathOn: 'sticker_cate04_f.png',
useYn: true,
fileInfos: [
{ index: '04_01', path: 'sticker_s_04_01.png' },
{ index: '04_02', path: 'sticker_s_04_02.png' },
{ index: '04_03', path: 'sticker_s_04_03.png' },
{ index: '04_04', path: 'sticker_s_04_04.png' },
{ index: '04_05', path: 'sticker_s_04_05.png' },
{ index: '04_06', path: 'sticker_s_04_06.png' },
{ index: '04_07', path: 'sticker_s_04_07.png' },
{ index: '04_08', path: 'sticker_s_04_08.png' }
]
},
{
index: '05',
title: '말풍선',
iconPath: 'sticker_cate05.png',
iconPathOn: 'sticker_cate05_f.png',
useYn: true,
fileInfos: [
{ index: '05_01', path: 'sticker_s_05_01.png' },
{ index: '05_02', path: 'sticker_s_05_02.png' },
{ index: '05_03', path: 'sticker_s_05_03.png' },
{ index: '05_04', path: 'sticker_s_05_04.png' },
{ index: '05_05', path: 'sticker_s_05_05.png' },
{ index: '05_06', path: 'sticker_s_05_06.png' },
{ index: '05_07', path: 'sticker_s_05_07.png' },
{ index: '05_08', path: 'sticker_s_05_08.png' },
{ index: '05_09', path: 'sticker_s_05_09.png' },
{ index: '05_10', path: 'sticker_s_05_10.png' },
{ index: '05_11', path: 'sticker_s_05_11.png' },
{ index: '05_12', path: 'sticker_s_05_12.png' },
{ index: '05_13', path: 'sticker_s_05_13.png' },
{ index: '05_14', path: 'sticker_s_05_14.png' },
{ index: '05_15', path: 'sticker_s_05_15.png' },
{ index: '05_16', path: 'sticker_s_05_16.png' }
]
},
{
index: '12',
title: '황소',
iconPath: 'sticker_cate12.png',
iconPathOn: 'sticker_cate12_f.png',
useYn: true,
fileInfos: [
{ index: '12_01', path: 'sticker_s_12_01.gif' },
{ index: '12_02', path: 'sticker_s_12_02.gif' },
{ index: '12_03', path: 'sticker_s_12_03.gif' },
{ index: '12_04', path: 'sticker_s_12_04.gif' },
{ index: '12_05', path: 'sticker_s_12_05.gif' },
{ index: '12_06', path: 'sticker_s_12_06.gif' },
{ index: '12_07', path: 'sticker_s_12_07.gif' },
{ index: '12_08', path: 'sticker_s_12_08.gif' },
{ index: '12_09', path: 'sticker_s_12_09.gif' },
{ index: '12_10', path: 'sticker_s_12_10.gif' },
{ index: '12_11', path: 'sticker_s_12_11.gif' },
{ index: '12_12', path: 'sticker_s_12_12.gif' },
{ index: '12_13', path: 'sticker_s_12_13.gif' },
{ index: '12_14', path: 'sticker_s_12_14.gif' },
{ index: '12_15', path: 'sticker_s_12_15.gif' },
{ index: '12_16', path: 'sticker_s_12_16.gif' }
]
}
];
const ActiveAndOrdering: string[] = ['00', '01', '02', '03', '04', '05'];
export class StickerUtil {
static getStickerInfoList(): StickerInfo[] {
const rtnStickerList: StickerInfo[] = [];
ActiveAndOrdering.forEach((idx) => {
const stickerInfos: StickerInfo[] = StickerMap.filter(
(sticker) => sticker.index === idx && sticker.useYn
);
if (!!stickerInfos && stickerInfos.length > 0) {
rtnStickerList.push(stickerInfos[0]);
}
});
return rtnStickerList;
}
static getStickerInfoSub(index: string): StickerFilesInfo[] {
const stickerFilesList: StickerFilesInfo[] = [];
const stickerInfos: StickerInfo[] = StickerMap.filter(
(sticker) => sticker.index === index && sticker.useYn
);
if (!!stickerInfos && stickerInfos.length > 0) {
stickerFilesList.concat(stickerInfos[0].fileInfos);
}
return stickerFilesList;
onClickClose() {
this.closeSticker.emit();
}
}

View File

@ -8,22 +8,18 @@
[selectedUserList]="selectedUserList"
[checkable]="checkable"
(clickMoreMenu)="!isDialog ? onClickMoreMenu($event) : null"
(checkGroup)="onCheckGroup($event)"
(checkGroup)="onToggleCheckGroup($event)"
>
<ng-template ucapGroupExpansionNode let-node>
<app-group-profile-list-item
<app-group-profile-list-item-02
[userInfo]="node.userInfo"
[isMe]="loginRes?.userSeq === node.userInfo.seq"
[group]="node.groupDetail"
defaultProfileImage="assets/images/img_nophoto_50.png"
[profileImageRoot]="versionInfo2Res?.profileRoot"
[checkable]="checkable"
[presence]="getStatusBulkInfo(node.userInfo) | async"
[isChecked]="getCheckedUser(node.userInfo)"
(checked)="onCheckedUser($event)"
(click)="onClickUser($event, node.userInfo)"
[isDialog]="isDialog"
[checked]="getCheckedUser(node.userInfo)"
[isMe]="loginRes?.userSeq === node.userInfo.seq"
(moreMenu)="onClikeMoreProfile($event)"
></app-group-profile-list-item>
(changeCheckUser)="onChangeCheckUser($event)"
></app-group-profile-list-item-02>
</ng-template>
<ng-template ucapGroupExpansionFavoriteHeader let-node>
@ -109,7 +105,7 @@
#profileContextMenuTrigger="matMenuTrigger"
[matMenuTriggerFor]="profileContextMenu"
></div>
<mat-menu #profileContextMenu="matMenu">
<mat-menu #profileContextMenu="matMenu" (closed)="onProfileMenuClose($event)">
<ng-template
matMenuContent
let-userInfo="userInfo"
@ -177,46 +173,5 @@
>
{{ 'moreMenu.profile.removeBuddy' | ucapI18n }}
</button>
<!-- <button
mat-menu-item
*ngIf="getShowProfileContextMenu('REGISTER_NICKNAME', userInfo, group)"
(click)="onClickProfileContextMenu('REGISTER_NICKNAME', userInfo)"
>
닉네임 설정
</button> -->
</ng-template>
</mat-menu>
<!-- <div
style="visibility: hidden; position: fixed;"
[style.left]="editGroupNamePosition.x"
[style.top]="editGroupNamePosition.y"
#editGroupNameTrigger="matMenuTrigger"
[matMenuTriggerFor]="groupNameMenu"
></div>
<mat-menu #groupNameMenu="matMenu">
<ng-template matMenuContent let-group="group">
<ucap-inline-edit-input
[initialMode]="true"
(apply)="
$event.stopPropagation();
onApplyEditGroupName(editGroupInput.value, group)
"
(edit)="$event.stopPropagation()"
(cancel)="$event.stopPropagation(); editGroupInput.value = group.name"
class="form-eidt"
>
<span ucapInlineEditInput="view">{{ group.name }}</span>
<span ucapInlineEditInput="edit"
><input
matInput
#editGroupInput
type="text"
[value]="group.name"
(click)="$event.stopPropagation()"
/></span>
</ucap-inline-edit-input>
</ng-template>
</mat-menu> -->

View File

@ -52,16 +52,9 @@ import { environment } from '@environments';
import { PresenceSelector } from '@ucap/ng-store-organization';
import { StatusCode } from '@ucap/core';
import { EditInlineInputDialogComponent } from '@app/sections/group/dialogs/edit-inline-input.dialog.component';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult,
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
} from '@ucap/ng-ui';
import { I18nService } from '@ucap/ng-i18n';
import { ProfileListItem01Component } from '@ucap/ng-ui-organization';
import { ProfileListItem02Component } from './profile-list-item-02.component';
export type UserInfoTypes =
| UserInfo
@ -104,12 +97,6 @@ export class ExpansionComponent implements OnInit, OnDestroy {
@Input()
showType: string;
@Output()
checked = new EventEmitter<{
isChecked: boolean;
userInfo: UserInfoTypes;
}>();
@Output()
profileMenu: EventEmitter<any> = new EventEmitter();
@ -119,17 +106,26 @@ export class ExpansionComponent implements OnInit, OnDestroy {
@Output()
selectGroupMenu = new EventEmitter<{
menuType: string;
group: GroupDetailData;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
rect: any;
}>();
// @Output()
// editGroupName = new EventEmitter<{
// editName: string;
// group: GroupDetailData;
// }>();
@Output()
selectProfileMenu: EventEmitter<{
menuType: string;
userInfo: UserInfoF;
group: GroupDetailData;
rect: any;
}> = new EventEmitter();
@Output()
checkGroup = new EventEmitter<{
toggleCheckUser: EventEmitter<{
checked: boolean;
userInfo: UserInfoSS;
}> = new EventEmitter();
@Output()
toggleCheckGroup = new EventEmitter<{
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}>();
@ -178,7 +174,6 @@ export class ExpansionComponent implements OnInit, OnDestroy {
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService,
private dialog: MatDialog,
private i18nService: I18nService,
@Self() private elementRef: ElementRef
) {}
@ -366,10 +361,6 @@ export class ExpansionComponent implements OnInit, OnDestroy {
this.profileMenu.emit({ event, userInfo, group, isSearchData });
}
onCheckedUser(params: { isChecked: boolean; userInfo: UserInfoTypes }) {
this.checked.emit(params);
}
onClickUser(event: MouseEvent, userInfo: UserInfo) {
this.clicked.emit({ event, userInfo });
}
@ -463,94 +454,59 @@ export class ExpansionComponent implements OnInit, OnDestroy {
}
onSelectGroupMenu(menuType: string, group: GroupDetailData) {
let rect: any;
if (menuType.localeCompare('RENAME') === 0) {
// this.editablGroup = group;
// this.onShowEditGroup(group);
const target = this.elementRef.nativeElement.querySelector(
'.mat-tree-node'
);
const rect = target.getBoundingClientRect();
const clickEventY = this.groupMenuEvent.clientY;
const tartgetY = Math.floor((clickEventY - 150) * 0.1) * 10;
const clientRect = target.getBoundingClientRect();
const dialogRef = this.dialog.open(EditInlineInputDialogComponent, {
width: rect.width,
height: rect.height,
panelClass: 'ucap-edit-group-name-dialog',
data: {
curValue: group.name,
placeholder: '그룹명을 입력하세요.',
left: rect.left,
top: clickEventY - 100 + rect.height
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (
!!result &&
result.choice &&
result.curValue.localeCompare(group.name) !== 0
) {
this.store.dispatch(
GroupActions.update({
req: {
groupSeq: group.seq,
groupName: result.curValue,
userSeqs: group.userSeqs
}
})
);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
rect = {
width: clientRect.width,
height: clientRect.height,
top: clickEventY - 100 + clientRect.height,
left: clientRect.left,
bottom: clientRect.bottom,
right: clientRect.right
};
}
this.selectGroupMenu.emit({ menuType, group });
}
// onApplyEditGroupName(editName: string, group: GroupDetailData) {
// // this.editablGroup = null;
// this.editGroupName.emit({
// editName,
// group
// });
// }
onCheckGroup(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
this.checkGroup.emit({
isChecked: params.isChecked,
groupBuddyList: params.groupBuddyList
const groupBuddyList = this.groupBuddies.filter(
(g) => g.group.seq === group.seq
);
this.selectGroupMenu.emit({
menuType,
groupBuddyList: groupBuddyList[0],
rect
});
}
/** 개별선택(토글) 이벤트 */
onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoSS }) {
this.toggleCheckUser.emit(param);
}
/** 개별 체크여부 */
getCheckedUser(userInfo: UserInfoTypes) {
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
return (
this.selectedUserList.filter((item) => item.seq === userInfo.seq)
.length > 0
this.selectedUserList.filter(
(item) => (item.seq as any) === userInfo.seq
).length > 0
);
}
return false;
}
getStatusBulkInfo(buddy: UserInfoTypes) {
return this.store.pipe(
select(PresenceSelector.selectEntitiesStatusBulkInfo),
map((statusBulkInfo) =>
!!statusBulkInfo ? statusBulkInfo[buddy.seq] : undefined
)
);
onToggleCheckGroup(params: {
isChecked: boolean;
groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] };
}) {
this.toggleCheckGroup.emit({
isChecked: params.isChecked,
groupBuddyList: params.groupBuddyList
});
}
getBuddiesForShowType(): { group: GroupDetailData; buddyList: UserInfo[] }[] {
@ -592,114 +548,30 @@ export class ExpansionComponent implements OnInit, OnDestroy {
) {
event.preventDefault();
event.stopPropagation();
switch (menuType) {
case 'VIEW_PROFILE':
this.onClickUser(event, userInfo as UserInfo);
break;
case 'REGISTER_FAVORITE':
this.store.dispatch(
BuddyActions.update({
req: {
seq: Number(userInfo.seq),
isFavorit: !userInfo.isFavorit
}
})
);
break;
case 'NICKNAME':
{
this.editNickname(event, userInfo, rect);
}
break;
case 'COPY_BUDDY':
break;
case 'MOVE_BUDDY':
break;
case 'REMOVE_BUDDY':
{
this.removeBuddy(userInfo, group);
}
break;
}
}
private removeBuddy(userInfo: UserInfoF, group: GroupDetailData) {
const dialogRef = this.dialog.open<
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
>(ConfirmDialogComponent, {
data: {
title: '',
html: this.i18nService.t('label.confirmRemoveBuddy')
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (!!result && result.choice) {
const trgtUserSeq = group.userSeqs.filter(
(user) => user + '' !== userInfo.seq + ''
);
this.store.dispatch(
GroupActions.updateMember({
targetGroup: group,
targetUserSeqs: trgtUserSeq
})
);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
private editNickname(event: MouseEvent, userInfo: UserInfoF, rect: any) {
const clickEventY = event.clientY;
const dialogRef = this.dialog.open(EditInlineInputDialogComponent, {
width: rect.width - 30 + '',
const clientRect = {
width: rect.width,
height: rect.height,
panelClass: 'ucap-edit-group-name-dialog',
data: {
curValue: userInfo.nickName,
placeholder: '닉네임을 설정하세요.',
left: rect.left + 70,
top: rect.top
}
});
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom
};
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (
!!result &&
result.choice &&
result.curValue.localeCompare(userInfo.nickName) !== 0
) {
this.store.dispatch(
BuddyActions.nickname({
req: {
userSeq: Number(userInfo.seq),
nickname: result.curValue
}
})
);
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
if (menuType === 'VIEW_PROFILE') {
this.onClickUser(event, userInfo as UserInfo);
}
this.selectProfileMenu.emit({
menuType,
userInfo,
group,
rect: clientRect
});
}
onProfileMenuClose(event: MouseEvent) {}
getShowProfileContextMenu(menuType: string, group: GroupDetailData) {
return true;
if (!!this.isSearchData) {

View File

@ -1,4 +1,10 @@
import { ExpansionComponent } from './expansion.component';
import { ProfileListItemComponent } from './profile-list-item.component';
export const COMPONENTS = [ExpansionComponent, ProfileListItemComponent];
import { ProfileListItem02Component } from './profile-list-item-02.component';
import { ProfileListComponent } from './profile-list.component';
export const COMPONENTS = [
ExpansionComponent,
ProfileListItem02Component,
ProfileListComponent
];

View File

@ -0,0 +1,91 @@
<div
class="profile-list-container"
(mouseover)="onMouseover($event)"
(mouseleave)="onMouseleave($event)"
>
<ucap-organization-profile-list-item-02
[userInfo]="userInfo"
[checkable]="checkable"
[checked]="checked"
(openProfile)="onOpenProfile($event)"
(changeCheck)="onChangeCheckUser($event)"
>
<ucap-organization-profile-image-01
ucapOrganizationProfileListItem01="profileImage"
[userInfo]="userInfo"
[profileImageRoot]="versionInfo2Res?.profileRoot"
defaultProfileImage="assets/images/ico/img_nophoto.svg"
>
</ucap-organization-profile-image-01>
<div class="info-content" appProfileListItemUserInfo="info" *ngIf="!isShow">
<div
*ngIf="
!!userInfo && userInfo.nickName !== '';
then nicknameBlock;
else introBlock
"
></div>
<ng-template #nicknameBlock>{{ userInfo.nickName }}</ng-template>
<ng-template #introBlock>{{ userInfo.intro }}</ng-template>
</div>
<div
class="action-content"
appProfileListItemUserAction="action"
*ngIf="isShow && !isDialog"
>
<ng-template
[ngTemplateOutlet]="profileMoreContextTemplate"
></ng-template>
</div>
</ucap-organization-profile-list-item-02>
</div>
<ng-template #profileMoreContextTemplate>
<button
mat-icon-button
aria-label="chat"
(click)="onClickProfileContextMenu($event, 'CHAT')"
>
<img src="../../../assets/images/ico/btn_lise_chat_a24.svg" alt="" />
</button>
<button
mat-icon-button
aria-label="message"
(click)="
$event.stopPropagation(); onClickProfileContextMenu($event, 'CHAT')
"
>
<img src="../../../assets/images/ico/btn_list_message_a24.svg" alt="" />
</button>
<button
mat-icon-button
aria-label="mobile"
(click)="onClickProfileContextMenu($event, 'CHAT')"
>
<img src="../../../assets/images/ico/btn_list_mobile_a24.svg" alt="" />
</button>
<button
mat-icon-button
aria-label="call"
(click)="onClickProfileContextMenu($event, 'CHAT')"
>
<img src="../../../assets/images/ico/btn_list_call_a24.svg" alt="" />
</button>
<button
mat-icon-button
aria-label="vc"
(click)="onClickProfileContextMenu($event, 'CHAT')"
>
<img src="../../../assets/images/ico/btn_list_vc-a24.svg" alt="" />
</button>
<button
mat-icon-button
aria-label="more"
*ngIf="true"
(click)="onClickMore($event)"
>
<mat-icon>more_horiz</mat-icon>
</button>
</ng-template>

View File

@ -0,0 +1,139 @@
@import '~@ucap/ng-ui-material/material';
/// var
/// --ucap-organization-profile-list-item-01-size: 70px
.ucap-group-profile-list-item-01-container {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
padding: 0 16px;
height: 70px;
align-items: center;
&.line-top {
// border-top: 1px solid $gray-rec;
}
.user-profile-info {
display: inline-flex;
flex-direction: row;
flex-grow: 2.3;
// .user-profile-thumb {
// @include profile-avatar-default(
// 0 5px 5px 0,
// 8,
// $green,
// 18px
// ); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 , 모바일 아이콘 bg크기
// .presence {
// //PC 상태
// @include presence-state(8px); //원크기
// }
// .profile-image {
// @include avatar-img(36px, 2px); //아바타 크기, 왼쪽공간
// }
// }
.user-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding-left: 16px;
.user-n-g {
display: flex;
flex-flow: row-reverse nowrap;
align-items: flex-end;
height: 22px;
.user-name {
@include ellipsis-column(1);
height: 22px;
font: {
size: 16px;
weight: 600;
}
// color: $gray-re21;
order: 1;
-ms-flex-order: 1;
}
.user-grade {
@include ellipsis(1);
align-self: stretch;
font: {
size: 13px;
}
// color: $gray-re70;
margin-left: 4px;
order: 0;
-ms-flex-order: 0;
}
}
.dept-name {
@include ellipsis(1);
font-size: 12px;
// color: $gray-re6;
line-height: 16px;
}
}
}
.intro {
display: inline-flex;
flex-flow: row nowrap;
flex-basis: 35%;
flex-grow: 0;
align-items: baseline;
p {
font-size: 11px;
line-height: 1.4;
@include ellipsis(2);
height: 30px;
}
&:before {
content: 'chat';
// @include font-family-ico($font-ico-default, 12, center, $lipstick);
flex-direction: row;
align-items: flex-start;
width: 12px;
height: 12px;
line-height: 12px;
margin-right: 4.8px;
position: relative;
top: 2px;
}
}
.btn-partner-set {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 25px;
border-top: 1px solid rgba(255, 255, 255, 0.8);
border-bottom: 1px solid rgba(255, 255, 255, 0.8);
height: 20px;
margin-top: 20px;
img {
vertical-align: top;
}
}
.intro-name {
display: inline-flex;
flex-flow: row nowrap;
flex-basis: 35%;
align-items: center;
justify-content: center;
overflow: hidden;
span {
display: inline-block;
text-align: center;
width: 100%;
height: 20px;
line-height: 20px;
// color: $gray-re70;
font-size: 11px;
padding: 0 10px;
border-radius: 30px;
// border: solid 1px $warm-pink;
background-color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ProfileListItemComponent } from './profile-list-item.component';
describe('ucap::ucap::organization::ProfileListItemComponent', () => {
let component: ProfileListItemComponent;
let fixture: ComponentFixture<ProfileListItemComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ProfileListItemComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProfileListItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,157 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input,
EventEmitter,
Output,
ElementRef,
Self
} from '@angular/core';
import { GroupDetailData } from '@ucap/protocol-sync';
import { UserInfoSS, UserInfoF } from '@ucap/protocol-query';
import { I18nService } from '@ucap/ng-i18n';
import { ucapAnimations } from '@ucap/ng-ui';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { select, Store } from '@ngrx/store';
import {
ConfigurationSelector,
LoginSelector
} from '@ucap/ng-store-authentication';
import { VersionInfo2Response } from '@ucap/api-public';
import { LoginResponse } from '@ucap/protocol-authentication';
@Component({
selector: 'app-group-profile-list-item-02',
templateUrl: './profile-list-item-02.component.html',
styleUrls: ['./profile-list-item-02.component.scss'],
animations: ucapAnimations,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProfileListItem02Component implements OnInit, OnDestroy {
@Input()
userInfo: UserInfoF;
@Input()
group: GroupDetailData;
@Input()
checkable = false;
@Input()
isMe = false;
@Input()
checked = false;
@Output()
moreMenu: EventEmitter<{
event: MouseEvent;
userInfo: UserInfoF;
group: GroupDetailData;
rect: any;
}> = new EventEmitter();
@Output()
changeCheckUser: EventEmitter<{
checked: boolean;
userInfo: UserInfoF;
}> = new EventEmitter();
@Input()
isShow = false;
@Input()
isDialog = false;
@Input()
isClickMore = false;
private ngOnDestroySubject: Subject<void>;
loginRes: LoginResponse;
versionInfo2Res: VersionInfo2Response;
tempRect: any;
constructor(
private changeDetectorRef: ChangeDetectorRef,
private i18nService: I18nService,
@Self() private elementRef: ElementRef,
private store: Store<any>
) {
this.i18nService.setDefaultNamespace('organization');
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject();
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
});
this.tempRect = this.elementRef.nativeElement.getBoundingClientRect();
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onOpenProfile(userInfo: UserInfoSS): void {
alert('Open Profile');
}
onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoF }) {
this.changeCheckUser.emit(param);
}
onMouseover(event: MouseEvent): void {
if (!this.isMe && !this.isDialog) {
this.isShow = true;
}
event.preventDefault();
event.stopPropagation();
}
onMouseleave(event: MouseEvent): void {
if (!this.isMe && !this.isDialog) {
this.isShow = false;
}
event.preventDefault();
event.stopPropagation();
}
onClickProfileContextMenu(event: MouseEvent, type: string) {}
onClickMore(event: MouseEvent) {
this.isClickMore = true;
const rect = this.elementRef.nativeElement.getBoundingClientRect();
this.moreMenu.emit({
event,
userInfo: this.userInfo,
group: this.group,
rect
});
event.preventDefault();
event.stopPropagation();
}
}

View File

@ -0,0 +1,21 @@
<div class="profile-list-container">
<ucap-organization-profile-list [userInfos]="userInfos">
<ng-template ucapOrganizationProfileListNode let-userInfo>
<app-group-profile-list-item-02
[userInfo]="userInfo"
[checkable]="checkable"
[isDialog]="isDialog"
[checked]="getCheckedUser(userInfo)"
(openProfile)="onOpenProfile($event)"
(changeCheckUser)="onChangeCheckUser($event)"
>
<ucap-organization-profile-image-01
ucapOrganizationProfileListItem01="profileImage"
[userInfo]="userInfo"
[profileImageRoot]="versionInfo2Res?.profileRoot"
defaultProfileImage="assets/images/ico/img_nophoto.svg"
></ucap-organization-profile-image-01>
</app-group-profile-list-item-02>
</ng-template>
</ucap-organization-profile-list>
</div>

View File

@ -0,0 +1,4 @@
.profile-list-container {
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,32 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Profile01Component } from './profile-01.component';
describe('app::ucap::organization::Profile01Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [Profile01Component]
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(Profile01Component);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'ucap-lg-web'`, () => {
const fixture = TestBed.createComponent(Profile01Component);
const app = fixture.componentInstance;
});
it('should render title', () => {
const fixture = TestBed.createComponent(Profile01Component);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain(
'ucap-lg-web app is running!'
);
});
});

View File

@ -0,0 +1,207 @@
import { Subject, of } from 'rxjs';
import { takeUntil, take, map, catchError } from 'rxjs/operators';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input,
Output,
EventEmitter
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import { VersionInfo2Response } from '@ucap/api-public';
import { LoginResponse } from '@ucap/protocol-authentication';
import {
AuthResponse,
UserInfoSS,
DeptSearchType,
DeptUserRequest
} from '@ucap/protocol-query';
import { LogService } from '@ucap/ng-logger';
import {
LoginSelector,
AuthorizationSelector,
ConfigurationSelector
} from '@ucap/ng-store-authentication';
import { QueryProtocolService } from '@ucap/ng-protocol-query';
import {
DepartmentSelector,
PresenceActions
} from '@ucap/ng-store-organization';
import {
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY
} from '@angular/cdk/scrolling';
import { SearchData } from '@app/ucap/organization/models/search-data';
export class ProfileListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
constructor() {
super(70, 250, 500); // (itemSize, minBufferPx, maxBufferPx)
}
}
@Component({
selector: 'app-group-profile-list',
templateUrl: './profile-list.component.html',
styleUrls: ['./profile-list.component.scss'],
providers: [
{
provide: VIRTUAL_SCROLL_STRATEGY,
useClass: ProfileListVirtualScrollStrategy
}
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProfileListComponent implements OnInit, OnDestroy {
@Input()
checkable = false;
@Input()
isDialog = false;
@Input()
set searchData(data: SearchData) {
if (!this.loginRes) {
this._searchData = data;
return;
}
this.searchMember(data);
}
// tslint:disable-next-line: variable-name
private _searchData: SearchData;
@Input() selectedUser: UserInfoSS[];
@Output()
searched: EventEmitter<UserInfoSS[]> = new EventEmitter();
@Output()
toggleCheck: EventEmitter<{
checked: boolean;
userInfo: UserInfoSS;
}> = new EventEmitter();
set userInfos(userInfos: UserInfoSS[]) {
this._userInfos = userInfos;
this.searched.emit(userInfos);
}
get userInfos() {
return this._userInfos;
}
// tslint:disable-next-line: variable-name
_userInfos: UserInfoSS[] = [];
loginRes: LoginResponse;
versionInfo2Res: VersionInfo2Response;
processing = false;
private ngOnDestroySubject: Subject<void>;
constructor(
private queryProtocolService: QueryProtocolService,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService
) {}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject();
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(ConfigurationSelector.versionInfo2Response)
)
.subscribe((versionInfo2Res) => {
this.versionInfo2Res = versionInfo2Res;
});
this.store
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
if (!!this._searchData) {
this.searchMember(this._searchData);
this._searchData = undefined;
}
});
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.complete();
}
}
/** 개별 체크여부 */
getCheckedUser(userInfo: UserInfoSS) {
if (!!this.selectedUser && this.selectedUser.length > 0) {
return (
this.selectedUser.filter((item) => item.seq === userInfo.seq).length > 0
);
}
return false;
}
/** 개별선택(토글) 이벤트 */
onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoSS }) {
this.toggleCheck.emit(param);
}
onOpenProfile(userInfo: UserInfoSS): void {
alert('Open Profile');
}
private searchMember(searchData: SearchData) {
if (!searchData) {
return;
}
const req = {
divCd: 'ORGS',
companyCode: searchData.companyCode,
searchRange: DeptSearchType.All,
search: searchData.searchWord,
senderCompanyCode: this.loginRes.userInfo.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
} as DeptUserRequest;
this.processing = true;
this.queryProtocolService
.deptUser(req)
.pipe(take(1))
.subscribe(
(data) => {
this.userInfos = data.userInfos.sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);
this.changeDetectorRef.detectChanges();
// 검색 결과에 따른 프레즌스 조회.
const userSeqList: string[] = [];
this.userInfos.map((user) => userSeqList.push(user.seq));
if (userSeqList.length > 0) {
this.store.dispatch(
PresenceActions.bulkInfo({
divCd: 'orgSrch',
userSeqs: userSeqList
})
);
}
},
(error) => {},
() => {
this.processing = false;
}
);
}
}

View File

@ -6,7 +6,7 @@
[checkable]="loginRes?.userSeq !== userInfo.seq"
[checked]="getCheckedUser(userInfo)"
(openProfile)="onOpenProfile($event)"
(changeCheck)="onToggleUser($event)"
(changeCheck)="onChangeCheckUser($event)"
>
<ucap-organization-profile-image-01
ucapOrganizationProfileListItem01="profileImage"

View File

@ -1,5 +1,5 @@
import { Subject, of } from 'rxjs';
import { takeUntil, take, map, catchError } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import {
Component,
@ -7,91 +7,115 @@ import {
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input
Input,
Output,
EventEmitter
} from '@angular/core';
import { Store, select } from '@ngrx/store';
import { VersionInfo2Response } from '@ucap/api-public';
import { LoginResponse } from '@ucap/protocol-authentication';
import { AuthResponse, UserInfoSS, DeptSearchType } from '@ucap/protocol-query';
import {
UserInfoSS,
DeptSearchType,
DeptUserRequest
} from '@ucap/protocol-query';
import { LogService } from '@ucap/ng-logger';
import {
LoginSelector,
AuthorizationSelector,
ConfigurationSelector
} from '@ucap/ng-store-authentication';
import { QueryProtocolService } from '@ucap/ng-protocol-query';
import {
DepartmentActions,
DepartmentSelector,
PresenceActions
} from '@ucap/ng-store-organization';
import {
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY
} from '@angular/cdk/scrolling';
import { SearchData } from '../models/search-data';
import { SortOrder } from '@ucap/core';
export class ProfileListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
constructor() {
super(70, 250, 500); // (itemSize, minBufferPx, maxBufferPx)
}
}
interface CheckedInfo {
checked: boolean;
userInfo: UserInfoSS;
}
@Component({
selector: 'app-organization-profile-list',
templateUrl: './profile-list.component.html',
styleUrls: ['./profile-list.component.scss'],
providers: [
{
provide: VIRTUAL_SCROLL_STRATEGY,
useClass: ProfileListVirtualScrollStrategy
}
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProfileListComponent implements OnInit, OnDestroy {
@Input()
set searchData(data: {
companyCode: string;
searchWord: string;
isSearch: boolean;
}) {
set searchData(data: SearchData) {
if (!this.loginRes) {
this._searchData = data;
return;
}
this.processing = true;
this.queryProtocolService
.deptUser({
divCd: 'ORGS',
companyCode: data.companyCode,
searchRange: DeptSearchType.All,
search: data.searchWord,
senderCompanyCode: this.loginRes.userInfo.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
})
.pipe(take(1))
.subscribe(
(res) => {
// 검색 결과 처리.
this.userInfos = res.userInfos.sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);
this.changeDetectorRef.detectChanges();
// 검색 결과에 따른 프레즌스 조회.
const userSeqList: string[] = [];
this.userInfos.map((user) => userSeqList.push(user.seq));
if (userSeqList.length > 0) {
this.store.dispatch(
PresenceActions.bulkInfo({
divCd: 'orgSrch',
userSeqs: userSeqList
})
);
}
},
(error) => {},
() => {
this.processing = false;
}
);
this.searchMember(data);
}
// tslint:disable-next-line: variable-name
private _searchData: SearchData;
@Input()
selectedUser: UserInfoSS[];
@Input()
set sortOrder(value: SortOrder) {
this._sortOrder = value;
this.userInfos = this.sort(this.userInfos);
}
get sortOrder() {
return this._sortOrder;
}
// tslint:disable-next-line: variable-name
_sortOrder: SortOrder = {
property: 'name',
ascending: true
};
@Output()
searched: EventEmitter<UserInfoSS[]> = new EventEmitter();
@Output()
changedCheck: EventEmitter<CheckedInfo[]> = new EventEmitter();
set userInfos(userInfos: UserInfoSS[]) {
this._userInfos = userInfos;
this.searched.emit(userInfos);
}
get userInfos() {
return this._userInfos;
}
// tslint:disable-next-line: variable-name
_userInfos: UserInfoSS[] = [];
loginRes: LoginResponse;
versionInfo2Res: VersionInfo2Response;
userInfos: UserInfoSS[] = [];
selectedUserInfos: UserInfoSS[] = [];
processing = false;
private ngOnDestroySubject: Subject<void>;
private myDeptDestroySubject: Subject<void>;
constructor(
private queryProtocolService: QueryProtocolService,
@ -116,6 +140,10 @@ export class ProfileListComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
.subscribe((loginRes) => {
this.loginRes = loginRes;
if (!!this._searchData) {
this.searchMember(this._searchData);
this._searchData = undefined;
}
});
}
@ -125,36 +153,156 @@ export class ProfileListComponent implements OnInit, OnDestroy {
}
}
checkAll() {
if (!this.userInfos || 0 === this.userInfos.length) {
return;
}
const checked: CheckedInfo[] = [];
this.userInfos.forEach((u) => {
checked.push({
checked: true,
userInfo: u
});
});
this.changedCheck.emit(checked);
}
/** 개별 체크여부 */
getCheckedUser(userInfo: UserInfoSS) {
if (!!this.selectedUserInfos && this.selectedUserInfos.length > 0) {
if (!!this.selectedUser && this.selectedUser.length > 0) {
return (
this.selectedUserInfos.filter((item) => item.seq === userInfo.seq)
.length > 0
this.selectedUser.filter((item) => item.seq === userInfo.seq).length > 0
);
}
return false;
}
/** 개별선택(토글) 이벤트 */
onToggleUser(param: { isChecked: boolean; userInfo: UserInfoSS }) {
if (!this.loginRes || param.userInfo.seq === this.loginRes.userSeq) {
return;
}
if (
!this.selectedUserInfos.some((user) => user.seq === param.userInfo.seq)
) {
this.selectedUserInfos = [...this.selectedUserInfos, param.userInfo];
} else {
this.selectedUserInfos = this.selectedUserInfos.filter(
(item) => item.seq !== param.userInfo.seq
);
}
this.changeDetectorRef.detectChanges();
onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoSS }) {
this.changedCheck.emit([param]);
}
onOpenProfile(userInfo: UserInfoSS): void {
alert('Open Profile');
}
private getMyDeptMember() {
this.myDeptDestroySubject = new Subject();
const req: DeptUserRequest = {
divCd: 'ORG',
companyCode: this.loginRes.companyCode,
seq: this.loginRes.departmentCode,
search: '',
searchRange: DeptSearchType.All,
senderCompanyCode: this.loginRes.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
};
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
select(DepartmentSelector.myDepartmentUserInfoList)
)
.subscribe(
(myDepartmentUserInfoList) => {
if (!myDepartmentUserInfoList) {
this.store.dispatch(DepartmentActions.myDeptUser({ req }));
return;
}
this.userInfos = this.sort(myDepartmentUserInfoList);
this.myDeptDestroySubject.next();
this.myDeptDestroySubject.complete();
this.myDeptDestroySubject = undefined;
},
(error) => {},
() => {}
);
}
private sort(userInfos: UserInfoSS[]): UserInfoSS[] {
if (!userInfos || 0 === userInfos.length) {
return userInfos;
}
const property = this.sortOrder.property;
const ascending = this.sortOrder.ascending;
return userInfos.slice().sort((a, b) => {
let c: any;
let d: any;
try {
c = ascending ? a[property] : b[property];
d = ascending ? b[property] : a[property];
} catch (error) {
console.log(error);
}
return c < d ? -1 : c > d ? 1 : 0;
});
}
private searchMember(searchData: SearchData) {
if (!searchData || (!searchData.companyCode && !searchData.deptSeq)) {
this.getMyDeptMember();
return;
}
if (!!this.myDeptDestroySubject) {
this.myDeptDestroySubject.next();
this.myDeptDestroySubject.complete();
this.myDeptDestroySubject = undefined;
}
let req: DeptUserRequest;
if (searchData.bySearch) {
req = {
divCd: 'ORGS',
companyCode: searchData.companyCode,
searchRange: DeptSearchType.All,
search: searchData.searchWord,
senderCompanyCode: this.loginRes.userInfo.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
};
} else {
req = {
divCd: 'ORG',
companyCode: this.loginRes.companyCode,
seq: Number(searchData.deptSeq),
search: '',
searchRange: DeptSearchType.All,
senderCompanyCode: this.loginRes.companyCode,
senderEmployeeType: this.loginRes.userInfo.employeeType
};
}
this.processing = true;
this.queryProtocolService
.deptUser(req)
.pipe(take(1))
.subscribe(
(data) => {
this.userInfos = this.sort(data.userInfos);
this.changeDetectorRef.detectChanges();
// 검색 결과에 따른 프레즌스 조회.
const userSeqList: string[] = [];
this.userInfos.map((user) => userSeqList.push(user.seq));
if (userSeqList.length > 0) {
this.store.dispatch(
PresenceActions.bulkInfo({
divCd: 'orgSrch',
userSeqs: userSeqList
})
);
}
},
(error) => {},
() => {
this.processing = false;
}
);
}
}

View File

@ -1,7 +1,8 @@
<div class="search-container">
<ucap-organization-search-for-tenant
[companyList]="companyList"
[defaultCompany]="defaultCompany"
[defaultCompany]="searchData.companyCode"
[defaultSearchWord]="searchData.searchWord"
placeholder="이름 부서명, 전화번호, 이메일"
(changed)="onChanged($event)"
(canceled)="onCanceled()"

View File

@ -8,7 +8,8 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Output,
EventEmitter
EventEmitter,
Input
} from '@angular/core';
import { Store, select } from '@ngrx/store';
@ -19,6 +20,8 @@ import { LogService } from '@ucap/ng-logger';
import { CompanySelector } from '@ucap/ng-store-organization';
import { AppAuthenticationService } from '@app/services/app-authentication.service';
import { SearchData } from '../models/search-data';
import { UserStore } from '@app/models/user-store';
@Component({
selector: 'app-organization-search-for-tenant',
@ -27,34 +30,45 @@ import { AppAuthenticationService } from '@app/services/app-authentication.servi
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchForTenantComponent implements OnInit, OnDestroy {
@Output()
changed: EventEmitter<{
isShowSearch: boolean;
companyCode: string;
searchWord: string;
}> = new EventEmitter();
@Output() searchDataChange: EventEmitter<SearchData> = new EventEmitter();
@Input() set searchData(value: SearchData) {
this._searchData = value;
if (!this._searchData) {
this._searchData = {
companyCode: this.userStore.companyCode
};
} else {
if (!this._searchData.companyCode) {
this._searchData.companyCode = this.userStore.companyCode;
}
}
}
get searchData() {
return this._searchData;
}
// tslint:disable-next-line: variable-name
_searchData: SearchData;
@Output()
canceled: EventEmitter<void> = new EventEmitter();
companyList: Company[];
defaultCompany: string;
private ngOnDestroySubject = new Subject<boolean>();
private userStore: UserStore;
constructor(
private appAuthenticationService: AppAuthenticationService,
private store: Store<any>,
private changeDetectorRef: ChangeDetectorRef,
private logService: LogService
) {}
) {
this.userStore = this.appAuthenticationService.getUserStore();
}
ngOnInit(): void {
this.ngOnDestroySubject = new Subject<boolean>();
const userStore = this.appAuthenticationService.getUserStore();
this.defaultCompany = userStore.companyCode;
this.store
.pipe(
takeUntil(this.ngOnDestroySubject),
@ -72,15 +86,14 @@ export class SearchForTenantComponent implements OnInit, OnDestroy {
}
onChanged(data: { companyCode: string; searchWord: string }): void {
this.changed.emit({
...data,
isShowSearch: true
this.searchDataChange.emit({
companyCode: data.companyCode,
searchWord: data.searchWord
});
}
onCanceled(): void {
this.changed.emit({
isShowSearch: false,
this.searchDataChange.emit({
companyCode: '',
searchWord: ''
});

View File

@ -53,7 +53,7 @@ export class TreeComponent implements OnInit, OnDestroy {
@Input()
set initialExpanded(seq: number) {
if (!!this.treeData) {
this.treeList.expand(seq);
this.expand(seq);
return;
}
this._initialExpanded = seq;

View File

@ -0,0 +1,6 @@
export interface SearchData {
deptSeq?: string;
companyCode?: string;
searchWord?: string;
bySearch?: boolean;
}

View File

@ -6,13 +6,10 @@
"openRoom": "Open room",
"turnOnRoomAlert": "Turn on room alert",
"turnOffRoomAlert": "Turn off room alert",
"exitFromRoom": "Exit room",
"dialog": {
"titleExitFromRoom": "Exit room",
"confirmExitFromRoom": "Do you want to exit the chat room?<br/>Exiting will delete your chat history and chat room information."
}
"exitFromRoom": "Exit room"
},
"label": {
"chat": "Chat",
"menu": "Menu",
"search": "Search",
"notificationIsOn": "Notification is on",
@ -26,5 +23,51 @@
"emailSend": "Send email for chat.",
"translation": "Trnaslation",
"gams": "+GAMS"
},
"event": {
"inviteToRoomWith": "{{owner}} invited {{inviter}}.",
"exitFromRoomWith": "{{exitor}} has left.",
"ejectedFromRoomWith": "{{requester}} has eject {{ejected}}.",
"renamedRoomWith": "{{requester}} has changed their chat room name to '{{roomName}}'.",
"setTimerWith": "{{requester}} set a timer ({{timer}})",
"iosCapture": "{{requester}} captured a conversation",
"showMassTranslationOfOriginal": "Show original",
"showMassTranslationOfTranslated": "Show translated",
"showMassDetail": "Show detail",
"readToHere": "Read to here",
"recalled": "Recalled",
"scheduleTypeNew": "[Event] Registered",
"scheduleTypeUpdate": "[Event] Modified",
"scheduleTypeDelete": "[Event] Deleted",
"scheduleTypeDefault": "[Event] Processing..",
"scheduleTypePrefix": "[Event] ",
"scheduleTypeSurfixLeft": " left",
"showPreviousEvents": "Show previous",
"moreUnreadEventsWith": "There is unread messages<span class=\"text-warn-color\">({{countOfUnread}})</span>"
},
"errors": {
"label": "Chat erros",
"inputChatMessage": "Please enter a chat message",
"maxLengthOfMassText": "If you include a sticker, you can't send more than {{maxLength}} characters.",
"maxCountOfRoomMemberWith": "you can't open room with more than {{maxCount}} peoples.",
"emptyOpenRoomType": "Pleas select type of room",
"translateServerError": "Failed to translate"
},
"dialog": {
"title": {
"exitFromRoom": "Exit room",
"newChatRoom": "Add new chat room"
},
"confirmExitFromRoom": "Do you want to exit the chat room?<br/>Exiting will delete your chat history and chat room information.",
"normalRoom": "Basic Room",
"timerRoom": "Timer Room",
"normalRoomDescription": "<strong>Up to {{maxCount}} peoples</strong><br/>can join.",
"timerRoomDescription": "<strong>When setting the timer,</strong><br/>the conversation is automatically deleted.",
"button": {
"cancel": "Cancel",
"previous": "Previous",
"selectRoomUser": "Choice user",
"openRoom": "Open chat room"
}
}
}

View File

@ -2,7 +2,8 @@
"common": {
"messages": {
"no": "No",
"yes": "Yes"
"yes": "Yes",
"confirm": "Confirm"
},
"units": {
"date": "Date",
@ -17,6 +18,14 @@
"tomorrowAfternoon": "Tomorrow afternoon",
"weekLaterWith": "(A) {{week}} week(s) later",
"monthLaterWith": "(A) {{month}} month(s) later"
},
"file": {
"errors": {
"failToUpload": "File upload failed.",
"notSupporedType": "File format is not supported. <br/> ({{supporedType}})",
"notAcceptableMime": "File type is invalid. <br/> ({{supporedType}})",
"oversize": "You cannot upload files larger than {{size}} megabytes."
}
}
}
}

View File

@ -6,13 +6,10 @@
"openRoom": "대화방 열기",
"turnOnRoomAlert": "대화방 알람 켜기",
"turnOffRoomAlert": "대화방 알람 끄기",
"exitFromRoom": "대화방 나가기",
"dialog": {
"titleExitFromRoom": "대화방 나가기",
"confirmExitFromRoom": "대화방을 나가시겠습니까?<br/>나가기를 하면 대화내용 및 대화방 정보가 삭제됩니다."
}
"exitFromRoom": "대화방 나가기"
},
"label": {
"chat": "대화",
"menu": "메뉴",
"search": "검색",
"notificationIsOn": "알림 켜짐",
@ -26,5 +23,51 @@
"emailSend": "대화내용 메일전송",
"translation": "대화내용 번역",
"gams": "+GAMS"
},
"event": {
"inviteToRoomWith": "{{owner}}님이 {{inviter}}님을 초대했습니다.",
"exitFromRoomWith": "{{exitor}}님이 퇴장하셨습니다.",
"ejectedFromRoomWith": "{{requester}}님이 {{ejected}}님을 퇴장 시키셨습니다.",
"renamedRoomWith": "{{requester}}님이 대화방명을 '{{roomName}}'으로 변경하셨습니다.",
"setTimerWith": "{{requester}}님이 타이머를 설정하였습니다. ({{timer}})",
"iosCapture": "{{requester}}님이 대화내용을 캡쳐하였습니다.",
"showMassTranslationOfOriginal": "원본 보기",
"showMassTranslationOfTranslated": "번역 보기",
"showMassDetail": "전체 보기",
"readToHere": "여기까지 읽었습니다.",
"recalled": "회수된 메시지",
"scheduleTypeNew": "[이벤트] 등록",
"scheduleTypeUpdate": "[이벤트] 수정",
"scheduleTypeDelete": "[이벤트] 삭제",
"scheduleTypeDefault": "[이벤트] 조회중..",
"scheduleTypePrefix": "[이벤트] ",
"scheduleTypeSurfixLeft": " 전 알림",
"showPreviousEvents": "이전 대화 보기",
"moreUnreadEventsWith": "안읽은 메시지가 <span class=\"text-warn-color\">({{countOfUnread}})</span>개 더 있습니다."
},
"errors": {
"label": "대화 에러",
"inputChatMessage": "대화 내용을 입력해 주세요.",
"maxLengthOfMassText": "스티커를 포함할 경우 {{maxLength}}자 이상 보낼 수 없습니다.",
"maxCountOfRoomMemberWith": "{{maxCount}}명 이상 대화할 수 없습니다.",
"emptyOpenRoomType": "대화방 타입을 선택해 주세요.",
"translateServerError": "번역하지 못했습니다."
},
"dialog": {
"title": {
"exitFromRoom": "대화방 나가기",
"newChatRoom": "새로운 대화방 추가"
},
"confirmExitFromRoom": "대화방을 나가시겠습니까?<br/>나가기를 하면 대화내용 및 대화방 정보가 삭제됩니다.",
"normalRoom": "일반 대화방",
"timerRoom": "타이머 대화방",
"normalRoomDescription": "<strong>{{maxCount}}명 까지</strong><br/>참여가 가능합니다.",
"timerRoomDescription": "<strong>타이머 설정시</strong><br/>대화내용이 자동으로 삭제됩니다.",
"button": {
"cancel": "취소",
"previous": "이전",
"selectRoomUser": "대화방 멤버 선택",
"openRoom": "대화방 생성"
}
}
}

View File

@ -2,7 +2,8 @@
"common": {
"messages": {
"no": "아니요",
"yes": "예"
"yes": "예",
"confirm": "확인"
},
"units": {
"date": "날짜",
@ -17,6 +18,14 @@
"tomorrowAfternoon": "내일 오후",
"weekLaterWith": "{{week}}주일 뒤",
"monthLaterWith": "{{month}}달 뒤"
},
"file": {
"errors": {
"failToUpload": "파일 업로드에 실패하였습니다.",
"notSupporedType": "지원하지 않는 파일형식입니다. <br/> ({{supporedType}})",
"notAcceptableMime": "유효하지 않은 파일 타입입니다. <br/> ({{supporedType}})",
"oversize": "{{maxSize}}MB 이상 파일을 업로드 할 수 없습니다."
}
}
}
}

View File

@ -1,9 +1,10 @@
// Material theming tools
@import '~@angular/material/theming';
@import 'mixins';
// Include core Angular Material styles
@include mat-core();
//creative
@import 'global/default';
@import 'global/ucap-ui';
@import 'global/material-ui';

View File

@ -1,10 +1,10 @@
// Material theming tools
@import '~@angular/material/theming';
@import '~@ucap/ng-ui-material/material';
@import 'setting/variables';
@import '~@ucap/ng-ui-material/material';
@import 'mixins/font';
@import 'mixins/dom';

View File

@ -139,7 +139,7 @@
.mat-tab-label[aria-selected='true'] {
.mat-tab-label-content {
.icon-item {
background: mat-color($accent, 300);
background: mat-color($accent, 700);
}
}
}
@ -212,6 +212,7 @@
.mat-form-field-infix {
align-self: center;
width: inherit;
padding: 0;
}
}
}
@ -499,10 +500,58 @@
.color-white {
color: $white;
}
//mat chip
.mat-chip {
&.mat-standard-chip {
background-color: mat-color($accent, A400);
color: mat-color($primary, default-contrast);
.mat-chip-remove {
//color: rgba(0, 0, 0, 0.87);
color: mat-color($primary, default-contrast);
}
}
}
.mat-chip.mat-standard-chip.mat-chip-selected.mat-primary {
background-color: mat-color($accent, 700);
cursor: pointer;
}
//mat checkbox
.mat-checkbox-inner-container {
width: 18px !important;
height: 18px !important;
background-color: #fff;
}
.mat-checkbox-checked {
&.mat-accent {
.mat-checkbox-background {
border: 2px solid mat-color($accent, 700);
background-color: #fff;
border-radius: 2px;
.mat-checkbox-checkmark-path {
stroke: mat-color($accent, 700) !important;
}
}
}
}
//mat dadge
.mat-badge-accent .mat-badge-content,
.weblink .mat-badge-content {
background-color: mat-color($warn, 400);
background-color: mat-color($warn, 500);
border: 1px solid mat-color($warn, 500);
}
.noti-sum {
.mat-badge-content {
right: 16px !important;
bottom: 12px !important;
min-width: 22px;
width: auto;
line-height: 20px;
align-items: center;
padding: 0 5px;
border-radius: 11px;
}
}
//mat-card
.allim-card {
@ -616,6 +665,43 @@
}
}
//mat slide toggle
.mat-slide-toggle {
.mat-slide-toggle-bar {
width: 32px;
height: 18px;
border-radius: 9px;
border: 1px solid #707070;
background-color: #fff;
.mat-slide-toggle-thumb-container {
width: 12px;
height: 12px;
top: 2px;
left: 2px;
.mat-slide-toggle-thumb {
height: 12px;
width: 12px;
box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);
background-color: #999;
}
}
}
&.mat-checked {
.mat-slide-toggle-bar {
background-color: #fff;
border: 1px solid #fd578a;
.mat-slide-toggle-thumb-container {
transform: translate3d(14px, 0, 0);
.mat-slide-toggle-thumb {
background-color: #e42f66;
}
}
}
}
}
// Btn Float
.btn-main-float {
right: 22px !important;

View File

@ -0,0 +1,6 @@
@import 'ucap/organization';
@import 'ucap/authentication';
@import 'ucap/group';
@import 'ucap/call';
@import 'ucap/message';
@import 'ucap/call';

View File

@ -0,0 +1,205 @@
.ucap-authentication-login-container {
background-color: transparent !important;
@include screen(mid) {
width: 350px !important;
}
@include screen(xs) {
width: 300px !important;
}
.ucap-authentication-login-input-field {
border: 1px solid #cccccc;
border-radius: 2px;
width: 100%;
max-width: 420px;
min-width: 150px;
height: 60px;
padding: 0 !important;
background-color: $white !important;
margin-top: 10px !important;
display: flex !important;
justify-content: center;
align-items: center;
overflow: hidden;
.ucap-authentication-login-input-icon {
align-self: center;
flex-basis: 45px;
margin-right: 16px !important;
margin-left: 16px;
height: 24px !important;
width: 24px !important;
@include screen(mid) {
height: 20px !important;
width: 20px !important;
margin-right: 10px !important;
}
@include screen(xs) {
height: 20px !important;
width: 20px !important;
margin-right: 10px !important;
}
}
.mat-form-field {
.mat-form-field-wrapper {
.mat-form-field-underline {
height: 0 !important;
}
}
&.mat-form-field-appearance-legacy {
.mat-form-field-ripple {
height: 0 !important;
}
&.mat-form-field-can-float {
.mat-form-field-autofill-control {
&:-webkit-autofill + .mat-form-field-label-wrapper {
.mat-form-field-label {
width: 0 !important;
}
}
}
}
}
}
&:nth-child(1) {
margin-top: 30px !important;
width: 420px;
.mat-form-field {
@include ucapMatSelect(60px, 0 16px);
}
@include screen(mid) {
margin-top: 23px !important;
width: 350px;
.mat-form-field {
@include ucapMatSelect(50px, 0 16px);
}
}
@include screen(xs) {
margin-top: 23px !important;
width: 300px;
.mat-form-field {
@include ucapMatSelect(42px, 0 16px);
}
}
}
&:nth-child(2) {
.mat-form-field {
@include ucapMatFormField(0, 0, auto, 360px, 360px, 60px, 60px, white);
}
@include screen(mid) {
.mat-form-field {
@include ucapMatFormField(
0,
0,
auto,
300px,
300px,
50px,
50px,
white
);
}
}
@include screen(xs) {
.mat-form-field {
@include ucapMatFormField(
0,
0,
auto,
250px,
250px,
42px,
42px,
white
);
}
}
}
&:nth-child(3) {
.mat-form-field {
@include ucapMatFormField(0, 0, 360px, 360px, 360px, 60px, 60px, white);
}
@include screen(mid) {
.mat-form-field {
@include ucapMatFormField(
0,
0,
auto,
300px,
300px,
50px,
50px,
white
);
}
}
@include screen(xs) {
.mat-form-field {
@include ucapMatFormField(
0,
0,
auto,
250px,
250px,
42px,
42px,
white
);
}
}
}
@include screen(mid) {
width: 350px;
height: 50px;
}
@include screen(xs) {
width: 300px;
height: 42px;
}
}
.ucap-authentication-login-error-container {
position: absolute;
width: 300px;
height: 80px;
//background-color: gold;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
bottom: 135px;
left: calc(50% - 150px);
font-size: 0.929em !important;
font-weight: 600;
color: $lipstick !important;
.mat-error {
font-size: 0.929em !important;
line-height: 1.7 !important;
color: $lipstick !important;
@include screen(xs) {
font-size: 0.857em !important;
line-height: 1.5 !important;
}
}
}
.ucap-authentication-login-button-submit {
//transform: all 0.3s;
width: 100%;
height: 60px;
background-color: $black;
border-radius: 2px !important;
color: $white;
font-size: 1.429em !important;
@include font-family($font-semibold);
border: 0;
margin: 12px 0 0 !important;
font-weight: 600 !important;
cursor: pointer;
@include screen(mid) {
margin-top: 8px !important;
font-size: 1.143em !important;
height: 50px;
}
@include screen(xs) {
font-size: 1em !important;
height: 42px;
}
}
}

View File

View File

View File

View File

@ -0,0 +1,7 @@
.ucap-organization-profile-image-01-image-container {
border-radius: 50%;
overflow: hidden;
width: 36px;
height: 36px;
margin-left: 2px;
}

View File

@ -1,15 +1,8 @@
:root {
--ucap-screen-max-xs: 575;
--ucap-screen-min-sm: 576;
--ucap-screen-max-sm: 767;
--ucap-screen-min-md: 768;
--ucap-screen-max-md: 991;
--ucap-screen-min-lg: 992;
--ucap-screen-max-lg: 1199;
--ucap-screen-min-xl: 1200;
--ucap-organization-search-for-tenant-border-width: 1px;
--ucap-organization-search-for-tenant-border-color: #e42f66;
--ucap-organization-profile-list-item-01-size: 70px;
}
// Fonts Family
@ -96,26 +89,22 @@ $bg-linear-gradient: linear-gradient(57deg, #ffe1ba 13%, #f5878c 87%);
height: 100vh;
}
$lg-red: (
50: #ffffff,
100: #fff9fc,
200: #f1e1e5,
300: #ef4c73,
400: #ffbf2a,
//#ec407a
500: #ed097e,
600: #d81b60,
700: #c2185b,
800: #ad1457,
900: #5f2a41,
A100: #ff80ab,
A200: #ff4081,
A400: #ff3399,
A700: #c51162,
B100: #4f4f4f,
B200: #67545b,
G100: #ef4c73,
G900: #352a37,
// Theming color set/////////////////////////////
$ucap-color-primary: (
50: #f0f0f0,
100: #e2e2e2,
200: #d4d4d4,
300: #cccccc,
400: #aaa4a6,
500: #81757a,
600: #584f52,
700: #474244,
800: #3b3637,
900: #2d2a2c,
A100: #f7f8fa,
A200: #f1f2f6,
A400: #999999,
A700: #333333,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
@ -128,11 +117,84 @@ $lg-red: (
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $light-primary-text,
A200: $dark-primary-text,
A400: $light-primary-text,
A700: $light-primary-text,
B100: $light-primary-text,
G100: $dark-primary-text,
G900: $light-primary-text
A700: $light-primary-text
)
);
$ucap-color-accent: (
50: #fef1f4,
100: #fce7ed,
200: #ffd9e5,
300: #ffb5cc,
400: #fd78a1,
500: #fd578a,
600: #f83772,
700: #e42f66,
800: #dc225b,
900: #c21e50,
A100: #fff2f3,
A200: #ffc5ca,
A400: #f67f8a,
A700: #ec5361,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $light-primary-text,
400: $light-primary-text,
500: $light-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $dark-primary-text,
A400: $light-primary-text,
A700: $light-primary-text
)
);
$ucap-color-warn: (
50: #fefaf6,
100: #fef6ec,
200: #ffe8cb,
300: #ffda9a,
400: #fbca69,
500: #fcb954,
600: #faa640,
700: #f69532,
800: #e68118,
900: #d8750f,
A100: #fff3ef,
A200: #ffeadf,
A400: #ffbf9e,
A700: #ff6c20,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
400: $light-primary-text,
500: $light-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $dark-primary-text,
A400: $light-primary-text,
A700: $light-primary-text
)
);
/////////////////////////////Theming color set //
$ucap-screen-max-xs: 575;
$ucap-screen-min-sm: 576;
$ucap-screen-max-sm: 767;
$ucap-screen-min-md: 768;
$ucap-screen-max-md: 991;
$ucap-screen-min-lg: 992;
$ucap-screen-max-lg: 1199;
$ucap-screen-min-xl: 1200;

View File

@ -103,7 +103,7 @@ export const environment: Environment = {
commonApiModuleConfig: {
hostConfig: {
protocol: 'http',
domain: 'lftalk2.lfcorp.com',
domain: '13.124.88.127',
port: 8033
},
urls: commonApiUrls,
@ -115,8 +115,8 @@ export const environment: Environment = {
publicApiModuleConfig: {
hostConfig: {
protocol: 'http',
domain: 'lftalk2.lfcorp.com',
port: 8033
domain: '13.124.88.127',
port: 8011
},
urls: publicApiUrls
},
@ -124,7 +124,7 @@ export const environment: Environment = {
externalApiModuleConfig: {
hostConfig: {
protocol: 'http',
domain: 'lftalk2.lfcorp.com',
domain: '13.124.88.127',
port: 8011
},
urls: externalApiUrls
@ -133,7 +133,7 @@ export const environment: Environment = {
messageApiModuleConfig: {
hostConfig: {
protocol: 'http',
domain: 'lftalk2.lfcorp.com',
domain: '13.124.88.127',
port: 9097
},
urls: messageApiUrls
@ -142,7 +142,7 @@ export const environment: Environment = {
promptApiModuleConfig: {
hostConfig: {
protocol: 'http',
domain: 'lftalk2.lfcorp.com',
domain: '13.124.88.127',
port: 9097
},
urls: promptUrls
@ -151,7 +151,7 @@ export const environment: Environment = {
piModuleConfig: {
hostConfig: {
protocol: 'http',
domain: 'lftalk2.lfcorp.com',
domain: '13.124.88.127',
port: 9097
},
urls: piUrls
@ -166,7 +166,7 @@ export const environment: Environment = {
protocolModuleConfig: {
hostConfig: {
protocol: 'ws',
domain: 'lftalk2.lfcorp.com',
domain: '13.124.88.127',
port: 8080
},
urls: protocolUrls,
@ -181,6 +181,6 @@ export const environment: Environment = {
},
pingProtocolModuleConfig: {
statusCode: 'O',
interval: 5
interval: 20
}
};

View File

@ -107,7 +107,7 @@ export const environment: Environment = {
hostConfig: {
protocol: 'http',
domain: '13.124.88.127',
port: 8033
port: 8011
},
urls: commonApiUrls,
acceptableFileExtensions: commonApiacceptableFileExtensions,

Some files were not shown because too many files have changed in this diff Show More