feat: Add contract summary calculations and update dashboard to display data by contract
This commit is contained in:
@@ -35,6 +35,9 @@ export async function GET(request) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Calculate values by contract
|
||||||
|
const contractSummary = {};
|
||||||
|
|
||||||
projects.forEach(project => {
|
projects.forEach(project => {
|
||||||
const value = parseFloat(project.wartosc_zlecenia) || 0;
|
const value = parseFloat(project.wartosc_zlecenia) || 0;
|
||||||
const type = project.project_type;
|
const type = project.project_type;
|
||||||
@@ -46,6 +49,26 @@ export async function GET(request) {
|
|||||||
} else if (project.wartosc_zlecenia && project.project_status !== 'cancelled') {
|
} else if (project.wartosc_zlecenia && project.project_status !== 'cancelled') {
|
||||||
typeSummary[type].unrealisedValue += value;
|
typeSummary[type].unrealisedValue += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group by contract
|
||||||
|
if (project.contract_number && project.wartosc_zlecenia && project.project_status !== 'cancelled') {
|
||||||
|
const contractKey = project.contract_number;
|
||||||
|
if (!contractSummary[contractKey]) {
|
||||||
|
contractSummary[contractKey] = {
|
||||||
|
contract_name: project.contract_name || project.contract_number,
|
||||||
|
realisedValue: 0,
|
||||||
|
unrealisedValue: 0,
|
||||||
|
totalValue: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.project_status === 'fulfilled' && project.completion_date) {
|
||||||
|
contractSummary[contractKey].realisedValue += value;
|
||||||
|
} else {
|
||||||
|
contractSummary[contractKey].unrealisedValue += value;
|
||||||
|
}
|
||||||
|
contractSummary[contractKey].totalValue += value;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate overall totals
|
// Calculate overall totals
|
||||||
@@ -132,6 +155,26 @@ export async function GET(request) {
|
|||||||
realisedValue: 158000,
|
realisedValue: 158000,
|
||||||
unrealisedValue: 242000
|
unrealisedValue: 242000
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
byContract: {
|
||||||
|
'UMK/001/2024': {
|
||||||
|
contract_name: 'Modernizacja budynku głównego',
|
||||||
|
realisedValue: 320000,
|
||||||
|
unrealisedValue: 180000,
|
||||||
|
totalValue: 500000
|
||||||
|
},
|
||||||
|
'UMK/002/2024': {
|
||||||
|
contract_name: 'Budowa parkingu wielopoziomowego',
|
||||||
|
realisedValue: 480000,
|
||||||
|
unrealisedValue: 320000,
|
||||||
|
totalValue: 800000
|
||||||
|
},
|
||||||
|
'UMK/003/2024': {
|
||||||
|
contract_name: 'Remont elewacji',
|
||||||
|
realisedValue: 158000,
|
||||||
|
unrealisedValue: 242000,
|
||||||
|
totalValue: 400000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -251,6 +294,17 @@ export async function GET(request) {
|
|||||||
unrealisedValue: Math.round(data.unrealisedValue)
|
unrealisedValue: Math.round(data.unrealisedValue)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
),
|
||||||
|
byContract: Object.fromEntries(
|
||||||
|
Object.entries(contractSummary).map(([contractNumber, data]) => [
|
||||||
|
contractNumber,
|
||||||
|
{
|
||||||
|
contract_name: data.contract_name,
|
||||||
|
realisedValue: Math.round(data.realisedValue),
|
||||||
|
unrealisedValue: Math.round(data.unrealisedValue),
|
||||||
|
totalValue: Math.round(data.totalValue)
|
||||||
|
}
|
||||||
|
])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -284,6 +284,87 @@ export default function TeamLeadsDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* By Contract Section */}
|
||||||
|
{summaryData?.byContract && Object.keys(summaryData.byContract).length > 0 && (
|
||||||
|
<div className="mt-8">
|
||||||
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-6">
|
||||||
|
{t('teamDashboard.byContract')}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="h-96">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<BarChart
|
||||||
|
data={Object.entries(summaryData.byContract).map(([contractNumber, data]) => ({
|
||||||
|
name: contractNumber,
|
||||||
|
fullName: data.contract_name,
|
||||||
|
realised: data.realisedValue,
|
||||||
|
unrealised: data.unrealisedValue,
|
||||||
|
total: data.totalValue
|
||||||
|
}))}
|
||||||
|
margin={{
|
||||||
|
top: 20,
|
||||||
|
right: 30,
|
||||||
|
left: 20,
|
||||||
|
bottom: 100,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" className="opacity-30" />
|
||||||
|
<XAxis
|
||||||
|
dataKey="name"
|
||||||
|
angle={-45}
|
||||||
|
textAnchor="end"
|
||||||
|
height={100}
|
||||||
|
className="text-gray-600 dark:text-gray-400"
|
||||||
|
fontSize={11}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
className="text-gray-600 dark:text-gray-400"
|
||||||
|
fontSize={12}
|
||||||
|
tickFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
content={({ active, payload }) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white dark:bg-gray-800 p-3 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg">
|
||||||
|
<p className="font-medium text-gray-900 dark:text-white mb-2">{payload[0].payload.fullName}</p>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">{payload[0].payload.name}</p>
|
||||||
|
<p className="text-green-600 dark:text-green-400 text-sm">
|
||||||
|
{`${t('teamDashboard.realised')}: ${formatCurrency(payload[0].payload.realised)}`}
|
||||||
|
</p>
|
||||||
|
<p className="text-purple-600 dark:text-purple-400 text-sm">
|
||||||
|
{`${t('teamDashboard.unrealised')}: ${formatCurrency(payload[0].payload.unrealised)}`}
|
||||||
|
</p>
|
||||||
|
<p className="text-blue-600 dark:text-blue-400 text-sm font-semibold mt-1">
|
||||||
|
{`${t('teamDashboard.total')}: ${formatCurrency(payload[0].payload.total)}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Legend />
|
||||||
|
<Bar
|
||||||
|
dataKey="realised"
|
||||||
|
stackId="a"
|
||||||
|
fill="#10b981"
|
||||||
|
name={t('teamDashboard.realised')}
|
||||||
|
radius={[0, 0, 0, 0]}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="unrealised"
|
||||||
|
stackId="a"
|
||||||
|
fill="#8b5cf6"
|
||||||
|
name={t('teamDashboard.unrealised')}
|
||||||
|
radius={[4, 4, 0, 0]}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ const translations = {
|
|||||||
realisedValue: "Wartość zrealizowana",
|
realisedValue: "Wartość zrealizowana",
|
||||||
unrealisedValue: "Wartość niezrealizowana",
|
unrealisedValue: "Wartość niezrealizowana",
|
||||||
byProjectType: "Według typu projektu",
|
byProjectType: "Według typu projektu",
|
||||||
|
byContract: "Według umowy",
|
||||||
|
total: "Razem",
|
||||||
monthLabel: "Miesiąc:",
|
monthLabel: "Miesiąc:",
|
||||||
monthlyValue: "Wartość miesięczna:",
|
monthlyValue: "Wartość miesięczna:",
|
||||||
cumulative: "Skumulowana:",
|
cumulative: "Skumulowana:",
|
||||||
@@ -769,6 +771,8 @@ const translations = {
|
|||||||
realisedValue: "Realised Value",
|
realisedValue: "Realised Value",
|
||||||
unrealisedValue: "Unrealised Value",
|
unrealisedValue: "Unrealised Value",
|
||||||
byProjectType: "By Project Type",
|
byProjectType: "By Project Type",
|
||||||
|
byContract: "By Contract",
|
||||||
|
total: "Total",
|
||||||
monthLabel: "Month:",
|
monthLabel: "Month:",
|
||||||
monthlyValue: "Monthly Value:",
|
monthlyValue: "Monthly Value:",
|
||||||
cumulative: "Cumulative:",
|
cumulative: "Cumulative:",
|
||||||
|
|||||||
Reference in New Issue
Block a user