added validations percentage
This commit is contained in:
parent
61ddf150e1
commit
c014fcf4bc
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,340 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
TextField,
|
||||
MenuItem,
|
||||
Button,
|
||||
IconButton,
|
||||
Chip,
|
||||
Divider,
|
||||
ToggleButtonGroup,
|
||||
ToggleButton,
|
||||
Avatar,
|
||||
Menu,
|
||||
MenuItem as MuiMenuItem,
|
||||
ListItemIcon,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ArrowRightAlt as ArrowRightAltIcon,
|
||||
FileDownload as FileDownloadIcon,
|
||||
FiberManualRecord as FiberManualRecordIcon,
|
||||
NotificationsNone as NotificationsIcon,
|
||||
HelpOutline as HelpIcon,
|
||||
Settings as SettingsIcon,
|
||||
Logout as LogoutIcon,
|
||||
Person as PersonIcon,
|
||||
KeyboardArrowDown as KeyboardArrowDownIcon
|
||||
} from '@mui/icons-material';
|
||||
import Card from '@/components/common/Card';
|
||||
|
||||
const ManufacturingIndex = () => {
|
||||
const [year, setYear] = useState('2025');
|
||||
const [selectedMonth, setSelectedMonth] = useState('Oct 2025');
|
||||
const [view, setView] = useState('manufacturing');
|
||||
|
||||
const years = ['2025', '2024', '2023'];
|
||||
const months = [
|
||||
{ month: 'Oct 2025', index: '114.8', mom: '+0.9', yoy: '+3.2', generated: '12 Nov 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Sep 2025', index: '113.9', mom: '-0.2', yoy: '+2.8', generated: '11 Oct 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Aug 2025', index: '114.1', mom: '+0.5', yoy: '+3.0', generated: '11 Sep 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Jul 2025', index: '113.6', mom: '+0.3', yoy: '+2.9', generated: '11 Aug 2025, 09:15', status: 'Completed' },
|
||||
{ month: 'Jun 2025', index: '113.3', mom: '+0.7', yoy: '+2.8', generated: '11 Jul 2025, 09:15', status: 'Completed' },
|
||||
];
|
||||
|
||||
const handleViewChange = (event, newView) => {
|
||||
if (newView !== null) {
|
||||
setView(newView);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header with logo only */}
|
||||
<header className="bg-white shadow-sm">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="h-16 flex items-center">
|
||||
<img
|
||||
className="h-10 w-auto"
|
||||
src="/assets/images/FCSCLogo.svg"
|
||||
alt="FCSC IPI"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-center mb-4 gap-4">
|
||||
<h1 className="text-2xl font-semibold text-gray-900">IIP Manufacturing Index - Monthly Overview</h1>
|
||||
{/* <Button
|
||||
variant="contained"
|
||||
className="bg-primary-600 hover:bg-primary-700 text-white"
|
||||
>
|
||||
Edit with ChatGPT
|
||||
</Button> */}
|
||||
</div>
|
||||
|
||||
<Card className="p-6 mb-6">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
||||
<div>
|
||||
<div className="flex items-center mb-2">
|
||||
{/* <Chip
|
||||
label="IPI ADMIN"
|
||||
size="small"
|
||||
className="bg-primary-50 text-primary-700 mr-2 font-medium"
|
||||
icon={<FiberManualRecordIcon className="text-primary-500 text-xs" />}
|
||||
/> */}
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
Manufacturing Index — Monthly overview
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-gray-600 text-sm">
|
||||
Monthly manufacturing indices are generated automatically on the 11th of each month using the finalized Laspeyres item indices.
|
||||
Use this view to monitor trends and drill down into ISIC groups.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<div className="text-green-600 flex items-center justify-end text-sm">
|
||||
<FiberManualRecordIcon className="text-xs mr-1" />
|
||||
Next scheduled run: 11 Nov 2025, 02:00 GST
|
||||
</div>
|
||||
<p className="text-gray-500 text-xs mt-1">
|
||||
Formula: Manufacturing index = Σ (sector weightᵢ × group indexᵢ)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Left Column */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="p-6 mb-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Manufacturing index — by reference month</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Filter by year and quickly jump to a month to see its ISIC breakdown.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
<Chip
|
||||
label="Base year: 2022 = 100"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className="text-gray-600 border-gray-300"
|
||||
/>
|
||||
<Chip
|
||||
label="Coverage: Manufacturing (ISIC C)"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
className="text-gray-600 border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3 mb-6">
|
||||
<TextField
|
||||
select
|
||||
label="Year"
|
||||
value={year}
|
||||
onChange={(e) => setYear(e.target.value)}
|
||||
size="small"
|
||||
className="min-w-[120px]"
|
||||
InputLabelProps={{ className: 'text-gray-700' }}
|
||||
>
|
||||
{years.map((y) => (
|
||||
<MenuItem key={y} value={y}>
|
||||
{y}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
placeholder="Search month"
|
||||
size="small"
|
||||
className="flex-grow"
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton size="small" className="text-gray-500">
|
||||
<FileDownloadIcon />
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4">
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-2 sm:mb-0">Showing 5 months for 2025</h4>
|
||||
<p className="text-sm text-gray-500">Click a row to open its ISIC breakdown on the right.</p>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">REFERENCE MONTH</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MANUFACTURING INDEX</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MOM CHANGE</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">YOY CHANGE</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">GENERATED ON</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">STATUS</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ACTIONS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{months.map((row) => (
|
||||
<tr
|
||||
key={row.month}
|
||||
onClick={() => setSelectedMonth(row.month)}
|
||||
className={`cursor-pointer ${selectedMonth === row.month ? 'bg-primary-50' : 'hover:bg-gray-50'}`}
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
<span className="text-sm font-medium text-gray-900">{row.month}</span>
|
||||
{selectedMonth === row.month && (
|
||||
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-100 text-primary-800">
|
||||
Selected
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{row.index} (2022=100)</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
row.mom.startsWith('+')
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{row.mom}%
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
row.yoy.startsWith('+')
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{row.yoy}%
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{row.generated}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
{row.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<IconButton size="small" className="text-gray-400 hover:text-gray-600">
|
||||
<ArrowRightAltIcon />
|
||||
</IconButton>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div>
|
||||
<Card className="p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">Monthly ISIC breakdown</h3>
|
||||
<Chip
|
||||
label={selectedMonth}
|
||||
size="small"
|
||||
className="bg-gray-900 text-white font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 text-sm mb-6">
|
||||
Start from the manufacturing total, then drill down into ISIC 2-digit and 3 & 4-digit groups for the selected month.
|
||||
</p>
|
||||
|
||||
<ToggleButtonGroup
|
||||
color="primary"
|
||||
value={view}
|
||||
exclusive
|
||||
onChange={handleViewChange}
|
||||
aria-label="View"
|
||||
fullWidth
|
||||
className="mb-6"
|
||||
>
|
||||
<ToggleButton
|
||||
value="manufacturing"
|
||||
className={`flex-1 ${view === 'manufacturing' ? 'bg-primary-50 text-primary-700' : 'text-gray-600 hover:bg-gray-50'}`}
|
||||
>
|
||||
Manufacturing total
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="isic2"
|
||||
className={`flex-1 ${view === 'isic2' ? 'bg-primary-50 text-primary-700' : 'text-gray-600 hover:bg-gray-50'}`}
|
||||
>
|
||||
ISIC 2-digit
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="isic34"
|
||||
className={`flex-1 ${view === 'isic34' ? 'bg-primary-50 text-primary-700' : 'text-gray-600 hover:bg-gray-50'}`}
|
||||
>
|
||||
ISIC 3 & 4-digit
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<div className="bg-gray-800 p-4 rounded-lg mb-6">
|
||||
<h4 className="text-gray-400 text-xs font-medium uppercase tracking-wider mb-2">MANUFACTURING INDEX (HEADLINE)</h4>
|
||||
<h3 className="text-2xl font-bold text-white mb-2">114.8 (2022=100)</h3>
|
||||
<div className="flex gap-4 mb-3">
|
||||
<p className="text-gray-400">MoM: <span className="text-green-500 font-semibold">+0.9%</span></p>
|
||||
<p className="text-gray-400">YoY: <span className="text-green-500 font-semibold">+3.2%</span></p>
|
||||
</div>
|
||||
<p className="text-gray-300 text-sm">
|
||||
The manufacturing index increased by 0.9% compared to the previous month and 3.2% compared to the same month last year.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Divider className="border-[#424242] my-6" />
|
||||
|
||||
<div>
|
||||
<h4 className="text-gray-500 text-sm font-medium uppercase tracking-wider mb-4">KEY CONTRIBUTORS</h4>
|
||||
|
||||
<div className="mb-5">
|
||||
<div className="flex justify-between mb-1">
|
||||
<p className="text-sm text-gray-800">Food products (ISIC 10-12)</p>
|
||||
<p className="font-semibold text-gray-900">+0.24</p>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-green-500 w-3/5"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<div className="flex justify-between mb-1">
|
||||
<p className="text-sm text-gray-800">Chemicals (ISIC 20-21)</p>
|
||||
<p className="font-semibold text-gray-900">+0.18</p>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-green-500 w-[45%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<div className="flex justify-between mb-1">
|
||||
<p className="text-sm text-gray-800">Basic metals (ISIC 24)</p>
|
||||
<p className="text-red-500 font-semibold">-0.12</p>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-red-500 w-[30%]"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManufacturingIndex;
|
||||
@ -1,11 +1,11 @@
|
||||
import { getRequest, postRequest } from '@/services/api/CommonService';
|
||||
import resolveEstablishmentId from '@/services/utils/establishment';
|
||||
import { putRequest } from '../api/CommonService';
|
||||
|
||||
|
||||
const endpoint = '/submissions';
|
||||
const ESTABLISHMENT_PRODUCTS_ENDPOINT = '/establishment-products';
|
||||
const QUARTER_PERIODS_ENDPOINT = '/getQuarterPeriods';
|
||||
|
||||
|
||||
// Add this new function to fetch submissions with pagination
|
||||
export const getSubmissions = async (page = 1, limit = 100, config = {}) => {
|
||||
try {
|
||||
@ -23,7 +23,7 @@ export const getSubmissions = async (page = 1, limit = 100, config = {}) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const submitSurvey = async (payload = {}, config = {}) => {
|
||||
const establishmentId = resolveEstablishmentId(payload.establishment_id);
|
||||
const requestBody = {
|
||||
@ -33,24 +33,24 @@ export const submitSurvey = async (payload = {}, config = {}) => {
|
||||
const response = await postRequest(endpoint, requestBody, config);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
export const resubmitSurvey = async (submissionId, payload = {}, config = {}) => {
|
||||
if (!submissionId) {
|
||||
throw new Error('Submission ID is required for resubmission');
|
||||
}
|
||||
|
||||
|
||||
const establishmentId = resolveEstablishmentId(payload.establishment_id);
|
||||
const requestBody = {
|
||||
...payload,
|
||||
...(establishmentId !== undefined ? { establishment_id: establishmentId } : {}),
|
||||
};
|
||||
|
||||
|
||||
const url = `${endpoint}/${submissionId}`;
|
||||
// Use putRequest instead of postRequest with method override
|
||||
const response = await putRequest(url, requestBody, config);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
export const fetchSubmissionHistory = async (establishmentId, config = {}) => {
|
||||
const resolvedId = resolveEstablishmentId(establishmentId);
|
||||
if (resolvedId === undefined) {
|
||||
@ -60,7 +60,7 @@ export const fetchSubmissionHistory = async (establishmentId, config = {}) => {
|
||||
const response = await getRequest(url, config);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
export const fetchSubmissionDetail = async (submissionId, config = {}) => {
|
||||
if (!submissionId) {
|
||||
throw new Error('Submission ID is required to fetch submission detail.');
|
||||
@ -69,7 +69,7 @@ export const fetchSubmissionDetail = async (submissionId, config = {}) => {
|
||||
const response = await getRequest(url, config);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
// Add the new function to the default export
|
||||
export const getQuarterPeriods = async (currentYear, currentQuarter) => {
|
||||
try {
|
||||
@ -83,7 +83,7 @@ export const getQuarterPeriods = async (currentYear, currentQuarter) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getPreviousForecastData = async (establishmentId, quarter, year, productId) => {
|
||||
try {
|
||||
const response = await getRequest(
|
||||
@ -105,7 +105,48 @@ export const getPreviousForecastData = async (establishmentId, quarter, year, pr
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getBeforePreviousData = async (
|
||||
establishmentId,
|
||||
currentQuarter,
|
||||
currentYear,
|
||||
productId
|
||||
) => {
|
||||
try {
|
||||
if (!establishmentId || !currentQuarter || !currentYear || !productId) {
|
||||
throw new Error("Missing required parameters");
|
||||
}
|
||||
|
||||
const q = Number(currentQuarter.replace("Q", ""));
|
||||
let beforePrevQuarter = `Q${q - 2}`;
|
||||
let beforePrevYear = currentYear;
|
||||
|
||||
if (q - 2 <= 0) {
|
||||
beforePrevQuarter = `Q${4 + (q - 2)}`;
|
||||
beforePrevYear = currentYear - 1;
|
||||
}
|
||||
|
||||
const response = await getRequest(
|
||||
"/submissions/getBeforePreviousData",
|
||||
{
|
||||
params: {
|
||||
establishment_id: establishmentId,
|
||||
current_quarter: beforePrevQuarter,
|
||||
current_year: beforePrevYear,
|
||||
product_id: productId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return response?.data ?? response;
|
||||
} catch (error) {
|
||||
console.error("Error fetching before-previous data:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const getSubmissionHistoryByEstablishment = async (establishmentId, config = {}) => {
|
||||
try {
|
||||
if (!establishmentId) {
|
||||
@ -121,27 +162,27 @@ export const getSubmissionHistoryByEstablishment = async (establishmentId, confi
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getSubmissionAuditHistory = async (establishmentId, params = {}) => {
|
||||
try {
|
||||
if (!establishmentId) {
|
||||
throw new Error('Establishment ID is required');
|
||||
}
|
||||
|
||||
|
||||
const response = await getRequest('/submission-audit-history', {
|
||||
params: {
|
||||
establishment_id: establishmentId,
|
||||
...params // Spread any additional parameters (year, quarter, product_id)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return response.data || response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching submission audit history:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getEstablishmentProducts = async (establishmentId, config = {}) => {
|
||||
try {
|
||||
if (!establishmentId) {
|
||||
@ -160,7 +201,7 @@ export const getEstablishmentProducts = async (establishmentId, config = {}) =>
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getProductSubmissionHistory = async (establishmentId, productId, config = {}) => {
|
||||
try {
|
||||
if (!establishmentId || !productId) {
|
||||
@ -180,7 +221,7 @@ export const getProductSubmissionHistory = async (establishmentId, productId, co
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
getSubmissions,
|
||||
submitSurvey,
|
||||
@ -192,5 +233,7 @@ export default {
|
||||
getSubmissionHistoryByEstablishment,
|
||||
getSubmissionAuditHistory,
|
||||
getEstablishmentProducts,
|
||||
getProductSubmissionHistory
|
||||
};
|
||||
getProductSubmissionHistory,
|
||||
getBeforePreviousData
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user