| import io |
| from typing import List, Dict, Any |
| import pandas as pd |
| from datetime import datetime, date |
|
|
| |
| from reportlab.lib.pagesizes import letter |
| from reportlab.lib import colors |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
|
|
| class ReportGenerator: |
| @staticmethod |
| def to_csv(data: List[Dict[str, Any]]) -> str: |
| """ |
| Converts list of dicts to CSV string. |
| """ |
| if not data: |
| return "" |
| df = pd.DataFrame(data) |
| return df.to_csv(index=False) |
|
|
| @staticmethod |
| def to_xlsx(data: List[Dict[str, Any]], sheet_name: str = "Report") -> bytes: |
| """ |
| Converts list of dicts to Excel binary stream. |
| """ |
| output = io.BytesIO() |
| if not data: |
| |
| df = pd.DataFrame([{"Message": "No data available"}]) |
| else: |
| df = pd.DataFrame(data) |
| |
| with pd.ExcelWriter(output, engine='openpyxl') as writer: |
| df.to_excel(writer, index=False, sheet_name=sheet_name) |
| |
| |
| workbook = writer.book |
| worksheet = writer.sheets[sheet_name] |
| |
| |
| for col in worksheet.columns: |
| max_len = max(len(str(cell.value or '')) for cell in col) |
| col_letter = col[0].column_letter |
| worksheet.column_dimensions[col_letter].width = max(max_len + 3, 10) |
| |
| output.seek(0) |
| return output.getvalue() |
|
|
| @staticmethod |
| def to_pdf(title: str, headers: List[str], rows: List[List[Any]], metadata: Dict[str, str] = None) -> bytes: |
| """ |
| Generates a premium, clean PDF report using ReportLab. |
| """ |
| buffer = io.BytesIO() |
| doc = SimpleDocTemplate( |
| buffer, |
| pagesize=letter, |
| rightMargin=36, |
| leftMargin=36, |
| topMargin=36, |
| bottomMargin=36 |
| ) |
| |
| styles = getSampleStyleSheet() |
| |
| |
| title_style = ParagraphStyle( |
| name='ReportTitle', |
| parent=styles['Heading1'], |
| fontName='Helvetica-Bold', |
| fontSize=22, |
| leading=26, |
| textColor=colors.HexColor('#0F172A'), |
| spaceAfter=15 |
| ) |
| |
| meta_label_style = ParagraphStyle( |
| name='MetaLabel', |
| parent=styles['Normal'], |
| fontName='Helvetica-Bold', |
| fontSize=10, |
| leading=14, |
| textColor=colors.HexColor('#475569') |
| ) |
| |
| meta_val_style = ParagraphStyle( |
| name='MetaVal', |
| parent=styles['Normal'], |
| fontName='Helvetica', |
| fontSize=10, |
| leading=14, |
| textColor=colors.HexColor('#0F172A') |
| ) |
| |
| table_header_style = ParagraphStyle( |
| name='TableHeader', |
| parent=styles['Normal'], |
| fontName='Helvetica-Bold', |
| fontSize=9, |
| leading=12, |
| textColor=colors.white |
| ) |
| |
| table_cell_style = ParagraphStyle( |
| name='TableCell', |
| parent=styles['Normal'], |
| fontName='Helvetica', |
| fontSize=9, |
| leading=12, |
| textColor=colors.HexColor('#334155') |
| ) |
|
|
| elements = [] |
| |
| |
| elements.append(Paragraph(title, title_style)) |
| elements.append(Spacer(1, 10)) |
| |
| |
| if metadata: |
| meta_data = [] |
| keys = list(metadata.keys()) |
| for i in range(0, len(keys), 2): |
| row = [] |
| |
| k1 = keys[i] |
| row.extend([Paragraph(k1, meta_label_style), Paragraph(metadata[k1], meta_val_style)]) |
| |
| if i + 1 < len(keys): |
| k2 = keys[i+1] |
| row.extend([Paragraph(k2, meta_label_style), Paragraph(metadata[k2], meta_val_style)]) |
| else: |
| row.extend([Paragraph("", meta_label_style), Paragraph("", meta_val_style)]) |
| meta_data.append(row) |
| |
| meta_table = Table(meta_data, colWidths=[100, 170, 100, 170]) |
| meta_table.setStyle(TableStyle([ |
| ('ALIGN', (0,0), (-1,-1), 'LEFT'), |
| ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), |
| ('BOTTOMPADDING', (0,0), (-1,-1), 4), |
| ('TOPPADDING', (0,0), (-1,-1), 4), |
| ])) |
| elements.append(meta_table) |
| elements.append(Spacer(1, 20)) |
| |
| |
| line_table = Table([[""]], colWidths=[540], rowHeights=[1]) |
| line_table.setStyle(TableStyle([ |
| ('LINEABOVE', (0,0), (-1,-1), 1, colors.HexColor('#E2E8F0')), |
| ])) |
| elements.append(line_table) |
| elements.append(Spacer(1, 15)) |
| |
| |
| table_data = [] |
| |
| table_data.append([Paragraph(h, table_header_style) for h in headers]) |
| |
| |
| for r in rows: |
| table_data.append([Paragraph(str(cell), table_cell_style) for cell in r]) |
| |
| |
| col_width = 540 / len(headers) |
| data_table = Table(table_data, colWidths=[col_width] * len(headers)) |
| |
| |
| grid_style = TableStyle([ |
| ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#0F172A')), |
| ('ALIGN', (0,0), (-1,-1), 'LEFT'), |
| ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), |
| ('TOPPADDING', (0,0), (-1,-1), 6), |
| ('BOTTOMPADDING', (0,0), (-1,-1), 6), |
| ('LEFTPADDING', (0,0), (-1,-1), 8), |
| ('RIGHTPADDING', (0,0), (-1,-1), 8), |
| ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#F1F5F9')), |
| ]) |
| |
| |
| for i in range(1, len(table_data)): |
| if i % 2 == 0: |
| grid_style.add('BACKGROUND', (0, i), (-1, i), colors.HexColor('#F8FAFC')) |
| |
| data_table.setStyle(grid_style) |
| elements.append(data_table) |
| |
| |
| doc.build(elements) |
| buffer.seek(0) |
| return buffer.getvalue() |
|
|