feat: enhance Excel export functionality with improved styling and download handling
All checks were successful
Publish Container / publish (push) Successful in 5m39s

This commit is contained in:
Marco 2026-05-20 11:34:54 +02:00
commit 8438a63bd8
3 changed files with 128 additions and 57 deletions

View file

@ -8,6 +8,8 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
private const int DayHeaderRow = 1;
private const int DayStartColumn = 3;
private const int TemplateDayCount = 30;
private const int SaturdaySampleColumn = 6;
private const int SundayHolidaySampleColumn = 7;
private static readonly IReadOnlyDictionary<string, int> TimesheetRowMap = new Dictionary<string, int>(StringComparer.Ordinal)
{
@ -37,9 +39,10 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
workbook.DefinedNames.DeleteAll();
var worksheet = workbook.Worksheet(1);
var dayCount = timesheet.Days.Count;
var styleCatalog = CaptureDayStyleCatalog(worksheet);
AdjustDayColumns(worksheet, dayCount);
ApplyDayHeaders(worksheet, timesheet.Days);
ApplyDayHeaders(worksheet, timesheet.Days, styleCatalog);
ApplyRowValues(worksheet, timesheet.Rows, dayCount);
ApplyTotalFormulas(worksheet, dayCount);
@ -63,7 +66,7 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
}
}
private static void ApplyDayHeaders(IXLWorksheet worksheet, IReadOnlyList<MonthlyTimesheetDayModel> days)
private static void ApplyDayHeaders(IXLWorksheet worksheet, IReadOnlyList<MonthlyTimesheetDayModel> days, DayStyleCatalog styleCatalog)
{
var lastDayColumn = DayStartColumn + days.Count - 1;
@ -74,11 +77,11 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
var headerCell = worksheet.Cell(DayHeaderRow, column);
headerCell.Value = day.Date.Day;
ApplyDayStyle(worksheet, headerCell, day, column, lastDayColumn, 1);
ApplyDayStyle(headerCell, day, column, lastDayColumn, 1, styleCatalog);
foreach (var rowNumber in TimesheetRowMap.Values)
{
ApplyDayStyle(worksheet, worksheet.Cell(rowNumber, column), day, column, lastDayColumn, rowNumber);
ApplyDayStyle(worksheet.Cell(rowNumber, column), day, column, lastDayColumn, rowNumber, styleCatalog);
}
}
}
@ -121,10 +124,32 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
}
}
private static void ApplyDayStyle(IXLWorksheet worksheet, IXLCell targetCell, MonthlyTimesheetDayModel day, int column, int lastDayColumn, int rowNumber)
private static DayStyleCatalog CaptureDayStyleCatalog(IXLWorksheet worksheet)
{
var baseCell = worksheet.Cell(GetBaseStyleRowAddress(rowNumber), GetBaseStyleColumn(column, lastDayColumn));
var baseStyle = baseCell.Style;
return new DayStyleCatalog(
CaptureStylesForColumn(worksheet, DayStartColumn),
CaptureStylesForColumn(worksheet, DayStartColumn + 1),
CaptureStylesForColumn(worksheet, DayStartColumn + TemplateDayCount - 1),
CaptureStylesForColumn(worksheet, SaturdaySampleColumn),
CaptureStylesForColumn(worksheet, SundayHolidaySampleColumn));
}
private static IReadOnlyDictionary<int, IXLStyle> CaptureStylesForColumn(IXLWorksheet worksheet, int column)
{
var rowNumbers = new[] { 1, 2, 4, 12 };
var styles = new Dictionary<int, IXLStyle>(rowNumbers.Length);
foreach (var rowNumber in rowNumbers)
{
styles[rowNumber] = worksheet.Cell(rowNumber, column).Style;
}
return styles;
}
private static void ApplyDayStyle(IXLCell targetCell, MonthlyTimesheetDayModel day, int column, int lastDayColumn, int rowNumber, DayStyleCatalog styleCatalog)
{
var baseStyle = styleCatalog.GetBaseStyle(rowNumber, column == DayStartColumn, column == lastDayColumn);
if (!ShouldHighlight(day))
{
@ -132,9 +157,7 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
return;
}
var sampleColumn = GetHighlightSampleColumn(day, rowNumber);
var sampleCell = worksheet.Cell(GetSampleStyleRow(rowNumber), sampleColumn);
var sampleStyle = sampleCell.Style;
var sampleStyle = styleCatalog.GetHighlightStyle(rowNumber, day);
targetCell.Style = sampleStyle;
targetCell.Style.Border.LeftBorder = baseStyle.Border.LeftBorder;
@ -152,7 +175,7 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
return day.IsWeekend || day.IsHoliday;
}
private static int GetBaseStyleRowAddress(int rowNumber)
private static int GetStyleRow(int rowNumber)
{
return rowNumber switch
{
@ -164,67 +187,42 @@ public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExport
};
}
private static int GetSampleStyleRow(int rowNumber)
private sealed class DayStyleCatalog(
IReadOnlyDictionary<int, IXLStyle> firstColumnStyles,
IReadOnlyDictionary<int, IXLStyle> middleColumnStyles,
IReadOnlyDictionary<int, IXLStyle> lastColumnStyles,
IReadOnlyDictionary<int, IXLStyle> saturdayStyles,
IReadOnlyDictionary<int, IXLStyle> sundayHolidayStyles)
{
return rowNumber switch
public IXLStyle GetBaseStyle(int rowNumber, bool isFirstColumn, bool isLastColumn)
{
1 => 1,
2 => 2,
4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 => 4,
12 => 12,
_ => rowNumber
};
}
var styleRow = GetStyleRow(rowNumber);
private static int GetBaseStyleColumn(int column, int lastDayColumn)
{
if (column == DayStartColumn)
{
return DayStartColumn;
}
if (column == lastDayColumn)
{
return lastDayColumn == DayStartColumn + TemplateDayCount - 1
? DayStartColumn + TemplateDayCount - 1
: DayStartColumn + 1;
}
return DayStartColumn + 1;
}
private static int GetHighlightSampleColumn(MonthlyTimesheetDayModel day, int rowNumber)
{
if (rowNumber == 1)
{
if (day.IsHoliday || day.Date.DayOfWeek == DayOfWeek.Sunday)
if (isFirstColumn)
{
return 7;
return firstColumnStyles[styleRow];
}
if (day.Date.DayOfWeek == DayOfWeek.Saturday)
if (isLastColumn)
{
return 6;
return lastColumnStyles[styleRow];
}
return 7;
return middleColumnStyles[styleRow];
}
if (rowNumber == 2)
public IXLStyle GetHighlightStyle(int rowNumber, MonthlyTimesheetDayModel day)
{
if (day.IsHoliday || day.Date.DayOfWeek == DayOfWeek.Sunday)
var styleRow = GetStyleRow(rowNumber);
if (rowNumber is 1 or 2)
{
return 7;
return day.IsHoliday || day.Date.DayOfWeek == DayOfWeek.Sunday
? sundayHolidayStyles[styleRow]
: saturdayStyles[styleRow];
}
if (day.Date.DayOfWeek == DayOfWeek.Saturday)
{
return 6;
}
return 7;
return saturdayStyles[styleRow];
}
return 6;
}
}