WebUI / Pages :
===============

	private async Task Export()
	{
		item.DT_WORK_FORCE_ID_WORK_FORCE = listDT_WORK_FORCE_ID_WORK_FORCE!.Where(e => e.ID_WORK_FORCE == item.ID_WORK_FORCE).FirstOrDefault();

		if(item.DATE_START>item.DATE_END)
		{
			var dtFin = item.DATE_START;
			var dtDebut = item.DATE_END;

			item.DATE_START = dtDebut;
			item.DATE_END = dtFin;
		}

		try
		{
			var resultat = await ApiService.GetExportByFiltersAsync($"api/DT_TIME_SHEET/export", item);
			if (resultat == null || resultat.Length == 0)
			{
				// Gérer le cas d'erreur ou pas de données
				ToastService.ShowInfo($"Il n'y a pas de données disponibles.", settings => { settings.Timeout = 2000; settings.IconType = IconType.None; settings.Position = ToastPosition.BottomCenter; });
				return;
			}

			// Nom de fichier avec timestamp
			var now = DateTime.Now;
			var fileName = $"reportings_{now:yyyyMMdd_HHmmss}.xlsx";

			// Convertir en base64
			var base64 = Convert.ToBase64String(resultat);

			// Appel JS pour lancer le téléchargement
			await JS!.InvokeVoidAsync("downloadFileFromByteArray", fileName, base64);
		}
		catch (Exception ex)
		{
			ToastService.ShowError($"Erreur dans les exports. Faites CTRL + F5 et recommencez... Si le soucis persiste, voir avec l'administrateur.", settings => { settings.Timeout = 2000; settings.IconType = IconType.None; settings.Position = ToastPosition.BottomCenter; });
		}
	}

WebUI / Services :
==================
        public async Task<byte[]?> GetExportByFiltersAsync<T>(string url, T data)
        {
            var response = await _httpClient.PostAsJsonAsync<T>(url, data);
            if (!response.IsSuccessStatusCode)
                return null;

            return await response.Content.ReadAsByteArrayAsync();
        }


API / CONTROLLER
================

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Temporis.Domain;


namespace Temporis.Api.Controllers
{

    public partial class DT_TIME_SHEETController
    {
        [HttpPost("export")]
        [Authorize(Roles = "Admin,Manager")]
        public async Task<ActionResult> Export([FromBody] DT_TIME_SHEET dt_time_sheet)
        {
            var bytes = await dt_time_sheetService.ExportReportings(dt_time_sheet);

            if (bytes == null || bytes.Length == 0)
                return NotFound("Aucune donnée à exporter.");

            return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"reportings.xlsx");
        }
    }
}

