This commit is contained in:
Senthamilselvi 2025-11-05 19:57:51 +05:30
commit 49e16e4fb3
6 changed files with 185 additions and 74 deletions

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.0306 8.53025L9.53063 13.0302C9.38973 13.1711 9.19863 13.2503 8.99938 13.2503C8.80012 13.2503 8.60902 13.1711 8.46813 13.0302C8.32723 12.8894 8.24807 12.6983 8.24807 12.499C8.24807 12.2997 8.32723 12.1086 8.46813 11.9677L11.6875 8.74962H2.5C2.30109 8.74962 2.11032 8.6706 1.96967 8.52995C1.82902 8.3893 1.75 8.19853 1.75 7.99962C1.75 7.80071 1.82902 7.60994 1.96967 7.46929C2.11032 7.32864 2.30109 7.24962 2.5 7.24962H11.6875L8.46937 4.02962C8.32848 3.88873 8.24932 3.69763 8.24932 3.49837C8.24932 3.29911 8.32848 3.10802 8.46937 2.96712C8.61027 2.82622 8.80137 2.74707 9.00062 2.74707C9.19988 2.74707 9.39098 2.82622 9.53187 2.96712L14.0319 7.46712C14.1018 7.53689 14.1573 7.61979 14.1951 7.71106C14.2329 7.80232 14.2523 7.90016 14.2522 7.99894C14.252 8.09773 14.2324 8.19552 14.1944 8.28669C14.1564 8.37787 14.1007 8.46064 14.0306 8.53025Z" fill="#7C2320"/>
</svg>

After

Width:  |  Height:  |  Size: 975 B

View File

