From c657e0f1c8ede3484ade912a6a3876b9c3d8b151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Thu, 18 Apr 2024 21:38:02 +0200 Subject: [PATCH 01/12] refactor bookingRequest into service and improve error handling --- .../Controllers/BookingRequestController.cs | 89 +++++++++---------- .../Dtos/BookingRequest/BookingRequestDto.cs | 31 +++++++ .../BookingRequest/BookingRequestService.cs | 81 +++++++++++++++++ .../Services/Export/CsvService.cs | 4 +- .../Entities/BookingRequestEntity.cs | 49 +++++----- 5 files changed, 178 insertions(+), 76 deletions(-) create mode 100644 kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs create mode 100644 kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs diff --git a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs index 5103ad7..826bbaa 100644 --- a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs +++ b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs @@ -1,9 +1,8 @@ using kabinizer_api.Dtos.BookingRequest; using kabinizer_api.Model; -using kabinizer_api.Services; +using kabinizer_api.Services.BookingRequest; using kabinizer_api.Services.Export; using kabinizer_data; -using kabinizer_data.Entities; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; @@ -12,71 +11,69 @@ namespace kabinizer_api.Controllers; [Route("api/[controller]")] [ApiController] -public class BookingRequestController(EntityContext entityContext, ITokenService tokenService) : ControllerBase +public class BookingRequestController( + EntityContext entityContext, + BookingRequestService bookingRequestService +) : ControllerBase { - [HttpGet] - public IEnumerable GetBookingRequests() + [HttpGet("{id:guid}")] + public async Task GetBookingRequest([Required] Guid id) { - var currentUserId = tokenService.GetUserId(); - return entityContext.BookingRequests - .Include(br => br.User) - .Include(br => br.Period) - .Where(b => b.UserId == currentUserId) - .AsEnumerable().Select(BookingRequest.FromModel); + try + { + var bookingRequest = await bookingRequestService.GetBookingRequest(id); + return Ok(BookingRequestDto.FromModel(bookingRequest)); + } + catch (Exception ex) + { + return NotFound(ex.Message); + } } - [HttpPost] - public void AddBookingRequests([Required] IEnumerable requests) + [HttpGet] + public async Task GetBookingRequests() { - var currentUserId = tokenService.GetUserId(); - var periodIds = requests.Select(e => e.PeriodId).ToList(); - - var periods = entityContext.Periods - .Where(p => periodIds.Contains(p.Id)) - .Include(p => p.Draw) - .ToList(); - - if (periods.Count != periodIds.Count) + try { - throw new Exception("One or more periods do not exist"); + var bookingRequests = await bookingRequestService.GetBookingRequests(); + return Ok(bookingRequests.Select(BookingRequestDto.FromModel)); } + catch (Exception ex) + { + return NotFound(ex.Message); + } + } - var periodsWithoutDraw = periods.Where(p => p.Draw == null).ToList(); - if (periodsWithoutDraw.Count != 0) + [HttpPost] + public async Task AddBookingRequests([Required] IEnumerable requests) + { + try { - throw new Exception("One or more periods are not part of a draw"); + var bookingRequest = await bookingRequestService.AddBookingRequest(requests.First()); + return Ok(BookingRequestDto.FromModel(bookingRequest)); } - - var periodsWithEndedDraw = periods.Where(p => p.Draw?.DeadlineEnd > DateTime.Now).ToList(); - if (periodsWithEndedDraw.Count != 0) + catch (Exception e) { - throw new Exception("Cannot make a booking request for a draw that has ended"); + return BadRequest(e.Message); } - - var bookingRequestEntities = periodIds.Select(id => new BookingRequestEntity(currentUserId, id)); - entityContext.BookingRequests.AddRange(bookingRequestEntities); - entityContext.SaveChanges(); } [HttpDelete] - public bool DeleteBookingRequests([Required] IEnumerable requests) + public async Task DeleteBookingRequests([Required] IEnumerable requests) { - var currentUserId = tokenService.GetUserId(); - - foreach (Guid requestId in requests) + try { - BookingRequestEntity entityToRemove = entityContext.BookingRequests.Single(br => br.Id == requestId); - - if (entityToRemove.UserId != currentUserId) + foreach (Guid request in requests) { - throw new Exception("You cannot remove a booking request for another user"); + await bookingRequestService.DeleteBookingRequest(request); } - entityContext.BookingRequests.Remove(entityToRemove); + return Ok("Booking requests deleted"); + } + catch (Exception e) + { + return BadRequest(e.Message); } - - entityContext.SaveChanges(); - return true; } [HttpGet] diff --git a/kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs b/kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs new file mode 100644 index 0000000..5601976 --- /dev/null +++ b/kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs @@ -0,0 +1,31 @@ +using kabinizer_data.Entities; + +namespace kabinizer_api.Dtos.BookingRequest; + +public class BookingRequestDto( + Guid id, + Guid userId, + Guid periodId, + DateTime createdDate, + Guid createdBy, + DateTime? updatedDate, + Guid? updatedBy, + UserEntity user, + PeriodEntity period) +{ + public Guid Id { get; set; } = id; + public Guid UserId { get; set; } = userId; + public UserEntity User { get; set; } = user; + public PeriodEntity Period { get; set; } = period; + public Guid PeriodId { get; set; } = periodId; + public DateTime CreatedDate { get; set; } = createdDate; + public Guid CreatedBy { get; set; } = createdBy; + public DateTime? UpdatedDate { get; set; } = updatedDate; + public Guid? UpdatedBy { get; set; } = updatedBy; + + public static BookingRequestDto FromModel(BookingRequestEntity? e) + { + return new BookingRequestDto(e.Id, e.UserId, e.PeriodId, e.CreatedDate, e.CreatedBy, e.UpdatedDate, e.UpdatedBy, + e.User, e.Period); + } +} \ No newline at end of file diff --git a/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs b/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs new file mode 100644 index 0000000..a73ace9 --- /dev/null +++ b/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs @@ -0,0 +1,81 @@ +using kabinizer_api.Dtos.BookingRequest; +using kabinizer_data; +using kabinizer_data.Entities; +using Microsoft.EntityFrameworkCore; + +namespace kabinizer_api.Services.BookingRequest; + +public abstract class BookingRequestService(EntityContext entityContext, ITokenService tokenService) +{ + private async Task GetBookingRequestById(Guid bookingRequestId) + { + return await entityContext.BookingRequests + .Include(br => br.User) + .Include(br => br.Period) + .FirstOrDefaultAsync(b => b.Id == bookingRequestId && b.UserId == tokenService.GetUserId()) + ?? throw new InvalidOperationException("No matching booking request found."); + } + + public async Task GetBookingRequest(Guid bookingRequestId) + { + BookingRequestEntity? id = await GetBookingRequestById(bookingRequestId); + if (id == null) + { + throw new Exception("Booking request does not exist or does not belong to the current user"); + } + + return id; + } + + public async Task> GetBookingRequests() + { + var bookingRequestEntities = await entityContext.BookingRequests + .Include(br => br.User) + .Include(br => br.Period) + .Where(b => b.UserId == tokenService.GetUserId()) + .ToListAsync(); + if (bookingRequestEntities == null) + { + throw new Exception("No booking requests found for the current user"); + } + + return bookingRequestEntities; + } + + public async Task AddBookingRequest(CreateBookingRequestDto request) + { + var period = await entityContext.Periods + .Include(p => p.Draw) + .FirstOrDefaultAsync(p => p.Draw != null && p.Id == request.PeriodId && p.Draw.DeadlineEnd >= DateTime.Now); + + if (period == null) + { + throw new Exception("Period does not exist, is not part of a draw, or the draw has ended"); + } + + var user = await entityContext.Users.FirstOrDefaultAsync(u => u.Id == tokenService.GetUserId()); + if (user == null) + { + throw new Exception("User does not exist"); + } + + var bookingRequest = new BookingRequestEntity(tokenService.GetUserId(), request.PeriodId, user, period); + entityContext.BookingRequests.Add(bookingRequest); + await entityContext.SaveChangesAsync(); + return bookingRequest; + } + + public async Task DeleteBookingRequest(Guid bookingRequestId) + { + var bookingRequest = await GetBookingRequestById(bookingRequestId); + if (bookingRequest == null) + { + throw new Exception( + $"Booking request with id {bookingRequestId} does not exist or does not belong to the current user"); + } + + entityContext.BookingRequests.Remove(bookingRequest); + await entityContext.SaveChangesAsync(); + return true; + } +} \ No newline at end of file diff --git a/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs b/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs index e244b7f..a803626 100644 --- a/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs +++ b/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs @@ -8,7 +8,7 @@ namespace kabinizer_api.Services.Export; public static class CsvService { - public static byte[] ExportToCsv(IEnumerable requests) + public static byte[] ExportToCsv(IEnumerable requests) { using MemoryStream memoryStream = new(); using StreamWriter streamWriter = new(memoryStream); @@ -25,7 +25,7 @@ private static int ConvertDateToWeekNumber(DateTime date) return ISOWeek.GetWeekOfYear(date); } - private static IEnumerable ConvertToCsvRecords(IEnumerable bookingRequests) + private static IEnumerable ConvertToCsvRecords(IEnumerable bookingRequests) { return bookingRequests .GroupBy( diff --git a/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs b/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs index 26d171a..a17b82e 100644 --- a/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs +++ b/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs @@ -3,36 +3,29 @@ namespace kabinizer_data.Entities; [Table("BookingRequest")] -public class BookingRequestEntity +public class BookingRequestEntity( + Guid id, + Guid userId, + Guid periodId, + DateTime createdDate, + Guid createdBy, + DateTime? updatedDate, + Guid? updatedBy, + UserEntity user, + PeriodEntity period) { - public Guid Id { get; set; } - public Guid UserId { get; set; } - public UserEntity User { get; set; } - public PeriodEntity Period { get; set; } - public Guid PeriodId { get; set; } - public DateTime CreatedDate { get; set; } - public Guid CreatedBy { get; set; } - public DateTime? UpdatedDate { get; set; } - public Guid? UpdatedBy { get; set; } + public Guid Id { get; set; } = id; + public Guid UserId { get; set; } = userId; + public UserEntity User { get; set; } = user; + public PeriodEntity Period { get; set; } = period; + public Guid PeriodId { get; set; } = periodId; + public DateTime CreatedDate { get; set; } = createdDate; + public Guid CreatedBy { get; set; } = createdBy; + public DateTime? UpdatedDate { get; set; } = updatedDate; + public Guid? UpdatedBy { get; set; } = updatedBy; - public BookingRequestEntity(Guid id, Guid userId, Guid periodId, DateTime createdDate, - Guid createdBy, DateTime? updatedDate, Guid? updatedBy) + public BookingRequestEntity(Guid userId, Guid periodId, UserEntity user, PeriodEntity period) : this(Guid.NewGuid(), + userId, periodId, DateTime.Now, userId, null, null, user, period) { - Id = id; - UserId = userId; - PeriodId = periodId; - CreatedDate = createdDate; - CreatedBy = createdBy; - UpdatedDate = updatedDate; - UpdatedBy = updatedBy; - } - - public BookingRequestEntity(Guid userId, Guid periodId) - { - Id = Guid.NewGuid(); - UserId = userId; - PeriodId = periodId; - CreatedDate = DateTime.Now; - CreatedBy = userId; // Is this even needed? Won't it always be the same as the user id? } } \ No newline at end of file From 47832e2fa9363cbb6210843448ed92bd1a05381e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Thu, 18 Apr 2024 21:44:54 +0200 Subject: [PATCH 02/12] fix build errors --- .../Controllers/BookingRequestController.cs | 10 ++++++++++ .../Dtos/BookingRequest/BookingRequestDto.cs | 13 +++++++------ kabinizer-back-end/kabinizer-api/Model/Period.cs | 4 ++-- kabinizer-back-end/kabinizer-api/Model/User.cs | 2 +- .../BookingRequest/BookingRequestService.cs | 10 +++++++++- .../kabinizer-api/Services/Export/CsvService.cs | 6 +++--- .../Entities/BookingRequestEntity.cs | 15 +++++++++------ .../kabinizer-data/Entities/PeriodEntity.cs | 2 +- .../kabinizer-data/Entities/UserEntity.cs | 2 +- 9 files changed, 43 insertions(+), 21 deletions(-) diff --git a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs index 826bbaa..ee9ae66 100644 --- a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs +++ b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs @@ -22,6 +22,11 @@ public async Task GetBookingRequest([Required] Guid id) try { var bookingRequest = await bookingRequestService.GetBookingRequest(id); + if (bookingRequest == null) + { + return NotFound("Booking request does not exist or does not belong to the current user"); + } + return Ok(BookingRequestDto.FromModel(bookingRequest)); } catch (Exception ex) @@ -50,6 +55,11 @@ public async Task AddBookingRequests([Required] IEnumerable> GetBookingRequests() throw new Exception("User does not exist"); } - var bookingRequest = new BookingRequestEntity(tokenService.GetUserId(), request.PeriodId, user, period); + var bookingRequest = new BookingRequestEntity + { + PeriodId = period.Id, + UserId = user.Id, + CreatedDate = DateTime.Now, + CreatedBy = user.Id, + User = user, + Period = period + }; entityContext.BookingRequests.Add(bookingRequest); await entityContext.SaveChangesAsync(); return bookingRequest; diff --git a/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs b/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs index a803626..ef7bfc1 100644 --- a/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs +++ b/kabinizer-back-end/kabinizer-api/Services/Export/CsvService.cs @@ -14,7 +14,7 @@ public static byte[] ExportToCsv(IEnumerable requests) using StreamWriter streamWriter = new(memoryStream); CsvConfiguration config = new(CultureInfo.CurrentCulture) { Delimiter = ";", Encoding = Encoding.UTF8 }; using CsvWriter csv = new(streamWriter, config); - + csv.WriteRecords(ConvertToCsvRecords(requests)); streamWriter.Flush(); return memoryStream.ToArray(); @@ -31,8 +31,8 @@ private static IEnumerable ConvertToCsvRecords(IEnumerable req.User.Name, req => ConvertDateToWeekNumber(req.Period.PeriodStart), - (name, weeks) => new CsvRecord(name.ToString(), string.Join(", ", weeks))); + (name, weeks) => new CsvRecord(name?.ToString(), string.Join(", ", weeks))); } - private record CsvRecord(string Name, string Weeks); + private record CsvRecord(string? Name, string Weeks); } \ No newline at end of file diff --git a/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs b/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs index a17b82e..aba3c79 100644 --- a/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs +++ b/kabinizer-back-end/kabinizer-data/Entities/BookingRequestEntity.cs @@ -12,8 +12,16 @@ public class BookingRequestEntity( DateTime? updatedDate, Guid? updatedBy, UserEntity user, - PeriodEntity period) + PeriodEntity period +) { + public BookingRequestEntity() : this(Guid.NewGuid(), Guid.Empty, Guid.Empty, DateTime.Now, Guid.Empty, null, null, + new UserEntity(), new PeriodEntity { Title = null, DrawId = default } + ) + { + } + + public Guid Id { get; set; } = id; public Guid UserId { get; set; } = userId; public UserEntity User { get; set; } = user; @@ -23,9 +31,4 @@ public class BookingRequestEntity( public Guid CreatedBy { get; set; } = createdBy; public DateTime? UpdatedDate { get; set; } = updatedDate; public Guid? UpdatedBy { get; set; } = updatedBy; - - public BookingRequestEntity(Guid userId, Guid periodId, UserEntity user, PeriodEntity period) : this(Guid.NewGuid(), - userId, periodId, DateTime.Now, userId, null, null, user, period) - { - } } \ No newline at end of file diff --git a/kabinizer-back-end/kabinizer-data/Entities/PeriodEntity.cs b/kabinizer-back-end/kabinizer-data/Entities/PeriodEntity.cs index d0b2af7..2f93db7 100644 --- a/kabinizer-back-end/kabinizer-data/Entities/PeriodEntity.cs +++ b/kabinizer-back-end/kabinizer-data/Entities/PeriodEntity.cs @@ -8,7 +8,7 @@ public class PeriodEntity public Guid Id { get; set; } public DateTime PeriodStart { get; set; } public DateTime PeriodEnd { get; set; } - public required string Title { get; set; } + public required string? Title { get; set; } public required Guid DrawId { get; set; } public DrawEntity? Draw { get; set; } diff --git a/kabinizer-back-end/kabinizer-data/Entities/UserEntity.cs b/kabinizer-back-end/kabinizer-data/Entities/UserEntity.cs index 5971595..5269265 100644 --- a/kabinizer-back-end/kabinizer-data/Entities/UserEntity.cs +++ b/kabinizer-back-end/kabinizer-data/Entities/UserEntity.cs @@ -6,5 +6,5 @@ namespace kabinizer_data.Entities; public class UserEntity { public Guid Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } } \ No newline at end of file From 311f4a0b969997451e6d8a646ded0af6cd8b643c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 08:56:28 +0200 Subject: [PATCH 03/12] swagger return types for bookingrequests --- .../Controllers/BookingRequestController.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs index ee9ae66..c203e7b 100644 --- a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs +++ b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs @@ -17,7 +17,9 @@ BookingRequestService bookingRequestService ) : ControllerBase { [HttpGet("{id:guid}")] - public async Task GetBookingRequest([Required] Guid id) + [ProducesResponseType(200, Type = typeof(BookingRequestDto))] + [ProducesResponseType(404, Type = typeof(string))] + public async Task> GetBookingRequest(Guid id) { try { @@ -36,7 +38,9 @@ public async Task GetBookingRequest([Required] Guid id) } [HttpGet] - public async Task GetBookingRequests() + [ProducesResponseType(200, Type = typeof(IEnumerable))] + [ProducesResponseType(404, Type = typeof(string))] + public async Task>> GetBookingRequests() { try { @@ -50,7 +54,9 @@ public async Task GetBookingRequests() } [HttpPost] - public async Task AddBookingRequests([Required] IEnumerable requests) + [ProducesResponseType(200, Type = typeof(BookingRequestDto))] + [ProducesResponseType(400, Type = typeof(string))] + public async Task>> AddBookingRequests([Required] IEnumerable requests) { try { @@ -69,6 +75,8 @@ public async Task AddBookingRequests([Required] IEnumerable DeleteBookingRequests([Required] IEnumerable requests) { try From b3c972c9b640328308dfbe6a78f478398fd465da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 11:18:32 +0200 Subject: [PATCH 04/12] dep injection + new endpoints --- .../Controllers/BookingRequestController.cs | 42 +++++++++++++++++++ kabinizer-back-end/kabinizer-api/Program.cs | 2 + .../BookingRequest/BookingRequestService.cs | 32 +++++++++++++- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs index c203e7b..af072c1 100644 --- a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs +++ b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs @@ -52,6 +52,48 @@ public async Task>> GetBookingReques return NotFound(ex.Message); } } + + [HttpGet("user/{userId:guid}")] + [ProducesResponseType(200, Type = typeof(IEnumerable))] + [ProducesResponseType(404, Type = typeof(string))] + public async Task>> GetBookingRequestsByUser(Guid userId) + { + try + { + var bookingRequests = await bookingRequestService.GetBookingRequestsByUser(userId); + if (bookingRequests.Count == 0) + { + return NotFound("No booking requests found for the user"); + } + + return Ok(bookingRequests.Select(BookingRequestDto.FromModel)); + } + catch (Exception ex) + { + return NotFound(ex.Message); + } + } + + [HttpGet("period/{periodId:guid}")] + [ProducesResponseType(200, Type = typeof(IEnumerable))] + [ProducesResponseType(404, Type = typeof(string))] + public async Task>> GetBookingRequestsByPeriod(Guid periodId) + { + try + { + var bookingRequests = await bookingRequestService.GetBookingRequestsByPeriod(periodId); + if (bookingRequests.Count == 0) + { + return NotFound("No booking requests found for the period"); + } + + return Ok(bookingRequests.Select(BookingRequestDto.FromModel)); + } + catch (Exception ex) + { + return NotFound(ex.Message); + } + } [HttpPost] [ProducesResponseType(200, Type = typeof(BookingRequestDto))] diff --git a/kabinizer-back-end/kabinizer-api/Program.cs b/kabinizer-back-end/kabinizer-api/Program.cs index 9665c9d..50f9b34 100644 --- a/kabinizer-back-end/kabinizer-api/Program.cs +++ b/kabinizer-back-end/kabinizer-api/Program.cs @@ -1,4 +1,5 @@ using kabinizer_api.Services; +using kabinizer_api.Services.BookingRequest; using kabinizer_api.Services.Draw; using kabinizer_data; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -11,6 +12,7 @@ builder.Services.AddCors(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Add services to the container. builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) diff --git a/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs b/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs index 48c4489..80b7da0 100644 --- a/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs +++ b/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs @@ -5,7 +5,7 @@ namespace kabinizer_api.Services.BookingRequest; -public abstract class BookingRequestService(EntityContext entityContext, ITokenService tokenService) +public class BookingRequestService(EntityContext entityContext, ITokenService tokenService) { private async Task GetBookingRequestById(Guid bookingRequestId) { @@ -86,4 +86,34 @@ public async Task DeleteBookingRequest(Guid bookingRequestId) await entityContext.SaveChangesAsync(); return true; } + + public async Task> GetBookingRequestsByUser(Guid userId) + { + var bookingRequests = await entityContext.BookingRequests + .Include(br => br.User) + .Include(br => br.Period) + .Where(b => b.UserId == userId) + .ToListAsync(); + if (bookingRequests == null) + { + throw new Exception("No booking requests found for the user"); + } + + return bookingRequests; + } + + public async Task> GetBookingRequestsByPeriod(Guid periodId) + { + var bookingRequests = await entityContext.BookingRequests + .Include(br => br.User) + .Include(br => br.Period) + .Where(b => b.PeriodId == periodId) + .ToListAsync(); + if (bookingRequests == null) + { + throw new Exception("No booking requests found for the period"); + } + + return bookingRequests; + } } \ No newline at end of file From 94eb9b74dd052afded605fb15a288010a6b25974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 11:58:47 +0200 Subject: [PATCH 05/12] recursion setting json --- kabinizer-back-end/kabinizer-api/Program.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kabinizer-back-end/kabinizer-api/Program.cs b/kabinizer-back-end/kabinizer-api/Program.cs index 50f9b34..dc85b88 100644 --- a/kabinizer-back-end/kabinizer-api/Program.cs +++ b/kabinizer-back-end/kabinizer-api/Program.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using Microsoft.OpenApi.Models; +using System.Text.Json.Serialization; var builder = WebApplication.CreateBuilder(args); @@ -13,6 +14,10 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddControllers().AddJsonOptions(options => +{ + options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve; +}); // Add services to the container. builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) From dd4b57fb011030163c944bb4f38f735772afd250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 12:57:04 +0200 Subject: [PATCH 06/12] openapi gen --- kabinizer-front-end/api/core/ApiError.ts | 36 +- .../api/core/ApiRequestOptions.ts | 29 +- kabinizer-front-end/api/core/ApiResult.ts | 10 +- .../api/core/CancelablePromise.ts | 221 ++++---- kabinizer-front-end/api/core/OpenAPI.ts | 38 +- kabinizer-front-end/api/core/request.ts | 529 ++++++++++-------- kabinizer-front-end/api/index.ts | 30 +- .../api/models/BookingRequest.ts | 14 - .../api/models/BookingRequestDto.ts | 19 + .../api/models/CreateBookingRequestDto.ts | 3 +- .../api/models/CreateDrawDto.ts | 13 +- kabinizer-front-end/api/models/Draw.ts | 15 +- kabinizer-front-end/api/models/DrawEntity.ts | 15 + kabinizer-front-end/api/models/DrawPeriod.ts | 7 +- kabinizer-front-end/api/models/Period.ts | 11 +- .../api/models/PeriodEntity.ts | 15 + .../api/models/{User.ts => UserEntity.ts} | 7 +- .../api/services/BookingRequestService.ts | 179 ++++-- .../api/services/DrawService.ts | 62 +- .../api/services/PeriodService.ts | 30 +- kabinizer-front-end/package.json | 3 +- 21 files changed, 717 insertions(+), 569 deletions(-) delete mode 100644 kabinizer-front-end/api/models/BookingRequest.ts create mode 100644 kabinizer-front-end/api/models/BookingRequestDto.ts create mode 100644 kabinizer-front-end/api/models/DrawEntity.ts create mode 100644 kabinizer-front-end/api/models/PeriodEntity.ts rename kabinizer-front-end/api/models/{User.ts => UserEntity.ts} (67%) diff --git a/kabinizer-front-end/api/core/ApiError.ts b/kabinizer-front-end/api/core/ApiError.ts index d6b8fcc..ebc1161 100644 --- a/kabinizer-front-end/api/core/ApiError.ts +++ b/kabinizer-front-end/api/core/ApiError.ts @@ -2,24 +2,28 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; +import type { ApiRequestOptions } from "./ApiRequestOptions"; +import type { ApiResult } from "./ApiResult"; export class ApiError extends Error { - public readonly url: string; - public readonly status: number; - public readonly statusText: string; - public readonly body: any; - public readonly request: ApiRequestOptions; + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; - constructor(request: ApiRequestOptions, response: ApiResult, message: string) { - super(message); + constructor( + request: ApiRequestOptions, + response: ApiResult, + message: string, + ) { + super(message); - this.name = 'ApiError'; - this.url = response.url; - this.status = response.status; - this.statusText = response.statusText; - this.body = response.body; - this.request = request; - } + this.name = "ApiError"; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } } diff --git a/kabinizer-front-end/api/core/ApiRequestOptions.ts b/kabinizer-front-end/api/core/ApiRequestOptions.ts index c19adcc..ac9a2ca 100644 --- a/kabinizer-front-end/api/core/ApiRequestOptions.ts +++ b/kabinizer-front-end/api/core/ApiRequestOptions.ts @@ -3,15 +3,22 @@ /* tslint:disable */ /* eslint-disable */ export type ApiRequestOptions = { - readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; - readonly url: string; - readonly path?: Record; - readonly cookies?: Record; - readonly headers?: Record; - readonly query?: Record; - readonly formData?: Record; - readonly body?: any; - readonly mediaType?: string; - readonly responseHeader?: string; - readonly errors?: Record; + readonly method: + | "GET" + | "PUT" + | "POST" + | "DELETE" + | "OPTIONS" + | "HEAD" + | "PATCH"; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; }; diff --git a/kabinizer-front-end/api/core/ApiResult.ts b/kabinizer-front-end/api/core/ApiResult.ts index ad8fef2..63ed6c4 100644 --- a/kabinizer-front-end/api/core/ApiResult.ts +++ b/kabinizer-front-end/api/core/ApiResult.ts @@ -3,9 +3,9 @@ /* tslint:disable */ /* eslint-disable */ export type ApiResult = { - readonly url: string; - readonly ok: boolean; - readonly status: number; - readonly statusText: string; - readonly body: any; + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; }; diff --git a/kabinizer-front-end/api/core/CancelablePromise.ts b/kabinizer-front-end/api/core/CancelablePromise.ts index 55fef85..6aefa9e 100644 --- a/kabinizer-front-end/api/core/CancelablePromise.ts +++ b/kabinizer-front-end/api/core/CancelablePromise.ts @@ -3,129 +3,128 @@ /* tslint:disable */ /* eslint-disable */ export class CancelError extends Error { - - constructor(message: string) { - super(message); - this.name = 'CancelError'; - } - - public get isCancelled(): boolean { - return true; - } + constructor(message: string) { + super(message); + this.name = "CancelError"; + } + + public get isCancelled(): boolean { + return true; + } } export interface OnCancel { - readonly isResolved: boolean; - readonly isRejected: boolean; - readonly isCancelled: boolean; + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; - (cancelHandler: () => void): void; + (cancelHandler: () => void): void; } export class CancelablePromise implements Promise { - #isResolved: boolean; - #isRejected: boolean; - #isCancelled: boolean; - readonly #cancelHandlers: (() => void)[]; - readonly #promise: Promise; - #resolve?: (value: T | PromiseLike) => void; - #reject?: (reason?: any) => void; - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: any) => void, - onCancel: OnCancel - ) => void - ) { - this.#isResolved = false; - this.#isRejected = false; - this.#isCancelled = false; - this.#cancelHandlers = []; - this.#promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - - const onResolve = (value: T | PromiseLike): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isResolved = true; - this.#resolve?.(value); - }; - - const onReject = (reason?: any): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#isRejected = true; - this.#reject?.(reason); - }; - - const onCancel = (cancelHandler: () => void): void => { - if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; - } - this.#cancelHandlers.push(cancelHandler); - }; - - Object.defineProperty(onCancel, 'isResolved', { - get: (): boolean => this.#isResolved, - }); - - Object.defineProperty(onCancel, 'isRejected', { - get: (): boolean => this.#isRejected, - }); - - Object.defineProperty(onCancel, 'isCancelled', { - get: (): boolean => this.#isCancelled, - }); - - return executor(onResolve, onReject, onCancel as OnCancel); - }); - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise"; - } - - public then( - onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onRejected?: ((reason: any) => TResult2 | PromiseLike) | null - ): Promise { - return this.#promise.then(onFulfilled, onRejected); - } - - public catch( - onRejected?: ((reason: any) => TResult | PromiseLike) | null - ): Promise { - return this.#promise.catch(onRejected); - } + #isResolved: boolean; + #isRejected: boolean; + #isCancelled: boolean; + readonly #cancelHandlers: (() => void)[]; + readonly #promise: Promise; + #resolve?: (value: T | PromiseLike) => void; + #reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: OnCancel, + ) => void, + ) { + this.#isResolved = false; + this.#isRejected = false; + this.#isCancelled = false; + this.#cancelHandlers = []; + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isResolved = true; + this.#resolve?.(value); + }; - public finally(onFinally?: (() => void) | null): Promise { - return this.#promise.finally(onFinally); - } + const onReject = (reason?: any): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isRejected = true; + this.#reject?.(reason); + }; - public cancel(): void { + const onCancel = (cancelHandler: () => void): void => { if (this.#isResolved || this.#isRejected || this.#isCancelled) { - return; + return; } - this.#isCancelled = true; - if (this.#cancelHandlers.length) { - try { - for (const cancelHandler of this.#cancelHandlers) { - cancelHandler(); - } - } catch (error) { - console.warn('Cancellation threw an error', error); - return; - } + this.#cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, "isResolved", { + get: (): boolean => this.#isResolved, + }); + + Object.defineProperty(onCancel, "isRejected", { + get: (): boolean => this.#isRejected, + }); + + Object.defineProperty(onCancel, "isCancelled", { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike) | null, + ): Promise { + return this.#promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: any) => TResult | PromiseLike) | null, + ): Promise { + return this.#promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.#promise.finally(onFinally); + } + + public cancel(): void { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isCancelled = true; + if (this.#cancelHandlers.length) { + try { + for (const cancelHandler of this.#cancelHandlers) { + cancelHandler(); } - this.#cancelHandlers.length = 0; - this.#reject?.(new CancelError('Request aborted')); + } catch (error) { + console.warn("Cancellation threw an error", error); + return; + } } + this.#cancelHandlers.length = 0; + this.#reject?.(new CancelError("Request aborted")); + } - public get isCancelled(): boolean { - return this.#isCancelled; - } + public get isCancelled(): boolean { + return this.#isCancelled; + } } diff --git a/kabinizer-front-end/api/core/OpenAPI.ts b/kabinizer-front-end/api/core/OpenAPI.ts index 0fdd044..dec19a3 100644 --- a/kabinizer-front-end/api/core/OpenAPI.ts +++ b/kabinizer-front-end/api/core/OpenAPI.ts @@ -2,31 +2,31 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiRequestOptions } from "./ApiRequestOptions"; type Resolver = (options: ApiRequestOptions) => Promise; type Headers = Record; export type OpenAPIConfig = { - BASE: string; - VERSION: string; - WITH_CREDENTIALS: boolean; - CREDENTIALS: 'include' | 'omit' | 'same-origin'; - TOKEN?: string | Resolver | undefined; - USERNAME?: string | Resolver | undefined; - PASSWORD?: string | Resolver | undefined; - HEADERS?: Headers | Resolver | undefined; - ENCODE_PATH?: ((path: string) => string) | undefined; + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: "include" | "omit" | "same-origin"; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + HEADERS?: Headers | Resolver | undefined; + ENCODE_PATH?: ((path: string) => string) | undefined; }; export const OpenAPI: OpenAPIConfig = { - BASE: '', - VERSION: '1.0', - WITH_CREDENTIALS: false, - CREDENTIALS: 'include', - TOKEN: undefined, - USERNAME: undefined, - PASSWORD: undefined, - HEADERS: undefined, - ENCODE_PATH: undefined, + BASE: "", + VERSION: "1.0", + WITH_CREDENTIALS: false, + CREDENTIALS: "include", + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, }; diff --git a/kabinizer-front-end/api/core/request.ts b/kabinizer-front-end/api/core/request.ts index b018a07..72ed014 100644 --- a/kabinizer-front-end/api/core/request.ts +++ b/kabinizer-front-end/api/core/request.ts @@ -2,283 +2,310 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import { ApiError } from './ApiError'; -import type { ApiRequestOptions } from './ApiRequestOptions'; -import type { ApiResult } from './ApiResult'; -import { CancelablePromise } from './CancelablePromise'; -import type { OnCancel } from './CancelablePromise'; -import type { OpenAPIConfig } from './OpenAPI'; - -export const isDefined = (value: T | null | undefined): value is Exclude => { - return value !== undefined && value !== null; +import { ApiError } from "./ApiError"; +import type { ApiRequestOptions } from "./ApiRequestOptions"; +import type { ApiResult } from "./ApiResult"; +import { CancelablePromise } from "./CancelablePromise"; +import type { OnCancel } from "./CancelablePromise"; +import type { OpenAPIConfig } from "./OpenAPI"; + +export const isDefined = ( + value: T | null | undefined, +): value is Exclude => { + return value !== undefined && value !== null; }; export const isString = (value: any): value is string => { - return typeof value === 'string'; + return typeof value === "string"; }; export const isStringWithValue = (value: any): value is string => { - return isString(value) && value !== ''; + return isString(value) && value !== ""; }; export const isBlob = (value: any): value is Blob => { - return ( - typeof value === 'object' && - typeof value.type === 'string' && - typeof value.stream === 'function' && - typeof value.arrayBuffer === 'function' && - typeof value.constructor === 'function' && - typeof value.constructor.name === 'string' && - /^(Blob|File)$/.test(value.constructor.name) && - /^(Blob|File)$/.test(value[Symbol.toStringTag]) - ); + return ( + typeof value === "object" && + typeof value.type === "string" && + typeof value.stream === "function" && + typeof value.arrayBuffer === "function" && + typeof value.constructor === "function" && + typeof value.constructor.name === "string" && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); }; export const isFormData = (value: any): value is FormData => { - return value instanceof FormData; + return value instanceof FormData; }; export const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString('base64'); - } + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString("base64"); + } }; export const getQueryString = (params: Record): string => { - const qs: string[] = []; + const qs: string[] = []; - const append = (key: string, value: any) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; + const append = (key: string, value: any) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; - const process = (key: string, value: any) => { - if (isDefined(value)) { - if (Array.isArray(value)) { - value.forEach(v => { - process(key, v); - }); - } else if (typeof value === 'object') { - Object.entries(value).forEach(([k, v]) => { - process(`${key}[${k}]`, v); - }); - } else { - append(key, value); - } - } - }; + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach((v) => { + process(key, v); + }); + } else if (typeof value === "object") { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v); + }); + } else { + append(key, value); + } + } + }; - Object.entries(params).forEach(([key, value]) => { - process(key, value); - }); + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); - if (qs.length > 0) { - return `?${qs.join('&')}`; - } + if (qs.length > 0) { + return `?${qs.join("&")}`; + } - return ''; + return ""; }; const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace('{api-version}', config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace("{api-version}", config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); - const url = `${config.BASE}${path}`; - if (options.query) { - return `${url}${getQueryString(options.query)}`; - } - return url; + const url = `${config.BASE}${path}`; + if (options.query) { + return `${url}${getQueryString(options.query)}`; + } + return url; }; -export const getFormData = (options: ApiRequestOptions): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); +export const getFormData = ( + options: ApiRequestOptions, +): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); - const process = (key: string, value: any) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; - Object.entries(options.formData) - .filter(([_, value]) => isDefined(value)) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach(v => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((v) => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; }; type Resolver = (options: ApiRequestOptions) => Promise; -export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { - if (typeof resolver === 'function') { - return (resolver as Resolver)(options); - } - return resolver; +export const resolve = async ( + options: ApiRequestOptions, + resolver?: T | Resolver, +): Promise => { + if (typeof resolver === "function") { + return (resolver as Resolver)(options); + } + return resolver; }; -export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { - const token = await resolve(options, config.TOKEN); - const username = await resolve(options, config.USERNAME); - const password = await resolve(options, config.PASSWORD); - const additionalHeaders = await resolve(options, config.HEADERS); - - const headers = Object.entries({ - Accept: 'application/json', - ...additionalHeaders, - ...options.headers, - }) - .filter(([_, value]) => isDefined(value)) - .reduce((headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), {} as Record); - - if (isStringWithValue(token)) { - headers['Authorization'] = `Bearer ${token}`; - } +export const getHeaders = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, +): Promise => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + + const headers = Object.entries({ + Accept: "application/json", + ...additionalHeaders, + ...options.headers, + }) + .filter(([_, value]) => isDefined(value)) + .reduce( + (headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), + {} as Record, + ); - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers['Authorization'] = `Basic ${credentials}`; + if (isStringWithValue(token)) { + headers["Authorization"] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers["Authorization"] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers["Content-Type"] = options.mediaType; + } else if (isBlob(options.body)) { + headers["Content-Type"] = options.body.type || "application/octet-stream"; + } else if (isString(options.body)) { + headers["Content-Type"] = "text/plain"; + } else if (!isFormData(options.body)) { + headers["Content-Type"] = "application/json"; } + } - if (options.body) { - if (options.mediaType) { - headers['Content-Type'] = options.mediaType; - } else if (isBlob(options.body)) { - headers['Content-Type'] = options.body.type || 'application/octet-stream'; - } else if (isString(options.body)) { - headers['Content-Type'] = 'text/plain'; - } else if (!isFormData(options.body)) { - headers['Content-Type'] = 'application/json'; - } - } - - return new Headers(headers); + return new Headers(headers); }; export const getRequestBody = (options: ApiRequestOptions): any => { - if (options.body !== undefined) { - if (options.mediaType?.includes('/json')) { - return JSON.stringify(options.body) - } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { - return options.body; - } else { - return JSON.stringify(options.body); - } + if (options.body !== undefined) { + if (options.mediaType?.includes("/json")) { + return JSON.stringify(options.body); + } else if ( + isString(options.body) || + isBlob(options.body) || + isFormData(options.body) + ) { + return options.body; + } else { + return JSON.stringify(options.body); } - return undefined; + } + return undefined; }; export const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: any, - formData: FormData | undefined, - headers: Headers, - onCancel: OnCancel + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel, ): Promise => { - const controller = new AbortController(); + const controller = new AbortController(); - const request: RequestInit = { - headers, - body: body ?? formData, - method: options.method, - signal: controller.signal, - }; + const request: RequestInit = { + headers, + body: body ?? formData, + method: options.method, + signal: controller.signal, + }; - if (config.WITH_CREDENTIALS) { - request.credentials = config.CREDENTIALS; - } + if (config.WITH_CREDENTIALS) { + request.credentials = config.CREDENTIALS; + } - onCancel(() => controller.abort()); + onCancel(() => controller.abort()); - return await fetch(url, request); + return await fetch(url, request); }; -export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { - if (responseHeader) { - const content = response.headers.get(responseHeader); - if (isString(content)) { - return content; - } +export const getResponseHeader = ( + response: Response, + responseHeader?: string, +): string | undefined => { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; } - return undefined; + } + return undefined; }; export const getResponseBody = async (response: Response): Promise => { - if (response.status !== 204) { - try { - const contentType = response.headers.get('Content-Type'); - if (contentType) { - const jsonTypes = ['application/json', 'application/problem+json'] - const isJSON = jsonTypes.some(type => contentType.toLowerCase().startsWith(type)); - if (isJSON) { - return await response.json(); - } else { - return await response.text(); - } - } - } catch (error) { - console.error(error); + if (response.status !== 204) { + try { + const contentType = response.headers.get("Content-Type"); + if (contentType) { + const jsonTypes = ["application/json", "application/problem+json"]; + const isJSON = jsonTypes.some((type) => + contentType.toLowerCase().startsWith(type), + ); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); } + } + } catch (error) { + console.error(error); } - return undefined; + } + return undefined; }; -export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { - const errors: Record = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Forbidden', - 404: 'Not Found', - 500: 'Internal Server Error', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - ...options.errors, - } - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - const errorStatus = result.status ?? 'unknown'; - const errorStatusText = result.statusText ?? 'unknown'; - const errorBody = (() => { - try { - return JSON.stringify(result.body, null, 2); - } catch (e) { - return undefined; - } - })(); - - throw new ApiError(options, result, - `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` - ); - } +export const catchErrorCodes = ( + options: ApiRequestOptions, + result: ApiResult, +): void => { + const errors: Record = { + 400: "Bad Request", + 401: "Unauthorized", + 403: "Forbidden", + 404: "Not Found", + 500: "Internal Server Error", + 502: "Bad Gateway", + 503: "Service Unavailable", + ...options.errors, + }; + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? "unknown"; + const errorStatusText = result.statusText ?? "unknown"; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError( + options, + result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`, + ); + } }; /** @@ -288,33 +315,47 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): * @returns CancelablePromise * @throws ApiError */ -export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options); - - if (!onCancel.isCancelled) { - const response = await sendRequest(config, options, url, body, formData, headers, onCancel); - const responseBody = await getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); - - const result: ApiResult = { - url, - ok: response.ok, - status: response.status, - statusText: response.statusText, - body: responseHeader ?? responseBody, - }; - - catchErrorCodes(options, result); - - resolve(result.body); - } - } catch (error) { - reject(error); - } - }); +export const request = ( + config: OpenAPIConfig, + options: ApiRequestOptions, +): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + const response = await sendRequest( + config, + options, + url, + body, + formData, + headers, + onCancel, + ); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader( + response, + options.responseHeader, + ); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); }; diff --git a/kabinizer-front-end/api/index.ts b/kabinizer-front-end/api/index.ts index 488602d..6e8aa38 100644 --- a/kabinizer-front-end/api/index.ts +++ b/kabinizer-front-end/api/index.ts @@ -2,19 +2,21 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export { ApiError } from './core/ApiError'; -export { CancelablePromise, CancelError } from './core/CancelablePromise'; -export { OpenAPI } from './core/OpenAPI'; -export type { OpenAPIConfig } from './core/OpenAPI'; +export { ApiError } from "./core/ApiError"; +export { CancelablePromise, CancelError } from "./core/CancelablePromise"; +export { OpenAPI } from "./core/OpenAPI"; +export type { OpenAPIConfig } from "./core/OpenAPI"; -export type { BookingRequest } from './models/BookingRequest'; -export type { CreateBookingRequestDto } from './models/CreateBookingRequestDto'; -export type { CreateDrawDto } from './models/CreateDrawDto'; -export type { Draw } from './models/Draw'; -export type { DrawPeriod } from './models/DrawPeriod'; -export type { Period } from './models/Period'; -export type { User } from './models/User'; +export type { BookingRequestDto } from "./models/BookingRequestDto"; +export type { CreateBookingRequestDto } from "./models/CreateBookingRequestDto"; +export type { CreateDrawDto } from "./models/CreateDrawDto"; +export type { Draw } from "./models/Draw"; +export type { DrawEntity } from "./models/DrawEntity"; +export type { DrawPeriod } from "./models/DrawPeriod"; +export type { Period } from "./models/Period"; +export type { PeriodEntity } from "./models/PeriodEntity"; +export type { UserEntity } from "./models/UserEntity"; -export { BookingRequestService } from './services/BookingRequestService'; -export { DrawService } from './services/DrawService'; -export { PeriodService } from './services/PeriodService'; +export { BookingRequestService } from "./services/BookingRequestService"; +export { DrawService } from "./services/DrawService"; +export { PeriodService } from "./services/PeriodService"; diff --git a/kabinizer-front-end/api/models/BookingRequest.ts b/kabinizer-front-end/api/models/BookingRequest.ts deleted file mode 100644 index 0b18f47..0000000 --- a/kabinizer-front-end/api/models/BookingRequest.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* generated using openapi-typescript-codegen -- do no edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { Period } from './Period'; -import type { User } from './User'; - -export type BookingRequest = { - bookingRequestId?: string; - period?: Period; - user?: User; -}; - diff --git a/kabinizer-front-end/api/models/BookingRequestDto.ts b/kabinizer-front-end/api/models/BookingRequestDto.ts new file mode 100644 index 0000000..25fd9d8 --- /dev/null +++ b/kabinizer-front-end/api/models/BookingRequestDto.ts @@ -0,0 +1,19 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PeriodEntity } from "./PeriodEntity"; +import type { UserEntity } from "./UserEntity"; + +export type BookingRequestDto = { + id?: string; + userId?: string; + user?: UserEntity; + period?: PeriodEntity; + periodId?: string; + createdDate?: string; + createdBy?: string; + updatedDate?: string | null; + updatedBy?: string | null; +}; diff --git a/kabinizer-front-end/api/models/CreateBookingRequestDto.ts b/kabinizer-front-end/api/models/CreateBookingRequestDto.ts index 8efb255..73bfd30 100644 --- a/kabinizer-front-end/api/models/CreateBookingRequestDto.ts +++ b/kabinizer-front-end/api/models/CreateBookingRequestDto.ts @@ -4,6 +4,5 @@ /* eslint-disable */ export type CreateBookingRequestDto = { - periodId?: string; + periodId?: string; }; - diff --git a/kabinizer-front-end/api/models/CreateDrawDto.ts b/kabinizer-front-end/api/models/CreateDrawDto.ts index f169d92..61e53a4 100644 --- a/kabinizer-front-end/api/models/CreateDrawDto.ts +++ b/kabinizer-front-end/api/models/CreateDrawDto.ts @@ -3,13 +3,12 @@ /* tslint:disable */ /* eslint-disable */ -import type { DrawPeriod } from './DrawPeriod'; +import type { DrawPeriod } from "./DrawPeriod"; export type CreateDrawDto = { - deadlineStart?: string; - deadlineEnd?: string; - title?: string | null; - drawPeriods?: Array | null; - isSpecial?: boolean; + deadlineStart?: string; + deadlineEnd?: string; + title?: string | null; + drawPeriods?: Array | null; + isSpecial?: boolean; }; - diff --git a/kabinizer-front-end/api/models/Draw.ts b/kabinizer-front-end/api/models/Draw.ts index f00c417..5add249 100644 --- a/kabinizer-front-end/api/models/Draw.ts +++ b/kabinizer-front-end/api/models/Draw.ts @@ -3,14 +3,13 @@ /* tslint:disable */ /* eslint-disable */ -import type { Period } from './Period'; +import type { Period } from "./Period"; export type Draw = { - id?: string; - start?: string; - end?: string; - title?: string | null; - periods?: Array | null; - isSpecial?: boolean; + id?: string; + start?: string; + end?: string; + title?: string | null; + periods?: Array | null; + isSpecial?: boolean; }; - diff --git a/kabinizer-front-end/api/models/DrawEntity.ts b/kabinizer-front-end/api/models/DrawEntity.ts new file mode 100644 index 0000000..fb18e48 --- /dev/null +++ b/kabinizer-front-end/api/models/DrawEntity.ts @@ -0,0 +1,15 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PeriodEntity } from "./PeriodEntity"; + +export type DrawEntity = { + id?: string; + deadlineStart?: string; + deadlineEnd?: string; + title?: string | null; + isSpecial?: boolean; + periods?: Array | null; +}; diff --git a/kabinizer-front-end/api/models/DrawPeriod.ts b/kabinizer-front-end/api/models/DrawPeriod.ts index 80a9566..729e794 100644 --- a/kabinizer-front-end/api/models/DrawPeriod.ts +++ b/kabinizer-front-end/api/models/DrawPeriod.ts @@ -4,8 +4,7 @@ /* eslint-disable */ export type DrawPeriod = { - start?: string; - end?: string; - title?: string | null; + start?: string; + end?: string; + title?: string | null; }; - diff --git a/kabinizer-front-end/api/models/Period.ts b/kabinizer-front-end/api/models/Period.ts index 72589c3..5fe4978 100644 --- a/kabinizer-front-end/api/models/Period.ts +++ b/kabinizer-front-end/api/models/Period.ts @@ -4,10 +4,9 @@ /* eslint-disable */ export type Period = { - id?: string; - periodStart?: string; - periodEnd?: string; - title?: string | null; - drawId?: string; + id?: string; + periodStart?: string; + periodEnd?: string; + title?: string | null; + drawId?: string; }; - diff --git a/kabinizer-front-end/api/models/PeriodEntity.ts b/kabinizer-front-end/api/models/PeriodEntity.ts new file mode 100644 index 0000000..59a2549 --- /dev/null +++ b/kabinizer-front-end/api/models/PeriodEntity.ts @@ -0,0 +1,15 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { DrawEntity } from "./DrawEntity"; + +export type PeriodEntity = { + id?: string; + periodStart?: string; + periodEnd?: string; + title?: string | null; + drawId?: string; + draw?: DrawEntity; +}; diff --git a/kabinizer-front-end/api/models/User.ts b/kabinizer-front-end/api/models/UserEntity.ts similarity index 67% rename from kabinizer-front-end/api/models/User.ts rename to kabinizer-front-end/api/models/UserEntity.ts index 427d455..c33ab00 100644 --- a/kabinizer-front-end/api/models/User.ts +++ b/kabinizer-front-end/api/models/UserEntity.ts @@ -3,8 +3,7 @@ /* tslint:disable */ /* eslint-disable */ -export type User = { - id?: string; - name?: string | null; +export type UserEntity = { + id?: string; + name?: string | null; }; - diff --git a/kabinizer-front-end/api/services/BookingRequestService.ts b/kabinizer-front-end/api/services/BookingRequestService.ts index f28625d..f7ce1ff 100644 --- a/kabinizer-front-end/api/services/BookingRequestService.ts +++ b/kabinizer-front-end/api/services/BookingRequestService.ts @@ -2,67 +2,136 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { BookingRequest } from '../models/BookingRequest'; -import type { CreateBookingRequestDto } from '../models/CreateBookingRequestDto'; +import type { BookingRequestDto } from "../models/BookingRequestDto"; +import type { CreateBookingRequestDto } from "../models/CreateBookingRequestDto"; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { CancelablePromise } from "../core/CancelablePromise"; +import { OpenAPI } from "../core/OpenAPI"; +import { request as __request } from "../core/request"; export class BookingRequestService { + /** + * @param id + * @returns BookingRequestDto Success + * @throws ApiError + */ + public static getApiBookingRequest( + id: string, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/BookingRequest/{id}", + path: { + id: id, + }, + errors: { + 404: `Not Found`, + }, + }); + } - /** - * @returns BookingRequest Success - * @throws ApiError - */ - public static getApiBookingRequest(): CancelablePromise> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/BookingRequest', - }); - } + /** + * @returns BookingRequestDto Success + * @throws ApiError + */ + public static getApiBookingRequest1(): CancelablePromise< + Array + > { + return __request(OpenAPI, { + method: "GET", + url: "/api/BookingRequest", + errors: { + 404: `Not Found`, + }, + }); + } - /** - * @param requestBody - * @returns any Success - * @throws ApiError - */ - public static postApiBookingRequest( - requestBody: Array, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/BookingRequest', - body: requestBody, - mediaType: 'application/json', - }); - } + /** + * @param requestBody + * @returns BookingRequestDto Success + * @throws ApiError + */ + public static postApiBookingRequest( + requestBody: Array, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/BookingRequest", + body: requestBody, + mediaType: "application/json", + errors: { + 400: `Bad Request`, + }, + }); + } - /** - * @param requestBody - * @returns boolean Success - * @throws ApiError - */ - public static deleteApiBookingRequest( - requestBody: Array, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/api/BookingRequest', - body: requestBody, - mediaType: 'application/json', - }); - } + /** + * @param requestBody + * @returns string Success + * @throws ApiError + */ + public static deleteApiBookingRequest( + requestBody: Array, + ): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/api/BookingRequest", + body: requestBody, + mediaType: "application/json", + errors: { + 400: `Bad Request`, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static getApiBookingRequestExport(): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/api/BookingRequest/export', - }); - } + /** + * @param userId + * @returns BookingRequestDto Success + * @throws ApiError + */ + public static getApiBookingRequestUser( + userId: string, + ): CancelablePromise> { + return __request(OpenAPI, { + method: "GET", + url: "/api/BookingRequest/user/{userId}", + path: { + userId: userId, + }, + errors: { + 404: `Not Found`, + }, + }); + } + /** + * @param periodId + * @returns BookingRequestDto Success + * @throws ApiError + */ + public static getApiBookingRequestPeriod( + periodId: string, + ): CancelablePromise> { + return __request(OpenAPI, { + method: "GET", + url: "/api/BookingRequest/period/{periodId}", + path: { + periodId: periodId, + }, + errors: { + 404: `Not Found`, + }, + }); + } + + /** + * @returns any Success + * @throws ApiError + */ + public static getApiBookingRequestExport(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/BookingRequest/export", + }); + } } diff --git a/kabinizer-front-end/api/services/DrawService.ts b/kabinizer-front-end/api/services/DrawService.ts index 6552f81..0a13d69 100644 --- a/kabinizer-front-end/api/services/DrawService.ts +++ b/kabinizer-front-end/api/services/DrawService.ts @@ -2,40 +2,38 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { CreateDrawDto } from '../models/CreateDrawDto'; -import type { Draw } from '../models/Draw'; +import type { CreateDrawDto } from "../models/CreateDrawDto"; +import type { Draw } from "../models/Draw"; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { CancelablePromise } from "../core/CancelablePromise"; +import { OpenAPI } from "../core/OpenAPI"; +import { request as __request } from "../core/request"; export class DrawService { + /** + * @returns Draw Success + * @throws ApiError + */ + public static getApiDraw(): CancelablePromise> { + return __request(OpenAPI, { + method: "GET", + url: "/api/Draw", + }); + } - /** - * @returns Draw Success - * @throws ApiError - */ - public static getApiDraw(): CancelablePromise> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/Draw', - }); - } - - /** - * @param requestBody - * @returns any Success - * @throws ApiError - */ - public static postApiDraw( - requestBody: CreateDrawDto, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/api/Draw', - body: requestBody, - mediaType: 'application/json', - }); - } - + /** + * @param requestBody + * @returns any Success + * @throws ApiError + */ + public static postApiDraw( + requestBody: CreateDrawDto, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/Draw", + body: requestBody, + mediaType: "application/json", + }); + } } diff --git a/kabinizer-front-end/api/services/PeriodService.ts b/kabinizer-front-end/api/services/PeriodService.ts index 4601463..65f9352 100644 --- a/kabinizer-front-end/api/services/PeriodService.ts +++ b/kabinizer-front-end/api/services/PeriodService.ts @@ -2,23 +2,21 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Period } from '../models/Period'; +import type { Period } from "../models/Period"; -import type { CancelablePromise } from '../core/CancelablePromise'; -import { OpenAPI } from '../core/OpenAPI'; -import { request as __request } from '../core/request'; +import type { CancelablePromise } from "../core/CancelablePromise"; +import { OpenAPI } from "../core/OpenAPI"; +import { request as __request } from "../core/request"; export class PeriodService { - - /** - * @returns Period Success - * @throws ApiError - */ - public static getApiPeriod(): CancelablePromise> { - return __request(OpenAPI, { - method: 'GET', - url: '/api/Period', - }); - } - + /** + * @returns Period Success + * @throws ApiError + */ + public static getApiPeriod(): CancelablePromise> { + return __request(OpenAPI, { + method: "GET", + url: "/api/Period", + }); + } } diff --git a/kabinizer-front-end/package.json b/kabinizer-front-end/package.json index ca6b8ca..54eed9d 100644 --- a/kabinizer-front-end/package.json +++ b/kabinizer-front-end/package.json @@ -9,7 +9,8 @@ "test": "vitest", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", - "generate": "openapi -i https://app-kabinizer-dev.azurewebsites.net/swagger/v1/swagger.json -o ./api --useUnionTypes & prettier -w ./api" + "generate": "openapi -i https://app-kabinizer-dev.azurewebsites.net/swagger/v1/swagger.json -o ./api --useUnionTypes & prettier -w ./api", + "generate:local": "openapi -i http://localhost:5111/swagger/v1/swagger.json -o ./api --useUnionTypes & prettier -w ./api" }, "dependencies": { "@radix-ui/react-avatar": "^1.0.4", From f4c9aa0bc2f2ffd3565e55d63c4b26385c472637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 13:33:11 +0200 Subject: [PATCH 07/12] undo serialization change, since that broke the json... fix circular dto --- .../Dtos/BookingRequest/BookingRequestDto.cs | 40 +++++++++---------- kabinizer-back-end/kabinizer-api/Program.cs | 4 -- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs b/kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs index b856f5a..bc7854a 100644 --- a/kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs +++ b/kabinizer-back-end/kabinizer-api/Dtos/BookingRequest/BookingRequestDto.cs @@ -2,31 +2,27 @@ namespace kabinizer_api.Dtos.BookingRequest; -public class BookingRequestDto( - Guid id, - Guid userId, - Guid periodId, - DateTime createdDate, - Guid createdBy, - DateTime? updatedDate, - Guid? updatedBy, - UserEntity? user, - PeriodEntity? period) +public class BookingRequestDto { - public Guid Id { get; set; } = id; - public Guid UserId { get; set; } = userId; - public UserEntity? User { get; set; } = user; - public PeriodEntity? Period { get; set; } = period; - public Guid PeriodId { get; set; } = periodId; - public DateTime CreatedDate { get; set; } = createdDate; - public Guid CreatedBy { get; set; } = createdBy; - public DateTime? UpdatedDate { get; set; } = updatedDate; - public Guid? UpdatedBy { get; set; } = updatedBy; + public Guid Id { get; set; } + public Guid UserId { get; set; } + public Guid PeriodId { get; set; } + public DateTime CreatedDate { get; set; } + public Guid CreatedBy { get; set; } + public DateTime? UpdatedDate { get; set; } + public Guid? UpdatedBy { get; set; } public static BookingRequestDto FromModel(BookingRequestEntity e) { - return new BookingRequestDto(e.Id, e.UserId, e.PeriodId, e.CreatedDate, e.CreatedBy, e.UpdatedDate, - e.UpdatedBy, - e.User, e.Period); + return new BookingRequestDto + { + Id = e.Id, + UserId = e.UserId, + PeriodId = e.PeriodId, + CreatedDate = e.CreatedDate, + CreatedBy = e.CreatedBy, + UpdatedDate = e.UpdatedDate, + UpdatedBy = e.UpdatedBy + }; } } \ No newline at end of file diff --git a/kabinizer-back-end/kabinizer-api/Program.cs b/kabinizer-back-end/kabinizer-api/Program.cs index dc85b88..610f268 100644 --- a/kabinizer-back-end/kabinizer-api/Program.cs +++ b/kabinizer-back-end/kabinizer-api/Program.cs @@ -14,10 +14,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddControllers().AddJsonOptions(options => -{ - options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve; -}); // Add services to the container. builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) From 9b5dd4437b2479b8ba01680ccb26d8688fd01278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 14:54:46 +0200 Subject: [PATCH 08/12] simplify front-end logic. move it to back-end --- .../Controllers/BookingRequestController.cs | 5 - .../BookingRequest/BookingRequestService.cs | 6 +- kabinizer-front-end/api/index.ts | 3 - .../api/models/BookingRequestDto.ts | 5 - kabinizer-front-end/api/models/DrawEntity.ts | 15 --- .../api/models/PeriodEntity.ts | 15 --- kabinizer-front-end/api/models/UserEntity.ts | 9 -- .../pages/selectPeriods/Calendar/Calendar.tsx | 52 --------- .../Calendar/DrawPeriodsCalendar.tsx | 66 +++++++++++ .../pages/selectPeriods/Calendar/Months.tsx | 1 + .../src/pages/selectPeriods/Deadline.tsx | 30 ----- .../src/pages/selectPeriods/DrawDeadlines.tsx | 35 ++++++ .../src/pages/selectPeriods/index.tsx | 109 +----------------- 13 files changed, 110 insertions(+), 241 deletions(-) delete mode 100644 kabinizer-front-end/api/models/DrawEntity.ts delete mode 100644 kabinizer-front-end/api/models/PeriodEntity.ts delete mode 100644 kabinizer-front-end/api/models/UserEntity.ts delete mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/Calendar.tsx create mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx delete mode 100644 kabinizer-front-end/src/pages/selectPeriods/Deadline.tsx create mode 100644 kabinizer-front-end/src/pages/selectPeriods/DrawDeadlines.tsx diff --git a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs index af072c1..a732209 100644 --- a/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs +++ b/kabinizer-back-end/kabinizer-api/Controllers/BookingRequestController.cs @@ -61,11 +61,6 @@ public async Task>> GetBookingReques try { var bookingRequests = await bookingRequestService.GetBookingRequestsByUser(userId); - if (bookingRequests.Count == 0) - { - return NotFound("No booking requests found for the user"); - } - return Ok(bookingRequests.Select(BookingRequestDto.FromModel)); } catch (Exception ex) diff --git a/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs b/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs index 80b7da0..459f725 100644 --- a/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs +++ b/kabinizer-back-end/kabinizer-api/Services/BookingRequest/BookingRequestService.cs @@ -73,9 +73,9 @@ public async Task> GetBookingRequests() return bookingRequest; } - public async Task DeleteBookingRequest(Guid bookingRequestId) + public async Task DeleteBookingRequest(Guid bookingRequestId) { - var bookingRequest = await GetBookingRequestById(bookingRequestId); + var bookingRequest = await entityContext.BookingRequests.FirstOrDefaultAsync(b => b.Id == bookingRequestId); if (bookingRequest == null) { throw new Exception( @@ -84,7 +84,6 @@ public async Task DeleteBookingRequest(Guid bookingRequestId) entityContext.BookingRequests.Remove(bookingRequest); await entityContext.SaveChangesAsync(); - return true; } public async Task> GetBookingRequestsByUser(Guid userId) @@ -109,6 +108,7 @@ public async Task> GetBookingRequestsByPeriod(Guid pe .Include(br => br.Period) .Where(b => b.PeriodId == periodId) .ToListAsync(); + if (bookingRequests == null) { throw new Exception("No booking requests found for the period"); diff --git a/kabinizer-front-end/api/index.ts b/kabinizer-front-end/api/index.ts index 6e8aa38..2fd69a1 100644 --- a/kabinizer-front-end/api/index.ts +++ b/kabinizer-front-end/api/index.ts @@ -11,11 +11,8 @@ export type { BookingRequestDto } from "./models/BookingRequestDto"; export type { CreateBookingRequestDto } from "./models/CreateBookingRequestDto"; export type { CreateDrawDto } from "./models/CreateDrawDto"; export type { Draw } from "./models/Draw"; -export type { DrawEntity } from "./models/DrawEntity"; export type { DrawPeriod } from "./models/DrawPeriod"; export type { Period } from "./models/Period"; -export type { PeriodEntity } from "./models/PeriodEntity"; -export type { UserEntity } from "./models/UserEntity"; export { BookingRequestService } from "./services/BookingRequestService"; export { DrawService } from "./services/DrawService"; diff --git a/kabinizer-front-end/api/models/BookingRequestDto.ts b/kabinizer-front-end/api/models/BookingRequestDto.ts index 25fd9d8..c47cca3 100644 --- a/kabinizer-front-end/api/models/BookingRequestDto.ts +++ b/kabinizer-front-end/api/models/BookingRequestDto.ts @@ -3,14 +3,9 @@ /* tslint:disable */ /* eslint-disable */ -import type { PeriodEntity } from "./PeriodEntity"; -import type { UserEntity } from "./UserEntity"; - export type BookingRequestDto = { id?: string; userId?: string; - user?: UserEntity; - period?: PeriodEntity; periodId?: string; createdDate?: string; createdBy?: string; diff --git a/kabinizer-front-end/api/models/DrawEntity.ts b/kabinizer-front-end/api/models/DrawEntity.ts deleted file mode 100644 index fb18e48..0000000 --- a/kabinizer-front-end/api/models/DrawEntity.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* generated using openapi-typescript-codegen -- do no edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { PeriodEntity } from "./PeriodEntity"; - -export type DrawEntity = { - id?: string; - deadlineStart?: string; - deadlineEnd?: string; - title?: string | null; - isSpecial?: boolean; - periods?: Array | null; -}; diff --git a/kabinizer-front-end/api/models/PeriodEntity.ts b/kabinizer-front-end/api/models/PeriodEntity.ts deleted file mode 100644 index 59a2549..0000000 --- a/kabinizer-front-end/api/models/PeriodEntity.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* generated using openapi-typescript-codegen -- do no edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -import type { DrawEntity } from "./DrawEntity"; - -export type PeriodEntity = { - id?: string; - periodStart?: string; - periodEnd?: string; - title?: string | null; - drawId?: string; - draw?: DrawEntity; -}; diff --git a/kabinizer-front-end/api/models/UserEntity.ts b/kabinizer-front-end/api/models/UserEntity.ts deleted file mode 100644 index c33ab00..0000000 --- a/kabinizer-front-end/api/models/UserEntity.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* generated using openapi-typescript-codegen -- do no edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type UserEntity = { - id?: string; - name?: string | null; -}; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Calendar.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/Calendar.tsx deleted file mode 100644 index 1c22f2f..0000000 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Calendar.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { CreateBookingRequestDto, Draw } from "../../../../api"; -import { CompareDates } from "../../../utils"; -import { GetWeeklyPeriods, getMonthsMap } from "@/utils/calendar"; -import Months from "./Months"; - -const Calendar = ({ - draws, - selected, - setSelected, -}: { - draws: Draw[]; - selected: CreateBookingRequestDto[]; - setSelected: (selected: CreateBookingRequestDto[]) => void; -}) => { - const periods = draws - .map((value) => value.periods ?? []) - .flat() - .sort((a, b) => { - return CompareDates(a, b); - }); - - const onClick = (periodId: string) => { - const checked = selected.find((s) => s.periodId === periodId); - - if (checked) { - setSelected(selected.filter((s) => s.periodId !== periodId)); - } else { - const period = periods.find((p) => p.id === periodId); - if (period) { - setSelected([ - ...selected, - { - periodId: period.id ?? "", - }, - ]); - } - } - }; - - if (periods.length === 0) { - return
No periods
; - } - - const data = GetWeeklyPeriods(periods, draws); - const monthMap = getMonthsMap(data); - - console.log(monthMap); - - return ; -}; - -export default Calendar; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx new file mode 100644 index 0000000..ed8c37b --- /dev/null +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx @@ -0,0 +1,66 @@ +import { + BookingRequestService, + CreateBookingRequestDto, + DrawService, +} from "../../../../api"; +import { CompareDates } from "@/utils"; +import DrawPeriodMonths from "./Months"; +import { useMutation, useQuery, useQueryClient } from "react-query"; +import useUser from "@/hooks/useUser.tsx"; +import { getMonthsMap, GetWeeklyPeriods } from "@/utils/calendar.ts"; + +const DrawPeriodsCalendar = () => { + const user = useUser(); + const queryClient = useQueryClient(); + + const { data: draws, isLoading: isLoadingDraws } = useQuery( + "getApiDraw", + DrawService.getApiDraw, + ); + const { data: bookingRequests, isLoading: isLoadingBookingRequests } = + useQuery( + "myBookingRequests", + () => BookingRequestService.getApiBookingRequestUser(user.localAccountId), + { enabled: !!user.localAccountId }, + ); + + const addBooking = useMutation( + "postApiBooking", + (payload: CreateBookingRequestDto[]) => + BookingRequestService.postApiBookingRequest(payload), + { onSuccess: () => queryClient.invalidateQueries("myBookingRequests") }, + ); + const removeBooking = useMutation( + "deleteApiBooking", + (payload: string[]) => + BookingRequestService.deleteApiBookingRequest(payload), + { onSuccess: () => queryClient.invalidateQueries("myBookingRequests") }, + ); + + if (isLoadingDraws || isLoadingBookingRequests) return
Loading...
; + + const periods = draws + ?.flatMap((value) => value.periods ?? []) + .sort(CompareDates); + + if (!periods?.length) return
No periods
; + + const monthMap = getMonthsMap(GetWeeklyPeriods(periods, draws || [])); + + const toggleBooking = (periodId: string) => { + const booking = bookingRequests?.find((b) => b.periodId === periodId); + booking?.id + ? removeBooking.mutate([booking.id]) + : addBooking.mutate([{ periodId }]); + }; + + return ( + + ); +}; + +export default DrawPeriodsCalendar; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx index 292f4b9..520d0b9 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx @@ -3,6 +3,7 @@ import { COLORS, MONTHS } from "@/options"; import { MonthMapType } from "@/types"; import Weeks from "./Weeks"; import { OptionsProps } from "./Options"; +import React from "react"; type MonthsProps = { months: MonthMapType; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Deadline.tsx b/kabinizer-front-end/src/pages/selectPeriods/Deadline.tsx deleted file mode 100644 index 8f8a1cd..0000000 --- a/kabinizer-front-end/src/pages/selectPeriods/Deadline.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Draw } from "../../../api"; -import { FormatDate } from "../../utils"; - -type DeadlineProps = { - draws: Draw[]; -}; - -const Deadline = ({ draws }: DeadlineProps) => { - return ( -
-
-

Deadline

-
-
- {draws.map((draw) => ( -
-

- {draw.title} -

-

- {draw.end ? FormatDate(new Date(draw.end)) : "–"} -

-
- ))} -
-
- ); -}; - -export default Deadline; diff --git a/kabinizer-front-end/src/pages/selectPeriods/DrawDeadlines.tsx b/kabinizer-front-end/src/pages/selectPeriods/DrawDeadlines.tsx new file mode 100644 index 0000000..e73930c --- /dev/null +++ b/kabinizer-front-end/src/pages/selectPeriods/DrawDeadlines.tsx @@ -0,0 +1,35 @@ +import { DrawService } from "../../../api"; +import { FormatDate } from "@/utils"; +import { useQuery } from "react-query"; + +const DrawDeadlines = () => { + const { data: draws, isLoading } = useQuery(["getApiDraw"], () => + DrawService.getApiDraw(), + ); + if (isLoading) return
Loading...
; + if (!draws) return
No data
; + + return ( +
+
+
+

Deadline

+
+
+ {draws?.map((draw) => ( +
+

+ {draw.title} +

+

+ {draw.end ? FormatDate(new Date(draw.end)) : "–"} +

+
+ ))} +
+
+
+ ); +}; + +export default DrawDeadlines; diff --git a/kabinizer-front-end/src/pages/selectPeriods/index.tsx b/kabinizer-front-end/src/pages/selectPeriods/index.tsx index 46255ac..8331c73 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/index.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/index.tsx @@ -1,128 +1,29 @@ -import { useMutation, useQuery, useQueryClient } from "react-query"; -import { - BookingRequest, - BookingRequestService, - CreateBookingRequestDto, - DrawService, -} from "../../../api/index.ts"; +import { CreateBookingRequestDto } from "../../../api"; import WeekDayRow from "../../components/WeekDayRow"; -import Calendar from "./Calendar/Calendar.tsx"; +import DrawPeriodsCalendar from "./Calendar/DrawPeriodsCalendar.tsx"; import { useState } from "react"; -import Button from "../../components/Button.tsx"; -import Deadline from "./Deadline.tsx"; +import DrawDeadlines from "./DrawDeadlines.tsx"; import Title from "../../components/Title.tsx"; -import { getAllBookingRequests } from "@/utils/calendar.ts"; - -const getDeletedBookings = ( - bookings: BookingRequest[] | undefined = [], - selected: CreateBookingRequestDto[], -) => { - return bookings.filter( - (b) => !selected.map((s) => s.periodId).includes(b.period?.id), - ); -}; const SelectPeriodsView = () => { - const queryClient = useQueryClient(); const [selected, setSelected] = useState([]); - const { data = [], isLoading } = useQuery(["getApiDraw"], () => - DrawService.getApiDraw(), - ); - - const getSelectedBookingRequests = async () => { - try { - const bookingRequests = - await BookingRequestService.getApiBookingRequest(); - - setSelected( - bookingRequests.map((d) => ({ - periodId: d.period?.id ?? "", - userId: d.user?.id ?? "", - bookingRequestId: d.bookingRequestId ?? "", - })), - ); - - return bookingRequests; - } catch (error) { - console.error(error); - } - }; - - const { data: bookings } = useQuery( - ["getApiBookingRequest"], - () => getSelectedBookingRequests(), - { staleTime: Infinity, cacheTime: Infinity }, - ); - - const handleUpdate = async () => { - try { - const deletedBookings = getDeletedBookings(bookings, selected); - const deletedBookingIds = deletedBookings.map( - (d) => d.bookingRequestId as string, - ); - - await BookingRequestService.deleteApiBookingRequest(deletedBookingIds); - await BookingRequestService.postApiBookingRequest(selected); - - queryClient.invalidateQueries("getApiBookingRequest"); - } catch (error) { - console.error(error); - } - }; - - const { mutateAsync: update, isLoading: isUpdating } = useMutation(() => - handleUpdate(), - ); - - const handleSelectAll = () => { - const allPeriods = getAllBookingRequests(data); - if (selected.length === allPeriods.length) { - setSelected([]); - } else { - setSelected(allPeriods); - } - }; - - if (isLoading) { - return ( -
Loading...
- ); - } - return (
Mine ønsker -
- -
+
- setSelected(selected)} />
-
- {isUpdating ? ( -

Lagrer...

- ) : ( - - )} -
); From 637698d72054964c26b0fd771a16ae0a949f5dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 15:03:22 +0200 Subject: [PATCH 09/12] refactor --- .../Calendar/DrawPeriodsCalendar.tsx | 20 ++++--- .../pages/selectPeriods/Calendar/Months.tsx | 54 ------------------- .../Calendar/MonthsContainer.tsx | 29 ++++++++++ 3 files changed, 43 insertions(+), 60 deletions(-) delete mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx create mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx index ed8c37b..2d9a1f6 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx @@ -4,10 +4,11 @@ import { DrawService, } from "../../../../api"; import { CompareDates } from "@/utils"; -import DrawPeriodMonths from "./Months"; import { useMutation, useQuery, useQueryClient } from "react-query"; import useUser from "@/hooks/useUser.tsx"; import { getMonthsMap, GetWeeklyPeriods } from "@/utils/calendar.ts"; +import Weeks from "@/pages/selectPeriods/Calendar/Weeks.tsx"; +import { MonthsContainer } from "@/pages/selectPeriods/Calendar/MonthsContainer.tsx"; const DrawPeriodsCalendar = () => { const user = useUser(); @@ -55,11 +56,18 @@ const DrawPeriodsCalendar = () => { }; return ( - +
+ {Object.entries(monthMap).map(([month, weeks]) => ( + + + + ))} +
); }; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx deleted file mode 100644 index 520d0b9..0000000 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Months.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import MonthColumn from "../../../components/MonthVertical"; -import { COLORS, MONTHS } from "@/options"; -import { MonthMapType } from "@/types"; -import Weeks from "./Weeks"; -import { OptionsProps } from "./Options"; -import React from "react"; - -type MonthsProps = { - months: MonthMapType; -} & Omit; - -const Months = ({ months, selected, onClick }: MonthsProps) => { - return ( -
- {Object.entries(months).map(([month, weeks]) => ( - - - - ))} -
- ); -}; - -type MonthProps = { - children: React.ReactNode; - month: number; -}; - -const Month = ({ children, month }: MonthProps) => { - return ( -
- -
- {children} -
-
- ); -}; - -export default Months; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx new file mode 100644 index 0000000..55c7662 --- /dev/null +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import MonthColumn from "@/components/MonthVertical.tsx"; +import { COLORS, MONTHS } from "@/options"; + +export const MonthsContainer = ({ + children, + month, +}: { + children: React.ReactNode; + month: number; +}) => { + return ( +
+ +
+ {children} +
+
+ ); +}; From 610e74a0ffc03a69196f4a8c9a0d9bf7d312e5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 20:38:01 +0200 Subject: [PATCH 10/12] refactor: remove abstractions and add loading status --- .../src/components/OptionButton.tsx | 63 +++++---- .../Calendar/DrawPeriodsCalendar.tsx | 128 ++++++++++++++---- .../Calendar/MonthsContainer.tsx | 29 ---- .../pages/selectPeriods/Calendar/Weeks.tsx | 40 ------ 4 files changed, 140 insertions(+), 120 deletions(-) delete mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx delete mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/Weeks.tsx diff --git a/kabinizer-front-end/src/components/OptionButton.tsx b/kabinizer-front-end/src/components/OptionButton.tsx index 77eedf4..fd8db6c 100644 --- a/kabinizer-front-end/src/components/OptionButton.tsx +++ b/kabinizer-front-end/src/components/OptionButton.tsx @@ -1,5 +1,5 @@ -import { ColorType } from "../types"; -import { NumberOfDays } from "../utils"; +import { ColorType } from "@/types"; +import { NumberOfDays } from "@/utils"; const OptionButton = ({ colors, @@ -8,6 +8,7 @@ const OptionButton = ({ isSpecial, selected, onClick, + isLoading = false, }: { colors: ColorType; from: Date; @@ -15,6 +16,7 @@ const OptionButton = ({ isSpecial: boolean; selected: boolean; onClick: () => void; + isLoading?: boolean; }) => { const days = NumberOfDays(from, to) + 1; @@ -27,36 +29,45 @@ const OptionButton = ({ return ( ); diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx index 2d9a1f6..b7c56ae 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx @@ -7,35 +7,69 @@ import { CompareDates } from "@/utils"; import { useMutation, useQuery, useQueryClient } from "react-query"; import useUser from "@/hooks/useUser.tsx"; import { getMonthsMap, GetWeeklyPeriods } from "@/utils/calendar.ts"; -import Weeks from "@/pages/selectPeriods/Calendar/Weeks.tsx"; -import { MonthsContainer } from "@/pages/selectPeriods/Calendar/MonthsContainer.tsx"; +import WeekNumber from "@/components/WeekNumber.tsx"; +import MonthColumn from "@/components/MonthVertical.tsx"; +import { COLORS, MONTHS } from "@/options"; +import OptionButton from "@/components/OptionButton.tsx"; +import { useState } from "react"; const DrawPeriodsCalendar = () => { const user = useUser(); const queryClient = useQueryClient(); - const { data: draws, isLoading: isLoadingDraws } = useQuery( - "getApiDraw", - DrawService.getApiDraw, + const { + data: draws, + isLoading: isLoadingDraws, + error: drawError, + } = useQuery("getApiDraw", DrawService.getApiDraw); + const { + data: bookingRequests, + isLoading: isLoadingBookingRequests, + error: bookingError, + } = useQuery( + "myBookingRequests", + () => BookingRequestService.getApiBookingRequestUser(user.localAccountId), + { enabled: !!user.localAccountId }, ); - const { data: bookingRequests, isLoading: isLoadingBookingRequests } = - useQuery( - "myBookingRequests", - () => BookingRequestService.getApiBookingRequestUser(user.localAccountId), - { enabled: !!user.localAccountId }, - ); - + const [loadingStates, setLoadingStates] = useState>({ + "": false, + }); const addBooking = useMutation( "postApiBooking", - (payload: CreateBookingRequestDto[]) => - BookingRequestService.postApiBookingRequest(payload), - { onSuccess: () => queryClient.invalidateQueries("myBookingRequests") }, + (payload: CreateBookingRequestDto[]) => { + payload.forEach((p) => { + setLoadingStates((prev) => ({ ...prev, [p.periodId || ""]: true })); + }); + return BookingRequestService.postApiBookingRequest(payload); + }, + { + onSettled: (_data, _error, variables) => { + variables.forEach((bookingRequestDto) => { + setLoadingStates((prev) => ({ + ...prev, + [bookingRequestDto.periodId || ""]: false, + })); + }); + return queryClient.invalidateQueries("myBookingRequests"); + }, + }, ); const removeBooking = useMutation( "deleteApiBooking", - (payload: string[]) => - BookingRequestService.deleteApiBookingRequest(payload), - { onSuccess: () => queryClient.invalidateQueries("myBookingRequests") }, + (payload: string[]) => { + payload.forEach((id) => { + setLoadingStates((prev) => ({ ...prev, [id]: true })); + }); + return BookingRequestService.deleteApiBookingRequest(payload); + }, + { + onSettled: (_data, _error, variables) => { + variables.forEach((id) => { + setLoadingStates((prev) => ({ ...prev, [id]: false })); + }); + return queryClient.invalidateQueries("myBookingRequests"); + }, + }, ); if (isLoadingDraws || isLoadingBookingRequests) return
Loading...
; @@ -44,6 +78,14 @@ const DrawPeriodsCalendar = () => { ?.flatMap((value) => value.periods ?? []) .sort(CompareDates); + if (drawError || bookingError) { + return ( +
+

An error occurred while fetching data. Please try again later.

+
+ ); + } + if (!periods?.length) return
No periods
; const monthMap = getMonthsMap(GetWeeklyPeriods(periods, draws || [])); @@ -58,14 +100,50 @@ const DrawPeriodsCalendar = () => { return (
{Object.entries(monthMap).map(([month, weeks]) => ( - - + - +
+
+ {Object.entries(weeks).map(([week, options]) => ( +
+ +
+ {options.map((option) => ( +
+ s.periodId === option.id, + ) + } + onClick={() => toggleBooking(option.id ?? "")} + isLoading={loadingStates[option.id || ""]} + /> +
+ ))} +
+
+ ))} +
+
+
))} ); diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx deleted file mode 100644 index 55c7662..0000000 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthsContainer.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import MonthColumn from "@/components/MonthVertical.tsx"; -import { COLORS, MONTHS } from "@/options"; - -export const MonthsContainer = ({ - children, - month, -}: { - children: React.ReactNode; - month: number; -}) => { - return ( -
- -
- {children} -
-
- ); -}; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Weeks.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/Weeks.tsx deleted file mode 100644 index 6c4964f..0000000 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/Weeks.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import WeekNumber from "../../../components/WeekNumber"; -import { WeekMapType } from "@/types"; -import Options, { OptionsProps } from "./Options"; - -type WeeksProps = { - weeks: WeekMapType; -} & Omit; - -const Weeks = ({ weeks, month, selected, onClick }: WeeksProps) => { - return ( -
- {Object.entries(weeks).map(([week, options]) => ( - - - - ))} -
- ); -}; - -type WeekProps = { - children: React.ReactNode; - week: number; -}; - -const Week = ({ children, week }: WeekProps) => { - return ( -
- - {children} -
- ); -}; - -export default Weeks; From 3de42ed32953445d9c7ae7f19a5e2746646d192c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Fri, 19 Apr 2024 21:19:55 +0200 Subject: [PATCH 11/12] refactor --- .../src/components/MonthVertical.tsx | 20 ------ .../src/components/VerticalText.tsx | 16 +++++ .../Calendar/DrawPeriodsCalendar.tsx | 67 +++++-------------- .../Calendar/MonthOptionsComponent.tsx | 45 +++++++++++++ .../Calendar/WeekOptionsComponent.tsx | 48 +++++++++++++ ...Deadlines.tsx => CurrentDrawDeadlines.tsx} | 24 ++++--- .../src/pages/selectPeriods/index.tsx | 63 ++++------------- 7 files changed, 157 insertions(+), 126 deletions(-) delete mode 100644 kabinizer-front-end/src/components/MonthVertical.tsx create mode 100644 kabinizer-front-end/src/components/VerticalText.tsx create mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthOptionsComponent.tsx create mode 100644 kabinizer-front-end/src/pages/selectPeriods/Calendar/WeekOptionsComponent.tsx rename kabinizer-front-end/src/pages/selectPeriods/{DrawDeadlines.tsx => CurrentDrawDeadlines.tsx} (53%) diff --git a/kabinizer-front-end/src/components/MonthVertical.tsx b/kabinizer-front-end/src/components/MonthVertical.tsx deleted file mode 100644 index 7c5ea08..0000000 --- a/kabinizer-front-end/src/components/MonthVertical.tsx +++ /dev/null @@ -1,20 +0,0 @@ -const MonthColumn = ({ month, color }: { month: string; color: string }) => { - return ( -
- {month} -
- ); -}; - -export default MonthColumn; diff --git a/kabinizer-front-end/src/components/VerticalText.tsx b/kabinizer-front-end/src/components/VerticalText.tsx new file mode 100644 index 0000000..536a468 --- /dev/null +++ b/kabinizer-front-end/src/components/VerticalText.tsx @@ -0,0 +1,16 @@ +const MonthColumn = ({ text }: { text: string }) => { + return ( +
+ {text} +
+ ); +}; + +export default MonthColumn; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx index b7c56ae..b465dc5 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx @@ -7,11 +7,9 @@ import { CompareDates } from "@/utils"; import { useMutation, useQuery, useQueryClient } from "react-query"; import useUser from "@/hooks/useUser.tsx"; import { getMonthsMap, GetWeeklyPeriods } from "@/utils/calendar.ts"; -import WeekNumber from "@/components/WeekNumber.tsx"; -import MonthColumn from "@/components/MonthVertical.tsx"; -import { COLORS, MONTHS } from "@/options"; -import OptionButton from "@/components/OptionButton.tsx"; import { useState } from "react"; +import MonthOptionsComponent from "@/pages/selectPeriods/Calendar/MonthOptionsComponent.tsx"; +import WeekDayRow from "@/components/WeekDayRow.tsx"; const DrawPeriodsCalendar = () => { const user = useUser(); @@ -22,6 +20,7 @@ const DrawPeriodsCalendar = () => { isLoading: isLoadingDraws, error: drawError, } = useQuery("getApiDraw", DrawService.getApiDraw); + const { data: bookingRequests, isLoading: isLoadingBookingRequests, @@ -31,9 +30,11 @@ const DrawPeriodsCalendar = () => { () => BookingRequestService.getApiBookingRequestUser(user.localAccountId), { enabled: !!user.localAccountId }, ); + const [loadingStates, setLoadingStates] = useState>({ "": false, }); + const addBooking = useMutation( "postApiBooking", (payload: CreateBookingRequestDto[]) => { @@ -54,6 +55,7 @@ const DrawPeriodsCalendar = () => { }, }, ); + const removeBooking = useMutation( "deleteApiBooking", (payload: string[]) => { @@ -99,52 +101,19 @@ const DrawPeriodsCalendar = () => { return (
- {Object.entries(monthMap).map(([month, weeks]) => ( -
- + +
+ {Object.entries(monthMap).map(([month, weeks]) => ( + -
-
- {Object.entries(weeks).map(([week, options]) => ( -
- -
- {options.map((option) => ( -
- s.periodId === option.id, - ) - } - onClick={() => toggleBooking(option.id ?? "")} - isLoading={loadingStates[option.id || ""]} - /> -
- ))} -
-
- ))} -
-
-
- ))} + ))} +
); }; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthOptionsComponent.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthOptionsComponent.tsx new file mode 100644 index 0000000..50650b0 --- /dev/null +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/MonthOptionsComponent.tsx @@ -0,0 +1,45 @@ +import { Option } from "@/types"; +import { CreateBookingRequestDto } from "../../../../api"; +import React from "react"; +import { MONTHS } from "@/options"; +import WeekOptionsComponent from "@/pages/selectPeriods/Calendar/WeekOptionsComponent.tsx"; +import VerticalText from "@/components/VerticalText.tsx"; + +type MonthOptionsComponentProps = { + month: string; + weeks: Record; + bookingRequests: CreateBookingRequestDto[] | undefined; + toggleBooking: (periodId: string) => void; + loadingStates: Record; +}; + +const MonthOptionsComponent: React.FC = ({ + month, + weeks, + bookingRequests, + toggleBooking, + loadingStates, +}) => ( +
+ +
+ {Object.entries(weeks).map(([week, options]) => ( + + ))} +
+
+); +export default MonthOptionsComponent; diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/WeekOptionsComponent.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/WeekOptionsComponent.tsx new file mode 100644 index 0000000..e772264 --- /dev/null +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/WeekOptionsComponent.tsx @@ -0,0 +1,48 @@ +import { Option } from "@/types"; +import { CreateBookingRequestDto } from "../../../../api"; +import React from "react"; +import WeekNumber from "@/components/WeekNumber.tsx"; +import OptionButton from "@/components/OptionButton.tsx"; +import { COLORS } from "@/options"; + +type WeekOptionsComponentProps = { + week: string; + month: string; + options: Option[]; + bookingRequests: CreateBookingRequestDto[] | undefined; + toggleBooking: (periodId: string) => void; + loadingStates: Record; +}; + +const WeekOptionsComponent: React.FC = ({ + week, + month, + options, + bookingRequests, + toggleBooking, + loadingStates, +}) => ( +
+ +
+ {options.map((option) => ( +
+ s.periodId === option.id)} + onClick={() => toggleBooking(option.id ?? "")} + isLoading={loadingStates[option.id || ""]} + /> +
+ ))} +
+
+); +export default WeekOptionsComponent; diff --git a/kabinizer-front-end/src/pages/selectPeriods/DrawDeadlines.tsx b/kabinizer-front-end/src/pages/selectPeriods/CurrentDrawDeadlines.tsx similarity index 53% rename from kabinizer-front-end/src/pages/selectPeriods/DrawDeadlines.tsx rename to kabinizer-front-end/src/pages/selectPeriods/CurrentDrawDeadlines.tsx index e73930c..ad89183 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/DrawDeadlines.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/CurrentDrawDeadlines.tsx @@ -2,26 +2,34 @@ import { DrawService } from "../../../api"; import { FormatDate } from "@/utils"; import { useQuery } from "react-query"; -const DrawDeadlines = () => { +const CurrentDrawDeadlines = () => { const { data: draws, isLoading } = useQuery(["getApiDraw"], () => DrawService.getApiDraw(), ); - if (isLoading) return
Loading...
; - if (!draws) return
No data
; + if (isLoading) return

Loading...

; return (
-
+
-

Deadline

+

+ Frist +

+ {!draws && ( +
+

+ Det er for tiden ingen aktive trekninger +

+
+ )} {draws?.map((draw) => (
-

+

{draw.title}

-

+

{draw.end ? FormatDate(new Date(draw.end)) : "–"}

@@ -32,4 +40,4 @@ const DrawDeadlines = () => { ); }; -export default DrawDeadlines; +export default CurrentDrawDeadlines; diff --git a/kabinizer-front-end/src/pages/selectPeriods/index.tsx b/kabinizer-front-end/src/pages/selectPeriods/index.tsx index 8331c73..cbf10f4 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/index.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/index.tsx @@ -1,54 +1,19 @@ -import { CreateBookingRequestDto } from "../../../api"; - -import WeekDayRow from "../../components/WeekDayRow"; import DrawPeriodsCalendar from "./Calendar/DrawPeriodsCalendar.tsx"; -import { useState } from "react"; -import DrawDeadlines from "./DrawDeadlines.tsx"; -import Title from "../../components/Title.tsx"; - -const SelectPeriodsView = () => { - const [selected, setSelected] = useState([]); - - return ( -
-
- Mine ønsker - -
-
-
- - setSelected(selected)} - /> -
-
-
- ); -}; +import CurrentDrawDeadlines from "./CurrentDrawDeadlines.tsx"; -const Label = () => { - return ( -
-

Ønsker

- - - +const SelectPeriodsView = () => ( +
+
+

+ Frister for trekning +

+ +

+ Velg ønsket periode +

+
- ); -}; +
+); export default SelectPeriodsView; From 74cc193fb62e49879c05d6fd2309b586cd0f57d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Sj=C3=B8en?= Date: Sat, 20 Apr 2024 13:18:00 +0200 Subject: [PATCH 12/12] add missing keys --- .../src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx | 1 + .../src/pages/selectPeriods/Calendar/MonthOptionsComponent.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx index b465dc5..f7c9d97 100644 --- a/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx +++ b/kabinizer-front-end/src/pages/selectPeriods/Calendar/DrawPeriodsCalendar.tsx @@ -106,6 +106,7 @@ const DrawPeriodsCalendar = () => {
{Object.entries(monthMap).map(([month, weeks]) => ( = ({ > {Object.entries(weeks).map(([week, options]) => (