init 0428

This commit is contained in:
Park Byung Eun 2020-04-28 21:09:35 +09:00
commit b7111951df
153 changed files with 25894 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"trailingComma": "none",
"tabWidth": 2,
"singleQuote": true
}

9
.storybook/main.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
stories: ['../src/**/*.stories.ts'],
addons: [
'@storybook/addon-actions',
'@storybook/addon-links',
'@storybook/addon-notes',
'@storybook/addon-knobs'
]
};

9
.storybook/tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.app.json",
"compilerOptions": {
"types": ["node"]
},
"exclude": ["../src/test.ts", "../src/**/*.spec.ts"],
"include": ["../src/**/*"],
"files": ["./typings.d.ts"]
}

4
.storybook/typings.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.md' {
const content: string;
export default content;
}

11
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"recommendations": [
"Angular.ng-template",
"msjsdiag.debugger-for-chrome",
"eamodio.gitlens",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-tslint-plugin",
"VisualStudioExptTeam.vscodeintellicode",
"nkokhelox.svg-font-previewer"
]
}

7
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}

14
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.autoClosingBrackets": "languageDefined",
"editor.trimAutoWhitespace": true,
"files.trimTrailingWhitespace": true,
"files.trimFinalNewlines": true,
"files.watcherExclude": {
"**/dist": true
},
"debug.node.autoAttach": "off"
}

15
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build:main:dev",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# UcapLgMockup
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.0.6.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

133
angular.json Normal file
View File

@ -0,0 +1,133 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ucap-lg-mockup": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/ucap-lg-mockup",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
},
"hmr": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.hmr.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "ucap-lg-mockup:build",
"hmrWarning": false
},
"configurations": {
"production": {
"browserTarget": "ucap-lg-mockup:build:production"
},
"hmr": {
"hmr": true,
"browserTarget": "ucap-lg-mockup:build:hmr"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "ucap-lg-mockup:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": ["**/node_modules/**"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "ucap-lg-mockup:serve"
},
"configurations": {
"production": {
"devServerTarget": "ucap-lg-mockup:serve:production"
}
}
}
}
}
},
"defaultProject": "ucap-lg-mockup",
"cli": {
"analytics": false
}
}

12
browserslist Normal file
View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

32
e2e/protractor.conf.js Normal file
View File

@ -0,0 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

23
e2e/src/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('ucap-lg-mockup app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

11
e2e/src/app.po.ts Normal file
View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

13
e2e/tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

32
karma.conf.js Normal file
View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/ucap-lg-mockup'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

18664
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

64
package.json Normal file
View File

@ -0,0 +1,64 @@
{
"name": "ucap-lg-mockup",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"start:hmr": "ng serve --configuration hmr",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"private": true,
"dependencies": {
"@angular/animations": "~9.0.6",
"@angular/cdk": "^9.2.1",
"@angular/common": "~9.0.6",
"@angular/compiler": "~9.0.6",
"@angular/core": "~9.0.6",
"@angular/flex-layout": "^9.0.0-beta.29",
"@angular/forms": "~9.0.6",
"@angular/material": "^9.2.1",
"@angular/material-moment-adapter": "^9.2.1",
"@angular/platform-browser": "~9.0.6",
"@angular/platform-browser-dynamic": "~9.0.6",
"@angular/router": "~9.0.6",
"ngx-perfect-scrollbar": "^9.0.0",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.6",
"@angular/cli": "~9.0.6",
"@angular/compiler-cli": "~9.0.6",
"@angular/language-service": "~9.0.6",
"@angularclass/hmr": "^2.1.3",
"@babel/core": "^7.9.0",
"@storybook/addon-actions": "^5.3.18",
"@storybook/addon-links": "^5.3.18",
"@storybook/addon-notes": "^5.3.18",
"@storybook/addon-knobs": "5.3.18",
"@storybook/addons": "^5.3.18",
"@storybook/angular": "^5.3.18",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"babel-loader": "^8.1.0",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.0",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"protractor": "~5.4.3",
"ts-node": "~8.3.0",
"tslint": "~5.18.0",
"typescript": "~3.7.5"
}
}

View File

@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './pages/account/login.component';
import { GroupComponent } from './pages/group/group.component';
import { ChatComponent } from './pages/chat/chat.component';
import { OrganizationComponent } from './pages/organization/organization.component';
import { MessageComponent } from './pages/message/message.component';
import { CallComponent } from './pages/call/call.component';
import { CommonComponent } from './pages/common/common.component';
import { LoadingComponent } from './pages/common/loading.component';
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'group', component: GroupComponent },
{ path: 'chat', component: ChatComponent },
{ path: 'organization', component: OrganizationComponent },
{ path: 'message', component: MessageComponent },
{ path: 'call', component: CallComponent },
{ path: 'common', component: CommonComponent },
{ path: 'loading', component: LoadingComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}

View File

@ -0,0 +1 @@
<router-outlet></router-outlet>

View File

View File

@ -0,0 +1,35 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'ucap-lg-mockup'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('ucap-lg-mockup');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('ucap-lg-mockup app is running!');
});
});

10
src/app/app.component.ts Normal file
View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'ucap-lg-mockup';
}

59
src/app/app.module.ts Normal file
View File

@ -0,0 +1,59 @@
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MaterialModule } from './material.module';
import { LoginComponent } from './pages/account/login.component';
import { GroupComponent } from './pages/group/group.component';
import { OrganizationComponent } from './pages/organization/organization.component';
import { ChatComponent } from './pages/chat/chat.component';
import { MessageComponent } from './pages/message/message.component';
import { CommonComponent } from './pages/common/common.component';
import { LoadingComponent } from './pages/common/loading.component';
import { CallComponent } from './pages/call/call.component';
import { UserItemComponent } from './pages/group/component/user-item.component';
import { TreeComponent } from './pages/organization/component/tree.component';
import { DetailTableComponent } from './pages/organization/component/detail-table.component';
import { ChatItemComponent } from './pages/chat/component/chat-item.component';
import { NewGroupComponent } from './pages/common/popup/new-group.component';
import { NewChatComponent } from './pages/common/popup/new-chat.component';
import { SettingComponent } from './pages/common/popup/setting.component';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
GroupComponent,
OrganizationComponent,
ChatComponent,
MessageComponent,
CommonComponent,
CallComponent,
LoadingComponent,
UserItemComponent,
TreeComponent,
DetailTableComponent,
ChatItemComponent,
NewGroupComponent,
NewChatComponent,
SettingComponent
],
imports: [
CommonModule,
BrowserModule,
BrowserAnimationsModule,
ReactiveFormsModule,
AppRoutingModule,
ScrollingModule,
MaterialModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}

47
src/app/app.theme.scss Normal file
View File

@ -0,0 +1,47 @@
// -----------------------------------------------------------------------------------------------------
// @ Typography
// -----------------------------------------------------------------------------------------------------
// Angular Material typography
// $input: mat-typography-level(16px, 1.125, 400) // line-height must be unitless !!!
$typography: mat-typography-config(
$font-family:
'나눔고딕, Malgun Gothic, 맑은고딕, Arial, Muli, Helvetica Neue, Arial, sans-serif',
$title: mat-typography-level(20px, 32px, 600),
$body-2: mat-typography-level(14px, 24px, 600),
$button: mat-typography-level(14px, 14px, 600),
$input: mat-typography-level(16px, 1.125, 400)
);
// Setup the typography
@include angular-material-typography($typography);
// -----------------------------------------------------------------------------------------------------
// @ Define theme --LG RED
// -----------------------------------------------------------------------------------------------------
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$lgRed-app-primary: mat-palette($lg-red);
$lgRed-app-accent: mat-palette($lg-red, A200, A100, A400);
// The warn palette is optional (defaults to red).
$lgRed-app-warn: mat-palette($lg-red);
// Create the theme object (a Sass map containing all of the palettes).
$lgRed-app-theme: mat-light-theme(
$lgRed-app-primary,
$lgRed-app-accent,
$lgRed-app-warn
);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($lgRed-app-theme);
// Apply the theme to the user components
/*
@include components-theme($lgRed-app-theme);
*/
@include ucap-material-theme($lgRed-app-theme);

View File

@ -0,0 +1,76 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatStepperModule } from '@angular/material/stepper';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTableModule } from '@angular/material/table';
import { MatTreeModule } from '@angular/material/tree';
import { MatBadgeModule } from '@angular/material/badge';
const MODULE = [
MatAutocompleteModule,
MatButtonModule,
MatButtonToggleModule,
MatBadgeModule,
MatCardModule,
MatChipsModule,
MatDatepickerModule,
MatDialogModule,
MatExpansionModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatCheckboxModule,
MatSelectModule,
MatSidenavModule,
MatSlideToggleModule,
MatSliderModule,
MatSnackBarModule,
MatStepperModule,
MatTabsModule,
MatTooltipModule,
MatPaginatorModule,
MatSortModule,
MatToolbarModule,
MatTableModule,
MatFormFieldModule,
MatTreeModule
];
@NgModule({
imports: [...MODULE],
exports: [...MODULE]
})
export class MaterialModule {}

View File

