bug fixed

This commit is contained in:
Malini 2025-11-10 14:37:47 +05:30
parent ebd0e4c4f5
commit 541e5a12a0
5 changed files with 250 additions and 66 deletions

View File

@ -14,18 +14,18 @@ const Footer = () => {
{/* About FCSC */}
<div>
<h3 className="text-[#92722A] font-semibold mb-3 text-lg">About FCSC</h3>
<ul className="space-y-1 text-sm">
<li><a href="#" className="hover:underline">The Federal Competitiveness and Statistics Centre (FCSC) provides reliable data and insights to enhance the UAEs competitiveness and support sustainable national development.</a></li>
</ul>
<p className="text-sm">
The Federal Competitiveness and Statistics Centre (FCSC) provides reliable data and insights to enhance the UAE's competitiveness and support sustainable national development.
</p>
</div>
{/* Our Policies */}
<div>
<h3 className="text-[#92722A] font-semibold mb-3 ml-16 text-lg">Our Policies</h3>
<ul className="space-y-1 ml-16 text-sm">
<li><a href="#" className="hover:underline">Disclaimer</a></li>
<li><a href="#" className="hover:underline">Privacy policy</a></li>
<li><a href="#" className="hover:underline">Terms and conditions</a></li>
<li>Disclaimer</li>
<li>Privacy policy</li>
<li>Terms and conditions</li>
</ul>
</div>
@ -33,9 +33,9 @@ const Footer = () => {
<div>
<h3 className="text-[#92722A] font-semibold mb-3 ml-1 text-lg">Information and Support</h3>
<ul className="space-y-1 ml-1 text-sm">
<li><a href="#" className="hover:underline">Contact us</a></li>
<li><a href="#" className="hover:underline">FAQs</a></li>
<li><a href="#" className="hover:underline">Feedback and complaints</a></li>
<li>Contact us</li>
<li>FAQs</li>
<li>Feedback and complaints</li>
</ul>
</div>

View File

@ -84,12 +84,12 @@ const HeaderBar = () => {
className={({ isActive, isPending }) => `inline-flex items-center gap-2 pb-1 border-b-2 ${
isActive
? 'text-[#92722A] font-medium border-[#92722A]'
: window.location.pathname === '/dashboard'
: (window.location.pathname === '/dashboard' || window.location.pathname === '/overview')
? 'text-gray-400 cursor-not-allowed'
: 'text-[#232528] hover:text-gray-900 border-transparent'
}`}
onClick={(e) => {
if (window.location.pathname === '/dashboard') {
if (window.location.pathname === '/dashboard' || window.location.pathname === '/overview') {
e.preventDefault();
}
}}
@ -99,7 +99,7 @@ const HeaderBar = () => {
<img
src={isActive ? surveyIconActiveSrc : surveyIconInactiveSrc}
alt="Survey"
className={`h-[18px] w-[18px] ${window.location.pathname === '/dashboard' ? 'opacity-50' : ''}`}
className={`h-[18px] w-[18px] ${(window.location.pathname === '/dashboard' || window.location.pathname === '/overview') ? 'opacity-50' : ''}`}
/>
<span>Survey</span>
</>

View File

@ -8,6 +8,7 @@ import {
fetchProducts,
fetchUnits,
} from '@/services/masters/masterService.js';
import { fetchEstablishmentDetail } from '@/services/establishments/establishmentService';
import { useLocation } from 'react-router-dom';
const caretDownSrc = '/assets/images/CaretDown-black.svg';
@ -446,6 +447,54 @@ const ProductData = ({
};
}, []);
// Fetch establishment details and products on component mount
useEffect(() => {
const fetchEstablishmentProducts = async () => {
try {
const establishmentId = sessionStorage.getItem('establishment_id');
if (!establishmentId) {
console.error('Establishment ID not found in session storage');
return;
}
const response = await fetchEstablishmentDetail(establishmentId);
if (response && response.data && Array.isArray(response.data.establishment_products)) {
// Transform products data to match the expected format
const formattedProducts = response.data.establishment_products.map(item => {
const product = item.product || {};
return {
value: product.hs_code || '',
label: product.product_name ?
`${product.product_name} (${product.hs_code || 'N/A'})` :
'Unknown Product',
productId: item.product_id, // Include the actual product ID
productData: product // Include the full product data
};
});
// Update the product options state
setProductOptions(formattedProducts);
// If you need to update the form with initial products, uncomment and modify:
// if (formattedProducts.length > 0) {
// const initialProducts = formattedProducts.map((product, index) => ({
// id: index + 1,
// product: product.value,
// unit: '',
// // ... other default values
// }));
// onProductsChange(initialProducts);
// }
}
} catch (err) {
console.error('Error fetching establishment products:', err);
setError('Failed to load establishment products');
}
};
fetchEstablishmentProducts();
}, []);
React.useEffect(() => {
const maxId = products.reduce((max, item) => (item && typeof item.id === 'number' ? Math.max(max, item.id) : max), 0);
if (maxId >= nextId.current) {
@ -996,7 +1045,8 @@ const location = useLocation();
...p,
product: selectedProduct?.value || selectedValue,
productName: selectedProduct?.label || '',
productId: selectedProduct?.value || selectedValue
productId: selectedProduct?.productId || selectedValue, // Use the actual product ID
product_id: selectedProduct?.productId || selectedValue // Add product_id for submission
};
// Update the products array with the updated product
const updatedProducts = products.map(prod =>

View File

@ -79,6 +79,8 @@ const ManageSubmissions = () => {
const [rejectReason, setRejectReason] = React.useState('');
const [showRejectError, setShowRejectError] = React.useState(false);
const pageSize = 10;
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
const toastTimeoutRef = React.useRef(null);
@ -115,6 +117,8 @@ const ManageSubmissions = () => {
const handleRejectClick = (submission) => {
setSelectedSubmission(submission);
setRejectReason('');
setShowRejectError(false);
setShowRejectModal(true);
};
@ -122,6 +126,7 @@ const ManageSubmissions = () => {
setShowRejectModal(false);
setSelectedSubmission(null);
setRejectReason('');
setShowRejectError(false);
};
const handleApproveConfirm = async () => {
@ -169,10 +174,14 @@ const ManageSubmissions = () => {
};
const handleRejectConfirm = async () => {
if (!selectedSubmission || isRejecting || !rejectReason.trim()) {
if (!rejectReason.trim()) {
showToast('error', 'Please provide a reason for rejection');
}
if (!selectedSubmission || isRejecting) {
return;
}
// Validate the rejection reason
if (!rejectReason.trim()) {
setShowRejectError(true);
showToast('error', 'Please provide a reason for rejection');
return;
}
@ -186,7 +195,7 @@ const ManageSubmissions = () => {
const ok = (response && response.data) || response?.status === 200 || response?.status === 'success';
if (ok) {
showToast('success', 'Submission has been rejected');
showToast('error', 'Submission has been rejected');
// Refresh the submissions list
const data = await fetchDashboardData(selectedQuarter, selectedYear);
const userProfile = JSON.parse(sessionStorage.getItem("user_profile"));
@ -201,7 +210,7 @@ const ManageSubmissions = () => {
submittedAt: formatDateTime(item.created_at),
status: item.status ?? '-',
reviewer: userProfile?.name || '-',
reviewerOn: formatDateTime(item.updated_at || item.created_at),
reviewerOn: formatDateTime(item.created_at),
}));
setSubmissions(mapped);
setRejectReason('');
@ -226,6 +235,45 @@ const ManageSubmissions = () => {
};
}, []);
// Fetch data when component mounts or when quarter/year changes
React.useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
const data = await fetchDashboardData(selectedQuarter, selectedYear);
const userProfile = JSON.parse(sessionStorage.getItem("user_profile"));
const mapped = (data || []).map((item) => ({
id: item.id,
establishment: item?.establishment?.factory_name || '-',
emirate: item?.establishment?.establishment_emirate?.name || '-',
year: String(item.year ?? '-'),
quarter: item.quarter ?? '-',
products: item?.product_count || '-',
submittedBy: userProfile?.name || '-',
submittedAt: formatDateTime(item.created_at),
status: item.status ?? '-',
reviewer: userProfile?.name || '-',
reviewerOn: formatDateTime(item.updated_at || item.created_at),
}));
setSubmissions(mapped);
setError(null);
} catch (err) {
console.error('Failed to load submissions', err);
setError('Unable to load submissions. Please try again later.');
setSubmissions([]);
} finally {
setLoading(false);
setIsInitialLoad(false);
}
};
// Only load data if we have valid quarter and year
if (selectedQuarter && selectedYear) {
loadData();
}
}, [selectedQuarter, selectedYear]);
React.useEffect(() => {
if (location.state?.showSuccessToast && location.state?.successMessage) {
showToast('success', location.state.successMessage);
@ -233,47 +281,49 @@ const ManageSubmissions = () => {
}
}, [location.state, showToast]);
const fetchDashboardData = async (quarter, year) => {
const fetchDashboardData = async (quarter, year, page = 1, limit = 10) => {
try {
// Add quarter and year to the API call to filter submissions
const params = new URLSearchParams();
if (quarter && quarter !== 'All') params.append('quarter', quarter);
if (year && year !== 'All') params.append('year', year);
const params = {
quarter: quarter && quarter !== 'All' ? quarter : undefined,
year: year && year !== 'All' ? year : undefined,
page,
limit
};
const result = await getSubmissions(params.toString());
return result?.status === 'success' ? result.data : [];
console.log('Fetching with params:', params);
const result = await getSubmissions(params);
console.log('API Response:', result);
// Check if result has a data property or is the data array itself
const data = Array.isArray(result) ? result : (result?.data || []);
console.log('Processed data:', data);
return data;
} catch (error) {
console.error('Error fetching dashboard data:', error);
return [];
}
};
// Fetch current quarter and year from admin_dashboard endpoint
// Fetch current quarter and year from admin_dashboard endpoint and initial data
React.useEffect(() => {
const fetchCurrentQuarterAndYear = async () => {
const fetchInitialData = async () => {
try {
setLoading(true);
// First, get the current quarter and year
const response = await apiClient.get('/admin_dashboard');
const currentQuarter = response?.data?.selected_quarter;
const currentQuarter = response?.data?.selected_quarter || 'Q1';
const currentYear = response?.data?.selected_year || new Date().getFullYear().toString();
console.log("currentQuarter",currentQuarter)
console.log("currentYear",currentYear)
// Set the filter states
setSelectedQuarter(currentQuarter);
setSelectedYear(currentYear);
setQuarter(currentQuarter);
setYear(currentYear);
} catch (error) {
console.error('Error fetching current quarter and year:', error);
}
};
fetchCurrentQuarterAndYear();
}, []);
React.useEffect(() => {
const fetchSubmissions = async () => {
try {
const data = await fetchDashboardData(selectedQuarter, selectedYear);
const userProfile = JSON.parse(sessionStorage.getItem("user_profile"));
// Then fetch the submissions with the current quarter and year
const data = await fetchDashboardData(currentQuarter, currentYear);
const userProfile = JSON.parse(sessionStorage.getItem("user_profile") || '{}');
const mapped = (data || []).map((item) => ({
id: item.id,
establishment: item?.establishment?.factory_name || '-',
@ -281,25 +331,26 @@ const ManageSubmissions = () => {
year: String(item.year ?? '-'),
quarter: item.quarter ?? '-',
products: item?.product_count || '-',
// submittedBy: item.created_by ?? '-',
submittedBy: userProfile?.name || '-',
submittedAt: formatDateTime(item.created_at),
status: item.status ?? '-',
// reviewer: item?.updated_by || '-',
status: item.status || 'Pending',
reviewer: userProfile?.name || '-',
// reviewerOn: formatDateTime(item?.updated_at),
reviewerOn: formatDateTime(item.created_at),
reviewerOn: formatDateTime(item.updated_at || item.created_at),
}));
setSubmissions(mapped);
setError(null);
} catch (err) {
console.error('Failed to load submissions', err);
console.error('Failed to load initial data:', err);
setError('Unable to load submissions. Please try again later.');
setSubmissions([]);
} finally {
setLoading(false);
setIsInitialLoad(false);
}
};
fetchSubmissions();
fetchInitialData();
}, []);
const yearOptions = React.useMemo(() => {
@ -324,21 +375,35 @@ const ManageSubmissions = () => {
}, [submissions]);
const filtered = React.useMemo(() => {
console.log('Current submissions:', submissions);
const term = search.trim().toLowerCase();
return submissions.filter((item) => {
const filteredItems = submissions.filter((item) => {
if (!item) return false;
const allValues = Object.values(item)
.map((v) => String(v ?? '').toLowerCase())
.join(' ');
const matchSearch = !term || allValues.includes(term);
const matchYear = !year || year === 'All' || item.year === year;
const matchYear = !year || year === 'All' || String(item.year) === String(year);
const matchQuarter = !quarter || quarter === 'All' || item.quarter === quarter;
const matchEmirate = !emirate || emirate === 'All' || item.emirate === emirate;
const matchStatus = !status || status === 'All' || item.status === status;
return matchSearch && matchYear && matchQuarter && matchEmirate && matchStatus;
const matches = matchSearch && matchYear && matchQuarter && matchEmirate && matchStatus;
if (matches) {
console.log('Matching item:', item);
} else {
console.log('Filtered out item:', item, { matchSearch, matchYear, matchQuarter, matchEmirate, matchStatus });
}
return matches;
});
console.log('Filtered items count:', filteredItems.length);
return filteredItems;
}, [submissions, search, year, quarter, emirate, status]);
const summaryCounts = React.useMemo(
@ -426,7 +491,7 @@ const ManageSubmissions = () => {
</div>
);
if (loading)
if (loading && isInitialLoad){
return (
<div className="flex items-center justify-center h-screen text-[#92722A]">
<svg className="animate-spin h-6 w-6 mr-3 text-[#92722A]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
@ -436,6 +501,7 @@ const ManageSubmissions = () => {
Loading submissions...
</div>
);
}
if (error) return <div className="flex items-center justify-center h-screen text-red-600">{error}</div>;
const downloadCsv = (data) => {
@ -506,6 +572,8 @@ const ManageSubmissions = () => {
className={`text-sm rounded px-3 py-2 border flex items-start justify-between gap-3 shadow ${
toast.type === 'success'
? 'bg-[#F3FAF4] border-[#C6E7D8] text-[#2F663C]'
: toast.type === 'reject'
? 'bg-[#FEF2F2] border-[#FECACA] text-[#B91C1C]'
: 'bg-[#FEF2F2] border-[#FECACA] text-[#B91C1C]'
}`}
>
@ -528,7 +596,7 @@ const ManageSubmissions = () => {
<Link to="/admin/dashboard" className="flex items-center gap-2 text-[#232528] hover:underline">
Dashboard <img src={caretUpSrc} alt="Dashboard" className="h-3 w-3" />
</Link>
<span className="text-[#92722A] font-medium">Manage Submissions</span>
<span className="text-[#7A7F87] font-medium">Manage Submissions</span>
</nav>
<div className="flex flex-wrap gap-[6px] rounded-[12px] bg-white px-5 py-3 shadow items-center justify-between">
@ -741,14 +809,24 @@ const ManageSubmissions = () => {
<textarea
id="rejectReason"
rows={4}
className={`w-full px-3 py-2 border ${!rejectReason.trim() && showRejectError ? 'border-red-500' : 'border-gray-300'} rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#92722A] focus:border-[#92722A] text-sm`}
className={`w-full px-3 py-2 border ${showRejectError && !rejectReason.trim() ? 'border-red-500' : 'border-gray-300'} rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[#92722A] focus:border-[#92722A] text-sm`}
placeholder="Please provide a reason for rejection"
value={rejectReason}
onChange={(e) => setRejectReason(e.target.value)}
onBlur={() => setShowRejectError(!rejectReason.trim())}
onChange={(e) => {
setRejectReason(e.target.value);
// Clear error when user starts typing
if (e.target.value.trim() && showRejectError) {
setShowRejectError(false);
}
}}
onBlur={() => {
if (!rejectReason.trim()) {
setShowRejectError(true);
}
}}
disabled={isRejecting}
/>
{!rejectReason.trim() && showRejectError && (
{showRejectError && !rejectReason.trim() && (
<p className="mt-1 text-sm text-red-600">Reason for rejection is required</p>
)}
</div>

View File

@ -1,9 +1,65 @@
import apiClient from '@/services/api/apiClient';
/** Fetch all submissions for the listing page */
export const getSubmissions = async () => {
const response = await apiClient.get('/submissions');
return response.data;
/**
* Fetch submissions with pagination and filtering
* @param {Object} params - Query parameters
* @param {string} [params.quarter] - Filter by quarter (Q1, Q2, Q3, Q4)
* @param {string} [params.year] - Filter by year
* @param {number} [params.page=1] - Page number for pagination
* @param {number} [params.limit=10] - Number of items per page
* @returns {Promise<Object>} - Response data
*/
export const getSubmissions = async (params = {}) => {
try {
// Set default values
const {
quarter = '',
year = '',
page = 1,
limit = 100,
...restParams
} = params;
console.log('getSubmissions called with params:', { quarter, year, page, limit, ...restParams });
// Build query string
const queryParams = new URLSearchParams();
// Add pagination
queryParams.append('page', page);
queryParams.append('limit', limit);
// Add filters if provided
if (quarter) queryParams.append('quarter', quarter);
if (year) queryParams.append('year', year);
// Add any additional parameters
Object.entries(restParams).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.append(key, value);
}
});
const url = `/submissions?${queryParams.toString()}`;
console.log('Making API request to:', url);
const response = await apiClient.get(url);
console.log('API response received:', response);
// Check if response.data is an array or has a data property
const responseData = Array.isArray(response.data) ? response.data : response.data?.data;
console.log('Processed response data:', responseData);
return responseData || [];
} catch (error) {
console.error('Error in getSubmissions:', error);
console.error('Error details:', {
message: error.message,
response: error.response?.data,
status: error.response?.status
});
throw error;
}
};
/** 🔹 Fetch a single submission by ID */