- 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.
144 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
}
|