This commit is contained in:
Park Byung Eun 2020-06-26 18:41:12 +09:00
parent c06a39a2f9
commit 3ac4ee7865
17 changed files with 1706 additions and 0 deletions

View File

@ -0,0 +1,94 @@
그룹 디버그
새그룹 추가
그룹명 지정하지 않았는데 "그룹지정 후 완료" 버튼으로 진행됨.
" " 블랭크 입력 후 "완료" 시 빈명칭의 그룹이 생성됨. (O)
ime 처리 X
전체 체크박스에 체크 안됨.
전체 체크박스에 체크해제 안됨.
기존 그룹 동료를 검색하면 체크가 되어 있지 않음(seq가 다른 타입)
그룹이름 바꾸기
max length 체크 안되며, 매우 길 경우 바뀌지 않음.
그룹멤버관리
프로필 수정 사항
프로필 이미지 출력 X
닉네임 길이 체크
닉네임 라벨이 없어지지 않음
기존 닉네임값 출력 되지 않음
"http://13.124.88.127:8011/ProfileImage"
/2019/12/16/PF_9824_84924202.jpg
문의
닉네임 입력 길이 문의
mat-form-field
form-field-input-searchword
ng-tns-c159-5
mat-primary
mat-form-field-type-mat-input
mat-form-field-can-float
mat-form-field-has-label
mat-form-field-should-float
mat-form-field
ng-tns-c159-7
mat-primary
mat-form-field-type-mat-input
mat-form-field-can-float
mat-form-field-should-float
mat-form-field-has-label
1 CL → SS SSVC_TYPE_QUERY_USER_SEQ_REQ 21 사용자 SEQ 검색 DivCD(s) 로그인IDs(s) 기관코드(s) 상세정보여부(s) 발신자회사코드(s) 발신자임직원유형(s)
2 SS → CL SSVC_TYPE_QUERY_USER_SEQ_DATA 22 사용자 SEQ 정보 (상세여부 E) DivCD(s) {사용자정보-F}1 {사용자정보-F}2 {사용자정보-F}n …
22 사용자 SEQ 정보 (상세여부 N) DivCD(s) {SEQ정보}1 {SEQ정보}2 {SEQ정보}n
2 SS → CL SSVC_TYPE_QUERY_USER_SEQ_DATA2 24 사용자 SEQ 정보 (상세여부 Y) DivCD(s) {사용자정보-SS}1 {사용자정보-SS}2 {사용자정보-SS}n …
3 SS → CL SSVC_TYPE_QUERY_USER_SEQ_RES 23 DivCD(s)
"66264고재범 (책임) GUC00601064210909 jbgoh@cnspartner.comN석유화학정보화팀YKY999A41454   CJaebeom Goh (Manager) Petrochemical Information Team 155YNP"
닉네임
상태메세지
대화 검색
userInfoF 프로토콜 생성 요청
ERROR TypeError: Cannot read property 'seq' of undefined
at profile-01.component.ts:156
at Array.filter (<anonymous>)
at SafeSubscriber._next (profile-01.component.ts:155)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
at Subscriber._next (Subscriber.js:72)
at Subscriber.next (Subscriber.js:49)
at DistinctUntilChangedSubscriber._next (distinctUntilChanged.js:50)
at DistinctUntilChangedSubscriber.next (Subscriber.js:49)
// switch (type) {
// case 'CHAT':
// this.appChatService.newOpenRoom(
// [this.userInfo.seq as any],
// false,
// this.loginRes
// );
// break;
// case 'MESSAGE':
// break;
// case 'MOBILE':
// break;
// case 'OFFICE':
// break;
// case 'VIDEO_CONFERENCE':
// break;
// }
assets/images/ico/img_nophoto.
http://13.124.88.127:8011/Prof
/2019/12/16/PF_9824_84924202.j

View File

