Merge branch 'master' of https://bitbucket.org/jubilian/fcsc_ipi_frontend
This commit is contained in:
commit
2eb18764f3
@ -106,7 +106,7 @@ const createEmptyProfile = () => ({
|
|||||||
contactEmail: '',
|
contactEmail: '',
|
||||||
contactWebsite: '',
|
contactWebsite: '',
|
||||||
// Corporate / head office contact details
|
// Corporate / head office contact details
|
||||||
corporateSameAs: false,
|
corporateSameAs: true,
|
||||||
corporateName: '',
|
corporateName: '',
|
||||||
corporateAddress: '',
|
corporateAddress: '',
|
||||||
corporateCityTown: '',
|
corporateCityTown: '',
|
||||||
@ -812,19 +812,29 @@ const CompanyProfile = () => {
|
|||||||
setActiveStep((prev) => Math.min(prev + 1, formSteps.length - 1));
|
setActiveStep((prev) => Math.min(prev + 1, formSteps.length - 1));
|
||||||
}, [activeStep, form.userProfilePassword, form.userProfileConfirmPassword, formSteps.length, validateStepFields]);
|
}, [activeStep, form.userProfilePassword, form.userProfileConfirmPassword, formSteps.length, validateStepFields]);
|
||||||
|
|
||||||
const handleToggleStatus = (item) => {
|
const handleToggleStatus = async (establishmentId, currentStatus) => {
|
||||||
const updatedStatus = item.status === 'Active' ? 'Inactive' : 'Active';
|
try {
|
||||||
|
const newStatus = !currentStatus;
|
||||||
|
await updateEstablishment(establishmentId, { is_active: newStatus });
|
||||||
|
|
||||||
// Optional: call your API to update backend
|
// Update the local state to reflect the change
|
||||||
// await updateEstablishmentStatus(item.id, updatedStatus);
|
setProfiles(prevProfiles =>
|
||||||
|
prevProfiles.map(profile =>
|
||||||
setEstablishments((prev) =>
|
profile.apiId === establishmentId
|
||||||
prev.map((est) =>
|
? { ...profile, isActive: newStatus, status: newStatus ? 'Active' : 'Inactive' }
|
||||||
est.id === item.id ? { ...est, status: updatedStatus } : est
|
: profile
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
showToast('success', `Establishment ${newStatus ? 'activated' : 'deactivated'} successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating status:', error);
|
||||||
|
const message = error?.response?.data?.message || 'Failed to update status. Please try again.';
|
||||||
|
showToast('error', message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const filteredProfiles = React.useMemo(() => {
|
const filteredProfiles = React.useMemo(() => {
|
||||||
if (!debouncedSearch.trim()) return profiles;
|
if (!debouncedSearch.trim()) return profiles;
|
||||||
const normalized = debouncedSearch.toLowerCase();
|
const normalized = debouncedSearch.toLowerCase();
|
||||||
@ -848,7 +858,7 @@ const CompanyProfile = () => {
|
|||||||
const renderStatusBadge = (status) => {
|
const renderStatusBadge = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
active: { text: 'Active', bgColor: 'bg-[#F3FAF4]', textColor: 'text-[#2F663C]' },
|
active: { text: 'Active', bgColor: 'bg-[#F3FAF4]', textColor: 'text-[#2F663C]' },
|
||||||
inactive: { text: 'Inactive', bgColor: 'bg-[#FEF2F2]', textColor: 'text-[#B91C1C]' },
|
inactive: { text: 'Inactive', bgColor: 'bg-[#E9EDF3]', textColor: 'text-[#232528]' },
|
||||||
pending: { text: 'Pending', bgColor: 'bg-[#FFFBEB]', textColor: 'text-[#B45309]' },
|
pending: { text: 'Pending', bgColor: 'bg-[#FFFBEB]', textColor: 'text-[#B45309]' },
|
||||||
// Add more status mappings as needed
|
// Add more status mappings as needed
|
||||||
};
|
};
|
||||||
@ -904,16 +914,18 @@ const CompanyProfile = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
className="h-[20px] w-[40px] grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
className="h-[20px] w-[40px] grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
||||||
title={item.status === 'Active' ? 'Deactivate' : 'Activate'}
|
title={item.status === 'Active' ? 'Deactivate' : 'Activate'}
|
||||||
aria-label={item.status === 'Active' ? 'Deactivate establishment' : 'Activate establishment'}
|
onClick={(e) => {
|
||||||
onClick={() => handleToggleStatus(item)}
|
e.stopPropagation();
|
||||||
|
handleToggleStatus(item.apiId, item.status === 'Active');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={item.status === 'Active' ? activeToggleSrc : inactiveToggleSrc}
|
src={item.status === 'Active' ? activeToggleSrc : inactiveToggleSrc}
|
||||||
alt={item.status === 'Active' ? 'Deactivate' : 'Activate'}
|
alt={item.status === 'Active' ? 'Active' : 'Inactive'}
|
||||||
className="h-[20px] w-[40px] object-contain"
|
className="h-[20px] w-[40px] object-contain"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
{/* <button
|
||||||
type="button"
|
type="button"
|
||||||
className="h-6 w-6 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
className="h-6 w-6 grid place-items-center rounded hover:bg-gray-50 cursor-pointer"
|
||||||
title={item.userProfileEmail ? 'Reset password' : 'Reset password'}
|
title={item.userProfileEmail ? 'Reset password' : 'Reset password'}
|
||||||
@ -926,7 +938,7 @@ const CompanyProfile = () => {
|
|||||||
alt="Reset password"
|
alt="Reset password"
|
||||||
className="h-4 w-4"
|
className="h-4 w-4"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button> */}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1890,7 +1902,11 @@ const loadProfiles = async () => {
|
|||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="px-6 pb-12">
|
<div className="px-6 pb-12">
|
||||||
<Loader size="lg" label="Loading company profiles..." />
|
<Loader size="md" label="Loading company profiles..." />
|
||||||
|
</div>
|
||||||
|
) : profiles.length === 0 ? (
|
||||||
|
<div className="px-4 py-10 text-center">
|
||||||
|
<h3 className="text-lg font-medium text-gray-900">No results found</h3>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import React from 'react';
|
|||||||
import Table from '@/components/common/Table';
|
import Table from '@/components/common/Table';
|
||||||
import { TextField, SelectField, DateField } from '@/components/common/FormControls';
|
import { TextField, SelectField, DateField } from '@/components/common/FormControls';
|
||||||
import StatusBadge from '@/components/common/StatusBadge';
|
import StatusBadge from '@/components/common/StatusBadge';
|
||||||
import { createQuarterlyWindow, updateQuarterlyWindow, getQuarterlyWindows } from '@/services/configuration/quarterlyWindows';
|
import { createQuarterlyWindow, updateQuarterlyWindow, getQuarterlyWindows, deleteQuarterlyWindow } from '@/services/configuration/quarterlyWindows';
|
||||||
|
import CustomToast from '@/components/common/CustomToast';
|
||||||
|
|
||||||
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';
|
||||||
@ -16,14 +17,15 @@ const searchIconSrc = '/assets/images/material-symbols_search-rounded.svg';
|
|||||||
const caretDownSrc = '/assets/images/CaretDown-black.svg';
|
const caretDownSrc = '/assets/images/CaretDown-black.svg';
|
||||||
|
|
||||||
const createEmptyQuarterForm = () => ({
|
const createEmptyQuarterForm = () => ({
|
||||||
|
survey_name: '',
|
||||||
establishment: '-', // Default to hyphen
|
establishment: '-', // Default to hyphen
|
||||||
year: '',
|
year: '',
|
||||||
quarter: '',
|
quarter: '',
|
||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: '',
|
endDate: '',
|
||||||
gracePeriod: '',
|
gracePeriod: '0 days',
|
||||||
submissionCount: '0',
|
submissionCount: '0',
|
||||||
status: '',
|
status: 'Active', // Default to Active
|
||||||
});
|
});
|
||||||
|
|
||||||
const QuarterlyWindows = () => {
|
const QuarterlyWindows = () => {
|
||||||
@ -41,6 +43,21 @@ const QuarterlyWindows = () => {
|
|||||||
const [currentPage, setCurrentPage] = React.useState(1);
|
const [currentPage, setCurrentPage] = React.useState(1);
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const [toast, setToast] = React.useState({ show: false, message: '', type: 'success' });
|
||||||
|
|
||||||
|
// Format date to yyyy-MM-dd for input fields
|
||||||
|
const formatDateForInput = (dateString) => {
|
||||||
|
if (!dateString) return '';
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toISOString().split('T')[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format date for display
|
||||||
|
const formatDateForDisplay = (dateString) => {
|
||||||
|
if (!dateString) return '';
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString('en-GB');
|
||||||
|
};
|
||||||
|
|
||||||
// Fetch quarterly windows data on component mount
|
// Fetch quarterly windows data on component mount
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -52,13 +69,14 @@ const QuarterlyWindows = () => {
|
|||||||
// Transform the API response to match the expected format
|
// Transform the API response to match the expected format
|
||||||
const formattedData = response.data.map(item => ({
|
const formattedData = response.data.map(item => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
establishment: item.establishment || '-', // Default to hyphen if empty
|
survey_name: item.survey_name || '',
|
||||||
|
establishment: item.establishment || '-',
|
||||||
year: item.year?.toString() || '',
|
year: item.year?.toString() || '',
|
||||||
quarter: item.quarter || '',
|
quarter: item.quarter || '',
|
||||||
startDate: item.start_date ? new Date(item.start_date).toLocaleDateString('en-GB') : '',
|
startDate: item.start_date || '',
|
||||||
endDate: item.end_date ? new Date(item.end_date).toLocaleDateString('en-GB') : '',
|
endDate: item.end_date || '',
|
||||||
gracePeriod: item.grace_periods_days ? `${item.grace_periods_days} days` : '0 days',
|
gracePeriod: item.grace_periods_days ? `${item.grace_periods_days} days` : '0 days',
|
||||||
submissionCount: '0', // Default value since it's not in the API response
|
submissionCount: '0',
|
||||||
status: item.is_active ? 'Active' : 'Inactive'
|
status: item.is_active ? 'Active' : 'Inactive'
|
||||||
}));
|
}));
|
||||||
setQuarterData(formattedData);
|
setQuarterData(formattedData);
|
||||||
@ -95,6 +113,7 @@ const QuarterlyWindows = () => {
|
|||||||
if (!matchesStatus) return false;
|
if (!matchesStatus) return false;
|
||||||
if (!term) return true;
|
if (!term) return true;
|
||||||
const haystack = [
|
const haystack = [
|
||||||
|
item.survey_name,
|
||||||
item.establishment,
|
item.establishment,
|
||||||
item.year,
|
item.year,
|
||||||
item.quarter,
|
item.quarter,
|
||||||
@ -111,11 +130,11 @@ const QuarterlyWindows = () => {
|
|||||||
}, [quarterData, searchTerm, statusFilter]);
|
}, [quarterData, searchTerm, statusFilter]);
|
||||||
|
|
||||||
const rows = filteredRows.map((item) => [
|
const rows = filteredRows.map((item) => [
|
||||||
item.establishment,
|
item.survey_name || '-',
|
||||||
item.year,
|
item.year,
|
||||||
item.quarter,
|
item.quarter,
|
||||||
item.startDate,
|
item.startDate ? formatDateForDisplay(item.startDate) : '-',
|
||||||
item.endDate,
|
item.endDate ? formatDateForDisplay(item.endDate) : '-',
|
||||||
item.gracePeriod,
|
item.gracePeriod,
|
||||||
item.submissionCount,
|
item.submissionCount,
|
||||||
<StatusBadge tone={String(item.status).toLowerCase() === 'active' ? 'green' : 'gray'} status={item.status} />,
|
<StatusBadge tone={String(item.status).toLowerCase() === 'active' ? 'green' : 'gray'} status={item.status} />,
|
||||||
@ -145,9 +164,15 @@ const QuarterlyWindows = () => {
|
|||||||
|
|
||||||
const openEditModal = (index) => {
|
const openEditModal = (index) => {
|
||||||
const selected = quarterData[index];
|
const selected = quarterData[index];
|
||||||
|
// Ensure dates are in the correct format for the form inputs
|
||||||
|
const formData = {
|
||||||
|
...selected,
|
||||||
|
startDate: selected.startDate ? formatDateForInput(selected.startDate) : '',
|
||||||
|
endDate: selected.endDate ? formatDateForInput(selected.endDate) : ''
|
||||||
|
};
|
||||||
setEditingRow(index);
|
setEditingRow(index);
|
||||||
setForm({ ...selected });
|
setForm(formData);
|
||||||
setOriginalForm({ ...selected });
|
setOriginalForm(formData);
|
||||||
setModalMode('edit');
|
setModalMode('edit');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -171,6 +196,8 @@ const QuarterlyWindows = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const openDeleteConfirm = (index) => {
|
const openDeleteConfirm = (index) => {
|
||||||
setDeletingRow(index);
|
setDeletingRow(index);
|
||||||
setShowDeleteConfirm(true);
|
setShowDeleteConfirm(true);
|
||||||
@ -181,22 +208,41 @@ const QuarterlyWindows = () => {
|
|||||||
setDeletingRow(null);
|
setDeletingRow(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showToast = (message, type = 'success') => {
|
||||||
|
setToast({ show: true, message, type });
|
||||||
|
setTimeout(() => setToast({ ...toast, show: false }), 4000);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
const formatDate = (dateString) => {
|
if (!form.survey_name || !form.year || !form.quarter || !form.startDate || !form.endDate) {
|
||||||
|
showToast('Please fill in all required fields', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formatDateForAPI = (dateString) => {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
|
// If already in YYYY-MM-DD format
|
||||||
|
if (dateString.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
// Handle DD/MM/YYYY format
|
||||||
|
if (dateString.includes('/')) {
|
||||||
const [day, month, year] = dateString.split('/');
|
const [day, month, year] = dateString.split('/');
|
||||||
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
// For any other format, let the Date object handle it
|
||||||
|
return new Date(dateString).toISOString().split('T')[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const formattedData = {
|
const formattedData = {
|
||||||
year: parseInt(form.year, 10) || 0,
|
survey_name: form.survey_name || '',
|
||||||
quarter: form.quarter || 'Q1',
|
year: form.year ? parseInt(form.year, 10) : 0,
|
||||||
start_date: formatDate(form.startDate) || new Date().toISOString().split('T')[0],
|
quarter: form.quarter || '',
|
||||||
end_date: formatDate(form.endDate) || new Date().toISOString().split('T')[0],
|
start_date: form.startDate ? formatDateForAPI(form.startDate) : '',
|
||||||
grace_periods_days: parseInt(form.gracePeriod?.split(' ')[0], 10) || 0,
|
end_date: form.endDate ? formatDateForAPI(form.endDate) : '',
|
||||||
|
grace_periods_days: form.gracePeriod ? parseInt(form.gracePeriod.split(' ')[0], 10) || 0 : 0,
|
||||||
is_active: form.status === 'Active',
|
is_active: form.status === 'Active',
|
||||||
establishment: '-', // Default to hyphen if empty
|
establishment: form.establishment || '-',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (modalMode === 'add') {
|
if (modalMode === 'add') {
|
||||||
@ -212,9 +258,17 @@ const QuarterlyWindows = () => {
|
|||||||
status: formattedData.is_active ? 'Active' : 'Inactive'
|
status: formattedData.is_active ? 'Active' : 'Inactive'
|
||||||
};
|
};
|
||||||
setQuarterData((prev) => [...prev, newItem]);
|
setQuarterData((prev) => [...prev, newItem]);
|
||||||
|
showToast('Quarterly window added successfully!');
|
||||||
|
} else {
|
||||||
|
showToast('Failed to add quarterly window', 'error');
|
||||||
}
|
}
|
||||||
} else if (modalMode === 'edit' && editingRow !== null) {
|
} else if (modalMode === 'edit' && editingRow !== null) {
|
||||||
const response = await updateQuarterlyWindow(quarterData[editingRow].id, formattedData);
|
const itemId = quarterData[editingRow]?.id;
|
||||||
|
if (!itemId) {
|
||||||
|
showToast('Error: Could not find the item to update', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await updateQuarterlyWindow(itemId, formattedData);
|
||||||
if (response.status === 'success') {
|
if (response.status === 'success') {
|
||||||
setQuarterData((prev) =>
|
setQuarterData((prev) =>
|
||||||
prev.map((item, idx) =>
|
prev.map((item, idx) =>
|
||||||
@ -230,23 +284,59 @@ const QuarterlyWindows = () => {
|
|||||||
: item
|
: item
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
showToast('Quarterly window updated successfully!');
|
||||||
|
} else {
|
||||||
|
showToast('Failed to update quarterly window', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving quarterly window:', error);
|
console.error('Error saving quarterly window:', error);
|
||||||
// You might want to add error handling UI here
|
showToast('An error occurred while saving. Please try again.', 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = async () => {
|
||||||
if (deletingRow !== null) {
|
if (deletingRow === null) return;
|
||||||
setQuarterData((prev) => prev.filter((_, index) => index !== deletingRow));
|
|
||||||
|
try {
|
||||||
|
// First, get the item to ensure we have the latest data
|
||||||
|
const itemToDelete = quarterData[deletingRow];
|
||||||
|
if (!itemToDelete?.id) {
|
||||||
|
showToast('Error: Could not find the item to delete', 'error');
|
||||||
|
closeDeleteConfirm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the delete API with the ID
|
||||||
|
const response = await deleteQuarterlyWindow(itemToDelete.id);
|
||||||
|
|
||||||
|
if (response.status === 'success') {
|
||||||
|
// Update the UI by removing the deleted item
|
||||||
|
setQuarterData(prev => prev.filter(item => item.id !== itemToDelete.id));
|
||||||
|
showToast('Quarterly window deleted successfully!');
|
||||||
|
} else {
|
||||||
|
showToast(response.message || 'Failed to delete quarterly window', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting quarterly window:', error);
|
||||||
|
showToast('An error occurred while deleting. Please try again.', 'error');
|
||||||
|
} finally {
|
||||||
closeDeleteConfirm();
|
closeDeleteConfirm();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{toast.show && (
|
||||||
|
<div className="fixed top-4 right-4 z-50">
|
||||||
|
<CustomToast
|
||||||
|
message={toast.message}
|
||||||
|
type={toast.type}
|
||||||
|
onClose={() => setToast({ ...toast, show: false })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="w-full max-w-[1280px] rounded-lg border border-[#C3C6CB] bg-white min-h-[348px] pb-6">
|
<div className="w-full max-w-[1280px] rounded-lg border border-[#C3C6CB] bg-white min-h-[348px] pb-6">
|
||||||
<div className="px-6 py-4">
|
<div className="px-6 py-4">
|
||||||
<div className="flex flex-col gap-3 md:h-12 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-3 md:h-12 md:flex-row md:items-center md:justify-between">
|
||||||
@ -421,8 +511,8 @@ const QuarterlyWindows = () => {
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<TextField
|
<TextField
|
||||||
label="Survey Name"
|
label="Survey Name"
|
||||||
value={form.establishment}
|
value={form.survey_name}
|
||||||
onChange={(e) => setForm({ ...form, establishment: e.target.value })}
|
onChange={(e) => setForm({ ...form, survey_name: e.target.value })}
|
||||||
placeholder="Enter survey name"
|
placeholder="Enter survey name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -557,6 +647,7 @@ const QuarterlyWindows = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user