@ -0,0 +1,65 @@
<div class="login-container">
<div class="login-box">
<div class="logo-img">
<img src="../../../assets/images/logo_140.png" alt="" />
</div>
<h1>Welcome to Messenger</h1>
<div class="login-content">
<mat-form-field class="example-full-width" appearance="none">
<mat-select
(change)="onChange($event)"
placeholder="선택"
class="login-input-area login-select-form"
>
<mat-option *ngFor="let company of companyList" [value]="company">
{{ company.companyName }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="login-input-area idpass-type">
<mat-form-field
class="login-idpass-txt"
appearance="none"
floatLabel=""
>
<mat-label>ID</mat-label>
<input matInput placeholder="ID" value="" />
</mat-form-field>
</div>
<div class="login-input-area idpass-type pass-type">
<mat-form-field class="login-idpass-txt" appearance="none">
<mat-label>Password</mat-label>
<input matInput type="password" placeholder="Password" value="" />
</mat-form-field>
</div>
<div class="error-container">
<mat-error *ngIf="isLoginId">
아이디를 입력하세요.
</mat-error>
<mat-error *ngIf="isLoginId">
패스워드 입력하세요.
</mat-error>
</div>
<button mat-raised-button class="login-input-submit" aria-label="LOG IN">
로그인
</button>
<div class="login-chk-area">
<mat-checkbox>Remember Me</mat-checkbox>
<mat-checkbox>실행 시 자동 로그인</mat-checkbox>
</div>
<div class="login-pass-info">
<ul>
<li><a href="">비밀번호 찾기</a></li>
<li><a href="" class="fir-pass">비밀번호 초기화</a></li>
</ul>
</div>
<div class="login-button-area">
<button type="button">이용 시 주의 사항</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,330 @@
@charset 'UTF-8';
@import '../../../assets/scss/components';
$login-bg-w: 100/1920;
$login-bg-h: 100/1080;
.login-container {
width: 100%;
min-height: 100vh;
background-color: $bg-gray;
background-image: url(../../../assets/images/bg/bg_login_circle_square01.svg),
url(../../../assets/images/bg/bg_login_circle_stroke01.svg),
url(../../../assets/images/bg/bg_login_circle01.svg),
url(../../../assets/images/bg/bg_login_circle03.svg),
url(../../../assets/images/bg/bg_login_circle_diagonal01.svg),
url(../../../assets/images/bg/bg_login_circle_square02.svg),
url(../../../assets/images/bg/bg_login_circle04.svg),
url(../../../assets/images/bg/bg_login_circle05.svg),
url(../../../assets/images/bg/bg_login_circle06.svg),
url(../../../assets/images/bg/bg_login_circle07.svg),
url(../../../assets/images/bg/bg_login_circle08.svg),
url(../../../assets/images/bg/bg_login_circle_diagonal02.svg),
url(../../../assets/images/bg/bg_login_circle_diagonal03.svg),
url(../../../assets/images/bg/bg_login_circle_stroke02.svg),
url(../../../assets/images/bg/bg_login_circle_stroke03.svg),
url(../../../assets/images/bg/bg_login_circle_stroke04.svg),
url(../../../assets/images/bg/bg_login_circle_stroke05.svg),
url(../../../assets/images/bg/bg_login_polygon01.svg),
url(../../../assets/images/bg/bg_login_polygon02.svg);
background-repeat: no-repeat;
background-position: unquote($login-bg-w * 323 + '%')
unquote($login-bg-h * 179 + '%'),
unquote($login-bg-w * -147 + '%') unquote($login-bg-h * 18 + '%'),
unquote($login-bg-w * 285 + '%') unquote($login-bg-h * 226 + '%'),
unquote($login-bg-w * 1235 + '%') unquote($login-bg-h * -101 + '%'),
unquote($login-bg-w * 1397 + '%') unquote($login-bg-h * 163 + '%'),
unquote($login-bg-w * 1569 + '%') unquote($login-bg-h * 580 + '%'),
unquote($login-bg-w * 426 + '%') unquote($login-bg-h * 293 + '%'),
unquote($login-bg-w * 1531 + '%') unquote($login-bg-h * 250 + '%'),
unquote($login-bg-w * 1774 + '%') unquote($login-bg-h * 166 + '%'),
unquote($login-bg-w * 1362 + '%') unquote($login-bg-h * 673 + '%'),
unquote($login-bg-w * 152 + '%') unquote($login-bg-h * 730 + '%'),
unquote($login-bg-w * 286 + '%') unquote($login-bg-h * 719 + '%'),
unquote($login-bg-w * 683 + '%') unquote($login-bg-h * 593 + '%'),
unquote($login-bg-w * 498 + '%') unquote($login-bg-h * 453 + '%'),
unquote($login-bg-w * 1709 + '%') unquote($login-bg-h * 599 + '%'),
unquote($login-bg-w * 1395 + '%') unquote($login-bg-h * 989 + '%'),
unquote(100 + $login-bg-w * 89 + '%') unquote(100 + $login-bg-h * 137 + '%'),
unquote($login-bg-w * 90 + '%') unquote($login-bg-h * 463 + '%'),
unquote($login-bg-w * 549 + '%') unquote($login-bg-h * 874 + '%');
background-size: unquote($login-bg-w * 79 + '%'),
unquote($login-bg-w * 333 + '%'), unquote($login-bg-w * 84 + '%'),
unquote($login-bg-w * 172 + '%'), unquote($login-bg-w * 210 + '%'),
unquote($login-bg-w * 94 + '%'), unquote($login-bg-w * 44 + '%'),
unquote($login-bg-w * 118 + '%'), unquote($login-bg-w * 52 + '%'),
unquote($login-bg-w * 70 + '%'), unquote($login-bg-w * 172 + '%'),
unquote($login-bg-w * 82 + '%'), unquote($login-bg-w * 135 + '%'),
unquote($login-bg-w * 102 + '%'), unquote($login-bg-w * 130 + '%'),
unquote($login-bg-w * 184 + '%'), unquote($login-bg-w * 370 + '%'),
unquote($login-bg-w * 122 + '%'), unquote($login-bg-w * 75 + '%');
box-sizing: border-box;
display: flex;
flex-direction: column;
.login-box {
@extend %clearfix;
padding: 0 0 45px;
width: 420px;
margin: auto;
text-align: center;
flex-basis: auto;
align-items: center;
.logo-img {
display: block;
text-align: center;
img {
margin-bottom: 7px;
vertical-align: top;
@include screen(mid) {
width: 120px;
}
@include screen(xs) {
width: 100px;
margin-bottom: 6px;
}
}
}
@extend %guideline; //Guide Line
h1 {
@include font-family($font-light);
font-size: 24px;
text-align: center;
color: $txt-color01;
font-weight: 600;
line-height: 1.2;
@include screen(mid) {
font-size: 19px;
}
@include screen(xs) {
font-size: 14px;
}
}
.login-content {
@extend %guideline2; //Guide Line2
margin: 30px auto 0;
.login-input-area {
border: 1px solid #cccccc;
border-radius: 2px;
width: 100%;
max-width: 420px;
min-width: 150px;
height: 60px;
background-color: $white;
margin-top: 10px;
&.login-select-form {
height: 60px;
line-height: 60px;
padding: 0 16px;
@include screen(mid) {
height: 50px;
line-height: 50px;
}
@include screen(xs) {
height: 42px;
line-height: 42px;
}
}
&:first-of-type {
margin-top: 0px;
}
&.idpass-type {
padding-left: 50px;
position: relative;
&::before {
font-family: 'material Icons';
font-size: 24px;
text-align: center;
line-height: 60px;
content: 'perm_identity';
display: block;
position: absolute;
top: 0;
left: 16px;
@include screen(mid) {
line-height: 50px;
}
@include screen(xs) {
line-height: 42px;
}
}
&.pass-type {
&::before {
content: 'https';
}
}
.login-idpass-txt {
width: 368px;
height: 60px;
line-height: 60px;
font-size: 14px;
@include screen(mid) {
width: 358 - 60 + px;
height: 50px;
line-height: 50px;
font-size: 14px;
}
@include screen(xs) {
width: 308 - 60 + px;
font-size: 14px;
height: 42px;
line-height: 42px;
}
input {
font-size: 18px;
line-height: 58px;
margin-top: 0;
vertical-align: top;
background-color: $white;
padding: 0 10px 0 5px;
@include screen(mid) {
font-size: 16px;
line-height: 48px;
}
@include screen(xs) {
font-size: 14px;
line-height: 40px;
}
}
}
}
@include screen(mid) {
margin-top: 8px;
}
}
.login-input-submit {
width: 100%;
height: 60px;
background-color: $black;
border-radius: 2px;
color: $white;
font-size: 20px;
@include font-family($font-semibold);
border: 0;
margin-top: 12px;
font-weight: 600;
cursor: pointer;
@include screen(mid) {
margin-top: 8px;
font-size: 16px;
height: 50px;
}
@include screen(xs) {
font-size: 14px;
height: 42px;
}
}
@include screen(mid) {
margin-top: 23px;
width: 350px;
.login-input-area {
height: 50px;
}
}
@include screen(xs) {
margin-top: 23px;
width: 300px;
.login-input-area {
height: 42px;
}
}
}
}
.login-chk-area {
margin-top: 6px;
font-size: 13px;
text-align: left;
@include screen(xs) {
font-size: 12px;
}
}
.login-pass-info {
overflow: hidden;
margin-top: 83px;
ul {
display: flex;
justify-content: center;
li {
height: 24px;
position: relative;
display: inline-flex;
align-items: center;
padding: 0 12% 0 8%;
&::before {
content: '';
height: 11px;
width: 1px;
display: flex;
background-color: $gray-re4a;
position: absolute;
top: 6.5px;
left: 0;
}
&:first-child {
padding-left: 0;
&::before {
display: none;
}
}
&:last-child {
padding-right: 0;
}
a {
line-height: 24px;
font-size: 12px;
color: $gray-re4a;
padding-left: 34px;
position: relative;
white-space: nowrap;
&::before {
font-family: 'material Icons';
font-size: 18px;
text-align: center;
content: 'search';
color: $white;
display: block;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: $black;
position: absolute;
top: 0;
left: 0;
}
&.fir-pass {
&::before {
content: 'sync';
}
}
}
}
}
}
.login-button-area {
margin-top: 14px;
@include screen(xs) {
margin-top: 20px;
}
button {
border: 0;
margin: 0;
width: 100%;
height: 46px;
border-radius: 4px;
background-color: #e0e3e7;
font-size: 12px;
color: $gray-re4a;
cursor: pointer;
@include screen(mid) {
height: 38px;
}
@include screen(xs) {
height: 34px;
}
}
}
.example-full-width {
width: 100%;
}
}

View File

@ -0,0 +1,46 @@
import { action } from '@storybook/addon-actions';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { array, boolean, text, object } from '@storybook/addon-knobs/angular';
import { LoginComponent } from './login.component';
import { moduleMetadata } from '@storybook/angular';
import { MaterialModule } from 'src/app/material.module';
export default {
title: 'LoginComponent',
component: LoginComponent,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, MaterialModule],
providers: []
})
]
};
const defaultProbs = (overrides = {}) =>
Object.assign(
{},
{
companyList: array('companyList', [
{ companyName: 'LG CNS' },
{ companyName: 'LG UCAP' }
]),
isLoginId: false,
isPasswd: false,
placeholder: text('placeholder', '선택'),
disabled: boolean('disabled', false),
onChange: action('change')
},
overrides
);
export const Page = () => ({
component: LoginComponent,
props: defaultProbs()
});
export const ErrorPage = () => ({
component: LoginComponent,
props: defaultProbs({ isLoginId: true, isPasswd: true })
});

View File

@ -0,0 +1,18 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-pages-account-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
constructor() {}
companyList;
selected;
isLoginId;
isPasswd;
ngOnInit(): void {}
onChange(event: MouseEvent) {}
}

View File

@ -0,0 +1 @@
<p>call works!</p>

View File

View File

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

View File

