From 95528cb84ce18141bdc2e30051ac03cc44fdc637 Mon Sep 17 00:00:00 2001 From: Malini Date: Fri, 31 Oct 2025 09:54:39 +0530 Subject: [PATCH] Added Overview screen in Establishement users --- .../components/overview/DetailedOverview.jsx | 301 ++++++++++++------ .../components/overview/ProductDetails.jsx | 105 ++++-- .../survey/ProductData/ProductData.jsx | 64 +++- .../survey/ReviewSubmit/ReviewSubmit.jsx | 48 +-- .../src/pages/History/History.jsx | 222 +++++++++++-- .../src/pages/Overview/Overview.jsx | 182 ++++++----- .../src/pages/Survey/Survey.jsx | 3 + .../services/submissions/submissionService.js | 22 +- 8 files changed, 695 insertions(+), 252 deletions(-) diff --git a/ipi-survey-platform/src/components/overview/DetailedOverview.jsx b/ipi-survey-platform/src/components/overview/DetailedOverview.jsx index ef5ecbf..3f7e863 100644 --- a/ipi-survey-platform/src/components/overview/DetailedOverview.jsx +++ b/ipi-survey-platform/src/components/overview/DetailedOverview.jsx @@ -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}`, - - {product.status} - , - product.submissionDate, - - ]); + 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'}`, + + {product.is_active ? 'Approved' : 'Pending'} + , + + ]; + }); + }, [filteredProducts]); + + // Show loading state for initial data load + if (isLoading) { + return ( +
+
+

Loading submission details...

+
+ ); + } + + + // Show error state + if (error) { + return ( +
+

Error

+

{error}

+ +
+ ); + } + + // Show no data state + if (!submissionData) { + return ( +
+

No submission data available

