netgescon-day0/resources/views/filament/components/archive-code-launcher.blade.php

96 lines
4.7 KiB
PHP

<div
class="rounded-2xl border border-amber-200 bg-gradient-to-r from-amber-50 via-white to-sky-50 p-4"
x-data="{
scannerActive: false,
scannerError: '',
stream: null,
detector: null,
rafId: null,
baseUrl: '/admin-filament/strumenti/documenti?tab=lettore-codici',
openReader(rawValue) {
const value = (rawValue || this.$refs.archiveCode?.value || '').trim();
if (value === '') {
return;
}
window.location.href = `${this.baseUrl}&code=${encodeURIComponent(value)}`;
},
async startScanner() {
this.scannerError = '';
if (!('BarcodeDetector' in window)) {
this.scannerError = 'BarcodeDetector non disponibile in questo browser. Usa il lettore esterno, il telefono o un tag NFC in input testo.';
return;
}
try {
this.detector = new BarcodeDetector({ formats: ['qr_code'] });
this.stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
this.$refs.video.srcObject = this.stream;
await this.$refs.video.play();
this.scannerActive = true;
this.scanFrame();
} catch (error) {
this.scannerError = 'Impossibile avviare la camera: ' + (error?.message || 'permesso negato');
}
},
stopScanner() {
this.scannerActive = false;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.stream) {
this.stream.getTracks().forEach((track) => track.stop());
this.stream = null;
}
if (this.$refs.video) {
this.$refs.video.srcObject = null;
}
},
async scanFrame() {
if (!this.scannerActive || !this.detector || !this.$refs.video) {
return;
}
try {
const codes = await this.detector.detect(this.$refs.video);
if (codes.length > 0 && (codes[0]?.rawValue || '') !== '') {
this.$refs.archiveCode.value = codes[0].rawValue;
this.openReader(codes[0].rawValue);
this.stopScanner();
return;
}
} catch (error) {
this.scannerError = 'Errore lettura QR: ' + (error?.message || 'lettura non disponibile');
this.stopScanner();
return;
}
this.rafId = requestAnimationFrame(() => this.scanFrame());
},
}"
>
<div class="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
<div>
<div class="text-sm font-semibold text-slate-900">{{ $title ?? 'Lettore QR archivio' }}</div>
<div class="mt-1 text-xs text-slate-600">{{ $description ?? 'Scansiona un QR archivio e apri subito il contenuto del nodo selezionato.' }}</div>
</div>
<div class="rounded-full bg-white px-3 py-1 text-xs font-semibold text-slate-700 shadow-sm">Visualizza contenuto senza aprire il contenitore</div>
</div>
<div class="mt-4 grid gap-3 md:grid-cols-[minmax(0,1fr)_auto_auto]">
<div>
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Codice archivio / payload QR / NFC</label>
<input x-ref="archiveCode" type="text" class="w-full rounded-lg border-slate-200 bg-white text-sm shadow-sm" placeholder="Es. AF-20-0001 oppure NGDOC|AF:AF-20-0001|..." x-on:keydown.enter.prevent="openReader()" />
</div>
<button type="button" class="inline-flex items-center self-end rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50" x-on:click="openReader()">Apri lettore</button>
<button type="button" class="inline-flex items-center self-end rounded-lg bg-slate-900 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-slate-800" x-on:click="scannerActive ? stopScanner() : startScanner()">
<span x-text="scannerActive ? 'Ferma camera' : 'Leggi QR'"></span>
</button>
</div>
<div class="mt-3 rounded-lg border border-dashed border-slate-300 bg-white/80 p-3" x-show="scannerActive || scannerError !== ''" x-cloak>
<template x-if="scannerActive">
<video x-ref="video" class="aspect-video w-full rounded-lg border border-slate-300 bg-slate-900" playsinline muted></video>
</template>
<template x-if="scannerError !== ''">
<div class="text-xs text-amber-800" x-text="scannerError"></div>
</template>
</div>
</div>