trello_fake/lib/exportPdf.ts

281 lines
7.1 KiB
TypeScript
Raw Permalink Normal View History

import { jsPDF } from "jspdf";
import autoTable from "jspdf-autotable";
import { Project, STATUS_CONFIG } from "@/types/project";
import { Task, TASK_STATUS_CONFIG, TASK_PRIORITY_CONFIG, TaskStatus, TaskPriority } from "@/types/task";
// Formateadores
function formatCurrency(amount: number): string {
return new Intl.NumberFormat("es-ES", {
style: "currency",
currency: "EUR",
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount);
}
function formatDate(dateString: string | null | undefined): string {
if (!dateString) return "-";
try {
return new Date(dateString).toLocaleDateString("es-ES", {
day: "2-digit",
month: "short",
year: "numeric",
});
} catch {
return "-";
}
}
function getStatusLabel(status: string): string {
return STATUS_CONFIG[status as keyof typeof STATUS_CONFIG]?.label || status;
}
function getTaskStatusLabel(status: TaskStatus): string {
return TASK_STATUS_CONFIG[status]?.label || status;
}
function getTaskPriorityLabel(priority: TaskPriority): string {
return TASK_PRIORITY_CONFIG[priority]?.label || priority;
}
/**
* Exporta una lista de proyectos a PDF (vista resumen)
*/
export function exportProjectsToPDF(projects: Project[]): void {
const doc = new jsPDF();
const pageWidth = doc.internal.pageSize.getWidth();
// Título
doc.setFontSize(20);
doc.setTextColor(59, 130, 246); // blue-500
doc.text("Listado de Proyectos", pageWidth / 2, 20, { align: "center" });
// Fecha de generación
doc.setFontSize(10);
doc.setTextColor(100);
doc.text(
`Generado el ${new Date().toLocaleDateString("es-ES", {
day: "numeric",
month: "long",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
})}`,
pageWidth / 2,
28,
{ align: "center" }
);
// Resumen
doc.setFontSize(11);
doc.setTextColor(60);
doc.text(`Total de proyectos: ${projects.length}`, 14, 40);
// Tabla de proyectos
const tableData = projects.map((project) => [
project.ref,
project.name,
project.client,
getStatusLabel(project.status),
`${project.progress.toFixed(0)}%`,
formatCurrency(project.budget),
formatDate(project.startDate),
formatDate(project.endDate),
]);
autoTable(doc, {
startY: 48,
head: [
["Ref", "Nombre", "Cliente", "Estado", "Progreso", "Presupuesto", "Inicio", "Fin"],
],
body: tableData,
styles: {
fontSize: 9,
cellPadding: 3,
},
headStyles: {
fillColor: [59, 130, 246], // blue-500
textColor: 255,
fontStyle: "bold",
},
alternateRowStyles: {
fillColor: [248, 250, 252], // slate-50
},
columnStyles: {
0: { cellWidth: 20 }, // Ref
1: { cellWidth: 40 }, // Nombre
2: { cellWidth: 30 }, // Cliente
3: { cellWidth: 22 }, // Estado
4: { cellWidth: 18 }, // Progreso
5: { cellWidth: 25 }, // Presupuesto
6: { cellWidth: 22 }, // Inicio
7: { cellWidth: 22 }, // Fin
},
});
// Guardar
const fileName = `proyectos_${new Date().toISOString().split("T")[0]}.pdf`;
doc.save(fileName);
}
/**
* Exporta un proyecto con sus tareas a PDF (vista detallada)
*/
export function exportProjectDetailToPDF(project: Project, tasks: Task[]): void {
const doc = new jsPDF();
const pageWidth = doc.internal.pageSize.getWidth();
// === HEADER ===
doc.setFontSize(22);
doc.setTextColor(59, 130, 246); // blue-500
doc.text(project.name, pageWidth / 2, 20, { align: "center" });
doc.setFontSize(12);
doc.setTextColor(100);
doc.text(`Referencia: ${project.ref}`, pageWidth / 2, 28, { align: "center" });
// Fecha de generación
doc.setFontSize(9);
doc.text(
`Generado el ${new Date().toLocaleDateString("es-ES", {
day: "numeric",
month: "long",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
})}`,
pageWidth / 2,
35,
{ align: "center" }
);
// === INFORMACIÓN DEL PROYECTO ===
let yPos = 48;
doc.setFontSize(14);
doc.setTextColor(30);
doc.text("Información del Proyecto", 14, yPos);
yPos += 8;
// Tabla de información
const projectInfo = [
["Cliente", project.client || "-"],
["Estado", getStatusLabel(project.status)],
["Progreso", `${project.progress.toFixed(0)}%`],
["Presupuesto", formatCurrency(project.budget)],
["Fecha de inicio", formatDate(project.startDate)],
["Fecha de fin", formatDate(project.endDate)],
];
autoTable(doc, {
startY: yPos,
body: projectInfo,
styles: {
fontSize: 10,
cellPadding: 4,
},
columnStyles: {
0: { fontStyle: "bold", cellWidth: 45, textColor: [100, 100, 100] },
1: { cellWidth: 60 },
},
theme: "plain",
margin: { left: 14 },
});
// Obtener posición después de la tabla
yPos = (doc as jsPDF & { lastAutoTable?: { finalY: number } }).lastAutoTable?.finalY || yPos + 50;
// Descripción si existe
if (project.description) {
yPos += 10;
doc.setFontSize(14);
doc.setTextColor(30);
doc.text("Descripción", 14, yPos);
yPos += 6;
doc.setFontSize(10);
doc.setTextColor(60);
const descriptionLines = doc.splitTextToSize(project.description, pageWidth - 28);
doc.text(descriptionLines, 14, yPos);
yPos += descriptionLines.length * 5 + 5;
}
// === TAREAS ===
if (tasks.length > 0) {
yPos += 10;
// Verificar si necesitamos nueva página
if (yPos > 250) {
doc.addPage();
yPos = 20;
}
doc.setFontSize(14);
doc.setTextColor(30);
doc.text(`Tareas (${tasks.length})`, 14, yPos);
yPos += 8;
// Tabla de tareas
const taskData = tasks.map((task) => [
task.ref,
task.title,
getTaskStatusLabel(task.status),
getTaskPriorityLabel(task.priority),
`${task.progress}%`,
formatDate(task.startDate),
formatDate(task.endDate),
]);
autoTable(doc, {
startY: yPos,
head: [["Ref", "Tarea", "Estado", "Prioridad", "Progreso", "Inicio", "Fin"]],
body: taskData,
styles: {
fontSize: 8,
cellPadding: 3,
},
headStyles: {
fillColor: [147, 51, 234], // purple-600
textColor: 255,
fontStyle: "bold",
},
alternateRowStyles: {
fillColor: [248, 250, 252], // slate-50
},
columnStyles: {
0: { cellWidth: 18 }, // Ref
1: { cellWidth: 50 }, // Tarea
2: { cellWidth: 25 }, // Estado
3: { cellWidth: 22 }, // Prioridad
4: { cellWidth: 18 }, // Progreso
5: { cellWidth: 22 }, // Inicio
6: { cellWidth: 22 }, // Fin
},
});
} else {
yPos += 15;
doc.setFontSize(11);
doc.setTextColor(100);
doc.text("Este proyecto no tiene tareas asignadas.", 14, yPos);
}
// === FOOTER ===
const pageCount = doc.getNumberOfPages();
for (let i = 1; i <= pageCount; i++) {
doc.setPage(i);
doc.setFontSize(8);
doc.setTextColor(150);
doc.text(
`Página ${i} de ${pageCount}`,
pageWidth / 2,
doc.internal.pageSize.getHeight() - 10,
{ align: "center" }
);
}
// Guardar
const fileName = `proyecto_${project.ref}_${new Date().toISOString().split("T")[0]}.pdf`;
doc.save(fileName);
}