@ -0,0 +1,44 @@
작업
그룹
관리 다이얼로그 컴포넌트
타이틀 변경
컨텍스트 변경에 대한 액션 변경
그룹 선택, 동료 선택 체크 처리
동료 삭제
동료 추가
현재 그룹 seq
기존 userseqs
변경된 userseqs
동료 이동
동료 복사
그룹 생성
변수
타이틀
현재 인덱스
그룹 이름
현재 선택된 유저 리스트
현재 선택된 그룹
그룹 셀렉트 컴포넌트
그룹 체크 박스 (전체, 해제)
유저 셀렉트 컴포넌트
프로필
list -> scroll checkbox label
전체 체크박스
아이템 한개 언체크 시 전체 체크박스 해제
<ng-container
*ngIf="
!!dataSource && !!dataSource.data && dataSource.data.length > 0;
else empty
"
>
<ucap-organization-profile-list [dataSource]="dataSource">

View File

@ -0,0 +1,67 @@
angular 수정
프로필 메뉴 클릭 시 사용자 바뀜
그룹 멤버 관리
그룹 복사, 그룹이동 시 선택된 유저가 없는 경우
버튼 비활성화
그룹 정렬 방식이 그룹 메뉴의 리스트와 다름
새그룹 추가시 validation 여부와 상관없이 확인 버튼 동작함
조회시 검색결과 카운트 표출안됨
조회시 검색결과 리스트 스크롤 오류
기존그룹 없을 시 출력될 화면
<perfect-scrollbar style="width: 100%; height: 100%;">
<app-group-profile-list
#groupProfileList
[searchData]="companySearchData"
[selectedUser]="selectedUserList"
[checkable]="checkable"
[isDialog]="isDialog"
(toggleCheck)="onToggleCheckUser($event)"
class="ucap-dialog-search-result-container"
></app-group-profile-list>
</perfect-scrollbar>
if (
!!datas &&
!!this.searchUserInfos &&
datas.length === this.searchUserInfos.length
) {
this.checkboxAllSearch.checked = datas[0].checked;
} else {
// if (!!this.searchUserInfos && this.searchUserInfos.length > 0) {
// const tempUserInfos: UserInfoSS[] = [];
// this.searchUserInfos.map((user) => {
// this.selectedUserList.every((selectUser) => {
// if (user.seq === selectUser.seq) {
// tempUserInfos.push(user);
// return false;
// }
// return true;
// });
// });
// if (
// tempUserInfos.length === this.searchUserInfos.length &&
// !!this.checkboxAllSearch &&
// !this.checkboxAllSearch.checked
// ) {
// this.checkboxAllSearch.checked = true;
// } else {
// this.checkboxAllSearch.checked = false;
// }
// }
this.selectedUserList.
}
그룹이동
그룹하나 선택
그룹 다중 선택
사용자 한명
사용자 다중 선택
기존 그룹에서 사용자 삭제
이동시킬 그룹으로 업데이트

View File

@ -0,0 +1,108 @@
<div class="dialog-container">
<app-layouts-default-dialog
[disableClose]="false"
(closed)="onClosed($event)"
>
<div appLayoutsDefaultDialog="header">
{{ data.title }}
</div>
<div class="dialog-body" appLayoutsDefaultDialog="body">
<mat-horizontal-stepper
fxFlexFill
[linear]="true"
#stepper
[selectedIndex]="currentStep"
>
<mat-step label="Step 1">
<div class="ucap-dialog-group-manage-container">
<div class="group-name">
<span class="sub-title">{{
data.groupBuddyList.group.name
}}</span>
<button
mat-button
(click)="onAdd(stepper)"
aria-label="대화상대 추가"
>
<mat-icon class="material-icons-outlined">person_add</mat-icon>
</button>
</div>
<div class="ucap-dialog-app-group-profile-list">
<perfect-scrollbar style="width: 100%; height: 100%;">
<app-group-profile-list-item-02
*ngFor="let userInfo of this.data.groupBuddyList.buddyList"
[userInfo]="userInfo"
[isDialog]="true"
[checked]="getCheckedUser(userInfo)"
[checkable]="true"
(changeCheckUser)="onToggleCheck($event)"
></app-group-profile-list-item-02>
</perfect-scrollbar>
</div>
</div>
</mat-step>
<mat-step label="Step 2">
<div class="ucap-dialog-app-group-select-user">
<ng-template #dialogContainer></ng-template>
<!-- <ng-container
*ngIf="currentStep > 0"
[ngSwitch]="selectContentType"
></ng-container>
<div *ngSwitchCase="ManageContentType.Add"></div> -->
</div>
<div class="ucap-dialog-organization-profile-selection">
<ucap-organization-profile-selection-01
[userInfoList]="selectedUserList"
[removableFor]="removableForSelection"
[colorFor]="colorForSelection"
(removed)="onRemovedProfileSelection($event)"
class="ucap-organization-selected-list"
>
<ng-template ucapOrganizationProfileSelection01Header>
선택된 대화상대 (<span class="number">{{
selectedUserList.length
}}</span
>)
</ng-template>
</ucap-organization-profile-selection-01>
</div>
</mat-step>
</mat-horizontal-stepper>
</div>
<div appLayoutsDefaultDialog="action" class="btn-box">
<div *ngIf="currentStep === 0">
<button mat-button mat-stroked-button (click)="onDelete(stepper)">
삭제
</button>
<button
mat-button
class="bg-primary-darkest"
(click)="onUpdateMember(stepper, GroupUserDialaogType.Copy)"
>
그룹복사
</button>
<button
mat-button
class="bg-primary-darkest"
(click)="onUpdateMember(stepper, GroupUserDialaogType.Move)"
>
그룹이동
</button>
</div>
<div *ngIf="currentStep > 0">
<button mat-button mat-stroked-button (click)="onCnacel(stepper)">
취소
</button>
<button
mat-button
class="bg-primary-darkest"
(click)="onConfirm(stepper)"
>
완료
</button>
</div>
</div>
</app-layouts-default-dialog>
</div>