@ -4,6 +4,20 @@ import { fetchSubmissionDetail } from '@/services/submissions/submissionService'
import Table from '@/components/common/Table';
import ProductDetails from './ProductDetails';
// Format date to readable format
const formatDate = (dateString) => {
if (!dateString) return '—';
const options = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: true
};
return new Date(dateString).toLocaleString('en-US', options);
};
const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const caretDownSrc = '/assets/images/CaretDown.svg';
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
@ -25,6 +39,25 @@ export const DetailedOverview = ({ submission, onBack }) => {
pageSizeOptions: [10, 20, 50, 100]
});
const getStatusBadge = (status) => {
const statusMap = {
'Approved': 'bg-[#F3FAF4] text-[#2F663C]',
'Submitted': 'bg-[#E7F5FF] text-[#003CFF]',
'Pending': 'bg-[#F9F7ED] text-[#7C5E24]',
'Rejected': 'bg-[#FEF2F2] text-[#B52520]'
};
const statusClass = statusMap[status] || 'bg-gray-100 text-gray-800';
return (
<span className={`inline-flex items-center rounded-md px-3 py-1 text-xs font-medium ${statusClass}`}>
{status || 'N/A'}
</span>
);
};
// Add state for tracking loading and error states
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
@ -217,24 +250,39 @@ export const DetailedOverview = ({ submission, onBack }) => {
unit.uom || 'N/A',
product.previous_quantity || '0',
`AED ${product.previous_cost || '0'}`,
<span
key={`status-${index}`}
className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs font-medium ${
product.is_active ? 'text-[#2F663C] bg-[#F3FAF4]' :
'text-[#7C5E24] bg-[#F9F7ED]'
}`}
>
{product.is_active ? 'Submitted' : 'Pending'}
{/* {product.is_active ? 'Rejected' : 'Rejected'} */}
// <span
// key={`status-${index}`}
// className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs font-medium ${
// product.is_active ? 'text-[#2F663C] bg-[#F3FAF4]' :
// 'text-[#7C5E24] bg-[#F9F7ED]'
// }`}
// >
// {product.is_active ? 'Submitted' : 'Pending'}
// {/* {product.is_active ? 'Rejected' : 'Rejected'} */}
// </span>,
// new Date(product.updated_at || product.created_at).toLocaleString('en-US', {
// year: 'numeric',
// month: 'short',
// day: 'numeric',
// hour: '2-digit',
// minute: '2-digit',
// hour12: true
// }),
<div className="flex items-start">
<span
key={`status-${index}`}
className={`inline-flex items-center justify-start rounded-md px-3 py-1 ml-[-18px] text-xs font-medium w-full ${getStatusBadge(submission.status)}`}
>
{getStatusBadge(submission.status) || 'Pending'}
</span>
</div>,
<span
key={`date-${index}`}
className="text-sm text-gray-600"
>
{formatDate(product.created_at) || '—'}
</span>,
new Date(product.updated_at || product.created_at).toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true
}),
<button
key={`view-${index}`}
onClick={() => handleViewDetails(product)}
@ -355,7 +403,18 @@ export const DetailedOverview = ({ submission, onBack }) => {
'Submission Date & Time',
'Action'
];
const columnWidths = {
'HS Code': '5%',
'Product': '1%',
'Unit': '15%',
'Quantity': '10%',
'Cost(AED)': '10%',
'Status': '15%',
'Submission Date & Time': '15%',
'Action': { width: '10%', className: 'justify-start' }
};
// Calculate total products and average cost
const totalProducts = filteredProducts?.length || 0;
const totalCost = filteredProducts?.reduce((sum, product) => {
@ -371,8 +430,8 @@ export const DetailedOverview = ({ submission, onBack }) => {
onBack={() => setSelectedProduct(null)}
showToast={showToast}
onToastShown={handleToastShown}
submissionDate={submissionData?.created_at || submissionData?.updated_at || new Date().toISOString()}
// submissionStatus={'R'} // Static status for testing
submissionDate={selectedProduct?.created_at || submissionData?.created_at || submissionData?.updated_at || new Date().toISOString()}
submissionStatus={selectedProduct?.status || submissionData?.status || 'submitted'}
/>
);
}
@ -438,7 +497,7 @@ export const DetailedOverview = ({ submission, onBack }) => {
<section className="bg-white rounded-lg border border-[#E2E8F0] shadow-sm">
<Table
headers={columns}
// columnwidth={columnwidth}
columnWidths={columnWidths}
rows={rows}
beforeHeader={toolbar}
separated

View File

@ -24,9 +24,9 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
if (shouldShowToast && !toastShownRef.current) {
const formattedDate = formatDate(submissionDate);
if (product?.is_active) {
const status = String(product.is_active).toLowerCase();
console.log("statuschecking",status)
if (submissionStatus) {
const status = String(submissionStatus).toLowerCase();
console.log("statuschecking", status);
const toastConfig = {
approved: {
type: 'approved',
@ -38,9 +38,10 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
rejected: {
type: 'rejected',
bgColor: 'bg-red-50',
textColor: 'text-red-700',
textColor: '#D83731',
iconColor: 'text-red-500',
message: 'Your survey has been rejected. Please review and resubmit.'
message: 'Your survey has been rejected. Please review and resubmit.',
// icon: 'warning-circle-fill-1.svg'
},
submitted: {
type: 'submitted',
@ -175,6 +176,19 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
</svg>
)}
<span className="text-sm font-medium">{toast.message}</span>
{toast.type === 'rejected' && (
<button
onClick={() => {
// Add your resubmit logic here
console.log('Resubmit button clicked');
// You can navigate to the edit page or trigger a resubmit function
}}
className="ml-auto px-3 py-1 bg-[#FEF2F2] text-[#7C2320] text-sm font-medium rounded hover:bg-[#FEF2F2] transition-colors"
>
Resubmit
<img src="/assets/images/arrow-right-bold 1.svg" alt="" className="w-4 h-4 ml-1 inline-block" />
</button>
)}
</div>
</div>
)}

View File

@ -11,14 +11,14 @@ const caretDownSrc = '/assets/images/CaretDown.svg';
const Badge = ({ variant = 'default', children }) => {
const styles = {
approved: 'bg-green-50 text-green-700 ring-1 ring-green-200',
submitted: 'bg-blue-50 text-blue-700 ring-1 ring-blue-200',
rejected: 'bg-red-50 text-red-600 ring-1 ring-red-200',
pending: 'bg-yellow-50 text-yellow-700 ring-1 ring-yellow-200',
default: 'bg-gray-50 text-gray-700 ring-1 ring-gray-200',
approved: 'bg-[#F3FAF4] text-[#2F663C]',
submitted: 'bg-[#E7F5FF] text-[#003CFF]',
pending: 'bg-[#F9F7ED] text-[#7C5E24]',
rejected: 'bg-[#FEF2F2] text-[#B52520]',
default: 'bg-gray-50 text-gray-700',
};
return (
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${styles[variant] || styles.default}`}>
<span className={`inline-flex items-center justify-center rounded-full text-xs font-medium px-3 py-1 ${styles[variant.toLowerCase()] || styles.default}`}>
{children}
</span>
);
@ -139,16 +139,6 @@ const History = () => {
{ name: 'Details', className: 'text-left flex-1 min-w-[260px]' }
];
const statusOptions = React.useMemo(
() => [
{ label: 'All Status', value: 'all' },
{ label: 'Approved', value: 'approved' },
{ label: 'Submitted', value: 'submitted' },
{ label: 'Pending', value: 'pending' },
{ label: 'Rejected', value: 'rejected' },
],
[]
);
const handleViewDetails = React.useCallback((submission) => {
if (!submission) return;
@ -175,6 +165,15 @@ const History = () => {
{ label: 'Q4', value: 'Q4' }
];
// Status options for the filter dropdown
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();
@ -193,6 +192,11 @@ const History = () => {
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 = [
@ -204,14 +208,16 @@ const History = () => {
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 && haystack.includes(term);
return matchesStatus && matchesYear && matchesQuarter && matchesHscode && haystack.includes(term);
}
return matchesStatus && matchesYear && matchesQuarter;
return matchesStatus && matchesYear && matchesQuarter && matchesHscode;
});
// Update total items when filtered rows change
@ -222,7 +228,7 @@ const History = () => {
}));
return filtered;
}, [rows, searchTerm, statusFilter, yearFilter, quarterFilter, toVariant, formatStatus, formatDate]);
}, [rows, searchTerm, statusFilter, yearFilter, quarterFilter, hscodeFilter, toVariant, formatStatus, formatDate]);
const formatNumber = (num) => {
if (num === null || num === undefined) return '—';
@ -427,13 +433,13 @@ const History = () => {
className="h-10 w-32 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
>
<option value="">Year</option>
{yearOptions.map(year => (
<option key={year} value={year}>{year}</option>
{yearOptions.map((year) => (
<option key={year} value={year}>
{year}
</option>
))}
</select>
<svg className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#6B7280]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9" />
</svg>
<img src={caretDownSrc} alt="Toggle" className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4" />
</div>
<div className="relative">
<select
@ -460,8 +466,8 @@ const History = () => {
>
<option value="">HS Code/Product</option>
{hscodeOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
<option key={option.id || option.value} value={option.id || option.value}>
{option.hs_code ? `${option.hs_code} - ${option.product_name || ''}` : option.label}
</option>
))}
</select>
@ -486,12 +492,15 @@ const History = () => {
</div>
<div className="relative">
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="h-10 w-32 rounded-md border border-[#E2E8F0] bg-white pl-3 pr-8 text-sm text-[#232528] focus:outline-none cursor-pointer appearance-none"
>
<option value="">Status</option>
<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>
<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" />
@ -502,12 +511,18 @@ const History = () => {
</div>
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-gray-200 mt-1">
<Table
headers={headers}
rows={tableRows}
loading={loading}
error={error}
className="mt-4"
{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>
) : (
<Table
headers={headers}
rows={tableRows}
loading={loading}
error={error}
className="mt-4"
pagination={{
currentPage: pagination.currentPage,
onPageChange: handlePageChange,
@ -517,10 +532,10 @@ const History = () => {
onPageSizeChange: handlePageSizeChange,
}}
renderCell={(value, ri, ci) => {
if (ci === 3) {
return <Badge variant={toVariant(filteredRows[ri]?.status)}>{value}</Badge>;
if (ci === 5) { // Status column
return <Badge variant={toVariant(filteredRows[ri]?.status)}>{formatStatus(filteredRows[ri]?.status)}</Badge>;
}
if (ci === 5) {
if (ci === 6) {
const submission = filteredRows[ri];
const disabled = !submission;
return (
@ -536,7 +551,8 @@ const History = () => {
}
return value;
}}
/>
/>
)}
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
// src/pages/Overview/Overview.jsx
import React, { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { getSubmissions } from '@/services/submissions/submissionService';
import { getSubmissions, getSubmissionHistoryByEstablishment } from '@/services/submissions/submissionService';
import HeaderBar from '@/components/layout/HeaderBar';
import Table from '@/components/common/Table';
import DetailedOverview from '@/components/overview/DetailedOverview';
@ -29,6 +29,7 @@ const Overview = () => {
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const establishmentId = 41; // You might want to get this from your auth context or props
const [pagination, setPagination] = useState({
currentPage: 1,
pageSize: 10,
@ -41,10 +42,11 @@ const Overview = () => {
const fetchSubmissions = async () => {
try {
setLoading(true);
const response = await getSubmissions(1, 1000); // Get all records
setSubmissions(response.data);
// Use getSubmissionHistoryByEstablishment instead of getSubmissions
const response = await getSubmissionHistoryByEstablishment(establishmentId);
setSubmissions(response.data || []);
} catch (err) {
setError('Failed to load submissions');
setError('Failed to load submission history');
console.error('Error:', err);
} finally {
setLoading(false);
@ -52,7 +54,7 @@ const Overview = () => {
};
fetchSubmissions();
}, []);
}, [establishmentId]);
const filteredRows = React.useMemo(() => {
const normalizedTerm = searchTerm.trim().toLowerCase();
@ -91,7 +93,7 @@ const Overview = () => {
`${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.total_cost !== undefined ? record.total_cost : '-', // Show total cost from API or '-' if not available
record.status,
formatDate(record.created_at),
'View Details',
@ -116,7 +118,7 @@ const Overview = () => {
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 headers = ['Year', 'Quarter', 'Submission Window', 'Total Products', 'Products Submitted', 'Total Cost (AED)', 'Status', 'Date Submitted', 'Action'];
const csvRows = [headers.join(',')];
filteredRows.forEach((record) => {
@ -126,7 +128,7 @@ const Overview = () => {
`${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.total_cost !== undefined ? record.total_cost : '-', // Show total cost from API or '-' if not available
record.status,
formatDate(record.created_at)
].map((value) => `"${String(value).replace(/"/g, '""')}"`).join(','));

View File

@ -2,7 +2,7 @@ import { getRequest, postRequest } from '@/services/api/CommonService';
import resolveEstablishmentId from '@/services/utils/establishment';
const endpoint = '/submissions';
const QUARTER_PERIODS_ENDPOINT = 'https://ipi.venbait.in/api/getQuarterPeriods';
const QUARTER_PERIODS_ENDPOINT = '/getQuarterPeriods';
// Add this new function to fetch submissions with pagination
export const getSubmissions = async (page = 1, limit = 100, config = {}) => {
@ -68,7 +68,7 @@ export const getQuarterPeriods = async (currentYear, currentQuarter) => {
export const getPreviousForecastData = async (establishmentId, quarter, year, productId) => {
try {
const response = await getRequest(
`https://ipi.venbait.in/api/submissions/getPreviousForecastData`,
'/submissions/getPreviousForecastData',
{
params: {
establishment_id: establishmentId,
@ -87,11 +87,28 @@ export const getPreviousForecastData = async (establishmentId, quarter, year, pr
}
};
export const getSubmissionHistoryByEstablishment = async (establishmentId, config = {}) => {
try {
if (!establishmentId) {
throw new Error('Establishment ID is required to fetch submission history');
}
const response = await getRequest(
`/submissions/history/${establishmentId}`,
config
);
return response.data;
} catch (error) {
console.error('Error fetching submission history by establishment ID:', error);
throw error;
}
};
export default {
getSubmissions,
submitSurvey,
fetchSubmissionHistory,
fetchSubmissionDetail,
getQuarterPeriods,
getPreviousForecastData
getPreviousForecastData,
getSubmissionHistoryByEstablishment
};