bug fixed in establishemnet

This commit is contained in:
Malini 2025-11-06 18:50:56 +05:30
parent 74174618db
commit 0e65ba4d13
9 changed files with 498 additions and 205 deletions

View File

@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.125 0C6.51803 0 4.94714 0.476523 3.611 1.36931C2.27485 2.2621 1.23344 3.53105 0.618482 5.0157C0.00352044 6.50035 -0.157382 8.13401 0.156123 9.71011C0.469628 11.2862 1.24346 12.7339 2.37976 13.8702C3.51606 15.0065 4.9638 15.7804 6.5399 16.0939C8.11599 16.4074 9.74966 16.2465 11.2343 15.6315C12.719 15.0166 13.9879 13.9752 14.8807 12.639C15.7735 11.3029 16.25 9.73197 16.25 8.125C16.2477 5.97081 15.391 3.90551 13.8677 2.38227C12.3445 0.85903 10.2792 0.00227486 8.125 0ZM7.5 4.375C7.5 4.20924 7.56585 4.05027 7.68306 3.93306C7.80027 3.81585 7.95924 3.75 8.125 3.75C8.29076 3.75 8.44974 3.81585 8.56695 3.93306C8.68416 4.05027 8.75 4.20924 8.75 4.375V8.75C8.75 8.91576 8.68416 9.07473 8.56695 9.19194C8.44974 9.30915 8.29076 9.375 8.125 9.375C7.95924 9.375 7.80027 9.30915 7.68306 9.19194C7.56585 9.07473 7.5 8.91576 7.5 8.75V4.375ZM8.125 12.5C7.93958 12.5 7.75833 12.445 7.60416 12.342C7.44999 12.239 7.32982 12.0926 7.25887 11.9213C7.18791 11.75 7.16934 11.5615 7.20552 11.3796C7.24169 11.1977 7.33098 11.0307 7.46209 10.8996C7.5932 10.7685 7.76025 10.6792 7.94211 10.643C8.12396 10.6068 8.31246 10.6254 8.48377 10.6964C8.65507 10.7673 8.80149 10.8875 8.90451 11.0417C9.00752 11.1958 9.0625 11.3771 9.0625 11.5625C9.0625 11.8111 8.96373 12.0496 8.78792 12.2254C8.6121 12.4012 8.37364 12.5 8.125 12.5Z" fill="#EA4F49"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -77,8 +77,9 @@ const Table = ({
const safeCurrentPage = enablePagination ? Math.min(Math.max(currentPage, 1), totalPages) : 1;
const startIndex = enablePagination ? (safeCurrentPage - 1) * pageSize : 0;
const endIndex = enablePagination ? startIndex + pageSize : rows.length;
// Calculate range start and end, handling the case when there are no items
const rangeStart = totalItems === 0 ? 0 : startIndex + 1;
const rangeEnd = enablePagination ? Math.min(endIndex, totalItems) : rows.length;
const rangeEnd = totalItems === 0 ? 0 : (enablePagination ? Math.min(endIndex, totalItems) : rows.length);
const displayedRows = React.useMemo(() => {
if (!enablePagination) return rows;

View File

@ -489,6 +489,15 @@ export const DetailedOverview = ({ submission, onBack }) => {
pagination={tablePagination}
/>
</section>
<button
onClick={onBack}
className="inline-flex items-center gap-2 h-9 px-4 rounded-md border border-[#92722A] text-[#92722A] bg-transparent hover:bg-[#92722A]/10"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
</svg>
<span>Back</span>
</button>
</div>
);
};

View File

@ -37,10 +37,12 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
rejected: {
type: 'rejected',
bgColor: 'bg-red-50',
textColor: '#D83731',
textColor: 'text-red-600',
iconColor: 'text-red-500',
message: 'Your survey has been rejected. Please review and resubmit.',
// icon: 'warning-circle-fill-1.svg'
icon: '/assets/images/Vector-rejected.svg',
iconAlt: 'Rejected',
iconClassName: 'mr-2 h-5 w-5 flex-shrink-0'
},
submitted: {
type: 'submitted',
@ -146,18 +148,11 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
/>
</svg>
) : toast.type === 'rejected' ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className={`w-5 h-5 mr-2 ${toast.iconColor}`}
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
clipRule="evenodd"
/>
</svg>
<img
src={toast.icon}
alt={toast.iconAlt || 'Rejected'}
className={toast.iconClassName || 'w-5 h-5 mr-2'}
/>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
@ -205,7 +200,7 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border border-[#E2E8F0] p-6">
<div className="bg-white rounded-lg shadow-sm border border-[#E2E8F0] p-6 mt-3">
{/* Basic Information */}
<div className="mb-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-4">
@ -353,7 +348,7 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
{formattedProduct.remarks}
</div>
</div>
{/* Submission Info */}
{/* <div className="mt-6 pt-4 border-t border-[#E2E8F0] text-sm">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
@ -380,6 +375,15 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
</div>
</div> */}
</div>
<button
onClick={onBack}
className="inline-flex items-center gap-2 h-9 px-4 mt-8 rounded-md border border-[#92722A] text-[#92722A] bg-transparent hover:bg-[#92722A]/10"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
</svg>
<span>Back</span>
</button>
</div>
);
};

