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();