Cài đặt tính năng in bảng thống kê ra pdf
This commit is contained in:
1 parent
f52580717f
commit
75cf96898c
4 files changed
+310
-5
No files matched your search
@@ -1,7 +1,9 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Wallet, Users, Info, PieChart, Tag, Calendar, User, FileText, TrendingUp, BarChart3 } from 'lucide-react';
|
||||
import { Wallet, Users, Info, PieChart, Tag, Calendar, User, FileText, TrendingUp, BarChart3, Download } from 'lucide-react';
|
||||
import { useTourStore } from '@/store/useTourStore';
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { jsPDF } from 'jspdf';
|
||||
import autoTable from 'jspdf-autotable';
|
||||
|
||||
export const ExpenseManager = () => {
|
||||
const { legs, currentTour } = useTourStore();
|
||||
@@ -85,6 +87,69 @@ export const ExpenseManager = () => {
|
||||
};
|
||||
}, [legs, adults, children, discount]);
|
||||
|
||||
// Hàm xuất PDF
|
||||
const handleExportPDF = () => {
|
||||
const doc = new jsPDF();
|
||||
|
||||
// 1. Khai báo chuỗi Base64 của font (Đây là ví dụ, bạn thay bằng chuỗi thực tế từ Bước 1)
|
||||
const robotoBase64 = "AAEAAAAOAIAAAwBgR0RFRgslCyYAAU70AAAAQEdQT1OW7t6tAAFPNAAAUZJHU1VCLJENOgABoMgAAAQsT1MvMrivKbYAAAFoAAAAYFZETVhu6nZPAAASiAAABeBjbWFwe1eWmQAAGGgAAAh+Z2x5ZnKsuk0AAClMAAD8BGhlYWT3/7sxAAAA7AAAADZoaGVhCywJXgAAASQAAAAkaG10eMBzVnEAAAHIAAAQwGxvY2Hm+KbaAAAg6AAACGJtYXhwBE8BKgAAAUgAAAAgbmFtZfDWLcMAASVQAAAEo3Bvc3RDYN+ZAAEp9AAAJP0AAQAAAAEAAOqeVyxfDzz1AAsIAAAAAADE8BEuAAAAANAzXr36jf3VCTAIcwAAAAkAAgAAAAAAAAABAAAHbP4MAAAJSfqN/dgJMAABAAAAAAAAAAAAAAAAAAAEMAABAAAEMACwABYAeAAFAAEAAAAAAAAAAAAAAAAAAwABAAMEigGQAAUAAAWaBTMAAAEfBZoFMwAAA9EAZgIAAAAAAAAAAAAAAAAA4AAC/1AAIFsAAAAgAAAAAHB5cnMAQAAA//0GAP4AAGYHmgIAIAABn08BAAAEOgWwAAAAIAACAfsAAAAAAAAB+wAAAfsAAAKPAIgE+wBGBH4AbgXcAGkE+QBlAWUAZwK8AIUCyAAmA3IAHASJAE4BkgAdAjUAJQIbAJADTAASBH4AcwR+AKoEfgBdBH4AXgR+ADUEfgCaBH0AgwR+AE0EfgBwBH4AZAHwAIYBsQApBBEASARkAJgELgCGA8cASwcvAGoFOAAcBPsAqQU1AHcFPwCpBIwAqQRsAKkFcwB6BbQAqQItALcEagA1BQQAqQROAKkG/ACpBbQAqQWAAHYFDACpBYAAbQTtAKgEvwBQBMYAMQUwAIwFFwAcBxkAPQUEADkEzgAPBMoAVgIfAJIDSAAoAh8ACQNYAEADnAAEAnkAOQRaAG0EfQCMBDAAXASDAF8EPQBdAscAPAR9AGAEaACMAfEAjQHp/78EDgCNAfEAnAcDAIsEagCMBJAAWwR9AIwEjABfArUAjAQgAF8CnQAJBGkAiAPgACEGAwArA/cAKQPJABYD9wBYArUAQAHzAK8CtQATBXEAgwHzAIsEYQBpBKYAWwW0AGkE2AAfAesAkwToAFoDWABmBkkAWwOTAJMDwQBmBG4AfwZKAFoDqgB4Av0AggRHAGEC7wBCAu8APgKCAHsEiACaA+kAQwIWAJMB+wB0ArkAegOjAHoDwABmBdwAVQY1AFAGOQBvA8kARAd6//IERABZBYEAdgS6AKYEwgCLBsEATgSwAH4EkQBHBIkAWwScAJUFmwAeAfoAmwRzAJoETwAjAioAIwWLAKIEiACRB6EAaQdEAGEB/ACgArn/5AV/AGUEkwBbBZAAjATzAIgCA/+0BDcAYgPEAKkDjQCMA2oAgQHxAI0CrQB5AioAMgPGAHsC/ABeAloAfgAA/TYAAP2GAAD8iwAA/V4AAPwnAe/9OAINALcECwBxAhcAkwRzALEFpAAfBXIAZwU+ADIEkQB4BbUAsgSRAEUFuwBNBYkAWgVSAHEEhQBkBL0AoAQCAC4EiABgBFAAYwQlAG0EiACRBI4AegKXAMMEbgAlA+wAZQTFACkEiACRBE0AZQSIAGAELABRBF0AjwWjAFcFmgBfBpcAegShAHkEQv/aBkgASgX/ACoFZAB7CJkAMQikALEGggA+BbQAsAULAKIGBAAyB0MAGwS/AFAFtACxBakALwUHAE0GLQBTBdkArwV6AJYHhwCwB8AAsAYSABEG6wCyBQUAowVkAJMHJwC3BRgAWQRsAGEEkgCdA1sAmgTUAC4GIAAVBBAAWASeAJwEUgCcBKAAKAXvAJ0EnQCcBJ4AnAPYACgFzQBkBL0AnARZAGcGeACcBp8AkQT3AB4GNgCdBFgAnQRNAGQGiACdBGQALwRo/+cETgBnBskAJwbkAJwEif/9BJ4AnAcIAJwGKwCBBFb/3AcsALgF+QCZBNIAKARGAA8HDADKBgwAvAbRAJMF4QCWCQUAtwfRAJsEJABQA9sATAVyAGcEjABbBQoAFgQDAC4FcgBnBIkAWwcBAJwGJAB+BwkAnAYsAIEFMgB1BEcAZAT9AHQAAPxnAAD8cQAA/WYAAP2kAAD6jQAA+qQEVv/cBRsAqASKAIwEZACiA5AAkQTbALEEBgCRBQkAowR+AJoGjABFBYQAPgfPAKgFtACRCDEAsAb0AJEF7gBxBNMAbQctADQFXAAfBXAAlgRrAIMFcACKBi8APwS+/90FCQCjBFoAmgWyALEEiACRBYcAXQSoAGgEqABpBLcAOgNJADsE9gBXBpQAWQbkAGQGVgA2BSsAMQRKAFMECAB5B8EARAZ1AD8H+wCpBqEAkAT2AHYEHQBlBa0AIwUgAEYFZACWBBQAAAgpAAAEFAAACCkAAAK5AAACCgAAAVwAAAR/AAACMAAAAaIAAADRAAAAAAAABUAAogY/AJADpgANAZkAYAGZADABlwAkAZoAUALUAGgC2wA8AsEAJARpAEYEjwBXArIAigPEAJQFWgCUB6oARAJmAGwCZgBZA6MAOwLvADYDYAB6BKYAWwaRAKcEfgBfBegAewPOAGgIOgCiBQEAZwUXAJgGJgBUBtcAZAbPAGMGagBZBI8AagWOAKkErwBFBJIAqATFAD8IOgBiAgz/sASCAGUEZACYBBEAPgQvAIUECAArAkwAtQKPAG4CAwBcBG4AHwAAAAAIMwBbCDUAXALvAFADC/+iA/P/XgPvAG4DpABbAf0AnwSFABMEUACKBHwAYASAAIoD5gCKA8sAigSsAGME4wCKAegAlwPPACsEVACKA7QAigYCAIoE4wCKBLsAYARcAIoEuwBZBEoAigQgAEMEJgAoBHwAdARnABQGFQAxBFQAJgQrAA0EIwBHBA8AYwQPAJgEDwBOBA8ATgQPADAEDwCABA4AcgQPAEIEDwBgBA8AVQOkAIMC7wB6Au8AQgLvAD4C7wA2Au8AWwLvAFYC7wA6Au8ATwLvAEkDlgCPArUAngNHAIEBkQBnAiUAGwH8/7YEDgBpBDoAHgN5AFcD9ABpBG8AWgTDAGQEiABgBUwAsQUkALIEEwCSBT0AsgQPAJIDnwCBA54AgQR+AE0FfgCDBYEAfgWuAH4FkwCyBeAAfgXjAH4D1QCgBIIAgwRYAA8EzwA+BGsAZQQuAEoEuQCCBQAAeAQgADsEfQCMBDIAXASTAFsEjABbBJ4AWgSNAIwEnABbBDAAXAQ9AF0EfQBgBH4AhQR+AFoFPwCpBYAAdwRaAG0DagCBAfsAAAI1ACUFXQAlBV0AJQSGAAAExgAxAp3/9AU4ABwFOAAcBTgAHAU4ABwFOAAcBTgAHAU4ABwFNQB3BIwAqQSMAKkEjACpBIwAqQIt/+ACLQCwAi3/6QIt/9YFtACpBYAAdgWAAHYFgAB2BYAAdgWAAHYFMACMBTAAjAUwAIwFMACMBM4ADwRaAG0EWgBtBFoAbQRaAG0EWgBtBFoAbQRaAG0EMABcBD0AXQQ9AF0EPQBdBD0AXQH6/8YB+gCWAfr/zwH6/7wEagCMBJAAWwSQAFsEkABbBJAAWwSQAFsEaQCIBGkAiARpAIgEaQCIA8kAFgPJABYFOAAcBFoAbQU4ABwEWgBtBTgAHARaAG0FNQB3BDAAXAU1AHcEMABcBTUAdwQwAFwFNQB3BDAAXAU/AKkFGQBfBIwAqQQ9AF0EjACpBD0AXQSMAKkEPQBdBIwAqQQ9AF0EjACpBD0AXQVzAHoEfQBgBXMAegR9AGAFcwB6BH0AYAVzAHoEfQBgBbQAqQRoAIwCLf+3Afr/nQIt/7YB+v+cAi3/7AH6/9ICLQAJAfH/+gItAKoGlwC3A9oAjQRqADUCA/+0BQQAqQQOAI0ETgChAfEAkwROAKkB8QBXBE4AqQKHAJwETgCpAs0AnAW0AKkEagCMBbQAqQRqAIwFtACpBGoAjARq/84FgAB2BJAAWwWAAHYEkABbBYAAdgSQAFsE7QCoArUAjATtAKgCtQBTBO0AqAK1AGMEvwBQBCAAXwS/AFAEIABfBL8AUAQgAF8EvwBQBCAAXwS/AFAEIABfBMYAMQKdAAkExgAxAp0ACQTGADECxQAJBTAAjARpAIgFMACMBGkAiAUwAIwEaQCIBTAAjARpAIgFMACMBGkAiAUwAIwEaQCIBxkAPQYDACsEzgAPA8kAFgTOAA8EygBWA/cAWATKAFYD9wBYBMoAVgP3AFgHev/yBsEATgWBAHYEiQBbBID/wQSA/8EEJgAoBIUAEwSFABMEhQATBIUAEwSFABMEhQATBIUAEwR8AGAD5gCKA+YAigPmAIoD5gCKAej/vgHoAI4B6P/HAej/tATjAIoEuwBgBLsAYAS7AGAEuwBgBLsAYAR8AHQEfAB0BHwAdAR8AHQEKwANBIUAEwSFABMEhQATBHwAYAR8AGAEfABgBHwAYASAAIoD5gCKA+YAigPmAIoD5gCKA+YAigSsAGMErABjBKwAYwSsAGME4wCKAej/lQHo/5QB6P/KAej/4wHoAIkDzwArBFQAigO0AIIDtACKA7QAigO0AIoE4wCKBOMAigTjAIoEuwBgBLsAYAS7AGAESgCKBEoAigRKAIoEIABDBCAAQwQgAEMEIABDBCYAKAQmACgEfAB0BHwAdAR8AHQEfAB0BHwAdAR8AHQGFQAxBCsADQQrAA0EIwBHBCMARwQjAEcIQABDBTgAHATw//AGGP/+ApEABAWUAD0FMv94BWYAMwKX/5sFOAAcBPsAqQSMAKkEygBWBbQAqQItALcFBACpBvwAqQW0AKkFgAB2BQwAqQTGADEEzgAPBQQAOQIt/9YEzgAPBIUAZARQAGMEiACRApcAwwRdAI8EcwCaBJAAWwSIAJoD4AAhA/cAKQKX/+YEXQCPBJAAWwRdAI8Gl
Line truncated
|
||||
|
||||
// Khai báo chuỗi Base64 của Logo (Thay bằng chuỗi thực tế của bạn)
|
||||
const logoBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAy...";
|
||||
|
||||
// 2. Thêm font vào Virtual File System của jsPDF
|
||||
doc.addFileToVFS("Roboto-Regular.ttf", robotoBase64);
|
||||
|
||||
// 3. Đăng ký font với tên gọi 'Roboto'
|
||||
doc.addFont("Roboto-Regular.ttf", "Roboto", "normal");
|
||||
|
||||
// 4. Thiết lập font mặc định cho tài liệu
|
||||
doc.setFont("Roboto");
|
||||
|
||||
// 5. Thêm Logo vào PDF (x, y, width, height)
|
||||
doc.addImage(logoBase64, 'PNG', 14, 10, 15, 15);
|
||||
|
||||
// Bây giờ bạn có thể viết tiếng Việt có dấu thoải mái
|
||||
doc.setFontSize(18);
|
||||
doc.setTextColor(37, 99, 235); // Blue-600
|
||||
doc.text("BÁO CÁO CHI PHÍ TOUR", 35, 21); // Đẩy tiêu đề sang phải logo
|
||||
|
||||
doc.setFontSize(11);
|
||||
doc.setTextColor(100);
|
||||
doc.text(`Chuyến đi: ${currentTour?.title || 'Hành trình'}`, 14, 40);
|
||||
doc.text(`Ngày xuất: ${format(new Date(), 'dd/MM/yyyy HH:mm')}`, 14, 47);
|
||||
|
||||
doc.setTextColor(0);
|
||||
doc.setFontSize(12);
|
||||
doc.text(`Tổng chi: ${stats.total.toLocaleString()} VNĐ`, 14, 57);
|
||||
doc.text(`Định mức: Người lớn: ${stats.adultPrice.toLocaleString()}đ | Trẻ em: ${stats.childPrice.toLocaleString()}đ`, 14, 64);
|
||||
|
||||
// Cấu hình bảng dữ liệu
|
||||
const tableColumn = ["STT", "Ngày giờ", "Chặng", "Dịch vụ", "Số tiền (VNĐ)", "Người chi"];
|
||||
const tableRows = stats.list.map((exp: any, index: number) => [
|
||||
index + 1,
|
||||
exp.date ? format(new Date(exp.date), 'dd/MM HH:mm') : '--/--',
|
||||
`Chặng ${exp.legSequence}`,
|
||||
exp.description || 'Không có mô tả',
|
||||
Number(exp.amount).toLocaleString(),
|
||||
exp.paidBy?.name || 'Chưa rõ'
|
||||
]);
|
||||
|
||||
autoTable(doc, {
|
||||
head: [tableColumn],
|
||||
body: tableRows,
|
||||
startY: 75, // Đẩy bảng xuống để không đè lên thông tin phía trên
|
||||
theme: 'grid',
|
||||
headStyles: { fillColor: [37, 99, 235], textColor: [255, 255, 255], fontStyle: 'normal' }, // Roboto Bold nếu bạn thêm font bold
|
||||
styles: { font: "Roboto", fontSize: 8, cellPadding: 3 }, // Quan trọng: Khai báo font ở đây cho autoTable
|
||||
columnStyles: {
|
||||
4: { halign: 'right' }
|
||||
}
|
||||
});
|
||||
|
||||
doc.save(`Chi-phi-tour-${currentTour?.title || 'export'}.pdf`);
|
||||
};
|
||||
|
||||
const categoryLabels: Record<string, { label: string, color: string }> = {
|
||||
ACCOMMODATION: { label: 'Chỗ ở', color: 'bg-blue-500' },
|
||||
FOOD: { label: 'Ăn uống', color: 'bg-orange-500' },
|
||||
@@ -212,9 +277,17 @@ export const ExpenseManager = () => {
|
||||
|
||||
{/* Bảng thống kê chi tiết chi phí */}
|
||||
<div className="bg-white rounded-3xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
<div className="p-6 border-b border-gray-100 bg-gray-50/30 flex items-center gap-2">
|
||||
<FileText className="w-5 h-5 text-blue-600" />
|
||||
<h3 className="font-black text-gray-900 uppercase tracking-tight">Bảng kê chi tiết các khoản chi</h3>
|
||||
<div className="p-6 border-b border-gray-100 bg-gray-50/30 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="w-5 h-5 text-blue-600" />
|
||||
<h3 className="font-black text-gray-900 uppercase tracking-tight">Bảng kê chi tiết các khoản chi</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleExportPDF}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-xl text-[11px] font-black uppercase tracking-wider transition-all shadow-lg shadow-blue-200 active:scale-95"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" /> Xuất PDF
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
|
||||
Reference in new issue
Block a user