조회 부서원 리스트 컴포넌트 분리
This commit is contained in:
parent
d52ace1f03
commit
7750c94001
|
@ -14,6 +14,7 @@
|
|||
></app-layout-messenger-messages>
|
||||
|
||||
<app-layout-messenger-organization
|
||||
(openProfile)="onClickOpenProfile($event)"
|
||||
[style.display]="
|
||||
MainMenu.Organization === (this.gnbMenuIndex$ | async) ? 'block' : 'none'
|
||||
"
|
||||
|
|
|
@ -20,6 +20,7 @@ export class MainContentsComponent implements OnInit {
|
|||
openProfile = new EventEmitter<{
|
||||
userSeq: number;
|
||||
}>();
|
||||
|
||||
@Output()
|
||||
closeRightDrawer = new EventEmitter();
|
||||
|
||||
|
@ -34,10 +35,11 @@ export class MainContentsComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
onClickOpenProfile(params: {
|
||||
userSeq: number;
|
||||
openProfileOptions?: OpenProfileOptions;
|
||||
}) {}
|
||||
onClickOpenProfile(userSeq: number) {
|
||||
this.openProfile.emit({ userSeq });
|
||||
}
|
||||
|
||||
onCloseRightDrawer() {}
|
||||
onCloseRightDrawer() {
|
||||
this.closeRightDrawer.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,9 +147,7 @@ import { AuthResponse } from '@ucap-webmessenger/protocol-query';
|
|||
})
|
||||
export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
@Output()
|
||||
openProfile = new EventEmitter<{
|
||||
userSeq: number;
|
||||
}>();
|
||||
openProfile = new EventEmitter<number>();
|
||||
@Output()
|
||||
closeRightDrawer = new EventEmitter();
|
||||
|
||||
|
@ -1728,7 +1726,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
roomType !== RoomType.Allim_Elephant &&
|
||||
roomType !== RoomType.Allim_TMS
|
||||
) {
|
||||
this.openProfile.emit({ userSeq });
|
||||
this.openProfile.emit(userSeq);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
></div>
|
||||
<div class="organization-info">
|
||||
<h3 class="organization-name">
|
||||
<ng-container *ngIf="!(isSearch$ | async)">
|
||||
<ng-container *ngIf="!(isSearch | async)">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
!!(selectedDepartmentProcessing$ | async);
|
||||
|
@ -21,10 +21,10 @@
|
|||
{{ selectedDepartment$ | async | ucapTranslate: 'name' }}
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!!(isSearch$ | async)">
|
||||
<ng-container *ngIf="!!(isSearch | async)">
|
||||
{{ 'common.searchResult' | translate
|
||||
}}<span class="text-accent-color"
|
||||
>({{ (searchDepartmentUserInfoList$ | async).length }}
|
||||
>({{ selectedDepartmentUserInfoList.length }}
|
||||
{{ 'common.units.persons' | translate }})</span
|
||||
>
|
||||
</ng-container>
|
||||
|
@ -46,156 +46,40 @@
|
|||
</div>
|
||||
</mat-toolbar>
|
||||
|
||||
<div fxFlex="1 1 auto" class="organization-content">
|
||||
<div fxFlex="auto" class="organization-content">
|
||||
<div fxFlex="0 0 auto" class="table-box">
|
||||
<perfect-scrollbar class="scrollbar">
|
||||
<table mat-table [dataSource]="departmentUserInfoList$ | async">
|
||||
<ng-container matColumnDef="profile">
|
||||
<th mat-header-cell *matHeaderCellDef class="profile">
|
||||
{{ 'search.fieldProfile' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<div class="table-item">
|
||||
<div class="profile">
|
||||
<span
|
||||
class="presence"
|
||||
[ngClass]="getPresence(element, PresenceType.PC)"
|
||||
[matTooltip]="getPresenceMsg(element)"
|
||||
matTooltipPosition="after"
|
||||
></span>
|
||||
<span class="thumbnail-mask">
|
||||
<img
|
||||
class="thumbnail"
|
||||
ucapImage
|
||||
[base]="profileImageRoot"
|
||||
[path]="element.profileImageFile"
|
||||
[default]="'assets/images/img_nophoto_50.png'"
|
||||
(click)="onClickOpenProfile($event, element.seq)"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
*ngIf="
|
||||
getPresence(element, PresenceType.MOBILE) === 'mobileOn'
|
||||
"
|
||||
class="text-accent-color marker-mobile-state"
|
||||
>
|
||||
<mat-icon>phone_android</mat-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="work-status"
|
||||
[ngClass]="getWorkstatusInfo(element, 'style')"
|
||||
>
|
||||
{{ getWorkstatusInfo(element, 'text') }}
|
||||
</span>
|
||||
<span class="name">
|
||||
{{ element.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="grade">
|
||||
<th mat-header-cell *matHeaderCellDef class="grade">
|
||||
{{ 'search.fieldGrade' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="grade">
|
||||
<div class="grade">
|
||||
{{ element.grade }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="deptName">
|
||||
<th mat-header-cell *matHeaderCellDef class="deptName">
|
||||
{{ 'search.fieldDeptartment' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<div class="deptName">
|
||||
{{ element.deptName }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="companyName">
|
||||
<th mat-header-cell *matHeaderCellDef class="companyName">
|
||||
{{ 'search.fieldCompany' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<div class="companyName">
|
||||
{{ element.companyName }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="lineNumber">
|
||||
<th mat-header-cell *matHeaderCellDef class="lineNumber">
|
||||
{{ 'search.fieldOfficePhoneNumber' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="lineNumber">
|
||||
<div class="lineNumber">
|
||||
{{ element.lineNumber }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="hpNumber">
|
||||
<th mat-header-cell *matHeaderCellDef class="hpNumber">
|
||||
{{ 'search.fieldHandphone' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="hpNumber">
|
||||
<div class="hpNumber">
|
||||
{{ element.hpNumber }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="email">
|
||||
<th mat-header-cell *matHeaderCellDef class="email">
|
||||
{{ 'search.fieldEmail' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="email">
|
||||
<div class="email">
|
||||
{{ element.email }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="responsibilities">
|
||||
<th mat-header-cell *matHeaderCellDef class="responsibilities">
|
||||
{{ 'search.fieldResponsibilities' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="responsibilities">
|
||||
<div class="responsibilities">
|
||||
{{ element.responsibilities }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="workplace">
|
||||
<th mat-header-cell *matHeaderCellDef class="workplace">
|
||||
{{ 'search.fieldWorkPlace' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="workplace">
|
||||
<div class="workplace">
|
||||
{{ element.workplace }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr
|
||||
mat-header-row
|
||||
*matHeaderRowDef="displayedColumns; sticky: true"
|
||||
></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</perfect-scrollbar>
|
||||
<div
|
||||
class="no-search-result"
|
||||
fxFlexFill
|
||||
*ngIf="
|
||||
!(departmentUserInfoList$ | async) ||
|
||||
0 === (departmentUserInfoList$ | async).length
|
||||
"
|
||||
>
|
||||
{{ 'common.noSearchResult' | translate }}
|
||||
</div>
|
||||
<ucap-organization-detail-table
|
||||
[presence]="presence$ | async"
|
||||
[selectedDepartmentUserInfoList]="selectedDepartmentUserInfoList"
|
||||
[profileImageRoot]="profileImageRoot"
|
||||
(openProfile)="onClickOpenProfile($event)"
|
||||
class="detail-table"
|
||||
></ucap-organization-detail-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div fxFlex="0 0 auto" fxLayout="column"></div>
|
||||
<div fxFlex="auto" fxLayout="column">
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
Personal data
|
||||
</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Type your name and age
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>First name</mat-label>
|
||||
<input matInput />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Age</mat-label>
|
||||
<input matInput type="number" min="1" />
|
||||
</mat-form-field>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -99,114 +99,7 @@
|
|||
overflow: hidden;
|
||||
display: flex;
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
.detail-table {
|
||||
width: 100%;
|
||||
th {
|
||||
@include disable-selection;
|
||||
text-align: center;
|
||||
&.profile {
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
td.mat-cell {
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
width: 100px;
|
||||
div {
|
||||
@include ellipsis(1);
|
||||
|
||||
&.table-item {
|
||||
display: flex;
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
font-size: 1em;
|
||||
div {
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
align-self: center;
|
||||
.name {
|
||||
@include ellipsis(1);
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.status {
|
||||
font-size: 0.84em;
|
||||
}
|
||||
|
||||
&.profile {
|
||||
width: 70px;
|
||||
text-overflow: unset;
|
||||
flex: 0 0 auto;
|
||||
.presence {
|
||||
transform: translateY(6px);
|
||||
}
|
||||
.thumbnail {
|
||||
cursor: pointer;
|
||||
&-mask {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 0;
|
||||
position: relative;
|
||||
img {
|
||||
width: 40px;
|
||||
height: auto;
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
}
|
||||
.marker-mobile-state {
|
||||
position: absolute;
|
||||
background-color: #ffffff;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
bottom: 4px;
|
||||
left: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
.mat-icon {
|
||||
font-size: 0.9em;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.work-status {
|
||||
display: inline-block;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
color: #ffffff;
|
||||
height: 100%;
|
||||
min-width: 32px;
|
||||
margin-right: 4px;
|
||||
border-radius: 24px;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-search-result {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import * as QueryStore from '@app/store/messenger/query';
|
|||
import * as SyncStore from '@app/store/messenger/sync';
|
||||
import * as ChatStore from '@app/store/messenger/chat';
|
||||
import * as StatusStore from '@app/store/messenger/status';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Observable, Subscription, combineLatest } from 'rxjs';
|
||||
import { PresenceType, StatusCode } from '@ucap-webmessenger/core';
|
||||
import {
|
||||
StatusBulkInfo,
|
||||
|
@ -26,6 +26,7 @@ import {
|
|||
} from '@ucap-webmessenger/protocol-status';
|
||||
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
|
||||
import { KEY_VER_INFO } from '@app/types';
|
||||
import { Sort } from '@angular/material/sort';
|
||||
|
||||
@Component({
|
||||
selector: 'app-layout-messenger-organization',
|
||||
|
@ -38,28 +39,19 @@ export class OrganizationComponent implements OnInit, OnDestroy {
|
|||
|
||||
sessionVerinfo: VersionInfo2Response;
|
||||
|
||||
isSearch$: Observable<boolean>;
|
||||
isSearch: boolean;
|
||||
isSearchSubscription: Subscription;
|
||||
selectedDepartmentProcessing$: Observable<boolean>;
|
||||
selectedDepartment$: Observable<SelectedDept>;
|
||||
|
||||
departmentUserInfoList$: Observable<UserInfoSS[]>;
|
||||
searchDepartmentUserInfoList$: Observable<UserInfoSS[]>;
|
||||
departmentUserInfoListSubscription: Subscription;
|
||||
searchDepartmentUserInfoListSubscription: Subscription;
|
||||
|
||||
selectedDepartmentUserInfoList: UserInfoSS[] = [];
|
||||
|
||||
profileImageRoot: string;
|
||||
presence$: Observable<StatusBulkInfo[]>;
|
||||
PresenceType = PresenceType;
|
||||
presenceSubscription: Subscription;
|
||||
presence: StatusBulkInfo[] = [];
|
||||
displayedColumns: string[] = [
|
||||
'profile',
|
||||
'deptName',
|
||||
'companyName',
|
||||
'grade',
|
||||
'lineNumber',
|
||||
'hpNumber',
|
||||
'email',
|
||||
'responsibilities',
|
||||
'workplace'
|
||||
];
|
||||
|
||||
constructor(
|
||||
private store: Store<any>,
|
||||
|
@ -75,9 +67,11 @@ export class OrganizationComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.isSearch$ = this.store.pipe(
|
||||
select(AppStore.MessengerSelector.QuerySelector.isSearch)
|
||||
);
|
||||
this.isSearchSubscription = this.store
|
||||
.pipe(select(AppStore.MessengerSelector.QuerySelector.isSearch))
|
||||
.subscribe(isSearch => {
|
||||
this.isSearch = isSearch;
|
||||
});
|
||||
|
||||
this.selectedDepartmentProcessing$ = this.store.pipe(
|
||||
select(
|
||||
|
@ -89,141 +83,48 @@ export class OrganizationComponent implements OnInit, OnDestroy {
|
|||
select(AppStore.MessengerSelector.QuerySelector.selectedDepartment)
|
||||
);
|
||||
|
||||
this.departmentUserInfoList$ = this.store.pipe(
|
||||
this.departmentUserInfoListSubscription = this.store
|
||||
.pipe(
|
||||
select(AppStore.MessengerSelector.QuerySelector.departmentUserInfoList)
|
||||
);
|
||||
)
|
||||
.subscribe(list => {
|
||||
if (!this.isSearch) {
|
||||
this.selectedDepartmentUserInfoList = list;
|
||||
}
|
||||
});
|
||||
|
||||
this.searchDepartmentUserInfoList$ = this.store.pipe(
|
||||
this.searchDepartmentUserInfoListSubscription = this.store
|
||||
.pipe(
|
||||
select(
|
||||
AppStore.MessengerSelector.QuerySelector.searchDepartmentUserInfoList
|
||||
)
|
||||
);
|
||||
|
||||
this.presenceSubscription = this.store
|
||||
.pipe(
|
||||
select(
|
||||
AppStore.MessengerSelector.StatusSelector.selectAllStatusBulkInfo
|
||||
)
|
||||
)
|
||||
.subscribe(presence => {
|
||||
this.presence = presence;
|
||||
.subscribe(list => {
|
||||
if (!!this.isSearch) {
|
||||
this.selectedDepartmentUserInfoList = list;
|
||||
}
|
||||
});
|
||||
|
||||
this.presence$ = this.store.pipe(
|
||||
select(AppStore.MessengerSelector.StatusSelector.selectAllStatusBulkInfo)
|
||||
);
|
||||
|
||||
this.profileImageRoot = this.sessionVerinfo.profileRoot;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.presenceSubscription) {
|
||||
this.presenceSubscription.unsubscribe();
|
||||
if (!!this.isSearchSubscription) {
|
||||
this.isSearchSubscription.unsubscribe();
|
||||
}
|
||||
if (!!this.departmentUserInfoListSubscription) {
|
||||
this.departmentUserInfoListSubscription.unsubscribe();
|
||||
}
|
||||
if (!!this.searchDepartmentUserInfoListSubscription) {
|
||||
this.searchDepartmentUserInfoListSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************
|
||||
* ABOUT TABLE
|
||||
*******************************/
|
||||
|
||||
getPresence(userInfo: UserInfoSS, type: PresenceType): string {
|
||||
const presences = this.presence.filter(p => p.userSeq === userInfo.seq);
|
||||
|
||||
let status: string;
|
||||
let rtnClass = '';
|
||||
if (!!presences && presences.length > 0) {
|
||||
const presence = presences[0];
|
||||
switch (type) {
|
||||
case PresenceType.PC:
|
||||
status = !!presence ? presence.pcStatus : undefined;
|
||||
break;
|
||||
case PresenceType.MOBILE:
|
||||
status = !!presence ? presence.mobileStatus : undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!!status) {
|
||||
switch (status) {
|
||||
case StatusCode.OnLine:
|
||||
rtnClass = type + 'On';
|
||||
break;
|
||||
case StatusCode.Away:
|
||||
rtnClass = type + 'Out';
|
||||
break;
|
||||
case StatusCode.Busy:
|
||||
rtnClass = type + 'Other';
|
||||
break;
|
||||
default:
|
||||
rtnClass = type + 'Off';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rtnClass = type + 'Off';
|
||||
}
|
||||
|
||||
return rtnClass;
|
||||
}
|
||||
getPresenceMsg(userInfo: UserInfoSS): string {
|
||||
const presences = this.presence.filter(p => p.userSeq === userInfo.seq);
|
||||
|
||||
if (!!presences && presences.length > 0) {
|
||||
const presence = presences[0];
|
||||
if (
|
||||
!!presence &&
|
||||
!!presence.statusMessage &&
|
||||
presence.statusMessage !== '.'
|
||||
) {
|
||||
return presence.statusMessage;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
getWorkstatusInfo(userInfo: UserInfoSS, type: string): string {
|
||||
let workstatus = userInfo.workstatus;
|
||||
|
||||
const presences = this.presence.filter(p => p.userSeq === userInfo.seq);
|
||||
|
||||
if (!!presences && presences.length > 0) {
|
||||
const presence = presences[0];
|
||||
if (
|
||||
!!presence &&
|
||||
!!presence.workstatus &&
|
||||
presence.workstatus.trim().length > 0
|
||||
) {
|
||||
workstatus = presence.workstatus;
|
||||
}
|
||||
}
|
||||
|
||||
let text = '';
|
||||
// morning-off: 오전 afternoon-off: 오후 day-off: 휴가 long-time: 장기 leave-of-absence: 휴직
|
||||
let style = '';
|
||||
switch (workstatus) {
|
||||
case WorkStatusType.VacationAM:
|
||||
style = 'morning-off';
|
||||
text = '오전';
|
||||
break;
|
||||
case WorkStatusType.VacationPM:
|
||||
style = 'afternoon-off';
|
||||
text = '오후';
|
||||
break;
|
||||
case WorkStatusType.VacationAll:
|
||||
style = 'day-off';
|
||||
text = '휴가';
|
||||
break;
|
||||
case WorkStatusType.LeaveOfAbsence:
|
||||
style = 'leave-of-absence';
|
||||
text = '휴직';
|
||||
break;
|
||||
case WorkStatusType.LongtermRefresh:
|
||||
style = 'long-time';
|
||||
text = '장기';
|
||||
break;
|
||||
}
|
||||
|
||||
return type === 'text' ? text : style;
|
||||
}
|
||||
|
||||
onClickOpenProfile(event: MouseEvent, userSeq: number) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
onClickOpenProfile(userSeq: number) {
|
||||
this.openProfile.emit(userSeq);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
<perfect-scrollbar class="scrollbar">
|
||||
<table
|
||||
mat-table
|
||||
matSort
|
||||
[dataSource]="sortedData"
|
||||
(matSortChange)="sortData($event)"
|
||||
>
|
||||
<ng-container matColumnDef="profileImage">
|
||||
<th mat-header-cell *matHeaderCellDef class="profileImage">
|
||||
{{ 'search.fieldProfile' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="profileImage">
|
||||
<div class="table-item">
|
||||
<span
|
||||
class="presence"
|
||||
[ngClass]="getPresence(element, PresenceType.PC)"
|
||||
[matTooltip]="getPresenceMsg(element)"
|
||||
matTooltipPosition="after"
|
||||
></span>
|
||||
<span class="thumbnail-mask">
|
||||
<img
|
||||
class="thumbnail"
|
||||
ucapImage
|
||||
[base]="profileImageRoot"
|
||||
[path]="element.profileImageFile"
|
||||
[default]="'assets/images/img_nophoto_50.png'"
|
||||
(click)="onClickOpenProfile($event, element.seq)"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
*ngIf="getPresence(element, PresenceType.MOBILE) === 'mobileOn'"
|
||||
class="text-accent-color marker-mobile-state"
|
||||
>
|
||||
<mat-icon>phone_android</mat-icon>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="profileInfo">
|
||||
<th mat-header-cell *matHeaderCellDef class="profileInfo">
|
||||
<div>
|
||||
<span mat-sort-header="name">
|
||||
{{ 'search.fieldName' | translate }}
|
||||
</span>
|
||||
<span mat-sort-header="grade">
|
||||
{{ 'search.fieldGrade' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
<div mat-sort-header="dept">
|
||||
{{ 'search.fieldDeptartment' | translate }}
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element" class="profileInfo">
|
||||
<div class="baseInfo">
|
||||
<span
|
||||
*ngIf="getWorkstatusInfo(element, 'style').length > 0"
|
||||
class="work-status"
|
||||
[ngClass]="getWorkstatusInfo(element, 'style')"
|
||||
>
|
||||
{{ getWorkstatusInfo(element, 'text') }}
|
||||
</span>
|
||||
<span class="name">
|
||||
{{ element.name }}
|
||||
</span>
|
||||
<span class="grade">
|
||||
{{ element.grade }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="deptName">
|
||||
{{ element.deptName }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="company_hpNumber">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div mat-sort-header="company">
|
||||
{{ 'search.fieldCompany' | translate }}
|
||||
</div>
|
||||
<div mat-sort-header="hpNumber">
|
||||
{{ 'search.fieldHandphone' | translate }}
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<div class="companyName">
|
||||
{{ element.companyName }}
|
||||
</div>
|
||||
<div class="hpNumber">
|
||||
{{ element.hpNumber | ucapStringFormatterPhone }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="workplace_lineNumber">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div mat-sort-header="workplace">
|
||||
{{ 'search.fieldWorkPlace' | translate }}
|
||||
</div>
|
||||
<div mat-sort-header="lineNumber">
|
||||
{{ 'search.fieldOfficePhoneNumber' | translate }}
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<div class="workplace">
|
||||
{{ element.workplace }}
|
||||
</div>
|
||||
<div class="lineNumber">
|
||||
{{ element.lineNumber | ucapStringFormatterPhone }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="responsibilities_email">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<div mat-sort-header="responsibilities">
|
||||
{{ 'search.fieldResponsibilities' | translate }}
|
||||
</div>
|
||||
<div mat-sort-header="email">
|
||||
{{ 'search.fieldEmail' | translate }}
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<div class="responsibilities">
|
||||
{{ element.responsibilities }}
|
||||
</div>
|
||||
<div class="email">
|
||||
{{ element.email }}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="checkable">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox
|
||||
#checkbox
|
||||
[checked]="getCheckedAllUser()"
|
||||
(change)="onCheckAllUser(checkbox.checked)"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mat-checkbox
|
||||
#checkbox
|
||||
[checked]="getCheckedAllUser()"
|
||||
(change)="onCheckAllUser(checkbox.checked)"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</perfect-scrollbar>
|
||||
<div
|
||||
class="no-search-result"
|
||||
fxFlexFill
|
||||
*ngIf="!sortedData || 0 === sortedData.length"
|
||||
>
|
||||
{{ 'common.noSearchResult' | translate }}
|
||||
</div>
|
|
@ -0,0 +1,129 @@
|
|||
@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;
|
||||
}
|
||||
}
|
||||
@mixin disable-selection {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
height: 550px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td.mat-cell {
|
||||
padding: 6px;
|
||||
position: relative;
|
||||
|
||||
&.profileImage {
|
||||
width: 70px;
|
||||
text-overflow: unset;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.table-item {
|
||||
display: flex;
|
||||
width: 70px;
|
||||
min-width: 70px;
|
||||
font-size: 1em;
|
||||
|
||||
.presence {
|
||||
transform: translateY(6px);
|
||||
}
|
||||
.thumbnail {
|
||||
cursor: pointer;
|
||||
&-mask {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
margin-right: 0;
|
||||
position: relative;
|
||||
img {
|
||||
width: 40px;
|
||||
height: auto;
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
}
|
||||
.marker-mobile-state {
|
||||
position: absolute;
|
||||
background-color: #ffffff;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
bottom: 4px;
|
||||
left: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
.mat-icon {
|
||||
font-size: 0.9em;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.profileInfo {
|
||||
.baseInfo {
|
||||
display: flex;
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
font-size: 1em;
|
||||
|
||||
.name {
|
||||
@include ellipsis(1);
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.work-status {
|
||||
display: inline-block;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
color: #ffffff;
|
||||
height: 100%;
|
||||
min-width: 32px;
|
||||
margin-right: 4px;
|
||||
border-radius: 24px;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-search-result {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
font-size: 1.1em;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DetailTableComponent } from './detail-table.component';
|
||||
|
||||
describe('Organization::DetailTableComponent', () => {
|
||||
let component: DetailTableComponent;
|
||||
let fixture: ComponentFixture<DetailTableComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DetailTableComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DetailTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,229 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { UserInfoSS } from '@ucap-webmessenger/protocol-query';
|
||||
import { PresenceType, StatusCode } from '@ucap-webmessenger/core';
|
||||
import {
|
||||
WorkStatusType,
|
||||
StatusBulkInfo
|
||||
} from '@ucap-webmessenger/protocol-status';
|
||||
import { Sort } from '@angular/material/sort';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-organization-detail-table',
|
||||
templateUrl: './detail-table.component.html',
|
||||
styleUrls: ['./detail-table.component.scss']
|
||||
})
|
||||
export class DetailTableComponent implements OnInit {
|
||||
@Input()
|
||||
selectedDepartmentUserInfoList: UserInfoSS[] = [];
|
||||
@Input()
|
||||
presence: StatusBulkInfo[];
|
||||
@Input()
|
||||
profileImageRoot: string;
|
||||
@Input()
|
||||
selectedUserList: UserInfoSS[];
|
||||
|
||||
@Output()
|
||||
openProfile = new EventEmitter<number>();
|
||||
|
||||
sortedData: UserInfoSS[] = [];
|
||||
|
||||
PresenceType = PresenceType;
|
||||
displayedColumns: string[] = [
|
||||
'profileImage',
|
||||
'profileInfo',
|
||||
'company_hpNumber',
|
||||
'workplace_lineNumber',
|
||||
'responsibilities_email',
|
||||
'checkable'
|
||||
];
|
||||
|
||||
constructor(private logger: NGXLogger) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.sortedData = this.selectedDepartmentUserInfoList;
|
||||
}
|
||||
|
||||
getPresence(userInfo: UserInfoSS, type: PresenceType): string {
|
||||
const presences = this.presence.filter(p => p.userSeq === userInfo.seq);
|
||||
|
||||
let status: string;
|
||||
let rtnClass = '';
|
||||
if (!!presences && presences.length > 0) {
|
||||
const presence = presences[0];
|
||||
switch (type) {
|
||||
case PresenceType.PC:
|
||||
status = !!presence ? presence.pcStatus : undefined;
|
||||
break;
|
||||
case PresenceType.MOBILE:
|
||||
status = !!presence ? presence.mobileStatus : undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!!status) {
|
||||
switch (status) {
|
||||
case StatusCode.OnLine:
|
||||
rtnClass = type + 'On';
|
||||
break;
|
||||
case StatusCode.Away:
|
||||
rtnClass = type + 'Out';
|
||||
break;
|
||||
case StatusCode.Busy:
|
||||
rtnClass = type + 'Other';
|
||||
break;
|
||||
default:
|
||||
rtnClass = type + 'Off';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rtnClass = type + 'Off';
|
||||
}
|
||||
|
||||
return rtnClass;
|
||||
}
|
||||
getPresenceMsg(userInfo: UserInfoSS): string {
|
||||
const presences = this.presence.filter(p => p.userSeq === userInfo.seq);
|
||||
|
||||
if (!!presences && presences.length > 0) {
|
||||
const presence = presences[0];
|
||||
if (
|
||||
!!presence &&
|
||||
!!presence.statusMessage &&
|
||||
presence.statusMessage !== '.'
|
||||
) {
|
||||
return presence.statusMessage;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
getWorkstatusInfo(userInfo: UserInfoSS, type: string): string {
|
||||
let workstatus = userInfo.workstatus;
|
||||
|
||||
const presences = this.presence.filter(p => p.userSeq === userInfo.seq);
|
||||
|
||||
if (!!presences && presences.length > 0) {
|
||||
const presence = presences[0];
|
||||
if (
|
||||
!!presence &&
|
||||
!!presence.workstatus &&
|
||||
presence.workstatus.trim().length > 0
|
||||
) {
|
||||
workstatus = presence.workstatus;
|
||||
}
|
||||
}
|
||||
|
||||
let text = '';
|
||||
// morning-off: 오전 afternoon-off: 오후 day-off: 휴가 long-time: 장기 leave-of-absence: 휴직
|
||||
let style = '';
|
||||
switch (workstatus) {
|
||||
case WorkStatusType.VacationAM:
|
||||
style = 'morning-off';
|
||||
text = '오전';
|
||||
break;
|
||||
case WorkStatusType.VacationPM:
|
||||
style = 'afternoon-off';
|
||||
text = '오후';
|
||||
break;
|
||||
case WorkStatusType.VacationAll:
|
||||
style = 'day-off';
|
||||
text = '휴가';
|
||||
break;
|
||||
case WorkStatusType.LeaveOfAbsence:
|
||||
style = 'leave-of-absence';
|
||||
text = '휴직';
|
||||
break;
|
||||
case WorkStatusType.LongtermRefresh:
|
||||
style = 'long-time';
|
||||
text = '장기';
|
||||
break;
|
||||
}
|
||||
|
||||
return type === 'text' ? text : style;
|
||||
}
|
||||
|
||||
sortData(sort: Sort) {
|
||||
console.log(sort);
|
||||
const data = this.selectedDepartmentUserInfoList.slice();
|
||||
if (!sort.active || sort.direction === '') {
|
||||
this.sortedData = data;
|
||||
return;
|
||||
}
|
||||
|
||||
this.sortedData = data.sort((a, b) => {
|
||||
const isAsc = sort.direction === 'asc';
|
||||
switch (sort.active) {
|
||||
case 'name':
|
||||
return this.compare(a.name, b.name, isAsc);
|
||||
case 'grade':
|
||||
return this.compare(a.grade, b.grade, isAsc);
|
||||
case 'dept':
|
||||
return this.compare(a.deptName, b.deptName, isAsc);
|
||||
case 'company':
|
||||
return this.compare(a.companyName, b.companyName, isAsc);
|
||||
case 'hpNumber':
|
||||
return this.compare(a.hpNumber, b.hpNumber, isAsc);
|
||||
case 'workplace':
|
||||
return this.compare(a.workplace, b.workplace, isAsc);
|
||||
case 'lineNumber':
|
||||
return this.compare(a.lineNumber, b.lineNumber, isAsc);
|
||||
case 'responsibilities':
|
||||
return this.compare(a.responsibilities, b.responsibilities, isAsc);
|
||||
case 'email':
|
||||
return this.compare(a.email, b.email, isAsc);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
compare(a: number | string, b: number | string, isAsc: boolean) {
|
||||
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||
}
|
||||
|
||||
/** 전체 체크여부 */
|
||||
getCheckedAllUser() {
|
||||
// if (!this.loginRes) {
|
||||
// return false;
|
||||
// }
|
||||
// const compareList: UserInfoSS[] = this.isShowSearch
|
||||
// ? this.searchUserInfos
|
||||
// : this.selectedDepartmentUserInfoList;
|
||||
// if (
|
||||
// !compareList ||
|
||||
// compareList.length === 0 ||
|
||||
// compareList
|
||||
// .filter(item => item.seq !== this.loginRes.userSeq)
|
||||
// .filter(
|
||||
// item =>
|
||||
// !(
|
||||
// this.selectedUserList.filter(user => user.seq === item.seq)
|
||||
// .length > 0
|
||||
// )
|
||||
// ).length > 0
|
||||
// ) {
|
||||
// return false;
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
/** 전체선택 이벤트 */
|
||||
onCheckAllUser(value: boolean) {
|
||||
// if (!this.loginRes) {
|
||||
// return false;
|
||||
// }
|
||||
// this.checkAllUser.emit({
|
||||
// isChecked: value,
|
||||
// userInfos: (this.isShowSearch
|
||||
// ? this.searchUserInfos
|
||||
// : this.selectedDepartmentUserInfoList
|
||||
// ).filter(user => user.seq !== this.loginRes.userSeq)
|
||||
// });
|
||||
// this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
onClickOpenProfile(event: MouseEvent, userSeq: number) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.openProfile.emit(userSeq);
|
||||
}
|
||||
}
|
|
@ -22,9 +22,14 @@ import { UCapUiModule } from '@ucap-webmessenger/ui';
|
|||
|
||||
import { TenantSearchComponent } from './components/tenant-search.component';
|
||||
import { TreeComponent } from './components/tree.component';
|
||||
import { DetailTableComponent } from './components/detail-table.component';
|
||||
import { OrganizationService } from './services/organization.service';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
|
||||
const COMPONENTS = [TenantSearchComponent, TreeComponent];
|
||||
const COMPONENTS = [TenantSearchComponent, TreeComponent, DetailTableComponent];
|
||||
const SERVICES = [OrganizationService];
|
||||
const DIRECTIVES = [];
|
||||
|
||||
|
@ -42,6 +47,10 @@ const DIRECTIVES = [];
|
|||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatTreeModule,
|
||||
MatTooltipModule,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatCheckboxModule,
|
||||
|
||||
TranslateModule,
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Public API Surface of ucap-webmessenger-ui-organization
|
||||
*/
|
||||
export * from './lib/components/detail-table.component';
|
||||
export * from './lib/components/tenant-search.component';
|
||||
export * from './lib/components/tree.component';
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||
@Pipe({ name: 'ucapStringEmptycheck' })
|
||||
export class StringEmptyCheckPipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
return value.trim().length > 0 ? value.trim() : '-';
|
||||
return !!value && value.trim().length > 0 ? value.trim() : '-';
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'ucapStringFormatterPhone' })
|
||||
export class StringFormatterPhonePipe implements PipeTransform {
|
||||
transform(value: string, hidden?: boolean): string {
|
||||
const num = value.trim().replace(/[-, ]/g, '');
|
||||
const num = value.replace(/[-, ]/g, '');
|
||||
let formatNum = '';
|
||||
if (num.length === 0) {
|
||||
return num;
|
||||
|
|
Loading…
Reference in New Issue
Block a user