조직도 UX 변경

This commit is contained in:
leejinho 2020-03-03 17:44:02 +09:00
parent 3393265b74
commit 3064213ed5
31 changed files with 941 additions and 28 deletions

View File

@ -1,21 +1,21 @@
import { IntroComponent } from './intro.component';
import { LeftNaviComponent } from './left-nav.component'; import { LeftNaviComponent } from './left-nav.component';
import { LeftSideComponent } from './left-side.component'; import { LeftSideComponent } from './left-side.component';
import { MessagesComponent } from './messages.component'; import { MainContentsComponent } from './main-contents.component';
import { RightSideComponent } from './right-side.component'; import { RightSideComponent } from './right-side.component';
import { RightDrawerComponent } from './right-drawer.component'; import { RightDrawerComponent } from './right-drawer.component';
import { LEFT_SIDENAV_COMPONENTS } from './left-sidenav'; import { LEFT_SIDENAV_COMPONENTS } from './left-sidenav';
import { MAIN_CONTENTS_COMPONENTS } from './main-contents';
import { RIGHT_DRAWER_COMPONENTS } from './right-drawer'; import { RIGHT_DRAWER_COMPONENTS } from './right-drawer';
export const COMPONENTS = [ export const COMPONENTS = [
IntroComponent,
LeftNaviComponent, LeftNaviComponent,
LeftSideComponent, LeftSideComponent,
MessagesComponent, MainContentsComponent,
RightSideComponent, RightSideComponent,
RightDrawerComponent, RightDrawerComponent,
...LEFT_SIDENAV_COMPONENTS, ...LEFT_SIDENAV_COMPONENTS,
...MAIN_CONTENTS_COMPONENTS,
...RIGHT_DRAWER_COMPONENTS ...RIGHT_DRAWER_COMPONENTS
]; ];

View File

@ -27,7 +27,10 @@
MainMenu.Organization === currentTabLable ? 'block' : 'none' MainMenu.Organization === currentTabLable ? 'block' : 'none'
" "
> >
<app-layout-chat-left-sidenav-organization <app-layout-chat-left-sidenav-organization-tree
class="organization-side"
></app-layout-chat-left-sidenav-organization-tree>
<!-- <app-layout-chat-left-sidenav-organization
[selectedUserList]="selectedUserList" [selectedUserList]="selectedUserList"
[isVisible]="currentTabLable === MainMenu.Organization" [isVisible]="currentTabLable === MainMenu.Organization"
(checkAllUser)="onCheckAllUser($event)" (checkAllUser)="onCheckAllUser($event)"
@ -39,7 +42,7 @@
(toggleUser)="onToggleUser($event)" (toggleUser)="onToggleUser($event)"
(resetSelectedUserList)="onResetSelectedUserList($event)" (resetSelectedUserList)="onResetSelectedUserList($event)"
class="organization-side" class="organization-side"
></app-layout-chat-left-sidenav-organization> ></app-layout-chat-left-sidenav-organization> -->
</div> </div>
<div <div
#tabs #tabs

View File

@ -1,6 +1,7 @@
import { CallComponent } from './call.component'; import { CallComponent } from './call.component';
import { ChatComponent } from './chat.component'; import { ChatComponent } from './chat.component';
import { GroupComponent } from './group.component'; import { GroupComponent } from './group.component';
import { OrganizationTreeComponent } from './organization-tree.component';
import { OrganizationComponent } from './organization.component'; import { OrganizationComponent } from './organization.component';
import { MessageBoxComponent } from './message.component'; import { MessageBoxComponent } from './message.component';
@ -8,6 +9,7 @@ export const LEFT_SIDENAV_COMPONENTS = [
CallComponent, CallComponent,
ChatComponent, ChatComponent,
GroupComponent, GroupComponent,
OrganizationTreeComponent,
OrganizationComponent, OrganizationComponent,
MessageBoxComponent MessageBoxComponent
]; ];

View File

@ -0,0 +1,23 @@
<div>
<div class="current-head">
<h3>{{ 'organization.chart' | translate }}</h3>
</div>
<ucap-organization-tenant-search
[companyList]="companyList$ | async"
[companyCode]="companyCode"
(keyDownEnter)="onKeyDownEnterOrganizationTenantSearch($event)"
(cancel)="onClickCancel()"
>
</ucap-organization-tenant-search>
</div>
<div class="oraganization-tab">
<div class="oraganization-tab-tree">
<ucap-organization-tree
[oraganizationList]="departmentInfoList$ | async"
[loginRes]="loginRes"
[activate$]="organizationTreeActivatedSubject.asObservable()"
(selected)="onSelectedOrganization($event)"
class="tab-tree-frame"
></ucap-organization-tree>
</div>
</div>