View File

@ -178,12 +178,25 @@ const ProductData = ({
const [unitsError, setUnitsError] = React.useState('');
const [activeTab, setActiveTab] = React.useState('current');
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
const [remarks, setRemarks] = React.useState('');
const [remarks, setRemarks] = React.useState(() => {
if (typeof window !== 'undefined') {
const savedRemarks = localStorage.getItem('surveyRemarks');
return savedRemarks || '';
}
return '';
});
const [formErrors, setFormErrors] = React.useState({});
const [quarterPeriods, setQuarterPeriods] = useState(null);
const [isLoadingPeriods, setIsLoadingPeriods] = useState(false);
// Fetch quarter periods when component mounts or quarter/year changes
const handleRemarksChange = (e) => {
const newRemarks = e.target.value;
setRemarks(newRemarks);
if (typeof window !== 'undefined') {
localStorage.setItem('surveyRemarks', newRemarks);
}
};
useEffect(() => {
const fetchQuarterPeriods = async () => {
if (!quarter || !year) return;
@ -1239,7 +1252,7 @@ const ProductData = ({
<Textarea
placeholder="Enter your remarks here..."
value={remarks}
onChange={(e) => setRemarks(e.target.value)}
onChange={handleRemarksChange}
className="min-h-[60px]"
/>
</div>

View File

@ -254,7 +254,13 @@ const ReviewSubmit = ({
hasSubmitted = false,
}) => {
const [confirm, setConfirm] = React.useState(false);
const [remarks, setRemarks] = React.useState('');
const [remarks, setRemarks] = React.useState(() => {
// Load remarks from localStorage on component mount
if (typeof window !== 'undefined') {
return localStorage.getItem('surveyRemarks') || '';
}
return '';
});
const establishmentDetails = React.useMemo(() => buildEstablishmentDetails(establishment), [establishment]);
const employmentDetails = React.useMemo(() => buildEmploymentDetails(establishment), [establishment]);
const productRows = React.useMemo(() => buildProductRows(products), [products]);
@ -598,6 +604,15 @@ const ReviewSubmit = ({
</div>
</div>
{/* Remarks Section */}
{/* {remarks && (
<Card title="Remarks" className="mb-6">
<div className="p-4 bg-gray-50 rounded-md">
<p className="text-gray-700 whitespace-pre-line">{remarks}</p>
</div>
</Card>
)} */}
{/* Table */}
<div className="overflow-x-auto">
<table className="min-w-full border border-gray-300 text-sm text-center">

View File

@ -1,9 +1,10 @@
import React, { useEffect, useState, useCallback } from 'react';
import React, { useEffect, useState, useMemo,useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import Table from '@/components/common/Table';
import HeaderBar from '@/components/layout/HeaderBar';
import { fetchSubmissionHistory } from '@/services/submissions/submissionService.js';
// import { fetchSubmissionHistory } from '@/services/submissions/submissionService.js';
import { fetchProducts } from '@/services/masters/masterService';
import { fetchSubmissionHistory, getSubmissionAuditHistory } from '@/services/submissions/submissionService';
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const BreadcrumbVector = '/assets/images/Breadcrumb-vector.svg';
@ -35,6 +36,11 @@ const History = () => {
const [hscodeFilter, setHscodeFilter] = React.useState('');
const [hscodeOptions, setHscodeOptions] = React.useState([]);
const [isLoadingHscodes, setIsLoadingHscodes] = React.useState(false);
const [auditHistory, setAuditHistory] = useState([]);
const [isLoadingAudit, setIsLoadingAudit] = useState(false);
const [actorOptions, setActorOptions] = useState([]);
const [selectedActor, setSelectedActor] = useState('');
const [pagination, setPagination] = React.useState({
currentPage: 1,
pageSize: 10,
@ -59,6 +65,105 @@ const History = () => {
loadHscodeOptions();
}, []);
// useEffect(() => {
// const loadAuditHistory = async () => {
// setIsLoadingAudit(true);
// try {
// const establishmentId = 41; // Replace with dynamic ID if available
// const response = await getSubmissionAuditHistory(establishmentId, {
// year: yearFilter || undefined,
// quarter: quarterFilter || undefined,
// product_id: hscodeFilter || undefined
// });
// setAuditHistory(response?.data || []);
// setPagination(prev => ({
// ...prev,
// totalItems: response?.data?.length,
// currentPage: 1,
// }));
// } catch (error) {
// console.error('Error loading audit history:', error);
// setAuditHistory([]);
// } finally {
// setIsLoadingAudit(false);
// }
// };
// loadAuditHistory();
// }, [yearFilter, quarterFilter, hscodeFilter]);
useEffect(() => {
const loadAuditHistory = async () => {
setIsLoadingAudit(true);
try {
const establishmentId = 41; // Replace with dynamic ID if available
// Extract product_name from selected value ("HS - Product" "Product")
const productName = hscodeFilter.includes(" - ")
? hscodeFilter.split(" - ")[1].trim()
: hscodeFilter.trim();
console.log("📦 Loading audit history with:", {
yearFilter,
quarterFilter,
productName,
});
const params = {
year: yearFilter || undefined,
quarter: quarterFilter || undefined,
product_name: productName || undefined,
};
const response = await getSubmissionAuditHistory(establishmentId, params);
const data = response?.data || [];
setAuditHistory(data);
const actors = [...new Set(data.map(item => item.actor).filter(Boolean))];
setActorOptions(actors.map(actor => ({
value: actor,
label: actor
})));
setPagination((prev) => ({
...prev,
totalItems: data.length,
currentPage: 1,
}));
console.log("✅ Loaded audit history rows:", data.length);
} catch (error) {
console.error(" Error loading audit history:", error);
setAuditHistory([]);
} finally {
setIsLoadingAudit(false);
}
};
loadAuditHistory();
}, [yearFilter, quarterFilter, hscodeFilter , searchTerm , selectedActor]);
const formatExportDateTime = (value) => {
if (!value) return '-';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '-';
// Format as DD/MM/YYYY, HH:MM AM/PM
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
let hours = date.getHours();
const minutes = String(date.getMinutes()).padStart(2, '0');
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
return `${day}/${month}/${year}, ${hours}:${minutes} ${ampm}`;
};
const toVariant = React.useCallback((status) => {
const text = String(status || '').toLowerCase();
if (text === 'approved') return 'approved';
@ -169,66 +274,45 @@ const History = () => {
const statusOptions = [
{ value: 'all', label: 'All Status' },
{ value: 'approved', label: 'Approved' },
{ value: 'pending', label: 'Pending' },
{ value: 'rejected', label: 'Rejected' },
{ value: 'submitted', label: 'Submitted' },
];
const filteredRows = React.useMemo(() => {
const term = searchTerm.trim().toLowerCase();
const statusValue = statusFilter.toLowerCase();
const filtered = rows.filter((entry) => {
// Status filter
const matchesStatus =
statusValue === 'all' ||
toVariant(entry.status) === statusValue ||
formatStatus(entry.status).toLowerCase() === statusValue;
// Year filter
const matchesYear = !yearFilter || String(entry.year) === yearFilter;
// Quarter filter
const matchesQuarter = !quarterFilter ||
(entry.quarter && entry.quarter.toUpperCase() === quarterFilter.toUpperCase());
// HS Code/Product filter
const matchesHscode = !hscodeFilter ||
(entry.hs_code && entry.hs_code.toString() === hscodeFilter) ||
(entry.product_id && entry.product_id.toString() === hscodeFilter);
// Search term filter
if (term) {
const haystack = [
entry.year,
entry.quarter,
entry.submission_window,
entry.total_products,
entry.products_submitted,
entry.total_cost,
formatStatus(entry.status),
formatDate(entry.created_at),
entry.hs_code,
entry.product_name
]
.map((val) => String(val || '').toLowerCase())
.join(' ');
return matchesStatus && matchesYear && matchesQuarter && matchesHscode && haystack.includes(term);
}
return matchesStatus && matchesYear && matchesQuarter && matchesHscode;
});
// Update total items when filtered rows change
setPagination(prev => ({
...prev,
totalItems: filtered.length,
currentPage: 1, // Reset to first page when filter changes
}));
return filtered;
}, [rows, searchTerm, statusFilter, yearFilter, quarterFilter, hscodeFilter, toVariant, formatStatus, formatDate]);
const filteredRows = useMemo(() => {
const term = searchTerm.trim().toLowerCase();
const filtered = auditHistory.filter((entry) => {
const matchesSearch =
!term ||
Object.values(entry)
.join(' ')
.toLowerCase()
.includes(term);
const matchesYear = !yearFilter || String(entry.year) === yearFilter;
const matchesQuarter = !quarterFilter || entry.quarter?.toUpperCase() === quarterFilter.toUpperCase();
const matchesHscode = (() => {
if (!hscodeFilter) return true;
const productName = hscodeFilter.includes(' - ')
? hscodeFilter.split(' - ')[1].trim()
: hscodeFilter.trim();
return entry.product_name?.toLowerCase().includes(productName.toLowerCase());
})();
return matchesSearch && matchesYear && matchesQuarter && matchesHscode;
});
setPagination((prev) => ({
...prev,
totalItems: filtered.length,
currentPage: 1,
}));
return filtered;
}, [auditHistory, searchTerm, yearFilter, quarterFilter, hscodeFilter]);
const formatNumber = (num) => {
if (num === null || num === undefined) return '—';
@ -242,19 +326,29 @@ const History = () => {
};
// 🟩 Updated tableRows with expandable details
const handlePageChange = (newPage) => {
const handlePageChange = React.useCallback((page) => {
setPagination(prev => ({
...prev,
currentPage: newPage,
currentPage: page,
}));
};
}, []);
const handlePageSizeChange = (newSize) => {
const handlePageSizeChange = React.useCallback((newSize) => {
setPagination(prev => ({
...prev,
pageSize: newSize,
currentPage: 1, // Reset to first page when page size changes
}));
}, []);
// Calculate pagination range text
const getPaginationRangeText = () => {
if (filteredRows.length === 0) {
return '0-0 of 0';
}
const start = (pagination.currentPage - 1) * pagination.pageSize + 1;
const end = Math.min(start + pagination.pageSize - 1, pagination.totalItems);
return `${start}-${end} of ${pagination.totalItems}`;
};
const downloadCsv = () => {
@ -281,18 +375,76 @@ const History = () => {
// Convert data to CSV rows
const csvRows = [
headers.join(','),
...filteredRows.map(row =>
[
getFieldValue(formatDateTime(row.created_at)),
getFieldValue(row.year),
getFieldValue(row.quarter),
getFieldValue(row.hs_code),
getFieldValue(row.product_name),
getFieldValue(formatStatus(row.status)),
(row.actors && row.actors.length ? row.actors.join(', ') : '-').replace(/"/g, '""'),
'Quantity and cost updates' // This is a placeholder for the details column
].map(field => `"${field}"`).join(',')
)
...filteredRows.map(row => {
// Get actor(s) - handle both array and single value
let actors = '';
if (Array.isArray(row.actor)) {
actors = row.actor.filter(a => a && a.trim() !== '').join(', ');
} else if (row.actor) {
actors = row.actor;
} else {
actors = '-';
}
// Calculate quantity and cost changes from details
const details = row.details || [];
const quantityChange = details
.filter(d => d.column.includes('quantity'))
.reduce((sum, d) => sum + (Number(d.new) - Number(d.old)), 0);
const costChange = details
.filter(d => d.column.includes('cost'))
.reduce((sum, d) => sum + (Number(d.new) - Number(d.old)), 0);
// Format the summary line with actual values
const summaryParts = [];
if (details.some(d => d.column.includes('quantity'))) {
const change = details
.filter(d => d.column.includes('quantity'))
.reduce((sum, d) => sum + (Number(d.new) - Number(d.old)), 0);
if (change !== 0) {
summaryParts.push(`Quantity updated: ${change > 0 ? '+' : ''}${change.toLocaleString('en-IN')}`);
}
}
if (details.some(d => d.column.includes('cost'))) {
const change = details
.filter(d => d.column.includes('cost'))
.reduce((sum, d) => sum + (Number(d.new) - Number(d.old)), 0);
if (change !== 0) {
const absChange = Math.abs(change);
const sign = change > 0 ? '+' : '-';
summaryParts.push(`Cost updated: ${sign}AED ${absChange.toLocaleString('en-IN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}`);
}
}
const displayText = summaryParts.length > 0
? summaryParts.join('; ')
: 'No changes';
// Create the row with proper escaping
const rowData = [
formatExportDateTime(row.created_at || row.submission_date) || '-',
row.year || '-',
row.quarter || '-',
row.hs_code || '-',
row.product_name || '-',
formatStatus(row.status) || '-',
actors,
displayText // Use the summary line here instead of detailsText
];
// Properly escape and quote CSV fields
return rowData
.map(field => `"${String(field || '').replace(/"/g, '""')}"`)
.join(',');
})
];
// Create CSV content
@ -312,66 +464,98 @@ const History = () => {
document.body.removeChild(link);
};
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 -; Cost updated -
</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>
const tableRows = auditHistory.map((entry, index) => {
const isExpanded = expandedRow === index;
const details = entry.details || [];
// Check if there are quantity or cost changes
const hasQuantityChanges = details.some(d => d.column.includes('quantity'));
const hasCostChanges = details.some(d => d.column.includes('cost'));
{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>-</div>
</div>
<div>
<div className="font-medium">Cost Updated:</div>
<div>-</div>
</div>
</div>
<div>
<div className="font-medium">Reason for variations:</div>
<div>-</div>
</div>
</div>
)}
return [
formatDateTime(entry.submission_date || entry.created_at),
entry.year || '—',
entry.quarter || '—',
entry.hs_code || '—',
entry.product_name || '—',
<div className="flex justify-center">
<Badge variant={toVariant(entry.status)}>
{formatStatus(entry.status)}
</Badge>
</div>,
entry.actor || '—',
<div className="flex flex-col w-full">
<div className="flex items-center justify-between text-sm pt-2 text-gray-800">
<span>
{hasQuantityChanges ? 'Quantity updated' : ''}
{hasQuantityChanges && hasCostChanges ? '; ' : ''}
{hasCostChanges ? 'Cost updated' : ''}
{!hasQuantityChanges && !hasCostChanges ? 'No changes' : ''}
</span>
<button
onClick={(e) => {
e.stopPropagation();
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 && details.length > 0 && (
<div className="mt-2 ml-1 border border-gray-200 rounded p-2 bg-gray-50 text-sm text-gray-700">
<div className="grid grid-cols-2 gap-4">
<div>
<div className="font-medium">Quantity Updated: </div>
<div>
{details
.filter(d => d.column.includes('quantity'))
.reduce((sum, d) => sum + (Number(d.new) - Number(d.old)), 0)
.toLocaleString('en-IN')}
</div>
</div>
<div>
<div className="font-medium">Cost Updated:</div>
<div>
{details
.filter(d => d.column.includes('cost'))
.reduce((sum, d) => sum + (Number(d.new) - Number(d.old)), 0)
.toLocaleString('en-IN', {
style: 'currency',
currency: 'AED',
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}
</div>
</div>
<div>
<div className="font-medium">Reason for variations: -</div>
<div>
</div>
</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">
@ -386,11 +570,16 @@ const History = () => {
<div className="relative w-full md:w-56">
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search submissions"
className="w-full h-10 rounded-md border border-[#E5E7EB] pl-10 pr-3 text-sm focus:outline-none"
onChange={(e) => {
const value = e.target.value;
setSearchTerm(value);
console.log("🔍 Searching for:", value);
}}
className="w-64 h-10 border border-gray-300 rounded-md px-3 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
/>
<img src={searchIconSrc} alt="Search" className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5" />
</div>
<div className="relative w-full md:w-40">
@ -512,35 +701,60 @@ const History = () => {
<polyline points="6 9 12 15 18 9" />
</svg>
</div>
{/* Replace both HS Code and Product dropdowns with this */}
<div className="relative">
<select
value={hscodeFilter}
onChange={(e) => {
console.log("HS filter selected:", e.target.value);
setHscodeFilter(e.target.value);
}}
className="h-10 w-48 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
>
<option value="">HS Codes & Products</option>
{hscodeOptions.map((option) => (
<option
key={option.id}
value={option.product_name} // this is the fix
>
{option.hs_code
? `${option.hs_code} - ${option.product_name || ""}`.trim()
: option.label}
</option>
))}
</select>
{isLoadingHscodes ? (
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 border-2 border-t-[#6B7280] border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div>
) : (
<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
value={hscodeFilter}
onChange={(e) => setHscodeFilter(e.target.value)}
disabled={isLoadingHscodes}
className={`h-[40px] w-[170px] rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none ${isLoadingHscodes ? 'opacity-70' : ''}`}
>
<option value="">HS Code/Product</option>
{hscodeOptions.map((option) => (
<option key={option.id || option.value} value={option.id || option.value}>
{option.hs_code ? `${option.hs_code} - ${option.product_name || ''}` : option.label}
</option>
))}
</select>
{isLoadingHscodes ? (
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 border-2 border-t-[#6B7280] border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div>
) : (
<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"
disabled
>
<option value="">Actor</option>
</select>
<select
value={selectedActor}
onChange={(e) => setSelectedActor(e.target.value)}
className="h-10 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
>
<option value="">Actors</option>
{actorOptions.map((actor) => (
<option key={actor.value} value={actor.value}>
{actor.label}
</option>
))}
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#6B7280]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
</svg>
@ -553,7 +767,6 @@ const History = () => {
>
<option value="all">Status</option>
<option value="approved">Approved</option>
<option value="pending">Pending</option>
<option value="rejected">Rejected</option>
<option value="submitted">Submitted</option>
</select>
@ -576,7 +789,7 @@ const History = () => {
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search establishment"
placeholder="Search Product"
className="w-full h-10 rounded-md border border-[#E2E8F0] pl-10 pr-3 text-sm text-[#232528] focus:outline-none"
/>
<img
@ -599,10 +812,23 @@ const History = () => {
</div>
</div>
{filteredRows.length === 0 && !loading ? (
<div className="flex flex-col items-center justify-center py-12">
<p className="text-gray-500 text-lg">No details found</p>
{/* <p className="text-gray-400 text-sm mt-1">Try adjusting your search or filter to find what you're looking for.</p> */}
</div>
<div className="w-full flex flex-col h-5items-center justify-start pt-1 pb-7 text-center">
<div className="w-full max-w-md mx-auto">
{/* <svg
className="w-16 h-16 text-gray-400 mx-auto mb-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
</svg> */}
<h3 className="text-lg font-medium text-[#92722A] pt-4 text-center">No submissions History found</h3>
{/* <p className="mt-1 text-sm text-gray-500">
No submission history is available for the current filters
</p> */}
</div>
</div>
) : (
<Table
headers={headers}
@ -622,20 +848,20 @@ const History = () => {
if (ci === 5) { // Status column
return <Badge variant={toVariant(filteredRows[ri]?.status)}>{formatStatus(filteredRows[ri]?.status)}</Badge>;
}
if (ci === 6) {
const submission = filteredRows[ri];
const disabled = !submission;
return (
<button
className={`text-[#92722A] hover:underline ${disabled ? 'cursor-not-allowed opacity-50 hover:no-underline' : ''}`}
type="button"
disabled={disabled}
onClick={() => handleViewDetails(submission)}
>
{String(value)}
</button>
);
}
// if (ci === 6) {
// const submission = filteredRows[ri];
// const disabled = !submission;
// return (
// <button
// className={`text-[#92722A] hover:underline ${disabled ? 'cursor-not-allowed opacity-50 hover:no-underline' : ''}`}
// type="button"
// disabled={disabled}
// onClick={() => handleViewDetails(submission)}
// >
// {String(value)}
// </button>
// );
// }
return value;
}}
/>

View File

@ -314,6 +314,7 @@ const Survey = () => {
return {
quarter: establishmentData?.quarter ?? '',
year: parseNumber(establishmentData?.year),
status: 'Submitted',
emirati_male: parseNumber(info.emiratiMale),
emirati_female: parseNumber(info.emiratiFemale),
non_emirati_male: parseNumber(info.nonEmiratiMale),

View File

@ -103,6 +103,26 @@ export const getSubmissionHistoryByEstablishment = async (establishmentId, confi
}
};
export const getSubmissionAuditHistory = async (establishmentId, params = {}) => {
try {
if (!establishmentId) {
throw new Error('Establishment ID is required');
}
const response = await getRequest('/submission-audit-history', {
params: {
establishment_id: establishmentId,
...params // Spread any additional parameters (year, quarter, product_id)
}
});
return response.data || response;
} catch (error) {
console.error('Error fetching submission audit history:', error);
throw error;
}
};
export default {
getSubmissions,
submitSurvey,
@ -110,5 +130,6 @@ export default {
fetchSubmissionDetail,
getQuarterPeriods,
getPreviousForecastData,
getSubmissionHistoryByEstablishment
getSubmissionHistoryByEstablishment,
getSubmissionAuditHistory
};