View File

@ -0,0 +1,394 @@
import { Subject, of } from 'rxjs';
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Inject,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
ComponentRef
} from '@angular/core';
import { Store } from '@ngrx/store';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialog
} from '@angular/material/dialog';
import { UserInfo, GroupDetailData } from '@ucap/protocol-sync';
import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query';
import { UserInfo as RoomUserInfo } from '@ucap/protocol-room';
import { MatStepper } from '@angular/material/stepper';
import { I18nService } from '@ucap/ng-i18n';
import { GroupActions } from '@ucap/ng-store-group';
import {
AlertDialogComponent,
AlertDialogData,
AlertDialogResult,
ConfirmDialogComponent,
ConfirmDialogResult,
ConfirmDialogData
} from '@ucap/ng-ui';
import { SelectUserSectionComponent } from '../components/select-user.section.component';
import { take, map, catchError } from 'rxjs/operators';
import { SelectGroupSectionComponent } from '../components/select-group.section.component';
import { SelectUserDialogType, GroupUserDialaogType } from '@app/types';
export type UserInfoTypes =
| UserInfo
| UserInfoSS
| UserInfoF
| UserInfoDN
| RoomUserInfo;
export enum ManageContentType {
Add = 'ADD_COMPONENT',
Copy = 'COPY_COMPONENT',
Move = 'MOVE_COMPONENT',
Delete = 'DELETE_COMPONENT',
None = 'NONE_COMPONENT'
}
export interface ManageDialogData {
title: string;
groupBuddyList?: { group: GroupDetailData; buddyList: UserInfo[] };
}
export interface ManageDialogResult {
type: GroupUserDialaogType;
groupName: string;
group?: GroupDetailData;
selelctUserList?: UserInfoTypes[];
selectGroupList?: GroupDetailData[];
}
@Component({
selector: 'app-dialog-group-manage',
templateUrl: './manage.dialog.component.html',
styleUrls: ['./manage.dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ManageDialogComponent implements OnInit, OnDestroy {
constructor(
public dialogRef: MatDialogRef<ManageDialogData, ManageDialogResult>,
@Inject(MAT_DIALOG_DATA) public data: ManageDialogData,
private changeDetectorRef: ChangeDetectorRef,
private store: Store<any>,
private i18nService: I18nService,
public dialog: MatDialog,
private cfResolver: ComponentFactoryResolver
) {}
@ViewChild('dialogContainer', { static: true, read: ViewContainerRef })
dialogContainer: ViewContainerRef;
componentRef: ComponentRef<any>;
private ngOnDestroySubject: Subject<void>;
currentType: GroupUserDialaogType;
SelectUserDialogType = SelectUserDialogType;
GroupUserDialaogType = GroupUserDialaogType;
currentStep = 0;
groupName = '';
ManageContentType = ManageContentType;
selectedUserList: UserInfoTypes[];
selectedGroupList: GroupDetailData[];
selectContentType: ManageContentType;
ngOnInit(): void {
this.ngOnDestroySubject = new Subject();
this.selectedUserList = [];
this.selectContentType = ManageContentType.None;
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
onClosed(event: MouseEvent): void {
this.dialogRef.close();
}
onDelete(stepper: MatStepper) {
if (
!!this.selectedUserList &&
this.selectedUserList.length > 0 &&
this.currentStep === 0
) {
let titleStr = '';
this.selectedUserList.forEach((user, idx) => {
let userTitle = user.name + ' ' + user.grade;
if (idx < this.selectedUserList.length) {
userTitle = userTitle + ', ';
}
titleStr = titleStr.concat('', userTitle);
});
const dialogRef = this.dialog.open<
ConfirmDialogComponent,
ConfirmDialogData,
ConfirmDialogResult
>(ConfirmDialogComponent, {
panelClass: 'min-create-dialog',
data: {
title: '동료 삭제',
html: titleStr + '을 삭제하시겠습니까?'
}
});
dialogRef
.afterClosed()
.pipe(
take(1),
map((result) => {
if (!!result && result.choice) {
const trgtUserSeq: string[] = [];
this.selectedUserList.forEach((userIfno) => {
const tempSeqs = this.data.groupBuddyList.group.userSeqs.filter(
(seq) => seq !== userIfno.seq
);
tempSeqs.map((seq) => trgtUserSeq.push(seq));
});
console.log(trgtUserSeq);
this.store.dispatch(
GroupActions.updateMember({
targetGroup: this.data.groupBuddyList.group,
targetUserSeqs: trgtUserSeq
})
);
this.dialogRef.close();
}
}),
catchError((err) => {
return of(err);
})
)
.subscribe();
}
}
onUpdateMember(stepper: MatStepper, type: GroupUserDialaogType) {
this.dialogContainer.clear();
this.currentType = type;
this.selectedGroupList = [];
const isMemberMove = type === GroupUserDialaogType.Copy ? false : true;
const factory = this.cfResolver.resolveComponentFactory(
SelectGroupSectionComponent
);
this.componentRef = this.dialogContainer.createComponent(factory);
const cpInstance = this.componentRef.instance;
// cpInstance.title = title;
cpInstance.isMemberMove = isMemberMove;
cpInstance.isDialog = true;
cpInstance.checkable = true;
cpInstance.curGroup = this.data.groupBuddyList.group;
cpInstance.selectedGroupList = this.selectedGroupList;
cpInstance.selectedUserList = this.selectedUserList;
cpInstance.changeUserList.subscribe(
(datas: { checked: boolean; userInfo: UserInfoSS }[]) => {
this.changeSelectedUserList(datas);
cpInstance.selectedUserList = this.selectedUserList;
}
);
cpInstance.changeGroupList.subscribe((data: { group: GroupDetailData }) => {
if (
this.selectedGroupList.filter((g) => g.seq === data.group.seq)
.length === 0
) {
this.selectedGroupList = [...this.selectedGroupList, data.group];
} else {
this.selectedGroupList = this.selectedGroupList.filter(
(g) => g.seq !== data.group.seq
);
}
cpInstance.selectedGroupList = this.selectedGroupList;
});
cpInstance.changeGroupName.subscribe((groupName) => {
this.groupName = groupName;
});
this.currentStep++;
stepper.next();
}
onAdd(stepper: MatStepper) {
this.dialogContainer.clear();
this.currentType = GroupUserDialaogType.Add;
const factory = this.cfResolver.resolveComponentFactory(
SelectUserSectionComponent
);
this.componentRef = this.dialogContainer.createComponent(factory);
const cpInstance = this.componentRef.instance;
cpInstance.isDialog = true;
cpInstance.checkable = true;
cpInstance.selectedUserList = this.data.groupBuddyList.buddyList;
// const cpElement: HTMLElement = this.componentRef.location.nativeElement;
// cpElement.style.height = '400px';
cpInstance.toggleCheckUser.subscribe(
(datas: { checked: boolean; userInfo: UserInfoSS }[]) => {
this.changeSelectedUserList(datas);
cpInstance.selectedUserList = this.selectedUserList;
}
);
this.currentStep++;
stepper.next();
}
onChangeUserList(datas: { checked: boolean; userInfo: UserInfoSS }[]) {
this.changeSelectedUserList(datas);
}
private changeSelectedUserList(
datas: {
checked: boolean;
userInfo: UserInfoSS;
}[]
) {
if (!datas || 0 === datas.length) {
return;
}
const pushs: UserInfoSS[] = [];
const pops: UserInfoSS[] = [];
datas.forEach((d) => {
const i = this.selectedUserList.findIndex(
(u) => u.seq === d.userInfo.seq
);
if (d.checked) {
if (-1 === i) {
pushs.push(d.userInfo);
}
} else {
if (-1 < i) {
pops.push(d.userInfo);
}
}
});
if (0 < pushs.length) {
this.selectedUserList = [...this.selectedUserList, ...pushs];
}
if (0 < pops.length) {
this.selectedUserList = this.selectedUserList.filter(
(u) => -1 === pops.findIndex((p) => p.seq === u.seq)
);
}
}
onCnacel(stepper: MatStepper) {
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
this.selectedUserList = [];
}
this.currentStep--;
stepper.previous();
}
onConfirm(stepper: MatStepper) {
switch (this.currentType) {
case GroupUserDialaogType.Add:
{
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
this.doAction();
}
}
break;
case GroupUserDialaogType.Copy:
case GroupUserDialaogType.Move:
{
if (
!!this.selectedUserList &&
this.selectedUserList.length === 0 &&
this.groupName === ''
) {
this.dialog.open<
AlertDialogComponent,
AlertDialogData,
AlertDialogResult
>(AlertDialogComponent, {
panelClass: 'min-create-dialog',
data: {
title: 'Error',
html: '선택된 유저가 없습니다.'
}
});
return;
}
this.doAction();
}
break;
}
}
doAction() {
this.dialogContainer.clear();
if (!!this.groupName && this.groupName.trim().localeCompare('') !== 0) {
this.currentType = GroupUserDialaogType.Create;
} else {
this.groupName = undefined;
}
this.dialogRef.close({
type: this.currentType,
groupName: this.groupName,
group: this.data.groupBuddyList.group,
selelctUserList: this.selectedUserList,
selectGroupList: this.selectedGroupList
});
}
/** 개별 체크여부 */
getCheckedUser(userInfo: UserInfoSS) {
if (!!this.selectedUserList && this.selectedUserList.length > 0) {
return (
this.selectedUserList.filter(
(item) => (item.seq as any) === (userInfo.seq as any)
).length > 0
);
}
return false;
}
onToggleCheck(data: { checked: boolean; userInfo: UserInfoSS }) {
this.changeSelectedUserList([data]);
}
onRemovedProfileSelection(userInfo: UserInfo) {
const i = this.selectedUserList.findIndex(
(u) => (u.seq as any) === (userInfo.seq as any)
);
if (-1 < i) {
this.selectedUserList = this.selectedUserList.filter(
(u) => (u.seq as any) !== (userInfo.seq as any)
);
}
}
removableForSelection = (userInfo: UserInfo) => {
return true;
};
colorForSelection = (userInfo: UserInfo) => {
return 'accent';
};
}

View File

@ -0,0 +1,510 @@
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": {}
}
}

