From dde991c76b6fe4f1cc9d08176ad44c0e01359ea5 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Thu, 28 Aug 2025 08:55:32 +0800 Subject: [PATCH 1/8] chore: form category --- .../{ => Controllers}/AccountController.cs | 2 +- .../{ => Controllers}/BookingsController.cs | 2 +- .../Bookings/Services/BookingService.cs | 2 +- .../{ => Controllers}/CategoriesController.cs | 2 +- .../{ => Controllers}/GatheringsController.cs | 3 +- .../HealthChecksController.cs | 2 +- .../src/lib/domains/models/upsert-dtos.ts | 34 +- .../src/lib/services/category-service.ts | 7 + src/evently.client/src/lib/services/index.ts | 1 + src/evently.client/src/routeTree.gen.ts | 576 +++++++++--------- .../gatherings/-components/gathering-form.tsx | 4 +- .../src/routes/(auth)/gatherings/create.tsx | 11 +- 12 files changed, 331 insertions(+), 315 deletions(-) rename src/Evently.Server/Features/Accounts/{ => Controllers}/AccountController.cs (98%) rename src/Evently.Server/Features/Bookings/{ => Controllers}/BookingsController.cs (98%) rename src/Evently.Server/Features/Categories/{ => Controllers}/CategoriesController.cs (94%) rename src/Evently.Server/Features/Gatherings/{ => Controllers}/GatheringsController.cs (98%) rename src/Evently.Server/Features/HealthChecks/{ => Controllers}/HealthChecksController.cs (94%) create mode 100644 src/evently.client/src/lib/services/category-service.ts diff --git a/src/Evently.Server/Features/Accounts/AccountController.cs b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs similarity index 98% rename from src/Evently.Server/Features/Accounts/AccountController.cs rename to src/Evently.Server/Features/Accounts/Controllers/AccountController.cs index 45c5770..553897a 100644 --- a/src/Evently.Server/Features/Accounts/AccountController.cs +++ b/src/Evently.Server/Features/Accounts/Controllers/AccountController.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc; using System.Security.Claims; -namespace Evently.Server.Features.Accounts; +namespace Evently.Server.Features.Accounts.Controllers; // Based on https://tinyurl.com/26arz8vk [ApiController] diff --git a/src/Evently.Server/Features/Bookings/BookingsController.cs b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs similarity index 98% rename from src/Evently.Server/Features/Bookings/BookingsController.cs rename to src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs index 1a7d71a..8c90526 100644 --- a/src/Evently.Server/Features/Bookings/BookingsController.cs +++ b/src/Evently.Server/Features/Bookings/Controllers/BookingsController.cs @@ -7,7 +7,7 @@ using System.Globalization; using System.Threading.Channels; -namespace Evently.Server.Features.Bookings; +namespace Evently.Server.Features.Bookings.Controllers; [ApiController] [Route("api/v1/[controller]")] diff --git a/src/Evently.Server/Features/Bookings/Services/BookingService.cs b/src/Evently.Server/Features/Bookings/Services/BookingService.cs index 08868be..f752d76 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingService.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingService.cs @@ -70,7 +70,7 @@ public async Task CreateBooking(BookingReqDto bookingReqDto) { if (!validationResult.IsValid) { throw new ArgumentException($"Account has already booked this gathering (GatheringId: {booking.GatheringId})"); } - + booking.BookingId = $"book_{await Nanoid.GenerateAsync(size: 10)}"; await db.Bookings.AddAsync(booking); await db.SaveChangesAsync(); diff --git a/src/Evently.Server/Features/Categories/CategoriesController.cs b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs similarity index 94% rename from src/Evently.Server/Features/Categories/CategoriesController.cs rename to src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs index f094205..032ada0 100644 --- a/src/Evently.Server/Features/Categories/CategoriesController.cs +++ b/src/Evently.Server/Features/Categories/Controllers/CategoriesController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc; using System.Globalization; -namespace Evently.Server.Features.Categories; +namespace Evently.Server.Features.Categories.Controllers; [ApiController] [Route("api/v1/[controller]")] diff --git a/src/Evently.Server/Features/Gatherings/GatheringsController.cs b/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs similarity index 98% rename from src/Evently.Server/Features/Gatherings/GatheringsController.cs rename to src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs index 37dd544..93fa591 100644 --- a/src/Evently.Server/Features/Gatherings/GatheringsController.cs +++ b/src/Evently.Server/Features/Gatherings/Controllers/GatheringsController.cs @@ -3,14 +3,13 @@ using Evently.Server.Common.Domains.Models; using Evently.Server.Common.Extensions; using Evently.Server.Features.Accounts.Services; -using FluentValidation; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using MimeKit; using System.Globalization; -namespace Evently.Server.Features.Gatherings; +namespace Evently.Server.Features.Gatherings.Controllers; [ApiController] [Route("api/v1/[controller]")] diff --git a/src/Evently.Server/Features/HealthChecks/HealthChecksController.cs b/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs similarity index 94% rename from src/Evently.Server/Features/HealthChecks/HealthChecksController.cs rename to src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs index 199acc7..4de00bb 100644 --- a/src/Evently.Server/Features/HealthChecks/HealthChecksController.cs +++ b/src/Evently.Server/Features/HealthChecks/Controllers/HealthChecksController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Diagnostics.HealthChecks; -namespace Evently.Server.Features.HealthChecks; +namespace Evently.Server.Features.HealthChecks.Controllers; [ApiController] [Route("api/v1/[controller]")] diff --git a/src/evently.client/src/lib/domains/models/upsert-dtos.ts b/src/evently.client/src/lib/domains/models/upsert-dtos.ts index 07becc4..23d601d 100644 --- a/src/evently.client/src/lib/domains/models/upsert-dtos.ts +++ b/src/evently.client/src/lib/domains/models/upsert-dtos.ts @@ -1,11 +1,11 @@ export class BookingReqDto { - public bookingId = ""; - public attendeeId = ""; - public gatheringId = 0; - public creationDateTime = new Date(); - public checkInDateTime: Date | null = null; - public checkoutDateTime: Date | null = null; - public cancellationDateTime: Date | null = null; + bookingId = ""; + attendeeId = ""; + gatheringId = 0; + creationDateTime = new Date(); + checkInDateTime: Date | null = null; + checkoutDateTime: Date | null = null; + cancellationDateTime: Date | null = null; constructor(partial: Partial = {}) { Object.assign(this, partial); @@ -13,16 +13,16 @@ } export class GatheringReqDto { - public gatheringId = 0; - public name = ""; - public description = ""; - public start = new Date(); - public end = new Date(); - public location = ""; - public organiserId = ""; - public coverSrc?: string | null = null; - public cancellationDateTime: Date | null = null; - + gatheringId = 0; + name = ""; + description = ""; + start = new Date(); + end = new Date(); + location = ""; + organiserId = ""; + coverSrc?: string | null = null; + cancellationDateTime: Date | null = null; + constructor(partial: Partial = {}) { Object.assign(this, partial); } diff --git a/src/evently.client/src/lib/services/category-service.ts b/src/evently.client/src/lib/services/category-service.ts new file mode 100644 index 0000000..bc8c264 --- /dev/null +++ b/src/evently.client/src/lib/services/category-service.ts @@ -0,0 +1,7 @@ +import {Category} from "~/lib/domains/entities"; +import axios from "axios"; + +export async function getCategories(): Promise { + const response = await axios.get("/api/v1/Categories"); + return response.data; +} \ No newline at end of file diff --git a/src/evently.client/src/lib/services/index.ts b/src/evently.client/src/lib/services/index.ts index 079c99d..fe8f783 100644 --- a/src/evently.client/src/lib/services/index.ts +++ b/src/evently.client/src/lib/services/index.ts @@ -3,3 +3,4 @@ export * from "./auth-service"; export * from "./store"; export * from "./gathering-service.ts"; export * from "./booking-service"; +export * from "./category-service.ts"; \ No newline at end of file diff --git a/src/evently.client/src/routeTree.gen.ts b/src/evently.client/src/routeTree.gen.ts index 65e370f..c3c8e8c 100644 --- a/src/evently.client/src/routeTree.gen.ts +++ b/src/evently.client/src/routeTree.gen.ts @@ -8,328 +8,330 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import { Route as rootRouteImport } from "./routes/__root"; -import { Route as IndexRouteImport } from "./routes/index"; -import { Route as LoginIndexRouteImport } from "./routes/login/index"; -import { Route as HealthcheckIndexRouteImport } from "./routes/healthcheck/index"; -import { Route as GatheringsIndexRouteImport } from "./routes/gatherings/index"; -import { Route as authGatheringsRouteRouteImport } from "./routes/(auth)/gatherings/route"; -import { Route as authBookingsRouteRouteImport } from "./routes/(auth)/bookings/route"; -import { Route as GatheringsGatheringIdIndexRouteImport } from "./routes/gatherings/$gatheringId/index"; -import { Route as authGatheringsCreateRouteImport } from "./routes/(auth)/gatherings/create"; -import { Route as authBookingsHostingIndexRouteImport } from "./routes/(auth)/bookings/hosting/index"; -import { Route as authBookingsAttendingIndexRouteImport } from "./routes/(auth)/bookings/attending/index"; -import { Route as authGatheringsGatheringIdUpdateRouteImport } from "./routes/(auth)/gatherings/$gatheringId/update"; -import { Route as authBookingsHostingGatheringIdDashboardIndexRouteImport } from "./routes/(auth)/bookings/hosting/$gatheringId/dashboard.index"; -import { Route as authBookingsHostingGatheringIdDashboardScanRouteImport } from "./routes/(auth)/bookings/hosting/$gatheringId/dashboard.scan"; +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as LoginIndexRouteImport } from './routes/login/index' +import { Route as HealthcheckIndexRouteImport } from './routes/healthcheck/index' +import { Route as GatheringsIndexRouteImport } from './routes/gatherings/index' +import { Route as authGatheringsRouteRouteImport } from './routes/(auth)/gatherings/route' +import { Route as authBookingsRouteRouteImport } from './routes/(auth)/bookings/route' +import { Route as GatheringsGatheringIdIndexRouteImport } from './routes/gatherings/$gatheringId/index' +import { Route as authGatheringsCreateRouteImport } from './routes/(auth)/gatherings/create' +import { Route as authBookingsHostingIndexRouteImport } from './routes/(auth)/bookings/hosting/index' +import { Route as authBookingsAttendingIndexRouteImport } from './routes/(auth)/bookings/attending/index' +import { Route as authGatheringsGatheringIdUpdateRouteImport } from './routes/(auth)/gatherings/$gatheringId/update' +import { Route as authBookingsHostingGatheringIdDashboardIndexRouteImport } from './routes/(auth)/bookings/hosting/$gatheringId/dashboard.index' +import { Route as authBookingsHostingGatheringIdDashboardScanRouteImport } from './routes/(auth)/bookings/hosting/$gatheringId/dashboard.scan' const IndexRoute = IndexRouteImport.update({ - id: "/", - path: "/", - getParentRoute: () => rootRouteImport -} as any); + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) const LoginIndexRoute = LoginIndexRouteImport.update({ - id: "/login/", - path: "/login/", - getParentRoute: () => rootRouteImport -} as any); + id: '/login/', + path: '/login/', + getParentRoute: () => rootRouteImport, +} as any) const HealthcheckIndexRoute = HealthcheckIndexRouteImport.update({ - id: "/healthcheck/", - path: "/healthcheck/", - getParentRoute: () => rootRouteImport -} as any); + id: '/healthcheck/', + path: '/healthcheck/', + getParentRoute: () => rootRouteImport, +} as any) const GatheringsIndexRoute = GatheringsIndexRouteImport.update({ - id: "/gatherings/", - path: "/gatherings/", - getParentRoute: () => rootRouteImport -} as any); + id: '/gatherings/', + path: '/gatherings/', + getParentRoute: () => rootRouteImport, +} as any) const authGatheringsRouteRoute = authGatheringsRouteRouteImport.update({ - id: "/(auth)/gatherings", - path: "/gatherings", - getParentRoute: () => rootRouteImport -} as any); + id: '/(auth)/gatherings', + path: '/gatherings', + getParentRoute: () => rootRouteImport, +} as any) const authBookingsRouteRoute = authBookingsRouteRouteImport.update({ - id: "/(auth)/bookings", - path: "/bookings", - getParentRoute: () => rootRouteImport -} as any); -const GatheringsGatheringIdIndexRoute = GatheringsGatheringIdIndexRouteImport.update({ - id: "/gatherings/$gatheringId/", - path: "/gatherings/$gatheringId/", - getParentRoute: () => rootRouteImport -} as any); + id: '/(auth)/bookings', + path: '/bookings', + getParentRoute: () => rootRouteImport, +} as any) +const GatheringsGatheringIdIndexRoute = + GatheringsGatheringIdIndexRouteImport.update({ + id: '/gatherings/$gatheringId/', + path: '/gatherings/$gatheringId/', + getParentRoute: () => rootRouteImport, + } as any) const authGatheringsCreateRoute = authGatheringsCreateRouteImport.update({ - id: "/create", - path: "/create", - getParentRoute: () => authGatheringsRouteRoute -} as any); -const authBookingsHostingIndexRoute = authBookingsHostingIndexRouteImport.update({ - id: "/hosting/", - path: "/hosting/", - getParentRoute: () => authBookingsRouteRoute -} as any); -const authBookingsAttendingIndexRoute = authBookingsAttendingIndexRouteImport.update({ - id: "/attending/", - path: "/attending/", - getParentRoute: () => authBookingsRouteRoute -} as any); -const authGatheringsGatheringIdUpdateRoute = authGatheringsGatheringIdUpdateRouteImport.update({ - id: "/$gatheringId/update", - path: "/$gatheringId/update", - getParentRoute: () => authGatheringsRouteRoute -} as any); + id: '/create', + path: '/create', + getParentRoute: () => authGatheringsRouteRoute, +} as any) +const authBookingsHostingIndexRoute = + authBookingsHostingIndexRouteImport.update({ + id: '/hosting/', + path: '/hosting/', + getParentRoute: () => authBookingsRouteRoute, + } as any) +const authBookingsAttendingIndexRoute = + authBookingsAttendingIndexRouteImport.update({ + id: '/attending/', + path: '/attending/', + getParentRoute: () => authBookingsRouteRoute, + } as any) +const authGatheringsGatheringIdUpdateRoute = + authGatheringsGatheringIdUpdateRouteImport.update({ + id: '/$gatheringId/update', + path: '/$gatheringId/update', + getParentRoute: () => authGatheringsRouteRoute, + } as any) const authBookingsHostingGatheringIdDashboardIndexRoute = - authBookingsHostingGatheringIdDashboardIndexRouteImport.update({ - id: "/hosting/$gatheringId/dashboard/", - path: "/hosting/$gatheringId/dashboard/", - getParentRoute: () => authBookingsRouteRoute - } as any); + authBookingsHostingGatheringIdDashboardIndexRouteImport.update({ + id: '/hosting/$gatheringId/dashboard/', + path: '/hosting/$gatheringId/dashboard/', + getParentRoute: () => authBookingsRouteRoute, + } as any) const authBookingsHostingGatheringIdDashboardScanRoute = - authBookingsHostingGatheringIdDashboardScanRouteImport.update({ - id: "/hosting/$gatheringId/dashboard/scan", - path: "/hosting/$gatheringId/dashboard/scan", - getParentRoute: () => authBookingsRouteRoute - } as any); + authBookingsHostingGatheringIdDashboardScanRouteImport.update({ + id: '/hosting/$gatheringId/dashboard/scan', + path: '/hosting/$gatheringId/dashboard/scan', + getParentRoute: () => authBookingsRouteRoute, + } as any) export interface FileRoutesByFullPath { - "/": typeof IndexRoute; - "/bookings": typeof authBookingsRouteRouteWithChildren; - "/gatherings": typeof GatheringsIndexRoute; - "/healthcheck": typeof HealthcheckIndexRoute; - "/login": typeof LoginIndexRoute; - "/gatherings/create": typeof authGatheringsCreateRoute; - "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; - "/gatherings/$gatheringId/update": typeof authGatheringsGatheringIdUpdateRoute; - "/bookings/attending": typeof authBookingsAttendingIndexRoute; - "/bookings/hosting": typeof authBookingsHostingIndexRoute; - "/bookings/hosting/$gatheringId/dashboard/scan": typeof authBookingsHostingGatheringIdDashboardScanRoute; - "/bookings/hosting/$gatheringId/dashboard": typeof authBookingsHostingGatheringIdDashboardIndexRoute; + '/': typeof IndexRoute + '/bookings': typeof authBookingsRouteRouteWithChildren + '/gatherings': typeof GatheringsIndexRoute + '/healthcheck': typeof HealthcheckIndexRoute + '/login': typeof LoginIndexRoute + '/gatherings/create': typeof authGatheringsCreateRoute + '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute + '/gatherings/$gatheringId/update': typeof authGatheringsGatheringIdUpdateRoute + '/bookings/attending': typeof authBookingsAttendingIndexRoute + '/bookings/hosting': typeof authBookingsHostingIndexRoute + '/bookings/hosting/$gatheringId/dashboard/scan': typeof authBookingsHostingGatheringIdDashboardScanRoute + '/bookings/hosting/$gatheringId/dashboard': typeof authBookingsHostingGatheringIdDashboardIndexRoute } export interface FileRoutesByTo { - "/": typeof IndexRoute; - "/bookings": typeof authBookingsRouteRouteWithChildren; - "/gatherings": typeof GatheringsIndexRoute; - "/healthcheck": typeof HealthcheckIndexRoute; - "/login": typeof LoginIndexRoute; - "/gatherings/create": typeof authGatheringsCreateRoute; - "/gatherings/$gatheringId": typeof GatheringsGatheringIdIndexRoute; - "/gatherings/$gatheringId/update": typeof authGatheringsGatheringIdUpdateRoute; - "/bookings/attending": typeof authBookingsAttendingIndexRoute; - "/bookings/hosting": typeof authBookingsHostingIndexRoute; - "/bookings/hosting/$gatheringId/dashboard/scan": typeof authBookingsHostingGatheringIdDashboardScanRoute; - "/bookings/hosting/$gatheringId/dashboard": typeof authBookingsHostingGatheringIdDashboardIndexRoute; + '/': typeof IndexRoute + '/bookings': typeof authBookingsRouteRouteWithChildren + '/gatherings': typeof GatheringsIndexRoute + '/healthcheck': typeof HealthcheckIndexRoute + '/login': typeof LoginIndexRoute + '/gatherings/create': typeof authGatheringsCreateRoute + '/gatherings/$gatheringId': typeof GatheringsGatheringIdIndexRoute + '/gatherings/$gatheringId/update': typeof authGatheringsGatheringIdUpdateRoute + '/bookings/attending': typeof authBookingsAttendingIndexRoute + '/bookings/hosting': typeof authBookingsHostingIndexRoute + '/bookings/hosting/$gatheringId/dashboard/scan': typeof authBookingsHostingGatheringIdDashboardScanRoute + '/bookings/hosting/$gatheringId/dashboard': typeof authBookingsHostingGatheringIdDashboardIndexRoute } export interface FileRoutesById { - __root__: typeof rootRouteImport; - "/": typeof IndexRoute; - "/(auth)/bookings": typeof authBookingsRouteRouteWithChildren; - "/(auth)/gatherings": typeof authGatheringsRouteRouteWithChildren; - "/gatherings/": typeof GatheringsIndexRoute; - "/healthcheck/": typeof HealthcheckIndexRoute; - "/login/": typeof LoginIndexRoute; - "/(auth)/gatherings/create": typeof authGatheringsCreateRoute; - "/gatherings/$gatheringId/": typeof GatheringsGatheringIdIndexRoute; - "/(auth)/gatherings/$gatheringId/update": typeof authGatheringsGatheringIdUpdateRoute; - "/(auth)/bookings/attending/": typeof authBookingsAttendingIndexRoute; - "/(auth)/bookings/hosting/": typeof authBookingsHostingIndexRoute; - "/(auth)/bookings/hosting/$gatheringId/dashboard/scan": typeof authBookingsHostingGatheringIdDashboardScanRoute; - "/(auth)/bookings/hosting/$gatheringId/dashboard/": typeof authBookingsHostingGatheringIdDashboardIndexRoute; + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/(auth)/bookings': typeof authBookingsRouteRouteWithChildren + '/(auth)/gatherings': typeof authGatheringsRouteRouteWithChildren + '/gatherings/': typeof GatheringsIndexRoute + '/healthcheck/': typeof HealthcheckIndexRoute + '/login/': typeof LoginIndexRoute + '/(auth)/gatherings/create': typeof authGatheringsCreateRoute + '/gatherings/$gatheringId/': typeof GatheringsGatheringIdIndexRoute + '/(auth)/gatherings/$gatheringId/update': typeof authGatheringsGatheringIdUpdateRoute + '/(auth)/bookings/attending/': typeof authBookingsAttendingIndexRoute + '/(auth)/bookings/hosting/': typeof authBookingsHostingIndexRoute + '/(auth)/bookings/hosting/$gatheringId/dashboard/scan': typeof authBookingsHostingGatheringIdDashboardScanRoute + '/(auth)/bookings/hosting/$gatheringId/dashboard/': typeof authBookingsHostingGatheringIdDashboardIndexRoute } export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: - | "/" - | "/bookings" - | "/gatherings" - | "/healthcheck" - | "/login" - | "/gatherings/create" - | "/gatherings/$gatheringId" - | "/gatherings/$gatheringId/update" - | "/bookings/attending" - | "/bookings/hosting" - | "/bookings/hosting/$gatheringId/dashboard/scan" - | "/bookings/hosting/$gatheringId/dashboard"; - fileRoutesByTo: FileRoutesByTo; - to: - | "/" - | "/bookings" - | "/gatherings" - | "/healthcheck" - | "/login" - | "/gatherings/create" - | "/gatherings/$gatheringId" - | "/gatherings/$gatheringId/update" - | "/bookings/attending" - | "/bookings/hosting" - | "/bookings/hosting/$gatheringId/dashboard/scan" - | "/bookings/hosting/$gatheringId/dashboard"; - id: - | "__root__" - | "/" - | "/(auth)/bookings" - | "/(auth)/gatherings" - | "/gatherings/" - | "/healthcheck/" - | "/login/" - | "/(auth)/gatherings/create" - | "/gatherings/$gatheringId/" - | "/(auth)/gatherings/$gatheringId/update" - | "/(auth)/bookings/attending/" - | "/(auth)/bookings/hosting/" - | "/(auth)/bookings/hosting/$gatheringId/dashboard/scan" - | "/(auth)/bookings/hosting/$gatheringId/dashboard/"; - fileRoutesById: FileRoutesById; + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/bookings' + | '/gatherings' + | '/healthcheck' + | '/login' + | '/gatherings/create' + | '/gatherings/$gatheringId' + | '/gatherings/$gatheringId/update' + | '/bookings/attending' + | '/bookings/hosting' + | '/bookings/hosting/$gatheringId/dashboard/scan' + | '/bookings/hosting/$gatheringId/dashboard' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/bookings' + | '/gatherings' + | '/healthcheck' + | '/login' + | '/gatherings/create' + | '/gatherings/$gatheringId' + | '/gatherings/$gatheringId/update' + | '/bookings/attending' + | '/bookings/hosting' + | '/bookings/hosting/$gatheringId/dashboard/scan' + | '/bookings/hosting/$gatheringId/dashboard' + id: + | '__root__' + | '/' + | '/(auth)/bookings' + | '/(auth)/gatherings' + | '/gatherings/' + | '/healthcheck/' + | '/login/' + | '/(auth)/gatherings/create' + | '/gatherings/$gatheringId/' + | '/(auth)/gatherings/$gatheringId/update' + | '/(auth)/bookings/attending/' + | '/(auth)/bookings/hosting/' + | '/(auth)/bookings/hosting/$gatheringId/dashboard/scan' + | '/(auth)/bookings/hosting/$gatheringId/dashboard/' + fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute; - authBookingsRouteRoute: typeof authBookingsRouteRouteWithChildren; - authGatheringsRouteRoute: typeof authGatheringsRouteRouteWithChildren; - GatheringsIndexRoute: typeof GatheringsIndexRoute; - HealthcheckIndexRoute: typeof HealthcheckIndexRoute; - LoginIndexRoute: typeof LoginIndexRoute; - GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute; + IndexRoute: typeof IndexRoute + authBookingsRouteRoute: typeof authBookingsRouteRouteWithChildren + authGatheringsRouteRoute: typeof authGatheringsRouteRouteWithChildren + GatheringsIndexRoute: typeof GatheringsIndexRoute + HealthcheckIndexRoute: typeof HealthcheckIndexRoute + LoginIndexRoute: typeof LoginIndexRoute + GatheringsGatheringIdIndexRoute: typeof GatheringsGatheringIdIndexRoute } -declare module "@tanstack/react-router" { - interface FileRoutesByPath { - "/": { - id: "/"; - path: "/"; - fullPath: "/"; - preLoaderRoute: typeof IndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/login/": { - id: "/login/"; - path: "/login"; - fullPath: "/login"; - preLoaderRoute: typeof LoginIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/healthcheck/": { - id: "/healthcheck/"; - path: "/healthcheck"; - fullPath: "/healthcheck"; - preLoaderRoute: typeof HealthcheckIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/gatherings/": { - id: "/gatherings/"; - path: "/gatherings"; - fullPath: "/gatherings"; - preLoaderRoute: typeof GatheringsIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/(auth)/gatherings": { - id: "/(auth)/gatherings"; - path: "/gatherings"; - fullPath: "/gatherings"; - preLoaderRoute: typeof authGatheringsRouteRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/(auth)/bookings": { - id: "/(auth)/bookings"; - path: "/bookings"; - fullPath: "/bookings"; - preLoaderRoute: typeof authBookingsRouteRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/gatherings/$gatheringId/": { - id: "/gatherings/$gatheringId/"; - path: "/gatherings/$gatheringId"; - fullPath: "/gatherings/$gatheringId"; - preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport; - parentRoute: typeof rootRouteImport; - }; - "/(auth)/gatherings/create": { - id: "/(auth)/gatherings/create"; - path: "/create"; - fullPath: "/gatherings/create"; - preLoaderRoute: typeof authGatheringsCreateRouteImport; - parentRoute: typeof authGatheringsRouteRoute; - }; - "/(auth)/bookings/hosting/": { - id: "/(auth)/bookings/hosting/"; - path: "/hosting"; - fullPath: "/bookings/hosting"; - preLoaderRoute: typeof authBookingsHostingIndexRouteImport; - parentRoute: typeof authBookingsRouteRoute; - }; - "/(auth)/bookings/attending/": { - id: "/(auth)/bookings/attending/"; - path: "/attending"; - fullPath: "/bookings/attending"; - preLoaderRoute: typeof authBookingsAttendingIndexRouteImport; - parentRoute: typeof authBookingsRouteRoute; - }; - "/(auth)/gatherings/$gatheringId/update": { - id: "/(auth)/gatherings/$gatheringId/update"; - path: "/$gatheringId/update"; - fullPath: "/gatherings/$gatheringId/update"; - preLoaderRoute: typeof authGatheringsGatheringIdUpdateRouteImport; - parentRoute: typeof authGatheringsRouteRoute; - }; - "/(auth)/bookings/hosting/$gatheringId/dashboard/": { - id: "/(auth)/bookings/hosting/$gatheringId/dashboard/"; - path: "/hosting/$gatheringId/dashboard"; - fullPath: "/bookings/hosting/$gatheringId/dashboard"; - preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardIndexRouteImport; - parentRoute: typeof authBookingsRouteRoute; - }; - "/(auth)/bookings/hosting/$gatheringId/dashboard/scan": { - id: "/(auth)/bookings/hosting/$gatheringId/dashboard/scan"; - path: "/hosting/$gatheringId/dashboard/scan"; - fullPath: "/bookings/hosting/$gatheringId/dashboard/scan"; - preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardScanRouteImport; - parentRoute: typeof authBookingsRouteRoute; - }; - } +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/login/': { + id: '/login/' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/healthcheck/': { + id: '/healthcheck/' + path: '/healthcheck' + fullPath: '/healthcheck' + preLoaderRoute: typeof HealthcheckIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/gatherings/': { + id: '/gatherings/' + path: '/gatherings' + fullPath: '/gatherings' + preLoaderRoute: typeof GatheringsIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/(auth)/gatherings': { + id: '/(auth)/gatherings' + path: '/gatherings' + fullPath: '/gatherings' + preLoaderRoute: typeof authGatheringsRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/(auth)/bookings': { + id: '/(auth)/bookings' + path: '/bookings' + fullPath: '/bookings' + preLoaderRoute: typeof authBookingsRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/gatherings/$gatheringId/': { + id: '/gatherings/$gatheringId/' + path: '/gatherings/$gatheringId' + fullPath: '/gatherings/$gatheringId' + preLoaderRoute: typeof GatheringsGatheringIdIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/(auth)/gatherings/create': { + id: '/(auth)/gatherings/create' + path: '/create' + fullPath: '/gatherings/create' + preLoaderRoute: typeof authGatheringsCreateRouteImport + parentRoute: typeof authGatheringsRouteRoute + } + '/(auth)/bookings/hosting/': { + id: '/(auth)/bookings/hosting/' + path: '/hosting' + fullPath: '/bookings/hosting' + preLoaderRoute: typeof authBookingsHostingIndexRouteImport + parentRoute: typeof authBookingsRouteRoute + } + '/(auth)/bookings/attending/': { + id: '/(auth)/bookings/attending/' + path: '/attending' + fullPath: '/bookings/attending' + preLoaderRoute: typeof authBookingsAttendingIndexRouteImport + parentRoute: typeof authBookingsRouteRoute + } + '/(auth)/gatherings/$gatheringId/update': { + id: '/(auth)/gatherings/$gatheringId/update' + path: '/$gatheringId/update' + fullPath: '/gatherings/$gatheringId/update' + preLoaderRoute: typeof authGatheringsGatheringIdUpdateRouteImport + parentRoute: typeof authGatheringsRouteRoute + } + '/(auth)/bookings/hosting/$gatheringId/dashboard/': { + id: '/(auth)/bookings/hosting/$gatheringId/dashboard/' + path: '/hosting/$gatheringId/dashboard' + fullPath: '/bookings/hosting/$gatheringId/dashboard' + preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardIndexRouteImport + parentRoute: typeof authBookingsRouteRoute + } + '/(auth)/bookings/hosting/$gatheringId/dashboard/scan': { + id: '/(auth)/bookings/hosting/$gatheringId/dashboard/scan' + path: '/hosting/$gatheringId/dashboard/scan' + fullPath: '/bookings/hosting/$gatheringId/dashboard/scan' + preLoaderRoute: typeof authBookingsHostingGatheringIdDashboardScanRouteImport + parentRoute: typeof authBookingsRouteRoute + } + } } interface authBookingsRouteRouteChildren { - authBookingsAttendingIndexRoute: typeof authBookingsAttendingIndexRoute; - authBookingsHostingIndexRoute: typeof authBookingsHostingIndexRoute; - authBookingsHostingGatheringIdDashboardScanRoute: typeof authBookingsHostingGatheringIdDashboardScanRoute; - authBookingsHostingGatheringIdDashboardIndexRoute: typeof authBookingsHostingGatheringIdDashboardIndexRoute; + authBookingsAttendingIndexRoute: typeof authBookingsAttendingIndexRoute + authBookingsHostingIndexRoute: typeof authBookingsHostingIndexRoute + authBookingsHostingGatheringIdDashboardScanRoute: typeof authBookingsHostingGatheringIdDashboardScanRoute + authBookingsHostingGatheringIdDashboardIndexRoute: typeof authBookingsHostingGatheringIdDashboardIndexRoute } const authBookingsRouteRouteChildren: authBookingsRouteRouteChildren = { - authBookingsAttendingIndexRoute: authBookingsAttendingIndexRoute, - authBookingsHostingIndexRoute: authBookingsHostingIndexRoute, - authBookingsHostingGatheringIdDashboardScanRoute: - authBookingsHostingGatheringIdDashboardScanRoute, - authBookingsHostingGatheringIdDashboardIndexRoute: - authBookingsHostingGatheringIdDashboardIndexRoute -}; + authBookingsAttendingIndexRoute: authBookingsAttendingIndexRoute, + authBookingsHostingIndexRoute: authBookingsHostingIndexRoute, + authBookingsHostingGatheringIdDashboardScanRoute: + authBookingsHostingGatheringIdDashboardScanRoute, + authBookingsHostingGatheringIdDashboardIndexRoute: + authBookingsHostingGatheringIdDashboardIndexRoute, +} -const authBookingsRouteRouteWithChildren = authBookingsRouteRoute._addFileChildren( - authBookingsRouteRouteChildren -); +const authBookingsRouteRouteWithChildren = + authBookingsRouteRoute._addFileChildren(authBookingsRouteRouteChildren) interface authGatheringsRouteRouteChildren { - authGatheringsCreateRoute: typeof authGatheringsCreateRoute; - authGatheringsGatheringIdUpdateRoute: typeof authGatheringsGatheringIdUpdateRoute; + authGatheringsCreateRoute: typeof authGatheringsCreateRoute + authGatheringsGatheringIdUpdateRoute: typeof authGatheringsGatheringIdUpdateRoute } const authGatheringsRouteRouteChildren: authGatheringsRouteRouteChildren = { - authGatheringsCreateRoute: authGatheringsCreateRoute, - authGatheringsGatheringIdUpdateRoute: authGatheringsGatheringIdUpdateRoute -}; + authGatheringsCreateRoute: authGatheringsCreateRoute, + authGatheringsGatheringIdUpdateRoute: authGatheringsGatheringIdUpdateRoute, +} -const authGatheringsRouteRouteWithChildren = authGatheringsRouteRoute._addFileChildren( - authGatheringsRouteRouteChildren -); +const authGatheringsRouteRouteWithChildren = + authGatheringsRouteRoute._addFileChildren(authGatheringsRouteRouteChildren) const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - authBookingsRouteRoute: authBookingsRouteRouteWithChildren, - authGatheringsRouteRoute: authGatheringsRouteRouteWithChildren, - GatheringsIndexRoute: GatheringsIndexRoute, - HealthcheckIndexRoute: HealthcheckIndexRoute, - LoginIndexRoute: LoginIndexRoute, - GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute -}; + IndexRoute: IndexRoute, + authBookingsRouteRoute: authBookingsRouteRouteWithChildren, + authGatheringsRouteRoute: authGatheringsRouteRouteWithChildren, + GatheringsIndexRoute: GatheringsIndexRoute, + HealthcheckIndexRoute: HealthcheckIndexRoute, + LoginIndexRoute: LoginIndexRoute, + GatheringsGatheringIdIndexRoute: GatheringsGatheringIdIndexRoute, +} export const routeTree = rootRouteImport - ._addFileChildren(rootRouteChildren) - ._addFileTypes(); + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx index 8f528c4..989a8b5 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx @@ -6,13 +6,15 @@ import { DateTime } from "luxon"; import { GatheringReqDto, ToastContent } from "~/lib/domains/models"; import { useRouter } from "@tanstack/react-router"; import { toIsoString } from "~/lib/services"; +import {Category} from "~/lib/domains/entities"; interface GatheringFormProps { file: File | null; setFile: (file: File | null) => void; form: IGatheringForm; + categories: Category[]; } -export function GatheringForm({ file, setFile, form }: GatheringFormProps): JSX.Element { +export function GatheringForm({ file, setFile, form, categories }: GatheringFormProps): JSX.Element { const router = useRouter(); const fileName: string = file?.name ?? ""; const coverSrc: string = diff --git a/src/evently.client/src/routes/(auth)/gatherings/create.tsx b/src/evently.client/src/routes/(auth)/gatherings/create.tsx index bd4985a..52f35fc 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/create.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/create.tsx @@ -1,16 +1,21 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Gathering } from "~/lib/domains/entities"; +import {Category, Gathering} from "~/lib/domains/entities"; import { useState, type JSX } from "react"; -import { createGathering, sleep } from "~/lib/services"; +import {createGathering, getCategories, sleep} from "~/lib/services"; import { useGatheringForm, type GatheringForm as IGatheringForm } from "./-services"; import { GatheringReqDto, ToastContent } from "~/lib/domains/models"; import { GatheringForm } from "~/routes/(auth)/gatherings/-components"; export const Route = createFileRoute("/(auth)/gatherings/create")({ + loader: async () => { + const categories: Category[] = await getCategories(); + return { categories }; + }, component: CreateGatheringPage }); function CreateGatheringPage(): JSX.Element { + const { categories } = Route.useLoaderData(); const { account } = Route.useRouteContext(); const accountId: string | undefined = account?.id; @@ -36,7 +41,7 @@ function CreateGatheringPage(): JSX.Element { const form: IGatheringForm = useGatheringForm(defaultGathering, onSubmit); return ( <> - + {toastMsg.show && (
From fe82d7f0b6aa153d4c11a7ad1b9b40ba791774c9 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Thu, 28 Aug 2025 20:32:22 +0800 Subject: [PATCH 2/8] chore: multi select --- .../src/lib/domains/models/upsert-dtos.ts | 8 +- .../src/lib/services/category-service.ts | 4 +- src/evently.client/src/lib/services/index.ts | 2 +- .../(auth)/gatherings/$gatheringId/update.tsx | 14 ++-- .../gatherings/-components/gathering-form.tsx | 82 ++++++++++++++++--- .../src/routes/(auth)/gatherings/create.tsx | 4 +- 6 files changed, 91 insertions(+), 23 deletions(-) diff --git a/src/evently.client/src/lib/domains/models/upsert-dtos.ts b/src/evently.client/src/lib/domains/models/upsert-dtos.ts index 23d601d..66b7b63 100644 --- a/src/evently.client/src/lib/domains/models/upsert-dtos.ts +++ b/src/evently.client/src/lib/domains/models/upsert-dtos.ts @@ -22,8 +22,14 @@ export class GatheringReqDto { organiserId = ""; coverSrc?: string | null = null; cancellationDateTime: Date | null = null; - + gatheringCategoryDetails: GatheringCategoryDetailReqDto[] = []; + constructor(partial: Partial = {}) { Object.assign(this, partial); } } + +export class GatheringCategoryDetailReqDto { + gatheringId = 0; + categoryId = 0; +} diff --git a/src/evently.client/src/lib/services/category-service.ts b/src/evently.client/src/lib/services/category-service.ts index bc8c264..1bc3152 100644 --- a/src/evently.client/src/lib/services/category-service.ts +++ b/src/evently.client/src/lib/services/category-service.ts @@ -1,7 +1,7 @@ -import {Category} from "~/lib/domains/entities"; +import { Category } from "~/lib/domains/entities"; import axios from "axios"; export async function getCategories(): Promise { const response = await axios.get("/api/v1/Categories"); return response.data; -} \ No newline at end of file +} diff --git a/src/evently.client/src/lib/services/index.ts b/src/evently.client/src/lib/services/index.ts index fe8f783..11e4e61 100644 --- a/src/evently.client/src/lib/services/index.ts +++ b/src/evently.client/src/lib/services/index.ts @@ -3,4 +3,4 @@ export * from "./auth-service"; export * from "./store"; export * from "./gathering-service.ts"; export * from "./booking-service"; -export * from "./category-service.ts"; \ No newline at end of file +export * from "./category-service.ts"; diff --git a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx index 64bf440..1a9be55 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx @@ -1,7 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Gathering } from "~/lib/domains/entities"; +import { Category, Gathering } from "~/lib/domains/entities"; import { useState, type JSX } from "react"; -import { getGathering, sleep, updateGathering } from "~/lib/services"; +import { getCategories, getGathering, sleep, updateGathering } from "~/lib/services"; import { useGatheringForm, type GatheringForm as IGatheringForm @@ -12,14 +12,16 @@ import { GatheringForm } from "~/routes/(auth)/gatherings/-components"; export const Route = createFileRoute("/(auth)/gatherings/$gatheringId/update")({ loader: async ({ params }) => { const gatheringId: number = parseInt(params.gatheringId); - const gathering: Gathering | null = await getGathering(gatheringId); - return gathering ?? new Gathering(); + let gathering: Gathering | null = await getGathering(gatheringId); + gathering = gathering ?? new Gathering(); + const categories: Category[] = await getCategories(); + return { gathering, categories }; }, component: UpdateGatheringPage }); function UpdateGatheringPage(): JSX.Element { - const gathering: Gathering = Route.useLoaderData(); + const { gathering, categories } = Route.useLoaderData(); const navigate = Route.useNavigate(); const defaultGathering: GatheringReqDto = { ...gathering @@ -38,7 +40,7 @@ function UpdateGatheringPage(): JSX.Element { const form: IGatheringForm = useGatheringForm(defaultGathering, onSubmit); return ( <> - + {toastMsg.show && (
diff --git a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx index 989a8b5..91550f9 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx @@ -3,10 +3,11 @@ import { compressImage, type GatheringForm as IGatheringForm } from "../-service import { FieldErrMsg as FieldInfo } from "~/lib/components"; import { Icon } from "@iconify/react"; import { DateTime } from "luxon"; -import { GatheringReqDto, ToastContent } from "~/lib/domains/models"; +import { GatheringCategoryDetailReqDto, GatheringReqDto, ToastContent } from "~/lib/domains/models"; import { useRouter } from "@tanstack/react-router"; import { toIsoString } from "~/lib/services"; -import {Category} from "~/lib/domains/entities"; +import { Category } from "~/lib/domains/entities"; + interface GatheringFormProps { file: File | null; setFile: (file: File | null) => void; @@ -14,7 +15,12 @@ interface GatheringFormProps { categories: Category[]; } -export function GatheringForm({ file, setFile, form, categories }: GatheringFormProps): JSX.Element { +export function GatheringForm({ + file, + setFile, + form, + categories +}: GatheringFormProps): JSX.Element { const router = useRouter(); const fileName: string = file?.name ?? ""; const coverSrc: string = @@ -99,6 +105,53 @@ export function GatheringForm({ file, setFile, form, categories }: GatheringForm )} /> + ( +
+ Categories +
    + {categories.map((category) => { + const wasChecked = field.state.value.some( + (detail) => detail.categoryId === category.categoryId + ); + return ( +
  • + +
  • + ); + })} +
+
+ )} + /> + -

{fileName}

{coverSrc != null && coverSrc.trim() !== "" && ( - Floor Plan + <> + Floor Plan +
+ {fileName} + {" "} +
+ )}
diff --git a/src/evently.client/src/routes/(auth)/gatherings/create.tsx b/src/evently.client/src/routes/(auth)/gatherings/create.tsx index 52f35fc..b2c3abf 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/create.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/create.tsx @@ -1,7 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; -import {Category, Gathering} from "~/lib/domains/entities"; +import { Category, Gathering } from "~/lib/domains/entities"; import { useState, type JSX } from "react"; -import {createGathering, getCategories, sleep} from "~/lib/services"; +import { createGathering, getCategories, sleep } from "~/lib/services"; import { useGatheringForm, type GatheringForm as IGatheringForm } from "./-services"; import { GatheringReqDto, ToastContent } from "~/lib/domains/models"; import { GatheringForm } from "~/routes/(auth)/gatherings/-components"; From 2a74e9072c49d61446815a0d4cff76cf599ffbce Mon Sep 17 00:00:00 2001 From: eugbyte Date: Thu, 28 Aug 2025 21:31:52 +0800 Subject: [PATCH 3/8] fix: select --- .../src/lib/services/util-service.ts | 15 +++ .../(auth)/gatherings/$gatheringId/update.tsx | 16 ++- .../gatherings/-components/gathering-form.tsx | 105 ++++++++++-------- 3 files changed, 90 insertions(+), 46 deletions(-) diff --git a/src/evently.client/src/lib/services/util-service.ts b/src/evently.client/src/lib/services/util-service.ts index d1dd599..80822a1 100644 --- a/src/evently.client/src/lib/services/util-service.ts +++ b/src/evently.client/src/lib/services/util-service.ts @@ -47,3 +47,18 @@ export function toIsoString(date: Date | null): string { const dateTime: DateTime = DateTime.fromJSDate(date); return dateTime.toFormat("yyyy-MM-dd'T'HH:mm"); } + +export async function fetchFile(src: string, fileName?: string): Promise { + const urlObj = new URL(src); + if (fileName == null) { + fileName = urlObj.pathname.split("/").pop() ?? ""; + } + + const response = await fetch(src); + if (!response.ok) { + throw new Error(`Failed to fetch ${src}`); + } + const dataBlob: Blob = await response.blob(); + const file: File = new File([dataBlob], fileName, { type: dataBlob.type }); + return file; +} diff --git a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx index 1a9be55..1b2c602 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx @@ -1,7 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; import { Category, Gathering } from "~/lib/domains/entities"; -import { useState, type JSX } from "react"; -import { getCategories, getGathering, sleep, updateGathering } from "~/lib/services"; +import { useEffect, useState, type JSX } from "react"; +import { fetchFile, getCategories, getGathering, sleep, updateGathering } from "~/lib/services"; import { useGatheringForm, type GatheringForm as IGatheringForm @@ -38,6 +38,18 @@ function UpdateGatheringPage(): JSX.Element { navigate({ to: `/gatherings/${gathering.gatheringId}` }); }; const form: IGatheringForm = useGatheringForm(defaultGathering, onSubmit); + + useEffect(() => { + // set the initial file + (async () => { + const coverSrc: string | null = gathering.coverSrc ?? null; + if (coverSrc == null) { + return; + } + const file: File = await fetchFile(coverSrc); + setFile(file); + })(); + }, [gathering]); return ( <> diff --git a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx index 91550f9..19475da 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx @@ -23,10 +23,14 @@ export function GatheringForm({ }: GatheringFormProps): JSX.Element { const router = useRouter(); const fileName: string = file?.name ?? ""; - const coverSrc: string = - file != null ? URL.createObjectURL(file) : (form.state.values.coverSrc ?? ""); + const coverSrc = file != null ? URL.createObjectURL(file) : (form.state.values.coverSrc ?? ""); + const gathering: GatheringReqDto = form.state.values; const [toastMsg, setToastMsg] = useState(new ToastContent(false)); + const categoryDict: Record = {}; + for (const category of categories) { + categoryDict[category.categoryId] = category; + } useEffect(() => { // prevent memory leak @@ -108,47 +112,56 @@ export function GatheringForm({ ( -
- Categories -
    - {categories.map((category) => { - const wasChecked = field.state.value.some( - (detail) => detail.categoryId === category.categoryId - ); - return ( -
  • - +
  • + ); + })} +
+
+
+ {field.state.value.map((detail) => ( +
+ {categoryDict[detail.categoryId]?.categoryName} +
+ ))} +
+ )} /> @@ -266,9 +279,13 @@ export function GatheringForm({ height="350px" className="block" /> -
+
{fileName} - {" "}
From 1f2a9c369d0d9467929a605cd2ee771b4dd4eea5 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Fri, 29 Aug 2025 08:37:45 +0800 Subject: [PATCH 4/8] chore: dto --- .../Domains/Models/GatheringCategoryDetailDto.cs | 3 +++ .../Common/Domains/Models/GatheringReqDto.cs | 3 ++- .../Common/Extensions/MapperExtension.cs | 13 ++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs diff --git a/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs b/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs new file mode 100644 index 0000000..601ed2d --- /dev/null +++ b/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs @@ -0,0 +1,3 @@ +namespace Evently.Server.Common.Domains.Models; + +public sealed record GatheringCategoryDetailDto(long GatheringId, long CategoryId); \ No newline at end of file diff --git a/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs b/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs index e5bb67c..1f45531 100644 --- a/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs +++ b/src/Evently.Server/Common/Domains/Models/GatheringReqDto.cs @@ -9,5 +9,6 @@ public sealed record GatheringReqDto( DateTimeOffset? CancellationDateTime, string Location, string OrganiserId, - string? CoverSrc + string? CoverSrc, + List GatheringCategoryDetails ); \ No newline at end of file diff --git a/src/Evently.Server/Common/Extensions/MapperExtension.cs b/src/Evently.Server/Common/Extensions/MapperExtension.cs index 01d0eee..9b85774 100644 --- a/src/Evently.Server/Common/Extensions/MapperExtension.cs +++ b/src/Evently.Server/Common/Extensions/MapperExtension.cs @@ -5,6 +5,12 @@ namespace Evently.Server.Common.Extensions; public static class MapperExtension { public static Gathering ToGathering(this GatheringReqDto gatheringReqDto) { + List gatheringCategoryDetails = gatheringReqDto.GatheringCategoryDetails + .Select((detail) => new GatheringCategoryDetail { + GatheringId = gatheringReqDto.GatheringId, + CategoryId = detail.CategoryId, + }) + .ToList(); Gathering gathering = new() { GatheringId = gatheringReqDto.GatheringId, Name = gatheringReqDto.Name, @@ -15,11 +21,15 @@ public static Gathering ToGathering(this GatheringReqDto gatheringReqDto) { Location = gatheringReqDto.Location, OrganiserId = gatheringReqDto.OrganiserId, CoverSrc = gatheringReqDto.CoverSrc, + GatheringCategoryDetails = gatheringCategoryDetails, }; return gathering; } public static GatheringReqDto ToGatheringDto(this Gathering gathering) { + List gatheringCategoryDetails = gathering.GatheringCategoryDetails + .Select(detail => new GatheringCategoryDetailDto(detail.GatheringId, detail.CategoryId)) + .ToList(); GatheringReqDto reqDto = new( gathering.GatheringId, gathering.Name, @@ -29,7 +39,8 @@ public static GatheringReqDto ToGatheringDto(this Gathering gathering) { gathering.CancellationDateTime, gathering.Location, gathering.OrganiserId, - gathering.CoverSrc + gathering.CoverSrc, + gatheringCategoryDetails ); return reqDto; } From 7bd133b666c3b34cc77afb4d2f383a52e2a6fafa Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 30 Aug 2025 11:50:39 +0800 Subject: [PATCH 5/8] fix: post create --- .../Models/GatheringCategoryDetailDto.cs | 1 + .../Gatherings/Services/GatheringService.cs | 2 + .../src/lib/components/card.tsx | 2 +- .../src/lib/services/gathering-service.ts | 38 ++++++++++--------- .../(auth)/gatherings/$gatheringId/update.tsx | 1 + .../gatherings/-components/gathering-form.tsx | 2 + 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs b/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs index 601ed2d..e20c703 100644 --- a/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs +++ b/src/Evently.Server/Common/Domains/Models/GatheringCategoryDetailDto.cs @@ -1,3 +1,4 @@ namespace Evently.Server.Common.Domains.Models; +// [{"categoryId":1,"gatheringId":22},{"categoryId":2,"gatheringId":22}] public sealed record GatheringCategoryDetailDto(long GatheringId, long CategoryId); \ No newline at end of file diff --git a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs index aa43552..377528c 100644 --- a/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs +++ b/src/Evently.Server/Features/Gatherings/Services/GatheringService.cs @@ -78,6 +78,7 @@ public async Task UpdateGathering(long gatheringId, GatheringReqDto g } Gathering current = await db.Gatherings.AsTracking() + .Include((g) => g.GatheringCategoryDetails) .FirstOrDefaultAsync((ex) => ex.GatheringId == gatheringId) ?? throw new KeyNotFoundException($"{gatheringId} not found"); @@ -87,6 +88,7 @@ public async Task UpdateGathering(long gatheringId, GatheringReqDto g current.End = gathering.End; current.Location = gathering.Location; current.CoverSrc = gathering.CoverSrc; + current.GatheringCategoryDetails = gathering.GatheringCategoryDetails; await db.SaveChangesAsync(); return current; diff --git a/src/evently.client/src/lib/components/card.tsx b/src/evently.client/src/lib/components/card.tsx index 9bfcffb..2413ffe 100644 --- a/src/evently.client/src/lib/components/card.tsx +++ b/src/evently.client/src/lib/components/card.tsx @@ -55,7 +55,7 @@ export function Card({ gathering, accountId }: CardProps): JSX.Element { {`${start.toLocaleString(DateTime.DATETIME_MED)} — ${end.toLocaleString(DateTime.DATETIME_MED)}`}
-
+
{categories.map((category) => (
{category.categoryName} diff --git a/src/evently.client/src/lib/services/gathering-service.ts b/src/evently.client/src/lib/services/gathering-service.ts index cc3c1d6..8e9f1fe 100644 --- a/src/evently.client/src/lib/services/gathering-service.ts +++ b/src/evently.client/src/lib/services/gathering-service.ts @@ -1,6 +1,6 @@ import type { Gathering } from "~/lib/domains/entities"; import axios from "axios"; -import { GatheringReqDto } from "~/lib/domains/models"; +import { GatheringCategoryDetailReqDto, GatheringReqDto } from "~/lib/domains/models"; import type { PageResult } from "~/lib/domains/interfaces"; export interface GetGatheringsParams { @@ -43,17 +43,7 @@ export async function createGathering( gatheringDto: GatheringReqDto, coverImg?: File | null ): Promise { - const formData = new FormData(); - for (const [key, value] of Object.entries(gatheringDto)) { - if (value != null) { - formData.set(key, value); - } - } - if (coverImg != null) { - formData.set("coverImg", coverImg, coverImg.name); - } - formData.set("start", gatheringDto.start.toISOString()); - formData.set("end", gatheringDto.end.toISOString()); + const formData: FormData = toFormData(gatheringDto, coverImg); const response = await axios.post(`/api/v1/Gatherings`, formData, { headers: { "Content-Type": "multipart/form-data" } @@ -66,12 +56,30 @@ export async function updateGathering( gatheringDto: GatheringReqDto, coverImg?: File | null ): Promise { + const formData: FormData = toFormData(gatheringDto, coverImg); + + const response = await axios.put(`/api/v1/Gatherings/${gatheringId}`, formData, { + headers: { "Content-Type": "multipart/form-data" } + }); + return response.data; +} + +function toFormData(gatheringDto: GatheringReqDto, coverImg?: File | null): FormData { const formData = new FormData(); for (const [key, value] of Object.entries(gatheringDto)) { if (value != null) { formData.set(key, value); } } + formData.delete("gatheringCategoryDetails"); + + for (let i = 0; i < gatheringDto.gatheringCategoryDetails.length; i++) { + const detail: GatheringCategoryDetailReqDto = gatheringDto.gatheringCategoryDetails[i]; + for (const [key, value] of Object.entries(detail)) { + formData.set(`gatheringCategoryDetails[${i}].${key}`, value); + } + } + if (coverImg != null) { formData.set("coverImg", coverImg, coverImg.name); } @@ -80,9 +88,5 @@ export async function updateGathering( if (gatheringDto.cancellationDateTime != null) { formData.set("cancellationDateTime", gatheringDto.cancellationDateTime.toISOString()); } - - const response = await axios.put(`/api/v1/Gatherings/${gatheringId}`, formData, { - headers: { "Content-Type": "multipart/form-data" } - }); - return response.data; + return formData; } diff --git a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx index 1b2c602..2a8bd07 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx @@ -32,6 +32,7 @@ function UpdateGatheringPage(): JSX.Element { const onSubmit = async (values: GatheringReqDto): Promise => { setToastMsg(new ToastContent(true, "Updating...")); + console.log({ values }); await updateGathering(values.gatheringId, values, file); setToastMsg(new ToastContent(true, "Successfully updated. Redirecting...")); await sleep(1500); diff --git a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx index 19475da..be3c087 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx @@ -144,6 +144,8 @@ export function GatheringForm({ ); } + console.log({ newValue }); + field.handleChange(newValue); }} /> From 743082327da2079cb115cb394fcaa5b7d554fefa Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 30 Aug 2025 11:52:16 +0800 Subject: [PATCH 6/8] chore: form category --- src/evently.client/src/routes/gatherings/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evently.client/src/routes/gatherings/index.tsx b/src/evently.client/src/routes/gatherings/index.tsx index 63e176c..4eecb98 100644 --- a/src/evently.client/src/routes/gatherings/index.tsx +++ b/src/evently.client/src/routes/gatherings/index.tsx @@ -22,7 +22,7 @@ export function GatheringsPage(): JSX.Element { const pageSize = 6; const [queryParams, setQueryParams] = useState({ - startDateAfter: new Date(), + endDateAfter: new Date(), offset: 0, limit: pageSize }); From be5da23e91058b2b32344c2bed0c772522d53d73 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 30 Aug 2025 11:58:16 +0800 Subject: [PATCH 7/8] chore: code review --- src/evently.client/src/lib/components/card.tsx | 2 +- .../src/routes/(auth)/gatherings/$gatheringId/update.tsx | 1 - .../routes/(auth)/gatherings/-components/gathering-form.tsx | 3 --- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/evently.client/src/lib/components/card.tsx b/src/evently.client/src/lib/components/card.tsx index 2413ffe..512083f 100644 --- a/src/evently.client/src/lib/components/card.tsx +++ b/src/evently.client/src/lib/components/card.tsx @@ -55,7 +55,7 @@ export function Card({ gathering, accountId }: CardProps): JSX.Element { {`${start.toLocaleString(DateTime.DATETIME_MED)} — ${end.toLocaleString(DateTime.DATETIME_MED)}`}
-
+
{categories.map((category) => (
{category.categoryName} diff --git a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx index 2a8bd07..1b2c602 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/$gatheringId/update.tsx @@ -32,7 +32,6 @@ function UpdateGatheringPage(): JSX.Element { const onSubmit = async (values: GatheringReqDto): Promise => { setToastMsg(new ToastContent(true, "Updating...")); - console.log({ values }); await updateGathering(values.gatheringId, values, file); setToastMsg(new ToastContent(true, "Successfully updated. Redirecting...")); await sleep(1500); diff --git a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx index be3c087..07f5df7 100644 --- a/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx +++ b/src/evently.client/src/routes/(auth)/gatherings/-components/gathering-form.tsx @@ -143,9 +143,6 @@ export function GatheringForm({ (detail) => detail.categoryId !== category.categoryId ); } - - console.log({ newValue }); - field.handleChange(newValue); }} /> From 4a83580fdc318367556d69d51901ebb4f58fae6f Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 30 Aug 2025 12:03:21 +0800 Subject: [PATCH 8/8] fix: test --- .../src/lib/services/gathering-service.mock.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/evently.client/src/lib/services/gathering-service.mock.ts b/src/evently.client/src/lib/services/gathering-service.mock.ts index 30029ab..e380ace 100644 --- a/src/evently.client/src/lib/services/gathering-service.mock.ts +++ b/src/evently.client/src/lib/services/gathering-service.mock.ts @@ -1,4 +1,4 @@ -import { Booking, Gathering, Category } from "~/lib/domains/entities"; +import { Booking, Gathering, Category, GatheringCategoryDetail } from "~/lib/domains/entities"; import { GatheringReqDto } from "~/lib/domains/models"; import type { GetGatheringsParams } from "./gathering-service"; import type { PageResult } from "~/lib/domains/interfaces"; @@ -101,7 +101,11 @@ export async function createMockGathering( ): Promise { return { ...new Gathering(), - ...gatheringDto + ...gatheringDto, + gatheringCategoryDetails: gatheringDto.gatheringCategoryDetails.map((detail) => ({ + ...detail, + ...new GatheringCategoryDetail() + })) }; } @@ -114,6 +118,10 @@ export async function updateMockGathering( return { ...new Gathering(), ...gatheringDto, - gatheringId: gatheringId + gatheringId: gatheringId, + gatheringCategoryDetails: gatheringDto.gatheringCategoryDetails.map((detail) => ({ + ...detail, + ...new GatheringCategoryDetail() + })) }; }