feat: initialize inventory management application with Prisma schema, QR scanning, and UI components
This commit is contained in:
186
app/print/page.js
Normal file
186
app/print/page.js
Normal file
@ -0,0 +1,186 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import QRCode from "react-qr-code";
|
||||
import { Printer, AlertCircle } from "lucide-react";
|
||||
|
||||
export default function PrintPage() {
|
||||
const [items, setItems] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchItems = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/inventory");
|
||||
if (!res.ok) throw new Error("Failed to fetch inventory from database");
|
||||
const data = await res.json();
|
||||
setItems(data);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchItems();
|
||||
}, []);
|
||||
|
||||
const handlePrint = () => {
|
||||
window.print();
|
||||
};
|
||||
|
||||
if (loading) return <div className="p-8 text-center">Loading inventory codes...</div>;
|
||||
if (error) return <div className="p-8 text-danger">Error: {error}</div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* On-screen Controls (Hidden in Print) */}
|
||||
<div className="glass no-print" style={{ padding: "2rem", marginBottom: "2rem" }}>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 className="flex items-center gap-2 mb-2">
|
||||
<Printer size={24} className="text-primary" />
|
||||
Print QR Labels
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
Designed for <strong>Avery 5160</strong> standard labels (1" x 2-5/8", 30 per sheet).
|
||||
</p>
|
||||
</div>
|
||||
<button className="btn btn-primary" onClick={handlePrint}>
|
||||
<Printer size={18} /> Print Now
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-start gap-4 mt-4" style={{ backgroundColor: "rgba(59, 130, 246, 0.1)", padding: "1rem", borderRadius: "8px", border: "1px solid var(--primary)" }}>
|
||||
<AlertCircle size={24} className="text-primary flex-shrink-0" />
|
||||
<div style={{ fontSize: "0.9rem" }}>
|
||||
<strong>Printer Settings to check before printing:</strong>
|
||||
<ul style={{ listStyleType: "disc", marginLeft: "1.5rem", marginTop: "0.5rem" }}>
|
||||
<li>Scale: <strong>100%</strong> (Do not "Fit to Page")</li>
|
||||
<li>Margins: <strong>Custom / None</strong> (Layout handles margins natively)</li>
|
||||
<li>Paper Size: <strong>Letter (8.5x11)</strong></li>
|
||||
<li>Headers and Footers: <strong>Disable</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Printable Sheet Area */}
|
||||
<div className="print-container">
|
||||
{items.map((item, index) => (
|
||||
<div key={item.id} className="label-cell">
|
||||
<div className="qr-wrapper">
|
||||
<QRCode
|
||||
value={item.qrCodeId || item.id}
|
||||
size={64}
|
||||
level="H"
|
||||
style={{ height: "auto", maxWidth: "100%", width: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="label-text">
|
||||
<div className="item-name">{item.name}</div>
|
||||
<div className="item-sku">{item.qrCodeId}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
/* Standard View Adjustments */
|
||||
.print-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
min-height: 50vh;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.label-cell {
|
||||
width: calc(33.333% - 1rem); /* Desktop preview layout */
|
||||
height: 1in;
|
||||
border: 1px dashed #ccc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.125in;
|
||||
box-sizing: border-box;
|
||||
color: black;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.qr-wrapper {
|
||||
flex-shrink: 0;
|
||||
width: 0.75in;
|
||||
height: 0.75in;
|
||||
margin-right: 0.1in;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.item-sku {
|
||||
font-size: 0.65rem;
|
||||
color: #555;
|
||||
margin-top: 0.1rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: letter;
|
||||
margin: 0; /* Reset browser margins entirely */
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white !important;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.no-print, nav, .navbar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Avery 5160 Layout Grid */
|
||||
.print-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 2.625in);
|
||||
grid-template-rows: repeat(10, 1in);
|
||||
gap: 0;
|
||||
padding: 0.5in 0.21975in; /* Top/Bottom 0.5", Left/Right approx 0.22" */
|
||||
width: 8.5in;
|
||||
height: 11in;
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.print-container {
|
||||
column-gap: 0.125in;
|
||||
row-gap: 0;
|
||||
}
|
||||
|
||||
.label-cell {
|
||||
width: 2.625in !important;
|
||||
height: 1in !important;
|
||||
border: none !important; /* Remove borders for actual print */
|
||||
margin: 0;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user