Files
craft2prints/src/app/api/orders/[id]/design-spec/route.ts
T
Andymick 58869263bd Fix design spec SVG overlay and decimal point in measurements
- Use preserveAspectRatio='none' to properly scale SVG lines to image
- Fix measurement formula: subtract print area offset, multiply by mmPerPx
- Add axis labels showing 0cm origin points
- Correct decimal precision in all dimension calculations
- Lines now properly overlay on product mockup image
2026-07-18 10:45:02 +01:00

266 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function GET(req: Request, { params }: { params: { id: string } }) {
try {
const { id } = params;
const order = await prisma.order.findUnique({
where: { id },
include: { items: true },
});
if (!order) {
return NextResponse.json({ error: 'Order not found' }, { status: 404 });
}
const item = order.items[0];
if (!item) {
return NextResponse.json({ error: 'No items in order' }, { status: 400 });
}
const design = JSON.parse(item.designJson) as { front: any[]; back: any[] };
const product = await prisma.product.findUnique({
where: { id: item.productId },
});
if (!product) {
return NextResponse.json({ error: 'Product not found' }, { status: 404 });
}
// Generate HTML/CSS for a printable spec sheet
const printAreaWidthMm = product.printAreaWidthMm || 200;
const printAreaHeightMm = product.printAreaHeightMm || 280;
// Calculate scale factor: pixels to millimeters
const DISPLAY = 560; // Same as in DesignCanvas
const printArea = JSON.parse(product.printArea) as {
xPct: number;
yPct: number;
wPct: number;
hPct: number;
};
const printAreaStartX = DISPLAY * (printArea.xPct / 100);
const printAreaStartY = DISPLAY * (printArea.yPct / 100);
const printAreaWidthPx = DISPLAY * (printArea.wPct / 100);
const printAreaHeightPx = DISPLAY * (printArea.hPct / 100);
// Scale: how many millimeters per pixel
const mmPerPx = printAreaWidthMm / printAreaWidthPx;
// Convert pixel measurements to CM
// First: subtract the print area offset to get position within print area
// Then: multiply by mmPerPx to get millimeters
// Finally: divide by 10 to get centimeters
const convertToCm = (px: number, elementPx: number | null = null) => {
if (elementPx !== null) {
// For element positions: subtract offset and scale
const relativeToStart = elementPx - px;
const mm = Math.max(0, relativeToStart * mmPerPx);
return (mm / 10).toFixed(1);
} else {
// For dimensions: just scale
const mm = px * mmPerPx;
return (mm / 10).toFixed(1);
}
};
const generateDesignSection = (designElements: any[], side: 'Front' | 'Back', placement: string | null, designPreview: string | null) => {
if (!designElements || designElements.length === 0) return '';
return `
<div class="spec-section">
<h2>${side} Design</h2>
<div class="print-area-info">
<strong>Printable Area:</strong> ${printAreaWidthMm}mm × ${printAreaHeightMm}mm (${convertToCm(printAreaWidthPx)} cm × ${convertToCm(printAreaHeightPx)} cm)
</div>
${placement ? `
<div class="mockup-container">
<p><em>Design on product with measurement guides</em></p>
<div style="position: relative; display: inline-block; margin: 15px 0;">
<img src="${placement}" alt="${side} design placement" class="mockup-image" style="max-height: 400px; width: auto; display: block; border: 1px solid #ddd;">
<svg style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;" preserveAspectRatio="none">
<!-- Print area border -->
<rect x="${printAreaStartX}" y="${printAreaStartY}" width="${printAreaWidthPx}" height="${printAreaHeightPx}"
fill="none" stroke="red" stroke-width="2" stroke-dasharray="5,5" opacity="0.8"/>
<!-- X-axis line (horizontal) -->
<line x1="${printAreaStartX}" y1="${printAreaStartY}" x2="${printAreaStartX + printAreaWidthPx}" y2="${printAreaStartY}"
stroke="blue" stroke-width="1.5" opacity="0.6"/>
<!-- Y-axis line (vertical) -->
<line x1="${printAreaStartX}" y1="${printAreaStartY}" x2="${printAreaStartX}" y2="${printAreaStartY + printAreaHeightPx}"
stroke="green" stroke-width="1.5" opacity="0.6"/>
<!-- Axis labels -->
<text x="${printAreaStartX + 5}" y="${printAreaStartY - 5}" font-size="12" fill="blue" font-weight="bold">X (0cm)</text>
<text x="${printAreaStartX - 25}" y="${printAreaStartY + 15}" font-size="12" fill="green" font-weight="bold">Y (0cm)</text>
<!-- Design elements with markers -->
${designElements.map((el, idx) => `
<circle cx="${el.x}" cy="${el.y}" r="5" fill="orange" opacity="0.9" stroke="white" stroke-width="1"/>
<text x="${el.x + 10}" y="${el.y - 10}" font-size="12" fill="orange" font-weight="bold" font-family="monospace">E${idx + 1}</text>
`).join('')}
</svg>
</div>
<div style="margin-top: 15px; font-size: 12px; color: #666; text-align: left; max-width: 400px;">
<p style="margin: 5px 0;"><strong style="color: red;">━━ Red dashed line:</strong> Print area boundary</p>
<p style="margin: 5px 0;"><strong style="color: blue;">━ Blue line:</strong> X-axis (horizontal, 0cm at left)</p>
<p style="margin: 5px 0;"><strong style="color: green;">━ Green line:</strong> Y-axis (vertical, 0cm at top)</p>
<p style="margin: 5px 0;"><strong style="color: orange;">● Orange circles:</strong> Element positions (E1, E2, etc.)</p>
</div>
</div>
` : ''}
<table class="measurements-table">
<thead>
<tr>
<th>Element</th>
<th>Type</th>
<th>Position (CM)</th>
<th>Size (CM)</th>
<th>Details</th>
</tr>
</thead>
<tbody>
${designElements.map((el, idx) => `
<tr>
<td>E${idx + 1}</td>
<td class="element-type">${el.type === 'text' ? '📝 Text' : '🖼️ Image'}</td>
<td>X: ${convertToCm(printAreaStartX, el.x)} | Y: ${convertToCm(printAreaStartY, el.y)}</td>
<td>${el.type === 'image' ? `${convertToCm(el.width)} × ${convertToCm(el.height)}` : 'N/A'}</td>
<td>
${el.type === 'text' ? `
Font: ${el.fontFamily}, ${Math.round(el.fontSize * mmPerPx / 10)}pt<br>
Color: ${el.fill}<br>
Text: "${el.text}"<br>
Rotation: ${Math.round(el.rotation)}°
` : `
Rotation: ${Math.round(el.rotation)}°
`}
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
};
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Design Specification - Order ${order.id}</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
max-width: 1000px;
margin: 0 auto;
}
h1 { font-size: 24px; margin-bottom: 10px; }
.order-info {
background: #f5f5f5;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.order-info p {
margin: 5px 0;
font-size: 14px;
}
.spec-section {
margin-top: 30px;
page-break-inside: avoid;
}
.spec-section h2 {
font-size: 18px;
border-bottom: 2px solid #333;
padding-bottom: 10px;
margin-bottom: 15px;
}
.mockup-container {
text-align: center;
margin: 20px 0;
}
.mockup-image {
max-width: 100%;
height: auto;
display: block;
}
.measurements-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
font-size: 14px;
}
.measurements-table th,
.measurements-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.measurements-table th {
background: #f0f0f0;
font-weight: bold;
}
.element-type {
font-weight: bold;
color: #333;
}
.print-area-info {
background: #e8f4f8;
padding: 10px;
border-radius: 5px;
margin: 15px 0;
font-size: 13px;
}
@media print {
body { padding: 0; }
.spec-section { page-break-inside: avoid; }
}
</style>
</head>
<body>
<h1>Design Specification Sheet</h1>
<div class="order-info">
<p><strong>Order ID:</strong> ${order.id}</p>
<p><strong>Product:</strong> ${item.productName}</p>
<p><strong>Color:</strong> ${item.color} ${item.size ? `| Size: ${item.size}` : ''}</p>
<p><strong>Order Date:</strong> ${new Date(order.createdAt).toLocaleDateString()}</p>
</div>
${generateDesignSection(design.front, 'Front', item.placementPreviewUrl, item.designPreviewUrl)}
${generateDesignSection(design.back, 'Back', item.placementPreviewUrlBack, item.designPreviewUrlBack)}
<div class="spec-section" style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ccc; font-size: 12px; color: #666;">
<p><strong>Notes:</strong></p>
<ul>
<li>All measurements are in centimeters (CM)</li>
<li>X-axis: 0cm at left edge of print area, increases to the right</li>
<li>Y-axis: 0cm at top edge of print area, increases downward</li>
<li>Print the design files separately for production</li>
<li>This specification sheet is for reference and planning only</li>
</ul>
</div>
</body>
</html>
`;
return new NextResponse(html, {
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Content-Disposition': `attachment; filename="design-spec-${order.id}.html"`,
},
});
} catch (err) {
console.error('Error generating design spec:', err);
return NextResponse.json({ error: 'Failed to generate specification' }, { status: 500 });
}
}