From 0e636a99ae816da0be0e121c8511842c8ccb92ba Mon Sep 17 00:00:00 2001 From: Dave Ngo Date: Fri, 27 Mar 2026 14:34:53 -0400 Subject: [PATCH 1/2] Finished Project --- .../20260325194708_InitialCreate.Designer.cs | 51 ++++++ .../20260325194708_InitialCreate.cs | 37 +++++ .../PhonebookContextModelSnapshot.cs | 48 ++++++ .../Data/Models/PhonebookProperties.cs | 10 ++ .../Data/PhonebookContext.cs | 24 +++ .../DocFiles/SeedData.xlsx | Bin 0 -> 7943 bytes .../DocumentProcessor.davetn657.csproj | 30 ++++ .../DocumentProcessor.davetn657.slnx | 3 + DocumentProcessor.davetn657/Phonebook.db | Bin 0 -> 32768 bytes DocumentProcessor.davetn657/Program.cs | 30 ++++ .../Properties/launchSettings.json | 8 + DocumentProcessor.davetn657/README.md | 6 + .../Services/DataSeederService.cs | 31 ++++ .../Services/ExportDataService.cs | 129 +++++++++++++++ .../Services/FileReaderService.cs | 110 +++++++++++++ .../Views/UserInterface.cs | 151 ++++++++++++++++++ DocumentProcessor.davetn657/appsettings.json | 5 + 17 files changed, 673 insertions(+) create mode 100644 DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.Designer.cs create mode 100644 DocumentProcessor.davetn657/Data/Migrations/20260325194708_InitialCreate.cs create mode 100644 DocumentProcessor.davetn657/Data/Migrations/PhonebookContextModelSnapshot.cs create mode 100644 DocumentProcessor.davetn657/Data/Models/PhonebookProperties.cs create mode 100644 DocumentProcessor.davetn657/Data/PhonebookContext.cs create mode 100644 DocumentProcessor.davetn657/DocFiles/SeedData.xlsx create mode 100644 DocumentProcessor.davetn657/DocumentProcessor.davetn657.csproj create mode 100644 DocumentProcessor.davetn657/DocumentProcessor.davetn657.slnx create mode 100644 DocumentProcessor.davetn657/Phonebook.db create mode 100644 DocumentProcessor.davetn657/Program.cs create mode 100644 DocumentProcessor.davetn657/Properties/launchSettings.json create mode 100644 DocumentProcessor.davetn657/README.md create mode 100644 DocumentProcessor.davetn657/Services/DataSeederService.cs create mode 100644 DocumentProcessor.davetn657/Services/ExportDataService.cs create mode 100644 DocumentProcessor.davetn657/Services/FileReaderService.cs create mode 100644 DocumentProcessor.davetn657/Views/UserInterface.cs create mode 100644 DocumentProcessor.davetn657/appsettings.json 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 0000000000000000000000000000000000000000..9a7370512031294c589eb08afa4a2881b7b2674e GIT binary patch literal 7943 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 + + + 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 0000000000000000000000000000000000000000..17d62dc57af866a1296a5d492ad85f8e4213fcc7 GIT binary patch literal 32768 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 literal 0 HcmV?d00001 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..2cd28b1 --- /dev/null +++ b/DocumentProcessor.davetn657/README.md @@ -0,0 +1,6 @@ +## Technologies + +- C# +- Sqlite +- IronXL +- IronPdf \ No newline at end of file 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 = @$" + + + + + + + + + + + + + + {HtmlTables()} +
NamePhone NumberEmailCategory
+ + "; + + 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 From acbc950141587be3c90aa99b9e17edd35f5900e0 Mon Sep 17 00:00:00 2001 From: Dave Ngo <57770867+davetn657@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:04:40 -0400 Subject: [PATCH 2/2] Update README with application overview and requirements Added an overview and requirements section to the README. --- DocumentProcessor.davetn657/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/DocumentProcessor.davetn657/README.md b/DocumentProcessor.davetn657/README.md index 2cd28b1..4dd0346 100644 --- a/DocumentProcessor.davetn657/README.md +++ b/DocumentProcessor.davetn657/README.md @@ -1,6 +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 \ No newline at end of file +- IronPdf