511 lines
12 KiB
TypeScript
511 lines
12 KiB
TypeScript
|
import { Subject } from 'rxjs';
|
||
|
import { takeUntil, take } from 'rxjs/operators';
|
||
|
|
||
|
import {
|
||
|
Component,
|
||
|
OnInit,
|
||
|
OnDestroy,
|
||
|
ChangeDetectionStrategy,
|
||
|
ChangeDetectorRef,
|
||
|
Input,
|
||
|
Output,
|
||
|
EventEmitter,
|
||
|
NgZone
|
||
|
} from '@angular/core';
|
||
|
|
||
|
import { Store, select } from '@ngrx/store';
|
||
|
|
||
|
import { SortOrder } from '@ucap/core';
|
||
|
|
||
|
import { VersionInfo2Response } from '@ucap/api-public';
|
||
|
import { LoginResponse } from '@ucap/protocol-authentication';
|
||
|
import {
|
||
|
UserInfoSS,
|
||
|
DeptSearchType,
|
||
|
DeptUserRequest
|
||
|
} from '@ucap/protocol-query';
|
||
|
|
||
|
import { LogService } from '@ucap/ng-logger';
|
||
|
import {
|
||
|
LoginSelector,
|
||
|
ConfigurationSelector
|
||
|
} from '@ucap/ng-store-authentication';
|
||
|
import { QueryProtocolService } from '@ucap/ng-protocol-query';
|
||
|
import {
|
||
|
DepartmentActions,
|
||
|
DepartmentSelector,
|
||
|
PresenceActions
|
||
|
} from '@ucap/ng-store-organization';
|
||
|
|
||
|
import { SearchData } from '../models/search-data';
|
||
|
|
||
|
const DEPT_ORDER_PROPERTY = 'order';
|
||
|
|
||
|
interface CheckedInfo {
|
||
|
checked: boolean;
|
||
|
userInfo: UserInfoSS;
|
||
|
}
|
||
|
|
||
|
@Component({
|
||
|
selector: 'app-organization-profile-list',
|
||
|
templateUrl: './profile-list.component.html',
|
||
|
styleUrls: ['./profile-list.component.scss'],
|
||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||
|
})
|
||
|
export class ProfileListComponent implements OnInit, OnDestroy {
|
||
|
@Input()
|
||
|
set searchData(data: SearchData) {
|
||
|
if (!this.loginRes) {
|
||
|
this._searchData = data;
|
||
|
return;
|
||
|
}
|
||
|
this.searchMember(data);
|
||
|
}
|
||
|
// tslint:disable-next-line: variable-name
|
||
|
private _searchData: SearchData;
|
||
|
|
||
|
@Input()
|
||
|
selectedUser: UserInfoSS[];
|
||
|
|
||
|
@Input()
|
||
|
/** Cycle order of sorting by use ascending: undefined -> true -> false */
|
||
|
set sortOrder(value: SortOrder) {
|
||
|
this._sortOrder = value;
|
||
|
this.userInfos = this.sort(this.userInfos);
|
||
|
}
|
||
|
get sortOrder() {
|
||
|
return this._sortOrder;
|
||
|
}
|
||
|
// tslint:disable-next-line: variable-name
|
||
|
_sortOrder: SortOrder = {
|
||
|
property: 'name',
|
||
|
ascending: undefined
|
||
|
};
|
||
|
|
||
|
@Output()
|
||
|
searched: EventEmitter<UserInfoSS[]> = new EventEmitter();
|
||
|
|
||
|
@Output()
|
||
|
changedCheck: EventEmitter<CheckedInfo[]> = new EventEmitter();
|
||
|
|
||
|
set userInfos(userInfos: UserInfoSS[]) {
|
||
|
this._userInfos = userInfos;
|
||
|
this.searched.emit(userInfos);
|
||
|
}
|
||
|
get userInfos() {
|
||
|
return this._userInfos;
|
||
|
}
|
||
|
// tslint:disable-next-line: variable-name
|
||
|
_userInfos: UserInfoSS[] = [];
|
||
|
|
||
|
loginRes: LoginResponse;
|
||
|
versionInfo2Res: VersionInfo2Response;
|
||
|
|
||
|
processing = false;
|
||
|
|
||
|
private ngOnDestroySubject: Subject<void> = new Subject();
|
||
|
private myDeptDestroySubject: Subject<void>;
|
||
|
|
||
|
constructor(
|
||
|
private queryProtocolService: QueryProtocolService,
|
||
|
private store: Store<any>,
|
||
|
private changeDetectorRef: ChangeDetectorRef,
|
||
|
private logService: LogService
|
||
|
) {}
|
||
|
|
||
|
ngOnInit(): void {
|
||
|
this.store
|
||
|
.pipe(
|
||
|
takeUntil(this.ngOnDestroySubject),
|
||
|
select(ConfigurationSelector.versionInfo2Response)
|
||
|
)
|
||
|
.subscribe((versionInfo2Res) => {
|
||
|
this.versionInfo2Res = versionInfo2Res;
|
||
|
});
|
||
|
|
||
|
this.store
|
||
|
.pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes))
|
||
|
.subscribe((loginRes) => {
|
||
|
this.loginRes = loginRes;
|
||
|
if (!!this._searchData) {
|
||
|
this.searchMember(this._searchData);
|
||
|
this._searchData = undefined;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ngOnDestroy(): void {
|
||
|
if (!!this.ngOnDestroySubject) {
|
||
|
this.ngOnDestroySubject.next();
|
||
|
this.ngOnDestroySubject.complete();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
checkAll() {
|
||
|
if (!this.userInfos || 0 === this.userInfos.length) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const checked: CheckedInfo[] = [];
|
||
|
this.userInfos.forEach((u) => {
|
||
|
checked.push({
|
||
|
checked: true,
|
||
|
userInfo: u
|
||
|
});
|
||
|
});
|
||
|
this.changedCheck.emit(checked);
|
||
|
}
|
||
|
|
||
|
uncheckAll() {
|
||
|
if (!this.userInfos || 0 === this.userInfos.length) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const unchecked: CheckedInfo[] = [];
|
||
|
this.userInfos.forEach((u) => {
|
||
|
unchecked.push({
|
||
|
checked: false,
|
||
|
userInfo: u
|
||
|
});
|
||
|
});
|
||
|
this.changedCheck.emit(unchecked);
|
||
|
}
|
||
|
|
||
|
/** 개별 체크여부 */
|
||
|
getCheckedUser(userInfo: UserInfoSS) {
|
||
|
if (!!this.selectedUser && this.selectedUser.length > 0) {
|
||
|
return (
|
||
|
this.selectedUser.filter((item) => item.seq === userInfo.seq).length > 0
|
||
|
);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/** 개별선택(토글) 이벤트 */
|
||
|
onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoSS }) {
|
||
|
this.changedCheck.emit([param]);
|
||
|
}
|
||
|
|
||
|
onOpenProfile(userInfo: UserInfoSS): void {
|
||
|
alert('Open Profile');
|
||
|
}
|
||
|
|
||
|
private getMyDeptMember() {
|
||
|
this.myDeptDestroySubject = new Subject();
|
||
|
|
||
|
const req: DeptUserRequest = {
|
||
|
divCd: 'ORG',
|
||
|
companyCode: this.loginRes.companyCode,
|
||
|
seq: this.loginRes.departmentCode,
|
||
|
search: '',
|
||
|
searchRange: DeptSearchType.All,
|
||
|
senderCompanyCode: this.loginRes.companyCode,
|
||
|
senderEmployeeType: this.loginRes.userInfo.employeeType
|
||
|
};
|
||
|
|
||
|
this.processing = true;
|
||
|
|
||
|
this.store
|
||
|
.pipe(
|
||
|
takeUntil(this.myDeptDestroySubject),
|
||
|
select(DepartmentSelector.myDepartmentUserInfoList)
|
||
|
)
|
||
|
.subscribe(
|
||
|
(myDepartmentUserInfoList) => {
|
||
|
if (!myDepartmentUserInfoList) {
|
||
|
this.store.dispatch(DepartmentActions.myDeptUser({ req }));
|
||
|
return;
|
||
|
}
|
||
|
this._refreshUserInfos(myDepartmentUserInfoList);
|
||
|
|
||
|
this.myDeptDestroySubject.next();
|
||
|
this.myDeptDestroySubject.complete();
|
||
|
this.myDeptDestroySubject = undefined;
|
||
|
},
|
||
|
(error) => {},
|
||
|
() => {
|
||
|
this.processing = false;
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private sort(userInfos: UserInfoSS[]): UserInfoSS[] {
|
||
|
if (!userInfos || 0 === userInfos.length) {
|
||
|
return userInfos;
|
||
|
}
|
||
|
|
||
|
const property = this.sortOrder.property;
|
||
|
const ascending = this.sortOrder.ascending;
|
||
|
let deptA: any;
|
||
|
let deptB: any;
|
||
|
let c: any;
|
||
|
let d: any;
|
||
|
|
||
|
return userInfos.slice().sort((a, b) => {
|
||
|
try {
|
||
|
deptA = a[DEPT_ORDER_PROPERTY];
|
||
|
deptB = b[DEPT_ORDER_PROPERTY];
|
||
|
|
||
|
if (undefined === ascending) {
|
||
|
return deptA < deptB
|
||
|
? -1
|
||
|
: deptA > deptB
|
||
|
? 1
|
||
|
: a[property] < b[property]
|
||
|
? -1
|
||
|
: a[property] > b[property]
|
||
|
? 1
|
||
|
: 0;
|
||
|
}
|
||
|
|
||
|
c = ascending ? a[property] : b[property];
|
||
|
d = ascending ? b[property] : a[property];
|
||
|
|
||
|
return c < d ? -1 : c > d ? 1 : 0;
|
||
|
} catch (error) {
|
||
|
console.log(error);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private searchMember(searchData: SearchData) {
|
||
|
if (
|
||
|
!searchData ||
|
||
|
(!searchData.bySearch && undefined === searchData.deptSeq)
|
||
|
) {
|
||
|
this.getMyDeptMember();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!!this.myDeptDestroySubject) {
|
||
|
this.myDeptDestroySubject.next();
|
||
|
this.myDeptDestroySubject.complete();
|
||
|
this.myDeptDestroySubject = undefined;
|
||
|
}
|
||
|
|
||
|
let req: DeptUserRequest;
|
||
|
if (searchData.bySearch) {
|
||
|
req = {
|
||
|
divCd: 'ORGS',
|
||
|
companyCode: searchData.companyCode,
|
||
|
searchRange: DeptSearchType.All,
|
||
|
search: searchData.searchWord,
|
||
|
senderCompanyCode: this.loginRes.userInfo.companyCode,
|
||
|
senderEmployeeType: this.loginRes.userInfo.employeeType
|
||
|
};
|
||
|
} else {
|
||
|
req = {
|
||
|
divCd: 'ORG',
|
||
|
companyCode: this.loginRes.companyCode,
|
||
|
seq: Number(searchData.deptSeq),
|
||
|
search: '',
|
||
|
searchRange: DeptSearchType.All,
|
||
|
senderCompanyCode: this.loginRes.companyCode,
|
||
|
senderEmployeeType: this.loginRes.userInfo.employeeType
|
||
|
};
|
||
|
}
|
||
|
|
||
|
this.processing = true;
|
||
|
this.queryProtocolService
|
||
|
.deptUser(req)
|
||
|
.pipe(take(1))
|
||
|
.subscribe(
|
||
|
(data) => {
|
||
|
this._refreshUserInfos(data.userInfos);
|
||
|
},
|
||
|
(error) => {},
|
||
|
() => {
|
||
|
this.processing = false;
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private _refreshUserInfos(userInfos: UserInfoSS[]) {
|
||
|
this.userInfos = this.sort(userInfos);
|
||
|
|
||
|
// 검색 결과에 따른 프레즌스 조회.
|
||
|
const userSeqList: string[] = userInfos.map((user) => user.seq);
|
||
|
|
||
|
if (userSeqList.length > 0) {
|
||
|
this.store.dispatch(
|
||
|
PresenceActions.bulkInfo({
|
||
|
divCd: 'orgSrch',
|
||
|
userSeqs: userSeqList
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
import { Subject } from 'rxjs';
|
||
|
import { takeUntil } from 'rxjs/operators';
|
||
|
|
||
|
import {
|
||
|
Component,
|
||
|
OnInit,
|
||
|
OnDestroy,
|
||
|
ChangeDetectionStrategy,
|
||
|
ChangeDetectorRef,
|
||
|
Input
|
||
|
} from '@angular/core';
|
||
|
|
||
|
import { Store, select } from '@ngrx/store';
|
||
|
|
||
|
import { VersionInfo2Response } from '@ucap/api-public';
|
||
|
import { UserInfoSS } from '@ucap/protocol-query';
|
||
|
|
||
|
import { LogService } from '@ucap/ng-logger';
|
||
|
import { PresenceSelector } from '@ucap/ng-store-organization';
|
||
|
import { StatusBulkInfo } from '@ucap/protocol-status';
|
||
|
|
||
|
@Component({
|
||
|
selector: 'app-organization-profile-image-01',
|
||
|
templateUrl: './profile-image-01.component.html',
|
||
|
styleUrls: ['./profile-image-01.component.scss']
|
||
|
})
|
||
|
export class ProfileImage01Component implements OnInit, OnDestroy {
|
||
|
@Input()
|
||
|
set userInfo(userInfo: UserInfoSS) {
|
||
|
this._userInfo = userInfo;
|
||
|
}
|
||
|
get userInfo(): UserInfoSS {
|
||
|
return this._userInfo;
|
||
|
}
|
||
|
_userInfo: UserInfoSS;
|
||
|
|
||
|
@Input()
|
||
|
versionInfo: VersionInfo2Response;
|
||
|
|
||
|
presenceInfo: StatusBulkInfo;
|
||
|
|
||
|
private ngOnDestroySubject: Subject<void> = new Subject();
|
||
|
|
||
|
constructor(
|
||
|
private store: Store<any>,
|
||
|
private changeDetectorRef: ChangeDetectorRef,
|
||
|
private logService: LogService
|
||
|
) {}
|
||
|
|
||
|
ngOnInit(): void {
|
||
|
this.store
|
||
|
.pipe(
|
||
|
takeUntil(this.ngOnDestroySubject),
|
||
|
select(
|
||
|
PresenceSelector.selectStatusBulkInfo,
|
||
|
Number(this.userInfo?.seq)
|
||
|
)
|
||
|
)
|
||
|
.subscribe((status) => {
|
||
|
this.presenceInfo = status;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ngOnDestroy(): void {
|
||
|
if (!!this.ngOnDestroySubject) {
|
||
|
this.ngOnDestroySubject.next();
|
||
|
this.ngOnDestroySubject.complete();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
label,
|
||
|
contextmenu,
|
||
|
profile,
|
||
|
group
|
||
|
confirm,
|
||
|
error,
|
||
|
|
||
|
{
|
||
|
"label": {
|
||
|
"organization": "조직도",
|
||
|
"selectedUsers": "선택한 대화상대",
|
||
|
"addGroup": "그룹추가",
|
||
|
"chat": "대화",
|
||
|
"message": "쪽지",
|
||
|
"call": "전화",
|
||
|
"videoConference": "화상",
|
||
|
"searchResult": "검색결과",
|
||
|
"sortName": "이름"
|
||
|
},
|
||
|
"presence": {
|
||
|
"offline": "오프라인",
|
||
|
"online": "온라인",
|
||
|
"away": "부재중",
|
||
|
"statusMessage1": "다른용무중",
|
||
|
"statusMessage2": "회의중",
|
||
|
"statusMessage3": "집중근무중"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
{
|
||
|
"label": {
|
||
|
"confirmRemoveBuddy": "선택한 멤버를 삭제하시겠습니까?\n해당 그룹에서만 선택하신 멤버가 삭제됩니다."
|
||
|
},
|
||
|
"category": {
|
||
|
"favorite": "즐겨찾기",
|
||
|
"default": "기본",
|
||
|
"myDept": "소속부서"
|
||
|
},
|
||
|
"moreMenu": {
|
||
|
"show": {
|
||
|
"all": "전체 보기",
|
||
|
"onlineBuddy": "접속한 동료만 보기",
|
||
|
"onOff": "온/오프라인 보기"
|
||
|
},
|
||
|
"group": {
|
||
|
"addNew": "새 그룹 추가",
|
||
|
"expandMore": "그룹 전체 열기",
|
||
|
"expandLess": "그룹 전체 닫기",
|
||
|
"changeOrder": "그룹 순서 바꾸기",
|
||
|
"startChatWithGroup": "그룹 대화하기",
|
||
|
"sendMessageToGroup": "그룹 쪽지 보내기",
|
||
|
"groupMemberManagement": "그룹 멤버 관리",
|
||
|
"changeGroupName": "그룹 이름 바꾸기",
|
||
|
"removeGroup": "그룹 삭제"
|
||
|
},
|
||
|
"profile": {
|
||
|
"open": "프로필 보기",
|
||
|
"unfavorite": "즐겨찾기 해제",
|
||
|
"favorite": "즐겨찾기 등록",
|
||
|
"nickname": "닉네임 설정",
|
||
|
"moveBuddy": "대화상대 이동",
|
||
|
"copyBuddy": "대화상대 복사",
|
||
|
"removeBuddy": "이 그룹에서 삭제"
|
||
|
},
|
||
|
"confirm": {
|
||
|
"removeGroup": "그룹을 삭제하시겠습니까?<br/>그룹 멤버는 해당 그룹에서만 삭제됩니다."
|
||
|
},
|
||
|
"error": {
|
||
|
"label": "그룹 에러",
|
||
|
"requireName": "그룹명은 필수입력입니다.",
|
||
|
"invalidName": "유효하지 않은 그룹명입니다.",
|
||
|
"invalidSelectedUser": "선택된 유저가 존재하지 않습니다."
|
||
|
}
|
||
|
},
|
||
|
|
||
|
"profile": {
|
||
|
"labels": {
|
||
|
"myProfile": "내 프로필",
|
||
|
"company": "회사",
|
||
|
"email": "이메일",
|
||
|
"linePhoneNumber": "사무실",
|
||
|
"mobilePhoneNumber": "핸드폰",
|
||
|
"department": "부서",
|
||
|
"mytalk": "MyTalk",
|
||
|
"setting": "환경설정",
|
||
|
"chat": "대화",
|
||
|
"sms": "SMS",
|
||
|
"videoConference": "화상회의",
|
||
|
"message": "쪽지",
|
||
|
"nickname": "닉네임 미설정"
|
||
|
},
|
||
|
"fields": {
|
||
|
"intro": "인트로"
|
||
|
},
|
||
|
"errors": {}
|
||
|
}
|
||
|
}
|