+
+ ); + } 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 ( setSelectedProduct(null)} /> ); @@ -198,43 +326,38 @@ export const DetailedOverview = ({ submission, onBack }) => {
-

Establishment ID:

-

Al Khazna Investment

+

Establishment:

+

{submissionData.establishment?.factory_name || 'N/A'}

Total Submitted Products:

-

678

+

{totalProducts}

-

Average Cost:

-

7889

+

Average Cost (AED):

+

{averageCost}

- +
- + diff --git a/ipi-survey-platform/src/components/overview/ProductDetails.jsx b/ipi-survey-platform/src/components/overview/ProductDetails.jsx index e2fc855..af00db1 100644 --- a/ipi-survey-platform/src/components/overview/ProductDetails.jsx +++ b/ipi-survey-platform/src/components/overview/ProductDetails.jsx @@ -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 (
@@ -47,7 +76,7 @@ const ProductDetails = ({ product, onBack }) => { -

Product Details

+

Product Details - {formattedProduct.name}

@@ -60,7 +89,7 @@ const ProductDetails = ({ product, onBack }) => {

- {product.name} + {formattedProduct.name}

@@ -68,7 +97,7 @@ const ProductDetails = ({ product, onBack }) => {

- {product.hsCode || '—'} + {formattedProduct.hsCode}

@@ -76,7 +105,7 @@ const ProductDetails = ({ product, onBack }) => {

- {product.unit || 'PCS'} + {formattedProduct.unit}

@@ -84,11 +113,11 @@ const ProductDetails = ({ product, onBack }) => {
- {product.status || '—'} + {formattedProduct.status}
@@ -132,17 +161,17 @@ const ProductDetails = ({ product, onBack }) => { {/* Quantity Row */} - Quantity ({product.unit || 'units'}) + Quantity ({formattedProduct.unit}) - {product.q4_oct_quantity || '—'} - {product.q4_nov_quantity || '—'} - {product.q4_dec_quantity || '—'} - {product.q1_jan_quantity || '—'} - {product.q1_feb_quantity || '—'} - {product.q1_mar_quantity || '—'} - {product.q2_apr_quantity || '—'} - {product.q2_may_quantity || '—'} - {product.q2_jun_quantity || '—'} + {formattedProduct.q4_oct_quantity} + {formattedProduct.q4_nov_quantity} + {formattedProduct.q4_dec_quantity} + {formattedProduct.q1_jan_quantity} + {formattedProduct.q1_feb_quantity} + {formattedProduct.q1_mar_quantity} + {formattedProduct.q2_apr_quantity} + {formattedProduct.q2_may_quantity} + {formattedProduct.q2_jun_quantity} {/* Cost Row */} @@ -150,15 +179,15 @@ const ProductDetails = ({ product, onBack }) => { Cost (AED) - {product.q4_oct_cost || '—'} - {product.q4_nov_cost || '—'} - {product.q4_dec_cost || '—'} - {product.q1_jan_cost || '—'} - {product.q1_feb_cost || '—'} - {product.q1_mar_cost || '—'} - {product.q2_apr_cost || '—'} - {product.q2_may_cost || '—'} - {product.q2_jun_cost || '—'} + {formattedProduct.q4_oct_cost} + {formattedProduct.q4_nov_cost} + {formattedProduct.q4_dec_cost} + {formattedProduct.q1_jan_cost} + {formattedProduct.q1_feb_cost} + {formattedProduct.q1_mar_cost} + {formattedProduct.q2_apr_cost} + {formattedProduct.q2_may_cost} + {formattedProduct.q2_jun_cost} {/* Reason Current */} @@ -170,7 +199,7 @@ const ProductDetails = ({ product, onBack }) => { -- -- - {product.variationReason || '—'} + {formattedProduct.variationReason} -- -- @@ -186,7 +215,7 @@ const ProductDetails = ({ product, onBack }) => { -- -- - {product.zeroTargetReason || '—'} + {formattedProduct.zeroTargetReason} @@ -197,7 +226,7 @@ const ProductDetails = ({ product, onBack }) => {

Remarks

- {product.remarks || 'No remarks provided for this product.'} + {formattedProduct.remarks}
@@ -205,16 +234,22 @@ const ProductDetails = ({ product, onBack }) => {
-

Submitted by: User Name

-

Submission Date: {product.submissionDate || '—'}

+

Submitted by: + {product.created_by || 'N/A'} +

+

Submission Date: + {new Date(product.created_at).toLocaleDateString() || '—'} +

-

Last Updated: {product.lastUpdated || product.submissionDate || '—'}

+

Last Updated: + {product.updated_at ? new Date(product.updated_at).toLocaleDateString() : '—'} +

Status: - {product.status} + {formattedProduct.status}

diff --git a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx index 58f9b3e..cf33a55 100644 --- a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx +++ b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx @@ -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 && (

{productsError}

@@ -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 && (

{unitsError}

@@ -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 && (

{reasonsError}

@@ -676,6 +722,7 @@ const ProductData = ({ type="number" required placeholder="Enter" + value={p.mayQuantity || ''} onChange={(e) => updateProductField(p.id, 'mayQuantity', e.target.value)} />
@@ -686,6 +733,7 @@ const ProductData = ({ type="number" required placeholder="Enter" + value={p.junQuantity || ''} onChange={(e) => updateProductField(p.id, 'junQuantity', e.target.value)} />
@@ -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 && (

{zeroReasonsError}

diff --git a/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx b/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx index d86cf2b..1ce99a5 100644 --- a/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx +++ b/ipi-survey-platform/src/components/survey/ReviewSubmit/ReviewSubmit.jsx @@ -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 = ({
{/* Header */}
-

- Product:{' '} +

+ Product:{' '} {product.product} {product.productCode && product.productCode !== product.product && ( - (ID: {product.productCode}) + )}

@@ -668,7 +669,7 @@ const ReviewSubmit = ({ -- -- - {product.variationReason || '—'} + {product.variationReasonName || product.variationReason || '—'} -- -- @@ -684,20 +685,25 @@ const ReviewSubmit = ({ -- -- - {product.zeroTargetReason || '—'} + {product.zeroTargetReasonName || product.zeroTargetReason || '—'}
- {/* Remarks */} -
-

Remarks:

-
- {product.remarks || 'No remarks provided'} -
-
+ {/* Remarks */} +
+

Remarks

+
+ {product.remarks?.trim() ? ( + product.remarks + ) : ( + Add Short Note.. + )} +
+
+
))} diff --git a/ipi-survey-platform/src/pages/History/History.jsx b/ipi-survey-platform/src/pages/History/History.jsx index 80fec69..b71db68 100644 --- a/ipi-survey-platform/src/pages/History/History.jsx +++ b/ipi-survey-platform/src/pages/History/History.jsx @@ -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(', ') : '—', +
+
+ + Quantity updated 1,450 to 1,500; Cost updated 87,000 to 90,000 + + + +
+ + {isExpanded && ( +
+
+
+
Quantity Updated:
+
1,450 → 1,500
+
+
+
Cost Updated:
+
87,000 → 90,000
+
+
+
+
Reason for variations:
+
Seasonal Production Surge
+
+
+ )} +
+ ]; + }); const toolbar = (
-

Quarter Overview

+

Submission History

{loading && Loading…} {error && {error}} @@ -218,20 +293,121 @@ const History = () => {
); + // 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 (
+ {/* Card Section */} +
+
+
+
+

Establishment ID:

+

{totalProducts}

+
+ {/*
+

Average Cost (AED):

+

{averageCost.toLocaleString('en-IN')}

+
*/} +
+
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+
+
+
{ - if (ci === 6) { + if (ci === 3) { return {value}; } - if (ci === headers.length - 1) { + if (ci === 5) { const submission = filteredRows[ri]; const disabled = !submission; return ( diff --git a/ipi-survey-platform/src/pages/Overview/Overview.jsx b/ipi-survey-platform/src/pages/Overview/Overview.jsx index 021de32..86bf97a 100644 --- a/ipi-survey-platform/src/pages/Overview/Overview.jsx +++ b/ipi-survey-platform/src/pages/Overview/Overview.jsx @@ -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" /> { 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' + }`} > ExportExport CSV @@ -179,6 +176,41 @@ const Overview = () => { setSelectedSubmission(filteredRows[rowIndex]); }; + if (loading) { + return ( +
+ +
+
+
+
+
+
+ ); + } + + if (error) { + return ( +
+ +
+
+
+
+ + + +
+
+

{error}

+
+
+
+
+
+ ); + } + if (selectedSubmission) { return (
@@ -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 ( {value} ); } - if (columnIndex === 8) { + if (columnIndex === 8) { // Action column return ( ); } - return {value}; + return value; }} /> @@ -237,4 +269,4 @@ const Overview = () => { ); }; -export default Overview; +export default Overview; \ No newline at end of file diff --git a/ipi-survey-platform/src/pages/Survey/Survey.jsx b/ipi-survey-platform/src/pages/Survey/Survey.jsx index d0b38f2..d90d2cf 100644 --- a/ipi-survey-platform/src/pages/Survey/Survey.jsx +++ b/ipi-survey-platform/src/pages/Survey/Survey.jsx @@ -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); diff --git a/ipi-survey-platform/src/services/submissions/submissionService.js b/ipi-survey-platform/src/services/submissions/submissionService.js index f480e3b..f394920 100644 --- a/ipi-survey-platform/src/services/submissions/submissionService.js +++ b/ipi-survey-platform/src/services/submissions/submissionService.js @@ -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, -}; +}; \ No newline at end of file