diff --git a/src/App.js b/src/App.js index dadb07a..58420ca 100644 --- a/src/App.js +++ b/src/App.js @@ -39,7 +39,27 @@ import { VerifyNpwp } from './screens/Biometric/OcrNpwp'; -// Import all other components following the dataMenu structure... +import { + VerifySim, + SummarySim, + TransactionSim +} from './screens/Biometric/OcrSim'; + +import { + VerifyDoc, + SummaryDoc, + TransactionDoc +} from './screens/Biometric/OcrDocument'; + +import { + SmsVerify +} from './screens/Sms/Verification'; + +import { + VerificationAnnoncement, + VerificationOtp +} from './screens/Sms/Verification/Section'; + const App = () => { return ( @@ -70,11 +90,11 @@ const App = () => { } /> {/* Add routes for the verify section */} - } /> + {/* } /> } /> } /> } /> - } /> + } /> */} {/* Biometric - Face Recognition (Summary) */} } /> @@ -91,6 +111,25 @@ const App = () => { } /> } /> + {/* Biometric - SIM */} + } /> + } /> + } /> + + {/* Biometric - Document */} + } /> + } /> + } /> + + {/* Sms Services - Verification */} + }> + {/* Anak rute */} + } /> + } /> + {/* Default route */} + } /> + + {/* } /> */} {/* Continue for each link */} diff --git a/src/assets/images/index.js b/src/assets/images/index.js index b2759d3..c6ccc7c 100644 --- a/src/assets/images/index.js +++ b/src/assets/images/index.js @@ -2,10 +2,12 @@ import ProfileImage from './Profile.jpeg'; import Logo from './Logo.png'; import DashboardImg from './dashboard-img.png'; import DummyKtp from './Dummy-Ktp.png'; +import ServerDownAnimation from './server-down.gif'; export { ProfileImage, Logo, DashboardImg, - DummyKtp + DummyKtp, + ServerDownAnimation } \ No newline at end of file diff --git a/src/assets/images/server-down.gif b/src/assets/images/server-down.gif new file mode 100644 index 0000000..34e11a7 Binary files /dev/null and b/src/assets/images/server-down.gif differ diff --git a/src/screens/Biometric/FaceRecognition/Section/Compare.jsx b/src/screens/Biometric/FaceRecognition/Section/Compare.jsx index f7e3122..a02d065 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Compare.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Compare.jsx @@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faChevronLeft, faChevronDown, faTimes, faImage } from '@fortawesome/free-solid-svg-icons'; import { FileUploader } from 'react-drag-drop-files'; import Select from 'react-select' +import { ServerDownAnimation } from '../../../../assets/images'; const Compare = () => { @@ -33,6 +34,7 @@ const Compare = () => { const [applicationIds, setApplicationIds] = useState([]); const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID + const [isServer, setIsServer] = useState(true); const thresholdIds = [ { id: 1, name: 'cosine', displayName: 'Basic' }, @@ -65,13 +67,16 @@ const Compare = () => { if (data.status_code === 200) { const ids = data.details.data.map(app => app.id); + setIsServer(true) console.log('Application Id: ' + ids); // Log the IDs setApplicationIds(data.details.data); // Update state with the fetched data } else { console.error('Failed to fetch data:', data.details.message); + setIsServer(false) } } catch (error) { console.error('Error fetching application IDs:', error); + setIsServer(false) } finally { setIsLoading(false) } @@ -318,6 +323,32 @@ const Compare = () => { } }; + if (!isServer) { + return ( +
+ Server Down Animation +

Server tidak dapat diakses

+

{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}

