Commit 66a890c0 by pantakan konthang

แก้ปุ่ม PDF

parent 530c28df
...@@ -617,7 +617,7 @@ ...@@ -617,7 +617,7 @@
<app-pms-idp [currentTap]="currentTap" [canSave]="canSave" [appraisalIdp]="compentency.data.idp" <app-pms-idp [currentTap]="currentTap" [canSave]="canSave" [appraisalIdp]="compentency.data.idp"
[evaluaterId]="evaluaterId" [evaluateeId]="evaluateeId" [evaluaterId]="evaluaterId" [evaluateeId]="evaluateeId"
[canEdit]="evaluationForm=='sup'?canEdit:false" [currentStep]="compentency.data.currentStep" [canEdit]="evaluationForm=='sup'?canEdit:false" [currentStep]="compentency.data.currentStep"
[dateIso]="dateIso" (idpForm)="compentency.data.idp=$event" [complete]="complete" [pdfPrint]="pdfPrint" (pdfPrinted)="onPdfPrinted()"></app-pms-idp> [dateIso]="dateIso" (idpForm)="compentency.data.idp=$event" [complete]="complete"></app-pms-idp>
</div> </div>
<ng-container *ngIf="compentency.data&&canSave"> <ng-container *ngIf="compentency.data&&canSave">
<div class="box-footer text-end space-x-3 rtl:space-x-reverse" <div class="box-footer text-end space-x-3 rtl:space-x-reverse"
...@@ -626,11 +626,11 @@ ...@@ -626,11 +626,11 @@
<textarea type="text" class="ti-form-input" rows="2" placeholder="ใส่ Comment ที่นี่" <textarea type="text" class="ti-form-input" rows="2" placeholder="ใส่ Comment ที่นี่"
[(ngModel)]="comment"></textarea> [(ngModel)]="comment"></textarea>
</div> </div>
<button (click)="toggleStatusPdfPrint()" class="ti-btn m-0 ti-btn-soft-warning" <!-- <button (click)="toggleStatusPdfPrint()" class="ti-btn m-0 ti-btn-soft-warning"
*ngIf="currentTap == 'แผนพัฒนาบุคลากร'"> *ngIf="currentTap == 'แผนพัฒนาบุคลากร'">
<i class="ri-draft-fill"></i> <i class="ri-draft-fill"></i>
PDF PDF
</button> </button> -->
<button (click)="save('approve')" class="ti-btn m-0 ti-btn-soft-secondary" <button (click)="save('approve')" class="ti-btn m-0 ti-btn-soft-secondary"
[disabled]="compentencyFormRemain||kpiFormRemain" [disabled]="compentencyFormRemain||kpiFormRemain"
[class.ti-btn-disabled]="compentencyFormRemain||kpiFormRemain"> [class.ti-btn-disabled]="compentencyFormRemain||kpiFormRemain">
......
...@@ -74,7 +74,6 @@ export class PmsFormEmployeeComponent { ...@@ -74,7 +74,6 @@ export class PmsFormEmployeeComponent {
complete = false complete = false
@ViewChild('scrollContainer') scrollContainer!: ElementRef; @ViewChild('scrollContainer') scrollContainer!: ElementRef;
pdfPrint = false;
constructor( constructor(
private router: Router, private router: Router,
...@@ -760,13 +759,4 @@ export class PmsFormEmployeeComponent { ...@@ -760,13 +759,4 @@ export class PmsFormEmployeeComponent {
imgElement.src = './assets/img/users/defaultperson.jpg'; imgElement.src = './assets/img/users/defaultperson.jpg';
} }
toggleStatusPdfPrint() {
this.pdfPrint = !this.pdfPrint; // สลับ true/false เพื่อให้ Child จับการเปลี่ยน
}
onPdfPrinted() {
// เมื่อ Child แจ้งว่าพิมพ์เสร็จ ให้รีเซ็ตกลับเป็น false
this.pdfPrint = false;
}
} }
...@@ -2,10 +2,26 @@ ...@@ -2,10 +2,26 @@
<ng-template #idpEvaluation> <ng-template #idpEvaluation>
<ng-container *ngIf="appraisalIdp"> <ng-container *ngIf="appraisalIdp">
<div style="overflow-y: auto; padding-top: 1rem" #pdfArea class="pdf-container"> <div style="overflow-y: auto; padding-top: 1rem" #pdfArea class="pdf-container">
<div class="pb-2">
<!-- <ng-container *ngIf="pdfPrintCheck === 1">
<div *ngFor="let item of [1,2,3]" class="ti-spinner w-8 h-8 text-secondary mx-1"
role="status" aria-label="loading">
<span class="sr-only">Loading...</span>
</div>
</ng-container> -->
<ng-container>
<div class="pb-2 flex justify-between items-center">
<div class="font-size-18px font-weight-700 text-gray-500"> <div class="font-size-18px font-weight-700 text-gray-500">
ส่วนที่ 1: ข้อมูลทั่วไป ส่วนที่ 1: ข้อมูลทั่วไป
</div> </div>
<!-- <button class="ti-btn m-0 ti-btn-soft-warning" *ngIf="pdfPrintCheck == 0" (click)="exportPdf()" >
<i class="ri-draft-fill"></i>
PDF
</button> -->
<button class="ti-btn m-0 ti-btn-soft-warning" data-html2canvas-ignore="true" data-hide-in-pdf (click)="exportPdf()">
<i class="ri-draft-fill"></i> PDF
</button>
</div> </div>
<div class="pt-2 pb-2rem"> <div class="pt-2 pb-2rem">
<div class="flex flex-row gap-2 "> <div class="flex flex-row gap-2 ">
...@@ -344,7 +360,7 @@ ...@@ -344,7 +360,7 @@
</ng-container> </ng-container>
</ng-container> </ng-container>
<ng-container *ngIf="pdfPrintCheck == 1"> <ng-container *ngIf="pdfPrintCheck != 0">
{{convertDateFormat(appraisalIdp.masfromEvaluationRound.apsPeriodStart)}} {{convertDateFormat(appraisalIdp.masfromEvaluationRound.apsPeriodStart)}}
<ng-container <ng-container
*ngIf="appraisalIdp.masfromEvaluationRound.apsPeriodStart &&appraisalIdp.masfromEvaluationRound.apsPeriodEnd"> *ngIf="appraisalIdp.masfromEvaluationRound.apsPeriodStart &&appraisalIdp.masfromEvaluationRound.apsPeriodEnd">
...@@ -361,6 +377,9 @@ ...@@ -361,6 +377,9 @@
</table> </table>
</div> </div>
</div> </div>
</ng-container>
</div> </div>
</ng-container> </ng-container>
</ng-template> </ng-template>
......
...@@ -37,7 +37,7 @@ export class PmsIdpComponent { ...@@ -37,7 +37,7 @@ export class PmsIdpComponent {
@Output() idpForm: EventEmitter<any> = new EventEmitter<any>(); @Output() idpForm: EventEmitter<any> = new EventEmitter<any>();
@Input() pdfPrint = false; @Input() pdfPrint = false;
@Output() pdfPrinted = new EventEmitter<void>(); @Output() pdfPrinted = new EventEmitter<void>();
pdfPrintCheck = 0 pdfPrintCheck: 0 | 1 = 0
@ViewChild('pdfArea') pdfArea!: ElementRef<HTMLElement>; @ViewChild('pdfArea') pdfArea!: ElementRef<HTMLElement>;
competencycourse: { loading: boolean, data: CompetencycourseMiniModel[] } = { loading: false, data: [] } competencycourse: { loading: boolean, data: CompetencycourseMiniModel[] } = { loading: false, data: [] }
...@@ -48,6 +48,7 @@ export class PmsIdpComponent { ...@@ -48,6 +48,7 @@ export class PmsIdpComponent {
pageSize: 10 pageSize: 10
} }
competencycourseIndex = -1 competencycourseIndex = -1
pdfLoading = true
@ViewChild("competencycourseModal") competencycourseModal: any; @ViewChild("competencycourseModal") competencycourseModal: any;
dialogRefCompetencycourseModal: any dialogRefCompetencycourseModal: any
constructor(private evaluationIdpService: EvaluationIdpService, constructor(private evaluationIdpService: EvaluationIdpService,
...@@ -62,17 +63,6 @@ export class PmsIdpComponent { ...@@ -62,17 +63,6 @@ export class PmsIdpComponent {
this.getCompetencycourseMiniList() this.getCompetencycourseMiniList()
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes['pdfPrint']) {
const { previousValue, currentValue } = changes['pdfPrint'];
console.log('pdfPrint changed:', previousValue, '→', currentValue);
// สั่งพิมพ์เฉพาะตอนเป็น true
if (currentValue === true) {
this.pdfPrintCheck = 1
this.exportPdf();
this.pdfPrinted.emit(); // แจ้ง Parent ให้รีเซ็ต flag
}
}
if (changes['currentTap']?.currentValue || changes['appraisalIdp']?.currentValue) { if (changes['currentTap']?.currentValue || changes['appraisalIdp']?.currentValue) {
this.getFormIdp() this.getFormIdp()
} }
...@@ -189,163 +179,167 @@ export class PmsIdpComponent { ...@@ -189,163 +179,167 @@ export class PmsIdpComponent {
} }
print() { print() {
window.print(); window.print();
} }
/** ล็อคความกว้างเทียบ A4 (≈794px @96dpi) ให้ layout เสถียรตอนแคปเจอร์ */ /** ล็อคความกว้างเทียบ A4 (≈794px @96dpi) ให้ layout เสถียรตอนแคปเจอร์ */
private freezeWidthForA4(el: HTMLElement) { private freezeWidthForA4(el: HTMLElement) {
const prev = { const prev = {
width: el.style.width, width: el.style.width,
maxWidth: el.style.maxWidth, maxWidth: el.style.maxWidth,
transform: el.style.transform, transform: el.style.transform,
transformOrigin: el.style.transformOrigin, transformOrigin: el.style.transformOrigin,
}; };
el.style.width = '794px'; el.style.width = '794px';
el.style.maxWidth = '794px'; el.style.maxWidth = '794px';
el.style.transformOrigin = 'top left'; el.style.transformOrigin = 'top left';
el.style.transform = 'scale(1)'; el.style.transform = 'scale(1)';
return () => { return () => {
el.style.width = prev.width; el.style.width = prev.width;
el.style.maxWidth = prev.maxWidth; el.style.maxWidth = prev.maxWidth;
el.style.transform = prev.transform; el.style.transform = prev.transform;
el.style.transformOrigin = prev.transformOrigin; el.style.transformOrigin = prev.transformOrigin;
}; };
} }
/** ขยายเป็นความกว้างจริง (scrollWidth) แล้วสเกลลงให้พอดี A4 */ /** ขยายเป็นความกว้างจริง (scrollWidth) แล้วสเกลลงให้พอดี A4 */
private expandToFullWidthThenScale(el: HTMLElement) { private expandToFullWidthThenScale(el: HTMLElement) {
const A4_PX = 794; // ≈ 210mm @ 96dpi const A4_PX = 794; // ≈ 210mm @ 96dpi
const full = el.scrollWidth; // ความกว้างจริงทั้งหมด (รวมพื้นที่ต้องเลื่อนขวา) const full = el.scrollWidth; // ความกว้างจริงทั้งหมด (รวมพื้นที่ต้องเลื่อนขวา)
const scale = Math.min(1, A4_PX / full); // สเกลลงให้พอดี A4 (ถ้ากว้างกว่า) const scale = Math.min(1, A4_PX / full); // สเกลลงให้พอดี A4 (ถ้ากว้างกว่า)
// เก็บค่าเดิมไว้คืนทีหลัง // เก็บค่าเดิมไว้คืนทีหลัง
const prev = { const prev = {
width: el.style.width, width: el.style.width,
maxWidth: el.style.maxWidth, maxWidth: el.style.maxWidth,
overflowX: el.style.overflowX, overflowX: el.style.overflowX,
transform: el.style.transform, transform: el.style.transform,
transformOrigin: el.style.transformOrigin, transformOrigin: el.style.transformOrigin,
}; };
// คลี่ความกว้างออกทั้งหมด แล้วสเกลให้พอดี A4 // คลี่ความกว้างออกทั้งหมด แล้วสเกลให้พอดี A4
el.style.width = `${full}px`; el.style.width = `${full}px`;
el.style.maxWidth = 'none'; el.style.maxWidth = 'none';
el.style.overflowX = 'visible'; el.style.overflowX = 'visible';
el.style.transformOrigin = 'top left'; el.style.transformOrigin = 'top left';
el.style.transform = `scale(${scale})`; el.style.transform = `scale(${scale})`;
// คืนค่า // คืนค่า
return () => { return () => {
el.style.width = prev.width; el.style.width = prev.width;
el.style.maxWidth = prev.maxWidth; el.style.maxWidth = prev.maxWidth;
el.style.overflowX = prev.overflowX; el.style.overflowX = prev.overflowX;
el.style.transform = prev.transform; el.style.transform = prev.transform;
el.style.transformOrigin = prev.transformOrigin; el.style.transformOrigin = prev.transformOrigin;
}; };
} }
/** ทำให้ทุกกล่องใน subtree ไม่ตัดขอบขวา: overflowX => visible, ยกเลิก position:sticky ชั่วคราว */ /** ทำให้ทุกกล่องใน subtree ไม่ตัดขอบขวา: overflowX => visible, ยกเลิก position:sticky ชั่วคราว */
private unclipOverflows(el: HTMLElement) { private unclipOverflows(el: HTMLElement) {
const patched: Array<{node: HTMLElement, prev: Partial<CSSStyleDeclaration>}> = []; const patched: Array<{ node: HTMLElement, prev: Partial<CSSStyleDeclaration> }> = [];
const walker = document.createTreeWalker(el, NodeFilter.SHOW_ELEMENT); const walker = document.createTreeWalker(el, NodeFilter.SHOW_ELEMENT);
const patchNode = (node: HTMLElement) => { const patchNode = (node: HTMLElement) => {
const style = window.getComputedStyle(node); const style = window.getComputedStyle(node);
const needOverflowPatch = style.overflowX !== 'visible' || style.overflow !== 'visible'; const needOverflowPatch = style.overflowX !== 'visible' || style.overflow !== 'visible';
const isSticky = style.position === 'sticky'; const isSticky = style.position === 'sticky';
if (needOverflowPatch || isSticky) { if (needOverflowPatch || isSticky) {
const prev = { const prev = {
overflow: node.style.overflow, overflow: node.style.overflow,
overflowX: node.style.overflowX, overflowX: node.style.overflowX,
overflowY: node.style.overflowY, overflowY: node.style.overflowY,
position: node.style.position, position: node.style.position,
clipPath: (node.style as any).clipPath, clipPath: (node.style as any).clipPath,
}; };
patched.push({ node, prev }); patched.push({ node, prev });
node.style.overflow = 'visible'; node.style.overflow = 'visible';
node.style.overflowX = 'visible'; node.style.overflowX = 'visible';
node.style.overflowY = 'visible'; node.style.overflowY = 'visible';
if (isSticky) node.style.position = 'static'; if (isSticky) node.style.position = 'static';
(node.style as any).clipPath = 'none'; (node.style as any).clipPath = 'none';
}
};
// รวม el เองด้วย
patchNode(el as HTMLElement);
while (walker.nextNode()) {
patchNode(walker.currentNode as HTMLElement);
} }
};
// รวม el เองด้วย // ฟังก์ชันคืนค่าเดิม
patchNode(el as HTMLElement); return () => {
while (walker.nextNode()) { for (const { node, prev } of patched) {
patchNode(walker.currentNode as HTMLElement); 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 || '';
}
};
} }
// ฟังก์ชันคืนค่าเดิม
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 || '';
}
};
}
// @ViewChild('pdfArea') pdfArea!: ElementRef<HTMLElement>;
// @ViewChild('pdfArea') pdfArea!: ElementRef<HTMLElement>; async exportPdf() {
this.pdfPrintCheck = 1
const el = this.pdfArea?.nativeElement;
if (!el) return;
async exportPdf() { const mod: any = await import('html2pdf.js');
const el = this.pdfArea?.nativeElement; const html2pdf = mod.default ?? mod;
if (!el) return;
const mod: any = await import('html2pdf.js'); // 1) คลี่กว้างจริงแล้วสเกลลงพอดี A4
const html2pdf = mod.default ?? mod; const cleanupScale = this.expandToFullWidthThenScale(el);
// 2) ปลดคลิป overflow/sticky ของลูก ๆ ทั้งหมด
const cleanupUnclip = this.unclipOverflows(el);
// 1) คลี่กว้างจริงแล้วสเกลลงพอดี A4 const prevBodyOverflow = document.body.style.overflow;
const cleanupScale = this.expandToFullWidthThenScale(el); document.body.style.overflow = 'hidden';
// 2) ปลดคลิป overflow/sticky ของลูก ๆ ทั้งหมด
const cleanupUnclip = this.unclipOverflows(el);
const prevBodyOverflow = document.body.style.overflow; // ขนาดจริงของ element (หลังถูกขยายแล้ว)
document.body.style.overflow = 'hidden'; const fullW = el.scrollWidth;
const fullH = el.scrollHeight;
const epsilon = 2; // กันเผื่อ
// ขนาดจริงของ element (หลังถูกขยายแล้ว) const opt = {
const fullW = el.scrollWidth; margin: [10, 10, 10, 10],
const fullH = el.scrollHeight; filename: `pms-${this.evaluateeId || 'employee'}-${new Date().toISOString().slice(0, 10)}.pdf`,
const epsilon = 2; // กันเผื่อ image: { type: 'jpeg', quality: 0.96 },
html2canvas: {
const opt = { scale: 2,
margin: [10,10,10,10], useCORS: true,
filename: `pms-${this.evaluateeId || 'employee'}-${new Date().toISOString().slice(0,10)}.pdf`, logging: false,
image: { type: 'jpeg', quality: 0.96 }, scrollX: 0,
html2canvas: { scrollY: -window.scrollY,
scale: 2, // 👇 บอกขนาดที่จะเรนเดอร์ให้เท่าของจริงทั้งหมด
useCORS: true, width: 800,
logging: false, // height: fullH,
scrollX: 0, height: Math.max(0, fullH - epsilon),
scrollY: -window.scrollY, windowWidth: 800,
// 👇 บอกขนาดที่จะเรนเดอร์ให้เท่าของจริงทั้งหมด // windowHeight: fullH,
width: 800, windowHeight: Math.max(0, fullH - epsilon),
// height: fullH, // foreignObjectRendering: true, // ลองเปิดถ้า layout ยังเพี้ยน (มีข้อจำกัดกับบางเคส)
height: Math.max(0, fullH - epsilon), },
windowWidth: 800, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
// windowHeight: fullH, pagebreak: { mode: ['css', 'legacy'], avoid: ['.no-break', 'table', 'img'] },
windowHeight: Math.max(0, fullH - epsilon), onclone: (doc:any) => {
// foreignObjectRendering: true, // ลองเปิดถ้า layout ยังเพี้ยน (มีข้อจำกัดกับบางเคส) doc.querySelectorAll('[data-hide-in-pdf]').forEach((el:any) => el.remove());
}, }
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }, };
pagebreak: { mode: ['css','legacy'], avoid: ['.no-break','table','img'] }
};
try { try {
await html2pdf().from(el).set(opt).save(); await html2pdf().from(el).set(opt).save();
} finally { } finally {
cleanupUnclip(); cleanupUnclip();
cleanupScale(); cleanupScale();
document.body.style.overflow = prevBodyOverflow; document.body.style.overflow = prevBodyOverflow;
}
this.pdfPrintCheck = 0
} }
this.pdfPrintCheck = 0
}
} }
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