View File

@ -0,0 +1,40 @@
기획 문의
그룹복사, 그룹이동 시 그룹 이름과 사용자들을 선택 시 -> 그룹생성으로 간주? 아니면 그룹생성 그룹 복사 둘다? 문의
<app-group-expansion
#appGroupExpansion
[showType]="showType"
(clicked)="onClickUser($event)"
(selectGroupMenu)="onSelectGroupMenu($event)"
(selectProfileMenu)="onSelectProfileMenu($event)"
(floatingProfileMenu)="onFloatingProfileMenu($event)"
(profileMenu)="onProfileMenu($event)"
>
_ngcontent-laf-c391
<div _ngcontent-laf-c391="" fxflex="1 1 auto" ng-reflect-fx-flex="1 1 auto" style="flex: 1 1
auto;box-sizing: border-box;"><app-organization-profile-list _ngcontent-laf-c391=""
_nghost-laf-c296="" ng-reflect-search-data="[object Object]"
ng-reflect-selected-user="[object Object],[object Object"
ng-reflect-sort-order="[object Object]">
{StatusCode: "500", ErrorMessage: "empty UserSeq", ProfileURL: "", ProfileSubDir: "", Intro: ""}
ErrorMessage: "empty UserSeq"
Intro: ""
ProfileSubDir: ""
ProfileURL: ""
StatusCode: "500"
554, 773966
701307
476791
8890
677714