View File

@ -0,0 +1,44 @@
@charset 'utf-8';
@mixin ellipsis($row) {
overflow: hidden;
text-overflow: ellipsis;
@if $row == 1 {
display: block;
white-space: nowrap;
word-wrap: normal;
} @else if $row >= 2 {
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
word-wrap: break-word;
}
}
.current-head {
h3 {
display: inline-flex;
padding-left: 10px;
align-items: center;
width: 100%;
}
.btn-box {
height: 100%;
margin-left: auto;
display: inline-flex;
align-items: center;
svg {
stroke: #333333;
}
}
}
.oraganization-tab {
// height: calc(100% - 120px);
flex-direction: inherit;
display: flex;
.oraganization-tab-tree {
height: 100%;
overflow-y: auto;
}
}

View File

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

View File

@ -0,0 +1,171 @@
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { ucapAnimations, DialogService } from '@ucap-webmessenger/ui';
import { Observable, Subscription, BehaviorSubject, combineLatest } from 'rxjs';
import {
DeptInfo,
QueryProtocolService,
DeptSearchType,
UserInfoSS
} from '@ucap-webmessenger/protocol-query';
import { Store, select } from '@ngrx/store';
import { NGXLogger } from 'ngx-logger';
import * as AppStore from '@app/store';
import * as QueryStore from '@app/store/messenger/query';
import * as StatusStore from '@app/store/messenger/status';
import { SessionStorageService } from '@ucap-webmessenger/web-storage';
import { LoginInfo, KEY_LOGIN_INFO, MainMenu } from '@app/types';
import { take, tap } from 'rxjs/operators';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { Company } from '@ucap-webmessenger/api-external';
import { TranslateService } from '@ngx-translate/core';
import { OrganizationService } from '@ucap-webmessenger/ui-organization';
@Component({
selector: 'app-layout-chat-left-sidenav-organization-tree',
templateUrl: './organization-tree.component.html',
styleUrls: ['./organization-tree.component.scss'],
animations: ucapAnimations
})
export class OrganizationTreeComponent implements OnInit, OnDestroy {
companyList$: Observable<Company[]>;
companyCode: string;
departmentInfoList$: Observable<DeptInfo[]>;
myDepartmentUserInfoListSubscription: Subscription;
loginInfo: LoginInfo;
loginRes: LoginResponse;
loginResSubscription: Subscription;
isShowSearch = false;
searchUserInfos: UserInfoSS[] = [];
organizationTreeActivatedSubject: BehaviorSubject<
boolean
> = new BehaviorSubject<boolean>(false);
organizationTreeActivatedSubscription: Subscription;
constructor(
private store: Store<any>,
private sessionStorageService: SessionStorageService,
private queryProtocolService: QueryProtocolService,
private organizationService: OrganizationService,
private logger: NGXLogger
) {
this.loginInfo = this.sessionStorageService.get<LoginInfo>(KEY_LOGIN_INFO);
}
ngOnInit() {
this.companyCode = this.loginInfo.companyCode;
this.companyList$ = this.store.pipe(
select(AppStore.SettingSelector.CompanySelector.companyList)
);
this.departmentInfoList$ = this.store.pipe(
select(AppStore.MessengerSelector.QuerySelector.departmentInfoList)
);
this.loginResSubscription = this.store
.pipe(
take(1),
select(AppStore.AccountSelector.AuthenticationSelector.loginRes),
tap(loginRes => {
this.loginRes = loginRes;
})
)
.subscribe();
this.myDepartmentUserInfoListSubscription = this.store
.pipe(
select(
AppStore.MessengerSelector.QuerySelector.myDepartmentUserInfoList
)
)
.subscribe(list => {
if (!list) {
this.store
.pipe(
take(1),
select(AppStore.AccountSelector.AuthenticationSelector.loginRes),
tap(loginRes => {
if (!!loginRes) {
this.store.dispatch(
QueryStore.selectedDept({
seq: loginRes.departmentCode,
name: loginRes.userInfo.deptName,
nameEn: loginRes.userInfo.deptNameEn,
nameCn: loginRes.userInfo.deptNameCn
})
);
}
this.loginRes = loginRes;
})
)
.subscribe();
return;
}
});
this.organizationTreeActivatedSubscription = combineLatest([
this.store.pipe(
select(AppStore.MessengerSelector.SettingsSelector.gnbMenuIndex)
),
this.store.pipe(
select(
AppStore.MessengerSelector.SettingsSelector.organizationTreeActivated
)
)
]).subscribe(([menu, activate]) => {
this.organizationTreeActivatedSubject.next(
menu === MainMenu.Organization || activate
);
});
}
ngOnDestroy(): void {
if (!!this.loginResSubscription) {
this.loginResSubscription.unsubscribe();
}
if (!!this.myDepartmentUserInfoListSubscription) {
this.myDepartmentUserInfoListSubscription.unsubscribe();
}
if (!!this.organizationTreeActivatedSubscription) {
this.organizationTreeActivatedSubscription.unsubscribe();
}
}
/** 유저검색 */
onKeyDownEnterOrganizationTenantSearch(params: {
companyCode: string;
searchWord: string;
}) {
if (params.searchWord.trim().length > 1) {
this.store.dispatch(
QueryStore.searchDeptUser({
companyCode: params.companyCode,
search: params.searchWord
})
);
}
}
/** 검색 취소 */
onClickCancel() {
this.store.dispatch(QueryStore.cancelSearchDeptUser({}));
}
/** 조직도 부서 선택 */
onSelectedOrganization(deptInfo: DeptInfo) {
this.onClickCancel();
this.store.dispatch(
QueryStore.selectedDept({
seq: deptInfo.seq,
name: deptInfo.name,
nameEn: deptInfo.nameEn,
nameCn: deptInfo.nameCn
})
);
}
}

