fix: lỗi font Bold của xuất pdf
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { io } from 'socket.io-client';
|
||||
import { jsPDF } from 'jspdf';
|
||||
import autoTable from 'jspdf-autotable';
|
||||
import { robotoBase64 } from '../utils/pdfFont';
|
||||
import { robotoBase64, robotoBoldBase64 } from '../utils/pdfFont';
|
||||
import { ItineraryTimeline } from '../components/ItineraryTimeline';
|
||||
import { ExpenseManager } from '../components/ExpenseManager';
|
||||
import { useTourStore } from '@/store/useTourStore';
|
||||
@@ -486,12 +486,12 @@ export const TourDetailPage = ({
|
||||
const handleExportPDF = async () => {
|
||||
const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
|
||||
|
||||
// 1. Thêm font vào Virtual File System của jsPDF để hỗ trợ tiếng Việt
|
||||
doc.addFileToVFS("Roboto-Regular.ttf", robotoBase64);
|
||||
doc.addFont("Roboto-Regular.ttf", "Roboto", "normal");
|
||||
doc.addFileToVFS("Roboto-Bold.ttf", robotoBoldBase64);
|
||||
doc.addFont("Roboto-Bold.ttf", "Roboto", "bold");
|
||||
doc.setFont("Roboto");
|
||||
|
||||
// 2. Vẽ Tiêu đề & Thông tin Tour ở đầu trang
|
||||
const titleText = `LỊCH TRÌNH TOUR: ${currentTour?.title?.toUpperCase() || 'HÀNH TRÌNH TOUR'}`;
|
||||
doc.setFontSize(16);
|
||||
doc.text(titleText, 148.5, 15, { align: 'center' });
|
||||
@@ -508,9 +508,9 @@ export const TourDetailPage = ({
|
||||
doc.text(dateRangeStr, 148.5, startY, { align: 'center' });
|
||||
startY += 8;
|
||||
|
||||
// 3. Chuẩn bị dữ liệu bảng
|
||||
const tableColumn = ["STT", "Ngày giờ", "Chặng", "Địa điểm", "Ghi chú"];
|
||||
const tableRows: any[] = [];
|
||||
const legRowRanges: Record<string, { start: number; count: number }> = {};
|
||||
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
@@ -525,30 +525,43 @@ export const TourDetailPage = ({
|
||||
};
|
||||
|
||||
let stt = 1;
|
||||
let globalRowIndex = 0;
|
||||
|
||||
if (currentTour?.legs && currentTour.legs.length > 0) {
|
||||
currentTour.legs.forEach((leg: any, legIdx: number) => {
|
||||
const legName = leg.note || `Chặng ${legIdx + 1}`;
|
||||
if (leg.locations && leg.locations.length > 0) {
|
||||
leg.locations.forEach((loc: any) => {
|
||||
const currentStt = stt++;
|
||||
|
||||
const arrivalStr = loc.arrivalTime ? `Đến: ${formatDateTime(loc.arrivalTime)}` : '';
|
||||
const departureStr = loc.departureTime ? `Đi: ${formatDateTime(loc.departureTime)}` : '';
|
||||
const timeText = [arrivalStr, departureStr].filter(Boolean).join('\n');
|
||||
|
||||
const locName = loc.name;
|
||||
const addressStr = loc.address ? `\n📍 Địa chỉ: ${loc.address}` : '';
|
||||
const locationText = `${locName}${addressStr}`;
|
||||
|
||||
const noteText = loc.notes || '';
|
||||
|
||||
const legLocations = (leg.locations || []).filter((loc: any) =>
|
||||
loc && (loc.plannedStart || loc.plannedEnd || loc.name)
|
||||
);
|
||||
|
||||
if (legLocations.length > 0) {
|
||||
const startRow = globalRowIndex;
|
||||
legRowRanges[leg.id] = { start: startRow, count: legLocations.length };
|
||||
|
||||
legLocations.forEach((loc: any) => {
|
||||
const timeStr = loc.plannedStart
|
||||
? formatDateTime(loc.plannedStart)
|
||||
: loc.plannedEnd
|
||||
? formatDateTime(loc.plannedEnd)
|
||||
: '';
|
||||
|
||||
const locName = loc.name || '';
|
||||
const addressStr = loc.address || '';
|
||||
const coordStr = loc.latitude && loc.longitude
|
||||
? `\n${loc.latitude}, ${loc.longitude}`
|
||||
: '';
|
||||
const locationText = [locName, addressStr, coordStr].filter(Boolean).join('\n');
|
||||
|
||||
const noteText = loc.note || '';
|
||||
|
||||
tableRows.push([
|
||||
currentStt,
|
||||
timeText,
|
||||
stt++,
|
||||
timeStr,
|
||||
legName,
|
||||
locationText,
|
||||
noteText
|
||||
]);
|
||||
globalRowIndex++;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -558,22 +571,67 @@ export const TourDetailPage = ({
|
||||
tableRows.push(["-", "-", "-", "Chưa có chặng hoặc địa điểm nào trong hành trình.", "-"]);
|
||||
}
|
||||
|
||||
// 4. Vẽ bảng dùng autoTable
|
||||
autoTable(doc, {
|
||||
head: [tableColumn],
|
||||
headStyles: {
|
||||
fillColor: [37, 99, 235],
|
||||
textColor: [255, 255, 255],
|
||||
fontStyle: 'bold',
|
||||
halign: 'center',
|
||||
valign: 'middle'
|
||||
},
|
||||
body: tableRows,
|
||||
startY: startY,
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [37, 99, 235], textColor: [255, 255, 255], fontStyle: 'normal' },
|
||||
styles: { font: "Roboto", fontSize: 9, cellPadding: 3, overflow: 'linebreak' },
|
||||
columnStyles: {
|
||||
0: { cellWidth: 12, halign: 'center' }, // STT
|
||||
1: { cellWidth: 50 }, // Ngày giờ
|
||||
2: { cellWidth: 45 }, // Chặng
|
||||
3: { cellWidth: 90 }, // Địa điểm
|
||||
4: { cellWidth: 70 } // Ghi chú
|
||||
styles: {
|
||||
font: "Roboto",
|
||||
fontSize: 9,
|
||||
cellPadding: 3,
|
||||
overflow: 'linebreak',
|
||||
valign: 'top'
|
||||
},
|
||||
margin: { left: 15, right: 15 }
|
||||
columnStyles: {
|
||||
0: { cellWidth: 12, halign: 'center', valign: 'middle' },
|
||||
1: { cellWidth: 50, halign: 'center', valign: 'middle' },
|
||||
2: { cellWidth: 45, halign: 'center', valign: 'middle' },
|
||||
3: { cellWidth: 90, valign: 'top' },
|
||||
4: { cellWidth: 70, valign: 'top' }
|
||||
},
|
||||
margin: { left: 15, right: 15 },
|
||||
didParseCell: (cell: any) => {
|
||||
if (cell.section === 'body') {
|
||||
if (cell.column.index === 2) {
|
||||
let assigned = false;
|
||||
for (const leg of currentTour?.legs || []) {
|
||||
const range = legRowRanges[leg.id];
|
||||
if (range && cell.row.index >= range.start && cell.row.index < range.start + range.count) {
|
||||
if (cell.row.index === range.start) {
|
||||
cell.rowSpan = range.count;
|
||||
} else {
|
||||
cell.rowSpan = 0;
|
||||
}
|
||||
assigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!assigned) {
|
||||
cell.rowSpan = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.column.index === 3) {
|
||||
const rowData = tableRows[cell.row.index];
|
||||
if (rowData && rowData[3]) {
|
||||
const coordMatch = rowData[3].match(/([\d.]+),\s*([\d.]+)/);
|
||||
if (coordMatch) {
|
||||
const lat = coordMatch[1];
|
||||
const lng = coordMatch[2];
|
||||
cell.cell.link = `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lng}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user