@ -0,0 +1,22 @@
import { action } from '@storybook/addon-actions';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';
import { CallComponent } from './call.component';
import { MaterialModule } from 'src/app/material.module';
export default {
title: 'CallComponent',
component: CallComponent,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, MaterialModule],
providers: []
})
]
};
export const Page = () => ({
component: CallComponent
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-call',
templateUrl: './call.component.html',
styleUrls: ['./call.component.scss']
})
export class CallComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,529 @@
<div class="container">
<div class="gnb">
<mat-toolbar>logo</mat-toolbar>
<mat-tab-group
mat-stretch-tabs
animationDuration="0ms"
backgroundColor="transparent"
class="global-menu"
>
<mat-tab aria-label="Group">
<ng-template mat-tab-label>
<div class="icon-item" matTooltip="그룹" matTooltipPosition="after">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Chat">
<ng-template mat-tab-label>
<div
class="icon-item"
matBadgeHidden="false"
matBadge="275"
matBadgeColor="accent"
matBadgePosition="above after"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<path
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"
></path>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Organization">
<ng-template mat-tab-label>
<div class="icon-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<circle class="st0" cx="18.4" cy="18.5" r="3" />
<circle class="st0" cx="12" cy="5" r="3" />
<path class="st0" d="M12.4,10.5h4c1.1,0,2,0.9,2,2v3" />
<circle class="st0" cx="6.1" cy="18.5" r="3" />
<path class="st0" d="M6.1,15.5v-3c0-1.1,0.9-2,2-2h4" />
<line class="st0" x1="12" y1="8" x2="12" y2="9" />
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Message">
<ng-template mat-tab-label>
<div class="icon-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<polygon
points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"
/>
</svg>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
<div class="menu-container">
<div class="contents">
<mat-drawer-container class="example-container" style="height: 600px;">
<mat-drawer #drawer mode="side" opened style="width: 400px;">
<mat-toolbar>
<div>M Messenger</div>
<div></div>
</mat-toolbar>
<div class="list-section">
<div class="title-section">
<div class="title">
<h3>대화</h3>
<div class="btn-box">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</div>
<div class="search-area">
<mat-form-field class="example-full-width">
<mat-label>대화방명, 대화 참여자 검색</mat-label>
<input
matInput
placeholder="대화방명, 대화 참여자 검색"
value=""
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</div>
</div>
<div class="list">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
2020년 04월 05일 목요일 (<span>오늘</span>)
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</mat-panel-title>
<mat-panel-description></mat-panel-description>
</mat-expansion-panel-header>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
2020년 04월 04일 수요일
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</mat-panel-title>
<mat-panel-description></mat-panel-description>
</mat-expansion-panel-header>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
<app-chat-item></app-chat-item>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</mat-drawer>
<mat-drawer-content style="width: 900px;">
<button
mat-icon-button
class="left-drawer-toggle"
(click)="drawer.toggle()"
>
<
</button>
<mat-toolbar>
<div><span>Today</span>2020.05.05</div>
<div>
<div class="app-layout-native-title-bar-actions">
<button
mat-icon-button
class="button app-layout-native-title-bar-minimize"
>
<!--<mat-icon>minimize</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="5" y1="18" x2="19" y2="18"></line>
</svg>
</button>
<button
mat-icon-button
class="button app-layout-native-title-bar-maximize"
>
<ng-container [ngSwitch]="'Maximized'">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
*ngSwitchCase="'Maximized'"
>
<path
class="st0"
d="M15,9.5v7c0,0.8-0.7,1.5-1.5,1.5h-7C5.7,18,5,17.3,5,16.5v-7C5,8.7,5.7,8,6.5,8h7C14.3,8,15,8.7,15,9.5z"
/>
<path
class="st0"
d="M8.8,6.8V6c0-0.8,0.7-1.5,1.5-1.5H17c0.8,0,1.5,0.7,1.5,1.5v6.8c0,0.8-0.7,1.5-1.5,1.5h-0.8"
/>
</svg>
<!--<mat-icon *ngSwitchDefault>crop_din</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
*ngSwitchDefault
>
<rect
x="5"
y="5"
width="12"
height="12"
rx="2"
ry="2"
></rect>
</svg>
</ng-container>
</button>
<button
mat-icon-button
class="button app-layout-native-title-bar-close"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
</mat-toolbar>
<div class="contents-main">
<div class="subtitle">
<mat-toolbar>
<mat-toolbar-row>
<div class="profileImage">
ProfileImage
</div>
<span>[원조] 프로젝트 방 (<strong>24</strong>)</span>
<span class="example-spacer"></span>
<button mat-mini-fab color="primary" aria-label="">
<mat-icon>search</mat-icon>
</button>
<button mat-mini-fab color="primary" aria-label="">
<mat-icon>alarm</mat-icon>
</button>
<button mat-mini-fab color="primary" aria-label="">
<mat-icon>group</mat-icon>
</button>
<button
mat-mini-fab
color="primary"
aria-label=""
(click)="rightDrawer.toggle()"
>
<mat-icon>menu</mat-icon>
</button>
</mat-toolbar-row>
</mat-toolbar>
</div>
<div class="message-area">
<mat-drawer-container autosize>
<div class="message-container">
<div class="chat-area">
<div class="others">
<dl>
<dt>
<div class="profileImage">ProfileImage</div>
</dt>
<dd>
<dl>
<dt>
<span class="name">이선임</span>
<span class="date">오전 12:47</span>
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</dt>
<dd class="type-text">
오전 회의결과 공유해 드려요. 각자 맡은 영역의
자료를 취합 후 정리하여 보내주시면 됩니다. 담당자
최우영께 문의해 줏세요
</dd>
</dl>
</dd>
<dd class="unread-count">
4
</dd>
</dl>
</div>
<div class="me">
<dl>
<dt style="display: none;">
<div class="profileImage">ProfileImage</div>
</dt>
<dd>
<dl>
<dt>
<span class="name" style="display: none;"
>내이름</span
>
<span class="date">오전 12:48</span>
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</dt>
<dd class="type-text">
오전 외부 지원으로 인해 참석하지 못해 진척사항과
함께 보내주신 내용 파악해서 보고 답변
드리겠습니다. 혹시 문의할 내용 있으면 다시
메신저로 연락드리겠습니다
</dd>
</dl>
</dd>
<dd class="unread-count">
4
</dd>
</dl>
</div>
<div class="others">
<dl>
<dt>
<div class="profileImage">ProfileImage</div>
</dt>
<dd>
<dl>
<dt>
<span class="name">김유정</span>
<span class="date">오전 12:49</span>
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</dt>
<dd class="type-file">
파일정보 영역 보여주시고,마우스 오버시
오버레이되는 영역의 코딩도
부탁드립니다.([min-대화]대화방-일반 참고)
</dd>
</dl>
</dd>
<dd class="unread-count">
4
</dd>
</dl>
</div>
<div class="others">
<dl>
<dt>
<div class="profileImage">ProfileImage</div>
</dt>
<dd>
<dl>
<dt>
<span class="name">김유정</span>
<span class="date">오전 12:49</span>
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</dt>
<dd class="type-translation">
<div class="original">
오전 회의결과 공유해 드려요. 각자 맡은 영역의
자료를 취합 후 정리하여 보내주시면 됩니다.
담당자 최우영께 문의해 줏세요
</div>
<div class="translate">
<span class="lang">EN</span>
We will share the results of the meeting. You
can collect the data of each area and send it
together. Please contact Choi Woo-young, the
person in charge
</div>
</dd>
</dl>
</dd>
<dd class="unread-count">
4
</dd>
</dl>
</div>
</div>
<div class="chat-form-area">
<mat-form-field
class="message-text"
fxFlex
floatLabel="never"
appearance="standard"
>
<textarea
matInput
#replyInput
placeholder=""
ngModel
name="message"
[matTextareaAutosize]="true"
[matAutosizeMaxRows]="20"
></textarea>
</mat-form-field>
<div class="button-area">
<button mat-icon-button aria-label="">
<mat-icon>attach_file</mat-icon>
</button>
<button mat-icon-button aria-label="">
<mat-icon>photo_library</mat-icon>
</button>
<button mat-icon-button aria-label="">
<mat-icon>control_camera</mat-icon>
</button>
<button mat-icon-button aria-label="">
<mat-icon>insert_emoticon</mat-icon>
</button>
<button mat-icon-button aria-label="">
<mat-icon>email</mat-icon>
</button>
<button mat-icon-button aria-label="">
<mat-icon>g_translate</mat-icon>
</button>
<button mat-button>+GAMS</button>
<button mat-fab color="accent" aria-label="">
<mat-icon>send</mat-icon>
</button>
</div>
</div>
</div>
<mat-drawer
#rightDrawer
mode="side"
position="end"
class="rightDrawer"
>
Right Sections.
</mat-drawer>
</mat-drawer-container>
</div>
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>
<div class="footer">
<div>
<span>현재버전 0.0.11</span>
<span>최신버전 0.0.11</span>
</div>
<div style="right: 0;">
HelpDesk 1661-9066
</div>
</div>
</div>
</div>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>

View File

@ -0,0 +1,140 @@
.container {
.gnb,
.menu-container {
float: left;
.list-section {
height: 100%;
}
}
.contents-main {
.mainProfile {
float: left;
height: 600px;
}
.info {
.allim {
.allimList {
.allim-card {
display: inline-block;
}
}
}
}
}
.footer {
clear: both;
}
}
.gnb {
.left-container {
display: flex;
width: 100%;
height: 100%;
.global-menu {
width: 100%;
}
}
::ng-deep .global-menu {
display: flex;
flex-direction: row;
.mat-tab-header {
border-bottom: none !important;
width: 100%;
}
.mat-tab-label-container {
.mat-tab-list {
.mat-tab-labels {
flex-flow: column;
height: 360px;
padding-top: 10px;
border-bottom: none;
.mat-tab-label {
width: 100%;
height: 80px;
padding: 0;
min-width: 0 !important;
.mat-tab-label-content {
.icon-item {
display: inline-flex;
width: 36px;
height: 36px;
border-radius: 50%;
justify-content: center;
align-items: center;
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.4, 0, 0, 1);
svg {
width: 24px;
height: 24px;
stroke: #ffffff;
stroke-width: 1.5;
stroke-linecap: square;
stroke-linejoin: miter;
fill: none;
}
.mat-badge-content {
right: -4px !important;
}
}
}
&.mat-tab-label-active {
opacity: 0;
}
&[aria-selected='true'] {
opacity: 1;
.mat-tab-label-content {
.icon-item {
transform: scale(1);
}
}
}
}
}
.mat-ink-bar {
opacity: 0;
}
}
}
.mat-tab-body-wrapper {
.mat-tab-body {
height: 100%;
width: 100%;
}
}
}
}
.footer {
div {
display: inline-block;
}
}
.left-drawer-toggle {
position: absolute;
top: calc(50% - 30px);
left: -4px;
border: 1px solid #dddddd;
background-color: #ffffff !important;
width: 24px;
height: 60px;
border-radius: 0 8px 8px 0;
display: flex;
justify-content: center;
justify-items: center;
font-size: 1.8em;
z-index: inherit;
}
.example-spacer {
flex: 1 1 auto;
}

View File

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

View File

@ -0,0 +1,24 @@
import { action } from '@storybook/addon-actions';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';
import { ChatComponent } from './chat.component';
import { MaterialModule } from 'src/app/material.module';
import { ChatItemComponent } from './component/chat-item.component';
export default {
title: 'ChatComponent',
component: ChatComponent,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, MaterialModule],
providers: [],
declarations: [ChatItemComponent]
})
]
};
export const Page = () => ({
component: ChatComponent
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.scss']
})
export class ChatComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { ChatItemComponent } from './component/chat-item.component';
const COMPONENT = [ChatItemComponent];
@NgModule({
exports: [...COMPONENT],
declarations: [...COMPONENT]
})
export class ChatModule {}

View File

@ -0,0 +1 @@
<p>chat-list works!</p>

View File

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

View File

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

View File

@ -0,0 +1,12 @@
<div>
<h3>popup components</h3>
<button mat-stroked-button color="primary" (click)="openDialog('CHAT')">
New-Chat
</button>
<button mat-stroked-button color="primary" (click)="openDialog('GROUP')">
New-Group
</button>
<button mat-stroked-button color="primary" (click)="openDialog('SETTING')">
Setting
</button>
</div>

View File

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

View File

@ -0,0 +1,29 @@
import { action } from '@storybook/addon-actions';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from 'src/app/material.module';
import { moduleMetadata } from '@storybook/angular';
import { CommonComponent } from './common.component';
import { NewGroupComponent } from './popup/new-group.component';
import { NewChatComponent } from './popup/new-chat.component';
import { SettingComponent } from './popup/setting.component';
import { DIALOGS } from './popup';
import { UserItemComponent } from '../group/component/user-item.component';
export default {
title: '99_Common/CommonComponent',
component: CommonComponent,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, MaterialModule],
providers: [],
declarations: [DIALOGS, UserItemComponent],
entryComponents: [DIALOGS]
})
]
};
export const Page = () => ({
component: CommonComponent
});

View File

@ -0,0 +1,39 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { NewChatComponent } from './popup/new-chat.component';
import { NewGroupComponent } from './popup/new-group.component';
import { SettingComponent } from './popup/setting.component';
@Component({
selector: 'app-common',
templateUrl: './common.component.html',
styleUrls: ['./common.component.scss']
})
export class CommonComponent implements OnInit {
constructor(public dialog: MatDialog) {}
ngOnInit(): void {}
openDialog(type: string): void {
switch (type) {
case 'CHAT':
this.dialog.open(NewChatComponent, {
width: '850px',
data: {}
});
break;
case 'GROUP':
this.dialog.open(NewGroupComponent, {
width: '250px',
data: {}
});
break;
case 'SETTING':
this.dialog.open(SettingComponent, {
width: '250px',
data: {}
});
break;
}
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { NewGroupComponent } from './popup/new-group.component';
import { NewChatComponent } from './popup/new-chat.component';
import { SettingComponent } from './popup/setting.component';
import { DIALOGS } from './popup';
const COMPONENT = [];
@NgModule({
exports: [...COMPONENT, DIALOGS],
declarations: [...COMPONENT, DIALOGS],
entryComponents: [...COMPONENT, DIALOGS]
})
export class CommonModule {}

View File

@ -0,0 +1,14 @@
<div
fxLayout="column"
fxLayoutAlign="space-between center"
style="
height: 100%;
flex-direction: column;
box-sizing: border-box;
display: flex;
place-content: center space-around;
align-items: center;
"
>
loading
</div>

View File

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

View File

@ -0,0 +1,22 @@
import { action } from '@storybook/addon-actions';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';
import { LoadingComponent } from './loading.component';
import { MaterialModule } from 'src/app/material.module';
export default {
title: '99_Common/LoadingComponent',
component: LoadingComponent,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, MaterialModule],
providers: []
})
]
};
export const Page = () => ({
component: LoadingComponent
});