View File

@ -0,0 +1,21 @@
<app-layout-messenger-intro
*ngIf="
!this.selectedChat && (this.gnbMenuIndex$ | async) !== MainMenu.Organization
"
></app-layout-messenger-intro>
<app-layout-messenger-messages
*ngIf="
!!this.selectedChat &&
(this.gnbMenuIndex$ | async) !== MainMenu.Organization
"
(openProfile)="onClickOpenProfile($event)"
(closeRightDrawer)="onCloseRightDrawer()"
></app-layout-messenger-messages>
<app-layout-messenger-organization
[style.display]="
MainMenu.Organization === (this.gnbMenuIndex$ | async) ? 'block' : 'none'
"
>
</app-layout-messenger-organization>

View File

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

View File

@ -0,0 +1,43 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Observable } from 'rxjs';
import { OpenProfileOptions } from '@ucap-webmessenger/protocol-buddy';
import * as AppStore from '@app/store';
import { select, Store } from '@ngrx/store';
import { MainMenu } from '@app/types';
@Component({
selector: 'app-layout-messenger-main-contents',
templateUrl: './main-contents.component.html',
styleUrls: ['./main-contents.component.scss']
})
export class MainContentsComponent implements OnInit {
@Input()
selectedChat: Observable<string | null>;
@Output()
openProfile = new EventEmitter<{
userSeq: number;
}>();
@Output()
closeRightDrawer = new EventEmitter();
MainMenu = MainMenu;
gnbMenuIndex$: Observable<string>;
constructor(private store: Store<any>) {}
ngOnInit() {
this.gnbMenuIndex$ = this.store.pipe(
select(AppStore.MessengerSelector.SettingsSelector.gnbMenuIndex)
);
}
onClickOpenProfile(params: {
userSeq: number;
openProfileOptions?: OpenProfileOptions;
}) {}
onCloseRightDrawer() {}
}

View File

