Merge branch 'master' of https://bitbucket.org/jubilian/fcsc_ipi_frontend
This commit is contained in:
commit
00491af737
@ -268,7 +268,7 @@ function App() {
|
|||||||
</RequireRole>
|
</RequireRole>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="profile">
|
{/* <Route path="profile">
|
||||||
<Route
|
<Route
|
||||||
index
|
index
|
||||||
element={
|
element={
|
||||||
@ -277,7 +277,20 @@ function App() {
|
|||||||
</RequireRole>
|
</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="/change-password" element={<ChangePassword />} />
|
||||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||||
<Route path="/reset-password" element={<PublicContactForm />} />
|
<Route path="/reset-password" element={<PublicContactForm />} />
|
||||||
|
|||||||
@ -329,6 +329,7 @@ export const SelectField = ({
|
|||||||
clearValue = '',
|
clearValue = '',
|
||||||
error = '',
|
error = '',
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
|
required = false,
|
||||||
}) => {
|
}) => {
|
||||||
const wrapperStyle = {};
|
const wrapperStyle = {};
|
||||||
if (width !== 'auto') {
|
if (width !== 'auto') {
|
||||||
@ -383,6 +384,7 @@ export const SelectField = ({
|
|||||||
{label && (
|
{label && (
|
||||||
<label htmlFor={id || name} className="block text-[14px] leading-[20px] font-medium text-[#232528] mb-1">
|
<label htmlFor={id || name} className="block text-[14px] leading-[20px] font-medium text-[#232528] mb-1">
|
||||||
{label}
|
{label}
|
||||||
|
{required && <span className="text-[#B91C1C] ml-1">*</span>}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
<div className="relative" style={{ width: '100%' }}>
|
<div className="relative" style={{ width: '100%' }}>
|
||||||
@ -399,6 +401,7 @@ export const SelectField = ({
|
|||||||
style={inputStyle}
|
style={inputStyle}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
|
required={required}
|
||||||
>
|
>
|
||||||
{placeholder && (
|
{placeholder && (
|
||||||
<option value="" disabled hidden>
|
<option value="" disabled hidden>
|
||||||
|
|||||||
@ -257,7 +257,7 @@ const handleEditProfile = (e) => {
|
|||||||
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
|
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
|
||||||
<span className="font-medium">Need to update details?</span> Go to Profile →{' '}
|
<span className="font-medium">Need to update details?</span> Go to Profile →{' '}
|
||||||
<a
|
<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]"
|
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
|
||||||
onClick={handleEditProfile}
|
onClick={handleEditProfile}
|
||||||
|
|
||||||
@ -343,7 +343,7 @@ const handleEditProfile = (e) => {
|
|||||||
|
|
||||||
/>
|
/>
|
||||||
<Field
|
<Field
|
||||||
label="Permanent Factory Code"
|
label="Permanent Factory Code(PFC)"
|
||||||
placeholder="Enter Permanent Factory Code"
|
placeholder="Enter Permanent Factory Code"
|
||||||
value={info.permanentFactoryCode}
|
value={info.permanentFactoryCode}
|
||||||
onChange={handleFieldChange('permanentFactoryCode')}
|
onChange={handleFieldChange('permanentFactoryCode')}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Table from '@/components/common/Table';
|
import Table from '@/components/common/Table';
|
||||||
import Loader from '@/components/common/Loader';
|
import Loader from '@/components/common/Loader';
|
||||||
@ -45,6 +45,24 @@ const validatePasswordComplexity = (password) => {
|
|||||||
|
|
||||||
const countryCodeOptions = [{ label: '+971', value: '+971' }];
|
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_URL_PATTERN = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/[^\s]*)?$/i;
|
||||||
const WEBSITE_ERROR_MESSAGE = 'Enter a valid URL (e.g., https://example.com).';
|
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 [fieldErrors, setFieldErrors] = React.useState({});
|
||||||
const [isDirty, setIsDirty] = React.useState(false);
|
const [isDirty, setIsDirty] = React.useState(false);
|
||||||
const initialSnapshotRef = React.useRef(createEmptyProfile());
|
const initialSnapshotRef = React.useRef(createEmptyProfile());
|
||||||
|
const corporateBackupRef = useRef(null);
|
||||||
const toastTimeoutRef = React.useRef(null);
|
const toastTimeoutRef = React.useRef(null);
|
||||||
const [isDeletingApi, setIsDeletingApi] = React.useState(false);
|
const [isDeletingApi, setIsDeletingApi] = React.useState(false);
|
||||||
const [totalItems, setTotalItems] = React.useState(0);
|
const [totalItems, setTotalItems] = React.useState(0);
|
||||||
|
const [currentUser, setCurrentUser] = React.useState(null);
|
||||||
|
|
||||||
const showToast = React.useCallback((type, message) => {
|
const showToast = React.useCallback((type, message) => {
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
@ -194,6 +214,7 @@ const CompanyProfile = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const computeIsDirty = React.useCallback(
|
const computeIsDirty = React.useCallback(
|
||||||
(nextForm) => {
|
(nextForm) => {
|
||||||
const initial = initialSnapshotRef.current;
|
const initial = initialSnapshotRef.current;
|
||||||
@ -282,6 +303,33 @@ const CompanyProfile = () => {
|
|||||||
[form, requiredFieldsByStep]
|
[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 = () => {
|
const renderStepContent = () => {
|
||||||
switch (activeStep) {
|
switch (activeStep) {
|
||||||
case 0:
|
case 0:
|
||||||
@ -309,7 +357,60 @@ const CompanyProfile = () => {
|
|||||||
required
|
required
|
||||||
error={fieldErrors.userProfileEmail}
|
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
|
<TextField
|
||||||
label="New Password"
|
label="New Password"
|
||||||
value={form.userProfilePassword}
|
value={form.userProfilePassword}
|
||||||
@ -322,8 +423,8 @@ const CompanyProfile = () => {
|
|||||||
/>
|
/>
|
||||||
{passwordError && <p className="text-xs text-[#B91C1C]">{passwordError}</p>}
|
{passwordError && <p className="text-xs text-[#B91C1C]">{passwordError}</p>}
|
||||||
{passwordReuseError && <p className="text-xs text-[#B91C1C]">{passwordReuseError}</p>}
|
{passwordReuseError && <p className="text-xs text-[#B91C1C]">{passwordReuseError}</p>}
|
||||||
</div>
|
</div> */}
|
||||||
<div className="space-y-1">
|
{/* <div className="space-y-1">
|
||||||
<TextField
|
<TextField
|
||||||
label="Confirm Password"
|
label="Confirm Password"
|
||||||
value={form.userProfileConfirmPassword}
|
value={form.userProfileConfirmPassword}
|
||||||
@ -335,7 +436,7 @@ const CompanyProfile = () => {
|
|||||||
className={confirmError ? 'border-red-500' : ''}
|
className={confirmError ? 'border-red-500' : ''}
|
||||||
/>
|
/>
|
||||||
{confirmError && <p className="text-xs text-[#B91C1C]">{confirmError}</p>}
|
{confirmError && <p className="text-xs text-[#B91C1C]">{confirmError}</p>}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -385,11 +486,12 @@ const CompanyProfile = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<TextField
|
<TextField
|
||||||
label="Description of Industry"
|
label="Description"
|
||||||
value={form.industryDescription}
|
value={form.industryDescription}
|
||||||
onChange={handleFormChange('industryDescription')}
|
onChange={handleFormChange('industryDescription')}
|
||||||
placeholder="Enter Description"
|
placeholder="Enter Description"
|
||||||
width="100%"
|
width="100%"
|
||||||
|
required
|
||||||
style={{ height: '72px' }}
|
style={{ height: '72px' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -1027,6 +1129,35 @@ const CompanyProfile = () => {
|
|||||||
[showToast]
|
[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(
|
const loadCorporateCityOptions = React.useCallback(
|
||||||
async (emirateId) => {
|
async (emirateId) => {
|
||||||
if (!emirateId) {
|
if (!emirateId) {
|
||||||
@ -1082,6 +1213,16 @@ const CompanyProfile = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const mapApiEstablishmentToProfile = React.useCallback((item) => {
|
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 base = createEmptyProfile();
|
||||||
const primaryUser = Array.isArray(item?.users) && item.users.length ? item.users[0] : item?.establishment_user ?? null;
|
const primaryUser = Array.isArray(item?.users) && item.users.length ? item.users[0] : item?.establishment_user ?? null;
|
||||||
const emiratiMale = item?.emirati_male ?? '';
|
const emiratiMale = item?.emirati_male ?? '';
|
||||||
@ -1094,6 +1235,24 @@ const CompanyProfile = () => {
|
|||||||
const corporateEmirateName = item?.corporate_emirate?.name ?? item?.emirate ?? '';
|
const corporateEmirateName = item?.corporate_emirate?.name ?? item?.emirate ?? '';
|
||||||
const establishmentCityName = item?.establishment_city?.name ?? base.contactCityTown;
|
const establishmentCityName = item?.establishment_city?.name ?? base.contactCityTown;
|
||||||
const corporateCityName = item?.corporate_city?.name ?? base.corporateCityTown;
|
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 {
|
return {
|
||||||
...base,
|
...base,
|
||||||
@ -1114,12 +1273,13 @@ const CompanyProfile = () => {
|
|||||||
totalEmployees: totalEmployees !== '' && totalEmployees !== null ? String(totalEmployees) : '',
|
totalEmployees: totalEmployees !== '' && totalEmployees !== null ? String(totalEmployees) : '',
|
||||||
status: item?.is_active === false ? 'Inactive' : 'Active',
|
status: item?.is_active === false ? 'Inactive' : 'Active',
|
||||||
isActive: item?.is_active !== false,
|
isActive: item?.is_active !== false,
|
||||||
createdBy: item?.created_by_name ?? primaryUser?.name ?? '',
|
createdBy: creatorName,
|
||||||
createdById: item?.created_by ?? base.createdById,
|
createdById: item?.created_by ?? base.createdById,
|
||||||
createdOn: formatApiDate(item?.created_at),
|
createdOn: formatApiDate(item?.created_at),
|
||||||
lastUpdated: item?.updated_by ?? formatApiDate(item?.updated_at),
|
lastUpdated: item?.updated_by ?? formatApiDate(item?.updated_at),
|
||||||
contactName: item?.factory_name ?? '',
|
contactName: item?.factory_name ?? '',
|
||||||
contactEmail: item?.email ?? '',
|
// contactEmail: item?.email ?? '',
|
||||||
|
contactEmail: item?.establishment_contact_email ?? base.contactEmail,
|
||||||
contactPostalCode: item?.establishment_postal_code ?? base.contactPostalCode,
|
contactPostalCode: item?.establishment_postal_code ?? base.contactPostalCode,
|
||||||
contactPoBox: item?.establishment_po_box ?? base.contactPoBox,
|
contactPoBox: item?.establishment_po_box ?? base.contactPoBox,
|
||||||
contactMakaniNumber: item?.establishment_makani_number ?? base.contactMakaniNumber,
|
contactMakaniNumber: item?.establishment_makani_number ?? base.contactMakaniNumber,
|
||||||
@ -1173,6 +1333,7 @@ const loadProfiles = async () => {
|
|||||||
try {
|
try {
|
||||||
const response = await fetchEstablishments({ params, signal: controller.signal });
|
const response = await fetchEstablishments({ params, signal: controller.signal });
|
||||||
const payload = response?.data ?? response;
|
const payload = response?.data ?? response;
|
||||||
|
console.log("payloadtest",payload)
|
||||||
const records = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
|
const records = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
|
||||||
|
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
@ -1257,73 +1418,103 @@ const loadProfiles = async () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEdit = (identifier) => {
|
const handleEdit = async (identifier) => {
|
||||||
if (modalMode && isDirty) {
|
if (modalMode && isDirty) {
|
||||||
const shouldContinue = window.confirm('You have unsaved changes. Do you want to discard them and continue?');
|
const shouldContinue = window.confirm('You have unsaved changes. Do you want to discard them and continue?');
|
||||||
if (!shouldContinue) {
|
if (!shouldContinue) {
|
||||||
return;
|
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
|
// Find the profile record
|
||||||
navigate(`?edit=${encodeURIComponent(identifier)}`);
|
|
||||||
if (!identifier) return;
|
|
||||||
const originalIndex = profiles.findIndex((item) => {
|
const originalIndex = profiles.findIndex((item) => {
|
||||||
if (item.apiId !== null && item.apiId !== undefined && item.apiId === identifier) {
|
if (item.apiId !== null && item.apiId !== undefined && item.apiId === identifier) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return item.establishmentId === identifier;
|
return item.establishmentId === identifier;
|
||||||
});
|
});
|
||||||
if (originalIndex === -1) return;
|
|
||||||
setEditingRow(originalIndex);
|
if (originalIndex === -1) {
|
||||||
|
showToast('error', 'Establishment not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const profileRecord = profiles[originalIndex];
|
const profileRecord = profiles[originalIndex];
|
||||||
const base = createEmptyProfile();
|
setEditingRow(originalIndex);
|
||||||
let nextForm = {
|
|
||||||
...base,
|
// If we have an API ID, fetch the latest data
|
||||||
...profileRecord,
|
if (profileRecord?.apiId) {
|
||||||
corporateSameAs: Boolean(profileRecord?.corporateSameAs),
|
const response = await fetchEstablishmentDetail(profileRecord.apiId);
|
||||||
};
|
const detail = response?.data || response;
|
||||||
if (nextForm.corporateSameAs) {
|
|
||||||
Object.entries(contactToCorporateMap).forEach(([source, target]) => {
|
if (!detail) {
|
||||||
nextForm[target] = nextForm[source];
|
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');
|
setModalMode('edit');
|
||||||
initialSnapshotRef.current = nextForm;
|
|
||||||
corporateBackupRef.current = captureCorporateValues(nextForm);
|
|
||||||
setIsDirty(false);
|
setIsDirty(false);
|
||||||
|
|
||||||
if (profileRecord?.apiId !== null && profileRecord?.apiId !== undefined) {
|
} catch (error) {
|
||||||
(async () => {
|
console.error('Error in handleEdit:', error);
|
||||||
try {
|
const message = error?.response?.data?.message || error?.message || 'Failed to load establishment details';
|
||||||
const response = await fetchEstablishmentDetail(profileRecord.apiId);
|
showToast('error', message);
|
||||||
const detail = response?.data || response;
|
setModalMode(null);
|
||||||
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);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = (establishmentId) => {
|
const handleDelete = (establishmentId) => {
|
||||||
if (!establishmentId) return;
|
if (!establishmentId) return;
|
||||||
@ -1387,7 +1578,7 @@ const loadProfiles = async () => {
|
|||||||
setModalMode(null);
|
setModalMode(null);
|
||||||
setForm(createEmptyProfile());
|
setForm(createEmptyProfile());
|
||||||
setEditingRow(null);
|
setEditingRow(null);
|
||||||
setErrors({});
|
setFieldErrors({});
|
||||||
setIsDirty(false);
|
setIsDirty(false);
|
||||||
// Clear URL parameters when closing modal
|
// Clear URL parameters when closing modal
|
||||||
navigate('');
|
navigate('');
|
||||||
@ -1401,6 +1592,56 @@ const loadProfiles = async () => {
|
|||||||
setDeletingRow(null);
|
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 () => {
|
const handleDeleteConfirm = async () => {
|
||||||
if (deletingRow === null) return;
|
if (deletingRow === null) return;
|
||||||
if (isDeletingApi) return;
|
if (isDeletingApi) return;
|
||||||
@ -1650,7 +1891,8 @@ const loadProfiles = async () => {
|
|||||||
non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0,
|
non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0,
|
||||||
total_emirati: Number(totalEmirati) || 0,
|
total_emirati: Number(totalEmirati) || 0,
|
||||||
total_employees: Number(employmentTotals) || 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: {
|
establishment_user: {
|
||||||
name: form.userProfileName || '',
|
name: form.userProfileName || '',
|
||||||
email: form.userProfileEmail || '',
|
email: form.userProfileEmail || '',
|
||||||
@ -1659,6 +1901,7 @@ const loadProfiles = async () => {
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const apiResponse = await createEstablishment(apiPayload);
|
const apiResponse = await createEstablishment(apiPayload);
|
||||||
|
console.log('API Response:', apiResponse);
|
||||||
const successMessage = apiResponse?.message || 'Establishment added successfully.';
|
const successMessage = apiResponse?.message || 'Establishment added successfully.';
|
||||||
showToast('success', successMessage);
|
showToast('success', successMessage);
|
||||||
const createdRecord = apiResponse?.data;
|
const createdRecord = apiResponse?.data;
|
||||||
@ -1689,25 +1932,50 @@ const loadProfiles = async () => {
|
|||||||
delete establishmentUserPayload.email;
|
delete establishmentUserPayload.email;
|
||||||
}
|
}
|
||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
establishment_code: form.establishmentId || '',
|
establishment_code: form.establishmentId || '',
|
||||||
factory_name: form.establishmentName || form.contactName || '',
|
factory_name: form.establishmentName || form.contactName || '',
|
||||||
email: form.contactEmail || form.userProfileEmail || '',
|
permanent_factory_code: form.permanentFactoryCode || '',
|
||||||
permanent_factory_code: form.permanentFactoryCode || '',
|
industry_code: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
||||||
industry_code: form.industryCodeCurrent || form.industryCodeBusiness || '',
|
license_number: form.uniqueLicenseNumber || '',
|
||||||
license_number: form.uniqueLicenseNumber || '',
|
isic_code: form.isicCode || form.industryCodeBusiness || '',
|
||||||
isic_code: form.industryCodeBusiness || form.isicCode || '',
|
description: form.industryDescription || '',
|
||||||
emirate: form.emirate || form.contactEmirate || '',
|
establishment_address: form.contactAddress || '',
|
||||||
emirati_male: Number(form.employmentEmiratiMale) || 0,
|
establishment_city_town_id: Number(form.contactCityTownId) || 0,
|
||||||
emirati_female: Number(form.employmentEmiratiFemale) || 0,
|
establishment_emirate_id: Number(form.contactEmirateId) || 0,
|
||||||
non_emirati_male: Number(form.employmentNonEmiratiMale) || 0,
|
establishment_postal_code: form.contactPostalCode || '',
|
||||||
non_emirati_female: Number(form.employmentNonEmiratiFemale) || 0,
|
establishment_po_box: form.contactPoBox || '',
|
||||||
total_emirati: Number(totalEmirati) || 0,
|
establishment_makani_number: form.contactMakaniNumber || '',
|
||||||
total_employees: Number(employmentTotals) || 0,
|
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) {
|
if (establishmentUserPayload.name || establishmentUserPayload.email || establishmentUserPayload.password) {
|
||||||
updatePayload.establishment_user = establishmentUserPayload;
|
updatePayload.establishment_user = establishmentUserPayload;
|
||||||
}
|
}
|
||||||
const apiResponse = await updateEstablishment(targetId, updatePayload);
|
const apiResponse = await updateEstablishment(targetId, updatePayload);
|
||||||
|
console.log("apiResponse",apiResponse)
|
||||||
const successMessage = apiResponse?.message || 'Establishment updated successfully.';
|
const successMessage = apiResponse?.message || 'Establishment updated successfully.';
|
||||||
showToast('success', successMessage);
|
showToast('success', successMessage);
|
||||||
apiResultData = apiResponse?.data;
|
apiResultData = apiResponse?.data;
|
||||||
@ -1932,7 +2200,7 @@ const loadProfiles = async () => {
|
|||||||
120, // ISIC Code (120px)
|
120, // ISIC Code (120px)
|
||||||
150, // Establishment ID (150px)
|
150, // Establishment ID (150px)
|
||||||
100, // Products (100px)
|
100, // Products (100px)
|
||||||
120, // Total Employees (120px)
|
130, // Total Employees (120px)
|
||||||
120, // Created By (120px)
|
120, // Created By (120px)
|
||||||
120, // Created On (120px)
|
120, // Created On (120px)
|
||||||
120, // Last Updated (120px)
|
120, // Last Updated (120px)
|
||||||
|
|||||||
@ -56,6 +56,17 @@ const IsicHsCodes = () => {
|
|||||||
const [form, setForm] = React.useState(createEmptyCodeForm());
|
const [form, setForm] = React.useState(createEmptyCodeForm());
|
||||||
const [unitOptions, setUnitOptions] = React.useState([]);
|
const [unitOptions, setUnitOptions] = React.useState([]);
|
||||||
const [toast, setToast] = React.useState({ show: false, message: '' });
|
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
|
// Fetch units on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -111,18 +122,32 @@ const IsicHsCodes = () => {
|
|||||||
: response.data.data || [];
|
: response.data.data || [];
|
||||||
|
|
||||||
if (products.length > 0) {
|
if (products.length > 0) {
|
||||||
const formattedData = products.map((product) => ({
|
const formattedData = await Promise.all(products.map(async (product) => {
|
||||||
id: product.id,
|
// Try to get creator's name if available
|
||||||
code: product.hs_code || 'N/A',
|
let createdByName = '-';
|
||||||
product: product.product_name || 'N/A',
|
if (product.created_by) {
|
||||||
unit: 'Unit',
|
try {
|
||||||
estimatedMapped: 0,
|
// If the API returns the creator's name in the product data, use it
|
||||||
createdBy: product.created_by || 'System',
|
createdByName = product.created_by_user?.name || '-';
|
||||||
updated: product.updated_at
|
} catch (error) {
|
||||||
? new Date(product.updated_at).toLocaleDateString()
|
console.error('Error fetching creator info:', error);
|
||||||
: '-',
|
}
|
||||||
status: product.is_active ? 'Active' : 'Inactive',
|
}
|
||||||
description: product.hs_description || '',
|
|
||||||
|
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);
|
setRowsData(formattedData);
|
||||||
setFilteredData(formattedData);
|
setFilteredData(formattedData);
|
||||||
@ -199,7 +224,7 @@ const IsicHsCodes = () => {
|
|||||||
setForm({
|
setForm({
|
||||||
code: product.hs_code || '',
|
code: product.hs_code || '',
|
||||||
product: product.product_name || '',
|
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',
|
status: product.is_active ? 'Active' : 'Inactive',
|
||||||
description: product.hs_description || ''
|
description: product.hs_description || ''
|
||||||
});
|
});
|
||||||
@ -235,7 +260,7 @@ const IsicHsCodes = () => {
|
|||||||
setForm({
|
setForm({
|
||||||
code: product.hs_code || '',
|
code: product.hs_code || '',
|
||||||
product: product.product_name || '',
|
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',
|
status: product.is_active ? 'Active' : 'Inactive',
|
||||||
description: product.hs_description || ''
|
description: product.hs_description || ''
|
||||||
});
|
});
|
||||||
@ -253,30 +278,98 @@ const IsicHsCodes = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (index) => {
|
// Store the row index when delete is clicked
|
||||||
setDeletingRow(index);
|
const [deletingRowIndex, setDeletingRowIndex] = React.useState(null);
|
||||||
setShowDeleteConfirm(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deletingCode = React.useMemo(() => {
|
const handleDelete = async (paginationIndex) => {
|
||||||
if (deletingRow === null || deletingRow < 0 || deletingRow >= rowsData.length)
|
// Convert pagination index to actual data index
|
||||||
return null;
|
const dataIndex = (currentPage - 1) * pageSize + paginationIndex;
|
||||||
return rowsData[deletingRow];
|
console.log('Delete button clicked, paginationIndex:', paginationIndex, 'dataIndex:', dataIndex);
|
||||||
}, [deletingRow, rowsData]);
|
|
||||||
|
if (dataIndex < 0 || dataIndex >= rowsData.length) {
|
||||||
const closeDeleteConfirm = () => {
|
console.error('Invalid row index for deletion');
|
||||||
setShowDeleteConfirm(false);
|
return;
|
||||||
setDeletingRow(null);
|
}
|
||||||
};
|
|
||||||
|
const productId = rowsData[dataIndex]?.id;
|
||||||
const handleDeleteConfirm = async () => {
|
if (!productId) {
|
||||||
if (deletingRow === null) return;
|
console.error('No product ID found for deletion');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the row index for later use
|
||||||
|
setDeletingRowIndex(dataIndex);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
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) {
|
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
|
// Call delete API
|
||||||
@ -376,6 +469,8 @@ const IsicHsCodes = () => {
|
|||||||
product_name: form.product,
|
product_name: form.product,
|
||||||
is_active: form.status === 'Active',
|
is_active: form.status === 'Active',
|
||||||
hs_code: form.code,
|
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 })
|
...(form.description && { hs_description: form.description })
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -391,35 +486,60 @@ const IsicHsCodes = () => {
|
|||||||
console.log(`Updating product with ID: ${productId}`);
|
console.log(`Updating product with ID: ${productId}`);
|
||||||
|
|
||||||
try {
|
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);
|
const response = await productService.updateProduct(productId, productData);
|
||||||
console.log('Update response:', response);
|
console.log('Update response:', response);
|
||||||
|
|
||||||
// Update the local state with the updated product data
|
// Find the selected unit from unitOptions to get the uom
|
||||||
setRowsData(prev =>
|
const selectedUnit = unitOptions.find(u => u.value === form.unit);
|
||||||
prev.map(item =>
|
console.log('Selected unit for update:', selectedUnit);
|
||||||
item.id === productId
|
|
||||||
? {
|
// Create the updated product object
|
||||||
...item,
|
const updatedProduct = {
|
||||||
code: form.code,
|
...rowsData[editingRow],
|
||||||
product: form.product,
|
code: form.code,
|
||||||
status: form.status,
|
product: form.product,
|
||||||
description: form.description || '',
|
unit: selectedUnit ? selectedUnit.label : 'N/A',
|
||||||
updated: new Date().toLocaleDateString('en-GB')
|
unit_id: form.unit ? parseInt(form.unit) : null,
|
||||||
}
|
updated: new Date().toLocaleDateString('en-GB'),
|
||||||
: item
|
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!');
|
showToast('Product updated successfully!');
|
||||||
closeModal();
|
closeModal();
|
||||||
return; // Exit the function after successful update
|
return;
|
||||||
} catch (updateError) {
|
} catch (updateError) {
|
||||||
console.error('Error updating product:', 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);
|
setError(errorMessage);
|
||||||
return; // Exit the function after error
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,19 +549,26 @@ const IsicHsCodes = () => {
|
|||||||
const response = await productService.createProduct(productData);
|
const response = await productService.createProduct(productData);
|
||||||
console.log('Create response:', response);
|
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 = {
|
const newProduct = {
|
||||||
id: response.data?.id || Date.now(), // Fallback to timestamp if no ID in response
|
id: response.data?.id || Date.now(), // Fallback to timestamp if no ID in response
|
||||||
code: form.code,
|
code: form.code,
|
||||||
product: form.product,
|
product: form.product,
|
||||||
unit: 'Unit',
|
unit: selectedUnit ? selectedUnit.label : 'N/A',
|
||||||
|
unit_id: form.unit ? parseInt(form.unit) : null,
|
||||||
estimatedMapped: 0,
|
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'),
|
updated: new Date().toLocaleDateString('en-GB'),
|
||||||
status: form.status,
|
status: form.status,
|
||||||
description: form.description || ''
|
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');
|
console.log('Product created successfully');
|
||||||
showToast('Product created successfully!');
|
showToast('Product created successfully!');
|
||||||
closeModal();
|
closeModal();
|
||||||
@ -782,14 +909,14 @@ const IsicHsCodes = () => {
|
|||||||
<div className="flex justify-end gap-4">
|
<div className="flex justify-end gap-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="h-10 px-6 rounded-md border border-[#92722A] text-sm font-medium text-[#92722A] bg-white"
|
className="h-10 px-6 rounded-md border border-[#92722A] text-sm font-medium text-[#92722A] bg-white hover:bg-gray-50"
|
||||||
onClick={handleDeleteConfirm}
|
onClick={closeDeleteConfirm}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="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}
|
onClick={handleDeleteConfirm}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
|
|||||||
@ -347,7 +347,7 @@ const QuarterlyWindows = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
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"
|
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" />
|
<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 && (
|
{showDeleteConfirm && (
|
||||||
<div className="fixed inset-0 z-50">
|
<div className="fixed inset-0 z-50">
|
||||||
<div className="absolute inset-0 bg-black/40" onClick={closeDeleteConfirm} />
|
<div
|
||||||
<div
|
className="absolute inset-0 bg-black/40"
|
||||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white border"
|
onClick={() => {
|
||||||
style={{
|
setShowDeleteConfirm(false);
|
||||||
width: '480px',
|
setDeletingRow(null);
|
||||||
maxWidth: '92vw',
|
}}
|
||||||
borderColor: '#CBD5E1',
|
/>
|
||||||
boxShadow: '0 20px 45px rgba(0,0,0,0.12)',
|
<div
|
||||||
}}
|
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg bg-white border"
|
||||||
>
|
style={{
|
||||||
<div className="px-8 py-6 space-y-6">
|
width: '480px',
|
||||||
<div className="flex items-start gap-3">
|
borderColor: '#CBD5E1',
|
||||||
<span
|
boxShadow: '0 20px 45px rgba(0,0,0,0.12)',
|
||||||
className="flex items-center justify-center"
|
}}
|
||||||
style={{ width: '32px', height: '36px' }}
|
>
|
||||||
>
|
<div className="px-8 py-6 space-y-6">
|
||||||
<img src={deleteIconSrc} alt="Delete" className="h-6 w-6" />
|
<div className="flex items-start gap-3">
|
||||||
</span>
|
<img src={deleteIconSrc} alt="Delete" className="h-6 w-6 mt-1" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-[18px] font-semibold text-[#232528]">Delete Quarter</h3>
|
<h3 className="text-[18px] font-semibold text-[#232528]">
|
||||||
<p className="mt-1 text-sm leading-5 text-[#4B5563]">
|
Delete Product
|
||||||
Are you sure you want to delete{' '}
|
</h3>
|
||||||
{deletingQuarter && (
|
<p className="mt-1 text-sm text-[#4B5563]">
|
||||||
<span className="font-semibold text-[#232528]">
|
Are you sure you want to delete{' '}
|
||||||
{`${deletingQuarter.year}-${deletingQuarter.quarter}`}
|
<span className="font-semibold text-[#232528]">
|
||||||
</span>
|
{rowsData[deletingRow]?.product || 'this product'}?
|
||||||
)}
|
</span>
|
||||||
?
|
</p>
|
||||||
</p>
|
<p className="mt-2 text-sm text-[#4B5563]">
|
||||||
<p className="mt-2 text-sm leading-5 text-[#4B5563]">
|
This action will permanently delete the data and cannot be undone.
|
||||||
This action will permanently delete the data and cannot be undone.
|
</p>
|
||||||
</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>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import Table from '@/components/common/Table';
|
|||||||
import StatusBadge from '@/components/common/StatusBadge';
|
import StatusBadge from '@/components/common/StatusBadge';
|
||||||
import { TextField, SelectField } from '@/components/common/FormControls';
|
import { TextField, SelectField } from '@/components/common/FormControls';
|
||||||
import { getUnits, createUnit, updateUnit, deleteUnit } from '@/services/configuration/unitService';
|
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 downloadIconSrc = '/assets/images/DownloadSimple.svg';
|
||||||
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
|
const addIconSrc = '/assets/images/ic_baseline-plus.svg';
|
||||||
const pencilActiveSrc = '/assets/images/PencilSimple.svg';
|
const pencilActiveSrc = '/assets/images/PencilSimple.svg';
|
||||||
@ -40,17 +41,47 @@ const UnitMaster = () => {
|
|||||||
fetchUnits();
|
fetchUnits();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchUnits = async () => {
|
// const fetchUnits = async () => {
|
||||||
try {
|
// try {
|
||||||
setLoading(true);
|
// setLoading(true);
|
||||||
const response = await getUnits();
|
// const response = await getUnits();
|
||||||
setUnits(response.data || []);
|
// setUnits(response.data || []);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('Error fetching units:', error);
|
// console.error('Error fetching units:', error);
|
||||||
} finally {
|
// } finally {
|
||||||
setLoading(false);
|
// 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(() => {
|
useEffect(() => {
|
||||||
const userProfile = sessionStorage.getItem('user_profile');
|
const userProfile = sessionStorage.getItem('user_profile');
|
||||||
@ -59,36 +90,45 @@ const UnitMaster = () => {
|
|||||||
setCurrentUser(JSON.parse(userProfile));
|
setCurrentUser(JSON.parse(userProfile));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const headers = [
|
const headers = [
|
||||||
'Unit Name',
|
'Unit Name',
|
||||||
'Description',
|
'Description',
|
||||||
'Mapped Products',
|
'Mapped Products',
|
||||||
'Created By',
|
'Created By',
|
||||||
'Created On',
|
'Created On',
|
||||||
'Status',
|
'Last Updated',
|
||||||
'Actions',
|
'Status',
|
||||||
];
|
'Actions',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
const rows = useMemo(() => {
|
const rows = useMemo(() => {
|
||||||
return units.map((item) => {
|
return units.map((item) => {
|
||||||
const createdDate = item.created_at ? new Date(item.created_at).toLocaleDateString('en-GB') : '-';
|
const createdDate = item.created_at
|
||||||
const currentUserName = currentUser?.name || '';
|
? new Date(item.created_at).toLocaleDateString('en-GB')
|
||||||
|
: '-';
|
||||||
const updatedDate =
|
const updatedDate =
|
||||||
item.updated_at && item.updated_at !== item.created_at
|
item.updated_at && item.updated_at !== item.created_at
|
||||||
? new Date(item.updated_at).toLocaleDateString('en-GB')
|
? new Date(item.updated_at).toLocaleDateString('en-GB')
|
||||||
: '-';
|
: '-';
|
||||||
|
const currentUserName = currentUser?.name || '';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
item.uom || item.unitName,
|
item.uom || item.unitName, // Unit Name
|
||||||
item.uom_short_name || item.description,
|
item.uom_short_name || item.description, // Description
|
||||||
Array.isArray(item.productsMapped) ? item.productsMapped.length : 0,
|
Array.isArray(item.productsMapped) ? item.productsMapped.length : 0, // Mapped Products
|
||||||
item.created_by_name || currentUserName || '-',
|
item.created_by_name || currentUserName || '-', // Created By
|
||||||
createdDate,
|
createdDate, // Created On
|
||||||
<StatusBadge status={item.is_active ? 'Active' : 'Inactive'} tone={item.is_active ? 'green' : 'gray'} />,
|
updatedDate, // Last Update (moved here)
|
||||||
'actions',
|
<StatusBadge
|
||||||
|
status={item.is_active ? 'Active' : 'Inactive'}
|
||||||
|
tone={item.is_active ? 'green' : 'gray'}
|
||||||
|
/>, // Status
|
||||||
|
'actions', // Actions
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}, [units]);
|
}, [units, currentUser]);
|
||||||
|
|
||||||
|
|
||||||
const statusOptions = React.useMemo(
|
const statusOptions = React.useMemo(
|
||||||
() => [
|
() => [
|
||||||
@ -201,9 +241,89 @@ const UnitMaster = () => {
|
|||||||
return `${day}/${month}/${year}`;
|
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 : [];
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,6 +331,7 @@ const handleSaveForm = async () => {
|
|||||||
uom: form.unitName,
|
uom: form.unitName,
|
||||||
uom_short_name: form.description,
|
uom_short_name: form.description,
|
||||||
is_active: form.status === 'Active',
|
is_active: form.status === 'Active',
|
||||||
|
productsMapped: selectedProducts,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -235,10 +356,13 @@ const handleSaveForm = async () => {
|
|||||||
created_at: item.created_at,
|
created_at: item.created_at,
|
||||||
created_by: item.created_by,
|
created_by: item.created_by,
|
||||||
created_by_name: item.created_by_name,
|
created_by_name: item.created_by_name,
|
||||||
|
productsMapped: selectedProducts,
|
||||||
}
|
}
|
||||||
: item
|
: item
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
toast.success('Unit updated successfully!');
|
||||||
} else {
|
} else {
|
||||||
const newUnit = await createUnit({
|
const newUnit = await createUnit({
|
||||||
...unitData,
|
...unitData,
|
||||||
@ -247,13 +371,22 @@ const handleSaveForm = async () => {
|
|||||||
created_by_name: currentUser?.name || '',
|
created_by_name: currentUser?.name || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
setUnits(prev => [newUnit, ...prev]);
|
setUnits(prev => [
|
||||||
|
{
|
||||||
|
...newUnit,
|
||||||
|
productsMapped: selectedProducts,
|
||||||
|
},
|
||||||
|
...prev,
|
||||||
|
]);
|
||||||
|
|
||||||
|
toast.success('New unit added successfully!');
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetchUnits();
|
await fetchUnits();
|
||||||
closeModal();
|
closeModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving unit:', error);
|
console.error('Error saving unit:', error);
|
||||||
|
toast.error('Something went wrong while saving the unit.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -261,6 +394,10 @@ const handleSaveForm = async () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const deletingUnit = React.useMemo(() => {
|
const deletingUnit = React.useMemo(() => {
|
||||||
if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) {
|
if (deletingRow === null || deletingRow < 0 || deletingRow >= units.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -289,10 +426,60 @@ const handleSaveForm = async () => {
|
|||||||
<div className="px-6 py-3 flex items-center justify-between border-b border-[#E5E7EB]">
|
<div className="px-6 py-3 flex items-center justify-between border-b border-[#E5E7EB]">
|
||||||
<h3 className="text-[16px] font-medium text-[#232528]">Unit Master</h3>
|
<h3 className="text-[16px] font-medium text-[#232528]">Unit Master</h3>
|
||||||
<div className="flex items-center gap-2">
|
<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" />
|
<img src={downloadIconSrc} alt="Export" className="h-5 w-5" />
|
||||||
<span className="font-medium text-[#232528]">Export CSV</span>
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="h-10 px-4 rounded-[6px] bg-[#92722A] text-white text-sm inline-flex items-center gap-2 cursor-pointer"
|
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="px-6 py-5">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<TextField
|
<TextField
|
||||||
label="Unit Name"
|
label={
|
||||||
value={form.unitName}
|
<>
|
||||||
onChange={handleFormChange('unitName')}
|
Unit Name <span className="text-red-500">*</span>
|
||||||
placeholder="Enter Unit Name"
|
</>
|
||||||
width="100%"
|
}
|
||||||
/>
|
value={form.unitName}
|
||||||
|
onChange={handleFormChange('unitName')}
|
||||||
|
placeholder="Enter Unit Name"
|
||||||
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Description"
|
label="Description"
|
||||||
value={form.description}
|
value={form.description}
|
||||||
@ -445,14 +635,18 @@ const handleSaveForm = async () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<SelectField
|
|
||||||
label="Status"
|
<SelectField
|
||||||
value={form.status}
|
label={
|
||||||
onChange={handleFormChange('status')}
|
<>
|
||||||
options={statusOptions}
|
Status <span className="text-red-500">*</span>
|
||||||
placeholder="Select Status"
|
</>
|
||||||
width="100%"
|
}
|
||||||
/>
|
value={form.status}
|
||||||
|
onChange={handleFormChange('status')}
|
||||||
|
options={statusOptions}
|
||||||
|
placeholder="Select Status"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex items-center justify-end gap-3">
|
<div className="mt-6 flex items-center justify-end gap-3">
|
||||||
@ -463,19 +657,21 @@ const handleSaveForm = async () => {
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="h-10 px-4 rounded-[6px] bg-[#92722A] text-white text-sm font-medium disabled:opacity-50"
|
className="h-10 px-4 rounded-[6px] bg-[#92722A] text-white text-sm font-medium disabled:opacity-50"
|
||||||
onClick={handleSaveForm}
|
onClick={handleSave}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex items-center justify-center">
|
<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>
|
<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...'}
|
{modalMode === 'edit' ? 'Updating...' : 'Saving...'}
|
||||||
</div>
|
</div>
|
||||||
) : modalMode === 'edit' ? 'Update' : 'Save'}
|
) : modalMode === 'edit' ? 'Update' : 'Save'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 tableRows = filteredRows.map((entry, index) => {
|
||||||
const isExpanded = expandedRow === index;
|
const isExpanded = expandedRow === index;
|
||||||
return [
|
return [
|
||||||
@ -511,6 +566,38 @@ const History = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white w-full rounded-lg shadow-sm ring-1 ring-gray-200 mt-1">
|
<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 ? (
|
{filteredRows.length === 0 && !loading ? (
|
||||||
<div className="flex flex-col items-center justify-center py-12">
|
<div className="flex flex-col items-center justify-center py-12">
|
||||||
<p className="text-gray-500 text-lg">No details found</p>
|
<p className="text-gray-500 text-lg">No details found</p>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user