+ +
+ ); + } + return (
{/* Inject keyframes for the spinner */} diff --git a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx index 440c83f..e94a6e1 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx @@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTimes, faImage } from '@fortawesome/free-solid-svg-icons'; import { FileUploader } from 'react-drag-drop-files'; import Select from 'react-select' +import { ServerDownAnimation } from '../../../../assets/images'; const Enroll = () => { @@ -33,32 +34,41 @@ const Enroll = () => { const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID const [options, setOptions] = useState([]); const [isMobile, setIsMobile] = useState(false); + const [isServer, setIsServer] = useState(true); useEffect(() => { const fetchApplicationIds = async () => { - setIsLoading(true); - const url = `${BASE_URL}/application/list`; try { - const response = await fetch(url, { + 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(); + console.log('Response Data:', data); + if (data.status_code === 200) { setApplicationIds(data.details.data); + setIsServer(true); } else { - console.error('Failed to fetch data:', data.details.message); + setIsServer(false); + throw new Error(data.details.message || 'Failed to fetch application IDs'); } } catch (error) { - console.error('Error fetching application IDs:', error); + setErrorMessage(error.message || 'Error fetching application IDs'); + setIsServer(false); } finally { setIsLoading(false); } - }; + }; fetchApplicationIds(); setOptions(subjectIds.map(id => ({ value: id, label: id }))); @@ -624,9 +634,35 @@ const Enroll = () => { marginTop: '10px', }, }; + + if (!isServer) { + return ( +
+ Server Down Animation +

Server tidak dapat diakses

+

{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}

+ +
+ ); + } return ( -
+ <> + {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 Document Result

+
+ {/* Gambar di kolom pertama */} +
+ DOC 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/OcrDocument/index.js b/src/screens/Biometric/OcrDocument/index.js new file mode 100644 index 0000000..55ba4d1 --- /dev/null +++ b/src/screens/Biometric/OcrDocument/index.js @@ -0,0 +1,9 @@ +import VerifyDoc from "./Verify"; +import SummaryDoc from "./Summary"; +import TransactionDoc from "./Transaction"; + +export { + VerifyDoc, + SummaryDoc, + TransactionDoc +} \ No newline at end of file diff --git a/src/screens/Biometric/OcrKtp/Verify.jsx b/src/screens/Biometric/OcrKtp/Verify.jsx index 5657820..a7bee9a 100644 --- a/src/screens/Biometric/OcrKtp/Verify.jsx +++ b/src/screens/Biometric/OcrKtp/Verify.jsx @@ -3,6 +3,7 @@ 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' +import { ServerDownAnimation } from '../../../assets/images'; const CustomLabel = ({ overRide, children, ...props }) => { return ; @@ -28,6 +29,7 @@ const Verify = () => { const [showResult, setShowResult] = useState(false); const [inputValueApplication, setInputValueApplication] = useState(''); const [selectedQuota, setSelectedQuota] = useState(0); + const [isServer, setIsServer] = useState(true); // Validation state const [validationErrors, setValidationErrors] = useState({ @@ -53,20 +55,22 @@ const Verify = () => { } const data = await response.json(); - // Log response data console.log('Response Data:', data); if (data.status_code === 200) { setApplicationIds(data.details.data); + setIsServer(true); } else { - throw new Error('Failed to fetch application IDs'); + setIsServer(false); + throw new Error(data.details.message || 'Failed to fetch application IDs'); } } catch (error) { setErrorMessage(error.message || 'Error fetching application IDs'); + setIsServer(false); } finally { setIsLoading(false); } - }; + }; fetchApplicationIds(); @@ -396,6 +400,32 @@ const Verify = () => { } }; + if (!isServer) { + return ( +
+ Server Down Animation +

Server tidak dapat diakses

+

{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}

+ +
+ ); + } + return (
{/* Inject keyframes for the spinner */} diff --git a/src/screens/Biometric/OcrNpwp/Verify.jsx b/src/screens/Biometric/OcrNpwp/Verify.jsx index 94e7f8a..b3d6547 100644 --- a/src/screens/Biometric/OcrNpwp/Verify.jsx +++ b/src/screens/Biometric/OcrNpwp/Verify.jsx @@ -3,6 +3,7 @@ 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' +import { ServerDownAnimation } from '../../../assets/images'; const CustomLabel = ({ overRide, children, ...props }) => { return ; @@ -28,6 +29,7 @@ const Verify = () => { const [showResult, setShowResult] = useState(false); const [inputValueApplication, setInputValueApplication] = useState(''); const [selectedQuota, setSelectedQuota] = useState(0); + const [isServer, setIsServer] = useState(true); // Validation state const [validationErrors, setValidationErrors] = useState({ @@ -47,23 +49,28 @@ const Verify = () => { 'x-api-key': API_KEY, }, }); - + if (!response.ok) { throw new Error('Failed to fetch application IDs'); } - + const data = await response.json(); + console.log('Response Data:', data); + if (data.status_code === 200) { setApplicationIds(data.details.data); + setIsServer(true); } else { - throw new Error('Failed to fetch application IDs'); + setIsServer(false); + throw new Error(data.details.message || 'Failed to fetch application IDs'); } } catch (error) { setErrorMessage(error.message || 'Error fetching application IDs'); + setIsServer(false); } finally { setIsLoading(false); } - }; + }; fetchApplicationIds(); @@ -383,6 +390,32 @@ const Verify = () => { } }; + if (!isServer) { + return ( +
+ Server Down Animation +

Server tidak dapat diakses

+

{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}

+ +
+ ); + } + return (
{/* Inject keyframes for the spinner */} diff --git a/src/screens/Biometric/OcrSim/Summary.jsx b/src/screens/Biometric/OcrSim/Summary.jsx new file mode 100644 index 0000000..f3604fc --- /dev/null +++ b/src/screens/Biometric/OcrSim/Summary.jsx @@ -0,0 +1,267 @@ +import React, { useState, useEffect } from 'react'; +import { + Extract, + Transaction, + Failed +} from '../../../assets/icon'; + +const Summary = () => { + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + const [application, setApplication] = useState(''); + const [isMobile, setIsMobile] = useState(false); + + const menuData = [ + { id: 1, value: '150', label: 'Total Transaction', icon: Transaction }, + { id: 2, value: '4', label: 'Total Extract', icon: Extract }, + { id: 3, value: '65', label: 'Total Failed', icon: Failed }, + ]; + + const handleApply = () => { + console.log({ startDate, endDate, application }); + }; + + const handleCancel = () => { + setStartDate(''); + setEndDate(''); + setApplication(''); + }; + + // Detect if the device is mobile based on window width + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth <= 768); // Define mobile screen as width <= 768px + }; + + // Initialize on component mount + handleResize(); + + // Add event listener to update state on resize + window.addEventListener('resize', handleResize); + + // Cleanup event listener on component unmount + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return ( +
+ {/* Welcome Message */} +
+
+
+

+ 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. +

+
+
+
+ +
+
+ {/* Row 1: Start Date and End Date */} +
+ setStartDate(e.target.value)} + style={styles.filterInput} + placeholder="Start Date" + /> + setEndDate(e.target.value)} + style={styles.filterInput} + placeholder="End Date" + /> +
+ + {/* Row 2: Application Select */} +
+ +
+ + {/* Row 3: Apply and Cancel Buttons */} +
+ + +
+
+ +
+ {menuData.map((item) => ( +
+
+

{item.value}

+

{item.label}

+
+
+ {item.label} +
+
+ ))} +
+ + +
+
+ ); +}; + +const styles = { + container: { + margin: '3rem', + }, + filters: { + display: 'flex', + justifyContent: 'space-between', + marginBottom: '20px', + flexDirection: 'row', // Horizontal layout on desktop + gap: '10px', + }, + filtersMobile: { + display: 'flex', + flexDirection: 'column', // Vertical layout on mobile + gap: '15px', + }, + filterRow: { + display: 'flex', + justifyContent: 'space-between', + gap: '10px', + width: '100%', + }, + filterInput: { + padding: '10px', + border: '1px solid #ccc', + borderRadius: '5px', + fontSize: '16px', + width: '48%', // Adjust width to take up 48% of the row for input elements + }, + applyButton: { + backgroundColor: '#0542cc', + color: '#fff', + padding: '10px 20px', + border: 'none', + borderRadius: '5px', + cursor: 'pointer', + width: '48%', // Adjust button width to take half of the row + }, + cancelButton: { + backgroundColor: '#fff', + color: '#000', + padding: '10px 20px', + border: '1px solid gray', + borderRadius: '5px', + cursor: 'pointer', + width: '48%', // Adjust button width to take half of the row + }, + summaryContainer: { + padding: '20px', + border: '0.1px solid rgba(0, 0, 0, 0.2)', + borderLeft: '4px solid #0542cc', + borderRadius: '10px', + width: '100%', + }, + summaryCards: { + display: 'grid', + gridTemplateColumns: 'repeat(4, 1fr)', // 4 columns on desktop + gap: '20px', + marginTop: '20px', + }, + summaryCardsMobile: { + display: 'grid', + gridTemplateColumns: '1fr', // 1 column on mobile (full width) + gap: '15px', + marginTop: '20px', + }, + card: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + padding: '15px', // Reduced padding for a more compact card + backgroundColor: 'white', + borderRadius: '8px', // Slightly smaller border-radius for a sharper look + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', + textAlign: 'left', + minWidth: '200px', // Ensure minimum width for mobile responsiveness + maxWidth: '300px', // Limit the maximum width to prevent cards from becoming too wide + width: '100%', + transition: 'transform 0.3s ease', // Smooth transition for hover effects + }, + cardHover: { + transform: 'scale(1.05)', // Slight zoom on hover for interactivity + }, + iconContainer: { + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + }, + textContainer: { + display: 'flex', + flexDirection: 'column', + }, + icon: { + width: '36px', // Smaller icon size for a more compact card + height: '36px', // Matching height for the icon + }, + cardTitle: { + fontSize: '18px', // Smaller font size for title to prevent overflow + margin: '0', + fontWeight: 'bold', + }, + cardText: { + fontSize: '12px', // Smaller text size for description to keep it balanced + color: '#555', + }, + + // Optional: Hover effect for the cards to enhance interactivity + cardHover: { + transform: 'scale(1.05)', // Slight zoom effect when hovering over card + }, + + // Media query for smaller screens (e.g., phones in portrait mode) + "@media (max-width: 768px)": { + summaryCards: { + gridTemplateColumns: '1fr', // 1 column on mobile screens + }, + card: { + padding: '10px', // Smaller padding for smaller screens + minWidth: '180px', // Adjust min width for mobile devices + maxWidth: '250px', // Adjust max width for mobile devices + }, + icon: { + width: '30px', // Slightly smaller icon size for mobile + height: '30px', // Matching height for mobile icon + }, + cardTitle: { + fontSize: '16px', // Smaller font size for the card title + }, + cardText: { + fontSize: '10px', // Smaller font size for card description + }, + }, +}; + +export default Summary; diff --git a/src/screens/Biometric/OcrSim/Transaction.jsx b/src/screens/Biometric/OcrSim/Transaction.jsx new file mode 100644 index 0000000..7271084 --- /dev/null +++ b/src/screens/Biometric/OcrSim/Transaction.jsx @@ -0,0 +1,400 @@ +import React, { useState, useEffect } from 'react'; +import { FaChevronLeft, FaChevronRight, FaFastBackward, FaFastForward, FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; // Icons for sorting +import { NoAvailable } from '../../../assets/icon'; + +// Pagination Component +const Pagination = ({ currentPage, totalPages, onPageChange }) => { + const handlePrev = () => { + if (currentPage > 1) { + onPageChange(currentPage - 1); + } + }; + + const handleNext = () => { + if (currentPage < totalPages) { + onPageChange(currentPage + 1); + } + }; + + const handleFirst = () => { + onPageChange(1); // Go to first page + }; + + const handleLast = () => { + onPageChange(totalPages); // Go to last page + }; + + // Logic to display only 3 pages in pagination + const getPaginationRange = () => { + const range = []; + const totalPagesCount = totalPages; + + let start = currentPage - 1; + let end = currentPage + 1; + + // Adjust start and end if near the boundaries + if (currentPage === 1) { + start = 1; + end = Math.min(3, totalPagesCount); + } else if (currentPage === totalPages) { + start = Math.max(totalPagesCount - 2, 1); + end = totalPagesCount; + } + + for (let i = start; i <= end; i++) { + range.push(i); + } + + return range; + }; + + const pageRange = getPaginationRange(); + + return ( +
+ {/* First Page Button */} + + + + + {/* Page Numbers */} + {pageRange.map((pageNum) => ( + + ))} + + + + {/* Last Page Button */} + +
+ ); +}; + +const Transaction = () => { + const [currentPage, setCurrentPage] = useState(1); + const [isMobile, setIsMobile] = useState(false); // State to detect mobile view + const [transactionData, setTransactionData] = useState([]); + const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' }); // Sorting state + const dataPerPage = 10; // Data per page (10 data per page) + + const buttonData = [ + { label: 'Copy', enabled: true }, + { label: 'CSV', enabled: true }, + { label: 'Excel', enabled: true }, + { label: 'PDF', enabled: true }, + { label: 'Print', enabled: true }, + { label: 'Column Visibility', enabled: true }, + ]; + + + // Generate 691 dummy transactions + const generateDummyData = (numOfItems) => { + const transactionData = []; + + for (let i = 1; i <= numOfItems; i++) { + transactionData.push({ + transactionId: `TX${String(i).padStart(3, '0')}`, + applicationName: `App ${Math.floor(Math.random() * 5) + 1}`, + createdAt: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toLocaleDateString(), // Random date + referenceId: `REF${String(i).padStart(3, '0')}`, + status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)], + mode: Math.random() > 0.5 ? 'Online' : 'Offline', + }); + } + + return transactionData; + }; + + + // Set the generated transaction data + useEffect(() => { + setTransactionData(generateDummyData(52)); // count data dummy transactions + }, []); + + // Sorting function + const sortData = (data, config) => { + const { key, direction } = config; + return [...data].sort((a, b) => { + if (a[key] < b[key]) { + return direction === 'asc' ? -1 : 1; + } + if (a[key] > b[key]) { + return direction === 'asc' ? 1 : -1; + } + return 0; + }); + }; + + // Handle column header sort click + const handleSort = (key) => { + let direction = 'asc'; + if (sortConfig.key === key && sortConfig.direction === 'asc') { + direction = 'desc'; // Toggle direction if the same column is clicked + } + setSortConfig({ key, direction }); + }; + + // Get the paginated data + const getPaginatedData = (data, page, perPage) => { + const sortedData = sortData(data, sortConfig); + const startIndex = (page - 1) * perPage; + const endIndex = startIndex + perPage; + return sortedData.slice(startIndex, endIndex); + }; + + // Handle page change + const handlePageChange = (page) => { + setCurrentPage(page); + }; + + // Calculate total pages based on the data and data per page + const totalPages = Math.ceil(transactionData.length / dataPerPage); + + // Paginated data + const paginatedData = getPaginatedData(transactionData, currentPage, dataPerPage); + + // Detect screen size and update isMobile state + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth <= 768); // Change 768 to your breakpoint + }; + + handleResize(); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return ( +
+ {/* Welcome Message */} +
+
+
+

+ 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. +

+
+
+
+ +
+ {/* Filter Form */} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Action Buttons */} +
+
+ {buttonData.map((button, index) => + button.enabled ? ( + + ) : null + )} +
+ + {/* Search Bar with Icon */} +
+ + + {/* FontAwesome search icon */} + +
+
+ + + {/* Table */} +
+ + + + {/* Kolom untuk Nomor Urut */} + + + + + + + + + + + {paginatedData.length > 0 ? ( + paginatedData.map((transaction, index) => ( + + {/* Kolom Nomor Urut */} + {/* Nomor urut berdasarkan halaman dan index */} + + + + + + + + )) + ) : ( + + + + )} + +
No. + + + + + + + + + + + +
{(currentPage - 1) * dataPerPage + index + 1}{transaction.transactionId}{transaction.applicationName}{transaction.createdAt}{transaction.referenceId}{transaction.status}{transaction.mode}
+
+ No Data Available +

Data not available

+
+
+
+ + {/* Pagination */} + +
+
+ ); +}; + +export default Transaction; + +const styles = { + contentContainer: { + padding: '20px', + border: '0.1px solid rgba(0, 0, 0, 0.2)', + borderLeft: '4px solid #0542cc', + borderRadius: '10px', + width: '100%', + }, + tableContainer: { + minHeight: '300px', + maxHeight: '1500px', + overflowY: 'auto', + }, + iconStyle: { + width: '50px', + height: '50px', + }, + // Add margin-left style for icons + iconMarginLeft: { + marginLeft: '0.7rem', // Adjust as needed + }, +}; + diff --git a/src/screens/Biometric/OcrSim/Verify.jsx b/src/screens/Biometric/OcrSim/Verify.jsx new file mode 100644 index 0000000..40cd979 --- /dev/null +++ b/src/screens/Biometric/OcrSim/Verify.jsx @@ -0,0 +1,607 @@ +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' +import { ServerDownAnimation } from '../../../assets/images'; + +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); + const [isServer, setIsServer] = useState(true); + + // 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(); + console.log('Response Data:', data); + + if (data.status_code === 200) { + setApplicationIds(data.details.data); + setIsServer(true); + } else { + setIsServer(false); + throw new Error(data.details.message || 'Failed to fetch application IDs'); + } + } catch (error) { + setErrorMessage(error.message || 'Error fetching application IDs'); + setIsServer(false); + } 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 updateQuota = result.details.data.quota + + 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 + }; + + setSelectedQuota(updateQuota) + + 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 + } + }; + + if (!isServer) { + return ( +
+ Server Down Animation +

Server tidak dapat diakses

+

{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}

+ +
+ ); + } + + 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 SIM Result

