diff --git a/src/App.js b/src/App.js index 5cb6b0d..d175fe2 100644 --- a/src/App.js +++ b/src/App.js @@ -30,6 +30,10 @@ import { VerifyKtp } from './screens/Biometric/OcrKtp'; +import { + VerifyNpwp +} from './screens/Biometric/OcrNpwp'; + // Import all other components following the dataMenu structure... const App = () => { @@ -74,6 +78,9 @@ const App = () => { {/* Biometric - KTP */} } /> + {/* Biometric - NPWP */} + } /> + {/* } /> */} {/* Continue for each link */} diff --git a/src/screens/Biometric/FaceRecognition/Section/Verify.jsx b/src/screens/Biometric/FaceRecognition/Section/Verify.jsx index 43ac7a8..27e1eb7 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Verify.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Verify.jsx @@ -699,24 +699,70 @@ const Verify = () => { Upload Face Photo - handleImageUpload(files[0])} - children={ -
- -

Drag and Drop Here

-

Or

- fileInputRef.current.click()}>Browse -

Recommended size: 300x300 (Max File Size: 2MB)

-

Supported file types: JPG, JPEG

+
+
{ + e.preventDefault(); + // Handle drop event + const file = e.dataTransfer.files[0]; + handleImageUpload(file); + }} + onDragOver={(e) => e.preventDefault()} + > + +

Drag and Drop Here

+

Or

+ { + e.preventDefault(); + // Only open file input if no file is selected + if (!fileInputRef.current.files.length) { + fileInputRef.current.click(); + } + }} + style={styles.browseLink} + > + Browse + +

+ Recommended size: 300x300 (Max File Size: 2MB) +

+

+ Supported file types: JPG, JPEG +

+
+ + {/* File Input */} + handleImageUpload(e.target.files[0])} + /> + + {/* Display selected file info */} + {selectedImageName && ( +
+

File: {selectedImageName}

+ {file && ( +

+ Size: {formatFileSize(file.size)} +

+ )} +
- } - /> - {uploadError && {uploadError}} + )} + + {/* Error Message */} + {uploadError && {uploadError}} +
diff --git a/src/screens/Biometric/OcrKtp/Verify.jsx b/src/screens/Biometric/OcrKtp/Verify.jsx index b55dde1..ec2925b 100644 --- a/src/screens/Biometric/OcrKtp/Verify.jsx +++ b/src/screens/Biometric/OcrKtp/Verify.jsx @@ -1,9 +1,8 @@ import React, { useState, useEffect, useRef } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faImage, faTimes, faChevronDown, faChevronLeft, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; +import { faImage, faTimes, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; import { Link } from 'react-router-dom'; import Select from 'react-select' -import { DummyKtp } from '../../../assets/images'; const CustomLabel = ({ overRide, children, ...props }) => { return ; @@ -18,6 +17,7 @@ const Verify = () => { const [isMobile, setIsMobile] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [selectedImageName, setSelectedImageName] = useState(''); + const [resultImageLabel, setResultImageLabel] = useState(''); const [file, setFile] = useState(null); const [applicationId, setApplicationId] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -211,6 +211,7 @@ const Verify = () => { setShowResult(true); setErrorMessage(''); setSelectedImageName(''); + setResultImageLabel(selectedImageName); // Fetch image if image URL exists in the result if (result.details.data?.image_url) { @@ -457,6 +458,7 @@ const Verify = () => { + {/* Image Upload */}
@@ -539,6 +541,9 @@ const Verify = () => { style={isMobile ? styles.imageStyleMobile : styles.imageStyle} className="img-fluid" // Menambahkan kelas Bootstrap img-fluid untuk responsif /> +

+ File Name: {resultImageLabel} {/* Display the resultImageLabel here */} +

{/* Tabel di kolom kedua */} diff --git a/src/screens/Biometric/OcrNpwp/Verify.jsx b/src/screens/Biometric/OcrNpwp/Verify.jsx new file mode 100644 index 0000000..7f99728 --- /dev/null +++ b/src/screens/Biometric/OcrNpwp/Verify.jsx @@ -0,0 +1,571 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faImage, faTimes, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; +import { Link } from 'react-router-dom'; +import Select from 'react-select' + +const CustomLabel = ({ overRide, children, ...props }) => { + return ; +}; + +const Verify = () => { + const BASE_URL = process.env.REACT_APP_BASE_URL; + const API_KEY = process.env.REACT_APP_API_KEY; + const fileTypes = ["image/jpeg", "image/png"]; + const fileInputRef = useRef(null); + + const [isMobile, setIsMobile] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const [selectedImageName, setSelectedImageName] = useState(''); + const [resultImageLabel, setResultImageLabel] = useState(''); + const [file, setFile] = useState(null); + const [applicationId, setApplicationId] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [applicationIds, setApplicationIds] = useState([]); + const [imageUrl, setImageUrl] = useState(''); + const [imageError, setImageError] = useState(''); + const [data, setData] = useState(null); + const [showResult, setShowResult] = useState(false); + const [inputValueApplication, setInputValueApplication] = useState(''); + const [selectedQuota, setSelectedQuota] = useState(0); + + // Validation state + const [validationErrors, setValidationErrors] = useState({ + applicationId: '', + file: '' + }); + + // Fetch Application IDs + useEffect(() => { + const fetchApplicationIds = async () => { + try { + setIsLoading(true); + const response = await fetch(`${BASE_URL}/application/list`, { + method: 'GET', + headers: { + 'accept': 'application/json', + 'x-api-key': API_KEY, + }, + }); + + if (!response.ok) { + throw new Error('Failed to fetch application IDs'); + } + + const data = await response.json(); + if (data.status_code === 200) { + setApplicationIds(data.details.data); + } else { + throw new Error('Failed to fetch application IDs'); + } + } catch (error) { + setErrorMessage(error.message || 'Error fetching application IDs'); + } finally { + setIsLoading(false); + } + }; + + fetchApplicationIds(); + + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); // Example: 768px as the threshold for mobile devices + }; + + // Check on initial load + checkMobile(); + + // Add resize listener to adjust `isMobile` state on window resize + window.addEventListener('resize', checkMobile); + + // Clean up the event listener when the component unmounts + return () => { + window.removeEventListener('resize', checkMobile); + }; + }, []); + + const handleInputChangeApplication = (inputValue) => { + setInputValueApplication(inputValue); + }; + + const handleApplicationChange = (selectedOption) => { + const selectedId = selectedOption.value; + const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId)); + + setApplicationId(selectedOption ? selectedOption.value : ''); + + if (selectedApp) { + setSelectedQuota(selectedApp.quota); + } + }; + + const applicationOptions = applicationIds.map(app => ({ + value: app.id, // This is what will be sent when an option is selected + label: app.name // This is what will be displayed in the dropdown + })); + + // Handle file upload + const handleFileDrop = (files) => { + if (files && files[0]) { + handleImageUpload(files[0]); + } else { + console.error('No valid files dropped'); + } + }; + + const handleImageUpload = (file) => { + setFile(file); + setSelectedImageName(file.name); + + // Validate file type + if (!fileTypes.includes(file.type)) { + setImageError('Invalid file type. Only JPG, JPEG, and PNG are allowed.'); + } else if (file.size > 2 * 1024 * 1024) { // Max 2MB + setImageError('File size exceeds 2MB.'); + } else { + setImageError(''); + } + }; + + const handleImageCancel = () => { + setFile(null); + setSelectedImageName(''); + setImageError(''); + fileInputRef.current.value = ''; + }; + + // Validate form inputs before submitting + const validateForm = () => { + const errors = { + applicationId: '', + file: '' + }; + + if (!applicationId) { + errors.applicationId = 'Please select an Application ID.'; + } + + if (!file) { + errors.file = 'Please upload an image file.'; + } else if (imageError) { + errors.file = imageError; + } + + setValidationErrors(errors); + return Object.values(errors).every(error => error === ''); + }; + + // Submit form and trigger OCR API + const handleCheckClick = async () => { + if (!validateForm()) { + return; // Form is not valid + } + + setIsLoading(true); + const formData = new FormData(); + formData.append('application_id', applicationId); + formData.append('file', file); + + try { + const response = await fetch(`${BASE_URL}/ocr-npwp`, { + method: 'POST', + headers: { + 'accept': 'application/json', + 'x-api-key': API_KEY, + }, + body: formData, + }); + + if (!response.ok) { + throw new Error('OCR processing failed'); + } + + const result = await response.json(); + + // Log the full result to verify structure + console.log('OCR API Response:', result); + + if (result.status_code === 201) { + const responseData = result.details.data?.['data-npwp'] || {}; + + const data = { + npwp: responseData.npwp || 'N/A', + npwpName: responseData.name || 'N/A', + npwpAddress: responseData.address || 'N/A', + npwpX: responseData.npwp_x || 'N/A', + imageUrl: result.details.data?.image_url || '', // Properly access image_url + }; + + console.log('Image URL from OCR:', result.details.data?.image_url); // Log the image URL correctly + + setData(data); + setShowResult(true); + setErrorMessage(''); + setSelectedImageName(''); + setResultImageLabel(selectedImageName); + + // Fetch image if image URL exists in the result + if (result.details.data?.image_url) { + const imageFileName = result.details.data.image_url.split('/').pop(); // Get the image filename + console.log('Image file name:', imageFileName); // Debug the file name + await fetchImage(imageFileName); // Call the fetchImage function to fetch the image + } + } else { + setErrorMessage('OCR processing failed'); + } + } catch (error) { + setErrorMessage(error.message || 'Error during OCR processing'); + } finally { + setIsLoading(false); + } + }; + + // The fetchImage function you already have in your code + const fetchImage = async (imageFileName) => { + setIsLoading(true); + try { + const response = await fetch(`${BASE_URL}/preview/image/${imageFileName}`, { + method: 'GET', + headers: { + 'accept': 'application/json', + 'x-api-key': API_KEY, + } + }); + + if (!response.ok) { + const errorDetails = await response.json(); + console.error('Image fetch error details:', errorDetails); + setErrorMessage('Failed to fetch image, please try again.'); + return; + } + + // Get the image blob + const imageBlob = await response.blob(); + const imageData = URL.createObjectURL(imageBlob); // Create object URL from the blob + + // Debugging: Make sure imageData is correct + console.log('Fetched image URL:', imageData); + + setImageUrl(imageData); // Update imageUrl state with the fetched image data + } catch (error) { + console.error('Error fetching image:', error); + setErrorMessage(error.message); + } finally { + setIsLoading(false); + } + }; + + + const styles = { + selectWrapper: { + position: 'relative', + display: 'inline-block', + width: '100%', + }, + select: { + fontSize: '16px', + padding: '10px', + width: '100%', + borderRadius: '4px', + border: '1px solid #ccc', + }, + remainingQuota: { + display: 'flex', + alignItems: 'center', + }, + quotaText: { + fontSize: '24px', + fontWeight: '600', + }, + timesText: { + fontSize: '16px', + fontWeight: '300', + }, + customLabel: { + fontSize: '18px', + fontWeight: '600', + color: '#1f2d3d', + }, + uploadWrapper: { + marginTop: '1rem', + }, + uploadArea: { + backgroundColor: '#e6f2ff', + height: !isMobile? '30svh' : '50svh', + cursor: 'pointer', + marginTop: '1rem', + paddingTop: '22px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + border: '1px solid #ced4da', + borderRadius: '0.25rem', + textAlign: 'center', + }, + uploadIcon: { + fontSize: '40px', + color: '#0542cc', + marginBottom: '10px', + }, + uploadText: { + color: '#1f2d3d', + fontWeight: '400', + fontSize: '16px', + }, + browseLink: { + color: '#0542cc', + textDecoration: 'none', + fontWeight: 'bold', + }, + uploadError: { + color: 'red', + fontSize: '12px', + marginTop: '10px', + }, + errorContainer: { + marginTop: '10px', + padding: '10px', + backgroundColor: '#f8d7da', + border: '1px solid #f5c6cb', + borderRadius: '4px', + }, + errorText: { + color: '#721c24', + fontSize: '14px', + margin: '0', + },loadingOverlay: { + position: 'fixed', // Gunakan fixed untuk overlay penuh layar + top: 0, + left: 0, + right: 0, + bottom: 0, // Pastikan menutupi seluruh layar + display: 'flex', + justifyContent: 'center', // Posisikan spinner di tengah secara horizontal + alignItems: 'center', // Posisikan spinner di tengah secara vertikal + backgroundColor: 'rgba(0, 0, 0, 0.5)', // Semitransparan di background + color: 'white', + fontSize: '24px', + zIndex: 1000, // Pastikan overlay muncul di atas konten lainnya + }, + spinner: { + border: '4px solid #f3f3f3', // Border gray untuk spinner + borderTop: '4px solid #3498db', // Border biru untuk spinner + borderRadius: '50%', // Membuat spinner bulat + width: '50px', // Ukuran spinner + height: '50px', // Ukuran spinner + animation: 'spin 2s linear infinite', // Menambahkan animasi spin + }, + loadingText: { + marginLeft: '20px', + }, + tableStyle: { + width: '100%', + borderCollapse: 'collapse', + marginTop: '20px', + }, + tableCell: { + padding: '8px 15px', + border: '1px solid #ccc', + textAlign: 'left', + }, + }; + + const formatFileSize = (sizeInBytes) => { + if (sizeInBytes < 1024) { + return `${sizeInBytes} bytes`; // Jika ukuran lebih kecil dari 1 KB + } else if (sizeInBytes < 1048576) { + return `${(sizeInBytes / 1024).toFixed(2)} KB`; // Jika ukuran lebih kecil dari 1 MB + } else { + return `${(sizeInBytes / 1048576).toFixed(2)} MB`; // Jika ukuran lebih besar dari 1 MB + } + }; + + return ( +
+ {/* Inject keyframes for the spinner */} + + {isLoading && ( +
+
+

Loading...

+
+ )} + +
+
+
+

Alert

+

+ Get started now by creating an Application ID and explore all the demo services available on the dashboard. + Experience the ease and flexibility of trying out all our features firsthand. +

+
+
+ + + +
+
+
+ +
+
+
+
+ handleImageUpload(e.target.files[0])} + /> + + {selectedImageName && ( +
+

File: {selectedImageName}

+ {file && ( +

+ Size: {formatFileSize(file.size)} +

+ )} + +
+ )} + {validationErrors.file &&

{validationErrors.file}

} +
+
+
+ +
+ +
+ + {errorMessage && ( +
+

{errorMessage}

+
+ )} +
+ + {showResult && data && ( +
+

OCR NPWP Result

+
+ {/* Gambar di kolom pertama */} +
+ NPWP Image +

+ File Name: {resultImageLabel} {/* Display the resultImageLabel here */} +

+
+ + {/* Tabel di kolom kedua */} +
+ + + + + + + + + + + + + + + + + + + +
NPWP{data.npwp}
NPWP Name{data.npwpName}
NPWP Address{data.npwpAddress}
NPWP (X){data.npwpX}
+
+
+ +
+ )} +
+ ); +}; + +export default Verify; + diff --git a/src/screens/Biometric/OcrNpwp/index.js b/src/screens/Biometric/OcrNpwp/index.js new file mode 100644 index 0000000..f0655f4 --- /dev/null +++ b/src/screens/Biometric/OcrNpwp/index.js @@ -0,0 +1,5 @@ +import VerifyNpwp from "./Verify"; + +export { + VerifyNpwp +} \ No newline at end of file