Files
craft2prints/src/app/api/orders/[id]/design-spec/route.ts
T
Andymick a1c6e022b8 Fix design spec measurements and add visual X/Y axis guides
- Fixed conversion formula for accurate CM measurements
- Add red dashed border around print area
- Add blue X-axis line (horizontal, 0cm at left)
- Add green Y-axis line (vertical, 0cm at top)
- Add orange circles marking design element positions
- Add reference legend explaining all visual guides
- Element labels changed from E1, E2 for clarity
2026-07-18 10:36:51 +01:00

250 lines
8.9 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 printAreaWidthPx = DISPLAY * (printArea.wPct / 100);
const printAreaHeightPx = DISPLAY * (printArea.hPct / 100);
// Scale: how many pixels per millimeter
const pxPerMm = printAreaWidthPx / printAreaWidthMm;
// Convert pixel measurements to CM (divide by pxPerMm to get mm, then divide by 10 for cm)
const convertToCm = (px: number) => {
const mm = px / pxPerMm;
return (mm / 10).toFixed(1);
};
const printAreaStartX = DISPLAY * (printArea.xPct / 100);
const printAreaStartY = DISPLAY * (printArea.yPct / 100);
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 (reference only) — with measurement guides</em></p>
<div style="position: relative; display: inline-block; max-width: 100%; max-height: 400px;">
<img src="${placement}" alt="${side} design placement" class="mockup-image" style="max-height: 400px; width: auto; display: block;">
<svg style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;" viewBox="0 0 ${DISPLAY} ${DISPLAY}">
<!-- Print area border -->
<rect x="${printAreaStartX}" y="${printAreaStartY}" width="${printAreaWidthPx}" height="${printAreaHeightPx}" fill="none" stroke="#ff0000" stroke-width="2" stroke-dasharray="5,5" opacity="0.7"/>
<!-- X-axis line -->
<line x1="${printAreaStartX}" y1="${printAreaStartY}" x2="${printAreaStartX + printAreaWidthPx}" y2="${printAreaStartY}" stroke="#0066ff" stroke-width="1" opacity="0.5"/>
<!-- Y-axis line -->
<line x1="${printAreaStartX}" y1="${printAreaStartY}" x2="${printAreaStartX}" y2="${printAreaStartY + printAreaHeightPx}" stroke="#00cc00" stroke-width="1" opacity="0.5"/>
<!-- Design elements with coordinate markers -->
${designElements.map((el, idx) => `
<circle cx="${el.x}" cy="${el.y}" r="4" fill="#ff6600" opacity="0.9"/>
<text x="${el.x + 8}" y="${el.y - 8}" font-size="11" fill="#ff6600" 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;">
<p style="margin: 5px 0;"><strong style="color: #ff0000;">━━ Red dashed:</strong> Print area boundary</p>
<p style="margin: 5px 0;"><strong style="color: #0066ff;">━ Blue:</strong> X-axis (0cm at left)</p>
<p style="margin: 5px 0;"><strong style="color: #00cc00;">━ Green:</strong> Y-axis (0cm at top)</p>
<p style="margin: 5px 0;"><strong style="color: #ff6600;">● Orange circles:</strong> Element positions with labels (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(el.x)} | Y: ${convertToCm(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 / pxPerMm * 10 / 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;
border: 1px solid #ddd;
margin: 10px 0;
}
.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>Coordinates are measured from top-left of the printable area (0,0)</li>
<li>X-axis runs left-to-right | Y-axis runs top-to-bottom</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 });
}
}