qrcode printing

This commit is contained in:
2026-06-13 21:28:16 +02:00
parent 9a5e0565cb
commit 7e7edbaeda
4 changed files with 444 additions and 4 deletions

View File

@@ -8,6 +8,7 @@ import MarkdownContent from "@/components/ui/MarkdownContent";
import CoresList from "@/components/cores/CoresList";
import NewCoreForm from "@/components/cores/NewCoreForm";
import DeleteCableButton from "@/components/cables/DeleteCableButton";
import PrintQRCodeButton from "@/components/cables/PrintQRCodeButton";
// Next.js 15: dynamic route params are a Promise
interface CableDetailPageProps {
@@ -82,6 +83,7 @@ export default async function CableDetailPage({
</div>
<div className="flex items-center gap-3">
<Link href={`/cables/${cable.id}/edit`} className="text-sm text-blue-600 hover:text-blue-800">Bearbeiten</Link>
<PrintQRCodeButton url={`http://192.168.178.20:3000/cables/${cable.id}`}/>
<DeleteCableButton id={cable.id} identifier={cable.identifier} />
</div>
</div>

View File

@@ -0,0 +1,165 @@
"use client";
import { useState, useEffect, useRef } from "react";
import QRCode from "qrcode";
import Image from "next/image";
export default function PrintQRCodeButton({ url }: { url: string }) {
const [isOpen, setIsOpen] = useState(false);
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
const dialogRef = useRef<HTMLDivElement>(null);
// Generate QR code whenever the modal opens
useEffect(() => {
if (!isOpen) return;
QRCode.toDataURL(url, {
width: 240,
margin: 2,
color: { dark: "#0f172a", light: "#ffffff" }, // slate-900 on white
})
.then(setQrDataUrl)
.catch(console.error);
}, [isOpen, url]);
// Close on Escape
useEffect(() => {
if (!isOpen) return;
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setIsOpen(false);
};
window.addEventListener("keydown", handleKey);
return () => window.removeEventListener("keydown", handleKey);
}, [isOpen]);
const handlePrint = () => {
if (!qrDataUrl) return;
const win = window.open("", "_blank");
if (!win) return;
win.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>QR-Code</title>
<style>
body {
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
gap: 12px;
}
img { width: 240px; height: 240px; display: block; }
</style>
</head>
<body>
<img src="${qrDataUrl}" alt="QR Code" />
</body>
</html>
`);
win.document.close();
win.focus();
win.print();
};
return (
<>
{/* Trigger button styled to sit alongside the "Bearbeiten" link */}
<button
onClick={() => setIsOpen(true)}
className="text-sm text-slate-600 hover:text-slate-800 transition-colors flex items-center gap-1.5"
aria-label="QR-Code anzeigen"
>
<svg
className="w-4 h-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="14" y="3" width="7" height="7" rx="1" />
<rect x="3" y="14" width="7" height="7" rx="1" />
<rect x="14" y="14" width="3" height="3" />
<rect x="18" y="18" width="3" height="3" />
<rect x="14" y="18" width="3" height="3" rx="0.5" />
<rect x="18" y="14" width="3" height="3" rx="0.5" />
</svg>
QR-Code
</button>
{/* Modal backdrop */}
{isOpen && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 backdrop-blur-sm"
onClick={() => setIsOpen(false)}
>
{/* Modal panel */}
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-label="QR-Code"
className="bg-white rounded-2xl shadow-xl border border-slate-200 p-6 flex flex-col items-center gap-5 w-72"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="w-full flex items-center justify-between">
<h2 className="text-sm font-semibold text-slate-900">QR-Code</h2>
<button
onClick={() => setIsOpen(false)}
className="text-slate-400 hover:text-slate-600 transition-colors"
aria-label="Schließen"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round">
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
</div>
{/* QR image or skeleton */}
{qrDataUrl ? (
<img
src={qrDataUrl}
alt={`QR-Code für ${url}`}
className="w-48 h-48 rounded-lg border border-slate-100"
/>
) : (
<div className="w-48 h-48 rounded-lg border border-slate-100 bg-slate-50 animate-pulse" />
)}
{/* URL label */}
<p className="text-xs text-slate-400 break-all text-center leading-relaxed max-w-[200px]">
{url}
</p>
{/* Actions */}
<div className="flex gap-2 w-full">
<button
onClick={handlePrint}
disabled={!qrDataUrl}
className="flex-1 flex items-center justify-center gap-1.5 bg-blue-600 hover:bg-blue-700 disabled:opacity-40 text-white text-sm font-medium px-4 py-2 rounded-lg transition-colors"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 6 2 18 2 18 9" />
<path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" />
<rect x="6" y="14" width="12" height="8" />
</svg>
Drucken
</button>
<button
onClick={() => setIsOpen(false)}
className="flex-1 border border-slate-200 text-slate-600 hover:bg-slate-50 text-sm font-medium px-4 py-2 rounded-lg transition-colors"
>
Schließen
</button>
</div>
</div>
</div>
)}
</>
);
}