-
-
-
- {renderStepContent()}
+
+
+ {step.label}
+
+
+
+
+ );
+ })}
+
+
+
+
+ {renderStepContent()}
+
-
-
- {/* Footer */}
-
-
-
- {loading ? (
-
-
- Saving profile…
-
- ) : (
-
- )}
-
- {activeStep === formSteps.length - 1 ? (
-
-
-
+
+ {/* Footer */}
+
+
+
+ {loading ? (
+
+
+ Saving profile…
+
+ ) : (
+
+ )}
- ) : (
-
- )}
+
+
+ {activeStep > 0 && (
+
+ )}
+
+ {activeStep === formSteps.length - 1 ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+
);
};
-
-export default EditCompanyProfile;
\ No newline at end of file
+
+export default EditCompanyProfile;
+
\ No newline at end of file
diff --git a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx
index 3be5c65..c08f82e 100644
--- a/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx
+++ b/ipi-survey-platform/src/pages/Admin/configuration/UnitMaster.jsx
@@ -1,8 +1,7 @@
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';
+import { TextField } from '@/components/common/FormControls';
+import { getUnits, createUnit, updateUnit, deleteUnit, uploadUnitCSV } from '@/services/configuration/unitService';
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
@@ -11,9 +10,7 @@ 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 checkboxIconSrc = '/assets/images/checkbox.svg';
+const uploadImportIconSrc = '/assets/images/UploadSimple.svg';
// Custom Toast Component
const CustomToast = ({ message, type, onClose }) => {
@@ -41,13 +38,13 @@ const CustomToast = ({ message, type, onClose }) => {
const getIcon = () => {
switch (type) {
case 'success':
- return '✅';
+ return '';
case 'error':
- return '❌';
+ return '';
case 'warning':
- return '⚠️';
+ return '';
default:
- return 'ℹ️';
+ return '';
}
};
@@ -66,10 +63,8 @@ const CustomToast = ({ message, type, onClose }) => {
};
const createEmptyUnitForm = () => ({
- unitName: '',
+ uom: '',
description: '',
- productsMapped: [],
- status: '',
});
const UnitMaster = () => {
@@ -78,16 +73,23 @@ const UnitMaster = () => {
const [editingRow, setEditingRow] = useState(null);
const [deletingRow, setDeletingRow] = useState(null);
const [modalMode, setModalMode] = useState(null);
- const [selectedUnit, setSelectedUnit] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [form, setForm] = useState(createEmptyUnitForm());
- const [formErrors, setFormErrors] = useState({});
- const [isProductDropdownOpen, setIsProductDropdownOpen] = useState(false);
- const productDropdownRef = useRef(null);
- const pageSize = 10;
const [currentUser, setCurrentUser] = useState(null);
const [toastData, setToastData] = useState(null);
- const [searchTerm, setSearchTerm] = React.useState('');
+ const [searchTerm, setSearchTerm] = useState('');
+ const [saveLoading, setSaveLoading] = useState(false);
+ const [selectedUnit, setSelectedUnit] = useState(null);
+
+ // Import CSV state
+ const [importModalOpen, setImportModalOpen] = useState(false);
+ const [file, setFile] = useState(null);
+ const [dragActive, setDragActive] = useState(false);
+ const [importLoading, setImportLoading] = useState(false);
+ const [importError, setImportError] = useState('');
+ const fileInputRef = useRef(null);
+
+ const pageSize = 10;
// Fetch units on component mount
useEffect(() => {
@@ -98,27 +100,45 @@ const UnitMaster = () => {
try {
setLoading(true);
const response = await getUnits();
- const unitsData = response.data || [];
+
+ // Handle different response formats
+ let unitsData = [];
+ if (Array.isArray(response)) {
+ unitsData = response;
+ } else if (response && Array.isArray(response.data)) {
+ unitsData = response.data;
+ } else if (response && response.data) {
+ // If response.data is an object, convert to array
+ if (typeof response.data === 'object' && !Array.isArray(response.data)) {
+ unitsData = Object.values(response.data);
+ } else {
+ unitsData = response.data;
+ }
+ }
- // ✅ Sort by latest created_at date (newest first)
+ // Sort by latest created_at date (newest first)
const sortedUnits = [...unitsData].sort(
- (a, b) => new Date(b.created_at) - new Date(a.created_at)
+ (a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0)
);
setUnits(sortedUnits);
} catch (error) {
console.error('Error fetching units:', error);
+ setToastData({
+ message: 'Failed to load units. Please try again.',
+ type: 'error'
+ });
+ setUnits([]);
} finally {
setLoading(false);
}
};
- const filteredUnits = React.useMemo(() => {
+ const filteredUnits = 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]);
@@ -126,14 +146,20 @@ const UnitMaster = () => {
useEffect(() => {
const userProfile = sessionStorage.getItem('user_profile');
if (userProfile) {
- setCurrentUser(JSON.parse(userProfile));
+ try {
+ setCurrentUser(JSON.parse(userProfile));
+ } catch (error) {
+ console.error('Error parsing user profile:', error);
+ setCurrentUser({ name: 'System User', id: 1 });
+ }
+ } else {
+ setCurrentUser({ name: 'System User', id: 1 });
}
}, []);
const headers = [
'Unit Name',
'Description',
- 'Mapped Products',
'Created By',
'Created On',
'Last Updated',
@@ -141,66 +167,33 @@ const UnitMaster = () => {
];
const rows = useMemo(() => {
- return units.map((item) => {
+ return filteredUnits.map((item) => {
const createdDate = item.created_at
? new Date(item.created_at).toLocaleDateString('en-GB')
: '-';
- const updatedDate =
- item.updated_at && item.updated_at !== item.created_at
- ? new Date(item.updated_at).toLocaleDateString('en-GB')
- : '-';
- const currentUserName = currentUser?.name || '';
+ const updatedDate = item.updated_at
+ ? new Date(item.updated_at).toLocaleDateString('en-GB')
+ : '-';
+
+ const createdByName = item.created_by_name || currentUser?.name || '-';
return [
- item.uom || item.unitName,
- item.uom_short_name || item.description,
- item.mapped_products_count !== undefined
- ? item.mapped_products_count
- : (Array.isArray(item.productsMapped) ? item.productsMapped.length : 0),
- item.created_by_name || currentUserName || '-',
+ item.uom || '-',
+ item.description || '-',
+ createdByName,
createdDate,
updatedDate,
'actions',
];
});
- }, [units, currentUser, filteredUnits]);
-
- const statusOptions = React.useMemo(
- () => [
- { label: 'Active', value: 'Active' },
- { label: 'Inactive', value: 'Inactive' },
- ],
- []
- );
-
- const filteredRows = units;
-
- const validateForm = () => {
- const errors = {};
-
- // Unit Name validation
- if (!form.unitName.trim()) {
- errors.unitName = 'Unit Name is required';
- }
-
- // Description validation - red color validation
- if (!form.description.trim()) {
- errors.description = 'Please enter a description.';
- }
-
- setFormErrors(errors);
- return Object.keys(errors).length === 0;
- };
+ }, [filteredUnits, currentUser]);
const openAddModal = () => {
setEditingRow(null);
setForm(createEmptyUnitForm());
- setFormErrors({});
setModalMode('add');
- setIsProductDropdownOpen(false);
};
-
- const handleView = (index) => {
+ const handleView = (index) => {
const selected = units[index];
if (!selected) return;
setSelectedUnit(selected);
@@ -208,19 +201,15 @@ const UnitMaster = () => {
};
const handleEdit = (index) => {
- const selected = units[index];
+ const selected = filteredUnits[index];
if (!selected) return;
setEditingRow(index);
setSelectedUnit(selected);
setForm({
- unitName: selected.uom || selected.unitName,
- description: selected.uom_short_name || selected.description,
- productsMapped: Array.isArray(selected.productsMapped) ? [...selected.productsMapped] : [],
- status: selected.is_active ? 'Active' : 'Inactive',
+ uom: selected.uom || '',
+ description: selected.description || '',
});
- setFormErrors({});
setModalMode('edit');
- setIsProductDropdownOpen(false);
};
const handleDelete = (index) => {
@@ -231,108 +220,74 @@ const UnitMaster = () => {
setModalMode(null);
setSelectedUnit(null);
setForm(createEmptyUnitForm());
- setFormErrors({});
setEditingRow(null);
- setIsProductDropdownOpen(false);
};
const handleFormChange = (field) => (event) => {
const value = event?.target?.value ?? event;
setForm((prev) => ({ ...prev, [field]: value }));
-
- // Clear error when user starts typing
- if (formErrors[field]) {
- setFormErrors(prev => ({
- ...prev,
- [field]: ''
- }));
- }
};
- const handleProductToggle = (value) => {
- setForm((prev) => {
- const current = Array.isArray(prev.productsMapped) ? prev.productsMapped : [];
- const exists = current.includes(value);
- return {
- ...prev,
- productsMapped: exists ? current.filter((item) => item !== value) : [...current, value],
- };
- });
- };
-
- React.useEffect(() => {
- const handleClickOutside = (event) => {
- if (!productDropdownRef.current) return;
- if (!productDropdownRef.current.contains(event.target)) {
- setIsProductDropdownOpen(false);
- }
- };
-
- if (isProductDropdownOpen) {
- document.addEventListener('mousedown', handleClickOutside);
- }
-
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, [isProductDropdownOpen]);
-
const handleSave = async () => {
- // Validate form before saving
- if (!validateForm()) {
+ // Validation checks
+ if (!form.uom?.trim()) {
+ setToastData({
+ message: 'Please enter Unit Name.',
+ type: 'error'
+ });
return;
}
-
- const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : [];
-
- // Check for duplicate unit name
- const nameExists = units.some(
- unit => unit.uom?.toLowerCase().trim() === form.unitName.toLowerCase().trim()
- );
-
- // Only check duplicates when adding (not editing)
- if (modalMode !== 'edit' && nameExists) {
+ if (!form.description?.trim()) {
setToastData({
- message: 'Unit name already exists!',
+ message: 'Please enter Description.',
type: 'error'
});
return;
}
+ // Check for duplicate unit name (only when adding)
+ if (modalMode !== 'edit') {
+ const nameExists = units.some(
+ unit => unit.uom?.toLowerCase().trim() === form.uom.toLowerCase().trim()
+ );
+
+ if (nameExists) {
+ setToastData({
+ message: 'Unit name already exists!',
+ type: 'error'
+ });
+ return;
+ }
+ }
+
+ // Prepare unit data - ensure proper format for backend
const unitData = {
- uom: form.unitName,
- uom_short_name: form.description,
- is_active: form.status === 'Active',
- productsMapped: selectedProducts.length,
+ uom: form.uom.trim(),
+ description: form.description.trim(),
};
+ // Add user information if available
+ if (currentUser?.id) {
+ unitData.created_by = currentUser.id;
+ unitData.created_by_name = currentUser.name;
+ }
+
+ console.log('Saving unit data:', unitData);
+
try {
- setLoading(true);
+ setSaveLoading(true);
if (modalMode === 'edit' && editingRow !== null) {
- const updatedUnitData = {
- ...unitData,
- updated_at: new Date().toISOString(),
- updated_by: currentUser?.id || null,
- };
+ const unitToUpdate = filteredUnits[editingRow];
+ await updateUnit(unitToUpdate.id, unitData);
- await updateUnit(units[editingRow].id, updatedUnitData);
-
- // ✅ Show success message for edit
setToastData({
message: 'Unit updated successfully!',
type: 'success'
});
} else {
- //Create new record
- await createUnit({
- ...unitData,
- created_at: new Date().toISOString(),
- created_by: currentUser?.id || null,
- created_by_name: currentUser?.name || '',
- });
+ await createUnit(unitData);
- // Show success message for add
setToastData({
message: 'New unit added successfully!',
type: 'success'
@@ -343,23 +298,94 @@ const UnitMaster = () => {
closeModal();
} catch (error) {
console.error('Error saving unit:', error);
+
+ // Get detailed error message with better error handling
+ let errorMessage = 'Failed to save unit. Please check the data and try again.';
+
+ if (error.response?.data) {
+ const errorData = error.response.data;
+
+ console.log('Error response data:', errorData);
+
+ // Handle different error response formats
+ if (typeof errorData === 'string') {
+ errorMessage = errorData;
+ } else if (errorData.message) {
+ errorMessage = errorData.message;
+ } else if (errorData.error) {
+ errorMessage = errorData.error;
+ } else if (Array.isArray(errorData)) {
+ errorMessage = errorData.join(', ');
+ } else if (typeof errorData === 'object') {
+ // Extract error messages from validation object
+ const errorMessages = [];
+
+ // Check for common validation error formats
+ if (errorData.errors) {
+ // Laravel-style validation errors
+ Object.values(errorData.errors).forEach(errArray => {
+ if (Array.isArray(errArray)) {
+ errorMessages.push(...errArray);
+ }
+ });
+ } else {
+ // Generic object with error messages
+ Object.values(errorData).forEach(value => {
+ if (Array.isArray(value)) {
+ errorMessages.push(...value);
+ } else if (typeof value === 'string') {
+ errorMessages.push(value);
+ }
+ });
+ }
+
+ if (errorMessages.length > 0) {
+ errorMessage = errorMessages.join(', ');
+ } else {
+ // If no specific messages found, show the first value
+ const firstError = Object.values(errorData)[0];
+ if (Array.isArray(firstError)) {
+ errorMessage = firstError[0];
+ } else {
+ errorMessage = firstError || errorMessage;
+ }
+ }
+ }
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ // Special handling for common backend validation errors
+ if (errorMessage.toLowerCase().includes('uom') || errorMessage.toLowerCase().includes('unit')) {
+ if (errorMessage.toLowerCase().includes('required')) {
+ errorMessage = 'Unit name is required.';
+ } else if (errorMessage.toLowerCase().includes('unique') || errorMessage.toLowerCase().includes('already exists')) {
+ errorMessage = 'Unit name already exists. Please choose a different name.';
+ }
+ }
+
+ if (errorMessage.toLowerCase().includes('description') && errorMessage.toLowerCase().includes('required')) {
+ errorMessage = 'Description is required.';
+ }
+
setToastData({
- message: 'Something went wrong while saving the unit.',
+ message: errorMessage,
type: 'error'
});
} finally {
- setLoading(false);
+ setSaveLoading(false);
}
};
const handleDeleteConfirm = async () => {
if (deletingRow === null) return;
try {
- const unitToDelete = units[deletingRow];
+ const unitToDelete = filteredUnits[deletingRow];
await deleteUnit(unitToDelete.id);
- setUnits(prev => prev.filter((_, index) => index !== deletingRow));
- // ✅ Show success message for delete
+ // Update local state
+ setUnits(prev => prev.filter(unit => unit.id !== unitToDelete.id));
+
setToastData({
message: 'Unit deleted successfully!',
type: 'success'
@@ -367,8 +393,16 @@ const UnitMaster = () => {
setDeletingRow(null);
} catch (error) {
console.error('Error deleting unit:', error);
+
+ let errorMessage = 'Failed to delete unit. Please try again.';
+ if (error.response?.data?.message) {
+ errorMessage = error.response.data.message;
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
setToastData({
- message: 'Failed to delete unit. Please try again.',
+ message: errorMessage,
type: 'error'
});
}
@@ -378,53 +412,202 @@ const UnitMaster = () => {
setDeletingRow(null);
};
- const deletingUnit = React.useMemo(() => {
- if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) {
+ const deletingUnit = useMemo(() => {
+ if (deletingRow === null || deletingRow < 0 || deletingRow >= filteredUnits.length) {
return null;
}
- return units[deletingRow];
- }, [deletingRow, units]);
+ return filteredUnits[deletingRow];
+ }, [deletingRow, filteredUnits]);
+
+ // Import CSV Functions
+ const openImportModal = () => {
+ setImportModalOpen(true);
+ setFile(null);
+ setImportError('');
+ setDragActive(false);
+ };
+
+ const closeImportModal = () => {
+ setImportModalOpen(false);
+ setFile(null);
+ setImportError('');
+ setDragActive(false);
+ };
+
+ const handleDrag = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.type === "dragenter" || e.type === "dragover") {
+ setDragActive(true);
+ } else if (e.type === "dragleave") {
+ setDragActive(false);
+ }
+ };
+
+ const handleDrop = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setDragActive(false);
+
+ if (e.dataTransfer.files && e.dataTransfer.files[0]) {
+ const droppedFile = e.dataTransfer.files[0];
+ if (droppedFile.type === 'text/csv' || droppedFile.name.endsWith('.csv')) {
+ setFile(droppedFile);
+ setImportError('');
+ } else {
+ setImportError('Please upload a CSV file only.');
+ }
+ }
+ };
+
+ const handleFileChange = (e) => {
+ if (e.target.files && e.target.files[0]) {
+ const selectedFile = e.target.files[0];
+ if (selectedFile.type === 'text/csv' || selectedFile.name.endsWith('.csv')) {
+ setFile(selectedFile);
+ setImportError('');
+ } else {
+ setImportError('Please upload a CSV file only.');
+ }
+ }
+ };
+
+ const downloadSampleCSV = () => {
+ const sampleData = [
+ ['uom', 'description'],
+ ['Kilogram', 'Weight measurement in kilograms'],
+ ['Gram', 'Weight measurement in grams'],
+ ['Liter', 'Volume measurement in liters'],
+ ['Meter', 'Length measurement in meters']
+ ];
+
+ const csvContent = sampleData.map(row =>
+ row.map(field => `"${field}"`).join(',')
+ ).join('\n');
+
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.setAttribute('download', 'unit-master-template.csv');
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ };
+
+ const handleImport = async () => {
+ if (!file) {
+ setImportError('Please select a CSV file to import.');
+ return;
+ }
+
+ try {
+ setImportLoading(true);
+ setImportError('');
+
+ const formData = new FormData();
+ formData.append('file', file);
+
+ // Add user info if needed by backend
+ if (currentUser?.id) {
+ formData.append('uploaded_by', currentUser.id);
+ formData.append('uploaded_by_name', currentUser.name);
+ }
+
+ const response = await uploadUnitCSV(formData);
+
+ setToastData({
+ message: response.message || 'Units imported successfully!',
+ type: 'success'
+ });
+
+ closeImportModal();
+ await fetchUnits();
+ } catch (error) {
+ console.error('Error importing CSV:', error);
+
+ let errorMessage = 'Failed to import units. Please try again.';
+
+ if (error.response?.status === 404) {
+ errorMessage = 'Unit CSV upload endpoint not found. Please contact administrator.';
+ } else if (error.response?.status === 400) {
+ errorMessage = error.response.data?.message || 'Invalid CSV format. Please check the file.';
+ } else if (error.response?.data?.message) {
+ errorMessage = error.response.data.message;
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+
+ setImportError(errorMessage);
+ setToastData({
+ message: errorMessage,
+ type: 'error'
+ });
+ } finally {
+ setImportLoading(false);
+ }
+ };
return (
- Unit Master ({units.length}{units.length === 1 ? ' unit' : ''})
+ Unit Master ({units.length} {units.length === 1 ? 'unit' : 'units'})
+
+ {/* Search Input */}
+
+
setSearchTerm(e.target.value)}
+ className="h-10 px-4 pr-10 rounded-[6px] border border-[#C3C6CB] text-sm focus:outline-none focus:ring-2 focus:ring-[#92722A] focus:border-transparent"
+ />
+
+
+
+
+