From e00e7a1a385600fcdd10a1b101ccc37360db7e32 Mon Sep 17 00:00:00 2001 From: Malini Date: Wed, 21 Jan 2026 18:17:15 +0530 Subject: [PATCH] changes added in company profile --- .../components/dashboard/WelcomeSection.jsx | 40 ++- .../src/pages/Admin/ValidationReview.jsx | 101 +++++-- .../src/pages/Admin/Validations.jsx | 17 +- .../Admin/configuration/CompanyProfile.jsx | 17 ++ .../pages/OTPVerification/OTPVerification.jsx | 266 ++++++++++-------- 5 files changed, 281 insertions(+), 160 deletions(-) diff --git a/ipi-survey-platform/src/components/dashboard/WelcomeSection.jsx b/ipi-survey-platform/src/components/dashboard/WelcomeSection.jsx index e5ee52c..1549acf 100644 --- a/ipi-survey-platform/src/components/dashboard/WelcomeSection.jsx +++ b/ipi-survey-platform/src/components/dashboard/WelcomeSection.jsx @@ -15,6 +15,20 @@ import HeaderBar from '@/components/layout/HeaderBar'; // return { quarter, year }; // }; +const formatName = (name = '') => { + return name + // camelCase → add space + .replace(/([a-z])([A-Z])/g, '$1 $2') + // split by space + .split(' ') + // capitalize each word + .map( + word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + ) + .join(' '); +}; + + export const WelcomeSection = ({ data = null, loading = false, error = '' }) => { // const { quarter: currentQuarter, year: currentYear } = getCurrentQuarterAndYear(); @@ -32,18 +46,20 @@ export const WelcomeSection = ({ data = null, loading = false, error = '' }) =>
-

- Welcome{' '} - {(() => { - try { - const userProfile = JSON.parse(localStorage.getItem('user_profile')); - return userProfile?.name ? userProfile.name : ''; - } catch (error) { - console.error('Error reading user_profile from sessionStorage:', error); - return ''; - } - })()}! -

+

+ Welcome{' '} + {(() => { + try { + const userProfile = JSON.parse(localStorage.getItem('user_profile')); + return userProfile?.name ? formatName(userProfile.name) : ''; + } catch (error) { + console.error('Error reading user_profile from localStorage:', error); + return ''; + } + })()} + ! +

