qrcode printing
This commit is contained in:
@@ -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>
|
||||
|
||||
165
src/components/cables/PrintQRCodeButton.tsx
Normal file
165
src/components/cables/PrintQRCodeButton.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user