dstpabuaran.com/resources/js/components/camera-capture.tsx
Yoga Pangestu e7aa582572 feat: add cutting management functionality with CRUD operations
- Implemented CuttingIndex component for listing and managing cuttings.
- Added routes for cutting management in web.php.
- Created CuttingTest for testing cutting-related features including authorization, validation, and stock management.
- Updated roles create and edit pages to include necessary imports.
- Refactored settings and profile pages to streamline imports.
- Enhanced permissions checks for cutting management actions.
2026-08-04 02:24:11 +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>
);
}