+ {/*

{ @@ -931,12 +933,29 @@ setAdditionalForecastMonths([

- +
@@ -1888,25 +1907,52 @@ setAdditionalForecastMonths([
)} -
-
- - -
+
+
+ {/* LEFT SIDE - Back */} + + + {/* RIGHT SIDE - Actions */} +
+ + + +
+
+
{isApproveOpen && ( @@ -1938,11 +1984,16 @@ setAdditionalForecastMonths([ > {actionLoading ? 'Processing...' : 'Approve'} + +
)} + + + ); }; diff --git a/ipi-survey-platform/src/pages/Admin/Validations.jsx b/ipi-survey-platform/src/pages/Admin/Validations.jsx index efa0162..5f847cf 100644 --- a/ipi-survey-platform/src/pages/Admin/Validations.jsx +++ b/ipi-survey-platform/src/pages/Admin/Validations.jsx @@ -66,18 +66,18 @@ const ManageSubmissions = () => { const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [search, setSearch] = React.useState(''); - const [year, setYear] = React.useState(''); + const [year, setYear] = React.useState('All'); // Function to get current quarter (1-4) const getCurrentQuarter = () => { const month = new Date().getMonth(); return `Q${Math.floor(month / 3) + 1}`; }; - const [quarter, setQuarter] = React.useState(getCurrentQuarter()); + const [quarter, setQuarter] = React.useState('All'); const [emirate, setEmirate] = React.useState('All'); const [status, setStatus] = React.useState('All'); - const [selectedQuarter, setSelectedQuarter] = React.useState(getCurrentQuarter()); - const [selectedYear, setSelectedYear] = React.useState(new Date().getFullYear().toString()); + const [selectedQuarter, setSelectedQuarter] = React.useState('All'); + const [selectedYear, setSelectedYear] = React.useState('All'); const [currentPage, setCurrentPage] = React.useState(1); const [toast, setToast] = React.useState(null); const [showApproveModal, setShowApproveModal] = React.useState(false); @@ -91,7 +91,7 @@ const ManageSubmissions = () => { const [isInitialLoad, setIsInitialLoad] = React.useState(true); const [filters, setFilters] = React.useState({ search: '', - year: '', + year: 'All', quarter: 'All', emirate: 'All', status: 'All' @@ -330,10 +330,9 @@ React.useEffect(() => { params, withCredentials: true }); - const currentQuarter = response?.data?.selected_quarter ; - // const currentQuarter = 'All'; - const currentYear = response?.data?.selected_year || new Date().getFullYear().toString(); - // const currentYear = 'All'; + // Always use 'All' as default for both quarter and year + const currentQuarter = 'All'; + const currentYear = 'All'; // Set the filter states setSelectedQuarter(currentQuarter); setSelectedYear(currentYear); diff --git a/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx b/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx index 36341bd..881d46f 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx @@ -3324,6 +3324,23 @@ const handleImport = async () => { }; })} rows={displayedRows} + renderCell={(value, rowIndex, colIndex) => { + // ISIC Code column is at index 3 (0-based) + if (colIndex === 3 && value) { + const truncated = value.length > 10 ? `${value.substring(0, 10)}…` : value; + return ( +
+
{truncated}
+ {value.length > 10 && ( +
+ {value} +
+ )} +
+ ); + } + return value; + }} columnWidths={[ 300, // Establishment Name (increased from 250) 180, // Contact Name (increased from 150) diff --git a/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx b/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx index 082d545..811a4de 100644 --- a/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx +++ b/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx @@ -4,28 +4,52 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { AlertCircle, Clock, CheckCircle } from "lucide-react"; const logoSrc = '/assets/images/FCSCLogo.svg'; - + const OTPVerification = () => { const navigate = useNavigate(); const location = useLocation(); - + const [otp, setOtp] = useState(['', '', '', '', '', '']); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const [toast, setToast] = useState(null); - const [resendDisabled, setResendDisabled] = useState(false); - const [countdown, setCountdown] = useState(600); + const [resendDisabled, setResendDisabled] = useState(false); // Resend is enabled by default + const [otpExpiryCountdown, setOtpExpiryCountdown] = useState(600); // 10 minutes countdown + const [resendCooldown, setResendCooldown] = useState(0); // No initial cooldown + const [showResendCooldown, setShowResendCooldown] = useState(false); // Only show cooldown after first click const [attemptsRemaining, setAttemptsRemaining] = useState(3); - + const inputRefs = useRef(Array(6).fill(null).map(() => React.createRef())); const toastTimeoutRef = useRef(null); const countdownRef = useRef(null); - + // Get email from location state or use a default - const maskedEmail = location.state?.email ? - `${location.state.email.split('@')[0].substring(0, 2)}***@${location.state.email.split('@')[1]}` : - 'na***@company.com'; - + const maskedEmail = (() => { + const email = location.state?.email; + if (!email) return 'na***@company.com'; + + const [local, domain] = email.split('@'); + + // local part masking + const maskedLocal = + local.length > 3 + ? `${local.slice(0, 2)}*****${local.slice(-1)}` + : `${local.slice(0, 1)}***`; + + // domain name + extension + const domainParts = domain.split('.'); + const domainName = domainParts[0]; + const extension = domainParts.slice(1).join('.'); + + const maskedDomain = + domainName.length > 3 + ? `${domainName.slice(0, 2)}****${domainName.slice(-1)}` + : `${domainName.slice(0, 1)}***`; + + return `${maskedLocal}@${maskedDomain}.${extension}`; +})(); + + // Toast handling const closeToast = useCallback(() => { if (toastTimeoutRef.current) { @@ -34,7 +58,7 @@ const OTPVerification = () => { } setToast(null); }, []); - + const showToast = useCallback((type, message) => { if (!message) return; if (toastTimeoutRef.current) { @@ -47,24 +71,24 @@ const OTPVerification = () => { toastTimeoutRef.current = null; }, 4000); }, []); - + // Handle OTP input change const handleOtpChange = (e, index) => { const value = e.target.value; - + // Only allow numbers and limit to 1 character if (value && !/^\d*$/.test(value)) return; - + const newOtp = [...otp]; newOtp[index] = value.slice(-1); setOtp(newOtp); - + // Move to next input if there's a value and we're not on the last input if (value && index < 5 && inputRefs.current[index + 1]?.current) { inputRefs.current[index + 1].current.focus(); } }; - + // Handle backspace const handleKeyDown = (e, index) => { if (e.key === 'Backspace' && !otp[index] && index > 0 && inputRefs.current[index - 1]?.current) { @@ -72,7 +96,7 @@ const OTPVerification = () => { inputRefs.current[index - 1].current.focus(); } }; - + // Handle paste const handlePaste = (e) => { e.preventDefault(); @@ -82,40 +106,40 @@ const OTPVerification = () => { setOtp([...newOtp, ...Array(6 - newOtp.length).fill('')]); } }; - + // Handle verify OTP const handleVerify = async (e) => { e.preventDefault(); const otpValue = otp.join(''); - + if (otpValue.length !== 6) { setError('Please enter a valid 6-digit code'); return; } - + setLoading(true); setError(''); - + try { - const response = await verifyOtp({ - registered_email: location.state?.email, - otp: otpValue + const response = await verifyOtp({ + registered_email: location.state?.email, + otp: otpValue }); - + if (response?.status === 'success') { // Show success message immediately showToast('success', 'Verified! You are signed in. Redirecting to your dashboard.'); - + // Get the user role from the most reliable source - const userRole = response?.data?.user?.role || - response?.data?.role || + const userRole = response?.data?.user?.role || + response?.data?.role || localStorage.getItem('user_role'); - + // Set the role in localStorage if not already set if (userRole && !localStorage.getItem('user_role')) { localStorage.setItem('user_role', userRole); } - + // Update user profile in local storage with OTP verification status try { const userProfile = JSON.parse(localStorage.getItem('user_profile') || '{}'); @@ -128,22 +152,22 @@ const OTPVerification = () => { } catch (error) { console.error('Failed to update user profile:', error); } - + // Determine the appropriate dashboard based on user role let redirectPath = '/dashboard'; - + // Check if user is admin (case-insensitive check) if (userRole && userRole.toLowerCase() === 'admin') { redirectPath = '/admin/dashboard'; console.log('Admin user detected, redirecting to admin dashboard'); } - + // If there's a saved path from before login, use that const savedPath = location.state?.from?.pathname; if (savedPath) { redirectPath = savedPath; } - + // Redirect after a short delay setLoading(true); setTimeout(() => { @@ -152,20 +176,20 @@ const OTPVerification = () => { } else { throw new Error(response?.message || 'Invalid OTP'); } - + } catch (err) { console.error('OTP verification failed', err); - const errorMessage = err.response?.data?.message || - err.message || + const errorMessage = err.response?.data?.message || + err.message || 'An error occurred during OTP verification. Please try again.'; - + const newAttempts = attemptsRemaining - 1; setAttemptsRemaining(newAttempts); - + // Set the error message for incorrect OTP const displayError = errorMessage || 'Invalid verification code'; setError(displayError); - + if (newAttempts <= 0) { setOtp(Array(6).fill('')); // Show error message and redirect to login after a short delay @@ -184,76 +208,85 @@ const OTPVerification = () => { setLoading(false); } }; - + + // Handle OTP expiry countdown + useEffect(() => { + const timer = setInterval(() => { + setOtpExpiryCountdown(prev => { + if (prev <= 0) { + clearInterval(timer); + // Redirect to login when OTP expires + navigate('/login', { + state: { + message: 'OTP has expired. Please try again.', + messageType: 'error' + }, + replace: true + }); + return 0; + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(timer); + }, [navigate]); + + // Handle resend cooldown timer + useEffect(() => { + if (resendCooldown <= 0) { + setResendDisabled(false); + return; + } + + setResendDisabled(true); + const cooldownTimer = setInterval(() => { + setResendCooldown(prev => { + if (prev <= 1) { + clearInterval(cooldownTimer); + setResendDisabled(false); + return 0; + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(cooldownTimer); + }, [resendCooldown]); + // Handle resend OTP const handleResend = async () => { if (resendDisabled) return; - + // Show loading state immediately setResendDisabled(true); setLoading(true); - + try { // Call the API to resend OTP const response = await requestOtp(location.state?.email); - - // Update state and show success toast immediately - const tenMinutesInSeconds = 10 * 60; - setCountdown(tenMinutesInSeconds); + + // Reset timers + setOtpExpiryCountdown(600); // Reset to 10 minutes + setResendCooldown(60); // Start 60 seconds cooldown + setShowResendCooldown(true); // Show the cooldown timer setAttemptsRemaining(3); setError(''); setOtp(Array(6).fill('')); - - // Use setTimeout to ensure the toast shows up in the next tick - setTimeout(() => { - showToast('success', response.message || 'A new OTP has been sent to your email.'); - }, 0); - + + // Show success message immediately + showToast('success', response.message || 'A new OTP has been sent to your email.'); + } catch (error) { console.error('Error resending OTP:', error); showToast('error', 'An error occurred while sending OTP. Please try again.'); + setResendDisabled(false); } finally { setLoading(false); - - // Re-enable resend after 60 seconds - const timer = setTimeout(() => { - setResendDisabled(false); - }, 60000); - - // Cleanup timer on component unmount - return () => clearTimeout(timer); } }; - - // Start countdown on component mount - useEffect(() => { - // Always use 10 minutes for the countdown - const tenMinutesInMs = 10 * 60 * 1000; - const expiryTime = Date.now() + tenMinutesInMs; - - const updateCountdown = () => { - const now = Date.now(); - const remainingSeconds = Math.max(0, Math.floor((expiryTime - now) / 1000)); - - setCountdown(remainingSeconds); - - if (remainingSeconds <= 0) { - clearInterval(countdownRef.current); - setResendDisabled(false); - } - }; - - // Initial update - updateCountdown(); - - // Set up interval for countdown updates - countdownRef.current = setInterval(updateCountdown, 1000); - - return () => { - if (countdownRef.current) clearInterval(countdownRef.current); - }; - }, []); - + + // Cleanup on unmount useEffect(() => { return () => { @@ -261,10 +294,13 @@ const OTPVerification = () => { if (countdownRef.current) clearInterval(countdownRef.current); }; }, []); - - const minutes = Math.floor(countdown / 60); - const seconds = countdown % 60; - + + const expiryMinutes = Math.floor(otpExpiryCountdown / 60); + const expirySeconds = otpExpiryCountdown % 60; + + // Format time for display (MM:SS) + const formatTime = (time) => time < 10 ? `0${time}` : time; + return (
{/* Toast Notification */} @@ -297,7 +333,7 @@ const OTPVerification = () => {
)} - +
{/* Logo Section */} @@ -309,13 +345,13 @@ const OTPVerification = () => { IIP (Index of Industrial Production)
- +
{/*

Email OTP

Step 2 of 2

*/} - +
@@ -324,7 +360,7 @@ const OTPVerification = () => { We sent a 6-digit authentication code to {maskedEmail}

- + {/* OTP Inputs */}
@@ -341,8 +377,8 @@ const OTPVerification = () => { onKeyDown={(e) => handleKeyDown(e, index)} onPaste={handlePaste} className={`w-12 h-12 text-center text-xl bg-transparent border-2 rounded focus:outline-none focus:ring-0 ${ - error ? 'border-red-500' : - digit ? 'border-[#92722A]' : + error ? 'border-red-500' : + digit ? 'border-[#92722A]' : 'border-gray-300 hover:border-gray-400 focus:border-[#92722A] focus:border-b-2 focus:border-b-[#92722A]' }`} autoFocus={index === 0} @@ -350,25 +386,25 @@ const OTPVerification = () => { /> ))}
- + {error && (
{error}
)} - +
- Code expires in {minutes}:{seconds < 10 ? `0${seconds}` : seconds} + Code expires in {formatTime(expiryMinutes)}:{formatTime(expirySeconds)}
- Attempts: {attemptsRemaining} of 3 + Attempts used: {attemptsRemaining}/3
-
- +
+
- +
- +

Having trouble?{' '} -

); }; - -export default OTPVerification; \ No newline at end of file + +export default OTPVerification;