feat: introduce new PDF layouts and modules, enhance shared UI components, and add wording guidelines.
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
StyleSheet as PDFStyleSheet,
|
||||
} from '@react-pdf/renderer';
|
||||
import { pdfStyles, Header, Footer, FoldingMarks, DocumentTitle } from './pdf/SharedUI';
|
||||
import { SimpleLayout } from './pdf/SimpleLayout';
|
||||
|
||||
const localStyles = PDFStyleSheet.create({
|
||||
sectionContainer: {
|
||||
@@ -52,9 +53,10 @@ interface AgbsPDFProps {
|
||||
state: any;
|
||||
headerIcon?: string;
|
||||
footerLogo?: string;
|
||||
mode?: 'estimation' | 'full';
|
||||
}
|
||||
|
||||
export const AgbsPDF = ({ state, headerIcon, footerLogo }: AgbsPDFProps) => {
|
||||
export const AgbsPDF = ({ state, headerIcon, footerLogo, mode = 'full' }: AgbsPDFProps) => {
|
||||
const date = new Date().toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
@@ -74,59 +76,79 @@ export const AgbsPDF = ({ state, headerIcon, footerLogo }: AgbsPDFProps) => {
|
||||
iban: "DE50 1001 1001 2620 4328 65"
|
||||
};
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<DocumentTitle title="Allgemeine Geschäftsbedingungen" subLines={[`Stand: ${date}`]} />
|
||||
<PDFView style={localStyles.sectionContainer}>
|
||||
<AGBSection index="01" title="Geltungsbereich">
|
||||
Diese Allgemeinen Geschäftsbedingungen gelten für alle Verträge zwischen Marc Mintel (nachfolgend „Auftragnehmer“) und dem jeweiligen Kunden (nachfolgend „Auftraggeber“). Abweichende oder ergänzende Bedingungen des Auftraggebers werden nicht Vertragsbestandteil, auch wenn ihrer Geltung nicht ausdrücklich widersprochen wird.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="02" title="Vertragsgegenstand">
|
||||
Der Auftragnehmer erbringt Dienstleistungen im Bereich: Webentwicklung, technische Umsetzung digitaler Systeme, Funktionen, Schnittstellen und Automatisierungen sowie Hosting, Betrieb und Wartung, sofern ausdrücklich vereinbard. Der Auftragnehmer schuldet ausschließlich die vereinbarte technische Leistung, nicht jedoch einen wirtschaftlichen Erfolg, bestimmte Umsätze, Conversions, Reichweiten, Suchmaschinen-Rankings oder rechtliche Ergebnisse.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="03" title="Mitwirkungspflichten des Auftraggebers">
|
||||
Der Auftraggeber verpflichtet sich, alle zur Leistungserbringung erforderlichen Inhalte, Informationen, Zugänge und Entscheidungen rechtzeitig, vollständig und korrekt bereitzustellen. Hierzu zählen insbesondere Texte, Bilder, Videos, Produktdaten, Freigaben, Feedback, Zugangsdaten sowie rechtlich erforderliche Inhalte (z. B. Impressum, DSGVO). Verzögerungen oder Unterlassungen führen zu Verschiebungen aller Termine ohne Schadensersatzanspruch.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="04" title="Ausführungs- und Bearbeitungszeiten">
|
||||
Angegebene Bearbeitungszeiten sind unverbindliche Schätzungen, keine garantierten Fristen. Fixe Termine oder Deadlines gelten nur, wenn sie ausdrücklich schriftlich als verbindlich vereinbart wurden.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="05" title="Abnahme">
|
||||
Die Leistung gilt als abgenommen, wenn der Auftraggeber sie produktiv nutzt oder innerhalb von 7 Tagen nach Bereitstellung keine wesentlichen Mängel angezeigt werden. Optische Abweichungen, Geschmacksfragen oder subjektive Einschätzungen stellen keine Mängel dar.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="06" title="Haftung">
|
||||
Der Auftragnehmer haftet nur für Schäden, die auf vorsätzlicher oder grob fahrlässiger Pflichtverletzung beruhen. Eine Haftung für entgangenen Gewinn, Umsatzausfälle, Datenverlust, Betriebsunterbrechungen, mittelbare oder Folgeschäden ist ausgeschlossen, soweit gesetzlich zulässig.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="07" title="Verfügbarkeit & Betrieb">
|
||||
Bei vereinbartem Hosting oder Betrieb schuldet der Auftragnehmer keine permanente Verfügbarkeit. Wartungsarbeiten, Updates, Sicherheitsmaßnahmen oder externe Störungen können zu zeitweisen Einschränkungen führen und begründen keine Haftungsansprüche.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="07a" title="Betriebs- und Pflegeleistung">
|
||||
Die Betriebs- und Pflegeleistung umfasst ausschließlich die Sicherstellung des technischen Betriebs, Wartung, Updates, Fehlerbehebung der bestehenden Systeme sowie Pflege bestehender Datensätze ohne Strukturänderung. Nicht Bestandteil sind die Erstellung neuer Inhalte (Blogartikel, News, Produkte), redaktionelle Tätigkeiten, strategische Planung oder der Aufbau neuer Features/Datenmodelle. Leistungen darüber hinaus gelten als Neuentwicklung.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="08" title="Drittanbieter & externe Systeme">
|
||||
Der Auftragnehmer übernimmt keine Verantwortung für Leistungen, Ausfälle oder Änderungen externer Dienste, APIs, Schnittstellen oder Plattformen Dritter. Eine Funktionsfähigkeit kann nur im Rahmen der jeweils aktuellen externen Schnittstellen gewährleistet werden.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="09" title="Inhalte & Rechtliches">
|
||||
Der Auftraggeber ist allein verantwortlich für Inhalte, rechtliche Konformität (DSGVO, Urheberrecht etc.) sowie bereitgestellte Daten. Der Auftragnehmer übernimmt keine rechtliche Prüfung.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="10" title="Vergütung & Zahlungsverzug">
|
||||
Alle Preise netto zzgl. MwSt. Rechnungen sind innerhalb von 7 Tagen fällig. Bei Zahlungsverzug ist der Auftragnehmer berechtigt, Leistungen auszusetzen, Systeme offline zu nehmen oder laufende Arbeiten zu stoppen.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="11" title="Kündigung laufender Leistungen">
|
||||
Laufende Leistungen (z. B. Hosting & Betrieb) können mit einer Frist von 4 Wochen zum Monatsende gekündigt werden, sofern nichts anderes vereinbart ist.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="12" title="Schlussbestimmungen">
|
||||
Es gilt das Recht der Bundesrepublik Deutschland. Gerichtsstand ist der Sitz des Auftragnehmers. Sollte eine Bestimmung unwirksam sein, bleibt die Wirksamkeit der übrigen Regelungen unberührt.
|
||||
</AGBSection>
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
|
||||
if (mode === 'full') {
|
||||
return (
|
||||
<SimpleLayout companyData={companyData} bankData={bankData} footerLogo={footerLogo} icon={headerIcon} pageNumber="10" showPageNumber={false}>
|
||||
{content}
|
||||
</SimpleLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PDFPage size="A4" style={pdfStyles.page}><FoldingMarks /><Header icon={headerIcon} showAddress={false} /><DocumentTitle title="Allgemeine Geschäftsbedingungen" subLines={[`Stand: ${date}`]} /><PDFView style={localStyles.sectionContainer}>
|
||||
<AGBSection index="01" title="Geltungsbereich">
|
||||
Diese Allgemeinen Geschäftsbedingungen gelten für alle Verträge zwischen Marc Mintel (nachfolgend „Auftragnehmer“) und dem jeweiligen Kunden (nachfolgend „Auftraggeber“). Abweichende oder ergänzende Bedingungen des Auftraggebers werden nicht Vertragsbestandteil, auch wenn ihrer Geltung nicht ausdrücklich widersprochen wird.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="02" title="Vertragsgegenstand">
|
||||
Der Auftragnehmer erbringt Dienstleistungen im Bereich: Webentwicklung, technische Umsetzung digitaler Systeme, Funktionen, Schnittstellen und Automatisierungen sowie Hosting, Betrieb und Wartung, sofern ausdrücklich vereinbart. Der Auftragnehmer schuldet ausschließlich die vereinbarte technische Leistung, nicht jedoch einen wirtschaftlichen Erfolg, bestimmte Umsätze, Conversions, Reichweiten, Suchmaschinen-Rankings oder rechtliche Ergebnisse.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="03" title="Mitwirkungspflichten des Auftraggebers">
|
||||
Der Auftraggeber verpflichtet sich, alle zur Leistungserbringung erforderlichen Inhalte, Informationen, Zugänge und Entscheidungen rechtzeitig, vollständig und korrekt bereitzustellen. Hierzu zählen insbesondere Texte, Bilder, Videos, Produktdaten, Freigaben, Feedback, Zugangsdaten sowie rechtlich erforderliche Inhalte (z. B. Impressum, DSGVO). Verzögerungen oder Unterlassungen führen zu Verschiebungen aller Termine ohne Schadensersatzanspruch.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="04" title="Ausführungs- und Bearbeitungszeiten">
|
||||
Angegebene Bearbeitungszeiten sind unverbindliche Schätzungen, keine garantierten Fristen. Fixe Termine oder Deadlines gelten nur, wenn sie ausdrücklich schriftlich als verbindlich vereinbart wurden.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="05" title="Abnahme">
|
||||
Die Leistung gilt als abgenommen, wenn der Auftraggeber sie produktiv nutzt oder innerhalb von 7 Tagen nach Bereitstellung keine wesentlichen Mängel angezeigt werden. Optische Abweichungen, Geschmacksfragen oder subjektive Einschätzungen stellen keine Mängel dar.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="06" title="Haftung">
|
||||
Der Auftragnehmer haftet nur für Schäden, die auf vorsätzlicher oder grob fahrlässiger Pflichtverletzung beruhen. Eine Haftung für entgangenen Gewinn, Umsatzausfälle, Datenverlust, Betriebsunterbrechungen, mittelbare oder Folgeschäden ist ausgeschlossen, soweit gesetzlich zulässig.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="07" title="Verfügbarkeit & Betrieb">
|
||||
Bei vereinbartem Hosting oder Betrieb schuldet der Auftragnehmer keine permanente Verfügbarkeit. Wartungsarbeiten, Updates, Sicherheitsmaßnahmen oder externe Störungen können zu zeitweisen Einschränkungen führen und begründen keine Haftungsansprüche.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="07a" title="Betriebs- und Pflegeleistung">
|
||||
Die Betriebs- und Pflegeleistung umfasst ausschließlich die Sicherstellung des technischen Betriebs, Wartung, Updates, Fehlerbehebung der bestehenden Systeme sowie Pflege bestehender Datensätze ohne Strukturänderung. Nicht Bestandteil sind die Erstellung neuer Inhalte (Blogartikel, News, Produkte), redaktionelle Tätigkeiten, strategische Planung oder der Aufbau neuer Features/Datenmodelle. Leistungen darüber hinaus gelten als Neuentwicklung.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="08" title="Drittanbieter & externe Systeme">
|
||||
Der Auftragnehmer übernimmt keine Verantwortung für Leistungen, Ausfälle oder Änderungen externer Dienste, APIs, Schnittstellen oder Plattformen Dritter. Eine Funktionsfähigkeit kann nur im Rahmen der jeweils aktuellen externen Schnittstellen gewährleistet werden.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="09" title="Inhalte & Rechtliches">
|
||||
Der Auftraggeber ist allein verantwortlich für Inhalte, rechtliche Konformität (DSGVO, Urheberrecht etc.) sowie bereitgestellte Daten. Der Auftragnehmer übernimmt keine rechtliche Prüfung.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="10" title="Vergütung & Zahlungsverzug">
|
||||
Alle Preise netto zzgl. MwSt. Rechnungen sind innerhalb von 7 Tagen fällig. Bei Zahlungsverzug ist der Auftragnehmer berechtigt, Leistungen auszusetzen, Systeme offline zu nehmen oder laufende Arbeiten zu stoppen.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="11" title="Kündigung laufender Leistungen">
|
||||
Laufende Leistungen (z. B. Hosting & Betrieb) können mit einer Frist von 4 Wochen zum Monatsende gekündigt werden, sofern nichts anderes vereinbart ist.
|
||||
</AGBSection>
|
||||
|
||||
<AGBSection index="12" title="Schlussbestimmungen">
|
||||
Es gilt das Recht der Bundesrepublik Deutschland. Gerichtsstand ist der Sitz des Auftragnehmers. Sollte eine Bestimmung unwirksam sein, bleibt die Wirksamkeit der übrigen Regelungen unberührt.
|
||||
</AGBSection>
|
||||
</PDFView><Footer logo={footerLogo} companyData={companyData} bankData={bankData} showDetails={false} /></PDFPage>
|
||||
<PDFPage size="A4" style={pdfStyles.page}>
|
||||
<FoldingMarks />
|
||||
<Header icon={headerIcon} showAddress={false} />
|
||||
{content}
|
||||
<Footer logo={footerLogo} companyData={companyData} bankData={bankData} showDetails={false} showPageNumber={false} />
|
||||
</PDFPage>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,8 +12,13 @@ interface CombinedProps {
|
||||
principles?: any[];
|
||||
}
|
||||
|
||||
export const CombinedQuotePDF = ({ estimationProps, showAgbs = true, techDetails, principles }: CombinedProps) => {
|
||||
export const CombinedQuotePDF = ({ estimationProps, showAgbs = true, techDetails, principles, mode = 'full' }: CombinedProps & { mode?: 'estimation' | 'full' }) => {
|
||||
return (
|
||||
<PDFDocument title={`Mintel - ${estimationProps.state.companyName || estimationProps.state.name}`}><EstimationPDF {...estimationProps} techDetails={techDetails} principles={principles} />{showAgbs && (<AgbsPDF state={estimationProps.state} headerIcon={estimationProps.headerIcon} footerLogo={estimationProps.footerLogo} />)}</PDFDocument>
|
||||
<PDFDocument title={`Mintel - ${estimationProps.state.companyName || estimationProps.state.name}`}>
|
||||
<EstimationPDF {...estimationProps} mode={mode} techDetails={techDetails} principles={principles} />
|
||||
{showAgbs && (
|
||||
<AgbsPDF mode={mode} state={estimationProps.state} headerIcon={estimationProps.headerIcon} footerLogo={estimationProps.footerLogo} />
|
||||
)}
|
||||
</PDFDocument>
|
||||
);
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
55
src/components/pdf/DINLayout.tsx
Normal file
55
src/components/pdf/DINLayout.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Page as PDFPage } from '@react-pdf/renderer';
|
||||
import { FoldingMarks, Header, Footer, pdfStyles } from './SharedUI';
|
||||
|
||||
interface DINLayoutProps {
|
||||
children: React.ReactNode;
|
||||
sender?: string;
|
||||
recipient?: {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
address?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
taxId?: string;
|
||||
};
|
||||
icon?: string;
|
||||
footerLogo?: string;
|
||||
companyData: any;
|
||||
bankData: any;
|
||||
showAddress?: boolean;
|
||||
showFooterDetails?: boolean;
|
||||
}
|
||||
|
||||
export const DINLayout = ({
|
||||
children,
|
||||
sender,
|
||||
recipient,
|
||||
icon,
|
||||
footerLogo,
|
||||
companyData,
|
||||
bankData,
|
||||
showAddress = true,
|
||||
showFooterDetails = true
|
||||
}: DINLayoutProps) => {
|
||||
return (
|
||||
<PDFPage size="A4" style={pdfStyles.page}>
|
||||
<FoldingMarks />
|
||||
<Header
|
||||
sender={sender}
|
||||
recipient={recipient}
|
||||
icon={icon}
|
||||
showAddress={showAddress}
|
||||
/>
|
||||
{children}
|
||||
<Footer
|
||||
logo={footerLogo}
|
||||
companyData={companyData}
|
||||
bankData={bankData}
|
||||
showDetails={showFooterDetails}
|
||||
/>
|
||||
</PDFPage>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,7 @@ export const pdfStyles = PDFStyleSheet.create({
|
||||
paddingTop: 45, // DIN 5008
|
||||
paddingLeft: 70, // ~25mm
|
||||
paddingRight: 57, // ~20mm
|
||||
paddingBottom: 48,
|
||||
paddingBottom: 80, // Safe buffer for absolute footer
|
||||
backgroundColor: '#ffffff',
|
||||
fontFamily: 'Helvetica',
|
||||
fontSize: 10,
|
||||
@@ -129,13 +129,98 @@ export const pdfStyles = PDFStyleSheet.create({
|
||||
width: 10,
|
||||
borderTopWidth: 0.5,
|
||||
borderTopColor: '#cbd5e1',
|
||||
}
|
||||
},
|
||||
// Atoms
|
||||
industrialListItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 6,
|
||||
},
|
||||
industrialBulletBox: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
backgroundColor: '#0f172a',
|
||||
marginRight: 8,
|
||||
marginTop: 5,
|
||||
},
|
||||
industrialTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
color: '#0f172a',
|
||||
marginBottom: 6,
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
industrialSubtitle: {
|
||||
fontSize: 8,
|
||||
fontWeight: 'bold',
|
||||
color: '#94a3b8',
|
||||
textTransform: 'uppercase',
|
||||
marginBottom: 16,
|
||||
letterSpacing: 2,
|
||||
},
|
||||
industrialTextLead: {
|
||||
fontSize: 10,
|
||||
color: '#334155',
|
||||
lineHeight: 1.6,
|
||||
marginBottom: 12,
|
||||
},
|
||||
industrialText: {
|
||||
fontSize: 9,
|
||||
color: '#64748b',
|
||||
lineHeight: 1.6,
|
||||
marginBottom: 8,
|
||||
},
|
||||
industrialCard: {
|
||||
padding: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: '#e2e8f0',
|
||||
marginBottom: 12,
|
||||
},
|
||||
industrialCardTitle: {
|
||||
fontSize: 10,
|
||||
fontWeight: 'bold',
|
||||
color: '#0f172a',
|
||||
marginBottom: 4,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
darkBox: {
|
||||
marginTop: 32,
|
||||
padding: 24,
|
||||
backgroundColor: '#0f172a',
|
||||
color: '#ffffff',
|
||||
},
|
||||
darkTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#ffffff',
|
||||
marginBottom: 8,
|
||||
},
|
||||
darkText: {
|
||||
fontSize: 9,
|
||||
color: '#94a3b8',
|
||||
lineHeight: 1.6,
|
||||
},
|
||||
});
|
||||
|
||||
export const IndustrialListItem = ({ children }: { children: React.ReactNode }) => (
|
||||
<PDFView style={pdfStyles.industrialListItem}>
|
||||
<PDFView style={pdfStyles.industrialBulletBox} />
|
||||
{children}
|
||||
</PDFView>
|
||||
);
|
||||
|
||||
export const IndustrialCard = ({ title, children, style = {} }: { title: string; children: React.ReactNode; style?: any }) => (
|
||||
<PDFView style={[pdfStyles.industrialCard, style]}>
|
||||
<PDFText style={pdfStyles.industrialCardTitle}>{title}</PDFText>
|
||||
{children}
|
||||
</PDFView>
|
||||
);
|
||||
|
||||
export const FoldingMarks = () => (<><PDFView style={[pdfStyles.foldingMark, { top: 297.6 }]} fixed /><PDFView style={[pdfStyles.foldingMark, { top: 420.9, width: 15 }]} fixed /><PDFView style={[pdfStyles.foldingMark, { top: 595.3 }]} fixed /></>);
|
||||
|
||||
export const Footer = ({ logo, companyData, bankData, showDetails = true }: { logo?: string; companyData: any; bankData: any; showDetails?: boolean }) => (
|
||||
<PDFView style={pdfStyles.footer}><PDFView style={pdfStyles.footerColumn}>{logo ? (<PDFImage src={logo} style={pdfStyles.footerLogo} />) : (<PDFText style={{ fontSize: 12, fontWeight: 'bold', marginBottom: 8 }}>marc mintel</PDFText>)}</PDFView>{showDetails && (<><PDFView style={pdfStyles.footerColumn}><PDFText style={pdfStyles.footerText}><PDFText style={pdfStyles.footerLabel}>{companyData.name}</PDFText>{"\n"}{companyData.address1}{"\n"}{companyData.address2}{"\n"}UST: {companyData.ustId}</PDFText></PDFView><PDFView style={[pdfStyles.footerColumn, { alignItems: 'flex-end' }]}><PDFText style={[pdfStyles.footerText, { textAlign: 'right' }]}><PDFText style={pdfStyles.footerLabel}>{bankData.name}</PDFText>{"\n"}{bankData.bic}{"\n"}{bankData.iban}</PDFText><PDFText style={pdfStyles.pageNumber} render={({ pageNumber, totalPages }) => `${pageNumber} / ${totalPages}`} fixed /></PDFView></>)}{!showDetails && (<PDFView style={[pdfStyles.footerColumn, { alignItems: 'flex-end' }]}><PDFText style={pdfStyles.pageNumber} render={({ pageNumber, totalPages }) => `${pageNumber} / ${totalPages}`} fixed /></PDFView>)}</PDFView>
|
||||
export const Footer = ({ logo, companyData, bankData, showDetails = true, showPageNumber = true }: { logo?: string; companyData: any; bankData: any; showDetails?: boolean; showPageNumber?: boolean }) => (
|
||||
<PDFView style={pdfStyles.footer}><PDFView style={pdfStyles.footerColumn}>{logo ? (<PDFImage src={logo} style={pdfStyles.footerLogo} />) : (<PDFText style={{ fontSize: 12, fontWeight: 'bold', marginBottom: 8 }}>marc mintel</PDFText>)}</PDFView>{showDetails && (<><PDFView style={pdfStyles.footerColumn}><PDFText style={pdfStyles.footerText}><PDFText style={pdfStyles.footerLabel}>{companyData.name}</PDFText>{"\n"}{companyData.address1}{"\n"}{companyData.address2}{"\n"}UST: {companyData.ustId}</PDFText></PDFView><PDFView style={[pdfStyles.footerColumn, { alignItems: 'flex-end' }]}>{showPageNumber && <PDFText style={pdfStyles.pageNumber} render={({ pageNumber, totalPages }) => `${pageNumber} / ${totalPages}`} fixed />}</PDFView></>)}{!showDetails && (<PDFView style={[pdfStyles.footerColumn, { alignItems: 'flex-end' }]}>{showPageNumber && <PDFText style={pdfStyles.pageNumber} render={({ pageNumber, totalPages }) => `${pageNumber} / ${totalPages}`} fixed />}</PDFView>)}</PDFView>
|
||||
);
|
||||
|
||||
export const Header = ({ sender, recipient, icon, showAddress = true }: { sender?: string; recipient?: { title: string; subtitle?: string; email?: string; address?: string; phone?: string; taxId?: string }; icon?: string; showAddress?: boolean; }) => (
|
||||
|
||||
66
src/components/pdf/SimpleLayout.tsx
Normal file
66
src/components/pdf/SimpleLayout.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Page as PDFPage, View as PDFView, Text as PDFText, StyleSheet } from '@react-pdf/renderer';
|
||||
import { Header, Footer, pdfStyles } from './SharedUI';
|
||||
|
||||
const simpleStyles = StyleSheet.create({
|
||||
industrialPage: {
|
||||
padding: 40,
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
industrialNumber: {
|
||||
fontSize: 60,
|
||||
fontWeight: 'bold',
|
||||
color: '#f1f5f9',
|
||||
position: 'absolute',
|
||||
top: -10,
|
||||
right: 0,
|
||||
zIndex: -1,
|
||||
},
|
||||
industrialSection: {
|
||||
marginTop: 32,
|
||||
paddingTop: 24,
|
||||
flexDirection: 'row',
|
||||
position: 'relative',
|
||||
},
|
||||
});
|
||||
|
||||
interface SimpleLayoutProps {
|
||||
children: React.ReactNode;
|
||||
pageNumber?: string;
|
||||
icon?: string;
|
||||
footerLogo?: string;
|
||||
companyData: any;
|
||||
bankData: any;
|
||||
showPageNumber?: boolean;
|
||||
}
|
||||
|
||||
export const SimpleLayout = ({
|
||||
children,
|
||||
pageNumber,
|
||||
icon,
|
||||
footerLogo,
|
||||
companyData,
|
||||
bankData,
|
||||
showPageNumber = true
|
||||
}: SimpleLayoutProps) => {
|
||||
return (
|
||||
<PDFPage size="A4" style={[pdfStyles.page, simpleStyles.industrialPage]}>
|
||||
<Header icon={icon} showAddress={false} />
|
||||
{pageNumber && <PDFText style={simpleStyles.industrialNumber}>{pageNumber}</PDFText>}
|
||||
<PDFView style={simpleStyles.industrialSection}>
|
||||
<PDFView style={{ width: '100%' }}>
|
||||
{children}
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
<Footer
|
||||
logo={footerLogo}
|
||||
companyData={companyData}
|
||||
bankData={bankData}
|
||||
showDetails={false}
|
||||
showPageNumber={showPageNumber}
|
||||
/>
|
||||
</PDFPage>
|
||||
);
|
||||
};
|
||||
99
src/components/pdf/modules/BrandingModules.tsx
Normal file
99
src/components/pdf/modules/BrandingModules.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { View as PDFView, Text as PDFText, StyleSheet } from '@react-pdf/renderer';
|
||||
import { IndustrialListItem, IndustrialCard } from '../SharedUI';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
industrialTitle: { fontSize: 24, fontWeight: 'bold', color: '#0f172a', marginBottom: 6, letterSpacing: -0.5 },
|
||||
industrialSubtitle: { fontSize: 8, fontWeight: 'bold', color: '#94a3b8', textTransform: 'uppercase', marginBottom: 16, letterSpacing: 2 },
|
||||
industrialTextLead: { fontSize: 10, color: '#334155', lineHeight: 1.6, marginBottom: 12 },
|
||||
industrialText: { fontSize: 9, color: '#64748b', lineHeight: 1.6, marginBottom: 8 },
|
||||
industrialGrid2: { flexDirection: 'row', gap: 32 },
|
||||
industrialCol: { width: '48%' },
|
||||
darkBox: { marginTop: 32, padding: 24, backgroundColor: '#0f172a', color: '#ffffff' },
|
||||
darkTitle: { fontSize: 18, fontWeight: 'bold', color: '#ffffff', marginBottom: 8 },
|
||||
darkText: { fontSize: 9, color: '#94a3b8', lineHeight: 1.6 },
|
||||
industrialBulletBox: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
backgroundColor: '#0f172a',
|
||||
marginRight: 8,
|
||||
marginTop: 5,
|
||||
},
|
||||
});
|
||||
|
||||
export const AboutModule = () => (
|
||||
<>
|
||||
<PDFText style={styles.industrialTitle}>Über mich</PDFText>
|
||||
<PDFText style={styles.industrialSubtitle}>Direkt. Sauber. Verantwortlich.</PDFText>
|
||||
<PDFView style={[styles.industrialGrid2, { marginTop: 20 }]}>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<PDFText style={styles.industrialTextLead}>Ich baue Websites und Systeme seit über 15 Jahren. Nicht weil ich Websites so liebe – sondern weil ich es hasse, wenn Dinge nicht funktionieren.</PDFText>
|
||||
<PDFText style={styles.industrialText}>In diesen Jahren habe ich Agenturen von innen gesehen, Konzerne erlebt und Startups aufgebaut. Ich habe gelernt, dass das Problem selten die Technik ist – sondern die Zuständigkeit.</PDFText>
|
||||
<PDFView style={{ marginTop: 16 }}>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold' }]}>Meine Arbeitsweise:</PDFText>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>schnell & stabil</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>"boring" (im besten Sinne)</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>erweiterbar & wartungsarm</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>unabhängig von Agenturen</PDFText></IndustrialListItem>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<IndustrialCard title="WAS SIE BEKOMMEN">
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Eine Person. Eine Verantwortung.</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Direkte Kommunikation ohne Stille Post.</PDFText></IndustrialListItem>
|
||||
<IndustrialListItem><PDFText style={styles.industrialText}>Code, der Ihnen gehört.</PDFText></IndustrialListItem>
|
||||
</IndustrialCard>
|
||||
<IndustrialCard title="WAS SIE NICHT BEKOMMEN" style={{ backgroundColor: '#ffffff', borderColor: '#cbd5e1' }}>
|
||||
<PDFText style={[styles.industrialText, { color: '#94a3b8', textDecoration: 'line-through' }]}>Projektmanager</PDFText>
|
||||
<PDFText style={[styles.industrialText, { color: '#94a3b8', textDecoration: 'line-through' }]}>Ticket-Systeme</PDFText>
|
||||
<PDFText style={[styles.industrialText, { color: '#94a3b8', textDecoration: 'line-through' }]}>CMS-Drama & Update-Angst</PDFText>
|
||||
</IndustrialCard>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
|
||||
export const CrossSellModule = ({ state }: any) => {
|
||||
const isWebsite = state.projectType === 'website';
|
||||
const title = isWebsite ? "Das könnte Sie auch interessieren" : "Websites & Ökosysteme";
|
||||
const subtitle = isWebsite ? "Kleine Helfer, die den Alltag entlasten" : "Digitale Visitenkarten mit Tiefe";
|
||||
|
||||
return (
|
||||
<>
|
||||
<PDFText style={styles.industrialTitle}>Das könnte Sie auch interessieren</PDFText>
|
||||
<PDFText style={styles.industrialSubtitle}>Kleine helfer, die den alltag entlasten</PDFText>
|
||||
<PDFView style={[styles.industrialGrid2, { marginTop: 12 }]}>
|
||||
{isWebsite ? (
|
||||
<>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<PDFText style={styles.industrialTextLead}>Zusätzlich zur digitalen Präsenz fressen in vielen Unternehmen wiederkehrende Aufgaben unzählige Stunden: Daten abtippen, Formulare ausfüllen, Angebote anpassen.</PDFText>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold' }]}>Das ist keine wertschöpfende Arbeit. Das ist Routine.</PDFText>
|
||||
<PDFView style={styles.darkBox}>
|
||||
<PDFText style={styles.darkTitle}>Der schnelle Check</PDFText>
|
||||
<PDFText style={styles.darkText}>Wenn bei Ihnen gerade etwas "von Hand gemacht wird" oder "ewig dauert" – ich sage Ihnen in 1-2 Tagen, ob und wie schnell man das sinnvoll digitalisieren kann.</PDFText>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
<PDFView style={styles.industrialCol}>
|
||||
<IndustrialCard title="PDF-GENERATOREN">
|
||||
<PDFText style={styles.industrialText}>Kurze Eingaben via Formular → fertiges Angebot/Bericht. Von 60min auf 5min reduziert.</PDFText>
|
||||
</IndustrialCard>
|
||||
<IndustrialCard title="EXCEL-AUTOMATISIERUNG">
|
||||
<PDFText style={styles.industrialText}>Verkaufszahlen, Lagerbestände oder Kundenlisten automatisch synchronisieren und auswerten.</PDFText>
|
||||
</IndustrialCard>
|
||||
<IndustrialCard title="KI-SCANNER">
|
||||
<PDFText style={styles.industrialText}>Rechnungen oder handschriftliche Notizen fotografieren → Daten landen direkt strukturiert in der Tabelle.</PDFText>
|
||||
</IndustrialCard>
|
||||
</PDFView>
|
||||
</>
|
||||
) : (
|
||||
<PDFView style={{ width: '100%' }}>
|
||||
<PDFText style={styles.industrialTextLead}>Neben Automatisierung baue ich komplette digitale Ökosysteme für Unternehmen, die Wert auf Qualität legen.</PDFText>
|
||||
<PDFText style={styles.industrialText}>Keine Baukästen. Keine langsamen WordPress-Themes. Sondern maßgeschneiderte Systeme, die technisch und optisch in der ersten Liga spielen.</PDFText>
|
||||
</PDFView>
|
||||
)}
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
};
|
||||
42
src/components/pdf/modules/BriefingModule.tsx
Normal file
42
src/components/pdf/modules/BriefingModule.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { View as PDFView, Text as PDFText, StyleSheet } from '@react-pdf/renderer';
|
||||
import { DocumentTitle } from '../SharedUI';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: { marginBottom: 24 },
|
||||
sectionTitle: { fontSize: 10, fontWeight: 'bold', marginBottom: 8, color: '#0f172a' },
|
||||
configGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginTop: 6 },
|
||||
configItem: { width: '24%', marginBottom: 4 },
|
||||
configLabel: { fontSize: 5, color: '#94a3b8', textTransform: 'uppercase', marginBottom: 2 },
|
||||
configValue: { fontSize: 7, color: '#0f172a', fontWeight: 'bold' },
|
||||
visionText: { fontSize: 9, color: '#334155', lineHeight: 1.8, textAlign: 'justify' },
|
||||
});
|
||||
|
||||
export const BriefingModule = ({ state }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Projektdetails" />
|
||||
{state.briefingSummary && (
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={styles.sectionTitle}>Briefing Analyse</PDFText>
|
||||
<PDFText style={{ fontSize: 8, color: '#334155', lineHeight: 1.6, textAlign: 'justify' }}>{state.briefingSummary}</PDFText>
|
||||
</PDFView>
|
||||
)}
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={styles.sectionTitle}>Kern-Informationen</PDFText>
|
||||
<PDFView style={styles.configGrid}>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Ansprechpartner</PDFText><PDFText style={styles.configValue}>{state.personName || "Ihr Team"}</PDFText></PDFView>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Status Quo</PDFText><PDFText style={styles.configValue}>{state.statusQuo || (state.existingWebsite ? 'Relaunch' : 'Neuentwicklung')}</PDFText></PDFView>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Mitarbeiter</PDFText><PDFText style={styles.configValue}>{state.employeeCount || "—"}</PDFText></PDFView>
|
||||
<PDFView style={styles.configItem}><PDFText style={styles.configLabel}>Deadline / Zeitplan</PDFText><PDFText style={styles.configValue}>{state.deadline || 'Flexibel'}</PDFText></PDFView>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
{state.designVision && (
|
||||
<PDFView style={[styles.section, { padding: 16, borderLeftWidth: 2, borderLeftColor: '#000000', backgroundColor: '#f9fafb' }]}>
|
||||
<PDFText style={[styles.sectionTitle, { color: '#000000' }]}>Strategische Vision</PDFText>
|
||||
<PDFText style={styles.visionText}>{state.designVision}</PDFText>
|
||||
</PDFView>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
100
src/components/pdf/modules/CommonModules.tsx
Normal file
100
src/components/pdf/modules/CommonModules.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { View as PDFView, Text as PDFText, StyleSheet, Image as PDFImage } from '@react-pdf/renderer';
|
||||
import { DocumentTitle } from '../SharedUI';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: { marginBottom: 24 },
|
||||
pricingGrid: { marginTop: 24 },
|
||||
pricingRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#f1f5f9', paddingVertical: 10, alignItems: 'flex-start' },
|
||||
pricingTitle: { width: '30%', fontSize: 9, fontWeight: 'bold', color: '#0f172a' },
|
||||
pricingDesc: { width: '55%', fontSize: 8, color: '#64748b', lineHeight: 1.4 },
|
||||
pricingTag: { width: '15%', fontSize: 9, fontWeight: 'bold', textAlign: 'right' },
|
||||
configLabel: { fontSize: 5, color: '#94a3b8', textTransform: 'uppercase', marginBottom: 8 },
|
||||
});
|
||||
|
||||
const CHROME_ICON = '/Users/marcmintel/Projects/mintel.me/src/assets/browser/chrome.png'; // Fallback to a placeholder if not found
|
||||
const SAFARI_ICON = '/Users/marcmintel/Projects/mintel.me/src/assets/browser/safari.png';
|
||||
|
||||
export const techPageModule = ({ techDetails, headerIcon }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Technische Umsetzung" />
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={{ fontSize: 8, color: '#64748b', lineHeight: 1.6, marginBottom: 16 }}>Ich entwickle Websites als moderne, performante Websysteme.</PDFText>
|
||||
<PDFView style={styles.pricingGrid}>
|
||||
{techDetails?.map((item: any, i: number) => (
|
||||
<PDFView key={i} style={styles.pricingRow}>
|
||||
<PDFText style={[styles.pricingTitle, { width: '35%' }]}>{item.t}</PDFText>
|
||||
<PDFText style={[styles.pricingDesc, { width: '65%' }]}>{item.d}</PDFText>
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
|
||||
export const TransparenzModule = ({ pricing }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Preis-Transparenz & Modell" />
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={{ fontSize: 10, fontWeight: 'bold', color: '#000000', marginBottom: 8 }}>Grundlage für kalkulierbare Investitionen</PDFText>
|
||||
<PDFText style={{ fontSize: 8, color: '#64748b', lineHeight: 1.6, marginBottom: 24 }}>Digitaler Erfolg basiert auf Transparenz. Dieses Dokument dient als detaillierte Grundlage für Ihre Investitionsentscheidung. Es handelt sich um ein Festpreis-Modell für die beschriebenen Leistungen, um Ihnen maximale Planungssicherheit zu gewährleisten.</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={{ fontSize: 9, color: '#0f172a', fontWeight: 'bold', marginBottom: 8 }}>Warum dieser modulare Ansatz?</PDFText>
|
||||
<PDFText style={{ fontSize: 8, color: '#64748b', lineHeight: 1.6, marginBottom: 20 }}>Meine Kalkulation basiert auf einem transparenten Festpreis-System. Sie zahlen nicht für Stunden, sondern für messbare Ergebnisse. Jeder Baustein ist darauf ausgelegt, Ihre digitale Präsenz zu stärken und den ROI Ihres Projekts zu maximieren.</PDFText>
|
||||
<PDFView style={styles.pricingGrid}>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>1. Das Fundament</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Infrastruktur, technisches SEO, Analytics und ein skalierbares Setup. Der Grundstein für eine Website, die technisch perfekt performt.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.BASE_WEBSITE?.toLocaleString('de-DE')} €</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>2. Individuelle Erlebnisse</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Pro Seite entwickeln wir ein UI/UX-Konzept, das Besucher führt und überzeugt. Keine Standard-Templates, sondern maßgeschneidertes Design.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.PAGE?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>3. Smarte Content-Systeme</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Automatisieren Sie Ihre Kommunikation. Ob Blog, Job-Board oder Case Studies – wir bauen Systeme, die sich einfach pflegen lassen.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.FEATURE?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>4. Funktionalität</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Suche, Filter oder interaktive Formulare. Wir reduzieren Reibung und machen die Nutzung für Ihre Kunden zum Erlebnis.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.FUNCTION?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>5. High-End Inszenierung</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Video-Integrationen, Mikro-Animationen und immersive Effekte. So heben Sie sich im Markt ab und wirken als Premium-Marke.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.COMPLEX_INTERACTION?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>6. Autonomie & Vernetzung</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Nahtlose Anbindung an Headless CMS oder Ihre Bestands-APIs (CRM, ERP). Maximale Freiheit bei voller Kontrolle.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>ab {pricing.API_INTEGRATION?.toLocaleString('de-DE')} €</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>7. Sorglos-Betrieb</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Full-Service Hosting, tägliche Backups und proaktive Sicherheitsupdates. Damit Sie sich auf Ihr Kerngeschäft konzentrieren können.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.HOSTING_MONTHLY?.toLocaleString('de-DE')} € / Mon</PDFText>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
|
||||
export const PrinciplesModule = ({ principles }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Meine Prinzipien" />
|
||||
<PDFView style={styles.pricingGrid}>
|
||||
{principles?.map((item: any, i: number) => (
|
||||
<PDFView key={i} style={styles.pricingRow}>
|
||||
<PDFText style={[styles.pricingTitle, { width: '35%' }]}>{item.t}</PDFText>
|
||||
<PDFText style={[styles.pricingDesc, { width: '65%' }]}>{item.d}</PDFText>
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
55
src/components/pdf/modules/EstimationModule.tsx
Normal file
55
src/components/pdf/modules/EstimationModule.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { View as PDFView, Text as PDFText, StyleSheet } from '@react-pdf/renderer';
|
||||
import { DocumentTitle } from '../SharedUI';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
table: { marginTop: 12 },
|
||||
tableHeader: { flexDirection: 'row', paddingBottom: 8, borderBottomWidth: 1, borderBottomColor: '#000000', marginBottom: 12 },
|
||||
tableRow: { flexDirection: 'row', paddingVertical: 6, borderBottomWidth: 1, borderBottomColor: '#eeeeee', alignItems: 'flex-start' },
|
||||
colPos: { width: '8%' },
|
||||
colDesc: { width: '62%' },
|
||||
colQty: { width: '10%', textAlign: 'center' },
|
||||
colPrice: { width: '20%', textAlign: 'right' },
|
||||
headerText: { fontSize: 7, fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: 1 },
|
||||
posText: { fontSize: 8, color: '#999999' },
|
||||
itemTitle: { fontSize: 10, fontWeight: 'bold', marginBottom: 4 },
|
||||
itemDesc: { fontSize: 8, color: '#666666', lineHeight: 1.4 },
|
||||
priceText: { fontSize: 10, fontWeight: 'bold' },
|
||||
summaryContainer: { borderTopWidth: 1, borderTopColor: '#000000', paddingTop: 8 },
|
||||
summaryRow: { flexDirection: 'row', justifyContent: 'flex-end', paddingVertical: 4, alignItems: 'baseline' },
|
||||
summaryLabel: { fontSize: 7, color: '#64748b', textTransform: 'uppercase', letterSpacing: 1, fontWeight: 'bold', marginRight: 12 },
|
||||
summaryValue: { fontSize: 9, fontWeight: 'bold', width: 100, textAlign: 'right' },
|
||||
totalRow: { flexDirection: 'row', justifyContent: 'flex-end', paddingTop: 12, marginTop: 8, borderTopWidth: 2, borderTopColor: '#000000', alignItems: 'baseline' },
|
||||
});
|
||||
|
||||
export const EstimationModule = ({ state, positions, totalPrice, date }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Kostenschätzung" subLines={[`Datum: ${date}`, `Projekt: ${state.projectType === 'website' ? 'Website' : 'Web App'}`]} />
|
||||
<PDFView style={styles.table}>
|
||||
<PDFView style={styles.tableHeader}>
|
||||
<PDFText style={[styles.headerText, styles.colPos]}>Pos</PDFText>
|
||||
<PDFText style={[styles.headerText, styles.colDesc]}>Beschreibung</PDFText>
|
||||
<PDFText style={[styles.headerText, styles.colQty]}>Menge</PDFText>
|
||||
<PDFText style={[styles.headerText, styles.colPrice]}>Betrag</PDFText>
|
||||
</PDFView>
|
||||
{positions.map((item: any, i: number) => (
|
||||
<PDFView key={i} style={styles.tableRow} wrap={false}>
|
||||
<PDFText style={[styles.posText, styles.colPos]}>{item.pos.toString().padStart(2, '0')}</PDFText>
|
||||
<PDFView style={styles.colDesc}>
|
||||
<PDFText style={styles.itemTitle}>{item.title}</PDFText>
|
||||
<PDFText style={styles.itemDesc}>{state.positionDescriptions?.[item.title] || item.desc}</PDFText>
|
||||
</PDFView>
|
||||
<PDFText style={[styles.posText, styles.colQty]}>{item.qty}</PDFText>
|
||||
<PDFText style={[styles.priceText, styles.colPrice]}>{item.price > 0 ? `${item.price.toLocaleString('de-DE')} €` : 'n. A.'}</PDFText>
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
<PDFView style={styles.summaryContainer} wrap={false}>
|
||||
<PDFView style={styles.summaryRow}><PDFText style={styles.summaryLabel}>Zwischensumme (Netto)</PDFText><PDFText style={styles.summaryValue}>{totalPrice.toLocaleString('de-DE')} €</PDFText></PDFView>
|
||||
<PDFView style={styles.summaryRow}><PDFText style={styles.summaryLabel}>zzgl. 19% MwSt.</PDFText><PDFText style={styles.summaryValue}>{(totalPrice * 0.19).toLocaleString('de-DE')} €</PDFText></PDFView>
|
||||
<PDFView style={styles.totalRow}><PDFText style={styles.summaryLabel}>Gesamtsumme (Brutto)</PDFText><PDFText style={[styles.summaryValue, { fontSize: 14 }]}>{(totalPrice * 1.19).toLocaleString('de-DE')} €</PDFText></PDFView>
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
73
src/components/pdf/modules/FrontPageModule.tsx
Normal file
73
src/components/pdf/modules/FrontPageModule.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { View as PDFView, Text as PDFText, Image as PDFImage, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titlePage: {
|
||||
padding: 60,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '90%',
|
||||
},
|
||||
titleBrandIcon: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
backgroundColor: '#000000',
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 40,
|
||||
},
|
||||
brandIconText: {
|
||||
fontSize: 40,
|
||||
color: '#ffffff',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
titleProjectName: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
color: '#0f172a',
|
||||
marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 2,
|
||||
},
|
||||
titleCustomerName: {
|
||||
fontSize: 14,
|
||||
color: '#64748b',
|
||||
marginBottom: 40,
|
||||
textAlign: 'center',
|
||||
},
|
||||
titleDocumentType: {
|
||||
fontSize: 10,
|
||||
color: '#94a3b8',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 4,
|
||||
marginBottom: 8,
|
||||
},
|
||||
titleDivider: {
|
||||
width: 40,
|
||||
height: 2,
|
||||
backgroundColor: '#000000',
|
||||
marginBottom: 40,
|
||||
},
|
||||
titleDate: {
|
||||
fontSize: 9,
|
||||
color: '#94a3b8',
|
||||
marginTop: 'auto',
|
||||
},
|
||||
});
|
||||
|
||||
export const FrontPageModule = ({ state, headerIcon, date }: any) => (
|
||||
<PDFView style={styles.titlePage}>
|
||||
<PDFView style={styles.titleBrandIcon}>
|
||||
{headerIcon ? <PDFImage src={headerIcon} style={{ width: 40, height: 40 }} /> : <PDFText style={styles.brandIconText}>M</PDFText>}
|
||||
</PDFView>
|
||||
<PDFText style={styles.titleDocumentType}>Kostenschätzung & Konzept</PDFText>
|
||||
<PDFText style={styles.titleProjectName}>{state.projectType === 'website' ? 'Digitale Präsenz' : 'Digitale Applikation'}</PDFText>
|
||||
<PDFView style={styles.titleDivider} />
|
||||
<PDFText style={styles.titleCustomerName}>für {state.companyName || "Ihre Projektanfrage"}</PDFText>
|
||||
<PDFText style={styles.titleDate}>{date} | Marc Mintel</PDFText>
|
||||
</PDFView>
|
||||
);
|
||||
52
src/components/pdf/modules/SitemapModule.tsx
Normal file
52
src/components/pdf/modules/SitemapModule.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { View as PDFView, Text as PDFText, StyleSheet } from '@react-pdf/renderer';
|
||||
import { DocumentTitle } from '../SharedUI';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: { marginBottom: 24 },
|
||||
sitemapTree: { marginTop: 20 },
|
||||
sitemapRootNode: { flexDirection: 'row', alignItems: 'center', marginBottom: 12 },
|
||||
sitemapRootDot: { width: 6, height: 6, borderRadius: 3, backgroundColor: '#000000', marginRight: 10 },
|
||||
sitemapRootTitle: { fontSize: 10, fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: 1 },
|
||||
sitemapMainLine: { position: 'absolute', left: 2, top: 20, bottom: 0, width: 0.5, backgroundColor: '#cbd5e1' },
|
||||
sitemapBranch: { marginLeft: 20, marginBottom: 12, position: 'relative' },
|
||||
sitemapNode: { flexDirection: 'row', alignItems: 'center', marginBottom: 4 },
|
||||
sitemapRootIcon: { width: 4, height: 4, backgroundColor: '#000000', marginRight: 8 },
|
||||
sitemapBranchTitle: { fontSize: 8, fontWeight: 'bold' },
|
||||
sitemapLeaf: { marginLeft: 12, borderLeftWidth: 0.5, borderLeftColor: '#cbd5e1', paddingLeft: 12, marginTop: 4 },
|
||||
sitemapLeafNode: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 6 },
|
||||
sitemapLeafPointer: { fontSize: 7, color: '#94a3b8', marginRight: 6 },
|
||||
sitemapLeafTitle: { fontSize: 7, fontWeight: 'bold' },
|
||||
sitemapLeafDesc: { fontSize: 6, color: '#64748b', lineHeight: 1.3, marginTop: 1 },
|
||||
});
|
||||
|
||||
export const SitemapModule = ({ state }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Seitenstruktur" />
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={{ fontSize: 8, color: '#64748b', lineHeight: 1.6, marginBottom: 16 }}>Die folgende Struktur bildet das Fundament für die Benutzerführung und Informationsarchitektur Ihres Projekts.</PDFText>
|
||||
<PDFView style={styles.sitemapTree}>
|
||||
<PDFView style={styles.sitemapRootNode}><PDFView style={styles.sitemapRootDot} /><PDFText style={styles.sitemapRootTitle}>{state.websiteTopic || 'Digitales Ökosystem'}</PDFText></PDFView>
|
||||
<PDFView style={styles.sitemapMainLine} />
|
||||
{state.sitemap?.map((cat: any, i: number) => (
|
||||
<PDFView key={i} style={styles.sitemapBranch}>
|
||||
<PDFView style={styles.sitemapNode}><PDFView style={styles.sitemapRootIcon} /><PDFText style={styles.sitemapBranchTitle}>{cat.category}</PDFText></PDFView>
|
||||
<PDFView style={styles.sitemapLeaf}>
|
||||
{cat.pages.map((p: any, j: number) => (
|
||||
<PDFView key={j} style={styles.sitemapLeafNode}>
|
||||
<PDFText style={styles.sitemapLeafPointer}>└─</PDFText>
|
||||
<PDFView style={{ flex: 1 }}>
|
||||
<PDFText style={styles.sitemapLeafTitle}>{p.title}</PDFText>
|
||||
{p.desc && <PDFText style={styles.sitemapLeafDesc}>{p.desc}</PDFText>}
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</>
|
||||
);
|
||||
@@ -44,7 +44,7 @@ export function calculatePositions(state: FormState, pricing: any): Position[] {
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: 'Logik-Funktionen',
|
||||
desc: `Erweiterte Funktionen: ${allFunctions.join(', ')}.`,
|
||||
desc: `Implementierung technischer Logik: ${allFunctions.join(', ')}.`,
|
||||
qty: allFunctions.length,
|
||||
price: allFunctions.length * pricing.FUNCTION
|
||||
});
|
||||
|
||||
@@ -102,7 +102,7 @@ export const FUNCTION_OPTIONS = [
|
||||
{ id: 'search', label: 'Suche', desc: 'Volltextsuche über alle Inhalte.' },
|
||||
{ id: 'filter', label: 'Filter-Systeme', desc: 'Kategorisierung und Sortierung.' },
|
||||
{ id: 'pdf', label: 'PDF-Export', desc: 'Automatisierte PDF-Erstellung.' },
|
||||
{ id: 'forms', label: 'Erweiterte Formulare', desc: 'Komplexe Abfragen & Logik.' },
|
||||
{ id: 'forms', label: 'Individuelle Formular-Logik', desc: 'Smarte Validierung & mehrstufige Prozesse.' },
|
||||
];
|
||||
|
||||
export const API_OPTIONS = [
|
||||
@@ -190,7 +190,7 @@ export const FUNCTION_LABELS: Record<string, string> = {
|
||||
search: 'Suche',
|
||||
filter: 'Filter-Systeme',
|
||||
pdf: 'PDF-Export',
|
||||
forms: 'Erweiterte Formulare',
|
||||
forms: 'Individuelle Formular-Logik',
|
||||
members: 'Mitgliederbereich',
|
||||
calendar: 'Event-Kalender',
|
||||
multilang: 'Mehrsprachigkeit',
|
||||
|
||||
Reference in New Issue
Block a user