- 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
250 lines
8.9 KiB
TypeScript
250 lines
8.9 KiB
TypeScript
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 });
|
||
}
|
||
}
|