View File

@ -0,0 +1,102 @@
프로필 이미지
리듀서
loginRes 업데이트
액션
프로필 변경 액션 정의
notification 액션 정의
이펙트
대화방 프로필 이미지 업데이트
대상 RoomType: bot, single
대화방 유저 리스트 프로필 이미지 업데이트
대화방 리스트, 대화방 유저 프로필 이미지
roomUsersShort.userInfos
동료 프로필 이미지 업데이트
BuddySelector.buddies
동료, 대화방 프로필 이미지 스테이트 확인
자신 프로필 변경
스토어 변경
동료 프로필 변경 알림
동료, 대화방 프로필 변경
프로필 변경 데이터
updateUserInfoNoti
updateProfileImageNoti
updateBuddyProfile
대화방
user seq find -> room info -> userList||userShort -> update
this.store.pipe(select(RoomSelector.rooms)),
this.store.pipe(select(RoomSelector.roomUsers)),
this.store.pipe(select(RoomSelector.roomUsersShort))
roomUsers = (roomUsers || []).filter(
(userMap) =>
rooms.findIndex((roomInfo) => roomInfo.roomId === userMap.roomId) >
-1
);
roomUsersShort = (roomUsersShort || []).filter(
(userMap) =>
rooms.findIndex((roomInfo) => roomInfo.roomId === userMap.roomId) >
-1
);
const recommendedWordList = [];
for (const r of rooms) {
if (!!r.roomName && '' !== r.roomName.trim()) {
recommendedWordList.push(r.roomName);
}
}
for (const ru of roomUsers) {
for (const u of ru.userInfos) {
if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) {
if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) {
recommendedWordList.push(u.name);
}
}
}
}
for (const ru of roomUsersShort) {
for (const u of ru.userInfos) {
if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) {
if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) {
recommendedWordList.push(u.name);
}
}
}
}
this.recommendedWordList = [
...recommendedWordList.filter(
(item, index) => recommendedWordList.indexOf(item) === index
)
];
auth 12
group 17
org 12
chat 44
const profile = {
userSeq: this.loginRes.userSeq + '',
deviceType: 'W',
token: this.loginRes.tokenString,
file: profileImageFileUploadItem.file,
fileUploadItem: profileImageFileUploadItem
} as FileProfileSaveRequest;
this.appFileServie.fileProfileSave(
profile,
this.versionInfo2Res.profileUploadUrl
);

