Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 77 additions & 64 deletions apps/web/modules/settings/my-account/holidays-view.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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() {
Expand Down Expand Up @@ -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>
);
}
Expand All @@ -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 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 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>
<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")}
{/* 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>
)}
</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" />
<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>
<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>
Expand Down
155 changes: 78 additions & 77 deletions apps/web/modules/settings/outOfOffice/OutOfOfficeEntriesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
}
Expand Down Expand Up @@ -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>
) : (
Expand Down