Skip to content

Commit

Permalink
feat: meeting readiness
Browse files Browse the repository at this point in the history
darmiel committed Nov 28, 2023

Verified

This commit was signed with the committer’s verified signature. The key has expired.
darmiel Daniel
1 parent 91f4640 commit c33f685
Showing 9 changed files with 119 additions and 3 deletions.
16 changes: 16 additions & 0 deletions backend/api/handlers/meeting_handler.go
Original file line number Diff line number Diff line change
@@ -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))
}
1 change: 1 addition & 0 deletions backend/api/routes/meeting_routes.go
Original file line number Diff line number Diff line change
@@ -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")
9 changes: 9 additions & 0 deletions backend/api/services/meeting_service.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions backend/main.go
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions backend/pkg/model/user.go
Original file line number Diff line number Diff line change
@@ -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 {
34 changes: 34 additions & 0 deletions frontend/src/api/functions.ts
Original file line number Diff line number Diff line change
@@ -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<never, meetingSetReadyVars>,
) {
return useMutation<BackendResponse, AxiosError, meetingSetReadyVars>({
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(
1 change: 1 addition & 0 deletions frontend/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ export type Meeting = {
project_id: number
assigned_users?: User[]
tags: Tag[]
is_ready: boolean
}

export type User = {
34 changes: 32 additions & 2 deletions frontend/src/components/meeting/MeetingOverview.tsx
Original file line number Diff line number Diff line change
@@ -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({
<OverviewTitle
creatorID={meeting.creator_id}
title={meeting.name}
titleID={meeting.ID}
tag={<MeetingTag start={startDate} end={endDate} />}
createdAt={new Date(meeting.CreatedAt)}
setEditTitle={setEditTitle}
isEdit={isEdit}
/>

{!meeting.is_ready && (
<div className="mb-4 flex w-full items-center space-x-2 rounded-md bg-red-500 bg-opacity-25 py-3 pl-4 pr-2 text-red-500">
<BsTriangleFill />
<span>This meeting has not been marked as ready</span>
<Tooltip content="This visual cue serves as a reminder to schedule your meetings in advance to avoid last-minute hassles.">
<span>
<BsInfoCircle />
</span>
</Tooltip>
</div>
)}

<span className="mb-3 text-neutral-500">
{isEdit ? (
<div className="mb-3">
@@ -299,8 +316,21 @@ export default function MeetingOverview({
className="w-full"
color="primary"
>
Create Topic
New Topic
</Button>
<Tooltip
content={meeting.is_ready ? "Mark Unready" : "Mark Ready"}
>
<Button
isIconOnly
startContent={meeting.is_ready ? <BsX /> : <BsCheck />}
onClick={() =>
markReadyMut.mutate({ ready: !meeting.is_ready })
}
isLoading={markReadyMut.isLoading}
color={meeting.is_ready ? "warning" : "success"}
/>
</Tooltip>
<Tooltip content="Edit Meeting">
<Button
isIconOnly
24 changes: 23 additions & 1 deletion frontend/src/components/meeting/cards/MeetingCardLarge.tsx
Original file line number Diff line number Diff line change
@@ -4,8 +4,11 @@ import {
Chip,
Progress,
ScrollShadow,
Tooltip,
} from "@nextui-org/react"
import clsx from "clsx"
import Link from "next/link"
import { BsTriangleFill } from "react-icons/bs"

import { Meeting } from "@/api/types"
import MeetingChip from "@/components/meeting/chips/MeetingChips"
@@ -53,7 +56,16 @@ export default function MeetingCardLarge({ meeting }: { meeting: Meeting }) {
hour12: false,
})
return (
<div className="flex w-full flex-col justify-between space-y-2 rounded-lg border border-neutral-800 px-5 py-4 transition-colors hover:border-neutral-700 hover:bg-neutral-800/30">
<div
className={clsx(
"flex w-full flex-col justify-between space-y-2 rounded-lg",
"border border-neutral-800 px-5 py-4 transition-colors",
"hover:border-neutral-700 hover:bg-neutral-800/30",
{
"border-red-600 hover:border-red-500": !meeting.is_ready,
},
)}
>
<div>
{/* Project Header */}
<Link
@@ -74,6 +86,16 @@ export default function MeetingCardLarge({ meeting }: { meeting: Meeting }) {
<span className="truncate text-start text-lg font-semibold">
{meeting.name}
</span>
{!meeting.is_ready && (
<Tooltip
content="This meeting has not been marked as ready"
color="danger"
>
<span className="text-red-500">
<BsTriangleFill />
</span>
</Tooltip>
)}
</Link>
</div>

0 comments on commit c33f685

Please sign in to comment.