bug fixed
This commit is contained in:
parent
ebd0e4c4f5
commit
541e5a12a0
@ -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 UAE’s 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">FAQ’s</a></li>
|
||||
<li><a href="#" className="hover:underline">Feedback and complaints</a></li>
|
||||
<li>Contact us</li>
|
||||
<li>FAQ’s</li>
|
||||
<li>Feedback and complaints</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
|
||||
@ -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 =>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user