+
+ {/* Gambar di kolom pertama */} +
+ SIM 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/OcrSim/index.js b/src/screens/Biometric/OcrSim/index.js new file mode 100644 index 0000000..2f272a6 --- /dev/null +++ b/src/screens/Biometric/OcrSim/index.js @@ -0,0 +1,9 @@ +import VerifySim from "./Verify"; +import SummarySim from "./Summary"; +import TransactionSim from "./Transaction"; + +export { + VerifySim, + SummarySim, + TransactionSim +} \ No newline at end of file diff --git a/src/screens/Sms/Verification/Section/Announcement.jsx b/src/screens/Sms/Verification/Section/Announcement.jsx new file mode 100644 index 0000000..43ab43a --- /dev/null +++ b/src/screens/Sms/Verification/Section/Announcement.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const Announcement = () => { + return ( +
+

Sms Verification - Announcement

+
+ ) +} + +export default Announcement diff --git a/src/screens/Sms/Verification/Section/Otp.jsx b/src/screens/Sms/Verification/Section/Otp.jsx new file mode 100644 index 0000000..bfeca50 --- /dev/null +++ b/src/screens/Sms/Verification/Section/Otp.jsx @@ -0,0 +1,279 @@ +import React, { useState, useEffect } from 'react'; +import Select from 'react-select'; + +const BASE_URL = process.env.REACT_APP_BASE_URL; +const API_KEY = process.env.REACT_APP_API_KEY; + +const Otp = () => { + const [isSelectOpen, setIsSelectOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [applicationId, setApplicationId] = useState(''); + const [expiryId, setExpiryId] = useState(0); + const [otpId, setOtpId] = useState(''); + const [messageId, setMessageId] = useState(''); + const [phoneId, setPhoneId] = useState(''); + const [inputValueApplication, setInputValueApplication] = useState(''); + const [applicationIds, setApplicationIds] = useState([]); + const [isMobile, setIsMobile] = useState(false); + + const applicationOptions = applicationIds.map(app => ({ + value: app.id, + label: app.name + })); + + const handleApplicationChange = (selectedOption) => { + setApplicationId(selectedOption.value); + }; + + const handleInputChangeApplication = (newInputValue) => { + if (newInputValue.length <= 15) { + setInputValueApplication(newInputValue); + } + }; + + const fetchApplicationIds = async () => { + try { + const response = await fetch(`${BASE_URL}/application/list`, { + method: 'GET', + headers: { + 'accept': 'application/json', + 'x-api-key': API_KEY, + }, + }); + const data = await response.json(); + return data.details.data; + } catch (error) { + console.error('Error fetching application IDs:', error); + return []; + } + }; + + useEffect(() => { + const fetchData = async () => { + setIsLoading(true); + const data = await fetchApplicationIds(); + setApplicationIds(data); + setIsLoading(false); + }; + + fetchData(); + + const checkIfMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + checkIfMobile(); + window.addEventListener('resize', checkIfMobile); + return () => window.removeEventListener('resize', checkIfMobile); + }, []); + + const handleFocus = () => { + setIsSelectOpen(true); + }; + + const handleBlur = () => { + setIsSelectOpen(false); + }; + + const handleClick = () => { + console.log('Make OTP Demo'); + }; + + +const styles = { + text: { + fontSize: '16px', + fontWeight: '400', + margin: '0', + marginTop: '8px', + }, + remainingQuotaWrapper: { + marginTop: '2vh', + }, + remainingQuota: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + marginTop: '4px', + }, + quotaText: { + fontSize: '40px', + color: '#0542cc', + fontWeight: '600', + }, + timesText: { + marginLeft: '8px', + verticalAlign: 'super', + fontSize: '20px', + }, + submitButton: { + marginLeft: 'auto', + marginTop: '4rem', + textAlign: 'start', + position: 'relative', + zIndex: 1, + }, + submitBtn: { + backgroundColor: '#0542CC', + }, + loadingOverlay: { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.2)', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + zIndex: 1000, + }, + spinner: { + border: '4px solid rgba(0, 0, 0, 0.1)', + borderLeftColor: '#0542cc', + borderRadius: '50%', + width: '90px', + height: '90px', + animation: 'spin 1s ease-in-out infinite', + }, + select: { + width: '100%', + paddingRight: '30px', + fontSize: '16px', + border: '1px solid #ccc', + outline: 'none', + }, + OtpId: { + marginTop: isMobile ? '2vh' : '0', + }, + PhoneNumber: { + marginTop: isMobile ? '2vh' : '-12vh', + }, + textarea: { + width: '100%', + height: '200px', + resize: 'none', + border: '1px solid #ccc', + outline: 'none', + }, + characterCount: { + display: 'flex', + justifyContent: 'flex-end', + marginTop: '8px', + }, + }; + + return ( +
+ + + {isLoading && ( +
+
+
+ )} + +
+
+ setExpiryId(parseInt(e.target.value) || 0)} + onFocus={handleFocus} + onBlur={handleBlur} + placeholder="Expiry Time" + /> +
+ +
+ setOtpId(e.target.value)} + /> +
+
+ +
+
+