From 1cc52cc02569138a286125fc221786e77252eb92 Mon Sep 17 00:00:00 2001 From: Rynare Date: Mon, 19 May 2025 21:20:59 +0700 Subject: [PATCH] backup --- components/auth/auth.vue | 2 +- .../dashboard/dataset/product/modal-add.vue | 17 +- .../dashboard/dataset/product/modal-new.vue | 72 +++++ components/my/barcodeScanner.vue | 33 +++ components/my/input-with-barcode.vue | 18 ++ composables/barcodeScanner.ts | 149 ++++++++++ composables/productFetch.ts | 9 +- layouts/main.vue | 26 +- package-lock.json | 276 ++++++++++++++++++ package.json | 2 + pages/dashboard/restock/index.vue | 133 ++++++++- 11 files changed, 731 insertions(+), 6 deletions(-) create mode 100644 components/dashboard/dataset/product/modal-new.vue create mode 100644 components/my/barcodeScanner.vue create mode 100644 components/my/input-with-barcode.vue create mode 100644 composables/barcodeScanner.ts diff --git a/components/auth/auth.vue b/components/auth/auth.vue index d1aea75..d306380 100644 --- a/components/auth/auth.vue +++ b/components/auth/auth.vue @@ -12,7 +12,7 @@
- +

Please Wait...

