Added Overview screen in Establishement users

This commit is contained in:
Malini 2025-10-31 09:54:39 +05:30
parent 02da3fedb0
commit 95528cb84c
8 changed files with 695 additions and 252 deletions

View File

@ -1,80 +1,133 @@
import React, { useState, useCallback, useMemo } from 'react';
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { fetchSubmissionDetail } from '@/services/submissions/submissionService';
import Table from '@/components/common/Table';
import ProductDetails from './ProductDetails';
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const caretDownSrc = '/assets/images/CaretDown.svg';
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
export const DetailedOverview = ({ submission, onBack }) => {
const navigate = useNavigate();
const [searchTerm, setSearchTerm] = useState('');
const [selectedStatus, setSelectedStatus] = useState('');
const [selectedProduct, setSelectedProduct] = useState(null);
const [loading, setLoading] = useState(true);
const [submissionData, setSubmissionData] = useState(null);
const [quarterPeriods, setQuarterPeriods] = useState(null);
const [loadingProduct, setLoadingProduct] = useState(false);
// Mock data - replace with actual data from props
const productData = [
{
id: 'PRD-2023-001',
hsCode: '1234.56.78',
name: 'Product 1',
unit: 'PCS',
quantity: 1,
cost: '1,000.00',
status: 'Active',
submissionDate: '30/10/2023 14:30',
},
{
id: 'PRD-2023-002',
hsCode: '8765.43.21',
name: 'Product 2',
unit: 'KG',
quantity: 5,
cost: '2,000.00',
status: 'Pending',
submissionDate: '29/10/2023 10:15',
},
];
// Add state for tracking loading and error states
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchSubmissionDetails = async () => {
try {
setIsLoading(true);
setError(null);
console.log('Fetching submission details for ID:', submission.id);
const response = await fetchSubmissionDetail(submission.id);
console.log('API Response:', response);
if (response && response.data) {
console.log('Setting submission data:', response.data);
setSubmissionData(response.data);
setQuarterPeriods(response.quarter_periods);
// Log the products array to verify it's being set correctly
if (response.data.products) {
console.log('Products in response:', response.data.products);
console.log('Number of products:', response.data.products.length);
}
} else {
console.warn('No data received in response');
}
} catch (error) {
console.error('Error fetching submission details:', error);
setError('Failed to load submission details. Please try again.');
} finally {
setIsLoading(false);
}
};
if (submission?.id) {
fetchSubmissionDetails();
} else {
console.error('No submission ID provided');
setError('No submission ID provided');
setIsLoading(false);
}
}, [submission]);
const filteredProducts = useMemo(() => {
console.log('Filtering products...');
console.log('Raw products data:', submissionData?.products);
if (!submissionData?.products || !Array.isArray(submissionData.products)) {
console.log('No products array found in submissionData');
return [];
}
const normalizedTerm = searchTerm.trim().toLowerCase();
return productData.filter((product) => {
const filtered = submissionData.products.filter((product) => {
// Add null checks for product and product.product
if (!product || !product.product) return false;
const productName = product.product?.product_name || '';
const hsCode = product.product?.hs_code || '';
const status = product.status || '';
const matchesSearch =
!normalizedTerm ||
product.name.toLowerCase().includes(normalizedTerm) ||
product.hsCode.toLowerCase().includes(normalizedTerm) ||
product.status.toLowerCase().includes(normalizedTerm);
const matchesStatus =
selectedStatus === '' ||
product.status.toLowerCase() === selectedStatus.toLowerCase();
productName.toString().toLowerCase().includes(normalizedTerm) ||
hsCode.toString().toLowerCase().includes(normalizedTerm) ||
status.toLowerCase().includes(ormalizedTerm) ||
'pending'.includes(normalizedTerm);
const matchesStatus = selectedStatus === '' ||
(selectedStatus === 'pending' && !product.is_active) ||
(selectedStatus === 'approved' && product.is_active);
return matchesSearch && matchesStatus;
});
}, [searchTerm, selectedStatus, productData]);
console.log('Filtered products:', filtered);
return filtered;
}, [searchTerm, selectedStatus, submissionData]);
const downloadCsv = useCallback(() => {
if (!filteredProducts.length) return;
if (!filteredProducts.length || !submissionData) return;
const headers = [
'HS Code',
'Product',
'Unit',
'Quantity',
'Cost (AED)',
'Status',
'Submission Date & Time'
'Previous Quarter Quantity',
'Previous Quarter Cost (AED)',
'Current Quarter Quantity',
'Current Quarter Cost (AED)',
'Forecast Quarter Quantity',
'Forecast Quarter Cost (AED)',
'Status'
];
const csvRows = [headers.join(',')];
filteredProducts.forEach((product) => {
csvRows.push([
product.hsCode,
product.name,
product.unit,
product.quantity,
product.cost,
product.status,
product.submissionDate
product.product.hs_code,
product.product.product_name,
product.unit?.uom || 'N/A',
product.previous_quantity || '0',
product.previous_cost || '0',
product.current_quantity || '0',
product.current_cost || '0',
product.forecast_quantity || '0',
product.forecast_cost || '0',
product.is_active ? 'Approved' : 'Pending'
].map((value) => `"${String(value).replace(/"/g, '""')}"`).join(','));
});
@ -89,26 +142,90 @@ export const DetailedOverview = ({ submission, onBack }) => {
URL.revokeObjectURL(url);
}, [filteredProducts]);
const rows = filteredProducts.map(product => [
product.hsCode,
product.name,
product.unit,
product.quantity,
`AED ${product.cost}`,
<span className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs font-medium ${
product.status === 'Active' ? 'text-[#2F663C] bg-[#F3FAF4]' :
'text-[#B45309] bg-[#FEF2F2]'
}`}>
{product.status}
</span>,
product.submissionDate,
<button
onClick={() => setSelectedProduct(product)}
className="text-[#92722A] hover:underline text-sm font-medium"
>
View Details
</button>
]);
const handleViewDetails = (product) => {
// Use the product data we already have from the submission
setSelectedProduct(product);
};
const rows = useMemo(() => {
if (!filteredProducts || !Array.isArray(filteredProducts)) {
console.log('No filtered products available');
return [];
}
console.log('Generating rows for filtered products:', filteredProducts);
return filteredProducts.map((product, index) => {
// Add null checks for product and product properties
const productData = product.product || {};
const unit = product.unit || {};
return [
productData.hs_code || '—',
productData.product_name || '—',
unit.uom || 'N/A',
product.previous_quantity || '0',
`AED ${product.previous_cost || '0'}`,
product.current_quantity || '0',
`AED ${product.current_cost || '0'}`,
product.forecast_quantity || '0',
`AED ${product.forecast_cost || '0'}`,
<span
key={`status-${index}`}
className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs font-medium ${
product.is_active ? 'text-[#2F663C] bg-[#F3FAF4]' :
'text-[#7C5E24] bg-[#F9F7ED]'
}`}
>
{product.is_active ? 'Approved' : 'Pending'}
</span>,
<button
key={`view-${index}`}
onClick={() => handleViewDetails(product)}
disabled={loadingProduct}
className="text-[#92722A] hover:underline text-sm font-medium disabled:opacity-50"
>
{loadingProduct && selectedProduct?.id === product.id ? 'Loading...' : 'View Details'}
</button>
];
});
}, [filteredProducts]);
// Show loading state for initial data load
if (isLoading) {
return (
<div className="flex flex-col items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-[#92722A]"></div>
<p className="mt-4 text-gray-600">Loading submission details...</p>
</div>
);
}
// Show error state
if (error) {
return (
<div className="p-4 bg-red-50 text-red-700 rounded-md">
<p className="font-medium">Error</p>
<p className="mt-1">{error}</p>
<button
onClick={() => window.location.reload()}
className="mt-3 px-4 py-2 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200"
>
Retry
</button>
</div>
);
}
// Show no data state
if (!submissionData) {
return (
<div className="text-center py-10">
<p className="text-gray-600">No submission data available</p>
</div>
);
}
const toolbar = (
@ -165,18 +282,29 @@ export const DetailedOverview = ({ submission, onBack }) => {
'HS Code',
'Product',
'Unit',
'Quantity',
'Cost (AED)',
'Previous Qty',
'Previous Cost (AED)',
'Current Qty',
'Current Cost (AED)',
'Forecast Qty',
'Forecast Cost (AED)',
'Status',
'Submission Date & Time',
'Action'
];
// Calculate total products and average cost
const totalProducts = filteredProducts.length;
const totalCost = submissionData.products?.reduce((sum, product) => {
return sum + parseFloat(product.current_cost || 0);
}, 0);
const averageCost = totalProducts > 0 ? (totalCost / totalProducts).toFixed(2) : 0;
// If a product is selected, show the product details view
if (selectedProduct) {
return (
<ProductDetails
product={selectedProduct}
quarterPeriods={quarterPeriods}
onBack={() => setSelectedProduct(null)}
/>
);
@ -198,43 +326,38 @@ export const DetailedOverview = ({ submission, onBack }) => {
</svg>
</button>
<div className="flex items-center space-x-2 pr-6">
<p className="text-sm text-[#232528] font-medium">Establishment ID:</p>
<p className="text-sm text-[#232528]">Al Khazna Investment</p>
<p className="text-sm text-[#232528] font-medium">Establishment:</p>
<p className="text-sm text-[#232528]">{submissionData.establishment?.factory_name || 'N/A'}</p>
</div>
<div className="flex items-center space-x-2 px-6">
<p className="text-sm text-[#232528] font-medium">Total Submitted Products:</p>
<p className="text-sm text-[#232528]">678</p>
<p className="text-sm text-[#232528]">{totalProducts}</p>
</div>
<div className="flex items-center space-x-2 pl-6">
<p className="text-sm text-[#232528] font-medium">Average Cost:</p>
<p className="text-sm text-[#232528]">7889</p>
<p className="text-sm text-[#232528] font-medium">Average Cost (AED):</p>
<p className="text-sm text-[#232528]">{averageCost}</p>
</div>
</div>
<div className="flex items-center space-x-4">
<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">
<option>2023</option>
<option>2024</option>
<option>2025</option>
<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"
value={submissionData.year}
disabled
>
<option>{submissionData.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" />
</svg>
</div>
<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">
<option>January</option>
<option>February</option>
<option>March</option>
<option>April</option>
<option>May</option>
<option>June</option>
<option>July</option>
<option>August</option>
<option>September</option>
<option>October</option>
<option>November</option>
<option>December</option>
<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"
value={submissionData.quarter}
disabled
>
<option>{submissionData.quarter}</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" />

View File

@ -31,8 +31,37 @@ const ProductDetails = ({ product, onBack }) => {
if (!product) return null;
// Merge product data with quarterly data
const productWithDetails = { ...product, ...quarterlyData };
// Format the product data to match the component's expected structure
const formatProductData = (product) => ({
...product,
name: product.product?.product_name || '—',
hsCode: product.product?.hs_code || '—',
unit: product.unit?.uom || 'PCS',
status: product.is_active ? 'Active' : 'Inactive',
q4_oct_quantity: product.previous_quantity_period_one || '—',
q4_nov_quantity: product.previous_quantity_period_two || '—',
q4_dec_quantity: product.previous_quantity_period_three || '—',
q1_jan_quantity: product.current_quantity_period_one || '—',
q1_feb_quantity: product.current_quantity_period_two || '—',
q1_mar_quantity: product.current_quantity_period_three || '—',
q2_apr_quantity: product.forecast_quantity_period_one || '—',
q2_may_quantity: product.forecast_quantity_period_two || '—',
q2_jun_quantity: product.forecast_quantity_period_three || '—',
q4_oct_cost: product.previous_cost_period_one || '—',
q4_nov_cost: product.previous_cost_period_two || '—',
q4_dec_cost: product.previous_cost_period_three || '—',
q1_jan_cost: product.current_cost_period_one || '—',
q1_feb_cost: product.current_cost_period_two || '—',
q1_mar_cost: product.current_cost_period_three || '—',
q2_apr_cost: product.forecast_cost_period_one || '—',
q2_may_cost: product.forecast_cost_period_two || '—',
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.'
});
const formattedProduct = formatProductData(product);
return (
<div className="space-y-6 w-full">
@ -47,7 +76,7 @@ const ProductDetails = ({ product, onBack }) => {
<path d="M12.5 15L7.5 10L12.5 5" stroke="#6B7280" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
<h1 className="text-lg font-medium text-[#232528]">Product Details</h1>
<h1 className="text-lg font-medium text-[#232528]">Product Details - {formattedProduct.name}</h1>
</div>
</div>
</div>
@ -60,7 +89,7 @@ const ProductDetails = ({ product, onBack }) => {
<label className="block text-sm font-medium text-[#6B7280]">Product Name</label>
<div className="mt-1">
<p className="block w-full rounded-md border-0 py-1.5 text-gray-900 bg-white">
{product.name}
{formattedProduct.name}
</p>
</div>
</div>
@ -68,7 +97,7 @@ const ProductDetails = ({ product, onBack }) => {
<label className="block text-sm font-medium text-[#6B7280]">HS Code</label>
<div className="mt-1">
<p className="block w-full rounded-md border-0 py-1.5 text-gray-900 bg-white">
{product.hsCode || '—'}
{formattedProduct.hsCode}
</p>
</div>
</div>
@ -76,7 +105,7 @@ const ProductDetails = ({ product, onBack }) => {
<label className="block text-sm font-medium text-[#6B7280]">Unit</label>
<div className="mt-1">
<p className="block w-full rounded-md border-0 py-1.5 text-gray-900 bg-white">
{product.unit || 'PCS'}
{formattedProduct.unit}
</p>
</div>
</div>
@ -84,11 +113,11 @@ const ProductDetails = ({ product, onBack }) => {
<label className="block text-sm font-medium text-[#6B7280]">Status</label>
<div className="mt-1">
<span className={`inline-flex items-center rounded-md px-3 py-1.5 text-sm font-medium ${
product.status === 'Active'
formattedProduct.status === 'Active'
? 'bg-green-50 text-green-700 ring-1 ring-inset ring-green-600/20'
: 'bg-yellow-50 text-yellow-700 ring-1 ring-inset ring-yellow-600/20'
}`}>
{product.status || '—'}
{formattedProduct.status}
</span>
</div>
</div>
@ -132,17 +161,17 @@ const ProductDetails = ({ product, onBack }) => {
{/* Quantity Row */}
<tr>
<td className="border border-gray-300 py-2 px-3 text-left font-medium">
Quantity ({product.unit || 'units'})
Quantity ({formattedProduct.unit})
</td>
<td className="border border-gray-300 py-2">{product.q4_oct_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q4_nov_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q4_dec_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q1_jan_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q1_feb_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q1_mar_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q2_apr_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q2_may_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{product.q2_jun_quantity || '—'}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q4_oct_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q4_nov_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q4_dec_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q1_jan_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q1_feb_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q1_mar_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q2_apr_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q2_may_quantity}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q2_jun_quantity}</td>
</tr>
{/* Cost Row */}
@ -150,15 +179,15 @@ const ProductDetails = ({ product, onBack }) => {
<td className="border border-gray-300 py-2 px-3 text-left font-medium">
Cost (AED)
</td>
<td className="border border-gray-300 py-2">{product.q4_oct_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q4_nov_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q4_dec_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q1_jan_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q1_feb_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q1_mar_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q2_apr_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q2_may_cost || '—'}</td>
<td className="border border-gray-300 py-2">{product.q2_jun_cost || '—'}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q4_oct_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q4_nov_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q4_dec_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q1_jan_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q1_feb_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q1_mar_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q2_apr_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q2_may_cost}</td>
<td className="border border-gray-300 py-2">{formattedProduct.q2_jun_cost}</td>
</tr>
{/* Reason Current */}
@ -170,7 +199,7 @@ const ProductDetails = ({ product, onBack }) => {
-- --
</td>
<td colSpan="3" className="border border-gray-300 py-2 text-center bg-[#F3FAF4] text-[#2F663C]">
{product.variationReason || '—'}
{formattedProduct.variationReason}
</td>
<td colSpan="3" className="border border-gray-300 py-2 text-center">
-- --
@ -186,7 +215,7 @@ const ProductDetails = ({ product, onBack }) => {
-- --
</td>
<td colSpan="3" className="border border-gray-300 py-2 text-center bg-[#FFF7E9] text-[#F29F0E]">
{product.zeroTargetReason || '—'}
{formattedProduct.zeroTargetReason}
</td>
</tr>
</tbody>
@ -197,7 +226,7 @@ const ProductDetails = ({ product, onBack }) => {
<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">
{product.remarks || 'No remarks provided for this product.'}
{formattedProduct.remarks}
</div>
</div>
@ -205,16 +234,22 @@ const ProductDetails = ({ product, onBack }) => {
<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">User Name</span></p>
<p className="text-[#6B7280]">Submission Date: <span className="text-[#232528] font-medium">{product.submissionDate || '—'}</span></p>
<p className="text-[#6B7280]">Submitted by: <span className="text-[#232528] font-medium">
{product.created_by || 'N/A'}
</span></p>
<p className="text-[#6B7280]">Submission Date: <span className="text-[#232528] font-medium">
{new Date(product.created_at).toLocaleDateString() || '—'}
</span></p>
</div>
<div>
<p className="text-[#6B7280]">Last Updated: <span className="text-[#232528] font-medium">{product.lastUpdated || product.submissionDate || '—'}</span></p>
<p className="text-[#6B7280]">Last Updated: <span className="text-[#232528] font-medium">
{product.updated_at ? new Date(product.updated_at).toLocaleDateString() : '—'}
</span></p>
<p className="text-[#6B7280]">Status:
<span className={`inline-flex items-center justify-center rounded-md px-2 py-0.5 text-xs font-medium ml-1 ${
product.status === 'Active' ? 'text-[#2F663C] bg-[#F3FAF4]' : 'text-[#B45309] bg-[#FEF2F2]'
formattedProduct.status === 'Active' ? 'text-[#2F663C] bg-[#F3FAF4]' : 'text-[#B45309] bg-[#FEF2F2]'
}`}>
{product.status}
{formattedProduct.status}
</span>
</p>
</div>

View File

@ -290,11 +290,57 @@ const ProductData = ({
onProductsChange(products.filter((p) => p.id !== id));
};
const updateProductField = (id, field, value) => {
const updateProductField = (id, field, value, options = []) => {
onProductsChange(
products.map((product) =>
product.id === id ? { ...product, [field]: value } : product
)
products.map((product) => {
if (product.id !== id) return product;
// When product is selected, store both ID and name
if (field === 'product') {
const selectedProduct = options.find(p => p.value === value || p.id === value);
return {
...product,
product: value,
productName: selectedProduct?.label || selectedProduct?.name || '',
[field]: value
};
}
// When unit is selected, store both ID and name
if (field === 'unit') {
const selectedUnit = options.find(u => u.value === value || u.id === value);
return {
...product,
unit: value,
unitName: selectedUnit?.label || selectedUnit?.name || '',
[field]: value
};
}
// When variation reason is selected, store both ID and name
if (field === 'variationReason') {
const selectedReason = options.find(r => r.value === value || r.id === value);
return {
...product,
variationReason: value,
variationReasonName: selectedReason?.label || selectedReason?.name || '',
[field]: value
};
}
// When zero target reason is selected, store both ID and name
if (field === 'zeroTargetReason') {
const selectedReason = options.find(r => r.value === value || r.id === value);
return {
...product,
zeroTargetReason: value,
zeroTargetReasonName: selectedReason?.label || selectedReason?.name || '',
[field]: value
};
}
return { ...product, [field]: value };
})
);
};
@ -467,7 +513,7 @@ const ProductData = ({
placeholder={isLoadingProducts ? 'Loading products...' : 'Select product HS code'}
options={productOptions}
value={p.product || ''}
onChange={(e) => updateProductField(p.id, 'product', e.target.value)}
onChange={(e) => updateProductField(p.id, 'product', e.target.value, productOptions)}
/>
{productsError && productOptions.length === 0 && (
<p className="mt-1 text-xs text-red-600">{productsError}</p>
@ -479,7 +525,7 @@ const ProductData = ({
placeholder={isLoadingUnits ? 'Loading units...' : 'Select unit'}
options={unitOptions}
value={p.unit || ''}
onChange={(e) => updateProductField(p.id, 'unit', e.target.value)}
onChange={(e) => updateProductField(p.id, 'unit', e.target.value, unitOptions)}
/>
{unitsError && unitOptions.length === 0 && (
<p className="mt-1 text-xs text-red-600">{unitsError}</p>
@ -646,7 +692,7 @@ const ProductData = ({
placeholder={isLoadingReasons ? 'Loading reasons...' : 'Select reason'}
options={variationReasons}
value={p.variationReason || ''}
onChange={(e) => updateProductField(p.id, 'variationReason', e.target.value)}
onChange={(e) => updateProductField(p.id, 'variationReason', e.target.value, variationReasons)}
/>
{reasonsError && variationReasons.length === 0 && (
<p className="mt-1 text-xs text-red-600">{reasonsError}</p>
@ -676,6 +722,7 @@ const ProductData = ({
type="number"
required
placeholder="Enter"
value={p.mayQuantity || ''}
onChange={(e) => updateProductField(p.id, 'mayQuantity', e.target.value)}
/>
</div>
@ -686,6 +733,7 @@ const ProductData = ({
type="number"
required
placeholder="Enter"
value={p.junQuantity || ''}
onChange={(e) => updateProductField(p.id, 'junQuantity', e.target.value)}
/>
</div>
@ -731,7 +779,7 @@ const ProductData = ({
placeholder={isLoadingZeroReasons ? 'Loading reasons...' : 'Select reason'}
options={zeroTargetReasons}
value={p.zeroTargetReason || ''}
onChange={(e) => updateProductField(p.id, 'zeroTargetReason', e.target.value)}
onChange={(e) => updateProductField(p.id, 'zeroTargetReason', e.target.value, zeroTargetReasons)}
/>
{zeroReasonsError && zeroTargetReasons.length === 0 && (
<p className="mt-1 text-xs text-red-600">{zeroReasonsError}</p>

View File

@ -179,15 +179,16 @@ const buildProductRows = (products = [], options = {}) => {
console.log("productName",productName)
const productCode = fullProduct?.code || product.productCode || product.code || product.product?.code || product.id || `CODE-${index + 1}`;
console.log("productCode",productCode)
// Find unit display name
const unitnames = findDisplayName(unitOptions)
console.log("unitnames",unitnames)
const unitName = findDisplayName(unitOptions, product.unit);
console.log("unitName",unitName)
// Use stored unitName if available, otherwise try to find it in unitOptions
let unitName = product.unitName;
if (!unitName && product.unit) {
unitName = findDisplayName(unitOptions, product.unit);
}
console.log("unitName", unitName)
// Find reason display names
const variationReasonName = findDisplayName(variationReasons, product.variationReason);
const zeroTargetReasonName = findDisplayName(zeroTargetReasons, product.zeroTargetReason);
// Use stored reason names if available, otherwise try to find them in the options
const variationReasonName = product.variationReasonName || findDisplayName(variationReasons, product.variationReason);
const zeroTargetReasonName = product.zeroTargetReasonName || findDisplayName(zeroTargetReasons, product.zeroTargetReason);
const capacity = product.capacity !== undefined && product.capacity !== null ? formatNumber(product.capacity) : '—';
@ -226,7 +227,7 @@ const buildProductRows = (products = [], options = {}) => {
// Reasons and remarks - use display names
variationReason: variationReasonName,
zeroTargetReason: zeroTargetReasonName,
remarks: product.remarks || '',
remarks: product.remarks || '',
// Keep the raw product data for debugging
_raw: { ...product }
@ -574,11 +575,11 @@ const ReviewSubmit = ({
<section key={product.id || index} className="border border-gray-200 rounded-md bg-white p-4 mb-6 shadow-sm">
{/* Header */}
<div className="flex justify-between items-start mb-4">
<h3 className="text-sm font-semibold text-[#2E2E2E]">
<span className="text-gray-500">Product:</span>{' '}
<h3 className="text-sm font-bold text-[#232528]">
<span className="font-bold">Product:</span>{' '}
<span className="font-normal">{product.product}</span>
{product.productCode && product.productCode !== product.product && (
<span className="text-gray-400 text-xs ml-2">(ID: {product.productCode})</span>
<span className="text-gray-400 text-xs ml-2"></span>
)}
</h3>
<div className="flex gap-3">
@ -668,7 +669,7 @@ const ReviewSubmit = ({
-- --
</td>
<td colSpan="3" className="border border-gray-300 py-2 text-center bg-[#F3FAF4] text-[#2F663C]">
{product.variationReason || '—'}
{product.variationReasonName || product.variationReason || '—'}
</td>
<td colSpan="3" className="border border-gray-300 py-2 text-center">
-- --
@ -684,20 +685,25 @@ const ReviewSubmit = ({
-- --
</td>
<td colSpan="3" className="border border-gray-300 py-2 text-center bg-[#FFF7E9] text-[#F29F0E]">
{product.zeroTargetReason || '—'}
{product.zeroTargetReasonName || product.zeroTargetReason || '—'}
</td>
</tr>
</tbody>
</table>
</div>
{/* Remarks */}
<div className="mt-4 text-xs text-gray-600">
<p className="font-semibold mb-1">Remarks:</p>
<div className="border border-[#F3B340] rounded-md p-2 text-sm bg-white">
{product.remarks || 'No remarks provided'}
</div>
</div>
{/* Remarks */}
<div className="mt-4">
<p className="text-sm font-semibold text-[#2E2E2E] mb-2">Remarks</p>
<div className="border border-[#CBA344] rounded-md p-3 text-sm w-[50%] min-h-[60px]">
{product.remarks?.trim() ? (
product.remarks
) : (
<span className="text-gray-400 ">Add Short Note..</span>
)}
</div>
</div>
</section>
))}

View File

@ -58,6 +58,20 @@ const History = () => {
return date.toLocaleDateString('en-GB');
}, []);
const formatDateTime = React.useCallback((value) => {
if (!value) return '—';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '—';
return date.toLocaleString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true
});
}, []);
React.useEffect(() => {
let cancelled = false;
const loadHistory = async () => {
@ -86,15 +100,14 @@ const History = () => {
}, []);
const headers = [
{ name: 'Year', className: 'text-left' },
{ name: 'Quarter', className: 'text-left' },
{ name: 'Submission Window', className: 'text-left' },
{ name: 'Total Products', className: 'text-center' },
{ name: 'Products Submitted', className: 'text-center' },
{ name: 'Total Cost (AED)', className: 'text-right' },
{ name: 'Status', className: 'text-center' },
{ name: 'Date Submitted', className: 'text-left' },
{ name: 'Actions', className: 'text-center' }
{ name: 'Submission Date & Time', className: 'text-left whitespace-nowrap w-[140px]' },
{ name: 'Year', className: 'text-left w-[60px]' },
{ name: 'Quarter', className: 'text-left w-[70px]' },
{ name: 'HS Code', className: 'text-left w-[80px]' },
{ name: 'Product', className: 'text-left w-[120px]' },
{ name: 'Status', className: 'text-center w-[100px]' },
{ name: 'Actors', className: 'text-left w-[150px]' },
{ name: 'Details', className: 'text-left flex-1 min-w-[200px]' }
];
const statusOptions = React.useMemo(
@ -107,6 +120,7 @@ const History = () => {
],
[]
);
const handleViewDetails = React.useCallback((submission) => {
if (!submission) return;
navigate('/survey', { state: { submission, initialStep: 3 } });
@ -135,22 +149,83 @@ const History = () => {
});
}, [rows, searchTerm, statusFilter, toVariant, formatStatus, formatDate]);
const tableRows = filteredRows.map((entry) => [
entry.year || '—',
entry.quarter || '—',
entry.submission_window || '—',
Number.isFinite(Number(entry.total_products)) ? entry.total_products : '—',
Number.isFinite(Number(entry.products_submitted)) ? entry.products_submitted : '—',
entry.total_cost ? Intl.NumberFormat('en-US').format(entry.total_cost) : '—',
formatStatus(entry.status),
formatDate(entry.created_at),
'View Details',
]);
const formatNumber = (num) => {
if (num === null || num === undefined) return '—';
return Number(num).toLocaleString('en-IN');
};
// 🟨 Added for expand/collapse logic
const [expandedRow, setExpandedRow] = React.useState(null);
const handleToggleDetails = (rowIndex) => {
setExpandedRow(expandedRow === rowIndex ? null : rowIndex);
};
// 🟩 Updated tableRows with expandable details
const tableRows = filteredRows.map((entry, index) => {
const isExpanded = expandedRow === index;
return [
formatDateTime(entry.created_at),
entry.year || '—',
entry.quarter || '—',
entry.hs_code || '—',
entry.product_name || '—',
formatStatus(entry.status),
entry.actors ? entry.actors.join(', ') : '—',
<div className="flex flex-col w-full">
<div className="flex items-center justify-between text-sm text-gray-800">
<span>
Quantity updated 1,450 to 1,500; Cost updated 87,000 to 90,000
</span>
<button
onClick={() => handleToggleDetails(index)}
className="flex items-center justify-center w-5 h-5 border border-gray-300 rounded hover:bg-gray-100"
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`transition-transform duration-200 ${isExpanded ? 'rotate-180' : ''}`}
>
<path
d="M6 9L12 15L18 9"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</div>
{isExpanded && (
<div className="mt-2 ml-2 border border-gray-200 rounded p-2 bg-gray-50 text-sm text-gray-700">
<div className="flex gap-6 mb-2">
<div>
<div className="font-medium">Quantity Updated:</div>
<div>1,450 1,500</div>
</div>
<div>
<div className="font-medium">Cost Updated:</div>
<div>87,000 90,000</div>
</div>
</div>
<div>
<div className="font-medium">Reason for variations:</div>
<div>Seasonal Production Surge</div>
</div>
</div>
)}
</div>
];
});
const toolbar = (
<div className="px-6 py-4 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="flex flex-col gap-1">
<h2 className="text-[18px] leading-[28px] font-medium text-[#232528]">Quarter Overview</h2>
<h2 className="text-[18px] leading-[28px] font-medium text-[#232528]">Submission History</h2>
<div className="flex gap-3 text-sm text-gray-500">
{loading && <span>Loading</span>}
{error && <span className="text-red-600">{error}</span>}
@ -218,20 +293,121 @@ const History = () => {
</div>
);
// Calculate total products and average cost for the card
const totalProducts = rows.length;
const averageCost = rows.length > 0
? Math.round(rows.reduce((sum, row) => sum + (parseFloat(row.total_cost) || 0), 0) / rows.length)
: 0;
return (
<div className="min-h-screen bg-[#F7F7F7]">
<HeaderBar />
<div className="mx-auto max-w-7xl px-6 py-10">
{/* Card Section */}
<div className="bg-white p-4 rounded-t-lg border-b border-[#E2E8F0] mb-6">
<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">
<p className="text-sm text-[#232528] font-medium">Establishment ID:</p>
<p className="text-sm text-[#232528]">{totalProducts}</p>
</div>
{/* <div className="flex items-center space-x-2 pl-6">
<p className="text-sm text-[#232528] font-medium">Average Cost (AED):</p>
<p className="text-sm text-[#232528]">{averageCost.toLocaleString('en-IN')}</p>
</div> */}
</div>
<div className="flex items-center space-x-4">
<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"
>
<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>
</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" />
</svg>
</div>
<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"
>
<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>
</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" />
</svg>
</div>
<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"
>
<option value="">Select HS Code</option>
<option value="code1">HS Code 1</option>
<option value="code2">HS Code 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" />
</svg>
</div>
<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"
>
<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" />
</svg>
</div>
<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"
>
<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" />
</svg>
</div>
<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"
>
<option value="">Select Status</option>
<option value="approved">Approved</option>
<option value="pending">Pending</option>
<option value="rejected">Rejected</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" />
</svg>
</div>
</div>
</div>
</div>
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-gray-200">
<Table
headers={headers}
rows={tableRows}
beforeHeader={toolbar}
renderCell={(value, ri, ci) => {
if (ci === 6) {
if (ci === 3) {
return <Badge variant={toVariant(filteredRows[ri]?.status)}>{value}</Badge>;
}
if (ci === headers.length - 1) {
if (ci === 5) {
const submission = filteredRows[ri];
const disabled = !submission;
return (

View File

@ -1,119 +1,112 @@
import React, { useState } from 'react';
// src/pages/Overview/Overview.jsx
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { getSubmissions } from '@/services/submissions/submissionService';
import HeaderBar from '@/components/layout/HeaderBar';
import Table from '@/components/common/Table';
import DetailedOverview from '@/components/overview/DetailedOverview';
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const caretDownSrc = '/assets/images/CaretDown.svg';
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
const statusStyles = {
approved: 'text-[#2F663C] bg-[#F3FAF4] ',
approved: 'text-[#2F663C] bg-[#F3FAF4]',
submitted: 'text-[#003CFF] bg-[#E7F5FF]',
pending: 'text-[#B45309] bg-[#FEF2F2]',
pending: 'text-[#7C5E24] bg-[#F9F7ED]',
rejected: 'text-[#B52520] bg-[#FEF2F2]',
};
const mockOverview = [
{
year: '2025',
quarter: 'Q4',
window: 'Oct-Dec 2025',
totalProducts: 10,
submittedProducts: 4,
totalCost: 195000,
status: 'Approved',
submittedOn: '18/10/2025',
},
{
year: '2025',
quarter: 'Q3',
window: 'Jul-Sep 2025',
totalProducts: 10,
submittedProducts: 6,
totalCost: 295000,
status: 'Submitted',
submittedOn: '28/08/2025',
},
{
year: '2025',
quarter: 'Q2',
window: 'Apr-Jun 2025',
totalProducts: 10,
submittedProducts: 6,
totalCost: 395000,
status: 'Rejected',
submittedOn: '24/05/2025',
},
{
year: '2025',
quarter: 'Q1',
window: 'Jan-Mar 2025',
totalProducts: 10,
submittedProducts: 6,
totalCost: 495000,
status: 'Rejected',
submittedOn: '21/03/2025',
},
];
const formatCurrency = (amount) => {
if (amount === null || amount === undefined) return '—';
return new Intl.NumberFormat('en-AE').format(amount);
const formatDate = (dateString) => {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleDateString('en-GB');
};
const Overview = () => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedYear, setSelectedYear] = useState('');
const [selectedQuarter, setSelectedQuarter] = useState('');
const [selectedStatus, setSelectedStatus] = useState('');
const [selectedSubmission, setSelectedSubmission] = useState(null);
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [pagination, setPagination] = useState({
page: 1,
limit: 100,
total: 0,
totalPages: 1
});
const navigate = useNavigate();
useEffect(() => {
const fetchSubmissions = async () => {
try {
setLoading(true);
const response = await getSubmissions(pagination.page, pagination.limit);
setSubmissions(response.data);
setPagination(response.pagination);
} catch (err) {
setError('Failed to load submissions');
console.error('Error:', err);
} finally {
setLoading(false);
}
};
fetchSubmissions();
}, [pagination.page, pagination.limit]);
const filteredRows = React.useMemo(() => {
const normalizedTerm = searchTerm.trim().toLowerCase();
return mockOverview.filter((record) => {
return submissions.filter((record) => {
const matchesSearch =
!normalizedTerm ||
record.year.toLowerCase().includes(normalizedTerm) ||
String(record.year).toLowerCase().includes(normalizedTerm) ||
record.quarter.toLowerCase().includes(normalizedTerm) ||
record.window.toLowerCase().includes(normalizedTerm) ||
record.status.toLowerCase().includes(normalizedTerm);
`${record.quarter} ${record.year}`.toLowerCase().includes(normalizedTerm) ||
record.status.toLowerCase().includes(normalizedTerm) ||
record.establishment?.factory_name?.toLowerCase().includes(normalizedTerm);
const matchesStatus =
selectedStatus === '' || record.status.toLowerCase() === selectedStatus;
!selectedStatus ||
(selectedStatus === 'approved' && record.status === 'Approved') ||
(selectedStatus === 'submitted' && record.status === 'Submitted') ||
(selectedStatus === 'rejected' && record.status === 'Rejected');
return matchesSearch && matchesStatus;
});
}, [searchTerm, selectedStatus]);
}, [searchTerm, selectedStatus, submissions]);
const rows = filteredRows.map((record) => [
record.year,
record.quarter,
record.window,
record.totalProducts,
record.submittedProducts,
`AED ${formatCurrency(record.totalCost)}`,
`${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,
record.submittedOn,
formatDate(record.created_at),
'View Details',
]);
const downloadCsv = React.useCallback(() => {
if (!filteredRows.length) return;
const headers = ['Year', 'Quarter', 'Submission Window', 'Total Products', 'Products Submitted', 'Total Cost (AED)', 'Status', 'Date Submitted'];
const csvRows = [headers.join(',')];
filteredRows.forEach((record) => {
csvRows.push([
record.year,
record.quarter,
record.window,
record.totalProducts,
record.submittedProducts,
record.totalCost,
`${record.quarter} ${record.year}`,
10, // Always show 10 for Total Products in CSV
record.product_count || 0, // Show actual submitted products count
'-', // Total cost not in API
record.status,
record.submittedOn,
formatDate(record.created_at)
].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);
const link = document.createElement('a');
@ -136,7 +129,7 @@ const Overview = () => {
type="text"
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
placeholder="Search establishment"
placeholder="Search submissions..."
className="w-full h-10 rounded-md border border-[#E2E8F0] pl-10 pr-3 text-sm text-[#232528] focus:outline-none"
/>
<img
@ -166,7 +159,11 @@ const Overview = () => {
type="button"
onClick={downloadCsv}
disabled={!filteredRows.length}
className={`inline-flex items-center gap-2 h-10 rounded-md border px-4 text-sm font-medium ${filteredRows.length ? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]' : 'border-gray-200 text-gray-400 cursor-not-allowed'}`}
className={`inline-flex items-center gap-2 h-10 rounded-md border px-4 text-sm font-medium ${
filteredRows.length
? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]'
: 'border-gray-200 text-gray-400 cursor-not-allowed'
}`}
>
<img src={downloadIconSrc} alt="Export" className="h-4 w-4" />
<span>Export CSV</span>
@ -179,6 +176,41 @@ const Overview = () => {
setSelectedSubmission(filteredRows[rowIndex]);
};
if (loading) {
return (
<div className="min-h-screen bg-[#F8FAFC]">
<HeaderBar />
<main className="max-w-[1280px] mx-auto px-4 py-6">
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-[#92722A]"></div>
</div>
</main>
</div>
);
}
if (error) {
return (
<div className="min-h-screen bg-[#F8FAFC]">
<HeaderBar />
<main className="max-w-[1280px] mx-auto px-4 py-6">
<div className="bg-red-50 border-l-4 border-red-400 p-4">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<p className="text-sm text-red-700">{error}</p>
</div>
</div>
</div>
</main>
</div>
);
}
if (selectedSubmission) {
return (
<div className="min-h-screen bg-[#F8FAFC]">
@ -209,16 +241,16 @@ const Overview = () => {
rowGapClass="border-spacing-y-2"
className="w-full min-w-[1200px] [&_td]:whitespace-nowrap [&_th]:whitespace-nowrap"
renderCell={(value, rowIndex, columnIndex) => {
if (columnIndex === 6) {
if (columnIndex === 6) { // Status column
const normalized = String(value).toLowerCase();
const style = statusStyles[normalized] || 'text-[#232528] bg-[#F8FAFC] ';
const style = statusStyles[normalized] || 'text-[#232528] bg-[#F8FAFC]';
return (
<span className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs font-medium ${style}`}>
{value}
</span>
);
}
if (columnIndex === 8) {
if (columnIndex === 8) { // Action column
return (
<button
className="text-[#92722A] hover:underline text-sm font-medium"
@ -228,7 +260,7 @@ const Overview = () => {
</button>
);
}
return <span className="inline-block">{value}</span>;
return value;
}}
/>
</section>
@ -237,4 +269,4 @@ const Overview = () => {
);
};
export default Overview;
export default Overview;

View File

@ -398,6 +398,9 @@ const Survey = () => {
try {
const payload = buildSubmissionPayload();
const response = await submitSurvey(payload);
// Navigate to overview page after successful submission
navigate('/overview');
console.log('Survey submission response:', response);
showToast('success', response?.message || 'You have successfully submitted the survey.');
setHasSubmitted(true);

View File

@ -3,6 +3,24 @@ import resolveEstablishmentId from '@/services/utils/establishment';
const endpoint = '/submissions';
// Add this new function to fetch submissions with pagination
export const getSubmissions = async (page = 1, limit = 100, config = {}) => {
try {
const response = await getRequest(endpoint, {
...config,
params: {
page,
limit,
...(config.params || {}) // Preserve any existing params
}
});
return response.data;
} catch (error) {
console.error('Error fetching submissions:', error);
throw error;
}
};
export const submitSurvey = async (payload = {}, config = {}) => {
const establishmentId = resolveEstablishmentId(payload.establishment_id);
const requestBody = {
@ -32,8 +50,10 @@ export const fetchSubmissionDetail = async (submissionId, config = {}) => {
return response.data;
};
// Add the new function to the default export
export default {
getSubmissions, // Add this line
submitSurvey,
fetchSubmissionHistory,
fetchSubmissionDetail,
};
};