From e0362de08c953d5996caf5f1f5a95e3499c6bc74 Mon Sep 17 00:00:00 2001 From: Malini Date: Thu, 30 Oct 2025 22:16:10 +0530 Subject: [PATCH] Added submission history for establishedment user --- .../public/assets/images/Duedate.svg | 3 + .../images/mdi_file-find-outline-active.svg | 3 + .../assets/images/mdi_file-find-outline.svg | 3 + .../assets/images/mdi_page-next-outline.svg | 3 + .../assets/images/mingcute_time-line.svg | 3 + ipi-survey-platform/src/App.jsx | 9 + .../src/components/admin/AdminHeader.jsx | 10 + .../src/components/common/ProductTable.jsx | 109 +++++ .../src/components/common/Table.jsx | 8 +- .../src/components/layout/HeaderBar.jsx | 39 +- .../components/overview/DetailedOverview.jsx | 262 ++++++++++++ .../components/overview/ProductDetails.jsx | 228 +++++++++++ .../EstablishmentInfo/EstablishmentInfo.jsx | 176 +++++--- .../survey/ProductData/ProductData.jsx | 375 ++++++++++++------ .../src/pages/History/History.jsx | 12 +- .../src/pages/Overview/Overview.jsx | 240 +++++++++++ .../src/pages/Overview/Overviewreview.jsx | 240 +++++++++++ .../src/pages/Survey/Survey.jsx | 79 +++- 18 files changed, 1608 insertions(+), 194 deletions(-) create mode 100644 ipi-survey-platform/public/assets/images/Duedate.svg create mode 100644 ipi-survey-platform/public/assets/images/mdi_file-find-outline-active.svg create mode 100644 ipi-survey-platform/public/assets/images/mdi_file-find-outline.svg create mode 100644 ipi-survey-platform/public/assets/images/mdi_page-next-outline.svg create mode 100644 ipi-survey-platform/public/assets/images/mingcute_time-line.svg create mode 100644 ipi-survey-platform/src/components/common/ProductTable.jsx create mode 100644 ipi-survey-platform/src/components/overview/DetailedOverview.jsx create mode 100644 ipi-survey-platform/src/components/overview/ProductDetails.jsx create mode 100644 ipi-survey-platform/src/pages/Overview/Overview.jsx create mode 100644 ipi-survey-platform/src/pages/Overview/Overviewreview.jsx diff --git a/ipi-survey-platform/public/assets/images/Duedate.svg b/ipi-survey-platform/public/assets/images/Duedate.svg new file mode 100644 index 0000000..b54e21b --- /dev/null +++ b/ipi-survey-platform/public/assets/images/Duedate.svg @@ -0,0 +1,3 @@ + + + diff --git a/ipi-survey-platform/public/assets/images/mdi_file-find-outline-active.svg b/ipi-survey-platform/public/assets/images/mdi_file-find-outline-active.svg new file mode 100644 index 0000000..33335ea --- /dev/null +++ b/ipi-survey-platform/public/assets/images/mdi_file-find-outline-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/ipi-survey-platform/public/assets/images/mdi_file-find-outline.svg b/ipi-survey-platform/public/assets/images/mdi_file-find-outline.svg new file mode 100644 index 0000000..f54e21a --- /dev/null +++ b/ipi-survey-platform/public/assets/images/mdi_file-find-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/ipi-survey-platform/public/assets/images/mdi_page-next-outline.svg b/ipi-survey-platform/public/assets/images/mdi_page-next-outline.svg new file mode 100644 index 0000000..4cc528f --- /dev/null +++ b/ipi-survey-platform/public/assets/images/mdi_page-next-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/ipi-survey-platform/public/assets/images/mingcute_time-line.svg b/ipi-survey-platform/public/assets/images/mingcute_time-line.svg new file mode 100644 index 0000000..a3ce1fe --- /dev/null +++ b/ipi-survey-platform/public/assets/images/mingcute_time-line.svg @@ -0,0 +1,3 @@ + + + diff --git a/ipi-survey-platform/src/App.jsx b/ipi-survey-platform/src/App.jsx index a0f6652..d827c71 100644 --- a/ipi-survey-platform/src/App.jsx +++ b/ipi-survey-platform/src/App.jsx @@ -7,6 +7,7 @@ import Validations from '@/pages/Admin/Validations'; import ValidationReview from '@/pages/Admin/ValidationReview'; import Configuration from '@/pages/Admin/Configuration'; import Survey from '@/pages/Survey/Survey'; +import Overview from '@/pages/Overview/Overview'; import Login from '@/pages/Login/Login'; import History from '@/pages/History/History'; import ChangePassword from '@/pages/ChangePassword/ChangePassword'; @@ -101,6 +102,14 @@ function App() { )} /> + + + + )} + /> } /> } /> } /> diff --git a/ipi-survey-platform/src/components/admin/AdminHeader.jsx b/ipi-survey-platform/src/components/admin/AdminHeader.jsx index 4b27f34..98c773d 100644 --- a/ipi-survey-platform/src/components/admin/AdminHeader.jsx +++ b/ipi-survey-platform/src/components/admin/AdminHeader.jsx @@ -106,6 +106,16 @@ const AdminHeader = () => {

{userName}

{userEmail}

+ + ), + }, + ]; + + return ( +
+ + + + {columns.map((column) => ( + + ))} + + + + {products.map((product, index) => ( + + {columns.map((column) => ( + + ))} + + ))} + +
+ {column.header} +
+ {column.render ? column.render(product) : product[column.key]} +
+
+ ); +}; + +export default ProductTable; diff --git a/ipi-survey-platform/src/components/common/Table.jsx b/ipi-survey-platform/src/components/common/Table.jsx index 152019c..4ffeb93 100644 --- a/ipi-survey-platform/src/components/common/Table.jsx +++ b/ipi-survey-platform/src/components/common/Table.jsx @@ -148,7 +148,9 @@ const Table = ({ > - {headers.map((h, i) => { + {headers.map((header, i) => { + const headerText = typeof header === 'object' ? header.name : header; + const headerClass = typeof header === 'object' ? header.className : 'text-left'; const widthValue = columnWidths[i]; const widthStyle = widthValue ? { @@ -159,12 +161,12 @@ const Table = ({ return ( - {h} + {headerText} ); })} diff --git a/ipi-survey-platform/src/components/layout/HeaderBar.jsx b/ipi-survey-platform/src/components/layout/HeaderBar.jsx index 94bc91d..f7a57f9 100644 --- a/ipi-survey-platform/src/components/layout/HeaderBar.jsx +++ b/ipi-survey-platform/src/components/layout/HeaderBar.jsx @@ -8,6 +8,8 @@ const dashboardIconActiveSrc = '/assets/images/cuida_dashboard-outline.svg'; const dashboardIconInactiveSrc = '/assets/images/cuida_dashboard-outline-inactive.svg'; const historyIconActiveSrc = '/assets/images/mdi_history.svg'; const historyIconInactiveSrc = '/assets/images/history.svg'; +const overviewIconActiveSrc = '/assets/images/mdi_file-find-outline.svg'; +const overviewIconInactiveSrc = '/assets/images/mdi_file-find-outline-active.svg'; const HeaderBar = () => { const [open, setOpen] = React.useState(false); @@ -88,6 +90,17 @@ const HeaderBar = () => { )} + `inline-flex items-center gap-2 pb-1 border-b-2 ${isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] hover:text-gray-900 border-transparent'}`} + > + {({ isActive }) => ( + <> + Overview + Overview + + )} + `inline-flex items-center gap-2 pb-1 border-b-2 ${isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] hover:text-gray-900 border-transparent'}`} @@ -114,6 +127,16 @@ const HeaderBar = () => {

{userName}

{userEmail &&

{userEmail}

} + + diff --git a/ipi-survey-platform/src/components/overview/DetailedOverview.jsx b/ipi-survey-platform/src/components/overview/DetailedOverview.jsx new file mode 100644 index 0000000..ef5ecbf --- /dev/null +++ b/ipi-survey-platform/src/components/overview/DetailedOverview.jsx @@ -0,0 +1,262 @@ +import React, { useState, useCallback, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +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); + + // 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', + }, + ]; + + const filteredProducts = useMemo(() => { + const normalizedTerm = searchTerm.trim().toLowerCase(); + return productData.filter((product) => { + 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(); + return matchesSearch && matchesStatus; + }); + }, [searchTerm, selectedStatus, productData]); + + const downloadCsv = useCallback(() => { + if (!filteredProducts.length) return; + + const headers = [ + 'HS Code', + 'Product', + 'Unit', + 'Quantity', + 'Cost (AED)', + 'Status', + 'Submission Date & Time' + ]; + + const csvRows = [headers.join(',')]; + + filteredProducts.forEach((product) => { + csvRows.push([ + product.hsCode, + product.name, + product.unit, + product.quantity, + product.cost, + product.status, + product.submissionDate + ].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'); + link.href = url; + link.setAttribute('download', 'product-list.csv'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + 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 toolbar = ( + +
+
+

Selected Quarter: {submission.quarter}

+
+
+
+ setSearchTerm(event.target.value)} + placeholder="Search establishment" + className="w-full h-10 rounded-md border border-[#E2E8F0] pl-10 pr-3 text-sm text-[#232528] focus:outline-none" + /> + Search +
+
+ + Open +
+ +
+
+ ); + + const columns = [ + 'HS Code', + 'Product', + 'Unit', + 'Quantity', + 'Cost (AED)', + 'Status', + 'Submission Date & Time', + 'Action' + ]; + + // If a product is selected, show the product details view + if (selectedProduct) { + return ( + setSelectedProduct(null)} + /> + ); + } + + // Otherwise show the main overview + return ( +
+ {/* ----- EXISTING CARD SECTION ----- */} +
+
+
+ +
+

Establishment ID:

+

Al Khazna Investment

+
+
+

Total Submitted Products:

+

678

+
+
+

Average Cost:

+

7889

+
+
+
+
+ + + + +
+
+ + + + +
+
+
+
+ + {/* ----- ADDED TABLE SECTION BELOW CARD ----- */} +
+ + + + ); +}; + +export default DetailedOverview; diff --git a/ipi-survey-platform/src/components/overview/ProductDetails.jsx b/ipi-survey-platform/src/components/overview/ProductDetails.jsx new file mode 100644 index 0000000..e2fc855 --- /dev/null +++ b/ipi-survey-platform/src/components/overview/ProductDetails.jsx @@ -0,0 +1,228 @@ +import React 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 navigate = useNavigate(); + + if (!product) return null; + + // Merge product data with quarterly data + const productWithDetails = { ...product, ...quarterlyData }; + + return ( +
+
+
+
+ +

Product Details

+
+
+
+ +
+ {/* Basic Information */} +
+
+
+ +
+

+ {product.name} +

+
+
+
+ +
+

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

+
+
+
+ +
+

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

+
+
+
+ +
+ + {product.status || '—'} + +
+
+
+
+ + {/* Quarterly Data Table */} +
+
+ + + + + + + + + {/* Second header row */} + + + + + + + + + + + + + + + {/* Quantity Row */} + + + + + + + + + + + + + + {/* Cost Row */} + + + + + + + + + + + + + + {/* Reason Current */} + + + + + + + + {/* Reason Forecast */} + + + + + + +
+ Metric + + Q4-2024 (Previous Quarter) + + Q1-2025 (Current Quarter) + + Q2-2025 (Next Quarter) +
OctNovDecJanFebMarAprMayJun
+ Quantity ({product.unit || 'units'}) + {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 || '—'}
+ 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 || '—'}
+ Reason (Current) + + -- -- + + {product.variationReason || '—'} + + -- -- +
+ Reason (Forecast) + + -- -- + + {product.zeroTargetReason || '—'} +
+
+ + {/* Remarks */} +
+

Remarks

+
+ {product.remarks || 'No remarks provided for this product.'} +
+
+ + {/* Submission Info */} +
+
+
+

Submitted by: User Name

+

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

+
+
+

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

+

Status: + + {product.status} + +

+
+
+
+ + + ); +}; + +export default ProductDetails; diff --git a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx index 4ff671f..6aba5ce 100644 --- a/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx +++ b/ipi-survey-platform/src/components/survey/EstablishmentInfo/EstablishmentInfo.jsx @@ -3,6 +3,9 @@ const caretDownSrc = '/assets/images/CaretDown.svg'; const backVectorSrc = '/assets/images/BackVector.svg'; const mailIconSrc = '/assets/images/material-symbols_mail-outline.svg'; const phoneIconSrc = '/assets/images/line-md_phone.svg'; +const calendarIcon = '/assets/images/Duedate.svg'; +const clockIcon = '/assets/images/mingcute_time-line.svg'; +const nextIcon = '/assets/images/mdi_page-next-outline.svg'; const Field = ({ label, placeholder = '', type = 'text', value = '', onChange = () => {}, disabled = true }) => (
@@ -71,7 +74,13 @@ const EstablishmentInfo = ({ onBack = () => {}, loading = false, error = '', + isComplete = false, }) => { + const [showInfoCard, setShowInfoCard] = React.useState(true); + + const handleCloseInfo = () => { + setShowInfoCard(false); + }; const info = React.useMemo( () => ({ quarter: '', @@ -108,67 +117,137 @@ const EstablishmentInfo = ({ }); }; -return ( + return (
{loading && (
)} -

IPI Quarterly Survey — Establishment Information (Step 1 of 3)

+

Industrial Production Index (IPI) Survey: {data.quarter} {data.year}

+ {/* Survey Information */} -
-

Industrial Production Index (IPI) survey: {data.quarter} {data.year}

-

- You're completing the IPI Quarterly Survey for {data.quarter} {data.year}. This step shows establishment details already registered with us. Step 1 is read-only. To make corrections, update them in Profile → Establishment, then return to this survey. -

- -
-
-
-
-
- Due: - Jan 31, 2025 -
-
- Estimated time: - 15-20 minutes (step 1: 1-2 mins) -
-
- Next: - Product Data, then Review & Submit -
-
-
-
    -
  • After submission, responses are locked.A receipt appears in History after submission
  • -
  • - After submission, responses are Data is used for statistical reporting and handled per our privacy policy locked. A receipt appears in History. -
  • -
-
-
- {/* Email */} -
- email - support@ipi.gov.example -
- - {/* Phone */} -
- phone - +971-2-000-0000 + {!showInfoCard && ( + + )} + {showInfoCard && ( +
+ + +
+

+ You're completing the IPI Quarterly Survey for {data.quarter} {data.year}. This step shows establishment details already registered with us. Step 1 is read-only. To make corrections, update them in Profile → Edit Profile, then return to this survey. +

+ +
+
+
+
+
+ Calendar + Due: + Jan 31, 2025 +
+
+ Time + Estimated time: + 15-20 minutes (step 1: 1-2 minutes) +
+
+ Next + Next: + Add Product Data → Review & Submit +
+
+
+
    +
  • After submission: responses are locked. A receipt appears in History after submission
  • +
  • After submission: responses are used for statistical reporting and handled per our privacy policy
  • +
+
+
+
+ email + support@ipi.gov.example +
+
+ phone + +971-2-000-0000 +
+
+ )} + {/* Blue Info Box */} +
+ + + + {/*

+ Need to update details? Go to{' '} + Profile → Edit Profile. Changes saved there will appear{' '} + + here + . +

*/} +

+ Need to update details? Go to Profile →{' '} + { + e.preventDefault(); + // Store current URL to return after login + localStorage.setItem('returnTo', window.location.pathname); + window.location.href = e.currentTarget.href; + }} + > + Edit Profile + + . Changes saved there will appear here. +

+
+
+

Step 1: Review Establishment Information

+ {isComplete && ( + Completed + )}
- -
+
{/* Reporting Period */}

Reporting Period

@@ -180,6 +259,7 @@ return ( value={info.quarter} onChange={handleFieldChange('quarter')} disabled={true} + required className="block w-full h-10 rounded-md border-2 border-[#E6D7A2] focus:border-[#92722A] focus:ring-0 px-4 pr-10 text-sm bg-gray-50 appearance-none cursor-not-allowed" > diff --git a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx index 3f97db7..58f9b3e 100644 --- a/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx +++ b/ipi-survey-platform/src/components/survey/ProductData/ProductData.jsx @@ -7,6 +7,9 @@ import { } from '@/services/masters/masterService.js'; const caretDownSrc = '/assets/images/CaretDown-black.svg'; +const clockIcon = '/assets/images/mingcute_time-line.svg'; +const calendarIcon = '/assets/images/Duedate.svg'; +const nextIcon = '/assets/images/mdi_page-next-outline.svg'; const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => {}, required = false }) => { const [isFocused, setIsFocused] = React.useState(false); @@ -28,37 +31,63 @@ const Input = ({ placeholder = '', type = 'text', value = '', onChange = () => { ); }; -const Select = ({ placeholder = '', options = [], value = '', onChange }) => { +const Select = ({ + placeholder = '', + options = [], + value = '', + onChange, + loading = false, + disabled = false +}) => { const [isFocused, setIsFocused] = React.useState(false); return (
- open -
+ + {loading ? ( + + ) : ( + options.map((opt) => { + if (opt && typeof opt === 'object') { + const optionValue = opt.value ?? ''; + const optionLabel = opt.label ?? optionValue; + if (!optionValue && !optionLabel) return null; + return ( + + ); + } + return ( + + ); + }) + )} + +
+ {loading ? ( +
+ ) : ( + open + )} +
+
); }; @@ -107,7 +136,12 @@ const ProductData = ({ onProductsChange = () => {}, onNext = () => {}, onBack = () => {}, + loading = false, + error = '', }) => { + const [showDataDefinitions, setShowDataDefinitions] = React.useState(true); + const [showEntryRules, setShowEntryRules] = React.useState(true); + const [showInfoCard, setShowInfoCard] = React.useState(true); const MAX_PRODUCTS = 10; const nextId = React.useRef(products.length + 1); const [variationReasons, setVariationReasons] = React.useState([]); @@ -122,6 +156,7 @@ const ProductData = ({ const [unitOptions, setUnitOptions] = React.useState([]); const [isLoadingUnits, setIsLoadingUnits] = React.useState(false); const [unitsError, setUnitsError] = React.useState(''); + const [isInitialLoad, setIsInitialLoad] = React.useState(true); React.useEffect(() => { const variationController = new AbortController(); @@ -139,8 +174,8 @@ const ProductData = ({ setReasonsError(''); } catch (error) { if (error.name !== 'AbortError') { - console.error('Failed to fetch variation reasons', error); - setReasonsError('Failed to load variation reasons.'); + // console.error('Failed to fetch variation reasons', error); + // setReasonsError('Failed to load variation reasons.'); } } finally { setIsLoadingReasons(false); @@ -156,8 +191,8 @@ const ProductData = ({ setZeroReasonsError(''); } catch (error) { if (error.name !== 'AbortError') { - console.error('Failed to fetch zero target reasons', error); - setZeroReasonsError('Failed to load zero target reasons.'); + // console.error('Failed to fetch zero target reasons', error); + // setZeroReasonsError('Failed to load zero target reasons.'); } } finally { setIsLoadingZeroReasons(false); @@ -174,8 +209,8 @@ const ProductData = ({ setProductsError(''); } catch (error) { if (error.name !== 'AbortError') { - console.error('Failed to fetch products', error); - setProductsError('Failed to load products.'); + // console.error('Failed to fetch products', error); + // setProductsError('Failed to load products.'); } } finally { setIsLoadingProducts(false); @@ -191,18 +226,28 @@ const ProductData = ({ setUnitsError(''); } catch (error) { if (error.name !== 'AbortError') { - console.error('Failed to fetch units', error); - setUnitsError('Failed to load units.'); + // console.error('Failed to fetch units', error); + // setUnitsError('Failed to load units.'); } } finally { setIsLoadingUnits(false); } }; - loadReasons(); - loadZeroReasons(); - loadProducts(); - loadUnits(); + const loadAllData = async () => { + try { + await Promise.all([ + loadReasons(), + loadZeroReasons(), + loadProducts(), + loadUnits() + ]); + } finally { + setIsInitialLoad(false); + } + }; + + loadAllData(); return () => { variationController.abort(); zeroController.abort(); @@ -255,93 +300,143 @@ const ProductData = ({ const canAddMoreProducts = products.length < MAX_PRODUCTS; + const isLoading = loading || isLoadingReasons || isLoadingZeroReasons || isLoadingProducts || isLoadingUnits; + const hasError = error || reasonsError || zeroReasonsError || productsError || unitsError; + return ( -
+
+ {isLoading && ( +
+
+
+ )} +

Step 2: Product Data — Monthly Output & Cost

+ {!showInfoCard && ( + + )} + {/* Informational Section */} -
-

- Why we collect monthly product data -

-

- This step collects monthly output quantity and production cost for each product you manufacture or process. The data inform the - Industrial Production Index (IPI) and capacity-utilization statistics. - Results are published only in aggregated form and your information is protected - by national statistics confidentiality provisions. -

+ {showInfoCard && ( +
+ +
+

Why we collect monthly product data

+

+ This step collects monthly output quantity and production cost for each product you manufacture or process. The data inform the Industrial Production Index (IPI) and capacity-utilization statistics. Results are published only in aggregated form and your information is protected by national statistics confidentiality provisions. +

+ +
+
+ + Due: + Jan 31, 2025 +
-
-
-
- Due: - Jan 31, 2025 -
-
- Estimated time: - Depends on number of products -
-
- What you'll do: - Add products → select HS code & unit → enter monthly Qty & Cost →set next-quarter targets -
+
+ + Estimated time: + Depends on number of products +
+ +
+ + What you'll do: + Add products → select HS code & unit → enter monthly Qty & Cost →set next-quarter targets. +
+
+ +
+
+
+ + Confidentiality: Used only for statistics; not shared for taxation or enforcement. +
- - - - - -
- -
-
-
- - Confidentiality: Used only for statistics; not shared for taxation or enforcement. - -
-
-
- - Completeness: Report all products with notable output in the quarter. - +
+
+ + Completeness: Report all products with notable output in the quarter. + +
+
+ )} + + {/* Data Definitions & Entry Rules Section */} +
+ {/* Left card - Data definitions */} + {showDataDefinitions && ( +
+ +

Data definitions

+

Product (HS Code – Name): Standard trade classification. Start typing to search by code or name.

+

Quantity (Qty): Physical output produced in the month, measured in the selected unit.

+

Cost (AED): Total production cost for that month, excluding VAT (e.g., materials, energy, direct labour, and manufacturing expenses). Do not include sales margins.

+

Annual Installed Capacity: Maximum achievable annual output under normal operating conditions with existing equipment; do not include extraordinary overtime.

+
+ )} + + {/* Right card - Entry rules */} + {showEntryRules && ( +
+ +

Entry rules & validation

+

Mandatory: All fields in this step are required except "Reason for variation/0 Target" and free-text Notes.

+

Monthly entries: Enter Qty and Cost for each month in the Current and Forecast quarters. Use 0 where there is no output or cost.

+

Number only: Use a decimal point where needed. No negative values.

+

Units: Use one measurement unit per product across all months.

+

Delete: Removing a product here affects this survey only; it does not change your profile.

+
+ )}
- {/* Info line section (Already exists) */} - - {/* Data Definitions & Entry Rules Section */} -
- {/* Left card - Data definitions */} -
-

Data definitions

-

Product (HS Code – Name): Standard trade classification. Start typing to search by code or name.

-

Quantity (Qty): Physical output produced in the month, measured in the selected unit.

-

Cost (AED): Total production cost for that month, excluding VAT (e.g., materials, energy, direct labour, and manufacturing expenses). Do not include sales margins.

-

Annual Installed Capacity: Maximum achievable annual output under normal operating conditions with existing equipment; do not include extraordinary overtime.

-
- - {/* Right card - Entry rules */} -
-

Entry rules & validation

-

Mandatory: All fields in this step are required except "Reason for variation/0 Target" and free-text Notes.

-

Monthly entries: Enter Qty and Cost for each month in the Current and Forecast quarters. Use 0 where there is no output or cost.

-

Number only: Use a decimal point where needed. No negative values.

-

Units: Use one measurement unit per product across all months.

-

Delete: Removing a product here affects this survey only; it does not change your profile.

-
-
- -

Product Data

- {!canAddMoreProducts && ( @@ -367,7 +462,7 @@ const ProductData = ({
- +
- + updateProductField(p.id, 'capacity', e.target.value)} + required />
- +
@@ -410,6 +506,7 @@ const ProductData = ({ updateProductField(p.id, 'previousQuantity', e.target.value)} /> @@ -419,6 +516,7 @@ const ProductData = ({ updateProductField(p.id, 'novQuantity', e.target.value)} /> @@ -428,6 +526,7 @@ const ProductData = ({ updateProductField(p.id, 'decQuantity', e.target.value)} /> @@ -440,6 +539,7 @@ const ProductData = ({ updateProductField(p.id, 'octCost', e.target.value)} /> @@ -449,6 +549,7 @@ const ProductData = ({ updateProductField(p.id, 'novCost', e.target.value)} /> @@ -457,6 +558,7 @@ const ProductData = ({ updateProductField(p.id, 'decCost', e.target.value)} @@ -466,7 +568,7 @@ const ProductData = ({
- +
@@ -492,7 +594,7 @@ const ProductData = ({ />
- +
- +
- +
- + - +
- +
- + updateProductField(p.id, 'mayQuantity', e.target.value)} />
-
- +
+ updateProductField(p.id, 'junQuantity', e.target.value)} />
- +
- +
- +
- + setSearchTerm(event.target.value)} + placeholder="Search establishment" + className="w-full h-10 rounded-md border border-[#E2E8F0] pl-10 pr-3 text-sm text-[#232528] focus:outline-none" + /> + Search +
+
+ + Open +
+ +
+
+ ); + + const handleViewDetails = (rowIndex) => { + setSelectedSubmission(filteredRows[rowIndex]); + }; + + if (selectedSubmission) { + return ( +
+ +
+ setSelectedSubmission(null)} + /> +
+
+ ); + } + + return ( +
+ +
+
+

Overview

+
+
+ { + if (columnIndex === 6) { + const normalized = String(value).toLowerCase(); + const style = statusStyles[normalized] || 'text-[#232528] bg-[#F8FAFC] '; + return ( + + {value} + + ); + } + if (columnIndex === 8) { + return ( + + ); + } + return {value}; + }} + /> + + + + ); +}; + +export default Overview; diff --git a/ipi-survey-platform/src/pages/Overview/Overviewreview.jsx b/ipi-survey-platform/src/pages/Overview/Overviewreview.jsx new file mode 100644 index 0000000..021de32 --- /dev/null +++ b/ipi-survey-platform/src/pages/Overview/Overviewreview.jsx @@ -0,0 +1,240 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +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] ', + submitted: 'text-[#003CFF] bg-[#E7F5FF]', + pending: 'text-[#B45309] bg-[#FEF2F2]', + 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 Overview = () => { + const [searchTerm, setSearchTerm] = useState(''); + const [selectedYear, setSelectedYear] = useState(''); + const [selectedQuarter, setSelectedQuarter] = useState(''); + const [selectedStatus, setSelectedStatus] = useState(''); + const [selectedSubmission, setSelectedSubmission] = useState(null); + const navigate = useNavigate(); + + const filteredRows = React.useMemo(() => { + const normalizedTerm = searchTerm.trim().toLowerCase(); + return mockOverview.filter((record) => { + const matchesSearch = + !normalizedTerm || + record.year.toLowerCase().includes(normalizedTerm) || + record.quarter.toLowerCase().includes(normalizedTerm) || + record.window.toLowerCase().includes(normalizedTerm) || + record.status.toLowerCase().includes(normalizedTerm); + const matchesStatus = + selectedStatus === '' || record.status.toLowerCase() === selectedStatus; + return matchesSearch && matchesStatus; + }); + }, [searchTerm, selectedStatus]); + + const rows = filteredRows.map((record) => [ + record.year, + record.quarter, + record.window, + record.totalProducts, + record.submittedProducts, + `AED ${formatCurrency(record.totalCost)}`, + record.status, + record.submittedOn, + '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.status, + record.submittedOn, + ].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'); + link.href = url; + link.setAttribute('download', 'quarter-overview.csv'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }, [filteredRows]); + + const toolbar = ( +
+
+

Quarter Overview

+
+
+
+ setSearchTerm(event.target.value)} + placeholder="Search establishment" + className="w-full h-10 rounded-md border border-[#E2E8F0] pl-10 pr-3 text-sm text-[#232528] focus:outline-none" + /> + Search +
+
+ + Open +
+ +
+
+ ); + + const handleViewDetails = (rowIndex) => { + setSelectedSubmission(filteredRows[rowIndex]); + }; + + if (selectedSubmission) { + return ( +
+ +
+ setSelectedSubmission(null)} + /> +
+
+ ); + } + + return ( +
+ +
+
+

Overview

+
+
+
{ + if (columnIndex === 6) { + const normalized = String(value).toLowerCase(); + const style = statusStyles[normalized] || 'text-[#232528] bg-[#F8FAFC] '; + return ( + + {value} + + ); + } + if (columnIndex === 8) { + return ( + + ); + } + return {value}; + }} + /> + + + + ); +}; + +export default Overview; diff --git a/ipi-survey-platform/src/pages/Survey/Survey.jsx b/ipi-survey-platform/src/pages/Survey/Survey.jsx index 0b39fb0..d0b38f2 100644 --- a/ipi-survey-platform/src/pages/Survey/Survey.jsx +++ b/ipi-survey-platform/src/pages/Survey/Survey.jsx @@ -7,12 +7,13 @@ import HeaderBar from '@/components/layout/HeaderBar'; import ProductData from '@/components/survey/ProductData/ProductData'; import ReviewSubmit from '@/components/survey/ReviewSubmit/ReviewSubmit'; -const checkCircleActiveSrc = '/assets/images/circleframe.svg'; +const checkCircleActiveSrc = '/assets/images/CircleFrame.svg'; const createEmptyProduct = (id) => ({ id, product: '', unit: '', + mand: '', capacity: '', previousQuantity: '', previousCost: '', @@ -318,26 +319,80 @@ const Survey = () => { products: productsData.map((product) => { const variationId = parseNumber(product.variationReason, null); const zeroTargetId = parseNumber(product.zeroTargetReason, null); + + // Map monthly data to period-specific fields return { - product_id: parseNumber(product.product), + product_id: parseNumber(product.productId) || parseNumber(product.product), unit_id: parseNumber(product.unit), annual_installed_capacity: stringify(product.capacity), - previous_quantity: stringify(product.previousQuantity), - previous_cost: stringify(product.previousCost), - current_quantity: stringify(product.currentQuantity), - current_cost: stringify(product.currentCost), - forecast_quantity: stringify(product.forecastQuantity), - forecast_cost: stringify(product.forecastCost), + + // Previous Quarter (Q4) - October, November, December + previous_quantity_period_one: stringify(product.octQuantity || '0'), + previous_quantity_period_two: stringify(product.novQuantity || '0'), + previous_quantity_period_three: stringify(product.decQuantity || '0'), + previous_cost_period_one: stringify(product.octCost || '0'), + previous_cost_period_two: stringify(product.novCost || '0'), + previous_cost_period_three: stringify(product.decCost || '0'), + + // Current Quarter (Q1) - January, February, March + current_quantity_period_one: stringify(product.janQuantity || '0'), + current_quantity_period_two: stringify(product.febQuantity || '0'), + current_quantity_period_three: stringify(product.marQuantity || '0'), + current_cost_period_one: stringify(product.janCost || '0'), + current_cost_period_two: stringify(product.febCost || '0'), + current_cost_period_three: stringify(product.marCost || '0'), + + // Forecast Quarter (Q2) - April, May, June + forecast_quantity_period_one: stringify(product.aprQuantity || '0'), + forecast_quantity_period_two: stringify(product.mayQuantity || '0'), + forecast_quantity_period_three: stringify(product.junQuantity || '0'), + forecast_cost_period_one: stringify(product.aprCost || '0'), + forecast_cost_period_two: stringify(product.mayCost || '0'), + forecast_cost_period_three: stringify(product.junCost || '0'), + + // Totals (kept for backward compatibility) + previous_quantity: stringify( + (parseNumber(product.octQuantity) || 0) + + (parseNumber(product.novQuantity) || 0) + + (parseNumber(product.decQuantity) || 0) + ), + previous_cost: stringify( + (parseNumber(product.octCost) || 0) + + (parseNumber(product.novCost) || 0) + + (parseNumber(product.decCost) || 0) + ), + current_quantity: stringify( + (parseNumber(product.janQuantity) || 0) + + (parseNumber(product.febQuantity) || 0) + + (parseNumber(product.marQuantity) || 0) + ), + current_cost: stringify( + (parseNumber(product.janCost) || 0) + + (parseNumber(product.febCost) || 0) + + (parseNumber(product.marCost) || 0) + ), + forecast_quantity: stringify( + (parseNumber(product.aprQuantity) || 0) + + (parseNumber(product.mayQuantity) || 0) + + (parseNumber(product.junQuantity) || 0) + ), + forecast_cost: stringify( + (parseNumber(product.aprCost) || 0) + + (parseNumber(product.mayCost) || 0) + + (parseNumber(product.junCost) || 0) + ), + + // Other fields variation_reason_master_id: Number.isFinite(variationId) ? variationId : null, - other_variation_reason: stringify(product.otherVariationReason), + other_variation_reason: stringify(product.otherVariationReason || ''), zero_target_reason_master_id: Number.isFinite(zeroTargetId) ? zeroTargetId : null, - other_zero_target_reason: stringify(product.otherZeroTargetReason), - remarks: stringify(product.remarks), + other_zero_target_reason: stringify(product.otherZeroTargetReason || ''), + remarks: stringify(product.remarks || '') }; }), }; }, [establishmentData, parseNumber, productsData, stringify]); - + console.log("productsData",productsData) const handleSubmit = React.useCallback(async () => { setIsSubmitting(true); try {