Bug resolved in products code
This commit is contained in:
parent
5735016d1c
commit
9b0dc99f0f
@ -268,7 +268,7 @@ function App() {
|
||||
</RequireRole>
|
||||
}
|
||||
/>
|
||||
<Route path="profile">
|
||||
{/* <Route path="profile">
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
@ -277,7 +277,20 @@ function App() {
|
||||
</RequireRole>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
</Route> */}
|
||||
<Route path="/profile*" element={
|
||||
<RequireRole allowedRoles={['EstablishmentUser']}>
|
||||
<EditCompanyProfile />
|
||||
</RequireRole>
|
||||
} />
|
||||
{/* <Route
|
||||
path="/admin/configuration/profile/:id?"
|
||||
element={
|
||||
<RequireRole allowedRoles={['EstablishmentUser']}>
|
||||
<EditCompanyProfile />
|
||||
</RequireRole>
|
||||
}
|
||||
/> */}
|
||||
<Route path="/change-password" element={<ChangePassword />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
<Route path="/reset-password" element={<PublicContactForm />} />
|
||||
|
||||
@ -329,6 +329,7 @@ export const SelectField = ({
|
||||
clearValue = '',
|
||||
error = '',
|
||||
readOnly = false,
|
||||
required = false,
|
||||
}) => {
|
||||
const wrapperStyle = {};
|
||||
if (width !== 'auto') {
|
||||
@ -383,6 +384,7 @@ export const SelectField = ({
|
||||
{label && (
|
||||
<label htmlFor={id || name} className="block text-[14px] leading-[20px] font-medium text-[#232528] mb-1">
|
||||
{label}
|
||||
{required && <span className="text-[#B91C1C] ml-1">*</span>}
|
||||
</label>
|
||||
)}
|
||||
<div className="relative" style={{ width: '100%' }}>
|
||||
@ -399,6 +401,7 @@ export const SelectField = ({
|
||||
style={inputStyle}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
required={required}
|
||||
>
|
||||
{placeholder && (
|
||||
<option value="" disabled hidden>
|
||||
|
||||
@ -257,7 +257,7 @@ const handleEditProfile = (e) => {
|
||||
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
|
||||
<span className="font-medium">Need to update details?</span> Go to Profile →{' '}
|
||||
<a
|
||||
href={`/admin/configuration/profile?edit=${sessionStorage.getItem('establishment_id') || ''}`}
|
||||
href={`/admin/configuration/profile/edit=${sessionStorage.getItem('establishment_id') || ''}`}
|
||||
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
|
||||
onClick={handleEditProfile}
|
||||
|
||||
@ -343,7 +343,7 @@ const handleEditProfile = (e) => {
|
||||
|
||||
/>
|
||||
<Field
|
||||
label="Permanent Factory Code"
|
||||
label="Permanent Factory Code(PFC)"
|
||||
placeholder="Enter Permanent Factory Code"
|
||||
value={info.permanentFactoryCode}
|
||||
onChange={handleFieldChange('permanentFactoryCode')}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Table from '@/components/common/Table';
|
||||
import Loader from '@/components/common/Loader';
|
||||
@ -45,6 +45,24 @@ const validatePasswordComplexity = (password) => {
|
||||
|
||||
const countryCodeOptions = [{ label: '+971', value: '+971' }];
|
||||
|
||||
const contactToCorporateMap = {
|
||||
contactName: 'corporateName',
|
||||
contactAddress: 'corporateAddress',
|
||||
contactCityTown: 'corporateCityTown',
|
||||
contactCityTownId: 'corporateCityTownId',
|
||||
contactEmirate: 'corporateEmirate',
|
||||
contactEmirateId: 'corporateEmirateId',
|
||||
contactPostalCode: 'corporatePostalCode',
|
||||
contactPoBox: 'corporatePoBox',
|
||||
contactMakaniNumber: 'corporateMakaniNumber',
|
||||
contactPersonName: 'corporateContactPersonName',
|
||||
contactPersonDesignation: 'corporateContactPersonDesignation',
|
||||
contactCountryCode: 'corporateCountryCode',
|
||||
contactMobileNumber: 'corporateMobileNumber',
|
||||
contactEmail: 'corporateEmail',
|
||||
contactWebsite: 'corporateWebsite'
|
||||
};
|
||||
|
||||
const WEBSITE_URL_PATTERN = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/[^\s]*)?$/i;
|
||||
const WEBSITE_ERROR_MESSAGE = 'Enter a valid URL (e.g., https://example.com).';
|
||||
|
||||
@ -164,9 +182,11 @@ const CompanyProfile = () => {
|
||||
const [fieldErrors, setFieldErrors] = React.useState({});
|
||||
const [isDirty, setIsDirty] = React.useState(false);
|
||||
const initialSnapshotRef = React.useRef(createEmptyProfile());
|
||||
const corporateBackupRef = useRef(null);
|
||||
const toastTimeoutRef = React.useRef(null);
|
||||
const [isDeletingApi, setIsDeletingApi] = React.useState(false);
|
||||
const [totalItems, setTotalItems] = React.useState(0);
|
||||
const [currentUser, setCurrentUser] = React.useState(null);
|
||||
|
||||
const showToast = React.useCallback((type, message) => {
|
||||
if (!message) return;
|
||||
@ -194,6 +214,7 @@ const CompanyProfile = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
const computeIsDirty = React.useCallback(
|
||||
(nextForm) => {
|
||||
const initial = initialSnapshotRef.current;
|
||||
@ -282,6 +303,33 @@ const CompanyProfile = () => {
|
||||
[form, requiredFieldsByStep]
|
||||
);
|
||||
|
||||
// Add this useEffect to load the current user from session storage
|
||||
const currentUserId = React.useMemo(() => {
|
||||
try {
|
||||
const profile = sessionStorage.getItem('user_profile');
|
||||
console.log("profile678",profile)
|
||||
if (!profile) return null;
|
||||
const parsed = JSON.parse(profile);
|
||||
console.log('Current user:', parsed);
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
console.error('Error parsing user profile:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
const profile = sessionStorage.getItem('user_profile');
|
||||
// console.log("profile123",profile)
|
||||
const userProfile = React.useMemo(() => {
|
||||
try {
|
||||
const profile = sessionStorage.getItem('user_profile');
|
||||
// console.log("profile",profile)
|
||||
return profile ? JSON.parse(profile) : null;
|
||||
} catch (error) {
|
||||
console.error('Error parsing user profile:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderStepContent = () => {
|
||||
switch (activeStep) {
|
||||
case 0:
|
||||
@ -309,7 +357,60 @@ const CompanyProfile = () => {
|
||||
required
|
||||
error={fieldErrors.userProfileEmail}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
{modalMode === 'add' && (
|
||||
<>
|
||||
<div className="space-y-1">
|
||||
<TextField
|
||||
label="New Password"
|
||||
value={form.userProfilePassword}
|
||||
onChange={handleFormChange('userProfilePassword')}
|
||||
placeholder="Enter password"
|
||||
width="100%"
|
||||
type="password"
|
||||
showToggle
|
||||
className={passwordError || passwordReuseError ? 'border-red-500' : ''}
|
||||
required
|
||||
/>
|
||||
{passwordError && <p className="text-xs text-[#B91C1C]">{passwordError}</p>}
|
||||
{passwordReuseError && <p className="text-xs text-[#B91C1C]">{passwordReuseError}</p>}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
value={form.userProfileConfirmPassword}
|
||||
onChange={handleFormChange('userProfileConfirmPassword')}
|
||||
placeholder="Confirm password"
|
||||
width="100%"
|
||||
type="password"
|
||||
showToggle
|
||||
className={confirmError ? 'border-red-500' : ''}
|
||||
required
|
||||
/>
|
||||
{confirmError && <p className="text-xs text-[#B91C1C]">{confirmError}</p>}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* For Edit Mode - Show only password field */}
|
||||
{modalMode === 'edit' && (
|
||||
<div className="space-y-1">
|
||||
<TextField
|
||||
label="Password"
|
||||
value="••••••••"
|
||||
placeholder="Enter new password (leave blank to keep current)"
|
||||
width="100%"
|
||||
type="password"
|
||||
disabled={true} // ensures non-editable
|
||||
showToggle={false} // optional: hide toggle if disabled
|
||||
className={passwordError ? 'border-red-500' : ''}
|
||||
/>
|
||||
{passwordError && (
|
||||
<p className="text-xs text-[#B91C1C]">{passwordError}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* <div className="space-y-1">
|
||||
<TextField
|
||||
label="New Password"
|
||||
value={form.userProfilePassword}
|
||||
@ -322,8 +423,8 @@ const CompanyProfile = () => {
|
||||
/>
|
||||
{passwordError && <p className="text-xs text-[#B91C1C]">{passwordError}</p>}
|
||||
{passwordReuseError && <p className="text-xs text-[#B91C1C]">{passwordReuseError}</p>}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
</div> */}
|
||||
{/* <div className="space-y-1">
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
value={form.userProfileConfirmPassword}
|
||||
@ -335,7 +436,7 @@ const CompanyProfile = () => {
|
||||
className={confirmError ? 'border-red-500' : ''}
|
||||
/>
|
||||
{confirmError && <p className="text-xs text-[#B91C1C]">{confirmError}</p>}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -385,11 +486,12 @@ const CompanyProfile = () => {
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<TextField
|
||||
label="Description of Industry"
|
||||
label="Description"
|
||||
value={form.industryDescription}
|
||||
onChange={handleFormChange('industryDescription')}
|
||||
placeholder="Enter Description"
|
||||
width="100%"
|
||||
required
|
||||
style={{ height: '72px' }}
|
||||
/>
|
||||
</div>
|
||||
@ -1027,6 +1129,35 @@ const CompanyProfile = () => {
|
||||
[showToast]
|
||||
);
|
||||
|
||||
const captureCorporateValues = (formData) => {
|
||||
const corporateValues = {};
|
||||
const corporateFields = [
|
||||
'corporateName',
|
||||
'corporateAddress',
|
||||
'corporateCityTown',
|
||||
'corporateCityTownId',
|
||||
'corporateEmirate',
|
||||
'corporateEmirateId',
|
||||
'corporatePostalCode',
|
||||
'corporatePoBox',
|
||||
'corporateMakaniNumber',
|
||||
'corporateContactPersonName',
|
||||
'corporateContactPersonDesignation',
|
||||
'corporateCountryCode',
|
||||
'corporateMobileNumber',
|
||||
'corporateEmail',
|
||||
'corporateWebsite'
|
||||
];
|
||||
|
||||
corporateFields.forEach(field => {
|
||||
corporateValues[field] = formData[field] || '';
|
||||
});
|
||||
|
||||
return corporateValues;
|
||||
};
|
||||
|
||||
// Add this array as well
|
||||
const corporateFieldKeys = Object.values(contactToCorporateMap);
|
||||
const loadCorporateCityOptions = React.useCallback(
|
||||
async (emirateId) => {
|
||||
if (!emirateId) {
|
||||
@ -1082,6 +1213,16 @@ const CompanyProfile = () => {
|
||||
}, []);
|
||||
|
||||
const mapApiEstablishmentToProfile = React.useCallback((item) => {
|
||||
// Get current user from session storage
|
||||
let currentUser = null;
|
||||
try {
|
||||
const profile = sessionStorage.getItem('user_profile');
|
||||
if (profile) {
|
||||
currentUser = JSON.parse(profile);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing user profile:', error);
|
||||
}
|
||||
const base = createEmptyProfile();
|
||||
const primaryUser = Array.isArray(item?.users) && item.users.length ? item.users[0] : item?.establishment_user ?? null;
|
||||
const emiratiMale = item?.emirati_male ?? '';
|
||||
@ -1094,6 +1235,24 @@ const CompanyProfile = () => {
|
||||
const corporateEmirateName = item?.corporate_emirate?.name ?? item?.emirate ?? '';
|
||||
const establishmentCityName = item?.establishment_city?.name ?? base.contactCityTown;
|
||||
const corporateCityName = item?.corporate_city?.name ?? base.corporateCityTown;
|
||||
const contactEmail = item?.establishment_contact_email ?? base.contactEmail;
|
||||
|
||||
// Get creator's name - check if current user is the creator
|
||||
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}`;
|
||||
} 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}`;
|
||||
}
|
||||
|
||||
console.log('Creator ID:', item?.created_by, 'Current User ID:', currentUser?.id);
|
||||
console.log('Creator Name:', creatorName);
|
||||
|
||||
|
||||
return {
|
||||
...base,
|
||||
@ -1114,12 +1273,13 @@ const CompanyProfile = () => {
|
||||
totalEmployees: totalEmployees !== '' && totalEmployees !== null ? String(totalEmployees) : '',
|
||||
status: item?.is_active === false ? 'Inactive' : 'Active',
|
||||
isActive: item?.is_active !== false,
|
||||
createdBy: item?.created_by_name ?? primaryUser?.name ?? '',
|
||||
createdBy: creatorName,
|
||||
createdById: item?.created_by ?? base.createdById,
|
||||
createdOn: formatApiDate(item?.created_at),
|
||||
lastUpdated: item?.updated_by ?? formatApiDate(item?.updated_at),
|
||||
contactName: item?.factory_name ?? '',
|
||||
contactEmail: item?.email ?? '',
|
||||
// contactEmail: item?.email ?? '',
|
||||
contactEmail: item?.establishment_contact_email ?? base.contactEmail,
|
||||
contactPostalCode: item?.establishment_postal_code ?? base.contactPostalCode,
|
||||
contactPoBox: item?.establishment_po_box ?? base.contactPoBox,
|
||||
contactMakaniNumber: item?.establishment_makani_number ?? base.contactMakaniNumber,
|
||||
@ -1173,6 +1333,7 @@ const loadProfiles = async () => {
|
||||
try {
|
||||
const response = await fetchEstablishments({ params, signal: controller.signal });
|
||||
const payload = response?.data ?? response;
|
||||
console.log("payloadtest",payload)
|
||||
const records = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
|
||||
|
||||
if (!cancelled) {
|
||||
@ -1257,73 +1418,103 @@ const loadProfiles = async () => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleEdit = (identifier) => {
|
||||
if (modalMode && isDirty) {
|
||||
const shouldContinue = window.confirm('You have unsaved changes. Do you want to discard them and continue?');
|
||||
if (!shouldContinue) {
|
||||
return;
|
||||
}
|
||||
const handleEdit = async (identifier) => {
|
||||
if (modalMode && isDirty) {
|
||||
const shouldContinue = window.confirm('You have unsaved changes. Do you want to discard them and continue?');
|
||||
if (!shouldContinue) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update URL with the establishment ID
|
||||
navigate(`?edit=${encodeURIComponent(identifier)}`);
|
||||
if (!identifier) return;
|
||||
|
||||
try {
|
||||
// Show loading state
|
||||
setModalMode('loading');
|
||||
|
||||
// Update URL with the establishment ID
|
||||
navigate(`?edit=${encodeURIComponent(identifier)}`);
|
||||
if (!identifier) return;
|
||||
// Find the profile record
|
||||
const originalIndex = profiles.findIndex((item) => {
|
||||
if (item.apiId !== null && item.apiId !== undefined && item.apiId === identifier) {
|
||||
return true;
|
||||
}
|
||||
return item.establishmentId === identifier;
|
||||
});
|
||||
if (originalIndex === -1) return;
|
||||
setEditingRow(originalIndex);
|
||||
|
||||
if (originalIndex === -1) {
|
||||
showToast('error', 'Establishment not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const profileRecord = profiles[originalIndex];
|
||||
const base = createEmptyProfile();
|
||||
let nextForm = {
|
||||
...base,
|
||||
...profileRecord,
|
||||
corporateSameAs: Boolean(profileRecord?.corporateSameAs),
|
||||
};
|
||||
if (nextForm.corporateSameAs) {
|
||||
Object.entries(contactToCorporateMap).forEach(([source, target]) => {
|
||||
nextForm[target] = nextForm[source];
|
||||
});
|
||||
setEditingRow(originalIndex);
|
||||
|
||||
// If we have an API ID, fetch the latest data
|
||||
if (profileRecord?.apiId) {
|
||||
const response = await fetchEstablishmentDetail(profileRecord.apiId);
|
||||
const detail = response?.data || response;
|
||||
|
||||
if (!detail) {
|
||||
showToast('error', 'No data received from server');
|
||||
return;
|
||||
}
|
||||
|
||||
// Map the API response to form fields
|
||||
const mapped = {
|
||||
...createEmptyProfile(),
|
||||
...mapApiEstablishmentToProfile(detail),
|
||||
};
|
||||
|
||||
mapped.apiId = mapped.apiId ?? profileRecord.apiId;
|
||||
mapped.corporateSameAs = Boolean(mapped.corporateSameAs);
|
||||
|
||||
if (mapped.corporateSameAs) {
|
||||
Object.entries(contactToCorporateMap).forEach(([source, target]) => {
|
||||
mapped[target] = mapped[source];
|
||||
});
|
||||
}
|
||||
|
||||
// Set the form with the mapped data
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
...mapped,
|
||||
...computeEmploymentTotals(mapped)
|
||||
}));
|
||||
|
||||
initialSnapshotRef.current = { ...mapped, ...computeEmploymentTotals(mapped) };
|
||||
corporateBackupRef.current = captureCorporateValues(mapped);
|
||||
} else {
|
||||
// Fallback to local data if no API ID
|
||||
const base = createEmptyProfile();
|
||||
const nextForm = {
|
||||
...base,
|
||||
...profileRecord,
|
||||
corporateSameAs: Boolean(profileRecord?.corporateSameAs),
|
||||
};
|
||||
|
||||
if (nextForm.corporateSameAs) {
|
||||
Object.entries(contactToCorporateMap).forEach(([source, target]) => {
|
||||
nextForm[target] = nextForm[source];
|
||||
});
|
||||
}
|
||||
|
||||
Object.assign(nextForm, computeEmploymentTotals(nextForm));
|
||||
setForm(nextForm);
|
||||
initialSnapshotRef.current = { ...nextForm };
|
||||
corporateBackupRef.current = captureCorporateValues(nextForm);
|
||||
}
|
||||
Object.assign(nextForm, computeEmploymentTotals(nextForm));
|
||||
setForm(nextForm);
|
||||
|
||||
setModalMode('edit');
|
||||
initialSnapshotRef.current = nextForm;
|
||||
corporateBackupRef.current = captureCorporateValues(nextForm);
|
||||
setIsDirty(false);
|
||||
|
||||
if (profileRecord?.apiId !== null && profileRecord?.apiId !== undefined) {
|
||||
(async () => {
|
||||
try {
|
||||
const response = await fetchEstablishmentDetail(profileRecord.apiId);
|
||||
const detail = response?.data || response;
|
||||
if (!detail) return;
|
||||
const mapped = {
|
||||
...createEmptyProfile(),
|
||||
...mapApiEstablishmentToProfile(detail),
|
||||
};
|
||||
mapped.apiId = mapped.apiId ?? profileRecord.apiId;
|
||||
mapped.corporateSameAs = Boolean(mapped.corporateSameAs);
|
||||
if (mapped.corporateSameAs) {
|
||||
Object.entries(contactToCorporateMap).forEach(([source, target]) => {
|
||||
mapped[target] = mapped[source];
|
||||
});
|
||||
}
|
||||
Object.assign(mapped, computeEmploymentTotals(mapped));
|
||||
setForm(mapped);
|
||||
initialSnapshotRef.current = mapped;
|
||||
corporateBackupRef.current = captureCorporateValues(mapped);
|
||||
setIsDirty(false);
|
||||
} catch (error) {
|
||||
const message = error?.response?.data?.message || error?.message || 'Failed to load establishment details.';
|
||||
showToast('error', message);
|
||||
}
|
||||
})();
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in handleEdit:', error);
|
||||
const message = error?.response?.data?.message || error?.message || 'Failed to load establishment details';
|
||||
showToast('error', message);
|
||||
setModalMode(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (establishmentId) => {
|
||||
if (!establishmentId) return;
|
||||
@ -1387,7 +1578,7 @@ const loadProfiles = async () => {
|
||||
setModalMode(null);
|
||||
setForm(createEmptyProfile());
|
||||
setEditingRow(null);
|
||||
setErrors({});
|
||||
setFieldErrors({});
|
||||
setIsDirty(false);
|
||||
// Clear URL parameters when closing modal
|
||||
navigate('');
|
||||
@ -1401,6 +1592,56 @@ const loadProfiles = async () => {
|
||||
setDeletingRow(null);
|
||||
};
|
||||
|
||||
const computeFieldErrorMessage = (field, value) => {
|
||||
// Required field validation
|
||||
if (!value && requiredFields.some(f => f.field === field)) {
|
||||
return 'This field is required';
|
||||
}
|
||||
|
||||
// Email validation
|
||||
if ((field === 'contactEmail' || field === 'corporateEmail' || field === 'userProfileEmail') && value) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(value)) {
|
||||
return 'Please enter a valid email address';
|
||||
}
|
||||
}
|
||||
|
||||
// Website URL validation
|
||||
if ((field === 'contactWebsite' || field === 'corporateWebsite') && value) {
|
||||
if (!/^https?:\/\/.+\..+/.test(value)) {
|
||||
return 'Please enter a valid URL (e.g., https://example.com)';
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric field validation
|
||||
const numericFields = [
|
||||
'employmentEmiratiMale',
|
||||
'employmentEmiratiFemale',
|
||||
'employmentNonEmiratiMale',
|
||||
'employmentNonEmiratiFemale',
|
||||
'employmentTotalEmirati',
|
||||
'employmentTotalEmployees'
|
||||
];
|
||||
if (numericFields.includes(field) && value && !/^\d+$/.test(value)) {
|
||||
return 'Please enter a valid number';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
// Add this array to define required fields
|
||||
const requiredFields = [
|
||||
{ field: 'userProfileName', label: 'Name' },
|
||||
{ field: 'userProfileEmail', label: 'Email' },
|
||||
{ field: 'contactName', label: 'Contact Name' },
|
||||
{ field: 'contactAddress', label: 'Contact Address' },
|
||||
{ field: 'contactEmirate', label: 'Emirate' },
|
||||
{ field: 'contactMobileNumber', label: 'Mobile Number' },
|
||||
{ field: 'corporateName', label: 'Corporate Name' },
|
||||
{ field: 'corporateAddress', label: 'Corporate Address' },
|
||||
{ field: 'corporateEmirate', label: 'Corporate Emirate' },
|
||||
{ field: 'corporateMobileNumber', label: 'Corporate Mobile Number' }
|
||||
];
|
||||
const handleDeleteConfirm = async () => {
|
||||
if (deletingRow === null) return;
|
||||
if (isDeletingApi) return;
|
||||
@ -1650,7 +1891,8 @@ const loadProfiles = async () => {
|
||||
non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0,
|
||||
total_emirati: Number(totalEmirati) || 0,
|
||||
total_employees: Number(employmentTotals) || 0,
|
||||
created_by: Number(form.createdById) || 0,
|
||||
created_by: currentUserId?.id,
|
||||
created_by_name: currentUserId?.name, // Add this line
|
||||
establishment_user: {
|
||||
name: form.userProfileName || '',
|
||||
email: form.userProfileEmail || '',
|
||||
@ -1659,6 +1901,7 @@ const loadProfiles = async () => {
|
||||
};
|
||||
try {
|
||||
const apiResponse = await createEstablishment(apiPayload);
|
||||
console.log('API Response:', apiResponse);
|
||||
const successMessage = apiResponse?.message || 'Establishment added successfully.';
|
||||
showToast('success', successMessage);
|
||||
const createdRecord = apiResponse?.data;
|
||||
@ -1689,25 +1932,50 @@ const loadProfiles = async () => {
|
||||
delete establishmentUserPayload.email;
|
||||
}
|
||||
const updatePayload = {
|
||||
establishment_code: form.establishmentId || '',
|
||||
factory_name: form.establishmentName || form.contactName || '',
|
||||
email: form.contactEmail || form.userProfileEmail || '',
|
||||
permanent_factory_code: form.permanentFactoryCode || '',
|
||||
industry_code: form.industryCodeCurrent || form.industryCodeBusiness || '',
|
||||
license_number: form.uniqueLicenseNumber || '',
|
||||
isic_code: form.industryCodeBusiness || form.isicCode || '',
|
||||
emirate: form.emirate || form.contactEmirate || '',
|
||||
emirati_male: Number(form.employmentEmiratiMale) || 0,
|
||||
emirati_female: Number(form.employmentEmiratiFemale) || 0,
|
||||
non_emirati_male: Number(form.employmentNonEmiratiMale) || 0,
|
||||
non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0,
|
||||
total_emirati: Number(totalEmirati) || 0,
|
||||
total_employees: Number(employmentTotals) || 0,
|
||||
establishment_code: form.establishmentId || '',
|
||||
factory_name: form.establishmentName || form.contactName || '',
|
||||
permanent_factory_code: form.permanentFactoryCode || '',
|
||||
industry_code: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
||||
license_number: form.uniqueLicenseNumber || '',
|
||||
isic_code: form.isicCode || form.industryCodeBusiness || '',
|
||||
description: form.industryDescription || '',
|
||||
establishment_address: form.contactAddress || '',
|
||||
establishment_city_town_id: Number(form.contactCityTownId) || 0,
|
||||
establishment_emirate_id: Number(form.contactEmirateId) || 0,
|
||||
establishment_postal_code: form.contactPostalCode || '',
|
||||
establishment_po_box: form.contactPoBox || '',
|
||||
establishment_makani_number: form.contactMakaniNumber || '',
|
||||
establishment_contact_person_name: form.contactPersonName || '',
|
||||
establishment_contact_person_designation: form.contactPersonDesignation || '',
|
||||
establishment_mobile_number: form.contactMobileNumber || '',
|
||||
establishment_contact_email: form.contactEmail || '',
|
||||
establishment_website: form.contactWebsite || '',
|
||||
corporate_same_as_establishment: Boolean(form.corporateSameAs),
|
||||
corporate_name: form.corporateName || '',
|
||||
corporate_address: form.corporateAddress || '',
|
||||
corporate_city_town_id: Number(form.corporateCityTownId) || 0,
|
||||
corporate_emirate_id: Number(form.corporateEmirateId) || 0,
|
||||
corporate_postal_code: form.corporatePostalCode || '',
|
||||
corporate_po_box: form.corporatePoBox || '',
|
||||
corporate_makani_number: form.corporateMakaniNumber || '',
|
||||
corporate_contact_person_name: form.corporateContactPersonName || '',
|
||||
corporate_contact_person_designation: form.corporateContactPersonDesignation || '',
|
||||
corporate_mobile_number: form.corporateMobileNumber || '',
|
||||
corporate_email: form.corporateEmail || '',
|
||||
corporate_website: form.corporateWebsite || '',
|
||||
emirati_male: Number(form.employmentEmiratiMale) || 0,
|
||||
emirati_female: Number(form.employmentEmiratiFemale) || 0,
|
||||
non_emirati_male: Number(form.employmentNonEmiratiMale) || 0,
|
||||
non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0,
|
||||
total_emirati: (Number(form.employmentEmiratiMale) || 0) + (Number(form.employmentEmiratiFemale) || 0),
|
||||
total_employees: Number(form.employmentTotalEmployees) || 0,
|
||||
updated_by: 1 // Replace with actual user ID from your auth context
|
||||
};
|
||||
if (establishmentUserPayload.name || establishmentUserPayload.email || establishmentUserPayload.password) {
|
||||
updatePayload.establishment_user = establishmentUserPayload;
|
||||
}
|
||||
const apiResponse = await updateEstablishment(targetId, updatePayload);
|
||||
console.log("apiResponse",apiResponse)
|
||||
const successMessage = apiResponse?.message || 'Establishment updated successfully.';
|
||||
showToast('success', successMessage);
|
||||
apiResultData = apiResponse?.data;
|
||||
@ -1932,7 +2200,7 @@ const loadProfiles = async () => {
|
||||
120, // ISIC Code (120px)
|
||||
150, // Establishment ID (150px)
|
||||
100, // Products (100px)
|
||||
120, // Total Employees (120px)
|
||||
130, // Total Employees (120px)
|
||||
120, // Created By (120px)
|
||||
120, // Created On (120px)
|
||||
120, // Last Updated (120px)
|
||||
|
||||
@ -56,6 +56,17 @@ const IsicHsCodes = () => {
|
||||
const [form, setForm] = React.useState(createEmptyCodeForm());
|
||||
const [unitOptions, setUnitOptions] = React.useState([]);
|
||||
const [toast, setToast] = React.useState({ show: false, message: '' });
|
||||
|
||||
// Get user profile from session
|
||||
const userProfile = React.useMemo(() => {
|
||||
try {
|
||||
const profile = sessionStorage.getItem('user_profile');
|
||||
return profile ? JSON.parse(profile) : null;
|
||||
} catch (error) {
|
||||
console.error('Error parsing user profile:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Fetch units on mount
|
||||
useEffect(() => {
|
||||
@ -111,18 +122,32 @@ const IsicHsCodes = () => {
|
||||
: response.data.data || [];
|
||||
|
||||
if (products.length > 0) {
|
||||
const formattedData = products.map((product) => ({
|
||||
id: product.id,
|
||||
code: product.hs_code || 'N/A',
|
||||
product: product.product_name || 'N/A',
|
||||
unit: 'Unit',
|
||||
estimatedMapped: 0,
|
||||
createdBy: product.created_by || 'System',
|
||||
updated: product.updated_at
|
||||
? new Date(product.updated_at).toLocaleDateString()
|
||||
: '-',
|
||||
status: product.is_active ? 'Active' : 'Inactive',
|
||||
description: product.hs_description || '',
|
||||
const formattedData = await Promise.all(products.map(async (product) => {
|
||||
// Try to get creator's name if available
|
||||
let createdByName = '-';
|
||||
if (product.created_by) {
|
||||
try {
|
||||
// If the API returns the creator's name in the product data, use it
|
||||
createdByName = product.created_by_user?.name || '-';
|
||||
} catch (error) {
|
||||
console.error('Error fetching creator info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
code: product.hs_code || 'N/A',
|
||||
product: product.product_name || 'N/A',
|
||||
unit: product.unit?.uom || 'N/A',
|
||||
estimatedMapped: 0,
|
||||
createdBy: createdByName,
|
||||
createdById: product.created_by || null,
|
||||
updated: product.updated_at
|
||||
? new Date(product.updated_at).toLocaleDateString()
|
||||
: '-',
|
||||
status: product.is_active ? 'Active' : 'Inactive',
|
||||
description: product.hs_description || '',
|
||||
};
|
||||
}));
|
||||
setRowsData(formattedData);
|
||||
setFilteredData(formattedData);
|
||||
@ -199,7 +224,7 @@ const IsicHsCodes = () => {
|
||||
setForm({
|
||||
code: product.hs_code || '',
|
||||
product: product.product_name || '',
|
||||
unit: 'Unit', // Update this if you have unit in the API response
|
||||
unit: product.unit_id ? String(product.unit_id) : '',
|
||||
status: product.is_active ? 'Active' : 'Inactive',
|
||||
description: product.hs_description || ''
|
||||
});
|
||||
@ -235,7 +260,7 @@ const IsicHsCodes = () => {
|
||||
setForm({
|
||||
code: product.hs_code || '',
|
||||
product: product.product_name || '',
|
||||
unit: 'Unit', // Update this if you have unit in the API response
|
||||
unit: product.unit_id ? String(product.unit_id) : '',
|
||||
status: product.is_active ? 'Active' : 'Inactive',
|
||||
description: product.hs_description || ''
|
||||
});
|
||||
@ -253,30 +278,98 @@ const IsicHsCodes = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (index) => {
|
||||
setDeletingRow(index);
|
||||
setShowDeleteConfirm(true);
|
||||
};
|
||||
// Store the row index when delete is clicked
|
||||
const [deletingRowIndex, setDeletingRowIndex] = React.useState(null);
|
||||
|
||||
const deletingCode = React.useMemo(() => {
|
||||
if (deletingRow === null || deletingRow < 0 || deletingRow >= rowsData.length)
|
||||
return null;
|
||||
return rowsData[deletingRow];
|
||||
}, [deletingRow, rowsData]);
|
||||
|
||||
const closeDeleteConfirm = () => {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeletingRow(null);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = async () => {
|
||||
if (deletingRow === null) return;
|
||||
const handleDelete = async (paginationIndex) => {
|
||||
// Convert pagination index to actual data index
|
||||
const dataIndex = (currentPage - 1) * pageSize + paginationIndex;
|
||||
console.log('Delete button clicked, paginationIndex:', paginationIndex, 'dataIndex:', dataIndex);
|
||||
|
||||
if (dataIndex < 0 || dataIndex >= rowsData.length) {
|
||||
console.error('Invalid row index for deletion');
|
||||
return;
|
||||
}
|
||||
|
||||
const productId = rowsData[dataIndex]?.id;
|
||||
if (!productId) {
|
||||
console.error('No product ID found for deletion');
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the row index for later use
|
||||
setDeletingRowIndex(dataIndex);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const productId = rowsData[deletingRow]?.id;
|
||||
console.log('Fetching product details before deletion, ID:', productId);
|
||||
|
||||
// Fetch the latest product data by ID
|
||||
const response = await productService.getProductById(productId);
|
||||
console.log('Product details from API:', response);
|
||||
|
||||
if (!response || !response.data) {
|
||||
throw new Error('Invalid product data received');
|
||||
}
|
||||
|
||||
// Update the row data with the latest from the server
|
||||
const updatedRowsData = [...rowsData];
|
||||
updatedRowsData[dataIndex] = {
|
||||
...updatedRowsData[dataIndex],
|
||||
...response.data
|
||||
};
|
||||
|
||||
setRowsData(updatedRowsData);
|
||||
setShowDeleteConfirm(true);
|
||||
} catch (error) {
|
||||
console.error('Error preparing product for deletion:', error);
|
||||
showToast('Error loading product details for deletion', 'error');
|
||||
setDeletingRowIndex(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const deletingCode = React.useMemo(() => {
|
||||
if (deletingRowIndex === null || deletingRowIndex < 0 || deletingRowIndex >= rowsData.length)
|
||||
return null;
|
||||
return rowsData[deletingRowIndex];
|
||||
}, [deletingRowIndex, rowsData]);
|
||||
|
||||
const closeDeleteConfirm = () => {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeletingRowIndex(null);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = async () => {
|
||||
console.log('Delete confirmed, deletingRowIndex:', deletingRowIndex);
|
||||
|
||||
if (deletingRowIndex === null || deletingRowIndex < 0 || deletingRowIndex >= rowsData.length) {
|
||||
const errorMsg = 'Invalid row selected for deletion';
|
||||
console.error(errorMsg, { deletingRowIndex, rowsDataLength: rowsData.length });
|
||||
showToast(errorMsg, 'error');
|
||||
setShowDeleteConfirm(false);
|
||||
setDeletingRowIndex(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const product = rowsData[deletingRowIndex];
|
||||
const productId = product?.id;
|
||||
|
||||
if (!productId) {
|
||||
throw new Error('Product ID not found for deletion');
|
||||
throw new Error('No product ID found for deletion');
|
||||
}
|
||||
|
||||
console.log('Deleting product:', { productId, product });
|
||||
console.log('Product ID to delete:', productId);
|
||||
|
||||
if (!productId) {
|
||||
const errorMsg = 'Product ID not found for deletion';
|
||||
console.error(errorMsg);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
// Call delete API
|
||||
@ -376,6 +469,8 @@ const IsicHsCodes = () => {
|
||||
product_name: form.product,
|
||||
is_active: form.status === 'Active',
|
||||
hs_code: form.code,
|
||||
unit_id: form.unit ? parseInt(form.unit) : null,
|
||||
created_by: userProfile?.id || null, // Add created_by with user ID
|
||||
...(form.description && { hs_description: form.description })
|
||||
};
|
||||
|
||||
@ -391,35 +486,60 @@ const IsicHsCodes = () => {
|
||||
console.log(`Updating product with ID: ${productId}`);
|
||||
|
||||
try {
|
||||
// Call the update API with only the necessary fields
|
||||
console.log('Sending update request with data:', {
|
||||
productId,
|
||||
productData,
|
||||
formUnit: form.unit,
|
||||
unitOptions: unitOptions
|
||||
});
|
||||
|
||||
const response = await productService.updateProduct(productId, productData);
|
||||
console.log('Update response:', response);
|
||||
|
||||
// Update the local state with the updated product data
|
||||
setRowsData(prev =>
|
||||
prev.map(item =>
|
||||
item.id === productId
|
||||
? {
|
||||
...item,
|
||||
code: form.code,
|
||||
product: form.product,
|
||||
status: form.status,
|
||||
description: form.description || '',
|
||||
updated: new Date().toLocaleDateString('en-GB')
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
// Find the selected unit from unitOptions to get the uom
|
||||
const selectedUnit = unitOptions.find(u => u.value === form.unit);
|
||||
console.log('Selected unit for update:', selectedUnit);
|
||||
|
||||
// Create the updated product object
|
||||
const updatedProduct = {
|
||||
...rowsData[editingRow],
|
||||
code: form.code,
|
||||
product: form.product,
|
||||
unit: selectedUnit ? selectedUnit.label : 'N/A',
|
||||
unit_id: form.unit ? parseInt(form.unit) : null,
|
||||
updated: new Date().toLocaleDateString('en-GB'),
|
||||
status: form.status,
|
||||
description: form.description || ''
|
||||
};
|
||||
|
||||
// Update the product in the list while maintaining the same position
|
||||
setRowsData(prev => {
|
||||
const updated = [...prev];
|
||||
updated[editingRow] = updatedProduct;
|
||||
return updated;
|
||||
});
|
||||
|
||||
// Also update filteredData if needed
|
||||
setFilteredData(prev => {
|
||||
const updated = [...prev];
|
||||
const index = updated.findIndex(item => item.id === updatedProduct.id);
|
||||
if (index !== -1) {
|
||||
updated[index] = updatedProduct;
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
|
||||
console.log('Product updated successfully');
|
||||
showToast('Product updated successfully!');
|
||||
closeModal();
|
||||
return; // Exit the function after successful update
|
||||
return;
|
||||
} catch (updateError) {
|
||||
console.error('Error updating product:', updateError);
|
||||
const errorMessage = updateError.response?.data?.message || 'Failed to update product. Please try again.';
|
||||
let errorMessage = 'Failed to update product. Please try again.';
|
||||
if (updateError.response?.data?.message) {
|
||||
errorMessage = updateError.response.data.message;
|
||||
}
|
||||
setError(errorMessage);
|
||||
return; // Exit the function after error
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,19 +549,26 @@ const IsicHsCodes = () => {
|
||||
const response = await productService.createProduct(productData);
|
||||
console.log('Create response:', response);
|
||||
|
||||
// Find the selected unit from unitOptions to get the uom
|
||||
const selectedUnit = unitOptions.find(u => u.value === form.unit);
|
||||
|
||||
const newProduct = {
|
||||
id: response.data?.id || Date.now(), // Fallback to timestamp if no ID in response
|
||||
code: form.code,
|
||||
product: form.product,
|
||||
unit: 'Unit',
|
||||
unit: selectedUnit ? selectedUnit.label : 'N/A',
|
||||
unit_id: form.unit ? parseInt(form.unit) : null,
|
||||
estimatedMapped: 0,
|
||||
createdBy: 'Current User',
|
||||
createdBy: userProfile?.name || 'System', // Use user's name or fallback to 'System'
|
||||
createdById: userProfile?.id || null, // Store user ID for reference
|
||||
updated: new Date().toLocaleDateString('en-GB'),
|
||||
status: form.status,
|
||||
description: form.description || ''
|
||||
};
|
||||
|
||||
setRowsData(prev => [...prev, newProduct]);
|
||||
// Add new product to the beginning of the array to show it at the top
|
||||
setRowsData(prev => [newProduct, ...prev]);
|
||||
setFilteredData(prev => [newProduct, ...prev]);
|
||||
console.log('Product created successfully');
|
||||
showToast('Product created successfully!');
|
||||
closeModal();
|
||||
@ -782,14 +909,14 @@ const IsicHsCodes = () => {
|
||||
<div className="flex justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-6 rounded-md border border-[#92722A] text-sm font-medium text-[#92722A] bg-white"
|
||||
onClick={handleDeleteConfirm}
|
||||
className="h-10 px-6 rounded-md border border-[#92722A] text-sm font-medium text-[#92722A] bg-white hover:bg-gray-50"
|
||||
onClick={closeDeleteConfirm}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-6 rounded-md bg-[#92722A] text-white text-sm font-semibold"
|
||||
className="h-10 px-6 rounded-md bg-red-600 text-white text-sm font-semibold hover:bg-red-700"
|
||||
onClick={handleDeleteConfirm}
|
||||
>
|
||||
Delete
|
||||
|
||||
@ -347,7 +347,7 @@ const QuarterlyWindows = () => {
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="Search Establishment"
|
||||
placeholder="Search Survey"
|
||||
className="h-10 w-full rounded-[4px] border border-[#C3C6CB] pl-10 pr-3 text-sm text-[#232528] focus:outline-none"
|
||||
/>
|
||||
<img src={searchIconSrc} alt="Search" className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5" />
|
||||
@ -590,62 +590,65 @@ const QuarterlyWindows = () => {
|
||||
)}
|
||||
|
||||
{showDeleteConfirm && (
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div className="absolute inset-0 bg-black/40" onClick={closeDeleteConfirm} />
|
||||
<div
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white border"
|
||||
style={{
|
||||
width: '480px',
|
||||
maxWidth: '92vw',
|
||||
borderColor: '#CBD5E1',
|
||||
boxShadow: '0 20px 45px rgba(0,0,0,0.12)',
|
||||
}}
|
||||
>
|
||||
<div className="px-8 py-6 space-y-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span
|
||||
className="flex items-center justify-center"
|
||||
style={{ width: '32px', height: '36px' }}
|
||||
>
|
||||
<img src={deleteIconSrc} alt="Delete" className="h-6 w-6" />
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-[18px] font-semibold text-[#232528]">Delete Quarter</h3>
|
||||
<p className="mt-1 text-sm leading-5 text-[#4B5563]">
|
||||
Are you sure you want to delete{' '}
|
||||
{deletingQuarter && (
|
||||
<span className="font-semibold text-[#232528]">
|
||||
{`${deletingQuarter.year}-${deletingQuarter.quarter}`}
|
||||
</span>
|
||||
)}
|
||||
?
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-5 text-[#4B5563]">
|
||||
This action will permanently delete the data and cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm font-medium text-[#92722A] cursor-pointer"
|
||||
onClick={closeDeleteConfirm}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-6 rounded-md bg-[#92722A] text-white text-sm font-semibold cursor-pointer shadow-sm"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fixed inset-0 z-50">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/40"
|
||||
onClick={() => {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeletingRow(null);
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white border"
|
||||
style={{
|
||||
width: '480px',
|
||||
borderColor: '#CBD5E1',
|
||||
boxShadow: '0 20px 45px rgba(0,0,0,0.12)',
|
||||
}}
|
||||
>
|
||||
<div className="px-8 py-6 space-y-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<img src={deleteIconSrc} alt="Delete" className="h-6 w-6 mt-1" />
|
||||
<div className="flex-1">
|
||||
<h3 className="text-[18px] font-semibold text-[#232528]">
|
||||
Delete Product
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-[#4B5563]">
|
||||
Are you sure you want to delete{' '}
|
||||
<span className="font-semibold text-[#232528]">
|
||||
{rowsData[deletingRow]?.product || 'this product'}?
|
||||
</span>
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-[#4B5563]">
|
||||
This action will permanently delete the data and cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-6 rounded-md border border-[#92722A] text-sm font-medium text-[#92722A] bg-white"
|
||||
onClick={() => {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeletingRow(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-6 rounded-md bg-[#92722A] text-white text-sm font-semibold"
|
||||
onClick={handleConfirmDelete}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Deleting...' : 'Delete'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -3,7 +3,8 @@ 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 { toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
const downloadIconSrc = '/assets/images/DownloadSimple.svg';
|
||||
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
|
||||
const pencilActiveSrc = '/assets/images/PencilSimple.svg';
|
||||
@ -40,17 +41,47 @@ const UnitMaster = () => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
// const fetchUnits = async () => {
|
||||
// try {
|
||||
// setLoading(true);
|
||||
// const response = await getUnits();
|
||||
// setUnits(response.data || []);
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching units:', error);
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
const fetchUnits = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getUnits();
|
||||
console.log("resposne",response)
|
||||
const unitsData = response.data || [];
|
||||
|
||||
// Process units to ensure mapped_products_count is included
|
||||
const processedUnits = unitsData.map(unit => ({
|
||||
...unit,
|
||||
// Use existing mapped_products_count or calculate from productsMapped array
|
||||
mapped_products_count: unit.mapped_products_count ||
|
||||
(Array.isArray(unit.productsMapped) ? unit.productsMapped.length : 0)
|
||||
}));
|
||||
console.log("processedUnits",processedUnits)
|
||||
|
||||
// Sort by latest created_at date (newest first)
|
||||
const sortedUnits = [...processedUnits].sort(
|
||||
(a, b) => new Date(b.created_at) - new Date(a.created_at)
|
||||
);
|
||||
|
||||
setUnits(sortedUnits);
|
||||
} catch (error) {
|
||||
console.error('Error fetching units:', error);
|
||||
toast.error('Failed to fetch units. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const userProfile = sessionStorage.getItem('user_profile');
|
||||
@ -59,36 +90,45 @@ const UnitMaster = () => {
|
||||
setCurrentUser(JSON.parse(userProfile));
|
||||
}
|
||||
}, []);
|
||||
const headers = [
|
||||
'Unit Name',
|
||||
'Description',
|
||||
'Mapped Products',
|
||||
'Created By',
|
||||
'Created On',
|
||||
'Status',
|
||||
'Actions',
|
||||
];
|
||||
const headers = [
|
||||
'Unit Name',
|
||||
'Description',
|
||||
'Mapped Products',
|
||||
'Created By',
|
||||
'Created On',
|
||||
'Last Updated',
|
||||
'Status',
|
||||
'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 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 || '';
|
||||
|
||||
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',
|
||||
item.uom || item.unitName, // Unit Name
|
||||
item.uom_short_name || item.description, // Description
|
||||
Array.isArray(item.productsMapped) ? item.productsMapped.length : 0, // Mapped Products
|
||||
item.created_by_name || currentUserName || '-', // Created By
|
||||
createdDate, // Created On
|
||||
updatedDate, // Last Update (moved here)
|
||||
<StatusBadge
|
||||
status={item.is_active ? 'Active' : 'Inactive'}
|
||||
tone={item.is_active ? 'green' : 'gray'}
|
||||
/>, // Status
|
||||
'actions', // Actions
|
||||
];
|
||||
});
|
||||
}, [units]);
|
||||
}, [units, currentUser]);
|
||||
|
||||
|
||||
const statusOptions = React.useMemo(
|
||||
() => [
|
||||
@ -201,9 +241,89 @@ const UnitMaster = () => {
|
||||
return `${day}/${month}/${year}`;
|
||||
};
|
||||
|
||||
const handleSaveForm = async () => {
|
||||
// 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) {
|
||||
// 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,
|
||||
// ...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 {
|
||||
// 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 handleSave = async () => {
|
||||
const selectedProducts = Array.isArray(form.productsMapped) ? form.productsMapped : [];
|
||||
if (!form.unitName || !form.description || !form.status) {
|
||||
|
||||
// Validation checks
|
||||
if (!form.unitName) {
|
||||
toast.error('Please enter Unit Name.');
|
||||
return;
|
||||
}
|
||||
if (!form.description) {
|
||||
toast.error('Please enter Description.');
|
||||
return;
|
||||
}
|
||||
if (!form.status) {
|
||||
toast.error('Please select Status.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
toast.error('Unit name already exists!');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -211,6 +331,7 @@ const handleSaveForm = async () => {
|
||||
uom: form.unitName,
|
||||
uom_short_name: form.description,
|
||||
is_active: form.status === 'Active',
|
||||
productsMapped: selectedProducts,
|
||||
};
|
||||
|
||||
try {
|
||||
@ -235,10 +356,13 @@ const handleSaveForm = async () => {
|
||||
created_at: item.created_at,
|
||||
created_by: item.created_by,
|
||||
created_by_name: item.created_by_name,
|
||||
productsMapped: selectedProducts,
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
|
||||
toast.success('Unit updated successfully!');
|
||||
} else {
|
||||
const newUnit = await createUnit({
|
||||
...unitData,
|
||||
@ -247,13 +371,22 @@ const handleSaveForm = async () => {
|
||||
created_by_name: currentUser?.name || '',
|
||||
});
|
||||
|
||||
setUnits(prev => [newUnit, ...prev]);
|
||||
setUnits(prev => [
|
||||
{
|
||||
...newUnit,
|
||||
productsMapped: selectedProducts,
|
||||
},
|
||||
...prev,
|
||||
]);
|
||||
|
||||
toast.success('New unit added successfully!');
|
||||
}
|
||||
|
||||
await fetchUnits();
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
console.error('Error saving unit:', error);
|
||||
toast.error('Something went wrong while saving the unit.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -261,6 +394,10 @@ const handleSaveForm = async () => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const deletingUnit = React.useMemo(() => {
|
||||
if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) {
|
||||
return null;
|
||||
@ -289,10 +426,60 @@ const handleSaveForm = async () => {
|
||||
<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</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<button className="h-10 px-4 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 cursor-pointer">
|
||||
{/* <button className="h-10 px-4 rounded-[6px] bg-[#F7F7F7] border border-[#C3C6CB] text-sm inline-flex items-center gap-2 cursor-pointer">
|
||||
<img src={downloadIconSrc} alt="Export" className="h-5 w-5" />
|
||||
<span className="font-medium text-[#232528]">Export CSV</span>
|
||||
</button>
|
||||
</button> */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!filteredRows.length) return;
|
||||
|
||||
//Remove "Actions" column from header
|
||||
const csvHeader = headers.slice(0, headers.length - 1).join(',');
|
||||
|
||||
//Map data fields based on Unit Master structure
|
||||
const csvRows = filteredRows.map((item) =>
|
||||
[
|
||||
item.unitName,
|
||||
item.description,
|
||||
// (item.productsMapped || []).join('; '), // Mapped Products
|
||||
item.mapped_products_count , // Mapped Products Count
|
||||
item.createdBy,
|
||||
item.createdOn,
|
||||
item.status,
|
||||
]
|
||||
.map((value) => `"${String(value ?? '').replace(/"/g, '""')}"`)
|
||||
.join(',')
|
||||
);
|
||||
|
||||
// ✅ Create CSV file
|
||||
const blob = new Blob([csvHeader + '\n' + csvRows.join('\n')], {
|
||||
type: 'text/csv;charset=utf-8;',
|
||||
});
|
||||
|
||||
// ✅ Download the file
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', 'unit-master.csv');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}}
|
||||
className={`h-10 px-4 rounded-[6px] border text-sm inline-flex items-center gap-2 ${
|
||||
filteredRows.length
|
||||
? 'bg-[#F7F7F7] border-[#C3C6CB] text-[#232528] cursor-pointer'
|
||||
: 'bg-[#F3F4F6] border-[#E5E7EB] text-[#9CA3AF] cursor-not-allowed'
|
||||
}`}
|
||||
disabled={!filteredRows.length}
|
||||
>
|
||||
<img src={downloadIconSrc} alt="Export" className="h-5 w-5" />
|
||||
<span className="font-medium">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 cursor-pointer"
|
||||
@ -385,13 +572,16 @@ const handleSaveForm = async () => {
|
||||
|
||||
<div className="px-6 py-5">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<TextField
|
||||
label="Unit Name"
|
||||
value={form.unitName}
|
||||
onChange={handleFormChange('unitName')}
|
||||
placeholder="Enter Unit Name"
|
||||
width="100%"
|
||||
/>
|
||||
<TextField
|
||||
label={
|
||||
<>
|
||||
Unit Name <span className="text-red-500">*</span>
|
||||
</>
|
||||
}
|
||||
value={form.unitName}
|
||||
onChange={handleFormChange('unitName')}
|
||||
placeholder="Enter Unit Name"
|
||||
/>
|
||||
<TextField
|
||||
label="Description"
|
||||
value={form.description}
|
||||
@ -445,14 +635,18 @@ const handleSaveForm = async () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SelectField
|
||||
label="Status"
|
||||
value={form.status}
|
||||
onChange={handleFormChange('status')}
|
||||
options={statusOptions}
|
||||
placeholder="Select Status"
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
<SelectField
|
||||
label={
|
||||
<>
|
||||
Status <span className="text-red-500">*</span>
|
||||
</>
|
||||
}
|
||||
value={form.status}
|
||||
onChange={handleFormChange('status')}
|
||||
options={statusOptions}
|
||||
placeholder="Select Status"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex items-center justify-end gap-3">
|
||||
@ -463,19 +657,21 @@ const handleSaveForm = async () => {
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-4 rounded-[6px] bg-[#92722A] text-white text-sm font-medium disabled:opacity-50"
|
||||
onClick={handleSaveForm}
|
||||
disabled={loading}
|
||||
>
|
||||
{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>
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-4 rounded-[6px] bg-[#92722A] text-white text-sm font-medium disabled:opacity-50"
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
|
||||
@ -257,6 +257,61 @@ const History = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const downloadCsv = () => {
|
||||
if (!filteredRows.length) return;
|
||||
|
||||
// Define CSV headers
|
||||
const headers = [
|
||||
'Submission Date & Time',
|
||||
'Year',
|
||||
'Quarter',
|
||||
'HS Code',
|
||||
'Product',
|
||||
'Status',
|
||||
'Actors',
|
||||
'Details'
|
||||
];
|
||||
|
||||
// Helper function to safely get field values
|
||||
const getFieldValue = (value) => {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value);
|
||||
};
|
||||
|
||||
// Convert data to CSV rows
|
||||
const csvRows = [
|
||||
headers.join(','),
|
||||
...filteredRows.map(row =>
|
||||
[
|
||||
getFieldValue(formatDateTime(row.created_at)),
|
||||
getFieldValue(row.year),
|
||||
getFieldValue(row.quarter),
|
||||
getFieldValue(row.hs_code),
|
||||
getFieldValue(row.product_name),
|
||||
getFieldValue(formatStatus(row.status)),
|
||||
(row.actors && row.actors.length ? row.actors.join(', ') : '-').replace(/"/g, '""'),
|
||||
'Quantity and cost updates' // This is a placeholder for the details column
|
||||
].map(field => `"${field}"`).join(',')
|
||||
)
|
||||
];
|
||||
|
||||
// Create CSV content
|
||||
const csvContent = csvRows.join('\n');
|
||||
|
||||
// Create download link
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `submission-history-${new Date().toISOString().split('T')[0]}.csv`);
|
||||
link.style.visibility = 'hidden';
|
||||
|
||||
// Trigger download
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
const tableRows = filteredRows.map((entry, index) => {
|
||||
const isExpanded = expandedRow === index;
|
||||
return [
|
||||
@ -511,6 +566,38 @@ const History = () => {
|
||||
</div>
|
||||
|
||||
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-gray-200 mt-1">
|
||||
<div className="px-6 pt-6 pb-4 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 className="text-[18px] font-medium text-[#232528]">Submission History</h2>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||
<div className="relative w-full md:w-72">
|
||||
<input
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="Search establishment"
|
||||
className="w-full h-10 rounded-md border border-[#E2E8F0] pl-10 pr-3 text-sm text-[#232528] focus:outline-none"
|
||||
/>
|
||||
<img
|
||||
src={searchIconSrc}
|
||||
alt="Search"
|
||||
className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={downloadCsv}
|
||||
disabled={!filteredRows.length}
|
||||
className={`inline-flex items-center gap-2 h-10 rounded-md border px-4 text-sm font-medium ${filteredRows.length ? 'border-[#92722A] text-[#92722A] hover:bg-[#F2ECCF]' : 'border-gray-200 text-gray-400 cursor-not-allowed'}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
<span>Export CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{filteredRows.length === 0 && !loading ? (
|
||||
<div className="flex flex-col items-center justify-center py-12">
|
||||
<p className="text-gray-500 text-lg">No details found</p>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user