View File

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

View File

@ -0,0 +1,5 @@
import { NewGroupComponent } from './new-group.component';
import { NewChatComponent } from './new-chat.component';
import { SettingComponent } from './setting.component';
export const DIALOGS = [NewGroupComponent, NewChatComponent, SettingComponent];

View File

@ -0,0 +1,137 @@
<mat-card class="confirm-card mat-elevation-z dialog-creat-chat">
<mat-card-header>
<mat-card-title cdkDrag cdkDragRootElement=".cdk-overlay-pane" cdkDragHandle
>새 대화추가</mat-card-title
>
<button class="icon-button btn-dialog-close">
<i class="mdi mdi-window-close"></i>
</button>
</mat-card-header>
<mat-card-content>
<div
fxLayout="column"
fxLayout.xs="column"
fxLayoutAlign="center"
fxLayoutGap="10px"
fxLayoutGap.xs="0"
class="mat-card-content-frame"
>
<div fxFlex class="container">
<!-- search-->
<div>
검색창
</div>
<mat-tab-group mat-stretch-tabs>
<!--그룹-->
<mat-tab>
<ng-template mat-tab-label>
<!-- <button class="icon-button">
<i class="mid mid-24 mdi-account-multiple"></i>
</button> -->
<p>그룹</p>
</ng-template>
<div fxFlexFill>
<div class="mat-tab-frame dialog-tab-grouplist">
<div class="list">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
즐겨찾기
<span>(<strong>6</strong>/7)</span>
</mat-panel-title>
<mat-panel-description></mat-panel-description>
</mat-expansion-panel-header>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"> </app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
그룹명을 넣습니다
<span>(<strong>6</strong>/7)</span>
</mat-panel-title>
<mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
<app-user-item [check]="true"></app-user-item>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</div>
</mat-tab>
<!--조직-->
<mat-tab>
<ng-template mat-tab-label>
<!-- <button class="icon-button">
<i class="mid mid-24 mdi-account-multiple"></i>
</button> -->
<p>조직도</p>
</ng-template>
<div fxFlexFill>
<div class="mat-tab-frame dialog-tab-grouplist">
<div><!-- group -->ffffffffff</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>
</div>
</mat-card-content>
<ng-template #selectedUserListTemplate>
<div class="list-chip">
<mat-chip-list aria-label="User selection">
<mat-chip *ngFor="let userInfo of selectedUserList" [selected]="true">
김선규
<mat-icon matChipRemove *ngIf="true">clear</mat-icon>
</mat-chip>
</mat-chip-list>
</div>
<ng-container
*ngIf="
data.type === UserSelectDialogType.NewChat;
then newchatcount;
else defaultcount
"
></ng-container>
<ng-template #newchatcount>
<span
[ngClass]="
selectedUserList.length >=
environment.productConfig.CommonSetting.maxChatRoomUser
? 'text-warn-color'
: ''
"
>
13 / 21 명
</span>
<span
class="text-warn-color"
style="float: right;"
*ngIf="selectedUserList.length >= 50"
>
에러
</span>
</ng-template>
</ng-template>
</mat-card>

View File

@ -0,0 +1,178 @@
.dialog-creat-chat {
height: 100%;
& > .mat-card-header {
.btn-dialog-close {
font-size: 20px;
display: flex;
margin-left: auto;
align-self: flex-start;
color: #444444;
}
}
& > .mat-card-content {
position: relative;
.mat-card-content-frame {
height: calc(100% - 50px);
.container {
height: 100%;
.container-frame {
height: 530px;
}
}
}
//그룹이름 있는 경우
.newgroup-form {
height: 70px;
& + .mat-card-content-frame {
height: calc(100% - 120px);
.container {
.mat-tab-frame {
.mat-tab-group {
& > .mat-tab-body {
height: 380px;
}
}
}
}
}
}
}
& > .mat-tab-body-wrapper {
height: calc(100% - 50px);
.mat-tag-body {
& > .dialog-tab-orglist {
}
}
}
}
.dialog-tab-grouplist {
height: calc(100% - 130px);
width: 100%;
.group,
.search-result {
width: 100%;
height: calc(100% - 50px);
&-expansion {
.list-item {
height: 60px;
}
}
&-list-item {
width: 100%;
}
}
}
/*::ng-deep .dialog-tab-chatlist {
height: 508px;
width: 100%;
.chat {
width: 100%;
}
.chat.checkbox {
& > .list-item {
padding: 0 10px;
.item-default {
width: calc(100% - 30px);
}
.mat-badge {
display: none;
}
}
}
}
*/
::ng-deep .dialog-tab-orglist {
width: 100%;
height: calc(100% - 130px);
border-bottom: 1px solid #dddddd;
position: relative;
.oraganization {
.ps__rail-y {
left: auto !important;
}
&-tab {
width: 100%;
height: calc(100% - 50px);
position: relative;
&-tree {
display: inline-flex;
width: 50%;
height: 100% !important;
border-right: 1px solid #dddddd;
overflow: auto;
.tab-tree-frame {
width: 100%;
height: 100%;
}
}
.select-list {
display: inline-flex;
flex-direction: column;
width: 50%;
height: 100% !important;
overflow: auto;
.search-list {
overflow: auto;
height: calc(100% - 40px);
}
}
}
}
}
.list-chip {
height: 100px;
width: 100%;
padding: 10px;
border: 1px solid #dddddd;
overflow: auto;
background-color: #f9f9f9;
margin-top: 10px;
}
.mat-chip.mat-standard-chip .mat-chip-remove {
}
.confirm-card {
.mat-card-content {
.content-box {
flex-direction: column;
flex-flow: column;
}
}
.button-form {
text-align: right;
.mat-primary {
margin-left: 4px;
}
}
}
/*::ng-deep .dialog-creat-chat {
& > .mat-tab-body-wrapper {
.mat-tab-body {
height: 380px;
&:nth-child(3) {
height: 480px;
}
.mat-tab-body-content {
width: 100%;
height: 100%;
}
}
}
}
*/
.mat-card-content {
.mat-card-content-frame {
height: 100%;
.container {
height: 100%;
.mat-tab-group {
height: 100%;
}
}
}
}

View File

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

View File

@ -0,0 +1,19 @@
import { Component, OnInit, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-new-chat',
templateUrl: './new-chat.component.html',
styleUrls: ['./new-chat.component.scss']
})
export class NewChatComponent implements OnInit {
selectedUserList: any[] = [
{ name: '김선구' },
{ name: '김선구1' },
{ name: '김선구2' },
{ name: '김선구3' }
];
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {}
ngOnInit(): void {}
}

View File

@ -0,0 +1 @@
<p>new-group works!</p>

View File

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

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-new-group',
templateUrl: './new-group.component.html',
styleUrls: ['./new-group.component.scss']
})
export class NewGroupComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1 @@
<p>setting works!</p>

View File

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

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-setting',
templateUrl: './setting.component.html',
styleUrls: ['./setting.component.scss']
})
export class SettingComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,21 @@
<div class="user-list">
<span class="presence">bullet</span>
<div class="profileImage">
ProfileImage
</div>
<div class="userInfo">
<div>
<div class="name">홍길동</div>
<div class="grade">대리</div>
</div>
<div class="deptName">
영업팀
</div>
</div>
<div class="intro">
맑은 산속 옹달샘 누가와서 먹나요??
</div>
<div *ngIf="check">
<mat-checkbox></mat-checkbox>
</div>
</div>

View File

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

View File

@ -0,0 +1,15 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-user-item',
templateUrl: './user-item.component.html',
styleUrls: ['./user-item.component.scss']
})
export class UserItemComponent implements OnInit {
@Input()
check = false;
constructor() {}
ngOnInit(): void {}
}

View File

@ -0,0 +1,461 @@
<div class="container">
<div class="gnb">
<mat-toolbar><span class="lgucap-logo blind">Logo</span></mat-toolbar>
<mat-tab-group
mat-stretch-tabs
animationDuration="0ms"
backgroundColor="transparent"
class="global-menu"
>
<mat-tab aria-label="Group">
<ng-template mat-tab-label>
<div class="icon-item" matTooltip="그룹" matTooltipPosition="after">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Chat">
<ng-template mat-tab-label>
<div
class="icon-item"
matBadgeHidden="false"
matBadge="275"
matBadgeColor="accent"
matBadgePosition="above after"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<path
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"
></path>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Organization">
<ng-template mat-tab-label>
<div class="icon-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<circle class="st0" cx="18.4" cy="18.5" r="3" />
<circle class="st0" cx="12" cy="5" r="3" />
<path class="st0" d="M12.4,10.5h4c1.1,0,2,0.9,2,2v3" />
<circle class="st0" cx="6.1" cy="18.5" r="3" />
<path class="st0" d="M6.1,15.5v-3c0-1.1,0.9-2,2-2h4" />
<line class="st0" x1="12" y1="8" x2="12" y2="9" />
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Message">
<ng-template mat-tab-label>
<div class="icon-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<polygon
points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"
/>
</svg>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
<div class="menu-container">
<div class="contents">
<mat-drawer-container class="example-container">
<mat-drawer #drawer mode="side" opened class="m-messenger-area">
<mat-toolbar class="m-messenger-tit">
<div>M Messenger</div>
<div></div>
</mat-toolbar>
<div class="list-section">
<div class="title-section">
<div class="title">
<h3>그룹</h3>
<div class="btn-box">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</div>
<div class="search-area">
<mat-select placeholder="선택">
<mat-option value="1">1</mat-option>
<mat-option value="1">1A</mat-option>
<mat-option value="1">1AB</mat-option>
<mat-option value="1">1ABCDEFGHIJKLMNOPQRSTUVWXYZ</mat-option>
</mat-select>
<mat-form-field class="example-full-width">
<mat-label>이름 부서명, 전화번호, 이메일</mat-label>
<input
matInput
placeholder="이름 부서명, 전화번호, 이메일"
value=""
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</div>
<app-user-item class="myProfile"></app-user-item>
</div>
<div class="list">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
즐겨찾기
<span>(<strong>6</strong>/7)</span>
</mat-panel-title>
<mat-panel-description></mat-panel-description>
</mat-expansion-panel-header>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
그룹명을 넣습니다
<span>(<strong>6</strong>/7)</span>
</mat-panel-title>
<mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
<app-user-item></app-user-item>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</mat-drawer>
<mat-drawer-content class="drawer-content">
<button
mat-icon-button
class="left-drawer-toggle"
(click)="drawer.toggle()"
>
&lt;
</button>
<mat-toolbar>
<div><span>Today</span>2020.05.05</div>
<div>
<div class="app-layout-native-title-bar-actions">
<button
mat-icon-button
class="button app-layout-native-title-bar-minimize"
>
<!--<mat-icon>minimize</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="5" y1="18" x2="19" y2="18"></line>
</svg>
</button>
<button
mat-icon-button
class="button app-layout-native-title-bar-maximize"
>
<ng-container [ngSwitch]="'Maximized'">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
*ngSwitchCase="'Maximized'"
>
<path
class="st0"
d="M15,9.5v7c0,0.8-0.7,1.5-1.5,1.5h-7C5.7,18,5,17.3,5,16.5v-7C5,8.7,5.7,8,6.5,8h7C14.3,8,15,8.7,15,9.5z"
/>
<path
class="st0"
d="M8.8,6.8V6c0-0.8,0.7-1.5,1.5-1.5H17c0.8,0,1.5,0.7,1.5,1.5v6.8c0,0.8-0.7,1.5-1.5,1.5h-0.8"
/>
</svg>
<!--<mat-icon *ngSwitchDefault>crop_din</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
*ngSwitchDefault
>
<rect
x="5"
y="5"
width="12"
height="12"
rx="2"
ry="2"
></rect>
</svg>
</ng-container>
</button>
<button
mat-icon-button
class="button app-layout-native-title-bar-close"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
</mat-toolbar>
<div class="contents-main">
<div class="subtitle">Welcome to M-Messenger</div>
<div class="mainProfile">
<mat-card class="example-card">
<mat-card-header>
<div
mat-card-avatar
class="profileImage"
style="background-size: cover;"
>
<img
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
style="width: 50px; height: auto;"
/>
</div>
<mat-card-title>홍길동 <span>선임</span></mat-card-title>
<mat-card-subtitle>(Hong Gil Dong)</mat-card-subtitle>
<mat-card-subtitle><span>O</span>온라인</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="intro">
<mat-form-field class="example-full-width">
<mat-label>이름 부서명, 전화번호, 이메일</mat-label>
<input
matInput
placeholder="이름 부서명, 전화번호, 이메일"
value=""
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</div>
<ul>
<li class="company"><label>회사</label>LGCNS</li>
<li class="dept"><label>부서</label>아키텍쳐 솔루션</li>
<li class="email"><label>이메일</label>lgcns@gmail.com</li>
<li class="office">
<label>사무실</label>+82) 041-111-2222
</li>
<li class="mobile"><label>모바일</label>031-2222-4444</li>
</ul>
</mat-card-content>
<mat-card-actions>
<button mat-button class="info">info</button>
<button mat-button class="theme">theme1</button>
<button mat-button class="theme">theme2</button>
<button mat-button class="theme checked">theme3</button>
</mat-card-actions>
</mat-card>
</div>
<div class="info">
<div class="bookmark">
<div class="subtitle">Bookmark</div>
<div class="chatlist">
<!-- loop > component > 대화 리스트 공용 -->
<div>
<div class="profileImage">
<img
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
style="width: 50px; height: 50px;"
/>
</div>
<div class="info">
<div class="roomName">UCAP 프로젝트방</div>
<div class="lastMessage">
대화방의 마지막대화내용이 들어갈껍니다.
</div>
</div>
<div class="subInfo">
<div class="lastDate" matBadge="4">2020.04.05</div>
</div>
</div>
<!--// loop > component > 대화 리스트 공용 -->
<div>
<div class="profileImage">
<img
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
style="width: 50px; height: 50px;"
/>
</div>
<div class="info">
<div class="roomName">UCAP 프로젝트방</div>
<div class="lastMessage">
대화방의 마지막대화내용이 들어갈껍니다.
</div>
</div>
<div class="subInfo">
<div class="lastDate">2020.04.05</div>
</div>
</div>
</div>
</div>
<div class="allim">
<div class="subtitle">알림봇</div>
<div class="allimList">
<mat-card class="allim-card">
<mat-card-header>
<div
mat-card-avatar
class="profileImage"
style="background-size: cover;"
>
<img
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
style="width: 50px; height: auto;"
/>
</div>
<mat-card-title>화상회의</mat-card-title>
<mat-card-subtitle>2020.04.05</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="title">화상회의 개설</div>
<div class="contents">화상회의가 개설되었습니다.</div>
</mat-card-content>
<mat-card-actions>
<button mat-button class="more">더보기</button>
</mat-card-actions>
</mat-card>
<mat-card class="allim-card">
<mat-card-header>
<div
mat-card-avatar
class="profileImage"
style="background-size: cover;"
>
<img
src="https://material.angular.io/assets/img/examples/shiba2.jpg"
style="width: 50px; height: auto;"
/>
</div>
<mat-card-title>화상회의</mat-card-title>
<mat-card-subtitle>2020.04.05</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="title">화상회의 개설</div>
<div class="contents">화상회의가 개설되었습니다.</div>
</mat-card-content>
<mat-card-actions>
<button mat-button class="more">더보기</button>
</mat-card-actions>
</mat-card>
</div>
</div>
<div class="banner">배너</div>
</div>
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>
<div class="footer">
<div>
<span>현재버전 0.0.11</span>
<span>최신버전 0.0.11</span>
</div>
<div style="right: 0;">
HelpDesk 1661-9066
</div>
</div>
</div>
</div>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>

