조회 부서원 리스트 컴포넌트 분리

This commit is contained in:
leejinho 2020-03-05 11:10:33 +09:00
parent d52ace1f03
commit 7750c94001
13 changed files with 640 additions and 411 deletions

View File

@ -14,6 +14,7 @@
></app-layout-messenger-messages> ></app-layout-messenger-messages>
<app-layout-messenger-organization <app-layout-messenger-organization
(openProfile)="onClickOpenProfile($event)"
[style.display]=" [style.display]="
MainMenu.Organization === (this.gnbMenuIndex$ | async) ? 'block' : 'none' MainMenu.Organization === (this.gnbMenuIndex$ | async) ? 'block' : 'none'
" "

View File

@ -20,6 +20,7 @@ export class MainContentsComponent implements OnInit {
openProfile = new EventEmitter<{ openProfile = new EventEmitter<{
userSeq: number; userSeq: number;
}>(); }>();
@Output() @Output()
closeRightDrawer = new EventEmitter(); closeRightDrawer = new EventEmitter();
@ -34,10 +35,11 @@ export class MainContentsComponent implements OnInit {
); );
} }
onClickOpenProfile(params: { onClickOpenProfile(userSeq: number) {
userSeq: number; this.openProfile.emit({ userSeq });
openProfileOptions?: OpenProfileOptions; }
}) {}
onCloseRightDrawer() {} onCloseRightDrawer() {
this.closeRightDrawer.emit();
}
} }

View File

@ -147,9 +147,7 @@ import { AuthResponse } from '@ucap-webmessenger/protocol-query';
}) })
export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit { export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
@Output() @Output()
openProfile = new EventEmitter<{ openProfile = new EventEmitter<number>();
userSeq: number;
}>();
@Output() @Output()
closeRightDrawer = new EventEmitter(); closeRightDrawer = new EventEmitter();
@ -1728,7 +1726,7 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
roomType !== RoomType.Allim_Elephant && roomType !== RoomType.Allim_Elephant &&
roomType !== RoomType.Allim_TMS roomType !== RoomType.Allim_TMS
) { ) {
this.openProfile.emit({ userSeq }); this.openProfile.emit(userSeq);
} }
} }

View File

@ -8,7 +8,7 @@
></div> ></div>
<div class="organization-info"> <div class="organization-info">
<h3 class="organization-name"> <h3 class="organization-name">
<ng-container *ngIf="!(isSearch$ | async)"> <ng-container *ngIf="!(isSearch | async)">
<ng-container <ng-container
*ngIf=" *ngIf="
!!(selectedDepartmentProcessing$ | async); !!(selectedDepartmentProcessing$ | async);
@ -21,10 +21,10 @@
{{ selectedDepartment$ | async | ucapTranslate: 'name' }} {{ selectedDepartment$ | async | ucapTranslate: 'name' }}
</ng-template> </ng-template>
</ng-container> </ng-container>
<ng-container *ngIf="!!(isSearch$ | async)"> <ng-container *ngIf="!!(isSearch | async)">
{{ 'common.searchResult' | translate {{ 'common.searchResult' | translate
}}<span class="text-accent-color" }}<span class="text-accent-color"
>({{ (searchDepartmentUserInfoList$ | async).length }} >({{ selectedDepartmentUserInfoList.length }}
{{ 'common.units.persons' | translate }})</span {{ 'common.units.persons' | translate }})</span
> >
</ng-container> </ng-container>
@ -46,156 +46,40 @@
</div> </div>
</mat-toolbar> </mat-toolbar>
<div fxFlex="1 1 auto" class="organization-content"> <div fxFlex="auto" class="organization-content">
<div fxFlex="0 0 auto" class="table-box"> <div fxFlex="0 0 auto" class="table-box">
<perfect-scrollbar class="scrollbar"> <ucap-organization-detail-table
<table mat-table [dataSource]="departmentUserInfoList$ | async"> [presence]="presence$ | async"
<ng-container matColumnDef="profile"> [selectedDepartmentUserInfoList]="selectedDepartmentUserInfoList"
<th mat-header-cell *matHeaderCellDef class="profile"> [profileImageRoot]="profileImageRoot"
{{ 'search.fieldProfile' | translate }} (openProfile)="onClickOpenProfile($event)"
</th> class="detail-table"
<td mat-cell *matCellDef="let element"> ></ucap-organization-detail-table>
<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>
</div> </div>
</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> </div>

