From 452c58a5f7c4a6e825671b99a6aa9b524d4d48bc Mon Sep 17 00:00:00 2001 From: Rai Zela Date: Fri, 26 Sep 2025 15:11:19 +0200 Subject: [PATCH 1/4] feat: Excel CRUD demo with form + table + download --- eeplus/.gitignore | 5 + eeplus/Apps/EEPlusDemo.cs | 164 ++++++++++++++++++++++++++++ eeplus/EEPlus.sln | 14 +++ eeplus/EEPlusSoftware.csproj | 28 +++++ eeplus/Entities/Book.cs | 40 +++++++ eeplus/GlobalUsings.cs | 18 +++ eeplus/Helpers/EPPLusExtensions.cs | 68 ++++++++++++ eeplus/Helpers/ExcelManipulation.cs | 94 ++++++++++++++++ eeplus/Program.cs | 15 +++ eeplus/README.md | 17 +++ 10 files changed, 463 insertions(+) create mode 100644 eeplus/.gitignore create mode 100644 eeplus/Apps/EEPlusDemo.cs create mode 100644 eeplus/EEPlus.sln create mode 100644 eeplus/EEPlusSoftware.csproj create mode 100644 eeplus/Entities/Book.cs create mode 100644 eeplus/GlobalUsings.cs create mode 100644 eeplus/Helpers/EPPLusExtensions.cs create mode 100644 eeplus/Helpers/ExcelManipulation.cs create mode 100644 eeplus/Program.cs create mode 100644 eeplus/README.md diff --git a/eeplus/.gitignore b/eeplus/.gitignore new file mode 100644 index 00000000..2cb90536 --- /dev/null +++ b/eeplus/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +*.sln.iml +.idea/**/* +**/.idea/* \ No newline at end of file diff --git a/eeplus/Apps/EEPlusDemo.cs b/eeplus/Apps/EEPlusDemo.cs new file mode 100644 index 00000000..9129c6d4 --- /dev/null +++ b/eeplus/Apps/EEPlusDemo.cs @@ -0,0 +1,164 @@ +using Ivy.Client; +using Ivy.Core.Hooks; +using Ivy.Views.Forms; + +namespace EEPlusSoftware.Apps; + +[App(icon: Icons.Box, title: "EEPlus Software Demo")] +public class EEPlusDemo : ViewBase +{ + public override object? Build() + { + var filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); + var client = UseService(); + + // Ensure file exists + if (!File.Exists(filePath)) + { + using var package = new ExcelPackage(); + package.Workbook.Worksheets.Add("Books"); + package.SaveAs(new FileInfo(filePath)); + } + + var booksState = UseState>(() => ExcelManipulation.ReadExcel()); + booksState.Value = ExcelManipulation.ReadExcel(); + + + var downloadUrl = this.UseDownload( + async () => + { + var filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); + + // Read the file into memory + byte[] fileBytes = await File.ReadAllBytesAsync(filePath); + + // Return as bytes + return fileBytes; + }, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + $"books-{DateTime.UtcNow:yyyy-MM-dd}.xlsx" + ); + + var downloadBtn = new Button("Download Excel") + .Primary().Icon(Icons.Download) + .Url(downloadUrl.Value); + + var book = UseState(() => new Book()); + var formBuilder = book.ToForm().Remove(x => x.ID) + .Label(m => m.Title, "Title") + .Builder(m => m.Title, s => s.ToTextInput().Placeholder("Insert title...")) + .Builder(m => m.Author, s => s.ToTextInput().Placeholder("Insert author...")) + .Builder(m => m.Year, s => s.ToNumberInput().Placeholder("Insert year...").Min(0)) + .Required(m => m.Title, m => m.Author, m => m.Year); + + var (onSubmit, formView, validationView, loading) = formBuilder.UseForm(this.Context); + + return Layout.Vertical() + | Text.H2("EEPlus Software Demo") | + new Button("Generate Excel File").HandleClick(async _ => ExcelManipulation.WriteExcel(booksState)).Loading(loading).Disabled(loading) + | booksState.Value.ToTable() + .Width(Size.Full()) + .Builder(p => p.Title, f => f.Text()) + .Builder(p => p.Author, f => f.Text()) + .Builder(p => p.Year, f => f.Default()) + | downloadBtn | new Button("Delete All Records").HandleClick(async _ => HandleDelete(booksState, filePath, client)) + .Loading(loading).Disabled(loading) + | formView | new Button("Save Book").HandleClick(_ => HandleSubmit(booksState, client, book, onSubmit)) + .Loading(loading).Disabled(loading) + | validationView; + + + } + + #region Handle + async ValueTask HandleSubmit(IState> booksState, IClientProvider client, IState book, Func> onSubmit) + { + if (await onSubmit()) + { + var filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); + + // Ensure file exists + if (!File.Exists(filePath)) + { + using var package = new ExcelPackage(); + package.Workbook.Worksheets.Add("Books"); + package.SaveAs(new FileInfo(filePath)); + } + + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var ws = package.Workbook.Worksheets[0]; + + // Calculate next row + int nextRow; + if (ws.Dimension == null) + { + // Sheet is empty → start at row 2 (row 1 is header) + nextRow = 2; + } + else + { + nextRow = ws.Dimension.End.Row + 1; + } + var bookRecord = new Book(book.Value.Title, book.Value.Author, book.Value.Year); + + ws.Cells[nextRow, 1].Value = (nextRow - 1).ToString(); // ID + ws.Cells[nextRow, 2].Value = bookRecord.Title; + ws.Cells[nextRow, 3].Value = bookRecord.Author; + ws.Cells[nextRow, 4].Value = bookRecord.Year; + + ws.Cells[1, 1, nextRow, 4].AutoFitColumns(); + + package.Save(); + } + } + + // refresh reactive books list + booksState.Value = ExcelManipulation.ReadExcel(); + + // clear the form state (reset book model) + book.Value = new Book(); + client.Toast("Book added!"); + } + async ValueTask HandleDelete(IState> booksState, string filePath, IClientProvider client) + { + + if (!File.Exists(filePath)) + { + // Nothing to clear + client.Toast("No Excel file found to clear."); + return; + } + + + using var package = new ExcelPackage(new FileInfo(filePath)); + var ws = package.Workbook.Worksheets.FirstOrDefault(); + if (ws == null) + { + client.Toast("Worksheet not found."); + return; + } + + // If there is no data (only headers or empty) + var lastRow = ws.Dimension?.End.Row ?? 0; + if (lastRow <= 1) + { + client.Toast("No records to clear."); + return; + } + + // Clear rows 2..lastRow (keep header in row 1) + ws.DeleteRow(2, lastRow - 1); + + //autosize columns after deletion + ws.Cells[1, 1, 1, 4].AutoFitColumns(); + + package.Save(); + + client.Toast("All records cleared."); + // refresh reactive books list + booksState.Value = ExcelManipulation.ReadExcel(); + } + #endregion Handle + +} \ No newline at end of file diff --git a/eeplus/EEPlus.sln b/eeplus/EEPlus.sln new file mode 100644 index 00000000..58ea5666 --- /dev/null +++ b/eeplus/EEPlus.sln @@ -0,0 +1,14 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/eeplus/EEPlusSoftware.csproj b/eeplus/EEPlusSoftware.csproj new file mode 100644 index 00000000..8af38c2a --- /dev/null +++ b/eeplus/EEPlusSoftware.csproj @@ -0,0 +1,28 @@ + + + + Exe + net9.0 + enable + enable + CS8618;CS8603;CS8602;CS8604;CS9113 + EEPlusSoftware + + + + + + + + + + + + + + + + + + + diff --git a/eeplus/Entities/Book.cs b/eeplus/Entities/Book.cs new file mode 100644 index 00000000..453d868e --- /dev/null +++ b/eeplus/Entities/Book.cs @@ -0,0 +1,40 @@ +namespace EEPlusSoftware.Entities; + +[AttributeUsage(AttributeTargets.All)] +public class Column : System.Attribute +{ + public int ColumnIndex { get; set; } + + public Column(int column) + { + ColumnIndex = column; + } +} +public class Book +{ + [@Column(1)] + [Required] + public string ID { get; set; } + + [@Column(2)] + [Required] + public string Title { get; set; } + [@Column(3)] + [Required] + public string Author { get; set; } + [@Column(4)] + [Required] + public int Year { get; set; } + + public Book(string title, string author, int year) + { + Title = title; + Author = author; + Year = year; + } + + public Book() + { + + } +}; diff --git a/eeplus/GlobalUsings.cs b/eeplus/GlobalUsings.cs new file mode 100644 index 00000000..359c2883 --- /dev/null +++ b/eeplus/GlobalUsings.cs @@ -0,0 +1,18 @@ +global using EEPlusSoftware.Entities; +global using EEPlusSoftware.Helpers; +global using Ivy; +global using Ivy.Apps; +global using Ivy.Chrome; +global using Ivy.Core; +global using Ivy.Hooks; +global using Ivy.Shared; +global using Ivy.Views; +global using Ivy.Views.Builders; +global using Ivy.Views.Tables; +global using OfficeOpenXml; +global using OfficeOpenXml.Style; +global using System.ComponentModel.DataAnnotations; +global using System.Globalization; +global using System.Reactive.Linq; +global using System.Reflection; + diff --git a/eeplus/Helpers/EPPLusExtensions.cs b/eeplus/Helpers/EPPLusExtensions.cs new file mode 100644 index 00000000..4dcc4e56 --- /dev/null +++ b/eeplus/Helpers/EPPLusExtensions.cs @@ -0,0 +1,68 @@ +namespace EEPlusSoftware.Helpers; + +public static class EPPLusExtensions +{ + public static IEnumerable ConvertSheetToObjects(this ExcelWorksheet worksheet) where T : new() + { + + Func columnOnly = y => y.AttributeType == typeof(Column); + + var columns = typeof(T) + .GetProperties() + .Where(x => x.CustomAttributes.Any(columnOnly)) + .Select(p => new + { + Property = p, + Column = p.GetCustomAttributes().First().ColumnIndex //safe because if where above + }).ToList(); + + + var rows = worksheet.Cells + .Select(cell => cell.Start.Row) + .Distinct() + .OrderBy(x => x); + + + //Create the collection container + var collection = rows.Skip(1) + .Select(row => + { + var tnew = new T(); + columns.ForEach(col => + { + //This is the real wrinkle to using reflection - Excel stores all numbers as double including int + var val = worksheet.Cells[row, col.Column]; + //If it is numeric it is a double since that is how excel stores all numbers + if (val.Value == null) + { + col.Property.SetValue(tnew, null); + return; + } + if (col.Property.PropertyType == typeof(Int32)) + { + col.Property.SetValue(tnew, val.GetValue()); + return; + } + if (col.Property.PropertyType == typeof(double)) + { + col.Property.SetValue(tnew, val.GetValue()); + return; + } + if (col.Property.PropertyType == typeof(DateTime)) + { + col.Property.SetValue(tnew, val.GetValue()); + return; + } + //Its a string + col.Property.SetValue(tnew, val.GetValue()); + }); + + return tnew; + }); + + + //Send it back + return collection; + } +} + diff --git a/eeplus/Helpers/ExcelManipulation.cs b/eeplus/Helpers/ExcelManipulation.cs new file mode 100644 index 00000000..ed4af489 --- /dev/null +++ b/eeplus/Helpers/ExcelManipulation.cs @@ -0,0 +1,94 @@ +using Ivy.Core.Hooks; + +namespace EEPlusSoftware.Helpers; + +public static class ExcelManipulation +{ + //using the EPPlus library to generate an Excel file + public static async ValueTask WriteExcel(IState> booksState) + { + var books = new[] + { + new Book("The Great Gatsby", "F. Scott Fitzgerald", 1925), + new Book("To Kill a Mockingbird", "Harper Lee", 1960), + new Book("1984", "George Orwell", 1949), + new Book("Pride and Prejudice", "Jane Austen", 1813), + new Book("The Catcher in the Rye", "J.D. Salinger", 1951) + }; + //Creating an instance of ExcelPackage + ExcelPackage excel = new ExcelPackage(); + + //name of the sheet + var worksheet = excel.Workbook.Worksheets.Add("Books"); + + //setting the properties of the sheet + worksheet.TabColor = System.Drawing.Color.Black; + worksheet.DefaultRowHeight = 12; + + // Setting the properties of the first row + worksheet.Row(1).Height = 20; + worksheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; + worksheet.Row(1).Style.Font.Bold = true; + + // Header of the Excel sheet + worksheet.Cells[1, 1].Value = "ID"; + worksheet.Cells[1, 2].Value = "Title"; + worksheet.Cells[1, 3].Value = "Author"; + worksheet.Cells[1, 4].Value = "Year"; + + // Inserting the article data into excel sheet by using the for each loop + // As we have values to the first row we will start with second row + int recordIndex = 2; + + foreach (var article in books) + { + worksheet.Cells[recordIndex, 1].Value = (recordIndex - 1).ToString(); + worksheet.Cells[recordIndex, 2].Value = article.Title; + worksheet.Cells[recordIndex, 3].Value = article.Author; + worksheet.Cells[recordIndex, 4].Value = article.Year; + recordIndex++; + } + + // By default, the column width is not set to auto fit for the content of the range, so we are using AutoFit() method here. + worksheet.Column(1).AutoFit(); + worksheet.Column(2).AutoFit(); + worksheet.Column(3).AutoFit(); + worksheet.Column(4).AutoFit(); + + // file name with .xlsx extension + string p_strPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); + + if (File.Exists(p_strPath)) + File.Delete(p_strPath); + + // Create excel file on physical disk + FileStream objFileStrm = File.Create(p_strPath); + objFileStrm.Close(); + + // Write content to excel file + File.WriteAllBytes(p_strPath, excel.GetAsByteArray()); + //Close Excel package + excel.Dispose(); + // refresh reactive books list + booksState.Value = ExcelManipulation.ReadExcel(); + } + + //using the EEPlus library to read an Excel file + public static List ReadExcel() + { + string filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); + string _fileName = "books.xlsx"; + var RESOURCES_WORKSHEET = 0; + //Creating an instance of ExcelPackage + using (FileStream fileStream = new FileStream(filePath, FileMode.Open)) + { + ExcelPackage excel = new ExcelPackage(fileStream); + var workSheet = excel.Workbook.Worksheets[RESOURCES_WORKSHEET]; + + IEnumerable newcollection = workSheet.ConvertSheetToObjects(); + return newcollection.ToList(); + } + } + + +} diff --git a/eeplus/Program.cs b/eeplus/Program.cs new file mode 100644 index 00000000..68c37ce0 --- /dev/null +++ b/eeplus/Program.cs @@ -0,0 +1,15 @@ + +CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US"); +var server = new Server(); +#if DEBUG +server.UseHotReload(); +#endif +server.AddAppsFromAssembly(); +server.AddConnectionsFromAssembly(); +var chromeSettings = new ChromeSettings() + + .UseTabs(preventDuplicates: true); +server.UseChrome(chromeSettings); + +ExcelPackage.License.SetNonCommercialOrganization("Ivy"); +await server.RunAsync(); diff --git a/eeplus/README.md b/eeplus/README.md new file mode 100644 index 00000000..e401172e --- /dev/null +++ b/eeplus/README.md @@ -0,0 +1,17 @@ +# EEPlusSoftware + +Web application created using [Ivy](https://github.com/Ivy-Interactive/Ivy). + +Ivy is a web framework for building interactive web applications using C# and .NET. + +## Run + +``` +dotnet watch +``` + +## Deploy + +``` +ivy deploy +``` \ No newline at end of file From e86d77c66bfe2ed51ffec0c0d590a85210b774eb Mon Sep 17 00:00:00 2001 From: Rai Zela Date: Sat, 27 Sep 2025 23:16:28 +0200 Subject: [PATCH 2/4] fixed warnings --- eeplus/Apps/EEPlusDemo.cs | 6 +++--- eeplus/Helpers/ExcelManipulation.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eeplus/Apps/EEPlusDemo.cs b/eeplus/Apps/EEPlusDemo.cs index 9129c6d4..d72e6e52 100644 --- a/eeplus/Apps/EEPlusDemo.cs +++ b/eeplus/Apps/EEPlusDemo.cs @@ -55,13 +55,13 @@ public class EEPlusDemo : ViewBase return Layout.Vertical() | Text.H2("EEPlus Software Demo") | - new Button("Generate Excel File").HandleClick(async _ => ExcelManipulation.WriteExcel(booksState)).Loading(loading).Disabled(loading) + new Button("Generate Excel File").HandleClick(_ => ExcelManipulation.WriteExcel(booksState)).Loading(loading).Disabled(loading) | booksState.Value.ToTable() .Width(Size.Full()) .Builder(p => p.Title, f => f.Text()) .Builder(p => p.Author, f => f.Text()) .Builder(p => p.Year, f => f.Default()) - | downloadBtn | new Button("Delete All Records").HandleClick(async _ => HandleDelete(booksState, filePath, client)) + | downloadBtn | new Button("Delete All Records").HandleClick(_ => HandleDelete(booksState, filePath, client)) .Loading(loading).Disabled(loading) | formView | new Button("Save Book").HandleClick(_ => HandleSubmit(booksState, client, book, onSubmit)) .Loading(loading).Disabled(loading) @@ -120,7 +120,7 @@ async ValueTask HandleSubmit(IState> booksState, IClientProvider clie book.Value = new Book(); client.Toast("Book added!"); } - async ValueTask HandleDelete(IState> booksState, string filePath, IClientProvider client) + void HandleDelete(IState> booksState, string filePath, IClientProvider client) { if (!File.Exists(filePath)) diff --git a/eeplus/Helpers/ExcelManipulation.cs b/eeplus/Helpers/ExcelManipulation.cs index ed4af489..22380c98 100644 --- a/eeplus/Helpers/ExcelManipulation.cs +++ b/eeplus/Helpers/ExcelManipulation.cs @@ -5,7 +5,7 @@ namespace EEPlusSoftware.Helpers; public static class ExcelManipulation { //using the EPPlus library to generate an Excel file - public static async ValueTask WriteExcel(IState> booksState) + public static void WriteExcel(IState> booksState) { var books = new[] { @@ -76,8 +76,8 @@ public static async ValueTask WriteExcel(IState> booksState) //using the EEPlus library to read an Excel file public static List ReadExcel() { - string filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); string _fileName = "books.xlsx"; + string filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), _fileName); var RESOURCES_WORKSHEET = 0; //Creating an instance of ExcelPackage using (FileStream fileStream = new FileStream(filePath, FileMode.Open)) From 920783b7da081fc3404636627c9eb344b3d8e0e5 Mon Sep 17 00:00:00 2001 From: Rai Zela Date: Sat, 27 Sep 2025 23:39:51 +0200 Subject: [PATCH 3/4] Copilot fixes --- eeplus/Apps/EEPlusDemo.cs | 148 +++++++++++++--------------- eeplus/Helpers/EPPLusExtensions.cs | 2 +- eeplus/Helpers/ExcelManipulation.cs | 6 +- 3 files changed, 77 insertions(+), 79 deletions(-) diff --git a/eeplus/Apps/EEPlusDemo.cs b/eeplus/Apps/EEPlusDemo.cs index d72e6e52..80623f0d 100644 --- a/eeplus/Apps/EEPlusDemo.cs +++ b/eeplus/Apps/EEPlusDemo.cs @@ -7,28 +7,32 @@ namespace EEPlusSoftware.Apps; [App(icon: Icons.Box, title: "EEPlus Software Demo")] public class EEPlusDemo : ViewBase { - public override object? Build() - { - var filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); - var client = UseService(); + private static string GetExcelFilePath() => + System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); - // Ensure file exists + private static void EnsureExcelFileExists(string filePath) + { if (!File.Exists(filePath)) { using var package = new ExcelPackage(); package.Workbook.Worksheets.Add("Books"); package.SaveAs(new FileInfo(filePath)); } + } + public override object? Build() + { + var filePath = GetExcelFilePath(); + var client = UseService(); + + // Ensure file exists + EnsureExcelFileExists(filePath); var booksState = UseState>(() => ExcelManipulation.ReadExcel()); - booksState.Value = ExcelManipulation.ReadExcel(); var downloadUrl = this.UseDownload( async () => { - var filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); - // Read the file into memory byte[] fileBytes = await File.ReadAllBytesAsync(filePath); @@ -61,9 +65,9 @@ public class EEPlusDemo : ViewBase .Builder(p => p.Title, f => f.Text()) .Builder(p => p.Author, f => f.Text()) .Builder(p => p.Year, f => f.Default()) - | downloadBtn | new Button("Delete All Records").HandleClick(_ => HandleDelete(booksState, filePath, client)) + | downloadBtn | new Button("Delete All Records").HandleClick(_ => HandleDeleteAsync(booksState, filePath, client)) .Loading(loading).Disabled(loading) - | formView | new Button("Save Book").HandleClick(_ => HandleSubmit(booksState, client, book, onSubmit)) + | formView | new Button("Save Book").HandleClick(async _ => await HandleSubmitAsync(booksState, client, book, onSubmit)) .Loading(loading).Disabled(loading) | validationView; @@ -71,93 +75,83 @@ public class EEPlusDemo : ViewBase } #region Handle - async ValueTask HandleSubmit(IState> booksState, IClientProvider client, IState book, Func> onSubmit) + async ValueTask HandleSubmitAsync(IState> booksState, IClientProvider client, IState book, Func> onSubmit) { - if (await onSubmit()) + try { - var filePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx"); - - // Ensure file exists - if (!File.Exists(filePath)) - { - using var package = new ExcelPackage(); - package.Workbook.Worksheets.Add("Books"); - package.SaveAs(new FileInfo(filePath)); - } - - using (var package = new ExcelPackage(new FileInfo(filePath))) + if (await onSubmit()) { - var ws = package.Workbook.Worksheets[0]; + var filePath = GetExcelFilePath(); + EnsureExcelFileExists(filePath); - // Calculate next row - int nextRow; - if (ws.Dimension == null) - { - // Sheet is empty → start at row 2 (row 1 is header) - nextRow = 2; - } - else + using (var package = new ExcelPackage(new FileInfo(filePath))) { - nextRow = ws.Dimension.End.Row + 1; - } - var bookRecord = new Book(book.Value.Title, book.Value.Author, book.Value.Year); + var ws = package.Workbook.Worksheets[0]; + int nextRow = ws.Dimension == null ? 2 : ws.Dimension.End.Row + 1; + var bookRecord = new Book(book.Value.Title, book.Value.Author, book.Value.Year); - ws.Cells[nextRow, 1].Value = (nextRow - 1).ToString(); // ID - ws.Cells[nextRow, 2].Value = bookRecord.Title; - ws.Cells[nextRow, 3].Value = bookRecord.Author; - ws.Cells[nextRow, 4].Value = bookRecord.Year; + ws.Cells[nextRow, 1].Value = (nextRow - 1).ToString(); + ws.Cells[nextRow, 2].Value = bookRecord.Title; + ws.Cells[nextRow, 3].Value = bookRecord.Author; + ws.Cells[nextRow, 4].Value = bookRecord.Year; + ws.Cells[1, 1, nextRow, 4].AutoFitColumns(); - ws.Cells[1, 1, nextRow, 4].AutoFitColumns(); + package.Save(); + } - package.Save(); + booksState.Value = ExcelManipulation.ReadExcel(); + book.Value = new Book(); + client.Toast("Book added!"); } } - - // refresh reactive books list - booksState.Value = ExcelManipulation.ReadExcel(); - - // clear the form state (reset book model) - book.Value = new Book(); - client.Toast("Book added!"); - } - void HandleDelete(IState> booksState, string filePath, IClientProvider client) - { - - if (!File.Exists(filePath)) + catch (IOException ex) { - // Nothing to clear - client.Toast("No Excel file found to clear."); - return; + client.Toast($"File access error. Please try again. Technical error: {ex.Message}"); + // Log ex.Message or ex.ToString() as needed } - - - using var package = new ExcelPackage(new FileInfo(filePath)); - var ws = package.Workbook.Worksheets.FirstOrDefault(); - if (ws == null) + catch (Exception ex) { - client.Toast("Worksheet not found."); - return; + client.Toast($"An unexpected error occurred. Technical error: {ex.Message}"); + // Log ex.Message or ex.ToString() as needed } + } - // If there is no data (only headers or empty) - var lastRow = ws.Dimension?.End.Row ?? 0; - if (lastRow <= 1) + void HandleDeleteAsync(IState> booksState, string filePath, IClientProvider client) + { + try { - client.Toast("No records to clear."); - return; - } + EnsureExcelFileExists(filePath); - // Clear rows 2..lastRow (keep header in row 1) - ws.DeleteRow(2, lastRow - 1); + using var package = new ExcelPackage(new FileInfo(filePath)); + var ws = package.Workbook.Worksheets.FirstOrDefault(); + if (ws == null) + { + client.Toast("Worksheet not found."); + return; + } - //autosize columns after deletion - ws.Cells[1, 1, 1, 4].AutoFitColumns(); + var lastRow = ws.Dimension?.End.Row ?? 0; + if (lastRow <= 1) + { + client.Toast("No records to clear."); + return; + } - package.Save(); + ws.DeleteRow(2, lastRow - 1); + ws.Cells[1, 1, 1, 4].AutoFitColumns(); + package.Save(); - client.Toast("All records cleared."); - // refresh reactive books list - booksState.Value = ExcelManipulation.ReadExcel(); + client.Toast("All records cleared."); + booksState.Value = ExcelManipulation.ReadExcel(); + } + catch (IOException ex) + { + client.Toast($"File access error. Please try again. Technical error: {ex.Message}"); + } + catch (Exception ex) + { + client.Toast($"An unexpected error occurred. Technical error: {ex.Message}"); + } } #endregion Handle diff --git a/eeplus/Helpers/EPPLusExtensions.cs b/eeplus/Helpers/EPPLusExtensions.cs index 4dcc4e56..31996847 100644 --- a/eeplus/Helpers/EPPLusExtensions.cs +++ b/eeplus/Helpers/EPPLusExtensions.cs @@ -13,7 +13,7 @@ public static class EPPLusExtensions .Select(p => new { Property = p, - Column = p.GetCustomAttributes().First().ColumnIndex //safe because if where above + Column = p.GetCustomAttributes().First().ColumnIndex //safe because of the where clause above }).ToList(); diff --git a/eeplus/Helpers/ExcelManipulation.cs b/eeplus/Helpers/ExcelManipulation.cs index 22380c98..d9e5f8a7 100644 --- a/eeplus/Helpers/ExcelManipulation.cs +++ b/eeplus/Helpers/ExcelManipulation.cs @@ -7,7 +7,10 @@ public static class ExcelManipulation //using the EPPlus library to generate an Excel file public static void WriteExcel(IState> booksState) { - var books = new[] + Book[] books = booksState.Value.ToArray(); + if (books is null || books.Count() == 0) + { + books = new[] { new Book("The Great Gatsby", "F. Scott Fitzgerald", 1925), new Book("To Kill a Mockingbird", "Harper Lee", 1960), @@ -15,6 +18,7 @@ public static void WriteExcel(IState> booksState) new Book("Pride and Prejudice", "Jane Austen", 1813), new Book("The Catcher in the Rye", "J.D. Salinger", 1951) }; + } //Creating an instance of ExcelPackage ExcelPackage excel = new ExcelPackage(); From e6bcc16632b930fcd8b482999dff88cd6a531a7e Mon Sep 17 00:00:00 2001 From: Rai Zela Date: Sat, 27 Sep 2025 23:58:45 +0200 Subject: [PATCH 4/4] refactoring --- eeplus/EEPlus.sln | 14 -------------- eeplus/EEPlusSoftware.csproj | 3 --- 2 files changed, 17 deletions(-) delete mode 100644 eeplus/EEPlus.sln diff --git a/eeplus/EEPlus.sln b/eeplus/EEPlus.sln deleted file mode 100644 index 58ea5666..00000000 --- a/eeplus/EEPlus.sln +++ /dev/null @@ -1,14 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/eeplus/EEPlusSoftware.csproj b/eeplus/EEPlusSoftware.csproj index 8af38c2a..bd5dee55 100644 --- a/eeplus/EEPlusSoftware.csproj +++ b/eeplus/EEPlusSoftware.csproj @@ -12,9 +12,6 @@ - - -