APPLICATION / Service :
=======================
>>> D'abord : Installer le nuget ClosedXML sur le projet "Application"

        #region Exports
        public async Task<byte[]?> ExportReportings(DT_TIME_SHEET filters)
        {
            if (filters == null)
                return null;

            var lst = await dt_time_sheetRepository.GetAll_by_filters_Async(filters);
            var lstItems = lst.ToList();

            if (lstItems == null || lstItems.Count == 0)
                return null;


            using (var workbook = new XLWorkbook())
            {
                // Grouper les lignes par ID_TIME_SHEET
                var groupByTimeSheet = lstItems
                    .GroupBy(l => l.ID_TIME_SHEET)
                    .ToList();

                foreach (var group in groupByTimeSheet)
                {
                    var timeSheet = group.First().DT_TIME_SHEET;
                    string nomOnglet = timeSheet?.LIBELLE ?? $"TimeSheet_{group.Key.ToString().Substring(0, 8)}";

                    // Crée l'onglet
                    var worksheet = workbook.Worksheets.Add(nomOnglet);

                    int row = 0;

                    // Identification
                    worksheet.Cell(row + 1, 1).Value = "Nom";
                    worksheet.Cell(row + 2, 1).Value = "Prénom";
                    worksheet.Cell(row + 3, 1).Value = "Début Période";
                    worksheet.Cell(row + 4, 1).Value = "Fin Période";
                    worksheet.Cell(row + 5, 1).Value = "Validation manager ?";

                    worksheet.Cell(row + 1, 1).Style.Font.Bold = true;
                    worksheet.Cell(row + 2, 1).Style.Font.Bold = true;
                    worksheet.Cell(row + 3, 1).Style.Font.Bold = true;
                    worksheet.Cell(row + 4, 1).Style.Font.Bold = true;
                    worksheet.Cell(row + 5, 1).Style.Font.Bold = true;

                    worksheet.Cell(row + 1, 2).Value = timeSheet!.DT_WORK_FORCE_ID_WORK_FORCE!.LAST_NAME;
                    worksheet.Cell(row + 2, 2).Value = timeSheet!.DT_WORK_FORCE_ID_WORK_FORCE!.FIRST_NAME;
                    worksheet.Cell(row + 3, 2).Value = timeSheet!.DATE_START.ToString("dd-MM-yyyy");
                    worksheet.Cell(row + 4, 2).Value = timeSheet!.DATE_END.ToString("dd-MM-yyyy");
                    worksheet.Cell(row + 5, 2).Value = timeSheet!.IsValidat ? "Oui" : "Non";

                    row = row + 7;
                    // Entêtes de lignes
                    worksheet.Cell(row, 1).Value = "Date";
                    worksheet.Cell(row, 2).Value = "Projet";
                    worksheet.Cell(row, 3).Value = "Quantité (j)";
                    worksheet.Cell(row, 4).Value = "Télétravail";

                    worksheet.Row(row).Style.Font.Bold = true;

                    // Remplir les lignes
                    row = row + 1;
                    double nbrTeleTravail = 0;
                    
                    var lstAlternance = group.Where(e=>e.DT_PROJECT?.ID_PROJECT_TYPE == ToolsRepo.ID_PROJECT_TYPE_ALTERNANCE).ToList();
                    foreach (var line in group)
                    {
                        worksheet.Cell(row, 1).Value = line.DAY.ToString("dd-MM-yyyy");
                        worksheet.Cell(row, 2).Value = line.DT_PROJECT?.LIBELLE_PROJECT ?? "";
                        worksheet.Cell(row, 3).Value = line.QUANTITY;
                        worksheet.Cell(row, 4).Value = line.TELETRAVAIL ? "Oui" : "Non";
                        if(line.TELETRAVAIL)
                        {
                            nbrTeleTravail = nbrTeleTravail + line.QUANTITY;
                        }

                        row++;
                    }

                    row = row + 2;
                    worksheet.Cell(row, 1).Value = "Total télétravail (j)";
                    worksheet.Cell(row, 1).Style.Font.Bold = true;
                    worksheet.Cell(row, 2).Value = nbrTeleTravail;

                    var lstAbs = await dt_day_offRepository.GetAll_By_ID_WORK_FORCE_And_Dates_Async(timeSheet!.ID_WORK_FORCE, timeSheet!.DATE_START, timeSheet!.DATE_END);
                    var lstAbsences = lstAbs.ToList();

                    var nbrJoursOuvres = Infrastructure.Tools.NbrJoursOuvres(timeSheet!.DATE_START, timeSheet!.DATE_END);
                    row = row + 2;
                    worksheet.Cell(row, 1).Value = "Nbre de Tickets Restaurant";
                    worksheet.Cell(row, 1).Style.Font.Bold = true;
                    worksheet.Cell(row, 2).Value = nbrJoursOuvres - lstAbsences.Count - lstAlternance.Count;

                    row = row + 2;
                    if (lstAbsences != null && lstAbsences.Count() > 0)
                    {
                        worksheet.Cell(row, 1).Value = "Absences";
                        worksheet.Cell(row, 1).Style.Font.Bold = true;
                        row = row + 1;

                        foreach (var item in lstAbsences)
                        {
                            worksheet.Cell(row, 1).Value = item.DATE_DAY_OFF.ToString("dd-MM-yyyy");
                            worksheet.Cell(row, 2).Value = item.DT_DAY_OFF!.DT_REF_DAY_OFF_TYPE!.LIBELLE_DAY_OFF_TYPE;
                            row++;
                        }
                    }

                    // Ajustement automatique des colonnes
                    worksheet.Columns().AdjustToContents();
                }


                using var stream = new MemoryStream();
                workbook.SaveAs(stream);
                return stream.ToArray();
            }
        }
        #endregion