diff --git a/components/dashboard/dataset/product/modal-add.vue b/components/dashboard/dataset/product/modal-add.vue index 047d223..5d47591 100644 --- a/components/dashboard/dataset/product/modal-add.vue +++ b/components/dashboard/dataset/product/modal-add.vue @@ -12,7 +12,21 @@ - +
+ +
+ + +
@@ -49,6 +63,7 @@ \ No newline at end of file diff --git a/components/my/barcodeScanner.vue b/components/my/barcodeScanner.vue new file mode 100644 index 0000000..65c5581 --- /dev/null +++ b/components/my/barcodeScanner.vue @@ -0,0 +1,33 @@ + + + diff --git a/components/my/input-with-barcode.vue b/components/my/input-with-barcode.vue new file mode 100644 index 0000000..b28fee5 --- /dev/null +++ b/components/my/input-with-barcode.vue @@ -0,0 +1,18 @@ + + \ No newline at end of file diff --git a/composables/barcodeScanner.ts b/composables/barcodeScanner.ts new file mode 100644 index 0000000..e9fc57a --- /dev/null +++ b/composables/barcodeScanner.ts @@ -0,0 +1,149 @@ +import { ref, onMounted, onBeforeUnmount, type Ref } from 'vue' +import { BrowserMultiFormatReader, type IScannerControls } from '@zxing/browser' + +export function useBarcodeScanner(videoElement: Ref) { + const barcode = ref(null) + const isSupported = ref(false) + const error = ref(null) + + const status = ref<'idle' | 'changing' | 'error' | 'scanning' | 'scanned'>('idle') + const permissionStatus = ref<'loading' | 'allowed' | 'denied'>('loading') + + let codeReader: BrowserMultiFormatReader + let controls: IScannerControls + let devices: MediaDeviceInfo[] = [] + const currentDeviceIndex = ref(0) + + const checkCameraSupport = (): boolean => { + return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) + } + + const requestCameraPermission = async (): Promise => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ video: true }) + stream.getTracks().forEach(track => track.stop()) + permissionStatus.value = 'allowed' + return true + } catch (err) { + error.value = 'Izin kamera ditolak atau gagal.' + permissionStatus.value = 'denied' + console.error('[BarcodeScanner] Permission error:', err) + return false + } + } + + const startScanner = async (deviceId?: string) => { + if (!checkCameraSupport()) { + error.value = 'Perangkat tidak mendukung akses kamera.' + status.value = 'error' + console.warn('[BarcodeScanner] getUserMedia tidak didukung.') + return + } + + if (permissionStatus.value !== 'allowed') { + const granted = await requestCameraPermission() + if (!granted) return + } + + codeReader = new BrowserMultiFormatReader() + + try { + devices = await BrowserMultiFormatReader.listVideoInputDevices() + if (!devices.length) throw new Error('Kamera tidak ditemukan') + + // Otomatis pakai kamera belakang kalau ada + if (!deviceId) { + const backCam = devices.find(device => + /back|rear/i.test(device.label) + ) + if (backCam) { + currentDeviceIndex.value = devices.findIndex(d => d.deviceId === backCam.deviceId) + deviceId = backCam.deviceId + } else { + // Fallback ke kamera pertama + currentDeviceIndex.value = 0 + deviceId = devices[0].deviceId + } + } + + controls = await codeReader.decodeFromVideoDevice( + deviceId, + videoElement.value!, + (result, err) => { + status.value = 'scanning' + if (result) { + barcode.value = result.getText() + status.value = 'scanned' + } + } + ) + } catch (err) { + console.error('[BarcodeScanner] Gagal inisialisasi:', err) + error.value = (err as Error).message + status.value = 'error' + throw new Error('[BarcodeScanner] Gagal inisialisasi') + } + } + + + const stopScanner = () => { + if (codeReader && controls) { + controls.stop() + } + } + + const switchCamera = async () => { + try { + status.value = 'changing' + if (devices.length <= 1) { + console.warn('[BarcodeScanner] Hanya ada satu kamera, tidak bisa switch.') + status.value = 'idle' + return + } + + stopScanner() + currentDeviceIndex.value = (currentDeviceIndex.value + 1) % devices.length + await startScanner(devices[currentDeviceIndex.value].deviceId) + + status.value = 'idle' + } catch (error) { + console.error('[BarcodeScanner] Gagal switch kamera:', error) + status.value = 'error' + } + } + + onMounted(async () => { + isSupported.value = checkCameraSupport() + if (!isSupported.value) { + console.warn('[BarcodeScanner] Kamera tidak didukung di browser ini.') + return + } + + await requestCameraPermission() + if (permissionStatus.value !== 'allowed') return + + devices = await BrowserMultiFormatReader.listVideoInputDevices() + if (devices.length === 0) { + error.value = 'Tidak ada kamera yang tersedia.' + return + } + + await startScanner() + }) + + onBeforeUnmount(() => { + stopScanner() + }) + + return { + barcode, + error, + isSupported, + startScanner, + stopScanner, + switchCamera, + devices, + status, + permissionStatus + } +} diff --git a/composables/productFetch.ts b/composables/productFetch.ts index bc52524..4cb5ba5 100644 --- a/composables/productFetch.ts +++ b/composables/productFetch.ts @@ -13,7 +13,14 @@ export function useProductList() { export function useAddProduct() { const toast = useToast() - const formState = reactive({ + const formState = reactive>({ product_code: undefined, product_name: undefined, stock: undefined, diff --git a/layouts/main.vue b/layouts/main.vue index b155cf6..b0dea04 100644 --- a/layouts/main.vue +++ b/layouts/main.vue @@ -1,5 +1,5 @@ \ No newline at end of file + +const productsContainer = ref() + +const productsFormState = ref< + { + product_code: string + product_name: string + price: number + qty: number + }[] +>([]) + +const newProduct = ref() +const newProductModalShown = computed({ + get() { return !!newProduct.value }, + set(newVal) { + if (!newVal) newProduct.value = undefined + } +}) + +const handleScan = (code: string) => { + const existing = productsContainer.value?.querySelector(`#id-${code}`) + + if (existing) { + productAlreadyExist.value = code + existing.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'nearest' + }) + return + } + + const { execute } = use$fetchWithAutoReNew(`/product/${code}`, { + onResponse(ctx) { + const data = ctx.response._data.data + productsFormState.value.push({ + product_code: code, + product_name: data.product_name, + price: data.buying_price, + qty: 1, + }) + }, + onResponseError(ctx) { + if (ctx.response.status === 404) + newProduct.value = code + } + }) + execute() +} + +const handleDelete = (index: number) => { + productsFormState.value.splice(index, 1) +} + +const deleteModalId = ref() +const deleteModalShown = computed({ + get() { return !!deleteModalId.value }, + set(newVal) { + if (!newVal) deleteModalId.value = undefined + } +}) + +const productAlreadyExist = ref() +const productExistMouseEnterHandle = () => { + productAlreadyExist.value = undefined +} +