diff --git a/Api.SeaHavenIndustries.Tests/CalendarControllerTests.cs b/Api.SeaHavenIndustries.Tests/CalendarControllerTests.cs index 571b606..306bece 100644 --- a/Api.SeaHavenIndustries.Tests/CalendarControllerTests.cs +++ b/Api.SeaHavenIndustries.Tests/CalendarControllerTests.cs @@ -3,34 +3,16 @@ using Api.SeaHavenIndustries.Controllers; using Api.SeaHavenIndustries.DTOs; using Data.SeaHavenIndustries; using FluentAssertions; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using Moq; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; using Xunit; namespace Api.SeaHavenIndustries.Tests; public class CalendarControllerTests { - private static ApplicationDbContext NewContext() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) - .Options; - return new ApplicationDbContext(options); - } - - private static UserManager MockUserManager() - { - var store = new Mock>(); - return new Mock>( - store.Object, null!, null!, null!, null!, null!, null!, null!, null!).Object; - } - - private static CalendarController NewController(ApplicationDbContext ctx) => - new(MockUserManager(), ctx); - private static EditEvent_DTO ValidDto(int? id = null, string title = "Standup") => new() { Id = id, @@ -43,164 +25,166 @@ public class CalendarControllerTests EndTime = "08:30 AM" }; - private static Events SeedEvent(ApplicationDbContext ctx, string title, DateTime startDate, bool? isDeleted = false) - { - var ev = new Events - { - Title = title, - EventColor = "primary", - ReminderMinutes = 15, - StartDate = startDate, - EndDate = startDate, - IsDeleted = isDeleted, - CreatedDate = DateTime.Now - }; - ctx.Events.Add(ev); - ctx.SaveChanges(); - return ev; - } + private static CalendarController NewController(Mock service) => + new(service.Object); private static object Prop(object source, string name) => source.GetType().GetProperty(name)!.GetValue(source)!; [Fact] - public async Task AddEvent_WhenValid_PersistsEventAndReturnsOk() + public async Task AddEvent_WhenServiceSucceeds_ReturnsOkWithCreatedMessage() { - using var ctx = NewContext(); - var result = await NewController(ctx).AddEvent(ValidDto()); + var service = new Mock(); + var result = await NewController(service).AddEvent(ValidDto(), CancellationToken.None); var ok = result.Should().BeOfType().Subject; - ok.Value.Should().BeOfType().Which.Status.Should().Be("200"); - - var saved = ctx.Events.Single(); - saved.Title.Should().Be("Standup"); - saved.IsDeleted.Should().Be(false); - saved.CreatedDate.Should().NotBeNull(); + var response = ok.Value.Should().BeOfType().Subject; + response.Status.Should().Be("200"); + response.Message.Should().Be("Event Created Successfully"); + service.Verify(s => s.CreateEventAsync(It.IsAny(), It.IsAny()), Times.Once); } [Fact] - public async Task EditEvent_WhenIdMissing_ReturnsBadRequest() + public async Task AddEvent_WhenServiceThrows_ReturnsBadRequestWithMessage() { - using var ctx = NewContext(); - var result = await NewController(ctx).EditEvent(ValidDto(id: null)); + var service = new Mock(); + service.Setup(s => s.CreateEventAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new InvalidOperationException("boom")); - result.Should().BeOfType(); + var result = await NewController(service).AddEvent(ValidDto(), CancellationToken.None); + + var bad = result.Should().BeOfType().Subject; + var response = bad.Value.Should().BeOfType().Subject; + response.Message.Should().Be("boom"); } [Fact] - public async Task EditEvent_WhenEventNotFound_ReturnsNotFound() + public async Task EditEvent_WhenIdMissing_ReturnsBadRequestWithoutCallingService() { - using var ctx = NewContext(); - var result = await NewController(ctx).EditEvent(ValidDto(id: 999)); + var service = new Mock(); + var result = await NewController(service).EditEvent(ValidDto(id: null), CancellationToken.None); + + var bad = result.Should().BeOfType().Subject; + var response = bad.Value.Should().BeOfType().Subject; + response.Message.Should().Be("Event ID is required for editing"); + service.Verify(s => s.UpdateEventAsync(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task EditEvent_WhenServiceReturnsFalse_ReturnsNotFound() + { + var service = new Mock(); + service.Setup(s => s.UpdateEventAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(false); + + var result = await NewController(service).EditEvent(ValidDto(id: 5), CancellationToken.None); + + var nf = result.Should().BeOfType().Subject; + var response = nf.Value.Should().BeOfType().Subject; + response.Message.Should().Be("Event not found"); + } + + [Fact] + public async Task EditEvent_WhenServiceReturnsTrue_ReturnsOkWithUpdatedMessage() + { + var service = new Mock(); + service.Setup(s => s.UpdateEventAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + var result = await NewController(service).EditEvent(ValidDto(id: 5, title: "New"), CancellationToken.None); + + var ok = result.Should().BeOfType().Subject; + ok.Value.Should().BeOfType().Which.Message.Should().Be("Event Updated Successfully"); + } + + [Fact] + public async Task GetEventById_WhenServiceReturnsNull_ReturnsNotFound() + { + var service = new Mock(); + service.Setup(s => s.GetEventByIdAsync(7, It.IsAny())).ReturnsAsync((EventDTO?)null); + + var result = await NewController(service).GetEventById(7, CancellationToken.None); result.Should().BeOfType(); } [Fact] - public async Task EditEvent_WhenEventExists_UpdatesFieldsAndStampsModified() + public async Task GetEventById_WhenServiceReturnsDto_ReturnsOkWithEventDto() { - using var ctx = NewContext(); - var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1)); + var service = new Mock(); + service.Setup(s => s.GetEventByIdAsync(7, It.IsAny())) + .ReturnsAsync(new EventDTO { Id = 7, Title = "Review", createdby = "abc" }); - var result = await NewController(ctx).EditEvent(ValidDto(id: existing.Id, title: "New")); - - result.Should().BeOfType(); - var updated = ctx.Events.Single(); - updated.Title.Should().Be("New"); - updated.LastModificationTime.Should().NotBeNull(); - } - - [Fact] - public async Task GetEventById_WhenExists_ReturnsEventDto() - { - using var ctx = NewContext(); - var existing = SeedEvent(ctx, "Review", new DateTime(2026, 6, 1)); - - var result = await NewController(ctx).GetEventById(existing.Id); + var result = await NewController(service).GetEventById(7, CancellationToken.None); var ok = result.Should().BeOfType().Subject; - var dto = ok.Value.Should().BeOfType().Subject.Data as Event_DTO; + var response = ok.Value.Should().BeOfType().Subject; + var dto = response.Data as Event_DTO; + dto.Should().NotBeNull(); dto!.Title.Should().Be("Review"); + dto!.createdby.Should().Be("abc"); } [Fact] - public async Task GetEventById_WhenSoftDeleted_ReturnsNotFound() + public async Task DeleteEvent_WhenServiceReturnsFalse_ReturnsNotFound() { - using var ctx = NewContext(); - var deleted = SeedEvent(ctx, "Gone", new DateTime(2026, 6, 1), isDeleted: true); + var service = new Mock(); + service.Setup(s => s.DeleteEventAsync(9, It.IsAny())).ReturnsAsync(false); - var result = await NewController(ctx).GetEventById(deleted.Id); + var result = await NewController(service).DeleteEvent(9, CancellationToken.None); result.Should().BeOfType(); } [Fact] - public async Task DeleteEvent_WhenExists_SoftDeletesAndKeepsRow() + public async Task DeleteEvent_WhenServiceReturnsTrue_ReturnsOkWithDeletedMessage() { - using var ctx = NewContext(); - var existing = SeedEvent(ctx, "Cancel", new DateTime(2026, 6, 1)); + var service = new Mock(); + service.Setup(s => s.DeleteEventAsync(9, It.IsAny())).ReturnsAsync(true); - var result = await NewController(ctx).DeleteEvent(existing.Id); + var result = await NewController(service).DeleteEvent(9, CancellationToken.None); - result.Should().BeOfType(); - var row = ctx.Events.Single(); - row.IsDeleted.Should().Be(true); - row.DeletionTime.Should().NotBeNull(); + var ok = result.Should().BeOfType().Subject; + ok.Value.Should().BeOfType().Which.Message.Should().Be("Event Deleted Successfully"); } [Fact] - public async Task DeleteEvent_WhenNotFound_ReturnsNotFound() + public async Task GetEventList_ReturnsOkWithRowsAndTotalCount() { - using var ctx = NewContext(); - var result = await NewController(ctx).DeleteEvent(12345); + var service = new Mock(); + service.Setup(s => s.GetEventListAsync("alpha", 1, 10, It.IsAny())) + .ReturnsAsync(new PagedEventsResult + { + TotalCount = 2, + Rows = new List + { + new() { Id = 1, Title = "Alpha meeting" }, + new() { Id = 2, Title = "Alpha review" } + } + }); - result.Should().BeOfType(); - } - - [Fact] - public void GetEventList_ExcludesDeletedAndCountsAfterFilter() - { - using var ctx = NewContext(); - SeedEvent(ctx, "Alpha meeting", new DateTime(2026, 6, 1)); - SeedEvent(ctx, "Beta meeting", new DateTime(2026, 6, 2)); - SeedEvent(ctx, "Alpha review", new DateTime(2026, 6, 3)); - SeedEvent(ctx, "Alpha archived", new DateTime(2026, 6, 4), isDeleted: true); - - var result = NewController(ctx).GetEventList(search: "alpha", page: 1, pageSize: 10); + var result = await NewController(service).GetEventList(search: "alpha", page: 1, pageSize: 10); var ok = result.Should().BeOfType().Subject; var data = ok.Value.Should().BeOfType().Subject.Data!; ((int)Prop(data, "totalCount")).Should().Be(2); - } - - [Fact] - public void GetEventList_OrdersByStartDateDescending() - { - using var ctx = NewContext(); - SeedEvent(ctx, "Earliest", new DateTime(2026, 6, 1)); - SeedEvent(ctx, "Latest", new DateTime(2026, 6, 10)); - SeedEvent(ctx, "Middle", new DateTime(2026, 6, 5)); - - var result = NewController(ctx).GetEventList(page: 1, pageSize: 10); - - var data = (result as OkObjectResult)!.Value.As().Data!; var rows = ((IEnumerable)Prop(data, "rows")).Cast().ToList(); - rows.Select(r => r.Title).Should().ContainInOrder("Latest", "Middle", "Earliest"); + rows.Should().HaveCount(2); } [Fact] - public void GetEventsForCalendar_ReturnsOnlyEventsOverlappingRange() + public async Task GetEventsForCalendar_ReturnsOkWithEvents() { - using var ctx = NewContext(); - SeedEvent(ctx, "InRange", new DateTime(2026, 6, 15)); - SeedEvent(ctx, "BeforeRange", new DateTime(2026, 5, 1)); - SeedEvent(ctx, "AfterRange", new DateTime(2026, 7, 1)); + var service = new Mock(); + service.Setup(s => s.GetEventsForCalendarAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(new List { new() { Id = 1, Title = "InRange" } }); - var result = NewController(ctx).GetEventsForCalendar( - start: new DateTime(2026, 6, 1), end: new DateTime(2026, 6, 30)); + var result = await NewController(service).GetEventsForCalendar( + start: new DateTime(2026, 6, 1), end: new DateTime(2026, 6, 30), CancellationToken.None); var ok = result.Should().BeOfType().Subject; - var events = ok.Value.As().Data as IEnumerable; - events!.Select(e => e.Title).Should().BeEquivalentTo(new[] { "InRange" }); + var data = ok.Value.Should().BeOfType().Subject.Data; + var events = ((IEnumerable)data!).Cast().ToList(); + events.Single().Title.Should().Be("InRange"); } } diff --git a/Api.SeaHavenIndustries.Tests/CalendarServiceTests.cs b/Api.SeaHavenIndustries.Tests/CalendarServiceTests.cs new file mode 100644 index 0000000..bceb520 --- /dev/null +++ b/Api.SeaHavenIndustries.Tests/CalendarServiceTests.cs @@ -0,0 +1,188 @@ +using Api.SeaHavenIndustries.DTOs; +using Data.SeaHavenIndustries; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Implementation; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Implementation; +using Xunit; + +namespace Api.SeaHavenIndustries.Tests; + +public class CalendarServiceTests +{ + private static ApplicationDbContext NewContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + return new ApplicationDbContext(options); + } + + private static CalendarService NewService(ApplicationDbContext ctx) => + new(new CalendarDataService(ctx)); + + private static EditEventInput ValidInput(int? id = null, string title = "Standup") => new() + { + Id = id, + Title = title, + ReminderMinutes = 15, + EventColor = "primary", + StartDate = new DateTime(2026, 6, 1), + StartTime = "08:00 AM", + EndDate = new DateTime(2026, 6, 1), + EndTime = "08:30 AM" + }; + + private static Events SeedEvent(ApplicationDbContext ctx, string title, DateTime startDate, bool? isDeleted = false) + { + var ev = new Events + { + Title = title, + EventColor = "primary", + ReminderMinutes = 15, + StartDate = startDate, + EndDate = startDate, + IsDeleted = isDeleted, + CreatedDate = DateTime.Now + }; + ctx.Events.Add(ev); + ctx.SaveChanges(); + return ev; + } + + private static object Prop(object source, string name) => + source.GetType().GetProperty(name)!.GetValue(source)!; + + [Fact] + public async Task CreateEventAsync_PersistsEventWithMappedFieldsAndCreatedDate() + { + using var ctx = NewContext(); + await NewService(ctx).CreateEventAsync(ValidInput(), CancellationToken.None); + + var saved = ctx.Events.Single(); + saved.Title.Should().Be("Standup"); + saved.IsDeleted.Should().Be(false); + saved.CreatedDate.Should().NotBeNull(); + } + + [Fact] + public async Task UpdateEventAsync_WhenNotFound_ReturnsFalse() + { + using var ctx = NewContext(); + var result = await NewService(ctx).UpdateEventAsync(ValidInput(id: 999), CancellationToken.None); + result.Should().BeFalse(); + } + + [Fact] + public async Task UpdateEventAsync_WhenSoftDeleted_ReturnsFalse() + { + using var ctx = NewContext(); + var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1), isDeleted: true); + + var result = await NewService(ctx).UpdateEventAsync(ValidInput(id: existing.Id, title: "New"), CancellationToken.None); + + result.Should().BeFalse(); + } + + [Fact] + public async Task UpdateEventAsync_WhenEventExists_UpdatesFieldsAndStampsModified() + { + using var ctx = NewContext(); + var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1)); + + var result = await NewService(ctx).UpdateEventAsync(ValidInput(id: existing.Id, title: "New"), CancellationToken.None); + + result.Should().BeTrue(); + var updated = ctx.Events.Single(); + updated.Title.Should().Be("New"); + updated.LastModificationTime.Should().NotBeNull(); + } + + [Fact] + public async Task GetEventByIdAsync_WhenExists_ReturnsMappedDto() + { + using var ctx = NewContext(); + var existing = SeedEvent(ctx, "Review", new DateTime(2026, 6, 1)); + + var dto = await NewService(ctx).GetEventByIdAsync(existing.Id, CancellationToken.None); + + dto.Should().NotBeNull(); + dto!.Title.Should().Be("Review"); + } + + [Fact] + public async Task GetEventByIdAsync_WhenSoftDeleted_ReturnsNull() + { + using var ctx = NewContext(); + var deleted = SeedEvent(ctx, "Gone", new DateTime(2026, 6, 1), isDeleted: true); + + var dto = await NewService(ctx).GetEventByIdAsync(deleted.Id, CancellationToken.None); + + dto.Should().BeNull(); + } + + [Fact] + public async Task DeleteEventAsync_WhenExists_SoftDeletesAndKeepsRow() + { + using var ctx = NewContext(); + var existing = SeedEvent(ctx, "Cancel", new DateTime(2026, 6, 1)); + + var result = await NewService(ctx).DeleteEventAsync(existing.Id, CancellationToken.None); + + result.Should().BeTrue(); + var row = ctx.Events.Single(); + row.IsDeleted.Should().Be(true); + row.DeletionTime.Should().NotBeNull(); + } + + [Fact] + public async Task DeleteEventAsync_WhenNotFound_ReturnsFalse() + { + using var ctx = NewContext(); + var result = await NewService(ctx).DeleteEventAsync(12345, CancellationToken.None); + result.Should().BeFalse(); + } + + [Fact] + public async Task GetEventListAsync_ExcludesDeletedAndCountsAfterFilter() + { + using var ctx = NewContext(); + SeedEvent(ctx, "Alpha meeting", new DateTime(2026, 6, 1)); + SeedEvent(ctx, "Beta meeting", new DateTime(2026, 6, 2)); + SeedEvent(ctx, "Alpha review", new DateTime(2026, 6, 3)); + SeedEvent(ctx, "Alpha archived", new DateTime(2026, 6, 4), isDeleted: true); + + var result = await NewService(ctx).GetEventListAsync("alpha", 1, 10, CancellationToken.None); + + result.TotalCount.Should().Be(2); + result.Rows.Should().HaveCount(2); + } + + [Fact] + public async Task GetEventListAsync_OrdersByStartDateDescending() + { + using var ctx = NewContext(); + SeedEvent(ctx, "Earliest", new DateTime(2026, 6, 1)); + SeedEvent(ctx, "Latest", new DateTime(2026, 6, 10)); + SeedEvent(ctx, "Middle", new DateTime(2026, 6, 5)); + + var result = await NewService(ctx).GetEventListAsync(null, 1, 10, CancellationToken.None); + + result.Rows.Select(r => r.Title).Should().ContainInOrder("Latest", "Middle", "Earliest"); + } + + [Fact] + public async Task GetEventsForCalendarAsync_ReturnsOnlyEventsOverlappingRange() + { + using var ctx = NewContext(); + SeedEvent(ctx, "InRange", new DateTime(2026, 6, 15)); + SeedEvent(ctx, "BeforeRange", new DateTime(2026, 5, 1)); + SeedEvent(ctx, "AfterRange", new DateTime(2026, 7, 1)); + + var events = await NewService(ctx).GetEventsForCalendarAsync( + new DateTime(2026, 6, 1), new DateTime(2026, 6, 30), CancellationToken.None); + + events.Select(e => e.Title).Should().BeEquivalentTo(new[] { "InRange" }); + } +} diff --git a/Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs b/Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs new file mode 100644 index 0000000..0af8f00 --- /dev/null +++ b/Api.SeaHavenIndustries.Tests/DashboardServiceTests.cs @@ -0,0 +1,49 @@ +using Data.SeaHavenIndustries; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Implementation; +using SeaHaven.Services.Implementation; +using Xunit; + +namespace Api.SeaHavenIndustries.Tests; + +public class DashboardServiceTests +{ + private static ApplicationDbContext NewContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + return new ApplicationDbContext(options); + } + + private static DashboardService NewService(ApplicationDbContext ctx) => + new(new DashboardDataService(ctx)); + + private static WorkOrder Wo(string? status, bool? isTemplate = false, string? assignTo = null) => + new() { Status = status, istemplate = isTemplate, AssignTo = assignTo }; + + [Fact] + public async Task GetStatsAsync_CountsByStatusExcludingTemplates() + { + using var ctx = NewContext(); + ctx.workOrders.AddRange( + Wo("Open", isTemplate: false), + Wo("Open", isTemplate: false, assignTo: "u1"), + Wo("In Progress"), + Wo("On Hold"), + Wo("Done"), + Wo("Done"), + Wo("Open", isTemplate: true), + Wo("Cancelled", isTemplate: false) + ); + ctx.SaveChanges(); + + var stats = await NewService(ctx).GetStatsAsync(CancellationToken.None); + + stats.Total.Should().Be(7); + stats.Open.Should().Be(4); + stats.NotDispatched.Should().Be(1); + stats.Completed.Should().Be(2); + } +} diff --git a/Api.SeaHavenIndustries.Tests/DropdownOptionsServiceTests.cs b/Api.SeaHavenIndustries.Tests/DropdownOptionsServiceTests.cs new file mode 100644 index 0000000..ce6a3f7 --- /dev/null +++ b/Api.SeaHavenIndustries.Tests/DropdownOptionsServiceTests.cs @@ -0,0 +1,146 @@ +using Data.SeaHavenIndustries; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Implementation; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Implementation; +using Xunit; + +namespace Api.SeaHavenIndustries.Tests; + +public class DropdownOptionsServiceTests +{ + private static ApplicationDbContext NewContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + return new ApplicationDbContext(options); + } + + private static DropdownOptionsService NewService(ApplicationDbContext ctx) => + new(new DropdownOptionsDataService(ctx)); + + private static DropdownOption Option(string category, string value, string? parent = null, int sort = 0, bool active = true) => + new() { Category = category, Value = value, ParentValue = parent, SortOrder = sort, IsActive = active }; + + [Fact] + public async Task GetAllAsync_FiltersActiveAndOrdersBySortThenValue() + { + using var ctx = NewContext(); + ctx.DropdownOptions.AddRange( + Option("Color", "Red", sort: 2), + Option("Color", "Blue", sort: 1), + Option("Color", "Green", sort: 1, active: false), + Option("Shape", "Circle") + ); + ctx.SaveChanges(); + + var result = (await NewService(ctx).GetAllAsync(null, CancellationToken.None)).ToList(); + + result.Should().HaveCount(3); + result.Select(d => d.Value).Should().ContainInOrder("Blue", "Red", "Circle"); + } + + [Fact] + public async Task GetByCategoryAsync_FiltersByCategoryAndParent() + { + using var ctx = NewContext(); + ctx.DropdownOptions.AddRange( + Option("State", "TX", parent: "US"), + Option("State", "CA", parent: "US"), + Option("State", "ON", parent: "CA") + ); + ctx.SaveChanges(); + + var result = (await NewService(ctx).GetByCategoryAsync("State", "US", CancellationToken.None)).ToList(); + + result.Select(d => d.Value).Should().BeEquivalentTo(new[] { "TX", "CA" }); + } + + [Fact] + public async Task CreateAsync_WhenCategoryOrValueMissing_Throws() + { + using var ctx = NewContext(); + var act = () => NewService(ctx).CreateAsync(new DropdownOptionDTO { Category = "", Value = "X" }, CancellationToken.None); + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task CreateAsync_WhenDuplicate_Throws() + { + using var ctx = NewContext(); + ctx.DropdownOptions.Add(Option("Color", "Red", parent: "Primary")); + ctx.SaveChanges(); + + var act = () => NewService(ctx).CreateAsync(new DropdownOptionDTO { Category = "Color", Value = "Red", ParentValue = "Primary" }, CancellationToken.None); + + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task CreateAsync_ForcesActiveTrueAndReturnsDtoWithId() + { + using var ctx = NewContext(); + var dto = await NewService(ctx).CreateAsync( + new DropdownOptionDTO { Category = "Color", Value = "Red", IsActive = false }, CancellationToken.None); + + dto.Id.Should().BeGreaterThan(0); + dto.IsActive.Should().BeTrue(); + ctx.DropdownOptions.Single().IsActive.Should().BeTrue(); + } + + [Fact] + public async Task UpdateAsync_WhenNotFound_ThrowsKeyNotFound() + { + using var ctx = NewContext(); + var act = () => NewService(ctx).UpdateAsync(404, new DropdownOptionDTO { Category = "Color", Value = "Red" }, CancellationToken.None); + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task UpdateAsync_UpdatesValueParentSortActiveButNotCategory() + { + using var ctx = NewContext(); + var existing = Option("Color", "Red"); + ctx.DropdownOptions.Add(existing); + ctx.SaveChanges(); + + await NewService(ctx).UpdateAsync(existing.Id, new DropdownOptionDTO + { + Category = "Ignored", + Value = "Crimson", + ParentValue = "Warm", + SortOrder = 5, + IsActive = false + }, CancellationToken.None); + + var row = ctx.DropdownOptions.Single(); + row.Category.Should().Be("Color"); + row.Value.Should().Be("Crimson"); + row.ParentValue.Should().Be("Warm"); + row.SortOrder.Should().Be(5); + row.IsActive.Should().BeFalse(); + } + + [Fact] + public async Task DeleteAsync_WhenNotFound_ThrowsKeyNotFound() + { + using var ctx = NewContext(); + var act = () => NewService(ctx).DeleteAsync(404, CancellationToken.None); + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task DeleteAsync_RemovesOption() + { + using var ctx = NewContext(); + var existing = Option("Color", "Red"); + ctx.DropdownOptions.Add(existing); + ctx.SaveChanges(); + + await NewService(ctx).DeleteAsync(existing.Id, CancellationToken.None); + + ctx.DropdownOptions.Should().BeEmpty(); + } +} diff --git a/Api.SeaHavenIndustries.Tests/ServiceRegistrationTests.cs b/Api.SeaHavenIndustries.Tests/ServiceRegistrationTests.cs new file mode 100644 index 0000000..2127983 --- /dev/null +++ b/Api.SeaHavenIndustries.Tests/ServiceRegistrationTests.cs @@ -0,0 +1,57 @@ +using Data.SeaHavenIndustries; +using FluentAssertions; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using SeaHaven.DataServices.DependencyInjection; +using SeaHaven.DataServices.Implementation; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DependencyInjection; +using SeaHaven.Services.Implementation; +using SeaHaven.Services.Interfaces; +using Xunit; + +namespace Api.SeaHavenIndustries.Tests; + +public class ServiceRegistrationTests +{ + private static ServiceProvider BuildProvider() + { + var services = new ServiceCollection(); + services.AddLogging(); + + services.AddDbContext(options => + options.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())); + + services.AddIdentity() + .AddEntityFrameworkStores(); + + services.AddDataServices(); + services.AddBusinessServices(); + + return services.BuildServiceProvider(); + } + + [Fact] + public void ConventionDi_RegistersAllFiveFeatureServices() + { + using var provider = BuildProvider(); + + provider.GetRequiredService().Should().BeOfType(); + provider.GetRequiredService().Should().BeOfType(); + provider.GetRequiredService().Should().BeOfType(); + provider.GetRequiredService().Should().BeOfType(); + provider.GetRequiredService().Should().BeOfType(); + } + + [Fact] + public void ConventionDi_RegistersFeatureDataServices() + { + using var provider = BuildProvider(); + + provider.GetRequiredService().Should().BeOfType(); + provider.GetRequiredService().Should().BeOfType(); + provider.GetRequiredService().Should().BeOfType(); + provider.GetRequiredService().Should().BeOfType(); + } +} diff --git a/Api.SeaHavenIndustries.Tests/TaskListTemplateServiceTests.cs b/Api.SeaHavenIndustries.Tests/TaskListTemplateServiceTests.cs new file mode 100644 index 0000000..7f96501 --- /dev/null +++ b/Api.SeaHavenIndustries.Tests/TaskListTemplateServiceTests.cs @@ -0,0 +1,146 @@ +using Data.SeaHavenIndustries; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Implementation; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Implementation; +using Xunit; + +namespace Api.SeaHavenIndustries.Tests; + +public class TaskListTemplateServiceTests +{ + private static ApplicationDbContext NewContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + return new ApplicationDbContext(options); + } + + private static TaskListTemplateService NewService(ApplicationDbContext ctx) => + new(new TaskListTemplateDataService(ctx)); + + private static TaskListTemplate Template(string name, bool active, params (string text, int sort)[] items) + { + var t = new TaskListTemplate { Name = name, IsActive = active }; + t.Items = items.Select(i => new TaskListTemplateItem { ItemText = i.text, SortOrder = i.sort }).ToList(); + return t; + } + + [Fact] + public async Task GetAllAsync_ReturnsOnlyActiveOrderedByNameWithOrderedItems() + { + using var ctx = NewContext(); + ctx.TaskListTemplates.AddRange( + Template("Zeta", true, ("b", 2), ("a", 1)), + Template("Alpha", true, ("first", 0)), + Template("Hidden", false) + ); + ctx.SaveChanges(); + + var result = (await NewService(ctx).GetAllAsync(CancellationToken.None)).ToList(); + + result.Select(t => t.Name).Should().ContainInOrder("Alpha", "Zeta"); + result.Single(t => t.Name == "Zeta").Items.Select(i => i.ItemText).Should().ContainInOrder("a", "b"); + } + + [Fact] + public async Task GetByIdAsync_WhenMissing_ReturnsNull() + { + using var ctx = NewContext(); + var result = await NewService(ctx).GetByIdAsync(99, CancellationToken.None); + result.Should().BeNull(); + } + + [Fact] + public async Task GetByIdAsync_ReturnsTemplateWithOrderedItems() + { + using var ctx = NewContext(); + var t = Template("Alpha", true, ("b", 2), ("a", 1)); + ctx.TaskListTemplates.Add(t); + ctx.SaveChanges(); + + var result = await NewService(ctx).GetByIdAsync(t.Id, CancellationToken.None); + + result.Should().NotBeNull(); + result!.Items.Select(i => i.ItemText).Should().ContainInOrder("a", "b"); + } + + [Fact] + public async Task CreateAsync_PersistsTemplateAndItemsAndReturnsId() + { + using var ctx = NewContext(); + var input = new TaskListTemplateInput + { + Name = "New", + Description = "desc", + IsActive = true, + Items = new() + { + new() { ItemText = "two", SortOrder = 0 }, + new() { ItemText = "one", SortOrder = 0 } + } + }; + + var id = await NewService(ctx).CreateAsync(input, CancellationToken.None); + + id.Should().BeGreaterThan(0); + var row = ctx.TaskListTemplates.Include(t => t.Items).Single(); + row.Name.Should().Be("New"); + row.Items.Should().HaveCount(2); + row.Items!.Select(i => i.SortOrder).Should().ContainInOrder(0, 1); + } + + [Fact] + public async Task UpdateAsync_WhenNotFound_ReturnsFalse() + { + using var ctx = NewContext(); + var result = await NewService(ctx).UpdateAsync(404, new TaskListTemplateInput { Name = "x" }, CancellationToken.None); + result.Should().BeFalse(); + } + + [Fact] + public async Task UpdateAsync_ReplacesItems() + { + using var ctx = NewContext(); + var t = Template("Alpha", true, ("old", 0)); + ctx.TaskListTemplates.Add(t); + ctx.SaveChanges(); + + var updated = await NewService(ctx).UpdateAsync(t.Id, new TaskListTemplateInput + { + Name = "Renamed", + IsActive = false, + Items = new() { new() { ItemText = "new1", SortOrder = 1 }, new() { ItemText = "new2", SortOrder = 2 } } + }, CancellationToken.None); + + updated.Should().BeTrue(); + var row = ctx.TaskListTemplates.Include(x => x.Items).Single(); + row.Name.Should().Be("Renamed"); + row.IsActive.Should().BeFalse(); + row.Items!.Select(i => i.ItemText).Should().BeEquivalentTo(new[] { "new1", "new2" }); + } + + [Fact] + public async Task DeleteAsync_SoftDeletesByDeactivating() + { + using var ctx = NewContext(); + var t = Template("Alpha", true); + ctx.TaskListTemplates.Add(t); + ctx.SaveChanges(); + + var result = await NewService(ctx).DeleteAsync(t.Id, CancellationToken.None); + + result.Should().BeTrue(); + ctx.TaskListTemplates.Single().IsActive.Should().BeFalse(); + } + + [Fact] + public async Task DeleteAsync_WhenNotFound_ReturnsFalse() + { + using var ctx = NewContext(); + var result = await NewService(ctx).DeleteAsync(404, CancellationToken.None); + result.Should().BeFalse(); + } +} diff --git a/Api.SeaHavenIndustries/Controllers/CalendarController.cs b/Api.SeaHavenIndustries/Controllers/CalendarController.cs index 2d7f85d..caa18be 100644 --- a/Api.SeaHavenIndustries/Controllers/CalendarController.cs +++ b/Api.SeaHavenIndustries/Controllers/CalendarController.cs @@ -1,13 +1,9 @@ -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; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; namespace Api.SeaHavenIndustries.Controllers { @@ -16,13 +12,11 @@ namespace Api.SeaHavenIndustries.Controllers [Route("api/Calendar")] public class CalendarController : Controller { - private readonly UserManager _userManager; - private readonly ApplicationDbContext _db; + private readonly ICalendarService _calendarService; - public CalendarController(UserManager userManager, ApplicationDbContext db) + public CalendarController(ICalendarService calendarService) { - _userManager = userManager; - _db = db; + _calendarService = calendarService; } // =================================================================================== @@ -30,31 +24,11 @@ namespace Api.SeaHavenIndustries.Controllers // =================================================================================== [HttpPost] [Route("AddEvent")] - public async Task AddEvent(EditEvent_DTO eventDto) + public async Task AddEvent(EditEvent_DTO eventDto, CancellationToken cancellationToken) { 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(); + await _calendarService.CreateEventAsync(MapToInput(eventDto), cancellationToken); return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" }); } @@ -69,7 +43,7 @@ namespace Api.SeaHavenIndustries.Controllers // =================================================================================== [HttpPost] [Route("EditEvent")] - public async Task EditEvent(EditEvent_DTO eventDto) + public async Task EditEvent(EditEvent_DTO eventDto, CancellationToken cancellationToken) { try { @@ -78,30 +52,12 @@ namespace Api.SeaHavenIndustries.Controllers 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) + var updated = await _calendarService.UpdateEventAsync(MapToInput(eventDto), cancellationToken); + if (!updated) { 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) @@ -115,40 +71,18 @@ namespace Api.SeaHavenIndustries.Controllers // =================================================================================== [HttpGet] [Route("GetEventById/{id}")] - public async Task GetEventById(int id) + public async Task GetEventById(int id, CancellationToken cancellationToken) { try { - var model = await _db.Events - .Where(e => e.Id == id && e.IsDeleted != true) - .FirstOrDefaultAsync(); + var dto = await _calendarService.GetEventByIdAsync(id, cancellationToken); - if (model == null) + if (dto == 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 }); + return Ok(new DataResponse { Message = "Success", Status = "200", Data = MapToResponse(dto) }); } catch (Exception ex) { @@ -161,23 +95,16 @@ namespace Api.SeaHavenIndustries.Controllers // =================================================================================== [HttpDelete] [Route("DeleteEvent/{id}")] - public async Task DeleteEvent(int id) + public async Task DeleteEvent(int id, CancellationToken cancellationToken) { try { - var model = await _db.Events.FindAsync(id); - if (model == null) + var deleted = await _calendarService.DeleteEventAsync(id, cancellationToken); + if (!deleted) { 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) @@ -191,58 +118,19 @@ namespace Api.SeaHavenIndustries.Controllers // =================================================================================== [HttpGet] [Route("GetEventList")] - public IActionResult GetEventList(string? search = "", int page = 1, int pageSize = 10) + public async Task GetEventList(string? search = "", int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) { try { - var query = _db.Events.Where(e => e.IsDeleted != true); + var result = await _calendarService.GetEventListAsync(search, page, pageSize, cancellationToken); - // 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(); + var events = result.Rows.Select(MapToResponse).ToList(); return Ok(new DataResponse { Message = "Success", Status = "200", - Data = new { rows = events, totalCount } + Data = new { rows = events, totalCount = result.TotalCount } }); } catch (Exception ex) @@ -256,51 +144,62 @@ namespace Api.SeaHavenIndustries.Controllers // =================================================================================== [HttpGet] [Route("GetEventsForCalendar")] - public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end) + public async Task GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end, CancellationToken cancellationToken) { try { - var query = _db.Events.Where(e => e.IsDeleted != true); + var events = await _calendarService.GetEventsForCalendarAsync(start, end, cancellationToken); - // 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 }); + return Ok(new DataResponse { Message = "Success", Status = "200", Data = events.Select(MapToResponse).ToList() }); } catch (Exception ex) { return BadRequest(new Response { Status = "Error", Message = ex.Message }); } } + + private static EditEventInput MapToInput(EditEvent_DTO eventDto) + { + return new EditEventInput + { + Id = eventDto.Id, + 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 + }; + } + + private static Event_DTO MapToResponse(EventDTO dto) + { + return new Event_DTO + { + Id = dto.Id, + Title = dto.Title, + Description = dto.Description, + Location = dto.Location, + RecordID = dto.RecordID, + RecordName = dto.RecordName, + RecordTargetType = dto.RecordTargetType, + ReminderMinutes = dto.ReminderMinutes, + EventColor = dto.EventColor, + StartDate = dto.StartDate, + StartTime = dto.StartTime, + EndDate = dto.EndDate, + EndTime = dto.EndTime, + AllDay = dto.AllDay, + CreatedDate = dto.CreatedDate, + createdby = dto.createdby + }; + } } } - diff --git a/Api.SeaHavenIndustries/Controllers/CommonController.cs b/Api.SeaHavenIndustries/Controllers/CommonController.cs index 2a44899..875834e 100644 --- a/Api.SeaHavenIndustries/Controllers/CommonController.cs +++ b/Api.SeaHavenIndustries/Controllers/CommonController.cs @@ -1,73 +1,49 @@ using Api.SeaHavenIndustries.DTOs; using Api.SeaHavenIndustries.Helper.ViewModel; -using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; +using SeaHaven.Services.Interfaces; namespace Api.SeaHavenIndustries.Controllers { [Authorize] [ApiController] [Route("[controller]")] - + public class CommonController : Controller { - private readonly UserManager _userManager; - private readonly ApplicationDbContext _db; - public CommonController(UserManager userManager, ApplicationDbContext db) + private readonly ICommonService _commonService; + + public CommonController(ICommonService commonService) { - _userManager = userManager; - _db = db; + _commonService = commonService; } [HttpGet] [Route("GetUsers")] - public async Task GetUsers(string? search = "", int page = 1, int pageSize = 10) + public async Task GetUsers(string? search = "", int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) { try { - var allUsersQuery = _db.Users - .Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower())) - .OrderByDescending(d => d.Id); + var result = await _commonService.GetUsersPagedAsync(search, page, pageSize, cancellationToken); - var totalCount = allUsersQuery.Count(); - - var paginatedUsers = allUsersQuery - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToList(); - // Calculate the number of pages - var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize); - - var filteredUsers = new List(); - - foreach (var user in paginatedUsers) + var filteredUsers = result.Users.Select(u => new UserRoleViewModel { - var roles = await _userManager.GetRolesAsync(user); - if (!roles.Contains("Admin")) - { - filteredUsers.Add(new UserRoleViewModel - { - RoleName = roles.Count()>0?roles.First():"", - Email = user.Email, - UserName = user.FirstName, - Date = user.CreatedDate?.Date.ToString(), - Status = user.UniqueName, - Id = user.Id - }); - } - } - // Create a view model containing the paged items and pagination metadata + RoleName = u.RoleName, + Email = u.Email, + UserName = u.UserName, + Date = u.Date, + Status = u.Status, + Id = u.Id + }).ToList(); + var viewModel = new Pagination_DTO { Data = filteredUsers, - PageNumber = page, - PageSize = pageSize, - TotalCount = totalCount, - TotalPages = totalPages + PageNumber = result.PageNumber, + PageSize = result.PageSize, + TotalCount = result.TotalCount, + TotalPages = result.TotalPages }; return Ok(viewModel); } @@ -79,12 +55,12 @@ namespace Api.SeaHavenIndustries.Controllers [HttpGet] [Route("GetRoles")] - public async Task GetRoles() + public async Task GetRoles(CancellationToken cancellationToken) { try { - var data = await _db.Roles.Select(s => new {Id=s.Name,s.Name}).ToListAsync(); - + var data = await _commonService.GetRolesAsync(cancellationToken); + return Ok(data); } catch (Exception ex) @@ -96,41 +72,26 @@ namespace Api.SeaHavenIndustries.Controllers [HttpGet] [Route("GetSitesName")] - public async Task GetSiteName() + public async Task GetSiteName(CancellationToken cancellationToken) { - var data =await _db.Locations.Select(n=> new - { - n.Id, - n.Name, - n.Title - }).ToListAsync(); + var data = await _commonService.GetSiteNamesAsync(cancellationToken); return Ok(data); } [HttpGet] [Route("GetContacts")] - public async Task GetContacts() + public async Task GetContacts(CancellationToken cancellationToken) { - var data =await _db.Contacts.Select(n=> new - { - n.Id, - Name = (n.FirstName + " " + n.LastName).Trim(), - email=n.Email, - phone=n.PhoneNumber - }).ToListAsync(); + var data = await _commonService.GetContactsAsync(cancellationToken); return Ok(data); } [HttpGet] [Route("GetCategories")] - public async Task GetCategories() + public async Task GetCategories(CancellationToken cancellationToken) { - var data = await _db.Categories.Select(s=> new - { - s.Id, - s.Name - }).ToListAsync(); + var data = await _commonService.GetCategoriesAsync(cancellationToken); return Ok(data); - } + } } } diff --git a/Api.SeaHavenIndustries/Controllers/DashboardController.cs b/Api.SeaHavenIndustries/Controllers/DashboardController.cs index b548af6..6955cc2 100644 --- a/Api.SeaHavenIndustries/Controllers/DashboardController.cs +++ b/Api.SeaHavenIndustries/Controllers/DashboardController.cs @@ -1,7 +1,6 @@ -using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.Interfaces; namespace Api.SeaHavenIndustries.Controllers { @@ -10,29 +9,24 @@ namespace Api.SeaHavenIndustries.Controllers [Route("api/[controller]")] public class DashboardController : Controller { - private readonly ApplicationDbContext _db; + private readonly IDashboardService _dashboardService; - public DashboardController(ApplicationDbContext db) + public DashboardController(IDashboardService dashboardService) { - _db = db; + _dashboardService = dashboardService; } [HttpGet("Stats")] - public async Task GetStats() + public async Task GetStats(CancellationToken cancellationToken) { - var query = _db.workOrders.Where(w => w.istemplate != true); - - var total = await query.CountAsync(); - var open = await query.CountAsync(w => w.Status == "Open" || w.Status == "In Progress" || w.Status == "On Hold"); - var notDispatched = await query.CountAsync(w => (w.Status == "Open") && (w.AssignTo == null || w.AssignTo == "")); - var completed = await query.CountAsync(w => w.Status == "Done"); + var stats = await _dashboardService.GetStatsAsync(cancellationToken); return Ok(new { - total, - open, - notDispatched, - completed + total = stats.Total, + open = stats.Open, + notDispatched = stats.NotDispatched, + completed = stats.Completed }); } } diff --git a/Api.SeaHavenIndustries/Controllers/DropdownOptionsController.cs b/Api.SeaHavenIndustries/Controllers/DropdownOptionsController.cs index 103ea27..1085c5d 100644 --- a/Api.SeaHavenIndustries/Controllers/DropdownOptionsController.cs +++ b/Api.SeaHavenIndustries/Controllers/DropdownOptionsController.cs @@ -1,7 +1,8 @@ -using Data.SeaHavenIndustries; +using Api.SeaHavenIndustries.DTOs; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; namespace Api.SeaHavenIndustries.Controllers { @@ -10,78 +11,80 @@ namespace Api.SeaHavenIndustries.Controllers [Route("api/[controller]")] public class DropdownOptionsController : Controller { - private readonly ApplicationDbContext _db; + private readonly IDropdownOptionsService _dropdownOptionsService; - public DropdownOptionsController(ApplicationDbContext db) + public DropdownOptionsController(IDropdownOptionsService dropdownOptionsService) { - _db = db; + _dropdownOptionsService = dropdownOptionsService; } [HttpGet] - public async Task GetAll([FromQuery] string? category = null) + public async Task GetAll([FromQuery] string? category = null, CancellationToken cancellationToken = default) { - var query = _db.DropdownOptions.Where(d => d.IsActive); - if (!string.IsNullOrWhiteSpace(category)) - query = query.Where(d => d.Category == category); - - var data = await query.OrderBy(d => d.Category).ThenBy(d => d.SortOrder).ThenBy(d => d.Value).ToListAsync(); + var data = await _dropdownOptionsService.GetAllAsync(category, cancellationToken); return Ok(data); } [HttpGet("ByCategory/{category}")] - public async Task GetByCategory(string category, [FromQuery] string? parent = null) + public async Task GetByCategory(string category, [FromQuery] string? parent = null, CancellationToken cancellationToken = default) { - var query = _db.DropdownOptions.Where(d => d.IsActive && d.Category == category); - if (!string.IsNullOrWhiteSpace(parent)) - query = query.Where(d => d.ParentValue == parent); - - var data = await query.OrderBy(d => d.SortOrder).ThenBy(d => d.Value).ToListAsync(); + var data = await _dropdownOptionsService.GetByCategoryAsync(category, parent, cancellationToken); return Ok(data); } [HttpPost] - public async Task Create([FromBody] DropdownOption model) + public async Task Create([FromBody] DropdownOption_DTO model, CancellationToken cancellationToken) { - if (string.IsNullOrWhiteSpace(model.Category) || string.IsNullOrWhiteSpace(model.Value)) - return BadRequest(new { message = "Category and Value are required" }); - - var existing = await _db.DropdownOptions - .AnyAsync(d => d.Category == model.Category && d.Value == model.Value && d.ParentValue == model.ParentValue); - if (existing) - return BadRequest(new { message = "This option already exists" }); - - model.Id = 0; - model.IsActive = true; - _db.DropdownOptions.Add(model); - await _db.SaveChangesAsync(); - return Ok(model); + try + { + var created = await _dropdownOptionsService.CreateAsync(MapToService(model), cancellationToken); + return Ok(created); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } } [HttpPut("{id}")] - public async Task Update(int id, [FromBody] DropdownOption model) + public async Task Update(int id, [FromBody] DropdownOption_DTO model, CancellationToken cancellationToken) { - var item = await _db.DropdownOptions.FindAsync(id); - if (item == null) + try + { + var updated = await _dropdownOptionsService.UpdateAsync(id, MapToService(model), cancellationToken); + return Ok(updated); + } + catch (KeyNotFoundException) + { return NotFound(new { message = "Option not found" }); - - item.Value = model.Value; - item.ParentValue = model.ParentValue; - item.SortOrder = model.SortOrder; - item.IsActive = model.IsActive; - await _db.SaveChangesAsync(); - return Ok(item); + } } [HttpDelete("{id}")] - public async Task Delete(int id) + public async Task Delete(int id, CancellationToken cancellationToken) { - var item = await _db.DropdownOptions.FindAsync(id); - if (item == null) + try + { + await _dropdownOptionsService.DeleteAsync(id, cancellationToken); + return Ok(new { message = "Deleted" }); + } + catch (KeyNotFoundException) + { return NotFound(new { message = "Option not found" }); + } + } - _db.DropdownOptions.Remove(item); - await _db.SaveChangesAsync(); - return Ok(new { message = "Deleted" }); + private static DropdownOptionDTO MapToService(DropdownOption_DTO model) + { + return new DropdownOptionDTO + { + Id = model.Id, + Category = model.Category, + Value = model.Value, + ParentValue = model.ParentValue, + SortOrder = model.SortOrder, + IsActive = model.IsActive + }; } } } diff --git a/Api.SeaHavenIndustries/Controllers/TaskListTemplateController.cs b/Api.SeaHavenIndustries/Controllers/TaskListTemplateController.cs index d424c9a..b4b62c4 100644 --- a/Api.SeaHavenIndustries/Controllers/TaskListTemplateController.cs +++ b/Api.SeaHavenIndustries/Controllers/TaskListTemplateController.cs @@ -2,7 +2,8 @@ using Api.SeaHavenIndustries.DTOs; using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; namespace Api.SeaHavenIndustries.Controllers { @@ -12,57 +13,24 @@ namespace Api.SeaHavenIndustries.Controllers [Route("api/tasklisttemplates")] public class TaskListTemplateController : Controller { - private readonly ApplicationDbContext _db; + private readonly ITaskListTemplateService _taskListTemplateService; - public TaskListTemplateController(ApplicationDbContext db) + public TaskListTemplateController(ITaskListTemplateService taskListTemplateService) { - _db = db; + _taskListTemplateService = taskListTemplateService; } [HttpGet] - public async Task GetAll() + public async Task GetAll(CancellationToken cancellationToken) { - var data = await _db.TaskListTemplates - .Where(t => t.IsActive) - .Include(t => t.Items.OrderBy(i => i.SortOrder)) - .OrderBy(t => t.Name) - .Select(t => new - { - t.Id, - t.Name, - t.Description, - t.IsActive, - items = t.Items.OrderBy(i => i.SortOrder).Select(i => new - { - i.Id, - i.ItemText, - i.SortOrder - }).ToList() - }) - .ToListAsync(); + var data = await _taskListTemplateService.GetAllAsync(cancellationToken); return Ok(data); } [HttpGet("{id}")] - public async Task GetById(int id) + public async Task GetById(int id, CancellationToken cancellationToken) { - var template = await _db.TaskListTemplates - .Include(t => t.Items.OrderBy(i => i.SortOrder)) - .Where(t => t.Id == id) - .Select(t => new - { - t.Id, - t.Name, - t.Description, - t.IsActive, - items = t.Items.OrderBy(i => i.SortOrder).Select(i => new - { - i.Id, - i.ItemText, - i.SortOrder - }).ToList() - }) - .FirstOrDefaultAsync(); + var template = await _taskListTemplateService.GetByIdAsync(id, cancellationToken); if (template == null) return NotFound(new Response { Status = "Error", Message = "Template not found" }); @@ -71,25 +39,13 @@ namespace Api.SeaHavenIndustries.Controllers } [HttpPost] - public async Task Create([FromBody] TaskListTemplate_DTO model) + public async Task Create([FromBody] TaskListTemplate_DTO model, CancellationToken cancellationToken) { try { - var template = new TaskListTemplate - { - Name = model.Name, - Description = model.Description, - IsActive = model.IsActive, - Items = model.Items.Select((item, idx) => new TaskListTemplateItem - { - ItemText = item.ItemText, - SortOrder = item.SortOrder > 0 ? item.SortOrder : idx - }).ToList() - }; - - _db.TaskListTemplates.Add(template); - await _db.SaveChangesAsync(); - return Ok(new { message = "Template created", id = template.Id }); + var input = MapToInput(model); + var id = await _taskListTemplateService.CreateAsync(input, cancellationToken); + return Ok(new { message = "Template created", id }); } catch (Exception ex) { @@ -98,31 +54,15 @@ namespace Api.SeaHavenIndustries.Controllers } [HttpPut("{id}")] - public async Task Update(int id, [FromBody] TaskListTemplate_DTO model) + public async Task Update(int id, [FromBody] TaskListTemplate_DTO model, CancellationToken cancellationToken) { try { - var template = await _db.TaskListTemplates - .Include(t => t.Items) - .FirstOrDefaultAsync(t => t.Id == id); - - if (template == null) + var input = MapToInput(model); + var updated = await _taskListTemplateService.UpdateAsync(id, input, cancellationToken); + if (!updated) return NotFound(new Response { Status = "Error", Message = "Template not found" }); - template.Name = model.Name; - template.Description = model.Description; - template.IsActive = model.IsActive; - - _db.TaskListTemplateItems.RemoveRange(template.Items); - - template.Items = model.Items.Select((item, idx) => new TaskListTemplateItem - { - TaskListTemplateId = id, - ItemText = item.ItemText, - SortOrder = item.SortOrder > 0 ? item.SortOrder : idx - }).ToList(); - - await _db.SaveChangesAsync(); return Ok(new { message = "Template updated" }); } catch (Exception ex) @@ -132,15 +72,28 @@ namespace Api.SeaHavenIndustries.Controllers } [HttpDelete("{id}")] - public async Task Delete(int id) + public async Task Delete(int id, CancellationToken cancellationToken) { - var template = await _db.TaskListTemplates.FindAsync(id); - if (template == null) + var deleted = await _taskListTemplateService.DeleteAsync(id, cancellationToken); + if (!deleted) return NotFound(new Response { Status = "Error", Message = "Template not found" }); - template.IsActive = false; - await _db.SaveChangesAsync(); return Ok(new { message = "Template deleted" }); } + + private static TaskListTemplateInput MapToInput(TaskListTemplate_DTO model) + { + return new TaskListTemplateInput + { + Name = model.Name, + Description = model.Description, + IsActive = model.IsActive, + Items = model.Items.Select(item => new TaskListTemplateInputItem + { + ItemText = item.ItemText, + SortOrder = item.SortOrder + }).ToList() + }; + } } } diff --git a/Api.SeaHavenIndustries/DTOs/DropdownOption_DTO.cs b/Api.SeaHavenIndustries/DTOs/DropdownOption_DTO.cs new file mode 100644 index 0000000..59dd4c6 --- /dev/null +++ b/Api.SeaHavenIndustries/DTOs/DropdownOption_DTO.cs @@ -0,0 +1,12 @@ +namespace Api.SeaHavenIndustries.DTOs +{ + public class DropdownOption_DTO + { + public int Id { get; set; } + public string Category { get; set; } = ""; + public string Value { get; set; } = ""; + public string? ParentValue { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; + } +} diff --git a/SeaHaven.DataServices/Implementation/CalendarDataService.cs b/SeaHaven.DataServices/Implementation/CalendarDataService.cs new file mode 100644 index 0000000..105ecb0 --- /dev/null +++ b/SeaHaven.DataServices/Implementation/CalendarDataService.cs @@ -0,0 +1,85 @@ +using Data.SeaHavenIndustries; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Interfaces; + +namespace SeaHaven.DataServices.Implementation +{ + public class CalendarDataService : ICalendarDataService + { + private readonly ApplicationDbContext _context; + + public CalendarDataService(ApplicationDbContext context) + { + _context = context; + } + + public async Task GetEventByIdAsync(int id, CancellationToken cancellationToken) + { + return await _context.Events + .AsNoTracking() + .Where(e => e.Id == id && e.IsDeleted != true) + .FirstOrDefaultAsync(cancellationToken); + } + + public async Task GetEventForEditAsync(int id, CancellationToken cancellationToken) + { + return await _context.Events.FindAsync(new object[] { id }, cancellationToken); + } + + public async Task AddEventAsync(Events ev, CancellationToken cancellationToken) + { + await _context.Events.AddAsync(ev, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return ev; + } + + public async Task UpdateEventAsync(Events ev, CancellationToken cancellationToken) + { + _context.Events.Update(ev); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task<(IEnumerable Items, int TotalCount)> GetEventListPagedAsync(int page, int pageSize, string? search, CancellationToken cancellationToken) + { + var query = _context.Events.Where(e => e.IsDeleted != true); + + 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))); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var events = await query + .OrderByDescending(e => e.StartDate) + .ThenByDescending(e => e.CreatedDate) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .AsNoTracking() + .ToListAsync(cancellationToken); + + return (events, totalCount); + } + + public async Task> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken) + { + var query = _context.Events.Where(e => e.IsDeleted != true); + + 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)); + } + + return await query + .AsNoTracking() + .ToListAsync(cancellationToken); + } + } +} diff --git a/SeaHaven.DataServices/Implementation/CommonDataService.cs b/SeaHaven.DataServices/Implementation/CommonDataService.cs new file mode 100644 index 0000000..b263155 --- /dev/null +++ b/SeaHaven.DataServices/Implementation/CommonDataService.cs @@ -0,0 +1,81 @@ +using Data.SeaHavenIndustries; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Interfaces; + +namespace SeaHaven.DataServices.Implementation +{ + public class CommonDataService : ICommonDataService + { + private readonly ApplicationDbContext _context; + + public CommonDataService(ApplicationDbContext context) + { + _context = context; + } + + public async Task<(IEnumerable Items, int TotalCount)> GetUsersPagedAsync(int page, int pageSize, string search, CancellationToken cancellationToken) + { + var query = _context.Users + .Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower())) + .OrderByDescending(d => d.Id); + + var totalCount = await query.CountAsync(cancellationToken); + + var paginatedUsers = await query + .Skip((page - 1) * pageSize) + .Take(pageSize) + .AsNoTracking() + .ToListAsync(cancellationToken); + + return (paginatedUsers, totalCount); + } + + public async Task> GetRoleNamesAsync(CancellationToken cancellationToken) + { + return await _context.Roles + .AsNoTracking() + .Select(s => s.Name) + .ToListAsync(cancellationToken); + } + + public async Task> GetSiteNamesAsync(CancellationToken cancellationToken) + { + return await _context.Locations + .AsNoTracking() + .Select(n => new Locations + { + Id = n.Id, + Name = n.Name, + Title = n.Title + }) + .ToListAsync(cancellationToken); + } + + public async Task> GetContactsAsync(CancellationToken cancellationToken) + { + return await _context.Contacts + .AsNoTracking() + .Select(n => new Contacts + { + Id = n.Id, + FirstName = n.FirstName, + LastName = n.LastName, + Email = n.Email, + PhoneNumber = n.PhoneNumber + }) + .ToListAsync(cancellationToken); + } + + public async Task> GetCategoriesAsync(CancellationToken cancellationToken) + { + return await _context.Categories + .AsNoTracking() + .Select(s => new Category + { + Id = s.Id, + Name = s.Name + }) + .ToListAsync(cancellationToken); + } + } +} diff --git a/SeaHaven.DataServices/Implementation/DashboardDataService.cs b/SeaHaven.DataServices/Implementation/DashboardDataService.cs new file mode 100644 index 0000000..d1e29c3 --- /dev/null +++ b/SeaHaven.DataServices/Implementation/DashboardDataService.cs @@ -0,0 +1,34 @@ +using Data.SeaHavenIndustries; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Interfaces; + +namespace SeaHaven.DataServices.Implementation +{ + public class DashboardDataService : IDashboardDataService + { + private readonly ApplicationDbContext _context; + + public DashboardDataService(ApplicationDbContext context) + { + _context = context; + } + + public async Task GetWorkOrderCountsAsync(CancellationToken cancellationToken) + { + var query = _context.workOrders.Where(w => w.istemplate != true); + + var total = await query.CountAsync(cancellationToken); + var open = await query.CountAsync(w => w.Status == "Open" || w.Status == "In Progress" || w.Status == "On Hold", cancellationToken); + var notDispatched = await query.CountAsync(w => (w.Status == "Open") && (w.AssignTo == null || w.AssignTo == ""), cancellationToken); + var completed = await query.CountAsync(w => w.Status == "Done", cancellationToken); + + return new DashboardCounts + { + Total = total, + Open = open, + NotDispatched = notDispatched, + Completed = completed + }; + } + } +} diff --git a/SeaHaven.DataServices/Implementation/DropdownOptionsDataService.cs b/SeaHaven.DataServices/Implementation/DropdownOptionsDataService.cs new file mode 100644 index 0000000..593dd8b --- /dev/null +++ b/SeaHaven.DataServices/Implementation/DropdownOptionsDataService.cs @@ -0,0 +1,71 @@ +using Data.SeaHavenIndustries; +using Microsoft.EntityFrameworkCore; +using SeaHaven.DataServices.Interfaces; + +namespace SeaHaven.DataServices.Implementation +{ + public class DropdownOptionsDataService : IDropdownOptionsDataService + { + private readonly ApplicationDbContext _context; + + public DropdownOptionsDataService(ApplicationDbContext context) + { + _context = context; + } + + public async Task> GetAllActiveAsync(string? category, CancellationToken cancellationToken) + { + var query = _context.DropdownOptions.Where(d => d.IsActive); + if (!string.IsNullOrWhiteSpace(category)) + query = query.Where(d => d.Category == category); + + return await query + .AsNoTracking() + .OrderBy(d => d.Category).ThenBy(d => d.SortOrder).ThenBy(d => d.Value) + .ToListAsync(cancellationToken); + } + + public async Task> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken) + { + var query = _context.DropdownOptions.Where(d => d.IsActive && d.Category == category); + if (!string.IsNullOrWhiteSpace(parent)) + query = query.Where(d => d.ParentValue == parent); + + return await query + .AsNoTracking() + .OrderBy(d => d.SortOrder).ThenBy(d => d.Value) + .ToListAsync(cancellationToken); + } + + public async Task ExistsAsync(string category, string value, string? parentValue, CancellationToken cancellationToken) + { + return await _context.DropdownOptions + .AsNoTracking() + .AnyAsync(d => d.Category == category && d.Value == value && d.ParentValue == parentValue, cancellationToken); + } + + public async Task AddAsync(DropdownOption option, CancellationToken cancellationToken) + { + await _context.DropdownOptions.AddAsync(option, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return option; + } + + public async Task GetByIdAsync(int id, CancellationToken cancellationToken) + { + return await _context.DropdownOptions.FindAsync(new object[] { id }, cancellationToken); + } + + public async Task UpdateAsync(DropdownOption option, CancellationToken cancellationToken) + { + _context.DropdownOptions.Update(option); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task RemoveAsync(DropdownOption option, CancellationToken cancellationToken) + { + _context.DropdownOptions.Remove(option); + await _context.SaveChangesAsync(cancellationToken); + } + } +} diff --git a/SeaHaven.DataServices/Implementation/TaskListTemplateDataService.cs b/SeaHaven.DataServices/Implementation/TaskListTemplateDataService.cs index f0ce1af..a5ec17d 100644 --- a/SeaHaven.DataServices/Implementation/TaskListTemplateDataService.cs +++ b/SeaHaven.DataServices/Implementation/TaskListTemplateDataService.cs @@ -57,5 +57,58 @@ namespace SeaHaven.DataServices.Implementation { return await _context.TaskListTemplates.AnyAsync(t => t.Id == id); } + + public async Task> GetActiveTemplatesWithItemsAsync(CancellationToken cancellationToken) + { + return await _context.TaskListTemplates + .Include(t => t.Items) + .Where(t => t.IsActive) + .OrderBy(t => t.Name) + .AsNoTracking() + .ToListAsync(cancellationToken); + } + + public async Task GetTemplateWithItemsReadOnlyAsync(int id, CancellationToken cancellationToken) + { + return await _context.TaskListTemplates + .Include(t => t.Items) + .Where(t => t.Id == id) + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); + } + + public async Task GetTemplateWithItemsForUpdateAsync(int id, CancellationToken cancellationToken) + { + return await _context.TaskListTemplates + .Include(t => t.Items) + .FirstOrDefaultAsync(t => t.Id == id, cancellationToken); + } + + public async Task AddTemplateAsync(TaskListTemplate template, CancellationToken cancellationToken) + { + await _context.TaskListTemplates.AddAsync(template, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return template; + } + + public async Task ReplaceTemplateItemsAsync(TaskListTemplate template, IEnumerable newItems, CancellationToken cancellationToken) + { + if (template.Items != null) + _context.TaskListTemplateItems.RemoveRange(template.Items); + + template.Items = newItems.ToList(); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task DeactivateTemplateAsync(int id, CancellationToken cancellationToken) + { + var template = await _context.TaskListTemplates.FindAsync(new object[] { id }, cancellationToken); + if (template == null) + return false; + + template.IsActive = false; + await _context.SaveChangesAsync(cancellationToken); + return true; + } } } diff --git a/SeaHaven.DataServices/Interfaces/ICalendarDataService.cs b/SeaHaven.DataServices/Interfaces/ICalendarDataService.cs new file mode 100644 index 0000000..7797bc2 --- /dev/null +++ b/SeaHaven.DataServices/Interfaces/ICalendarDataService.cs @@ -0,0 +1,14 @@ +using Data.SeaHavenIndustries; + +namespace SeaHaven.DataServices.Interfaces +{ + public interface ICalendarDataService + { + Task GetEventByIdAsync(int id, CancellationToken cancellationToken); + Task GetEventForEditAsync(int id, CancellationToken cancellationToken); + Task AddEventAsync(Events ev, CancellationToken cancellationToken); + Task UpdateEventAsync(Events ev, CancellationToken cancellationToken); + Task<(IEnumerable Items, int TotalCount)> GetEventListPagedAsync(int page, int pageSize, string? search, CancellationToken cancellationToken); + Task> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.DataServices/Interfaces/ICommonDataService.cs b/SeaHaven.DataServices/Interfaces/ICommonDataService.cs new file mode 100644 index 0000000..8d36d68 --- /dev/null +++ b/SeaHaven.DataServices/Interfaces/ICommonDataService.cs @@ -0,0 +1,13 @@ +using Data.SeaHavenIndustries; + +namespace SeaHaven.DataServices.Interfaces +{ + public interface ICommonDataService + { + Task<(IEnumerable Items, int TotalCount)> GetUsersPagedAsync(int page, int pageSize, string search, CancellationToken cancellationToken); + Task> GetRoleNamesAsync(CancellationToken cancellationToken); + Task> GetSiteNamesAsync(CancellationToken cancellationToken); + Task> GetContactsAsync(CancellationToken cancellationToken); + Task> GetCategoriesAsync(CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.DataServices/Interfaces/IDashboardDataService.cs b/SeaHaven.DataServices/Interfaces/IDashboardDataService.cs new file mode 100644 index 0000000..7acb41f --- /dev/null +++ b/SeaHaven.DataServices/Interfaces/IDashboardDataService.cs @@ -0,0 +1,15 @@ +namespace SeaHaven.DataServices.Interfaces +{ + public interface IDashboardDataService + { + Task GetWorkOrderCountsAsync(CancellationToken cancellationToken); + } + + public class DashboardCounts + { + public int Total { get; set; } + public int Open { get; set; } + public int NotDispatched { get; set; } + public int Completed { get; set; } + } +} diff --git a/SeaHaven.DataServices/Interfaces/IDropdownOptionsDataService.cs b/SeaHaven.DataServices/Interfaces/IDropdownOptionsDataService.cs new file mode 100644 index 0000000..063dcae --- /dev/null +++ b/SeaHaven.DataServices/Interfaces/IDropdownOptionsDataService.cs @@ -0,0 +1,15 @@ +using Data.SeaHavenIndustries; + +namespace SeaHaven.DataServices.Interfaces +{ + public interface IDropdownOptionsDataService + { + Task> GetAllActiveAsync(string? category, CancellationToken cancellationToken); + Task> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken); + Task ExistsAsync(string category, string value, string? parentValue, CancellationToken cancellationToken); + Task AddAsync(DropdownOption option, CancellationToken cancellationToken); + Task GetByIdAsync(int id, CancellationToken cancellationToken); + Task UpdateAsync(DropdownOption option, CancellationToken cancellationToken); + Task RemoveAsync(DropdownOption option, CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.DataServices/Interfaces/ITaskListTemplateDataService.cs b/SeaHaven.DataServices/Interfaces/ITaskListTemplateDataService.cs index ac763d3..0c63709 100644 --- a/SeaHaven.DataServices/Interfaces/ITaskListTemplateDataService.cs +++ b/SeaHaven.DataServices/Interfaces/ITaskListTemplateDataService.cs @@ -11,5 +11,13 @@ namespace SeaHaven.DataServices.Interfaces Task UpdateAsync(TaskListTemplate template); Task DeleteAsync(int id); Task ExistsAsync(int id); + + // Cancelable query/command operations used by the TaskListTemplate service layer + Task> GetActiveTemplatesWithItemsAsync(CancellationToken cancellationToken); + Task GetTemplateWithItemsReadOnlyAsync(int id, CancellationToken cancellationToken); + Task GetTemplateWithItemsForUpdateAsync(int id, CancellationToken cancellationToken); + Task AddTemplateAsync(TaskListTemplate template, CancellationToken cancellationToken); + Task ReplaceTemplateItemsAsync(TaskListTemplate template, IEnumerable newItems, CancellationToken cancellationToken); + Task DeactivateTemplateAsync(int id, CancellationToken cancellationToken); } } diff --git a/SeaHaven.Services/DTOs/CalendarDTOs.cs b/SeaHaven.Services/DTOs/CalendarDTOs.cs new file mode 100644 index 0000000..5e5b1c5 --- /dev/null +++ b/SeaHaven.Services/DTOs/CalendarDTOs.cs @@ -0,0 +1,50 @@ +namespace SeaHaven.Services.DTOs +{ + public class EventDTO + { + 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; } + } + + public class PagedEventsResult + { + public List Rows { get; set; } = new(); + public int TotalCount { get; set; } + } + + public class EditEventInput + { + 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; } + } +} diff --git a/SeaHaven.Services/DTOs/CommonDTOs.cs b/SeaHaven.Services/DTOs/CommonDTOs.cs new file mode 100644 index 0000000..639da7d --- /dev/null +++ b/SeaHaven.Services/DTOs/CommonDTOs.cs @@ -0,0 +1,48 @@ +namespace SeaHaven.Services.DTOs +{ + public class UserListItemDTO + { + public string? Id { get; set; } + public string? UserName { get; set; } + public string? Email { get; set; } + public string? RoleName { get; set; } + public string? Status { get; set; } + public string? Date { get; set; } + } + + public class PagedUsersResult + { + public List Users { get; set; } = new(); + public int PageNumber { get; set; } + public int PageSize { get; set; } + public int TotalCount { get; set; } + public int TotalPages { get; set; } + } + + public class RoleListItemDTO + { + public string? Id { get; set; } + public string? Name { get; set; } + } + + public class SiteNameDTO + { + public int Id { get; set; } + public string? Name { get; set; } + public string? Title { get; set; } + } + + public class ContactListItemDTO + { + public int Id { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + public string? Phone { get; set; } + } + + public class CategoryListItemDTO + { + public int Id { get; set; } + public string? Name { get; set; } + } +} diff --git a/SeaHaven.Services/DTOs/DashboardDTOs.cs b/SeaHaven.Services/DTOs/DashboardDTOs.cs new file mode 100644 index 0000000..6bac68c --- /dev/null +++ b/SeaHaven.Services/DTOs/DashboardDTOs.cs @@ -0,0 +1,10 @@ +namespace SeaHaven.Services.DTOs +{ + public class DashboardStatsDTO + { + public int Total { get; set; } + public int Open { get; set; } + public int NotDispatched { get; set; } + public int Completed { get; set; } + } +} diff --git a/SeaHaven.Services/DTOs/DropdownOptionDTOs.cs b/SeaHaven.Services/DTOs/DropdownOptionDTOs.cs new file mode 100644 index 0000000..754a7f7 --- /dev/null +++ b/SeaHaven.Services/DTOs/DropdownOptionDTOs.cs @@ -0,0 +1,12 @@ +namespace SeaHaven.Services.DTOs +{ + public class DropdownOptionDTO + { + public int Id { get; set; } + public string Category { get; set; } = ""; + public string Value { get; set; } = ""; + public string? ParentValue { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; + } +} diff --git a/SeaHaven.Services/DTOs/TaskListTemplateDTOs.cs b/SeaHaven.Services/DTOs/TaskListTemplateDTOs.cs new file mode 100644 index 0000000..9057525 --- /dev/null +++ b/SeaHaven.Services/DTOs/TaskListTemplateDTOs.cs @@ -0,0 +1,32 @@ +namespace SeaHaven.Services.DTOs +{ + public class TaskListTemplateDTO + { + public int Id { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } + public bool IsActive { get; set; } + public List Items { get; set; } = new(); + } + + public class TaskListTemplateItemDTO + { + public int Id { get; set; } + public string? ItemText { get; set; } + public int SortOrder { get; set; } + } + + public class TaskListTemplateInput + { + public string? Name { get; set; } + public string? Description { get; set; } + public bool IsActive { get; set; } = true; + public List Items { get; set; } = new(); + } + + public class TaskListTemplateInputItem + { + public string? ItemText { get; set; } + public int SortOrder { get; set; } + } +} diff --git a/SeaHaven.Services/Implementation/CalendarService.cs b/SeaHaven.Services/Implementation/CalendarService.cs new file mode 100644 index 0000000..cece13d --- /dev/null +++ b/SeaHaven.Services/Implementation/CalendarService.cs @@ -0,0 +1,125 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + public class CalendarService : ICalendarService + { + private readonly ICalendarDataService _dataService; + + public CalendarService(ICalendarDataService dataService) + { + _dataService = dataService; + } + + public async Task CreateEventAsync(EditEventInput input, CancellationToken cancellationToken) + { + var model = new Events + { + Title = input.Title, + Description = input.Description, + Location = input.Location, + RecordID = input.RecordID, + RecordName = input.RecordName, + RecordTargetType = input.RecordTargetType, + ReminderMinutes = input.ReminderMinutes, + EventColor = input.EventColor, + StartDate = input.StartDate, + StartTime = input.StartTime, + EndDate = input.EndDate, + EndTime = input.EndTime, + AllDay = input.AllDay, + CreatedDate = DateTime.Now, + IsDeleted = false + }; + + await _dataService.AddEventAsync(model, cancellationToken); + } + + public async Task UpdateEventAsync(EditEventInput input, CancellationToken cancellationToken) + { + var model = await _dataService.GetEventForEditAsync(input.Id!.Value, cancellationToken); + if (model == null || model.IsDeleted == true) + return false; + + model.Title = input.Title; + model.Description = input.Description; + model.Location = input.Location; + model.RecordID = input.RecordID; + model.RecordName = input.RecordName; + model.RecordTargetType = input.RecordTargetType; + model.ReminderMinutes = input.ReminderMinutes; + model.EventColor = input.EventColor; + model.StartDate = input.StartDate; + model.StartTime = input.StartTime; + model.EndDate = input.EndDate; + model.EndTime = input.EndTime; + model.AllDay = input.AllDay; + model.LastModificationTime = DateTime.Now; + + await _dataService.UpdateEventAsync(model, cancellationToken); + return true; + } + + public async Task GetEventByIdAsync(int id, CancellationToken cancellationToken) + { + var model = await _dataService.GetEventByIdAsync(id, cancellationToken); + return model == null ? null : MapToDTO(model); + } + + public async Task DeleteEventAsync(int id, CancellationToken cancellationToken) + { + var model = await _dataService.GetEventForEditAsync(id, cancellationToken); + if (model == null) + return false; + + model.IsDeleted = true; + model.DeletionTime = DateTime.Now; + + await _dataService.UpdateEventAsync(model, cancellationToken); + return true; + } + + public async Task GetEventListAsync(string? search, int page, int pageSize, CancellationToken cancellationToken) + { + var (items, totalCount) = await _dataService.GetEventListPagedAsync(page, pageSize, search, cancellationToken); + + return new PagedEventsResult + { + Rows = items.Select(MapToDTO).ToList(), + TotalCount = totalCount + }; + } + + public async Task> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken) + { + var events = await _dataService.GetEventsForCalendarAsync(start, end, cancellationToken); + return events.Select(MapToDTO); + } + + private static EventDTO MapToDTO(Events model) + { + return new EventDTO + { + 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/CommonService.cs b/SeaHaven.Services/Implementation/CommonService.cs new file mode 100644 index 0000000..4cb0aa2 --- /dev/null +++ b/SeaHaven.Services/Implementation/CommonService.cs @@ -0,0 +1,85 @@ +using Data.SeaHavenIndustries; +using Microsoft.AspNetCore.Identity; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + public class CommonService : ICommonService + { + private readonly ICommonDataService _dataService; + private readonly UserManager _userManager; + + public CommonService(ICommonDataService dataService, UserManager userManager) + { + _dataService = dataService; + _userManager = userManager; + } + + public async Task GetUsersPagedAsync(string? search, int page, int pageSize, CancellationToken cancellationToken) + { + var (paginatedUsers, totalCount) = await _dataService.GetUsersPagedAsync(page, pageSize, search ?? "", cancellationToken); + + var filteredUsers = new List(); + + foreach (var user in paginatedUsers) + { + var roles = await _userManager.GetRolesAsync(user); + if (!roles.Contains("Admin")) + { + filteredUsers.Add(new UserListItemDTO + { + RoleName = roles.Count() > 0 ? roles.First() : "", + Email = user.Email, + UserName = user.FirstName, + Date = user.CreatedDate?.Date.ToString(), + Status = user.UniqueName, + Id = user.Id + }); + } + } + + var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize); + + return new PagedUsersResult + { + Users = filteredUsers, + PageNumber = page, + PageSize = pageSize, + TotalCount = totalCount, + TotalPages = totalPages + }; + } + + public async Task> GetRolesAsync(CancellationToken cancellationToken) + { + var names = await _dataService.GetRoleNamesAsync(cancellationToken); + return names.Select(name => new RoleListItemDTO { Id = name, Name = name }); + } + + public async Task> GetSiteNamesAsync(CancellationToken cancellationToken) + { + var sites = await _dataService.GetSiteNamesAsync(cancellationToken); + return sites.Select(n => new SiteNameDTO { Id = n.Id, Name = n.Name, Title = n.Title }); + } + + public async Task> GetContactsAsync(CancellationToken cancellationToken) + { + var contacts = await _dataService.GetContactsAsync(cancellationToken); + return contacts.Select(n => new ContactListItemDTO + { + Id = n.Id, + Name = (n.FirstName + " " + n.LastName).Trim(), + Email = n.Email, + Phone = n.PhoneNumber + }); + } + + public async Task> GetCategoriesAsync(CancellationToken cancellationToken) + { + var categories = await _dataService.GetCategoriesAsync(cancellationToken); + return categories.Select(s => new CategoryListItemDTO { Id = s.Id, Name = s.Name }); + } + } +} diff --git a/SeaHaven.Services/Implementation/DashboardService.cs b/SeaHaven.Services/Implementation/DashboardService.cs new file mode 100644 index 0000000..0bec513 --- /dev/null +++ b/SeaHaven.Services/Implementation/DashboardService.cs @@ -0,0 +1,29 @@ +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + public class DashboardService : IDashboardService + { + private readonly IDashboardDataService _dataService; + + public DashboardService(IDashboardDataService dataService) + { + _dataService = dataService; + } + + public async Task GetStatsAsync(CancellationToken cancellationToken) + { + var counts = await _dataService.GetWorkOrderCountsAsync(cancellationToken); + + return new DashboardStatsDTO + { + Total = counts.Total, + Open = counts.Open, + NotDispatched = counts.NotDispatched, + Completed = counts.Completed + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/DropdownOptionsService.cs b/SeaHaven.Services/Implementation/DropdownOptionsService.cs new file mode 100644 index 0000000..6963272 --- /dev/null +++ b/SeaHaven.Services/Implementation/DropdownOptionsService.cs @@ -0,0 +1,88 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + public class DropdownOptionsService : IDropdownOptionsService + { + private readonly IDropdownOptionsDataService _dataService; + + public DropdownOptionsService(IDropdownOptionsDataService dataService) + { + _dataService = dataService; + } + + public async Task> GetAllAsync(string? category, CancellationToken cancellationToken) + { + var data = await _dataService.GetAllActiveAsync(category, cancellationToken); + return data.Select(MapToDTO); + } + + public async Task> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken) + { + var data = await _dataService.GetByCategoryAsync(category, parent, cancellationToken); + return data.Select(MapToDTO); + } + + public async Task CreateAsync(DropdownOptionDTO option, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(option.Category) || string.IsNullOrWhiteSpace(option.Value)) + throw new InvalidOperationException("Category and Value are required"); + + if (await _dataService.ExistsAsync(option.Category, option.Value, option.ParentValue, cancellationToken)) + throw new InvalidOperationException("This option already exists"); + + var entity = new DropdownOption + { + Id = 0, + Category = option.Category, + Value = option.Value, + ParentValue = option.ParentValue, + SortOrder = option.SortOrder, + IsActive = true + }; + + var created = await _dataService.AddAsync(entity, cancellationToken); + return MapToDTO(created); + } + + public async Task UpdateAsync(int id, DropdownOptionDTO option, CancellationToken cancellationToken) + { + var item = await _dataService.GetByIdAsync(id, cancellationToken); + if (item == null) + throw new KeyNotFoundException("Option not found"); + + item.Value = option.Value; + item.ParentValue = option.ParentValue; + item.SortOrder = option.SortOrder; + item.IsActive = option.IsActive; + + await _dataService.UpdateAsync(item, cancellationToken); + return MapToDTO(item); + } + + public async Task DeleteAsync(int id, CancellationToken cancellationToken) + { + var item = await _dataService.GetByIdAsync(id, cancellationToken); + if (item == null) + throw new KeyNotFoundException("Option not found"); + + await _dataService.RemoveAsync(item, cancellationToken); + } + + private static DropdownOptionDTO MapToDTO(DropdownOption option) + { + return new DropdownOptionDTO + { + Id = option.Id, + Category = option.Category, + Value = option.Value, + ParentValue = option.ParentValue, + SortOrder = option.SortOrder, + IsActive = option.IsActive + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/TaskListTemplateService.cs b/SeaHaven.Services/Implementation/TaskListTemplateService.cs new file mode 100644 index 0000000..294e4d7 --- /dev/null +++ b/SeaHaven.Services/Implementation/TaskListTemplateService.cs @@ -0,0 +1,92 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + public class TaskListTemplateService : ITaskListTemplateService + { + private readonly ITaskListTemplateDataService _dataService; + + public TaskListTemplateService(ITaskListTemplateDataService dataService) + { + _dataService = dataService; + } + + public async Task> GetAllAsync(CancellationToken cancellationToken) + { + var templates = await _dataService.GetActiveTemplatesWithItemsAsync(cancellationToken); + return templates.Select(MapToDTO); + } + + public async Task GetByIdAsync(int id, CancellationToken cancellationToken) + { + var template = await _dataService.GetTemplateWithItemsReadOnlyAsync(id, cancellationToken); + return template == null ? null : MapToDTO(template); + } + + public async Task CreateAsync(TaskListTemplateInput input, CancellationToken cancellationToken) + { + var template = new TaskListTemplate + { + Name = input.Name, + Description = input.Description, + IsActive = input.IsActive, + Items = input.Items.Select((item, idx) => new TaskListTemplateItem + { + ItemText = item.ItemText, + SortOrder = item.SortOrder > 0 ? item.SortOrder : idx + }).ToList() + }; + + var created = await _dataService.AddTemplateAsync(template, cancellationToken); + return created.Id; + } + + public async Task UpdateAsync(int id, TaskListTemplateInput input, CancellationToken cancellationToken) + { + var template = await _dataService.GetTemplateWithItemsForUpdateAsync(id, cancellationToken); + if (template == null) + return false; + + template.Name = input.Name; + template.Description = input.Description; + template.IsActive = input.IsActive; + + var newItems = input.Items.Select((item, idx) => new TaskListTemplateItem + { + TaskListTemplateId = id, + ItemText = item.ItemText, + SortOrder = item.SortOrder > 0 ? item.SortOrder : idx + }).ToList(); + + await _dataService.ReplaceTemplateItemsAsync(template, newItems, cancellationToken); + return true; + } + + public async Task DeleteAsync(int id, CancellationToken cancellationToken) + { + return await _dataService.DeactivateTemplateAsync(id, cancellationToken); + } + + private static TaskListTemplateDTO MapToDTO(TaskListTemplate template) + { + return new TaskListTemplateDTO + { + Id = template.Id, + Name = template.Name, + Description = template.Description, + IsActive = template.IsActive, + Items = (template.Items ?? new List()) + .OrderBy(i => i.SortOrder) + .Select(i => new TaskListTemplateItemDTO + { + Id = i.Id, + ItemText = i.ItemText, + SortOrder = i.SortOrder + }).ToList() + }; + } + } +} diff --git a/SeaHaven.Services/Interfaces/ICalendarService.cs b/SeaHaven.Services/Interfaces/ICalendarService.cs new file mode 100644 index 0000000..3bc1cbc --- /dev/null +++ b/SeaHaven.Services/Interfaces/ICalendarService.cs @@ -0,0 +1,14 @@ +using SeaHaven.Services.DTOs; + +namespace SeaHaven.Services.Interfaces +{ + public interface ICalendarService + { + Task CreateEventAsync(EditEventInput input, CancellationToken cancellationToken); + Task UpdateEventAsync(EditEventInput input, CancellationToken cancellationToken); + Task GetEventByIdAsync(int id, CancellationToken cancellationToken); + Task DeleteEventAsync(int id, CancellationToken cancellationToken); + Task GetEventListAsync(string? search, int page, int pageSize, CancellationToken cancellationToken); + Task> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.Services/Interfaces/ICommonService.cs b/SeaHaven.Services/Interfaces/ICommonService.cs new file mode 100644 index 0000000..4b27e9a --- /dev/null +++ b/SeaHaven.Services/Interfaces/ICommonService.cs @@ -0,0 +1,13 @@ +using SeaHaven.Services.DTOs; + +namespace SeaHaven.Services.Interfaces +{ + public interface ICommonService + { + Task GetUsersPagedAsync(string? search, int page, int pageSize, CancellationToken cancellationToken); + Task> GetRolesAsync(CancellationToken cancellationToken); + Task> GetSiteNamesAsync(CancellationToken cancellationToken); + Task> GetContactsAsync(CancellationToken cancellationToken); + Task> GetCategoriesAsync(CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.Services/Interfaces/IDashboardService.cs b/SeaHaven.Services/Interfaces/IDashboardService.cs new file mode 100644 index 0000000..bfc2e2d --- /dev/null +++ b/SeaHaven.Services/Interfaces/IDashboardService.cs @@ -0,0 +1,9 @@ +using SeaHaven.Services.DTOs; + +namespace SeaHaven.Services.Interfaces +{ + public interface IDashboardService + { + Task GetStatsAsync(CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.Services/Interfaces/IDropdownOptionsService.cs b/SeaHaven.Services/Interfaces/IDropdownOptionsService.cs new file mode 100644 index 0000000..3eb291f --- /dev/null +++ b/SeaHaven.Services/Interfaces/IDropdownOptionsService.cs @@ -0,0 +1,13 @@ +using SeaHaven.Services.DTOs; + +namespace SeaHaven.Services.Interfaces +{ + public interface IDropdownOptionsService + { + Task> GetAllAsync(string? category, CancellationToken cancellationToken); + Task> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken); + Task CreateAsync(DropdownOptionDTO option, CancellationToken cancellationToken); + Task UpdateAsync(int id, DropdownOptionDTO option, CancellationToken cancellationToken); + Task DeleteAsync(int id, CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.Services/Interfaces/ITaskListTemplateService.cs b/SeaHaven.Services/Interfaces/ITaskListTemplateService.cs new file mode 100644 index 0000000..596aa2f --- /dev/null +++ b/SeaHaven.Services/Interfaces/ITaskListTemplateService.cs @@ -0,0 +1,13 @@ +using SeaHaven.Services.DTOs; + +namespace SeaHaven.Services.Interfaces +{ + public interface ITaskListTemplateService + { + Task> GetAllAsync(CancellationToken cancellationToken); + Task GetByIdAsync(int id, CancellationToken cancellationToken); + Task CreateAsync(TaskListTemplateInput input, CancellationToken cancellationToken); + Task UpdateAsync(int id, TaskListTemplateInput input, CancellationToken cancellationToken); + Task DeleteAsync(int id, CancellationToken cancellationToken); + } +} diff --git a/SeaHaven.Services/SeaHaven.Services.csproj b/SeaHaven.Services/SeaHaven.Services.csproj index 281c47c..993548e 100644 --- a/SeaHaven.Services/SeaHaven.Services.csproj +++ b/SeaHaven.Services/SeaHaven.Services.csproj @@ -16,4 +16,8 @@ + + + +