Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: synchronize no show state in booking listing item between edit & display #18648

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
164 changes: 76 additions & 88 deletions apps/web/components/booking/BookingListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AssignmentReason } from "@prisma/client";
import Link from "next/link";
import { useState } from "react";
import { useState, useEffect } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";

import type { getEventLocationValue } from "@calcom/app-store/locations";
Expand Down Expand Up @@ -420,16 +420,38 @@ function BookingListItem(booking: BookingItemProps) {
];

const showPendingPayment = paymentAppData.enabled && booking.payment.length && !booking.paid;
const attendeeList = booking.attendees.map((attendee) => {
return {
name: attendee.name,
email: attendee.email,
id: attendee.id,
noShow: attendee.noShow || false,
phoneNumber: attendee.phoneNumber,
};

const [attendeeList, setAttendeeList] = useState(
booking.attendees.map((attendee) => {
return {
name: attendee.name,
email: attendee.email,
id: attendee.id,
noShow: attendee.noShow || false,
phoneNumber: attendee.phoneNumber,
};
})
);

const noShowMutation = trpc.viewer.markNoShow.useMutation({
onSuccess: async (data) => {
showToast(data.message, "success");
},
onError: (error) => {
showToast(error.message, "error");
},
});

const handleNoShowChange = (email: string, noShow: boolean) => {
noShowMutation.mutate({
bookingUid: booking.uid,
attendees: [{ email, noShow }],
});
setAttendeeList((prev) =>
prev.map((attendee) => (attendee.email === email ? { ...attendee, noShow } : attendee))
);
};

return (
<>
<RescheduleDialog
Expand Down Expand Up @@ -476,8 +498,8 @@ function BookingListItem(booking: BookingItemProps) {
)}
{isNoShowDialogOpen && (
<NoShowAttendeesDialog
bookingUid={booking.uid}
attendees={attendeeList}
handleNoShowChange={handleNoShowChange}
setIsOpen={setIsNoShowDialogOpen}
isOpen={isNoShowDialogOpen}
/>
Expand Down Expand Up @@ -641,6 +663,7 @@ function BookingListItem(booking: BookingItemProps) {
currentEmail={userEmail}
bookingUid={booking.uid}
isBookingInPast={isBookingInPast}
handleNoShowChange={handleNoShowChange}
/>
)}
{isCancelled && booking.rescheduled && (
Expand Down Expand Up @@ -865,36 +888,16 @@ type AttendeeProps = {
type NoShowProps = {
bookingUid: string;
isBookingInPast: boolean;
handleNoShowChange: (email: string, noShow: boolean) => void;
};

const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
const { email, name, bookingUid, isBookingInPast, noShow: noShowAttendee, phoneNumber } = attendeeProps;
const { email, name, isBookingInPast, noShow, phoneNumber, handleNoShowChange } = attendeeProps;
const { t } = useLocale();

const [noShow, setNoShow] = useState(noShowAttendee);
const [openDropdown, setOpenDropdown] = useState(false);
const { copyToClipboard, isCopied } = useCopy();

const noShowMutation = trpc.viewer.markNoShow.useMutation({
onSuccess: async (data) => {
showToast(data.message, "success");
},
onError: (err) => {
showToast(err.message, "error");
},
});

function toggleNoShow({
attendee,
bookingUid,
}: {
attendee: { email: string; noShow: boolean };
bookingUid: string;
}) {
noShowMutation.mutate({ bookingUid, attendees: [attendee] });
setNoShow(!noShow);
}

return (
<Dropdown open={openDropdown} onOpenChange={setOpenDropdown}>
<DropdownMenuTrigger asChild>
Expand Down Expand Up @@ -948,7 +951,7 @@ const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
onClick={(e) => {
e.preventDefault();
setOpenDropdown(false);
toggleNoShow({ attendee: { noShow: false, email }, bookingUid });
handleNoShowChange(email, false);
}}
StartIcon="eye">
{t("unmark_as_no_show")}
Expand All @@ -959,7 +962,7 @@ const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
onClick={(e) => {
e.preventDefault();
setOpenDropdown(false);
toggleNoShow({ attendee: { noShow: true, email }, bookingUid });
handleNoShowChange(email, true);
}}
StartIcon="eye-off">
{t("mark_as_no_show")}
Expand All @@ -974,29 +977,15 @@ const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {

type GroupedAttendeeProps = {
attendees: AttendeeProps[];
bookingUid: string;
handleNoShowChange: (email: string, noShow: boolean) => void;
};

const GroupedAttendees = (groupedAttendeeProps: GroupedAttendeeProps) => {
const { bookingUid } = groupedAttendeeProps;
const attendees = groupedAttendeeProps.attendees.map((attendee) => {
return {
id: attendee.id,
email: attendee.email,
name: attendee.name,
noShow: attendee.noShow || false,
};
});
const { attendees, handleNoShowChange } = groupedAttendeeProps;

const { t } = useLocale();
const noShowMutation = trpc.viewer.markNoShow.useMutation({
onSuccess: async (data) => {
showToast(t(data.message), "success");
},
onError: (err) => {
showToast(err.message, "error");
},
});
const { control, handleSubmit } = useForm<{

const { control, handleSubmit, reset } = useForm<{
attendees: AttendeeProps[];
}>({
defaultValues: {
Expand All @@ -1010,9 +999,17 @@ const GroupedAttendees = (groupedAttendeeProps: GroupedAttendeeProps) => {
name: "attendees",
});

useEffect(() => {
reset({
attendees,
});
}, [attendees, reset]);

const onSubmit = (data: { attendees: AttendeeProps[] }) => {
const filteredData = data.attendees.slice(1);
noShowMutation.mutate({ bookingUid, attendees: filteredData });
for (const attendee of filteredData) {
handleNoShowChange(attendee.email, attendee.noShow);
}
setOpenDropdown(false);
};

Expand Down Expand Up @@ -1074,50 +1071,24 @@ const NoShowAttendeesDialog = ({
attendees,
isOpen,
setIsOpen,
bookingUid,
handleNoShowChange,
}: {
attendees: AttendeeProps[];
isOpen: boolean;
setIsOpen: (value: boolean) => void;
bookingUid: string;
handleNoShowChange: (email: string, noShow: boolean) => void;
}) => {
const { t } = useLocale();
const [noShowAttendees, setNoShowAttendees] = useState(
attendees.map((attendee) => ({
id: attendee.id,
email: attendee.email,
name: attendee.name,
noShow: attendee.noShow || false,
}))
);

const noShowMutation = trpc.viewer.markNoShow.useMutation({
onSuccess: async (data) => {
const newValue = data.attendees[0];
setNoShowAttendees((old) =>
old.map((attendee) =>
attendee.email === newValue.email ? { ...attendee, noShow: newValue.noShow } : attendee
)
);
showToast(t(data.message), "success");
},
onError: (err) => {
showToast(err.message, "error");
},
});

return (
<Dialog open={isOpen} onOpenChange={() => setIsOpen(false)}>
<DialogContent title={t("mark_as_no_show_title")} description={t("no_show_description")}>
{noShowAttendees.map((attendee) => (
{attendees.map((attendee) => (
<form
key={attendee.id}
onSubmit={(e) => {
e.preventDefault();
noShowMutation.mutate({
bookingUid,
attendees: [{ email: attendee.email, noShow: !attendee.noShow }],
});
handleNoShowChange(attendee.email, !attendee.noShow);
}}>
<div className="bg-muted flex items-center justify-between rounded-md px-4 py-2">
<span className="text-emphasis flex flex-col text-sm">
Expand Down Expand Up @@ -1208,12 +1179,14 @@ const DisplayAttendees = ({
currentEmail,
bookingUid,
isBookingInPast,
handleNoShowChange,
}: {
attendees: AttendeeProps[];
user: UserProps | null;
currentEmail?: string | null;
bookingUid: string;
isBookingInPast: boolean;
handleNoShowChange: (email: string, noShow: boolean) => void;
}) => {
const { t } = useLocale();
attendees.sort((a, b) => a.id - b.id);
Expand All @@ -1222,25 +1195,40 @@ const DisplayAttendees = ({
<div className="text-emphasis text-sm">
{user && <FirstAttendee user={user} currentEmail={currentEmail} />}
{attendees.length > 1 ? <span>,&nbsp;</span> : <span>&nbsp;{t("and")}&nbsp;</span>}
<Attendee {...attendees[0]} bookingUid={bookingUid} isBookingInPast={isBookingInPast} />
<Attendee
{...attendees[0]}
bookingUid={bookingUid}
isBookingInPast={isBookingInPast}
handleNoShowChange={handleNoShowChange}
/>
{attendees.length > 1 && (
<>
<div className="text-emphasis inline-block text-sm">&nbsp;{t("and")}&nbsp;</div>
{attendees.length > 2 ? (
<Tooltip
content={attendees.slice(1).map((attendee) => (
<p key={attendee.email}>
<Attendee {...attendee} bookingUid={bookingUid} isBookingInPast={isBookingInPast} />
<Attendee
{...attendee}
bookingUid={bookingUid}
isBookingInPast={isBookingInPast}
handleNoShowChange={handleNoShowChange}
/>
</p>
))}>
{isBookingInPast ? (
<GroupedAttendees attendees={attendees} bookingUid={bookingUid} />
<GroupedAttendees attendees={attendees} handleNoShowChange={handleNoShowChange} />
) : (
<GroupedGuests guests={attendees} />
)}
</Tooltip>
) : (
<Attendee {...attendees[1]} bookingUid={bookingUid} isBookingInPast={isBookingInPast} />
<Attendee
{...attendees[1]}
bookingUid={bookingUid}
isBookingInPast={isBookingInPast}
handleNoShowChange={handleNoShowChange}
/>
)}
</>
)}
Expand Down
Loading