added camera input for andriod conversion

This commit is contained in:
unknown 2026-02-18 14:58:30 +05:30
parent 6f7e2f6cdd
commit f0cdce7150
1 changed files with 128 additions and 288 deletions

View File

@ -2,16 +2,9 @@ import { useState, useRef, useEffect } from 'react';
import {
Upload,
Button,
Spin,
Form,
Input,
Select,
message,
Divider,
Row,
Col,
Table,
Image,
DatePicker,
} from 'antd';
import dayjs from 'dayjs';
@ -21,7 +14,6 @@ import {
CheckOutlined,
CloseOutlined,
ReloadOutlined,
EyeOutlined,
DeleteFilled,
CameraOutlined,
EditOutlined,
@ -37,218 +29,8 @@ import { InputField } from '../../Components/Forms/InputField.jsx';
import TextAreaInput from '../../Components/Forms/TextArea.jsx';
import { uploadImage } from '../../Features/upload/upload.js';
import { getSession } from '../../Services/Others.js';
const { Option } = Select;
const { Column } = Table;
const CameraModal = ({
visible,
onClose,
onCapture,
setCameraModalVisible = () => { },
}) => {
const videoRef = useRef(null);
const canvasRef = useRef(null);
const [stream, setStream] = useState(null);
const [image, setImage] = useState(null);
const [cameraOn, setCameraOn] = useState(false);
const startCamera = async () => {
console.log('Starting camera function called...');
// First test - just set camera on without stream
setCameraOn(true);
console.log('Camera state forced to ON for testing');
try {
console.log('Checking getUserMedia support...');
if (!navigator.mediaDevices?.getUserMedia) {
console.error('getUserMedia not supported');
message.error('Camera not supported on this device');
setCameraModalVisible(false);
return;
}
console.log('Requesting camera access...');
let mediaStream;
try {
// Try high quality first
mediaStream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 1920 },
height: { ideal: 1080 },
},
});
} catch (highResError) {
console.log(
'High resolution failed, trying basic constraints:',
highResError.message
);
// Fallback to basic constraints
mediaStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
});
}
console.log('Got media stream:', mediaStream);
if (videoRef.current) {
console.log('Setting video source...');
videoRef.current.srcObject = mediaStream;
setStream(mediaStream);
console.log('Stream set successfully');
} else {
console.error('Video ref is null');
}
} catch (err) {
console.error('Camera error:', err.name, err.message);
message.error(`Camera error: ${err.message}`);
setCameraModalVisible(false);
}
};
const stopCamera = () => {
console.log('Stopping camera...');
if (stream) {
stream.getTracks().forEach((track) => {
console.log('Stopping track:', track);
track.stop();
});
}
setStream(null);
setCameraOn(false);
console.log('Camera stopped');
};
const captureImage = () => {
const video = videoRef.current;
const canvas = canvasRef.current;
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(video, 0, 0);
const imageData = canvas.toDataURL('image/jpeg', 0.95);
setImage(imageData);
stopCamera();
};
const retake = () => {
setImage(null);
startCamera();
};
const handleUse = () => {
canvasRef.current.toBlob(
(blob) => {
const file = new File([blob], 'camera-capture.jpg', {
type: 'image/jpeg',
});
onCapture(file, true); // Pass true to indicate direct submit
handleClose();
},
'image/jpeg',
0.95
);
};
const handleClose = () => {
stopCamera();
setImage(null);
setCameraOn(false);
onClose();
};
useEffect(() => {
if (visible) {
startCamera(); // Auto-start camera when modal opens
return () => stopCamera();
}
}, [visible]);
return (
<DefaultModal
title="Camera"
open={visible}
handleCancel={handleClose}
footer={false}
width={400}
>
<div style={{ textAlign: 'center' }}>
{/* <div style={{ marginBottom: 10, padding: 10, backgroundColor: '#f0f0f0' }}>
<p>Debug: Camera={cameraOn ? 'ON' : 'OFF'}, Stream={stream ? 'Active' : 'None'}, Image={image ? 'Yes' : 'No'}</p>
</div> */}
{!cameraOn && !image && (
<div>
<p style={{ marginBottom: 16, color: '#666' }}>
Starting camera...
</p>
</div>
)}
{cameraOn && (
<div>
<p>Camera is ON - Video should appear below:</p>
<video
ref={videoRef}
autoPlay
playsInline
muted
width="300"
height="400"
style={{ border: '2px solid red', marginBottom: 10 }}
onCanPlay={() => console.log('Video can play')}
onError={(e) => console.error('Video error:', e)}
onLoadedMetadata={() => console.log('Video metadata loaded')}
/>
<br />
<Button onClick={captureImage} type="primary">
Capture
</Button>
<Button
onClick={() => setCameraOn(false)}
style={{ marginLeft: 10 }}
>
Stop
</Button>
</div>
)}
{image && (
<>
<div
style={{
display: 'flex',
gap: '10px',
justifyContent: 'center',
marginBottom: '1rem',
}}
>
<Button onClick={retake}>Retake</Button>
<Button onClick={handleUse} type="primary">
Use Photo
</Button>
</div>
<img
src={image}
alt="Captured"
style={{
width: '80vw',
borderRadius: 8,
marginBottom: 10,
height: '60vh',
}}
/>
</>
)}
<canvas ref={canvasRef} style={{ display: 'none' }} />
</div>
</DefaultModal>
);
};
import ResizableCropper from '../../Components/PedalOCR/CropperArea.jsx';
import ThumbnailsList from '../../Components/PedalOCR/ThumbnailsList.jsx';
const ScanModal = ({
visible = false,
@ -258,42 +40,77 @@ const ScanModal = ({
processingStep = null,
ocrLoading = false,
handleClearImage = () => { },
uploadedImage = null,
selectedFile = null,
images = [],
setImages = () => { },
finalImages = [],
setCurrentIndex = () => { },
setFinalImages = () => { },
currentIndex = null,
}) => {
const cameraRef = useRef(null);
const isMobile =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
) ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0;
const [cameraModalVisible, setCameraModalVisible] = useState(false);
const [buttonDisable, setButtonDisable] = useState(false);
const handleCameraCapture = (file, directSubmit = false) => {
console.log(
'Camera capture received:',
file,
'Direct submit:',
directSubmit
);
const handleCapture = (file) => {
console.log('Handle capture called with file:', file?.name);
handleUpload(file);
if (directSubmit) {
setButtonDisable(true);
setTimeout(() => {
try {
onSubmit(file, true);
console.log('Triggering submit after camera capture');
} catch (err) {
console.error('Submit Error:', err?.message);
} finally {
setButtonDisable(false);
}
}, 100);
}
const reader = new FileReader();
reader.onload = (e) => {
const newImage = {
id: Date.now(),
src: e.target.result,
file: file,
};
setImages([newImage]);
setCurrentIndex(0);
};
reader.readAsDataURL(file);
};
const handleCropComplete = (croppedImage) => {
setFinalImages((prev) => {
const newImages = [...prev];
newImages[currentIndex] = croppedImage;
return newImages;
});
fetch(croppedImage)
.then((res) => res.blob())
.then((blob) => {
const file = new File([blob], 'camera-capture.jpg', {
type: 'image/jpeg',
});
handleUpload(file);
});
}
const handleRemoveImage = (index) => {
setFinalImages((prev) => prev.filter((_, i) => i !== index));
handleClearImage();
};
const handleRecropImage = (index) => {
const imageToRecrop = finalImages[index];
setFinalImages((prev) => prev.filter((_, i) => i !== index));
// Add back to images for re-cropping
const newImage = { id: Date.now(), src: imageToRecrop };
setImages((prev) => [...prev, newImage]);
setCurrentIndex(images.length);
};
const resetAll = () => {
setImages([]);
setFinalImages([]);
setCurrentIndex(null);
};
return (
<DefaultModal
title={
@ -368,12 +185,52 @@ const ScanModal = ({
alignItems: 'center',
}}
>
<input
ref={cameraRef}
type="file"
accept="image/*"
capture="environment"
multiple
hidden
onChange={(e) => {
const rawFile = e?.target?.files?.[0];
if (!rawFile) return;
handleCapture(rawFile);
}}
/>
{currentIndex !== null && images[currentIndex] && (
<ResizableCropper
imageSrc={images[currentIndex].src}
currentIndex={currentIndex}
total={images.length}
onCropComplete={handleCropComplete}
onCancel={resetAll}
onNext={() => {
if (currentIndex + 1 < images.length) {
setCurrentIndex(currentIndex + 1);
} else {
setCurrentIndex(null);
setImages([]);
}
}}
/>
)}
{finalImages.length > 0 && currentIndex === null && (
<>
<h4>Uploaded Image :</h4>
<ThumbnailsList
images={finalImages}
onRemove={handleRemoveImage}
onRecrop={handleRecropImage}
/>
</>
)}
{isMobile && (
<Button
type="primary"
size="large"
icon={<CameraOutlined />}
onClick={() => setCameraModalVisible(true)}
onClick={() => cameraRef.current.click()}
style={{
display: 'flex',
flexDirection: 'row',
@ -399,14 +256,14 @@ const ScanModal = ({
)}
<Upload
accept="image/*"
beforeUpload={handleUpload}
beforeUpload={handleCapture}
showUploadList={false}
>
<div
style={{
margin: '0 auto',
border: '2px dashed #999',
padding: 30,
padding: isMobile ? '12px 30px' : 30,
borderRadius: 10,
}}
>
@ -414,48 +271,11 @@ const ScanModal = ({
style={{ fontSize: 40, color: '#1292ee' }}
/>
<p style={{ marginTop: '4px' }}>
Click or drag image to upload
{`${isMobile ? 'Upload' : 'Click or drag image to upload'}`}
</p>
</div>
</Upload>
</div>
{uploadedImage && (
<div style={{ marginTop: 20 }}>
<div className="preview-header">
<div style={{ fontFamily: 'Poppins', fontWeight: '500' }}>
Preview:
</div>
<Button
type="default"
onClick={handleClearImage}
icon={
<CloseOutlined
style={{ height: '15px', width: '15px' }}
/>
}
color="danger"
>
Clear
</Button>
</div>
<Image
src={uploadedImage}
alt="Preview"
style={{
maxWidth: '100%',
maxHeight: 300,
border: '1px solid #d9d9d9',
}}
/>
</div>
)}
<CameraModal
visible={cameraModalVisible}
onClose={() => setCameraModalVisible(false)}
onCapture={handleCameraCapture}
setCameraModalVisible={setCameraModalVisible}
/>
</>
)}
</div>
@ -498,6 +318,9 @@ const InvoiceImageExtractorModal = ({
const extractedTableRef = useRef(null);
const [form] = Form.useForm();
const [images, setImages] = useState([]);
const [finalImages, setFinalImages] = useState([]);
const [currentIndex, setCurrentIndex] = useState(null);
const [ocrLoading, setOcrLoading] = useState(false);
const [uploadedImage, setUploadedImage] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);
@ -661,6 +484,9 @@ const InvoiceImageExtractorModal = ({
cleanupUploadedURL();
setProcessingStep(null);
setSelectedFile(null);
setImages([]);
setCurrentIndex(null);
setFinalImages([]);
if (visible || submit) {
form.resetFields();
setExtractedText('');
@ -728,6 +554,7 @@ const InvoiceImageExtractorModal = ({
CompId,
BranchId
}
console.log('Dispatching OCR request with data:', data);
const response = await dispatch(
getInvoiceImageData(data)
).unwrap();
@ -823,6 +650,9 @@ const InvoiceImageExtractorModal = ({
} else if (uploadMoreVisible) {
setUploadMoreVisible(false);
}
setImages([]);
setCurrentIndex(null);
setFinalImages([]);
message.success('Invoice processed successfully!');
} else {
if (response?.statusCode === 1 && response?.data?.length === 0) {
@ -832,7 +662,7 @@ const InvoiceImageExtractorModal = ({
}
}
} catch (err) {
console.error('API OCR Error:', err);
console.error('API OCR Error:', err?.message);
message.error('Failed to process invoice.');
} finally {
setOcrLoading(false);
@ -954,8 +784,13 @@ const InvoiceImageExtractorModal = ({
processingStep={processingStep}
ocrLoading={ocrLoading}
handleClearImage={handleClearImage}
uploadedImage={uploadedImage}
selectedFile={selectedFile}
images={images}
setImages={setImages}
finalImages={finalImages}
setFinalImages={setFinalImages}
currentIndex={currentIndex}
setCurrentIndex={setCurrentIndex}
/>
{/* PREVIEW MODAL */}
@ -1213,8 +1048,13 @@ const InvoiceImageExtractorModal = ({
processingStep={processingStep}
ocrLoading={ocrLoading}
handleClearImage={handleClearImage}
uploadedImage={uploadedImage}
selectedFile={selectedFile}
images={images}
setImages={setImages}
finalImages={finalImages}
setFinalImages={setFinalImages}
currentIndex={currentIndex}
setCurrentIndex={setCurrentIndex}
/>
</DefaultModal>
</>