View File

@ -0,0 +1,39 @@
const findIdx = noti.info.indexOf('ProfileImage');
let imgInfo: string = noti.info;
if (findIdx > -1) {
const startIdx = noti.info.indexOf('/', findIdx);
imgInfo = noti.info.substring(startIdx);
}
const roomInfo1 = rooms.filter(
(r) => r.roomId === ru.roomId
)[0];
const cUser = {
...u,
profileImageFile: 'imgInfo'
};
const idx = ru.userInfos.findIndex(
(u3) => Number(u3.seq) === Number(u.seq)
);
if (idx > -1) {
ru.userInfos[idx] = cUser;
}
const tempShort: UserInfoShort[] = [];
ru.userInfos.map((u2) => {
if (Number(u2.seq) === Number(u.seq)) {
tempShort.push(cUser);
} else {
tempShort.push(u2);
}
});
const tempUserShort = [...ru.userInfos, cUser];
tempRoomList.push({
roomInfo: roomInfo1,
userInfoS: tempShort
});
/2020/06/24/PF_338127_103928272.jpg

View File

@ -0,0 +1 @@
즐겨찾기 구성원을 모두 해제 시 즐겨찾기 그룹 없어지지 않음

View File

@ -0,0 +1,124 @@
this.mediaObserver
.asObservable()
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((changes) => {
if (!changes || 0 === changes.length) {
return;
}
for (const change of changes) {
switch (change.mqAlias) {
case 'lt-sm':
this.layoutMode = 'min';
this.changeDetectorRef.detectChanges();
return;
case 'sm':
case 'md':
this.layoutMode = 'mid';
this.changeDetectorRef.detectChanges();
return;
case 'gt-md':
this.layoutMode = 'max';
this.changeDetectorRef.detectChanges();
return;
default:
break;
}
}
});
}
대화 이슈
리스트
백 스페이스로 입력된 검색어 모두 삭제 시 기존 리스트를 출력 하지 않음
대화
폼 컴포넌트 입력창 간헐적으로 입력이 안됨
-> 한글일 때 키보드 연속으로 누르고 있을 때 화면에 나타나지 않다가 key up 할때 한번에 입력
번역
간략보기 기능 미구현
대화방 멤버
me 표시 없음
프레즌스 표현안됨
컨텍스트 메뉴 (미구현)
프로필, 대화, 쪽지
삭제, 회수
모바일, 피시 간 연동 안됨, 상대방 대화에서 회수되지 않음(방이동 이나 새로고침)
다이얼로그
일반 대화방 생성
-> 인원 선택 X 대화방 생성 버튼 활성화
타이머 대화방 상동
1:n 대화방 생성 후 '대화방 이름 설정 안내 팝업' 노출되지 않음
대화 x
type(pin):"J"
senderSeq(pin):"770074"
타이머 설정 안내 팝업 노출되지 않음
<form name="chatSettingForm" [formGroup]="chatSettingForm" novalidate>
<div class="ucap-chat-room-setting-body">
<div class="setting-content-box roomName">
<span class="tit-setting-content">{{
'chat:dialog.roomName' | ucapI18n
}}</span>
<mat-form-field
hintLabel="{{ 'common:useSpecialCharactor' | ucapI18n }}"
style="display: block; margin-bottom: 10px;"
color="accent"
>
<input
matInput
#input
maxlength="20"
formControlName="roomName"
(keyup)="onKeyupName()"
/>
<mat-hint align="end"
><em>{{ input.value?.length || 0 }}</em
>/20</mat-hint
>
</mat-form-field>
</div>
<div class="setting-content-box roomName-target">
<span class="tit-setting-content">{{
'chat:dialog.roomNameChangeTarget' | ucapI18n
}}</span>
<mat-radio-group
aria-label="Select an option"
value="me"
formControlName="changeTarget"
>
<mat-radio-button value="me">{{
'chat:dialog.me' | ucapI18n
}}</mat-radio-button>
<mat-radio-button value="all">{{
'chat:dialog.all' | ucapI18n
}}</mat-radio-button>
</mat-radio-group>
<!-- <div class="radio-hint-copy">
※ 설정시간이 초과되면 대화내용이 삭제됩니다.
</div> -->
</div>
<div
class="setting-content-box timer"
[style.display]="!!roomInfo?.isTimeRoom ? 'block' : 'none'"
>
<span class="tit-setting-content">{{
'chat:dialog.settingTimer' | ucapI18n
}}</span>
<mat-form-field class="form-select-timer" color="accent">
<mat-select formControlName="timerInterval">
<mat-option
*ngFor="let timer of timerArray"
[value]="timer.value"
>
{{ timer.text }}
</mat-option>
</mat-select>
<mat-hint>{{
'chat:dialog:settingTimerHint' | ucapI18n
}}</mat-hint>
</mat-form-field>
</div>
</div>
</form>

