diff --git a/din_test_final.pdf b/din_test_final.pdf deleted file mode 100644 index 7846872..0000000 Binary files a/din_test_final.pdf and /dev/null differ diff --git a/din_test_final_v2.pdf b/din_test_final_v2.pdf deleted file mode 100644 index 5b932cd..0000000 Binary files a/din_test_final_v2.pdf and /dev/null differ diff --git a/din_test_v2.pdf b/din_test_v2.pdf deleted file mode 100644 index ed976de..0000000 Binary files a/din_test_v2.pdf and /dev/null differ diff --git a/scripts/generate-quote.ts b/scripts/generate-quote.ts deleted file mode 100644 index ed85ed6..0000000 --- a/scripts/generate-quote.ts +++ /dev/null @@ -1,167 +0,0 @@ -import * as React from 'react'; -import { renderToFile } from '@react-pdf/renderer'; -import { EstimationPDF } from '../src/components/EstimationPDF'; -import { - PRICING, - initialState, - PAGE_SAMPLES, - FEATURE_OPTIONS, - FUNCTION_OPTIONS, - API_OPTIONS, - DESIGN_OPTIONS, - DEADLINE_LABELS, - ASSET_OPTIONS, - EMPLOYEE_OPTIONS, - calculatePositions, - FormState -} from '../src/logic/pricing'; -import * as fs from 'fs'; -import * as path from 'path'; -import { parseArgs } from 'util'; -import * as readline from 'readline/promises'; - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, -}); - -async function ask(question: string, defaultValue?: string): Promise { - const answer = await rl.question(`${question}${defaultValue ? ` [${defaultValue}]` : ''}: `); - return answer.trim() || defaultValue || ''; -} - -async function selectOne(question: string, options: { id: string; label: string }[], defaultValue?: string): Promise { - console.log(`\n${question}`); - options.forEach((opt, i) => console.log(`${i + 1}. ${opt.label}`)); - const answer = await ask('Select number'); - const index = parseInt(answer) - 1; - return options[index]?.id || defaultValue || options[0].id; -} - -async function selectMany(question: string, options: { id: string; label: string }[]): Promise { - console.log(`\n${question} (comma separated numbers, e.g. 1,2,4)`); - options.forEach((opt, i) => console.log(`${i + 1}. ${opt.label}`)); - const answer = await ask('Selection'); - if (!answer) return []; - return answer.split(',') - .map(n => parseInt(n.trim()) - 1) - .filter(i => options[i]) - .map(i => options[i].id); -} - -async function generate() { - const { values } = parseArgs({ - options: { - input: { type: 'string', short: 'i' }, - output: { type: 'string', short: 'o' }, - interactive: { type: 'boolean', short: 'I', default: false }, - name: { type: 'string' }, - company: { type: 'string' }, - email: { type: 'string' }, - 'project-type': { type: 'string' }, - }, - }); - - let state: FormState = { ...initialState }; - - // Apply command line flags first - if (values.name) state.name = values.name as string; - if (values.company) state.companyName = values.company as string; - if (values.email) state.email = values.email as string; - if (values['project-type']) state.projectType = values['project-type'] as 'website' | 'web-app'; - - if (values.input) { - const inputPath = path.resolve(process.cwd(), values.input); - if (fs.existsSync(inputPath)) { - const fileData = JSON.parse(fs.readFileSync(inputPath, 'utf-8')); - state = { ...state, ...fileData }; - } - } - - if (values.interactive) { - console.log('--- Mintel Quote Generator Wizard ---'); - - state.name = await ask('Client Name', state.name); - state.companyName = await ask('Company Name', state.companyName); - state.email = await ask('Client Email', state.email); - - state.projectType = (await selectOne('Project Type', [ - { id: 'website', label: 'Website' }, - { id: 'web-app', label: 'Web App' } - ], state.projectType)) as 'website' | 'web-app'; - - if (state.projectType === 'website') { - state.websiteTopic = await ask('Website Topic', state.websiteTopic); - state.selectedPages = await selectMany('Pages', PAGE_SAMPLES); - state.features = await selectMany('Features', FEATURE_OPTIONS); - state.functions = await selectMany('Functions', FUNCTION_OPTIONS); - state.apiSystems = await selectMany('APIs', API_OPTIONS); - - const cms = await ask('CMS Setup? (y/n)', state.cmsSetup ? 'y' : 'n'); - state.cmsSetup = cms.toLowerCase() === 'y'; - - const langs = await ask('Languages (comma separated)', state.languagesList.join(', ')); - state.languagesList = langs.split(',').map(l => l.trim()).filter(Boolean); - } else { - state.targetAudience = await selectOne('Target Audience', [ - { id: 'internal', label: 'Internal Tool' }, - { id: 'customers', label: 'Customer Portal' } - ], state.targetAudience); - - state.platformType = await selectOne('Platform', [ - { id: 'web-only', label: 'Web Only' }, - { id: 'cross-platform', label: 'Cross Platform' } - ], state.platformType); - - state.dataSensitivity = await selectOne('Data Sensitivity', [ - { id: 'standard', label: 'Standard' }, - { id: 'high', label: 'High / Sensitive' } - ], state.dataSensitivity); - } - - state.employeeCount = await selectOne('Company Size', EMPLOYEE_OPTIONS, state.employeeCount); - state.designVibe = await selectOne('Design Vibe', DESIGN_OPTIONS, state.designVibe); - state.deadline = await selectOne('Timeline', Object.entries(DEADLINE_LABELS).map(([id, label]) => ({ id, label })), state.deadline); - state.assets = await selectMany('Available Assets', ASSET_OPTIONS); - - state.message = await ask('Additional Message / Remarks', state.message); - } - - rl.close(); - - const totalPagesCount = state.selectedPages.length + state.otherPages.length + (state.otherPagesCount || 0); - const positions = calculatePositions(state, PRICING); - const totalPrice = positions.reduce((sum, p) => sum + p.price, 0); - const monthlyPrice = PRICING.HOSTING_MONTHLY + (state.storageExpansion * PRICING.STORAGE_EXPANSION_MONTHLY); - - const outputPath = values.output - ? path.resolve(process.cwd(), values.output) - : path.resolve(process.cwd(), `quote_${state.companyName.replace(/\s+/g, '_') || 'client'}_${new Date().toISOString().split('T')[0]}.pdf`); - - console.log(`\nGenerating PDF for ${state.companyName || state.name || 'Unknown Client'}...`); - - const headerIcon = path.resolve(process.cwd(), 'src/assets/logo/Icon White Transparent.png'); - const footerLogo = path.resolve(process.cwd(), 'src/assets/logo/Logo Black Transparent.png'); - - try { - // @ts-ignore - await renderToFile( - React.createElement(EstimationPDF, { - state, - totalPrice, - monthlyPrice, - totalPagesCount, - pricing: PRICING, - headerIcon, - footerLogo, - }), - outputPath - ); - console.log(`Successfully generated: ${outputPath}`); - } catch (error) { - console.error('Error generating PDF:', error); - process.exit(1); - } -} - -generate(); diff --git a/src/components/AgbsPDF.tsx b/src/components/AgbsPDF.tsx new file mode 100644 index 0000000..b8bcb33 --- /dev/null +++ b/src/components/AgbsPDF.tsx @@ -0,0 +1,154 @@ +'use client'; + +import * as React from 'react'; +import { + Page as PDFPage, + Text as PDFText, + View as PDFView, + StyleSheet as PDFStyleSheet, +} from '@react-pdf/renderer'; +import { pdfStyles, Header, Footer, FoldingMarks, DocumentTitle } from './pdf/SharedUI'; + +const localStyles = PDFStyleSheet.create({ + sectionContainer: { + marginTop: 0, + }, + agbSection: { + marginBottom: 20, + }, + labelRow: { + flexDirection: 'row', + alignItems: 'baseline', + marginBottom: 6, + }, + monoNumber: { + fontSize: 7, + fontWeight: 'bold', + color: '#94a3b8', + letterSpacing: 2, + width: 25, + }, + sectionTitle: { + fontSize: 9, + fontWeight: 'bold', + color: '#000000', + textTransform: 'uppercase', + letterSpacing: 0.5, + }, + officialText: { + fontSize: 8, + lineHeight: 1.5, + color: '#334155', + textAlign: 'justify', + paddingLeft: 25, + } +}); + +const AGBSection = ({ index, title, children }: { index: string; title: string; children: React.ReactNode }) => ( + + + {index} + {title} + + {children} + +); + +interface AgbsPDFProps { + state: any; + headerIcon?: string; + footerLogo?: string; +} + +export const AgbsPDF = ({ state, headerIcon, footerLogo }: AgbsPDFProps) => { + const date = new Date().toLocaleDateString('de-DE', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + + const companyData = { + name: "Marc Mintel", + address1: "Georg-Meistermann-Straße 7", + address2: "54586 Schüller", + ustId: "DE367588065" + }; + + const bankData = { + name: "N26", + bic: "NTSBDEB1XXX", + iban: "DE50 1001 1001 2620 4328 65" + }; + + return ( + + +
+ + + + + + Diese Allgemeinen Geschäftsbedingungen gelten für alle Verträge zwischen Marc Mintel (nachfolgend „Auftragnehmer“) und dem Auftraggeber. Abweichende Bedingungen des Auftraggebers werden nicht Vertragsbestandteil. + + + + Dienstleistungen in Webentwicklung, technischer Umsetzung und Hosting. Der Auftragnehmer schuldet eine fachgerechte technische Ausführung, jedoch keinen wirtschaftlichen Erfolg. + + + + Der Auftraggeber stellt alle erforderlichen Inhalte und Zugänge rechtzeitig bereit. Verzögerungen durch fehlende Mitwirkung gehen zu Lasten der Projektlaufzeit. + + + + Die Abnahme erfolgt durch produktive Nutzung oder Ablauf von 7 Tagen nach Projektabschluss. Subjektives Nichtgefallen stellt keinen technischen Mangel dar. + + + + Haftung besteht nur bei Vorsatz oder grober Fahrlässigkeit. Die Haftung für indirekte Schäden oder entgangenen Gewinn wird ausgeschlossen. + + + + Wartung sichert den Betrieb des Ist-Zustands. Erweiterungen oder Funktionsänderungen sind separat zu beauftragen. + + + + Alle Preise netto. Fälligkeit innerhalb von 7 Tagen. Bei erheblichem Verzug ist der Auftragnehmer berechtigt, die Leistung einzustellen. + + + +