Skip to content

Commit 06bb994

Browse files
fix: Improve wrong assignment report (calcom#27162)
* Pass already fetched booking * Fetch the assignment reason * Update tests * Label as first assignment reason * Pass single booking object * Remove unused repository method * Only pass bookingUid to handler * Rename booking `assignmentReason` to `assignmentReasonSortedByCreatedAt` * fix: use renamed assignmentReasonSortedByCreatedAt property Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * Remove take statement * fix: address Udit's review comments - Restore BookingAccessService for access control checks - Rename repository method from findByUidForWrongAssignmentReport to findByUidIncludeUserAndEventTypeTeamAndAttendeesAndAssignmentReason Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * test: update tests to use BookingAccessService mock Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: use latest assignment reason in BookingDetailsSheet for consistency Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent b185cd2 commit 06bb994

11 files changed

Lines changed: 254 additions & 209 deletions

File tree

apps/web/components/booking/BookingListItem.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -552,19 +552,14 @@ function BookingListItem(booking: BookingItemProps) {
552552
userTimeZone={userTimeZone}
553553
isRescheduled={isRescheduled}
554554
onAssignmentReasonClick={
555-
booking.assignmentReason.length > 0 ? () => setIsOpenRoutingTraceSheet(true) : undefined
555+
booking.assignmentReasonSortedByCreatedAt.length > 0 ? () => setIsOpenRoutingTraceSheet(true) : undefined
556556
}
557557
/>
558558
{isBookingFromRoutingForm && (
559559
<WrongAssignmentDialog
560560
isOpenDialog={isOpenWrongAssignmentDialog}
561561
setIsOpenDialog={setIsOpenWrongAssignmentDialog}
562-
bookingUid={booking.uid}
563-
routingReason={booking.assignmentReason[0]?.reasonString ?? null}
564-
guestEmail={booking.attendees[0]?.email ?? ""}
565-
hostEmail={booking.user?.email ?? ""}
566-
hostName={booking.user?.name ?? null}
567-
teamId={booking.eventType?.team?.id ?? null}
562+
booking={booking}
568563
/>
569564
)}
570565
</div>
@@ -606,7 +601,7 @@ const BookingItemBadges = ({
606601
</Badge>
607602
</Tooltip>
608603
)}
609-
{isRejected && !isRescheduled && booking.assignmentReason.length === 0 && (
604+
{isRejected && !isRescheduled && booking.assignmentReasonSortedByCreatedAt.length === 0 && (
610605
<Badge variant="gray" className="ltr:mr-2 rtl:ml-2">
611606
{t("rejected")}
612607
</Badge>
@@ -616,9 +611,9 @@ const BookingItemBadges = ({
616611
{booking.eventType.team.name}
617612
</Badge>
618613
)}
619-
{booking?.assignmentReason.length > 0 && (
614+
{booking?.assignmentReasonSortedByCreatedAt.length > 0 && (
620615
<AssignmentReasonTooltip
621-
assignmentReason={booking.assignmentReason[0]}
616+
assignmentReason={booking.assignmentReasonSortedByCreatedAt[booking.assignmentReasonSortedByCreatedAt.length - 1]}
622617
onClick={onAssignmentReasonClick}
623618
/>
624619
)}

apps/web/components/booking/actions/BookingActionsDropdown.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -458,15 +458,10 @@ export function BookingActionsDropdown({
458458
<WrongAssignmentDialog
459459
isOpenDialog={isOpenWrongAssignmentDialog}
460460
setIsOpenDialog={setIsOpenWrongAssignmentDialog}
461-
bookingUid={booking.uid}
462-
routingReason={booking.assignmentReason[0]?.reasonString ?? null}
463-
guestEmail={booking.attendees[0]?.email ?? ""}
464-
hostEmail={booking.user?.email ?? ""}
465-
hostName={booking.user?.name ?? null}
466-
teamId={booking.eventType?.team?.id ?? null}
461+
booking={booking}
467462
/>
468463
)}
469-
{booking.assignmentReason.length > 0 && (
464+
{booking.assignmentReasonSortedByCreatedAt.length > 0 && (
470465
<RoutingTraceSheet
471466
isOpen={isOpenRoutingTraceSheet}
472467
setIsOpen={setIsOpenRoutingTraceSheet}

apps/web/components/booking/actions/bookingActions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function createMockContext(overrides: Partial<BookingActionContext> = {}): Booki
8484
location: "integrations:daily",
8585
payment: [],
8686
seatsReferences: [],
87-
assignmentReason: [],
87+
assignmentReasonSortedByCreatedAt: [],
8888
metadata: null,
8989
routedFromRoutingFormReponse: null,
9090
listingStatus: "upcoming",

apps/web/components/dialog/WrongAssignmentDialog.tsx

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Dialog } from "@calcom/features/components/controlled-dialog";
22
import { useCopy } from "@calcom/lib/hooks/useCopy";
33
import { useLocale } from "@calcom/lib/hooks/useLocale";
4+
import type { AssignmentReasonEnum } from "@calcom/prisma/enums";
45
import { trpc } from "@calcom/trpc/react";
56
import { Alert } from "@calcom/ui/components/alert";
7+
import { Badge } from "@calcom/ui/components/badge";
68
import { Button } from "@calcom/ui/components/button";
79
import { DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/components/dialog";
810
import { Label, Select, TextArea } from "@calcom/ui/components/form";
@@ -12,15 +14,32 @@ import type { Dispatch, SetStateAction } from "react";
1214
import type { Control, ControllerRenderProps } from "react-hook-form";
1315
import { Controller, useForm } from "react-hook-form";
1416

17+
import assignmentReasonBadgeTitleMap from "@lib/booking/assignmentReasonBadgeTitleMap";
18+
19+
interface BookingData {
20+
uid: string;
21+
eventType?: {
22+
team?: {
23+
id: number;
24+
} | null;
25+
} | null;
26+
user?: {
27+
email: string;
28+
name: string | null;
29+
} | null;
30+
assignmentReasonSortedByCreatedAt: Array<{
31+
reasonString: string | null;
32+
reasonEnum: AssignmentReasonEnum | null;
33+
}>;
34+
attendees: Array<{
35+
email: string;
36+
}>;
37+
}
38+
1539
interface IWrongAssignmentDialog {
1640
isOpenDialog: boolean;
1741
setIsOpenDialog: Dispatch<SetStateAction<boolean>>;
18-
bookingUid: string;
19-
routingReason: string | null;
20-
guestEmail: string;
21-
hostEmail: string;
22-
hostName: string | null;
23-
teamId: number | null;
42+
booking: BookingData;
2443
}
2544

2645
interface FormValues {
@@ -36,6 +55,7 @@ interface TeamMemberOption {
3655

3756
interface RoutingInfoSectionProps {
3857
routingReason: string | null;
58+
routingReasonEnum: AssignmentReasonEnum | null;
3959
noRoutingReasonText: string;
4060
routingReasonLabel: string;
4161
guestEmail: string;
@@ -48,8 +68,10 @@ interface RoutingInfoSectionProps {
4868
}
4969

5070
function RoutingInfoSection(props: RoutingInfoSectionProps): JSX.Element {
71+
const { t } = useLocale();
5172
const {
5273
routingReason,
74+
routingReasonEnum,
5375
noRoutingReasonText,
5476
routingReasonLabel,
5577
guestEmail,
@@ -79,9 +101,14 @@ function RoutingInfoSection(props: RoutingInfoSectionProps): JSX.Element {
79101
<div className="-mt-2 mb-4 space-y-3">
80102
<div>
81103
<Label className="text-emphasis mb-1 block text-sm font-medium">{routingReasonLabel}</Label>
82-
<p className="text-default bg-muted rounded-md px-3 py-2 text-sm">
83-
{routingReason || noRoutingReasonText}
84-
</p>
104+
<div className="text-default bg-muted flex items-center gap-2 rounded-md px-3 py-2 text-sm">
105+
{routingReasonEnum && (
106+
<Badge variant="gray" className="shrink-0">
107+
{t(assignmentReasonBadgeTitleMap(routingReasonEnum))}
108+
</Badge>
109+
)}
110+
<span className="flex-1">{routingReason || noRoutingReasonText}</span>
111+
</div>
85112
</div>
86113

87114
<div>
@@ -212,16 +239,15 @@ export function WrongAssignmentDialog(props: IWrongAssignmentDialog): JSX.Elemen
212239
const { t } = useLocale();
213240
const utils = trpc.useUtils();
214241
const { copyToClipboard, isCopied } = useCopy();
215-
const {
216-
isOpenDialog,
217-
setIsOpenDialog,
218-
bookingUid,
219-
routingReason,
220-
guestEmail,
221-
hostEmail,
222-
hostName,
223-
teamId,
224-
} = props;
242+
const { isOpenDialog, setIsOpenDialog, booking } = props;
243+
244+
const bookingUid = booking.uid;
245+
const teamId = booking.eventType?.team?.id ?? null;
246+
const routingReason = booking.assignmentReasonSortedByCreatedAt[0]?.reasonString ?? null;
247+
const routingReasonEnum = booking.assignmentReasonSortedByCreatedAt[0]?.reasonEnum ?? null;
248+
const guestEmail = booking.attendees[0]?.email ?? "";
249+
const hostEmail = booking.user?.email ?? "";
250+
const hostName = booking.user?.name ?? null;
225251

226252
const {
227253
control,
@@ -286,8 +312,9 @@ export function WrongAssignmentDialog(props: IWrongAssignmentDialog): JSX.Elemen
286312

287313
<RoutingInfoSection
288314
routingReason={routingReason}
315+
routingReasonEnum={routingReasonEnum}
289316
noRoutingReasonText={t("no_routing_reason")}
290-
routingReasonLabel={t("routing_reason")}
317+
routingReasonLabel={t("first_assignment_reason")}
291318
guestEmail={guestEmail}
292319
whoBookedItLabel={t("who_booked_it")}
293320
hostEmail={hostEmail}

apps/web/modules/bookings/components/BookingDetailsSheet.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ function BookingDetailsSheetInner({
215215
.map(([question, answer]) => [question, answer] as [string, unknown])
216216
: [];
217217

218-
const reason = booking.assignmentReason?.[0];
218+
const reason = booking.assignmentReasonSortedByCreatedAt?.[booking.assignmentReasonSortedByCreatedAt.length - 1];
219219
const reasonTitle = reason && assignmentReasonBadgeTitleMap(reason.reasonEnum);
220220

221221
return (
@@ -597,12 +597,12 @@ function RecurringInfoSection({
597597
function AssignmentReasonSection({ booking }: { booking: BookingOutput }) {
598598
const { t } = useLocale();
599599

600-
if (!booking.assignmentReason || booking.assignmentReason.length === 0) {
600+
if (!booking.assignmentReasonSortedByCreatedAt || booking.assignmentReasonSortedByCreatedAt.length === 0) {
601601
return null;
602602
}
603603

604-
// we fetch only one assignment reason.
605-
const reason = booking.assignmentReason[0];
604+
const reason =
605+
booking.assignmentReasonSortedByCreatedAt[booking.assignmentReasonSortedByCreatedAt.length - 1];
606606
if (!reason.reasonString) {
607607
return null;
608608
}

apps/web/public/static/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@
848848
"wrong_assignment_reported": "Wrong assignment reported successfully",
849849
"wrong_assignment_already_reported": "A wrong assignment report has already been submitted for this booking.",
850850
"routing_reason": "Routing Reason",
851+
"first_assignment_reason": "First Assignment Reason",
851852
"no_routing_reason": "No routing reason available",
852853
"who_booked_it": "Who booked it?",
853854
"who_received_it": "Who received it?",

packages/features/bookings/repositories/BookingRepository.ts

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,27 +2123,33 @@ export class BookingRepository implements IBookingRepository {
21232123
});
21242124
}
21252125

2126-
async findByUidIncludeEventTypeAndTeamAndAssignmentReason({ bookingUid }: { bookingUid: string }) {
2126+
async updateRecordedStatus({
2127+
bookingUid,
2128+
isRecorded,
2129+
}: {
2130+
bookingUid: string;
2131+
isRecorded: boolean;
2132+
}): Promise<void> {
2133+
await this.prismaClient.booking.update({
2134+
where: { uid: bookingUid },
2135+
data: { isRecorded },
2136+
});
2137+
}
2138+
2139+
async findByUidIncludeUserAndEventTypeTeamAndAttendeesAndAssignmentReason({ bookingUid }: { bookingUid: string }) {
21272140
return await this.prismaClient.booking.findUnique({
2128-
where: {
2129-
uid: bookingUid,
2130-
},
2141+
where: { uid: bookingUid },
21312142
select: {
21322143
id: true,
21332144
uid: true,
21342145
title: true,
21352146
startTime: true,
21362147
endTime: true,
21372148
status: true,
2149+
userId: true,
21382150
user: {
21392151
select: {
21402152
id: true,
2141-
name: true,
2142-
email: true,
2143-
},
2144-
},
2145-
attendees: {
2146-
select: {
21472153
email: true,
21482154
name: true,
21492155
},
@@ -2153,13 +2159,26 @@ export class BookingRepository implements IBookingRepository {
21532159
id: true,
21542160
title: true,
21552161
slug: true,
2156-
teamId: true,
2162+
team: {
2163+
select: {
2164+
id: true,
2165+
parentId: true,
2166+
},
2167+
},
2168+
},
2169+
},
2170+
attendees: {
2171+
select: {
2172+
email: true,
21572173
},
21582174
},
21592175
assignmentReason: {
21602176
select: {
21612177
reasonString: true,
2162-
reasonEnum: true,
2178+
},
2179+
take: 1,
2180+
orderBy: {
2181+
createdAt: "asc" as const,
21632182
},
21642183
},
21652184
routedFromRoutingFormReponse: {
@@ -2170,17 +2189,4 @@ export class BookingRepository implements IBookingRepository {
21702189
},
21712190
});
21722191
}
2173-
2174-
async updateRecordedStatus({
2175-
bookingUid,
2176-
isRecorded,
2177-
}: {
2178-
bookingUid: string;
2179-
isRecorded: boolean;
2180-
}): Promise<void> {
2181-
await this.prismaClient.booking.update({
2182-
where: { uid: bookingUid },
2183-
data: { isRecorded },
2184-
});
2185-
}
21862192
}

packages/trpc/server/routers/viewer/bookings/get.handler.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -631,9 +631,8 @@ export async function getBookings({
631631
.selectFrom("AssignmentReason")
632632
.selectAll()
633633
.whereRef("AssignmentReason.bookingId", "=", "Booking.id")
634-
.orderBy("AssignmentReason.createdAt", "desc")
635-
.limit(1)
636-
).as("assignmentReason"),
634+
.orderBy("AssignmentReason.createdAt", "asc")
635+
).as("assignmentReasonSortedByCreatedAt"),
637636
jsonObjectFrom(
638637
eb
639638
.selectFrom("BookingReport")

0 commit comments

Comments
 (0)