mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 09:33:13 +00:00
553 lines
22 KiB
C#
553 lines
22 KiB
C#
/*
|
|
===================================================================================
|
|
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<ApplicationUser> _userManager;
|
|
private readonly ApplicationDbContext _db;
|
|
|
|
public CalendarController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
|
|
{
|
|
_userManager = userManager;
|
|
_db = db;
|
|
}
|
|
|
|
// ===================================================================================
|
|
// CREATE EVENT
|
|
// ===================================================================================
|
|
[HttpPost]
|
|
[Route("AddEvent")]
|
|
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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> 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<Events> 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
|
|
===================================================================================
|
|
*/
|