diff --git a/Api.SeaHavenIndustries/Controllers/CalendarController.cs b/Api.SeaHavenIndustries/Controllers/CalendarController.cs new file mode 100644 index 0000000..f3074ad --- /dev/null +++ b/Api.SeaHavenIndustries/Controllers/CalendarController.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Api.SeaHavenIndustries.DTOs; +using Data.SeaHavenIndustries; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Api.SeaHavenIndustries.Controllers +{ + //[Authorize] // Uncomment when authentication is needed + [ApiController] + [Route("api/Calendar")] + public class CalendarController : Controller + { + private readonly UserManager _userManager; + private readonly ApplicationDbContext _db; + + public CalendarController(UserManager userManager, ApplicationDbContext db) + { + _userManager = userManager; + _db = db; + } + + // =================================================================================== + // CREATE EVENT + // =================================================================================== + [HttpPost] + [Route("AddEvent")] + public async Task AddEvent(EditEvent_DTO eventDto) + { + try + { + var model = new Events + { + Title = eventDto.Title, + Description = eventDto.Description, + Location = eventDto.Location, + RecordID = eventDto.RecordID, + RecordName = eventDto.RecordName, + RecordTargetType = eventDto.RecordTargetType, + ReminderMinutes = eventDto.ReminderMinutes, + EventColor = eventDto.EventColor, + StartDate = eventDto.StartDate, + StartTime = eventDto.StartTime, + EndDate = eventDto.EndDate, + EndTime = eventDto.EndTime, + AllDay = eventDto.AllDay, + CreatedDate = DateTime.Now, + IsDeleted = false + }; + + _db.Events.Add(model); + await _db.SaveChangesAsync(); + + return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // UPDATE EVENT + // =================================================================================== + [HttpPost] + [Route("EditEvent")] + public async Task EditEvent(EditEvent_DTO eventDto) + { + try + { + if (!eventDto.Id.HasValue) + { + return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" }); + } + + var model = await _db.Events.FindAsync(eventDto.Id.Value); + if (model == null || model.IsDeleted == true) + { + return NotFound(new Response { Status = "Error", Message = "Event not found" }); + } + + model.Title = eventDto.Title; + model.Description = eventDto.Description; + model.Location = eventDto.Location; + model.RecordID = eventDto.RecordID; + model.RecordName = eventDto.RecordName; + model.RecordTargetType = eventDto.RecordTargetType; + model.ReminderMinutes = eventDto.ReminderMinutes; + model.EventColor = eventDto.EventColor; + model.StartDate = eventDto.StartDate; + model.StartTime = eventDto.StartTime; + model.EndDate = eventDto.EndDate; + model.EndTime = eventDto.EndTime; + model.AllDay = eventDto.AllDay; + model.LastModificationTime = DateTime.Now; + + _db.Events.Update(model); + await _db.SaveChangesAsync(); + + return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // GET EVENT BY ID + // =================================================================================== + [HttpGet] + [Route("GetEventById/{id}")] + public async Task GetEventById(int id) + { + try + { + var model = await _db.Events + .Where(e => e.Id == id && e.IsDeleted != true) + .FirstOrDefaultAsync(); + + if (model == null) + { + return NotFound(new Response { Status = "Error", Message = "Event not found" }); + } + + var dto = new Event_DTO + { + Id = model.Id, + Title = model.Title, + Description = model.Description, + Location = model.Location, + RecordID = model.RecordID, + RecordName = model.RecordName, + RecordTargetType = model.RecordTargetType, + ReminderMinutes = model.ReminderMinutes, + EventColor = model.EventColor, + StartDate = model.StartDate, + StartTime = model.StartTime, + EndDate = model.EndDate, + EndTime = model.EndTime, + AllDay = model.AllDay, + CreatedDate = model.CreatedDate, + createdby = model.createdby + }; + + return Ok(new DataResponse { Message = "Success", Status = "200", Data = dto }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // DELETE EVENT + // =================================================================================== + [HttpDelete] + [Route("DeleteEvent/{id}")] + public async Task DeleteEvent(int id) + { + try + { + var model = await _db.Events.FindAsync(id); + if (model == null) + { + return NotFound(new Response { Status = "Error", Message = "Event not found" }); + } + + // Soft delete + model.IsDeleted = true; + model.DeletionTime = DateTime.Now; + + _db.Events.Update(model); + await _db.SaveChangesAsync(); + + return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // GET EVENTS LIST (WITH PAGINATION AND FILTERS) + // =================================================================================== + [HttpGet] + [Route("GetEventList")] + public IActionResult GetEventList(string? search = "", int page = 1, int pageSize = 10) + { + try + { + var query = _db.Events.Where(e => e.IsDeleted != true); + + // Apply search filter if provided + if (!string.IsNullOrWhiteSpace(search)) + { + var searchLower = search.ToLower(); + query = query.Where(e => + e.Title.ToLower().Contains(searchLower) || + (e.Description != null && e.Description.ToLower().Contains(searchLower)) || + (e.Location != null && e.Location.ToLower().Contains(searchLower)) + ); + } + + // IMPORTANT: Calculate totalCount AFTER applying filters + var totalCount = query.Count(); + + // Apply pagination + var events = query + .OrderByDescending(e => e.StartDate) + .ThenByDescending(e => e.CreatedDate) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(e => new Event_DTO + { + Id = e.Id, + Title = e.Title, + Description = e.Description, + Location = e.Location, + RecordID = e.RecordID, + RecordName = e.RecordName, + RecordTargetType = e.RecordTargetType, + ReminderMinutes = e.ReminderMinutes, + EventColor = e.EventColor, + StartDate = e.StartDate, + StartTime = e.StartTime, + EndDate = e.EndDate, + EndTime = e.EndTime, + AllDay = e.AllDay, + CreatedDate = e.CreatedDate, + createdby = e.createdby + }) + .ToList(); + + return Ok(new DataResponse + { + Message = "Success", + Status = "200", + Data = new { rows = events, totalCount } + }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // GET EVENTS FOR CALENDAR (Date Range Filter) + // =================================================================================== + [HttpGet] + [Route("GetEventsForCalendar")] + public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end) + { + try + { + var query = _db.Events.Where(e => e.IsDeleted != true); + + // Filter by date range if provided + if (start.HasValue && end.HasValue) + { + query = query.Where(e => + (e.StartDate >= start.Value && e.StartDate <= end.Value) || + (e.EndDate >= start.Value && e.EndDate <= end.Value) || + (e.StartDate <= start.Value && e.EndDate >= end.Value) + ); + } + + var events = query + .Select(e => new Event_DTO + { + Id = e.Id, + Title = e.Title, + Description = e.Description, + Location = e.Location, + RecordID = e.RecordID, + RecordName = e.RecordName, + RecordTargetType = e.RecordTargetType, + ReminderMinutes = e.ReminderMinutes, + EventColor = e.EventColor, + StartDate = e.StartDate, + StartTime = e.StartTime, + EndDate = e.EndDate, + EndTime = e.EndTime, + AllDay = e.AllDay, + CreatedDate = e.CreatedDate, + createdby = e.createdby + }) + .ToList(); + + return Ok(new DataResponse { Message = "Success", Status = "200", Data = events }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + } +} + diff --git a/Api.SeaHavenIndustries/DTOs/EditEvent_DTO.cs b/Api.SeaHavenIndustries/DTOs/EditEvent_DTO.cs new file mode 100644 index 0000000..0083de5 --- /dev/null +++ b/Api.SeaHavenIndustries/DTOs/EditEvent_DTO.cs @@ -0,0 +1,48 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Api.SeaHavenIndustries.DTOs +{ + /// + /// DTO for POST/PUT requests - Create/Edit Event + /// Used to create or update event information + /// + public class EditEvent_DTO + { + public int? Id { get; set; } // Null for create, populated for edit + + [Required(ErrorMessage = "Please enter a title")] + [StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")] + public string Title { get; set; } = null!; + + public string? Description { get; set; } + + [StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")] + public string? Location { get; set; } + + public int? RecordID { get; set; } + public string? RecordName { get; set; } + public string? RecordTargetType { get; set; } + + [Required(ErrorMessage = "The Remind Attendees field is required")] + public int ReminderMinutes { get; set; } = 15; + + [Required(ErrorMessage = "The Select a Calendar Color field is required")] + public string EventColor { get; set; } = "primary"; + + [Required(ErrorMessage = "Please select a start date")] + public DateTime StartDate { get; set; } + + [Required(ErrorMessage = "Please select a start time")] + public string StartTime { get; set; } = null!; + + [Required(ErrorMessage = "Please select an end date")] + public DateTime EndDate { get; set; } + + [Required(ErrorMessage = "Please select an end time")] + public string EndTime { get; set; } = null!; + + public bool AllDay { get; set; } = false; + } +} + diff --git a/Api.SeaHavenIndustries/DTOs/Event_DTO.cs b/Api.SeaHavenIndustries/DTOs/Event_DTO.cs new file mode 100644 index 0000000..2543831 --- /dev/null +++ b/Api.SeaHavenIndustries/DTOs/Event_DTO.cs @@ -0,0 +1,33 @@ +using System; + +namespace Api.SeaHavenIndustries.DTOs +{ + /// + /// DTO for GET responses - Event data + /// Used to return event information to the client + /// + public class Event_DTO + { + public int Id { get; set; } + public string Title { get; set; } = null!; + public string? Description { get; set; } + public string? Location { get; set; } + + public int? RecordID { get; set; } + public string? RecordName { get; set; } + public string? RecordTargetType { get; set; } + + public int ReminderMinutes { get; set; } + public string EventColor { get; set; } = null!; + + public DateTime StartDate { get; set; } + public string? StartTime { get; set; } + public DateTime EndDate { get; set; } + public string? EndTime { get; set; } + public bool AllDay { get; set; } + + public DateTime? CreatedDate { get; set; } + public string? createdby { get; set; } + } +} + diff --git a/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json b/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json index fab4ccf..9f79baf 100644 --- a/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json +++ b/Api.SeaHavenIndustries/aws-beanstalk-tools-defaults.json @@ -1,13 +1,12 @@ - { - "region" : "us-east-1", - "configuration" : "Debug", - "framework" : "net8.0", - "self-contained" : true, - "application" : "SeaHavenIndustries", - "environment" : "SeaHavenIndustries-dev", - "enable-xray" : false, - "enhanced-health-type" : "basic", - "additional-options" : "", - "proxy-server" : "nginx" + "region": "us-east-1", + "configuration": "Debug", + "framework": "net8.0", + "self-contained": true, + "application": "SeaHavenIndustries", + "environment": "SeaHavenIndustries-dev", + "enable-xray": false, + "enhanced-health-type": "basic", + "additional-options": "", + "proxy-server": "nginx" } \ No newline at end of file diff --git a/Api.SeaHavenIndustries/eventapicontext.txt b/Api.SeaHavenIndustries/eventapicontext.txt new file mode 100644 index 0000000..f79dc93 --- /dev/null +++ b/Api.SeaHavenIndustries/eventapicontext.txt @@ -0,0 +1,553 @@ +/* +=================================================================================== +CALENDAR/EVENTS MODULE - COMPLETE IMPLEMENTATION +=================================================================================== +This file contains everything needed for the Calendar/Events module: +1. Events Model (Database Entity) +2. Event_DTO (for GET responses) +3. EditEvent_DTO (for POST/PUT requests) +4. CalendarController (API endpoints) +5. SQL Script for database table creation and sample data + +Author: AI Agent +Date: 2026-03-30 +=================================================================================== +*/ + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Api.SeaHavenIndustries.DTOs; +using Data.SeaHavenIndustries; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +// =================================================================================== +// SECTION 1: DATABASE MODEL (Data.SeaHavenIndustries/Models/Events.cs) +// =================================================================================== + +namespace Data.SeaHavenIndustries +{ + public class Events : FullAuditEntity + { + public string Title { get; set; } = null!; + public string? Description { get; set; } + public string? Location { get; set; } + + // Related Record (can link to Accounts, Contacts, Work Orders, etc.) + public int? RecordID { get; set; } + public string? RecordName { get; set; } + public string? RecordTargetType { get; set; } + + // Reminder Settings + public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before + + // Color for calendar display + public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger + + // Schedule + public DateTime StartDate { get; set; } + public string? StartTime { get; set; } // e.g., "08:00 AM" + public DateTime EndDate { get; set; } + public string? EndTime { get; set; } // e.g., "08:30 AM" + public bool AllDay { get; set; } = false; + } +} + +// =================================================================================== +// SECTION 2: DTOs (Api.SeaHavenIndustries/DTOs/Event_DTO.cs) +// =================================================================================== + +namespace Api.SeaHavenIndustries.DTOs +{ + // DTO for GET responses + public class Event_DTO + { + public int Id { get; set; } + public string Title { get; set; } = null!; + public string? Description { get; set; } + public string? Location { get; set; } + + public int? RecordID { get; set; } + public string? RecordName { get; set; } + public string? RecordTargetType { get; set; } + + public int ReminderMinutes { get; set; } + public string EventColor { get; set; } = null!; + + public DateTime StartDate { get; set; } + public string? StartTime { get; set; } + public DateTime EndDate { get; set; } + public string? EndTime { get; set; } + public bool AllDay { get; set; } + + public DateTime? CreatedDate { get; set; } + public string? createdby { get; set; } + } + + // DTO for POST/PUT requests (Create/Edit) + public class EditEvent_DTO + { + public int? Id { get; set; } // Null for create, populated for edit + + [Required(ErrorMessage = "Please enter a title")] + [StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")] + public string Title { get; set; } = null!; + + public string? Description { get; set; } + + [StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")] + public string? Location { get; set; } + + public int? RecordID { get; set; } + public string? RecordName { get; set; } + public string? RecordTargetType { get; set; } + + [Required(ErrorMessage = "The Remind Attendees field is required")] + public int ReminderMinutes { get; set; } = 15; + + [Required(ErrorMessage = "The Select a Calendar Color field is required")] + public string EventColor { get; set; } = "primary"; + + [Required(ErrorMessage = "Please select a start date")] + public DateTime StartDate { get; set; } + + [Required(ErrorMessage = "Please select a start time")] + public string StartTime { get; set; } = null!; + + [Required(ErrorMessage = "Please select an end date")] + public DateTime EndDate { get; set; } + + [Required(ErrorMessage = "Please select an end time")] + public string EndTime { get; set; } = null!; + + public bool AllDay { get; set; } = false; + } +} + +// =================================================================================== +// SECTION 3: CONTROLLER (Api.SeaHavenIndustries/Controllers/CalendarController.cs) +// =================================================================================== + +namespace Api.SeaHavenIndustries.Controllers +{ + //[Authorize] // Uncomment when authentication is needed + [ApiController] + [Route("api/Calendar")] + public class CalendarController : Controller + { + private readonly UserManager _userManager; + private readonly ApplicationDbContext _db; + + public CalendarController(UserManager userManager, ApplicationDbContext db) + { + _userManager = userManager; + _db = db; + } + + // =================================================================================== + // CREATE EVENT + // =================================================================================== + [HttpPost] + [Route("AddEvent")] + public async Task AddEvent(EditEvent_DTO eventDto) + { + try + { + var model = new Events + { + Title = eventDto.Title, + Description = eventDto.Description, + Location = eventDto.Location, + RecordID = eventDto.RecordID, + RecordName = eventDto.RecordName, + RecordTargetType = eventDto.RecordTargetType, + ReminderMinutes = eventDto.ReminderMinutes, + EventColor = eventDto.EventColor, + StartDate = eventDto.StartDate, + StartTime = eventDto.StartTime, + EndDate = eventDto.EndDate, + EndTime = eventDto.EndTime, + AllDay = eventDto.AllDay, + CreatedDate = DateTime.Now, + IsDeleted = false + }; + + _db.Events.Add(model); + await _db.SaveChangesAsync(); + + return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // UPDATE EVENT + // =================================================================================== + [HttpPost] + [Route("EditEvent")] + public async Task EditEvent(EditEvent_DTO eventDto) + { + try + { + if (!eventDto.Id.HasValue) + { + return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" }); + } + + var model = await _db.Events.FindAsync(eventDto.Id.Value); + if (model == null || model.IsDeleted == true) + { + return NotFound(new Response { Status = "Error", Message = "Event not found" }); + } + + model.Title = eventDto.Title; + model.Description = eventDto.Description; + model.Location = eventDto.Location; + model.RecordID = eventDto.RecordID; + model.RecordName = eventDto.RecordName; + model.RecordTargetType = eventDto.RecordTargetType; + model.ReminderMinutes = eventDto.ReminderMinutes; + model.EventColor = eventDto.EventColor; + model.StartDate = eventDto.StartDate; + model.StartTime = eventDto.StartTime; + model.EndDate = eventDto.EndDate; + model.EndTime = eventDto.EndTime; + model.AllDay = eventDto.AllDay; + model.LastModificationTime = DateTime.Now; + + _db.Events.Update(model); + await _db.SaveChangesAsync(); + + return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // GET EVENT BY ID + // =================================================================================== + [HttpGet] + [Route("GetEventById/{id}")] + public async Task GetEventById(int id) + { + try + { + var model = await _db.Events + .Where(e => e.Id == id && e.IsDeleted != true) + .FirstOrDefaultAsync(); + + if (model == null) + { + return NotFound(new Response { Status = "Error", Message = "Event not found" }); + } + + var dto = new Event_DTO + { + Id = model.Id, + Title = model.Title, + Description = model.Description, + Location = model.Location, + RecordID = model.RecordID, + RecordName = model.RecordName, + RecordTargetType = model.RecordTargetType, + ReminderMinutes = model.ReminderMinutes, + EventColor = model.EventColor, + StartDate = model.StartDate, + StartTime = model.StartTime, + EndDate = model.EndDate, + EndTime = model.EndTime, + AllDay = model.AllDay, + CreatedDate = model.CreatedDate, + createdby = model.createdby + }; + + return Ok(new DataResponse { Message = "Success", Status = "200", Data = dto }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + + // =================================================================================== + // DELETE EVENT + // =================================================================================== + [HttpDelete] + [Route("DeleteEvent/{id}")] + public async Task DeleteEvent(int id) + { + try + { + var model = await _db.Events.FindAsync(id); + if (model == null) + { + return NotFound(new Response { Status = "Error", Message = "Event not found" }); + } + + // Soft delete + model.IsDeleted = true; + model.DeletionTime = DateTime.Now; + + _db.Events.Update(model); + await _db.SaveChangesAsync(); + + return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // GET EVENTS LIST (WITH PAGINATION AND FILTERS) + // =================================================================================== + [HttpPost] + [Route("GetEventList")] + public IActionResult GetEventList(Pagination_DTO dto) + { + try + { + var query = _db.Events.Where(e => e.IsDeleted != true); + + // Apply search filter if provided + if (!string.IsNullOrWhiteSpace(dto.Search)) + { + query = query.Where(e => + e.Title.Contains(dto.Search) || + (e.Description != null && e.Description.Contains(dto.Search)) || + (e.Location != null && e.Location.Contains(dto.Search)) + ); + } + + // IMPORTANT: Calculate totalCount AFTER applying filters + var totalCount = query.Count(); + + // Apply pagination + var events = query + .OrderByDescending(e => e.StartDate) + .ThenByDescending(e => e.CreatedDate) + .Skip((dto.PageNumber - 1) * dto.PageSize) + .Take(dto.PageSize) + .Select(e => new Event_DTO + { + Id = e.Id, + Title = e.Title, + Description = e.Description, + Location = e.Location, + RecordID = e.RecordID, + RecordName = e.RecordName, + RecordTargetType = e.RecordTargetType, + ReminderMinutes = e.ReminderMinutes, + EventColor = e.EventColor, + StartDate = e.StartDate, + StartTime = e.StartTime, + EndDate = e.EndDate, + EndTime = e.EndTime, + AllDay = e.AllDay, + CreatedDate = e.CreatedDate, + createdby = e.createdby + }) + .ToList(); + + return Ok(new DataResponse + { + Message = "Success", + Status = "200", + Data = new { rows = events, totalCount } + }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + + // =================================================================================== + // GET EVENTS FOR CALENDAR (Date Range Filter) + // =================================================================================== + [HttpGet] + [Route("GetEventsForCalendar")] + public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end) + { + try + { + var query = _db.Events.Where(e => e.IsDeleted != true); + + // Filter by date range if provided + if (start.HasValue && end.HasValue) + { + query = query.Where(e => + (e.StartDate >= start.Value && e.StartDate <= end.Value) || + (e.EndDate >= start.Value && e.EndDate <= end.Value) || + (e.StartDate <= start.Value && e.EndDate >= end.Value) + ); + } + + var events = query + .Select(e => new Event_DTO + { + Id = e.Id, + Title = e.Title, + Description = e.Description, + Location = e.Location, + RecordID = e.RecordID, + RecordName = e.RecordName, + RecordTargetType = e.RecordTargetType, + ReminderMinutes = e.ReminderMinutes, + EventColor = e.EventColor, + StartDate = e.StartDate, + StartTime = e.StartTime, + EndDate = e.EndDate, + EndTime = e.EndTime, + AllDay = e.AllDay, + CreatedDate = e.CreatedDate, + createdby = e.createdby + }) + .ToList(); + + return Ok(new DataResponse { Message = "Success", Status = "200", Data = events }); + } + catch (Exception ex) + { + return BadRequest(new Response { Status = "Error", Message = ex.Message }); + } + } + } +} + +/* +=================================================================================== +SECTION 4: DATABASE SETUP +=================================================================================== + +STEP 1: Add Events DbSet to ApplicationDbContext.cs +------------------------------------------------------- +Add this line in the ApplicationDbContext class (around line 48): + + public DbSet Events { get; set; } + + +STEP 2: Create Database Migration +------------------------------------------------------- +Run these commands in the terminal from the Api.SeaHavenIndustries project directory: + + dotnet ef migrations add AddEventsTable --project ../Data.SeaHavenIndustries + dotnet ef database update + + +STEP 3: SQL Script for Manual Table Creation (Alternative to migrations) +------------------------------------------------------- +*/ + +-- SQL SCRIPT TO CREATE EVENTS TABLE +/* +CREATE TABLE Events ( + Id INT PRIMARY KEY IDENTITY(1,1), + Title NVARCHAR(100) NOT NULL, + Description NVARCHAR(MAX) NULL, + Location NVARCHAR(150) NULL, + + RecordID INT NULL, + RecordName NVARCHAR(255) NULL, + RecordTargetType NVARCHAR(50) NULL, + + ReminderMinutes INT NOT NULL DEFAULT 15, + EventColor NVARCHAR(20) NOT NULL DEFAULT 'primary', + + StartDate DATETIME NOT NULL, + StartTime NVARCHAR(10) NULL, + EndDate DATETIME NOT NULL, + EndTime NVARCHAR(10) NULL, + AllDay BIT NOT NULL DEFAULT 0, + + -- Audit fields + IsDeleted BIT NULL, + createdby NVARCHAR(450) NULL, + DeleterUserId NVARCHAR(450) NULL, + DeletionTime DATETIME NULL, + CreatedDate DATETIME NULL, + LastModificationTime DATETIME NULL, + LastModifierUserId INT NULL +); + +-- Insert sample data +INSERT INTO Events (Title, Description, Location, StartDate, StartTime, EndDate, EndTime, AllDay, EventColor, ReminderMinutes, CreatedDate, IsDeleted) +VALUES +('Team Meeting', 'Weekly team sync', 'Conference Room A', '2026-04-01', '10:00 AM', '2026-04-01', '11:00 AM', 0, 'primary', 15, GETDATE(), 0), +('Client Presentation', 'Q1 Results Review with ABC Corp', 'Virtual - Zoom', '2026-04-03', '02:00 PM', '2026-04-03', '03:30 PM', 0, 'success', 30, GETDATE(), 0), +('Project Deadline', 'Final delivery for Project Phoenix', NULL, '2026-04-05', NULL, '2026-04-05', NULL, 1, 'danger', 1440, GETDATE(), 0), +('Training Session', 'New system training for all employees', 'Main Office', '2026-04-08', '09:00 AM', '2026-04-08', '12:00 PM', 0, 'info', 60, GETDATE(), 0), +('Equipment Maintenance', 'Scheduled maintenance for all assets', 'Warehouse', '2026-04-10', '08:00 AM', '2026-04-10', '05:00 PM', 0, 'warning', 120, GETDATE(), 0); +*/ + +/* +=================================================================================== +SECTION 5: IMPLEMENTATION CHECKLIST +=================================================================================== + +BACKEND SETUP: +-------------- +1. ✓ Create Events.cs in Data.SeaHavenIndustries/Models/ +2. ✓ Create Event_DTO.cs in Api.SeaHavenIndustries/DTOs/ +3. ✓ Create EditEvent_DTO.cs in Api.SeaHavenIndustries/DTOs/ +4. ✓ Create CalendarController.cs in Api.SeaHavenIndustries/Controllers/ +5. ☐ Add DbSet to ApplicationDbContext.cs +6. ☐ Run migrations (or execute SQL script) +7. ☐ Test all endpoints using Postman/Swagger + +FRONTEND SETUP (Next Step): +--------------------------- +1. ☐ Create src/pages/calendar/CalendarPage.js (List view with FullCalendar) +2. ☐ Create src/pages/calendar/EventFormPage.js (Create/Edit form) +3. ☐ Create src/pages/calendar/api.js (API calls) +4. ☐ Add routes to App.js +5. ☐ Install FullCalendar: npm install @fullcalendar/react @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/interaction + +API ENDPOINTS SUMMARY: +--------------------- +POST /api/Calendar/AddEvent - Create new event +POST /api/Calendar/EditEvent - Update existing event +GET /api/Calendar/GetEventById/{id} - Get single event by ID +DELETE /api/Calendar/DeleteEvent/{id} - Delete event (soft delete) +POST /api/Calendar/GetEventList - Get paginated list with search +GET /api/Calendar/GetEventsForCalendar - Get events for calendar view (with date range) + +KEY FEATURES IMPLEMENTED: +------------------------- +✓ CRUD operations (Create, Read, Update, Delete) +✓ Pagination with search filter +✓ Date range filtering for calendar view +✓ Soft delete (IsDeleted flag) +✓ Related record linking (RecordID, RecordName, RecordTargetType) +✓ Color coding for calendar display +✓ All-day event support +✓ Reminder settings +✓ Proper error handling +✓ Consistent with existing controller patterns (AssetController) +✓ Fixed pagination bug (totalCount calculated AFTER filters) + +NOTES: +------ +- EventColor values: 'primary', 'success', 'info', 'warning', 'danger' +- ReminderMinutes: 0=Never, 5, 10, 15, 30, 60, 120, 240, 360, 720, 1440 (1 day) +- StartTime/EndTime format: "08:00 AM", "02:30 PM", etc. +- AllDay: When true, time fields can be null/ignored +- RecordTargetType: Can be "Account", "Contact", "WorkOrder", "FollowUp", etc. + +=================================================================================== +END OF FILE +=================================================================================== +*/ diff --git a/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs b/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs index 098c514..f727b07 100644 --- a/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs +++ b/Data.SeaHavenIndustries/Auth/ApplicationDbContext.cs @@ -46,6 +46,7 @@ namespace Data.SeaHavenIndustries public DbSet FollowUps { get; set; } public DbSet PMSchedules { get; set; } public DbSet ContactDetails { get; set; } + public DbSet Events { get; set; } // Employee related DbSets public DbSet EmployeeAddresses { get; set; } diff --git a/Data.SeaHavenIndustries/Models/Events.cs b/Data.SeaHavenIndustries/Models/Events.cs new file mode 100644 index 0000000..97ed818 --- /dev/null +++ b/Data.SeaHavenIndustries/Models/Events.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Data.SeaHavenIndustries +{ + /// + /// Events entity for Calendar/Events module + /// Tracks calendar events, appointments, meetings, etc. + /// + public class Events : FullAuditEntity + { + [Required] + [StringLength(100)] + public string Title { get; set; } = null!; + + public string? Description { get; set; } + + [StringLength(150)] + public string? Location { get; set; } + + // Related Record (can link to Accounts, Contacts, Work Orders, etc.) + public int? RecordID { get; set; } + public string? RecordName { get; set; } + public string? RecordTargetType { get; set; } + + // Reminder Settings + public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before + + // Color for calendar display + [StringLength(20)] + public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger + + // Schedule + [Required] + public DateTime StartDate { get; set; } + + [StringLength(10)] + public string? StartTime { get; set; } // e.g., "08:00 AM" + + [Required] + public DateTime EndDate { get; set; } + + [StringLength(10)] + public string? EndTime { get; set; } // e.g., "08:30 AM" + + public bool AllDay { get; set; } = false; + } +} +