Added Unit master

This commit is contained in:
Malini 2025-10-31 22:11:15 +05:30
parent 0e8557d49a
commit 2bbce71b96
5 changed files with 202 additions and 176 deletions

View File

@ -13,7 +13,8 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-google-recaptcha": "^3.1.0",
"react-router-dom": "^7.9.4"
"react-router-dom": "^7.9.4",
"react-toastify": "^11.0.5"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
@ -1982,6 +1983,15 @@
"node": ">=18"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -3612,6 +3622,19 @@
"react-dom": ">=18"
}
},
"node_modules/react-toastify": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
"integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
"license": "MIT",
"dependencies": {
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",

View File

@ -15,7 +15,8 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-google-recaptcha": "^3.1.0",
"react-router-dom": "^7.9.4"
"react-router-dom": "^7.9.4",
"react-toastify": "^11.0.5"
},
"devDependencies": {
"@eslint/js": "^9.36.0",

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.1853 11.6962C23.1525 11.6222 22.3584 9.86062 20.5931 8.09531C18.2409 5.74312 15.27 4.5 12 4.5C8.72999 4.5 5.75905 5.74312 3.40687 8.09531C1.64155 9.86062 0.843741 11.625 0.814679 11.6962C0.772035 11.7922 0.75 11.896 0.75 12.0009C0.75 12.1059 0.772035 12.2097 0.814679 12.3056C0.847491 12.3797 1.64155 14.1403 3.40687 15.9056C5.75905 18.2569 8.72999 19.5 12 19.5C15.27 19.5 18.2409 18.2569 20.5931 15.9056C22.3584 14.1403 23.1525 12.3797 23.1853 12.3056C23.2279 12.2097 23.25 12.1059 23.25 12.0009C23.25 11.896 23.2279 11.7922 23.1853 11.6962ZM12 18C9.11437 18 6.59343 16.9509 4.50655 14.8828C3.65028 14.0313 2.92179 13.0603 2.34374 12C2.92164 10.9396 3.65014 9.9686 4.50655 9.11719C6.59343 7.04906 9.11437 6 12 6C14.8856 6 17.4066 7.04906 19.4934 9.11719C20.3514 9.9684 21.0815 10.9394 21.6609 12C20.985 13.2619 18.0403 18 12 18ZM12 7.5C11.11 7.5 10.2399 7.76392 9.49993 8.25839C8.7599 8.75285 8.18313 9.45566 7.84253 10.2779C7.50194 11.1002 7.41282 12.005 7.58646 12.8779C7.76009 13.7508 8.18867 14.5526 8.81801 15.182C9.44735 15.8113 10.2492 16.2399 11.1221 16.4135C11.995 16.5872 12.8998 16.4981 13.7221 16.1575C14.5443 15.8169 15.2471 15.2401 15.7416 14.5001C16.2361 13.76 16.5 12.89 16.5 12C16.4988 10.8069 16.0242 9.66303 15.1806 8.81939C14.337 7.97575 13.1931 7.50124 12 7.5ZM12 15C11.4066 15 10.8266 14.8241 10.3333 14.4944C9.83993 14.1648 9.45542 13.6962 9.22835 13.1481C9.00129 12.5999 8.94188 11.9967 9.05764 11.4147C9.17339 10.8328 9.45911 10.2982 9.87867 9.87868C10.2982 9.45912 10.8328 9.1734 11.4147 9.05764C11.9967 8.94189 12.5999 9.0013 13.148 9.22836C13.6962 9.45542 14.1648 9.83994 14.4944 10.3333C14.824 10.8266 15 11.4067 15 12C15 12.7956 14.6839 13.5587 14.1213 14.1213C13.5587 14.6839 12.7956 15 12 15Z" fill="#5F646D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -14,7 +14,7 @@ const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
const pencilActiveSrc = '/assets/images/PencilSimple.svg';
const pencilInactiveSrc = '/assets/images/PencilSimple-inactive.svg';
const pencilInactiveSrc = '/assets/images/pencilsimple-inactive.svg';
const trashActiveSrc = '/assets/images/Trash - active.svg';
const trashInactiveSrc = '/assets/images/Trash - Inactive.svg';
const resetPasswordActiveSrc = '/assets/images/hugeicons_reset-password.svg';
@ -633,6 +633,7 @@ const CompanyProfile = () => {
onCountryCodeChange={handleFormChange('contactCountryCode')}
value={form.contactMobileNumber}
onChange={handleFormChange('contactMobileNumber')}
// placeholder="---------"
width="100%"
required
error={fieldErrors.contactMobileNumber}
@ -766,6 +767,7 @@ const CompanyProfile = () => {
onCountryCodeChange={handleFormChange('corporateCountryCode')}
value={form.corporateMobileNumber}
onChange={handleFormChange('corporateMobileNumber')}
placeholder="-------"
width="100%"
required
error={fieldErrors.corporateMobileNumber}
@ -987,36 +989,37 @@ const CompanyProfile = () => {
);
const headers = [
renderSortableHeader('Establishment Name', 'establishmentName'),
'Name',
'Emirates',
'Establishment Name',
'Contact Name',
'Emirate',
'ISIC Code',
'Establishment ID',
'Products',
renderSortableHeader('Total Employees', 'totalEmployees'),
'Total Employees',
'Created By',
renderSortableHeader('Created on', 'createdOn'),
renderSortableHeader('Last Updated', 'lastUpdated'),
'Created On',
'Last Updated',
'Status',
'Actions',
];
const columnWidths = React.useMemo(() => {
const base = Array(headers.length).fill(undefined);
base[0] = 250; // Establishment Name
base[1] = 118; // Name
base[2] = 118; // Emirates
base[3] = 132; // ISIC Code
base[4] = 208; // Establishment ID
base[5] = 100; // Products
base[6] = 152; // Total Employees
base[7] = 152; // Created By
base[8] = 148; // Created on
base[9] = 136; // Last Updated
base[10] = 120; // Status
base[11] = 119; // Actions
const base = [
250, // Establishment Name (250px)
150, // Contact Name (150px)
120, // Emirate (120px)
120, // ISIC Code (120px)
150, // Establishment ID (150px)
100, // Products (100px)
120, // Total Employees (120px)
120, // Created By (120px)
120, // Created On (120px)
120, // Last Updated (120px)
100, // Status (100px)
150, // Actions (150px)
];
return base;
}, [headers.length]);
}, []);
const contactToCorporateMap = React.useMemo(
() => ({

View File

@ -1,17 +1,18 @@
import React from 'react';
import React, { useState, useEffect, useMemo, useRef } from 'react';
import Table from '@/components/common/Table';
import StatusBadge from '@/components/common/StatusBadge';
import { TextField, SelectField } from '@/components/common/FormControls';
import { getUnits, createUnit, updateUnit, deleteUnit } from '@/services/configuration/unitService';
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
const pencilActiveSrc = '/assets/images/PencilSimple.svg';
const pencilInactiveSrc = '/assets/images/PencilSimple-inactive.svg';
const pencilInactiveSrc = '/assets/images/pencilsimple-inactive.svg';
const trashActiveSrc = '/assets/images/Trash - active.svg';
const trashInactiveSrc = '/assets/images/Trash - Inactive.svg';
const deleteIconSrc = '/assets/images/delete.svg';
const caretDownIconSrc = '/assets/images/caretdown-active.svg';
const rectangleIconSrc = '/assets/images/rectangle.svg';
const rectangleIconSrc = '/assets/images/Rectangle.svg';
const checkboxIconSrc = '/assets/images/checkbox.svg';
const createEmptyUnitForm = () => ({
@ -22,128 +23,72 @@ const createEmptyUnitForm = () => ({
});
const UnitMaster = () => {
const [units, setUnits] = React.useState([
{
unitName: 'Kg',
description: 'Kilogram',
productsMapped: ['Wheat', 'Barley', 'Oats', 'Corn', 'Soybeans'],
createdBy: 'Akbar',
createdOn: '25/03/2025',
lastUpdated: '31/03/2025',
status: 'Active',
},
{
unitName: 'Lt',
description: 'Liters',
productsMapped: ['Rice', 'Sorghum', 'Millet', 'Quinoa', 'Buckwheat', 'Corn'],
createdBy: 'Zara',
createdOn: '01/04/2025',
lastUpdated: '10/04/2025',
status: 'Active',
},
{
unitName: 'Kg',
description: 'Kilogram',
productsMapped: ['Rice', 'Oats'],
createdBy: 'Ravi',
createdOn: '15/05/2025',
lastUpdated: '25/05/2025',
status: 'Inactive',
},
{
unitName: 'Lt',
description: 'Liters',
productsMapped: ['Wheat', 'Barley', 'Corn', 'Soybeans', 'Millet', 'Quinoa', 'Sorghum'],
createdBy: 'Maya',
createdOn: '10/06/2025',
lastUpdated: '20/06/2025',
status: 'Active',
},
{
unitName: 'Kg',
description: 'Kilogram',
productsMapped: ['Buckwheat'],
createdBy: 'Jin',
createdOn: '30/07/2025',
lastUpdated: '05/08/2025',
status: 'Active',
},
{
unitName: 'Lt',
description: 'Liters',
productsMapped: ['Rice', 'Quinoa', 'Millet', 'Corn'],
createdBy: 'Leo',
createdOn: '12/09/2025',
lastUpdated: '20/09/2025',
status: 'Inactive',
},
{
unitName: 'Kg',
description: 'Kilogram',
productsMapped: ['Wheat', 'Soybeans', 'Corn', 'Sorghum'],
createdBy: 'Lila',
createdOn: '05/10/2025',
lastUpdated: '15/10/2025',
status: 'Active',
},
{
unitName: 'Lt',
description: 'Liters',
productsMapped: ['Barley', 'Oats', 'Millet'],
createdBy: 'Nina',
createdOn: '30/07/2025',
lastUpdated: '05/08/2025',
status: 'Inactive',
},
{
unitName: 'Kg',
description: 'Kilogram',
productsMapped: ['Rice', 'Corn', 'Soybeans', 'Quinoa', 'Millet', 'Sorghum'],
createdBy: 'Owen',
createdOn: '10/06/2025',
lastUpdated: '20/06/2025',
status: 'Inactive',
},
{
unitName: 'Lt',
description: 'Liters',
productsMapped: ['Wheat', 'Barley'],
createdBy: 'Riya',
createdOn: '28/03/2025',
lastUpdated: '31/03/2025',
status: 'Inactive',
},
]);
const [editingRow, setEditingRow] = React.useState(null);
const [deletingRow, setDeletingRow] = React.useState(null);
const [modalMode, setModalMode] = React.useState(null);
const [currentPage, setCurrentPage] = React.useState(1);
const pageSize = 10;
const [form, setForm] = React.useState(createEmptyUnitForm());
const [isProductDropdownOpen, setIsProductDropdownOpen] = React.useState(false);
const productDropdownRef = React.useRef(null);
const [units, setUnits] = useState([]);
const [loading, setLoading] = useState(true);
const [editingRow, setEditingRow] = useState(null);
const [deletingRow, setDeletingRow] = useState(null);
const [modalMode, setModalMode] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [form, setForm] = useState(createEmptyUnitForm());
const [isProductDropdownOpen, setIsProductDropdownOpen] = useState(false);
const productDropdownRef = useRef(null);
const pageSize = 10; // Add this line
const [currentUser, setCurrentUser] = useState(null);
// Fetch units on component mount
useEffect(() => {
fetchUnits();
}, []);
const fetchUnits = async () => {
try {
setLoading(true);
const response = await getUnits();
setUnits(response.data || []);
} catch (error) {
console.error('Error fetching units:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
const userProfile = sessionStorage.getItem('user_profile');
if (userProfile) {
setCurrentUser(JSON.parse(userProfile));
}
}, []);
const headers = [
'Unit Name',
'Description',
'Mapped Products',
'Created By',
'Created On',
'Last Updated',
'Status',
'Actions',
];
const rows = units.map((item) => [
item.unitName,
item.description,
Array.isArray(item.productsMapped) ? item.productsMapped.length : item.productsMapped,
item.createdBy,
item.createdOn,
item.lastUpdated,
<StatusBadge status={item.status} tone={item.status === 'Active' ? 'green' : 'gray'} />,
'actions',
]);
const rows = useMemo(() => {
return units.map((item) => {
const createdDate = item.created_at ? new Date(item.created_at).toLocaleDateString('en-GB') : '-';
const currentUserName = currentUser?.name || '';
const updatedDate =
item.updated_at && item.updated_at !== item.created_at
? new Date(item.updated_at).toLocaleDateString('en-GB')
: '-';
return [
item.uom || item.unitName,
item.uom_short_name || item.description,
Array.isArray(item.productsMapped) ? item.productsMapped.length : 0,
item.created_by_name || currentUserName || '-',
createdDate,
<StatusBadge status={item.is_active ? 'Active' : 'Inactive'} tone={item.is_active ? 'green' : 'gray'} />,
'actions',
];
});
}, [units]);
const statusOptions = React.useMemo(
() => [
@ -183,19 +128,30 @@ const UnitMaster = () => {
if (!selected) return;
setEditingRow(index);
setForm({
unitName: selected.unitName,
description: selected.description,
unitName: selected.uom || selected.unitName,
description: selected.uom_short_name || selected.description,
productsMapped: Array.isArray(selected.productsMapped) ? [...selected.productsMapped] : [],
status: selected.status,
status: selected.is_active ? 'Active' : 'Inactive',
});
setModalMode('edit');
setIsProductDropdownOpen(false);
};
};
const handleDelete = (index) => {
setDeletingRow(index);
};
const confirmDelete = async () => {
if (deletingRow === null) return;
try {
await deleteUnit(units[deletingRow].id);
setUnits(prev => prev.filter((_, index) => index !== deletingRow));
setDeletingRow(null);
} catch (error) {
console.error('Error deleting unit:', error);
}
};
const closeModal = () => {
setModalMode(null);
setForm(createEmptyUnitForm());
@ -219,6 +175,7 @@ const UnitMaster = () => {
});
};
React.useEffect(() => {
const handleClickOutside = (event) => {
if (!productDropdownRef.current) return;
@ -244,42 +201,65 @@ const UnitMaster = () => {
return `${day}/${month}/${year}`;
};
const handleSaveForm = () => {
const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : [];
if (!form.unitName || !form.description || selectedProducts.length === 0 || !form.status) {
return;
}
const handleSaveForm = async () => {
const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : [];
if (!form.unitName || !form.description || !form.status) {
return;
}
const unitData = {
uom: form.unitName,
uom_short_name: form.description,
is_active: form.status === 'Active',
};
try {
setLoading(true);
if (modalMode === 'edit' && editingRow !== null) {
setUnits((prev) =>
const updatedUnitData = {
...unitData,
updated_at: new Date().toISOString(),
updated_by: currentUser?.id || null,
};
const updatedUnit = await updateUnit(units[editingRow].id, updatedUnitData);
setUnits(prev =>
prev.map((item, index) =>
index === editingRow
? {
...item,
unitName: form.unitName,
description: form.description,
productsMapped: [...selectedProducts],
status: form.status,
lastUpdated: getToday(),
...updatedUnit,
updated_at: updatedUnitData.updated_at,
created_at: item.created_at,
created_by: item.created_by,
created_by_name: item.created_by_name,
}
: item
)
);
} else {
setUnits((prev) => [
{
unitName: form.unitName,
description: form.description,
productsMapped: [...selectedProducts],
createdBy: 'Admin',
createdOn: getToday(),
lastUpdated: getToday(),
status: form.status,
},
...prev,
]);
const newUnit = await createUnit({
...unitData,
created_at: new Date().toISOString(),
created_by: currentUser?.id || null,
created_by_name: currentUser?.name || '',
});
setUnits(prev => [newUnit, ...prev]);
}
await fetchUnits();
closeModal();
};
} catch (error) {
console.error('Error saving unit:', error);
} finally {
setLoading(false);
}
};
const deletingUnit = React.useMemo(() => {
if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) {
@ -291,12 +271,18 @@ const UnitMaster = () => {
const closeDeleteConfirm = () => {
setDeletingRow(null);
};
const handleDeleteConfirm = () => {
const handleDeleteConfirm = async () => {
if (deletingRow === null) return;
setUnits((prev) => prev.filter((_, index) => index !== deletingRow));
closeDeleteConfirm();
};
try {
await deleteUnit(units[deletingRow].id);
setUnits(prev => prev.filter((_, index) => index !== deletingRow));
closeDeleteConfirm();
} catch (error) {
console.error('Error deleting unit:', error);
}
};
return (
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-[#E5E7EB]">
@ -317,7 +303,11 @@ const UnitMaster = () => {
</button>
</div>
</div>
{loading ? (
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-gray-900"></div>
</div>
) : (
<Table
headers={headers}
rows={rows}
@ -364,7 +354,7 @@ const UnitMaster = () => {
totalItems: filteredRows.length,
}}
/>
)}
{modalMode && (
<div className="fixed inset-0 z-50">
<div className="absolute inset-0 bg-black/40" onClick={closeModal} />
@ -399,14 +389,14 @@ const UnitMaster = () => {
label="Unit Name"
value={form.unitName}
onChange={handleFormChange('unitName')}
placeholder="Enter Unit Code"
placeholder="Enter Unit Name"
width="100%"
/>
<TextField
label="Description"
value={form.description}
onChange={handleFormChange('description')}
placeholder="Enter Unit Name"
placeholder="Enter Description"
width="100%"
/>
<div className="w-full" ref={productDropdownRef}>
@ -475,10 +465,16 @@ const UnitMaster = () => {
</button>
<button
type="button"
className="h-10 px-8 rounded-md bg-[#92722A] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)]"
className="h-10 px-4 rounded-[6px] bg-[#92722A] text-white text-sm font-medium disabled:opacity-50"
onClick={handleSaveForm}
disabled={loading}
>
{modalMode === 'edit' ? 'Update' : 'Add'}
{loading ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-5 w-5 border-t-2 border-b-2 border-white mr-2"></div>
{modalMode === 'edit' ? 'Updating...' : 'Saving...'}
</div>
) : modalMode === 'edit' ? 'Update' : 'Save'}
</button>
</div>
</div>
@ -511,7 +507,7 @@ const UnitMaster = () => {
<p className="mt-1 text-sm leading-5 text-[#4B5563]">
Are you sure you want to delete{' '}
<span className="font-semibold text-[#232528]">
{`${deletingUnit.unitName} - ${deletingUnit.description}`}
{`${deletingUnit?.unitName || deletingUnit?.uom || 'N/A'} - ${deletingUnit?.description || deletingUnit?.uom_short_name || 'N/A'}`}
</span>
?
</p>