bug fixed
This commit is contained in:
parent
346790205a
commit
234f6c4794
@ -279,11 +279,11 @@ function App() {
|
||||
}
|
||||
/>
|
||||
</Route> */}
|
||||
<Route path="/profile/edit/:id" element={
|
||||
{/* <Route path="/profile/edit/:id" element={
|
||||
<RequireRole allowedRoles={['EstablishmentUser']}>
|
||||
<EditCompanyProfile />
|
||||
</RequireRole>
|
||||
} />
|
||||
} /> */}
|
||||
{/* <Route
|
||||
path="/admin/configuration/profile/:id?"
|
||||
element={
|
||||
@ -292,6 +292,14 @@ function App() {
|
||||
</RequireRole>
|
||||
}
|
||||
/> */}
|
||||
<Route
|
||||
path="/edit-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 />} />
|
||||
|
||||
@ -18,6 +18,7 @@ const HeaderBar = () => {
|
||||
const [userOpen, setUserOpen] = React.useState(false);
|
||||
const profileRef = React.useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const establishmentId = sessionStorage.getItem("establishment_id") || "";
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
React.useEffect(() => {
|
||||
@ -131,6 +132,37 @@ const HeaderBar = () => {
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to={`/edit-profile/${establishmentId}`}
|
||||
className={({ isActive }) =>
|
||||
`inline-flex items-center gap-2 pb-1 border-b-2 ${
|
||||
isActive
|
||||
? "text-[#92722A] font-medium border-[#92722A]"
|
||||
: "text-[#232528] hover:text-gray-900 border-transparent"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<svg
|
||||
className={`h-[16px] w-[18px] ${
|
||||
isActive ? "text-[#92722A]" : "text-[#232528]"
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Profile</span>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
{/* <NavLink
|
||||
to="/history"
|
||||
className={({ isActive }) => `inline-flex items-center gap-2 pb-1 border-b-2 ${isActive ? 'text-[#92722A] font-medium border-[#92722A]' : 'text-[#232528] hover:text-gray-900 border-transparent'}`}
|
||||
|
||||
@ -236,19 +236,33 @@ const EstablishmentInfo = ({
|
||||
});
|
||||
};
|
||||
|
||||
// const handleEditProfile = (e) => {
|
||||
// e.preventDefault();
|
||||
|
||||
// const establishmentId = sessionStorage.getItem('establishment_id');
|
||||
// if (!establishmentId) {
|
||||
// alert('No establishment ID found in session');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// localStorage.setItem('returnTo', window.location.pathname);
|
||||
// sessionStorage.setItem('edit_profile_from', 'EstablishmentUser');
|
||||
// // navigate(`/profile/edit/${establishmentId}`);
|
||||
// };
|
||||
const handleEditProfile = (e) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
const establishmentId = sessionStorage.getItem('establishment_id');
|
||||
if (!establishmentId) {
|
||||
alert('No establishment ID found in session');
|
||||
return;
|
||||
}
|
||||
const establishmentId = sessionStorage.getItem('establishment_id');
|
||||
if (!establishmentId) {
|
||||
alert('No establishment ID found in session');
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem('returnTo', window.location.pathname);
|
||||
sessionStorage.setItem('edit_profile_from', 'EstablishmentUser');
|
||||
navigate(`/profile/edit/${establishmentId}`);
|
||||
localStorage.setItem('returnTo', window.location.pathname);
|
||||
sessionStorage.setItem('edit_profile_from', 'EstablishmentUser');
|
||||
navigate(`/edit-profile/${establishmentId}`);
|
||||
};
|
||||
|
||||
const handleEmployeeChange = (field) => (event) => {
|
||||
onChange({
|
||||
...info,
|
||||
@ -375,7 +389,7 @@ const EstablishmentInfo = ({
|
||||
here
|
||||
</a>.
|
||||
</p> */}
|
||||
<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 →{' '}
|
||||
<a
|
||||
href={`/admin/configuration/profile/edit=${sessionStorage.getItem('establishment_id') || ''}`}
|
||||
@ -386,7 +400,17 @@ const EstablishmentInfo = ({
|
||||
Edit Profile
|
||||
</a>
|
||||
. Changes saved there will appear here.
|
||||
</p>
|
||||
</p> */}
|
||||
<p className="text-sm text-[#043DFF] bg-[#E7F5FF]">
|
||||
<span className="font-medium">Need to update details?</span> Go to Profile →{' '}
|
||||
<a
|
||||
href={`/admin/configuration/edit-profile/${sessionStorage.getItem('establishment_id') || ''}`}
|
||||
className="underline font-medium text-[#043DFF] hover:text-[#063B82]"
|
||||
onClick={handleEditProfile}
|
||||
>
|
||||
Edit Profile
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h2 className="text-lg font-semibold text-[#232528]">Step 1: Review Establishment Information</h2>
|
||||
|
||||
@ -15,6 +15,8 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [passwordTouched, setPasswordTouched] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [toastData, setToastData] = useState(null); // 👈 for custom toast
|
||||
|
||||
@ -41,16 +43,14 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
||||
|
||||
if (!formData.status) newErrors.status = 'Status is required';
|
||||
|
||||
if (!formData.password) {
|
||||
newErrors.password = 'Password is required';
|
||||
} else if (formData.password.length < 8) {
|
||||
newErrors.password = 'Password must be at least 8 characters';
|
||||
} else if (formData.password.length > 12) {
|
||||
newErrors.password = 'Password cannot exceed 12 characters';
|
||||
const passwordValidation = validatePassword(formData.password);
|
||||
if (passwordValidation) {
|
||||
newErrors.password = passwordValidation;
|
||||
}
|
||||
|
||||
if (formData.password !== formData.confirmPassword)
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
newErrors.confirmPassword = 'Passwords do not match';
|
||||
}
|
||||
|
||||
return newErrors;
|
||||
};
|
||||
@ -70,10 +70,38 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const validatePassword = (password) => {
|
||||
if (!password) return 'Password is required';
|
||||
if (password.length < 8) return 'Password must be at least 8 characters';
|
||||
if (!/[A-Z]/.test(password)) return 'Must contain at least one uppercase letter';
|
||||
if (!/[a-z]/.test(password)) return 'Must contain at least one lowercase letter';
|
||||
if (!/\d/.test(password)) return 'Must contain at least one number';
|
||||
if (!/[!@#$%^&*]/.test(password)) return 'Must contain at least one special character (!@#$%^&*)';
|
||||
return '';
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
|
||||
// Clear any existing error for this field
|
||||
if (errors[name]) setErrors((prev) => ({ ...prev, [name]: '' }));
|
||||
|
||||
// Real-time password validation
|
||||
if (name === 'password') {
|
||||
setPasswordTouched(true);
|
||||
const error = validatePassword(value);
|
||||
setPasswordError(error);
|
||||
}
|
||||
|
||||
// Clear confirm password error when either password changes
|
||||
if ((name === 'password' || name === 'confirmPassword') && formData.password && formData.confirmPassword) {
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setErrors(prev => ({ ...prev, confirmPassword: 'Passwords do not match' }));
|
||||
} else {
|
||||
setErrors(prev => ({ ...prev, confirmPassword: '' }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
@ -156,29 +184,37 @@ const AddAdminUsers = ({ isOpen, onClose, onSave }) => {
|
||||
placeholder="Enter email"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
error={errors.password}
|
||||
required
|
||||
placeholder="Enter password"
|
||||
showToggle
|
||||
/>
|
||||
<div className="relative">
|
||||
<TextField
|
||||
label="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
onBlur={() => setPasswordTouched(true)}
|
||||
error={passwordTouched && passwordError ? passwordError : ''}
|
||||
required
|
||||
placeholder="Enter password"
|
||||
showToggle
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
error={errors.confirmPassword}
|
||||
required
|
||||
placeholder="Confirm password"
|
||||
showToggle
|
||||
/>
|
||||
<div className="relative">
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
error={errors.confirmPassword}
|
||||
required
|
||||
placeholder="Confirm password"
|
||||
showToggle
|
||||
/>
|
||||
{formData.confirmPassword && !errors.confirmPassword && (
|
||||
<p className="mt-1 text-xs text-green-600">Passwords match</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2 space-y-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
|
||||
@ -3,7 +3,8 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { TextField, SelectField, PhoneField } from "@/components/common/FormControls";
|
||||
import Loader from "@/components/common/Loader";
|
||||
import { fetchEstablishmentDetail, updateEstablishment } from '@/services/establishments/establishmentService';
|
||||
import { fetchEmirates, fetchCityTowns } from '@/services/masters/masterService';
|
||||
import { fetchEmirates, fetchCityTowns, fetchProducts } from '@/services/masters/masterService';
|
||||
import HeaderBar from "@/components/layout/HeaderBar";
|
||||
|
||||
// Helper functions
|
||||
const createEmptyProfile = () => ({
|
||||
@ -26,20 +27,6 @@ const createEmptyProfile = () => ({
|
||||
contactPostalCode: '',
|
||||
contactPoBox: '',
|
||||
contactWebsite: '',
|
||||
corporateName: '',
|
||||
corporateAddress: '',
|
||||
corporateCityTown: '',
|
||||
corporateCityTownId: '',
|
||||
corporateEmirate: '',
|
||||
corporateEmirateId: '',
|
||||
corporateMakaniNumber: '',
|
||||
corporateContactPersonName: '',
|
||||
corporateMobileNumber: '',
|
||||
corporateEmail: '',
|
||||
corporatePostalCode: '',
|
||||
corporatePoBox: '',
|
||||
corporateWebsite: '',
|
||||
corporateSameAs: false,
|
||||
employmentEmiratiMale: 0,
|
||||
employmentEmiratiFemale: 0,
|
||||
employmentNonEmiratiMale: 0,
|
||||
@ -69,20 +56,6 @@ const mapApiEstablishmentToProfile = (apiData) => {
|
||||
contactPostalCode: data.establishment_postal_code || '',
|
||||
contactPoBox: data.establishment_po_box || '',
|
||||
contactWebsite: data.establishment_website || '',
|
||||
corporateName: data.corporate_name || '',
|
||||
corporateAddress: data.corporate_address || '',
|
||||
corporateCityTown: data.corporate_city?.name || '',
|
||||
corporateCityTownId: data.corporate_city?.id || '',
|
||||
corporateEmirate: data.corporate_emirate?.name || '',
|
||||
corporateEmirateId: data.corporate_emirate?.id || '',
|
||||
corporateMakaniNumber: data.corporate_makani_number || '',
|
||||
corporateContactPersonName: data.corporate_contact_person_name || '',
|
||||
corporateMobileNumber: data.corporate_mobile_number || '',
|
||||
corporateEmail: data.corporate_email || '',
|
||||
corporatePostalCode: data.corporate_postal_code || '',
|
||||
corporatePoBox: data.corporate_po_box || '',
|
||||
corporateWebsite: data.corporate_website || '',
|
||||
corporateSameAs: data.corporate_same_as_establishment || false,
|
||||
employmentEmiratiMale: data.emirati_male || 0,
|
||||
employmentEmiratiFemale: data.emirati_female || 0,
|
||||
employmentNonEmiratiMale: data.non_emirati_male || 0,
|
||||
@ -90,22 +63,6 @@ const mapApiEstablishmentToProfile = (apiData) => {
|
||||
};
|
||||
};
|
||||
|
||||
const contactToCorporateMap = {
|
||||
contactName: 'corporateName',
|
||||
contactAddress: 'corporateAddress',
|
||||
contactCityTown: 'corporateCityTown',
|
||||
contactEmirate: 'corporateEmirate',
|
||||
contactCityTownId: 'corporateCityTownId',
|
||||
contactEmirateId: 'corporateEmirateId',
|
||||
contactMakaniNumber: 'corporateMakaniNumber',
|
||||
contactPersonName: 'corporateContactPersonName',
|
||||
contactMobileNumber: 'corporateMobileNumber',
|
||||
contactEmail: 'corporateEmail',
|
||||
contactPostalCode: 'corporatePostalCode',
|
||||
contactPoBox: 'corporatePoBox',
|
||||
contactWebsite: 'corporateWebsite',
|
||||
};
|
||||
|
||||
const computeEmploymentTotals = (data) => {
|
||||
const emiratiMale = parseInt(data.employmentEmiratiMale) || 0;
|
||||
const nonEmiratiMale = parseInt(data.employmentNonEmiratiMale) || 0;
|
||||
@ -121,18 +78,11 @@ const computeEmploymentTotals = (data) => {
|
||||
};
|
||||
};
|
||||
|
||||
const captureCorporateValues = (data) => {
|
||||
const corporateData = {};
|
||||
Object.values(contactToCorporateMap).forEach(key => {
|
||||
corporateData[key] = data[key];
|
||||
});
|
||||
return corporateData;
|
||||
};
|
||||
|
||||
const EditCompanyProfile = ({ onClose: propOnClose }) => {
|
||||
const EditCompanyProfile = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id: establishmentId } = useParams();
|
||||
const onClose = propOnClose || (() => navigate('/survey'));
|
||||
console.log('EditCompanyProfile mounted, establishmentId from params:', establishmentId);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form, setForm] = useState(createEmptyProfile());
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
@ -144,16 +94,18 @@ const EditCompanyProfile = ({ onClose: propOnClose }) => {
|
||||
const [emirateOptions, setEmirateOptions] = useState([]);
|
||||
const [emirateLookup, setEmirateLookup] = useState({});
|
||||
const [contactCityOptions, setContactCityOptions] = useState([]);
|
||||
const [corporateCityOptions, setCorporateCityOptions] = useState([]);
|
||||
const [availableProducts, setAvailableProducts] = useState([]);
|
||||
const [selectedProducts, setSelectedProducts] = useState([]);
|
||||
const [productSearchTerm, setProductSearchTerm] = useState('');
|
||||
const [isLoadingProducts, setIsLoadingProducts] = useState(false);
|
||||
const initialSnapshotRef = useRef(null);
|
||||
const corporateBackupRef = useRef(null);
|
||||
|
||||
const formSteps = React.useMemo(
|
||||
() => [
|
||||
{ label: 'User Profile' },
|
||||
{ label: 'Identification Particulars' },
|
||||
{ label: 'Establishment Contact Details' },
|
||||
{ label: 'Corporate / Head Office Contact' },
|
||||
{ label: 'Products' },
|
||||
{ label: 'Employment in Establishment' },
|
||||
],
|
||||
[]
|
||||
@ -182,14 +134,7 @@ const EditCompanyProfile = ({ onClose: propOnClose }) => {
|
||||
{ field: 'contactEmail', label: 'Email' },
|
||||
],
|
||||
3: [
|
||||
{ field: 'corporateName', label: 'Name' },
|
||||
{ field: 'corporateAddress', label: 'Address' },
|
||||
{ field: 'corporateCityTown', label: 'City/Town' },
|
||||
{ field: 'corporateEmirate', label: 'Emirate' },
|
||||
{ field: 'corporateMakaniNumber', label: 'Makani Number' },
|
||||
{ field: 'corporateContactPersonName', label: 'Contact Person Name' },
|
||||
{ field: 'corporateMobileNumber', label: 'Mobile Number' },
|
||||
{ field: 'corporateEmail', label: 'Email' },
|
||||
// Products are optional, no required fields
|
||||
],
|
||||
4: [
|
||||
{ field: 'employmentEmiratiMale', label: 'Number of Emirati Male' },
|
||||
@ -231,16 +176,6 @@ const EditCompanyProfile = ({ onClose: propOnClose }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (field === 'corporateCityTown') {
|
||||
const foundCity = corporateCityOptions.find(option => option.value === value || option.label === value);
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
corporateCityTown: foundCity?.name || '',
|
||||
corporateCityTownId: foundCity?.id || ''
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
setForm((prev) => ({ ...prev, [field]: value }));
|
||||
setFieldErrors((prev) => ({ ...prev, [field]: "" }));
|
||||
|
||||
@ -251,7 +186,7 @@ const EditCompanyProfile = ({ onClose: propOnClose }) => {
|
||||
if (field === 'userProfileConfirmPassword') {
|
||||
setConfirmError("");
|
||||
}
|
||||
}, [contactCityOptions, corporateCityOptions]);
|
||||
}, [contactCityOptions]);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
if (validateStepFields(activeStep)) {
|
||||
@ -292,53 +227,169 @@ const EditCompanyProfile = ({ onClose: propOnClose }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (!establishmentId) {
|
||||
// Load products
|
||||
const loadProducts = useCallback(async () => {
|
||||
try {
|
||||
setIsLoadingProducts(true);
|
||||
const response = await fetchProducts();
|
||||
|
||||
let productsData = [];
|
||||
|
||||
if (Array.isArray(response)) {
|
||||
productsData = response;
|
||||
} else if (response?.data) {
|
||||
productsData = Array.isArray(response.data) ? response.data : response.data.products || [];
|
||||
}
|
||||
|
||||
console.log('Processed products data:', productsData);
|
||||
|
||||
// Format products data
|
||||
const formattedProducts = productsData.map(p => ({
|
||||
id: p.id || p.value,
|
||||
product_id: p.id || p.value,
|
||||
hs_code: p.hs_code || p.hsCode || '',
|
||||
hsCode: p.hs_code || p.hsCode || '',
|
||||
product_name: p.product_name || p.productName || p.label || '',
|
||||
productName: p.product_name || p.productName || p.label || '',
|
||||
label: p.product_name || p.productName || p.label || '',
|
||||
value: p.id || p.value
|
||||
}));
|
||||
|
||||
setAvailableProducts(formattedProducts);
|
||||
return formattedProducts;
|
||||
} catch (error) {
|
||||
console.error('Error loading products:', error);
|
||||
return [];
|
||||
} finally {
|
||||
setIsLoadingProducts(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Product search handler
|
||||
const handleProductSearch = (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
setProductSearchTerm(searchTerm);
|
||||
|
||||
if (!searchTerm) {
|
||||
setAvailableProducts(prev => prev.filter(p =>
|
||||
!selectedProducts.some(sp => sp.id === p.id)
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetchEstablishmentDetail(establishmentId);
|
||||
const filtered = availableProducts.filter(product =>
|
||||
(product.label?.toLowerCase().includes(searchTerm) ||
|
||||
product.hs_code?.toLowerCase().includes(searchTerm)) &&
|
||||
!selectedProducts.some(sp => sp.id === product.id)
|
||||
);
|
||||
|
||||
if (!response) {
|
||||
console.error('Empty response received');
|
||||
return;
|
||||
}
|
||||
setAvailableProducts(filtered);
|
||||
};
|
||||
|
||||
const mappedProfile = mapApiEstablishmentToProfile(response);
|
||||
// Add product handler
|
||||
const handleAddProduct = (product) => {
|
||||
setSelectedProducts(prev => {
|
||||
const updated = [...prev, product];
|
||||
return updated;
|
||||
});
|
||||
|
||||
const formData = {
|
||||
...createEmptyProfile(),
|
||||
...mappedProfile,
|
||||
apiId: establishmentId,
|
||||
corporateSameAs: Boolean(mappedProfile.corporateSameAs)
|
||||
};
|
||||
// Remove from available products
|
||||
setAvailableProducts(prev => prev.filter(p => p.id !== product.id));
|
||||
};
|
||||
|
||||
if (formData.corporateSameAs) {
|
||||
Object.entries(contactToCorporateMap).forEach(([source, target]) => {
|
||||
formData[target] = formData[source];
|
||||
});
|
||||
}
|
||||
// Remove product handler
|
||||
const handleRemoveProduct = (product) => {
|
||||
setSelectedProducts(prev => prev.filter(p => p.id !== product.id));
|
||||
|
||||
const totals = computeEmploymentTotals(formData);
|
||||
Object.assign(formData, totals);
|
||||
|
||||
setForm(formData);
|
||||
initialSnapshotRef.current = formData;
|
||||
corporateBackupRef.current = captureCorporateValues(formData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load establishment details:', error);
|
||||
alert('Failed to load company details. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Add back to available products if not already there and matches search
|
||||
if (!productSearchTerm ||
|
||||
(product.hs_code && product.hs_code.toLowerCase().includes(productSearchTerm)) ||
|
||||
(product.product_name && product.product_name.toLowerCase().includes(productSearchTerm))) {
|
||||
setAvailableProducts(prev => {
|
||||
if (!prev.some(p => p.id === product.id)) {
|
||||
return [...prev, product];
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [establishmentId]);
|
||||
useEffect(() => {
|
||||
console.log('useEffect triggered, establishmentId:', establishmentId);
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!establishmentId) {
|
||||
console.log('No establishmentId found, skipping fetch');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log('Starting API call to fetch establishment details for ID:', establishmentId);
|
||||
|
||||
const response = await fetchEstablishmentDetail(establishmentId);
|
||||
console.log('API Response received:', response);
|
||||
|
||||
if (!response) {
|
||||
console.error('Empty response received');
|
||||
return;
|
||||
}
|
||||
|
||||
const mappedProfile = mapApiEstablishmentToProfile(response);
|
||||
console.log('Mapped Profile:', mappedProfile);
|
||||
|
||||
const formData = {
|
||||
...createEmptyProfile(),
|
||||
...mappedProfile,
|
||||
apiId: establishmentId
|
||||
};
|
||||
|
||||
const totals = computeEmploymentTotals(formData);
|
||||
Object.assign(formData, totals);
|
||||
|
||||
console.log('Final Form Data:', formData);
|
||||
|
||||
setForm(formData);
|
||||
initialSnapshotRef.current = formData;
|
||||
|
||||
// Load products and set selected products
|
||||
const allProducts = await loadProducts();
|
||||
|
||||
// Set selected products from API response
|
||||
const establishmentProducts = response.data?.establishment_products || response.establishment_products || [];
|
||||
if (Array.isArray(establishmentProducts) && establishmentProducts.length > 0) {
|
||||
const formattedSelectedProducts = establishmentProducts.map(ep => {
|
||||
const productData = ep.product || {};
|
||||
return {
|
||||
id: ep.id || 0,
|
||||
product_id: ep.product_id || 0,
|
||||
hs_code: productData.hs_code || ep.hs_code || '',
|
||||
hsCode: productData.hs_code || ep.hs_code || '',
|
||||
product_name: productData.product_name || ep.product_name || 'Unnamed Product',
|
||||
productName: productData.product_name || ep.product_name || 'Unnamed Product',
|
||||
label: `${productData.product_name || ep.product_name || 'Unnamed Product'}${productData.hs_code ? ` (${productData.hs_code})` : ''}`,
|
||||
value: ep.product_id ? String(ep.product_id) : '',
|
||||
...productData
|
||||
};
|
||||
});
|
||||
setSelectedProducts(formattedSelectedProducts);
|
||||
|
||||
// Remove selected products from available products
|
||||
setAvailableProducts(prev =>
|
||||
prev.filter(p => !formattedSelectedProducts.some(sp => sp.id === p.id))
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load establishment details:', error);
|
||||
alert('Failed to load company details. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [establishmentId, loadProducts]);
|
||||
|
||||
// Load emirates on component mount
|
||||
useEffect(() => {
|
||||
@ -391,19 +442,6 @@ useEffect(() => {
|
||||
loadContactCities();
|
||||
}, [form.contactEmirateId, loadCities]);
|
||||
|
||||
// Load corporate cities when emirate ID changes
|
||||
useEffect(() => {
|
||||
const loadCorporateCities = async () => {
|
||||
if (form.corporateEmirateId) {
|
||||
await loadCities(form.corporateEmirateId, setCorporateCityOptions);
|
||||
} else {
|
||||
setCorporateCityOptions([]);
|
||||
}
|
||||
};
|
||||
|
||||
loadCorporateCities();
|
||||
}, [form.corporateEmirateId, loadCities]);
|
||||
|
||||
// Calculate totals when employment numbers change
|
||||
useEffect(() => {
|
||||
const totals = computeEmploymentTotals(form);
|
||||
@ -418,34 +456,9 @@ useEffect(() => {
|
||||
form.employmentNonEmiratiFemale
|
||||
]);
|
||||
|
||||
// Handle corporate same as establishment checkbox
|
||||
const handleCorporateSameAsChange = useCallback((checked) => {
|
||||
if (checked) {
|
||||
// Save current corporate values before overwriting
|
||||
corporateBackupRef.current = captureCorporateValues(form);
|
||||
|
||||
// Copy contact details to corporate
|
||||
const corporateUpdates = { corporateSameAs: true };
|
||||
Object.entries(contactToCorporateMap).forEach(([source, target]) => {
|
||||
corporateUpdates[target] = form[source];
|
||||
});
|
||||
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
...corporateUpdates
|
||||
}));
|
||||
} else {
|
||||
// Restore original corporate values
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
corporateSameAs: false,
|
||||
...(corporateBackupRef.current || {})
|
||||
}));
|
||||
corporateBackupRef.current = null;
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
const handleSave = async () => {
|
||||
console.log("Submitted data:", form);
|
||||
console.log("Selected products:", selectedProducts);
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
@ -461,8 +474,6 @@ useEffect(() => {
|
||||
establishment_address: form.contactAddress || "",
|
||||
establishment_city_town_id: form.contactCityTownId ? Number(form.contactCityTownId) : null,
|
||||
establishment_emirate_id: form.contactEmirateId ? Number(form.contactEmirateId) : null,
|
||||
corporate_city_town_id: form.corporateCityTownId ? Number(form.corporateCityTownId) : null,
|
||||
corporate_emirate_id: form.corporateEmirateId ? Number(form.corporateEmirateId) : null,
|
||||
establishment_postal_code: form.contactPostalCode || "",
|
||||
establishment_po_box: form.contactPoBox || "",
|
||||
establishment_makani_number: form.contactMakaniNumber || "",
|
||||
@ -471,17 +482,6 @@ useEffect(() => {
|
||||
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_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,
|
||||
@ -495,12 +495,20 @@ useEffect(() => {
|
||||
(Number(form.employmentNonEmiratiMale) || 0) +
|
||||
(Number(form.employmentNonEmiratiFemale) || 0),
|
||||
updated_by: 1,
|
||||
establishment_products: Array.isArray(selectedProducts) && selectedProducts.length > 0
|
||||
? selectedProducts.map(p => ({
|
||||
product_id: p.product_id || p.id || 0
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
|
||||
console.log("Payload sent:", establishmentData);
|
||||
|
||||
if (establishmentId) {
|
||||
console.log("Updating establishment with ID:", establishmentId);
|
||||
await updateEstablishment(establishmentId, establishmentData);
|
||||
onClose();
|
||||
console.log("Profile updated successfully!");
|
||||
navigate('/survey');
|
||||
} else {
|
||||
console.error("No establishment ID provided for update");
|
||||
alert("Error: No establishment ID provided for update");
|
||||
@ -760,184 +768,106 @@ useEffect(() => {
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h4 className="text-[16px] font-semibold text-[#232528]">Corporate/Head Office Contact</h4>
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-[16px] font-semibold text-[#232528]">Products</h4>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Available Products Panel */}
|
||||
<div className="bg-white rounded-lg border border-[#E5E7EB] overflow-hidden">
|
||||
<div className="bg-[#F9FAFB] px-4 py-3 border-b border-[#E5E7EB]">
|
||||
<h5 className="text-sm font-medium text-[#111827]">Available Products</h5>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="relative mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search products..."
|
||||
value={productSearchTerm}
|
||||
onChange={handleProductSearch}
|
||||
className="w-full px-3 py-2 border border-[#D1D5DB] rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[#92722A] focus:border-transparent"
|
||||
/>
|
||||
<svg
|
||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="border border-[#E5E7EB] rounded-md overflow-hidden">
|
||||
{isLoadingProducts ? (
|
||||
<div className="flex justify-center items-center p-4">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#92722A]"></div>
|
||||
</div>
|
||||
) : availableProducts.length > 0 ? (
|
||||
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
|
||||
{availableProducts.map((product) => (
|
||||
<li key={product.id} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#111827]">
|
||||
{product.hsCode || ''}
|
||||
{product.hsCode && (product.productName || product.label || product.product_name) ? ' - ' : ''}
|
||||
{product.productName || product.label || product.product_name || ''}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAddProduct(product)}
|
||||
className="text-[#92722A] hover:text-[#7A5F1E] text-sm font-medium"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="p-4 text-center text-sm text-[#6B7280]">
|
||||
{productSearchTerm ? 'No matching products found' : 'No products available'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label className="inline-flex items-center gap-2 text-sm text-[#232528]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.corporateSameAs}
|
||||
onChange={(e) => handleCorporateSameAsChange(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-2 border-[#CBA344] text-[#92722A] focus:ring-[#92722A]"
|
||||
/>
|
||||
Same as Establishment Contact
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<TextField
|
||||
label="Name"
|
||||
value={form.corporateName || ""}
|
||||
onChange={(e) => handleFormChange("corporateName", e.target.value)}
|
||||
placeholder="Enter Name"
|
||||
width="100%"
|
||||
required
|
||||
disabled={form.corporateSameAs}
|
||||
error={fieldErrors.corporateName}
|
||||
/>
|
||||
<TextField
|
||||
label="Address"
|
||||
value={form.corporateAddress || ""}
|
||||
onChange={(e) => handleFormChange("corporateAddress", e.target.value)}
|
||||
placeholder="Enter Address"
|
||||
width="100%"
|
||||
required
|
||||
disabled={form.corporateSameAs}
|
||||
error={fieldErrors.corporateAddress}
|
||||
/>
|
||||
{form.corporateSameAs ? (
|
||||
<>
|
||||
<TextField
|
||||
label="Emirate"
|
||||
value={form.contactEmirate || ""}
|
||||
disabled={true}
|
||||
width="100%"
|
||||
/>
|
||||
<TextField
|
||||
label="City/Town"
|
||||
value={form.contactCityTown || ""}
|
||||
disabled={true}
|
||||
width="100%"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SelectField
|
||||
label="Emirate"
|
||||
name="corporateEmirate"
|
||||
value={form.corporateEmirateId || ""}
|
||||
onChange={async (e) => {
|
||||
const selectedValue = e.target.value;
|
||||
const selectedEmirate = emirateOptions.find(opt => opt.value === selectedValue);
|
||||
|
||||
handleFormChange("corporateEmirate", selectedEmirate?.name || '');
|
||||
handleFormChange("corporateEmirateId", selectedEmirate?.id || '');
|
||||
handleFormChange("corporateCityTown", '');
|
||||
handleFormChange("corporateCityTownId", '');
|
||||
|
||||
if (selectedEmirate?.id) {
|
||||
const cities = await loadCities(selectedEmirate.id, setCorporateCityOptions);
|
||||
setCorporateCityOptions(cities);
|
||||
} else {
|
||||
setCorporateCityOptions([]);
|
||||
}
|
||||
}}
|
||||
options={emirateOptions.map(opt => ({
|
||||
value: opt.value,
|
||||
label: opt.label
|
||||
}))}
|
||||
placeholder="Select Emirate"
|
||||
width="100%"
|
||||
required
|
||||
error={fieldErrors.corporateEmirate}
|
||||
/>
|
||||
<SelectField
|
||||
label="City/Town"
|
||||
name="corporateCityTown"
|
||||
value={form.corporateCityTownId || ''}
|
||||
onChange={(e) => {
|
||||
const selectedValue = e.target.value;
|
||||
const selectedCity = corporateCityOptions.find(opt => opt.value === selectedValue);
|
||||
if (selectedCity) {
|
||||
handleFormChange("corporateCityTown", selectedCity.value);
|
||||
}
|
||||
}}
|
||||
options={corporateCityOptions.map(opt => ({
|
||||
value: opt.value,
|
||||
label: opt.label
|
||||
}))}
|
||||
placeholder={form.corporateEmirateId ? "Select City/Town" : "Select emirate first"}
|
||||
width="100%"
|
||||
required
|
||||
disabled={!form.corporateEmirateId}
|
||||
error={fieldErrors.corporateCityTown}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<TextField
|
||||
label="Postal Code"
|
||||
value={form.corporatePostalCode || ""}
|
||||
onChange={(e) => handleFormChange("corporatePostalCode", e.target.value)}
|
||||
placeholder="Enter Postal Code"
|
||||
width="100%"
|
||||
disabled={form.corporateSameAs}
|
||||
/>
|
||||
<TextField
|
||||
label="PO Box"
|
||||
value={form.corporatePoBox || ""}
|
||||
onChange={(e) => handleFormChange("corporatePoBox", e.target.value)}
|
||||
placeholder="Enter P.O. Box"
|
||||
width="100%"
|
||||
disabled={form.corporateSameAs}
|
||||
/>
|
||||
<TextField
|
||||
label="Makani Number"
|
||||
value={form.corporateMakaniNumber || ""}
|
||||
onChange={(e) => handleFormChange("corporateMakaniNumber", e.target.value)}
|
||||
placeholder="Enter Makani Number"
|
||||
width="100%"
|
||||
required
|
||||
disabled={form.corporateSameAs}
|
||||
error={fieldErrors.corporateMakaniNumber}
|
||||
/>
|
||||
<TextField
|
||||
label="Contact Person Name"
|
||||
value={form.corporateContactPersonName || ""}
|
||||
onChange={(e) => handleFormChange("corporateContactPersonName", e.target.value)}
|
||||
placeholder="Enter Contact Person Name"
|
||||
width="100%"
|
||||
required
|
||||
disabled={form.corporateSameAs}
|
||||
error={fieldErrors.corporateContactPersonName}
|
||||
/>
|
||||
<TextField
|
||||
label="Contact Person Designation"
|
||||
value={form.corporateContactPersonDesignation || ""}
|
||||
onChange={(e) => handleFormChange("corporateContactPersonDesignation", e.target.value)}
|
||||
placeholder="Enter contact person designation"
|
||||
width="100%"
|
||||
disabled={form.corporateSameAs}
|
||||
/>
|
||||
<PhoneField
|
||||
label="Mobile Number"
|
||||
value={form.corporateMobileNumber || ""}
|
||||
onChange={(value) => handleFormChange("corporateMobileNumber", value)}
|
||||
placeholder="Enter Mobile Number"
|
||||
width="100%"
|
||||
required
|
||||
disabled={form.corporateSameAs}
|
||||
error={fieldErrors.corporateMobileNumber}
|
||||
/>
|
||||
<TextField
|
||||
label="Email"
|
||||
value={form.corporateEmail || ""}
|
||||
onChange={(e) => handleFormChange("corporateEmail", e.target.value)}
|
||||
placeholder="Enter Email"
|
||||
width="100%"
|
||||
type="email"
|
||||
required
|
||||
disabled={form.corporateSameAs}
|
||||
error={fieldErrors.corporateEmail}
|
||||
/>
|
||||
<TextField
|
||||
label="Website"
|
||||
value={form.corporateWebsite || ""}
|
||||
onChange={(e) => handleFormChange("corporateWebsite", e.target.value)}
|
||||
placeholder="Enter Website"
|
||||
width="100%"
|
||||
disabled={form.corporateSameAs}
|
||||
/>
|
||||
{/* Selected Products Panel */}
|
||||
<div className="bg-white rounded-lg border border-[#E5E7EB] overflow-hidden">
|
||||
<div className="bg-[#F9FAFB] px-4 py-3 border-b border-[#E5E7EB] flex justify-between items-center">
|
||||
<h5 className="text-sm font-medium text-[#111827]">Selected Products</h5>
|
||||
{selectedProducts.length > 0 && (
|
||||
<span className="bg-[#FEF3C7] text-[#92400E] text-xs font-medium px-2 py-0.5 rounded-full">
|
||||
{selectedProducts.length} selected
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
{selectedProducts.length > 0 ? (
|
||||
<ul className="divide-y divide-[#E5E7EB] max-h-96 overflow-y-auto">
|
||||
{selectedProducts.map((product) => (
|
||||
<li key={product.id} className="p-3 hover:bg-[#F9FAFB] flex justify-between items-center">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-[#111827]">
|
||||
{product.hs_code || product.hsCode || ''}
|
||||
{product.hs_code || product.hsCode ? ' - ' : ''}
|
||||
{product.product_name || product.productName || product.label || 'Unnamed Product'}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveProduct(product)}
|
||||
className="text-[#EF4444] hover:text-[#DC2626] text-sm font-medium"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="p-4 text-center text-sm text-[#6B7280] border border-[#E5E7EB] rounded-md">
|
||||
No products selected
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -1013,118 +943,128 @@ useEffect(() => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
|
||||
<div className="bg-white w-full max-w-7xl rounded-xl shadow-2xl overflow-hidden flex flex-col h-[75vh]">
|
||||
{loading && <Loader />}
|
||||
<div className="min-h-screen bg-[#F8FAFC]">
|
||||
<HeaderBar />
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center border-b border-[#F1F2F4] px-8 py-5 bg-white sticky top-0 z-10">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">Edit Company Profile</h2>
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="bg-white rounded-xl shadow-lg overflow-hidden">
|
||||
{loading && <Loader />}
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center border-b border-[#F1F2F4] px-8 py-5 bg-white">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900">Edit Company Profile</h2>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex h-7 w-7 text-lg items-center justify-center rounded hover:bg-gray-100 hover:bg-opacity-10 text-[#92722A] hover:text-[#92722A] transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<aside className="w-64 bg-[#F9FAFB] border-r border-[#E5E7EB] overflow-y-auto" style={{ maxHeight: 'calc(75vh - 120px)' }}>
|
||||
<ol className="py-6 px-4 space-y-2">
|
||||
{formSteps.map((step, index) => {
|
||||
const isActive = index === activeStep;
|
||||
const isCompleted = index < activeStep;
|
||||
return (
|
||||
<li key={step.label}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveStep(index)}
|
||||
className={`w-full flex items-center gap-3 rounded-md px-3 py-3 text-left transition-colors ${
|
||||
isActive
|
||||
? 'bg-[#92722A] text-white shadow-[0_8px_18px_rgba(146,114,42,0.22)]'
|
||||
: 'bg-transparent text-[#1F2937] hover:bg-[#F3F4F6]'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-sm font-semibold ${
|
||||
{/* Body */}
|
||||
<div className="flex flex-1">
|
||||
<aside className="w-64 bg-[#F9FAFB] border-r border-[#E5E7EB]">
|
||||
<ol className="py-6 px-4 space-y-2">
|
||||
{formSteps.map((step, index) => {
|
||||
const isActive = index === activeStep;
|
||||
const isCompleted = index < activeStep;
|
||||
return (
|
||||
<li key={step.label}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveStep(index)}
|
||||
className={`w-full flex items-center gap-3 rounded-md px-3 py-3 text-left transition-colors ${
|
||||
isActive
|
||||
? 'border-[#FDF7E7] bg-[#FDF7E7] text-[#92722A]'
|
||||
: isCompleted
|
||||
? 'border-[#92722A] bg-[#FDF7E7] text-[#92722A]'
|
||||
: 'border-[#D1D5DB] text-[#6B7280]'
|
||||
? 'bg-[#92722A] text-white shadow-[0_8px_18px_rgba(146,114,42,0.22)]'
|
||||
: 'bg-transparent text-[#1F2937] hover:bg-[#F3F4F6]'
|
||||
}`}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
<span className="flex-1">
|
||||
<span className={`block text-[15px] font-semibold ${isActive ? 'text-white' : 'text-[#111827]'}`}>
|
||||
{step.label}
|
||||
<span
|
||||
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-sm font-semibold ${
|
||||
isActive
|
||||
? 'border-[#FDF7E7] bg-[#FDF7E7] text-[#92722A]'
|
||||
: isCompleted
|
||||
? 'border-[#92722A] bg-[#FDF7E7] text-[#92722A]'
|
||||
: 'border-[#D1D5DB] text-[#6B7280]'
|
||||
}`}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</aside>
|
||||
<div className="flex-1 overflow-y-auto" style={{ maxHeight: 'calc(85vh - 120px)' }}>
|
||||
<div className="px-6 py-6">
|
||||
{renderStepContent()}
|
||||
<span className="flex-1">
|
||||
<span className={`block text-[15px] font-semibold ${isActive ? 'text-white' : 'text-[#111827]'}`}>
|
||||
{step.label}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</aside>
|
||||
<div className="flex-1">
|
||||
<div className="px-6 py-6">
|
||||
{renderStepContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t border-[#F1F2F4] bg-white">
|
||||
<div className="px-8 py-4 flex items-center justify-between gap-3">
|
||||
<div className="min-h-[1rem] flex items-center text-xs text-[#B45309]">
|
||||
{loading ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<svg className="h-4 w-4 animate-spin text-[#92722A]" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8v3a5 5 0 00-5 5H4z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Saving profile…</span>
|
||||
</span>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</div>
|
||||
{activeStep === formSteps.length - 1 ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-[#92722A] bg-white px-4 py-2 text-sm font-medium text-[#92722A] shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => onClose && onClose()}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`h-10 px-8 rounded-md bg-[#92722A] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)] ${loading ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Saving…' : 'Update'}
|
||||
</button>
|
||||
{/* Footer */}
|
||||
<div className="border-t border-[#F1F2F4] bg-white">
|
||||
<div className="px-8 py-4 flex items-center justify-between gap-3">
|
||||
<div className="min-h-[1rem] flex items-center text-xs text-[#B45309]">
|
||||
{loading ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<svg className="h-4 w-4 animate-spin text-[#92722A]" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8v3a5 5 0 00-5 5H4z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Saving profile…</span>
|
||||
</span>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-8 rounded-md bg-[#92722A] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)]"
|
||||
onClick={handleNext}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{activeStep > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-[#92722A] bg-white px-4 py-2 text-sm font-medium text-[#92722A] shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={handlePrev}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
)}
|
||||
|
||||
{activeStep === formSteps.length - 1 ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-[#92722A] bg-white px-4 py-2 text-sm font-medium text-[#92722A] shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => navigate('/survey')}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`h-10 px-8 rounded-md bg-[#92722A] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)] ${loading ? 'opacity-60 cursor-not-allowed' : ''}`}
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Saving…' : 'Update'}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="h-10 px-8 rounded-md bg-[#92722A] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)]"
|
||||
onClick={handleNext}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1133,3 +1073,4 @@ useEffect(() => {
|
||||
};
|
||||
|
||||
export default EditCompanyProfile;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -15,20 +15,33 @@ export const getUnits = async () => {
|
||||
|
||||
export const createUnit = async (unitData) => {
|
||||
try {
|
||||
// Log the data being sent for debugging
|
||||
console.log('Creating unit with data:', unitData);
|
||||
|
||||
const response = await postRequest(UNIT_MASTER_ENDPOINT, unitData);
|
||||
console.log('Unit created successfully:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error creating unit:', error);
|
||||
console.error('Error details:', {
|
||||
status: error.response?.status,
|
||||
data: error.response?.data,
|
||||
headers: error.response?.headers
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUnit = async (id, unitData) => {
|
||||
try {
|
||||
console.log('Updating unit with data:', unitData);
|
||||
|
||||
const response = await putRequest(`${UNIT_MASTER_ENDPOINT}/${id}`, unitData);
|
||||
console.log('Unit updated successfully:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error updating unit:', error);
|
||||
console.error('Error details:', error.response?.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@ -36,9 +49,25 @@ export const updateUnit = async (id, unitData) => {
|
||||
export const deleteUnit = async (id) => {
|
||||
try {
|
||||
const response = await deleteRequest(`${UNIT_MASTER_ENDPOINT}/${id}`);
|
||||
console.log('Unit deleted successfully:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error deleting unit:', error);
|
||||
console.error('Error details:', error.response?.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadUnitCSV = async (formData) => {
|
||||
try {
|
||||
const response = await postRequest(`${UNIT_MASTER_ENDPOINT}/uploadCSV`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error uploading unit CSV:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user