Spaces:
Sleeping
Sleeping
File size: 6,605 Bytes
9bc686b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | import io
from typing import List, Dict, Any
import pandas as pd
from datetime import datetime, date
# ReportLab imports for PDF generation
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:
# Create an empty excel file
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)
# Format sheet with openpyxl
workbook = writer.book
worksheet = writer.sheets[sheet_name]
# Auto-fit columns
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()
# Custom styles
title_style = ParagraphStyle(
name='ReportTitle',
parent=styles['Heading1'],
fontName='Helvetica-Bold',
fontSize=22,
leading=26,
textColor=colors.HexColor('#0F172A'), # Slate 900
spaceAfter=15
)
meta_label_style = ParagraphStyle(
name='MetaLabel',
parent=styles['Normal'],
fontName='Helvetica-Bold',
fontSize=10,
leading=14,
textColor=colors.HexColor('#475569') # Slate 600
)
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') # Slate 700
)
elements = []
# Add Title
elements.append(Paragraph(title, title_style))
elements.append(Spacer(1, 10))
# Add Metadata Block
if metadata:
meta_data = []
keys = list(metadata.keys())
for i in range(0, len(keys), 2):
row = []
# First col
k1 = keys[i]
row.extend([Paragraph(k1, meta_label_style), Paragraph(metadata[k1], meta_val_style)])
# Second col
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))
# Draw a line
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))
# Prepare Data Table
table_data = []
# Header row
table_data.append([Paragraph(h, table_header_style) for h in headers])
# Data rows
for r in rows:
table_data.append([Paragraph(str(cell), table_cell_style) for cell in r])
# Calculate widths dynamically
col_width = 540 / len(headers)
data_table = Table(table_data, colWidths=[col_width] * len(headers))
# Table Styling
grid_style = TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#0F172A')), # Dark Header
('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')),
])
# Alternating row colors
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)
# Build Document
doc.build(elements)
buffer.seek(0)
return buffer.getvalue()
|