262 lines
8.3 KiB
JavaScript
262 lines
8.3 KiB
JavaScript
// Force this API route to use Node.js runtime
|
|
export const runtime = "nodejs";
|
|
|
|
import { NextResponse } from "next/server";
|
|
import { auth } from "@/lib/auth";
|
|
import { getAllProjects } from "@/lib/queries/projects";
|
|
|
|
export async function GET(request) {
|
|
try {
|
|
const session = await auth();
|
|
|
|
if (!session?.user) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
// Only team leads can access dashboard data
|
|
if (session.user.role !== 'team_lead') {
|
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
}
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const selectedYear = searchParams.get('year') ? parseInt(searchParams.get('year')) : null;
|
|
|
|
// Get all projects
|
|
const projects = getAllProjects();
|
|
|
|
// Calculate realised and unrealised values by project type
|
|
const projectTypes = ['design', 'design+construction', 'construction'];
|
|
const typeSummary = {};
|
|
|
|
projectTypes.forEach(type => {
|
|
typeSummary[type] = {
|
|
realisedValue: 0,
|
|
unrealisedValue: 0
|
|
};
|
|
});
|
|
|
|
projects.forEach(project => {
|
|
const value = parseFloat(project.wartosc_zlecenia) || 0;
|
|
const type = project.project_type;
|
|
|
|
if (!type || !projectTypes.includes(type)) return;
|
|
|
|
if (project.project_status === 'fulfilled' && project.completion_date && project.wartosc_zlecenia) {
|
|
typeSummary[type].realisedValue += value;
|
|
} else if (project.wartosc_zlecenia && project.project_status !== 'cancelled') {
|
|
typeSummary[type].unrealisedValue += value;
|
|
}
|
|
});
|
|
|
|
// Calculate overall totals
|
|
let realisedValue = 0;
|
|
let unrealisedValue = 0;
|
|
|
|
Object.values(typeSummary).forEach(summary => {
|
|
realisedValue += summary.realisedValue;
|
|
unrealisedValue += summary.unrealisedValue;
|
|
});
|
|
|
|
// Filter completed projects (those with completion_date and fulfilled status)
|
|
const completedProjects = projects.filter(project =>
|
|
project.completion_date &&
|
|
project.wartosc_zlecenia &&
|
|
project.project_status === 'fulfilled'
|
|
);
|
|
|
|
// If no data, return sample data for demonstration
|
|
let chartData;
|
|
let summary;
|
|
if (completedProjects.length === 0) {
|
|
// Generate continuous sample data based on selected year or default range
|
|
const currentDate = new Date();
|
|
let startDate, endDate;
|
|
|
|
if (selectedYear) {
|
|
startDate = new Date(selectedYear, 0, 1); // Jan 1st of selected year
|
|
endDate = new Date(selectedYear, 11, 31); // Dec 31st of selected year
|
|
if (endDate > currentDate) endDate = currentDate;
|
|
} else {
|
|
startDate = new Date(2024, 0, 1); // Jan 2024
|
|
endDate = currentDate;
|
|
}
|
|
|
|
chartData = [];
|
|
let cumulative = 0;
|
|
|
|
let tempDate = new Date(startDate);
|
|
while (tempDate <= endDate) {
|
|
const monthName = tempDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short' });
|
|
let monthlyValue = 0;
|
|
|
|
// Add some sample values for certain months (only if they match the selected year or no year selected)
|
|
const shouldAddData = !selectedYear || tempDate.getFullYear() === selectedYear;
|
|
|
|
if (shouldAddData) {
|
|
if (tempDate.getMonth() === 0 && tempDate.getFullYear() === 2024) monthlyValue = 50000; // Jan 2024
|
|
else if (tempDate.getMonth() === 1 && tempDate.getFullYear() === 2024) monthlyValue = 75000; // Feb 2024
|
|
else if (tempDate.getMonth() === 2 && tempDate.getFullYear() === 2024) monthlyValue = 60000; // Mar 2024
|
|
else if (tempDate.getMonth() === 7 && tempDate.getFullYear() === 2024) monthlyValue = 10841; // Aug 2024 (real data)
|
|
else if (tempDate.getMonth() === 8 && tempDate.getFullYear() === 2024) monthlyValue = 18942; // Sep 2024
|
|
else if (tempDate.getMonth() === 9 && tempDate.getFullYear() === 2024) monthlyValue = 13945; // Oct 2024
|
|
else if (tempDate.getMonth() === 10 && tempDate.getFullYear() === 2024) monthlyValue = 12542; // Nov 2024
|
|
else if (tempDate.getMonth() === 0 && tempDate.getFullYear() === 2025) monthlyValue = 25000; // Jan 2025
|
|
else if (tempDate.getMonth() === 1 && tempDate.getFullYear() === 2025) monthlyValue = 35000; // Feb 2025
|
|
}
|
|
|
|
cumulative += monthlyValue;
|
|
chartData.push({
|
|
month: monthName,
|
|
value: monthlyValue,
|
|
cumulative: cumulative
|
|
});
|
|
|
|
tempDate.setMonth(tempDate.getMonth() + 1);
|
|
}
|
|
|
|
summary = {
|
|
total: {
|
|
realisedValue: 958000,
|
|
unrealisedValue: 1242000
|
|
},
|
|
byType: {
|
|
design: {
|
|
realisedValue: 320000,
|
|
unrealisedValue: 480000
|
|
},
|
|
'design+construction': {
|
|
realisedValue: 480000,
|
|
unrealisedValue: 520000
|
|
},
|
|
construction: {
|
|
realisedValue: 158000,
|
|
unrealisedValue: 242000
|
|
}
|
|
}
|
|
};
|
|
} else {
|
|
// Group by month and calculate monthly totals first
|
|
const monthlyData = {};
|
|
|
|
// Sort projects by completion date
|
|
completedProjects.sort((a, b) => new Date(a.completion_date) - new Date(b.completion_date));
|
|
|
|
// First pass: calculate monthly totals
|
|
completedProjects.forEach(project => {
|
|
const date = new Date(project.completion_date);
|
|
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
|
|
const monthName = date.toLocaleDateString('en-US', { year: 'numeric', month: 'short' });
|
|
|
|
if (!monthlyData[monthKey]) {
|
|
monthlyData[monthKey] = {
|
|
month: monthName,
|
|
value: 0
|
|
};
|
|
}
|
|
|
|
const projectValue = parseFloat(project.wartosc_zlecenia) || 0;
|
|
monthlyData[monthKey].value += projectValue;
|
|
});
|
|
|
|
// Generate continuous timeline from earliest completion to current date
|
|
let startDate = new Date();
|
|
let endDate = new Date();
|
|
|
|
if (completedProjects.length > 0) {
|
|
// Find earliest completion date
|
|
const earliestCompletion = completedProjects.reduce((earliest, project) => {
|
|
const projectDate = new Date(project.completion_date);
|
|
return projectDate < earliest ? projectDate : earliest;
|
|
}, new Date());
|
|
|
|
startDate = new Date(earliestCompletion.getFullYear(), earliestCompletion.getMonth(), 1);
|
|
} else {
|
|
// If no completed projects, start from 6 months ago
|
|
startDate = new Date();
|
|
startDate.setMonth(startDate.getMonth() - 6);
|
|
startDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
|
|
}
|
|
|
|
// If a specific year is selected, adjust the date range
|
|
if (selectedYear) {
|
|
startDate = new Date(selectedYear, 0, 1); // January 1st of selected year
|
|
endDate = new Date(selectedYear, 11, 31); // December 31st of selected year
|
|
|
|
// Don't go beyond current date
|
|
if (endDate > new Date()) {
|
|
endDate = new Date();
|
|
}
|
|
}
|
|
|
|
// Generate all months from start to current
|
|
const allMonths = {};
|
|
let currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= endDate) {
|
|
const monthKey = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}`;
|
|
const monthName = currentDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short' });
|
|
|
|
allMonths[monthKey] = {
|
|
month: monthName,
|
|
value: monthlyData[monthKey]?.value || 0
|
|
};
|
|
|
|
currentDate.setMonth(currentDate.getMonth() + 1);
|
|
}
|
|
|
|
// Calculate cumulative values
|
|
let cumulativeValue = 0;
|
|
const sortedMonths = Object.keys(allMonths).sort((a, b) => a.localeCompare(b));
|
|
|
|
sortedMonths.forEach(monthKey => {
|
|
cumulativeValue += allMonths[monthKey].value;
|
|
allMonths[monthKey].cumulative = cumulativeValue;
|
|
});
|
|
|
|
// Convert to array
|
|
chartData = sortedMonths.map(monthKey => ({
|
|
month: allMonths[monthKey].month,
|
|
value: Math.round(allMonths[monthKey].value),
|
|
cumulative: Math.round(allMonths[monthKey].cumulative)
|
|
}));
|
|
summary = {
|
|
total: {
|
|
realisedValue: Math.round(realisedValue),
|
|
unrealisedValue: Math.round(unrealisedValue)
|
|
},
|
|
byType: Object.fromEntries(
|
|
Object.entries(typeSummary).map(([type, data]) => [
|
|
type,
|
|
{
|
|
realisedValue: Math.round(data.realisedValue),
|
|
unrealisedValue: Math.round(data.unrealisedValue)
|
|
}
|
|
])
|
|
)
|
|
};
|
|
}
|
|
|
|
return NextResponse.json({
|
|
chartData,
|
|
summary: {
|
|
total: {
|
|
realisedValue: Math.round(realisedValue),
|
|
unrealisedValue: Math.round(unrealisedValue)
|
|
},
|
|
byType: Object.fromEntries(
|
|
Object.entries(typeSummary).map(([type, data]) => [
|
|
type,
|
|
{
|
|
realisedValue: Math.round(data.realisedValue),
|
|
unrealisedValue: Math.round(data.unrealisedValue)
|
|
}
|
|
])
|
|
)
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Dashboard API error:', error);
|
|
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
}
|
|
} |