diff --git a/DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.Designer.cs b/DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.Designer.cs
new file mode 100644
index 0000000..dfb6b25
--- /dev/null
+++ b/DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.Designer.cs
@@ -0,0 +1,51 @@
+//
+using DocumentProcessor.davetn657.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace DocumentProcessor.davetn657.Data.Migrations
+{
+ [DbContext(typeof(PhonebookContext))]
+ [Migration("20260325194708_InitialCreate")]
+ partial class InitialCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
+
+ modelBuilder.Entity("DocumentProcessor.davetn657.Data.Models.PhonebookProperties", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Contacts");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.cs b/DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.cs
new file mode 100644
index 0000000..1c1bb5f
--- /dev/null
+++ b/DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.cs
@@ -0,0 +1,37 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DocumentProcessor.davetn657.Data.Migrations
+{
+ ///
+ public partial class InitialCreate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Contacts",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Name = table.Column(type: "TEXT", nullable: false),
+ PhoneNumber = table.Column(type: "TEXT", nullable: false),
+ Email = table.Column(type: "TEXT", nullable: false),
+ Category = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Contacts", x => x.Id);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Contacts");
+ }
+ }
+}
diff --git a/DocumentProcessor.davetn657/Data/Migrations/PhonebookContextModelSnapshot.cs b/DocumentProcessor.davetn657/Data/Migrations/PhonebookContextModelSnapshot.cs
new file mode 100644
index 0000000..1ebae2b
--- /dev/null
+++ b/DocumentProcessor.davetn657/Data/Migrations/PhonebookContextModelSnapshot.cs
@@ -0,0 +1,48 @@
+//
+using DocumentProcessor.davetn657.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace DocumentProcessor.davetn657.Data.Migrations
+{
+ [DbContext(typeof(PhonebookContext))]
+ partial class PhonebookContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
+
+ modelBuilder.Entity("DocumentProcessor.davetn657.Data.Models.PhonebookProperties", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Contacts");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/DocumentProcessor.davetn657/Data/Models/PhonebookProperties.cs b/DocumentProcessor.davetn657/Data/Models/PhonebookProperties.cs
new file mode 100644
index 0000000..c61f8ce
--- /dev/null
+++ b/DocumentProcessor.davetn657/Data/Models/PhonebookProperties.cs
@@ -0,0 +1,10 @@
+namespace DocumentProcessor.davetn657.Data.Models;
+
+public class PhonebookProperties
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public string PhoneNumber { get; set; } = string.Empty;
+ public string Email { get; set; } = string.Empty;
+ public string Category { get; set; } = string.Empty;
+}
diff --git a/DocumentProcessor.davetn657/Data/PhonebookContext.cs b/DocumentProcessor.davetn657/Data/PhonebookContext.cs
new file mode 100644
index 0000000..373dda7
--- /dev/null
+++ b/DocumentProcessor.davetn657/Data/PhonebookContext.cs
@@ -0,0 +1,24 @@
+using DocumentProcessor.davetn657.Data.Models;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+
+namespace DocumentProcessor.davetn657.Data;
+
+public class PhonebookContext : DbContext
+{
+ public DbSet Contacts { get; set; }
+ private string DbPath { get; set; }
+
+ public PhonebookContext()
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json")
+ .Build();
+
+ DbPath = builder.GetConnectionString("DefaultConnection");
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder options)
+ => options.UseSqlite(DbPath);
+}
\ No newline at end of file
diff --git a/DocumentProcessor.davetn657/DocFiles/SeedData.xlsx b/DocumentProcessor.davetn657/DocFiles/SeedData.xlsx
new file mode 100644
index 0000000..9a73705
Binary files /dev/null and b/DocumentProcessor.davetn657/DocFiles/SeedData.xlsx differ
diff --git a/DocumentProcessor.davetn657/DocumentProcessor.davetn657.csproj b/DocumentProcessor.davetn657/DocumentProcessor.davetn657.csproj
new file mode 100644
index 0000000..cb41a78
--- /dev/null
+++ b/DocumentProcessor.davetn657/DocumentProcessor.davetn657.csproj
@@ -0,0 +1,30 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/DocumentProcessor.davetn657/DocumentProcessor.davetn657.slnx b/DocumentProcessor.davetn657/DocumentProcessor.davetn657.slnx
new file mode 100644
index 0000000..f54a560
--- /dev/null
+++ b/DocumentProcessor.davetn657/DocumentProcessor.davetn657.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/DocumentProcessor.davetn657/Phonebook.db b/DocumentProcessor.davetn657/Phonebook.db
new file mode 100644
index 0000000..17d62dc
Binary files /dev/null and b/DocumentProcessor.davetn657/Phonebook.db differ
diff --git a/DocumentProcessor.davetn657/Program.cs b/DocumentProcessor.davetn657/Program.cs
new file mode 100644
index 0000000..5fd4ef9
--- /dev/null
+++ b/DocumentProcessor.davetn657/Program.cs
@@ -0,0 +1,30 @@
+using DocumentProcessor.davetn657.Data;
+using DocumentProcessor.davetn657.Services;
+using DocumentProcessor.davetn657.Views;
+using IronSoftware.Abstractions.Pdf;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace DocumentProcessor.davetn657;
+
+internal class Program
+{
+ static void Main(string[] args)
+ {
+ var services = new ServiceCollection()
+ .AddDbContext()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .BuildServiceProvider();
+
+ using var scope = services.CreateScope();
+
+ var dataSeeder = scope.ServiceProvider.GetRequiredService();
+ dataSeeder.SeedIfEmpty();
+
+ var ui = scope.ServiceProvider.GetRequiredService();
+ ui.Start();
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.davetn657/Properties/launchSettings.json b/DocumentProcessor.davetn657/Properties/launchSettings.json
new file mode 100644
index 0000000..a93ff4e
--- /dev/null
+++ b/DocumentProcessor.davetn657/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "DocumentProcessor.davetn657": {
+ "commandName": "Project",
+ "workingDirectory": "C:\\Users\\davei\\Documents\\.Coding\\.C#Academy\\.Backup\\DocumentProcessor.davetn657\\DocumentProcessor.davetn657"
+ }
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.davetn657/README.md b/DocumentProcessor.davetn657/README.md
new file mode 100644
index 0000000..4dd0346
--- /dev/null
+++ b/DocumentProcessor.davetn657/README.md
@@ -0,0 +1,21 @@
+# Document Processor Application
+
+## Overview
+
+Console based application that processes data from xlsx or csv file to import into a database or export database data to xlsx, csv, or pdf form.
+
+## Requirements
+
+- Needs to be able to seed data from xlsx or csv files into the database
+- If no data is in database needs to auto seed data
+- Export data to a pdf
+- Handles errors
+- Option to import/export data
+
+
+## Technologies
+
+- C#
+- Sqlite
+- IronXL
+- IronPdf
diff --git a/DocumentProcessor.davetn657/Services/DataSeederService.cs b/DocumentProcessor.davetn657/Services/DataSeederService.cs
new file mode 100644
index 0000000..1d8f74c
--- /dev/null
+++ b/DocumentProcessor.davetn657/Services/DataSeederService.cs
@@ -0,0 +1,31 @@
+using DocumentProcessor.davetn657.Data;
+
+namespace DocumentProcessor.davetn657.Services;
+
+public interface IDataSeederService
+{
+ public void SeedIfEmpty();
+}
+
+public class DataSeederService : IDataSeederService
+{
+ private readonly PhonebookContext _dbContext;
+ private readonly IFileReaderService _fileReader;
+ public DataSeederService(PhonebookContext dbContext, IFileReaderService fileReader)
+ {
+ _dbContext = dbContext;
+ _fileReader = fileReader;
+ }
+
+ public void SeedIfEmpty()
+ {
+
+ if (!_dbContext.Contacts.Any())
+ {
+ var path = Path.Combine(Directory.GetCurrentDirectory(), "DocFiles");
+ var contacts = _fileReader.FormatFile(path, "SeedData.xlsx");
+ _dbContext.Contacts.AddRange(contacts);
+ _dbContext.SaveChanges();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.davetn657/Services/ExportDataService.cs b/DocumentProcessor.davetn657/Services/ExportDataService.cs
new file mode 100644
index 0000000..7d97605
--- /dev/null
+++ b/DocumentProcessor.davetn657/Services/ExportDataService.cs
@@ -0,0 +1,129 @@
+using DocumentProcessor.davetn657.Data;
+using IronXL;
+using IronPdf;
+using Spectre.Console;
+using System.Text;
+using IronSoftware.Abstractions.Pdf;
+
+namespace DocumentProcessor.davetn657.Services;
+
+public interface IExportDataService
+{
+ void ExportToPdf();
+ void ExportToXlsx();
+ void ExportToCsv();
+}
+
+public class ExportDataService : IExportDataService
+{
+ private readonly PhonebookContext _dbContext;
+ private readonly IExtensibleRenderer _renderer;
+ public ExportDataService(PhonebookContext dbContext, IExtensibleRenderer renderer)
+ {
+ _dbContext = dbContext;
+ _renderer = renderer;
+ }
+
+ public void ExportToPdf()
+ {
+ try
+ {
+ var _renderer = new ChromePdfRenderer();
+
+ var htmlContent = @$"
+
+
+
+
+
+
+
+
+ | Name |
+ Phone Number |
+ Email |
+ Category |
+
+ {HtmlTables()}
+
+
+ ";
+
+ var pdf = _renderer.RenderHtmlAsPdf(htmlContent);
+
+ pdf.SaveAs("DocFiles\\Contacts.pdf");
+ AnsiConsole.WriteLine("Successfully exported to pdf");
+ }
+ catch
+ {
+ AnsiConsole.WriteLine("Failed to fully export to pdf - data may be missing or incomplete!");
+ }
+ }
+
+ public void ExportToXlsx()
+ {
+ ExportWorkBook(wb => wb.SaveAs("DocFiles\\Contacts.xlsx"));
+ }
+
+ public void ExportToCsv()
+ {
+ ExportWorkBook(wb => wb.SaveAsCsv("DocFiles\\Contacts.csv"));
+ }
+
+ private string HtmlTables()
+ {
+ var contacts = _dbContext.Contacts;
+ var htmlTableContent = new StringBuilder();
+
+ foreach(var contact in contacts)
+ {
+ htmlTableContent.Append(@$"
+
+ | {contact.Name} |
+ {contact.PhoneNumber} |
+ {contact.Email} |
+ {contact.Category} |
+
+ ");
+ }
+
+ return htmlTableContent.ToString();
+ }
+
+ private void ExportWorkBook(Action save)
+ {
+ try
+ {
+ var contacts = _dbContext.Contacts.ToList();
+
+ var workbook = WorkBook.Create(ExcelFileFormat.XLSX);
+ var worksheet = workbook.CreateWorkSheet("Contacts");
+
+ worksheet["A1"].Value = "Names";
+ worksheet["B1"].Value = "Phone Numbers";
+ worksheet["C1"].Value = "Emails";
+ worksheet["D1"].Value = "Categories";
+
+ for (int i = 0; i < contacts.Count; i++)
+ {
+ var row = i + 2;
+ worksheet["A" + row].Value = contacts[i].Name;
+ worksheet["B" + row].Value = contacts[i].PhoneNumber;
+ worksheet["C" + row].Value = contacts[i].Email;
+ worksheet["D" + row].Value = contacts[i].Category;
+ }
+
+ save(workbook);
+ AnsiConsole.WriteLine("Successfully exported workbook");
+ }
+ catch
+ {
+ AnsiConsole.WriteLine("Failed to fully export workbook - some data may be missing or incomplete!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.davetn657/Services/FileReaderService.cs b/DocumentProcessor.davetn657/Services/FileReaderService.cs
new file mode 100644
index 0000000..6b840b6
--- /dev/null
+++ b/DocumentProcessor.davetn657/Services/FileReaderService.cs
@@ -0,0 +1,110 @@
+using DocumentProcessor.davetn657.Data.Models;
+using IronXL;
+using Spectre.Console;
+
+namespace DocumentProcessor.davetn657.Services;
+
+public interface IFileReaderService
+{
+ public List FormatFile(string filePath, string fileName);
+}
+
+public class FileReaderService : IFileReaderService
+{
+ public FileReaderService()
+ {
+
+ }
+
+ public List FormatFile(string filePath, string fileName)
+ {
+ var fullPath = Path.Combine(filePath, fileName);
+ var file = fileName.Split('.');
+ var fileType = file[1];
+
+ var properties = new List();
+ WorkSheet? workSheet;
+
+ switch (fileType)
+ {
+ case "xlsx":
+ workSheet = ReadXlsxFile(fullPath);
+ break;
+ case "csv":
+ workSheet = ReadCsvFile(fullPath);
+ break;
+ default:
+ workSheet = null;
+ break;
+ }
+
+ if (workSheet == null)
+ {
+ AnsiConsole.WriteLine("File is empty or could not open!");
+ AnsiConsole.Prompt(new TextPrompt("Return?").AllowEmpty());
+ return [];
+ }
+ properties = FormatWorkSheetData(workSheet);
+
+ return properties;
+ }
+
+ private WorkSheet? ReadXlsxFile(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ Console.WriteLine("File doesn't exist");
+ return null;
+ }
+
+ var workBook = WorkBook.Load(filePath);
+ var workSheet = workBook.WorkSheets.First();
+
+ return workSheet;
+ }
+
+ private WorkSheet? ReadCsvFile(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ Console.WriteLine("File doesn't exist");
+ return null;
+ }
+
+ var workBook = WorkBook.LoadCSV(filePath, ExcelFileFormat.XLSX, listDelimiter: ",");
+ var workSheet = workBook.DefaultWorkSheet;
+
+ return workSheet;
+ }
+
+ private List FormatWorkSheetData(WorkSheet sheetData)
+ {
+ var properties = new List();
+ var rowCount = sheetData.RowCount;
+
+ for (var i = 2; i < rowCount; i++)
+ {
+ try
+ {
+ var data = new PhonebookProperties
+ {
+ Name = sheetData["A" + i].TryGetValue(out var name) ? name : string.Empty,
+ PhoneNumber = sheetData["B" + i].TryGetValue(out var phone) ? phone : string.Empty,
+ Email = sheetData["C" + i].TryGetValue(out var email) ? email : string.Empty,
+ Category = sheetData["D" + i].TryGetValue(out var category) ? category : string.Empty,
+ };
+
+ properties.Add(data);
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.WriteLine("Could not create property! spreadsheet format is not correct!");
+ AnsiConsole.WriteLine(ex.Message);
+ AnsiConsole.Prompt(new TextPrompt("Return").AllowEmpty());
+ break;
+ }
+ }
+
+ return properties;
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.davetn657/Views/UserInterface.cs b/DocumentProcessor.davetn657/Views/UserInterface.cs
new file mode 100644
index 0000000..21ce01c
--- /dev/null
+++ b/DocumentProcessor.davetn657/Views/UserInterface.cs
@@ -0,0 +1,151 @@
+using DocumentProcessor.davetn657.Data;
+using DocumentProcessor.davetn657.Services;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Spectre.Console;
+
+namespace DocumentProcessor.davetn657.Views;
+
+public class UserInterface
+{
+ private readonly IFileReaderService _fileReader;
+ private readonly IExportDataService _exporter;
+ private readonly PhonebookContext _dbContext;
+
+ public UserInterface(IFileReaderService fileReader, IExportDataService exporter, PhonebookContext dbContext)
+ {
+ _fileReader = fileReader;
+ _exporter = exporter;
+ _dbContext = dbContext;
+ }
+
+ public void Start()
+ {
+ while (true)
+ {
+ TitleCard("Main Menu");
+ var filePath = Path.Combine(Directory.GetCurrentDirectory(), "DocFiles");
+ try
+ {
+ var fileNames = Directory.GetFiles(filePath)
+ .Where(s => s.EndsWith(".xlsx") || s.EndsWith(".csv"));
+
+ var menuOptions = new List()
+ {
+ "Exit",
+ "Export",
+ "Delete all database data"
+ };
+
+ foreach (var file in fileNames)
+ {
+ menuOptions.Add(Path.GetFileName(file));
+ }
+
+ AnsiConsole.WriteLine("All files in DocFiles directory");
+ var selected = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions));
+
+ switch (selected)
+ {
+ case "Exit":
+ return;
+ case "Export":
+ ExportData();
+ break;
+ case "Delete all database data":
+ _dbContext.Contacts.ExecuteDelete();
+ _dbContext.SaveChanges();
+ break;
+ default:
+ FileDetails(selected, filePath);
+ break;
+ }
+ }
+ catch (DirectoryNotFoundException ex)
+ {
+
+ AnsiConsole.WriteLine($"Couldn't find file path");
+ AnsiConsole.WriteLine("Error: " + ex);
+ AnsiConsole.WriteLine("Try entering a new path?");
+
+ var selected = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(new string[] { "Yes", "No" }));
+
+ if (selected.Equals("No")) return;
+
+ filePath = AnsiConsole.Ask("Enter here:");
+ }
+ }
+ }
+
+ public void FileDetails(string fileName, string filePath)
+ {
+ TitleCard(fileName + " Details");
+
+ var table = new Table();
+ var contacts = _fileReader.FormatFile(filePath, fileName);
+
+ table.AddColumns(new string[]{
+ "Name",
+ "Phone Number",
+ "Email"
+ });
+
+ foreach(var contact in contacts.Take(10))
+ {
+ table.AddRow(contact.Name, contact.PhoneNumber, contact.Email);
+ }
+
+ AnsiConsole.WriteLine("Top Excel Rows:");
+ AnsiConsole.Write(table);
+
+ var menuOptions = new List
+ {
+ "Import Data",
+ "Return"
+ };
+
+ var selected = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions));
+
+ if (selected.Equals("Return")) return;
+
+ _dbContext.Contacts.AddRange(contacts);
+ _dbContext.SaveChanges();
+
+ AnsiConsole.WriteLine("Successfully imported data!");
+ AnsiConsole.Prompt(new TextPrompt("Return?"));
+ }
+
+ public void ExportData()
+ {
+ TitleCard("Export Current Data");
+
+ var menuOptions = new Dictionary()
+ {
+ { "Return", null },
+ {".pdf", _exporter.ExportToPdf },
+ { ".xlsx", _exporter.ExportToXlsx },
+ { ".csv", _exporter.ExportToCsv }
+ };
+
+ var selected = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions.Keys));
+
+ if (menuOptions.TryGetValue(selected, out var action) && action != null)
+ {
+ action();
+ }
+
+ AnsiConsole.Prompt(new TextPrompt("Return").AllowEmpty());
+ }
+
+ public void TitleCard(string title)
+ {
+ var titleCard = new FigletText(title)
+ {
+ Justification = Justify.Center,
+ Color = Color.Blue1
+ };
+
+ AnsiConsole.Clear();
+ AnsiConsole.Write(titleCard);
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.davetn657/appsettings.json b/DocumentProcessor.davetn657/appsettings.json
new file mode 100644
index 0000000..1b40b17
--- /dev/null
+++ b/DocumentProcessor.davetn657/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "DefaultConnection": "Data Source=Phonebook.db"
+ }
+}
\ No newline at end of file