bug fixed in admin dashboard
This commit is contained in:
parent
d0d8bd13f3
commit
9986f7c16b
@ -84,10 +84,16 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
|
|||||||
Rejected
|
Rejected
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else if (normalized === 'pending') {
|
} else if (normalized === 'submitted') {
|
||||||
return (
|
return (
|
||||||
<span className="px-3 py-1 rounded-md text-blue-600 bg-blue-50 font-medium text-sm whitespace-nowrap">
|
<span className="px-3 py-1 rounded-md text-blue-600 bg-blue-50 font-medium text-sm whitespace-nowrap">
|
||||||
Pending
|
Submitted
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (normalized === 'resubmitted') {
|
||||||
|
return (
|
||||||
|
<span className="px-3 py-1 rounded-md text-[#7C5E24] bg-[#F9F7ED] font-medium text-sm whitespace-nowrap">
|
||||||
|
Resubmitted
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -133,12 +139,14 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="border border-[#E5E7EB] bg-white shadow-[0_16px_32px_rgba(15,23,42,0.06)] rounded-[8px]"
|
className="border border-[#E5E7EB] bg-white shadow-[0_8px_16px_rgba(15,23,42,0.05)] rounded-[6px] w-full overflow-hidden py-2"
|
||||||
style={{
|
style={{
|
||||||
width: '1250px',
|
minHeight: filtered.length === 0 || loading ? 'auto' : '200px',
|
||||||
minHeight: filtered.length === 0 || loading ? 'auto' : '780px',
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<h2 className="px-4 pr-100 py-2 mt-3 text-[18px] leading-[28px] font-medium text-[#232528]">
|
||||||
|
{titleText}
|
||||||
|
</h2>
|
||||||
<div className="space-y-[1px] overflow-x-auto">
|
<div className="space-y-[1px] overflow-x-auto">
|
||||||
{/* <div className="px-6 pt-6 pb-4">
|
{/* <div className="px-6 pt-6 pb-4">
|
||||||
<h2 className="text-[18px] leading-[28px] font-medium text-[#232528]">
|
<h2 className="text-[18px] leading-[28px] font-medium text-[#232528]">
|
||||||
@ -155,7 +163,7 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
|
|||||||
No Submission Found
|
No Submission Found
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="min-w-[1200px]">
|
<div className="w-full overflow-x-auto pt-5 h-[550px]">
|
||||||
<Table
|
<Table
|
||||||
headers={headers}
|
headers={headers}
|
||||||
rows={tableRows}
|
rows={tableRows}
|
||||||
@ -235,7 +243,7 @@ const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/admin/validations')}
|
onClick={() => navigate('/admin/validations')}
|
||||||
className="inline-flex h-[44px] w-[190px] items-center justify-center gap-2 rounded-md bg-[#9E792B] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)] hover:bg-[#846522]"
|
className="inline-flex h-[44px] w-[190px] items-center justify-center gap-2 rounded-md bg-[#9E792B] text-sm font-medium text-white shadow-[0_8px_18px_rgba(146,114,42,0.35)] hover:bg-[#846522] py-6"
|
||||||
>
|
>
|
||||||
View All Submissions
|
View All Submissions
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -3,12 +3,14 @@ import React from 'react';
|
|||||||
const toneClasses = {
|
const toneClasses = {
|
||||||
green: 'bg-[#F3FAF4] text-[#2F663C] ring-1 ring-[#C4E5CC]',
|
green: 'bg-[#F3FAF4] text-[#2F663C] ring-1 ring-[#C4E5CC]',
|
||||||
gray: 'bg-[#E9EDF3] text-[#232528] ring-1 ring-[#D6DBE4]',
|
gray: 'bg-[#E9EDF3] text-[#232528] ring-1 ring-[#D6DBE4]',
|
||||||
|
resubmitted: 'bg-[#F9F7ED] text-[#8B8000] ring-1 ring-[#E5DFC5]',
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeTone = (tone, status) => {
|
const normalizeTone = (tone, status) => {
|
||||||
if (tone) return tone;
|
if (tone) return tone;
|
||||||
const normalizedStatus = String(status || '').toLowerCase();
|
const normalizedStatus = String(status || '').toLowerCase();
|
||||||
if (normalizedStatus === 'active' || normalizedStatus === 'open') return 'green';
|
if (normalizedStatus === 'active' || normalizedStatus === 'open') return 'green';
|
||||||
|
if (normalizedStatus === 'resubmitted') return 'resubmitted';
|
||||||
return 'gray';
|
return 'gray';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -30,16 +30,28 @@ const getStatusBadge = (status) => {
|
|||||||
Approved
|
Approved
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
} else if (normalized === 'submitted') {
|
||||||
|
return (
|
||||||
|
<span className="px-3 py-1 rounded-md text-green-700 bg-green-50 font-medium text-sm whitespace-nowrap">
|
||||||
|
Submitted
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (normalized === 'resubmitted') {
|
||||||
|
return (
|
||||||
|
<span className="px-3 py-1 rounded-md text-[#7C5E24] bg-[#F9F7ED] font-medium text-sm whitespace-nowrap">
|
||||||
|
Resubmitted
|
||||||
|
</span>
|
||||||
|
);
|
||||||
} else if (['pending', 'under review', 'in-progress'].includes(normalized)) {
|
} else if (['pending', 'under review', 'in-progress'].includes(normalized)) {
|
||||||
return (
|
return (
|
||||||
<span className="px-3 py-1 rounded-md text-blue-600 bg-blue-50 font-medium text-sm whitespace-nowrap">
|
<span className="px-3 py-1 rounded-md text-blue-600 bg-blue-50 font-medium text-sm whitespace-nowrap">
|
||||||
Under Review
|
Under Review
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else if (['rejected', 'returned'].includes(normalized)) {
|
} else if (normalized === 'rejected') {
|
||||||
return (
|
return (
|
||||||
<span className="px-3 py-1 rounded-md text-red-600 bg-red-50 font-medium text-sm whitespace-nowrap">
|
<span className="px-3 py-1 rounded-md text-red-600 bg-red-50 font-medium text-sm whitespace-nowrap">
|
||||||
Returned
|
Rejected
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -63,7 +75,7 @@ const downloadCsv = (data) => {
|
|||||||
const normalized = String(item.status || '').toLowerCase();
|
const normalized = String(item.status || '').toLowerCase();
|
||||||
if (normalized === 'approved') return 'Approved';
|
if (normalized === 'approved') return 'Approved';
|
||||||
if (['pending', 'under review', 'in-progress'].includes(normalized)) return 'Under Review';
|
if (['pending', 'under review', 'in-progress'].includes(normalized)) return 'Under Review';
|
||||||
if (['rejected', 'returned'].includes(normalized)) return 'Returned';
|
if (['rejected', 'rejected'].includes(normalized)) return 'Returned';
|
||||||
return item.status || '—';
|
return item.status || '—';
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const SurveyCarousel = ({
|
|||||||
setCurrentIndex((prev) => (prev === surveys.length - 1 ? 0 : prev + 1));
|
setCurrentIndex((prev) => (prev === surveys.length - 1 ? 0 : prev + 1));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-black rounded-lg shadow-sm ring-1 ring-gray-200 mb-8 ml-2 ">
|
<div className="bg-black rounded-lg shadow-sm ring-1 w-[1275px] ring-gray-200 h-[148px] mb-8 ml-2 ">
|
||||||
<div className="rounded-none bg-[#F2ECCF] px-8 py-8 min-h-[148px] flex items-center justify-between transition-all duration-500">
|
<div className="rounded-none bg-[#F2ECCF] px-8 py-8 min-h-[148px] flex items-center justify-between transition-all duration-500">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-[18px] leading-[28px] font-medium text-[#232528]">
|
<h3 className="text-[18px] leading-[28px] font-medium text-[#232528]">
|
||||||
|
|||||||
@ -44,7 +44,8 @@ export const DetailedOverview = ({ submission, onBack }) => {
|
|||||||
'Approved': 'bg-[#F3FAF4] text-[#2F663C]',
|
'Approved': 'bg-[#F3FAF4] text-[#2F663C]',
|
||||||
'Submitted': 'bg-[#E7F5FF] text-[#003CFF]',
|
'Submitted': 'bg-[#E7F5FF] text-[#003CFF]',
|
||||||
'Pending': 'bg-[#F9F7ED] text-[#7C5E24]',
|
'Pending': 'bg-[#F9F7ED] text-[#7C5E24]',
|
||||||
'Rejected': 'bg-[#FEF2F2] text-[#B52520]'
|
'Rejected': 'bg-[#FEF2F2] text-[#B52520]',
|
||||||
|
'Resubmitted': 'bg-[#F9F7ED] text-[#7C5E24]'
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusClass = statusMap[status] || 'bg-gray-100 text-gray-800';
|
const statusClass = statusMap[status] || 'bg-gray-100 text-gray-800';
|
||||||
@ -172,7 +173,7 @@ export const DetailedOverview = ({ submission, onBack }) => {
|
|||||||
product.current_cost || '0',
|
product.current_cost || '0',
|
||||||
product.forecast_quantity || '0',
|
product.forecast_quantity || '0',
|
||||||
product.forecast_cost || '0',
|
product.forecast_cost || '0',
|
||||||
product.status || 'Pending'
|
product.status || 'Submitted'
|
||||||
].map((value) => `"${String(value).replace(/"/g, '""')}"`).join(','));
|
].map((value) => `"${String(value).replace(/"/g, '""')}"`).join(','));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -231,8 +232,8 @@ export const DetailedOverview = ({ submission, onBack }) => {
|
|||||||
productData.hs_code || '—',
|
productData.hs_code || '—',
|
||||||
productData.product_name || '—',
|
productData.product_name || '—',
|
||||||
unit.uom || 'N/A',
|
unit.uom || 'N/A',
|
||||||
product.previous_quantity || '0',
|
product.current_quantity || '0',
|
||||||
`AED ${product.previous_cost || '0'}`,
|
`AED ${product.current_cost || '0'}`,
|
||||||
// <span
|
// <span
|
||||||
// key={`status-${index}`}
|
// key={`status-${index}`}
|
||||||
// className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs font-medium ${
|
// className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-xs font-medium ${
|
||||||
|
|||||||
@ -50,6 +50,13 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
|
|||||||
textColor: 'text-[#043DFF]',
|
textColor: 'text-[#043DFF]',
|
||||||
iconColor: 'text-[#286CFF]',
|
iconColor: 'text-[#286CFF]',
|
||||||
message: 'Your survey was successfully submitted and is waiting for approval.'
|
message: 'Your survey was successfully submitted and is waiting for approval.'
|
||||||
|
},
|
||||||
|
resubmitted: {
|
||||||
|
type: 'resubmitted',
|
||||||
|
bgColor: 'bg-[#E7F5FF]',
|
||||||
|
textColor: 'text-[#043DFF]',
|
||||||
|
iconColor: 'text-[#286CFF]',
|
||||||
|
message: 'Your survey was successfully Resubmitted and is waiting for approval.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -182,9 +189,11 @@ const ProductDetails = ({ product, onBack, showToast: shouldShowToast, onToastSh
|
|||||||
industry_code: product.industry_code,
|
industry_code: product.industry_code,
|
||||||
license_number: product.license_number,
|
license_number: product.license_number,
|
||||||
isic_code: product.isic_code,
|
isic_code: product.isic_code,
|
||||||
emirate: product.emirate
|
emirate: product.emirate,
|
||||||
|
status: product.status // Include status for reference
|
||||||
},
|
},
|
||||||
isResubmit: true
|
isResubmit: true,
|
||||||
|
submissionId: product.submission_id // Add submissionId for resubmission
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -252,6 +252,7 @@ const ReviewSubmit = ({
|
|||||||
onToastClose = () => {},
|
onToastClose = () => {},
|
||||||
isSubmitting = false,
|
isSubmitting = false,
|
||||||
hasSubmitted = false,
|
hasSubmitted = false,
|
||||||
|
submitButtonText = 'Submit',
|
||||||
}) => {
|
}) => {
|
||||||
const [confirm, setConfirm] = React.useState(false);
|
const [confirm, setConfirm] = React.useState(false);
|
||||||
const [remarks, setRemarks] = React.useState(() => {
|
const [remarks, setRemarks] = React.useState(() => {
|
||||||
@ -734,11 +735,10 @@ const ReviewSubmit = ({
|
|||||||
<h3 className="text-base font-semibold text-gray-900 mb-4">Remarks</h3>
|
<h3 className="text-base font-semibold text-gray-900 mb-4">Remarks</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<textarea
|
<textarea
|
||||||
placeholder="Enter your remarks here..."
|
|
||||||
readOnly
|
readOnly
|
||||||
value={remarks}
|
value={remarks || ''}
|
||||||
onChange={(e) => setRemarks(e.target.value)}
|
placeholder="No remarks provided"
|
||||||
className="w-full min-h-[100px] p-3 border border-#CBA344 rounded-md focus:ring-2 focus:ring-[#CBA344] focus:border-transparent"
|
className="w-full min-h-[100px] p-3 border border-gray-200 rounded-md bg-gray-50 text-gray-700 resize-none focus:outline-none focus:ring-0 focus:border-gray-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -792,7 +792,7 @@ const ReviewSubmit = ({
|
|||||||
: 'bg-[#92722A] text-white hover:bg-[#7b5c1f]'
|
: 'bg-[#92722A] text-white hover:bg-[#7b5c1f]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{submitLabel}</span>
|
<span>{submitButtonText}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -33,6 +33,7 @@ const ValidationReview = () => {
|
|||||||
const [actionLoading, setActionLoading] = React.useState(false);
|
const [actionLoading, setActionLoading] = React.useState(false);
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
const [submissionData, setSubmissionData] = React.useState(null);
|
const [submissionData, setSubmissionData] = React.useState(null);
|
||||||
|
const [quarterPeriods, setQuarterPeriods] = React.useState(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchSubmission = async () => {
|
const fetchSubmission = async () => {
|
||||||
@ -49,10 +50,16 @@ const ValidationReview = () => {
|
|||||||
const result = await getSubmissionById(id);
|
const result = await getSubmissionById(id);
|
||||||
|
|
||||||
let data = null;
|
let data = null;
|
||||||
if (result?.status === 'success' && result?.data) {
|
if (result?.status === 'success') {
|
||||||
data = result.data;
|
data = result.data;
|
||||||
|
if (result.quarter_periods) {
|
||||||
|
setQuarterPeriods(result.quarter_periods);
|
||||||
|
}
|
||||||
} else if (result?.data) {
|
} else if (result?.data) {
|
||||||
data = result.data;
|
data = result.data;
|
||||||
|
if (result.quarter_periods) {
|
||||||
|
setQuarterPeriods(result.quarter_periods);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data = result;
|
data = result;
|
||||||
}
|
}
|
||||||
@ -239,10 +246,9 @@ const ValidationReview = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const quarterPeriods = submissionData?.quarter_periods || {};
|
const previousQuarter = quarterPeriods?.previous_quarter;
|
||||||
const previousQuarter = quarterPeriods.previous_quarter || 'Q1';
|
const currentQuarter = quarterPeriods?.current_quarter;
|
||||||
const currentQuarter = quarterPeriods.current_quarter || 'Q2';
|
const forecastQuarter = quarterPeriods?.forecast_quarter;
|
||||||
const forecastQuarter = quarterPeriods.forecast_quarter || 'Q3';
|
|
||||||
const year = submissionData?.year || new Date().getFullYear();
|
const year = submissionData?.year || new Date().getFullYear();
|
||||||
|
|
||||||
const products = submissionData?.products || [];
|
const products = submissionData?.products || [];
|
||||||
@ -380,13 +386,7 @@ const ValidationReview = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{productDetails.length > 0 ? (
|
{productDetails.length > 0 ? (
|
||||||
<div
|
<div className={`grid gap-4 ${productDetails.length === 1 ? 'grid-cols-1' : 'grid-cols-1 lg:grid-cols-2'}`}>
|
||||||
className={`grid gap-4 ${
|
|
||||||
productDetails.length === 1
|
|
||||||
? 'grid-cols-1'
|
|
||||||
: 'grid-cols-1 lg:grid-cols-2'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{productDetails.map((product, idx) => (
|
{productDetails.map((product, idx) => (
|
||||||
<section
|
<section
|
||||||
key={product.code + idx}
|
key={product.code + idx}
|
||||||
@ -420,113 +420,180 @@ const ValidationReview = () => {
|
|||||||
Metric
|
Metric
|
||||||
</th>
|
</th>
|
||||||
<th colSpan="3" className="bg-[#F2ECCF] text-[#92722A] px-4 py-2 text-xs font-semibold uppercase text-center">
|
<th colSpan="3" className="bg-[#F2ECCF] text-[#92722A] px-4 py-2 text-xs font-semibold uppercase text-center">
|
||||||
Q4-2024 (Previous Quarter)
|
{quarterPeriods?.previous_quarter}-{quarterPeriods?.previous_year} (Previous Quarter)
|
||||||
</th>
|
</th>
|
||||||
<th colSpan="3" className="bg-[#F2ECCF] text-[#92722A] px-4 py-2 text-xs font-semibold uppercase text-center">
|
<th colSpan="3" className="bg-[#F2ECCF] text-[#92722A] px-4 py-2 text-xs font-semibold uppercase text-center">
|
||||||
Q1-2025 (Current Quarter)
|
{quarterPeriods?.current_quarter}-{quarterPeriods?.current_year} (Current Quarter)
|
||||||
</th>
|
</th>
|
||||||
<th colSpan="3" className="bg-[#F2ECCF] text-[#92722A] px-4 py-2 text-xs font-semibold uppercase text-center">
|
<th colSpan="3" className="bg-[#F2ECCF] text-[#92722A] px-4 py-2 text-xs font-semibold uppercase text-center">
|
||||||
Q2-2025 (Next Quarter)
|
{quarterPeriods?.forecast_quarter}-{quarterPeriods?.forecast_year} (Next Quarter)
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th className="bg-[#F4F8FF] px-4 py-2 font-medium text-[#286CFF]">Oct</th>
|
<th className="bg-[#F4F8FF] px-4 py-2 font-medium text-[#286CFF]">{quarterPeriods?.current_month?.current_period_one}</th>
|
||||||
<th className="bg-[#F4F8FF] px-4 py-2 font-medium text-[#286CFF]">Nov</th>
|
<th className="bg-[#F4F8FF] px-4 py-2 font-medium text-[#286CFF]">{quarterPeriods?.current_month?.current_period_two}</th>
|
||||||
<th className="bg-[#F4F8FF] px-4 py-2 font-medium text-[#286CFF]">Dec</th>
|
<th className="bg-[#F4F8FF] px-4 py-2 font-medium text-[#286CFF]">{quarterPeriods?.current_month?.current_period_three}</th>
|
||||||
|
|
||||||
<th className="bg-[#F3FAF4] px-4 py-2 font-medium text-[#2F663C]">Jan</th>
|
<th className="bg-[#F3FAF4] px-4 py-2 font-medium text-[#2F663C]">{quarterPeriods?.forecast_month?.forecast_period_one}</th>
|
||||||
<th className="bg-[#F3FAF4] px-4 py-2 font-medium text-[#2F663C]">Feb</th>
|
<th className="bg-[#F3FAF4] px-4 py-2 font-medium text-[#2F663C]">{quarterPeriods?.forecast_month?.forecast_period_two}</th>
|
||||||
<th className="bg-[#F3FAF4] px-4 py-2 font-medium text-[#2F663C]">Mar</th>
|
<th className="bg-[#F3FAF4] px-4 py-2 font-medium text-[#2F663C]">{quarterPeriods?.forecast_month?.forecast_period_three}</th>
|
||||||
|
|
||||||
<th className="bg-[#FFF6EC] px-4 py-2 font-medium text-[#F29F0E]">Apr</th>
|
<th className="bg-[#FFF6EC] px-4 py-2 font-medium text-[#F29F0E]">{quarterPeriods?.previous_month?.previous_period_one}</th>
|
||||||
<th className="bg-[#FFF6EC] px-4 py-2 font-medium text-[#F29F0E]">May</th>
|
<th className="bg-[#FFF6EC] px-4 py-2 font-medium text-[#F29F0E]">{quarterPeriods?.previous_month?.previous_period_two}</th>
|
||||||
<th className="bg-[#FFF6EC] px-4 py-2 font-medium text-[#F29F0E]">Jun</th>
|
<th className="bg-[#FFF6EC] px-4 py-2 font-medium text-[#F29F0E]">{quarterPeriods?.previous_month?.previous_period_three}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr className="border-t border-[#E5E7EB]">
|
{submissionData?.products?.map((product, idx) => (
|
||||||
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
<React.Fragment key={idx}>
|
||||||
Quantity (liters)
|
{/* Quantity Row */}
|
||||||
</td>
|
<tr className="border-t border-[#E5E7EB]">
|
||||||
<td className="px-4 py-3 text-[#232528]">1200</td>
|
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
||||||
<td className="px-4 py-3 text-red-600">1180 ▼</td>
|
Quantity ({product.unit?.uom || 'units'})
|
||||||
<td className="px-4 py-3 text-green-600">1250 ▲</td>
|
</td>
|
||||||
|
{/* Previous Quarter Quantities */}
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.previous_quantity_period_one || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.previous_quantity_period_two || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.previous_quantity_period_three || '0'}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Current Quarter Quantities */}
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.current_quantity_period_one || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.current_quantity_period_two || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.current_quantity_period_three || '0'}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Forecast Quarter Quantities */}
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.forecast_quantity_period_one || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.forecast_quantity_period_two || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.forecast_quantity_period_three || '0'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<td className="px-4 py-3 text-[#232528]">1280</td>
|
{/* Cost Row */}
|
||||||
<td className="px-4 py-3 text-[#232528]">1340</td>
|
<tr className="border-t border-[#E5E7EB]">
|
||||||
<td className="px-4 py-3 text-[#232528]">1375</td>
|
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
||||||
|
Cost (AED)
|
||||||
|
</td>
|
||||||
|
{/* Previous Quarter Costs */}
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.previous_cost_period_one || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.previous_cost_period_two || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.previous_cost_period_three || '0'}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Current Quarter Costs */}
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.current_cost_period_one || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.current_cost_period_two || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.current_cost_period_three || '0'}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Forecast Quarter Costs */}
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.forecast_cost_period_one || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.forecast_cost_period_two || '0'}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.forecast_cost_period_three || '0'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{/* Remarks Row (if exists) */}
|
||||||
|
{product.remarks && (
|
||||||
|
<tr className="border-t border-[#E5E7EB] bg-gray-100">
|
||||||
|
<td className="px-4 py-3 font-medium text-[#232528]">
|
||||||
|
Remarks
|
||||||
|
</td>
|
||||||
|
<td colSpan="9" className="px-4 py-3 text-[#232528]">
|
||||||
|
{product.remarks}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Current Quarter Reason Row */}
|
||||||
|
<tr className="border-t border-[#E5E7EB]">
|
||||||
|
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
||||||
|
Reason (Current)
|
||||||
|
</td>
|
||||||
|
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
||||||
|
<td colSpan="3" className="px-4 py-3 text-center bg-[#E9F6EC] text-[#1E7C34] font-medium rounded-md">
|
||||||
|
{product.variation_reason?.reason || 'No reason provided'}
|
||||||
|
{product.other_variation_reason && (
|
||||||
|
<div className="text-xs mt-1">Note: {product.other_variation_reason}</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<td className="px-4 py-3 text-[#232528]">1400</td>
|
{/* Forecast Quarter Reason Row */}
|
||||||
<td className="px-4 py-3 text-[#232528]">1450</td>
|
<tr className="border-t border-[#E5E7EB]">
|
||||||
<td className="px-4 py-3 text-[#232528]">1500</td>
|
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
||||||
</tr>
|
Reason (Forecast)
|
||||||
|
</td>
|
||||||
<tr className="border-t border-[#E5E7EB]">
|
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
||||||
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
||||||
Cost (AED)
|
<td colSpan="3" className="px-4 py-3 text-center bg-[#FFF2E0] text-[#D97706] font-medium rounded-md">
|
||||||
</td>
|
{product.zero_target_reason?.reason || 'No reason provided'}
|
||||||
<td className="px-4 py-3 text-[#232528]">39,000</td>
|
{product.other_zero_target_reason && (
|
||||||
<td className="px-4 py-3 text-red-600">37,500 ▼</td>
|
<div className="text-xs mt-1">Note: {product.other_zero_target_reason}</div>
|
||||||
<td className="px-4 py-3 text-green-600">41,000 ▲</td>
|
)}
|
||||||
|
</td>
|
||||||
<td className="px-4 py-3 text-[#232528]">40,000</td>
|
</tr>
|
||||||
<td className="px-4 py-3 text-[#232528]">42,000</td>
|
</React.Fragment>
|
||||||
<td className="px-4 py-3 text-[#232528]">43,500</td>
|
))}
|
||||||
|
|
||||||
<td className="px-4 py-3 text-[#232528]">44,000</td>
|
|
||||||
<td className="px-4 py-3 text-[#232528]">45,000</td>
|
|
||||||
<td className="px-4 py-3 text-[#232528]">46,000</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr className="border-t border-[#E5E7EB]">
|
|
||||||
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
|
||||||
Reason (Current)
|
|
||||||
</td>
|
|
||||||
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
|
||||||
<td colSpan="3" className="px-4 py-3 text-center bg-[#E9F6EC] text-[#1E7C34] font-medium rounded-md">
|
|
||||||
Seasonal Increase
|
|
||||||
</td>
|
|
||||||
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr className="border-t border-[#E5E7EB]">
|
|
||||||
<td className="bg-[#FFFCF2] px-4 py-3 font-medium text-[#232528]">
|
|
||||||
Reason (Forecast)
|
|
||||||
</td>
|
|
||||||
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
|
||||||
<td colSpan="3" className="px-4 py-3 text-center text-[#6B7280]">--</td>
|
|
||||||
<td colSpan="3" className="px-4 py-3 text-center bg-[#FFF2E0] text-[#D97706] font-medium rounded-md">
|
|
||||||
Projected demand
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
|
<div className="mt-4">
|
||||||
|
{/* <p>
|
||||||
<div className="text-xs text-[#4B5563] mt-2">
|
<span className="text-[#16A34A] font-semibold">Green ▲</span> indicates upward trend;{' '}
|
||||||
<p>
|
<span className="text-[#DC2626] font-semibold">Red ▼</span> indicates downward trend compared to previous month or quarter
|
||||||
<span className="text-[#16A34A] font-semibold">Green ▲</span> indicates upward trend;{' '}
|
</p> */}
|
||||||
<span className="text-[#DC2626] font-semibold">Red ▼</span> indicates downward trend compared to previous month or quarter
|
<p className="mt-3 text-sm text-[#4B5563]">
|
||||||
</p>
|
<strong className="font-semibold text-[#232528]">Remarks:</strong> NA
|
||||||
<p className="mt-3 text-sm text-[#4B5563]">
|
</p>
|
||||||
<strong className="font-semibold text-[#232528]">Remarks:</strong> NA
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-3">
|
|
||||||
<label className="block text-sm font-semibold mb-1 text-[#232528]">Admin Remarks</label>
|
|
||||||
<textarea
|
|
||||||
rows="2"
|
|
||||||
className="w-full rounded-[10px] border border-[#D1D5DB] bg-[#FFFCF2] px-4 py-2 text-sm text-[#232528] focus:ring-2 focus:ring-[#92722A] focus:outline-none"
|
|
||||||
placeholder="Add short note..."
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3">
|
||||||
|
<label className="block text-sm font-semibold mb-1 text-[#232528]">Admin Remarks</label>
|
||||||
|
<textarea
|
||||||
|
rows="2"
|
||||||
|
className="w-full rounded-[10px] border border-[#D1D5DB] bg-[#FFFCF2] px-4 py-2 text-sm text-[#232528] focus:ring-2 focus:ring-[#92722A] focus:outline-none"
|
||||||
|
placeholder="Add short note..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-lg bg-amber-50 border border-amber-200 p-6 text-center">
|
<div className="rounded-lg bg-amber-50 border border-amber-200 p-6 text-center">
|
||||||
<p className="text-amber-800">No products found for this submission.</p>
|
<p className="text-amber-800">No products found for this submission.</p>
|
||||||
|
|||||||
@ -13,6 +13,8 @@ const STATUS_VARIANTS = {
|
|||||||
Approved: { background: '#F3FAF4', text: '#2F663C', border: '#F3FAF4' },
|
Approved: { background: '#F3FAF4', text: '#2F663C', border: '#F3FAF4' },
|
||||||
Pending: { background: '#E7F5FF', text: '#286CFF', border: '#E7F5FF' },
|
Pending: { background: '#E7F5FF', text: '#286CFF', border: '#E7F5FF' },
|
||||||
Rejected: { background: '#FEF2F2', text: '#B52520', border: '#FEF2F2' },
|
Rejected: { background: '#FEF2F2', text: '#B52520', border: '#FEF2F2' },
|
||||||
|
Resubmitted: { background: '#F9F7ED', text: '#7C5E24', border: '#FEF2F2' },
|
||||||
|
Submitted: { background: '#E7F5FF', text: '#003CFF', border: '#FEF2F2' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUMMARY_VARIANTS = {
|
const SUMMARY_VARIANTS = {
|
||||||
@ -20,8 +22,12 @@ const SUMMARY_VARIANTS = {
|
|||||||
approved: { background: '#F3FAF4', label: '#2F663C', value: '#232528' },
|
approved: { background: '#F3FAF4', label: '#2F663C', value: '#232528' },
|
||||||
pending: { background: '#E7F5FF', label: '#003CFF', value: '#232528' },
|
pending: { background: '#E7F5FF', label: '#003CFF', value: '#232528' },
|
||||||
rejected: { background: '#FEF2F2', label: '#B52520', value: '#232528' },
|
rejected: { background: '#FEF2F2', label: '#B52520', value: '#232528' },
|
||||||
|
resubmitted: { background: '#F9F7ED', text: '#7C5E24', border: '#FEF2F2' },
|
||||||
|
submitted: { background: '#E7F5FF', text: '#003CFF', border: '#FEF2F2' },
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const formatDateTime = (value) => {
|
const formatDateTime = (value) => {
|
||||||
if (!value) return '—';
|
if (!value) return '—';
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
|
|||||||
@ -1947,6 +1947,7 @@ const requiredFields = [
|
|||||||
factory_name: form.establishmentName || form.contactName || '',
|
factory_name: form.establishmentName || form.contactName || '',
|
||||||
permanent_factory_code: form.permanentFactoryCode || '',
|
permanent_factory_code: form.permanentFactoryCode || '',
|
||||||
industry_code: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
industry_code: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
||||||
|
industry_code_production: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
||||||
license_number: form.uniqueLicenseNumber || '',
|
license_number: form.uniqueLicenseNumber || '',
|
||||||
isic_code: form.isicCode || form.industryCodeBusiness || '',
|
isic_code: form.isicCode || form.industryCodeBusiness || '',
|
||||||
description: form.industryDescription || '',
|
description: form.industryDescription || '',
|
||||||
@ -2025,6 +2026,7 @@ const requiredFields = [
|
|||||||
factory_name: form.establishmentName || form.contactName || '',
|
factory_name: form.establishmentName || form.contactName || '',
|
||||||
permanent_factory_code: form.permanentFactoryCode || '',
|
permanent_factory_code: form.permanentFactoryCode || '',
|
||||||
industry_code: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
industry_code: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
||||||
|
industry_code_production: form.industryCodeBusiness || form.industryCodeCurrent || '',
|
||||||
license_number: form.uniqueLicenseNumber || '',
|
license_number: form.uniqueLicenseNumber || '',
|
||||||
isic_code: form.isicCode || form.industryCodeBusiness || '',
|
isic_code: form.isicCode || form.industryCodeBusiness || '',
|
||||||
description: form.industryDescription || '',
|
description: form.industryDescription || '',
|
||||||
|
|||||||
@ -11,15 +11,40 @@ const BreadcrumbVector = '/assets/images/Breadcrumb-vector.svg';
|
|||||||
const caretDownSrc = '/assets/images/CaretDown.svg';
|
const caretDownSrc = '/assets/images/CaretDown.svg';
|
||||||
|
|
||||||
const Badge = ({ variant = 'default', children }) => {
|
const Badge = ({ variant = 'default', children }) => {
|
||||||
|
// Define all possible status variants and their styles
|
||||||
const styles = {
|
const styles = {
|
||||||
approved: 'bg-[#F3FAF4] text-[#2F663C]',
|
// Approved status
|
||||||
submitted: 'bg-[#E7F5FF] text-[#003CFF]',
|
approved: 'text-green-700 bg-green-50',
|
||||||
pending: 'bg-[#F9F7ED] text-[#7C5E24]',
|
|
||||||
rejected: 'bg-[#FEF2F2] text-[#B52520]',
|
// Submitted status
|
||||||
default: 'bg-gray-50 text-gray-700',
|
submitted: 'text-green-700 bg-green-50',
|
||||||
|
|
||||||
|
// Under Review status (includes pending and in-progress)
|
||||||
|
'under review': 'text-blue-600 bg-blue-50',
|
||||||
|
pending: 'text-blue-600 bg-blue-50',
|
||||||
|
'in-progress': 'text-blue-600 bg-blue-50',
|
||||||
|
|
||||||
|
// Rejected status (includes returned)
|
||||||
|
rejected: 'text-red-600 bg-red-50',
|
||||||
|
returned: 'text-red-600 bg-red-50',
|
||||||
|
|
||||||
|
// Resubmitted status
|
||||||
|
resubmitted: 'text-[#7C5E24] bg-[#F9F7ED]',
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
default: 'text-gray-600 bg-gray-50',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Normalize the variant key
|
||||||
|
const normalizedVariant = String(variant || '').toLowerCase().trim();
|
||||||
|
|
||||||
|
// Get the appropriate style, fallback to default if not found
|
||||||
|
const style = styles[normalizedVariant] || styles.default;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`inline-flex items-center justify-center rounded-full text-xs font-medium px-3 py-1 ${styles[variant.toLowerCase()] || styles.default}`}>
|
<span
|
||||||
|
className={`inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap ${style}`}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@ -180,8 +205,9 @@ const formatExportDateTime = (value) => {
|
|||||||
const text = String(status || '').toLowerCase();
|
const text = String(status || '').toLowerCase();
|
||||||
if (text === 'approved') return 'approved';
|
if (text === 'approved') return 'approved';
|
||||||
if (text === 'submitted') return 'submitted';
|
if (text === 'submitted') return 'submitted';
|
||||||
if (text === 'rejected') return 'rejected';
|
if (text === 'resubmitted') return 'resubmitted';
|
||||||
if (text === 'pending' || text === 'in-progress' || text === 'under review') return 'pending';
|
if (text === 'rejected' || text === 'returned') return 'rejected';
|
||||||
|
if (text === 'pending' || text === 'in-progress' || text === 'under review') return 'under review';
|
||||||
return 'default';
|
return 'default';
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -375,7 +401,7 @@ const filteredRows = useMemo(() => {
|
|||||||
'Quarter',
|
'Quarter',
|
||||||
'HS Code',
|
'HS Code',
|
||||||
'Product',
|
'Product',
|
||||||
'Status',
|
{ name: 'Status', className: 'text-center' },
|
||||||
'Actors',
|
'Actors',
|
||||||
'Details'
|
'Details'
|
||||||
];
|
];
|
||||||
@ -850,14 +876,24 @@ const tableRows = auditHistory.map((entry, index) => {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
pagination={{
|
columnWidths={[
|
||||||
currentPage: pagination.currentPage,
|
{ width: 180, style: { whiteSpace: 'normal', wordWrap: 'break-word' } }, // Submission Date
|
||||||
onPageChange: handlePageChange,
|
{ width: 80, style: { whiteSpace: 'normal', wordWrap: 'break-word' } }, // Year
|
||||||
pageSize: pagination.pageSize,
|
{ width: 80, style: { whiteSpace: 'normal', wordWrap: 'break-word' } }, // Quarter
|
||||||
totalItems: pagination.totalItems,
|
{ width: 120, style: { whiteSpace: 'normal', wordWrap: 'break-word' } }, // HS Code
|
||||||
pageSizeOptions: [10, 25, 50, 100],
|
{ width: 200, style: { whiteSpace: 'normal', wordWrap: 'break-word' } }, // Product
|
||||||
onPageSizeChange: handlePageSizeChange,
|
{ width: 130, align: 'center', style: { whiteSpace: 'normal', wordWrap: 'break-word' } }, // Status
|
||||||
}}
|
{ width: 140, style: { whiteSpace: 'normal', wordWrap: 'break-word' } }, // Actors
|
||||||
|
{ width: 270, style: { whiteSpace: 'normal', wordWrap: 'break-word' } } // Details
|
||||||
|
]}
|
||||||
|
pagination={{
|
||||||
|
currentPage: pagination.currentPage,
|
||||||
|
onPageChange: handlePageChange,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
totalItems: pagination.totalItems,
|
||||||
|
pageSizeOptions: [10, 25, 50, 100],
|
||||||
|
onPageSizeChange: handlePageSizeChange,
|
||||||
|
}}
|
||||||
renderCell={(value, ri, ci) => {
|
renderCell={(value, ri, ci) => {
|
||||||
if (ci === 5) { // Status column
|
if (ci === 5) { // Status column
|
||||||
return <Badge variant={toVariant(filteredRows[ri]?.status)}>{formatStatus(filteredRows[ri]?.status)}</Badge>;
|
return <Badge variant={toVariant(filteredRows[ri]?.status)}>{formatStatus(filteredRows[ri]?.status)}</Badge>;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const statusStyles = {
|
|||||||
submitted: 'text-[#003CFF] bg-[#E7F5FF]',
|
submitted: 'text-[#003CFF] bg-[#E7F5FF]',
|
||||||
pending: 'text-[#7C5E24] bg-[#F9F7ED]',
|
pending: 'text-[#7C5E24] bg-[#F9F7ED]',
|
||||||
rejected: 'text-[#B52520] bg-[#FEF2F2]',
|
rejected: 'text-[#B52520] bg-[#FEF2F2]',
|
||||||
|
resubmitted: 'text-[#7C5E24] bg-[#F9F7ED]',
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { submitSurvey, fetchSubmissionDetail } from '@/services/submissions/submissionService';
|
import { submitSurvey, fetchSubmissionDetail, resubmitSurvey } from '@/services/submissions/submissionService';
|
||||||
import { fetchEstablishmentDetail, fetchEstablishmentDashboard } from '@/services/establishments/establishmentService';
|
import { fetchEstablishmentDetail, fetchEstablishmentDashboard } from '@/services/establishments/establishmentService';
|
||||||
import EstablishmentInfo from '@/components/survey/EstablishmentInfo/EstablishmentInfo';
|
import EstablishmentInfo from '@/components/survey/EstablishmentInfo/EstablishmentInfo';
|
||||||
import HeaderBar from '@/components/layout/HeaderBar';
|
import HeaderBar from '@/components/layout/HeaderBar';
|
||||||
@ -71,6 +71,8 @@ const Survey = () => {
|
|||||||
const [establishmentLoading, setEstablishmentLoading] = React.useState(false);
|
const [establishmentLoading, setEstablishmentLoading] = React.useState(false);
|
||||||
const [establishmentError, setEstablishmentError] = React.useState('');
|
const [establishmentError, setEstablishmentError] = React.useState('');
|
||||||
const isViewMode = React.useMemo(() => Boolean(locationState.submission), [locationState.submission]);
|
const isViewMode = React.useMemo(() => Boolean(locationState.submission), [locationState.submission]);
|
||||||
|
const isResubmit = React.useMemo(() => locationState?.isResubmit === true, [locationState?.isResubmit]);
|
||||||
|
const submissionId = React.useMemo(() => locationState?.submissionId || locationState?.submission?.id, [locationState]);
|
||||||
const toastTimeoutRef = React.useRef(null);
|
const toastTimeoutRef = React.useRef(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -98,8 +100,18 @@ const Survey = () => {
|
|||||||
|
|
||||||
const initializeSurveyData = async () => {
|
const initializeSurveyData = async () => {
|
||||||
try {
|
try {
|
||||||
|
// If this is a resubmission, use the quarter and year from the submission
|
||||||
|
if (isResubmit && locationState?.submission) {
|
||||||
|
if (!cancelled) {
|
||||||
|
setEstablishmentData(prev => ({
|
||||||
|
...prev,
|
||||||
|
quarter: locationState.submission.quarter || '',
|
||||||
|
year: locationState.submission.year?.toString() || ''
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
// If quarter and year are not provided in location state, fetch them
|
// If quarter and year are not provided in location state, fetch them
|
||||||
if (!locationState?.currentQuarter || !locationState?.currentYear) {
|
else if (!locationState?.currentQuarter || !locationState?.currentYear) {
|
||||||
const { quarter, year } = await fetchCurrentQuarterAndYear();
|
const { quarter, year } = await fetchCurrentQuarterAndYear();
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setEstablishmentData(prev => ({
|
setEstablishmentData(prev => ({
|
||||||
@ -128,7 +140,7 @@ const Survey = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [locationState?.currentQuarter, locationState?.currentYear]); // Only depend on these values
|
}, [locationState?.currentQuarter, locationState?.currentYear]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@ -204,19 +216,39 @@ const Survey = () => {
|
|||||||
|
|
||||||
const mergeEstablishmentData = (detail) => {
|
const mergeEstablishmentData = (detail) => {
|
||||||
const summary = locationState.submission || {};
|
const summary = locationState.submission || {};
|
||||||
|
const establishment = detail?.establishment || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
quarter: ensureString(detail?.quarter) || ensureString(summary.quarter) || '',
|
quarter: ensureString(detail?.quarter) || ensureString(summary.quarter) || '',
|
||||||
year: ensureString(detail?.year) || ensureString(summary.year) || '',
|
year: ensureString(detail?.year) || ensureString(summary.year) || '',
|
||||||
establishmentName: ensureString(summary.establishment_name) || establishmentData.establishmentName || '',
|
establishmentName: ensureString(establishment.factory_name) ||
|
||||||
permanentFactoryCode:
|
ensureString(summary.establishment_name) ||
|
||||||
ensureString(summary.permanent_factory_code) ||
|
establishmentData.establishmentName || '',
|
||||||
ensureString(detail?.establishment_id) ||
|
permanentFactoryCode: ensureString(establishment.permanent_factory_code) ||
|
||||||
establishmentData.permanentFactoryCode || '',
|
ensureString(summary.permanent_factory_code) ||
|
||||||
industryCode: ensureString(summary.industry_code) || establishmentData.industryCode || '',
|
ensureString(detail?.establishment_id) ||
|
||||||
licenseNumber: ensureString(summary.license_number) || establishmentData.licenseNumber || '',
|
establishmentData.permanentFactoryCode || '',
|
||||||
isicCode: ensureString(summary.isic_code) || establishmentData.isicCode || '',
|
industryCode: ensureString(establishment.industry_code) ||
|
||||||
emirate: ensureString(summary.emirate) || establishmentData.emirate || '',
|
ensureString(summary.industry_code) ||
|
||||||
employeeInfo: { ...establishmentData.employeeInfo },
|
establishmentData.industryCode || '',
|
||||||
|
licenseNumber: ensureString(establishment.license_number) ||
|
||||||
|
ensureString(summary.license_number) ||
|
||||||
|
establishmentData.licenseNumber || '',
|
||||||
|
isicCode: ensureString(establishment.isic_code) ||
|
||||||
|
ensureString(summary.isic_code) ||
|
||||||
|
establishmentData.isicCode || '',
|
||||||
|
emirate: ensureString(establishment.establishment_emirate_id) ||
|
||||||
|
ensureString(establishment.emirate) ||
|
||||||
|
ensureString(summary.emirate) ||
|
||||||
|
establishmentData.emirate || '',
|
||||||
|
employeeInfo: {
|
||||||
|
emiratiMale: ensureString(establishment.emirati_male) || establishmentData.employeeInfo.emiratiMale || '',
|
||||||
|
emiratiFemale: ensureString(establishment.emirati_female) || establishmentData.employeeInfo.emiratiFemale || '',
|
||||||
|
nonEmiratiMale: ensureString(establishment.non_emirati_male) || establishmentData.employeeInfo.nonEmiratiMale || '',
|
||||||
|
nonEmiratiFemale: ensureString(establishment.non_emirati_female) || establishmentData.employeeInfo.nonEmiratiFemale || '',
|
||||||
|
totalEmirati: ensureString(establishment.total_emirati) || establishmentData.employeeInfo.totalEmirati || '',
|
||||||
|
totalEmployees: ensureString(establishment.total_employees) || establishmentData.employeeInfo.totalEmployees || '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -224,20 +256,50 @@ const Survey = () => {
|
|||||||
if (!Array.isArray(detailProducts)) return [];
|
if (!Array.isArray(detailProducts)) return [];
|
||||||
return detailProducts.map((product, index) => ({
|
return detailProducts.map((product, index) => ({
|
||||||
id: product?.id ?? index + 1,
|
id: product?.id ?? index + 1,
|
||||||
|
productId: product?.product_id || product?.product?.id || '',
|
||||||
product: ensureString(product?.product?.product_name) || ensureString(product?.product_id) || '',
|
product: ensureString(product?.product?.product_name) || ensureString(product?.product_id) || '',
|
||||||
unit: ensureString(product?.unit?.name) || ensureString(product?.unit_id) || '',
|
unit: ensureString(product?.unit?.uom) || ensureString(product?.unit?.name) || ensureString(product?.unit_id) || '',
|
||||||
|
unitId: product?.unit_id || product?.unit?.id || '',
|
||||||
capacity: ensureString(product?.annual_installed_capacity),
|
capacity: ensureString(product?.annual_installed_capacity),
|
||||||
previousQuantity: ensureString(product?.previous_quantity),
|
|
||||||
previousCost: ensureString(product?.previous_cost),
|
// Previous Quarter (Q4) - October, November, December
|
||||||
currentQuantity: ensureString(product?.current_quantity),
|
octQuantity: ensureString(product?.previous_quantity_period_one || ''),
|
||||||
currentCost: ensureString(product?.current_cost),
|
novQuantity: ensureString(product?.previous_quantity_period_two || ''),
|
||||||
forecastQuantity: ensureString(product?.forecast_quantity),
|
decQuantity: ensureString(product?.previous_quantity_period_three || ''),
|
||||||
forecastCost: ensureString(product?.forecast_cost),
|
octCost: ensureString(product?.previous_cost_period_one || ''),
|
||||||
variationReason: ensureString(product?.variation_reason_master_id),
|
novCost: ensureString(product?.previous_cost_period_two || ''),
|
||||||
otherVariationReason: ensureString(product?.other_variation_reason),
|
decCost: ensureString(product?.previous_cost_period_three || ''),
|
||||||
zeroTargetReason: ensureString(product?.zero_target_reason_master_id),
|
|
||||||
otherZeroTargetReason: ensureString(product?.other_zero_target_reason),
|
// Current Quarter (Q1) - January, February, March
|
||||||
remarks: ensureString(product?.remarks),
|
janQuantity: ensureString(product?.current_quantity_period_one || ''),
|
||||||
|
febQuantity: ensureString(product?.current_quantity_period_two || ''),
|
||||||
|
marQuantity: ensureString(product?.current_quantity_period_three || ''),
|
||||||
|
janCost: ensureString(product?.current_cost_period_one || ''),
|
||||||
|
febCost: ensureString(product?.current_cost_period_two || ''),
|
||||||
|
marCost: ensureString(product?.current_cost_period_three || ''),
|
||||||
|
|
||||||
|
// Forecast Quarter (Q2) - April, May, June
|
||||||
|
aprQuantity: ensureString(product?.forecast_quantity_period_one || ''),
|
||||||
|
mayQuantity: ensureString(product?.forecast_quantity_period_two || ''),
|
||||||
|
junQuantity: ensureString(product?.forecast_quantity_period_three || ''),
|
||||||
|
aprCost: ensureString(product?.forecast_cost_period_one || ''),
|
||||||
|
mayCost: ensureString(product?.forecast_cost_period_two || ''),
|
||||||
|
junCost: ensureString(product?.forecast_cost_period_three || ''),
|
||||||
|
|
||||||
|
// Totals (for backward compatibility)
|
||||||
|
previousQuantity: ensureString(product?.previous_quantity || ''),
|
||||||
|
previousCost: ensureString(product?.previous_cost || ''),
|
||||||
|
currentQuantity: ensureString(product?.current_quantity || ''),
|
||||||
|
currentCost: ensureString(product?.current_cost || ''),
|
||||||
|
forecastQuantity: ensureString(product?.forecast_quantity || ''),
|
||||||
|
forecastCost: ensureString(product?.forecast_cost || ''),
|
||||||
|
|
||||||
|
// Reasons and remarks
|
||||||
|
variationReason: ensureString(product?.variation_reason_master_id || ''),
|
||||||
|
otherVariationReason: ensureString(product?.other_variation_reason || ''),
|
||||||
|
zeroTargetReason: ensureString(product?.zero_target_reason_master_id || ''),
|
||||||
|
otherZeroTargetReason: ensureString(product?.other_zero_target_reason || ''),
|
||||||
|
remarks: ensureString(product?.remarks || '')
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -309,7 +371,7 @@ const Survey = () => {
|
|||||||
return String(value);
|
return String(value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const buildSubmissionPayload = React.useCallback(() => {
|
const buildSubmissionPayload = React.useCallback((isResubmitFlow = false) => {
|
||||||
const info = establishmentData?.employeeInfo ?? {};
|
const info = establishmentData?.employeeInfo ?? {};
|
||||||
return {
|
return {
|
||||||
quarter: establishmentData?.quarter ?? '',
|
quarter: establishmentData?.quarter ?? '',
|
||||||
@ -324,11 +386,13 @@ const Survey = () => {
|
|||||||
products: productsData.map((product) => {
|
products: productsData.map((product) => {
|
||||||
const variationId = parseNumber(product.variationReason, null);
|
const variationId = parseNumber(product.variationReason, null);
|
||||||
const zeroTargetId = parseNumber(product.zeroTargetReason, null);
|
const zeroTargetId = parseNumber(product.zeroTargetReason, null);
|
||||||
|
const productId = parseNumber(product.id);
|
||||||
|
|
||||||
// Map monthly data to period-specific fields
|
// Map monthly data to period-specific fields
|
||||||
return {
|
return {
|
||||||
|
...(isResubmitFlow && productId ? { id: productId } : {}), // Only include ID during resubmit
|
||||||
product_id: parseNumber(product.productId) || parseNumber(product.product),
|
product_id: parseNumber(product.productId) || parseNumber(product.product),
|
||||||
unit_id: parseNumber(product.unit),
|
unit_id: parseNumber(product.unitId) || parseNumber(product.unit),
|
||||||
annual_installed_capacity: stringify(product.capacity),
|
annual_installed_capacity: stringify(product.capacity),
|
||||||
|
|
||||||
// Previous Quarter (Q4) - October, November, December
|
// Previous Quarter (Q4) - October, November, December
|
||||||
@ -412,7 +476,31 @@ const Survey = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = React.useCallback(async () => {
|
const handleSubmit = React.useCallback(async (e) => {
|
||||||
|
if (e) e.preventDefault();
|
||||||
|
if (isSubmitting) return;
|
||||||
|
|
||||||
|
// Validate current step before proceeding
|
||||||
|
if (step === 1) {
|
||||||
|
const establishmentError = validateEstablishmentInfo();
|
||||||
|
if (establishmentError) {
|
||||||
|
setError(establishmentError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (step === 2) {
|
||||||
|
const productError = validateProductData();
|
||||||
|
if (productError) {
|
||||||
|
setError(productError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the final step (step 3) and it's a resubmission, include the submission ID
|
||||||
|
if (step === 3 && isResubmit && submissionId) {
|
||||||
|
await handleResubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
const payload = buildSubmissionPayload();
|
const payload = buildSubmissionPayload();
|
||||||
@ -432,6 +520,57 @@ const Survey = () => {
|
|||||||
}
|
}
|
||||||
}, [buildSubmissionPayload, showToast]);
|
}, [buildSubmissionPayload, showToast]);
|
||||||
|
|
||||||
|
const handleResubmit = async () => {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
...buildSubmissionPayload(true), // Pass true to indicate resubmission flow
|
||||||
|
status: 'Resubmitted', // Set status to Resubmitted for resubmissions
|
||||||
|
id: submissionId // Include submission ID in the payload
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the resubmitSurvey API with the submission ID and payload
|
||||||
|
await resubmitSurvey(submissionId, payload);
|
||||||
|
|
||||||
|
setShowSuccessPopup(true);
|
||||||
|
setHasSubmitted(true);
|
||||||
|
showToast('success', 'Survey resubmitted successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Resubmission failed:', error);
|
||||||
|
const message = error?.response?.data?.message || error?.message || 'Failed to resubmit survey. Please try again.';
|
||||||
|
setError(message);
|
||||||
|
showToast('error', message);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFinalSubmit = async () => {
|
||||||
|
if (isResubmit && submissionId) {
|
||||||
|
await handleResubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = buildSubmissionPayload();
|
||||||
|
await submitSurvey(payload);
|
||||||
|
setShowSuccessPopup(true);
|
||||||
|
setHasSubmitted(true);
|
||||||
|
showToast('success', 'Survey submitted successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
const message = error?.response?.data?.message || error?.message || 'Failed to submit survey. Please try again.';
|
||||||
|
setError(message);
|
||||||
|
showToast('error', message);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const StepBadge = ({ state, number }) => {
|
const StepBadge = ({ state, number }) => {
|
||||||
if (state === 'active') {
|
if (state === 'active') {
|
||||||
return (
|
return (
|
||||||
@ -634,7 +773,8 @@ const Survey = () => {
|
|||||||
establishment={establishmentData}
|
establishment={establishmentData}
|
||||||
products={productsData}
|
products={productsData}
|
||||||
onBack={() => setStep(2)}
|
onBack={() => setStep(2)}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleFinalSubmit}
|
||||||
|
submitButtonText={isResubmit ? 'Resubmit' : 'Submit'}
|
||||||
toast={toast}
|
toast={toast}
|
||||||
onToastClose={() => {
|
onToastClose={() => {
|
||||||
if (toastTimeoutRef.current) {
|
if (toastTimeoutRef.current) {
|
||||||
@ -644,7 +784,7 @@ const Survey = () => {
|
|||||||
setToast(null);
|
setToast(null);
|
||||||
}}
|
}}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
hasSubmitted={hasSubmitted}
|
hasSubmitted={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -674,12 +814,12 @@ const Survey = () => {
|
|||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900">
|
||||||
Submission successful
|
{isResubmit ? 'Resubmission successful' : 'Submission successful'}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Message */}
|
{/* Message */}
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700">
|
||||||
Thank you. Your IP Quarterly Survey for <span className="font-semibold text-gray-900">{establishmentData?.quarter} {establishmentData?.year}</span> has been submitted.
|
Thank you. Your IP Quarterly Survey for <span className="font-semibold text-gray-900">{establishmentData?.quarter} {establishmentData?.year}</span> has been {isResubmit ? 'resubmitted' : 'submitted'}.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Button */}
|
{/* Button */}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user