bug fixed in overview screen

This commit is contained in:
Malini 2025-11-05 09:07:27 +05:30
parent c257cf356c
commit 43c4d65a25
7 changed files with 422 additions and 316 deletions

View File

@ -17,6 +17,13 @@ export const DetailedOverview = ({ submission, onBack }) => {
const [submissionData, setSubmissionData] = useState(null);
const [quarterPeriods, setQuarterPeriods] = useState(null);
const [loadingProduct, setLoadingProduct] = useState(false);
const [showToast, setShowToast] = useState(false);
const [pagination, setPagination] = useState({
currentPage: 1,
pageSize: 10,
totalItems: 0,
pageSizeOptions: [10, 20, 50, 100]
});
// Add state for tracking loading and error states
const [isLoading, setIsLoading] = useState(true);
@ -71,6 +78,11 @@ export const DetailedOverview = ({ submission, onBack }) => {
return [];
}
// Log is_active status for each product
submissionData.products.forEach((product, index) => {
console.log(`Product ${index + 1} is_active status:`, product?.is_active);
});
const normalizedTerm = searchTerm.trim().toLowerCase();
const filtered = submissionData.products.filter((product) => {
// Add null checks for product and product.product
@ -84,7 +96,7 @@ export const DetailedOverview = ({ submission, onBack }) => {
!normalizedTerm ||
productName.toString().toLowerCase().includes(normalizedTerm) ||
hsCode.toString().toLowerCase().includes(normalizedTerm) ||
status.toLowerCase().includes(ormalizedTerm) ||
status.toLowerCase().includes(normalizedTerm) ||
'pending'.includes(normalizedTerm);
const matchesStatus = selectedStatus === '' ||
@ -94,6 +106,13 @@ export const DetailedOverview = ({ submission, onBack }) => {
return matchesSearch && matchesStatus;
});
// Update pagination total items and reset to first page when filters change
setPagination(prev => ({
...prev,
totalItems: filtered.length,
currentPage: 1 // Reset to first page when filters change
}));
console.log('Filtered products:', filtered);
return filtered;
}, [searchTerm, selectedStatus, submissionData]);
@ -133,9 +152,9 @@ export const DetailedOverview = ({ submission, onBack }) => {
product.current_cost || '0',
product.forecast_quantity || '0',
product.forecast_cost || '0',
product.is_active ? 'Approved' : 'Pending'
product.status || 'Pending'
].map((value) => `"${String(value).replace(/"/g, '""')}"`).join(','));
});
});
const blob = new Blob([csvRows.join('\n')], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
@ -149,19 +168,45 @@ export const DetailedOverview = ({ submission, onBack }) => {
}, [filteredProducts]);
const handleViewDetails = (product) => {
// Use the product data we already have from the submission
setSelectedProduct(product);
setShowToast(true);
};
const handleToastShown = () => {
setShowToast(false);
};
const handlePageChange = (newPage) => {
setPagination(prev => ({
...prev,
currentPage: newPage
}));
};
const handlePageSizeChange = (newSize) => {
setPagination(prev => ({
...prev,
pageSize: newSize,
currentPage: 1 // Reset to first page when changing page size
}));
};
// Apply pagination to filtered products
const paginatedProducts = useMemo(() => {
if (!filteredProducts || !Array.isArray(filteredProducts)) return [];
const startIndex = (pagination.currentPage - 1) * pagination.pageSize;
return filteredProducts.slice(startIndex, startIndex + pagination.pageSize);
}, [filteredProducts, pagination.currentPage, pagination.pageSize]);
const rows = useMemo(() => {
if (!filteredProducts || !Array.isArray(filteredProducts)) {
console.log('No filtered products available');
return [];
}
console.log('Generating rows for filtered products:', filteredProducts);
console.log('Generating rows for filtered products:', paginatedProducts);
return filteredProducts.map((product, index) => {
return paginatedProducts.map((product, index) => {
// Add null checks for product and product properties
const productData = product.product || {};
const unit = product.unit || {};
@ -179,7 +224,8 @@ export const DetailedOverview = ({ submission, onBack }) => {
'text-[#7C5E24] bg-[#F9F7ED]'
}`}
>
{product.is_active ? 'Approved' : 'Pending'}
{product.is_active ? 'Submitted' : 'Pending'}
{/* {product.is_active ? 'Rejected' : 'Rejected'} */}
</span>,
new Date(product.updated_at || product.created_at).toLocaleString('en-US', {
year: 'numeric',
@ -202,6 +248,16 @@ export const DetailedOverview = ({ submission, onBack }) => {
});
}, [filteredProducts]);
// Add pagination props to the Table component
const tablePagination = {
currentPage: pagination.currentPage,
onPageChange: handlePageChange,
pageSize: pagination.pageSize,
totalItems: filteredProducts.length,
pageSizeOptions: pagination.pageSizeOptions,
onPageSizeChange: handlePageSizeChange
};
// Show loading state for initial data load
if (isLoading) {
return (
@ -311,9 +367,12 @@ export const DetailedOverview = ({ submission, onBack }) => {
if (selectedProduct) {
return (
<ProductDetails
product={selectedProduct}
quarterPeriods={quarterPeriods}
product={selectedProduct}
onBack={() => setSelectedProduct(null)}
showToast={showToast}
onToastShown={handleToastShown}
submissionDate={submissionData?.created_at || submissionData?.updated_at || new Date().toISOString()}
// submissionStatus={'R'} // Static status for testing
/>
);
}
@ -385,6 +444,7 @@ export const DetailedOverview = ({ submission, onBack }) => {
separated
rowGapClass="border-spacing-y-2"
className="w-full border-collapse [&_td]:whitespace-nowrap [&_th]:whitespace-nowrap [&_th]:px-3 [&_td]:px-3 [&_th:last-child]:text-right [&_td:last-child]:text-right [&_th:last-child]:pr-4 [&_td:last-child]:pr-4"
pagination={tablePagination}
/>
</section>
</div>

View File

@ -1,32 +1,84 @@
import React from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
// Mock data for the quarterly details - replace with actual data from props
const quarterlyData = {
q4_oct_quantity: '1,000',
q4_nov_quantity: '1,200',
q4_dec_quantity: '1,500',
q1_jan_quantity: '1,300',
q1_feb_quantity: '1,400',
q1_mar_quantity: '1,600',
q2_apr_quantity: '1,700',
q2_may_quantity: '1,800',
q2_jun_quantity: '2,000',
q4_oct_cost: '10,000',
q4_nov_cost: '12,000',
q4_dec_cost: '15,000',
q1_jan_cost: '13,000',
q1_feb_cost: '14,000',
q1_mar_cost: '16,000',
q2_apr_cost: '17,000',
q2_may_cost: '18,000',
q2_jun_cost: '20,000',
variationReason: 'Seasonal demand increase',
zeroTargetReason: 'Expected market expansion',
remarks: 'Product shows steady growth with seasonal variations.'
};
const ProductDetails = ({ product, onBack }) => {
const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastShown, submissionDate, submissionStatus = 'approved' }) => {
const [toast, setToast] = useState(null);
const toastShownRef = useRef(false);
// Format date to 'DD-MM-YYYY, hh:mm A' format
const formatDate = (dateString) => {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true
});
};
useEffect(() => {
if (shouldShowToast && !toastShownRef.current) {
const formattedDate = formatDate(submissionDate);
if (product?.is_active) {
const status = String(product.is_active).toLowerCase();
console.log("statuschecking",status)
const toastConfig = {
approved: {
type: 'approved',
bgColor: 'bg-[#F3FAF4]',
textColor: 'text-[#3F8E50]',
iconColor: 'text-[#4A9D5C]',
message: 'Your survey was successfully submitted and is waiting for approval.'
},
rejected: {
type: 'rejected',
bgColor: 'bg-red-50',
textColor: 'text-red-700',
iconColor: 'text-red-500',
message: 'Your survey has been rejected. Please review and resubmit.'
},
submitted: {
type: 'submitted',
bgColor: 'bg-[#E7F5FF]',
textColor: 'text-[#043DFF]',
iconColor: 'text-[#286CFF]',
message: 'Your survey was successfully submitted and is waiting for approval.'
}
};
const toastType = status in toastConfig ? status : 'submitted';
setToast(toastConfig[toastType]);
} else {
// Default pending state (amber)
setToast({
type: 'pending',
message: 'Your survey is in draft status. Please complete and submit it for review.',
bgColor: 'bg-amber-50',
textColor: 'text-amber-700',
iconColor: 'text-amber-500'
});
}
toastShownRef.current = true;
onToastShown?.();
}
}, [shouldShowToast, product?.is_active, onToastShown, submissionDate, submissionStatus]);
const closeToast = () => {
setToast(null);
};
// Debug log to check product status and is_active
useEffect(() => {
console.log('Product Status:', product?.status);
console.log('Product is_active:', product?.is_active);
}, [product?.status, product?.is_active]);
const navigate = useNavigate();
if (!product) return null;
@ -58,14 +110,76 @@ const ProductDetails = ({ product, onBack }) => {
q2_jun_cost: product.forecast_cost_period_three || '—',
variationReason: product.variation_reason?.reason || '—',
zeroTargetReason: product.zero_target_reason?.reason || '—',
remarks: product.remarks || 'No remarks provided for this product.'
remarks: product.remarks || 'NA'
});
const formattedProduct = formatProductData(product);
return (
<div className="space-y-6 w-full">
<div className="bg-white p-4 rounded-t-lg border-b border-[#E2E8F0]">
<div className="w-full relative">
{/* Toast Notification */}
{toast && (
<div className="flex justify-center mb-2">
<div className={`w-full max-w-[1281px] h-[56px] ${toast.bgColor} ${toast.textColor} rounded-md px-4 py-3 flex items-center shadow-sm`}>
{toast.type === 'approved' ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className={`w-5 h-5 mr-2 ${toast.iconColor}`}
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clipRule="evenodd"
/>
</svg>
) : toast.type === 'submitted' ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={`w-5 h-5 mr-2 ${toast.iconColor}`}
>
<path
fillRule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.67-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z"
clipRule="evenodd"
/>
</svg>
) : toast.type === 'rejected' ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className={`w-5 h-5 mr-2 ${toast.iconColor}`}
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
clipRule="evenodd"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={`w-5 h-5 mr-2 ${toast.iconColor}`}
>
<path
fillRule="evenodd"
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z"
clipRule="evenodd"
/>
</svg>
)}
<span className="text-sm font-medium">{toast.message}</span>
</div>
</div>
)}
<div className="bg-white p-4 rounded-t-lg border-b border-[#E2E8F0] mt-2">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<button
@ -73,15 +187,15 @@ const ProductDetails = ({ product, onBack }) => {
className="text-[#6B7280] hover:text-[#374151] p-1 -ml-1"
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 15L7.5 10L12.5 5" stroke="#6B7280" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M15.8332 10H4.1665M4.1665 10L9.99984 15.8333M4.1665 10L9.99984 4.16667" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
<h1 className="text-lg font-medium text-[#232528]">Product Details - {formattedProduct.name}</h1>
<h2 className="text-lg font-medium text-gray-900">Product Details</h2>
</div>
</div>
</div>
<div className="bg-white rounded-lg border border-[#E2E8F0] p-6">
<div className="bg-white rounded-lg shadow-sm border border-[#E2E8F0] p-6">
{/* Basic Information */}
<div className="mb-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-4">
@ -114,7 +228,7 @@ const ProductDetails = ({ product, onBack }) => {
<div className="mt-1">
<span className={`inline-flex items-center rounded-md px-3 py-1.5 text-sm font-medium ${
formattedProduct.status === 'Active'
? 'bg-green-50 text-green-700 ring-1 ring-inset ring-green-600/20'
? 'bg-[#F3FAF4] text-[#2F663C] ring-1 ring-inset ring-green-600/20'
: 'bg-yellow-50 text-yellow-700 ring-1 ring-inset ring-yellow-600/20'
}`}>
{formattedProduct.status}
@ -225,13 +339,13 @@ const ProductDetails = ({ product, onBack }) => {
{/* Remarks */}
<div className="mt-6">
<h3 className="text-sm font-medium text-[#232528] mb-2">Remarks</h3>
<div className="border border-[#F3B340] rounded-md p-3 text-sm bg-white">
<div className="border border-[#CBA344] rounded-md p-3 text-sm bg-white">
{formattedProduct.remarks}
</div>
</div>
{/* Submission Info */}
<div className="mt-6 pt-4 border-t border-[#E2E8F0] text-sm">
{/* <div className="mt-6 pt-4 border-t border-[#E2E8F0] text-sm">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p className="text-[#6B7280]">Submitted by: <span className="text-[#232528] font-medium">
@ -254,7 +368,7 @@ const ProductDetails = ({ product, onBack }) => {
</p>
</div>
</div>
</div>
</div> */}
</div>
</div>
);

View File

@ -926,37 +926,25 @@ const ProductData = ({
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_one } Qty
</label>
<Input
className="w-24"
type="number"
placeholder="Enter"
value={p.octQuantity || ''}
onChange={(e) => updateProductField(p.id, 'octQuantity', e.target.value)}
/>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.octQuantity || '0'}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_two } Qty
</label>
<Input
className="w-24"
type="number"
placeholder="Enter"
value={p.novQuantity || ''}
onChange={(e) => updateProductField(p.id, 'novQuantity', e.target.value)}
/>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.novQuantity || '0'}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_three} Qty
</label>
<Input
className="w-24"
type="number"
placeholder="Enter"
value={p.decQuantity || ''}
onChange={(e) => updateProductField(p.id, 'decQuantity', e.target.value)}
/>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.decQuantity || '0'}
</div>
</div>
</div>
<div className="flex items-end space-x-4 mt-4">
@ -964,37 +952,25 @@ const ProductData = ({
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_one } Cost (AED)
</label>
<Input
className="w-24"
type="number"
placeholder="Enter"
value={p.octCost || ''}
onChange={(e) => updateProductField(p.id, 'octCost', e.target.value)}
/>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.octCost || '0'}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_two } Cost (AED)
</label>
<Input
className="w-24"
type="number"
placeholder="Enter"
value={p.novCost || ''}
onChange={(e) => updateProductField(p.id, 'novCost', e.target.value)}
/>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.novCost || '0'}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{quarterPeriods?.previous_month?.previous_period_three } Cost (AED)
</label>
<Input
className="w-24"
placeholder="Enter"
type="number"
value={p.decCost || ''}
onChange={(e) => updateProductField(p.id, 'decCost', e.target.value)}
/>
<div className="flex items-center h-10 px-3 py-2 text-sm text-gray-700 rounded-md w-24" style={{ border: '1px solid #E6D7A2' }}>
{p.decCost || '0'}
</div>
</div>
</div>
</div>
@ -1119,6 +1095,14 @@ const ProductData = ({
<p className="mt-1 text-xs text-red-600">{reasonsError}</p>
)}
</div>
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 mb-1">Remarks</label>
<Textarea
placeholder="Enter any additional remarks"
value={p.remarks || ''}
onChange={(e) => updateProductField(p.id, 'remarks', e.target.value)}
/>
</div>
</div>
</SectionBox>

View File

@ -399,6 +399,11 @@ const ReviewSubmit = ({
<span className="font-medium">Variation Reason:</span> {product.variationReason}
</p>
)}
{product.remarks && (
<p className="text-xs text-gray-600">
<span className="font-medium">Remarks:</span> {product.remarks}
</p>
)}
{product.zeroTargetReason && product.zeroTargetReason !== '—' && (
<p className="text-xs text-gray-600">
<span className="font-medium">Zero Target Reason:</span> {product.zeroTargetReason}

View File

@ -132,157 +132,6 @@ const createEmptyProfile = () => ({
createdById: '',
});
// const companyProfilesSeed = [
// {
// ...createEmptyProfile(),
// establishmentName: 'CASCADE MARINE FOODS LLC',
// emirate: 'Sharjah',
// isicCode: '2098',
// establishmentId: 'MSCensus-23-04-001276',
// products: '10',
// totalEmployees: '300',
// createdBy: 'Amend (Owner)',
// createdOn: '18/10/2025',
// lastUpdated: 'Omar',
// contactName: 'CASCADE MARINE FOODS LLC',
// contactEmirate: 'Sharjah',
// corporateName: 'CASCADE MARINE FOODS LLC',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Khazan Meat Factory',
// emirate: 'Sharjah',
// isicCode: '1098',
// establishmentId: 'MSCensus-23-05-002829',
// products: '6',
// totalEmployees: '100',
// createdBy: 'Sara (Admin)',
// createdOn: '18/10/2025',
// lastUpdated: 'Abdul',
// contactName: 'Khazan Meat Factory',
// contactEmirate: 'Sharjah',
// corporateName: 'Khazan Meat Factory',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Spinneys Fresh Food Industries L.L.C',
// emirate: 'Dubai',
// isicCode: '3198',
// establishmentId: 'MSCensus-Dubai-1407',
// products: '8',
// totalEmployees: '40',
// createdBy: 'Shaik (Admin)',
// createdOn: '18/10/2025',
// lastUpdated: 'Abdul',
// contactName: 'Spinneys Fresh Food Industries L.L.C',
// contactEmirate: 'Dubai',
// corporateName: 'Spinneys Fresh Food Industries L.L.C',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Freshly Frozen Foods Factory L.L.C',
// emirate: 'Dubai',
// isicCode: '2098',
// establishmentId: 'MSCensus-Dubai-906',
// products: '4',
// totalEmployees: '50',
// createdBy: 'Abdul (Manager)',
// createdOn: '18/10/2025',
// lastUpdated: 'Abdul',
// contactName: 'Freshly Frozen Foods Factory L.L.C',
// contactEmirate: 'Dubai',
// corporateName: 'Freshly Frozen Foods Factory L.L.C',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Sahar Food Industry (L.L.C)',
// emirate: 'Dubai',
// isicCode: '1095',
// establishmentId: 'MSCensus-Dubai-472',
// products: '8',
// totalEmployees: '80',
// createdBy: 'Amend (Owner)',
// createdOn: '18/10/2025',
// lastUpdated: 'Abdul',
// contactName: 'Sahar Food Industry (L.L.C)',
// contactEmirate: 'Dubai',
// corporateName: 'Sahar Food Industry (L.L.C)',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Continental Food Processing LLC',
// emirate: 'Ajman',
// isicCode: '0035',
// establishmentId: 'MSCensus-23-06-005496',
// products: '5',
// totalEmployees: '400',
// createdBy: 'Sara (Admin)',
// createdOn: '18/10/2025',
// lastUpdated: 'Abdul',
// contactName: 'Continental Food Processing LLC',
// contactEmirate: 'Ajman',
// corporateName: 'Continental Food Processing LLC',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Al Khazna Poultry Farm',
// emirate: 'Abu Dhabi',
// isicCode: '0986',
// establishmentId: 'MSCensus-23-05-002320',
// products: '7',
// totalEmployees: '800',
// createdBy: 'Sara (Admin)',
// createdOn: '18/10/2025',
// lastUpdated: 'Fathima',
// contactName: 'Al Khazna Poultry Farm',
// contactEmirate: 'Abu Dhabi',
// corporateName: 'Al Khazna Poultry Farm',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Diamond Meat Processing L.L.C',
// emirate: 'Abu Dhabi',
// isicCode: '8766',
// establishmentId: 'MSCensus-23-05-002899',
// products: '7',
// totalEmployees: '300',
// createdBy: 'Sara (Admin)',
// createdOn: '18/10/2025',
// lastUpdated: 'Abdul',
// contactName: 'Diamond Meat Processing L.L.C',
// contactEmirate: 'Abu Dhabi',
// corporateName: 'Diamond Meat Processing L.L.C',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Alliance Foods Co L.L.C',
// emirate: 'Abu Dhabi',
// isicCode: '9883',
// establishmentId: 'MSCensus-Dubai-128',
// products: '7',
// totalEmployees: '200',
// createdBy: 'Amend (Owner)',
// createdOn: '18/10/2025',
// lastUpdated: 'Abdul',
// contactName: 'Alliance Foods Co L.L.C',
// contactEmirate: 'Abu Dhabi',
// corporateName: 'Alliance Foods Co L.L.C',
// },
// {
// ...createEmptyProfile(),
// establishmentName: 'Krustasia Foods L.L.C',
// emirate: 'Dubai',
// isicCode: '9986',
// establishmentId: 'MSCensus-23-05-002320',
// products: '8',
// totalEmployees: '500',
// createdBy: 'Sara (Admin)',
// createdOn: '18/10/2025',
// lastUpdated: 'Fathima',
// contactName: 'Krustasia Foods L.L.C',
// contactEmirate: 'Dubai',
// },
// ];
const CompanyProfile = () => {
const navigate = useNavigate();

View File

@ -5,6 +5,7 @@ import HeaderBar from '@/components/layout/HeaderBar';
import { fetchSubmissionHistory } from '@/services/submissions/submissionService.js';
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const BreadcrumbVector = '/assets/images/Breadcrumb-vector.svg';
const caretDownSrc = '/assets/images/CaretDown.svg';
const Badge = ({ variant = 'default', children }) => {
@ -28,6 +29,13 @@ const History = () => {
const [error, setError] = React.useState('');
const [searchTerm, setSearchTerm] = React.useState('');
const [statusFilter, setStatusFilter] = React.useState('all');
const [yearFilter, setYearFilter] = React.useState('');
const [quarterFilter, setQuarterFilter] = React.useState('');
const [pagination, setPagination] = React.useState({
currentPage: 1,
pageSize: 10,
totalItems: 0,
});
const navigate = useNavigate();
const toVariant = React.useCallback((status) => {
@ -126,28 +134,68 @@ const History = () => {
navigate('/survey', { state: { submission, initialStep: 3 } });
}, [navigate]);
// Get unique years from the data
const yearOptions = React.useMemo(() => {
const years = [...new Set(rows.map(entry => entry.year).filter(Boolean))].sort((a, b) => b - a);
return years;
}, [rows]);
const quarterOptions = [
{ label: 'Quarter', value: '' },
{ label: 'Q1', value: 'Q1' },
{ label: 'Q2', value: 'Q2' },
{ label: 'Q3', value: 'Q3' },
{ label: 'Q4', value: 'Q4' }
];
const filteredRows = React.useMemo(() => {
const term = searchTerm.trim().toLowerCase();
const statusValue = statusFilter.toLowerCase();
return rows.filter((entry) => {
const filtered = rows.filter((entry) => {
// Status filter
const matchesStatus =
statusValue === 'all' || toVariant(entry.status) === statusValue || formatStatus(entry.status).toLowerCase() === statusValue;
if (!term) return matchesStatus;
const haystack = [
entry.year,
entry.quarter,
entry.submission_window,
entry.total_products,
entry.products_submitted,
entry.total_cost,
formatStatus(entry.status),
formatDate(entry.created_at),
]
.map((val) => String(val || '').toLowerCase())
.join(' ');
return matchesStatus && haystack.includes(term);
statusValue === 'all' ||
toVariant(entry.status) === statusValue ||
formatStatus(entry.status).toLowerCase() === statusValue;
// Year filter
const matchesYear = !yearFilter || String(entry.year) === yearFilter;
// Quarter filter
const matchesQuarter = !quarterFilter ||
(entry.quarter && entry.quarter.toUpperCase() === quarterFilter.toUpperCase());
// Search term filter
if (term) {
const haystack = [
entry.year,
entry.quarter,
entry.submission_window,
entry.total_products,
entry.products_submitted,
entry.total_cost,
formatStatus(entry.status),
formatDate(entry.created_at),
]
.map((val) => String(val || '').toLowerCase())
.join(' ');
return matchesStatus && matchesYear && matchesQuarter && haystack.includes(term);
}
return matchesStatus && matchesYear && matchesQuarter;
});
}, [rows, searchTerm, statusFilter, toVariant, formatStatus, formatDate]);
// Update total items when filtered rows change
setPagination(prev => ({
...prev,
totalItems: filtered.length,
currentPage: 1, // Reset to first page when filter changes
}));
return filtered;
}, [rows, searchTerm, statusFilter, yearFilter, quarterFilter, toVariant, formatStatus, formatDate]);
const formatNumber = (num) => {
if (num === null || num === undefined) return '—';
@ -161,6 +209,21 @@ const History = () => {
};
// 🟩 Updated tableRows with expandable details
const handlePageChange = (newPage) => {
setPagination(prev => ({
...prev,
currentPage: newPage,
}));
};
const handlePageSizeChange = (newSize) => {
setPagination(prev => ({
...prev,
pageSize: newSize,
currentPage: 1, // Reset to first page when page size changes
}));
};
const tableRows = filteredRows.map((entry, index) => {
const isExpanded = expandedRow === index;
return [
@ -302,9 +365,22 @@ const History = () => {
return (
<div className="min-h-screen bg-[#F7F7F7]">
<HeaderBar />
<div className="mx-auto max-w-7xl px-6 py-10">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-2 mt-3">
<nav className="flex items-center text-sm text-gray-500 space-x-2">
<a href="/" className="hover:text-[#92722A] transition-colors flex items-center gap-1">
Dashboard
<img src={BreadcrumbVector} alt="" className="h-3 w-3 ml-3" />
</a>
<span className="hover:text-[#92722A] transition-colors flex items-center ml-2">
History
<img src={BreadcrumbVector} alt="" className="h-3 w-3 ml-3" />
</span>
<span className="text-[#92722A] ml-2">Submission History</span>
</nav>
</div>
<div className="mx-auto max-w-7xl px-6 py-2 ">
{/* Card Section */}
<div className="bg-white p-4 rounded-t-lg border-b border-[#E2E8F0] mb-6">
<div className="bg-white p-4 rounded-t-lg border-b border-[#E2E8F0] mb-6 mt-1">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2 pr-6">
@ -319,13 +395,14 @@ const History = () => {
<div className="flex items-center space-x-4">
<div className="relative">
<select
value={yearFilter}
onChange={(e) => setYearFilter(e.target.value)}
className="h-10 w-32 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
>
<option value="">Select Year</option>
<option value="2024">2024</option>
<option value="2023">2023</option>
<option value="2022">2022</option>
<option value="2021">2021</option>
{yearOptions.map(year => (
<option key={year} value={year}>{year}</option>
))}
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#6B7280]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
@ -333,13 +410,15 @@ const History = () => {
</div>
<div className="relative">
<select
value={quarterFilter}
onChange={(e) => setQuarterFilter(e.target.value)}
className="h-10 w-32 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
>
<option value="">Select Quarter</option>
<option value="Q1">Q1</option>
<option value="Q2">Q2</option>
<option value="Q3">Q3</option>
<option value="Q4">Q4</option>
{quarterOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#6B7280]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
@ -360,10 +439,9 @@ const History = () => {
<div className="relative">
<select
className="h-10 w-32 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
disabled
>
<option value="">Select Product</option>
<option value="product1">Product 1</option>
<option value="product2">Product 2</option>
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#6B7280]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
@ -372,10 +450,9 @@ const History = () => {
<div className="relative">
<select
className="h-10 w-32 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
disabled
>
<option value="">Select Actor</option>
<option value="actor1">Actor 1</option>
<option value="actor2">Actor 2</option>
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#6B7280]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
@ -398,11 +475,21 @@ const History = () => {
</div>
</div>
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-gray-200">
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-gray-200 mt-1">
<Table
headers={headers}
rows={tableRows}
beforeHeader={toolbar}
loading={loading}
error={error}
className="mt-4"
pagination={{
currentPage: pagination.currentPage,
onPageChange: handlePageChange,
pageSize: pagination.pageSize,
totalItems: pagination.totalItems,
pageSizeOptions: [10, 25, 50, 100],
onPageSizeChange: handlePageSizeChange,
}}
renderCell={(value, ri, ci) => {
if (ci === 3) {
return <Badge variant={toVariant(filteredRows[ri]?.status)}>{value}</Badge>;

View File

@ -29,13 +29,11 @@ const Overview = () => {
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10; // Number of items per page
const [pagination, setPagination] = useState({
page: 1,
limit: 100,
total: 0,
totalPages: 1
currentPage: 1,
pageSize: 10,
totalItems: 0,
pageSizeOptions: [10, 20, 50, 100]
});
const navigate = useNavigate();
@ -43,9 +41,8 @@ const Overview = () => {
const fetchSubmissions = async () => {
try {
setLoading(true);
const response = await getSubmissions(pagination.page, pagination.limit);
const response = await getSubmissions(1, 1000); // Get all records
setSubmissions(response.data);
setPagination(response.pagination);
} catch (err) {
setError('Failed to load submissions');
console.error('Error:', err);
@ -55,11 +52,11 @@ const Overview = () => {
};
fetchSubmissions();
}, [pagination.page, pagination.limit]);
}, []);
const filteredRows = React.useMemo(() => {
const normalizedTerm = searchTerm.trim().toLowerCase();
return submissions.filter((record) => {
const filtered = submissions.filter((record) => {
const matchesSearch =
!normalizedTerm ||
String(record.year).toLowerCase().includes(normalizedTerm) ||
@ -76,29 +73,45 @@ const Overview = () => {
return matchesSearch && matchesStatus;
});
// Update total items in pagination
setPagination(prev => ({
...prev,
totalItems: filtered.length,
currentPage: 1 // Reset to first page when filters change
}));
return filtered;
}, [searchTerm, selectedStatus, submissions]);
// Calculate paginated data
const paginatedRows = useMemo(() => {
if (!filteredRows.length) return [];
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return filteredRows.slice(startIndex, endIndex);
}, [filteredRows, currentPage, pageSize]);
const rows = useMemo(() => {
return paginatedRows.map((record) => [
record.year,
record.quarter,
`${record.quarter} ${record.year}`,
10, // Always show 10 for Total Products
record.product_count || 0, // Show actual submitted products count
`-`, // Total cost not in API
return filteredRows.map((record) => [
record.year,
record.quarter,
`${record.quarter} ${record.year}`,
10, // Always show 10 for Total Products
record.product_count || 0, // Show actual submitted products count
`-`, // Total cost not in API
record.status,
formatDate(record.created_at),
'View Details',
]);
}, [paginatedRows]);
}, [filteredRows]);
const handlePageChange = (newPage) => {
setPagination(prev => ({
...prev,
currentPage: newPage
}));
};
const handlePageSizeChange = (newSize) => {
setPagination(prev => ({
...prev,
pageSize: newSize,
currentPage: 1 // Reset to first page when changing page size
}));
};
const downloadCsv = React.useCallback(() => {
if (!filteredRows.length) return;
@ -185,15 +198,8 @@ const Overview = () => {
);
const handleViewDetails = (rowIndex) => {
// Calculate the actual index in the filtered array
const actualIndex = (currentPage - 1) * pageSize + rowIndex;
setSelectedSubmission(filteredRows[actualIndex]);
setSelectedSubmission(filteredRows[rowIndex]);
};
// Reset to first page when search or filter changes
useEffect(() => {
setCurrentPage(1);
}, [searchTerm, selectedStatus]);
if (loading) {
return (
@ -258,13 +264,6 @@ const Overview = () => {
beforeHeader={toolbar}
separated
rowGapClass="border-spacing-y-2"
pagination={{
currentPage,
onPageChange: setCurrentPage,
pageSize,
totalItems: filteredRows.length,
pageSizeOptions: [10, 25, 50, 100]
}}
className="w-full min-w-[1200px] [&_td]:whitespace-nowrap [&_th]:whitespace-nowrap"
renderCell={(value, rowIndex, columnIndex) => {
if (columnIndex === 6) { // Status column
@ -278,8 +277,8 @@ const Overview = () => {
}
if (columnIndex === 8) { // Action column
return (
<button
className="text-[#92722A] hover:underline text-sm font-medium"
<button
className="text-[#92722A] hover:underline text-sm font-medium focus:outline-none"
onClick={() => handleViewDetails(rowIndex)}
>
{value}
@ -288,6 +287,14 @@ const Overview = () => {
}
return value;
}}
pagination={{
currentPage: pagination.currentPage,
onPageChange: handlePageChange,
pageSize: pagination.pageSize,
totalItems: pagination.totalItems,
pageSizeOptions: pagination.pageSizeOptions,
onPageSizeChange: handlePageSizeChange,
}}
/>
</section>
</main>