feat: unified 0 and 90 degree PDF envelope and category descriptors
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import { Document, Page, Text, View, StyleSheet, Image as PdfImage, pdf } from '@react-pdf/renderer';
|
||||
import { useWindStore } from '../store/appStore';
|
||||
import { useCaptureStore } from '../store/captureStore';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: { flexDirection: 'column', padding: 40, fontSize: 10, fontFamily: 'Helvetica', color: '#333' },
|
||||
header: { marginBottom: 20, borderBottom: '2pt solid #6b21a8', paddingBottom: 10 },
|
||||
title: { fontSize: 20, fontWeight: 'bold', color: '#6b21a8' },
|
||||
subtitle: { fontSize: 10, color: '#666', marginTop: 4 },
|
||||
section: { marginTop: 15, marginBottom: 10 },
|
||||
sectionTitle: { fontSize: 14, fontWeight: 'bold', marginBottom: 8, color: '#111' },
|
||||
row: { flexDirection: 'row', marginBottom: 4 },
|
||||
label: { width: 200, fontWeight: 'bold' },
|
||||
value: { flex: 1 },
|
||||
text: { fontSize: 10, marginBottom: 4, lineHeight: 1.4 },
|
||||
table: { display: 'flex', flexDirection: 'column', marginTop: 10, borderTop: '1pt solid #ccc', borderLeft: '1pt solid #ccc' },
|
||||
tableRow: { flexDirection: 'row' },
|
||||
tableHeader: { backgroundColor: '#f3f4f6', fontWeight: 'bold' },
|
||||
tableCell: { flex: 1, padding: 5, borderRight: '1pt solid #ccc', borderBottom: '1pt solid #ccc', textAlign: 'center' },
|
||||
tableCellFirst: { flex: 1, padding: 5, borderRight: '1pt solid #ccc', borderBottom: '1pt solid #ccc', textAlign: 'left' },
|
||||
footer: { position: 'absolute', bottom: 30, left: 40, right: 40, textAlign: 'center', color: '#999', fontSize: 8, borderTop: '1pt solid #eaeaea', paddingTop: 10 },
|
||||
sceneImage: { width: 480, height: 270, objectFit: 'contain', marginVertical: 8, border: '1pt solid #ddd' },
|
||||
sceneCaption: { fontSize: 8, color: '#666', fontStyle: 'italic', textAlign: 'center', marginBottom: 8 },
|
||||
});
|
||||
|
||||
export interface GenericPDFSection {
|
||||
title: string;
|
||||
type: 'table' | 'text' | 'grid';
|
||||
content?: string;
|
||||
tableHeaders?: string[];
|
||||
tableRows?: (string | number)[][];
|
||||
gridItems?: { label: string; value: string | number }[];
|
||||
}
|
||||
|
||||
export interface GenericPDFProps {
|
||||
moduleName: string;
|
||||
sections: GenericPDFSection[];
|
||||
wind: ReturnType<typeof useWindStore.getState>;
|
||||
sceneImage?: string | null;
|
||||
}
|
||||
|
||||
const GenericReportDocument = ({ moduleName, sections, wind, sceneImage }: GenericPDFProps) => {
|
||||
return (
|
||||
<Document>
|
||||
<Page size="A4" style={styles.page}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>VentoApp — Memória de Cálculo</Text>
|
||||
<Text style={styles.subtitle}>Cargas de Vento: {moduleName} — NBR 6123:2023</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>1. Parâmetros Globais do Vento e Pressão Dinâmica</Text>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Velocidade Básica (V₀):</Text>
|
||||
<Text style={styles.value}>{wind.v0} m/s (conforme Figura 1 e Anexo C da NBR 6123)</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Fator Topográfico (S₁):</Text>
|
||||
<Text style={styles.value}>{wind.s1} (conforme Seção 5.2)</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Fator de Rugosidade (S₂):</Text>
|
||||
<Text style={styles.value}>{wind.s2.toFixed(3)} (Categoria {wind.terrainCategory}, Classe {wind.structureClass}, conforme Tabela 2)</Text>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.label}>Fator Estatístico (S₃):</Text>
|
||||
<Text style={styles.value}>{wind.s3.toFixed(2)} (Grupo {wind.s3Group}, conforme Tabela 4)</Text>
|
||||
</View>
|
||||
|
||||
<View style={{ marginTop: 10, padding: 8, backgroundColor: '#f9fafb', borderLeft: '3pt solid #6b21a8' }}>
|
||||
<Text style={{ fontSize: 11, fontWeight: 'bold', marginBottom: 4 }}>Memória de Cálculo (Sec 4.2 e 4.3):</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 4 }}>
|
||||
Vₖ = V₀ × S₁ × S₂ × S₃
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 8, color: '#4b5563' }}>
|
||||
Vₖ = {wind.v0} × {wind.s1} × {wind.s2.toFixed(3)} × {wind.s3.toFixed(2)} = {wind.vk.toFixed(2)} m/s
|
||||
</Text>
|
||||
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', marginBottom: 4 }}>
|
||||
q = 0,613 × (Vₖ)²
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, fontFamily: 'Courier', color: '#4b5563' }}>
|
||||
q = 0,613 × ({wind.vk.toFixed(2)})² = {(0.613 * Math.pow(wind.vk, 2) / 1000).toFixed(4)} kN/m²
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{sceneImage && (
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>2. Modelo 3D (Captura de Cena)</Text>
|
||||
<PdfImage src={sceneImage} style={styles.sceneImage} />
|
||||
<Text style={styles.sceneCaption}>
|
||||
Vista isométrica capturada em tempo real pelo usuário.
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{sections.map((sec, idx) => (
|
||||
<View style={styles.section} key={idx} wrap={false}>
|
||||
<Text style={styles.sectionTitle}>
|
||||
{sceneImage ? idx + 3 : idx + 2}. {sec.title}
|
||||
</Text>
|
||||
|
||||
{sec.type === 'text' && sec.content && (
|
||||
<Text style={styles.text}>{sec.content}</Text>
|
||||
)}
|
||||
|
||||
{sec.type === 'grid' && sec.gridItems && (
|
||||
sec.gridItems.map((item, i) => (
|
||||
<View style={styles.row} key={i}>
|
||||
<Text style={styles.label}>{item.label}:</Text>
|
||||
<Text style={styles.value}>{item.value}</Text>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
|
||||
{sec.type === 'table' && sec.tableHeaders && sec.tableRows && (
|
||||
<View style={styles.table}>
|
||||
<View style={[styles.tableRow, styles.tableHeader]}>
|
||||
{sec.tableHeaders.map((th, i) => (
|
||||
<Text key={i} style={i === 0 ? styles.tableCellFirst : styles.tableCell}>
|
||||
{th}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
{sec.tableRows.map((tr, rIdx) => (
|
||||
<View style={styles.tableRow} key={rIdx}>
|
||||
{tr.map((tc, cIdx) => (
|
||||
<Text key={cIdx} style={cIdx === 0 ? styles.tableCellFirst : styles.tableCell}>
|
||||
{tc}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
|
||||
<Text style={styles.footer}>
|
||||
Gerado por VentoApp — Ferramenta de Auxílio ao Cálculo Estrutural (NBR 6123:2023)
|
||||
</Text>
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
};
|
||||
|
||||
export async function exportGenericToPDF(moduleName: string, sections: GenericPDFSection[]) {
|
||||
const wind = useWindStore.getState();
|
||||
const sceneImage = useCaptureStore.getState().capturedImage;
|
||||
|
||||
const blob = await pdf(
|
||||
<GenericReportDocument moduleName={moduleName} sections={sections} wind={wind} sceneImage={sceneImage} />
|
||||
).toBlob();
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `memoria_calculo_${moduleName.toLowerCase().replace(/\s+/g, '_')}.pdf`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
Reference in New Issue
Block a user