Merge branch 'Antrean-Code' of https://git.rssa.top/arie.bagus.2905/web-antrean into Antrean-Code
This commit is contained in:
+46
-16
@@ -1,9 +1,9 @@
|
||||
// composables/useWebSocket.ts
|
||||
import { ref, computed, onUnmounted, getCurrentInstance } from 'vue'
|
||||
import { ref, computed, onUnmounted, getCurrentInstance, toValue } from 'vue'
|
||||
|
||||
export interface WebSocketConfig {
|
||||
url: string
|
||||
clientId: string
|
||||
url: string | any
|
||||
clientId: string | any
|
||||
onMessage?: (data: any) => void
|
||||
onOpen?: () => void
|
||||
onClose?: () => void
|
||||
@@ -24,8 +24,11 @@ export const useWebSocket = (config: WebSocketConfig) => {
|
||||
const reconnectAttempts = ref(0)
|
||||
const reconnectTimer = ref<NodeJS.Timeout | null>(null)
|
||||
|
||||
const currentUrl = computed(() => toValue(config.url))
|
||||
const currentClientId = computed(() => toValue(config.clientId))
|
||||
|
||||
const wsUrl = computed(() => {
|
||||
let baseUrl = config.url
|
||||
let baseUrl = currentUrl.value
|
||||
|
||||
// Automatically upgrade to secure protocols if caller uses HTTPS
|
||||
if (typeof window !== 'undefined' && window.location.protocol === 'https:') {
|
||||
@@ -37,21 +40,44 @@ export const useWebSocket = (config: WebSocketConfig) => {
|
||||
}
|
||||
|
||||
const url = new URL(baseUrl)
|
||||
url.searchParams.set('client_id', config.clientId)
|
||||
url.searchParams.set('client_id', currentClientId.value)
|
||||
return url.toString()
|
||||
})
|
||||
|
||||
const clearHandlers = (wsInstance: WebSocket | null) => {
|
||||
if (wsInstance) {
|
||||
wsInstance.onopen = null
|
||||
wsInstance.onmessage = null
|
||||
wsInstance.onclose = null
|
||||
wsInstance.onerror = null
|
||||
}
|
||||
}
|
||||
|
||||
const connect = () => {
|
||||
try {
|
||||
// Close existing connection if any
|
||||
if (ws.value && ws.value.readyState !== WebSocket.CLOSED) {
|
||||
ws.value.close()
|
||||
// Clear any pending reconnect timer first
|
||||
if (reconnectTimer.value) {
|
||||
clearTimeout(reconnectTimer.value)
|
||||
reconnectTimer.value = null
|
||||
}
|
||||
|
||||
ws.value = new WebSocket(wsUrl.value)
|
||||
// Close existing connection if any, and CLEAN HANDLERS to prevent recursion
|
||||
if (ws.value) {
|
||||
if (ws.value.readyState !== WebSocket.CLOSED) {
|
||||
console.log('🔌 Closing existing WebSocket before new connection...')
|
||||
clearHandlers(ws.value)
|
||||
ws.value.close()
|
||||
}
|
||||
ws.value = null
|
||||
}
|
||||
|
||||
const connectionUrl = wsUrl.value
|
||||
console.log('🔌 Connecting to WebSocket:', connectionUrl)
|
||||
|
||||
ws.value = new WebSocket(connectionUrl)
|
||||
|
||||
ws.value.onopen = () => {
|
||||
console.log('WebSocket connected:', config.clientId)
|
||||
console.log('✅ WebSocket connected:', currentClientId.value)
|
||||
isConnected.value = true
|
||||
reconnectAttempts.value = 0
|
||||
config.onOpen?.()
|
||||
@@ -67,16 +93,17 @@ export const useWebSocket = (config: WebSocketConfig) => {
|
||||
}
|
||||
|
||||
ws.value.onclose = () => {
|
||||
console.log('WebSocket closed:', config.clientId)
|
||||
console.log('❌ WebSocket closed:', currentClientId.value)
|
||||
isConnected.value = false
|
||||
config.onClose?.()
|
||||
|
||||
// Attempt to reconnect
|
||||
// Attempt to reconnect only if we didn't just reach max attempts
|
||||
// The reconnectTimer is cleared in disconnect() and at start of connect()
|
||||
if (reconnectAttempts.value < (config.maxReconnectAttempts || 5)) {
|
||||
reconnectAttempts.value++
|
||||
const interval = config.reconnectInterval || 3000
|
||||
console.log(`⏳ Reconnecting in ${interval}ms... Attempt ${reconnectAttempts.value}`)
|
||||
reconnectTimer.value = setTimeout(() => {
|
||||
console.log(`Reconnecting... Attempt ${reconnectAttempts.value}`)
|
||||
connect()
|
||||
}, interval)
|
||||
} else {
|
||||
@@ -85,11 +112,11 @@ export const useWebSocket = (config: WebSocketConfig) => {
|
||||
}
|
||||
|
||||
ws.value.onerror = (error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
console.error('⚠️ WebSocket error:', error)
|
||||
config.onError?.(error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating WebSocket:', error)
|
||||
console.error('❌ Error creating WebSocket:', error)
|
||||
config.onError?.(error as Event)
|
||||
}
|
||||
}
|
||||
@@ -100,10 +127,13 @@ export const useWebSocket = (config: WebSocketConfig) => {
|
||||
reconnectTimer.value = null
|
||||
}
|
||||
if (ws.value) {
|
||||
console.log('🔌 Manual disconnect: Cleaning handlers and closing...')
|
||||
clearHandlers(ws.value)
|
||||
ws.value.close()
|
||||
ws.value = null
|
||||
}
|
||||
isConnected.value = false
|
||||
reconnectAttempts.value = 0
|
||||
}
|
||||
|
||||
const sendMessage = (message: any) => {
|
||||
@@ -125,7 +155,7 @@ export const useWebSocket = (config: WebSocketConfig) => {
|
||||
postUrl = config.fallbackPostUrl
|
||||
} else {
|
||||
// Derive post URL from WebSocket URL (legacy behavior)
|
||||
let baseUrl = config.url
|
||||
let baseUrl = currentUrl.value
|
||||
if (baseUrl.startsWith('ws://')) {
|
||||
baseUrl = baseUrl.replace('ws://', 'http://')
|
||||
} else if (baseUrl.startsWith('wss://')) {
|
||||
|
||||
@@ -1798,8 +1798,9 @@ watch(
|
||||
background-color: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 20px;
|
||||
max-height: 420px;
|
||||
}
|
||||
|
||||
.doctor-info-unit {
|
||||
@@ -1814,9 +1815,8 @@ watch(
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
color: var(--color-neutral-800);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -1852,6 +1852,10 @@ watch(
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
max-height: 540px;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.doctor-button-wrapper {
|
||||
@@ -1924,10 +1928,6 @@ watch(
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.3;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Responsive untuk layar lebih kecil */
|
||||
|
||||
+168
-101
@@ -111,23 +111,42 @@
|
||||
<span class="text-caption text-grey"
|
||||
>Preview kamera sedang berjalan</span
|
||||
>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
size="small"
|
||||
:color="primaryColor"
|
||||
@click="switchCamera"
|
||||
title="Ganti Kamera"
|
||||
>
|
||||
<v-icon>mdi-camera-flip</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div
|
||||
id="qr-reader"
|
||||
class="qr-reader-wrapper"
|
||||
:class="{ 'is-front': isFrontCamera }"
|
||||
role="region"
|
||||
aria-label="Area pemindaian QR code"
|
||||
aria-live="polite"
|
||||
:aria-busy="cameraChecking"
|
||||
>
|
||||
<div class="scanner-loading-overlay" v-if="!cameraReady">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="white"
|
||||
size="48"
|
||||
></v-progress-circular>
|
||||
<p class="text-white mt-4">Memuat kamera...</p>
|
||||
</div>
|
||||
></div>
|
||||
<!-- Hidden input for Photo Mode -->
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
style="display: none"
|
||||
accept="image/*"
|
||||
@change="handleFileSelect"
|
||||
/>
|
||||
<!-- Moved overlay outside to prevent Vue/DOM conflict -->
|
||||
<div class="scanner-loading-overlay" v-show="!cameraReady && isScanning">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="white"
|
||||
size="48"
|
||||
></v-progress-circular>
|
||||
<p class="text-white mt-4">Memuat kamera...</p>
|
||||
</div>
|
||||
<div class="scanner-instruction">
|
||||
<v-icon :color="primaryColor" size="16"
|
||||
@@ -1703,6 +1722,10 @@ const qrCodeId = "qr-reader";
|
||||
let lastScannedQR: string | null = null;
|
||||
let lastScanTime: number = 0;
|
||||
const SCAN_DEBOUNCE_MS = 2000; // 2 detik debounce untuk mencegah scan berulang
|
||||
const currentFacingMode = ref<"user" | "environment">("environment");
|
||||
const isFrontCamera = computed(() => currentFacingMode.value === "user");
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
const isProcessingFile = ref(false);
|
||||
|
||||
// Daftar QR code yang sudah berhasil di-scan (untuk mencegah double antrian)
|
||||
const SUCCESSFUL_SCANS_KEY = "successful_qr_scans";
|
||||
@@ -2124,23 +2147,15 @@ const startScanning = async () => {
|
||||
if (html5QrCode) {
|
||||
cameraReady.value = false;
|
||||
|
||||
// Konfigurasi kamera untuk mobile dan desktop
|
||||
const cameraConfig = isMobile.value
|
||||
? { facingMode: "environment" } // Mobile: gunakan kamera belakang
|
||||
: { facingMode: "user" }; // Desktop: gunakan kamera depan (webcam)
|
||||
// Konfigurasi kamera dinamis
|
||||
const cameraConfig = { facingMode: currentFacingMode.value };
|
||||
|
||||
// Video constraints yang dioptimalkan untuk kualitas tinggi
|
||||
const baseVideoConstraints = isMobile.value
|
||||
? {
|
||||
facingMode: "environment",
|
||||
width: { ideal: 1280, min: 640, max: 1920 },
|
||||
height: { ideal: 720, min: 480, max: 1080 },
|
||||
}
|
||||
: {
|
||||
facingMode: "user",
|
||||
width: { ideal: 1920, min: 1280, max: 1920 },
|
||||
height: { ideal: 1080, min: 720, max: 1080 },
|
||||
};
|
||||
// Video constraints yang dioptimalkan
|
||||
const baseVideoConstraints = {
|
||||
facingMode: currentFacingMode.value,
|
||||
width: { ideal: 1920, min: 1280 },
|
||||
height: { ideal: 1080, min: 720 },
|
||||
};
|
||||
|
||||
// Tambahkan advanced constraints hanya jika didukung
|
||||
const videoConstraints: any = { ...baseVideoConstraints };
|
||||
@@ -2155,17 +2170,14 @@ const startScanning = async () => {
|
||||
await html5QrCode.start(
|
||||
cameraConfig,
|
||||
{
|
||||
fps: 15, // Reduced FPS improved stability on mobile
|
||||
qrbox: function (
|
||||
viewfinderWidth: number,
|
||||
viewfinderHeight: number,
|
||||
) {
|
||||
fps: 20, // Increased for better responsiveness
|
||||
qrbox: (viewfinderWidth: number, viewfinderHeight: number) => {
|
||||
const minEdgeSize = Math.min(viewfinderWidth, viewfinderHeight);
|
||||
// Use 70% of the viewfinder for the qrbox to ensure it's comfortably inside
|
||||
const qrboxSize = Math.floor(minEdgeSize * 0.7);
|
||||
// Set to 72% for optimal balance between target size and quiet zone
|
||||
const qrboxSize = Math.floor(minEdgeSize * 0.72);
|
||||
return {
|
||||
width: Math.max(qrboxSize, 200),
|
||||
height: Math.max(qrboxSize, 200),
|
||||
width: Math.max(qrboxSize, 250),
|
||||
height: Math.max(qrboxSize, 250),
|
||||
};
|
||||
},
|
||||
aspectRatio: 1.0,
|
||||
@@ -2173,8 +2185,8 @@ const startScanning = async () => {
|
||||
videoConstraints: videoConstraints,
|
||||
rememberLastUsedCamera: true,
|
||||
showTorchButtonIfSupported: true,
|
||||
// Tambahkan opsi untuk meningkatkan deteksi
|
||||
verbose: false, // Set true untuk debugging
|
||||
useBarCodeDetectorIfSupported: true, // Native scanning if available
|
||||
verbose: false, // Set true for debugging
|
||||
},
|
||||
(decodedText: string, decodedResult: any) => {
|
||||
// QR Code berhasil di-scan
|
||||
@@ -2230,23 +2242,14 @@ const startScanning = async () => {
|
||||
await html5QrCode.start(
|
||||
{ facingMode: "user" },
|
||||
{
|
||||
fps: 30,
|
||||
qrbox: function (
|
||||
viewfinderWidth: number,
|
||||
viewfinderHeight: number,
|
||||
) {
|
||||
const minEdgeSize = Math.min(
|
||||
viewfinderWidth,
|
||||
viewfinderHeight,
|
||||
);
|
||||
const percentageSize = Math.floor(minEdgeSize * 0.85);
|
||||
const fixedSize = 300;
|
||||
const qrboxSize = Math.max(percentageSize, fixedSize);
|
||||
const finalSize = Math.min(qrboxSize, minEdgeSize * 0.95);
|
||||
|
||||
fps: 20,
|
||||
qrbox: (viewfinderWidth: number, viewfinderHeight: number) => {
|
||||
const minEdgeSize = Math.min(viewfinderWidth, viewfinderHeight);
|
||||
// Optimized 72%
|
||||
const qrboxSize = Math.floor(minEdgeSize * 0.72);
|
||||
return {
|
||||
width: Math.max(finalSize, 250),
|
||||
height: Math.max(finalSize, 250),
|
||||
width: Math.max(qrboxSize, 250),
|
||||
height: Math.max(qrboxSize, 250),
|
||||
};
|
||||
},
|
||||
aspectRatio: 1.0,
|
||||
@@ -2257,6 +2260,7 @@ const startScanning = async () => {
|
||||
height: { ideal: 720, min: 480, max: 1080 },
|
||||
},
|
||||
rememberLastUsedCamera: true,
|
||||
useBarCodeDetectorIfSupported: true,
|
||||
verbose: false,
|
||||
},
|
||||
(decodedText: string, decodedResult: any) => {
|
||||
@@ -2311,41 +2315,25 @@ const startScanning = async () => {
|
||||
await html5QrCode.start(
|
||||
{ deviceId: { exact: deviceId } },
|
||||
{
|
||||
fps: 30,
|
||||
qrbox: function (
|
||||
viewfinderWidth: number,
|
||||
viewfinderHeight: number,
|
||||
) {
|
||||
const minEdgeSize = Math.min(
|
||||
viewfinderWidth,
|
||||
viewfinderHeight,
|
||||
);
|
||||
const percentageSize = Math.floor(minEdgeSize * 0.8);
|
||||
const fixedSize = isMobile.value ? 300 : 400;
|
||||
const qrboxSize = Math.max(percentageSize, fixedSize);
|
||||
const finalSize = Math.min(qrboxSize, minEdgeSize * 0.8);
|
||||
|
||||
fps: 20,
|
||||
qrbox: (viewfinderWidth: number, viewfinderHeight: number) => {
|
||||
const minEdgeSize = Math.min(viewfinderWidth, viewfinderHeight);
|
||||
// Optimized 72%
|
||||
const qrboxSize = Math.floor(minEdgeSize * 0.85);
|
||||
return {
|
||||
width: Math.max(finalSize, 250),
|
||||
height: Math.max(finalSize, 250),
|
||||
width: Math.max(qrboxSize, 250),
|
||||
height: Math.max(qrboxSize, 250),
|
||||
};
|
||||
},
|
||||
aspectRatio: 1.0,
|
||||
disableFlip: false,
|
||||
videoConstraints: {
|
||||
deviceId: { exact: deviceId },
|
||||
width: {
|
||||
ideal: isMobile.value ? 1280 : 1920,
|
||||
min: isMobile.value ? 640 : 1280,
|
||||
max: isMobile.value ? 1920 : 1920,
|
||||
},
|
||||
height: {
|
||||
ideal: isMobile.value ? 720 : 1080,
|
||||
min: isMobile.value ? 480 : 720,
|
||||
max: isMobile.value ? 1080 : 1080,
|
||||
},
|
||||
width: { ideal: isMobile.value ? 1280 : 1920 },
|
||||
height: { ideal: isMobile.value ? 720 : 1080 },
|
||||
},
|
||||
rememberLastUsedCamera: true,
|
||||
useBarCodeDetectorIfSupported: true,
|
||||
verbose: false,
|
||||
},
|
||||
(decodedText: string, decodedResult: any) => {
|
||||
@@ -2424,23 +2412,28 @@ const startScanning = async () => {
|
||||
const stopScanning = async () => {
|
||||
try {
|
||||
if (html5QrCode) {
|
||||
// Stop scanner
|
||||
if (isScanning.value) {
|
||||
await html5QrCode.stop();
|
||||
await html5QrCode.clear();
|
||||
}
|
||||
|
||||
// Note: Media tracks are automatically stopped when html5QrCode.stop() is called
|
||||
|
||||
// Immediate state update
|
||||
const scanner = html5QrCode;
|
||||
html5QrCode = null;
|
||||
isScanning.value = false;
|
||||
cameraReady.value = false;
|
||||
|
||||
// Stop and clear
|
||||
try {
|
||||
if (scanner.getState && scanner.getState() > 1) { // 1 is stopped, 2 is scanning, 3 is paused
|
||||
await scanner.stop();
|
||||
}
|
||||
await scanner.clear();
|
||||
} catch (err) {
|
||||
console.warn("Scanner shutdown error (non-critical):", err);
|
||||
}
|
||||
|
||||
showSnackbar("Info", "Scanner dihentikan", "info", "mdi-camera-off");
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("Error stopping scanner:", err);
|
||||
console.error("Error in stopScanning:", err);
|
||||
isScanning.value = false;
|
||||
cameraReady.value = false;
|
||||
// Force cleanup
|
||||
html5QrCode = null;
|
||||
}
|
||||
};
|
||||
@@ -2913,6 +2906,80 @@ const matchNoAntrian = (input: string, noAntrian: string): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
// --- FUNGSI TAMBAHAN SCANNER ---
|
||||
|
||||
/**
|
||||
* Berpindah antara kamera depan dan belakang
|
||||
*/
|
||||
const switchCamera = async () => {
|
||||
if (!isScanning.value) return;
|
||||
|
||||
const newMode = currentFacingMode.value === "user" ? "environment" : "user";
|
||||
console.log(`Switching camera to: ${newMode}`);
|
||||
|
||||
// Stop scanner dulu
|
||||
if (html5QrCode) {
|
||||
try {
|
||||
await html5QrCode.stop();
|
||||
} catch (err) {
|
||||
console.warn("Error stopping for camera switch:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Update mode dan restart
|
||||
currentFacingMode.value = newMode;
|
||||
cameraReady.value = false;
|
||||
await startScanning();
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger pemilih file (Photo Mode)
|
||||
*/
|
||||
const triggerFileSelect = () => {
|
||||
if (fileInput.value) {
|
||||
fileInput.value.click();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle scan dari file gambar (Photo Mode)
|
||||
*/
|
||||
const handleFileSelect = async (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (!input.files || input.files.length === 0) return;
|
||||
|
||||
const file = input.files[0];
|
||||
isProcessingFile.value = true;
|
||||
|
||||
try {
|
||||
// Pastikan scanner ter-inisialisasi
|
||||
if (!html5QrCode) {
|
||||
const { Html5Qrcode: Html5QrcodeClass } = await import("html5-qrcode");
|
||||
html5QrCode = new Html5QrcodeClass(qrCodeId);
|
||||
}
|
||||
|
||||
console.log("Processing file scan...");
|
||||
const decodedText = await html5QrCode.scanFile(file, true);
|
||||
|
||||
if (decodedText) {
|
||||
console.log("✅ QR Code detected from file:", decodedText);
|
||||
handleQRScanSuccess(decodedText);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error scanning file:", err);
|
||||
showSnackbar(
|
||||
"Error",
|
||||
"Gagal mendeteksi QR code dari foto. Pastikan foto jelas dan terang.",
|
||||
"error",
|
||||
"mdi-alert-circle",
|
||||
);
|
||||
} finally {
|
||||
isProcessingFile.value = false;
|
||||
// Reset input agar bisa pilih file yang sama lagi jika perlu
|
||||
if (input) input.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const onDetect = async (decodedText: string) => {
|
||||
// Format QR Code yang didukung:
|
||||
// 1. Format thermal print: hanya BARCODE saja (contoh: 250811100163)
|
||||
@@ -5158,14 +5225,14 @@ onUnmounted(() => {
|
||||
height: 100% !important;
|
||||
border-radius: 16px;
|
||||
display: block !important;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background: #000;
|
||||
aspect-ratio: 1 / 1;
|
||||
transform: scaleX(-1); /* Mirror effect */
|
||||
-webkit-transform: scaleX(-1); /* Safari support */
|
||||
/* Reduce brightness to prevent overexposure/bloom */
|
||||
filter: brightness(0.75) contrast(1.1);
|
||||
-webkit-filter: brightness(0.75) contrast(1.1);
|
||||
}
|
||||
|
||||
.qr-reader-wrapper.is-front :deep(video) {
|
||||
transform: scaleX(-1); /* Mirror effect only for front camera */
|
||||
-webkit-transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.qr-reader-wrapper :deep(canvas) {
|
||||
@@ -5212,12 +5279,12 @@ onUnmounted(() => {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
transform: scaleX(-1); /* Mirror effect */
|
||||
-webkit-transform: scaleX(-1); /* Safari support */
|
||||
/* Reduce brightness to prevent overexposure/bloom */
|
||||
filter: brightness(0.75) contrast(1.1);
|
||||
-webkit-filter: brightness(0.75) contrast(1.1);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.qr-reader-wrapper.is-front :deep(#qr-reader__scan_region video) {
|
||||
transform: scaleX(-1); /* Mirror effect only for front camera */
|
||||
-webkit-transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.scanner-status {
|
||||
|
||||
@@ -327,7 +327,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
|
||||
const { connect, disconnect, sendViaPost, isConnected } = useWebSocket({
|
||||
url: wsBaseUrl,
|
||||
clientId: wsClientId.value,
|
||||
clientId: wsClientId,
|
||||
onOpen: () => {
|
||||
console.log('✅ [queueStore] WebSocket connected');
|
||||
isWsConnected.value = true;
|
||||
|
||||
Reference in New Issue
Block a user