Commit 9cf643ab by Nattana Chaiyamat

Merge branch 'UAT' of https://mygit.myhr.co.th/angular/mySkill-x into UAT

# Conflicts:
#	src/app/components/dashboard/dashboard-routing.module.ts
#	src/app/components/dashboard/dashboard.module.ts
#	src/app/shared/services/navservice.ts
parents 157df7d3 3386e717
......@@ -47,6 +47,7 @@
"browser-sync": "^2.29.3",
"chart.js": "^4.3.0",
"date-fns": "^2.30.0",
"echarts": "^5.6.0",
"firebase": "^8.10.1",
"glob-watcher": "^6.0.0",
"hammerjs": "^2.0.8",
......@@ -67,7 +68,7 @@
"ngx-daterangepicker-material": "^6.0.4",
"ngx-drag-drop": "^16.0.0",
"ngx-dropzone": "^3.1.0",
"ngx-echarts": "^16.0.0",
"ngx-echarts": "^16.2.0",
"ngx-editor": "^16.0.1",
"ngx-mat-timepicker": "^16.0.2",
"ngx-owl-carousel-o": "^16.0.0",
......@@ -10018,20 +10019,19 @@
}
},
"node_modules/echarts": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz",
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
"peer": true,
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.4.4"
"zrender": "5.6.1"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"peer": true
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
},
"node_modules/ee-first": {
"version": "1.1.1",
......@@ -14569,9 +14569,10 @@
}
},
"node_modules/ngx-echarts": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-16.0.0.tgz",
"integrity": "sha512-hdM7/CL29bY3sF3V5ihb7H1NeUsQlhijp8tVxT23+vkNTf9SJrUHPjs9oHOMkbTlr2Q8HB+eVpckYAL/tuK0CQ==",
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-16.2.0.tgz",
"integrity": "sha512-yhuDbp6qdkmR4kRVLS06Z0Iumod7xOj5n/Z++clRiKM24OQ4sM8WuOTicdfWy6eeYDNywdGSrri4Y5SUGRD8bg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
......@@ -19196,10 +19197,10 @@
}
},
"node_modules/zrender": {
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz",
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
"peer": true,
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
......@@ -19208,7 +19209,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"peer": true
"license": "0BSD"
}
}
}
......@@ -33,6 +33,16 @@
"@ng-select/ng-select": "^11.0.0",
"@ngrx/store": "^16.0.1",
"@swimlane/ngx-charts": "^20.4.1",
"@syncfusion/ej2-angular-base": "26.2.10",
"@syncfusion/ej2-angular-dropdowns": "26.2.13",
"@syncfusion/ej2-angular-grids": "26.2.14",
"@syncfusion/ej2-angular-inputs": "26.2.14",
"@syncfusion/ej2-base": "26.2.10",
"@syncfusion/ej2-buttons": "26.2.10",
"@syncfusion/ej2-data": "26.2.14",
"@syncfusion/ej2-dropdowns": "26.2.13",
"@syncfusion/ej2-grids": "26.2.14",
"@syncfusion/ej2-inputs": "26.2.14",
"@tailwindcss/forms": "^0.5.3",
"angular-calendar": "^0.31.0",
"angular2-multiselect-dropdown": "^5.0.4",
......@@ -42,6 +52,7 @@
"browser-sync": "^2.29.3",
"chart.js": "^4.3.0",
"date-fns": "^2.30.0",
"echarts": "^5.6.0",
"firebase": "^8.10.1",
"glob-watcher": "^6.0.0",
"hammerjs": "^2.0.8",
......@@ -62,7 +73,7 @@
"ngx-daterangepicker-material": "^6.0.4",
"ngx-drag-drop": "^16.0.0",
"ngx-dropzone": "^3.1.0",
"ngx-echarts": "^16.0.0",
"ngx-echarts": "^16.2.0",
"ngx-editor": "^16.0.1",
"ngx-mat-timepicker": "^16.0.2",
"ngx-owl-carousel-o": "^16.0.0",
......@@ -78,17 +89,7 @@
"swiper": "^8.4.6",
"tslib": "^2.5.3",
"wnumb": "^1.2.0",
"zone.js": "~0.13.1",
"@syncfusion/ej2-angular-base": "26.2.10",
"@syncfusion/ej2-angular-dropdowns": "26.2.13",
"@syncfusion/ej2-angular-grids": "26.2.14",
"@syncfusion/ej2-angular-inputs": "26.2.14",
"@syncfusion/ej2-base": "26.2.10",
"@syncfusion/ej2-buttons": "26.2.10",
"@syncfusion/ej2-data": "26.2.14",
"@syncfusion/ej2-dropdowns": "26.2.13",
"@syncfusion/ej2-grids": "26.2.14",
"@syncfusion/ej2-inputs": "26.2.14"
"zone.js": "~0.13.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^16.1.1",
......
......@@ -45,6 +45,7 @@ import { RolePermissionConfigComponent } from '../company-components/account-set
import { PmsFormEmployeeComponent } from '../performance-evaluation/pms-form-employee/pms-form-employee.component';
import { EmployeeSelfServiceComponent } from '../employee-self-service/employee-self-service.component';
import { MySkillXModuleComponent } from '../my-skill-x-module/my-skill-x-module.component';
import { DashboardEvaluationComponent } from './projects/dashboard-evaluation.component';
......@@ -61,9 +62,11 @@ const routes: Routes = [
{ path: "dashboard/hrm", component: HrmComponent },
{ path: "dashboard/personal", component: PersonalComponent },
{ path: "dashboard/nft", component: NftComponent },
{ path: "dashboard/projects", component: ProjectsComponent },
// { path: "dashboard/projects", component: ProjectsComponent },
{ path: "dashboard/stocks", component: StocksComponent },
{ path: "dashboard/course", component: CourseComponent },
{ path: "dashboard/projects", component: DashboardEvaluationComponent },
// myComponent
{ path: "company-registration", title: 'ทะเบียนบริษัท', component: CompanyRegistrationComponent },
......
......@@ -178,6 +178,8 @@ import { PmsSummaryComponent } from '../performance-evaluation/pms-form-employee
import { BarRatingModule } from 'ngx-bar-rating';
import { EmployeeSelfServiceComponent } from '../employee-self-service/employee-self-service.component';
import { MySkillXModuleComponent } from '../my-skill-x-module/my-skill-x-module.component';
import { DashboardEvaluationComponent } from './projects/dashboard-evaluation.component';
import { NgxEchartsModule } from 'ngx-echarts';
export const MY_DATE_FORMATS = {
parse: {
......@@ -313,7 +315,8 @@ export class CustomDateAdapter extends NativeDateAdapter {
PmsIdpComponent,
PmsSummaryComponent,
EmployeeSelfServiceComponent,
MySkillXModuleComponent
MySkillXModuleComponent,
DashboardEvaluationComponent,
],
imports: [
CommonModule,
......@@ -337,7 +340,10 @@ export class CustomDateAdapter extends NativeDateAdapter {
DropDownListModule,
RatingModule,
MatDialogModule,
BarRatingModule
BarRatingModule,
NgxEchartsModule.forRoot({ // ✅ โหลด ECharts เพียงครั้งเดียว
echarts: () => import('echarts')
})
],
providers: [
Bu1Service,
......
<app-page-header
[title]="'Dashboard Appraisal'"
activeitem="Home">
</app-page-header>
<div class="container mx-auto px-4 py-8 space-y-8">
<!-- ░░ Row #1 – KPI Cards ░░ -->
<div class="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
<ng-container *ngFor="let card of kpiCards; trackBy: trackByLabel">
<div
class="bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6
flex items-center gap-4">
<!-- icon -->
<div class="w-14 h-14 flex items-center justify-center rounded-full" [ngClass]="card.color">
<i class="text-sky-700 text-xl" [ngClass]="card.icon"></i>
</div>
<!-- value -->
<div class="flex-1 text-right">
<p class="text-sm text-gray-500 whitespace-nowrap">{{ card.label }}</p>
<p class="text-2xl font-bold text-gray-800">{{ card.value | number }}</p>
<span class="text-xs font-semibold"
[ngClass]="card.change >= 0 ? 'text-green-600' : 'text-red-600'">
{{ card.change | number:'1.0-2' }}%
</span>
</div>
</div>
</ng-container>
</div>
<!-- ░░ Row #2 – Bar per Department ░░ -->
<div
class="bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6">
<h2 class="font-semibold text-slate-600 mb-4">
{{ 'dashboard.deptChart' }}
</h2>
<div echarts class="h-[350px] w-full" [options]="deptScoreOption" [autoResize]="true"></div>
</div>
<!-- ░░ Row #3 – Mini-Charts (3 cols on md+) ░░ -->
<div class="grid gap-6 md:grid-cols-3">
<!-- Donut -->
<div
class="bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6 flex flex-col items-center">
<div echarts class="h-[220px] w-full" [options]="summaryDonutOption" [autoResize]="true"></div>
<p class="mt-3 text-sm text-gray-500">
{{ 'dashboard.overallSummary' }}
</p>
</div>
<!-- Year compare -->
<div
class="bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">
{{ 'dashboard.yearCompare' }}
</h3>
<div echarts class="h-[220px] w-full" [options]="yearlyCompareOption" [autoResize]="true"></div>
</div>
<!-- Level distribution -->
<div
class="bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">
{{ 'dashboard.levelDist' }}
</h3>
<div echarts class="h-[220px] w-full" [options]="levelDistOption" [autoResize]="true"></div>
</div>
</div>
<!-- ░░ Row #4 – Tables (stack → 2-cols on lg) ░░ -->
<div class="grid gap-6 lg:grid-cols-2">
<!-- pending table -->
<div
class="bg-white rounded-2xl shadow overflow-hidden transition
motion-safe:hover:-translate-y-1 hover:shadow-xl">
<h2 class="px-6 py-3 bg-slate-50 border-b font-semibold text-slate-600">
{{ 'dashboard.todoList' }}
</h2>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="bg-slate-50 text-slate-500">
<tr>
<th class="px-6 py-3 text-left">ผู้ถูกประเมิน</th>
<th class="px-6 py-3 text-left">ผู้ประเมิน</th>
<th class="px-6 py-3 text-left">รอบ</th>
<th class="px-6 py-3 text-left">ฝ่าย</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of pendingList"
class="odd:bg-white even:bg-slate-50 hover:bg-sky-50">
<td class="px-6 py-3">{{ row.employee }}</td>
<td class="px-6 py-3">{{ row.evaluator }}</td>
<td class="px-6 py-3">{{ row.round }}</td>
<td class="px-6 py-3">{{ row.department }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- top-score table -->
<div
class="bg-white rounded-2xl shadow overflow-hidden transition
motion-safe:hover:-translate-y-1 hover:shadow-xl">
<h2 class="px-6 py-3 bg-slate-50 border-b font-semibold text-slate-600">
{{ 'dashboard.topScore' }}
</h2>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="bg-slate-50 text-slate-500">
<tr>
<th class="px-6 py-3 text-left">ผู้ถูกประเมิน</th>
<th class="px-6 py-3 text-left">คะแนน</th>
<th class="px-6 py-3 text-left">เกรด</th>
<th class="px-6 py-3 text-left">รอบ</th>
<th class="px-6 py-3 text-left">ผู้ประเมิน</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of topScoreList"
class="odd:bg-white even:bg-slate-50 hover:bg-sky-50">
<td class="px-6 py-3">{{ row.employee }}</td>
<td class="px-6 py-3 font-semibold text-slate-700">{{ row.score }}</td>
<td class="px-6 py-3">
<span class="px-2 py-0.5 rounded text-white"
[ngClass]="{
'bg-emerald-600': row.grade === 'S',
'bg-sky-600' : row.grade === 'A',
'bg-amber-600' : row.grade === 'B'
}">
{{ row.grade }}
</span>
</td>
<td class="px-6 py-3">{{ row.round }}</td>
<td class="px-6 py-3">{{ row.evaluator }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { EChartsOption } from 'echarts';
interface KpiCard {
label: string;
value: number;
change: number;
icon: string;
color: string; // ใช้ไฮไลต์พื้นหลัง
}
/* ---------- ข้อมูลตารางจำลอง ---------- */
interface PendingEval {
employee: string;
evaluator: string;
round: string;
department: string;
}
interface TopScore {
employee: string;
score: number;
grade: string;
round: string;
evaluator: string;
}
@Component({
selector: 'app-dashboard-evaluation',
templateUrl: './dashboard-evaluation.component.html',
styleUrls: ['./dashboard-evaluation.component.scss']
})
export class DashboardEvaluationComponent implements OnInit {
/* ========== KPI Cards (mock) ========== */
kpiCards: KpiCard[] = [
{ label: 'ประเมินเสร็จสิ้น', value: 1_293, change: 1.35, icon: 'heroicons-outline:check-circle', color: 'bg-emerald-100' },
{ label: 'กำลังประเมิน', value: 853, change: 0.82, icon: 'heroicons-outline:pencil-square', color: 'bg-amber-100' },
{ label: 'ยังไม่ประเมิน', value: 625, change: -2.11, icon: 'heroicons-outline:clipboard', color: 'bg-rose-100' },
{ label: 'ผลประเมินต่ำสุด', value: 4, change: 2.78, icon: 'heroicons-outline:trending-down', color: 'bg-sky-100' },
{ label: 'ผลประเมินสูงสุด', value: 12, change: 12.05, icon: 'heroicons-outline:trending-up', color: 'bg-purple-100' }
];
/* ========== Chart Options (mock) ========== */
deptScoreOption!: EChartsOption;
summaryDonutOption!: EChartsOption;
yearlyCompareOption!: EChartsOption;
levelDistOption!: EChartsOption;
/* ========== Table Data (mock) ========== */
pendingList: PendingEval[] = [
{ employee: 'น.ส. บุญใจ บุญโต', evaluator: 'นาย สุริชัย จินโต', round: '2024/2', department: 'ฝ่ายผลิต' },
{ employee: 'นาย อาทิตย์ รุ่งเรือง', evaluator: 'นาง ภัทธิดา วงศ์ดาว', round: '2024/2', department: 'การตลาด' },
{ employee: 'น.ส. เยาวเรศ พิทักษ์', evaluator: 'นาย พลชัย อธิชาติ', round: '2024/2', department: 'ทรัพยากรบุคคล' }
];
topScoreList: TopScore[] = [
{ employee: 'นาย ชัยยศ สุวรรณ', score: 99, grade: 'S', round: '1/2025', evaluator: 'นาย สุริชัย จินโต' },
{ employee: 'น.ส. อรอนงค์ พรรณา', score: 98, grade: 'S', round: '1/2025', evaluator: 'นาง ภัทธิดา วงศ์ดาว' },
{ employee: 'น.ส. บุญใจ บุญโต', score: 98, grade: 'S', round: '1/2025', evaluator: 'นาย สุริชัย จินโต' },
{ employee: 'นาย อาทิตย์ รุ่งเรือง', score: 97, grade: 'A', round: '1/2025', evaluator: 'นาง ภัทธิดา วงศ์ดาว' },
{ employee: 'น.ส. เยาวเรศ พิทักษ์', score: 96, grade: 'A', round: '1/2025', evaluator: 'นาย พลชัย อธิชาติ' }
];
ngOnInit(): void {
this.initCharts();
}
/* ---------- กำหนดกราฟ ---------- */
private initCharts(): void {
/* 1. Bar: คะแนนตามแผนก */
this.deptScoreOption = {
toolbox: { show: true, feature: { saveAsImage: { title: 'PNG' } } },
tooltip: { trigger: 'axis' },
grid: { top: 20, left: 90, right: 20, bottom: 40 },
xAxis: {
type: 'category',
data: [
'ทรัพยากรบุคคล', 'การเงิน/บัญชี', 'การตลาด',
'ฝ่ายขาย', 'ไอที', 'ฝ่ายผลิต', 'ลูกค้าสัมพันธ์'
],
axisLabel: { rotate: 25, color: '#6B7280' }
},
yAxis: { type: 'value', name: 'คะแนน', nameTextStyle: { fontWeight: 600 } },
series: [{
name: 'คะแนนเฉลี่ย',
type: 'bar',
data: [88, 91, 79, 81, 83, 80, 93],
barWidth: '55%',
itemStyle: { color: '#3b82f6', borderRadius: [6, 6, 0, 0] },
label: { show: true, position: 'top' }
}]
};
this.deptScoreOption = {
toolbox: { show: true, feature: { saveAsImage: { title: 'PNG' } } },
tooltip: { trigger: 'axis' },
grid: { top: 20, left: 90, right: 20, bottom: 40 },
xAxis: {
type: 'category',
data: [
'ทรัพยากรบุคคล', 'การเงิน/บัญชี', 'การตลาด',
'ฝ่ายขาย', 'ไอที', 'ฝ่ายผลิต', 'ลูกค้าสัมพันธ์'
],
axisLabel: { rotate: 25, color: '#6B7280' }
},
yAxis: { type: 'value', nameTextStyle: { fontWeight: 600 } },
series: [{
name: 'คะแนนเฉลี่ย',
type: 'bar',
barWidth: '55%',
label: { show: true, position: 'top' },
data: [
{
value: 88,
itemStyle: {
color: '#00CFE8', // สีของ "ทรัพยากรบุคคล"
borderRadius: [6, 6, 0, 0],
},
},
{
value: 91,
itemStyle: {
color: '#00D2FF', // สีของ "การเงิน/บัญชี"
borderRadius: [6, 6, 0, 0],
},
},
{
value: 79,
itemStyle: {
color: '#5A9CFF', // การตลาด
borderRadius: [6, 6, 0, 0],
},
},
{
value: 81,
itemStyle: {
color: '#7B7BFF', // ฝ่ายขาย
borderRadius: [6, 6, 0, 0],
},
},
{
value: 83,
itemStyle: {
color: '#A05BDB', // ไอที
borderRadius: [6, 6, 0, 0],
},
},
{
value: 80,
itemStyle: {
color: '#C218A4', // ฝ่ายผลิต
borderRadius: [6, 6, 0, 0],
},
},
{
value: 93,
itemStyle: {
color: '#D4004D', // ลูกค้าสัมพันธ์
borderRadius: [6, 6, 0, 0],
},
},
]
}]
};
/* 2. Donut: ผ่าน/ไม่ผ่าน */
this.summaryDonutOption = {
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { bottom: 0 },
series: [{
type: 'pie',
radius: ['55%', '80%'],
data: [
{ value: 87, name: 'ผ่าน', itemStyle: { color: '#10b981' } },
{ value: 13, name: 'ไม่ผ่าน', itemStyle: { color: '#ef4444' } }
],
label: { show: false },
labelLine: { show: false }
}]
};
/* 3. Column: เปรียบเทียบปี */
this.yearlyCompareOption = {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { top: 0 },
grid: { top: 40, left: 20, right: 20, bottom: 20 },
xAxis: { type: 'category', data: ['คะแนนรวม'] },
yAxis: { type: 'value' },
series: [
{ name: '2024', type: 'bar', data: [79], itemStyle: { color: '#9af0f5', borderRadius: 4 } },
{ name: '2025', type: 'bar', data: [88], itemStyle: { color: '#34bdeb', borderRadius: 4 } }
]
};
/* 4. Stacked Horizontal Bar: การกระจายเกรด */
this.levelDistOption = {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { top: 0 },
grid: { top: 40, left: 10, right: 10, bottom: 10 },
xAxis: { type: 'value' },
yAxis: { type: 'category', data: [''] },
series: [
{ name: 'S', type: 'bar', stack: 'total', data: [50], itemStyle: { color: '#2B547E' } },
{ name: 'A', type: 'bar', stack: 'total', data: [40], itemStyle: { color: '#9af0f5' } },
{ name: 'B', type: 'bar', stack: 'total', data: [30], itemStyle: { color: '#34bdeb' } },
{ name: 'C', type: 'bar', stack: 'total', data: [10], itemStyle: { color: '#3090C7' } },
{ name: 'D', type: 'bar', stack: 'total', data: [5], itemStyle: { color: '#659EC7' } }
]
};
}
trackByLabel = (_: number, i: KpiCard) => i.label;
}
......@@ -89,6 +89,16 @@ export class NavService implements OnDestroy {
}
private MENUITEMS: Menu[] = [
{
title: 'Dashboard Appraisal',
type: 'link',
selected: false,
active: false,
path: '/dashboard/projects',
id: 'm0',
show: true,
icon: 'assets/img/icons-menu/house_3781618.png',
},
// {
// title: 'ประเมินผล',
// type: 'sub',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment