-
Notifications
You must be signed in to change notification settings - Fork 7.7k
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
feat: Implement editable OOO events feature #15932
Changes from 2 commits
2b39a67
b109791
07a0e74
b737ad1
410bdc0
9625ab3
e8f0dc3
cbbbad0
64127f8
825bb46
1cd1df1
0862e92
909498e
27d2100
f0c7a7f
a3bf1e9
d47952f
1a68ebf
b385146
298965d
53a41a3
95f41ce
92777c7
5c8c10c
57b7b59
acea1ab
470e01c
ea2f347
c9f6ec3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import { Trans } from "next-i18next"; | ||
import { useSearchParams } from "next/navigation"; | ||
import { useEffect, useState } from "react"; | ||
import { useEffect, useMemo, useState } from "react"; | ||
import { Controller, useForm, useFormState } from "react-hook-form"; | ||
|
||
import dayjs from "@calcom/dayjs"; | ||
|
@@ -39,18 +39,58 @@ export type BookingRedirectForm = { | |
toTeamUserId: number | null; | ||
reasonId: number; | ||
notes?: string; | ||
uuid?: string; | ||
}; | ||
|
||
export type outOfOfficeEntryData = { | ||
uuid: string; | ||
selectedReason: number | null; | ||
profileRedirect: boolean; | ||
selectedMember: number | null; | ||
notes: string | null; | ||
start: Date; | ||
end: Date; | ||
}; | ||
|
||
const CreateOutOfOfficeEntryModal = ({ | ||
openModal, | ||
closeModal, | ||
currentlyEditingOutOfOfficeEntry, | ||
anikdhabal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}: { | ||
openModal: boolean; | ||
closeModal: () => void; | ||
currentlyEditingOutOfOfficeEntry: outOfOfficeEntryData | null; | ||
}) => { | ||
const { t } = useLocale(); | ||
const utils = trpc.useUtils(); | ||
|
||
const { data: listMembers } = trpc.viewer.teams.listMembers.useQuery({}); | ||
const me = useMeQuery(); | ||
const memberListOptions: { | ||
value: number | null; | ||
label: string; | ||
}[] = useMemo(() => { | ||
return ( | ||
listMembers | ||
?.filter((member) => me?.data?.id !== member.id) | ||
.map((member) => ({ | ||
value: member.id || null, | ||
label: member.name || "", | ||
})) || [] | ||
); | ||
}, [listMembers, me?.data?.id]); | ||
|
||
const { data: outOfOfficeReasonList } = trpc.viewer.outOfOfficeReasonList.useQuery(); | ||
|
||
const reasonList = useMemo(() => { | ||
return [ | ||
...(outOfOfficeReasonList || []).map((reason) => ({ | ||
label: `${reason.emoji} ${reason.userId === null ? t(reason.reason) : reason.reason}`, | ||
value: reason.id, | ||
})), | ||
]; | ||
}, [outOfOfficeReasonList, t]); | ||
anikdhabal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const [selectedReason, setSelectedReason] = useState<{ label: string; value: number } | null>(null); | ||
const [profileRedirect, setProfileRedirect] = useState(false); | ||
const [selectedMember, setSelectedMember] = useState<{ label: string; value: number | null } | null>(null); | ||
anikdhabal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
@@ -61,18 +101,6 @@ const CreateOutOfOfficeEntryModal = ({ | |
}); | ||
|
||
const { hasTeamPlan } = useHasTeamPlan(); | ||
const { data: listMembers } = trpc.viewer.teams.listMembers.useQuery({}); | ||
const me = useMeQuery(); | ||
const memberListOptions: { | ||
value: number | null; | ||
label: string; | ||
}[] = | ||
listMembers | ||
?.filter((member) => me?.data?.id !== member.id) | ||
.map((member) => ({ | ||
value: member.id || null, | ||
label: member.name || "", | ||
})) || []; | ||
|
||
const { handleSubmit, setValue, control, register } = useForm<BookingRedirectForm>({ | ||
defaultValues: { | ||
|
@@ -88,8 +116,16 @@ const CreateOutOfOfficeEntryModal = ({ | |
|
||
const createOutOfOfficeEntry = trpc.viewer.outOfOfficeCreate.useMutation({ | ||
onSuccess: () => { | ||
showToast(t("success_entry_created"), "success"); | ||
if (currentlyEditingOutOfOfficeEntry) { | ||
showToast(t("success_edited_entry_out_of_office"), "success"); | ||
} else { | ||
showToast(t("success_entry_created"), "success"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could do it in one line:-
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Done! |
||
utils.viewer.outOfOfficeEntriesList.invalidate(); | ||
setValue("toTeamUserId", null); | ||
setValue("notes", ""); | ||
setSelectedReason(null); | ||
setSelectedMember(null); | ||
setProfileRedirect(false); | ||
closeModal(); | ||
}, | ||
|
@@ -98,14 +134,50 @@ const CreateOutOfOfficeEntryModal = ({ | |
}, | ||
}); | ||
|
||
const { data: outOfOfficeReasonList } = trpc.viewer.outOfOfficeReasonList.useQuery(); | ||
useEffect(() => { | ||
if (currentlyEditingOutOfOfficeEntry === null) { | ||
setValue("toTeamUserId", null); | ||
setValue("notes", ""); | ||
setValue("dateRange", dateRange); | ||
setValue("reasonId", 1); | ||
setSelectedReason(null); | ||
setSelectedMember(null); | ||
setProfileRedirect(false); | ||
return; | ||
} | ||
|
||
const selectedReason = | ||
reasonList.find((reason) => { | ||
return reason.value === currentlyEditingOutOfOfficeEntry.selectedReason; | ||
}) ?? null; | ||
if (selectedReason && selectedReason.value) { | ||
setValue("reasonId", selectedReason.value); | ||
setSelectedReason(selectedReason); | ||
} | ||
|
||
const selectedMember = | ||
memberListOptions.find((member) => { | ||
return member.value === currentlyEditingOutOfOfficeEntry.selectedMember; | ||
}) ?? null; | ||
|
||
setProfileRedirect(currentlyEditingOutOfOfficeEntry.profileRedirect); | ||
|
||
if (selectedMember && selectedMember?.value) { | ||
setValue("toTeamUserId", selectedMember.value); | ||
setSelectedMember(selectedMember); | ||
} | ||
|
||
if (currentlyEditingOutOfOfficeEntry.notes) { | ||
setValue("notes", currentlyEditingOutOfOfficeEntry.notes); | ||
} | ||
|
||
const reasonList = [ | ||
...(outOfOfficeReasonList || []).map((reason) => ({ | ||
label: `${reason.emoji} ${reason.userId === null ? t(reason.reason) : reason.reason}`, | ||
value: reason.id, | ||
})), | ||
]; | ||
setValue("dateRange", { | ||
startDate: dayjs(currentlyEditingOutOfOfficeEntry.start).startOf("d").toDate(), | ||
endDate: dayjs(currentlyEditingOutOfOfficeEntry.end).subtract(1, "d").endOf("d").toDate(), | ||
}); | ||
|
||
setValue("uuid", currentlyEditingOutOfOfficeEntry.uuid); | ||
}, [currentlyEditingOutOfOfficeEntry, setValue, reasonList, dateRange, memberListOptions]); | ||
|
||
return ( | ||
<Dialog open={openModal}> | ||
|
@@ -118,13 +190,13 @@ const CreateOutOfOfficeEntryModal = ({ | |
className="h-full" | ||
onSubmit={handleSubmit((data) => { | ||
createOutOfOfficeEntry.mutate(data); | ||
setValue("toTeamUserId", null); | ||
setValue("notes", ""); | ||
setSelectedReason(null); | ||
setSelectedMember(null); | ||
})}> | ||
<div className="px-1"> | ||
<DialogHeader title={t("create_an_out_of_office")} /> | ||
<DialogHeader | ||
title={ | ||
currentlyEditingOutOfOfficeEntry ? t("edit_an_out_of_office") : t("create_an_out_of_office") | ||
} | ||
/> | ||
<div> | ||
<p className="text-emphasis mb-1 block text-sm font-medium capitalize">{t("dates")}</p> | ||
<div> | ||
|
@@ -234,7 +306,7 @@ const CreateOutOfOfficeEntryModal = ({ | |
type="submit" | ||
disabled={createOutOfOfficeEntry.isPending} | ||
data-testid="create-entry-ooo-redirect"> | ||
{t("create")} | ||
{currentlyEditingOutOfOfficeEntry ? t("edit") : t("create")} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of "edit" it should be "save" |
||
</Button> | ||
</div> | ||
</DialogFooter> | ||
|
@@ -243,7 +315,11 @@ const CreateOutOfOfficeEntryModal = ({ | |
); | ||
}; | ||
|
||
const OutOfOfficeEntriesList = () => { | ||
const OutOfOfficeEntriesList = ({ | ||
editOutOfOfficeEntry, | ||
}: { | ||
editOutOfOfficeEntry: (entry: outOfOfficeEntryData) => void; | ||
}) => { | ||
const { t } = useLocale(); | ||
const utils = trpc.useUtils(); | ||
const { data, isPending } = trpc.viewer.outOfOfficeEntriesList.useQuery(); | ||
|
@@ -324,17 +400,38 @@ const OutOfOfficeEntriesList = () => { | |
</div> | ||
</div> | ||
|
||
<Button | ||
className="self-center rounded-lg border" | ||
type="button" | ||
color="minimal" | ||
variant="icon" | ||
disabled={deleteOutOfOfficeEntryMutation.isPending} | ||
StartIcon="trash-2" | ||
onClick={() => { | ||
deleteOutOfOfficeEntryMutation.mutate({ outOfOfficeUid: item.uuid }); | ||
}} | ||
/> | ||
<div className="flex flex-row items-center"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need space between these button |
||
<Button | ||
className="self-center rounded-lg border" | ||
type="button" | ||
color="minimal" | ||
variant="icon" | ||
StartIcon="pencil" | ||
onClick={() => { | ||
const outOfOfficeEntryData = { | ||
uuid: item.uuid, | ||
profileRedirect: item.toUserId ? true : false, | ||
selectedReason: item.reason?.id ?? null, | ||
selectedMember: item.toUserId, | ||
notes: item.notes, | ||
start: item.start, | ||
end: item.end, | ||
}; | ||
editOutOfOfficeEntry(outOfOfficeEntryData); | ||
}} | ||
/> | ||
<Button | ||
className="self-center rounded-lg border" | ||
type="button" | ||
color="minimal" | ||
variant="icon" | ||
disabled={deleteOutOfOfficeEntryMutation.isPending} | ||
StartIcon="trash-2" | ||
onClick={() => { | ||
deleteOutOfOfficeEntryMutation.mutate({ outOfOfficeUid: item.uuid }); | ||
}} | ||
/> | ||
</div> | ||
</TableCell> | ||
</TableRow> | ||
))} | ||
|
@@ -375,6 +472,14 @@ const OutOfOfficePage = () => { | |
}, [openModalOnStart]); | ||
|
||
const [openModal, setOpenModal] = useState(false); | ||
const [currentlyEditingOutOfOfficeEntry, setCurrentlyEditingOutOfOfficeEntry] = | ||
useState<outOfOfficeEntryData | null>(null); | ||
|
||
const editOutOfOfficeEntry = (entry: outOfOfficeEntryData) => { | ||
setCurrentlyEditingOutOfOfficeEntry(entry); | ||
setOpenModal(true); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Meta | ||
|
@@ -391,8 +496,15 @@ const OutOfOfficePage = () => { | |
</Button> | ||
} | ||
/> | ||
<CreateOutOfOfficeEntryModal openModal={openModal} closeModal={() => setOpenModal(false)} /> | ||
<OutOfOfficeEntriesList /> | ||
<CreateOutOfOfficeEntryModal | ||
openModal={openModal} | ||
closeModal={() => { | ||
setOpenModal(false); | ||
setCurrentlyEditingOutOfOfficeEntry(null); | ||
}} | ||
currentlyEditingOutOfOfficeEntry={currentlyEditingOutOfOfficeEntry} | ||
/> | ||
<OutOfOfficeEntriesList editOutOfOfficeEntry={editOutOfOfficeEntry} /> | ||
</> | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2333,6 +2333,7 @@ | |||||
"redirect_team_disabled": "Provide a link to a team member when OOO (Team plan required)", | ||||||
"out_of_office_unavailable_list": "Out of office unavailability list", | ||||||
"success_deleted_entry_out_of_office": "Successfully deleted entry", | ||||||
"success_edited_entry_out_of_office": "Successfully edited entry", | ||||||
"temporarily_out_of_office": "Temporarily Out-Of-Office?", | ||||||
"add_a_redirect": "Add a redirect", | ||||||
"create_entry": "Create entry", | ||||||
|
@@ -2454,6 +2455,7 @@ | |||||
"ooo_create_entry_modal": "Go Out of Office", | ||||||
"ooo_select_reason": "Select reason", | ||||||
"create_an_out_of_office": "Go Out of Office", | ||||||
"edit_an_out_of_office": "Edit this Out of Office Entry", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||||||
"submit_feedback": "Submit Feedback", | ||||||
"host_no_show": "Your host didn't show up", | ||||||
"no_show_description": "You can reschedule another meeting with them", | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @anikdhabal Just FYI - To avoid creation of another new mutation, I have made the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't looked at all the code thoroughly; I will check it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! 👍 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,6 +79,11 @@ export const outOfOfficeCreate = async ({ ctx, input }: TBookingRedirect) => { | |
where: { | ||
AND: [ | ||
{ userId: ctx.user.id }, | ||
{ | ||
uuid: { | ||
not: input.uuid, | ||
}, | ||
}, | ||
{ | ||
OR: [ | ||
{ | ||
|
@@ -154,8 +159,11 @@ export const outOfOfficeCreate = async ({ ctx, input }: TBookingRedirect) => { | |
const startDateUtc = dayjs.utc(startDate).add(input.offset, "minute"); | ||
const endDateUtc = dayjs.utc(endDate).add(input.offset, "minute"); | ||
|
||
const createdRedirect = await prisma.outOfOfficeEntry.create({ | ||
data: { | ||
const createdRedirect = await prisma.outOfOfficeEntry.upsert({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
where: { | ||
uuid: input.uuid ?? "", | ||
}, | ||
create: { | ||
uuid: uuidv4(), | ||
start: startDateUtc.startOf("day").toISOString(), | ||
end: endDateUtc.endOf("day").toISOString(), | ||
|
@@ -166,6 +174,14 @@ export const outOfOfficeCreate = async ({ ctx, input }: TBookingRedirect) => { | |
createdAt: new Date(), | ||
updatedAt: new Date(), | ||
}, | ||
update: { | ||
start: startDateUtc.startOf("day").toISOString(), | ||
end: endDateUtc.endOf("day").toISOString(), | ||
notes: input.notes, | ||
userId: ctx.user.id, | ||
reasonId: input.reasonId, | ||
toUserId: toUserId, | ||
}, | ||
}); | ||
|
||
if (toUserId) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be
CreateOrUpdateOutOfOfficeEntryModal
. In fact, everything needs to be changed fromcreate
tocreateOrUpdate
. Please go through the code thoroughly and make the necessary changesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.. will do it..