From c33f685348c73b79d55fade997fc3d0e351bd73f Mon Sep 17 00:00:00 2001 From: darmiel <71837281+darmiel@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:42:42 +0100 Subject: [PATCH] feat: meeting readiness --- backend/api/handlers/meeting_handler.go | 16 +++++++++ backend/api/routes/meeting_routes.go | 1 + backend/api/services/meeting_service.go | 9 +++++ backend/main.go | 1 + backend/pkg/model/user.go | 2 ++ frontend/src/api/functions.ts | 34 +++++++++++++++++++ frontend/src/api/types.ts | 1 + .../components/meeting/MeetingOverview.tsx | 34 +++++++++++++++++-- .../meeting/cards/MeetingCardLarge.tsx | 24 ++++++++++++- 9 files changed, 119 insertions(+), 3 deletions(-) diff --git a/backend/api/handlers/meeting_handler.go b/backend/api/handlers/meeting_handler.go index 9dd93df..19a6867 100644 --- a/backend/api/handlers/meeting_handler.go +++ b/backend/api/handlers/meeting_handler.go @@ -205,3 +205,19 @@ func (h *MeetingHandler) UnlinkTag(ctx *fiber.Ctx) error { tag := ctx.Locals("tag").(model.Tag) return fiberResponseNoVal(ctx, "unlinked tag", h.srv.UnlinkTag(meeting.ID, tag.ID)) } + +type editReadyPayload struct { + Ready bool `json:"ready"` +} + +func (h *MeetingHandler) EditReady(ctx *fiber.Ctx) error { + m := ctx.Locals("meeting").(model.Meeting) + var payload editReadyPayload + if err := ctx.BodyParser(&payload); err != nil { + return ctx.Status(fiber.StatusBadRequest).JSON(presenter.ErrorResponse(err)) + } + if err := h.srv.SetReady(m.ID, payload.Ready); err != nil { + return ctx.Status(fiber.StatusInternalServerError).JSON(presenter.ErrorResponse(err)) + } + return ctx.Status(fiber.StatusOK).JSON(presenter.SuccessResponse("meeting edited", nil)) +} diff --git a/backend/api/routes/meeting_routes.go b/backend/api/routes/meeting_routes.go index a6be81e..63b332f 100644 --- a/backend/api/routes/meeting_routes.go +++ b/backend/api/routes/meeting_routes.go @@ -18,6 +18,7 @@ func MeetingRoutes(router fiber.Router, handler *handlers.MeetingHandler, middle specific.Get("/", handler.GetMeeting) specific.Delete("/", handler.DeleteMeeting) specific.Put("/", handler.EditMeeting) + specific.Put("/ready", handler.EditReady) // linkUser routes linkUser := specific.Group("/link/user/:user_id") diff --git a/backend/api/services/meeting_service.go b/backend/api/services/meeting_service.go index a821439..9413d71 100644 --- a/backend/api/services/meeting_service.go +++ b/backend/api/services/meeting_service.go @@ -17,6 +17,7 @@ type MeetingService interface { UnlinkUser(meetingID uint, userID string) error LinkTag(meetingID, tagID uint) error UnlinkTag(meetingID, tagID uint) error + SetReady(meetingID uint, ready bool) error } type meetingService struct { @@ -145,3 +146,11 @@ func (m *meetingService) UnlinkTag(meetingID, tagID uint) error { }, }) } + +func (m *meetingService) SetReady(meetingID uint, ready bool) error { + return m.DB.Model(&model.Meeting{ + Model: gorm.Model{ + ID: meetingID, + }, + }).Update("IsReady", ready).Error +} diff --git a/backend/main.go b/backend/main.go index 779f4d8..509b3b7 100644 --- a/backend/main.go +++ b/backend/main.go @@ -58,6 +58,7 @@ func main() { new(model.Action), new(model.Tag), new(model.Notification), + new(model.ProjectFile), ); err != nil { sugar.With(err).Fatalln("cannot migrate user") return diff --git a/backend/pkg/model/user.go b/backend/pkg/model/user.go index 0308052..97992a8 100644 --- a/backend/pkg/model/user.go +++ b/backend/pkg/model/user.go @@ -134,6 +134,8 @@ type Meeting struct { AssignedUsers []User `gorm:"many2many:meeting_user_assignments" json:"assigned_users"` // Tags contains all tags of the meeting Tags []Tag `gorm:"many2many:meeting_tag_assignments" json:"tags"` + // IsReady indicates if the meeting is ready to start (user defined) + IsReady bool `json:"is_ready"` } func (m Meeting) CheckProjectOwnership(projectID uint) bool { diff --git a/frontend/src/api/functions.ts b/frontend/src/api/functions.ts index cf4a0ca..24b60c9 100644 --- a/frontend/src/api/functions.ts +++ b/frontend/src/api/functions.ts @@ -83,6 +83,10 @@ export type meetingLinkTagVars = { link: boolean } +export type meetingSetReadyVars = { + ready: boolean +} + // ====================== // Topic Types // ====================== @@ -560,6 +564,19 @@ export const functions = (axios: Axios, client: QueryClient) => { linkTagMutKey(projectID: number, meetingID: number) { return [{ projectID }, { meetingID }, "link-tag-mut"] }, + + setReadyMutFn(projectID: number, meetingID: number) { + return async ({ ready }: meetingSetReadyVars) => + ( + await axios.put( + `/project/${projectID}/meeting/${meetingID}/ready`, + { ready }, + ) + ).data + }, + setReadyMutKey(projectID: number, meetingID: number) { + return [{ projectID }, { meetingID }, "set-ready-mut"] + }, }, topics: { listQueryFn(projectID: number, meetingID: number) { @@ -1378,6 +1395,23 @@ export const functions = (axios: Axios, client: QueryClient) => { ), }) }, + useSetReady( + projectID: number, + meetingID: number, + callback: SuccessCallback, + ) { + return useMutation({ + mutationKey: functions.meetings.setReadyMutKey(projectID, meetingID), + mutationFn: functions.meetings.setReadyMutFn(projectID, meetingID), + onSuccess: invalidateAllCallback( + callback, + functions.meetings.findQueryKey(projectID, meetingID), + functions.meetings.listQueryKey(projectID), + functions.meetings.listUpcomingQueryKey(), + ), + onError: toastError("Cannot set Meeting ready:"), + }) + }, }, topics: { useDelete( diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 8d36e80..14fcee5 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -44,6 +44,7 @@ export type Meeting = { project_id: number assigned_users?: User[] tags: Tag[] + is_ready: boolean } export type User = { diff --git a/frontend/src/components/meeting/MeetingOverview.tsx b/frontend/src/components/meeting/MeetingOverview.tsx index cbdb9b4..9d029e1 100644 --- a/frontend/src/components/meeting/MeetingOverview.tsx +++ b/frontend/src/components/meeting/MeetingOverview.tsx @@ -13,11 +13,15 @@ import { useEffect, useState } from "react" import ReactDatePicker from "react-datepicker" import { BsBack, + BsCheck, BsHouse, + BsInfoCircle, BsPen, BsPlusCircleFill, BsTrash, BsTrashFill, + BsTriangleFill, + BsX, } from "react-icons/bs" import { BarLoader } from "react-spinners" import { toast } from "sonner" @@ -90,6 +94,8 @@ export default function MeetingOverview({ const linkUser = meetings!.useLinkUser(projectID, meetingID, () => {}) + const markReadyMut = meetings!.useSetReady(projectID, meetingID, () => {}) + const [progress, setProgress] = useState(0) let startDate: Date | undefined @@ -196,13 +202,24 @@ export default function MeetingOverview({ } createdAt={new Date(meeting.CreatedAt)} setEditTitle={setEditTitle} isEdit={isEdit} /> + {!meeting.is_ready && ( +
+ + This meeting has not been marked as ready + + + + + +
+ )} + {isEdit ? (
@@ -299,8 +316,21 @@ export default function MeetingOverview({ className="w-full" color="primary" > - Create Topic + New Topic + +