Commit 2abf8a01 by Nattana Chaiyamat

ติดตามหลังการประเมิน

parent 1e5e6bae
......@@ -66,6 +66,7 @@ import { JobFamilyMatrixComponent } from '../job-family-matrix/job-family-matrix
import { DocumentUploadManagerComponent } from '../company-components/account-settings/document-upload-manager/document-upload-manager.component';
import { DisciplinaryActionComponent } from '../disciplinary-action/disciplinary-action.component';
import { OutstandingPerformanceComponent } from '../outstanding-performance/outstanding-performance.component';
import { PostEvaluationTrackingComponent } from '../post-evaluation-tracking/post-evaluation-tracking.component';
......@@ -147,6 +148,7 @@ const routes: Routes = [
{ path: "ess/job-family-matrix", title: 'รายละเอียดกลุ่มงานตามวิชาชีพ', component: JobFamilyMatrixComponent },
{ path: "ess/disciplinary-action", title: 'วินัยและการลงโทษ', component: DisciplinaryActionComponent },
{ path: "ess/outstanding-performance", title: 'ผลงานดีเด่น', component: OutstandingPerformanceComponent },
{ path: "ess/post-evaluation-tracking", title: 'ผลงานดีเด่น', component: PostEvaluationTrackingComponent },
]
}
];
......
......@@ -219,6 +219,8 @@ import { JobFamilyMatrixComponent } from '../job-family-matrix/job-family-matrix
import { DocumentUploadManagerComponent } from '../company-components/account-settings/document-upload-manager/document-upload-manager.component';
import { DisciplinaryActionComponent } from '../disciplinary-action/disciplinary-action.component';
import { OutstandingPerformanceComponent } from '../outstanding-performance/outstanding-performance.component';
import { PostEvaluationTrackingComponent } from '../post-evaluation-tracking/post-evaluation-tracking.component';
import { EvaluationConfirmationComponent } from '../post-evaluation-tracking/evaluation-confirmation/evaluation-confirmation.component';
export const MY_DATE_FORMATS = {
parse: {
......@@ -377,7 +379,9 @@ export class CustomDateAdapter extends NativeDateAdapter {
JobFamilyMatrixComponent,
DocumentUploadManagerComponent,
DisciplinaryActionComponent,
OutstandingPerformanceComponent
OutstandingPerformanceComponent,
PostEvaluationTrackingComponent,
EvaluationConfirmationComponent
], imports: [
TranslateModule,
CommonModule,
......
......@@ -93,7 +93,7 @@ export class DisciplinaryActionComponent {
}
getEmployeeList() {
this.employee.loading = true
this.employeeService.getList().subscribe({
this.employeeService.getSubordinatesList().subscribe({
next: response => {
this.employee.list = response.map((x: any) => new MyEmployeeModel(x))
this.employee.loading = false
......
......@@ -93,7 +93,7 @@ export class OutstandingPerformanceComponent {
}
getEmployeeList() {
this.employee.loading = true
this.employeeService.getList().subscribe({
this.employeeService.getSubordinatesList().subscribe({
next: response => {
this.employee.list = response.map((x: any) => new MyEmployeeModel(x))
this.employee.loading = false
......
......@@ -204,7 +204,7 @@
</div>
</div>
<ng-container *ngIf="pageEvalution!=''">
<app-pms-form-employee [evaluationForm]="'sup'" [currentTap]="'ข้อมูลการประเมิน'"
<app-pms-form-employee [evaluationForm]="'sup'" [currentTap]="'EvaluationInfo'"
(sendReturnPath)="pageEvalution='';getBossList() ; pathTitle = ['การประเมินผล', 'ประเมินโดยหัวหน้า']"
[evaluaterId]="formEvaluation.evaluaterId" [evaluateeId]="formEvaluation.evaluateeId"></app-pms-form-employee>
</ng-container>
......
<ng-container *ngIf="page==1">
<div class="flex item-center w-full font-size-18px font-weight-700 text-primary px-5 mt-1 mb-4"
style="height: 50px;align-items: center;border-width: 1px;background: #eff6fe; border-radius:20px">
รายละเอียดการประเมิน
</div>
<div class="flex w-full mb-4">
<div class="flex w-1/4 justify-between">
</div>
<div class="flex w-3/4 justify-end">
<div class="px-1">
<div class="relative shadow-md">
<input type="text" class="ti-form-input ltr:pl-11 rtl:pr-11 focus:z-10 "
[placeholder]="'SearchByNoOrName' | translate" [(ngModel)]="search"
(ngModelChange)="setSyncfutionDataList()">
<div
class="absolute inset-y-0 ltr:left-0 rtl:right-0 flex items-center pointer-events-none z-20 ltr:pl-4 rtl:pr-4">
<i class="ri-search-line text-gray"></i>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-col w-100">
<div class="mb-4">
<ng-container *ngTemplateOutlet="Datagrid"></ng-container>
</div>
</div>
</ng-container>
<ng-container *ngIf="page==2">
<app-pms-form-employee [evaluationForm]="'sup'" [currentTap]="'EvaluationInfo'" (sendReturnPath)="page=1"
[evaluaterId]="evaluaterId" [evaluateeId]="evaluaterId"></app-pms-form-employee>
</ng-container>
<ng-template #Datagrid>
<ejs-grid #grid id='Grid' [locale]="locale" [dataSource]="syncfution.dataList" [allowFiltering]="true"
[filterSettings]="filterSettings" [selectionSettings]="selectionOptions"
[searchSettings]="syncfution.searchSettings" [groupSettings]="groupSettings" [toolbar]="toolbarOptions"
[editSettings]="editSettings" [loadingIndicator]='loadingIndicator' [query]="query"
[columnMenuItems]="columnMenuItems" [pageSettings]="initialPage" [allowMultiSorting]="true" [allowPaging]="true"
[allowGrouping]="true" [allowSorting]="true" [showColumnMenu]="true" [allowPdfExport]="true"
[allowExcelExport]="true" [allowReordering]="true" width="auto" rowHeight="60" allowEditing="false"
(columnMenuClick)="onColumnMenuClick($event)" (toolbarClick)='toolbarClick($event)'>
<e-columns>
<ng-container *ngFor="let col of syncfution.columns">
<ng-container *ngIf="col.headerText">
<e-column [field]="col.field" [headerText]="col.headerText" [width]="col.width" [format]="col.format"
[isPrimaryKey]="col.isPrimaryKey" [validationRules]="col.validationRules" [visible]="col.visible"
[editType]="false" [allowEditing]="false" [allowFiltering]="true" [allowSorting]="true" [type]="col.type"
textAlign="center">
<ng-template #headerTemplate let-data>
<span class="font-size-12px font-weight-700 text-primary">
{{ col.headerText | translate}}
</span>
</ng-template>
<!-- <ng-template #template let-data *ngIf="col.headerText=='EmployeeCode'">
<div class="flex-col gap-2">
<img class=" avatar shadow-none rounded-full !ring-transparent object-cover h-12 w-12"
[src]="data.picture?getImg(data.picture):'./assets/img/users/defaultperson.jpg'"
(error)="onImageError($event)">
{{data.employeeId}}
</div>
</ng-template>
<ng-template #template let-data *ngIf="col.headerText=='Status'">
<div class="flex justify-center">
<button type="button" class="ti-btn rounded-sm " [class]="statusButtonClass(data.statusEdesc)"
style="height: 30px; width: auto; font-size: 12px; display: flex; align-items: center; justify-content: center;margin-left:4px;"
(click)="selectSubordinate(data);">
{{data.status}}
</button>
</div>
</ng-template> -->
</e-column>
</ng-container>
</ng-container>
<e-column headerText='action' textAlign='Center'>
<ng-template #headerTemplate let-data>
<span class="font-size-12px font-weight-700 text-primary">{{'Action' | translate}}</span>
</ng-template>
<ng-template #template let-data>
<i class="ti ti-eye cursor-pointer i-gray fs-l px-1" (click)="page=2"></i>
<i class="ti ti-file cursor-pointer i-gray fs-l px-1" (click)="openEmployeeDialog()"></i>
</ng-template>
</e-column>
</e-columns>
<e-aggregates>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesSum" [field]="col.field" [type]="'Sum'"
[footerTemplate]="'Sum: ${Sum}'" [groupFooterTemplate]="'Sum: ${Sum}'"
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesCount" [field]="col.field" [type]="'Count'"
[footerTemplate]="'Count: ${Count}'" [groupFooterTemplate]="'Count: ${Count}'"
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesAvg" [field]="col.field" [type]="'Average'"
[footerTemplate]="'Average: ${Average}'" [groupFooterTemplate]="'Average: ${Average}'"
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesMin" [field]="col.field" [type]="'Min'"
[footerTemplate]="'Min: ${Min}'" [groupFooterTemplate]="'Min: ${Min}'"
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesMax" [field]="col.field" [type]="'Max'"
[footerTemplate]="'Max: ${Max}'" [groupFooterTemplate]="'Max: ${Max}'"
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid>
</ng-template>
<ng-template #employeeDialog let-modal>
<h3 mat-dialog-title>
<!-- {{'รายงานสรุปผลการประเมิน' | translate}} -->
<div class="relative">
<div class="absolute" style="top: -43px;">
{{'รายงานสรุปผลการประเมิน' | translate}}
</div>
<div class="absolute" style="right: 0;top: -43px;">
<button type="button" class="hs-dropdown-toggle ti-modal-clode-btn text-danger" mat-button [mat-dialog-close]>
<span class="sr-only">Close</span>
<i class="ti ti-circle-x fs-xxl"></i>
</button>
</div>
</div>
</h3>
<div class="w-full flex justify-end mb-1rem">
<div class="absolute flex" style="z-index: 9;">
<div class="px-1">
<button type="button" class="ti-btn ti-btn-soft-info h-45px m-0 shadow-md" (click)="exportPdf()">
<span class="e-btn-icon e-print e-icons e-icon-left"></span>
Print
</button>
</div>
</div>
</div>
<mat-dialog-content>
<div #pdfArea class="row w-full pdf-container" style="justify-content: center;align-content: start;">
<div class="row" style="height: 1123px; width: 794px;justify-content: center;align-content: space-between;">
<div class="row col-12">
<div class="row col-12 pb-3"
style="background-color: hsl(214 58% 86% / 1);justify-content: center;align-content: start;">
<div class="col-12 flex p-3 pt-0 " style="justify-content: end;font-size:small;font-weight: bold;">
Performance Management System
</div>
<div class="col-12 flex pb-3" style="justify-content: center;font-size:x-large;font-weight: bold;">
Performance Management System ประจําปี 2568
</div>
<div class="col-12 flex" style="justify-content: center;font-size:larger;font-weight: bold;">
รหัส 60177 ชื่อพนักงาน น.ส.แววตา อาสา
</div>
<div class="col-12 flex" style="justify-content: center;font-size:larger;font-weight: bold;">
สังกัด/ฝ่าย/หน่วยงาน Administration
</div>
</div>
<div class="row col-12">
<div class="col-12 pb-3 pt-3" style="font-size:larger;font-weight: bold;">
ส่วนที่ 1: ประวัติพนักงาน
</div>
<div class="col-12 border row p-1">
<div class="col-6 row">
<div class="col-12" style="align-content: center">
ตำแหน่งงาน : Payroll Officer
</div>
<div class="col-12" style="align-content: center">
ระดับ : S1
</div>
</div>
<div class="col-6 row" style="justify-content: end;">
<img class="border" [src]="'./assets/img/users/defaultperson.jpg'">
</div>
</div>
</div>
<div class="row col-12">
<div class="col-12 pb-3 pt-3" style="font-size:larger;font-weight: bold;">
ส่วนที่ 2: ผลดําาเนินงานประจําปี 2568
</div>
<div class="col-12 border row p-1">
<div class="row col-12" style="background-color: hsl(180.47deg 100% 25.29%);color: white;">
<div class="col-9 row">
<div class="col-4 pl-1 pr-1 pb-3 pt-2">
การประเมินผล (Evaluation Factor)
</div>
<div class="col-2 pl-1 pr-1 pb-3 pt-2">
คะแนนดิบคิดเป็นร้อยละ
</div>
<div class="col-2 pl-1 pr-1 pb-3 pt-2">
ร้อยละของปัจจัย
</div>
<div class="col-2 pl-1 pr-1 pb-3 pt-2">
คะแนนที่ได้คิดเป็นร้อยละ
</div>
<div class="col-2 pl-1 pr-1 pb-3 pt-2">
คะแนนสุทธิ
</div>
</div>
<div class="col-3"></div>
</div>
<div class="row col-12">
<div class="col-9 row">
<ng-container *ngFor="let item of ['Part 1 : ประเมินผลการปฏิบัติงานตามนโยบายบริษัท',
'Part 2 : ประเมินผลการปฏิบัติงานประจํา (Job based KPIs)',
'Part 3 : ประเมินผลสมรรถนะที่สนับสนุนการปฏิบัติงาน',
'Part 4 : จํานวนการเข้างาน( Time Attendance)',
'Part 5: จานที่ได้รับมอบหมายเพิ่มเติม (Cross Functional Project Assignment)',
'Part 6: กิจกรรมพิเศษ (Special Activities)'],let i = index;let o = odd">
<div class="col-12 row" [ngStyle]="{'background-color':o?'hsl(0deg 0% 90.2%)':'white'}">
<div class="col-4 pl-1 pr-1 pb-3 pt-2">
{{item}}
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
{{i}}
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
{{i}}
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
{{i}}
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
{{i}}
</div>
</div>
</ng-container>
<div class="col-12 row">
<div class="col-4 pl-1 pr-1 pb-3 pt-2">
รวม
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
6
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
6
</div>
<div class="col-2 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
6
</div>
</div>
</div>
<div class="col-3 row">
<div class="col-12" style="align-content: center;">
<div class="row border pl-1 pr-1 pb-3 pt-2">
<div class="col-12 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
Final การประเมิน PMS
</div>
<div class="col-12 row pl-1 pr-1 pb-3 pt-2"
style="justify-content: center;background-color: hsl(214 58% 86% / 1)">
สรุปปี 2568
</div>
<div class="col-12 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
คะแนน 75.76
</div>
<div class="col-12 row pl-1 pr-1 pb-3 pt-2" style="justify-content: center;">
Grade C
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row col-12">
<div class="col-12 pb-3 pt-3" style="font-size:larger;font-weight: bold;">
ส่วนที่ 3 : โปรดระบุรายละเอียดที่สําคัญของพนักงาน
</div>
<div class="col-12 border-b row p-1">
เช่น รางวัล หรือผลงานดีเด่นในช่วงเวลาของการประเมิน(ผู้บังคับบัญชากรอก)
</div>
</div>
<div class="row col-12">
<div class="col-12 pb-3 pt-3" style="font-size:larger;font-weight: bold;">
ส่วนที่ 4 จุดแข็ง จุดอ่อน ที่ต้องพัฒนา (ผู้บังคับบัญชากรอก)
</div>
<div class="col-12 border row p-1">
<div class="row col-12" style="background-color: hsl(180.47deg 100% 25.29%);color: white;">
<div class="col-3 pl-1 pr-1 pb-3 pt-2">
วิเคราะห์
</div>
<div class="col-4 pl-1 pr-1 pb-3 pt-2">
รายละเอียด จุดแข็ง/จุดอ่อน
</div>
<div class="col-3 pl-1 pr-1 pb-3 pt-2">
หัวข้อที่ต้องเรียนรู้เพิ่มเติม
</div>
<div class="col-1 pl-1 pr-1 pb-3 pt-2">
ผู้เสนอ
</div>
<div class="col-1 pl-1 pr-1 pb-3 pt-2">
ผู้อนุมัติ
</div>
</div>
<div class="row col-12">
<div class="col-3 pl-1 pr-1 pb-3 pt-2">
1. จุดแข็ง
</div>
<div class="col-4 pl-1 pr-1 pb-3 pt-2">
</div>
<div class="col-3 pl-1 pr-1 pb-3 pt-2">
</div>
<div class="col-1 pl-1 pr-1 pb-3 pt-2">
</div>
<div class="col-1 pl-1 pr-1 pb-3 pt-2">
</div>
</div>
<div class="row col-12">
<div class="col-3 pl-1 pr-1 pb-3 pt-2">
2. จุดอ่อนที่ต้องพัฒนา
</div>
<div class="col-4 pl-1 pr-1 pb-3 pt-2">
</div>
<div class="col-3 pl-1 pr-1 pb-3 pt-2">
</div>
<div class="col-1 pl-1 pr-1 pb-3 pt-2">
</div>
<div class="col-1 pl-1 pr-1 pb-3 pt-2">
</div>
</div>
</div>
</div>
<div class="row col-12">
<div class="col-12 pb-3 pt-3">
หมายเหตุ: เกณฑ์การสรุปคะแนน
</div>
<div class="col-12 row" style="justify-content: center;">
<div class="col-8 border p-1 row">
<ng-container *ngFor="let item of [{a:'A',b:'ดีเลิศ (Outstanding)',c:'>91'},
{a:'B',b:'ดีมาก (Above Expectations)',c:'>81-90'},
{a:'C',b:'ดี (Meet Expectations)',c:'>61-80'},
{a:'D',b:'ควรปรับปรุง (Below Expectations)' ,c:'>51-60'},
{a:'E',b:'ไม่น่าพอใจ (Unsatisfactory)',c:'<50'},]">
<div class="row col-12">
<div class="col-2 pl-1 pr-1 pb-3 pt-2">{{item.a}}</div>
<div class="col-8 pl-1 pr-1 pb-3 pt-2">{{item.b}}</div>
<div class="col-2 pl-1 pr-1 pb-3 pt-2">{{item.c}}</div>
</div>
</ng-container>
</div>
</div>
</div>
</div>
<div class="row col-12 pb-3" style="justify-content: space-between;margin-top: 2rem;">
<div class="col-auto">
ลงชื่อ ............................ พนักงาน
</div>
<div class="col-auto">
ลงชื่อ ............................ ผู้ตรวจสอบ
</div>
<div class="col-auto">
ลงชื่อ ............................ ผู้อนุมัติ
</div>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button type="button" mat-button [mat-dialog-close]
class="hs-dropdown-toggle ti-btn ti-border font-medium bg-white text-gray-700 shadow-sm align-middle hover:bg-gray-50 focus:ring-offset-white focus:ring-primary dark:bg-bgdark dark:hover:bg-black/20 dark:border-white/10 dark:text-white/70 dark:hover:text-white dark:focus:ring-offset-white/10">
ย้อนกลับ
</button>
<button type="button" (click)="save()" class="ti-btn ti-btn-success">
รับทราบผล
</button>
</mat-dialog-actions>
</ng-template>
\ No newline at end of file
// th{
// position: relative; // เทียบเท่า class "relative"
// padding: 10px; // เทียบเท่า class "px-10px py-10px" (อาจเปลี่ยนตามต้องการ)
// background-color: rgb(96 165 250 / 0.1); // ตัวอย่างแทน "bg-soft-secondary"
// color: #2b2b2b; // ตัวอย่างแทน "text-primary"
// text-align: center !important; // เทียบเท่า "!text-center"
// // หากต้องการดีไซน์อื่น ๆ เพิ่มเติมก็ใส่ในนี้ได้เลย เช่น:
// font-weight: 600;
// border-bottom: 1px solid #eee;
// }
.e-headercell,
.e-detailheadercell {
background-color: rgb(96 165 250 / 0.1) !important;
}
.e-pager .e-currentitem,
.e-pager .e-currentitem:hover {
background: rgb(96 165 250) !important;
color: #fff;
opacity: 1 !important;
}
.e-checkbox-wrapper .e-frame.e-check,
.e-css.e-checkbox-wrapper .e-frame.e-check {
background-color: rgb(96 165 250) !important;
border-color: transparent;
color: #fff;
}
.e-checkbox-wrapper .e-frame,
.e-css.e-checkbox-wrapper .e-frame {
border: 1px solid !important;
border-radius: 2px;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
font-family: "e-icons";
height: 18px;
line-height: 10px;
padding: 2px 0;
text-align: center;
vertical-align: middle;
width: 1rem !important;
border-color: #64748b !important;
}
.e-grid td.e-selectionbackground {
background-color: #aec2ec !important;
}
.row {
display: flex;
flex-wrap: wrap;
}
.col {
flex: 1;
}
@for $i from 1 through 12 {
$width: (
$i / 12) * 100%;
.col-#{$i} {
flex: 0 0 $width;
max-width: $width;
}
}
@for $i from 1 through 100 {
.m-#{$i}rem {
margin: #{$i}rem;
}
.mt-#{$i}rem {
margin-top: #{$i}rem;
}
.ml-#{$i}rem {
margin-left: #{$i}rem;
}
.mb-#{$i}rem {
margin-bottom: #{$i}rem;
}
.mr-#{$i}rem {
margin-right: #{$i}rem;
}
.p-#{$i}rem {
padding: #{$i}rem;
}
.pt-#{$i}rem {
padding-top: #{$i}rem;
}
.pl-#{$i}rem {
padding-left: #{$i}rem;
}
.pb-#{$i}rem {
padding-bottom: #{$i}rem;
}
.pr-#{$i}rem {
padding-right: #{$i}rem;
}
}
@media print {
body * {
visibility: hidden;
/* ซ่อนทุก element */
}
#printArea,
#printArea * {
visibility: visible;
/* แสดงเฉพาะ #printArea */
}
#printArea {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
}
.pdf-container {
transform: translateZ(0
);
}
\ No newline at end of file
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output, SimpleChanges, ViewChild } from "@angular/core";
import { MatDialog, MatDialogRef } from "@angular/material/dialog";
import { Router, ActivatedRoute } from "@angular/router";
import { TranslateService } from "@ngx-translate/core";
import { AppraisalKpiSettingEmpModel, MyAppraisalKpiSettingEmpModel, MyPmsTypeModel, MyTopicModel, PmsTypeModel, TopicModel } from "src/app/shared/model/appraisal-kpi-setting-emp.model";
import { EmployeeModel, MyEmployeeModel } from "src/app/shared/model/employee.model";
import { AppraisalService } from "src/app/shared/services/appraisal.service";
import { EmployeeService } from "src/app/shared/services/employee.service";
import { FileService } from "src/app/shared/services/file.service";
import { PmstypeService } from "src/app/shared/services/pmstype.service";
import { TokenService } from "src/app/shared/services/token.service";
import { ToastrService } from 'ngx-toastr';
import Swal from 'sweetalert2';
import { AggregateService, Column, ColumnMenuClickEventArgs, ColumnMenuService, ColumnModel, DetailRowService, EditService, ExcelExportProperties, ExcelExportService, FilterService, FilterSettingsModel, GridComponent, GroupService, GroupSettingsModel, LoadingIndicatorModel, PageService, PdfExportService, ReorderService, SearchService, SelectionSettingsModel, SortService, ToolbarService } from "@syncfusion/ej2-angular-grids";
import { Query } from '@syncfusion/ej2-data';
import { setCulture } from '@syncfusion/ej2-base';
@Component({
selector: 'app-evaluation-confirmation',
templateUrl: './evaluation-confirmation.component.html',
providers: [AggregateService, SortService, GroupService, ColumnMenuService, PageService, FilterService, ToolbarService, PdfExportService, ExcelExportService, DetailRowService, ReorderService, EditService, SearchService],
styleUrls: ['./evaluation-confirmation.component.scss']
})
export class EvaluationConfirmationComponent {
page = 1
search = ''
syncfution: {
dataList: any[],
searchSettings: {
fields: string[],
operator: 'contains',
ignoreCase: false
},
columns: ColumnModel[]
} = {
dataList: [],
searchSettings: {
fields: [
'round',
'year',
'status'],
operator: 'contains',
ignoreCase: false
},
columns: [{
field: "round",
headerText: "รอบการประเมิน",
type: "string",
isPrimaryKey: true,
},
{
field: "year",
headerText: "ปีการประเมิน",
type: "string"
},
{
field: "status",
headerText: "Status",
type: "string"
}]
}
@ViewChild('grid') public grid?: GridComponent;
filterSettings: FilterSettingsModel = { type: 'Excel' };
selectionOptions: SelectionSettingsModel = { checkboxOnly: true };
groupSettings: GroupSettingsModel = { allowReordering: true, showGroupedColumn: true, showDropArea: false };
toolbarOptions: any[] = ['Print', 'ExcelExport', 'CsvExport'];
editSettings? = { allowEditing: true, mode: 'Batch' };
loadingIndicator: LoadingIndicatorModel = { indicatorType: 'Shimmer' };
query: Query = new Query().addParams('dataCount', '1000');
columnMenuItems: any[] = [
'AutoFit', 'AutoFitAll', 'SortAscending', 'SortDescending',
'Group', 'Ungroup', 'ColumnChooser', 'Filter',
{ text: 'Sum', id: 'aggregate_sum' },
{ text: 'Count', id: 'aggregate_count' },
{ text: 'Average', id: 'aggregate_average' },
{ text: 'Min', id: 'aggregate_min' },
{ text: 'Max', id: 'aggregate_max' }
];
initialPage? = { pageSizes: true, pageSize: 10 };
aggregatesSum: any[] = [];
aggregatesCount: any[] = [];
aggregatesAvg: any[] = [];
aggregatesMin: any[] = [];
aggregatesMax: any[] = [];
locale = 'th-TH'
@ViewChild('employeeDialog') employeeDialog: any
employeeDialogRef: any
evaluaterId = ''
constructor(private appraisalService: AppraisalService,
private fileService: FileService,
private translateService: TranslateService,
private tokenService: TokenService,
private dialog: MatDialog,
private cdr: ChangeDetectorRef) {
this.locale = this.translateService.getCurrentLang() == 'th' ? 'th-TH' : 'en-US'
this.translateService.onLangChange.subscribe((event) => {
if (event.lang === 'th') {
setCulture('th-TH');
this.locale = 'th-TH'
} else if (event.lang === 'en') {
setCulture('en-US');
this.locale = 'en-US'
}
this.toolbarOptions = [
{ text: this.translateService.instant('Print'), prefixIcon: 'e-print', id: 'Print' },
{ text: this.translateService.instant('ExcelExport'), prefixIcon: 'e-excelexport', id: 'ExcelExport' },
{ text: this.translateService.instant('CSVExport'), prefixIcon: 'e-csvexport', id: 'CsvExport' }
]
this.setSyncfutionDataList()
this.cdr.markForCheck()
});
}
ngOnInit(): void {
this.evaluaterId = this.decodeJWT(sessionStorage.getItem("accessToken") || '').employeeid
this.getEmpKpi()
}
decodeJWT(token: string) {
let base64Url = token.split('.')[1];
let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
let jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
openEmployeeDialog() {
this.employeeDialogRef = this.dialog.open(this.employeeDialog, {
width: '1000px',
})
}
closeEmployeeDialog() {
this.employeeDialogRef.close()
}
getEmpKpi() {
this.syncfution.dataList = []
this.setSyncfutionDataList()
// this.appraisalService.getEmpKpi().subscribe({
// next: response => {
// this.subordinate.dataList = response.map(e => new MyAppraisalKpiSettingModel(e))
// this.setSyncfutionDataList()
// this.cdr.markForCheck()
// }, error: error => {
// this.cdr.markForCheck()
// }
// })
}
setSyncfutionDataList() {
// this.syncfution.dataList = this.subordinateFilter().map(e => ({
// employeeId: e.apsassessy.employeeId,
// fullName: this.translateText(e.apsassessy.thFullName, e.apsassessy.engFullName),
// position: this.translateText(e.apsassessy.position.tdesc, e.apsassessy.position.edesc),
// status: this.translateText(e.statusApprove.tdesc, e.statusApprove.edesc),
// statusCode: e.statusApprove.code,
// statusEdesc: e.statusApprove.edesc,
// picture: e.apsassessy.picture
// }))
this.syncfution.dataList = this.dataFilter().map(e => ({
round: e,
year: e,
status: e
}))
}
dataFilter() {
return [1, 2, 3, 4].filter(x => {
return (x + '').toLowerCase().includes(this.search.toLowerCase())
})
}
onColumnMenuClick(args: ColumnMenuClickEventArgs): void {
if (!args.item.id) { return; }
if (args.item.id.startsWith('aggregate_')) {
const colField = (args.column as any)?.field;
if (!colField) { return; }
const selectedAgg = args.item.id.split('_')[1];
if (selectedAgg === 'sum') {
if (this.aggregatesSum.find(a => a.field === colField)) {
this.aggregatesSum = this.aggregatesSum.filter(a => a.field !== colField);
} else {
this.aggregatesSum.push({
field: colField,
type: 'Sum',
footerTemplate: 'Sum: ${Sum}'
});
}
this.cdr.markForCheck()
}
else if (selectedAgg === 'count') {
this.aggregatesCount.push({
field: colField,
type: 'Count',
footerTemplate: 'Count: ${Count}'
});
} else if (selectedAgg === 'average') {
this.aggregatesAvg.push({
field: colField,
type: 'Average',
footerTemplate: 'Avg: ${Average}'
});
}
else if (selectedAgg === 'min') {
this.aggregatesMin.push({
field: colField,
type: 'Min',
footerTemplate: 'Min: ${Min}'
});
}
else if (selectedAgg === 'max') {
this.aggregatesMax.push({
field: colField,
type: 'Max',
footerTemplate: 'Max: ${Max}'
});
}
setTimeout(() => {
this.grid?.refresh();
}, 500);
}
}
toolbarClick(args: any): void {
if (args.item.id === 'Grid_excelexport') {
let exportProperties: ExcelExportProperties = {
columns: this.syncfution.columns.map(col => ({
field: col.field,
headerText: col.headerText
})) as Column[]
};
this.grid?.excelExport(exportProperties);
} else if (args.item.id === 'Grid_csvexport') {
let exportColumns = this.syncfution.columns.map(col => ({
field: col.field || '',
headerText: col.headerText || ''
}));
this.grid?.csvExport({ columns: exportColumns as Column[] });
} else if (args.item.id === 'Grid_print') {
this.cdr.markForCheck()
setTimeout(() => {
this.cdr.markForCheck()
}, 1000)
}
}
@ViewChild('pdfArea') pdfArea!: ElementRef<HTMLElement>;
async exportPdf() {
const el = this.pdfArea?.nativeElement;
if (!el) return;
// 👉 ลบ shadow ออกจากทุก element ที่มี shadow class
const shadowEls = el.querySelectorAll('.shadow');
shadowEls.forEach(e => e.classList.remove('shadow'));
const mod: any = await import('html2pdf.js');
const html2pdf = mod.default ?? mod;
const cleanupScale = this.expandToFullWidthThenScale(el);
const cleanupUnclip = this.unclipOverflows(el);
const prevBodyOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
const fullW = el.scrollWidth;
const fullH = el.scrollHeight;
const epsilon = 2;
console.log('🥷🏿Math.max(0, fullH - epsilon) --->', Math.max(0, fullH - epsilon));
const opt = {
margin: [10, 10, 10, 10],
filename: `test.pdf`,
image: { type: 'jpeg', quality: 0.96 },
html2canvas: {
scale: 2,
useCORS: true,
logging: false,
scrollX: 0,
scrollY: -window.scrollY,
width: 794,
// height: Math.max(0, fullH - epsilon),
height: 1123,
windowWidth: 794,
// windowHeight: Math.max(0, fullH - epsilon),
windowHeight: 1123,
},
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
pagebreak: { mode: ['css', 'legacy'], avoid: ['.no-break', 'table', 'img'] },
onclone: (doc: any) => {
doc.querySelectorAll('[data-hide-in-pdf]').forEach((el: any) => el.remove());
},
};
try {
await html2pdf().from(el).set(opt).save();
} finally {
cleanupUnclip();
cleanupScale();
document.body.style.overflow = prevBodyOverflow;
// 👉 คืน class shadow กลับ
shadowEls.forEach(e => e.classList.add('shadow'));
}
}
expandToFullWidthThenScale(el: HTMLElement) {
const A4_PX = 794; // ≈ 210mm @ 96dpi
const full = el.scrollWidth; // ความกว้างจริงทั้งหมด (รวมพื้นที่ต้องเลื่อนขวา)
const scale = Math.min(1, A4_PX / full); // สเกลลงให้พอดี A4 (ถ้ากว้างกว่า)
// เก็บค่าเดิมไว้คืนทีหลัง
const prev = {
width: el.style.width,
maxWidth: el.style.maxWidth,
overflowX: el.style.overflowX,
transform: el.style.transform,
transformOrigin: el.style.transformOrigin,
};
// คลี่ความกว้างออกทั้งหมด แล้วสเกลให้พอดี A4
el.style.width = `${full}px`;
el.style.maxWidth = 'none';
el.style.overflowX = 'visible';
el.style.transformOrigin = 'top left';
el.style.transform = `scale(${scale})`;
// คืนค่า
return () => {
el.style.width = prev.width;
el.style.maxWidth = prev.maxWidth;
el.style.overflowX = prev.overflowX;
el.style.transform = prev.transform;
el.style.transformOrigin = prev.transformOrigin;
};
}
private unclipOverflows(el: HTMLElement) {
const patched: Array<{ node: HTMLElement, prev: Partial<CSSStyleDeclaration> }> = [];
const walker = document.createTreeWalker(el, NodeFilter.SHOW_ELEMENT);
const patchNode = (node: HTMLElement) => {
const style = window.getComputedStyle(node);
const needOverflowPatch = style.overflowX !== 'visible' || style.overflow !== 'visible';
const isSticky = style.position === 'sticky';
if (needOverflowPatch || isSticky) {
const prev = {
overflow: node.style.overflow,
overflowX: node.style.overflowX,
overflowY: node.style.overflowY,
position: node.style.position,
clipPath: (node.style as any).clipPath,
};
patched.push({ node, prev });
node.style.overflow = 'visible';
node.style.overflowX = 'visible';
node.style.overflowY = 'visible';
if (isSticky) node.style.position = 'static';
(node.style as any).clipPath = 'none';
}
};
// รวม el เองด้วย
patchNode(el as HTMLElement);
while (walker.nextNode()) {
patchNode(walker.currentNode as HTMLElement);
}
// ฟังก์ชันคืนค่าเดิม
return () => {
for (const { node, prev } of patched) {
node.style.overflow = prev.overflow || '';
node.style.overflowX = prev.overflowX || '';
node.style.overflowY = prev.overflowY || '';
node.style.position = prev.position || '';
(node.style as any).clipPath = prev.clipPath || '';
}
};
}
save() {
Swal.fire({
icon: 'question',
title: 'แจ้งเตือน',
text: 'ยืนยันการบันทึกข้อมูลหรือไม่',
showCancelButton: true,
confirmButtonText: 'บันทึกข้อมูล',
cancelButtonText: 'ย้อนกลับ',
reverseButtons: true,
}).then((result) => {
if (result.isConfirmed) {
this.showAlert('บันทึกข้อมูลสำเร็จ', 'success')
this.closeEmployeeDialog();
}
});
}
showAlert(text: string, type: 'success' | 'error') {
Swal.fire({
title: 'แจ้งเตือน',
text: text,
icon: type,
confirmButtonText: 'ตกลง',
});
}
}
<div class=" pt-1.5rem mb-2">
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-2 w-full">
<div class="w-full mb-2">
<div class="font-size-18px font-weight-700 text-primary">
<span class="whitespace-nowrap relative">
ติดตามหลังการประเมิน
</span>
</div>
</div>
<div class="box shadow-md hover:shadow-xl transition m-0" style="border-radius:20px">
<div class="box-body py-2">
<nav class="flex rtl:space-x-reverse space-x-2">
<a style="border-radius: 20px;width: 210px;height: 35px;"
class=" border justify-center rounded-4px hs-tab-active:!bg-primary hs-tab-active:border-primary hs-tab-active:!text-white -mb-px py-2 px-3 inline-flex items-center gap-2 font-size-16px font-weight-500 text-center text-gray-600 hover:text-gray-900 active"
href="javascript:void(0);" id="card-type-item-1" data-hs-tab="#card-type-1" aria-controls="card-type-1">
{{'ผลการประเมิน' | translate}}
</a>
<a style="border-radius: 20px;width: 210px;height: 35px;"
class="border justify-center rounded-4px hs-tab-active:!bg-primary hs-tab-active:border-primary hs-tab-active:!text-white -mb-px py-2 px-3 inline-flex items-center gap-2 font-size-16px font-weight-500 text-center text-gray-600 hover:text-gray-900"
href="javascript:void(0);" id="card-type-item-2" data-hs-tab="#card-type-2" aria-controls="card-type-2">
{{'การติดตามผล Gap' | translate}}
</a>
<a style="border-radius: 20px;width: 210px;height: 35px;"
class="border justify-center rounded-4px hs-tab-active:!bg-primary hs-tab-active:border-primary hs-tab-active:!text-white -mb-px py-2 px-3 inline-flex items-center gap-2 font-size-16px font-weight-500 text-center text-gray-600 hover:text-gray-900"
href="javascript:void(0);" id="card-type-item-3" data-hs-tab="#card-type-3" aria-controls="card-type-3">
{{'ติดตามผล Gap โดยหัวหน้า' | translate}}
</a>
</nav>
<div class="pt-20px">
<div id="card-type-1" role="tabpanel" aria-labelledby="card-type-item-1">
<app-evaluation-confirmation></app-evaluation-confirmation>
</div>
<div id="card-type-2" class="hidden" role="tabpanel" aria-labelledby="card-type-item-2">
<app-evaluation-confirmation></app-evaluation-confirmation>
</div>
<div id="card-type-3" class="hidden" role="tabpanel" aria-labelledby="card-type-item-3">
<app-evaluation-confirmation></app-evaluation-confirmation>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
\ No newline at end of file
.hover-visible {
.hover-show {
opacity: 0;
}
}
.hover-visible:hover {
.hover-show {
opacity: 1;
}
}
import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges, ViewChild } from "@angular/core";
import { MatDialog, MatDialogRef } from "@angular/material/dialog";
import { Router, ActivatedRoute } from "@angular/router";
import { TranslateService } from "@ngx-translate/core";
import { AppraisalKpiSettingEmpModel, MyAppraisalKpiSettingEmpModel, MyPmsTypeModel, MyTopicModel, PmsTypeModel, TopicModel } from "src/app/shared/model/appraisal-kpi-setting-emp.model";
import { EmployeeModel, MyEmployeeModel } from "src/app/shared/model/employee.model";
import { AppraisalService } from "src/app/shared/services/appraisal.service";
import { EmployeeService } from "src/app/shared/services/employee.service";
import { FileService } from "src/app/shared/services/file.service";
import { PmstypeService } from "src/app/shared/services/pmstype.service";
import { TokenService } from "src/app/shared/services/token.service";
import { ToastrService } from 'ngx-toastr';
import Swal from 'sweetalert2';
import { Column, ColumnMenuClickEventArgs, ColumnModel, ExcelExportProperties, FilterSettingsModel, GridComponent, GroupSettingsModel, LoadingIndicatorModel, SelectionSettingsModel } from "@syncfusion/ej2-angular-grids";
import { Query } from '@syncfusion/ej2-data';
import { setCulture } from '@syncfusion/ej2-base';
@Component({
selector: 'app-post-evaluation-tracking',
templateUrl: './post-evaluation-tracking.component.html',
styleUrls: ['./post-evaluation-tracking.component.scss']
})
export class PostEvaluationTrackingComponent {
@Output() sendPathTitle: EventEmitter<string[]> = new EventEmitter<string[]>();
currentTab = 1
onSendPathTitle(pathTitle: string) {
this.sendPathTitle.emit(['menu.Organization', 'menu.Company', 'BusinessUnit', pathTitle])
}
}
......@@ -2,6 +2,13 @@
<div class=" pt-1.5rem mb-2">
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-2 w-full">
<div class="w-full mb-2">
<div class="font-size-18px font-weight-700 text-primary">
<span class="whitespace-nowrap relative">
แก้ไข Individual KPI โดยหัวหน้า
</span>
</div>
</div>
<div class="box shadow-md hover:shadow-xl transition m-0" style="border-radius:20px">
<div class="box-body py-2">
<div class="flex item-center w-full font-size-18px font-weight-700 text-primary px-5 mt-1 mb-4"
......
......@@ -27,6 +27,9 @@ export class EmployeeService {
);
}
getSubordinatesList(): Observable<EmployeeModel[]> {
return this.http.get<EmployeeModel[]>(this.baseUrlapi + "/subordinates-list")
}
getList(): Observable<EmployeeModel[]> {
return this.http.get<EmployeeModel[]>(this.baseUrlapi + "/profile/create-short/lists")
}
......
......@@ -142,6 +142,16 @@ export class NavService implements OnDestroy {
show: true,
icon: ''
},
{
title: 'ติดตามหลังการประเมิน',
type: 'link',
selected: false,
active: false,
path: 'ess/post-evaluation-tracking',
id: 'm4',
show: true,
icon: ''
},
];
}
......
......@@ -7602,6 +7602,14 @@ select option:focus {
padding-top: 0.25rem;
}
.pr-1 {
padding-right: 0.25rem;
}
.pl-1 {
padding-left: 0.25rem;
}
.pt-2 {
padding-top: 0.5rem;
}
......
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