From 523060614498a6f12129defc6356afa39e17bd2b Mon Sep 17 00:00:00 2001 From: bonamin Date: Mon, 28 Sep 2026 11:50:29 +0300 Subject: [PATCH] feat(manager): show LAN waiter/manager addresses and pairing QR in App Info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App Info now lists 'Σερβιτόροι (WiFi)' http:// with a QR code encoding http:///?pair= (phone camera opens the waiter app; the native app will read the same code) and 'Διαχείριση (WiFi)' http:///manager. The LAN IP comes from HOST_IP, else from the dashboard's own address when opened by IP; otherwise a hint to set HOST_IP. QRModal takes an optional caption; the legacy waiter-domain QR is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../src/pages/Settings/tabs/AppInfoTab.jsx | 63 ++++++++++++++++--- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/manager_dashboard/src/pages/Settings/tabs/AppInfoTab.jsx b/manager_dashboard/src/pages/Settings/tabs/AppInfoTab.jsx index 330fd6a..3a858df 100644 --- a/manager_dashboard/src/pages/Settings/tabs/AppInfoTab.jsx +++ b/manager_dashboard/src/pages/Settings/tabs/AppInfoTab.jsx @@ -302,7 +302,7 @@ function DataTransferSection() { } -function QRModal({ url, onClose }) { +function QRModal({ url, caption = 'Σαρώστε με το κινητό για σύνδεση στο σύστημα.', onClose }) { return (
-

- Σαρώστε με το κινητό για σύνδεση στο σύστημα. -

+

{caption}

) } +const IPV4 = /^\d{1,3}(\.\d{1,3}){3}$/ + +// Address phones use on the LAN. HOST_IP from the server wins; otherwise, if this +// dashboard was itself opened by IP (http://192.168.x.x:8081), that IP is the server. +function resolveLanIp(status) { + if (status?.lan_ip) return status.lan_ip + const host = window.location.hostname + return IPV4.test(host) && !host.startsWith('127.') ? host : null +} + +// Pairing URL: opens the waiter app when scanned with the phone camera, and the +// native app reads the same code (?pair=) to pair with this venue. +function waiterPairUrl(lanIp, siteId) { + return `http://${lanIp}/` + (siteId ? `?pair=${encodeURIComponent(siteId)}` : '') +} + function formatUptime(seconds) { const h = Math.floor(seconds / 3600) const m = Math.floor((seconds % 3600) / 60) @@ -334,7 +348,7 @@ export default function AppInfoTab() { const user = useAuthStore(s => s.user) const qc = useQueryClient() const [refreshing, setRefreshing] = useState(false) - const [qrOpen, setQrOpen] = useState(false) + const [qr, setQr] = useState(null) // { url, caption } | null const { data: status, isLoading } = useQuery({ queryKey: ['system-status'], queryFn: () => client.get('/api/system/status').then(r => r.data), @@ -414,13 +428,46 @@ export default function AppInfoTab() {
{new Date(status.expires_at).toLocaleDateString('el-GR')}
)} + {(() => { + const lanIp = resolveLanIp(status) + if (!lanIp) { + return ( + <> +
Τοπική πρόσβαση
+
+ Άγνωστη IP του server — ορίστε HOST_IP στο .env +
+ + ) + } + const pairUrl = waiterPairUrl(lanIp, status?.site_id) + return ( + <> +
Σερβιτόροι (WiFi)
+
+ http://{lanIp} + +
+
Διαχείριση (WiFi)
+
http://{lanIp}/manager
+ + ) + })()} {status?.waiter_domain && ( <>
Waiter Domain
{status.waiter_domain}
)} - {qrOpen && status?.waiter_domain && ( - setQrOpen(false)} /> - )} + {qr && setQr(null)} />} ) }