View File

@ -99,114 +99,7 @@
overflow: hidden; overflow: hidden;
display: flex; display: flex;
table { .detail-table {
table-layout: fixed;
width: 100%; 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;
}

View File

@ -18,7 +18,7 @@ import * as QueryStore from '@app/store/messenger/query';
import * as SyncStore from '@app/store/messenger/sync'; import * as SyncStore from '@app/store/messenger/sync';
import * as ChatStore from '@app/store/messenger/chat'; import * as ChatStore from '@app/store/messenger/chat';
import * as StatusStore from '@app/store/messenger/status'; 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 { PresenceType, StatusCode } from '@ucap-webmessenger/core';
import { import {
StatusBulkInfo, StatusBulkInfo,
@ -26,6 +26,7 @@ import {
} from '@ucap-webmessenger/protocol-status'; } from '@ucap-webmessenger/protocol-status';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import { KEY_VER_INFO } from '@app/types'; import { KEY_VER_INFO } from '@app/types';
import { Sort } from '@angular/material/sort';
@Component({ @Component({
selector: 'app-layout-messenger-organization', selector: 'app-layout-messenger-organization',
@ -38,28 +39,19 @@ export class OrganizationComponent implements OnInit, OnDestroy {
sessionVerinfo: VersionInfo2Response; sessionVerinfo: VersionInfo2Response;
isSearch$: Observable<boolean>; isSearch: boolean;
isSearchSubscription: Subscription;
selectedDepartmentProcessing$: Observable<boolean>; selectedDepartmentProcessing$: Observable<boolean>;
selectedDepartment$: Observable<SelectedDept>; selectedDepartment$: Observable<SelectedDept>;
departmentUserInfoList$: Observable<UserInfoSS[]>; departmentUserInfoListSubscription: Subscription;
searchDepartmentUserInfoList$: Observable<UserInfoSS[]>; searchDepartmentUserInfoListSubscription: Subscription;
selectedDepartmentUserInfoList: UserInfoSS[] = [];
profileImageRoot: string; profileImageRoot: string;
presence$: Observable<StatusBulkInfo[]>;
PresenceType = PresenceType; PresenceType = PresenceType;
presenceSubscription: Subscription;
presence: StatusBulkInfo[] = [];
displayedColumns: string[] = [
'profile',
'deptName',
'companyName',
'grade',
'lineNumber',
'hpNumber',
'email',
'responsibilities',
'workplace'
];
constructor( constructor(
private store: Store<any>, private store: Store<any>,
@ -75,9 +67,11 @@ export class OrganizationComponent implements OnInit, OnDestroy {
} }
ngOnInit() { ngOnInit() {
this.isSearch$ = this.store.pipe( this.isSearchSubscription = this.store
select(AppStore.MessengerSelector.QuerySelector.isSearch) .pipe(select(AppStore.MessengerSelector.QuerySelector.isSearch))
); .subscribe(isSearch => {
this.isSearch = isSearch;
});
this.selectedDepartmentProcessing$ = this.store.pipe( this.selectedDepartmentProcessing$ = this.store.pipe(
select( select(
@ -89,141 +83,48 @@ export class OrganizationComponent implements OnInit, OnDestroy {
select(AppStore.MessengerSelector.QuerySelector.selectedDepartment) select(AppStore.MessengerSelector.QuerySelector.selectedDepartment)
); );
this.departmentUserInfoList$ = this.store.pipe( this.departmentUserInfoListSubscription = this.store
.pipe(
select(AppStore.MessengerSelector.QuerySelector.departmentUserInfoList) select(AppStore.MessengerSelector.QuerySelector.departmentUserInfoList)
); )
.subscribe(list => {
if (!this.isSearch) {
this.selectedDepartmentUserInfoList = list;
}
});
this.searchDepartmentUserInfoList$ = this.store.pipe( this.searchDepartmentUserInfoListSubscription = this.store
.pipe(
select( select(
AppStore.MessengerSelector.QuerySelector.searchDepartmentUserInfoList AppStore.MessengerSelector.QuerySelector.searchDepartmentUserInfoList
) )
);
this.presenceSubscription = this.store
.pipe(
select(
AppStore.MessengerSelector.StatusSelector.selectAllStatusBulkInfo
) )
) .subscribe(list => {
.subscribe(presence => { if (!!this.isSearch) {
this.presence = presence; this.selectedDepartmentUserInfoList = list;
}
}); });
this.presence$ = this.store.pipe(
select(AppStore.MessengerSelector.StatusSelector.selectAllStatusBulkInfo)
);
this.profileImageRoot = this.sessionVerinfo.profileRoot; this.profileImageRoot = this.sessionVerinfo.profileRoot;
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (!!this.presenceSubscription) { if (!!this.isSearchSubscription) {
this.presenceSubscription.unsubscribe(); this.isSearchSubscription.unsubscribe();
}
if (!!this.departmentUserInfoListSubscription) {
this.departmentUserInfoListSubscription.unsubscribe();
}
if (!!this.searchDepartmentUserInfoListSubscription) {
this.searchDepartmentUserInfoListSubscription.unsubscribe();
} }
} }
/******************************* onClickOpenProfile(userSeq: number) {
* 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();
this.openProfile.emit(userSeq); this.openProfile.emit(userSeq);
} }
} }

View File

@ -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>

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

@ -22,9 +22,14 @@ import { UCapUiModule } from '@ucap-webmessenger/ui';
import { TenantSearchComponent } from './components/tenant-search.component'; import { TenantSearchComponent } from './components/tenant-search.component';
import { TreeComponent } from './components/tree.component'; import { TreeComponent } from './components/tree.component';
import { DetailTableComponent } from './components/detail-table.component';
import { OrganizationService } from './services/organization.service'; 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 SERVICES = [OrganizationService];
const DIRECTIVES = []; const DIRECTIVES = [];
@ -42,6 +47,10 @@ const DIRECTIVES = [];
MatInputModule, MatInputModule,
MatSelectModule, MatSelectModule,
MatTreeModule, MatTreeModule,
MatTooltipModule,
MatTableModule,
MatSortModule,
MatCheckboxModule,
TranslateModule, TranslateModule,

View File

@ -1,6 +1,7 @@
/* /*
* Public API Surface of ucap-webmessenger-ui-organization * 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/tenant-search.component';
export * from './lib/components/tree.component'; export * from './lib/components/tree.component';

View File

@ -3,14 +3,14 @@ import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'ucapStringEmptycheck' }) @Pipe({ name: 'ucapStringEmptycheck' })
export class StringEmptyCheckPipe implements PipeTransform { export class StringEmptyCheckPipe implements PipeTransform {
transform(value: string): string { transform(value: string): string {
return value.trim().length > 0 ? value.trim() : '-'; return !!value && value.trim().length > 0 ? value.trim() : '-';
} }
} }
@Pipe({ name: 'ucapStringFormatterPhone' }) @Pipe({ name: 'ucapStringFormatterPhone' })
export class StringFormatterPhonePipe implements PipeTransform { export class StringFormatterPhonePipe implements PipeTransform {
transform(value: string, hidden?: boolean): string { transform(value: string, hidden?: boolean): string {
const num = value.trim().replace(/[-, ]/g, ''); const num = value.replace(/[-, ]/g, '');
let formatNum = ''; let formatNum = '';
if (num.length === 0) { if (num.length === 0) {
return num; return num;