added sort

This commit is contained in:
Malini 2025-11-28 18:22:08 +05:30
parent ccda98a6a5
commit 4a07c5e47c
5 changed files with 430 additions and 44 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="M4.19561 2.86196C4.32062 2.73698 4.49016 2.66677 4.66694 2.66677C4.84371 2.66677 5.01325 2.73698 5.13827 2.86196L7.80494 5.52863C7.92638 5.65436 7.99357 5.82276 7.99205 5.99756C7.99054 6.17236 7.92042 6.33957 7.79682 6.46317C7.67321 6.58678 7.506 6.65689 7.33121 6.65841C7.15641 6.65993 6.98801 6.59273 6.86227 6.47129L5.33361 4.94263V12.6666C5.33361 12.8434 5.26337 13.013 5.13834 13.138C5.01332 13.2631 4.84375 13.3333 4.66694 13.3333C4.49013 13.3333 4.32056 13.2631 4.19553 13.138C4.07051 13.013 4.00027 12.8434 4.00027 12.6666V4.94263L2.4716 6.47129C2.34587 6.59273 2.17747 6.65993 2.00267 6.65841C1.82787 6.65689 1.66066 6.58678 1.53706 6.46317C1.41345 6.33957 1.34334 6.17236 1.34182 5.99756C1.3403 5.82276 1.4075 5.65436 1.52894 5.52863L4.19561 2.86196ZM10.6669 11.0573V3.33329C10.6669 3.15648 10.7372 2.98691 10.8622 2.86189C10.9872 2.73686 11.1568 2.66663 11.3336 2.66663C11.5104 2.66663 11.68 2.73686 11.805 2.86189C11.93 2.98691 12.0003 3.15648 12.0003 3.33329V11.0573L13.5289 9.52863C13.6547 9.40719 13.8231 9.33999 13.9979 9.34151C14.1727 9.34303 14.3399 9.41314 14.4635 9.53675C14.5871 9.66035 14.6572 9.82756 14.6587 10.0024C14.6602 10.1772 14.593 10.3456 14.4716 10.4713L11.8049 13.138C11.6799 13.2629 11.5104 13.3332 11.3336 13.3332C11.1568 13.3332 10.9873 13.2629 10.8623 13.138L8.19561 10.4713C8.07417 10.3456 8.00697 10.1772 8.00849 10.0024C8.01001 9.82756 8.08012 9.66035 8.20373 9.53675C8.32733 9.41314 8.49454 9.34303 8.66934 9.34151C8.84414 9.33999 9.01254 9.40719 9.13827 9.52863L10.6669 11.0573Z" fill="#92722A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,3 @@
<svg width="14" height="11" viewBox="0 0 14 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.85381 0.195333C2.97883 0.0703528 3.14837 0.000142415 3.32514 0.000142415C3.50192 0.000142415 3.67146 0.0703528 3.79647 0.195333L6.46314 2.862C6.58458 2.98774 6.65178 3.15614 6.65026 3.33093C6.64874 3.50573 6.57863 3.67294 6.45502 3.79655C6.33142 3.92015 6.16421 3.99026 5.98941 3.99178C5.81461 3.9933 5.64621 3.92611 5.52047 3.80467L3.99181 2.276V10C3.99181 10.1768 3.92157 10.3464 3.79655 10.4714C3.67152 10.5964 3.50195 10.6667 3.32514 10.6667C3.14833 10.6667 2.97876 10.5964 2.85374 10.4714C2.72871 10.3464 2.65847 10.1768 2.65847 10V2.276L1.12981 3.80467C1.00407 3.92611 0.835672 3.9933 0.660874 3.99178C0.486076 3.99026 0.318868 3.92015 0.195262 3.79655C0.0716568 3.67294 0.00154415 3.50573 2.52018e-05 3.33093C-0.00149374 3.15614 0.0657025 2.98774 0.187141 2.862L2.85381 0.195333ZM9.32514 8.39067V0.666667C9.32514 0.489856 9.39538 0.320287 9.5204 0.195262C9.64543 0.070238 9.815 0 9.99181 0C10.1686 0 10.3382 0.070238 10.4632 0.195262C10.5882 0.320287 10.6585 0.489856 10.6585 0.666667V8.39067L12.1871 6.862C12.3129 6.74056 12.4813 6.67337 12.6561 6.67488C12.8309 6.6764 12.9981 6.74652 13.1217 6.87012C13.2453 6.99373 13.3154 7.16093 13.3169 7.33573C13.3184 7.51053 13.2512 7.67893 13.1298 7.80467L10.4631 10.4713C10.3381 10.5963 10.1686 10.6665 9.99181 10.6665C9.81503 10.6665 9.64549 10.5963 9.52047 10.4713L6.85381 7.80467C6.73237 7.67893 6.66517 7.51053 6.66669 7.33573C6.66821 7.16093 6.73832 6.99373 6.86193 6.87012C6.98553 6.74652 7.15274 6.6764 7.32754 6.67488C7.50234 6.67337 7.67074 6.74056 7.79647 6.862L9.32514 8.39067Z" fill="#9EA2A9"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -375,6 +375,10 @@ const IsicHsCodes = () => {
});
const [showImportModal, setShowImportModal] = React.useState(false);
const [isRefreshing, setIsRefreshing] = React.useState(false);
const [sortConfig, setSortConfig] = React.useState({
key: null,
direction: 'asc'
});
// Get user profile from session
const userProfile = React.useMemo(() => {
@ -459,7 +463,7 @@ const IsicHsCodes = () => {
if (product.created_by) {
try {
createdByName = product.created_by_user?.name || '-';
createdByName = product.created_by_name?.name || '-';
} catch (error) {
console.error('Error fetching creator info:', error);
}
@ -471,7 +475,7 @@ const IsicHsCodes = () => {
product: product.product_name || 'N/A',
unit: product.unit?.uom || 'N/A',
estimatedMapped: product.mapped_establishment_count || 0,
createdBy: createdByName,
createdBy: product.created_by_name || 'N/A',
createdOn: createdDate || null,
updated: updatedDate,
createdById: createdById,
@ -499,16 +503,100 @@ const IsicHsCodes = () => {
fetchProducts();
}, []);
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}
setSortConfig({ key, direction });
};
const getSortedData = (data) => {
if (!sortConfig.key) return data;
return [...data].sort((a, b) => {
let aValue = a[sortConfig.key];
let bValue = b[sortConfig.key];
if (sortConfig.key === 'estimatedMapped') {
return sortConfig.direction === 'asc'
? aValue - bValue
: bValue - aValue;
}
if (aValue < bValue) {
return sortConfig.direction === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sortConfig.direction === 'asc' ? 1 : -1;
}
return 0;
});
};
const headers = [
'HS Code',
'Product Name',
'Description',
'Unit',
'Establishment Mapped',
'Created By',
'Last Updated',
'Action',
];
{
name: <span className="whitespace-nowrap">HS Code</span>,
key: 'code',
sortable: true
},
{
name: <span className="whitespace-nowrap">Product Name</span>,
key: 'product',
sortable: true
},
{
name: <span className="whitespace-nowrap">Description</span>,
key: 'description',
sortable: true
},
{
name: <span className="whitespace-nowrap">Unit</span>,
key: 'unit',
sortable: true
},
{
name: <span className="whitespace-nowrap">Establishment Mapped</span>,
key: 'estimatedMapped',
sortable: true
},
{
name: <span className="whitespace-nowrap">Created By</span>,
key: 'createdBy',
sortable: true
},
{
name: <span className="whitespace-nowrap">Last Updated</span>,
key: 'updated',
sortable: true
},
{
name: 'Actions',
key: 'actions',
sortable: false
}
].map(header => {
if (!header.sortable) return { name: header.name };
const isActive = sortConfig.key === header.key;
const isAsc = sortConfig.direction === 'asc';
return {
name: (
<div
className="flex items-center cursor-pointer gap-1"
onClick={() => handleSort(header.key)}
>
<span>{header.name}</span>
<img
src={isActive ? "/assets/images/active-sort.svg" : "/assets/images/inactive-sort.svg"}
alt="Sort"
className={`w-3 h-3 transition-transform ${isActive && isAsc ? 'transform rotate-180' : ''}`}
/>
</div>
)
};
});
// Filter data based on search term
React.useEffect(() => {
@ -527,7 +615,9 @@ const IsicHsCodes = () => {
setCurrentPage(1); // Reset to first page when searching
}, [searchTerm, rowsData]);
const rows = filteredData.map((item) => {
const sortedData = getSortedData(filteredData);
const rows = sortedData.map((item) => {
let displayName = item.createdBy;
if (displayName === '-' && item.createdById) {
try {
@ -548,7 +638,7 @@ const IsicHsCodes = () => {
item.product,
item.description || '-',
item.unit,
item.estimatedMapped,
<div className="whitespace-nowrap">{item.estimatedMapped}</div>,
displayName,
item.updated || item.createdOn || '-',
'actions',
@ -1212,10 +1302,21 @@ const IsicHsCodes = () => {
</div>
{/* Table - Desktop */}
<div className="hidden md:block">
<div className="hidden md:block w-full overflow-x-auto">
<Table
headers={headers}
rows={rows}
columnWidths={[
'140px', // HS Code
'250px', // Product Name
'200px', // Description
'140px', // Unit
'170px', // Establishment Mapped
'150px', // Created By
'130px', // Last Updated
'130px' // Actions
]}
className="w-full"
renderCell={(value, rowIndex, colIndex) => {
// Handle product name and description columns with text wrapping
if (colIndex === 1 || colIndex === 2) {
@ -1225,6 +1326,13 @@ const IsicHsCodes = () => {
</div>
);
}
if (colIndex === 5 || colIndex === 6) {
return (
<div className="max-w-[200px] whitespace-normal break-words">
{value}
</div>
);
}
// Handle action buttons column (index 7)
if (colIndex === 7) {

View File

@ -89,6 +89,10 @@ const QuarterlyWindows = () => {
const [isLoading, setIsLoading] = React.useState(false);
const [validationErrors, setValidationErrors] = React.useState({});
const [toastData, setToastData] = React.useState(null);
const [sortConfig, setSortConfig] = React.useState({
key: null,
direction: 'asc'
});
// Format date to yyyy-MM-dd for input fields
const formatDateForInput = (dateString) => {
@ -142,19 +146,139 @@ const QuarterlyWindows = () => {
}, []);
// Table headers with left alignment and no wrapping
const handleSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}
setSortConfig({ key, direction });
};
const getSortedData = (data) => {
if (!sortConfig.key) return data;
return [...data].sort((a, b) => {
let aValue = a[sortConfig.key];
let bValue = b[sortConfig.key];
// Handle numeric comparisons
if (['year', 'assigned', 'responded', 'not_responded', 'submissionCount'].includes(sortConfig.key)) {
aValue = Number(aValue) || 0;
bValue = Number(bValue) || 0;
return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
}
// Handle date comparisons
if (['startDate', 'endDate'].includes(sortConfig.key)) {
aValue = aValue ? new Date(aValue).getTime() : 0;
bValue = bValue ? new Date(bValue).getTime() : 0;
return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
}
// Default string comparison
aValue = String(aValue || '').toLowerCase();
bValue = String(bValue || '').toLowerCase();
if (aValue < bValue) {
return sortConfig.direction === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sortConfig.direction === 'asc' ? 1 : -1;
}
return 0;
});
};
const headers = [
{ name: 'Survey Name', className: 'whitespace-nowrap text-left' },
{ name: 'Year', className: 'whitespace-nowrap text-left' },
{ name: 'Quarter', className: 'whitespace-nowrap text-left' },
{ name: 'Start Date', className: 'whitespace-nowrap text-left' },
{ name: 'End Date', className: 'whitespace-nowrap text-left' },
{ name: 'Grace Period', className: 'whitespace-nowrap text-left' },
{ name: 'Assigned', className: 'whitespace-nowrap text-left' },
{ name: 'Responded', className: 'whitespace-nowrap text-left' },
{ name: 'Not Responded', className: 'whitespace-nowrap text-left' },
{ name: 'Submission Count', className: 'whitespace-nowrap text-left' },
{ name: 'Actions', className: 'whitespace-nowrap text-left' },
];
{
name: 'Survey Name',
key: 'survey_name',
sortable: true,
className: 'whitespace-nowrap text-left'
},
{
name: 'Year',
key: 'year',
sortable: true,
className: 'whitespace-nowrap text-left'
},
{
name: 'Quarter',
key: 'quarter',
sortable: true,
className: 'whitespace-nowrap text-left'
},
{
name: 'Start Date',
key: 'startDate',
sortable: true,
className: 'whitespace-nowrap text-left'
},
{
name: 'End Date',
key: 'endDate',
sortable: true,
className: 'whitespace-nowrap text-left'
},
{
name: 'Grace Period',
key: 'gracePeriod',
sortable: false,
className: 'whitespace-nowrap text-left'
},
{
name: 'Assigned',
key: 'assigned',
sortable: false,
className: 'whitespace-nowrap text-left'
},
{
name: 'Responded',
key: 'responded',
sortable: false,
className: 'whitespace-nowrap text-left'
},
{
name: 'Not Responded',
key: 'not_responded',
sortable: false,
className: 'whitespace-nowrap text-left'
},
{
name: 'Submission Count',
key: 'submissionCount',
sortable: false,
className: 'whitespace-nowrap text-left'
},
{
name: 'Actions',
key: 'actions',
sortable: false,
className: 'whitespace-nowrap text-left'
},
].map(header => {
if (!header.sortable) return { name: header.name, className: header.className };
const isActive = sortConfig.key === header.key;
const isAsc = sortConfig.direction === 'asc';
return {
name: (
<div
className="flex items-center cursor-pointer gap-1"
onClick={() => handleSort(header.key)}
>
<span>{header.name}</span>
<img
src={isActive ? "/assets/images/active-sort.svg" : "/assets/images/inactive-sort.svg"}
alt="Sort"
className={`w-3 h-3 transition-transform ${isActive && isAsc ? 'transform rotate-180' : ''}`}
/>
</div>
),
className: header.className
};
});
const columnwidth = [
220, // Survey Name
@ -215,7 +339,9 @@ const QuarterlyWindows = () => {
</div>
);
const rows = filteredRows.map((item) => [
const sortedRows = getSortedData(filteredRows);
const rows = sortedRows.map((item) => [
item.survey_name || '-',
item.year,
item.quarter,

View File

@ -89,8 +89,53 @@ const UnitMaster = () => {
const [importError, setImportError] = useState('');
const fileInputRef = useRef(null);
const [pageSize, setPageSize] = React.useState(10);
// const pageSize = 10;
const [sortConfig, setSortConfig] = React.useState({
key: 'created_at',
direction: 'desc'
});
// Sort units based on sortConfig
const sortedUnits = React.useMemo(() => {
const sortableItems = [...units];
if (sortConfig.key) {
sortableItems.sort((a, b) => {
let aValue, bValue;
// Get the values to sort by
if (sortConfig.key === 'created_by') {
// For created_by, use created_by_name for sorting
aValue = (a.created_by_name || '').toString().toLowerCase();
bValue = (b.created_by_name || '').toString().toLowerCase();
} else if (sortConfig.key === 'created_at' || sortConfig.key === 'updated_at') {
// Handle date fields
aValue = a[sortConfig.key] ? new Date(a[sortConfig.key]) : new Date(0);
bValue = b[sortConfig.key] ? new Date(b[sortConfig.key]) : new Date(0);
} else {
// Handle other string fields
aValue = (a[sortConfig.key] || '').toString().toLowerCase();
bValue = (b[sortConfig.key] || '').toString().toLowerCase();
}
if (aValue < bValue) {
return sortConfig.direction === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sortConfig.direction === 'asc' ? 1 : -1;
}
return 0;
});
}
return sortableItems;
}, [units, sortConfig]);
// Handle sort request
const requestSort = (key) => {
let direction = 'asc';
if (sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}
setSortConfig({ key, direction });
};
// Fetch units on component mount
useEffect(() => {
@ -101,7 +146,7 @@ const UnitMaster = () => {
try {
setLoading(true);
const response = await getUnits();
// Handle different response formats
let unitsData = [];
if (Array.isArray(response)) {
@ -136,13 +181,19 @@ const UnitMaster = () => {
};
const filteredUnits = useMemo(() => {
if (!searchTerm) return units;
const term = searchTerm.toLowerCase();
return units.filter(unit =>
(unit.uom && unit.uom.toLowerCase().includes(term)) ||
(unit.description && unit.description.toLowerCase().includes(term))
);
}, [units, searchTerm]);
let result = [...sortedUnits];
// Apply search filter if searchTerm exists
if (searchTerm) {
const term = searchTerm.toLowerCase();
result = result.filter(unit =>
(unit.uom && unit.uom.toLowerCase().includes(term)) ||
(unit.description && unit.description.toLowerCase().includes(term))
);
}
return result;
}, [sortedUnits, searchTerm]);
useEffect(() => {
const userProfile = sessionStorage.getItem('user_profile');
@ -159,15 +210,110 @@ const UnitMaster = () => {
}, []);
const headers = [
'Unit Name',
'Description',
'Created By',
'Created On',
'Last Updated',
'Actions',
{
name: (
<div className="flex items-center gap-1">
<span>Unit Name</span>
<button
onClick={() => requestSort('uom')}
className="text-gray-400 hover:text-gray-600"
>
<img
src={sortConfig.key === 'uom' ? "/assets/images/active-sort.svg" : "/assets/images/inactive-sort.svg"}
alt="Sort"
className={`w-3 h-3 transition-transform ${sortConfig.key === 'uom' && sortConfig.direction === 'asc' ? 'transform rotate-180' : ''}`}
/>
</button>
</div>
),
key: 'uom',
sortable: true
},
{
name: (
<div className="flex items-center gap-1">
<span>Description</span>
<button
onClick={() => requestSort('description')}
className="text-gray-400 hover:text-gray-600"
>
<img
src={sortConfig.key === 'description' ? "/assets/images/active-sort.svg" : "/assets/images/inactive-sort.svg"}
alt="Sort"
className={`w-3 h-3 transition-transform ${sortConfig.key === 'description' && sortConfig.direction === 'asc' ? 'transform rotate-180' : ''}`}
/>
</button>
</div>
),
key: 'description',
sortable: true
},
{
name: (
<div className="flex items-center gap-1">
<span>Created By</span>
<button
onClick={() => requestSort('created_by_name')}
className="text-gray-400 hover:text-gray-600"
>
<img
src={sortConfig.key === 'created_by_name' ? "/assets/images/active-sort.svg" : "/assets/images/inactive-sort.svg"}
alt="Sort"
className={`w-3 h-3 transition-transform ${sortConfig.key === 'created_by_name' && sortConfig.direction === 'asc' ? 'transform rotate-180' : ''}`}
/>
</button>
</div>
),
key: 'created_by',
sortable: true
},
{
name: (
<div className="flex items-center gap-1">
<span>Created On</span>
<button
onClick={() => requestSort('created_at')}
className="text-gray-400 hover:text-gray-600"
>
<img
src={sortConfig.key === 'created_at' ? "/assets/images/active-sort.svg" : "/assets/images/inactive-sort.svg"}
alt="Sort"
className={`w-3 h-3 transition-transform ${sortConfig.key === 'created_at' && sortConfig.direction === 'asc' ? 'transform rotate-180' : ''}`}
/>
</button>
</div>
),
key: 'created_at',
sortable: true
},
{
name: (
<div className="flex items-center gap-1">
<span>Last Updated</span>
<button
onClick={() => requestSort('updated_at')}
className="text-gray-400 hover:text-gray-600"
>
<img
src={sortConfig.key === 'updated_at' ? "/assets/images/active-sort.svg" : "/assets/images/inactive-sort.svg"}
alt="Sort"
className={`w-3 h-3 transition-transform ${sortConfig.key === 'updated_at' && sortConfig.direction === 'asc' ? 'transform rotate-180' : ''}`}
/>
</button>
</div>
),
key: 'updated_at',
sortable: true
},
{
name: 'Actions',
key: 'actions',
sortable: false
},
];
const rows = useMemo(() => {
// Use filteredUnits which is already sorted
return filteredUnits.map((item) => {
const createdDate = item.created_at
? new Date(item.created_at).toLocaleDateString('en-GB')
@ -176,7 +322,7 @@ const UnitMaster = () => {
? new Date(item.updated_at).toLocaleDateString('en-GB')
: '-';
const createdByName = item.created_by_name || currentUser?.name || '-';
const createdByName = item.created_by_name ||'-';
return [
item.uom || '-',