Files
mintel.me/scripts/generate-estimate.ts

132 lines
4.6 KiB
TypeScript

import * as fs from 'node:fs';
import * as path from 'node:path';
import * as readline from 'node:readline/promises';
import { fileURLToPath } from 'node:url';
import { createElement } from 'react';
import { renderToFile } from '@react-pdf/renderer';
import { calculatePositions, calculateTotals } from '../src/logic/pricing/calculator.js';
import { CombinedQuotePDF } from '../src/components/CombinedQuotePDF.js';
import { initialState, PRICING } from '../src/logic/pricing/constants.js';
import { getTechDetails, getPrinciples } from '../src/logic/content-provider.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function main() {
const args = process.argv.slice(2);
const isInteractive = args.includes('--interactive') || args.includes('-I');
const isEstimationOnly = args.includes('--estimation') || args.includes('-E');
const inputPath = args.find((_, i) => args[i - 1] === '--input' || args[i - 1] === '-i');
let state = { ...initialState };
if (inputPath) {
const rawData = fs.readFileSync(path.resolve(process.cwd(), inputPath), 'utf8');
const diskState = JSON.parse(rawData);
state = { ...state, ...diskState };
}
if (isInteractive) {
state = await runWizard(state);
}
// Final confirmation of data needed for PDF
if (!state.name || !state.email) {
console.warn('⚠️ Missing recipient name or email. Document might look incomplete.');
}
const totals = calculateTotals(state, PRICING);
const { totalPrice, monthlyPrice, totalPagesCount } = totals;
const finalOutputPath = generateDefaultPath(state);
const outputDir = path.dirname(finalOutputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Resolve assets for the PDF
const assetsDir = path.resolve(process.cwd(), 'src/assets');
const headerIcon = path.join(assetsDir, 'logo/Icon White Transparent.png');
const footerLogo = path.join(assetsDir, 'logo/Logo Black Transparent.png');
console.log(`🚀 Generating PDF: ${finalOutputPath}`);
const estimationProps = {
state,
totalPrice,
monthlyPrice,
totalPagesCount,
pricing: PRICING,
headerIcon,
footerLogo
};
await renderToFile(
createElement(CombinedQuotePDF as any, {
estimationProps,
techDetails: getTechDetails(),
principles: getPrinciples(),
mode: isEstimationOnly ? 'estimation' : 'full',
showAgbs: !isEstimationOnly // AGBS only for full quotes
}) as any,
finalOutputPath
);
console.log('✅ Done!');
}
async function runWizard(state: any) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log('\n--- Mintel Quote Generator Wizard ---\n');
const ask = async (q: string, def?: string) => {
const answer = await rl.question(`${q}${def ? ` [${def}]` : ''}: `);
return answer || def || '';
};
const selectOne = async (q: string, options: { id: string, label: string }[]) => {
console.log(`\n${q}:`);
options.forEach((opt, i) => console.log(`${i + 1}) ${opt.label}`));
const answer = await rl.question('Selection (number): ');
const idx = parseInt(answer) - 1;
return options[idx]?.id || options[0].id;
};
state.name = await ask('Recipient Name', state.name);
state.email = await ask('Recipient Email', state.email);
state.companyName = await ask('Company Name', state.companyName);
state.projectType = await selectOne('Project Type', [
{ id: 'website', label: 'Website' },
{ id: 'web-app', label: 'Web App' }
]);
if (state.projectType === 'website') {
state.websiteTopic = await ask('Website Topic', state.websiteTopic);
// Simplified for now, in a real tool we'd loop through all options
}
rl.close();
return state;
}
function generateDefaultPath(state: any) {
const now = new Date();
const month = now.toISOString().slice(0, 7);
const day = now.toISOString().slice(0, 10);
// Add seconds and minutes for 100% unique names without collision
const time = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }).replace(/:/g, '-');
const company = (state.companyName || state.name || 'Unknown').replace(/[^a-z0-9]/gi, '_');
return path.join(process.cwd(), 'out', 'estimations', month, `${day}_${time}_${company}_${state.projectType}.pdf`);
}
main().catch(err => {
console.error('❌ Error:', err);
process.exit(1);
});