fix: Organised the OOO page (#27865)

* fixed text issue

* matched style of api keys page

* organised the out of office page

* Clean up unused imports in OutOfOfficeEntriesList

Removed unused imports from OutOfOfficeEntriesList.tsx

* fixed import issues

* remove unused imports

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Romit <romitgabani1.work@gmail.com>
Co-authored-by: Romit <85230081+romitg2@users.noreply.github.com>
This commit is contained in:
Ram Shukla
2026-02-12 22:35:43 +05:30
committed by GitHub
co-authored by Cursor Romit Romit
parent f8abce44cb
commit bc9a9c0c24
2 changed files with 159 additions and 145 deletions
@@ -1,7 +1,5 @@
"use client";
import { memo, useMemo, useCallback } from "react";
import dayjs from "@calcom/dayjs";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { getHolidayEmoji } from "@calcom/lib/holidays/getHolidayEmoji";
@@ -10,12 +8,12 @@ import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Alert } from "@calcom/ui/components/alert";
import { Button } from "@calcom/ui/components/button";
import { Select } from "@calcom/ui/components/form";
import { Switch } from "@calcom/ui/components/form";
import { Select, Switch } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton";
import { CalendarIcon, TriangleAlertIcon } from "@coss/ui/icons";
import { TriangleAlertIcon } from "@coss/ui/icons";
import { showToast } from "@calcom/ui/components/toast";
import { memo, useCallback, useMemo } from "react";
import { OutOfOfficeToggleGroup } from "~/settings/outOfOffice/OutOfOfficeToggleGroup";
function HolidaysCTA() {
@@ -244,25 +242,37 @@ export function HolidaysView() {
if (isLoading) {
return (
<SettingsHeader title={t("holidays")} description={t("holidays_description")} CTA={<HolidaysCTA />}>
<SkeletonContainer>
<div className="space-y-4">
<SkeletonText className="h-10 w-64" />
<SkeletonText className="h-64 w-full" />
</div>
</SkeletonContainer>
<SettingsHeader
title={t("holidays")}
description={t("holidays_description")}
borderInShellHeader={true}
CTA={<HolidaysCTA />}>
<div className="border-subtle rounded-b-lg border border-t-0 px-4 py-6 sm:px-6">
<SkeletonContainer>
<div className="space-y-4">
<SkeletonText className="h-10 w-64" />
<SkeletonText className="h-64 w-full" />
</div>
</SkeletonContainer>
</div>
</SettingsHeader>
);
}
if (hasError) {
return (
<SettingsHeader title={t("holidays")} description={t("holidays_description")} CTA={<HolidaysCTA />}>
<Alert
severity="error"
title={t("something_went_wrong")}
message={countriesError?.message || settingsError?.message}
/>
<SettingsHeader
title={t("holidays")}
description={t("holidays_description")}
borderInShellHeader={true}
CTA={<HolidaysCTA />}>
<div className="border-subtle rounded-b-lg border border-t-0 px-4 py-6 sm:px-6">
<Alert
severity="error"
title={t("something_went_wrong")}
message={countriesError?.message || settingsError?.message}
/>
</div>
</SettingsHeader>
);
}
@@ -271,57 +281,60 @@ export function HolidaysView() {
<SettingsHeader
title={t("out_of_office")}
description={t("out_of_office_description")}
borderInShellHeader={true}
CTA={<HolidaysCTA />}>
<div className="space-y-6">
{conflictsData?.conflicts && conflictsData.conflicts.length > 0 && (
<ConflictWarning conflicts={conflictsData.conflicts} />
)}
<div className="border-subtle bg-muted overflow-hidden rounded-lg border p-5">
{/* Header with title and country selector */}
<div className="mb-4 flex items-center justify-between">
<div>
<h3 className="text-emphasis font-semibold">{t("holidays")}</h3>
<p className="text-subtle text-sm">{t("holidays_description")}</p>
</div>
<CountrySelector
countries={countries || []}
value={settings?.countryCode || ""}
onChange={handleCountryChange}
isLoading={isLoadingCountries}
/>
</div>
{/* Holidays list - inner container */}
{settings?.countryCode ? (
<div className="border-subtle bg-default overflow-hidden rounded-md border">
{settings.holidays && settings.holidays.length > 0 ? (
settings.holidays.map((holiday) => (
<HolidayListItem
key={holiday.id}
holiday={holiday}
onToggle={handleToggleHoliday}
isToggling={
toggleHolidayMutation.isPending &&
toggleHolidayMutation.variables?.holidayId === holiday.id
}
/>
))
) : (
<div className="text-subtle py-8 text-center text-sm">
{t("no_holidays_found_for_country")}
</div>
)}
</div>
) : (
<div className="bg-default flex flex-col items-center rounded-md py-14 text-center">
<div className="bg-emphasis mb-4 flex h-16 w-16 items-center justify-center rounded-full">
<CalendarIcon className="text-default h-8 w-8" />
</div>
<h4 className="text-emphasis mb-1 font-medium">{t("no_holidays_selected")}</h4>
<p className="text-subtle text-sm">{t("select_country_to_see_holidays")}</p>
</div>
<div className="border-subtle rounded-b-lg border border-t-0 px-4 py-6 sm:px-6">
<div className="space-y-6">
{conflictsData?.conflicts && conflictsData.conflicts.length > 0 && (
<ConflictWarning conflicts={conflictsData.conflicts} />
)}
<div className="border-subtle bg-muted overflow-hidden rounded-lg border p-5">
{/* Header with title and country selector */}
<div className="mb-4 flex items-center justify-between">
<div>
<h3 className="text-emphasis font-semibold">{t("holidays")}</h3>
<p className="text-subtle text-sm">{t("holidays_description")}</p>
</div>
<CountrySelector
countries={countries || []}
value={settings?.countryCode || ""}
onChange={handleCountryChange}
isLoading={isLoadingCountries}
/>
</div>
{/* Holidays list - inner container */}
{settings?.countryCode ? (
<div className="border-subtle bg-default overflow-hidden rounded-md border justify-between">
{settings.holidays && settings.holidays.length > 0 ? (
settings.holidays.map((holiday) => (
<HolidayListItem
key={holiday.id}
holiday={holiday}
onToggle={handleToggleHoliday}
isToggling={
toggleHolidayMutation.isPending &&
toggleHolidayMutation.variables?.holidayId === holiday.id
}
/>
))
) : (
<div className="text-subtle py-8 text-center text-sm">
{t("no_holidays_found_for_country")}
</div>
)}
</div>
) : (
<div className="bg-default flex flex-col items-center rounded-md py-14 text-center">
<div className="bg-emphasis mb-4 flex h-16 w-16 items-center justify-center rounded-full">
<Icon name="calendar" className="text-default h-8 w-8" />
</div>
<h4 className="text-emphasis mb-1 font-medium">{t("no_holidays_selected")}</h4>
<p className="text-subtle text-sm">{t("select_country_to_see_holidays")}</p>
</div>
)}
</div>
</div>
</div>
</SettingsHeader>
@@ -78,18 +78,21 @@ export default function OutOfOfficeEntriesList({
<SettingsHeader
title={t("out_of_office")}
description={t("out_of_office_description")}
borderInShellHeader={true}
CTA={
<div className="flex gap-2">
<OutOfOfficeToggleGroup />
<CreateNewOutOfOfficeEntryButton data-testid="add_entry_ooo" onClick={onOpenCreateDialog} />
</div>
}>
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments}>
<OutOfOfficeEntriesListContent
onOpenCreateDialog={onOpenCreateDialog}
onOpenEditDialog={onOpenEditDialog}
/>
</DataTableProvider>
<div className="border-subtle rounded-b-lg border border-t-0 px-4 py-6 sm:px-6">
<DataTableProvider tableIdentifier={pathname} useSegments={useSegments}>
<OutOfOfficeEntriesListContent
onOpenCreateDialog={onOpenCreateDialog}
onOpenEditDialog={onOpenEditDialog}
/>
</DataTableProvider>
</div>
</SettingsHeader>
);
}
@@ -168,92 +171,90 @@ function OutOfOfficeEntriesListContent({
}),
...(selectedTab === OutOfOfficeTab.TEAM
? [
columnHelper.display({
id: "member",
header: `Member`,
size: 300,
cell: ({ row }) => {
if (!row.original || !row.original.user || isPending || isFetching) {
return <SkeletonText className="h-8 w-full" />;
}
const { avatarUrl, username, email, name } = row.original.user;
const memberName =
name ||
(() => {
const emailName = email.split("@")[0];
return emailName.charAt(0).toUpperCase() + emailName.slice(1);
})();
return (
<div className="flex items-center gap-2">
<Avatar
size="sm"
alt={username || email}
imageSrc={getUserAvatarUrl({
avatarUrl,
})}
/>
<div className="">
<div
data-testid={`ooo-member-${username}-username`}
className="text-emphasis text-sm font-medium leading-none">
{memberName}
</div>
<div
data-testid={`ooo-member-${username}-email`}
className="text-subtle mt-1 text-sm leading-none">
{email}
</div>
columnHelper.display({
id: "member",
header: `Member`,
size: 220,
cell: ({ row }) => {
if (!row.original || !row.original.user || isPending || isFetching) {
return <SkeletonText className="h-8 w-full" />;
}
const { avatarUrl, username, email, name } = row.original.user;
const memberName =
name ||
(() => {
const emailName = email.split("@")[0];
return emailName.charAt(0).toUpperCase() + emailName.slice(1);
})();
return (
<div className="flex items-center gap-2">
<Avatar
size="sm"
alt={username || email}
imageSrc={getUserAvatarUrl({
avatarUrl,
})}
/>
<div className="">
<div
data-testid={`ooo-member-${username}-username`}
className="text-emphasis text-sm font-medium leading-none">
{memberName}
</div>
<div
data-testid={`ooo-member-${username}-email`}
className="text-subtle mt-1 text-sm leading-none">
{email}
</div>
</div>
);
},
}),
]
</div>
);
},
}),
]
: []),
columnHelper.display({
id: "outOfOffice",
header: `${t("out_of_office")} (${totalRowCount})`,
size: selectedTab === OutOfOfficeTab.TEAM ? 370 : 660,
size: 570,
cell: ({ row }) => {
const item = row.original;
return (
<>
{row.original && !isPending && !isFetching ? (
<div
className="flex flex-row justify-between p-2"
className="flex flex-row items-center gap-3 py-2"
data-testid={`table-redirect-${item.toUser?.username || "n-a"}`}>
<div className="flex flex-row items-center">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-gray-50">
{item?.reason?.emoji || "🏝️"}
</div>
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-subtle">
{item?.reason?.emoji || "🏝️"}
</div>
<div className="ml-2 flex flex-col">
<p className="px-2 font-bold">
{dayjs.utc(item.start).format("ll")} - {dayjs.utc(item.end).format("ll")}
</p>
<p className="px-2">
{item.toUser?.username ? (
<ServerTrans
t={t}
i18nKey="ooo_forwarding_to"
values={{
username: item.toUser?.username,
}}
components={[<span key="ooo-username" className="text-subtle font-bold" />]}
/>
) : (
<>{t("ooo_not_forwarding")}</>
)}
</p>
{item.notes && (
<p className="px-2">
<span className="text-subtle">{t("notes")}: </span>
<span data-testid={`ooo-entry-note-${item.toUser?.username || "n-a"}`}>
{item.notes}
</span>
</p>
<div className="flex flex-col">
<p className="font-bold">
{dayjs.utc(item.start).format("ll")} - {dayjs.utc(item.end).format("ll")}
</p>
<p>
{item.toUser?.username ? (
<ServerTrans
t={t}
i18nKey="ooo_forwarding_to"
values={{
username: item.toUser?.username,
}}
components={[<span key="ooo-username" className="text-subtle font-bold" />]}
/>
) : (
<>{t("ooo_not_forwarding")}</>
)}
</div>
</p>
{item.notes && (
<p>
<span className="text-subtle">{t("notes")}: </span>
<span data-testid={`ooo-entry-note-${item.toUser?.username || "n-a"}`}>
{item.notes}
</span>
</p>
)}
</div>
</div>
) : (