@@ -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
{children} ;
+};
+
+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 && (
+
+ )}
+
+
+
+
+
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.
+
+
+
+
+
+
+ Create New App ID
+
+
+
+
+
+
+
+
+
+
+ option.value === applicationId)}
+ onChange={handleApplicationChange}
+ options={applicationOptions}
+ placeholder="Select Application ID"
+ isSearchable
+ menuPortalTarget={document.body}
+ menuPlacement="auto"
+ inputValue={inputValueApplication}
+ onInputChange={handleInputChangeApplication} // Limit input length for Application ID
+ />
+
+ {validationErrors.applicationId &&
{validationErrors.applicationId}
}
+
+
+
+ Remaining Quota
+
+
+ {selectedQuota}
+ (times)
+
+
+
+
+ {/* Image Upload */}
+
+
+
+ Upload Image (NPWP)
+
+
+ {/* Drag and Drop File Input */}
+
+
+ {/* File Input */}
+
handleImageUpload(e.target.files[0])}
+ />
+
+ {selectedImageName && (
+
+
File: {selectedImageName}
+ {file && (
+
+ Size: {formatFileSize(file.size)}
+
+ )}
+
+ Cancel
+
+
+ )}
+ {validationErrors.file &&
{validationErrors.file}
}
+
+
+
+
+
+
+
+ Check NPWP
+
+
+
+ {errorMessage && (
+
+ )}
+
+
+ {showResult && data && (
+
+
OCR NPWP Result
+
+ {/* Gambar di kolom pertama */}
+
+
+
+ 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