sozsoft-platform/ui/src/components/shared/BarcodeScanner/BarcodeScannerDialog.tsx
2026-09-21 18:01:40 +03:00

111 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react'
import { Button, Dialog } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useBarcodeReader, type BarcodeReaderStatus } from './useBarcodeReader'
export type BarcodeScannerDialogProps = {
open: boolean
onDialogClose: () => void
/**
* Okunan kod. `false` dönerse kod reddedilmiş sayılır ve listeye yazılmaz;
* sürekli modda yazma hatasını kullanıcıya göstermek için kullanılır.
*/
onDetect: (code: string) => boolean | void | Promise<boolean | void>
/** Açıkken diyalog okumadan sonra kapanmaz, okunanları listeler. */
continuous?: boolean
title?: string
}
const statusMessageKeys: Record<BarcodeReaderStatus, string> = {
starting: '::App.BarcodeScanner.Starting',
scanning: '::App.BarcodeScanner.Aim',
denied: '::App.BarcodeScanner.PermissionDenied',
insecure: '::App.BarcodeScanner.InsecureContext',
standaloneIos: '::App.BarcodeScanner.StandaloneIos',
error: '::App.BarcodeScanner.CameraError',
}
/**
* Kamerayla barkod/QR okuma diyaloğu. Tek okuma ve sürekli tarama aynı
* bileşendir; fark yalnızca okumadan sonra kapanıp kapanmadığıdır.
*/
const BarcodeScannerDialog = ({
open,
onDialogClose,
onDetect,
continuous = false,
title,
}: BarcodeScannerDialogProps) => {
const { translate } = useLocalization()
const [scanned, setScanned] = useState<string[]>([])
const [failed, setFailed] = useState<string>()
const handleDetect = async (code: string) => {
const accepted = await onDetect(code)
if (accepted === false) {
setFailed(code)
return
}
if (!continuous) {
onDialogClose()
return
}
setFailed(undefined)
setScanned((previous) => [code, ...previous])
}
const { videoRef, status } = useBarcodeReader({ active: open, onDetect: handleDetect })
return (
<Dialog isOpen={open} width={520} onClose={onDialogClose} onRequestClose={onDialogClose}>
<h5 className="mb-4">{title ?? translate('::App.BarcodeScanner.Title')}</h5>
<div className="relative overflow-hidden rounded-lg bg-black">
<video
ref={videoRef}
autoPlay
muted
playsInline
className="h-64 w-full object-cover sm:h-80"
/>
{status === 'scanning' && (
// Nişangah: kullanıcıya kodu nereye tutacağını gösterir.
<div className="pointer-events-none absolute inset-x-10 inset-y-16 rounded-lg border-2 border-white/80" />
)}
</div>
<p className="mt-3 text-sm opacity-80">{translate(statusMessageKeys[status])}</p>
{failed && (
<p className="mt-1 text-sm text-red-500">
{translate('::App.BarcodeScanner.SaveFailed')} ({failed})
</p>
)}
{continuous && scanned.length > 0 && (
<div className="mt-3">
<p className="mb-1 text-sm font-semibold">
{translate('::App.BarcodeScanner.ScannedCount')}: {scanned.length}
</p>
<ul className="max-h-32 overflow-auto rounded border text-sm">
{scanned.map((code, index) => (
<li key={`${code}-${index}`} className="border-b px-2 py-1 last:border-b-0">
{code}
</li>
))}
</ul>
</div>
)}
<div className="mt-5 text-right">
<Button variant="solid" onClick={onDialogClose}>
{translate(continuous ? '::App.Platform.Close' : '::App.Platform.Cancel')}
</Button>
</div>
</Dialog>
)
}
export default BarcodeScannerDialog