Excel Export eingebaut
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
@inject ISnackbar Snackbar
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>KW @_kw – Wochenübersicht – Timetracker</PageTitle>
|
||||
|
||||
@@ -34,6 +35,10 @@ else
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="0">
|
||||
<MudTooltip Text="Woche als Excel exportieren">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Download"
|
||||
Style="color: var(--mud-palette-primary-text)" OnClick="ExportWeekExcel" />
|
||||
</MudTooltip>
|
||||
@if (!IsCurrentWeek)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Style="color: var(--mud-palette-primary-text)"
|
||||
@@ -491,6 +496,10 @@ else
|
||||
private async Task PrevWeek() { _monday = _monday.AddDays(-7); await LoadWeek(); }
|
||||
private async Task NextWeek() { _monday = _monday.AddDays(7); await LoadWeek(); }
|
||||
private async Task GoToCurrentWeek() { _monday = GetMonday(DateOnly.FromDateTime(DateTime.Today)); await LoadWeek(); }
|
||||
private void ExportWeekExcel()
|
||||
{
|
||||
NavigationManager.NavigateTo($"api/tracker/export/week?monday={_monday:yyyy-MM-dd}", forceLoad: true);
|
||||
}
|
||||
|
||||
private static DateOnly GetMonday(DateOnly date)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
@inject ITimetrackerService TrackerService
|
||||
@inject IHolidayService HolidayService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>@_deCulture.DateTimeFormat.GetMonthName(_month) @_year – Monatsübersicht – Timetracker</PageTitle>
|
||||
|
||||
@@ -32,6 +33,10 @@ else
|
||||
</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="0">
|
||||
<MudTooltip Text="Monat als Excel exportieren">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Download"
|
||||
Style="color: var(--mud-palette-primary-text)" OnClick="ExportMonthExcel" />
|
||||
</MudTooltip>
|
||||
@if (!IsCurrentMonth)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Style="color: var(--mud-palette-primary-text)"
|
||||
@@ -268,6 +273,11 @@ else
|
||||
await LoadMonth();
|
||||
}
|
||||
|
||||
private void ExportMonthExcel()
|
||||
{
|
||||
NavigationManager.NavigateTo($"api/tracker/export/month?year={_year}&month={_month}", forceLoad: true);
|
||||
}
|
||||
|
||||
private static string FormatTs(TimeSpan ts, bool sign = false)
|
||||
{
|
||||
var neg = ts < TimeSpan.Zero;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@attribute [Authorize]
|
||||
@inject ITimetrackerService TrackerService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@using System.Security.Claims
|
||||
@using timetracker.Shared
|
||||
|
||||
@@ -210,6 +211,75 @@ else
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
@* ── Excel Export ── *@
|
||||
<MudItem xs="12">
|
||||
<MudCard Elevation="3" Class="rounded-xl">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6" Style="font-weight:700; color: var(--mud-palette-text-primary)">Excel-Datenexport</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">Lade deine erfassten Arbeitszeiten in verschiedenen Zeiträumen als formatierte Excel-Datei herunter.</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudGrid Spacing="3">
|
||||
@* Wochenexport *@
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.subtitle2" Style="font-weight:700">Wochenexport</MudText>
|
||||
<MudDatePicker Label="Woche wählen" @bind-Date="_exportWeekDate" PickerVariant="PickerVariant.Dialog" Variant="Variant.Outlined" Color="Color.Primary" />
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Download" Variant="Variant.Filled" Color="Color.Primary" OnClick="ExportWeek" FullWidth="true">
|
||||
Woche exportieren
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
@* Monatsexport *@
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.subtitle2" Style="font-weight:700">Monatsexport</MudText>
|
||||
<MudStack Row="true" Spacing="2">
|
||||
<MudSelect T="int" Label="Monat" @bind-Value="_exportMonth" Variant="Variant.Outlined">
|
||||
@for (int m = 1; m <= 12; m++)
|
||||
{
|
||||
var monthVal = m;
|
||||
<MudSelectItem Value="@monthVal">@_deCulture.DateTimeFormat.GetMonthName(monthVal)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudSelect T="int" Label="Jahr" @bind-Value="_exportMonthYear" Variant="Variant.Outlined">
|
||||
@for (int y = DateTime.Today.Year - 5; y <= DateTime.Today.Year + 1; y++)
|
||||
{
|
||||
var yearVal = y;
|
||||
<MudSelectItem Value="@yearVal">@yearVal</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Download" Variant="Variant.Filled" Color="Color.Primary" OnClick="ExportMonth" FullWidth="true">
|
||||
Monat exportieren
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
@* Jahresexport *@
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.subtitle2" Style="font-weight:700">Jahresexport</MudText>
|
||||
<MudSelect T="int" Label="Jahr" @bind-Value="_exportYear" Variant="Variant.Outlined">
|
||||
@for (int y = DateTime.Today.Year - 5; y <= DateTime.Today.Year + 1; y++)
|
||||
{
|
||||
var yearVal = y;
|
||||
<MudSelectItem Value="@yearVal">@yearVal</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Download" Variant="Variant.Filled" Color="Color.Primary" OnClick="ExportYear" FullWidth="true">
|
||||
Jahr exportieren
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
@* ── Diagnostics Panel ── *@
|
||||
<MudItem xs="12">
|
||||
@@ -263,6 +333,12 @@ else
|
||||
private double _weekTargetHours;
|
||||
private double _weekOvertimeHours;
|
||||
private double _maxHoursValue = 10.0;
|
||||
|
||||
// Excel Export variables
|
||||
private DateTime? _exportWeekDate = DateTime.Today;
|
||||
private int _exportMonth = DateTime.Today.Month;
|
||||
private int _exportMonthYear = DateTime.Today.Year;
|
||||
private int _exportYear = DateTime.Today.Year;
|
||||
|
||||
// Debug variables
|
||||
private int _rawDbDaysCount = 0;
|
||||
@@ -373,6 +449,25 @@ else
|
||||
return $"{prefix}{(int)abs.TotalHours}:{abs.Minutes:D2}";
|
||||
}
|
||||
|
||||
private void ExportWeek()
|
||||
{
|
||||
if (_exportWeekDate.HasValue)
|
||||
{
|
||||
var date = DateOnly.FromDateTime(_exportWeekDate.Value);
|
||||
NavigationManager.NavigateTo($"api/tracker/export/week?monday={date:yyyy-MM-dd}", forceLoad: true);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportMonth()
|
||||
{
|
||||
NavigationManager.NavigateTo($"api/tracker/export/month?year={_exportMonthYear}&month={_exportMonth}", forceLoad: true);
|
||||
}
|
||||
|
||||
private void ExportYear()
|
||||
{
|
||||
NavigationManager.NavigateTo($"api/tracker/export/year?year={_exportYear}", forceLoad: true);
|
||||
}
|
||||
|
||||
private sealed class DayStat
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
using ClosedXML.Excel;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using timetracker.Shared;
|
||||
|
||||
namespace timetracker.Data;
|
||||
|
||||
public static class ExcelExporter
|
||||
{
|
||||
public static async Task<byte[]> GenerateExcelExportAsync(
|
||||
int userId,
|
||||
DateOnly startDate,
|
||||
DateOnly endDate,
|
||||
string title,
|
||||
ITimetrackerService trackerService,
|
||||
TimetrackerDbContext db)
|
||||
{
|
||||
var settings = await trackerService.GetSettingsAsync(userId);
|
||||
|
||||
var workDays = await db.WorkDays
|
||||
.Include(w => w.Breaks)
|
||||
.Where(w => w.UserId == userId && w.Date >= startDate && w.Date <= endDate)
|
||||
.ToDictionaryAsync(w => w.Date);
|
||||
|
||||
var holidaysList = await db.PublicHolidays
|
||||
.Where(h => h.Date >= startDate && h.Date <= endDate)
|
||||
.ToListAsync();
|
||||
|
||||
var state = settings.GermanState;
|
||||
var holidayMap = holidaysList
|
||||
.Where(h => string.IsNullOrEmpty(h.Counties) || (!string.IsNullOrEmpty(state) && h.Counties.Split(',').Contains(state)))
|
||||
.ToDictionary(h => h.Date, h => h.Name);
|
||||
|
||||
var vacationMap = await db.VacationDays
|
||||
.Where(v => v.UserId == userId && v.Date >= startDate && v.Date <= endDate)
|
||||
.ToDictionaryAsync(v => v.Date, v => v.Note ?? "Urlaub");
|
||||
|
||||
var user = await db.Users.FindAsync(userId);
|
||||
var userName = user != null ? user.Username : "Mitarbeiter";
|
||||
|
||||
var rows = new List<ExportRow>();
|
||||
|
||||
for (var date = startDate; date <= endDate; date = date.AddDays(1))
|
||||
{
|
||||
bool isWorkDay = settings.IsWorkDay(date.DayOfWeek);
|
||||
bool isHoliday = holidayMap.ContainsKey(date);
|
||||
bool isVacation = vacationMap.ContainsKey(date);
|
||||
|
||||
double targetHours = (isWorkDay && !isHoliday && !isVacation) ? settings.DailyTargetHours : 0.0;
|
||||
|
||||
string status = "Wochenende";
|
||||
if (isHoliday) status = "Feiertag";
|
||||
else if (isVacation) status = "Urlaub";
|
||||
else if (isWorkDay) status = "Arbeitstag";
|
||||
|
||||
TimeSpan? startTime = null;
|
||||
TimeSpan? endTime = null;
|
||||
TimeSpan breakTime = TimeSpan.Zero;
|
||||
double actualHours = 0.0;
|
||||
|
||||
string note = "";
|
||||
if (isHoliday) note = holidayMap[date];
|
||||
else if (isVacation) note = vacationMap[date];
|
||||
|
||||
if (workDays.TryGetValue(date, out var wd))
|
||||
{
|
||||
if (wd.StartTime.HasValue) startTime = wd.StartTime.Value.ToTimeSpan();
|
||||
if (wd.EndTime.HasValue) endTime = wd.EndTime.Value.ToTimeSpan();
|
||||
|
||||
if (startTime.HasValue && endTime.HasValue)
|
||||
{
|
||||
var gross = endTime.Value - startTime.Value;
|
||||
if (gross > TimeSpan.Zero)
|
||||
{
|
||||
var breakTotal = wd.Breaks
|
||||
.Where(b => b.StartTime.HasValue && b.EndTime.HasValue && b.EndTime > b.StartTime)
|
||||
.Aggregate(TimeSpan.Zero, (s, b) =>
|
||||
s + (b.EndTime!.Value.ToTimeSpan() - b.StartTime!.Value.ToTimeSpan()));
|
||||
|
||||
var minMinutes = gross.TotalHours >= 9.0 ? 45 : 30;
|
||||
var minBreak = TimeSpan.FromMinutes(Math.Max(minMinutes, settings.MinimumBreakMinutes));
|
||||
var finalBreak = breakTotal > minBreak ? breakTotal : minBreak;
|
||||
|
||||
breakTime = finalBreak;
|
||||
actualHours = (gross - finalBreak).TotalHours;
|
||||
if (actualHours < 0) actualHours = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rows.Add(new ExportRow
|
||||
{
|
||||
Date = date,
|
||||
Status = status,
|
||||
StartTime = startTime,
|
||||
EndTime = endTime,
|
||||
BreakTime = breakTime,
|
||||
ActualHours = actualHours,
|
||||
TargetHours = targetHours,
|
||||
DiffHours = actualHours - targetHours,
|
||||
Note = note
|
||||
});
|
||||
}
|
||||
|
||||
return ExportToExcel(title, userName, startDate, endDate, rows);
|
||||
}
|
||||
|
||||
public static byte[] ExportToExcel(
|
||||
string title,
|
||||
string userName,
|
||||
DateOnly startDate,
|
||||
DateOnly endDate,
|
||||
List<ExportRow> rows)
|
||||
{
|
||||
using var workbook = new XLWorkbook();
|
||||
var worksheet = workbook.Worksheets.Add("Arbeitszeiten");
|
||||
|
||||
// Gitterlinien explizit einblenden
|
||||
worksheet.ShowGridLines = true;
|
||||
|
||||
// Title Block
|
||||
worksheet.Cell("A1").Value = title;
|
||||
worksheet.Cell("A1").Style.Font.Bold = true;
|
||||
worksheet.Cell("A1").Style.Font.FontSize = 16;
|
||||
worksheet.Cell("A1").Style.Font.FontColor = XLColor.FromHtml("#0F172A"); // Slate 900
|
||||
|
||||
// Info Block
|
||||
worksheet.Cell("A2").Value = $"Mitarbeiter: {userName}";
|
||||
worksheet.Cell("A2").Style.Font.FontSize = 11;
|
||||
worksheet.Cell("A2").Style.Font.Italic = true;
|
||||
|
||||
worksheet.Cell("A3").Value = $"Zeitraum: {startDate:dd.MM.yyyy} bis {endDate:dd.MM.yyyy}";
|
||||
worksheet.Cell("A3").Style.Font.FontSize = 11;
|
||||
worksheet.Cell("A3").Style.Font.Italic = true;
|
||||
|
||||
// Tabellenüberschriften (Zeile 5)
|
||||
string[] headers = {
|
||||
"Datum", "Wochentag", "Status", "Beginn", "Ende",
|
||||
"Pause", "Netto (Ist)", "Soll-Stunden", "Differenz (Gleitzeit)", "Notiz / Feiertag / Urlaub"
|
||||
};
|
||||
|
||||
for (int i = 0; i < headers.Length; i++)
|
||||
{
|
||||
var cell = worksheet.Cell(5, i + 1);
|
||||
cell.Value = headers[i];
|
||||
cell.Style.Font.Bold = true;
|
||||
cell.Style.Font.FontColor = XLColor.White;
|
||||
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#0EA5E9"); // Primary color (Sky 500)
|
||||
cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
||||
}
|
||||
|
||||
// Datenzeilen
|
||||
int currentRow = 6;
|
||||
var deCulture = new CultureInfo("de-DE");
|
||||
foreach (var r in rows)
|
||||
{
|
||||
worksheet.Cell(currentRow, 1).Value = r.Date.ToString("dd.MM.yyyy");
|
||||
worksheet.Cell(currentRow, 2).Value = r.Date.ToString("dddd", deCulture);
|
||||
worksheet.Cell(currentRow, 3).Value = r.Status;
|
||||
|
||||
if (r.StartTime.HasValue)
|
||||
{
|
||||
worksheet.Cell(currentRow, 4).Value = r.StartTime.Value.ToString(@"hh\:mm");
|
||||
}
|
||||
else
|
||||
{
|
||||
worksheet.Cell(currentRow, 4).Value = "—";
|
||||
}
|
||||
|
||||
if (r.EndTime.HasValue)
|
||||
{
|
||||
worksheet.Cell(currentRow, 5).Value = r.EndTime.Value.ToString(@"hh\:mm");
|
||||
}
|
||||
else
|
||||
{
|
||||
worksheet.Cell(currentRow, 5).Value = "—";
|
||||
}
|
||||
|
||||
worksheet.Cell(currentRow, 6).Value = r.BreakTime.TotalMinutes > 0 ? r.BreakTime.ToString(@"hh\:mm") : "—";
|
||||
|
||||
worksheet.Cell(currentRow, 7).Value = r.ActualHours;
|
||||
worksheet.Cell(currentRow, 7).Style.NumberFormat.Format = "0.00";
|
||||
|
||||
worksheet.Cell(currentRow, 8).Value = r.TargetHours;
|
||||
worksheet.Cell(currentRow, 8).Style.NumberFormat.Format = "0.00";
|
||||
|
||||
worksheet.Cell(currentRow, 9).Value = r.DiffHours;
|
||||
worksheet.Cell(currentRow, 9).Style.NumberFormat.Format = "+0.00;-0.00;0.00";
|
||||
|
||||
if (r.DiffHours > 0)
|
||||
{
|
||||
worksheet.Cell(currentRow, 9).Style.Font.FontColor = XLColor.FromHtml("#16A34A"); // Green 600
|
||||
worksheet.Cell(currentRow, 9).Style.Font.Bold = true;
|
||||
}
|
||||
else if (r.DiffHours < 0)
|
||||
{
|
||||
worksheet.Cell(currentRow, 9).Style.Font.FontColor = XLColor.FromHtml("#DC2626"); // Red 600
|
||||
worksheet.Cell(currentRow, 9).Style.Font.Bold = true;
|
||||
}
|
||||
|
||||
worksheet.Cell(currentRow, 10).Value = r.Note;
|
||||
|
||||
// Zebra-Streifen
|
||||
if (currentRow % 2 == 0)
|
||||
{
|
||||
for (int col = 1; col <= headers.Length; col++)
|
||||
{
|
||||
worksheet.Cell(currentRow, col).Style.Fill.BackgroundColor = XLColor.FromHtml("#F8FAFC"); // Slate 50
|
||||
}
|
||||
}
|
||||
|
||||
// Rahmenlinien
|
||||
for (int col = 1; col <= headers.Length; col++)
|
||||
{
|
||||
worksheet.Cell(currentRow, col).Style.Border.BottomBorder = XLBorderStyleValues.Thin;
|
||||
worksheet.Cell(currentRow, col).Style.Border.BottomBorderColor = XLColor.FromHtml("#E2E8F0");
|
||||
}
|
||||
|
||||
currentRow++;
|
||||
}
|
||||
|
||||
// Summenzeile
|
||||
currentRow++;
|
||||
|
||||
worksheet.Cell(currentRow, 6).Value = "Gesamt:";
|
||||
worksheet.Cell(currentRow, 6).Style.Font.Bold = true;
|
||||
worksheet.Cell(currentRow, 6).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right;
|
||||
|
||||
var actualSumRange = $"G6:G{currentRow - 2}";
|
||||
worksheet.Cell(currentRow, 7).FormulaA1 = $"SUM({actualSumRange})";
|
||||
worksheet.Cell(currentRow, 7).Style.Font.Bold = true;
|
||||
worksheet.Cell(currentRow, 7).Style.NumberFormat.Format = "0.00";
|
||||
|
||||
var targetSumRange = $"H6:H{currentRow - 2}";
|
||||
worksheet.Cell(currentRow, 8).FormulaA1 = $"SUM({targetSumRange})";
|
||||
worksheet.Cell(currentRow, 8).Style.Font.Bold = true;
|
||||
worksheet.Cell(currentRow, 8).Style.NumberFormat.Format = "0.00";
|
||||
|
||||
var diffSumRange = $"I6:I{currentRow - 2}";
|
||||
worksheet.Cell(currentRow, 9).FormulaA1 = $"SUM({diffSumRange})";
|
||||
worksheet.Cell(currentRow, 9).Style.Font.Bold = true;
|
||||
worksheet.Cell(currentRow, 9).Style.NumberFormat.Format = "+0.00;-0.00;0.00";
|
||||
|
||||
// Summenrahmen
|
||||
for (int col = 6; col <= 9; col++)
|
||||
{
|
||||
worksheet.Cell(currentRow, col).Style.Border.TopBorder = XLBorderStyleValues.Thin;
|
||||
worksheet.Cell(currentRow, col).Style.Border.TopBorderColor = XLColor.Black;
|
||||
worksheet.Cell(currentRow, col).Style.Border.BottomBorder = XLBorderStyleValues.Double;
|
||||
worksheet.Cell(currentRow, col).Style.Border.BottomBorderColor = XLColor.Black;
|
||||
}
|
||||
|
||||
// Spaltenbreiten automatisch anpassen
|
||||
worksheet.Columns().AdjustToContents();
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
workbook.SaveAs(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class ExportRow
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
public string Status { get; set; } = "";
|
||||
public TimeSpan? StartTime { get; set; }
|
||||
public TimeSpan? EndTime { get; set; }
|
||||
public TimeSpan BreakTime { get; set; }
|
||||
public double ActualHours { get; set; }
|
||||
public double TargetHours { get; set; }
|
||||
public double DiffHours { get; set; }
|
||||
public string Note { get; set; } = "";
|
||||
}
|
||||
@@ -459,6 +459,83 @@ trackerApi.MapGet("/month", async (ClaimsPrincipal claimsPrincipal, [FromQuery]
|
||||
return Results.Ok(days);
|
||||
});
|
||||
|
||||
trackerApi.MapGet("/export/week", async (ClaimsPrincipal claimsPrincipal, [FromQuery] string monday, ITimetrackerService trackerService, TimetrackerDbContext db) =>
|
||||
{
|
||||
var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();
|
||||
|
||||
if (DateOnly.TryParse(monday, out var date))
|
||||
{
|
||||
var mondayDate = date;
|
||||
int diff = ((int)mondayDate.DayOfWeek - (int)DayOfWeek.Monday + 7) % 7;
|
||||
mondayDate = mondayDate.AddDays(-diff);
|
||||
var sundayDate = mondayDate.AddDays(6);
|
||||
|
||||
var fileBytes = await ExcelExporter.GenerateExcelExportAsync(
|
||||
userId,
|
||||
mondayDate,
|
||||
sundayDate,
|
||||
$"Wochenbericht KW {System.Globalization.ISOWeek.GetWeekOfYear(new DateTime(mondayDate.Year, mondayDate.Month, mondayDate.Day))}",
|
||||
trackerService,
|
||||
db);
|
||||
|
||||
return Results.File(
|
||||
fileBytes,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
$"Wochenbericht_KW_{System.Globalization.ISOWeek.GetWeekOfYear(new DateTime(mondayDate.Year, mondayDate.Month, mondayDate.Day))}_{mondayDate:yyyyMMdd}.xlsx");
|
||||
}
|
||||
return Results.BadRequest("Ungültiges Datum.");
|
||||
});
|
||||
|
||||
trackerApi.MapGet("/export/month", async (ClaimsPrincipal claimsPrincipal, [FromQuery] int year, [FromQuery] int month, ITimetrackerService trackerService, TimetrackerDbContext db) =>
|
||||
{
|
||||
var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();
|
||||
|
||||
if (month < 1 || month > 12) return Results.BadRequest("Ungültiger Monat.");
|
||||
|
||||
var startDate = new DateOnly(year, month, 1);
|
||||
var endDate = startDate.AddMonths(1).AddDays(-1);
|
||||
|
||||
var deCulture = new System.Globalization.CultureInfo("de-DE");
|
||||
var monthName = deCulture.DateTimeFormat.GetMonthName(month);
|
||||
|
||||
var fileBytes = await ExcelExporter.GenerateExcelExportAsync(
|
||||
userId,
|
||||
startDate,
|
||||
endDate,
|
||||
$"Monatsbericht {monthName} {year}",
|
||||
trackerService,
|
||||
db);
|
||||
|
||||
return Results.File(
|
||||
fileBytes,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
$"Monatsbericht_{monthName}_{year}.xlsx");
|
||||
});
|
||||
|
||||
trackerApi.MapGet("/export/year", async (ClaimsPrincipal claimsPrincipal, [FromQuery] int year, ITimetrackerService trackerService, TimetrackerDbContext db) =>
|
||||
{
|
||||
var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (!int.TryParse(idClaim, out var userId)) return Results.Unauthorized();
|
||||
|
||||
var startDate = new DateOnly(year, 1, 1);
|
||||
var endDate = new DateOnly(year, 12, 31);
|
||||
|
||||
var fileBytes = await ExcelExporter.GenerateExcelExportAsync(
|
||||
userId,
|
||||
startDate,
|
||||
endDate,
|
||||
$"Jahresbericht {year}",
|
||||
trackerService,
|
||||
db);
|
||||
|
||||
return Results.File(
|
||||
fileBytes,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
$"Jahresbericht_{year}.xlsx");
|
||||
});
|
||||
|
||||
// ── Holiday-API-Endpoints (Protected) ─────────────────────────────────────────
|
||||
var holidaysApi = app.MapGroup("/api/holidays").RequireAuthorization();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ClosedXML" Version="0.105.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="*" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />
|
||||
|
||||
Reference in New Issue
Block a user