@ -0,0 +1,9 @@
import { IntroComponent } from './intro.component';
import { MessagesComponent } from './messages.component';
import { OrganizationComponent } from './organization.component';
export const MAIN_CONTENTS_COMPONENTS = [
IntroComponent,
MessagesComponent,
OrganizationComponent
];

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ucapAnimations } from '@ucap-webmessenger/ui'; import { ucapAnimations } from '@ucap-webmessenger/ui';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../../environments/environment';
@Component({ @Component({
selector: 'app-layout-messenger-intro', selector: 'app-layout-messenger-intro',

View File

@ -95,7 +95,7 @@ import {
CreateChatDialogComponent, CreateChatDialogComponent,
CreateChatDialogData, CreateChatDialogData,
CreateChatDialogResult CreateChatDialogResult
} from '../dialogs/chat/create-chat.dialog.component'; } from '../../dialogs/chat/create-chat.dialog.component';
import { import {
FileViewerDialogComponent, FileViewerDialogComponent,
FileViewerDialogData, FileViewerDialogData,
@ -108,18 +108,18 @@ import {
EditChatRoomDialogComponent, EditChatRoomDialogComponent,
EditChatRoomDialogResult, EditChatRoomDialogResult,
EditChatRoomDialogData EditChatRoomDialogData
} from '../dialogs/chat/edit-chat-room.dialog.component'; } from '../../dialogs/chat/edit-chat-room.dialog.component';
import { import {
SelectGroupDialogComponent, SelectGroupDialogComponent,
SelectGroupDialogResult, SelectGroupDialogResult,
SelectGroupDialogData SelectGroupDialogData
} from '../dialogs/group/select-group.dialog.component'; } from '../../dialogs/group/select-group.dialog.component';
import { GroupDetailData } from '@ucap-webmessenger/protocol-sync'; import { GroupDetailData } from '@ucap-webmessenger/protocol-sync';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../../environments/environment';
import { import {
MassDetailComponent, MassDetailComponent,
MassDetailDialogData MassDetailDialogData
} from '../dialogs/chat/mass-detail.component'; } from '../../dialogs/chat/mass-detail.component';
import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native'; import { NativeService, UCAP_NATIVE_SERVICE } from '@ucap-webmessenger/native';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -127,14 +127,14 @@ import {
ClipboardDialogComponent, ClipboardDialogComponent,
ClipboardDialogData, ClipboardDialogData,
ClipboardDialogResult ClipboardDialogResult
} from '../dialogs/chat/clipboard.dialog.component'; } from '../../dialogs/chat/clipboard.dialog.component';
import { AppFileService } from '@app/services/file.service'; import { AppFileService } from '@app/services/file.service';
import { FileType } from '@ucap-webmessenger/protocol-file'; import { FileType } from '@ucap-webmessenger/protocol-file';
import { import {
ConferenceDetailDialogComponent, ConferenceDetailDialogComponent,
ConferenceDetailDialogResult, ConferenceDetailDialogResult,
ConferenceDetailDialogData ConferenceDetailDialogData
} from '../dialogs/conference/conference-detail.dialog.component'; } from '../../dialogs/conference/conference-detail.dialog.component';
import { ConferenceService } from '@ucap-webmessenger/api-prompt'; import { ConferenceService } from '@ucap-webmessenger/api-prompt';
import { AuthResponse } from '@ucap-webmessenger/protocol-query'; import { AuthResponse } from '@ucap-webmessenger/protocol-query';

View File

@ -0,0 +1,25 @@
<div class="container" fxFlex fxLayout="column">
<mat-toolbar class="organization-toolbar">
<div fxFlex fxLayout="row" class="organization-header">
<div fxLayout="row" fxLayoutAlign="start center" class="profile-img">
icon
</div>
<div class="organization-info">
<h3 class="organization-name">
title
</h3>
</div>
<div class="organization-option">
<button mat-icon-button aria-label="more">
<mat-icon>more_vert</mat-icon>
</button>
</div>
</div>
</mat-toolbar>
<div fxFlex="1 1 auto" class="organization-content">
contents
</div>
<div fxFlex="0 0 auto" fxLayout="column"></div>
</div>

View File

@ -0,0 +1,74 @@
@charset 'utf-8';
:host {
display: flex;
width: 100%;
height: 100%;
}
@mixin ellipsis($row) {
overflow: hidden;
text-overflow: ellipsis;
@if $row == 1 {
display: block;
white-space: nowrap;
word-wrap: normal;
} @else if $row >= 2 {
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
word-wrap: break-word;
}
}
.container {
position: relative;
width: 100%;
}
.organization-toolbar {
position: relative;
display: flex;
flex-flow: column;
width: 100%;
height: auto;
align-items: center;
background-color: #ffffff !important;
border-bottom: 1px solid #dddddd;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
z-index: 1;
padding: 0;
.organization-header {
width: 100%;
align-items: center;
display: flex;
justify-content: space-between;
justify-items: center;
padding: 4px 20px;
.organization-info {
display: flex;
flex-flow: row;
overflow: hidden;
align-items: center;
.organization-name {
font-size: 0.94rem;
line-height: normal;
@include ellipsis(1);
}
}
.organization-option {
margin-left: auto;
margin-right: -10px;
.icon-button {
transform: translateY(-2px);
i {
font-size: 0.9em;
}
}
}
}
}
.organization-content {
position: relative;
background: transparent;
overflow: auto;
-webkit-overflow-scrolling: touch;
}

View File

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

View File

@ -0,0 +1,12 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-layout-messenger-organization',
templateUrl: './organization.component.html',
styleUrls: ['./organization.component.scss']
})
export class OrganizationComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@ -25,14 +25,12 @@
</div> </div>
<div class="messenger-statusbar-actions"></div> <div class="messenger-statusbar-actions"></div>
</div> --> </div> -->
<app-layout-messenger-intro <app-layout-messenger-main-contents
*ngIf="!(this.selectedChat$ | async)" [selectedChat]="this.selectedChat$ | async"
></app-layout-messenger-intro>
<app-layout-messenger-messages
*ngIf="!!(this.selectedChat$ | async)"
(openProfile)="onClickOpenProfile($event)" (openProfile)="onClickOpenProfile($event)"
(closeRightDrawer)="onCloseRightDrawer()" (closeRightDrawer)="onCloseRightDrawer()"
></app-layout-messenger-messages> >
</app-layout-messenger-main-contents>
<button <button
mat-icon-button mat-icon-button
class="left-drawer-toggle" class="left-drawer-toggle"

