270 lines
8.8 KiB
JavaScript
270 lines
8.8 KiB
JavaScript
import React, { Component } from 'react';
|
|
import {
|
|
Box,
|
|
Paper,
|
|
Typography,
|
|
Select,
|
|
MenuItem,
|
|
FormControl,
|
|
InputLabel,
|
|
Grid,
|
|
Button,
|
|
ListSubheader,
|
|
Divider,
|
|
} from '@mui/material';
|
|
import {
|
|
Download as DownloadIcon,
|
|
CalendarToday as CalendarIcon,
|
|
DateRange as QuarterIcon,
|
|
Event as YearIcon,
|
|
} from '@mui/icons-material';
|
|
|
|
class SummaryHeader extends Component {
|
|
formatAmount = (amount) => {
|
|
return new Intl.NumberFormat('de-DE', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
}).format(amount);
|
|
};
|
|
|
|
getMonthName = (monthYear) => {
|
|
if (!monthYear) return '';
|
|
const [year, month] = monthYear.split('-');
|
|
const date = new Date(year, month - 1);
|
|
return date.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' });
|
|
};
|
|
|
|
getQuarterName = (year, quarter) => {
|
|
return `Q${quarter} ${year}`;
|
|
};
|
|
|
|
getYearName = (year) => {
|
|
return `Jahr ${year}`;
|
|
};
|
|
|
|
generateTimeRangeOptions = (months) => {
|
|
if (!months || months.length === 0) return { months: [], quarters: [], years: [] };
|
|
|
|
// Extract years from months
|
|
const years = [...new Set(months.map(month => month.split('-')[0]))].sort().reverse();
|
|
|
|
// Generate quarters
|
|
const quarters = [];
|
|
years.forEach(year => {
|
|
for (let q = 4; q >= 1; q--) {
|
|
const quarterMonths = this.getQuarterMonths(year, q);
|
|
// Only include quarter if we have data for at least one month in it
|
|
if (quarterMonths.some(month => months.includes(month))) {
|
|
quarters.push({ year, quarter: q, value: `${year}-Q${q}` });
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
months: months,
|
|
quarters: quarters,
|
|
years: years.map(year => ({ year, value: year }))
|
|
};
|
|
};
|
|
|
|
getQuarterMonths = (year, quarter) => {
|
|
const startMonth = (quarter - 1) * 3 + 1;
|
|
return [
|
|
`${year}-${startMonth.toString().padStart(2, '0')}`,
|
|
`${year}-${(startMonth + 1).toString().padStart(2, '0')}`,
|
|
`${year}-${(startMonth + 2).toString().padStart(2, '0')}`
|
|
];
|
|
};
|
|
|
|
getSelectedDisplayName = (selectedValue, timeRangeOptions) => {
|
|
if (!selectedValue) return '';
|
|
|
|
if (selectedValue.includes('-Q')) {
|
|
const [year, quarterPart] = selectedValue.split('-Q');
|
|
return this.getQuarterName(year, quarterPart);
|
|
} else if (selectedValue.length === 4) {
|
|
return this.getYearName(selectedValue);
|
|
} else {
|
|
return this.getMonthName(selectedValue);
|
|
}
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
months,
|
|
selectedMonth,
|
|
summary,
|
|
loading,
|
|
onMonthChange
|
|
} = this.props;
|
|
|
|
if (!summary) return null;
|
|
|
|
const timeRangeOptions = this.generateTimeRangeOptions(months);
|
|
|
|
return (
|
|
<Paper elevation={1} sx={{ p: { xs: 1.5, sm: 2 }, mb: 2 }}>
|
|
<Grid container alignItems="center" spacing={{ xs: 1, sm: 2 }}>
|
|
<Grid item xs={12} md={3}>
|
|
<FormControl fullWidth size="small">
|
|
<InputLabel>Zeitraum</InputLabel>
|
|
<Select
|
|
value={selectedMonth}
|
|
onChange={onMonthChange}
|
|
label="Zeitraum"
|
|
sx={{
|
|
'& .MuiSelect-select': {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1
|
|
}
|
|
}}
|
|
>
|
|
{/* Months Section */}
|
|
<ListSubheader sx={{
|
|
color: 'primary.main',
|
|
fontWeight: 'bold',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
backgroundColor: 'background.paper',
|
|
lineHeight: '36px'
|
|
}}>
|
|
<CalendarIcon fontSize="small" />
|
|
Monate
|
|
</ListSubheader>
|
|
{timeRangeOptions.months.map((month) => (
|
|
<MenuItem
|
|
key={month}
|
|
value={month}
|
|
sx={{
|
|
pl: 4,
|
|
'&:hover': { backgroundColor: 'action.hover' }
|
|
}}
|
|
>
|
|
{this.getMonthName(month)}
|
|
</MenuItem>
|
|
))}
|
|
|
|
<Divider sx={{ my: 1 }} />
|
|
|
|
{/* Quarters Section */}
|
|
<ListSubheader sx={{
|
|
color: 'secondary.main',
|
|
fontWeight: 'bold',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
backgroundColor: 'background.paper',
|
|
lineHeight: '36px'
|
|
}}>
|
|
<QuarterIcon fontSize="small" />
|
|
Quartale
|
|
</ListSubheader>
|
|
{timeRangeOptions.quarters.map((quarter) => (
|
|
<MenuItem
|
|
key={quarter.value}
|
|
value={quarter.value}
|
|
sx={{
|
|
pl: 4,
|
|
color: 'secondary.main',
|
|
'&:hover': { backgroundColor: 'secondary.light', color: 'secondary.contrastText' }
|
|
}}
|
|
>
|
|
{this.getQuarterName(quarter.year, quarter.quarter)}
|
|
</MenuItem>
|
|
))}
|
|
|
|
<Divider sx={{ my: 1 }} />
|
|
|
|
{/* Years Section */}
|
|
<ListSubheader sx={{
|
|
color: 'success.main',
|
|
fontWeight: 'bold',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
backgroundColor: 'background.paper',
|
|
lineHeight: '36px'
|
|
}}>
|
|
<YearIcon fontSize="small" />
|
|
Jahre
|
|
</ListSubheader>
|
|
{timeRangeOptions.years.map((year) => (
|
|
<MenuItem
|
|
key={year.value}
|
|
value={year.value}
|
|
sx={{
|
|
pl: 4,
|
|
color: 'success.main',
|
|
'&:hover': { backgroundColor: 'success.light', color: 'success.contrastText' }
|
|
}}
|
|
>
|
|
{this.getYearName(year.year)}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={6} sm={4} md={2}>
|
|
<Box textAlign="center">
|
|
<Typography variant="caption" color="textSecondary">Transaktionen</Typography>
|
|
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#1976d2', fontSize: { xs: '0.9rem', sm: '1.25rem' } }}>
|
|
{summary.totalTransactions}
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
<Grid item xs={6} sm={4} md={2}>
|
|
<Box textAlign="center">
|
|
<Typography variant="caption" color="textSecondary">Einnahmen</Typography>
|
|
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#388e3c', fontSize: { xs: '0.9rem', sm: '1.25rem' } }}>
|
|
{this.formatAmount(summary.totalIncome)}
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
<Grid item xs={6} sm={4} md={2}>
|
|
<Box textAlign="center">
|
|
<Typography variant="caption" color="textSecondary">Ausgaben</Typography>
|
|
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#d32f2f', fontSize: { xs: '0.9rem', sm: '1.25rem' } }}>
|
|
{this.formatAmount(summary.totalExpenses)}
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
<Grid item xs={6} sm={4} md={2}>
|
|
<Box textAlign="center">
|
|
<Typography variant="caption" color="textSecondary">Nettobetrag</Typography>
|
|
<Typography
|
|
variant="h6"
|
|
sx={{
|
|
fontWeight: 'bold',
|
|
color: summary.netAmount >= 0 ? '#388e3c' : '#d32f2f',
|
|
fontSize: { xs: '0.9rem', sm: '1.25rem' }
|
|
}}
|
|
>
|
|
{this.formatAmount(summary.netAmount)}
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
<Grid item xs={6} sm={4} md={1}>
|
|
<Box textAlign="center">
|
|
<Typography variant="caption" color="textSecondary">JTL ✓</Typography>
|
|
<Typography
|
|
variant="h6"
|
|
sx={{
|
|
fontWeight: 'bold',
|
|
color: summary.jtlMatches === undefined ? '#856404' : '#388e3c',
|
|
fontSize: { xs: '0.9rem', sm: '1.25rem' }
|
|
}}
|
|
>
|
|
{summary.jtlMatches === undefined ? '?' : summary.jtlMatches}
|
|
</Typography>
|
|
</Box>
|
|
</Grid>
|
|
|
|
</Grid>
|
|
</Paper>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default SummaryHeader;
|