View File

@ -0,0 +1,185 @@
@charset 'UTF-8';
@import '../../../assets/scss/components.scss';
.container {
@extend %clearfix;
@extend %guideline; //Guide Line
background-color: $gray-ref0;
width: 100%;
min-height: 100%;
.gnb,
.menu-container {
float: left;
.list-section {
margin-top: 50px;
}
}
.contents-main {
@extend %clearfix;
height: calc(100vh - 122px);
.mainProfile {
float: left;
height: 600px;
}
.info {
.allim {
.allimList {
.allim-card {
display: inline-block;
}
}
}
}
}
.footer {
@extend %clearfix;
height: 40px;
width: 100%;
@extend %guideline; //Guide Line
}
}
.gnb {
@extend %guideline; //Guide Line
background-color: $gray-ref0;
width: 60px;
height: 100%;
display: flex;
flex-direction: column;
.lgucap-logo {
display: block;
width: 32px;
height: 32px;
background: url(../../../assets/images/logo_140.png) no-repeat 50% 0;
background-size: 32px;
}
.left-container {
display: flex;
width: calc(100% - 28px);
height: 100%;
}
.global-menu {
width: 100%;
background-color: $gray-ref0;
}
::ng-deep .global-menu {
display: flex;
flex-direction: row;
.mat-tab-header {
border-bottom: none !important;
width: 100%;
}
.mat-tab-label-container {
.mat-tab-list {
.mat-tab-labels {
@extend %clearfix;
flex-flow: column;
height: 100vh;
padding-top: 10px;
border-bottom: none;
.mat-tab-label {
width: 100%;
height: 32px;
padding: 0;
min-width: 0 !important;
.mat-tab-label-content {
.icon-item {
display: inline-flex;
width: 32px;
height: 32px;
border-radius: 50%;
justify-content: center;
align-items: center;
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.4, 0, 0, 1);
svg {
width: 24px;
height: 24px;
stroke: #ffffff;
stroke-width: 1.5;
stroke-linecap: square;
stroke-linejoin: miter;
fill: none;
}
.mat-badge-content {
right: -4px !important;
}
}
}
&.mat-tab-label-active {
opacity: 0;
}
&[aria-selected='true'] {
opacity: 1;
.mat-tab-label-content {
.icon-item {
transform: scale(1);
}
}
}
}
}
.mat-ink-bar {
opacity: 0;
}
}
}
.mat-tab-body-wrapper {
.mat-tab-body {
height: 100%;
width: 100%;
}
}
}
}
.menu-container {
@extend %clearfix;
@extend %guideline2; //Guide Line2
width: calc(100% - 71px);
@extend %height100vh;
box-sizing: border-box;
.m-messenger-area {
width: 400px;
padding-top: 50px;
box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.6);
.m-messenger-tit {
width: 400px;
height: 50px;
position: fixed;
top: 0;
}
}
}
.footer {
@extend %clearfix;
div {
display: inline-block;
}
}
.left-drawer-toggle {
position: absolute;
top: calc(50% - 30px);
left: -4px;
border: 1px solid #dddddd;
background-color: #ffffff !important;
width: 24px;
height: 60px;
border-radius: 0 8px 8px 0;
display: flex;
justify-content: center;
justify-items: center;
font-size: 1.8em;
z-index: inherit;
}

View File

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

View File

@ -0,0 +1,24 @@
import { action } from '@storybook/addon-actions';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';
import { GroupComponent } from './group.component';
import { MaterialModule } from 'src/app/material.module';
import { UserItemComponent } from './component/user-item.component';
export default {
title: 'GroupComponent',
component: GroupComponent,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, MaterialModule],
providers: [],
declarations: [UserItemComponent]
})
]
};
export const Page = () => ({
component: GroupComponent
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-group',
templateUrl: './group.component.html',
styleUrls: ['./group.component.scss']
})
export class GroupComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { UserItemComponent } from './component/user-item.component';
const COMPONENT = [UserItemComponent];
@NgModule({
exports: [...COMPONENT],
declarations: [...COMPONENT]
})
export class GroupModule {}

View File

@ -0,0 +1 @@
<p>message works!</p>

View File

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

View File

