ing
This commit is contained in:
parent
0fc503db87
commit
550fff3849
4346
package-lock.json
generated
4346
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
|
@ -69,15 +69,15 @@
|
|||
"@ucap/ng-protocol-sync": "~0.0.3",
|
||||
"@ucap/ng-protocol-umg": "~0.0.3",
|
||||
"@ucap/ng-store-authentication": "~0.0.10",
|
||||
"@ucap/ng-store-chat": "~0.0.5",
|
||||
"@ucap/ng-store-group": "~0.0.6",
|
||||
"@ucap/ng-store-chat": "~0.0.6",
|
||||
"@ucap/ng-store-group": "~0.0.7",
|
||||
"@ucap/ng-store-organization": "~0.0.4",
|
||||
"@ucap/ng-web-socket": "~0.0.2",
|
||||
"@ucap/ng-web-storage": "~0.0.3",
|
||||
"@ucap/ng-ui": "~0.0.4",
|
||||
"@ucap/ng-ui-organization": "~0.0.2",
|
||||
"@ucap/ng-ui-authentication": "~0.0.16",
|
||||
"@ucap/ng-ui-group": "~0.0.3",
|
||||
"@ucap/ng-ui": "~0.0.7",
|
||||
"@ucap/ng-ui-organization": "~0.0.15",
|
||||
"@ucap/ng-ui-authentication": "~0.0.19",
|
||||
"@ucap/ng-ui-group": "~0.0.28",
|
||||
"@ucap/ng-ui-skin-default": "~0.0.1",
|
||||
"@ucap/pi": "~0.0.5",
|
||||
"@ucap/protocol": "~0.0.17",
|
||||
|
@ -97,7 +97,7 @@
|
|||
"@ucap/protocol-sync": "~0.0.4",
|
||||
"@ucap/protocol-umg": "~0.0.5",
|
||||
"@ucap/web-socket": "~0.0.10",
|
||||
"@ucap/web-storage": "~0.0.5",
|
||||
"@ucap/web-storage": "~0.0.9",
|
||||
"autolinker": "^3.13.0",
|
||||
"axios": "^0.19.2",
|
||||
"classlist.js": "^1.1.20150312",
|
||||
|
|
|
@ -14,10 +14,16 @@ import { AppSessionResolver } from './resolvers/app-session.resolver';
|
|||
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';
|
||||
|
||||
const GUARDS = [AppAuthenticationGuard];
|
||||
const RESOLVERS = [AppSessionResolver];
|
||||
const SERVICES = [AppService, AppAuthenticationService, AppNativeService];
|
||||
const SERVICES = [
|
||||
AppService,
|
||||
AppAuthenticationService,
|
||||
AppNativeService,
|
||||
AppChatService
|
||||
];
|
||||
|
||||
const axiosFactory = () => {
|
||||
const i = axios.create();
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
<app-layouts-top-bar></app-layouts-top-bar>
|
||||
<router-outlet></router-outlet>
|
||||
<div class="app-container">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
:host {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
height: 100%;
|
||||
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import { debounce } from 'rxjs/operators';
|
|||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
showTopbar = true;
|
||||
showFooter = false;
|
||||
|
||||
private resizeWindowSubscription: Subscription;
|
||||
|
||||
constructor(private store: Store<any>) {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
@ -65,6 +67,8 @@ import { environment } from '@environments';
|
|||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
|
||||
FlexLayoutModule,
|
||||
|
||||
LoggerModule.forRoot({}),
|
||||
|
||||
CommonApiModule.forRoot(environment.commonApiModuleConfig),
|
||||
|
|
|
@ -16,164 +16,32 @@ $typography: mat-typography-config(
|
|||
// Setup the typography
|
||||
@include angular-material-typography($typography);
|
||||
|
||||
@mixin components-theme($theme) {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Define the default theme
|
||||
// @ Define theme --LG RED
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// 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);
|
||||
|
||||
// Define the primary, accent and warn palettes
|
||||
$default-primary-palette: mat-palette($mat-indigo);
|
||||
$default-accent-palette: mat-palette($mat-light-blue, 600, 400, 700);
|
||||
$default-warn-palette: mat-palette($mat-red);
|
||||
// The warn palette is optional (defaults to red).
|
||||
$lgRed-app-warn: mat-palette($lg-red);
|
||||
|
||||
// Create the Material theme object
|
||||
$theme: mat-light-theme(
|
||||
$default-primary-palette,
|
||||
$default-accent-palette,
|
||||
$default-warn-palette
|
||||
// Create the theme object (a Sass map containing all of the palettes).
|
||||
$lgRed-app-theme: mat-light-theme(
|
||||
$lgRed-app-primary,
|
||||
$lgRed-app-accent,
|
||||
$lgRed-app-warn
|
||||
);
|
||||
|
||||
// Add ".theme-default" class to the body to activate this theme.
|
||||
// Class name must start with "theme-" !!!
|
||||
/*body.theme-default {
|
||||
// Create an Angular Material theme from the $theme map
|
||||
@include angular-material-theme($theme);
|
||||
// Include theme styles for core and each component used in your app.
|
||||
// Alternatively, you can import and @include the theme mixins for each component
|
||||
// that you are using.
|
||||
@include angular-material-theme($lgRed-app-theme);
|
||||
|
||||
// Apply the theme to the user components
|
||||
@include components-theme($theme);
|
||||
@include ucap-material-theme($theme);
|
||||
}*/
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Define a blue-gray dark theme
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Define the primary, accent and warn palettes
|
||||
$blue-gray-dark-theme-primary-palette: mat-palette($mat-blue);
|
||||
$blue-gray-dark-theme-accent-palette: mat-palette($mat-blue-gray);
|
||||
$blue-gray-dark-theme-warn-palette: mat-palette($mat-red);
|
||||
|
||||
// Create the Material theme object
|
||||
$blue-gray-dark-theme: mat-dark-theme(
|
||||
$blue-gray-dark-theme-primary-palette,
|
||||
$blue-gray-dark-theme-accent-palette,
|
||||
$blue-gray-dark-theme-warn-palette
|
||||
);
|
||||
|
||||
// Add ".theme-blue-gray-dark" class to the body to activate this theme.
|
||||
// Class name must start with "theme-" !!!
|
||||
body.theme-blue-gray-dark {
|
||||
// Generate the Angular Material theme
|
||||
@include angular-material-theme($blue-gray-dark-theme);
|
||||
|
||||
// Apply the theme to the user components
|
||||
@include components-theme($blue-gray-dark-theme);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Define a pink dark theme
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Define the primary, accent and warn palettes
|
||||
$pink-dark-theme-primary-palette: mat-palette($mat-pink);
|
||||
$pink-dark-theme-accent-palette: mat-palette($mat-pink);
|
||||
$pink-dark-theme-warn-palette: mat-palette($mat-red);
|
||||
|
||||
// Create the Material theme object
|
||||
$pink-dark-theme: mat-dark-theme(
|
||||
$pink-dark-theme-primary-palette,
|
||||
$pink-dark-theme-accent-palette,
|
||||
$pink-dark-theme-warn-palette
|
||||
);
|
||||
|
||||
// Add ".theme-pink-dark" class to the body to activate this theme.
|
||||
// Class name must start with "theme-" !!!
|
||||
body.theme-pink-dark {
|
||||
// Generate the Angular Material theme
|
||||
@include angular-material-theme($pink-dark-theme);
|
||||
|
||||
// Apply the theme to the user components
|
||||
@include components-theme($pink-dark-theme);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Define a pink light theme --LG RED 변경 예정(샘플링)
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Define the primary, accent and warn palettes
|
||||
$lgRed-light-theme-primary-palette: mat-palette($mat-grey, 800);
|
||||
$lgRed-light-theme-accent-palette: mat-palette($lg-red, 400);
|
||||
$lgRed-light-theme-warn-palette: mat-palette($mat-cyan);
|
||||
|
||||
// Create the Material theme object
|
||||
$lgRed-light-theme: mat-light-theme(
|
||||
$lgRed-light-theme-primary-palette,
|
||||
$lgRed-light-theme-accent-palette,
|
||||
$lgRed-light-theme-warn-palette
|
||||
);
|
||||
|
||||
// Add ".theme-pink-dark" class to the body to activate this theme.
|
||||
// Class name must start with "theme-" !!!
|
||||
body.theme-lgRed {
|
||||
// Generate the Angular Material theme
|
||||
@include angular-material-theme($lgRed-light-theme);
|
||||
|
||||
// Apply the theme to the user components
|
||||
@include components-theme($lgRed-light-theme);
|
||||
@include ucap-material-theme($lgRed-light-theme);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
//aqua-blue-daesang
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
$aquaBlue-light-theme-primary-palette: mat-palette($daesang-grey, 900);
|
||||
$aquaBlue-theme-accent-palette: mat-palette($aquaBlue-daesang);
|
||||
$aquaBlue-theme-warn-palette: mat-palette($mat-orange);
|
||||
|
||||
// Create the Material theme object
|
||||
$aquaBlue-light-theme: mat-light-theme(
|
||||
$aquaBlue-light-theme-primary-palette,
|
||||
$aquaBlue-theme-accent-palette,
|
||||
$aquaBlue-theme-warn-palette
|
||||
);
|
||||
|
||||
// Add ".theme-pink-dark" class to the body to activate this theme.
|
||||
// Class name must start with "theme-" !!!
|
||||
body.theme-default {
|
||||
// Generate the Angular Material theme
|
||||
@include angular-material-theme($aquaBlue-light-theme);
|
||||
|
||||
// Apply the theme to the user components
|
||||
@include components-theme($aquaBlue-light-theme);
|
||||
@include ucap-material-theme($aquaBlue-light-theme);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Define a red theme --LF 변경 예정(샘플링)
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Define the primary, accent and warn palettes
|
||||
$lfRed-light-theme-primary-palette: mat-palette($lf-blue-grey, 800);
|
||||
$lfRed-light-theme-accent-palette: mat-palette($lf-red, 400);
|
||||
$lfRed-light-theme-warn-palette: mat-palette($lf-amber);
|
||||
|
||||
// Create the Material theme object
|
||||
$lfRed-light-theme: mat-light-theme(
|
||||
$lfRed-light-theme-primary-palette,
|
||||
$lfRed-light-theme-accent-palette,
|
||||
$lfRed-light-theme-warn-palette
|
||||
);
|
||||
|
||||
// Add ".theme-pink-dark" class to the body to activate this theme.
|
||||
// Class name must start with "theme-" !!!
|
||||
body.theme-lfRed {
|
||||
// Generate the Angular Material theme
|
||||
@include angular-material-theme($lfRed-light-theme);
|
||||
|
||||
// Apply the theme to the user components
|
||||
@include components-theme($lfRed-light-theme);
|
||||
@include ucap-material-theme($lfRed-light-theme);
|
||||
}
|
||||
// Apply the theme to the user components
|
||||
/*
|
||||
@include components-theme($lgRed-app-theme);
|
||||
*/
|
||||
@include ucap-material-theme($lgRed-app-theme);
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { PiService } from '@ucap/ng-pi';
|
||||
|
||||
import { LoginActions } from '@ucap/ng-store-authentication';
|
||||
|
@ -26,7 +27,8 @@ export class AppAuthenticationGuard implements CanActivate {
|
|||
private piService: PiService,
|
||||
private appAuthenticationService: AppAuthenticationService,
|
||||
private store: Store<any>,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
canActivate(
|
||||
|
@ -38,59 +40,61 @@ export class AppAuthenticationGuard implements CanActivate {
|
|||
| Observable<boolean | UrlTree>
|
||||
| Promise<boolean | UrlTree> {
|
||||
return new Promise<boolean | UrlTree>((resolve, reject) => {
|
||||
if (this.appAuthenticationService.loggedIn()) {
|
||||
const loggedIn = this.appAuthenticationService.loggedIn();
|
||||
if (loggedIn) {
|
||||
resolve(true);
|
||||
} else {
|
||||
const userStore = this.appAuthenticationService.useAutoLogin();
|
||||
if (!!userStore) {
|
||||
const loginSession = this.appAuthenticationService.getLoginSession();
|
||||
|
||||
const onWebLoginFailure = (error: any) => {
|
||||
userStore.settings.general.autoLogin = false;
|
||||
this.appAuthenticationService.setUserStore(userStore);
|
||||
|
||||
this.router.navigateByUrl('/account/login');
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
this.piService
|
||||
.login2({
|
||||
companyCode: userStore.companyCode,
|
||||
loginId: userStore.loginId,
|
||||
loginPw: userStore.loginPw,
|
||||
deviceType: loginSession.deviceType
|
||||
})
|
||||
.pipe(take(1))
|
||||
.subscribe(
|
||||
(res) => {
|
||||
if ('success' !== res.status.toLowerCase()) {
|
||||
onWebLoginFailure(res.status);
|
||||
return;
|
||||
} else {
|
||||
this.store.dispatch(
|
||||
LoginActions.webLoginSuccess({
|
||||
companyCode: userStore.companyCode,
|
||||
loginId: userStore.loginId,
|
||||
loginPw: userStore.loginPw,
|
||||
autoLogin: true,
|
||||
rememberMe: userStore.rememberMe,
|
||||
login2Response: res
|
||||
})
|
||||
);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
onWebLoginFailure(error);
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
} else {
|
||||
this.router.navigateByUrl('/account/login');
|
||||
resolve(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const userStore = this.appAuthenticationService.useAutoLogin();
|
||||
if (!userStore) {
|
||||
this.router.navigateByUrl('/account/login');
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const loginSession = this.appAuthenticationService.getLoginSession();
|
||||
|
||||
const onWebLoginFailure = (error: any) => {
|
||||
userStore.settings.general.autoLogin = false;
|
||||
this.appAuthenticationService.setUserStore(userStore);
|
||||
|
||||
this.router.navigateByUrl('/account/login');
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
this.piService
|
||||
.login2({
|
||||
companyCode: userStore.companyCode,
|
||||
loginId: userStore.loginId,
|
||||
loginPw: userStore.loginPw,
|
||||
deviceType: loginSession.deviceType
|
||||
})
|
||||
.pipe(take(1))
|
||||
.subscribe(
|
||||
(res) => {
|
||||
if ('success' !== res.status.toLowerCase()) {
|
||||
onWebLoginFailure(res.status);
|
||||
return;
|
||||
} else {
|
||||
this.store.dispatch(
|
||||
LoginActions.webLoginSuccess({
|
||||
companyCode: userStore.companyCode,
|
||||
loginId: userStore.loginId,
|
||||
loginPw: userStore.loginPw,
|
||||
autoLogin: true,
|
||||
rememberMe: userStore.rememberMe,
|
||||
login2Response: res
|
||||
})
|
||||
);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
onWebLoginFailure(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div fxFlexFill class="layout-container">
|
||||
<div class="navi-container">
|
||||
<div class="layout-container" fxLayout="row">
|
||||
<div class="navitab-page" fxFlex="60px">
|
||||
<mat-tab-group
|
||||
#navTabGroup
|
||||
vertical
|
||||
|
@ -93,15 +93,45 @@
|
|||
</ng-template>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<ucap-float-action-button
|
||||
*ngIf="fabButtonShow"
|
||||
[buttons]="fabButtons"
|
||||
(buttonClick)="onClickFab($event)"
|
||||
>
|
||||
</ucap-float-action-button>
|
||||
</div>
|
||||
<div class="content-container" fxFlexFill>
|
||||
<mat-sidenav-container autosize="true" fxFlexFill>
|
||||
<div class="content-page" fxFlex="1 1 auto">
|
||||
<mat-sidenav-container autosize="true">
|
||||
<mat-sidenav #leftSidenav class="left-sidenav" mode="side" opened="true">
|
||||
<router-outlet></router-outlet>
|
||||
<div class="left-sidenav-container" fxLayout="column">
|
||||
<div class="top-bar" fxFlex="40px">
|
||||
M-Messenger
|
||||
</div>
|
||||
<div fxFlex="1 1 auto">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</mat-sidenav>
|
||||
<div fxFlex="1 1 auto">
|
||||
<router-outlet name="content"></router-outlet>
|
||||
</div>
|
||||
<mat-sidenav
|
||||
#rightSidenav
|
||||
class="right-sidenav"
|
||||
mode="side"
|
||||
opened="true"
|
||||
position="end"
|
||||
>
|
||||
Right
|
||||
</mat-sidenav>
|
||||
|
||||
<mat-sidenav-content>
|
||||
<div class="content-sidenav-container" fxLayout="column">
|
||||
<div class="content-sidenav-top-bar" fxFlex="40px">
|
||||
<app-layouts-top-bar></app-layouts-top-bar>
|
||||
</div>
|
||||
<div class="content-sidenav-body" fxFlex="1 1 auto">
|
||||
<router-outlet name="content"></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</mat-sidenav-content>
|
||||
</mat-sidenav-container>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,47 @@
|
|||
.layout-container {
|
||||
display: flex;
|
||||
:host {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navi-container {
|
||||
width: 70px;
|
||||
.layout-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.navitab-page {
|
||||
}
|
||||
|
||||
.content-container {
|
||||
.left-sidenav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 370px;
|
||||
height: 100%;
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.content-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.content-drawer {
|
||||
flex: 0 0 auto;
|
||||
mat-sidenav-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.left-sidenav {
|
||||
width: 370px;
|
||||
max-width: 90%;
|
||||
|
||||
.left-sidenav-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.right-sidenav {
|
||||
width: 370px;
|
||||
}
|
||||
|
||||
mat-sidenav-content {
|
||||
.content-sidenav-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.content-sidenav-body {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,12 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
|||
@ViewChild('leftSidenav', { static: true })
|
||||
leftSidenav: MatSidenav;
|
||||
|
||||
showFooter = false;
|
||||
|
||||
/** FAB */
|
||||
fabButtonShow = true;
|
||||
fabButtons: { icon: string; tooltip?: string; divisionType?: string }[];
|
||||
|
||||
private windowSizeSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
|
@ -50,6 +56,8 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
|
||||
this.setTabGroup(this.router.url);
|
||||
|
||||
this.setFabInitial(NAVS[0]);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -70,6 +78,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
|||
NAVS[event.index],
|
||||
{ outlets: { content: 'index' } }
|
||||
]);
|
||||
this.setFabInitial(NAVS[event.index]);
|
||||
}
|
||||
|
||||
onClickToggleLeftSidenav() {
|
||||
|
@ -85,4 +94,101 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
|||
url.startsWith(`/${v}`)
|
||||
);
|
||||
}
|
||||
|
||||
setFabInitial(type: string) {
|
||||
switch (type) {
|
||||
case 'group':
|
||||
{
|
||||
this.fabButtonShow = true;
|
||||
this.fabButtons = [
|
||||
{
|
||||
icon: 'add',
|
||||
tooltip: '그룹 추가',
|
||||
divisionType: 'GROUP_NEW_ADD'
|
||||
}
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'chat':
|
||||
{
|
||||
this.fabButtonShow = true;
|
||||
this.fabButtons = [
|
||||
{
|
||||
icon: 'chat',
|
||||
tooltip: '대화 추가',
|
||||
divisionType: 'CAHT_NEW_ADD'
|
||||
}
|
||||
];
|
||||
|
||||
// if (environment.productConfig.CommonSetting.useTimerRoom) {
|
||||
// this.fabButtons.push({
|
||||
// icon: 'timer',
|
||||
// tooltip: this.translateService.instant('chat.newTimerChat'),
|
||||
// divisionType: 'CHAT_NEW_TIMER_ADD'
|
||||
// });
|
||||
// }
|
||||
}
|
||||
break;
|
||||
case 'organization':
|
||||
{
|
||||
this.fabButtonShow = false;
|
||||
}
|
||||
break;
|
||||
case 'message':
|
||||
{
|
||||
this.fabButtonShow = true;
|
||||
this.fabButtons = [
|
||||
{
|
||||
icon: 'add',
|
||||
tooltip: '쪽지 추가',
|
||||
divisionType: 'MESSAGE_NEW'
|
||||
}
|
||||
];
|
||||
}
|
||||
break;
|
||||
// case MainMenu.Call:
|
||||
// {
|
||||
// this.fabButtonShow = false;
|
||||
// }
|
||||
// break;
|
||||
|
||||
default: {
|
||||
this.fabButtonShow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClickFab(params: { btn: any }) {
|
||||
const btn = params.btn as {
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
divisionType?: string;
|
||||
};
|
||||
|
||||
switch (btn.divisionType) {
|
||||
case 'GROUP_NEW_ADD':
|
||||
{
|
||||
this.logService.debug('GROUP_NEW_ADD');
|
||||
}
|
||||
break;
|
||||
case 'CAHT_NEW_ADD':
|
||||
{
|
||||
this.logService.debug('CAHT_NEW_ADD');
|
||||
}
|
||||
break;
|
||||
case 'CHAT_NEW_TIMER_ADD':
|
||||
{
|
||||
// if (environment.productConfig.CommonSetting.useTimerRoom) {
|
||||
// this.onClickNewChat('TIMER');
|
||||
// }
|
||||
}
|
||||
break;
|
||||
|
||||
case 'MESSAGE_NEW':
|
||||
{
|
||||
this.logService.debug('MESSAGE_NEW');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
<router-outlet></router-outlet>
|
||||
<div class="layout-container" fxFlexFill fxLayout="column">
|
||||
<div *ngIf="showTopbar" class="top-bar" fxFlex="50px">
|
||||
<app-layouts-top-bar></app-layouts-top-bar>
|
||||
</div>
|
||||
<div class="layout-content" fxFlex="1 1 auto">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
.layout-container {
|
||||
}
|
|
@ -6,5 +6,7 @@ import { Component } from '@angular/core';
|
|||
styleUrls: ['./no-navi.layout.component.scss']
|
||||
})
|
||||
export class NoNaviLayoutComponent {
|
||||
showTopbar = true;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<mat-toolbar class=".title-bar">
|
||||
<div class="title-bar">
|
||||
<ucap-title-bar
|
||||
[platform]="platform"
|
||||
[native]="native"
|
||||
|
@ -7,4 +7,4 @@
|
|||
(minimized)="onMinimizedTitleBar()"
|
||||
>
|
||||
</ucap-title-bar>
|
||||
</mat-toolbar>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
.title-bar {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
<div class="login-page-container" fxLayout="row">
|
||||
<div fxFlex="1 1 auto">Login</div>
|
||||
<div class="login-section-container">
|
||||
<app-sections-account-login
|
||||
[companyGroupCode]="companyGroupCode"
|
||||
[fixedCompanyCode]="fixedCompanyCode"
|
||||
[userStore]="userStore"
|
||||
[useRememberMe]="useRememberMe"
|
||||
[useAutoLogin]="useAutoLogin"
|
||||
></app-sections-account-login>
|
||||
</div>
|
||||
<div class="login-container">
|
||||
<app-sections-account-login
|
||||
[companyGroupCode]="companyGroupCode"
|
||||
[fixedCompanyCode]="fixedCompanyCode"
|
||||
[userStore]="userStore"
|
||||
[useRememberMe]="useRememberMe"
|
||||
[useAutoLogin]="useAutoLogin"
|
||||
></app-sections-account-login>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,62 @@
|
|||
.login-page-container {
|
||||
.login-section-container {
|
||||
width: 400px;
|
||||
margin: 40px;
|
||||
}
|
||||
@import '../../../../assets/scss/components';
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
min-height: 100vh;
|
||||
background-color: $bg-gray;
|
||||
background-image: url(../../../../assets/images/bg/bg_login_circle_square01.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_stroke01.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle01.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle03.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_diagonal01.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_square02.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle04.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle05.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle06.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle07.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle08.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_diagonal02.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_diagonal03.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_stroke02.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_stroke03.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_stroke04.svg),
|
||||
url(../../../../assets/images/bg/bg_login_circle_stroke05.svg),
|
||||
url(../../../../assets/images/bg/bg_login_polygon01.svg),
|
||||
url(../../../../assets/images/bg/bg_login_polygon02.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: unquote($login-bg-w * 323 + '%')
|
||||
unquote($login-bg-h * 179 + '%'),
|
||||
unquote($login-bg-w * -147 + '%') unquote($login-bg-h * 18 + '%'),
|
||||
unquote($login-bg-w * 285 + '%') unquote($login-bg-h * 226 + '%'),
|
||||
unquote($login-bg-w * 1235 + '%') unquote($login-bg-h * -101 + '%'),
|
||||
unquote($login-bg-w * 1397 + '%') unquote($login-bg-h * 163 + '%'),
|
||||
unquote($login-bg-w * 1569 + '%') unquote($login-bg-h * 580 + '%'),
|
||||
unquote($login-bg-w * 426 + '%') unquote($login-bg-h * 293 + '%'),
|
||||
unquote($login-bg-w * 1531 + '%') unquote($login-bg-h * 250 + '%'),
|
||||
unquote($login-bg-w * 1774 + '%') unquote($login-bg-h * 166 + '%'),
|
||||
unquote($login-bg-w * 1362 + '%') unquote($login-bg-h * 673 + '%'),
|
||||
unquote($login-bg-w * 152 + '%') unquote($login-bg-h * 730 + '%'),
|
||||
unquote($login-bg-w * 286 + '%') unquote($login-bg-h * 719 + '%'),
|
||||
unquote($login-bg-w * 683 + '%') unquote($login-bg-h * 593 + '%'),
|
||||
unquote($login-bg-w * 498 + '%') unquote($login-bg-h * 453 + '%'),
|
||||
unquote($login-bg-w * 1709 + '%') unquote($login-bg-h * 599 + '%'),
|
||||
unquote($login-bg-w * 1395 + '%') unquote($login-bg-h * 989 + '%'),
|
||||
unquote(100 + $login-bg-w * 89 + '%') unquote(100 + $login-bg-h * 137 + '%'),
|
||||
unquote($login-bg-w * 90 + '%') unquote($login-bg-h * 463 + '%'),
|
||||
unquote($login-bg-w * 549 + '%') unquote($login-bg-h * 874 + '%');
|
||||
background-size: unquote($login-bg-w * 79 + '%'),
|
||||
unquote($login-bg-w * 333 + '%'), unquote($login-bg-w * 84 + '%'),
|
||||
unquote($login-bg-w * 172 + '%'), unquote($login-bg-w * 210 + '%'),
|
||||
unquote($login-bg-w * 94 + '%'), unquote($login-bg-w * 44 + '%'),
|
||||
unquote($login-bg-w * 118 + '%'), unquote($login-bg-w * 52 + '%'),
|
||||
unquote($login-bg-w * 70 + '%'), unquote($login-bg-w * 172 + '%'),
|
||||
unquote($login-bg-w * 82 + '%'), unquote($login-bg-w * 135 + '%'),
|
||||
unquote($login-bg-w * 102 + '%'), unquote($login-bg-w * 130 + '%'),
|
||||
unquote($login-bg-w * 184 + '%'), unquote($login-bg-w * 370 + '%'),
|
||||
unquote($login-bg-w * 122 + '%'), unquote($login-bg-w * 75 + '%');
|
||||
padding-top: 5%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import { environment } from '@environments';
|
|||
|
||||
import { UserStore } from '@app/models/user-store';
|
||||
import { AppKey } from '@app/types/app-key.type';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pages-account-login',
|
||||
|
@ -23,14 +25,25 @@ export class LoginPageComponent implements OnInit, OnDestroy {
|
|||
|
||||
readonly fixedCompanyCode = environment.companyConfig.fixedCompanyCode;
|
||||
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
constructor(private localStorageService: LocalStorageService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userStore = this.localStorageService.encGet<UserStore>(
|
||||
AppKey.UserStore,
|
||||
environment.productConfig.localEncriptionKey
|
||||
);
|
||||
this.ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
this.localStorageService
|
||||
.encGet$<UserStore>(
|
||||
AppKey.UserStore,
|
||||
environment.productConfig.localEncriptionKey
|
||||
)
|
||||
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||
.subscribe((userStore) => (this.userStore = userStore));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,32 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
|
||||
import { AppChatSectionModule } from '@app/sections/chat/chat.section.module';
|
||||
|
||||
import { AppChatRoutingPageModule } from './chat-routing.page.module';
|
||||
|
||||
import { IndexPageComponent } from './components/index.page.component';
|
||||
import { SidenavPageComponent } from './components/sidenav.page.component';
|
||||
|
||||
import { UiModule } from '@ucap/ng-ui';
|
||||
|
||||
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
|
||||
|
||||
export { IndexPageComponent, SidenavPageComponent };
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FlexLayoutModule, AppChatRoutingPageModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FlexLayoutModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
AppChatSectionModule,
|
||||
AppChatRoutingPageModule,
|
||||
UiModule
|
||||
],
|
||||
declarations: [...COMPONENTS],
|
||||
entryComponents: []
|
||||
})
|
||||
|
|
|
@ -1,3 +1,27 @@
|
|||
<div fxFlexFill>
|
||||
sidenav page of chat is works!
|
||||
<div fxFlexFill class="sidenav-container">
|
||||
<div class="group-header">
|
||||
<h3>대화</h3>
|
||||
<div class="group-menu-btn">
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="groupMenu"
|
||||
aria-label="group menu"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<app-sections-chat-search
|
||||
(keyDownEnter)="onKeyDownSearch($event)"
|
||||
(searchCancel)="onClickCancel()"
|
||||
></app-sections-chat-search>
|
||||
<app-sections-chat-list
|
||||
fxFlexFill
|
||||
[searchObj]="searchObj"
|
||||
></app-sections-chat-list>
|
||||
</div>
|
||||
|
||||
<mat-menu #groupMenu="matMenu">
|
||||
<button mat-menu-item>Item 1</button>
|
||||
<button mat-menu-item>Item 2</button>
|
||||
</mat-menu>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { Component, Inject, ChangeDetectorRef } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
|
@ -9,5 +9,29 @@ import { LogService } from '@ucap/ng-logger';
|
|||
styleUrls: ['./sidenav.page.component.scss']
|
||||
})
|
||||
export class SidenavPageComponent {
|
||||
constructor(private logService: LogService) {}
|
||||
searchObj: any = {
|
||||
isShowSearch: false,
|
||||
searchWord: ''
|
||||
};
|
||||
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private changeDetectorRef: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
onKeyDownSearch(params: { companyCode: string; searchWord: string }) {
|
||||
this.searchObj = {
|
||||
isShowSearch: true,
|
||||
searchWord: params.searchWord
|
||||
};
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
onClickCancel() {
|
||||
this.searchObj = {
|
||||
isShowSearch: false,
|
||||
searchWord: ''
|
||||
};
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
<div>
|
||||
index of group
|
||||
<div fxLayout="column">
|
||||
<div class="subtitle" fxFlex="30px">Welcome to M-Messenger</div>
|
||||
<div class="content-container" fxFlex="1 1 auto" fxLayout="row">
|
||||
<div class="profile-container" fxFlex="44%">
|
||||
<app-sections-group-profile [userSeq]="userSeq">
|
||||
</app-sections-group-profile>
|
||||
</div>
|
||||
<div class="group-info-container" fxFlex="1 1 auto">
|
||||
<app-sections-group-info [userSeq]="userSeq"></app-sections-group-info>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.profile-container {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.group-info-container {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
|
@ -21,10 +21,13 @@ export class IndexPageComponent implements OnInit, OnDestroy {
|
|||
private logService: LogService
|
||||
) {}
|
||||
|
||||
userSeq: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.paramsSubscription = this.activatedRoute.queryParams.subscribe(
|
||||
(params: Params) => {
|
||||
console.log('IndexPageComponent', params[QueryParams.ID]);
|
||||
const seqParam = params[QueryParams.ID];
|
||||
this.userSeq = !!seqParam ? seqParam : undefined;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,50 @@
|
|||
<div fxFlexFill class="sidenav-container">
|
||||
<app-sections-group-search></app-sections-group-search>
|
||||
<app-sections-group-list fxFlexFill></app-sections-group-list>
|
||||
<div class="sidenav-container" fxFlexFill fxLayout="column">
|
||||
<div class="sub-header" fxFlex="50px" fxLayout="row">
|
||||
<h3 fxFlex="1 1 auto">그룹</h3>
|
||||
<div class="menu-btn" fxFlex="70px">
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="groupViewMenu"
|
||||
aria-label="group view menu"
|
||||
>
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="groupMenu"
|
||||
aria-label="group menu"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extra-box" fxFlex="50px">
|
||||
<app-sections-group-search
|
||||
(keyDownEnter)="onKeyDownSearch($event)"
|
||||
(searchCancel)="onClickCancel()"
|
||||
>
|
||||
</app-sections-group-search>
|
||||
</div>
|
||||
|
||||
<div fxFlex="1 1 auto">
|
||||
<app-sections-group-list
|
||||
fxFlexFill
|
||||
[searchObj]="searchObj"
|
||||
></app-sections-group-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-menu #groupMenu="matMenu">
|
||||
<button mat-menu-item (click)="onClickGroupMenu('GROUP_NEW')">
|
||||
새그룹 추가
|
||||
</button>
|
||||
<button mat-menu-item>그룹전체 열기</button>
|
||||
<button mat-menu-item>그룹전체 닫기</button>
|
||||
<button mat-menu-item>그룹순서 바꾸기</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #groupViewMenu="matMenu">
|
||||
<button mat-menu-item>전체보기</button>
|
||||
<button mat-menu-item>접속한 동료만 보기</button>
|
||||
<button mat-menu-item>온/오프라인 보기</button>
|
||||
</mat-menu>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||
import { ActivatedRoute, Router, Params } from '@angular/router';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
|
||||
import { QueryParams } from '../types/params.type';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { CreateChatDialogComponent } from '@app/sections/group/components/component-ui/dialogs/create-chat.dialog.component';
|
||||
import { SelectUserDialogType } from '@app/types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pages-group-sidenav',
|
||||
|
@ -13,25 +16,57 @@ import { QueryParams } from '../types/params.type';
|
|||
styleUrls: ['./sidenav.page.component.scss']
|
||||
})
|
||||
export class SidenavPageComponent implements OnInit, OnDestroy {
|
||||
private queryParamsSubscription: Subscription;
|
||||
searchObj: any = {
|
||||
isShowSearch: false,
|
||||
companyCode: '',
|
||||
searchWord: ''
|
||||
};
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private logService: LogService
|
||||
private logService: LogService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
public dialog: MatDialog
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.queryParamsSubscription = this.activatedRoute.queryParams.subscribe(
|
||||
(params: Params) => {
|
||||
console.log('SidenavPageComponent', params[QueryParams.ID]);
|
||||
}
|
||||
);
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
|
||||
onClickFab(event: MouseEvent) {}
|
||||
onKeyDownSearch(params: { companyCode: string; searchWord: string }) {
|
||||
this.searchObj = {
|
||||
isShowSearch: true,
|
||||
companyCode: params.companyCode,
|
||||
searchWord: params.searchWord
|
||||
};
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.queryParamsSubscription) {
|
||||
this.queryParamsSubscription.unsubscribe();
|
||||
onClickCancel() {
|
||||
this.searchObj = {
|
||||
isShowSearch: false,
|
||||
companyCode: '',
|
||||
searchWord: ''
|
||||
};
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
onClickGroupMenu(menuType: string) {
|
||||
switch (menuType) {
|
||||
case 'GROUP_NEW':
|
||||
{
|
||||
this.dialog.open(CreateChatDialogComponent, {
|
||||
width: '850px',
|
||||
height: '600px',
|
||||
data: {
|
||||
type: SelectUserDialogType.NewGroup,
|
||||
title: '새 그룹 추가'
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
|
||||
import { AppGroupSectionModule } from '@app/sections/group/group.section.module';
|
||||
|
||||
import { AppGroupRoutingPageModule } from './group-routing.page.module';
|
||||
|
@ -10,6 +13,8 @@ import { AppGroupRoutingPageModule } from './group-routing.page.module';
|
|||
import { IndexPageComponent } from './components/index.page.component';
|
||||
import { SidenavPageComponent } from './components/sidenav.page.component';
|
||||
|
||||
import { UiModule } from '@ucap/ng-ui';
|
||||
|
||||
export const COMPONENTS = [IndexPageComponent, SidenavPageComponent];
|
||||
|
||||
export { IndexPageComponent, SidenavPageComponent };
|
||||
|
@ -18,8 +23,11 @@ export { IndexPageComponent, SidenavPageComponent };
|
|||
imports: [
|
||||
CommonModule,
|
||||
FlexLayoutModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
AppGroupSectionModule,
|
||||
AppGroupRoutingPageModule
|
||||
AppGroupRoutingPageModule,
|
||||
UiModule
|
||||
],
|
||||
declarations: [...COMPONENTS],
|
||||
entryComponents: []
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
<div fxFlexFill>
|
||||
sidenav page of ogranization is works!
|
||||
<div class="sidenav-container" fxFlexFill fxLayout="column">
|
||||
<div class="sub-header" fxFlex="50px" fxLayout="row">
|
||||
<h3 fxFlex="1 1 auto">조직도</h3>
|
||||
<div class="menu-btn" fxFlex="70px">
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="organizationMenu"
|
||||
aria-label="organization menu"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extra-box" fxFlex="50px">
|
||||
LG CNS
|
||||
</div>
|
||||
|
||||
<div fxFlex="1 1 auto">
|
||||
<app-sections-organization-tree></app-sections-organization-tree>
|
||||
</div>
|
||||
</div>
|
||||
<mat-menu #organizationMenu="matMenu">
|
||||
<button mat-menu-item>Item 1</button>
|
||||
<button mat-menu-item>Item 2</button>
|
||||
</mat-menu>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
.sidenav-container {
|
||||
padding-bottom: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.organization-header {
|
||||
}
|
||||
|
||||
.organization-tree {
|
||||
}
|
||||
}
|
|
@ -3,12 +3,24 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
|
||||
import { AppOrganizationSectionModule } from '@app/sections/organization/organization.section.module';
|
||||
|
||||
import { AppOrganizationRoutingPageModule } from './organization-routing.page.module';
|
||||
|
||||
import { COMPONENTS } from './components';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FlexLayoutModule, AppOrganizationRoutingPageModule],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FlexLayoutModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
AppOrganizationSectionModule,
|
||||
AppOrganizationRoutingPageModule
|
||||
],
|
||||
declarations: [...COMPONENTS],
|
||||
entryComponents: []
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Observable, forkJoin, Subject } from 'rxjs';
|
||||
import { take, filter, map, takeUntil } from 'rxjs/operators';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
|
@ -13,7 +13,6 @@ import { Store } from '@ngrx/store';
|
|||
import { StatusCode } from '@ucap/api';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { SessionStorageService } from '@ucap/ng-web-storage';
|
||||
import { PublicApiService } from '@ucap/ng-api-public';
|
||||
import { ExternalApiService } from '@ucap/ng-api-external';
|
||||
import { ProtocolService } from '@ucap/ng-protocol';
|
||||
|
@ -22,7 +21,6 @@ import { CompanyActions } from '@ucap/ng-store-organization';
|
|||
import { ConfigurationActions } from '@ucap/ng-store-authentication';
|
||||
|
||||
import { AppAuthenticationService } from '@app/services/app-authentication.service';
|
||||
import { AppKey } from '@app/types/app-key.type';
|
||||
import { LoginSession } from '@app/models/login-session';
|
||||
|
||||
@Injectable()
|
||||
|
@ -32,7 +30,6 @@ export class AppSessionResolver implements Resolve<void> {
|
|||
private externalApiService: ExternalApiService,
|
||||
private protocolService: ProtocolService,
|
||||
private store: Store<any>,
|
||||
private sessionStorageService: SessionStorageService,
|
||||
private appAuthenticationService: AppAuthenticationService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
@ -44,7 +41,6 @@ export class AppSessionResolver implements Resolve<void> {
|
|||
return new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
const loginSession = this.appAuthenticationService.getLoginSession();
|
||||
|
||||
if (loginSession.alive) {
|
||||
resolve();
|
||||
return;
|
||||
|
@ -115,18 +111,15 @@ export class AppSessionResolver implements Resolve<void> {
|
|||
})
|
||||
);
|
||||
|
||||
const destroy$ = new Subject<boolean>();
|
||||
this.sessionStorageService.changed$
|
||||
.pipe(
|
||||
takeUntil(destroy$),
|
||||
filter((param) => AppKey.LoginSession === param.key),
|
||||
map((param) => param.value)
|
||||
)
|
||||
const destroySubject = new Subject<boolean>();
|
||||
this.appAuthenticationService
|
||||
.getLoginSession$()
|
||||
.pipe(takeUntil(destroySubject))
|
||||
.subscribe(
|
||||
(v) => {
|
||||
if ((v as LoginSession).alive) {
|
||||
destroy$.next(true);
|
||||
destroy$.unsubscribe();
|
||||
destroySubject.next(true);
|
||||
destroySubject.complete();
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -10,6 +10,24 @@ import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
|||
import { AuthenticationUiModule } from '@ucap/ng-ui-authentication';
|
||||
|
||||
import { COMPONENTS } from './components';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatSliderModule } from '@angular/material/slider';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -17,7 +35,28 @@ import { COMPONENTS } from './components';
|
|||
FlexLayoutModule,
|
||||
MatCheckboxModule,
|
||||
I18nModule,
|
||||
AuthenticationUiModule
|
||||
AuthenticationUiModule,
|
||||
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatButtonToggleModule,
|
||||
MatCardModule,
|
||||
MatDatepickerModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatMenuModule,
|
||||
MatProgressBarModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatCheckboxModule,
|
||||
MatSelectModule,
|
||||
MatSidenavModule,
|
||||
MatSliderModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
MatToolbarModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule
|
||||
],
|
||||
exports: [...COMPONENTS],
|
||||
declarations: [...COMPONENTS],
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
<div class="login-box">
|
||||
<ng-content select="[ucapAuthenticationLogin='header']"></ng-content>
|
||||
|
||||
<div class="login-content">
|
||||
<form name="loginForm" [formGroup]="loginForm" novalidate>
|
||||
<mat-form-field
|
||||
[style.display]="!!fixedCompanyCode ? 'none' : 'block'"
|
||||
class="login-company"
|
||||
appearance="none"
|
||||
>
|
||||
<mat-select
|
||||
[formControl]="companyCodeFormControl"
|
||||
[value]="companyCode || fixedCompanyCode"
|
||||
placeholder="{{ 'login.labels.selectCompany' | ucapI18n }}"
|
||||
class="login-input-area login-select-form"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let company of companyList"
|
||||
[value]="company.companyCode"
|
||||
>{{ company.companyName }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="login-input-area idpass-type">
|
||||
<mat-form-field
|
||||
class="login-idpass-txt"
|
||||
appearance="none"
|
||||
floatLabel=""
|
||||
>
|
||||
<!-- <mat-label>{{ 'login.fields.loginId' | ucapI18n }}</mat-label> -->
|
||||
<input
|
||||
matInput
|
||||
[formControl]="loginIdFormControl"
|
||||
placeholder="{{ 'login.fields.loginId' | ucapI18n }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="login-input-area idpass-type pass-type">
|
||||
<mat-form-field class="login-idpass-txt" appearance="none">
|
||||
<!-- <mat-label>{{ 'login.fields.loginPw' | ucapI18n }}</mat-label> -->
|
||||
<input
|
||||
matInput
|
||||
type="password"
|
||||
[formControl]="loginPwFormControl"
|
||||
placeholder="{{ 'login.fields.loginPw' | ucapI18n }}"
|
||||
#loginPw
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="error-container">
|
||||
<mat-error
|
||||
*ngIf="
|
||||
companyCodeFormControl.dirty &&
|
||||
companyCodeFormControl.invalid &&
|
||||
companyCodeFormControl.hasError('required')
|
||||
"
|
||||
>
|
||||
{{ 'login.errors.requireCompany' | ucapI18n }}
|
||||
</mat-error>
|
||||
<mat-error
|
||||
*ngIf="
|
||||
loginIdFormControl.dirty &&
|
||||
loginIdFormControl.invalid &&
|
||||
loginIdFormControl.hasError('required')
|
||||
"
|
||||
>
|
||||
{{ 'login.errors.requireLoginId' | ucapI18n }}
|
||||
</mat-error>
|
||||
<mat-error
|
||||
*ngIf="
|
||||
loginPwFormControl.dirty &&
|
||||
loginPwFormControl.invalid &&
|
||||
loginPwFormControl.hasError('required')
|
||||
"
|
||||
>
|
||||
{{ 'login.errors.requireLoginPw' | ucapI18n }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="loginFailed">
|
||||
{{ 'login.errors.failed' | ucapI18n }}
|
||||
</mat-error>
|
||||
</div>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
class="login-input-submit"
|
||||
aria-label="LOG IN"
|
||||
[disabled]="
|
||||
loginForm.invalid ||
|
||||
disable ||
|
||||
(!!loginTry && !!loginTry.remainTimeForNextTry)
|
||||
"
|
||||
(click)="onClickLogin()"
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="!processing && (!loginTry || !loginTry.remainTimeForNextTry)"
|
||||
>
|
||||
{{ 'login.labels.doLogin' | ucapI18n }}
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!!loginTry && !!loginTry.remainTimeForNextTry">
|
||||
{{ 'login.errors.attemptsExceeded' | ucapI18n }}
|
||||
(
|
||||
{{
|
||||
moment
|
||||
.utc(
|
||||
moment
|
||||
.duration(loginTry.remainTimeForNextTry, 'seconds')
|
||||
.asMilliseconds()
|
||||
)
|
||||
.format('mm:ss')
|
||||
}}
|
||||
)
|
||||
</ng-container>
|
||||
|
||||
<mat-spinner
|
||||
*ngIf="processing && (!loginTry || !loginTry.remainTimeForNextTry)"
|
||||
>
|
||||
</mat-spinner>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<ng-content select="[ucapAuthenticationLogin='footer']"></ng-content>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,160 @@
|
|||
@import '../../../../../assets/scss/components';
|
||||
|
||||
.login-box {
|
||||
@extend %clearfix;
|
||||
padding: 0 0 45px;
|
||||
width: 420px;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
flex-basis: auto;
|
||||
align-items: center;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@extend %guideline;
|
||||
|
||||
.login-content {
|
||||
@extend %guideline2; //Guide Line2
|
||||
margin: 30px auto 0;
|
||||
.login-input-area {
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 2px;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
min-width: 150px;
|
||||
height: 60px;
|
||||
background-color: $white;
|
||||
margin-top: 10px;
|
||||
&.login-select-form {
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
padding: 0 16px;
|
||||
@include screen(mid) {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
@include screen(xs) {
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
&:first-of-type {
|
||||
margin-top: 0px;
|
||||
}
|
||||
&.idpass-type {
|
||||
padding-left: 50px;
|
||||
position: relative;
|
||||
&::before {
|
||||
font-family: 'material Icons';
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
content: 'perm_identity';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 16px;
|
||||
@include screen(mid) {
|
||||
line-height: 50px;
|
||||
}
|
||||
@include screen(xs) {
|
||||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
&.pass-type {
|
||||
&::before {
|
||||
content: 'https';
|
||||
}
|
||||
}
|
||||
.login-idpass-txt {
|
||||
width: 368px;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
font-size: 14px;
|
||||
@include screen(mid) {
|
||||
width: 358 - 60 + px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 14px;
|
||||
}
|
||||
@include screen(xs) {
|
||||
width: 308 - 60 + px;
|
||||
font-size: 14px;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
}
|
||||
input {
|
||||
font-size: 18px;
|
||||
line-height: 58px;
|
||||
margin-top: 0;
|
||||
vertical-align: top;
|
||||
background-color: $white;
|
||||
padding: 0 10px 0 5px;
|
||||
@include screen(mid) {
|
||||
font-size: 16px;
|
||||
line-height: 48px;
|
||||
}
|
||||
@include screen(xs) {
|
||||
font-size: 14px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@include screen(mid) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
.login-input-submit {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: $black;
|
||||
border-radius: 2px;
|
||||
color: $white;
|
||||
font-size: 20px;
|
||||
@include font-family($font-semibold);
|
||||
border: 0;
|
||||
margin-top: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
@include screen(mid) {
|
||||
margin-top: 8px;
|
||||
font-size: 16px;
|
||||
height: 50px;
|
||||
}
|
||||
@include screen(xs) {
|
||||
font-size: 14px;
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
@include screen(mid) {
|
||||
margin-top: 23px;
|
||||
width: 350px;
|
||||
.login-input-area {
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
@include screen(xs) {
|
||||
margin-top: 23px;
|
||||
width: 300px;
|
||||
.login-input-area {
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.login-company {
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectorRef } from '@angular/core';
|
||||
import { I18nService, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
||||
import { AuthenticationUiModule } from '../authentication-ui.module';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { Company } from '@ucap/api-external';
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
|
||||
describe('ui::authentication::LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule
|
||||
],
|
||||
providers: [
|
||||
AuthenticationUiModule,
|
||||
// { provide: FormBuilder, useValue: new FormBuilder() },
|
||||
// { provide: ChangeDetectorRef, useValue: ChangeDetectorRef },
|
||||
{ provide: I18nService, useValue: new I18nService(new LogService({})) },
|
||||
{
|
||||
provide: UCAP_I18N_NAMESPACE,
|
||||
useValue: 'authentication'
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
component.companyList = [
|
||||
{ companyName: 'LG CNS', companyCode: 'GUC100' },
|
||||
{ companyName: 'LG UCAP', companyCode: 'GUC101' }
|
||||
] as Company[];
|
||||
component.loginId = 'test';
|
||||
component.companyCode = 'GUC100';
|
||||
fixture.detectChanges();
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('login', (done) => {
|
||||
component.companyList = [
|
||||
{ companyName: 'LG CNS', companyCode: 'GUC100' },
|
||||
{ companyName: 'LG UCAP', companyCode: 'GUC101' }
|
||||
] as Company[];
|
||||
component.loginId = 'test';
|
||||
component.companyCode = 'GUC100';
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
component.login.subscribe((value) => {
|
||||
console.log(value);
|
||||
done();
|
||||
});
|
||||
|
||||
component.onClickLogin();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,110 @@
|
|||
import moment from 'moment';
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import {
|
||||
FormGroup,
|
||||
FormBuilder,
|
||||
Validators,
|
||||
FormControl,
|
||||
ValidatorFn
|
||||
} from '@angular/forms';
|
||||
import { Company } from '@ucap/api-external';
|
||||
import { LoginTry } from '@ucap/pi';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-authentication-login-local',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
@Input()
|
||||
companyList: Company[];
|
||||
|
||||
@Input()
|
||||
fixedCompanyCode: string;
|
||||
|
||||
@Input()
|
||||
companyCode: string;
|
||||
|
||||
@Input()
|
||||
loginId: string;
|
||||
|
||||
@Input()
|
||||
disable = false;
|
||||
|
||||
@Input()
|
||||
processing = false;
|
||||
|
||||
@Input()
|
||||
loginTry: LoginTry;
|
||||
|
||||
@Output()
|
||||
login = new EventEmitter<{
|
||||
companyCode: string;
|
||||
loginId: string;
|
||||
loginPw: string;
|
||||
notValid: () => void;
|
||||
}>();
|
||||
|
||||
@ViewChild('loginPw', { static: true }) loginPwElementRef: ElementRef;
|
||||
|
||||
loginForm: FormGroup;
|
||||
companyCodeFormControl = new FormControl('');
|
||||
loginIdFormControl = new FormControl('');
|
||||
loginPwFormControl = new FormControl('');
|
||||
loginFailed = false;
|
||||
|
||||
moment = moment;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private changeDetectorRef: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
const companyCodeValidators: ValidatorFn[] = [Validators.required];
|
||||
this.companyCodeFormControl.setValidators(companyCodeValidators);
|
||||
if (!!this.fixedCompanyCode) {
|
||||
this.companyCodeFormControl.setValue(this.fixedCompanyCode);
|
||||
}
|
||||
if (!!this.companyCode) {
|
||||
this.companyCodeFormControl.setValue(this.companyCode);
|
||||
}
|
||||
const loginIdValidators: ValidatorFn[] = [Validators.required];
|
||||
this.loginIdFormControl.setValidators(loginIdValidators);
|
||||
if (!!this.loginId) {
|
||||
this.loginIdFormControl.setValue(this.loginId);
|
||||
}
|
||||
const loginPwValidators: ValidatorFn[] = [Validators.required];
|
||||
this.loginPwFormControl.setValidators(loginPwValidators);
|
||||
|
||||
this.loginForm = this.formBuilder.group({
|
||||
companyCodeFormControl: this.companyCodeFormControl,
|
||||
loginIdFormControl: this.loginIdFormControl,
|
||||
loginPwFormControl: this.loginPwFormControl
|
||||
});
|
||||
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
onClickLogin() {
|
||||
this.login.emit({
|
||||
companyCode: this.loginForm.get('companyCodeFormControl').value,
|
||||
loginId: this.loginForm.get('loginIdFormControl').value,
|
||||
loginPw: this.loginForm.get('loginPwFormControl').value,
|
||||
notValid: () => {
|
||||
this.loginFailed = true;
|
||||
this.loginPwElementRef.nativeElement.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { LoginSectionComponent } from './login.section.component';
|
||||
import { LoginComponent } from './component-ui/login.component';
|
||||
|
||||
export const COMPONENTS = [LoginSectionComponent];
|
||||
export const COMPONENTS = [LoginSectionComponent, LoginComponent];
|
||||
|
|
|
@ -1,68 +1,67 @@
|
|||
<ucap-authentication-login
|
||||
[companyList]="companyList"
|
||||
[fixedCompanyCode]="fixedCompanyCode"
|
||||
[companyCode]="userStore?.companyCode"
|
||||
[loginId]="userStore?.loginId"
|
||||
[disable]="disableLoginForm"
|
||||
[processing]="loginProcessing"
|
||||
[loginTry]="loginTry"
|
||||
(login)="onLogin($event)"
|
||||
>
|
||||
<div
|
||||
ucapAuthenticationLogin="header"
|
||||
style="background-image: url(./assets/images/logo/bg_logo_login.png);"
|
||||
<div class="login-section-container">
|
||||
<ucap-authentication-login-local
|
||||
[companyList]="companyList"
|
||||
[fixedCompanyCode]="fixedCompanyCode"
|
||||
[companyCode]="userStore?.companyCode"
|
||||
[loginId]="userStore?.loginId"
|
||||
[disable]="disableLoginForm"
|
||||
[processing]="loginProcessing"
|
||||
[loginTry]="loginTry"
|
||||
(login)="onLogin($event)"
|
||||
>
|
||||
{{ 'login.labels.instructionsOfLogin' | ucapI18n }}
|
||||
</div>
|
||||
<div ucapAuthenticationLogin="footer">
|
||||
<div
|
||||
class="remember-forgot-password"
|
||||
fxLayout="row"
|
||||
fxLayout.xs="column"
|
||||
fxLayoutAlign="space-between center"
|
||||
>
|
||||
<mat-checkbox
|
||||
#chkUseRememberMe
|
||||
*ngIf="useRememberMe"
|
||||
class="remember-me"
|
||||
aria-label="Remember Me"
|
||||
[checked]="!!userStore && userStore.rememberMe"
|
||||
>
|
||||
{{ 'login.labels.rememberMe' | ucapI18n }}
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-checkbox
|
||||
#chkUseAutoLogin
|
||||
*ngIf="useAutoLogin"
|
||||
class="auto-login"
|
||||
aria-label="Auto Login"
|
||||
[checked]="
|
||||
!!userStore &&
|
||||
!!userStore.settings &&
|
||||
!!userStore.settings.general &&
|
||||
userStore.settings.general.autoLogin
|
||||
"
|
||||
>
|
||||
{{ 'login.labels.autoLogin' | ucapI18n }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="register" fxLayout="column" fxLayoutAlign="center center">
|
||||
<button
|
||||
class="link btn-login-forgot"
|
||||
(click)="onClickForgotPassword('ko')"
|
||||
>
|
||||
Forgot Password? KO
|
||||
</button>
|
||||
<button
|
||||
class="link btn-login-forgot"
|
||||
(click)="onClickForgotPassword('en')"
|
||||
>
|
||||
Forgot Password? EN
|
||||
</button>
|
||||
<div ucapAuthenticationLogin="header">
|
||||
<div class="logo-img">
|
||||
<img src="../../../assets/images/logo_140.png" alt="" />
|
||||
</div>
|
||||
<h1>Welcome to Messenger</h1>
|
||||
</div>
|
||||
|
||||
<div class="policy bg-primary-light">
|
||||
<a class="link">개인정보 처리방침</a>
|
||||
<div ucapAuthenticationLogin="footer">
|
||||
<div class="login-chk-area">
|
||||
<div>
|
||||
<mat-checkbox
|
||||
#chkUseRememberMe
|
||||
*ngIf="useRememberMe"
|
||||
aria-label="Remember Me"
|
||||
[checked]="!!userStore && userStore.rememberMe"
|
||||
>
|
||||
{{ 'login.labels.rememberMe' | ucapI18n }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-checkbox
|
||||
#chkUseAutoLogin
|
||||
*ngIf="useAutoLogin"
|
||||
aria-label="Auto Login"
|
||||
[checked]="
|
||||
!!userStore &&
|
||||
!!userStore.settings &&
|
||||
!!userStore.settings.general &&
|
||||
userStore.settings.general.autoLogin
|
||||
"
|
||||
>
|
||||
{{ 'login.labels.autoLogin' | ucapI18n }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-pass-info">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="">{{ 'login.labels.forgotPassword' | ucapI18n }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="fir-pass">{{
|
||||
'login.labels.resetPassword' | ucapI18n
|
||||
}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="login-button-area">
|
||||
<button type="button">
|
||||
{{ 'login.labels.notesOnUse' | ucapI18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ucap-authentication-login>
|
||||
</ucap-authentication-login-local>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
@import '../../../../assets/scss/components';
|
||||
|
||||
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-section-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-chk-area {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
@include screen(xs) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.login-pass-info {
|
||||
overflow: hidden;
|
||||
margin-top: 83px;
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
li {
|
||||
height: 24px;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 12% 0 8%;
|
||||
&::before {
|
||||
content: '';
|
||||
height: 11px;
|
||||
width: 1px;
|
||||
display: flex;
|
||||
background-color: $gray-re4a;
|
||||
position: absolute;
|
||||
top: 6.5px;
|
||||
left: 0;
|
||||
}
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
a {
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
color: $gray-re4a;
|
||||
padding-left: 34px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
&::before {
|
||||
font-family: 'material Icons';
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
content: 'search';
|
||||
color: $white;
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: $black;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
&.fir-pass {
|
||||
&::before {
|
||||
content: 'sync';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-button-area {
|
||||
margin-top: 14px;
|
||||
@include screen(xs) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
button {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
border-radius: 4px;
|
||||
background-color: #e0e3e7;
|
||||
font-size: 12px;
|
||||
color: $gray-re4a;
|
||||
cursor: pointer;
|
||||
@include screen(mid) {
|
||||
height: 38px;
|
||||
}
|
||||
@include screen(xs) {
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Subscription } from 'rxjs';
|
||||
import { take, filter } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core';
|
||||
|
||||
|
@ -56,8 +56,7 @@ export class LoginSectionComponent implements OnInit, OnDestroy {
|
|||
loginProcessing = false;
|
||||
loginTry: LoginTry;
|
||||
|
||||
private companyListSubscription: Subscription;
|
||||
private loginTrySubscription: Subscription;
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
private piService: PiService,
|
||||
|
@ -70,9 +69,17 @@ export class LoginSectionComponent implements OnInit, OnDestroy {
|
|||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loginSession = this.appAuthenticationService.getLoginSession();
|
||||
this.ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
this.loginTry = this.sessionStorageService.get<LoginTry>(AppKey.LoginTry);
|
||||
this.appAuthenticationService
|
||||
.getLoginSession$()
|
||||
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||
.subscribe((loginSession) => (this.loginSession = loginSession));
|
||||
|
||||
this.sessionStorageService
|
||||
.get$<LoginTry>(AppKey.LoginTry)
|
||||
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||
.subscribe((loginTry) => (this.loginTry = loginTry));
|
||||
|
||||
this.protocolService.disconnect();
|
||||
|
||||
|
@ -82,25 +89,19 @@ export class LoginSectionComponent implements OnInit, OnDestroy {
|
|||
})
|
||||
);
|
||||
|
||||
this.companyListSubscription = this.store
|
||||
.pipe(select(CompanySelector.companyList))
|
||||
this.store
|
||||
.pipe(
|
||||
takeUntil(this.ngOnDestroySubject),
|
||||
select(CompanySelector.companyList)
|
||||
)
|
||||
.subscribe((companyList) => {
|
||||
this.companyList = companyList;
|
||||
});
|
||||
|
||||
this.loginTrySubscription = this.sessionStorageService.changed$
|
||||
.pipe(filter((param) => AppKey.LoginTry === param.key))
|
||||
.subscribe((param) => {
|
||||
this.loginTry = param.value as LoginTry;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.companyListSubscription) {
|
||||
this.companyListSubscription.unsubscribe();
|
||||
}
|
||||
if (!!this.loginTrySubscription) {
|
||||
this.loginTrySubscription.unsubscribe();
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
71
src/app/sections/chat/chat.section.module.ts
Normal file
71
src/app/sections/chat/chat.section.module.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
|
||||
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
||||
|
||||
import { UiModule } from '@ucap/ng-ui';
|
||||
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
|
||||
// import { GroupUiModule } from '@ucap/ng-ui-group';
|
||||
|
||||
import { COMPONENTS, DIRECTIVES } from './components';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatTreeModule } from '@angular/material/tree';
|
||||
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
FlexLayoutModule,
|
||||
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatFormFieldModule,
|
||||
MatAutocompleteModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatBadgeModule,
|
||||
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatIconModule,
|
||||
MatRippleModule,
|
||||
MatTreeModule,
|
||||
|
||||
PerfectScrollbarModule,
|
||||
ScrollingModule,
|
||||
|
||||
I18nModule,
|
||||
UiModule,
|
||||
OrganizationUiModule
|
||||
// GroupUiModule
|
||||
],
|
||||
exports: [...COMPONENTS, ...DIRECTIVES],
|
||||
declarations: [...COMPONENTS, ...DIRECTIVES],
|
||||
entryComponents: [],
|
||||
providers: [
|
||||
{
|
||||
provide: UCAP_I18N_NAMESPACE,
|
||||
useValue: ['chat']
|
||||
}
|
||||
]
|
||||
})
|
||||
export class AppChatSectionModule {}
|
|
@ -0,0 +1,21 @@
|
|||
<div class="chat-list-item">
|
||||
<div class="profileImage">{{ defaultProfileImage }}</div>
|
||||
<div class="roomName">
|
||||
{{ roomName }}
|
||||
<strong *ngIf="roomInfo.roomType === RoomType.Multi"
|
||||
>({{ roomInfo.joinUserCount }})</strong
|
||||
>
|
||||
</div>
|
||||
<div class="lastMessage">{{ roomInfo.finalEventMessage }}</div>
|
||||
<div class="date">{{ roomInfo.finalEventDate | ucapDate: 'LT' }}</div>
|
||||
|
||||
<span
|
||||
class="noti-sum"
|
||||
*ngIf="!!roomInfo.noReadCnt && roomInfo.noReadCnt > 0"
|
||||
[matBadgeHidden]="roomInfo.noReadCnt === 0"
|
||||
[matBadge]="roomInfo.noReadCnt"
|
||||
matBadgeOverlap="true"
|
||||
matBadgeColor="accent"
|
||||
matBadgePosition="below after"
|
||||
></span>
|
||||
</div>
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChatListItemComponent } from './chat-list-item.component';
|
||||
|
||||
describe('ChatListItemComponent', () => {
|
||||
let component: ChatListItemComponent;
|
||||
let fixture: ComponentFixture<ChatListItemComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChatListItemComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChatListItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { RoomInfo, RoomType } from '@ucap/protocol-room';
|
||||
import {
|
||||
RoomUserMap,
|
||||
RoomUserShortMap
|
||||
} from '@ucap/ng-store-chat/lib/store/room/state';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chat-list-item',
|
||||
templateUrl: './chat-list-item.component.html',
|
||||
styleUrls: ['./chat-list-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChatListItemComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
roomInfo: RoomInfo;
|
||||
|
||||
@Input()
|
||||
defaultProfileImage: string;
|
||||
|
||||
@Input()
|
||||
profileImage: string;
|
||||
|
||||
@Input()
|
||||
roomName: string;
|
||||
|
||||
RoomType = RoomType;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<div class="ucap-group-expansion-container" fxFlexFill>
|
||||
<cdk-virtual-scroll-viewport #cvsvList perfectScrollbar fxFlexFill>
|
||||
<ng-container
|
||||
*cdkVirtualFor="
|
||||
let node of dataSource.expandedData$;
|
||||
templateCacheSize: 0
|
||||
"
|
||||
></ng-container>
|
||||
|
||||
<mat-tree #treeList [dataSource]="dataSource" [treeControl]="treeControl">
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node"
|
||||
[attr.node-type]="node?.nodeType"
|
||||
matRipple
|
||||
>
|
||||
<li>
|
||||
<div>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: node?.node }"
|
||||
></ng-container>
|
||||
</div>
|
||||
</li>
|
||||
</mat-tree-node>
|
||||
|
||||
<mat-tree-node
|
||||
*matTreeNodeDef="let node; when: isHeader"
|
||||
class="tree-node-frame ucap-clickable"
|
||||
[attr.node-type]="node?.nodeType"
|
||||
matRipple
|
||||
>
|
||||
<li class="tree-node-header" matTreeNodeToggle>
|
||||
<div class="path">
|
||||
<button
|
||||
mat-icon-button
|
||||
[attr.aria-label]="'toggle '"
|
||||
class="btn-toggle"
|
||||
*ngIf="!checkable"
|
||||
>
|
||||
<mat-icon class="mat-icon-rtl-mirror">
|
||||
{{
|
||||
treeControl.isExpanded(node) ? 'expand_less' : 'expand_more'
|
||||
}}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<div class="group-info">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="headerTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: node?.node }"
|
||||
>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<mat-checkbox
|
||||
*ngIf="checkable"
|
||||
#checkbox
|
||||
[checked]="isCheckedGroup(node)"
|
||||
[disabled]="!isCheckableGroup(node)"
|
||||
(change)="onChangeCheckGroup(checkbox.checked, node)"
|
||||
(click)="$event.stopPropagation()"
|
||||
class="group-check"
|
||||
>
|
||||
</mat-checkbox>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="group-header-menu"
|
||||
*ngIf="!checkable"
|
||||
(click)="
|
||||
$event.stopPropagation(); onClickHeaderMenu($event, node)
|
||||
"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<ul [class.group-tree-node-invisible]="!treeControl.isExpanded(node)">
|
||||
<div *ngIf="treeControl.isExpanded(node)" class="boxnone">
|
||||
<div class="vertical-line"></div>
|
||||
<ng-container matTreeNodeOutlet></ng-container>
|
||||
</div>
|
||||
</ul>
|
||||
</li>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { ExpansionComponent } from './expansion.component';
|
||||
|
||||
describe('ucap::ui-group::ExpansionComponent', () => {
|
||||
let component: ExpansionComponent;
|
||||
let fixture: ComponentFixture<ExpansionComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ExpansionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ExpansionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,248 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
ContentChild,
|
||||
TemplateRef,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Directive
|
||||
} from '@angular/core';
|
||||
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { FlatTreeControl } from '@angular/cdk/tree';
|
||||
|
||||
import { MatTreeFlattener, MatTree } from '@angular/material/tree';
|
||||
|
||||
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
|
||||
|
||||
import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui';
|
||||
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
|
||||
import { RoomInfo } from '@ucap/protocol-room';
|
||||
|
||||
export interface ChatGroupNode {
|
||||
nodeType: string;
|
||||
roomInfo?: RoomInfo;
|
||||
children?: ChatGroupNode[];
|
||||
}
|
||||
|
||||
export interface FlatNode {
|
||||
expandable: boolean;
|
||||
level: number;
|
||||
node: ChatGroupNode;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapChatExpansionNode]'
|
||||
})
|
||||
export class ExpansionNodeDirective {}
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapChatExpansionHeader]'
|
||||
})
|
||||
export class ExpansionHeaderDirective {}
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-expansion',
|
||||
templateUrl: './expansion.component.html',
|
||||
styleUrls: ['./expansion.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ExpansionComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
set chatGroup(list: { division: string; roomList: RoomInfo[] }[]) {
|
||||
if (!list || 0 === list.length) {
|
||||
} else {
|
||||
list.sort((a, b) =>
|
||||
a.division < b.division ? 1 : a.division > b.division ? -1 : 0
|
||||
);
|
||||
|
||||
for (const item of list) {
|
||||
const nodeType = item.division;
|
||||
const node: ChatGroupNode = {
|
||||
nodeType,
|
||||
children: []
|
||||
};
|
||||
|
||||
item.roomList.sort((a, b) =>
|
||||
a.finalEventDate < b.finalEventDate
|
||||
? 1
|
||||
: a.finalEventDate > b.finalEventDate
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
|
||||
item.roomList.forEach((roomInfo) => {
|
||||
node.children.push({
|
||||
nodeType,
|
||||
roomInfo
|
||||
});
|
||||
});
|
||||
|
||||
if (!!this.nodeMap.get(item.division)) {
|
||||
this.nodeMap[item.division].push(node);
|
||||
} else {
|
||||
this.nodeMap.set(item.division, [node]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.refreshNodes();
|
||||
}
|
||||
|
||||
@Input()
|
||||
checkable = false;
|
||||
|
||||
// @Input()
|
||||
// selectedUserList?: (UserInfo | UserInfoSS | UserInfoF | UserInfoDN)[] = [];
|
||||
|
||||
// @Input()
|
||||
// unselectableUserList?: (
|
||||
// | UserInfo
|
||||
// | UserInfoSS
|
||||
// | UserInfoF
|
||||
// | UserInfoDN
|
||||
// )[] = [];
|
||||
|
||||
@ViewChild('treeList', { static: false })
|
||||
treeList: MatTree<FlatNode>;
|
||||
|
||||
@ViewChild('cvsvList', { static: false })
|
||||
cvsvList: CdkVirtualScrollViewport;
|
||||
|
||||
@ViewChild(PerfectScrollbarDirective, { static: false })
|
||||
psDirectiveRef?: PerfectScrollbarDirective;
|
||||
|
||||
@ContentChild(ExpansionNodeDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
nodeTemplate: TemplateRef<ExpansionNodeDirective>;
|
||||
|
||||
@ContentChild(ExpansionHeaderDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
headerTemplate: TemplateRef<ExpansionHeaderDirective>;
|
||||
|
||||
treeControl: FlatTreeControl<FlatNode>;
|
||||
treeFlattener: MatTreeFlattener<ChatGroupNode, FlatNode>;
|
||||
dataSource: VirtualScrollTreeFlatDataSource<ChatGroupNode, FlatNode>;
|
||||
|
||||
private nodeMap: Map<string, ChatGroupNode[]> = new Map();
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _ngOnDestroySubject: Subject<void>;
|
||||
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {
|
||||
this.treeControl = new FlatTreeControl<FlatNode>(
|
||||
(node) => node.level,
|
||||
(node) => node.expandable
|
||||
);
|
||||
|
||||
this.treeFlattener = new MatTreeFlattener<ChatGroupNode, FlatNode>(
|
||||
(node: ChatGroupNode, level: number) => {
|
||||
return {
|
||||
expandable: !!node.children && node.children.length > 0,
|
||||
level,
|
||||
nodeType: node.nodeType,
|
||||
node
|
||||
};
|
||||
},
|
||||
(node) => node.level,
|
||||
(node) => node.expandable,
|
||||
(node) => node.children
|
||||
);
|
||||
|
||||
this.dataSource = new VirtualScrollTreeFlatDataSource<
|
||||
ChatGroupNode,
|
||||
FlatNode
|
||||
>(this.treeControl, this.treeFlattener);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._ngOnDestroySubject = new Subject();
|
||||
|
||||
this.dataSource.cdkVirtualScrollViewport = this.cvsvList;
|
||||
this.treeControl.expansionModel.changed
|
||||
.pipe(takeUntil(this._ngOnDestroySubject))
|
||||
.subscribe(() => {
|
||||
this.cvsvList.checkViewportSize();
|
||||
this.psDirectiveRef.update();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this._ngOnDestroySubject) {
|
||||
this._ngOnDestroySubject.next();
|
||||
this._ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onClickHeaderMenu(event: MouseEvent, node: FlatNode) {}
|
||||
|
||||
isCheckedGroup(node: FlatNode): boolean {
|
||||
// const groupDetail = node.node.groupDetail;
|
||||
|
||||
// if (!groupDetail || groupDetail === undefined) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (groupDetail.userSeqs.length === 0) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (!!this.selectedUserList && this.selectedUserList.length > 0) {
|
||||
// let allExist = true;
|
||||
// groupDetail.userSeqs.some((seq) => {
|
||||
// if (
|
||||
// this.selectedUserList.filter((item) => item.seq === seq).length === 0
|
||||
// ) {
|
||||
// allExist = false;
|
||||
// return true;
|
||||
// }
|
||||
// });
|
||||
// return allExist;
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
isCheckableGroup(node: FlatNode): boolean {
|
||||
// if (!!this.unselectableUserList && this.unselectableUserList.length > 0) {
|
||||
// const groupDetail = node.node.groupDetail;
|
||||
// let allExist = true;
|
||||
// groupDetail.userSeqs.some((seq) => {
|
||||
// if (
|
||||
// this.unselectableUserList.filter((item) => item.seq === seq)
|
||||
// .length === 0
|
||||
// ) {
|
||||
// allExist = false;
|
||||
// return true;
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (allExist) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onChangeCheckGroup(value: boolean, node: FlatNode) {}
|
||||
|
||||
isHeader = (_: number, node: FlatNode) => 0 === node.level;
|
||||
|
||||
private refreshNodes() {
|
||||
const rootNode: ChatGroupNode[] = [];
|
||||
this.nodeMap.forEach((node) => rootNode.push(...node));
|
||||
|
||||
this.dataSource.data = rootNode;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
}
|
10
src/app/sections/chat/components/component-ui/index.ts
Normal file
10
src/app/sections/chat/components/component-ui/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { ChatListItemComponent } from './chat-list-item.component';
|
||||
import {
|
||||
ExpansionComponent,
|
||||
ExpansionNodeDirective,
|
||||
ExpansionHeaderDirective
|
||||
} from './expansion.component';
|
||||
|
||||
export const COMPONENTS = [ChatListItemComponent, ExpansionComponent];
|
||||
|
||||
export const DIRECTIVES = [ExpansionNodeDirective, ExpansionHeaderDirective];
|
15
src/app/sections/chat/components/index.ts
Normal file
15
src/app/sections/chat/components/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { SearchSectionComponent } from './search.section.component';
|
||||
import { ListSectionComponent } from './list.section.component';
|
||||
|
||||
import {
|
||||
COMPONENTS as COMPONENTS_UI,
|
||||
DIRECTIVES as DIRECTIVES_UI
|
||||
} from './component-ui';
|
||||
|
||||
export const COMPONENTS = [
|
||||
...COMPONENTS_UI,
|
||||
SearchSectionComponent,
|
||||
ListSectionComponent
|
||||
];
|
||||
|
||||
export const DIRECTIVES = [...DIRECTIVES_UI];
|
42
src/app/sections/chat/components/list.section.component.html
Normal file
42
src/app/sections/chat/components/list.section.component.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<div
|
||||
*ngIf="!!searchObj && !searchObj.isShowSearch"
|
||||
fxFlexFill
|
||||
class="list-container"
|
||||
>
|
||||
<!-- <app-chat-list-item
|
||||
*ngFor="let roomInfo of roomList"
|
||||
[roomInfo]="roomInfo"
|
||||
[roomName]="getRoomName(roomInfo)"
|
||||
defaultProfileImage="assets/images/img_nophoto_50.png"
|
||||
profileImage=""
|
||||
></app-chat-list-item> -->
|
||||
|
||||
<ucap-chat-expansion [chatGroup]="chatGroup">
|
||||
<ng-template ucapChatExpansionNode let-node>
|
||||
<app-chat-list-item
|
||||
[roomInfo]="node.roomInfo"
|
||||
[roomName]="getRoomName(node.roomInfo)"
|
||||
defaultProfileImage="assets/images/img_nophoto_50.png"
|
||||
profileImage=""
|
||||
></app-chat-list-item>
|
||||
</ng-template>
|
||||
|
||||
<ng-template ucapChatExpansionHeader let-node>
|
||||
<span class="header-buddy">
|
||||
<span>{{ node.nodeType }} {{ node.nodeType | ucapDate: 'dddd' }}</span>
|
||||
<span *ngIf="isToday(node.nodeType)">
|
||||
({{ 'room.today' | ucapI18n }})
|
||||
</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
</ucap-chat-expansion>
|
||||
</div>
|
||||
<div *ngIf="!!searchObj && searchObj.isShowSearch">
|
||||
<app-chat-list-item
|
||||
*ngFor="let roomInfo of searchRoomList"
|
||||
[roomInfo]="roomInfo"
|
||||
[roomName]="getRoomName(roomInfo)"
|
||||
defaultProfileImage="assets/images/img_nophoto_50.png"
|
||||
profileImage=""
|
||||
></app-chat-list-item>
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
.list-container {
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ListSectionComponent } from './list.section.component';
|
||||
|
||||
describe('app::sections::group::ListSectionComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [ListSectionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(ListSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'ucap-lg-web'`, () => {
|
||||
const fixture = TestBed.createComponent(ListSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(ListSectionComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||
'ucap-lg-web app is running!'
|
||||
);
|
||||
});
|
||||
});
|
300
src/app/sections/chat/components/list.section.component.ts
Normal file
300
src/app/sections/chat/components/list.section.component.ts
Normal file
|
@ -0,0 +1,300 @@
|
|||
import { Observable, Subject, combineLatest, of } from 'rxjs';
|
||||
import { filter, takeUntil, take, map, catchError } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
import { Store, select, State } from '@ngrx/store';
|
||||
|
||||
import {
|
||||
VirtualScrollStrategy,
|
||||
FixedSizeVirtualScrollStrategy,
|
||||
VIRTUAL_SCROLL_STRATEGY,
|
||||
CdkVirtualScrollViewport
|
||||
} from '@angular/cdk/scrolling';
|
||||
|
||||
import { VersionInfo2Response } from '@ucap/api-public';
|
||||
import { Company } from '@ucap/api-external';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { NodeType } from '@ucap/ng-ui-group';
|
||||
import { SessionStorageService } from '@ucap/ng-web-storage';
|
||||
import {
|
||||
LoginSelector,
|
||||
ConfigurationSelector
|
||||
} from '@ucap/ng-store-authentication';
|
||||
import { CompanySelector } from '@ucap/ng-store-organization';
|
||||
import { BuddySelector, GroupSelector } from '@ucap/ng-store-group';
|
||||
|
||||
import { AppAuthenticationService } from '@app/services/app-authentication.service';
|
||||
import { AppKey } from '@app/types/app-key.type';
|
||||
import { LoginSession } from '@app/models/login-session';
|
||||
import { QueryProtocolService } from '@ucap/ng-protocol-query';
|
||||
import {
|
||||
UserInfoSS,
|
||||
DeptSearchType,
|
||||
SSVC_TYPE_QUERY_DEPT_USER_DATA,
|
||||
DeptUserData,
|
||||
SSVC_TYPE_QUERY_DEPT_USER_RES
|
||||
} from '@ucap/protocol-query';
|
||||
import { RoomSelector } from '@ucap/ng-store-chat';
|
||||
import {
|
||||
RoomInfo,
|
||||
UserInfo as RoomUserInfo,
|
||||
UserInfoShort as RoomUserInfoShort,
|
||||
RoomType
|
||||
} from '@ucap/protocol-room';
|
||||
import {
|
||||
RoomUserMap,
|
||||
RoomUserShortMap
|
||||
} from '@ucap/ng-store-chat/lib/store/room/state';
|
||||
import { Dictionary } from '@ngrx/entity';
|
||||
import {
|
||||
TranslatePipe as OrganizationTranslate,
|
||||
TranslateService
|
||||
} from '@ucap/ng-ui-organization';
|
||||
import { I18nService } from '@ucap/ng-i18n';
|
||||
import { AppChatService } from '@app/services/app-chat.service';
|
||||
import { DateService } from '@ucap/ng-ui';
|
||||
|
||||
import moment from 'moment';
|
||||
import 'moment-timezone';
|
||||
|
||||
export class ChatVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
|
||||
constructor() {
|
||||
super(60, 150, 200); // (itemSize, minBufferPx, maxBufferPx)
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-sections-chat-list',
|
||||
templateUrl: './list.section.component.html',
|
||||
styleUrls: ['./list.section.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: VIRTUAL_SCROLL_STRATEGY,
|
||||
useClass: ChatVirtualScrollStrategy
|
||||
}
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ListSectionComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
set searchObj(obj: { isShowSearch: boolean; searchWord: string }) {
|
||||
this._searchObj = obj;
|
||||
console.log(this._searchObj);
|
||||
|
||||
if (obj.isShowSearch && obj.searchWord.localeCompare('') !== 0) {
|
||||
this.onRoomSearch(obj);
|
||||
} else {
|
||||
this._searchObj.isShowSearch = false;
|
||||
this.searchRoomList = [];
|
||||
}
|
||||
}
|
||||
|
||||
get searchObj() {
|
||||
return this._searchObj;
|
||||
}
|
||||
// tslint:disable-next-line: variable-name
|
||||
_searchObj: any;
|
||||
|
||||
loginRes: LoginResponse;
|
||||
|
||||
roomList: RoomInfo[];
|
||||
roomUsersDictionary: Dictionary<RoomUserMap>;
|
||||
roomUsersShortDictionary: Dictionary<RoomUserShortMap>;
|
||||
|
||||
searchRoomList: RoomInfo[];
|
||||
|
||||
chatGroup: { division: string; roomList: RoomInfo[] }[];
|
||||
|
||||
organizationTranslate: OrganizationTranslate;
|
||||
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
private appChatService: AppChatService,
|
||||
private dateService: DateService,
|
||||
private sessionStorageService: SessionStorageService,
|
||||
private i18nService: I18nService,
|
||||
private translateService: TranslateService,
|
||||
private store: Store<any>,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private logService: LogService
|
||||
) {
|
||||
this.translateService.setDefaultLang(this.i18nService.currentLng);
|
||||
this.translateService.use(this.i18nService.currentLng);
|
||||
this.organizationTranslate = new OrganizationTranslate(
|
||||
this.translateService,
|
||||
this.changeDetectorRef
|
||||
);
|
||||
|
||||
this.i18nService.setDefaultNamespace('chat');
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
this.store
|
||||
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||||
.subscribe((loginRes) => {
|
||||
this.loginRes = loginRes;
|
||||
});
|
||||
|
||||
this.store
|
||||
.pipe(takeUntil(this.ngOnDestroySubject), select(RoomSelector.rooms))
|
||||
.subscribe((rooms) => {
|
||||
rooms = (rooms || []).filter((info) => info.isJoinRoom);
|
||||
this.roomList = rooms;
|
||||
|
||||
// groupping.
|
||||
this.initGroup();
|
||||
|
||||
this.changeDetectorRef.detectChanges();
|
||||
});
|
||||
|
||||
this.store
|
||||
.pipe(
|
||||
takeUntil(this.ngOnDestroySubject),
|
||||
select(
|
||||
(state: any) =>
|
||||
state.chat.room.roomUsers.entities as Dictionary<RoomUserMap>
|
||||
)
|
||||
)
|
||||
.subscribe((roomUsers) => {
|
||||
this.roomUsersDictionary = roomUsers;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
});
|
||||
|
||||
this.store
|
||||
.pipe(
|
||||
takeUntil(this.ngOnDestroySubject),
|
||||
select(
|
||||
(state: any) =>
|
||||
state.chat.room.roomUsersShort.entities as Dictionary<
|
||||
RoomUserShortMap
|
||||
>
|
||||
)
|
||||
)
|
||||
.subscribe((roomUsersShort) => {
|
||||
this.roomUsersShortDictionary = roomUsersShort;
|
||||
this.changeDetectorRef.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
initGroup() {
|
||||
this.chatGroup = [];
|
||||
|
||||
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.chatGroup.findIndex(
|
||||
(info) => info.division === division
|
||||
);
|
||||
if (index > -1) {
|
||||
this.chatGroup[index] = {
|
||||
...this.chatGroup[index],
|
||||
roomList: [...this.chatGroup[index].roomList, roomInfo]
|
||||
};
|
||||
} else {
|
||||
this.chatGroup.push({
|
||||
division,
|
||||
roomList: [roomInfo]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getRoomName(roomInfo: RoomInfo): string {
|
||||
if (!roomInfo) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const roomName = this.appChatService.getRoomName(
|
||||
this.organizationTranslate,
|
||||
this.loginRes,
|
||||
roomInfo,
|
||||
this.roomUsersDictionary,
|
||||
this.roomUsersShortDictionary
|
||||
);
|
||||
|
||||
return roomName;
|
||||
}
|
||||
|
||||
isToday(date: any) {
|
||||
return this.dateService.isToday(date);
|
||||
}
|
||||
|
||||
onRoomSearch(obj: { isShowSearch: boolean; searchWord: string }) {
|
||||
const searchRoomList: RoomInfo[] = [];
|
||||
|
||||
this.roomList.forEach((roomInfo) => {
|
||||
if (roomInfo.roomName.indexOf(obj.searchWord) > -1) {
|
||||
searchRoomList.push(roomInfo);
|
||||
} else {
|
||||
const roomId = roomInfo.roomId;
|
||||
const roomUsers = !!this.roomUsersDictionary
|
||||
? this.roomUsersDictionary[roomId]
|
||||
: undefined;
|
||||
const roomUsersShort = !!this.roomUsersShortDictionary
|
||||
? this.roomUsersShortDictionary[roomId]
|
||||
: undefined;
|
||||
|
||||
let users = [];
|
||||
let existUsers = false;
|
||||
if (!!roomUsers && roomUsers.userInfos.length > 0) {
|
||||
existUsers = true;
|
||||
users = roomUsers.userInfos.filter(
|
||||
(userInfo) => userInfo.seq !== Number(this.loginRes.userSeq)
|
||||
);
|
||||
} else if (!!roomUsersShort && roomUsersShort.userInfos.length > 0) {
|
||||
existUsers = true;
|
||||
users = roomUsersShort.userInfos.filter(
|
||||
(userInfo) => userInfo.seq !== Number(this.loginRes.userSeq)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
existUsers &&
|
||||
users.filter(
|
||||
(userInfo) =>
|
||||
userInfo.name.indexOf(obj.searchWord) > -1 ||
|
||||
userInfo.nameEn.indexOf(obj.searchWord) > -1 ||
|
||||
userInfo.nameCn.indexOf(obj.searchWord) > -1
|
||||
).length > 0
|
||||
) {
|
||||
searchRoomList.push(roomInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.searchRoomList = searchRoomList;
|
||||
}
|
||||
}
|
31
src/app/sections/chat/components/list.section.strategy.ts
Normal file
31
src/app/sections/chat/components/list.section.strategy.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import {
|
||||
VirtualScrollStrategy,
|
||||
CdkVirtualScrollViewport
|
||||
} from '@angular/cdk/scrolling';
|
||||
import { distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
export class ChatGroupVirtualScrollStrategy implements VirtualScrollStrategy {
|
||||
scrolledIndexChange: Observable<number>;
|
||||
|
||||
private indexSubject = new Subject<number>();
|
||||
private viewport: CdkVirtualScrollViewport | null = null;
|
||||
|
||||
constructor() {
|
||||
this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged());
|
||||
}
|
||||
|
||||
attach(viewport: CdkVirtualScrollViewport): void {
|
||||
this.viewport = viewport;
|
||||
}
|
||||
detach(): void {
|
||||
this.indexSubject.complete();
|
||||
this.viewport = null;
|
||||
}
|
||||
onContentScrolled(): void {}
|
||||
onDataLengthChanged(): void {}
|
||||
onContentRendered(): void {}
|
||||
onRenderedOffsetChanged(): void {}
|
||||
scrollToIndex(index: number, behavior: ScrollBehavior): void {}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<div class="search-container">
|
||||
<div class="searchbox">
|
||||
<form [formGroup]="fgSearch">
|
||||
<mat-form-field floatLabel="never">
|
||||
<mat-label>{{ 'room.searchRoomByName' | ucapI18n }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
#searchWordInput
|
||||
type="text"
|
||||
maxlength="20"
|
||||
placeholder="{{ 'room.searchRoomByName' | ucapI18n }}"
|
||||
formControlName="searchInput"
|
||||
[matAutocomplete]="auto"
|
||||
(keydown.enter)="onKeyDownEnter($event, searchWordInput.value)"
|
||||
/>
|
||||
<mat-autocomplete #auto="matAutocomplete">
|
||||
<mat-option
|
||||
*ngFor="let filteredRecommendedWord of filteredRecommendedWordList"
|
||||
[value]="filteredRecommendedWord"
|
||||
>
|
||||
{{ filteredRecommendedWord }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
<button
|
||||
mat-button
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
aria-label="Clear"
|
||||
(click)="searchWordInput.value = ''; onClickCancel()"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { SearchSectionComponent } from './search.section.component';
|
||||
|
||||
describe('app::sections::group::SearchSectionComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [SearchSectionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'ucap-lg-web'`, () => {
|
||||
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(SearchSectionComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||
'ucap-lg-web app is running!'
|
||||
);
|
||||
});
|
||||
});
|
147
src/app/sections/chat/components/search.section.component.ts
Normal file
147
src/app/sections/chat/components/search.section.component.ts
Normal file
|
@ -0,0 +1,147 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectorRef,
|
||||
ChangeDetectionStrategy,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
|
||||
import { Store, select } from '@ngrx/store';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { takeUntil, debounceTime } from 'rxjs/operators';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
|
||||
import { RoomSelector } from '@ucap/ng-store-chat';
|
||||
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||||
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sections-chat-search',
|
||||
templateUrl: './search.section.component.html',
|
||||
styleUrls: ['./search.section.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SearchSectionComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
keyDownEnter = new EventEmitter<{
|
||||
searchWord: string;
|
||||
}>();
|
||||
|
||||
@Output()
|
||||
searchCancel = new EventEmitter<any>();
|
||||
|
||||
loginRes: LoginResponse;
|
||||
|
||||
fgSearch: FormGroup;
|
||||
recommendedWordList: string[];
|
||||
filteredRecommendedWordList: string[];
|
||||
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
@ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger;
|
||||
|
||||
constructor(
|
||||
private store: Store<any>,
|
||||
private formBuilder: FormBuilder,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
this.fgSearch = this.formBuilder.group({
|
||||
searchInput: null
|
||||
});
|
||||
|
||||
this.fgSearch
|
||||
.get('searchInput')
|
||||
.valueChanges.pipe(debounceTime(100))
|
||||
.subscribe((value) => {
|
||||
if (value !== null && value.length > 0) {
|
||||
this.filteredRecommendedWordList = this.recommendedWordList.filter(
|
||||
(v) => {
|
||||
return v.includes(value);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.filteredRecommendedWordList = [];
|
||||
}
|
||||
|
||||
this.changeDetectorRef.detectChanges();
|
||||
});
|
||||
|
||||
this.store
|
||||
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||||
.subscribe((loginRes) => {
|
||||
this.loginRes = loginRes;
|
||||
});
|
||||
|
||||
combineLatest([
|
||||
this.store.pipe(select(RoomSelector.rooms)),
|
||||
this.store.pipe(select(RoomSelector.roomUsers)),
|
||||
this.store.pipe(select(RoomSelector.roomUsersShort))
|
||||
])
|
||||
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||
.subscribe(([rooms, roomUsers, roomUsersShort]) => {
|
||||
rooms = rooms || [];
|
||||
roomUsers = roomUsers || [];
|
||||
roomUsersShort = roomUsersShort || [];
|
||||
|
||||
const recommendedWordList = [];
|
||||
for (const r of rooms) {
|
||||
if (!!r.roomName && '' !== r.roomName.trim()) {
|
||||
recommendedWordList.push(r.roomName);
|
||||
}
|
||||
}
|
||||
for (const ru of roomUsers) {
|
||||
for (const u of ru.userInfos) {
|
||||
if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) {
|
||||
if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) {
|
||||
recommendedWordList.push(u.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const ru of roomUsersShort) {
|
||||
for (const u of ru.userInfos) {
|
||||
if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) {
|
||||
if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) {
|
||||
recommendedWordList.push(u.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.recommendedWordList = [
|
||||
...recommendedWordList.filter(
|
||||
(item, index) => recommendedWordList.indexOf(item) === index
|
||||
)
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDownEnter(event: KeyboardEvent, searchWord: string) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.autocomplete.closePanel();
|
||||
|
||||
this.keyDownEnter.emit({ searchWord });
|
||||
}
|
||||
onClickCancel() {
|
||||
this.searchCancel.emit();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
<mat-card class="confirm-card mat-elevation-z dialog-creat-chat">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
cdkDrag
|
||||
cdkDragRootElement=".cdk-overlay-pane"
|
||||
cdkDragHandle
|
||||
>{{ data.title }}</mat-card-title
|
||||
>
|
||||
<button class="icon-button btn-dialog-close" (click)="onClickChoice(false)">
|
||||
<i class="mdi mdi-window-close"></i>
|
||||
</button>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div>
|
||||
<mat-horizontal-stepper [linear]="isLinear" #stepper>
|
||||
<mat-step label="Step 1" state="phone">
|
||||
<ucap-local-organization-select-user
|
||||
(changeUserList)="onChangeSelectedUserList($event)"
|
||||
></ucap-local-organization-select-user>
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="selectedUserListTemplate"
|
||||
></ng-template>
|
||||
<div>
|
||||
<button mat-button>취소</button>
|
||||
<button mat-button matStepperNext>
|
||||
완료
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-step label="Step 2" state="chat">
|
||||
<div>
|
||||
<mat-label>새 그룹 추가</mat-label>
|
||||
<input
|
||||
matInput
|
||||
#searchWordInput
|
||||
placeholder="그룹 이름을 입력해주세요."
|
||||
/>
|
||||
<button
|
||||
mat-button
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
aria-label="Clear"
|
||||
(click)="searchWordInput.value = ''; onClickCancel()"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="selectedUserListTemplate"
|
||||
></ng-template>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button (click)="onClickComplete(searchWordInput.value)">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</div>
|
||||
|
||||
<!-- <mat-card-actions class="button-form flex-row">
|
||||
<button
|
||||
mat-stroked-button
|
||||
(click)="onClickChoice(false)"
|
||||
class="mat-primary"
|
||||
>
|
||||
{{ 'common.messages.no' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
[disabled]="getBtnValid()"
|
||||
(click)="onClickChoice(true)"
|
||||
class="mat-primary"
|
||||
>
|
||||
{{ 'common.messages.yes' | translate }}
|
||||
</button>
|
||||
</mat-card-actions> -->
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<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>
|
|
@ -0,0 +1,26 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { CreateChatDialogComponent } from './create-chat.dialog.component';
|
||||
|
||||
describe('ucap::ui-organization::CreateChatDialogComponent', () => {
|
||||
let component: CreateChatDialogComponent;
|
||||
let fixture: ComponentFixture<CreateChatDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [CreateChatDialogComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CreateChatDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,137 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Inject,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
|
||||
import {
|
||||
UserInfoSS,
|
||||
UserInfoF,
|
||||
UserInfoDN,
|
||||
DeptInfo
|
||||
} from '@ucap/protocol-query';
|
||||
|
||||
import { Store, select } from '@ngrx/store';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import {
|
||||
CompanySelector,
|
||||
DepartmentSelector
|
||||
} from '@ucap/ng-store-organization';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { AppAuthenticationService } from '@app/services/app-authentication.service';
|
||||
import { SelectUserDialogType } from '@app/types';
|
||||
import { RoomInfo, UserInfo as RoomUserInfo } from '@ucap/protocol-room';
|
||||
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
import { environment } from '@environments';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { SelectUserSectionComponent } from '../../select-user.section.component';
|
||||
import { GroupActions } from '@ucap/ng-store-group';
|
||||
import { UserInfoTypes } from '../profile-list-item.component';
|
||||
|
||||
export interface CreateChatDialogData {
|
||||
type?: SelectUserDialogType;
|
||||
title: string;
|
||||
/** CASE :: EditMember */
|
||||
group?: GroupDetailData;
|
||||
/** CASE :: EventForward */
|
||||
ignoreRoom?: RoomInfo[];
|
||||
/** CASE :: EditChatMember */
|
||||
curRoomUser?: (
|
||||
| UserInfo
|
||||
| UserInfoSS
|
||||
| UserInfoF
|
||||
| UserInfoDN
|
||||
| RoomUserInfo
|
||||
)[];
|
||||
}
|
||||
export interface CreateChatDialogResult {}
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-local-organization-create-chat.dialog',
|
||||
templateUrl: './create-chat.dialog.component.html',
|
||||
styleUrls: ['./create-chat.dialog.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CreateChatDialogComponent implements OnInit, OnDestroy {
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
isLinear = false;
|
||||
firstFormGroup: FormGroup;
|
||||
secondFormGroup: FormGroup;
|
||||
selectedUserList: UserInfoTypes[] = [];
|
||||
SelectUserDialogType = SelectUserDialogType;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<
|
||||
CreateChatDialogData,
|
||||
CreateChatDialogResult
|
||||
>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: CreateChatDialogData,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private store: Store<any>,
|
||||
private appAuthenticationService: AppAuthenticationService
|
||||
) {}
|
||||
|
||||
@ViewChild('selectBoxUserComponent', { static: false })
|
||||
selectBoxUserComponent: SelectUserSectionComponent;
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onClickCancel() {}
|
||||
onClickChoice(s: boolean) {}
|
||||
getBtnValid() {}
|
||||
getChipsRemoveYn(userInfo: UserInfo) {}
|
||||
onClickDeleteUser(userInfo: UserInfo) {}
|
||||
|
||||
onChangeSelectedUserList(userList: UserInfoTypes[]) {
|
||||
this.selectedUserList = userList;
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
onClickComplete(groupName: string) {
|
||||
switch (this.data.type) {
|
||||
case SelectUserDialogType.NewGroup:
|
||||
{
|
||||
const userSeqs: string[] = [];
|
||||
this.selectedUserList.map((user) =>
|
||||
userSeqs.push(user.seq.toString())
|
||||
);
|
||||
this.store.dispatch(
|
||||
GroupActions.create({
|
||||
groupName,
|
||||
targetUserSeqs: userSeqs
|
||||
})
|
||||
);
|
||||
}
|
||||
break;
|
||||
case SelectUserDialogType.NewChat:
|
||||
{
|
||||
}
|
||||
break;
|
||||
case SelectUserDialogType.EditChatMember:
|
||||
{
|
||||
}
|
||||
break;
|
||||
case SelectUserDialogType.EditMember:
|
||||
{
|
||||
}
|
||||
break;
|
||||
case SelectUserDialogType.MessageForward:
|
||||
{
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import { CreateChatDialogComponent } from './create-chat.dialog.component';
|
||||
|
||||
export const DIALOGS = [CreateChatDialogComponent];
|
9
src/app/sections/group/components/component-ui/index.ts
Normal file
9
src/app/sections/group/components/component-ui/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ProfileListItemComponent } from './profile-list-item.component';
|
||||
import { ProfileComponent } from './profile.component';
|
||||
import { TenantSearchComponent } from './tenant-search.component';
|
||||
|
||||
export const COMPONENTS = [
|
||||
ProfileListItemComponent,
|
||||
ProfileComponent,
|
||||
TenantSearchComponent
|
||||
];
|
|
@ -0,0 +1,35 @@
|
|||
<div class="ucap-organization-profile-list-item-container">
|
||||
<span class="ucap-organization-profile-list-item-presence">bullet</span>
|
||||
<div class="ucap-organization-profile-list-item-profile-image">
|
||||
<img
|
||||
class="thumbnail"
|
||||
ucapImage
|
||||
[base]="profileImageRoot"
|
||||
[path]="userInfo.profileImageFile"
|
||||
[default]="defaultProfileImage"
|
||||
(click)="onClickProfileImage($event, userInfo)"
|
||||
/>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div>
|
||||
<div class="user-name">
|
||||
{{ userInfo | ucapOrganizationTranslate: 'name' }}
|
||||
</div>
|
||||
<div class="user-grade">
|
||||
{{ userInfo | ucapOrganizationTranslate: 'grade' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dept-name">
|
||||
{{ userInfo | ucapOrganizationTranslate: 'deptName' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="intro">
|
||||
{{ userInfo.intro }}
|
||||
</div>
|
||||
<div *ngIf="checkable">
|
||||
<mat-checkbox
|
||||
#checkbox
|
||||
(change)="onChangeCheck(checkbox.checked, userInfo)"
|
||||
></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
|
@ -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::ui-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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { UserInfo } from '@ucap/protocol-sync';
|
||||
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
|
||||
|
||||
export type UserInfoTypes = UserInfo | UserInfoSS | UserInfoF | UserInfoDN;
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-local-organization-profile-list-item',
|
||||
templateUrl: './profile-list-item.component.html',
|
||||
styleUrls: ['./profile-list-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ProfileListItemComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
set userInfo(user: UserInfoTypes) {
|
||||
this._userInfo = user;
|
||||
}
|
||||
get userInfo(): UserInfoTypes {
|
||||
return this._userInfo;
|
||||
}
|
||||
_userInfo: UserInfoTypes;
|
||||
|
||||
@Input()
|
||||
defaultProfileImage: string;
|
||||
|
||||
@Input()
|
||||
profileImageRoot: string;
|
||||
|
||||
@Input()
|
||||
checkable = false;
|
||||
|
||||
@Output()
|
||||
checkUser = new EventEmitter<{
|
||||
isChecked: boolean;
|
||||
userInfo: UserInfoTypes;
|
||||
}>();
|
||||
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
|
||||
onClickProfileImage(event: Event, userInfo: UserInfoTypes): void {}
|
||||
|
||||
onChangeCheck(
|
||||
value: boolean,
|
||||
userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN
|
||||
) {
|
||||
this.checkUser.emit({
|
||||
isChecked: value,
|
||||
userInfo
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<div class="mainProfile">
|
||||
<mat-card class="example-card">
|
||||
<mat-card-header>
|
||||
<div mat-card-avatar class="profileImage" style="background-size: cover;">
|
||||
<img
|
||||
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
|
||||
style="width: 50px; height: auto;"
|
||||
/>
|
||||
</div>
|
||||
<mat-card-title class="name"
|
||||
>{{ userInfo.name }}
|
||||
<span class="grade">{{ userInfo.grade }}</span></mat-card-title
|
||||
>
|
||||
<mat-card-subtitle>({{ userInfo.nameEn }})</mat-card-subtitle>
|
||||
<mat-card-subtitle><span>O</span>온라인</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<ng-container *ngIf="isMe; then isMe; else other"></ng-container>
|
||||
|
||||
<ng-template #isMe>
|
||||
<div class="intro">
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>이름 부서명, 전화번호, 이메일</mat-label>
|
||||
<input matInput placeholder="인트로" value="" />
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #other>
|
||||
<mat-card-actions>
|
||||
<button mat-button class="info" aria-label="메세지">메세지</button>
|
||||
<button mat-button class="theme" aria-label="쪽지">쪽지</button>
|
||||
<button mat-button class="theme" aria-label="휴대폰">휴대폰</button>
|
||||
<button mat-button class="theme" aria-label="콜">콜</button>
|
||||
<button mat-button class="theme" aria-label="화상회의">
|
||||
화상회의
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</ng-template>
|
||||
|
||||
<ul>
|
||||
<li class="company">
|
||||
<label>{{ 'profile.labels.company' | ucapI18n }}</label
|
||||
>{{ userInfo.companyName }}
|
||||
</li>
|
||||
<li class="dept">
|
||||
<label>{{ 'profile.labels.deptartment' | ucapI18n }}</label
|
||||
>{{ userInfo.deptName }}
|
||||
</li>
|
||||
<li class="email">
|
||||
<label>{{ 'profile.labels.email' | ucapI18n }}</label
|
||||
>{{ userInfo.email }}
|
||||
</li>
|
||||
<li class="office">
|
||||
<label>{{ 'profile.labels.officePhoneNumber' | ucapI18n }}</label
|
||||
>{{ userInfo.lineNumber }}
|
||||
</li>
|
||||
<li class="mobile">
|
||||
<label>{{ 'profile.labels.handphone' | ucapI18n }}</label
|
||||
>{{ userInfo.hpNumber }}
|
||||
</li>
|
||||
</ul>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button class="info">info</button>
|
||||
<button mat-button class="theme">theme1</button>
|
||||
<button mat-button class="theme">theme2</button>
|
||||
<button mat-button class="theme checked">theme3</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
.profile-container {
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ProfileComponent } from './profile.component';
|
||||
|
||||
describe('app::sections::group::ProfileComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [ProfileComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(ProfileComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'ucap-lg-web'`, () => {
|
||||
const fixture = TestBed.createComponent(ProfileComponent);
|
||||
const app = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(ProfileComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||
'ucap-lg-web app is running!'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,280 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { AppKey } from '@app/types/app-key.type';
|
||||
import { LoginSession } from '@app/models/login-session';
|
||||
import { Subject } from 'rxjs';
|
||||
import { UserInfoSS, AuthResponse } from '@ucap/protocol-query';
|
||||
import { OpenProfileOptions } from '@ucap/protocol-buddy';
|
||||
import { FileUploadItem } from '@ucap/api';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { WorkStatusType } from '@ucap/protocol';
|
||||
import { I18nService } from '@ucap/ng-i18n';
|
||||
|
||||
@Component({
|
||||
selector: 'app-group-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrls: ['./profile.component.scss'],
|
||||
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ProfileComponent implements OnInit, OnDestroy {
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
@Input()
|
||||
profileImageRoot: string;
|
||||
|
||||
@Input()
|
||||
isMe: boolean;
|
||||
@Input()
|
||||
isBuddy: boolean;
|
||||
@Input()
|
||||
isFavorit: boolean;
|
||||
|
||||
@Input()
|
||||
set userInfo(u: UserInfoSS) {
|
||||
this._userInfo = u;
|
||||
}
|
||||
get userInfo(): UserInfoSS {
|
||||
return this._userInfo;
|
||||
}
|
||||
_userInfo: UserInfoSS;
|
||||
@Input()
|
||||
myMadn?: string;
|
||||
@Input()
|
||||
openProfileOptions?: OpenProfileOptions;
|
||||
@Input()
|
||||
useBuddyToggleButton: boolean;
|
||||
@Input()
|
||||
authInfo: AuthResponse;
|
||||
|
||||
@Output()
|
||||
profileImageView = new EventEmitter<void>();
|
||||
@Output()
|
||||
openChat = new EventEmitter<UserInfoSS>();
|
||||
@Output()
|
||||
sendMessage = new EventEmitter<UserInfoSS>();
|
||||
@Output()
|
||||
sendCall = new EventEmitter<string>();
|
||||
@Output()
|
||||
sendSms = new EventEmitter<string>();
|
||||
@Output()
|
||||
createConference = new EventEmitter<number>();
|
||||
@Output()
|
||||
toggleFavorit = new EventEmitter<{
|
||||
userInfo: UserInfoSS;
|
||||
isFavorit: boolean;
|
||||
}>();
|
||||
@Output()
|
||||
toggleBuddy = new EventEmitter<{
|
||||
userInfo: UserInfoSS;
|
||||
isBuddy: boolean;
|
||||
}>();
|
||||
@Output()
|
||||
uploadProfileImage = new EventEmitter<FileUploadItem>();
|
||||
@Output()
|
||||
updateIntro = new EventEmitter<string>();
|
||||
|
||||
@ViewChild('profileImageFileInput', { static: false })
|
||||
profileImageFileInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
userIntroFormControl = new FormControl('');
|
||||
profileImageFileUploadItem: FileUploadItem;
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onClickProfileImageView() {
|
||||
this.profileImageView.emit();
|
||||
}
|
||||
|
||||
onClickOpenChat() {
|
||||
this.openChat.emit(this.userInfo);
|
||||
}
|
||||
|
||||
onClickCall(type: string) {
|
||||
let calleeNumber = '';
|
||||
|
||||
if (type === 'LINE') {
|
||||
calleeNumber = this.userInfo.lineNumber;
|
||||
} else {
|
||||
calleeNumber = this.userInfo.hpNumber;
|
||||
}
|
||||
this.sendCall.emit(calleeNumber);
|
||||
}
|
||||
|
||||
onClickSMS() {
|
||||
this.sendSms.emit(this.userInfo.employeeNum);
|
||||
}
|
||||
|
||||
onClickVideoConference() {
|
||||
this.createConference.emit(Number(this.userInfo.seq));
|
||||
}
|
||||
|
||||
onClickMessage() {
|
||||
this.sendMessage.emit(this.userInfo);
|
||||
}
|
||||
|
||||
onToggleFavorit() {
|
||||
this.isFavorit = !this.isFavorit;
|
||||
|
||||
this.toggleFavorit.emit({
|
||||
userInfo: this.userInfo,
|
||||
isFavorit: this.isFavorit
|
||||
});
|
||||
}
|
||||
|
||||
onClickAddBuddy() {
|
||||
this.toggleBuddy.emit({
|
||||
userInfo: this.userInfo,
|
||||
isBuddy: true
|
||||
});
|
||||
}
|
||||
|
||||
onClickDelBuddy() {
|
||||
this.toggleBuddy.emit({
|
||||
userInfo: this.userInfo,
|
||||
isBuddy: false
|
||||
});
|
||||
}
|
||||
onApplyIntroMessage(intro: string) {
|
||||
if (intro.trim().length < 1) {
|
||||
this.updateIntro.emit(' ');
|
||||
} else {
|
||||
this.updateIntro.emit(intro);
|
||||
}
|
||||
}
|
||||
|
||||
onChangeFileInput() {
|
||||
this.profileImageFileUploadItem = FileUploadItem.fromFiles(
|
||||
this.profileImageFileInput.nativeElement.files
|
||||
)[0];
|
||||
|
||||
this.uploadProfileImage.emit(this.profileImageFileUploadItem);
|
||||
|
||||
this.profileImageFileInput.nativeElement.value = '';
|
||||
}
|
||||
|
||||
getWorkstatus(userInfo: UserInfoSS): string {
|
||||
let workstatus = '';
|
||||
if (!!userInfo && !!userInfo.workstatus) {
|
||||
switch (userInfo.workstatus) {
|
||||
case WorkStatusType.VacationAM:
|
||||
workstatus = '오전';
|
||||
break;
|
||||
case WorkStatusType.VacationPM:
|
||||
workstatus = '오후';
|
||||
break;
|
||||
case WorkStatusType.VacationAll:
|
||||
workstatus = '휴가';
|
||||
break;
|
||||
case WorkStatusType.LeaveOfAbsence:
|
||||
workstatus = '휴직';
|
||||
break;
|
||||
case WorkStatusType.LongtermRefresh:
|
||||
workstatus = '장기';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return workstatus;
|
||||
}
|
||||
getWorkstatusStyle(userInfo: UserInfoSS): string {
|
||||
// morning-off: 오전 afternoon-off: 오후 day-off: 휴가 long-time: 장기 leave-of-absence: 휴직
|
||||
let style = '';
|
||||
if (!!userInfo && !!userInfo.workstatus) {
|
||||
switch (userInfo.workstatus) {
|
||||
case WorkStatusType.VacationAM:
|
||||
style = 'morning-off';
|
||||
break;
|
||||
case WorkStatusType.VacationPM:
|
||||
style = 'afternoon-off';
|
||||
break;
|
||||
case WorkStatusType.VacationAll:
|
||||
style = 'day-off';
|
||||
break;
|
||||
case WorkStatusType.LeaveOfAbsence:
|
||||
style = 'leave-of-absence';
|
||||
break;
|
||||
case WorkStatusType.LongtermRefresh:
|
||||
style = 'long-time';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
getDisabledBtn(type: string): boolean {
|
||||
if (!this.myMadn || this.myMadn.trim().length === 0) {
|
||||
if (type === 'LINE' || type === 'MOBILE') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'LINE') {
|
||||
if (
|
||||
!!this.userInfo &&
|
||||
!!this.userInfo.lineNumber &&
|
||||
this.userInfo.lineNumber.trim().length > 0
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else if (type === 'MOBILE') {
|
||||
if (
|
||||
!!this.userInfo &&
|
||||
!!this.userInfo.hpNumber &&
|
||||
this.userInfo.hpNumber.trim().length > 0
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else if (type === 'SMS') {
|
||||
// const smsUtils = new SmsUtils(
|
||||
// this.sessionStorageService,
|
||||
// this.nativeService
|
||||
// );
|
||||
// return !smsUtils.getAuthSms();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getShowBuddyToggleBtn(type: 'DEL' | 'ADD'): boolean {
|
||||
let rtn = false;
|
||||
if (!this.useBuddyToggleButton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type === 'ADD') {
|
||||
if (!this.isBuddy) {
|
||||
rtn = true;
|
||||
}
|
||||
} else if (type === 'DEL') {
|
||||
if (!!this.isBuddy) {
|
||||
rtn = true;
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<div class="search-container">
|
||||
<div class="selectbox">
|
||||
<mat-select [(value)]="companyCode" disableOptionCentering>
|
||||
<mat-option
|
||||
*ngFor="let company of companyList"
|
||||
[value]="company.companyCode"
|
||||
>{{ company.companyName }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
<div class="searchbox">
|
||||
<mat-form-field>
|
||||
<mat-label>이름 부서명, 전화번호, 이메일</mat-label>
|
||||
<input
|
||||
matInput
|
||||
#searchWordInput
|
||||
placeholder="이름 부서명, 전화번호, 이메일"
|
||||
(keydown.enter)="onKeyDownEnter(searchWordInput.value)"
|
||||
/>
|
||||
<button
|
||||
mat-button
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
aria-label="Clear"
|
||||
(click)="searchWordInput.value = ''; onClickCancel()"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { TenantSearchComponent } from './tenant-search.component';
|
||||
|
||||
describe('ucap::ui-organization::TenantSearchComponent', () => {
|
||||
let component: TenantSearchComponent;
|
||||
let fixture: ComponentFixture<TenantSearchComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TenantSearchComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TenantSearchComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,73 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { UserInfo } from '@ucap/protocol-sync';
|
||||
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
|
||||
|
||||
import { Store, select } from '@ngrx/store';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { CompanySelector } from '@ucap/ng-store-organization';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Company } from '@ucap/api-external';
|
||||
import { AppAuthenticationService } from '@app/services/app-authentication.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-local-organization-tenant-search',
|
||||
templateUrl: './tenant-search.component.html',
|
||||
styleUrls: ['./tenant-search.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TenantSearchComponent implements OnInit, OnDestroy {
|
||||
companyList: Company[];
|
||||
companyCode: string;
|
||||
|
||||
@Output()
|
||||
keyDownEnter = new EventEmitter<{
|
||||
companyCode: string;
|
||||
searchWord: string;
|
||||
}>();
|
||||
|
||||
@Output()
|
||||
searchCancel = new EventEmitter<any>();
|
||||
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private store: Store<any>,
|
||||
private appAuthenticationService: AppAuthenticationService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
const userStore = this.appAuthenticationService.getUserStore();
|
||||
this.companyCode = userStore.companyCode;
|
||||
|
||||
this.store
|
||||
.pipe(
|
||||
takeUntil(this.ngOnDestroySubject),
|
||||
select(CompanySelector.companyList)
|
||||
)
|
||||
.subscribe((companyList) => {
|
||||
this.companyList = companyList;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDownEnter(searchWord: string) {
|
||||
this.keyDownEnter.emit({ companyCode: this.companyCode, searchWord });
|
||||
}
|
||||
onClickCancel() {
|
||||
this.searchCancel.emit();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,19 @@
|
|||
import { ListSectionComponent } from './list.section.component';
|
||||
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';
|
||||
|
||||
export const COMPONENTS = [ListSectionComponent, SearchSectionComponent];
|
||||
import { COMPONENTS as COMPONENTS_UI } from './component-ui';
|
||||
import { DIALOGS as DIALOGS_UI } from './component-ui/dialogs';
|
||||
|
||||
export const COMPONENTS = [
|
||||
...COMPONENTS_UI,
|
||||
ListSectionComponent,
|
||||
SearchSectionComponent,
|
||||
ProfileSectionComponent,
|
||||
InfoSectionComponent,
|
||||
SelectUserSectionComponent
|
||||
];
|
||||
|
||||
export const DIALOGS = [...DIALOGS_UI];
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<div class="info">
|
||||
<ng-container *ngIf="!!isMe; then myInfo; else otherInfo"> </ng-container>
|
||||
<ng-template #myInfo>
|
||||
<div class="bookmark">
|
||||
<div class="subtitle">Bookmark</div>
|
||||
<div class="chatlist">
|
||||
<!-- loop > component > 대화 리스트 공용 -->
|
||||
<div>
|
||||
<div class="profileImage">
|
||||
<img
|
||||
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
|
||||
style="width: 50px; height: 50px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="roomName">UCAP 프로젝트방</div>
|
||||
<div class="lastMessage">
|
||||
대화방의 마지막대화내용이 들어갈껍니다.
|
||||
</div>
|
||||
</div>
|
||||
<div class="subInfo">
|
||||
<div class="lastDate" matBadge="4">2020.04.05</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--// loop > component > 대화 리스트 공용 -->
|
||||
<div>
|
||||
<div class="profileImage">
|
||||
<img
|
||||
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
|
||||
style="width: 50px; height: 50px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="roomName">UCAP 프로젝트방</div>
|
||||
<div class="lastMessage">
|
||||
대화방의 마지막대화내용이 들어갈껍니다.
|
||||
</div>
|
||||
</div>
|
||||
<div class="subInfo">
|
||||
<div class="lastDate">2020.04.05</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="allim">
|
||||
<div class="subtitle">알림봇</div>
|
||||
<div class="allimList">
|
||||
<mat-card class="allim-card">
|
||||
<mat-card-header>
|
||||
<div
|
||||
mat-card-avatar
|
||||
class="profileImage"
|
||||
style="background-size: cover;"
|
||||
>
|
||||
<img
|
||||
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
|
||||
style="width: 50px; height: auto;"
|
||||
/>
|
||||
</div>
|
||||
<mat-card-title>화상회의</mat-card-title>
|
||||
<mat-card-subtitle>2020.04.05</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div class="title">화상회의 개설</div>
|
||||
<div class="contents">화상회의가 개설되었습니다.</div>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button class="more">더보기</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
<mat-card class="allim-card">
|
||||
<mat-card-header>
|
||||
<div
|
||||
mat-card-avatar
|
||||
class="profileImage"
|
||||
style="background-size: cover;"
|
||||
>
|
||||
<img
|
||||
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
|
||||
style="width: 50px; height: auto;"
|
||||
/>
|
||||
</div>
|
||||
<mat-card-title>화상회의</mat-card-title>
|
||||
<mat-card-subtitle>2020.04.05</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div class="title">화상회의 개설</div>
|
||||
<div class="contents">화상회의가 개설되었습니다.</div>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button class="more">더보기</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #otherInfo> </ng-template>
|
||||
<div class="banner">배너</div>
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
.info-container {
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { InfoSectionComponent } from './info.section.component';
|
||||
|
||||
describe('app::sections::group::InfoSectionComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [InfoSectionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(InfoSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'ucap-lg-web'`, () => {
|
||||
const fixture = TestBed.createComponent(InfoSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(InfoSectionComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||
'ucap-lg-web app is running!'
|
||||
);
|
||||
});
|
||||
});
|
58
src/app/sections/group/components/info.section.component.ts
Normal file
58
src/app/sections/group/components/info.section.component.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
|
||||
import { AppKey } from '@app/types/app-key.type';
|
||||
import { LoginSession } from '@app/models/login-session';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Store, select } from '@ngrx/store';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sections-group-info',
|
||||
templateUrl: './info.section.component.html',
|
||||
styleUrls: ['./info.section.component.scss'],
|
||||
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class InfoSectionComponent implements OnInit, OnDestroy {
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
@Input()
|
||||
userSeq: string;
|
||||
@Input()
|
||||
isMe: boolean;
|
||||
|
||||
loginRes: LoginResponse;
|
||||
|
||||
constructor(private store: Store<any>) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store
|
||||
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||||
.subscribe((loginRes) => {
|
||||
this.loginRes = loginRes;
|
||||
|
||||
if (
|
||||
(!!this.userSeq &&
|
||||
this.userSeq.localeCompare(loginRes.userSeq) === 0) ||
|
||||
!this.userSeq
|
||||
) {
|
||||
this.isMe = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,59 @@
|
|||
<div fxFlexFill class="list-container">
|
||||
<ucap-group-expansion-list></ucap-group-expansion-list>
|
||||
<div
|
||||
*ngIf="!!searchObj && !searchObj.isShowSearch"
|
||||
fxFlexFill
|
||||
class="list-container"
|
||||
>
|
||||
<ucap-group-expansion
|
||||
[displayOrder]="displayOrder"
|
||||
[profile]="loginRes?.userInfo"
|
||||
[favorites]="favorites"
|
||||
[groupBuddies]="groupBuddies"
|
||||
>
|
||||
<ng-template ucapGroupExpansionNode let-node>
|
||||
<ucap-local-organization-profile-list-item
|
||||
[userInfo]="node.userInfo"
|
||||
defaultProfileImage="assets/images/img_nophoto_50.png"
|
||||
[profileImageRoot]="versionInfo2Res?.profileRoot"
|
||||
[checkable]="checkable"
|
||||
(checkUser)="onCheckUser($event)"
|
||||
></ucap-local-organization-profile-list-item>
|
||||
</ng-template>
|
||||
|
||||
<ng-template ucapGroupExpansionFavoriteHeader let-node>
|
||||
<span class="header-favorite">
|
||||
<span>
|
||||
{{ 'category.favorite' | ucapI18n }}
|
||||
</span>
|
||||
<span>{{ node.children?.length }}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template ucapGroupExpansionBuddyHeader let-node>
|
||||
<span class="header-buddy">
|
||||
<span>{{ node.groupDetail.name }}</span>
|
||||
<span>
|
||||
{{ node.children?.length }}
|
||||
</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template ucapGroupExpansionDefaultHeader let-node>
|
||||
<span class="header-default">
|
||||
<span>
|
||||
{{ 'category.default' | ucapI18n }}
|
||||
</span>
|
||||
<span>{{ node.children?.length }}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
</ucap-group-expansion>
|
||||
</div>
|
||||
<div *ngIf="!!searchObj && searchObj.isShowSearch" class="search-wrpper">
|
||||
<perfect-scrollbar fxFlex="1 1 auto">
|
||||
<ucap-local-organization-profile-list-item
|
||||
*ngFor="let userInfo of searchUserInfos"
|
||||
[userInfo]="userInfo"
|
||||
[checkable]="checkable"
|
||||
defaultProfileImage="assets/images/img_nophoto_50.png"
|
||||
(checkUser)="onCheckUser($event)"
|
||||
>
|
||||
</ucap-local-organization-profile-list-item>
|
||||
</perfect-scrollbar>
|
||||
</div>
|
||||
|
|
|
@ -1,2 +1,7 @@
|
|||
.list-container {
|
||||
}
|
||||
.search-wrpper {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
height: 350px;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
import { Observable } from 'rxjs';
|
||||
import { Observable, Subject, combineLatest, of } from 'rxjs';
|
||||
import { filter, takeUntil, take, map, catchError } from 'rxjs/operators';
|
||||
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
ViewChild,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
import { Store, select } from '@ngrx/store';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import {
|
||||
VirtualScrollStrategy,
|
||||
FixedSizeVirtualScrollStrategy,
|
||||
|
@ -10,6 +22,34 @@ import {
|
|||
CdkVirtualScrollViewport
|
||||
} from '@angular/cdk/scrolling';
|
||||
|
||||
import { VersionInfo2Response } from '@ucap/api-public';
|
||||
import { Company } from '@ucap/api-external';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { NodeType } from '@ucap/ng-ui-group';
|
||||
import { SessionStorageService } from '@ucap/ng-web-storage';
|
||||
import {
|
||||
LoginSelector,
|
||||
ConfigurationSelector
|
||||
} from '@ucap/ng-store-authentication';
|
||||
import { CompanySelector } from '@ucap/ng-store-organization';
|
||||
import { BuddySelector, GroupSelector } from '@ucap/ng-store-group';
|
||||
|
||||
import { AppAuthenticationService } from '@app/services/app-authentication.service';
|
||||
import { AppKey } from '@app/types/app-key.type';
|
||||
import { LoginSession } from '@app/models/login-session';
|
||||
import { QueryProtocolService } from '@ucap/ng-protocol-query';
|
||||
import {
|
||||
UserInfoSS,
|
||||
DeptSearchType,
|
||||
SSVC_TYPE_QUERY_DEPT_USER_DATA,
|
||||
DeptUserData,
|
||||
SSVC_TYPE_QUERY_DEPT_USER_RES
|
||||
} from '@ucap/protocol-query';
|
||||
import { UserInfoTypes } from './component-ui/profile-list-item.component';
|
||||
|
||||
export class GroupVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
|
||||
constructor() {
|
||||
super(60, 150, 200); // (itemSize, minBufferPx, maxBufferPx)
|
||||
|
@ -25,12 +65,220 @@ export class GroupVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
|
|||
provide: VIRTUAL_SCROLL_STRATEGY,
|
||||
useClass: GroupVirtualScrollStrategy
|
||||
}
|
||||
]
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ListSectionComponent implements OnInit, OnDestroy {
|
||||
constructor(private logService: LogService) {}
|
||||
@Input()
|
||||
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.searchUserInfos = [];
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
get searchObj() {
|
||||
return this._searchObj;
|
||||
}
|
||||
_searchObj: any;
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
@Input()
|
||||
set checkable(check: boolean) {
|
||||
console.log(check);
|
||||
this._checkable = check;
|
||||
}
|
||||
get checkable(): boolean {
|
||||
return this._checkable;
|
||||
}
|
||||
_checkable = false;
|
||||
|
||||
@Output()
|
||||
checkUser = new EventEmitter<{
|
||||
isChecked: boolean;
|
||||
userInfo: UserInfoTypes;
|
||||
}>();
|
||||
|
||||
loginSession: LoginSession;
|
||||
versionInfo2Res: VersionInfo2Response;
|
||||
loginRes: LoginResponse;
|
||||
companyList: Company[];
|
||||
|
||||
searchUserInfos: UserInfoSS[] = [];
|
||||
|
||||
displayOrder: NodeType[] = [
|
||||
NodeType.Profile,
|
||||
NodeType.Favorite,
|
||||
NodeType.Buddy,
|
||||
NodeType.Default
|
||||
];
|
||||
|
||||
profile: UserInfo;
|
||||
favorites: UserInfo[];
|
||||
groupBuddies: { group: GroupDetailData; buddyList: UserInfo[] }[];
|
||||
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
private appAuthenticationService: AppAuthenticationService,
|
||||
private sessionStorageService: SessionStorageService,
|
||||
private store: Store<any>,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private logService: LogService,
|
||||
private queryProtocolService: QueryProtocolService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
this.appAuthenticationService
|
||||
.getLoginSession$()
|
||||
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||
.subscribe((loginSession) => (this.loginSession = loginSession));
|
||||
|
||||
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.store
|
||||
.pipe(
|
||||
takeUntil(this.ngOnDestroySubject),
|
||||
select(CompanySelector.companyList)
|
||||
)
|
||||
.subscribe((companyList) => {
|
||||
this.companyList = companyList;
|
||||
});
|
||||
|
||||
combineLatest([
|
||||
this.store.pipe(select(BuddySelector.buddies)),
|
||||
this.store.pipe(select(GroupSelector.groups))
|
||||
])
|
||||
.pipe(takeUntil(this.ngOnDestroySubject))
|
||||
.subscribe(([buddies, groups]) => {
|
||||
buddies = buddies || [];
|
||||
groups = groups || [];
|
||||
|
||||
const favorites = buddies
|
||||
.filter((buddy) => buddy.isFavorit)
|
||||
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
||||
|
||||
if (!!favorites && 0 < favorites.length) {
|
||||
this.favorites = favorites;
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
const tempOrder: GroupDetailData[] = [];
|
||||
let defaultGroup: GroupDetailData;
|
||||
const buddyGroup: GroupDetailData[] = [];
|
||||
groups.forEach((group) => {
|
||||
if (0 === group.seq) {
|
||||
defaultGroup = group;
|
||||
} else {
|
||||
buddyGroup.push(group);
|
||||
}
|
||||
});
|
||||
|
||||
tempOrder.push(
|
||||
...buddyGroup.sort((a, b) =>
|
||||
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
||||
)
|
||||
);
|
||||
|
||||
if (!!defaultGroup) {
|
||||
tempOrder.push(defaultGroup);
|
||||
}
|
||||
|
||||
groups = tempOrder;
|
||||
|
||||
if (!!groups && 0 < groups.length) {
|
||||
this.groupBuddies = [];
|
||||
for (const group of groups) {
|
||||
this.groupBuddies.push({
|
||||
group,
|
||||
buddyList: buddies.filter((buddy) => {
|
||||
return -1 < group.userSeqs.indexOf(String(buddy.seq));
|
||||
})
|
||||
});
|
||||
}
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onOrganizationTenantSearch(obj: {
|
||||
isShowSearch: boolean;
|
||||
companyCode: string;
|
||||
searchWord: string;
|
||||
}) {
|
||||
const searchUserInfos: UserInfoSS[] = [];
|
||||
|
||||
this.queryProtocolService
|
||||
.deptUser({
|
||||
divCd: 'GRP',
|
||||
companyCode: this._searchObj.companyCode,
|
||||
searchRange: DeptSearchType.All,
|
||||
search: this._searchObj.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();
|
||||
}
|
||||
|
||||
onCheckUser(params: { isChecked: boolean; userInfo: UserInfoTypes }) {
|
||||
this.checkUser.emit(params);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div fxFlexFill class="profile-container">
|
||||
<app-group-profile
|
||||
[isMe]="isMe"
|
||||
[userInfo]="loginRes?.userInfo"
|
||||
></app-group-profile>
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
.profile-container {
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ProfileSectionComponent } from './profile.section.component';
|
||||
|
||||
describe('app::sections::group::ProfileSectionComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [ProfileSectionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(ProfileSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'ucap-lg-web'`, () => {
|
||||
const fixture = TestBed.createComponent(ProfileSectionComponent);
|
||||
const app = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(ProfileSectionComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain(
|
||||
'ucap-lg-web app is running!'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input
|
||||
} from '@angular/core';
|
||||
|
||||
import { AppKey } from '@app/types/app-key.type';
|
||||
import { LoginSession } from '@app/models/login-session';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Store, select } from '@ngrx/store';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sections-group-profile',
|
||||
templateUrl: './profile.section.component.html',
|
||||
styleUrls: ['./profile.section.component.scss'],
|
||||
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ProfileSectionComponent implements OnInit, OnDestroy {
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
|
||||
@Input()
|
||||
userSeq: string;
|
||||
loginRes: LoginResponse;
|
||||
isMe = false;
|
||||
|
||||
constructor(private store: Store<any>) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store
|
||||
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||||
.subscribe((loginRes) => {
|
||||
this.loginRes = loginRes;
|
||||
|
||||
if (
|
||||
!!this.userSeq &&
|
||||
this.userSeq.localeCompare(loginRes.userSeq) === 0
|
||||
) {
|
||||
this.isMe = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
<div>
|
||||
search section of group
|
||||
</div>
|
||||
<ucap-local-organization-tenant-search
|
||||
(keyDownEnter)="onKeyDownEnter($event)"
|
||||
(searchCancel)="onClickCancel()"
|
||||
>
|
||||
</ucap-local-organization-tenant-search>
|
||||
|
|
|
@ -1,16 +1,44 @@
|
|||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ChangeDetectionStrategy
|
||||
} from '@angular/core';
|
||||
|
||||
import { LogService } from '@ucap/ng-logger';
|
||||
import { I18nService } from '@ucap/ng-i18n';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sections-group-search',
|
||||
templateUrl: './search.section.component.html',
|
||||
styleUrls: ['./search.section.component.scss']
|
||||
styleUrls: ['./search.section.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SearchSectionComponent implements OnInit, OnDestroy {
|
||||
constructor(private logService: LogService) {}
|
||||
@Output()
|
||||
keyDownEnter = new EventEmitter<{
|
||||
companyCode: string;
|
||||
searchWord: string;
|
||||
}>();
|
||||
|
||||
@Output()
|
||||
searchCancel = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
|
||||
onKeyDownEnter(params: { companyCode: string; searchWord: string }) {
|
||||
this.keyDownEnter.emit(params);
|
||||
}
|
||||
onClickCancel() {
|
||||
this.searchCancel.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<div fxFlexFill>
|
||||
<div fxFlex class="container">
|
||||
<!-- search start-->
|
||||
<ucap-local-organization-tenant-search
|
||||
(keyDownEnter)="onKeyDownEnter($event)"
|
||||
(searchCancel)="onClickCancel()"
|
||||
></ucap-local-organization-tenant-search>
|
||||
<!-- search end-->
|
||||
|
||||
<mat-tab-group mat-stretch-tabs class="tap-container">
|
||||
<!--그룹-->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<!-- <button class="icon-button">
|
||||
<i class="mid mid-24 mdi-account-multiple"></i>
|
||||
</button> -->
|
||||
<p>그룹</p>
|
||||
</ng-template>
|
||||
<div fxFlexFill>
|
||||
<div class="mat-tab-frame dialog-tab-grouplist">
|
||||
<app-sections-group-list
|
||||
fxFlexFill
|
||||
[searchObj]="searchObj"
|
||||
[checkable]="true"
|
||||
(checkUser)="onCheckUser($event)"
|
||||
></app-sections-group-list>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<!--조직-->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<!-- <button class="icon-button">
|
||||
<i class="mid mid-24 mdi-account-multiple"></i>
|
||||
</button> -->
|
||||
<p>조직도</p>
|
||||
</ng-template>
|
||||
<div fxFlexFill>
|
||||
<!-- <div class="mat-tab-frame dialog-tab-grouplist">
|
||||
<app-sections-organization-tree></app-sections-organization-tree>
|
||||
</div> -->
|
||||
<div>
|
||||
<span *ngFor="let breadcrumb of breadcrumbs">
|
||||
<button mat-raised-button>{{ breadcrumb.label }}</button>
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div class="organization-tree">
|
||||
<app-sections-organization-tree></app-sections-organization-tree>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
.container {
|
||||
overflow: hidden;
|
||||
.tap-container {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.organization-tree {
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { SelectUserSectionComponent } from './select-user.section.component';
|
||||
|
||||
describe('ucap::ui-organization::SelectUserSectionComponent', () => {
|
||||
let component: SelectUserSectionComponent;
|
||||
let fixture: ComponentFixture<SelectUserSectionComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SelectUserSectionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SelectUserSectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,107 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Output,
|
||||
EventEmitter
|
||||
} from '@angular/core';
|
||||
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
|
||||
import {
|
||||
UserInfoSS,
|
||||
UserInfoF,
|
||||
UserInfoDN,
|
||||
DeptInfo
|
||||
} from '@ucap/protocol-query';
|
||||
|
||||
import { Store, select } from '@ngrx/store';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import {
|
||||
CompanySelector,
|
||||
DepartmentSelector
|
||||
} from '@ucap/ng-store-organization';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { AppAuthenticationService } from '@app/services/app-authentication.service';
|
||||
import { SelectUserDialogType } from '@app/types';
|
||||
import { RoomInfo, UserInfo as RoomUserInfo } from '@ucap/protocol-room';
|
||||
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||||
import { LoginResponse } from '@ucap/protocol-authentication';
|
||||
import { environment } from '@environments';
|
||||
import { UserInfoTypes } from './component-ui/profile-list-item.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-local-organization-select-user',
|
||||
templateUrl: './select-user.section.component.html',
|
||||
styleUrls: ['./select-user.section.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SelectUserSectionComponent implements OnInit, OnDestroy {
|
||||
breadcrumbs: any = [
|
||||
{
|
||||
label: 'LGCNS'
|
||||
},
|
||||
{
|
||||
label: 'IT Helpdesk'
|
||||
},
|
||||
{
|
||||
label: 'Issue Log'
|
||||
}
|
||||
];
|
||||
|
||||
@Output()
|
||||
changeUserList = new EventEmitter<UserInfoTypes[]>();
|
||||
|
||||
searchObj: any = {
|
||||
isShowSearch: false,
|
||||
companyCode: '',
|
||||
searchWord: ''
|
||||
};
|
||||
SelectUserDialogType = SelectUserDialogType;
|
||||
|
||||
selectedUserList: UserInfoTypes[] = [];
|
||||
|
||||
private ngOnDestroySubject = new Subject<boolean>();
|
||||
constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private store: Store<any>,
|
||||
private appAuthenticationService: AppAuthenticationService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ngOnDestroySubject = new Subject<boolean>();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDownEnter(params: { companyCode: string; searchWord: string }) {
|
||||
this.searchObj = {
|
||||
isShowSearch: true,
|
||||
companyCode: params.companyCode,
|
||||
searchWord: params.searchWord
|
||||
};
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
onClickCancel() {
|
||||
this.searchObj = {
|
||||
isShowSearch: false,
|
||||
companyCode: '',
|
||||
searchWord: ''
|
||||
};
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
onCheckUser(params: { isChecked: boolean; userInfo: UserInfoTypes }) {
|
||||
console.log(params);
|
||||
this.selectedUserList = [...this.selectedUserList, params.userInfo];
|
||||
this.changeUserList.emit(this.selectedUserList);
|
||||
}
|
||||
|
||||
getSelectedUserList(): UserInfoTypes[] {
|
||||
return this.selectedUserList;
|
||||
}
|
||||
}
|
|
@ -1,27 +1,60 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
|
||||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
||||
|
||||
import { UiModule } from '@ucap/ng-ui';
|
||||
import { GroupUiModule } from '@ucap/ng-ui-group';
|
||||
|
||||
import { COMPONENTS } from './components';
|
||||
import { COMPONENTS, DIALOGS } from './components';
|
||||
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||
import { AppOrganizationSectionModule } from '@app/sections/organization/organization.section.module';
|
||||
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
FlexLayoutModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatTabsModule,
|
||||
MatChipsModule,
|
||||
MatButtonModule,
|
||||
MatStepperModule,
|
||||
PerfectScrollbarModule,
|
||||
I18nModule,
|
||||
UiModule,
|
||||
AppOrganizationSectionModule,
|
||||
OrganizationUiModule,
|
||||
GroupUiModule
|
||||
],
|
||||
exports: [...COMPONENTS],
|
||||
declarations: [...COMPONENTS],
|
||||
entryComponents: [],
|
||||
exports: [...COMPONENTS, ...DIALOGS],
|
||||
declarations: [...COMPONENTS, ...DIALOGS],
|
||||
entryComponents: [...DIALOGS],
|
||||
providers: [
|
||||
{
|
||||
provide: UCAP_I18N_NAMESPACE,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const COMPONENTS = [];
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user