View File

@ -7,9 +7,11 @@ import {
DeptInfo, DeptInfo,
DeptUserRequest, DeptUserRequest,
UserInfoSS, UserInfoSS,
DeptUserResponse DeptUserResponse,
SelectedDept
} from '@ucap-webmessenger/protocol-query'; } from '@ucap-webmessenger/protocol-query';
/** 권한 조회 */
export const auth = createAction( export const auth = createAction(
'[Messenger::Query] Auth', '[Messenger::Query] Auth',
props<AuthRequest>() props<AuthRequest>()
@ -25,6 +27,7 @@ export const authFailure = createAction(
props<{ error: any }>() props<{ error: any }>()
); );
/** 부서 조회 */
export const dept = createAction( export const dept = createAction(
'[Messenger::Query] Dept', '[Messenger::Query] Dept',
props<DeptRequest>() props<DeptRequest>()
@ -39,6 +42,39 @@ export const deptFailure = createAction(
'[Messenger::Query] Dept Failure', '[Messenger::Query] Dept Failure',
props<{ error: any }>() props<{ error: any }>()
); );
/** 트리 > 부서 선택 > 부서원 조회 */
export const selectedDept = createAction(
'[Messenger::Query] selected Department on tree',
props<SelectedDept>()
);
export const selectedDeptSuccess = createAction(
'[Messenger::Query] selected Department on tree Success',
props()
);
export const deptUser = createAction(
'[Messenger::Query] Dept User',
props<DeptUserRequest>()
);
export const deptUserSuccess = createAction(
'[Messenger::Query] Dept User Success',
props<{ userInfos: UserInfoSS[] }>()
);
export const deptUserFailure = createAction(
'[Messenger::Query] Dept User Failure',
props<{ error: any }>()
);
/** 내 부서 조회 */
export const selectedMyDept = createAction(
'[Messenger::Query] selected Department on tree',
props<SelectedDept>()
);
export const myDeptUser = createAction( export const myDeptUser = createAction(
'[Messenger::Query] My Dept User', '[Messenger::Query] My Dept User',
props<DeptUserRequest>() props<DeptUserRequest>()
@ -53,3 +89,27 @@ export const myDeptUserFailure = createAction(
'[Messenger::Query] My Dept User Failure', '[Messenger::Query] My Dept User Failure',
props<{ error: any }>() props<{ error: any }>()
); );
/** 조직도 > 조회 */
export const searchDeptUser = createAction(
'[Messenger::Query] Search Dept User',
props<{
companyCode: string;
search: string;
}>()
);
export const searchDeptUserSuccess = createAction(
'[Messenger::Query] Search Dept User Success',
props<{ userInfos: UserInfoSS[] }>()
);
export const searchDeptUserFailure = createAction(
'[Messenger::Query] Search Dept User Failure',
props()
);
export const cancelSearchDeptUser = createAction(
'[Messenger::Query] Cancel Search Dept User Success',
props()
);

View File

@ -2,8 +2,17 @@ import { Injectable } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects'; import { Actions, ofType, createEffect } from '@ngrx/effects';
import * as StatusStore from '@app/store/messenger/status';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { catchError, map, tap, switchMap } from 'rxjs/operators'; import {
catchError,
map,
tap,
switchMap,
withLatestFrom,
take
} from 'rxjs/operators';
import { import {
dept, dept,
@ -11,7 +20,16 @@ import {
deptFailure, deptFailure,
myDeptUserSuccess, myDeptUserSuccess,
myDeptUser, myDeptUser,
myDeptUserFailure myDeptUserFailure,
deptUserSuccess,
deptUser,
selectedDept,
deptUserFailure,
selectedDeptSuccess,
selectedMyDept,
searchDeptUser,
searchDeptUserSuccess,
cancelSearchDeptUser
} from './actions'; } from './actions';
import { import {
@ -24,13 +42,16 @@ import {
SSVC_TYPE_QUERY_DEPT_USER_DATA, SSVC_TYPE_QUERY_DEPT_USER_DATA,
DeptUserData, DeptUserData,
SSVC_TYPE_QUERY_DEPT_USER_RES, SSVC_TYPE_QUERY_DEPT_USER_RES,
DeptUserResponse DeptUserResponse,
DeptSearchType
} from '@ucap-webmessenger/protocol-query'; } from '@ucap-webmessenger/protocol-query';
import { Store } from '@ngrx/store'; import { Store, select } from '@ngrx/store';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { KEY_LOGIN_RES_INFO } from '@app/types'; import { KEY_LOGIN_RES_INFO } from '@app/types';
import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { SessionStorageService } from '@ucap-webmessenger/web-storage';
import { OrganizationService } from '@ucap-webmessenger/ui-organization';
import { NGXLogger } from 'ngx-logger';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -70,6 +91,209 @@ export class Effects {
{ dispatch: false } { dispatch: false }
); );
selectedDept$ = createEffect(
() => {
return this.actions$.pipe(
ofType(selectedDept),
withLatestFrom(
this.store.pipe(
select(
(state: any) =>
state.account.authentication.loginRes as LoginResponse
)
)
),
switchMap(([req, loginResInfo]) => {
return this.organizationService
.getDeptUser({
divCd: 'ORG',
companyCode: loginResInfo.companyCode,
seq: req.seq,
search: '',
searchRange: DeptSearchType.All,
senderCompanyCode: loginResInfo.companyCode,
senderEmployeeType: loginResInfo.userInfo.employeeType
})
.pipe(
map(datas => {
const userInfos: UserInfoSS[] = datas.userInfos;
this.store.dispatch(
deptUserSuccess({
userInfos
})
);
// 검색 결과에 따른 프레즌스 조회.
const userSeqList: number[] = [];
userInfos.map(user => userSeqList.push(user.seq));
if (userSeqList.length > 0) {
this.store.dispatch(
StatusStore.bulkInfo({
divCd: 'orgtrSrch',
userSeqs: userSeqList
})
);
}
this.store.dispatch(selectedDeptSuccess({}));
}),
catchError(error => of(deptUserFailure({ error })))
);
})
);
},
{ dispatch: false }
);
selectedMyDept$ = createEffect(
() => {
return this.actions$.pipe(
ofType(selectedMyDept),
withLatestFrom(
this.store.pipe(
select(
(state: any) =>
state.account.authentication.loginRes as LoginResponse
)
)
),
switchMap(([req, loginResInfo]) => {
return this.organizationService
.getDeptUser({
divCd: 'ORG_MY',
companyCode: loginResInfo.companyCode,
seq: req.seq,
search: '',
searchRange: DeptSearchType.All,
senderCompanyCode: loginResInfo.companyCode,
senderEmployeeType: loginResInfo.userInfo.employeeType
})
.pipe(
map(datas => {
const userInfos: UserInfoSS[] = datas.userInfos;
this.store.dispatch(
deptUserSuccess({
userInfos
})
);
this.store.dispatch(
myDeptUserSuccess({
userInfos
})
);
this.store.dispatch(selectedDeptSuccess({}));
}),
catchError(error => of(myDeptUserFailure({ error })))
);
})
);
},
{ dispatch: false }
);
searchDeptUser$ = createEffect(
() => {
return this.actions$.pipe(
ofType(searchDeptUser),
withLatestFrom(
this.store.pipe(
select(
(state: any) =>
state.account.authentication.loginRes as LoginResponse
)
)
),
switchMap(([req, loginResInfo]) => {
return this.organizationService
.getDeptUser({
divCd: 'ORG_SRCH',
companyCode: req.companyCode,
search: req.search,
searchRange: DeptSearchType.All,
senderCompanyCode: loginResInfo.companyCode,
senderEmployeeType: loginResInfo.userInfo.employeeType
})
.pipe(
map(datas => {
const userInfos: UserInfoSS[] = datas.userInfos;
this.store.dispatch(
searchDeptUserSuccess({
userInfos
})
);
// 검색 결과에 따른 프레즌스 조회.
const userSeqList: number[] = [];
userInfos.map(user => userSeqList.push(user.seq));
if (userSeqList.length > 0) {
this.store.dispatch(
StatusStore.bulkInfo({
divCd: 'orgtrSrch',
userSeqs: userSeqList
})
);
}
}),
catchError(error => of(deptUserFailure({ error })))
);
})
);
},
{ dispatch: false }
);
deptUser$ = createEffect(
() => {
let userInfos: UserInfoSS[];
return this.actions$.pipe(
ofType(deptUser),
tap(() => {
userInfos = [];
}),
switchMap(req => {
return this.queryProtocolService.deptUser(req).pipe(
map(res => {
switch (res.SSVC_TYPE) {
case SSVC_TYPE_QUERY_DEPT_USER_DATA:
userInfos.push(...(res as DeptUserData).userInfos);
break;
case SSVC_TYPE_QUERY_DEPT_USER_RES:
userInfos.sort((a, b) =>
a.order < b.order
? -1
: a.order > b.order
? 1
: a.name < b.name
? -1
: a.name > b.name
? 1
: 0
);
this.store.dispatch(
deptUserSuccess({
userInfos
})
);
this.store.dispatch(
myDeptUserSuccess({
userInfos
})
);
break;
}
}),
catchError(error => of(deptUserFailure({ error })))
);
})
);
},
{ dispatch: false }
);
myDeptUser$ = createEffect( myDeptUser$ = createEffect(
() => { () => {
let userInfos: UserInfoSS[]; let userInfos: UserInfoSS[];
@ -99,6 +323,11 @@ export class Effects {
: 0 : 0
); );
this.store.dispatch(
deptUserSuccess({
userInfos
})
);
this.store.dispatch( this.store.dispatch(
myDeptUserSuccess({ myDeptUserSuccess({
userInfos userInfos
@ -119,6 +348,8 @@ export class Effects {
private actions$: Actions, private actions$: Actions,
private store: Store<any>, private store: Store<any>,
private queryProtocolService: QueryProtocolService, private queryProtocolService: QueryProtocolService,
private sessionStorageService: SessionStorageService private organizationService: OrganizationService,
private sessionStorageService: SessionStorageService,
private logger: NGXLogger
) {} ) {}
} }

View File

@ -1,6 +1,19 @@
import { createReducer, on } from '@ngrx/store'; import { createReducer, on } from '@ngrx/store';
import { initialState } from './state'; import { initialState } from './state';
import { authSuccess, deptSuccess, myDeptUserSuccess } from './actions'; import {
authSuccess,
deptSuccess,
myDeptUserSuccess,
deptUserSuccess,
selectedDept,
selectedDeptSuccess,
searchDeptUser,
searchDeptUserSuccess,
searchDeptUserFailure,
deptUserFailure,
cancelSearchDeptUser,
myDeptUserFailure
} from './actions';
import * as AuthenticationStore from '@app/store/account/authentication'; import * as AuthenticationStore from '@app/store/account/authentication';
@ -20,12 +33,76 @@ export const reducer = createReducer(
}; };
}), }),
on(selectedDept, (state, action) => {
return {
...state,
selectedDepartment: action,
selectedDepartmentProcessing: true
};
}),
on(selectedDeptSuccess, (state, action) => {
return {
...state,
selectedDepartmentProcessing: false
};
}),
on(deptUserSuccess, (state, action) => {
return {
...state,
departmentUserInfoList: action.userInfos
};
}),
on(deptUserFailure, state => {
return {
...state,
selectedDepartmentProcessing: false
};
}),
on(myDeptUserSuccess, (state, action) => { on(myDeptUserSuccess, (state, action) => {
return { return {
...state, ...state,
myDepartmentUserInfoList: action.userInfos myDepartmentUserInfoList: action.userInfos
}; };
}), }),
on(myDeptUserFailure, state => {
return {
...state,
selectedDepartmentProcessing: false
};
}),
on(searchDeptUser, (state, action) => {
return {
...state,
isSearch: true,
selectedDepartmentProcessing: true
};
}),
on(searchDeptUserSuccess, (state, action) => {
return {
...state,
searchDepartmentUserInfoList: action.userInfos,
selectedDepartmentProcessing: false
};
}),
on(searchDeptUserFailure, (state, action) => {
return {
...state,
selectedDepartmentProcessing: false
};
}),
on(cancelSearchDeptUser, (state, action) => {
return {
...state,
isSearch: false,
searchDepartmentUserInfoList: null
};
}),
on(AuthenticationStore.logoutInitialize, (state, action) => { on(AuthenticationStore.logoutInitialize, (state, action) => {
return { return {

View File

@ -2,7 +2,8 @@ import { Selector, createSelector } from '@ngrx/store';
import { import {
AuthResponse, AuthResponse,
DeptInfo, DeptInfo,
UserInfoSS UserInfoSS,
SelectedDept
} from '@ucap-webmessenger/protocol-query'; } from '@ucap-webmessenger/protocol-query';
export interface State { export interface State {
@ -10,12 +11,25 @@ export interface State {
departmentInfoList: DeptInfo[] | null; departmentInfoList: DeptInfo[] | null;
isSearch: boolean;
selectedDepartmentProcessing: boolean;
selectedDepartment: SelectedDept | null;
searchDepartmentUserInfoList: UserInfoSS[] | null;
departmentUserInfoList: UserInfoSS[] | null;
myDepartmentUserInfoList: UserInfoSS[] | null; myDepartmentUserInfoList: UserInfoSS[] | null;
} }
export const initialState: State = { export const initialState: State = {
auth: null, auth: null,
departmentInfoList: null, departmentInfoList: null,
isSearch: false,
selectedDepartmentProcessing: false,
selectedDepartment: null,
searchDepartmentUserInfoList: null,
departmentUserInfoList: null,
myDepartmentUserInfoList: null myDepartmentUserInfoList: null
}; };
@ -26,6 +40,25 @@ export function selectors<S>(selector: Selector<any, State>) {
selector, selector,
(state: State) => state.departmentInfoList (state: State) => state.departmentInfoList
), ),
isSearch: createSelector(selector, (state: State) => state.isSearch),
selectedDepartmentProcessing: createSelector(
selector,
(state: State) => state.selectedDepartmentProcessing
),
selectedDepartment: createSelector(
selector,
(state: State) => state.selectedDepartment
),
searchDepartmentUserInfoList: createSelector(
selector,
(state: State) => state.searchDepartmentUserInfoList
),
departmentUserInfoList: createSelector(
selector,
(state: State) => state.myDepartmentUserInfoList
),
myDepartmentUserInfoList: createSelector( myDepartmentUserInfoList: createSelector(
selector, selector,
(state: State) => state.myDepartmentUserInfoList (state: State) => state.myDepartmentUserInfoList

View File

@ -0,0 +1,12 @@
import { DeptType } from '../types/dept.type';
export interface SelectedDept {
/** 부서SEQ */
seq: number;
/** 부서명 */
name: string;
/** 부서명(영어) */
nameEn: string;
/** 부서명(중국어) */
nameCn: string;
}

View File

@ -3,6 +3,7 @@
*/ */
export * from './lib/models/dept-info'; export * from './lib/models/dept-info';
export * from './lib/models/selected-dept';
export * from './lib/models/user-info-dn'; export * from './lib/models/user-info-dn';
export * from './lib/models/user-info-f'; export * from './lib/models/user-info-f';
export * from './lib/models/user-info-ss'; export * from './lib/models/user-info-ss';