@ -0,0 +1,22 @@
import { action } from '@storybook/addon-actions';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';
import { MessageComponent } from './message.component';
import { MaterialModule } from 'src/app/material.module';
export default {
title: 'MessageComponent',
component: MessageComponent,
decorators: [
moduleMetadata({
imports: [BrowserModule, BrowserAnimationsModule, MaterialModule],
providers: []
})
]
};
export const Page = () => ({
component: MessageComponent
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-message',
templateUrl: './message.component.html',
styleUrls: ['./message.component.scss']
})
export class MessageComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,624 @@
import {
sequence,
trigger,
animate,
style,
group,
query,
transition,
animateChild,
state,
animation,
useAnimation,
stagger,
keyframes
} from '@angular/animations';
const customAnimation = animation(
[
style({
opacity: '{{opacity}}',
transform: 'scale({{scale}}) translate3d({{x}}, {{y}}, {{z}})'
}),
animate('{{duration}} {{delay}} cubic-bezier(0.0, 0.0, 0.2, 1)', style('*'))
],
{
params: {
duration: '200ms',
delay: '0ms',
opacity: '0',
scale: '1',
x: '0',
y: '0',
z: '0'
}
}
);
export const ucapAnimations = [
trigger('animate', [
transition('void => *', [useAnimation(customAnimation)])
]),
trigger('animateStagger', [
state('50', style('*')),
state('100', style('*')),
state('200', style('*')),
transition(
'void => 50',
query('@*', [stagger('50ms', [animateChild()])], {
optional: true
})
),
transition(
'void => 100',
query('@*', [stagger('100ms', [animateChild()])], {
optional: true
})
),
transition(
'void => 200',
query('@*', [stagger('200ms', [animateChild()])], {
optional: true
})
)
]),
trigger('fadeInOut', [
state(
'0, void',
style({
opacity: 0
})
),
state(
'1, *',
style({
opacity: 1
})
),
transition('1 => 0', animate('300ms ease-out')),
transition('0 => 1', animate('300ms ease-in')),
transition('void <=> *', animate('300ms ease-in'))
]),
trigger('slideInOut', [
state(
'0',
style({
height: '0px'
})
),
state(
'1',
style({
height: '*'
})
),
transition('1 => 0', animate('300ms ease-out')),
transition('0 => 1', animate('300ms ease-in'))
]),
trigger('slideIn', [
transition('void => left', [
style({
transform: 'translateX(100%)'
}),
animate(
'300ms ease-in',
style({
transform: 'translateX(0)'
})
)
]),
transition('left => void', [
style({
transform: 'translateX(0)'
}),
animate(
'300ms ease-in',
style({
transform: 'translateX(-100%)'
})
)
]),
transition('void => right', [
style({
transform: 'translateX(-100%)'
}),
animate(
'300ms ease-in',
style({
transform: 'translateX(0)'
})
)
]),
transition('right => void', [
style({
transform: 'translateX(0)'
}),
animate(
'300ms ease-in',
style({
transform: 'translateX(100%)'
})
)
])
]),
trigger('slideInLeft', [
state(
'void',
style({
transform: 'translateX(-100%)'
})
),
state(
'*',
style({
transform: 'translateX(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('slideInRight', [
state(
'void',
style({
transform: 'translateX(100%)'
})
),
state(
'*',
style({
transform: 'translateX(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('slideInTop', [
state(
'void',
style({
transform: 'translateY(-100%)'
})
),
state(
'*',
style({
transform: 'translateY(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('slideInBottom', [
state(
'void',
style({
transform: 'translateY(100%)'
})
),
state(
'*',
style({
transform: 'translateY(0)'
})
),
transition('void => *', animate('300ms')),
transition('* => void', animate('300ms'))
]),
trigger('expandCollapse', [
state(
'void',
style({
height: '0px'
})
),
state(
'*',
style({
height: '*'
})
),
transition('void => *', animate('300ms ease-out')),
transition('* => void', animate('300ms ease-in'))
]),
// -----------------------------------------------------------------------------------------------------
// @ Router animations
// -----------------------------------------------------------------------------------------------------
trigger('routerTransitionLeft', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateX(100%)',
opacity: 0
})
],
{ optional: true }
),
sequence([
group([
query(
'content > :leave',
[
style({
transform: 'translateX(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(-100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateX(100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
])
]),
trigger('routerTransitionRight', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateX(-100%)',
opacity: 0
})
],
{ optional: true }
),
sequence([
group([
query(
'content > :leave',
[
style({
transform: 'translateX(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateX(-100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateX(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
])
]),
trigger('routerTransitionUp', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateY(100%)',
opacity: 0
})
],
{ optional: true }
),
group([
query(
'content > :leave',
[
style({
transform: 'translateY(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(-100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateY(100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
]),
trigger('routerTransitionDown', [
transition('* => *', [
query(
'content > :enter, content > :leave',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
transform: 'translateY(-100%)',
opacity: 0
})
],
{ optional: true }
),
sequence([
group([
query(
'content > :leave',
[
style({
transform: 'translateY(0)',
opacity: 1
}),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(100%)',
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({ transform: 'translateY(-100%)' }),
animate(
'600ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
transform: 'translateY(0%)',
opacity: 1
})
)
],
{ optional: true }
)
]),
query('content > :leave', animateChild(), { optional: true }),
query('content > :enter', animateChild(), { optional: true })
])
])
]),
trigger('routerTransitionFade', [
transition(
'* => *',
group([
query(
'content > :enter, content > :leave ',
[
style({
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
})
],
{ optional: true }
),
query(
'content > :enter',
[
style({
opacity: 0
})
],
{ optional: true }
),
query(
'content > :leave',
[
style({
opacity: 1
}),
animate(
'300ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
opacity: 0
})
)
],
{ optional: true }
),
query(
'content > :enter',
[
style({
opacity: 0
}),
animate(
'300ms cubic-bezier(0.0, 0.0, 0.2, 1)',
style({
opacity: 1
})
)
],
{ optional: true }
),
query('content > :enter', animateChild(), { optional: true }),
query('content > :leave', animateChild(), { optional: true })
])
)
]),
trigger('treeToggler', [
state(
'0, void',
style({
opacity: 0
})
),
state(
'1, *',
style({
opacity: 1
})
),
transition('1 => 0', animate('300ms ease-out')),
transition('0 => 1', animate('300ms ease-in')),
transition('void <=> *', animate('300ms ease-in'))
]),
/**
* Floating ACtion Button Animation.
*/
trigger('fabToggler', [
state(
'inactive',
style({
transform: 'rotate(0deg)'
})
),
state(
'active',
style({
transform: 'rotate(225deg)'
})
),
transition('* <=> *', animate('200ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
]),
trigger('speedDialStagger', [
transition('* => *', [
query(':enter', style({ opacity: 0 }), { optional: true }),
query(
':enter',
stagger('40ms', [
animate(
'200ms cubic-bezier(0.4, 0.0, 0.2, 1)',
keyframes([
style({ opacity: 0, transform: 'translateY(10px)' }),
style({ opacity: 1, transform: 'translateY(0)' })
])
)
]),
{ optional: true }
),
query(
':leave',
animate(
'200ms cubic-bezier(0.4, 0.0, 0.2, 1)',
keyframes([style({ opacity: 1 }), style({ opacity: 0 })])
),
{ optional: true }
)
])
])
];

View File

@ -0,0 +1,198 @@
<!-- <cdk-virtual-scroll-viewport
#dtViewPort
perfectScrollbar
tvsItemSize="53"
headerHeight="56"
style="height: 400px;"
> -->
<div class="">
<p>아키텍처솔루션팀 20명</p>
<mat-form-field class="example-full-width" appearance="none">
<mat-select placeholder="회사">
<mat-option
*ngFor="let searchItem of searchItemList"
[value]="searchItem"
>
{{ searchItem.itemName }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="example-full-width" appearance="none">
<mat-select placeholder="상태">
<mat-option
*ngFor="let searchItem of searchItemList"
[value]="searchItem"
>
{{ searchItem.itemName }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="example-full-width" appearance="none">
<mat-select placeholder="부서">
<mat-option
*ngFor="let searchItem of searchItemList"
[value]="searchItem"
>
{{ searchItem.itemName }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="example-full-width" appearance="none">
<mat-select placeholder="직급(직책)">
<mat-option
*ngFor="let searchItem of searchItemList"
[value]="searchItem"
>
{{ searchItem.itemName }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div>
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" matSort>
<ng-container matColumnDef="profileImage">
<th mat-header-cell *matHeaderCellDef class="profileImage">
<span class="ui-column-divider"></span>
<span>
profileImage
</span>
</th>
<td mat-cell *matCellDef="let element" class="profileImage">
<div class="table-item">
<span
class="presence"
[matTooltip]="true"
matTooltipPosition="after"
></span>
<span class="thumbnail-mask">
<img />
</span>
<span *ngIf="false" 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" minWidth="150">
<span class="ui-column-divider resizable"></span>
<span mat-sort-header="name">
이름
</span>
</th>
<td mat-cell *matCellDef="let element" class="profileInfo">
<div class="baseInfo">
<span *ngIf="true" class="work-status" [ngClass]="work - status">
휴가중
</span>
<span class="name">
이름
</span>
<span class="grade">
직급
</span>
</div>
<div class="deptName">
아키텍처솔루션팀
</div>
<div class="workplace">
빅데이터,인사정보
</div>
</td>
</ng-container>
<ng-container matColumnDef="company_hpNumber">
<th mat-header-cell *matHeaderCellDef minWidth="75">
<span class="ui-column-divider resizable"></span>
<div mat-sort-header="company">
연락처
</div>
</th>
<td mat-cell *matCellDef="let element">
<div class="companyName">
LGCNS
</div>
<div class="hpNumber" (click)="onClickCall('MOBILE', element)">
010-3043-2921
</div>
</td>
</ng-container>
<ng-container matColumnDef="responsibilities_email">
<th mat-header-cell *matHeaderCellDef minWidth="90">
<span class="ui-column-divider"></span>
<div mat-sort-header="responsibilities">
이메일
</div>
</th>
<td mat-cell *matCellDef="let element">
<div class="responsibilities">
dkdk
</div>
<div class="email">
avdkekdl@namver.com
</div>
</td>
</ng-container>
<ng-container matColumnDef="checkable">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
[aria-label]="checkboxLabel()"
>
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let row">
<mat-checkbox
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)"
[aria-label]="checkboxLabel(row)"
>
</mat-checkbox>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<div>
<mat-form-field class="example-chip-list">
<mat-chip-list #chipList aria-label="Fruit selection">
<mat-chip
*ngFor="let fruit of fruits"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(fruit)"
>
{{ fruit }}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input
placeholder="New fruit..."
#fruitInput
[formControl]="fruitCtrl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="add($event)"
/>
</mat-chip-list>
<mat-autocomplete
#auto="matAutocomplete"
(optionSelected)="selected($event)"
>
<mat-option *ngFor="let fruit of filteredFruits | async" [value]="fruit">
{{ fruit }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<!-- </cdk-virtual-scroll-viewport> -->
<!-- <div
class="no-search-result"
fxFlexFill
*ngIf="!sortedData || 0 === sortedData.length"
></div> -->

View File

@ -0,0 +1,235 @@
@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;
}*/
.wrapper {
height: 300px;
}
mat-form-field {
font-size: 14px;
width: 100%;
}
.list-chip {
height: 100px;
width: 100%;
padding: 10px;
border: 1px solid #dddddd;
overflow: auto;
background-color: #f9f9f9;
margin-top: 10px;
}
table {
width: 100%;
min-width: 600px;
table-layout: fixed;
}
th.mat-header-cell {
@include disable-selection;
position: relative;
padding: 0 10px;
span.ui-column-divider {
display: block;
position: absolute;
top: 10px;
right: 0;
margin: 0;
width: 2px;
height: 40px;
padding: 0;
cursor: initial;
border: none;
background-color: #d4d4d4;
&.resizable {
cursor: col-resize;
}
}
span {
&[mat-sort-header='name'],
&[mat-sort-header='grade'] {
display: inline-flex;
padding-right: 10px;
}
}
&.profileImage {
width: 90px;
}
&.mat-column-checkable {
width: 50px;
}
}
td.mat-cell {
padding: 6px;
position: relative;
div {
@include ellipsis(1);
}
div:nth-chlid(2) {
padding-top: 4px;
}
&.profileImage {
width: 90px;
text-overflow: unset;
flex: 0 0 90px;
.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: 0;
left: 64px;
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;
}
}
}
}
&.mat-column-checkable {
padding-left: 10px;
flex: 0 0 50px;
}
&.profileInfo {
cursor: pointer;
flex: 0 0 200px;
.baseInfo {
display: flex;
font-size: 1em;
@include ellipsis(1);
.name {
font-size: 1em;
font-weight: 600;
}
.grade {
font-size: 0.86em;
color: #666666;
}
}
.deptName {
font-size: 0.9em;
color: #666666;
}
}
.companyName,
.workplace,
.responsibilities {
font-size: 0.86em;
font-weight: 600;
}
.hpNumber {
cursor: pointer;
}
.lineNumber {
cursor: pointer;
}
}
.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;
}
::ng-deep .integrate-search-org {
td.mat-cell {
&.profileInfo {
cursor: initial !important;
}
}
}
::ng-deep .ps__rail-y {
z-index: 101;
}
.example-chip-list {
width: 100%;
}

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,197 @@
import {
Component,
ViewChild,
OnInit,
ChangeDetectorRef,
ElementRef
} from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { MatTableDataSource } from '@angular/material/table';
import { SelectionModel } from '@angular/cdk/collections';
import { MatSort } from '@angular/material/sort';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { startWith, map } from 'rxjs/operators';
import {
MatAutocomplete,
MatAutocompleteSelectedEvent
} from '@angular/material/autocomplete';
import { MatChipInputEvent } from '@angular/material/chips';
export interface PeriodicElement {
name: string;
position: number;
weight: string;
}
export interface UserInfo {
/** 사용자SEQ */
seq: number;
/** 사용자명 */
name: string;
}
const ELEMENT_DATA: PeriodicElement[] = [
{ position: 1, name: 'LGCNS', weight: '' },
{ position: 2, name: 'LGCNS', weight: '' },
{ position: 3, name: 'LGCNS', weight: '' },
{ position: 4, name: 'LGCNS', weight: '' },
{ position: 5, name: 'LGCNS', weight: '' },
{ position: 6, name: 'LGCNS', weight: '' }
];
@Component({
selector: 'app-organization-detail-table',
templateUrl: './detail-table.component.html',
styleUrls: ['./detail-table.component.scss']
})
export class DetailTableComponent implements OnInit {
displayedColumns2: string[] = ['position', 'name', 'weight', 'select'];
displayedColumns: string[] = [
'profileImage',
'profileInfo',
'company_hpNumber',
'responsibilities_email',
'checkable'
];
selectedUserList1: UserInfo[] = [
{ seq: 1, name: 'Helium' },
{ seq: 2, name: 'Hydrogen' },
{ seq: 3, name: 'Lithium' },
{ seq: 4, name: 'Beryllium' },
{ seq: 5, name: 'Carbon' },
{ seq: 6, name: 'Boron' }
];
selectedUserList: UserInfo[] = [
{ seq: 1, name: 'Helium' },
{ seq: 2, name: 'Hydrogen' },
{ seq: 3, name: 'Lithium' },
{ seq: 4, name: 'Beryllium' },
{ seq: 5, name: 'Carbon' },
{ seq: 6, name: 'Boron' }
];
searchItemList: any[] = [{ itemName: '전체' }, { itemName: '부서' }];
visible = true;
selectable = true;
removable = true;
separatorKeysCodes: number[] = [ENTER, COMMA];
fruitCtrl = new FormControl();
filteredFruits: Observable<string[]>;
fruits: string[] = ['Lemon'];
allFruits: string[] = ['Apple', 'Lemon', 'Lime', 'Orange', 'Strawberry'];
@ViewChild(MatSort)
sort: MatSort;
@ViewChild('fruitInput') fruitInput: ElementRef<HTMLInputElement>;
@ViewChild('auto') matAutocomplete: MatAutocomplete;
dataSource: MatTableDataSource<PeriodicElement>;
selection: SelectionModel<PeriodicElement>;
constructor(private changeDetectorRef: ChangeDetectorRef) {
this.dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);
this.selection = new SelectionModel<PeriodicElement>(true, []);
this.filteredFruits = this.fruitCtrl.valueChanges.pipe(
startWith(null),
map((fruit: string | null) =>
fruit ? this._filter(fruit) : this.allFruits.slice()
)
);
}
ngOnInit() {
this.dataSource.sort = this.sort;
this.dataSource.filterPredicate = (
data: PeriodicElement,
filterJson: string
) => {
const matchFilter = [];
const filters = JSON.parse(filterJson);
filters.forEach((filter) => {
const val = data[filter.id] === null ? '' : data[filter.id];
matchFilter.push(
val.toLowerCase().includes(filter.value.toLowerCase())
);
});
return matchFilter.every(Boolean);
};
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected()
? this.selection.clear()
: this.dataSource.data.forEach((row) => this.selection.select(row));
}
/** The label for the checkbox on the passed row */
checkboxLabel(row?: PeriodicElement): string {
if (!row) {
return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
}
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${
row.position + 1
}`;
}
onClickDeleteUser(userInfo: UserInfo) {
this.selectedUserList = this.selectedUserList.filter(
(item) => item.seq !== userInfo.seq
);
this.changeDetectorRef.detectChanges();
}
add(event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
// Add our fruit
if ((value || '').trim()) {
this.fruits.push(value.trim());
}
// Reset the input value
if (input) {
input.value = '';
}
this.fruitCtrl.setValue(null);
}
remove(fruit: string): void {
const index = this.fruits.indexOf(fruit);
if (index >= 0) {
this.fruits.splice(index, 1);
}
}
selected(event: MatAutocompleteSelectedEvent): void {
this.fruits.push(event.option.viewValue);
this.fruitInput.nativeElement.value = '';
this.fruitCtrl.setValue(null);
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.allFruits.filter(
(fruit) => fruit.toLowerCase().indexOf(filterValue) === 0
);
}
}

View File

@ -0,0 +1,102 @@
<cdk-virtual-scroll-viewport
#cvsvOrganization
itemSize="36"
perfectScrollbar
fxFlexFill
style="height: 400px;"
>
<ng-container
*cdkVirtualFor="let node of dataSource.expandedData$"
></ng-container>
<mat-tree
fxFlexFill
#orgranizationTree
[dataSource]="dataSource"
[treeControl]="treeControl"
class="organization-tree"
>
<!-- This is the tree node template for leaf nodes -->
<mat-tree-node
*matTreeNodeDef="let node"
matTreeNodePadding
matTreeNodePaddingIndent="20"
class="tree-no-child"
>
<div
class="tree-node-closer-container"
*ngFor="let i of appendDivArray(node.level)"
>
<div
class="tree-node-closer-top"
[style.left.px]="20 - 20 * i"
[attr.last-node]="isLastNode(node, i) ? '' : null"
[attr.sub-node]="0 < i ? '' : null"
></div>
<div
class="tree-node-closer-bottom"
[style.left.px]="20 - 20 * i"
[attr.last-node]="isLastNode(node, i) ? '' : null"
[attr.sub-node]="0 < i ? '' : null"
></div>
</div>
<li (click)="onClickNode(node)" matRipple>
<div class="tree-node-body">
<!-- {{ node.deptInfo | ucapTranslate: 'name' }} -->
{{ node.deptInfo.name }}
</div>
</li>
</mat-tree-node>
<!-- This is the tree node template for expandable nodes -->
<mat-tree-node
*matTreeNodeDef="let node; when: hasChild"
matTreeNodePadding
matTreeNodePaddingIndent="20"
class="tree-has-child"
>
<div
class="tree-node-closer-container"
*ngFor="let i of appendDivArray(node.level)"
>
<div
class="tree-node-closer-top"
[style.left.px]="20 - 20 * i"
[attr.last-node]="isLastNode(node, i) ? '' : null"
[attr.sub-node]="0 < i ? '' : null"
></div>
<div
class="tree-node-closer-bottom"
[style.left.px]="20 - 20 * i"
[attr.expanded]="treeControl.isExpanded(node) ? '' : null"
[attr.last-node]="isLastNode(node, i) ? '' : null"
[attr.sub-node]="0 < i ? '' : null"
></div>
</div>
<li (click)="onClickNode(node)" matRipple>
<div class="tree-node-body">
<span class="horizontal-line"></span>
<button
mat-icon-button
color="accent"
matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.filename"
>
<mat-icon
class="tree-node-expand-btn"
[@romvoeAdd]="treeControl.isExpanded(node) ? 'remove' : 'add'"
>
{{ treeControl.isExpanded(node) ? 'remove' : 'add' }}
</mat-icon>
</button>
<span class="dept-name">
<!-- {{
node.deptInfo | ucapTranslate: 'name'
}} -->
{{ node.deptInfo.name }}
</span>
</div>
<!-- <ul class="tree-node-closer"></ul> -->
</li>
</mat-tree-node>
</mat-tree>
</cdk-virtual-scroll-viewport>

View File

@ -0,0 +1,97 @@
.organization-tree {
padding: 5px;
.tree-node-closer-container {
position: relative;
.tree-node-closer-top {
width: 15px;
height: 40px;
position: absolute;
border: 1px dotted #cccccc;
border-width: 0 0 1px 1px;
top: -40px;
}
.tree-node-closer-bottom {
width: 15px;
height: 40px;
position: absolute;
border: 1px dotted #cccccc;
border-width: 0 0 1px 1px;
top: 0px;
}
.tree-node-closer-top[sub-node] {
border-width: 0 0 0 1px;
}
.tree-node-closer-top[sub-node][last-node] {
border-width: 0 0 0 0;
}
.tree-node-closer-bottom[expanded] {
border-width: 0 0 1px 0px;
}
.tree-node-closer-bottom[last-node] {
border-width: 0 0 1px 0px;
}
.tree-node-closer-bottom[sub-node] {
border-width: 0 0 0 1px;
}
.tree-node-closer-bottom[sub-node][last-node] {
border-width: 0 0 0 0;
}
}
li {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
}
.tree-has-child {
height: 36px;
min-height: 36px;
li {
display: flex;
align-items: center;
cursor: pointer;
width: 100%;
margin-left: 10px;
margin-right: 10px;
.tree-node-body {
display: flex;
align-items: center;
width: 100%;
height: 30px;
border-radius: 4px;
button {
line-height: normal;
width: 30px;
height: 30px;
.tree-node-expand-btn {
background-color: transparent;
font-size: 18px;
}
}
}
}
}
.tree-no-child {
height: 36px;
min-height: 36px;
font-size: 13px;
li {
display: flex;
align-items: center;
width: 100%;
height: 100%;
cursor: pointer;
.tree-node-body {
padding-left: 40px;
}
}
}
}

View File

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

View File

@ -0,0 +1,261 @@
import {
Component,
OnInit,
ViewChild,
ChangeDetectorRef,
AfterViewInit
} from '@angular/core';
import { trigger, transition, style, animate } from '@angular/animations';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { FlatTreeControl } from '@angular/cdk/tree';
import { VirtualScrollTreeFlatDataSource } from '../datasource/virtual-scroll-tree-flat.data-source';
import { MatTreeFlattener, MatTree } from '@angular/material/tree';
import { ucapAnimations } from './animation';
export enum DeptType {
// R: 기관Root
Root = 'R',
// D: 본부
Department = 'D',
// T: 부서
Team = 'T'
}
interface DeptInfo {
/** 부서SEQ */
seq: number;
/** 부서명 */
name: string;
/** 부서타입 */
type: DeptType;
/** 본부SEQ */
rootSeq: number;
/** 상위부서SEQ */
parentSeq: number;
}
interface OrganizationNode {
deptInfo: DeptInfo;
name: string;
children?: OrganizationNode[];
}
interface FlatNode {
expandable: boolean;
name: string;
level: number;
deptInfo: DeptInfo;
}
const deptInfoList: DeptInfo[] = [
{
seq: 1,
name: '사장실',
type: DeptType.Root,
rootSeq: 0,
parentSeq: 0
},
{
seq: 2,
name: '고문실',
type: DeptType.Department,
rootSeq: 1,
parentSeq: 1
},
{
seq: 3,
name: '아키텍처솔루션팀',
type: DeptType.Department,
rootSeq: 1,
parentSeq: 1
},
{
seq: 4,
name: '사장님 O/H',
type: DeptType.Team,
rootSeq: 1,
parentSeq: 2
},
{
seq: 5,
name: 'DT 사업부',
type: DeptType.Department,
rootSeq: 1,
parentSeq: 3
},
{
seq: 6,
name: '클라우드 사업부',
type: DeptType.Department,
rootSeq: 1,
parentSeq: 3
},
{
seq: 7,
name: 'LF 팀',
type: DeptType.Team,
rootSeq: 2,
parentSeq: 2
}
];
@Component({
selector: 'app-tree',
templateUrl: './tree.component.html',
styleUrls: ['./tree.component.scss'],
animations: [
ucapAnimations,
trigger('romvoeAdd', [
transition('remove <=> add', [
style({
transform: `rotate(45deg)`,
opacity: 0
}),
animate('.2s 0s ease-out')
])
])
]
})
export class TreeComponent implements OnInit, AfterViewInit {
@ViewChild('cvsvOrganization', { static: false })
cvsvOrganization: CdkVirtualScrollViewport;
treeControl: FlatTreeControl<FlatNode>;
treeFlattener: MatTreeFlattener<OrganizationNode, FlatNode>;
dataSource: VirtualScrollTreeFlatDataSource<OrganizationNode, FlatNode>;
constructor() {
this.treeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
(node) => node.expandable
);
this.treeFlattener = new MatTreeFlattener<OrganizationNode, FlatNode>(
(node: OrganizationNode, level: number) => {
return {
expandable: !!node.children && node.children.length > 0,
name: node.name,
level,
deptInfo: node.deptInfo
};
},
(node) => node.level,
(node) => node.expandable,
(node) => node.children
);
this.dataSource = new VirtualScrollTreeFlatDataSource<
OrganizationNode,
FlatNode
>(this.treeControl, this.treeFlattener);
const nodeMap = new Map<number, OrganizationNode>();
const rootNodeList: OrganizationNode[] = [];
const remainChildNodeList: OrganizationNode[] = [];
deptInfoList.forEach((deptInfo) => {
const node: OrganizationNode = {
deptInfo,
name: deptInfo.name,
children: []
};
if (nodeMap.has(deptInfo.seq)) {
return;
}
nodeMap.set(deptInfo.seq, node);
if (0 === deptInfo.parentSeq) {
rootNodeList.push(node);
return;
}
if (nodeMap.has(deptInfo.parentSeq)) {
nodeMap.get(deptInfo.parentSeq).children.push(node);
} else {
remainChildNodeList.push(node);
}
});
remainChildNodeList.forEach((node) => {
if (nodeMap.has(node.deptInfo.parentSeq)) {
nodeMap.get(node.deptInfo.parentSeq).children.push(node);
}
});
this.dataSource.data = rootNodeList;
}
ngOnInit(): void {}
ngAfterViewInit(): void {
this.dataSource.cdkVirtualScrollViewport = this.cvsvOrganization;
this.cvsvOrganization.scrollToOffset(100);
}
hasChild = (_: number, node: FlatNode) => node.expandable;
onClickNode(node: OrganizationNode) {
// this.selected.emit(node.deptInfo);
console.log(node);
}
isLastNode(node: FlatNode, depth: number): boolean {
const i = this.findNodeIndex(node, depth);
if (-1 === i) {
return false;
}
if (i === this.dataSource.expandedDataSubject.value.length - 1) {
return true;
}
const n = this.dataSource.expandedDataSubject.value[i];
for (
let idx = i + 1;
idx < this.dataSource.expandedDataSubject.value.length;
idx++
) {
const element = this.dataSource.expandedDataSubject.value[idx];
if (n.level === element.level) {
return false;
}
if (n.level > element.level) {
return true;
}
}
return true;
}
appendDivArray(level: number): number[] {
if (0 === level) {
return [];
}
return Array.from({ length: level }, (_, i: number) => i);
}
private findNodeIndex(node: FlatNode, depth: number): number {
if (!node) {
return -1;
}
const i = this.dataSource.expandedDataSubject.value.findIndex((n) => {
return node.deptInfo.seq === n.deptInfo.seq;
});
if (0 === depth) {
return i;
}
return this.findNodeIndex(this.findParentNode(i), depth - 1);
}
private findParentNode(index: number): FlatNode {
const node = this.dataSource.expandedDataSubject.value[index];
for (let idx = index - 1; idx >= 0; idx--) {
const n = this.dataSource.expandedDataSubject.value[idx];
if (n.level === node.level - 1) {
return n;
}
}
return undefined;
}
}

View File

@ -0,0 +1,131 @@
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { FlatTreeControl } from '@angular/cdk/tree';
import {
BehaviorSubject,
merge,
Observable,
Subject,
Subscription
} from 'rxjs';
import { map, share } from 'rxjs/operators';
import { MatTreeFlattener } from '@angular/material/tree';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
export class VirtualScrollTreeFlatDataSource<T, F> extends DataSource<F> {
private flattenedDataSubject = new BehaviorSubject<F[]>([]);
expandedDataSubject = new BehaviorSubject<F[]>([]);
expandedData$: Observable<F[]>;
private connectSubject: Subject<F[]>;
private dataChangeSubscription: Subscription;
private rangeChangeSubscription: Subscription;
private rangeSubject: BehaviorSubject<{
start: number;
end: number;
}>;
// tslint:disable-next-line: variable-name
private _cdkVirtualScrollViewport: CdkVirtualScrollViewport;
set cdkVirtualScrollViewport(
cdkVirtualScrollViewport: CdkVirtualScrollViewport
) {
if (!cdkVirtualScrollViewport) {
return;
}
this._cdkVirtualScrollViewport = cdkVirtualScrollViewport;
this.rangeSubject = new BehaviorSubject<{ start: number; end: number }>({
start: 0,
end: 1
});
this.rangeChangeSubscription = cdkVirtualScrollViewport.renderedRangeStream.subscribe(
(range) => {
this.rangeSubject.next({
start: range.start,
end: range.end
});
if (!!this.connectSubject) {
this.connectSubject.next(
this.expandedDataSubject.value.slice(
this.rangeSubject.value.start,
this.rangeSubject.value.end
)
);
}
}
);
}
// tslint:disable-next-line: variable-name
private _data: BehaviorSubject<T[]>;
get data() {
return this._data.value;
}
set data(value: T[]) {
this._data.next(value);
this.flattenedDataSubject.next(this.treeFlattener.flattenNodes(this.data));
this.treeControl.dataNodes = this.flattenedDataSubject.value;
}
constructor(
private treeControl: FlatTreeControl<F>,
private treeFlattener: MatTreeFlattener<T, F>,
initialData: T[] = []
) {
super();
this._data = new BehaviorSubject<T[]>(initialData);
this.expandedData$ = this.expandedDataSubject.asObservable().pipe(share());
}
connect(collectionViewer: CollectionViewer): Observable<F[]> {
this.connectSubject = new Subject<F[]>();
this.dataChangeSubscription = merge(
collectionViewer.viewChange,
this.treeControl.expansionModel.changed,
this.flattenedDataSubject
)
.pipe(
map(() => {
this.expandedDataSubject.next(
this.treeFlattener.expandFlattenedNodes(
this.flattenedDataSubject.value,
this.treeControl
)
);
return !this.rangeSubject
? this.expandedDataSubject.value
: this.expandedDataSubject.value.slice(
this.rangeSubject.value.start,
this.rangeSubject.value.end
);
})
)
.subscribe((datas) => {
this.connectSubject.next(datas);
if (!!this._cdkVirtualScrollViewport) {
this._cdkVirtualScrollViewport.checkViewportSize();
}
});
return this.connectSubject.asObservable();
}
disconnect() {
if (!!this.connectSubject) {
this.connectSubject.next();
this.connectSubject.unsubscribe();
}
if (!!this.dataChangeSubscription) {
this.dataChangeSubscription.unsubscribe();
}
if (!!this.rangeChangeSubscription) {
this.rangeChangeSubscription.unsubscribe();
}
}
}

View File

@ -0,0 +1,298 @@
<div class="container">
<div class="gnb">
<mat-toolbar>logo</mat-toolbar>
<mat-tab-group
mat-stretch-tabs
animationDuration="0ms"
backgroundColor="transparent"
class="global-menu"
[selectedIndex]="2"
>
<mat-tab aria-label="Group">
<ng-template mat-tab-label>
<div class="icon-item" matTooltip="그룹" matTooltipPosition="after">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="round"
>
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Chat">
<ng-template mat-tab-label>
<div
class="icon-item"
matBadgeHidden="false"
matBadge="275"
matBadgeColor="accent"
matBadgePosition="above after"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<path
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"
></path>
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Organization">
<ng-template mat-tab-label>
<div class="icon-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<circle class="st0" cx="18.4" cy="18.5" r="3" />
<circle class="st0" cx="12" cy="5" r="3" />
<path class="st0" d="M12.4,10.5h4c1.1,0,2,0.9,2,2v3" />
<circle class="st0" cx="6.1" cy="18.5" r="3" />
<path class="st0" d="M6.1,15.5v-3c0-1.1,0.9-2,2-2h4" />
<line class="st0" x1="12" y1="8" x2="12" y2="9" />
</svg>
</div>
</ng-template>
</mat-tab>
<mat-tab aria-label="Message">
<ng-template mat-tab-label>
<div class="icon-item">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#000000"
stroke-width="1.5"
stroke-linecap="butt"
stroke-linejoin="bevel"
>
<polygon
points="21.368 12.001 3 21.609 3 14 11 12 3 9.794 3 2.394"
/>
</svg>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>
<div class="menu-container">
<div class="contents">
<mat-drawer-container class="example-container" style="height: 600px;">
<mat-drawer #rightSideDrawer mode="side" opened style="width: 400px;">
<mat-toolbar>
<div>Organization</div>
<div></div>
</mat-toolbar>
<div class="list-section">
<div class="title-section">
<div class="title">
<h3>조직도</h3>
<div class="btn-box">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
aria-label="group menu"
>
<mat-icon>more_vert</mat-icon>
</button>
</div>
</div>
</div>
<div class="list"><app-tree></app-tree></div>
</div>
</mat-drawer>
<!-- (click)="onClickToggle($event)" -->
<mat-drawer-content style="width: 500px;">
<mat-toolbar>
<div><span>Today</span>2020.05.05</div>
<div>
<div class="app-layout-native-title-bar-actions">
<button
mat-icon-button
class="button app-layout-native-title-bar-minimize"
>
<!--<mat-icon>minimize</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="5" y1="18" x2="19" y2="18"></line>
</svg>
</button>
<button
mat-icon-button
class="button app-layout-native-title-bar-maximize"
>
<ng-container [ngSwitch]="'Maximized'">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
*ngSwitchCase="'Maximized'"
>
<path
class="st0"
d="M15,9.5v7c0,0.8-0.7,1.5-1.5,1.5h-7C5.7,18,5,17.3,5,16.5v-7C5,8.7,5.7,8,6.5,8h7C14.3,8,15,8.7,15,9.5z"
/>
<path
class="st0"
d="M8.8,6.8V6c0-0.8,0.7-1.5,1.5-1.5H17c0.8,0,1.5,0.7,1.5,1.5v6.8c0,0.8-0.7,1.5-1.5,1.5h-0.8"
/>
</svg>
<!--<mat-icon *ngSwitchDefault>crop_din</mat-icon>-->
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
*ngSwitchDefault
>
<rect
x="5"
y="5"
width="12"
height="12"
rx="2"
ry="2"
></rect>
</svg>
</ng-container>
</button>
<button
mat-icon-button
class="button app-layout-native-title-bar-close"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
</mat-toolbar>
<div class="contents-main">
<!-- search start-->
<div fxLayout="column" fxFlex="1 1 auto" class="ingegrated-search">
<div class="org-search-container">
<mat-form-field class="example-full-width" appearance="none">
<mat-select placeholder="전체">
<mat-option
*ngFor="let searchItem of searchItemList"
[value]="searchItem"
>
{{ searchItem.itemName }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="검색어를 입력하세요." />
<button
mat-icon-button
*ngIf="true"
class="icon-button font-accent-color"
aria-label="Clear"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="butt"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
<!-- <mat-icon>close</mat-icon> -->
<!-- <i class="mdi mdi-close">X</i> -->
</button>
</mat-form-field>
</div>
</div>
<!-- search end-->
<!-- content table start-->
<app-organization-detail-table></app-organization-detail-table>
<!-- content table end-->
contents area
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>
<div class="footer">
<div>
<span>현재버전 0.0.11</span>
<span>최신버전 0.0.11</span>
</div>
<div style="right: 0;">
HelpDesk 1661-9066
</div>
</div>
</div>
</div>
<mat-menu #menu="matMenu">
<button mat-menu-item>Item 1</button>
<button mat-menu-item>Item 2</button>
</mat-menu>

View File

@ -0,0 +1,110 @@
.container {
.gnb,
.menu-container {
float: left;
.list-section {
height: 100%;
}
}
// .contents {
// .list-section,
// .contents-main {
// float: left;
// }
// }
.footer {
clear: both;
}
}
.gnb {
.left-container {
display: flex;
width: 100%;
height: 100%;
.global-menu {
width: 100%;
}
}
::ng-deep .global-menu {
display: flex;
flex-direction: row;
.mat-tab-header {
border-bottom: none !important;
width: 100%;
}
.mat-tab-label-container {
.mat-tab-list {
.mat-tab-labels {
flex-flow: column;
height: 360px;
padding-top: 10px;
border-bottom: none;
.mat-tab-label {
width: 100%;
height: 80px;
padding: 0;
min-width: 0 !important;
.mat-tab-label-content {
.icon-item {
display: inline-flex;
width: 36px;
height: 36px;
border-radius: 50%;
justify-content: center;
align-items: center;
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.4, 0, 0, 1);
svg {
width: 24px;
height: 24px;
stroke: #ffffff;
stroke-width: 1.5;
stroke-linecap: square;
stroke-linejoin: miter;
fill: none;
}
.mat-badge-content {
right: -4px !important;
}
}
}
&.mat-tab-label-active {
opacity: 0;
}
&[aria-selected='true'] {
opacity: 1;
.mat-tab-label-content {
.icon-item {
transform: scale(1);
}
}
}
}
}
.mat-ink-bar {
opacity: 0;
}
}
}
.mat-tab-body-wrapper {
.mat-tab-body {
height: 100%;
width: 100%;
}
}
}
}
.footer {
div {
display: inline-block;
}
}

View File

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

View File

@ -0,0 +1,36 @@
import { action } from '@storybook/addon-actions';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { ReactiveFormsModule } from '@angular/forms';
import { moduleMetadata } from '@storybook/angular';
import { OrganizationComponent } from './organization.component';
import { MaterialModule } from 'src/app/material.module';
import { TreeComponent } from './component/tree.component';
import { DetailTableComponent } from './component/detail-table.component';
import { UserItemComponent } from '../../pages/group/component/user-item.component';
export default {
title: 'OrganizationComponent',
component: OrganizationComponent,
decorators: [
moduleMetadata({
imports: [
CommonModule,
BrowserModule,
ReactiveFormsModule,
ScrollingModule,
BrowserAnimationsModule,
MaterialModule
],
providers: [],
declarations: [TreeComponent, DetailTableComponent, UserItemComponent]
})
]
};
export const Page = () => ({
component: OrganizationComponent
});

Some files were not shown because too many files have changed in this diff Show More