dstpabuaran.com/resources/js/components/inputs/camera-capture.tsx
Yoga Pangestu 166419134f Refactor component imports for consistency and organization
- Updated import paths for various components to align with new directory structure.
- Changed imports from 'row-actions', 'confirm-dialog', 'image-preview-button', and 'file-upload' to their respective new locations in 'data-display', 'dialogs', and 'inputs'.
- Adjusted imports in multiple pages including purchase, restock, transaction, category, customer, product, raw-material, supplier, roles, and settings.
- Ensured all relevant components are imported from their new locations to maintain functionality.
2026-08-07 13:48:46 +07:00

144 lines
4.8 KiB
TypeScript

import { Camera, RotateCcw, X } from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
interface CameraCaptureProps {
onCapture: (dataUrl: string) => void;
onClose: () => void;
}
export function CameraCapture({ onCapture, onClose }: CameraCaptureProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const [stream, setStream] = useState<MediaStream | null>(null);
const [capturedImage, setCapturedImage] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const startCamera = useCallback(async () => {
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user', width: 640, height: 480 },
});
setStream(mediaStream);
if (videoRef.current) {
videoRef.current.srcObject = mediaStream;
}
setError(null);
} catch {
setError(
'Tidak dapat mengakses kamera. Pastikan izin kamera diberikan.',
);
}
}, []);
useEffect(() => {
startCamera();
return () => {
stream?.getTracks().forEach((track) => track.stop());
};
}, []);
const capture = () => {
if (!videoRef.current || !canvasRef.current) {
return;
}
const canvas = canvasRef.current;
const video = videoRef.current;
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
if (!ctx) {
return;
}
ctx.translate(canvas.width, 0);
ctx.scale(-1, 1);
ctx.drawImage(video, 0, 0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
setCapturedImage(dataUrl);
stream?.getTracks().forEach((track) => track.stop());
};
const retake = () => {
setCapturedImage(null);
startCamera();
};
const confirm = () => {
if (capturedImage) {
onCapture(capturedImage);
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div className="relative w-full max-w-md rounded-lg bg-background p-4 shadow-lg">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-lg font-semibold">Ambil Foto</h3>
<Button variant="ghost" size="icon" onClick={onClose}>
<X className="h-4 w-4" />
</Button>
</div>
{error ? (
<div className="flex flex-col items-center gap-4 py-8">
<p className="text-center text-sm text-muted-foreground">
{error}
</p>
<Button onClick={startCamera}>Coba Lagi</Button>
</div>
) : capturedImage ? (
<div className="flex flex-col gap-4">
<img
src={capturedImage}
alt="Captured"
className="w-full rounded-lg"
/>
<div className="flex gap-2">
<Button
variant="outline"
className="flex-1"
onClick={retake}
>
<RotateCcw className="mr-2 h-4 w-4" />
Ulangi
</Button>
<Button className="flex-1" onClick={confirm}>
Gunakan Foto
</Button>
</div>
</div>
) : (
<div className="flex flex-col gap-4">
<div className="relative overflow-hidden rounded-lg bg-black">
<video
ref={videoRef}
autoPlay
playsInline
muted
className="w-full"
style={{ transform: 'scaleX(-1)' }}
/>
</div>
<Button className="w-full" onClick={capture}>
<Camera className="mr-2 h-4 w-4" />
Ambil Foto
</Button>
</div>
)}
<canvas ref={canvasRef} className="hidden" />
</div>
</div>
);
}