View File

@ -0,0 +1,183 @@
import {
Component,
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef,
Input,
EventEmitter,
Output
} from '@angular/core';
import { Subject, combineLatest, merge } from 'rxjs';
import { Store, select } from '@ngrx/store';
import { takeUntil } from 'rxjs/operators';
import { LoginResponse } from '@ucap/protocol-authentication';
import {
RoomInfo,
UpdateRequest,
UpdateTimerSetRequest
} from '@ucap/protocol-room';
import { RoomSelector, RoomActions } from '@ucap/ng-store-chat';
import { LoginSelector } from '@ucap/ng-store-authentication';
import { I18nService } from '@ucap/ng-i18n';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-drawer-chat-setting',
templateUrl: './setting.drawer.component.html',
styleUrls: ['./setting.drawer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SettingDrawerComponent implements OnInit, OnDestroy {
private roomIdSubject = new Subject<string>();
private ngOnDestroySubject = new Subject<boolean>();
@Input()
set roomId(value: string) {
this._roomId = value;
this.roomIdSubject.next(value);
this._initializeData();
}
get roomId(): string {
return this._roomId;
}
// tslint:disable-next-line: variable-name
_roomId: string;
@Output()
closed = new EventEmitter<void>();
loginRes: LoginResponse;
roomInfo: RoomInfo;
roomName: string;
timerArray: { value: number; text: string }[];
chatSettingForm: FormGroup;
constructor(
private i18nService: I18nService,
private store: Store<any>,
private formBuilder: FormBuilder,
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit(): void {
this._initializeData();
this.i18nService.languageChanged$
.pipe(takeUntil(this.ngOnDestroySubject))
.subscribe((_) => {
this.setTimerArray();
});
this.setTimerArray();
}
ngOnDestroy(): void {
if (!!this.ngOnDestroySubject) {
this.ngOnDestroySubject.next();
this.ngOnDestroySubject.complete();
}
}
private _initializeData() {
combineLatest([
this.store.pipe(select(LoginSelector.loginRes)),
this.store.pipe(select(RoomSelector.room, this.roomId))
])
.pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)))
.subscribe(([loginRes, roomInfo]) => {
this.loginRes = loginRes;
this.roomInfo = roomInfo;
this.chatSettingForm = this.formBuilder.group({
roomName: [
this.roomInfo?.roomName,
!this.roomInfo?.isTimeRoom ? [Validators.required] : []
],
changeTarget: ['me'],
timerInterval: [this.roomInfo?.timeRoomInterval]
});
this.changeDetectorRef.detectChanges();
});
}
onChange() {
const checkInvalid = this.chatSettingForm.invalid;
let roomName: string;
if (checkInvalid) {
roomName = '';
} else {
roomName = this.chatSettingForm.get('roomName').value;
}
}
onKeyupName() {
this.chatSettingForm.get('roomName').markAsTouched();
}
setTimerArray() {
const hourFrom = this.i18nService.t('common:units.hourFrom');
const minute = this.i18nService.t('common:units.minute');
const second = this.i18nService.t('common:units.second');
this.timerArray = [
{ value: 5, text: `5 ${second}` },
{ value: 10, text: `10 ${second}` },
{ value: 30, text: `30 ${second}` },
{ value: 60, text: `1 ${minute}` },
{ value: 300, text: `5 ${minute}` },
{ value: 600, text: `10 ${minute}` },
{ value: 1800, text: `30 ${minute}` },
{ value: 3600, text: `1 ${hourFrom}` },
{ value: 21600, text: `6 ${hourFrom}` },
{ value: 43200, text: `12 ${hourFrom}` },
{ value: 86400, text: `24 ${hourFrom}` }
];
}
onChangeGroupName(name: string) {
this.roomName = name;
}
onClosed(event: MouseEvent): void {
this.closed.emit();
}
const forbidden = /[\{\}\[\]\/?.;:|\)*~`!^+<>@\#$%&\\\=\(\'\"]/g.test(
inputValue
);
onConfirm(): void {
const roomName = this.chatSettingForm.get('roomName').value;
const roomNameChangeTarget = this.chatSettingForm.get('changeTarget').value;
const timerInterval = this.chatSettingForm.get('timerInterval').value;
this.store.dispatch(
RoomActions.update({
req: {
roomId: this.roomInfo.roomId,
roomName,
receiveAlarm: this.roomInfo.receiveAlarm,
syncAll: roomNameChangeTarget.toUpperCase() === 'ALL' ? true : false
} as UpdateRequest
})
);
if (!!this.roomInfo?.isTimeRoom) {
this.store.dispatch(
RoomActions.updateTimeRoomInterval({
req: {
roomId: this.roomInfo.roomId,
timerInterval
} as UpdateTimerSetRequest
})
);
}
this.closed.emit();
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.