diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config
new file mode 100644
index 0000000..066adb6
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Configurations/App.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs
new file mode 100644
index 0000000..bfb2198
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataExporting/DataExport.cs
@@ -0,0 +1,108 @@
+using CsvHelper;
+using DocumentProcessor.JJHH17.Models;
+using Spectre.Console;
+using Azure.Storage.Blobs;
+using System.Configuration;
+using Azure.Storage.Blobs.Models;
+
+namespace Document.Processor.JJHH17.DataExporting;
+
+public class DataExport
+{
+ enum ExportMenuOptions
+ {
+ PDF,
+ CSV,
+ AzureBlobStorage
+ }
+
+ public static void ExportMenu()
+ {
+ Console.Clear();
+
+ AnsiConsole.MarkupLine("[bold yellow]Export Data to a given file type[/]");
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select a file type to export to:")
+ .AddChoices(Enum.GetValues()));
+ switch (choice)
+ {
+ case ExportMenuOptions.PDF:
+ CreateExportPDF();
+ break;
+
+ case ExportMenuOptions.CSV:
+ CreateExportCsv();
+ break;
+
+ case ExportMenuOptions.AzureBlobStorage:
+ CreateAzureBlobExport();
+ break;
+ }
+ }
+
+ public static void CreateExportPDF()
+ {
+ using (var context = new PhoneBookContext())
+ {
+ var entries = context.Phonebooks.ToList();
+ using (var writer = new StreamWriter("ExportedPhonebook.pdf"))
+ using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture))
+ {
+ csv.WriteRecords(entries);
+ }
+ }
+ AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.pdf successfully![/]");
+ AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.pdf\'[/]");
+ }
+
+ public static void CreateExportCsv()
+ {
+ using (var context = new PhoneBookContext())
+ {
+ var entries = context.Phonebooks.ToList();
+ using (var writer = new StreamWriter("ExportedPhonebook.csv"))
+ using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture))
+ {
+ csv.WriteRecords(entries);
+ }
+ }
+
+ AnsiConsole.MarkupLine("[green]Data exported to ExportedPhonebook.csv successfully![/]");
+ AnsiConsole.MarkupLine("[green]You can find the CSV in 'CodeReviews.Console.DocumentProcessor\\DocumentProcessor.JJHH17\\DocumentProcessor.JJHH17\\bin\\Debug\\net8.0\\ExportedPhonebook.csv\'[/]");
+ }
+
+ public static async Task CreateAzureBlobExport()
+ {
+ var localCsv = "ExportedPhonebook.csv";
+ if (!File.Exists(localCsv))
+ {
+ AnsiConsole.MarkupLine("[yellow]CSV file not found. Creating CSV file first...[/]");
+ CreateExportCsv();
+ }
+
+ var connectionString = ConfigurationManager.AppSettings["AzureBlobConnectionString"];
+ var containerName = ConfigurationManager.AppSettings["ContainerName"];
+
+ if (string.IsNullOrEmpty(connectionString) || string.IsNullOrEmpty(containerName))
+ {
+ AnsiConsole.MarkupLine("[red]Azure Blob Storage connection string or container name is not configured properly. Please check in the app.config file.[/]");
+ return;
+ }
+
+ var blobServiceClient = new BlobServiceClient(connectionString);
+ var containerClient = blobServiceClient.GetBlobContainerClient(containerName);
+ await containerClient.CreateIfNotExistsAsync();
+
+ var blobClient = containerClient.GetBlobClient("ExportedPhonebook.csv");
+ using var fileStream = File.OpenRead(localCsv);
+
+ var options = new BlobUploadOptions
+ {
+ HttpHeaders = new BlobHttpHeaders { ContentType = "text/csv" }
+ };
+
+ await blobClient.UploadAsync(fileStream, options);
+ AnsiConsole.MarkupLine("[green]CSV file uploaded to Azure Blob Storage successfully![/]");
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs
new file mode 100644
index 0000000..eee1771
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DataSeeding/DataSeeding.cs
@@ -0,0 +1,190 @@
+using DocumentProcessor.JJHH17.Models;
+using ExcelDataReader;
+using Spectre.Console;
+using System.Data;
+
+namespace Document.Processor.JJHH17.DataSeeding;
+
+public class DataSeed
+{
+ enum FileTypes
+ {
+ CSV,
+ XLS,
+ XLSX
+ }
+
+ public static void SeedOption()
+ {
+ Console.Clear();
+ AnsiConsole.MarkupLine("[bold yellow]Seed Database via a given file type[/]");
+
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select a file type to import from:")
+ .AddChoices(Enum.GetValues()));
+
+ switch (choice)
+ {
+ case FileTypes.CSV:
+ SeedCSVData();
+ break;
+ case FileTypes.XLS:
+ SeedXLSData();
+ break;
+ case FileTypes.XLSX:
+ SeedXLSXData();
+ break;
+ }
+ }
+
+ public static List ReadFile(string filePath)
+ {
+ List rows = new List();
+
+ try
+ {
+ string[] lines = File.ReadAllLines(filePath);
+
+ foreach (var line in lines)
+ {
+ var values = line.Split(',');
+ rows.Add(values);
+ }
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[red]Error reading file: {ex.Message}[/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+
+ return rows;
+ }
+
+ public static List ReadExcel(string filePath)
+ {
+ System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
+
+ using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read);
+ using var reader = ExcelReaderFactory.CreateReader(stream);
+
+ var dataset = reader.AsDataSet(new ExcelDataSetConfiguration
+ {
+ ConfigureDataTable = _ => new ExcelDataTableConfiguration
+ {
+ UseHeaderRow = true
+ }
+ });
+
+ var table = dataset.Tables[0];
+ var rows = new List();
+
+ foreach (DataRow dr in table.Rows)
+ {
+ var name = dr["Name"].ToString()?.Trim();
+ var email = dr["Email"].ToString()?.Trim();
+ var phoneNumber = dr["PhoneNumber"].ToString()?.Trim();
+
+ rows.Add(new string[] { name, email, phoneNumber });
+ }
+
+ return rows;
+ }
+
+ public static void SeedCSVData()
+ {
+ try
+ {
+ string csvFilePath = "Import Data - Sheet1.csv";
+ List csvData = ReadFile(csvFilePath);
+
+ foreach (string[] row in csvData)
+ {
+ using (var context = new PhoneBookContext())
+ {
+ var newEntry = new Phonebook
+ {
+ Name = row[0],
+ Email = row[1],
+ PhoneNumber = row[2]
+ };
+ context.Phonebooks.Add(newEntry);
+ context.SaveChanges();
+ }
+ }
+
+ AnsiConsole.MarkupLine("[green]Database seeded successfully from CSV![/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[red]Error reading CSV file: {ex.Message}[/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+ }
+
+ public static void SeedXLSData()
+ {
+ try
+ {
+ string xlsFilePath = "Import Data - Sheet1.xls";
+ var xlsData = ReadExcel(xlsFilePath);
+ using var context = new PhoneBookContext();
+ foreach (var row in xlsData)
+ {
+ var newEntry = new Phonebook
+ {
+ Name = row[0],
+ Email = row[1],
+ PhoneNumber = row[2]
+ };
+ context.Phonebooks.Add(newEntry);
+ }
+ context.SaveChanges();
+ AnsiConsole.MarkupLine("[green]Database seeded successfully from XLS![/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[red]Error reading XLS file: {ex.Message}[/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+ }
+
+ public static void SeedXLSXData()
+ {
+ try
+ {
+ string xlsxFilePath = "Import Data - Sheet1.xlsx";
+ var xlsxData = ReadExcel(xlsxFilePath);
+
+ using var context = new PhoneBookContext();
+ foreach (var row in xlsxData)
+ {
+ var newEntry = new Phonebook
+ {
+ Name = row[0],
+ Email = row[1],
+ PhoneNumber = row[2]
+ };
+ context.Phonebooks.Add(newEntry);
+ }
+
+ context.SaveChanges();
+ AnsiConsole.MarkupLine("[green]Database seeded successfully from XLSX![/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[red]Error reading XLSX file: {ex.Message}[/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj
new file mode 100644
index 0000000..50bb9be
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17.csproj
@@ -0,0 +1,30 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs
new file mode 100644
index 0000000..560092c
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Models/Phonebook.cs
@@ -0,0 +1,26 @@
+using System.Configuration;
+using Microsoft.EntityFrameworkCore;
+
+namespace DocumentProcessor.JJHH17.Models;
+
+public class Phonebook
+{
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string Email { get; set; }
+ public string PhoneNumber { get; set; }
+}
+
+public class PhoneBookContext : DbContext
+{
+ private static readonly string server = ConfigurationManager.AppSettings["Server"];
+ private static readonly string databaseInstance = ConfigurationManager.AppSettings["DatabaseName"];
+ public static string connectionString = $@"Server=({server})\{databaseInstance};Integrated Security=true;";
+
+ public DbSet Phonebooks { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseSqlServer(connectionString);
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs
new file mode 100644
index 0000000..5150eaf
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/Program.cs
@@ -0,0 +1,35 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace DocumentProcessor.JJHH17;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ var builder = Host.CreateApplicationBuilder(args);
+ builder.Services.AddHostedService();
+ builder.Services.AddLogging();
+
+ using var host = builder.Build();
+ var runHost = host.RunAsync();
+
+ var uiCts = new CancellationTokenSource();
+ var uiTask = Task.Run(() => UserInterface.UserInterface.Menu(), uiCts.Token);
+
+ await Task.WhenAny(runHost, uiTask);
+
+ if (uiTask.IsCompleted)
+ {
+ await host.StopAsync();
+ }
+ else
+ {
+ uiCts.Cancel();
+ try { await uiTask; }
+ catch (OperationCanceledException) { }
+
+ await runHost;
+ }
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md
new file mode 100644
index 0000000..95072d2
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/README.md
@@ -0,0 +1,97 @@
+# Document Processor Application - PhoneBook Tracker
+A phonebook tracker application, allowing the user to seed and export data via local files, as well as via Azure Blob storage.
+
+## Project Overview
+This is a project that:
+- Allows the user to create and store phonebook user details.
+- The user can seed data from an XLS, XLSX or CSV File (local file).
+- The user can export the databases items into a CSV, XLSX or CSV file, as well as exporting to an Azure Blob Storage instance.
+- The data seeding element of the application will only be presented to the user if the database contains 0 elements upon startup.
+- Finally, we also run an export of data based on a scheduled task, which by default is executed daily at 09:00 am (BST / GMT UK time).
+- Import and Export local files are stored in the following directory:
+
+```JJHH17\bin\Debug\net8.0\```
+
+## Technologies Used
+- SQL Server (Local DB Instance) for database storage
+- Entity Framework Core
+- Spectre Console (for UI / Console navigation)
+- Azure Blob Storage
+- Excel Data Reader (package)
+- CSV Helper (CSV Writing Package)
+- Configuration Manager
+- CRONOS package - For running the file export on an automated basis.
+
+## Usage Steps
+### Creating an SQL Server Local DB Instance
+- This project uses a Local DB instance of SQL Server, meaning a Database file will need to be created in order for the program to run
+- You can create a Local DB instance by running the following terminal command, with SQL Server installed:
+
+```sqllocaldb create documentProcessor```
+
+### Connection strings to Local DB
+- Connection strings are managed via the "app.config" file.
+- Keep the "Server" string as "localdb".
+- Change the "Database Name" string to whatever value you name your local db instance as.
+- These values will then be appended to the full database connection string.
+
+### Creating and using the application
+- Clone the application and open it in your IDE of choice.
+- Run the migrations command for Entity Framework - this is done via the following commands:
+
+```dotnet ef migrations add InitialCreate```
+
+then
+
+```dotnet ef database update```
+
+This will create the relevant EF tables.
+
+### File Importing
+- The app allows users to import data via CSV, XLSX or XLS files.
+If the apps database instance is empty when the app is started, the user is prompted to import data via the selected file type:
+
+
+
+All Files must be stored within the following directory, with the following naming conventions:
+
+```JJHH17\bin\Debug\net8.0\```
+
+- For CSV: Import Data - Sheet1.csv
+- For XLSX: Import Data - Sheet1.xlsx
+- For XLS: Import Data - Sheet1.xls
+
+### Import File Formats:
+In order for data to be imported correctly, they must be in the correct format:
+
+CSV:
+- Name,Email Address,Telephone Number
+
+XLSX and XLS:
+These must contain the following cell headers:
+Name EmailAddress TelephoneNumber
+
+### File Exporting
+
+
+
+- All files exported can be located in the following location, inside of the projects directory:
+```JJHH17\bin\Debug\net8.0\```
+
+- Users can export as a CSV, PDF, or route the export to an Azure Blob Storage instance (more details on this found below).
+
+### Automated File Export
+- We use the CRONOS package for automating the export of the file export to a CSV file.
+- This runs daily at 9am (BST / GMT (UK local time)).
+- The class for this work can be found inside of the "Scheduled Export" folder.
+
+### Azure Blob Storage Connection
+For data exporting, users can export the file to an Azure Blob instance.
+
+All connection strings can be located and adjusted in the App.Config file of the project.
+
+The main requirements here are:
+- The connection string - This can be gathered when a Blob container has been created.
+- The container name - This is the blob container name where you will store the file.
+
+Once these values have been added, any data found in the Database will be exported to your blob instance.
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/ScheduledExport/ScheduledExport.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/ScheduledExport/ScheduledExport.cs
new file mode 100644
index 0000000..1959916
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/ScheduledExport/ScheduledExport.cs
@@ -0,0 +1,61 @@
+using Cronos;
+using Document.Processor.JJHH17.DataExporting;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace DocumentProcessor.JJHH17.ScheduledExport;
+
+public class ScheduledExport
+{
+
+}
+
+public class ScheduledExportJob : BackgroundService
+{
+ private static string TimeZoneID = "Europe/London";
+
+ // Scheduled to run every day, 09:00 AM (BST / GMT)
+ private static readonly CronExpression cron = CronExpression.Parse("0 9 * * *");
+ private static readonly TimeZoneInfo Tz = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneID);
+ private readonly ILogger _logger;
+
+ public ScheduledExportJob(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stopToken)
+ {
+ _logger.LogInformation("PDF Export is generating");
+
+ while (!stopToken.IsCancellationRequested)
+ {
+ var next = cron.GetNextOccurrence(DateTimeOffset.Now, Tz);
+ if (next is null) break;
+
+ var delay = next.Value - DateTimeOffset.Now;
+ _logger.LogInformation($"Next PDF Export scheduled at {next})", next.Value);
+
+ try
+ {
+ await Task.Delay(delay, stopToken);
+ }
+ catch (TaskCanceledException)
+ {
+ break;
+ }
+
+ try
+ {
+ DataExport.CreateExportPDF();
+ _logger.LogInformation("PDF Export generated successfully");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error occurred while generating PDF Export");
+ }
+ }
+
+ _logger.LogInformation("Scheduled PDF Export is stopping");
+ }
+}
\ No newline at end of file
diff --git a/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs
new file mode 100644
index 0000000..b535e90
--- /dev/null
+++ b/DocumentProcessor.JJHH17/DocumentProcessor.JJHH17/UserInterface/UserInterface.cs
@@ -0,0 +1,204 @@
+using DocumentProcessor.JJHH17.Models;
+using Spectre.Console;
+using Document.Processor.JJHH17.DataSeeding;
+using Document.Processor.JJHH17.DataExporting;
+
+namespace DocumentProcessor.JJHH17.UserInterface;
+
+public class UserInterface
+{
+ enum MenuOptions
+ {
+ AddEntry,
+ Read,
+ Delete,
+ Export,
+ Exit
+ }
+
+ public static void Menu()
+ {
+ // Seeds data if database is empty
+ using (var context = new PhoneBookContext())
+ {
+ if (!context.Phonebooks.Any())
+ {
+ DataSeed.SeedOption();
+ }
+ }
+
+
+ bool running = true;
+ while (running)
+ {
+ AnsiConsole.MarkupLine("[bold yellow]Document Processor Menu[/]");
+ Console.Clear();
+
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select an option:")
+ .AddChoices(Enum.GetValues()));
+
+ switch (choice)
+ {
+ case MenuOptions.AddEntry:
+ Console.Clear();
+ AddEntry();
+ break;
+
+ case MenuOptions.Read:
+ Console.Clear();
+ ReadEntries();
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ break;
+
+ case MenuOptions.Delete:
+ Console.Clear();
+ DeleteEntry();
+ break;
+
+ case MenuOptions.Export:
+ Console.Clear();
+ DataExport.ExportMenu();
+ Console.WriteLine("Enter any key to return to the menu...");
+ Console.ReadKey();
+ break;
+
+ case MenuOptions.Exit:
+ Console.WriteLine("Press any key to exit...");
+ Console.ReadKey();
+ running = false;
+ break;
+ }
+ }
+ }
+
+ public static void AddEntry()
+ {
+ AnsiConsole.MarkupLine("[bold yellow]Add Entry[/]");
+ Console.WriteLine("Enter name:");
+ string name = Console.ReadLine();
+ string email = EmailInput();
+ string phoneNumber = PhoneNumberInput();
+
+ using (var context = new PhoneBookContext())
+ {
+ var newEntry = new Phonebook
+ {
+ Name = name,
+ Email = email,
+ PhoneNumber = phoneNumber
+ };
+
+ context.Phonebooks.Add(newEntry);
+ context.SaveChanges();
+ }
+
+ AnsiConsole.MarkupLine("[green]Entry added successfully![/]");
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+
+ public static void ReadEntries()
+ {
+ using (var context = new PhoneBookContext())
+ {
+ var query = context.Phonebooks.ToList();
+
+ if (query.Count == 0)
+ {
+ AnsiConsole.MarkupLine("[yellow]No entries were found[/]");
+ }
+ else
+ {
+ var table = new Table();
+ table.AddColumn("ID");
+ table.AddColumn("Name");
+ table.AddColumn("Email");
+ table.AddColumn("Phone Number");
+
+ foreach (var entry in query)
+ {
+ table.AddRow(entry.Id.ToString(), entry.Name, entry.Email, entry.PhoneNumber);
+ }
+ AnsiConsole.Write(table);
+ }
+ }
+ }
+
+ public static void DeleteEntry()
+ {
+ using (var context = new PhoneBookContext())
+ {
+ AnsiConsole.MarkupLine("[bold yellow]Delete Entry[/]");
+ ReadEntries();
+ Console.WriteLine("Enter the ID of the entry to delete:");
+
+ if (int.TryParse(Console.ReadLine(), out int id))
+ {
+ var entry = context.Phonebooks.Find(id);
+ if (entry != null)
+ {
+ context.Phonebooks.Remove(entry);
+ context.SaveChanges();
+ AnsiConsole.MarkupLine("[green]Entry deleted successfully![/]");
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("[red]Entry not found.[/]");
+ }
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("[red]Invalid ID format.[/]");
+ }
+
+ Console.WriteLine("Press any key to return to the menu...");
+ Console.ReadKey();
+ }
+ }
+
+ public static string EmailInput()
+ {
+ string email;
+ while (true)
+ {
+ Console.WriteLine("Enter email address:");
+ email = Console.ReadLine();
+
+ if (email.Contains("@") && email.Contains("."))
+ {
+ break;
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("[red]Invalid email format. Please try again.[/]");
+ }
+ }
+
+ return email;
+ }
+
+ public static string PhoneNumberInput()
+ {
+ string phoneNumber;
+ while (true)
+ {
+ Console.WriteLine("Enter phone number (must be 11 digits):");
+ phoneNumber = Console.ReadLine();
+
+ if (phoneNumber.Length == 11 && long.TryParse(phoneNumber, out _))
+ {
+ break;
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("[red]Invalid phone number. Please enter exactly 11 digits.[/]");
+ }
+ }
+
+ return phoneNumber;
+ }
+
+}
\ No newline at end of file