Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions eeplus/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
obj/
*.sln.iml
.idea/**/*
**/.idea/*
158 changes: 158 additions & 0 deletions eeplus/Apps/EEPlusDemo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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
{
private static string GetExcelFilePath() =>
System.IO.Path.Combine(System.IO.Path.GetTempPath(), "books.xlsx");

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<IClientProvider>();

// Ensure file exists
EnsureExcelFileExists(filePath);

var booksState = UseState<List<Book>>(() => ExcelManipulation.ReadExcel());


var downloadUrl = this.UseDownload(
async () =>
{
// 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(_ => 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(_ => HandleDeleteAsync(booksState, filePath, client))
.Loading(loading).Disabled(loading)
| formView | new Button("Save Book").HandleClick(async _ => await HandleSubmitAsync(booksState, client, book, onSubmit))
.Loading(loading).Disabled(loading)
| validationView;


}

#region Handle
async ValueTask HandleSubmitAsync(IState<List<Book>> booksState, IClientProvider client, IState<Book> book, Func<Task<bool>> onSubmit)
{
try
{
if (await onSubmit())
{
var filePath = GetExcelFilePath();
EnsureExcelFileExists(filePath);

using (var package = new ExcelPackage(new FileInfo(filePath)))
{
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();
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();
}

booksState.Value = ExcelManipulation.ReadExcel();
book.Value = new Book();
client.Toast("Book added!");
}
}
catch (IOException ex)
{
client.Toast($"File access error. Please try again. Technical error: {ex.Message}");
// Log ex.Message or ex.ToString() as needed
}
catch (Exception ex)
{
client.Toast($"An unexpected error occurred. Technical error: {ex.Message}");
// Log ex.Message or ex.ToString() as needed
}
}

void HandleDeleteAsync(IState<List<Book>> booksState, string filePath, IClientProvider client)
{
try
{
EnsureExcelFileExists(filePath);

using var package = new ExcelPackage(new FileInfo(filePath));
var ws = package.Workbook.Worksheets.FirstOrDefault();
if (ws == null)
{
client.Toast("Worksheet not found.");
return;
}

var lastRow = ws.Dimension?.End.Row ?? 0;
if (lastRow <= 1)
{
client.Toast("No records to clear.");
return;
}

ws.DeleteRow(2, lastRow - 1);
ws.Cells[1, 1, 1, 4].AutoFitColumns();
package.Save();

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

}
25 changes: 25 additions & 0 deletions eeplus/EEPlusSoftware.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CS8618;CS8603;CS8602;CS8604;CS9113</NoWarn>
<RootNamespace>EEPlusSoftware</RootNamespace>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="Assets/**/*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EPPlus" Version="8.2.0" />
<PackageReference Include="Ivy" Version="1.0.113.0" />
</ItemGroup>


<ItemGroup>
<Folder Include="Connections" />
</ItemGroup>

</Project>
40 changes: 40 additions & 0 deletions eeplus/Entities/Book.cs
Original file line number Diff line number Diff line change
@@ -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()
{

}
};
18 changes: 18 additions & 0 deletions eeplus/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -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;

68 changes: 68 additions & 0 deletions eeplus/Helpers/EPPLusExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
namespace EEPlusSoftware.Helpers;

public static class EPPLusExtensions
{
public static IEnumerable<T> ConvertSheetToObjects<T>(this ExcelWorksheet worksheet) where T : new()
{

Func<CustomAttributeData, bool> 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<Column>().First().ColumnIndex //safe because of the where clause 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<int>());
return;
}
if (col.Property.PropertyType == typeof(double))
{
col.Property.SetValue(tnew, val.GetValue<double>());
return;
}
if (col.Property.PropertyType == typeof(DateTime))
{
col.Property.SetValue(tnew, val.GetValue<DateTime>());
return;
}
//Its a string
col.Property.SetValue(tnew, val.GetValue<string>());
});

return tnew;
});


//Send it back
return collection;
}
}

Loading
Loading