feat(sysadmin): Part 4 — QR code section in SiteDetailPage

- Install qrcode.react in sysadmin_panel
- Add QR Codes section above Remote Managers with two codes side by side:
  Menu QR (https://yourdomain.com/menu/{site_id}) and
  Order QR (https://yourdomain.com/menu/{site_id}/order)
- Each card shows the URL as copyable text and a Download PNG button
- Uses site.site_id as the slug (no slug field exists on the Site model)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 10:07:28 +03:00
parent ac58d150ea
commit 82b853369b
3 changed files with 55 additions and 0 deletions

View File

@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"axios": "^1.7.9",
"qrcode.react": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",
@@ -2472,6 +2473,15 @@
"node": ">=10"
}
},
"node_modules/qrcode.react": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz",
"integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==",
"license": "ISC",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",

View File

@@ -10,6 +10,7 @@
},
"dependencies": {
"axios": "^1.7.9",
"qrcode.react": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import toast from 'react-hot-toast'
import { QRCodeCanvas } from 'qrcode.react'
import client, { getManagersBySite, addManagerToSite, removeManagerSiteAccess } from '../api/client'
import LicenseStatus from '../components/LicenseStatus'
import ConfirmModal from '../components/ConfirmModal'
@@ -322,6 +323,49 @@ export default function SiteDetailPage() {
</div>
</div>
{/* QR Codes */}
{(() => {
const slug = site.site_id
const menuUrl = `https://yourdomain.com/menu/${slug}`
const orderUrl = `https://yourdomain.com/menu/${slug}/order`
function handleDownload(canvasId, filename) {
const canvas = document.getElementById(canvasId)
if (!canvas) return
const url = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
}
return (
<div className="bg-gray-900 border border-gray-700 rounded-xl p-4 mt-4">
<h2 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-4">QR Codes</h2>
<div className="flex flex-wrap gap-8">
{[
{ id: `qr-menu-${site.id}`, url: menuUrl, label: 'Menu QR Code', file: `menu-qr-${slug}.png` },
{ id: `qr-order-${site.id}`, url: orderUrl, label: 'Order QR Code', file: `order-qr-${slug}.png` },
].map(({ id, url, label, file }) => (
<div key={id} className="flex flex-col items-center gap-3">
<p className="text-xs font-medium text-gray-400">{label}</p>
<div className="bg-white p-3 rounded-xl">
<QRCodeCanvas id={id} value={url} size={180} includeMargin={false} />
</div>
<p className="text-xs text-gray-600 font-mono break-all max-w-[220px] text-center">{url}</p>
<button
onClick={() => handleDownload(id, file)}
className="text-xs text-cyan-400 hover:text-cyan-300 transition-colors"
>
Download PNG
</button>
</div>
))}
</div>
</div>
)
})()}
{/* Remote Managers */}
<div className="bg-gray-900 border border-gray-700 rounded-xl p-4 mt-4">
<div className="flex items-center justify-between mb-3">