diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 006965a5d2..8fe725a599 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2994,6 +2994,7 @@ "ooo_slots_returning": "<0>{{displayName}} can take their meetings while they are away.", "ooo_slots_book_with": "Book {{displayName}}", "ooo_select_reason": "Select reason", + "show_note_publicly_description": "Show note on public booking page", "create_an_out_of_office": "Go Out of Office", "edit_an_out_of_office": "Edit Out of Office Entry", "submit_feedback": "Submit Feedback", diff --git a/packages/app-store/zapier/api/subscriptions/listOOOEntries.ts b/packages/app-store/zapier/api/subscriptions/listOOOEntries.ts index 759a4d570f..b7e4f8bbfe 100644 --- a/packages/app-store/zapier/api/subscriptions/listOOOEntries.ts +++ b/packages/app-store/zapier/api/subscriptions/listOOOEntries.ts @@ -14,6 +14,7 @@ export const selectOOOEntries = { createdAt: true, updatedAt: true, notes: true, + showNotePublicly: true, reason: { select: { reason: true, diff --git a/packages/features/availability/lib/getUserAvailability.ts b/packages/features/availability/lib/getUserAvailability.ts index f135e61159..afc8a69d98 100644 --- a/packages/features/availability/lib/getUserAvailability.ts +++ b/packages/features/availability/lib/getUserAvailability.ts @@ -77,7 +77,7 @@ export type GetUserAvailabilityInitialData = { seatsReferences: number; }; })[]; - outOfOfficeDays?: (Pick & { + outOfOfficeDays?: (Pick & { user: Pick; toUser: Pick | null; reason: Pick | null; @@ -144,6 +144,8 @@ export interface IOutOfOfficeData { toUser?: IToUser | null; reason?: string | null; emoji?: string | null; + notes?: string | null; + showNotePublicly?: boolean; }; } @@ -615,40 +617,45 @@ export class UserAvailabilityService { return {}; } - return outOfOfficeDays.reduce((acc: IOutOfOfficeData, { start, end, toUser, user, reason }) => { - // here we should use startDate or today if start is before today - // consider timezone in start and end date range - const startDateRange = dayjs(start).utc().isBefore(dayjs().startOf("day").utc()) - ? dayjs().utc().startOf("day") - : dayjs(start).utc().startOf("day"); + return outOfOfficeDays.reduce( + (acc: IOutOfOfficeData, { start, end, toUser, user, reason, notes, showNotePublicly }) => { + // here we should use startDate or today if start is before today + // consider timezone in start and end date range + const startDateRange = dayjs(start).utc().isBefore(dayjs().startOf("day").utc()) + ? dayjs().utc().startOf("day") + : dayjs(start).utc().startOf("day"); - // get number of day in the week and see if it's on the availability - const flattenDays = Array.from(new Set(availability.flatMap((a) => ("days" in a ? a.days : [])))).sort( - (a, b) => a - b - ); + // get number of day in the week and see if it's on the availability + const flattenDays = Array.from( + new Set(availability.flatMap((a) => ("days" in a ? a.days : []))) + ).sort((a, b) => a - b); - const endDateRange = dayjs(end).utc().endOf("day"); + const endDateRange = dayjs(end).utc().endOf("day"); - for (let date = startDateRange; date.isBefore(endDateRange); date = date.add(1, "day")) { - const dayNumberOnWeek = date.day(); + for (let date = startDateRange; date.isBefore(endDateRange); date = date.add(1, "day")) { + const dayNumberOnWeek = date.day(); - if (!flattenDays?.includes(dayNumberOnWeek)) { - continue; // Skip to the next iteration if day not found in flattenDays + if (!flattenDays?.includes(dayNumberOnWeek)) { + continue; // Skip to the next iteration if day not found in flattenDays + } + + acc[date.format("YYYY-MM-DD")] = { + // @TODO: would be good having start and end availability time here, but for now should be good + // you can obtain that from user availability defined outside of here + fromUser: { id: user.id, displayName: user.name }, + // optional chaining destructuring toUser + toUser: toUser ? { id: toUser.id, displayName: toUser.name, username: toUser.username } : null, + reason: reason ? reason.reason : null, + emoji: reason ? reason.emoji : null, + notes: showNotePublicly ? notes || null : null, + showNotePublicly: showNotePublicly ?? false, + }; } - acc[date.format("YYYY-MM-DD")] = { - // @TODO: would be good having start and end availability time here, but for now should be good - // you can obtain that from user availability defined outside of here - fromUser: { id: user.id, displayName: user.name }, - // optional chaining destructuring toUser - toUser: toUser ? { id: toUser.id, displayName: toUser.name, username: toUser.username } : null, - reason: reason ? reason.reason : null, - emoji: reason ? reason.emoji : null, - }; - } - - return acc; - }, {}); + return acc; + }, + {} + ); } async _getUsersAvailability({ users, query, initialData }: GetUsersAvailabilityProps) { diff --git a/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx b/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx index 4555f17ca3..f05d766796 100644 --- a/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx +++ b/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx @@ -191,23 +191,27 @@ export const AvailableTimeSlots = ({
) : ( slotsPerDay.length > 0 && - slotsPerDay.map((slots) => ( - - )) + slotsPerDay.map((slots) => { + // Check if this day is OOO - since OOO is date-level, just check the first slot + const isOOODay = slots.slots.length > 0 && slots.slots[0]?.away; + return ( + + ); + }) )}
@@ -223,7 +227,7 @@ export const AvailableTimeSlots = ({ {!isLoading && slotsPerDay.length > 0 && slotsPerDay.map((slots) => ( -
+
{ const { t } = useLocale(); - const { fromUser, toUser, emoji = "🏝️", borderDashed = true, date, className } = props; + const { + fromUser, + toUser, + emoji = "🏝️", + borderDashed = true, + date, + className, + notes, + showNotePublicly, + } = props; const searchParams = useCompatSearchParams(); const router = useRouter(); - if (!fromUser || !toUser) return null; + if (!fromUser) return null; return (
{
{emoji}
-
+

{t("ooo_user_is_ooo", { displayName: fromUser.displayName })}

+ {notes && showNotePublicly && ( +

+ {notes} +

+ )} + {fromUser?.displayName && toUser?.displayName && (

= (props) => { const isPlatform = useIsPlatform(); - const { fromUser, toUser, reason, emoji, time, className = "" } = props; + const { fromUser, toUser, reason, emoji, notes, showNotePublicly, time, className = "" } = props; if (isPlatform) return <>; return ( @@ -322,6 +324,8 @@ const OOOSlot: React.FC = (props) => { date={dayjs(time).format("YYYY-MM-DD")} reason={reason} emoji={emoji} + notes={notes} + showNotePublicly={showNotePublicly} borderDashed className={className} /> diff --git a/packages/features/calendars/DatePicker.tsx b/packages/features/calendars/DatePicker.tsx index 6b393baec9..63bc4888f6 100644 --- a/packages/features/calendars/DatePicker.tsx +++ b/packages/features/calendars/DatePicker.tsx @@ -54,6 +54,7 @@ export type DatePickerProps = { toUser?: IToUser; reason?: string; emoji?: string; + showNotePublicly?: boolean; }[] >; periodData?: PeriodData; @@ -271,7 +272,9 @@ const Days = ({ const isOOOAllDay = daySlots.length > 0 && daySlots.every((slot) => slot.away); const away = isOOOAllDay; - const disabled = away ? !oooInfo?.toUser : isNextMonth ? !hasAvailableSlots : !included || excluded; + // OOO dates are selectable only if there's a redirect user OR the note is public + const oooIsSelectable = oooInfo?.toUser || oooInfo?.showNotePublicly; + const disabled = away ? !oooIsSelectable : isNextMonth ? !hasAvailableSlots : !included || excluded; return { day, diff --git a/packages/features/schedules/lib/slots.ts b/packages/features/schedules/lib/slots.ts index 3dc027aafc..9884272c64 100644 --- a/packages/features/schedules/lib/slots.ts +++ b/packages/features/schedules/lib/slots.ts @@ -191,12 +191,14 @@ function buildSlotsWithDateRanges({ toUser?: IToUser; reason?: string; emoji?: string; + notes?: string | null; + showNotePublicly?: boolean; } = { time: slotStartTime, }; if (dateOutOfOfficeExists) { - const { toUser, fromUser, reason, emoji } = dateOutOfOfficeExists; + const { toUser, fromUser, reason, emoji, notes, showNotePublicly } = dateOutOfOfficeExists; slotData = { time: slotStartTime, @@ -205,6 +207,8 @@ function buildSlotsWithDateRanges({ ...(toUser && { toUser }), ...(reason && { reason }), ...(emoji && { emoji }), + ...(notes && showNotePublicly && { notes }), + ...(showNotePublicly !== undefined && { showNotePublicly }), }; } diff --git a/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx b/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx index 5d47f890c0..7772f36ebd 100644 --- a/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx +++ b/packages/features/settings/outOfOffice/CreateOrEditOutOfOfficeModal.tsx @@ -14,7 +14,7 @@ import classNames from "@calcom/ui/classNames"; import { UpgradeTeamsBadge } from "@calcom/ui/components/badge"; import { Button } from "@calcom/ui/components/button"; import { DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/components/dialog"; -import { DateRangePicker, TextArea, Input } from "@calcom/ui/components/form"; +import { DateRangePicker, TextArea, Input, Checkbox } from "@calcom/ui/components/form"; import { Label } from "@calcom/ui/components/form"; import { Select } from "@calcom/ui/components/form"; import { Switch } from "@calcom/ui/components/form"; @@ -29,6 +29,7 @@ export type BookingRedirectForm = { toTeamUserId: number | null; reasonId: number; notes?: string; + showNotePublicly?: boolean; uuid?: string | null; forUserId: number | null; forUserName?: string; @@ -155,6 +156,7 @@ export const CreateOrEditOutOfOfficeEntryModal = ({ toTeamUserId: null, reasonId: 1, forUserId: null, + showNotePublicly: false, }, }); @@ -180,7 +182,6 @@ export const CreateOrEditOutOfOfficeEntryModal = ({ return (

{ if (!open) { closeModal(); @@ -188,7 +189,6 @@ export const CreateOrEditOutOfOfficeEntryModal = ({ }}> { event.preventDefault(); }}> @@ -338,16 +338,33 @@ export const CreateOrEditOutOfOfficeEntryModal = ({ {/* Notes input */}
-

{t("notes")}

+

{t("notes")}