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)
-
+
-
+
Having trouble?{' '}
-
);
};
-
-export default OTPVerification;
\ No newline at end of file
+
+export default OTPVerification;