changes in admin dasboard
This commit is contained in:
parent
546425a85c
commit
b2a01bb935
@ -30,7 +30,7 @@ const SurveyCarousel = ({
|
||||
setCurrentIndex((prev) => (prev === surveys.length - 1 ? 0 : prev + 1));
|
||||
|
||||
return (
|
||||
<div className="bg-black rounded-lg shadow-sm ring-1 w-[1275px] ring-gray-200 h-[148px] mb-10 ml-2 ">
|
||||
<div className="bg-black rounded-lg shadow-sm ring-1 w-[1275px] ring-gray-200 h-[148px] mt-[-10px] mb-10 ml-2 ">
|
||||
<div className="rounded-none bg-[#F2ECCF] px-8 py-10 flex items-center justify-between transition-all duration-500">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-[18px] leading-[28px] font-medium text-[#232528]">
|
||||
|
||||
@ -30,9 +30,9 @@ export const WelcomeSection = ({ data = null, loading = false, error = '' }) =>
|
||||
return (
|
||||
<div className="min-h-[4rem] bg-gray-50">
|
||||
<HeaderBar />
|
||||
<div className="max-w-[1280px] mx-auto px-4 pt-4 ">
|
||||
<div className="max-w-[1280px] mx-auto px-4 pt-4">
|
||||
<div className="space-y-1">
|
||||
<h2 className="w-[360px] text-[18px] pt-4 leading-[28px] font-medium text-[#232528]">
|
||||
<h2 className="w-[360px] text-[18px] pt-2 leading-[28px] font-medium text-[#232528]">
|
||||
Welcome{' '}
|
||||
{(() => {
|
||||
try {
|
||||
|
||||
@ -127,9 +127,7 @@ const EstablishmentInfo = ({
|
||||
React.useEffect(() => {
|
||||
// Only update if we don't already have survey data
|
||||
if (!surveyDataRef.current?.quarter && location.state?.survey) {
|
||||
const { quarter, year, endDate } = location.state.survey;
|
||||
console.log('Initial survey data:', { quarter, year, endDate });
|
||||
|
||||
const { quarter, year, endDate } = location.state.survey;
|
||||
const newSurveyData = {
|
||||
quarter: quarter || '',
|
||||
year: year || '',
|
||||
@ -198,8 +196,6 @@ const EstablishmentInfo = ({
|
||||
|
||||
localStorage.setItem('returnTo', window.location.pathname);
|
||||
sessionStorage.setItem('edit_profile_from', 'EstablishmentUser');
|
||||
|
||||
console.log('Navigating to:', `/profile/edit/${establishmentId}`);
|
||||
navigate(`/profile/edit/${establishmentId}`);
|
||||
};
|
||||
const handleEmployeeChange = (field) => (event) => {
|
||||
|
||||
@ -336,7 +336,6 @@ const location = useLocation();
|
||||
// Get survey data from navigation state if available
|
||||
if (location.state?.survey) {
|
||||
const { quarter, year, endDate } = location.state.survey;
|
||||
console.log('Received survey datassdd:', { quarter, year, endDate });
|
||||
setSurveyData({
|
||||
quarter: quarter || '',
|
||||
year: year || '',
|
||||
@ -635,9 +634,7 @@ const location = useLocation();
|
||||
};
|
||||
|
||||
|
||||
const handleProductSelect = async (productId, establishmentId, id) => {
|
||||
console.log('handleProductSelect called with:', { productId, establishmentId, id });
|
||||
|
||||
const handleProductSelect = async (productId, establishmentId, id) => {
|
||||
if (!productId || !establishmentId) {
|
||||
console.warn('Missing required parameters in handleProductSelect:', {
|
||||
hasProductId: !!productId,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import AdminHeader from '@/components/admin/AdminHeader';
|
||||
import StatCard from '@/components/admin/StatCard';
|
||||
import SubmissionTable from '@/components/admin/SubmissionTable';
|
||||
@ -29,8 +29,18 @@ const AdminDashboard = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedQuarter, setSelectedQuarter] = useState('All');
|
||||
const [selectedYear, setSelectedYear] = useState('All');
|
||||
const isMounted = useRef(false);
|
||||
const prevParams = useRef({ quarter: null, year: null });
|
||||
|
||||
const fetchDashboardData = React.useCallback(async (quarter, year) => {
|
||||
// Skip if params haven't changed
|
||||
if (prevParams.current.quarter === quarter && prevParams.current.year === year) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update previous params
|
||||
prevParams.current = { quarter, year };
|
||||
|
||||
const fetchDashboardData = async (quarter, year) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const params = new URLSearchParams();
|
||||
@ -44,13 +54,16 @@ const AdminDashboard = () => {
|
||||
}
|
||||
|
||||
const response = await apiClient.get(`/admin_dashboard?${params.toString()}`);
|
||||
|
||||
if (!isMounted.current) return;
|
||||
|
||||
const data = response?.data?.summary || {};
|
||||
const selectedQuarterFromApi = response?.data?.selected_quarter || quarter || 'All';
|
||||
const selectedYearFromApi = response?.data?.selected_year || year || 'All';
|
||||
const quarterlyWindowsData = response?.data?.quarterly_windows || {};
|
||||
console.log("selectedQuarter",selectedQuarterFromApi)
|
||||
console.log("selectedYear",selectedYearFromApi)
|
||||
|
||||
console.log("Fetching data for:", { quarter, year });
|
||||
|
||||
setSummary({
|
||||
total_establishments: data.total_establishments || 0,
|
||||
submitted: data.submitted || 0,
|
||||
@ -60,30 +73,39 @@ const AdminDashboard = () => {
|
||||
pending: data.pending || 0,
|
||||
});
|
||||
|
||||
setQuarterlyWindows({
|
||||
setQuarterlyWindows(prev => ({
|
||||
...prev,
|
||||
end_date: quarterlyWindowsData.end_date,
|
||||
start_date: quarterlyWindowsData.start_date,
|
||||
grace_periods_days: quarterlyWindowsData.grace_periods_days || 0
|
||||
});
|
||||
}));
|
||||
|
||||
setSelectedQuarter(selectedQuarterFromApi);
|
||||
setSelectedYear(selectedYearFromApi);
|
||||
// Only update these if they're different to prevent unnecessary re-renders
|
||||
setSelectedQuarter(prev => prev !== selectedQuarterFromApi ? selectedQuarterFromApi : prev);
|
||||
setSelectedYear(prev => prev !== selectedYearFromApi ? selectedYearFromApi : prev);
|
||||
} catch (error) {
|
||||
console.error('Error fetching dashboard summary:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (isMounted.current) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
fetchDashboardData(selectedQuarter, selectedYear);
|
||||
}, [selectedQuarter, selectedYear]);
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, [fetchDashboardData, selectedQuarter, selectedYear]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#F6F5F1]">
|
||||
<AdminHeader />
|
||||
<div className="max-w-[1280px] mx-auto box-border px-4 py-8 flex flex-col gap-6">
|
||||
<div className="flex items-center justify-between w-full max-w-[1280px] mx-auto mt-6 px-4">
|
||||
<div className="max-w-[1280px] mx-auto box-border px-4 py-4 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<h1 className="text-[18px] font-semibold text-[#232528]">
|
||||
Admin Dashboard{' '}
|
||||
<span className="font-normal text-[#5F646D]">– Survey Overview</span>
|
||||
|
||||
@ -66,7 +66,7 @@ const ManageSubmissions = () => {
|
||||
const [year, setYear] = React.useState('');
|
||||
const [quarter, setQuarter] = React.useState('');
|
||||
const [emirate, setEmirate] = React.useState('');
|
||||
const [status, setStatus] = React.useState('');
|
||||
const [status, setStatus] = React.useState('All');
|
||||
const [selectedQuarter, setSelectedQuarter] = React.useState('Q1');
|
||||
const [selectedYear, setSelectedYear] = React.useState(new Date().getFullYear().toString());
|
||||
const [currentPage, setCurrentPage] = React.useState(1);
|
||||
@ -303,8 +303,13 @@ const ManageSubmissions = () => {
|
||||
}, []);
|
||||
|
||||
const yearOptions = React.useMemo(() => {
|
||||
const years = ['All', ...new Set(submissions.map((i) => i.year))];
|
||||
return years.sort((a, b) => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [];
|
||||
// Generate years from 2022 to current year + 1 (to include current year)
|
||||
for (let y = 2022; y <= currentYear + 1; y++) {
|
||||
years.push(y.toString());
|
||||
}
|
||||
return ['All', ...years].sort((a, b) => {
|
||||
if (a === 'All') return -1;
|
||||
if (b === 'All') return 1;
|
||||
return b.localeCompare(a);
|
||||
@ -313,7 +318,10 @@ const ManageSubmissions = () => {
|
||||
|
||||
const quarterOptions = React.useMemo(() => ['All', 'Q1', 'Q2', 'Q3', 'Q4'], []);
|
||||
const emirateOptions = React.useMemo(() => ['All', ...new Set(submissions.map((i) => i.emirate))], [submissions]);
|
||||
const statusOptions = React.useMemo(() => ['All', ...new Set(submissions.map((i) => i.status))], [submissions]);
|
||||
const statusOptions = React.useMemo(() => {
|
||||
const statuses = [...new Set(submissions.map((i) => i.status))];
|
||||
return ['All', ...statuses];
|
||||
}, [submissions]);
|
||||
|
||||
const filtered = React.useMemo(() => {
|
||||
const term = search.trim().toLowerCase();
|
||||
|
||||
@ -492,7 +492,6 @@ const CompanyProfile = () => {
|
||||
onChange={handleFormChange('industryDescription')}
|
||||
placeholder="Enter Description"
|
||||
width="100%"
|
||||
required
|
||||
style={{ height: '72px' }}
|
||||
/>
|
||||
</div>
|
||||
@ -585,7 +584,6 @@ const CompanyProfile = () => {
|
||||
onChange={handleFormChange('contactMakaniNumber')}
|
||||
placeholder="Enter Makani Number"
|
||||
width="100%"
|
||||
required
|
||||
error={fieldErrors.contactMakaniNumber}
|
||||
/>
|
||||
<TextField
|
||||
@ -1284,13 +1282,17 @@ const corporateFieldKeys = Object.values(contactToCorporateMap);
|
||||
let creatorName = '-';
|
||||
if (currentUser && item?.created_by && currentUser.id === item.created_by) {
|
||||
// If current user is the creator, use their name
|
||||
creatorName = currentUser.name || currentUser.username || `User ${item.created_by}`;
|
||||
const name = currentUser.name || currentUser.username || `User ${item.created_by}`;
|
||||
// Ensure proper spacing between first name and last initial
|
||||
creatorName = name.replace(/([a-zA-Z])([A-Z])/g, '$1 $2');
|
||||
} else if (item?.created_by) {
|
||||
// If not the current user, try to get creator's name from the item or use ID as fallback
|
||||
creatorName = item?.created_by_name ||
|
||||
item?.created_by_user?.name ||
|
||||
item?.creator?.name ||
|
||||
`User ${item.created_by}`;
|
||||
const name = item?.created_by_name ||
|
||||
item?.created_by_user?.name ||
|
||||
item?.creator?.name ||
|
||||
`User ${item.created_by}`;
|
||||
// Ensure proper spacing between first name and last initial
|
||||
creatorName = name.replace(/([a-zA-Z])([A-Z])/g, '$1 $2');
|
||||
}
|
||||
|
||||
console.log('Creator ID:', item?.created_by, 'Current User ID:', currentUser?.id);
|
||||
@ -1894,7 +1896,11 @@ const requiredFields = [
|
||||
return `${day}/${month}/${year}`;
|
||||
};
|
||||
|
||||
const handleSaveForm = async () => {
|
||||
const handleSaveForm = async (e) => {
|
||||
// Prevent default form submission behavior
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (!validateStepFields(activeStep)) {
|
||||
return;
|
||||
}
|
||||
@ -2124,13 +2130,35 @@ const requiredFields = [
|
||||
}
|
||||
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
|
||||
// For edits, update the local state immediately
|
||||
if (modalMode === 'edit' && editingRow !== null) {
|
||||
const updatedProfiles = [...profiles];
|
||||
updatedProfiles[editingRow] = nextForm;
|
||||
setProfiles(updatedProfiles);
|
||||
} else {
|
||||
setProfiles((prev) => [nextForm, ...prev]);
|
||||
}
|
||||
|
||||
// For new records, wait for the API response before updating the UI
|
||||
if (modalMode !== 'edit') {
|
||||
if (apiResultData) {
|
||||
// Use the API response data to update the state
|
||||
const newProfile = mapApiEstablishmentToProfile(apiResultData);
|
||||
setProfiles(prev => [newProfile, ...prev]);
|
||||
}
|
||||
} else {
|
||||
// For edits, refresh the data to ensure consistency
|
||||
try {
|
||||
const response = await getEstablishments();
|
||||
if (response?.data) {
|
||||
const mappedProfiles = response.data.map(mapApiEstablishmentToProfile);
|
||||
setProfiles(mappedProfiles);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error refreshing company profiles:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset form and close modal
|
||||
const emptyProfile = createEmptyProfile();
|
||||
Object.assign(emptyProfile, computeEmploymentTotals(emptyProfile));
|
||||
setForm(emptyProfile);
|
||||
@ -2219,10 +2247,9 @@ const requiredFields = [
|
||||
)}
|
||||
<div className="px-6 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-[16px] font-medium text-[#232528]">Company Profile</h3>
|
||||
<span className="text-sm bg-gray-100 text-gray-600 px-2 py-0.5 rounded-full">
|
||||
{totalItems} {totalItems === 1 ? 'record' : 'records'}
|
||||
</span>
|
||||
<h3 className="text-[16px] font-medium text-[#232528]">
|
||||
Company Profile ({totalItems})
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative min-w-[260px]">
|
||||
@ -2328,7 +2355,7 @@ const requiredFields = [
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
},
|
||||
pageSize,
|
||||
totalItems: filteredProfiles.length, // Use filtered count for pagination
|
||||
totalItems: totalItems, // Use the total count from API response
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
onPageSizeChange: (size) => {
|
||||
setPageSize(size);
|
||||
|
||||
@ -446,14 +446,13 @@ const IsicHsCodes = () => {
|
||||
}, []);
|
||||
|
||||
const headers = [
|
||||
'Code',
|
||||
'HS Code',
|
||||
'Product Name',
|
||||
'Unit',
|
||||
'Mapped Establishments',
|
||||
'Created by',
|
||||
'Estimated Mapped',
|
||||
'Created By',
|
||||
'Last Updated',
|
||||
'Status',
|
||||
'Actions',
|
||||
'Action',
|
||||
];
|
||||
|
||||
// Filter data based on search term
|
||||
@ -495,7 +494,6 @@ const IsicHsCodes = () => {
|
||||
item.estimatedMapped,
|
||||
displayName,
|
||||
item.updated || item.createdOn || '-',
|
||||
item.status,
|
||||
'actions',
|
||||
];
|
||||
});
|
||||
@ -508,7 +506,6 @@ const IsicHsCodes = () => {
|
||||
setIsLoading(true);
|
||||
const productId = selected.id;
|
||||
console.log(`Fetching product details for ID: ${productId}`);
|
||||
|
||||
const response = await productService.getProductById(productId);
|
||||
console.log('Product details response:', response);
|
||||
|
||||
@ -769,8 +766,6 @@ const IsicHsCodes = () => {
|
||||
};
|
||||
|
||||
const handleSaveForm = async () => {
|
||||
console.log('handleSaveForm called with mode:', modalMode);
|
||||
|
||||
if (!form.code || !form.product) {
|
||||
const errorMsg = 'Please fill in all required fields';
|
||||
console.error(errorMsg);
|
||||
@ -790,24 +785,16 @@ const IsicHsCodes = () => {
|
||||
created_by: userProfile?.id || null,
|
||||
...(form.description && { hs_description: form.description })
|
||||
};
|
||||
|
||||
console.log('Saving product data:', JSON.stringify(productData, null, 2));
|
||||
|
||||
|
||||
if (modalMode === 'edit' && editingRow !== null) {
|
||||
const productId = rowsData[editingRow]?.id;
|
||||
if (!productId) {
|
||||
throw new Error('Product ID not found for editing');
|
||||
}
|
||||
|
||||
console.log(`Updating product with ID: ${productId}`);
|
||||
|
||||
|
||||
try {
|
||||
const response = await productService.updateProduct(productId, productData);
|
||||
console.log('Update response:', response);
|
||||
|
||||
const selectedUnit = unitOptions.find(u => u.value === form.unit);
|
||||
console.log('Selected unit for update:', selectedUnit);
|
||||
|
||||
const response = await productService.updateProduct(productId, productData);
|
||||
const selectedUnit = unitOptions.find(u => u.value === form.unit);
|
||||
const updatedProduct = {
|
||||
...rowsData[editingRow],
|
||||
code: form.code,
|
||||
@ -929,10 +916,25 @@ const IsicHsCodes = () => {
|
||||
|
||||
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-[#E5E7EB]">
|
||||
{/* Header */}
|
||||
<div className="px-6 py-3 flex items-center justify-between">
|
||||
<h3 className="text-[16px] font-medium text-[#232528]">HS Codes</h3>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="relative w-80">
|
||||
<div className="p-4 md:px-6 md:py-3">
|
||||
{/* Mobile Header */}
|
||||
<div className="md:hidden mb-4">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h3 className="text-lg font-medium text-[#232528]">
|
||||
HS Codes ({rowsData.length})
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 w-10 flex items-center justify-center rounded-[6px] bg-[#92722A] text-white hover:bg-[#7a5f22]"
|
||||
onClick={openAddModal}
|
||||
aria-label="Add Code"
|
||||
>
|
||||
<img src={addIconSrc} alt="Add" className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search - Mobile */}
|
||||
<div className="relative mb-3">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2">
|
||||
<img src={searchIconSrc} alt="Search" className="h-4 w-4 text-[#5F646D]" />
|
||||
</div>
|
||||
@ -944,114 +946,224 @@ const IsicHsCodes = () => {
|
||||
className="h-10 w-full rounded-md border border-[#C3C6CB] pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="h-10 px-4 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 hover:bg-gray-50"
|
||||
onClick={() => setShowImportModal(true)}
|
||||
>
|
||||
<img src={uploadImportIconSrc} alt="Import" className="h-5 w-5" />
|
||||
<span className="font-medium text-[#232528]">Import CSV</span>
|
||||
</button>
|
||||
<button
|
||||
className="h-10 px-4 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 hover:bg-gray-50"
|
||||
onClick={handleExportCSV}
|
||||
>
|
||||
<img src={downloadIconSrc} alt="Export" className="h-5 w-5" />
|
||||
<span className="font-medium text-[#232528]">Export CSV</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-4 rounded-[6px] bg-[#92722A] text-white text-sm inline-flex items-center gap-2 hover:bg-[#7a5f22]"
|
||||
onClick={openAddModal}
|
||||
>
|
||||
<img src={addIconSrc} alt="Add" className="h-5 w-5" />
|
||||
<span className="font-medium">Add Code</span>
|
||||
</button>
|
||||
|
||||
{/* Action Buttons - Mobile */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
className="h-10 px-3 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center justify-center gap-2 hover:bg-gray-50"
|
||||
onClick={() => setShowImportModal(true)}
|
||||
>
|
||||
<img src={uploadImportIconSrc} alt="Import" className="h-4 w-4" />
|
||||
<span className="text-xs font-medium text-[#232528]">Import</span>
|
||||
</button>
|
||||
<button
|
||||
className="h-10 px-3 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center justify-center gap-2 hover:bg-gray-50"
|
||||
onClick={handleExportCSV}
|
||||
>
|
||||
<img src={downloadIconSrc} alt="Export" className="h-4 w-4" />
|
||||
<span className="text-xs font-medium text-[#232528]">Export</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop Header */}
|
||||
<div className="hidden md:flex items-center justify-between">
|
||||
<h3 className="text-[16px] font-medium text-[#232528]">
|
||||
HS Codes ({rowsData.length})
|
||||
</h3>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="relative w-60 xl:w-80">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2">
|
||||
<img src={searchIconSrc} alt="Search" className="h-4 w-4 text-[#5F646D]" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="Search by code or name"
|
||||
className="h-10 w-full rounded-md border border-[#C3C6CB] pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="h-10 px-3 xl:px-4 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 hover:bg-gray-50 whitespace-nowrap"
|
||||
onClick={() => setShowImportModal(true)}
|
||||
>
|
||||
<img src={uploadImportIconSrc} alt="Import" className="h-5 w-5" />
|
||||
<span className="hidden xl:inline font-medium text-[#232528]">Import CSV</span>
|
||||
<span className="xl:hidden font-medium text-[#232528]">Import</span>
|
||||
</button>
|
||||
<button
|
||||
className="h-10 px-3 xl:px-4 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 hover:bg-gray-50 whitespace-nowrap"
|
||||
onClick={handleExportCSV}
|
||||
>
|
||||
<img src={downloadIconSrc} alt="Export" className="h-5 w-5" />
|
||||
<span className="hidden xl:inline font-medium text-[#232528]">Export CSV</span>
|
||||
<span className="xl:hidden font-medium text-[#232528]">Export</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-3 xl:px-4 rounded-[6px] bg-[#92722A] text-white text-sm inline-flex items-center gap-2 hover:bg-[#7a5f22] whitespace-nowrap"
|
||||
onClick={openAddModal}
|
||||
>
|
||||
<img src={addIconSrc} alt="Add" className="h-5 w-5" />
|
||||
<span className="font-medium">Add Code</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<Table
|
||||
headers={headers}
|
||||
rows={rows}
|
||||
renderCell={(value, rowIndex, colIndex) => {
|
||||
if (colIndex === 6) {
|
||||
return (
|
||||
<StatusBadge
|
||||
status={value}
|
||||
tone={value === 'Active' ? 'green' : 'gray'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
{/* Table - Desktop */}
|
||||
<div className="hidden md:block">
|
||||
<Table
|
||||
headers={headers}
|
||||
rows={rows}
|
||||
renderCell={(value, rowIndex, colIndex) => {
|
||||
if (colIndex === 6) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
||||
title="Edit"
|
||||
aria-label="Edit code"
|
||||
onClick={() => handleEdit(rowIndex)}
|
||||
onMouseEnter={() => setEditingRow(rowIndex)}
|
||||
onMouseLeave={() => setEditingRow(null)}
|
||||
>
|
||||
<img
|
||||
src={editingRow === rowIndex ? pencilActiveSrc : pencilInactiveSrc}
|
||||
alt="Edit"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
||||
title="View"
|
||||
aria-label="View code"
|
||||
onClick={() => handleView(rowIndex)}
|
||||
>
|
||||
<img
|
||||
src={eyeIconSrc}
|
||||
alt="View"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 flex items-center justify-center rounded hover:bg-gray-50 cursor-pointer focus:outline-none"
|
||||
title="Delete"
|
||||
aria-label="Delete code"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete(rowIndex);
|
||||
}}
|
||||
onMouseEnter={() => setDeletingRow(rowIndex)}
|
||||
onMouseLeave={() => setDeletingRow(null)}
|
||||
>
|
||||
<img
|
||||
src={deletingRow === rowIndex ? trashActiveSrc : trashInactiveSrc}
|
||||
alt="Delete"
|
||||
className="h-4 w-4 pointer-events-none"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}}
|
||||
pagination={{
|
||||
currentPage,
|
||||
onPageChange: setCurrentPage,
|
||||
pageSize,
|
||||
totalItems: filteredData.length,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
if (colIndex === 7) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
||||
title="Edit"
|
||||
aria-label="Edit code"
|
||||
onClick={() => handleEdit(rowIndex)}
|
||||
onMouseEnter={() => setEditingRow(rowIndex)}
|
||||
onMouseLeave={() => setEditingRow(null)}
|
||||
>
|
||||
<img
|
||||
src={editingRow === rowIndex ? pencilActiveSrc : pencilInactiveSrc}
|
||||
alt="Edit"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
||||
title="View"
|
||||
aria-label="View code"
|
||||
onClick={() => handleView(rowIndex)}
|
||||
>
|
||||
<img
|
||||
src={eyeIconSrc}
|
||||
alt="View"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 flex items-center justify-center rounded hover:bg-gray-50 cursor-pointer focus:outline-none"
|
||||
title="Delete"
|
||||
aria-label="Delete code"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
console.log('Delete button clicked for row:', rowIndex);
|
||||
handleDelete(rowIndex);
|
||||
}}
|
||||
onMouseEnter={() => setDeletingRow(rowIndex)}
|
||||
onMouseLeave={() => setDeletingRow(null)}
|
||||
>
|
||||
<img
|
||||
src={deletingRow === rowIndex ? trashActiveSrc : trashInactiveSrc}
|
||||
alt="Delete"
|
||||
className="h-4 w-4 pointer-events-none"
|
||||
/>
|
||||
</button>
|
||||
{/* Mobile List View */}
|
||||
<div className="md:hidden">
|
||||
{rows.length === 0 ? (
|
||||
<div className="p-4 text-center text-gray-500">No HS Codes found</div>
|
||||
) : (
|
||||
<div className="divide-y divide-gray-200">
|
||||
{rows.map((row, rowIndex) => (
|
||||
<div key={rowIndex} className="p-4 hover:bg-gray-50">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{row[1]}</div>
|
||||
<div className="text-sm text-gray-500">{row[0]}</div>
|
||||
<div className="mt-1 text-sm">
|
||||
<span className="text-gray-500">Unit: </span>
|
||||
<span className="font-medium">{row[2]}</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-500">Mapped: </span>
|
||||
<span className="font-medium">{row[3]}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-100"
|
||||
onClick={() => handleView(rowIndex)}
|
||||
aria-label="View"
|
||||
>
|
||||
<img src={eyeIconSrc} alt="View" className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-8 w-8 grid place-items-center rounded hover:bg-gray-100"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEdit(rowIndex);
|
||||
}}
|
||||
aria-label="Edit"
|
||||
>
|
||||
<img src={pencilInactiveSrc} alt="Edit" className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}}
|
||||
pagination={{
|
||||
currentPage,
|
||||
onPageChange: setCurrentPage,
|
||||
pageSize,
|
||||
totalItems: filteredData.length,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile Pagination */}
|
||||
{rows.length > 0 && (
|
||||
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200">
|
||||
<div className="flex-1 flex justify-between items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
|
||||
disabled={currentPage === 1}
|
||||
className={`relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium ${currentPage === 1 ? 'text-gray-300 cursor-not-allowed' : 'text-gray-700 hover:bg-gray-50'}`}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span className="text-sm text-gray-700">
|
||||
Page {currentPage} of {Math.ceil(filteredData.length / pageSize)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCurrentPage(prev => Math.min(prev + 1, Math.ceil(filteredData.length / pageSize)))}
|
||||
disabled={currentPage * pageSize >= filteredData.length}
|
||||
className={`relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium ${currentPage * pageSize >= filteredData.length ? 'text-gray-300 cursor-not-allowed' : 'text-gray-700 hover:bg-gray-50'}`}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Add/Edit/View Modal */}
|
||||
{modalMode && (
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div className="absolute inset-0 bg-black/40" onClick={closeModal} />
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[520px] bg-white rounded-lg shadow-xl border border-[#E5E7EB] max-h-[90vh] overflow-y-auto">
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90%] sm:w-[520px] bg-white rounded-lg shadow-xl border border-[#E5E7EB] max-h-[90vh] overflow-y-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-[#E5E7EB]">
|
||||
<h3 className="text-lg font-semibold text-[#111827]">
|
||||
@ -1162,9 +1274,9 @@ const IsicHsCodes = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveForm}
|
||||
disabled={!form.code || !form.product || !form.unit || !form.status}
|
||||
disabled={!form.code || !form.product || !form.unit}
|
||||
className={`h-10 px-6 text-sm font-medium text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#92722A] ${
|
||||
!form.code || !form.product || !form.unit || !form.status
|
||||
!form.code || !form.product || !form.unit
|
||||
? 'bg-gray-300 cursor-not-allowed'
|
||||
: 'bg-[#92722A] hover:bg-[#7a5f22]'
|
||||
}`}
|
||||
|
||||
@ -32,7 +32,8 @@ const QuarterlyWindows = () => {
|
||||
const [form, setForm] = React.useState(createEmptyQuarterForm());
|
||||
const [originalForm, setOriginalForm] = React.useState(null);
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const [statusFilter, setStatusFilter] = React.useState('all');
|
||||
const [yearFilter, setYearFilter] = React.useState('all');
|
||||
const [quarterFilter, setQuarterFilter] = React.useState('all');
|
||||
const [hoveredEdit, setHoveredEdit] = React.useState(null);
|
||||
const [currentPage, setCurrentPage] = React.useState(1);
|
||||
const pageSize = 10;
|
||||
@ -92,43 +93,44 @@ const QuarterlyWindows = () => {
|
||||
fetchQuarterlyWindows();
|
||||
}, []);
|
||||
|
||||
// Table headers with whitespace-nowrap to prevent line breaks
|
||||
// Table headers with left alignment and no wrapping
|
||||
const headers = [
|
||||
{ name: 'Survey Name', className: 'whitespace-nowrap' },
|
||||
{ name: 'Year', className: 'whitespace-nowrap' },
|
||||
{ name: 'Quarter', className: 'whitespace-nowrap' },
|
||||
{ name: 'Start Date', className: 'whitespace-nowrap' },
|
||||
{ name: 'End Date', className: 'whitespace-nowrap' },
|
||||
{ name: 'Grace Period', className: 'whitespace-nowrap' },
|
||||
{ name: 'Assigned', className: 'whitespace-nowrap' },
|
||||
{ name: 'Responded', className: 'whitespace-nowrap' },
|
||||
{ name: 'Not Responded', className: 'whitespace-nowrap' },
|
||||
{ name: 'Submission Count', className: 'whitespace-nowrap' },
|
||||
{ name: 'Status', className: 'whitespace-nowrap' },
|
||||
{ name: 'Actions', className: 'whitespace-nowrap' },
|
||||
{ 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' },
|
||||
];
|
||||
// Column widths significantly increased for better content visibility
|
||||
const columnwidth = [
|
||||
550, // Survey Name
|
||||
150, // Year
|
||||
180, // Quarter
|
||||
220, // Start Date
|
||||
220, // End Date
|
||||
250, // Grace Period (increased from 200 to 250)
|
||||
180, // Assigned
|
||||
200, // Responded
|
||||
220, // Not Responded
|
||||
240, // Submission Count
|
||||
180, // Status
|
||||
160 // Actions
|
||||
];
|
||||
220, // Survey Name (reduced from 550)
|
||||
100, // Year (reduced from 150)
|
||||
120, // Quarter (reduced from 180)
|
||||
150, // Start Date (reduced from 220)
|
||||
150, // End Date (reduced from 220)
|
||||
180, // Grace Period (reduced from 250)
|
||||
120, // Assigned (reduced from 180)
|
||||
120, // Responded (reduced from 200)
|
||||
140, // Not Responded (reduced from 220)
|
||||
140, // Submission Count (reduced from 240)
|
||||
120 // Actions (reduced from 160)
|
||||
];
|
||||
const filteredRows = React.useMemo(() => {
|
||||
const term = searchTerm.trim().toLowerCase();
|
||||
const statusValue = statusFilter.toLowerCase();
|
||||
return quarterData.filter((item) => {
|
||||
const matchesStatus =
|
||||
statusValue === 'all' || String(item.status).toLowerCase() === statusValue;
|
||||
if (!matchesStatus) return false;
|
||||
// Apply year filter
|
||||
if (yearFilter !== 'all' && item.year !== yearFilter) return false;
|
||||
|
||||
// Apply quarter filter
|
||||
if (quarterFilter !== 'all' && item.quarter !== quarterFilter) return false;
|
||||
|
||||
// Apply search term
|
||||
if (!term) return true;
|
||||
const haystack = [
|
||||
item.survey_name,
|
||||
@ -148,7 +150,7 @@ const QuarterlyWindows = () => {
|
||||
.join(' ');
|
||||
return haystack.includes(term);
|
||||
});
|
||||
}, [quarterData, searchTerm, statusFilter]);
|
||||
}, [quarterData, searchTerm, yearFilter, quarterFilter]);
|
||||
|
||||
const Tooltip = ({ children, text }) => (
|
||||
<div className="flex items-center gap-1 group relative">
|
||||
@ -181,19 +183,36 @@ const QuarterlyWindows = () => {
|
||||
{item.not_responded || '0'}
|
||||
</Tooltip>,
|
||||
item.submissionCount,
|
||||
<StatusBadge status={item.status} />,
|
||||
'actions',
|
||||
]);
|
||||
|
||||
|
||||
const statusOptions = React.useMemo(
|
||||
() => [
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'Active', value: 'Active' },
|
||||
{ label: 'Inactive', value: 'Inactive' },
|
||||
],
|
||||
[]
|
||||
);
|
||||
// Generate year options from 2022 to current year, plus any additional years in data
|
||||
const yearOptions = React.useMemo(() => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = new Set(['all']);
|
||||
|
||||
// Add years from 2022 to current year
|
||||
for (let y = 2022; y <= currentYear; y++) {
|
||||
years.add(y.toString());
|
||||
}
|
||||
|
||||
// Add any additional years that might be in the data
|
||||
quarterData.forEach(item => {
|
||||
if (item.year && !years.has(item.year)) {
|
||||
years.add(item.year);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort years in descending order (newest first)
|
||||
return Array.from(years).sort((a, b) => {
|
||||
if (a === 'all') return -1;
|
||||
if (b === 'all') return 1;
|
||||
return parseInt(b) - parseInt(a);
|
||||
});
|
||||
}, [quarterData]);
|
||||
|
||||
const quarterOptions = ['all', 'Q1', 'Q2', 'Q3', 'Q4'];
|
||||
|
||||
const openAddModal = () => {
|
||||
setForm(createEmptyQuarterForm());
|
||||
@ -370,19 +389,39 @@ const QuarterlyWindows = () => {
|
||||
/>
|
||||
<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-[134px]">
|
||||
<div className="relative w-full md:w-[150px]">
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
value={yearFilter}
|
||||
onChange={(e) => setYearFilter(e.target.value)}
|
||||
className="w-full appearance-none rounded-[4px] border border-[#C3C6CB] bg-white text-sm text-[#232528] focus:outline-none cursor-pointer h-10 pl-[12px] pr-[36px] py-[10px]"
|
||||
>
|
||||
{statusOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
<option value="all">All Years</option>
|
||||
{yearOptions
|
||||
.filter(year => year !== 'all')
|
||||
.map((year) => (
|
||||
<option key={year} value={year}>
|
||||
{year}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<img src={caretDownSrc} alt="Status" className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5" />
|
||||
<img src={caretDownSrc} alt="" className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none" />
|
||||
</div>
|
||||
<div className="relative w-full md:w-[130px]">
|
||||
<select
|
||||
value={quarterFilter}
|
||||
onChange={(e) => setQuarterFilter(e.target.value)}
|
||||
className="w-full appearance-none rounded-[4px] border border-[#C3C6CB] bg-white text-sm text-[#232528] focus:outline-none cursor-pointer h-10 pl-[12px] pr-[36px] py-[10px]"
|
||||
>
|
||||
<option value="all">All Quarters</option>
|
||||
{quarterOptions
|
||||
.filter(q => q !== 'all')
|
||||
.map((quarter) => (
|
||||
<option key={quarter} value={quarter}>
|
||||
{quarter}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<img src={caretDownSrc} alt="" className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 pointer-events-none" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@ -443,12 +482,11 @@ const QuarterlyWindows = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto w-full">
|
||||
<div className="w-full" style={{ minWidth: '1200px' }}>
|
||||
<Table
|
||||
headers={headers}
|
||||
columnWidth={columnwidth}
|
||||
columnWidths={columnwidth}
|
||||
rows={rows}
|
||||
disableHorizontalScroll={false}
|
||||
renderCell={(value, rowIndex, colIndex) => {
|
||||
if (colIndex === headers.length - 1) {
|
||||
return (
|
||||
@ -475,9 +513,18 @@ const QuarterlyWindows = () => {
|
||||
}}
|
||||
pagination={{
|
||||
currentPage,
|
||||
onPageChange: setCurrentPage,
|
||||
onPageChange: (page) => {
|
||||
setCurrentPage(page);
|
||||
// Scroll to top when changing pages
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
},
|
||||
pageSize,
|
||||
totalItems: filteredRows.length,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
onPageSizeChange: (size) => {
|
||||
setPageSize(size);
|
||||
setCurrentPage(1); // Reset to first page when changing page size
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -15,6 +15,7 @@ const caretDownIconSrc = '/assets/images/caretdown-active.svg';
|
||||
const rectangleIconSrc = '/assets/images/Rectangle.svg';
|
||||
const checkboxIconSrc = '/assets/images/checkbox.svg';
|
||||
|
||||
|
||||
// Custom Toast Component
|
||||
const CustomToast = ({ message, type, onClose }) => {
|
||||
useEffect(() => {
|
||||
@ -85,6 +86,8 @@ const UnitMaster = () => {
|
||||
const pageSize = 10;
|
||||
const [currentUser, setCurrentUser] = useState(null);
|
||||
const [toastData, setToastData] = useState(null);
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
|
||||
|
||||
// Fetch units on component mount
|
||||
useEffect(() => {
|
||||
@ -110,6 +113,15 @@ const UnitMaster = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const filteredUnits = React.useMemo(() => {
|
||||
if (!searchTerm) return units;
|
||||
const term = searchTerm.toLowerCase();
|
||||
return units.filter(unit =>
|
||||
(unit.uom && unit.uom.toLowerCase().includes(term)) ||
|
||||
(unit.uom_short_name && unit.uom_short_name.toLowerCase().includes(term)) ||
|
||||
(unit.description && unit.description.toLowerCase().includes(term))
|
||||
);
|
||||
}, [units, searchTerm]);
|
||||
useEffect(() => {
|
||||
const userProfile = sessionStorage.getItem('user_profile');
|
||||
if (userProfile) {
|
||||
@ -124,7 +136,6 @@ const UnitMaster = () => {
|
||||
'Created By',
|
||||
'Created On',
|
||||
'Last Updated',
|
||||
'Status',
|
||||
'Actions',
|
||||
];
|
||||
|
||||
@ -148,14 +159,10 @@ const UnitMaster = () => {
|
||||
item.created_by_name || currentUserName || '-',
|
||||
createdDate,
|
||||
updatedDate,
|
||||
<StatusBadge
|
||||
status={item.is_active ? 'Active' : 'Inactive'}
|
||||
tone={item.is_active ? 'green' : 'gray'}
|
||||
/>,
|
||||
'actions',
|
||||
];
|
||||
});
|
||||
}, [units, currentUser]);
|
||||
}, [units, currentUser,filteredUnits]);
|
||||
|
||||
const statusOptions = React.useMemo(
|
||||
() => [
|
||||
@ -165,21 +172,21 @@ const UnitMaster = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
const productOptions = React.useMemo(
|
||||
() => [
|
||||
{ label: 'Wheat', value: 'Wheat' },
|
||||
{ label: 'Barley', value: 'Barley' },
|
||||
{ label: 'Oats', value: 'Oats' },
|
||||
{ label: 'Rice', value: 'Rice' },
|
||||
{ label: 'Corn', value: 'Corn' },
|
||||
{ label: 'Soybeans', value: 'Soybeans' },
|
||||
{ label: 'Sorghum', value: 'Sorghum' },
|
||||
{ label: 'Millet', value: 'Millet' },
|
||||
{ label: 'Quinoa', value: 'Quinoa' },
|
||||
{ label: 'Buckwheat', value: 'Buckwheat' },
|
||||
],
|
||||
[]
|
||||
);
|
||||
// const productOptions = React.useMemo(
|
||||
// () => [
|
||||
// { label: 'Wheat', value: 'Wheat' },
|
||||
// { label: 'Barley', value: 'Barley' },
|
||||
// { label: 'Oats', value: 'Oats' },
|
||||
// { label: 'Rice', value: 'Rice' },
|
||||
// { label: 'Corn', value: 'Corn' },
|
||||
// { label: 'Soybeans', value: 'Soybeans' },
|
||||
// { label: 'Sorghum', value: 'Sorghum' },
|
||||
// { label: 'Millet', value: 'Millet' },
|
||||
// { label: 'Quinoa', value: 'Quinoa' },
|
||||
// { label: 'Buckwheat', value: 'Buckwheat' },
|
||||
// ],
|
||||
// []
|
||||
// );
|
||||
|
||||
const filteredRows = units;
|
||||
|
||||
@ -266,13 +273,7 @@ const UnitMaster = () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!form.status) {
|
||||
setToastData({
|
||||
message: 'Please select Status.',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Status validation removed as per request
|
||||
|
||||
// ✅ Check for duplicate unit name
|
||||
const nameExists = units.some(
|
||||
@ -314,7 +315,7 @@ const UnitMaster = () => {
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
// ✅ Create new record
|
||||
//Create new record
|
||||
await createUnit({
|
||||
...unitData,
|
||||
created_at: new Date().toISOString(),
|
||||
@ -322,7 +323,7 @@ const UnitMaster = () => {
|
||||
created_by_name: currentUser?.name || '',
|
||||
});
|
||||
|
||||
// ✅ Show success message for add
|
||||
// Show success message for add
|
||||
setToastData({
|
||||
message: 'New unit added successfully!',
|
||||
type: 'success'
|
||||
@ -378,11 +379,26 @@ const UnitMaster = () => {
|
||||
return (
|
||||
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-[#E5E7EB]">
|
||||
<div className="px-6 py-3 flex items-center justify-between border-b border-[#E5E7EB]">
|
||||
<h3 className="text-[16px] font-medium text-[#232528]">Unit Master (
|
||||
<span className="text-gray-600 text-sm font-medium pl-1 py-1 rounded-md">
|
||||
{units.length} {units.length === 1 ? 'unit': ''}
|
||||
</span>
|
||||
)</h3>
|
||||
<h3 className="text-[16px] font-medium text-[#232528]">
|
||||
Unit Master ({units.length}{units.length === 1 ? ' unit' : ''})
|
||||
</h3>
|
||||
{/* <div className="flex-1 max-w-md">
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2">
|
||||
<img src={searchIconSrc} alt="Search" className="h-4 w-4 text-[#5F646D]" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="Search by code or name"
|
||||
className="h-10 w-full rounded-md border border-[#C3C6CB] pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
|
||||
{/* </div> */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@ -549,7 +565,7 @@ const UnitMaster = () => {
|
||||
placeholder="Enter Description"
|
||||
width="100%"
|
||||
/>
|
||||
<div className="w-full" ref={productDropdownRef}>
|
||||
{/* <div className="w-full" ref={productDropdownRef}>
|
||||
<label className="block text-[14px] leading-[20px] font-medium text-[#232528] mb-1">Mapped Products</label>
|
||||
<button
|
||||
type="button"
|
||||
@ -594,9 +610,9 @@ const UnitMaster = () => {
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<SelectField
|
||||
{/* <SelectField
|
||||
label={
|
||||
<>
|
||||
Status <span className="text-red-500">*</span>
|
||||
@ -606,7 +622,7 @@ const UnitMaster = () => {
|
||||
onChange={handleFormChange('status')}
|
||||
options={statusOptions}
|
||||
placeholder="Select Status"
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex items-center justify-end gap-3">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user