fix: use DateRangePicker component in my-account/out-of-office (#13207)
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
.custom-date > .tremor-DateRangePicker-root > .tremor-DateRangePicker-button {
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Media query for screens larger than 768px */
|
||||
@media (max-width: 639) {
|
||||
.custom-date > .tremor-DateRangePicker-root > .tremor-DateRangePicker-button {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.recharts-cartesian-grid-horizontal line{
|
||||
@apply stroke-emphasis
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-button button{
|
||||
@apply !h-9 !max-h-9 border-default hover:border-emphasis
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarButton,
|
||||
.tremor-DateRangePicker-dropdownButton {
|
||||
@apply border-subtle bg-default focus-within:ring-emphasis hover:border-subtle dark:focus-within:ring-emphasis hover:bg-subtle text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:ring-0;
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-dropdownModal{
|
||||
@apply divide-none
|
||||
}
|
||||
|
||||
.tremor-DropdownItem-root{
|
||||
@apply !h-9 !max-h-9 bg-default hover:bg-subtle text-default hover:text-emphasis
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarButtonText,
|
||||
.tremor-DateRangePicker-dropdownButtonText {
|
||||
@apply text-default;
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarHeaderText{
|
||||
@apply !text-default
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarHeader svg{
|
||||
@apply text-default
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarHeader button{
|
||||
@apply hover:bg-emphasis shadow-none focus:ring-0
|
||||
}
|
||||
|
||||
|
||||
.tremor-DateRangePicker-calendarHeader button:hover svg{
|
||||
@apply text-emphasis
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarButtonIcon{
|
||||
@apply text-default
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarModal,
|
||||
.tremor-DateRangePicker-dropdownModal {
|
||||
@apply bg-default border-subtle shadow-dropdown
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarBodyDate button{
|
||||
@apply text-default hover:bg-emphasis
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarBodyDate button:disabled,
|
||||
.tremor-DateRangePicker-calendarBodyDate button[disabled]{
|
||||
@apply opacity-25
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarHeader button{
|
||||
@apply border-default text-default
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarBodyDate .bg-gray-100{
|
||||
@apply bg-subtle
|
||||
}
|
||||
|
||||
.tremor-DateRangePicker-calendarBodyDate .bg-gray-500{
|
||||
@apply !bg-brand-default text-inverted
|
||||
}
|
||||
|
||||
|
||||
.tremor-Card-root {
|
||||
@apply p-5 bg-default;
|
||||
}
|
||||
|
||||
.tremor-TableCell-root {
|
||||
@apply pl-0;
|
||||
}
|
||||
|
||||
.recharts-responsive-container {
|
||||
@apply -mx-4;
|
||||
}
|
||||
.tremor-Card-root > p {
|
||||
@apply mb-2 text-base font-semibold;
|
||||
}
|
||||
|
||||
.tremor-Legend-legendItem {
|
||||
@apply ml-2;
|
||||
}
|
||||
|
||||
.tremor-TableBody-root {
|
||||
@apply divide-subtle;
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { BookingRedirectForm } from "@pages/settings/my-account/out-of-office";
|
||||
import { DateRangePicker } from "@tremor/react";
|
||||
import type { UseFormSetValue } from "react-hook-form";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import "./DateSelect.css";
|
||||
|
||||
interface IOutOfOfficeDateRangeSelectProps {
|
||||
dateRange: [Date | null, Date | null, null];
|
||||
setDateRange: React.Dispatch<React.SetStateAction<[Date | null, Date | null, null]>>;
|
||||
setValue: UseFormSetValue<BookingRedirectForm>;
|
||||
}
|
||||
|
||||
const OutOfOfficeDateRangePicker = (props: IOutOfOfficeDateRangeSelectProps) => {
|
||||
const { t } = useLocale();
|
||||
const { dateRange, setDateRange, setValue } = props;
|
||||
return (
|
||||
<div className="custom-date">
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
defaultValue={dateRange}
|
||||
onValueChange={(datesArray) => {
|
||||
const [start, end] = datesArray;
|
||||
|
||||
if (start) {
|
||||
setDateRange([start, end as Date | null, null]);
|
||||
}
|
||||
if (start && end) {
|
||||
setValue("startDate", start.toISOString());
|
||||
setValue("endDate", end.toISOString());
|
||||
}
|
||||
}}
|
||||
color="gray"
|
||||
options={undefined}
|
||||
enableDropdown={false}
|
||||
placeholder={t("select_date_range")}
|
||||
enableYearPagination={true}
|
||||
minDate={dayjs().startOf("d").toDate()}
|
||||
maxDate={dayjs().add(2, "y").endOf("d").toDate()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { OutOfOfficeDateRangePicker };
|
||||
@@ -1,23 +1,31 @@
|
||||
import { Trash2 } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { useForm, useFormState } from "react-hook-form";
|
||||
import { Controller, useForm, useFormState } from "react-hook-form";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { ShellMain } from "@calcom/features/shell/Shell";
|
||||
import { useHasTeamPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import { Button, Meta, showToast, Select, SkeletonText, UpgradeTeamsBadge, Switch } from "@calcom/ui";
|
||||
import {
|
||||
Button,
|
||||
Meta,
|
||||
showToast,
|
||||
Select,
|
||||
SkeletonText,
|
||||
UpgradeTeamsBadge,
|
||||
Switch,
|
||||
DateRangePicker,
|
||||
} from "@calcom/ui";
|
||||
import { TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@calcom/ui";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
import { OutOfOfficeDateRangePicker } from "@components/out-of-office/DateRangePicker";
|
||||
|
||||
export type BookingRedirectForm = {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
dateRange: { startDate: Date; endDate: Date };
|
||||
toTeamUserId: number | null;
|
||||
};
|
||||
|
||||
@@ -25,18 +33,20 @@ const OutOfOfficeSection = () => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const [dateRange, setDateRange] = useState<[Date | null, Date | null, null | null]>([
|
||||
dayjs().startOf("d").toDate(),
|
||||
dayjs().add(1, "d").endOf("d").toDate(),
|
||||
null,
|
||||
]);
|
||||
const [profileRedirect, setProfileRedirect] = useState(false);
|
||||
const [selectedMember, setSelectedMember] = useState<{ label: string; value: number | null } | null>(null);
|
||||
|
||||
const { handleSubmit, setValue } = useForm<BookingRedirectForm>({
|
||||
const [dateRange] = useState<{ startDate: Date; endDate: Date }>({
|
||||
startDate: dayjs().startOf("d").toDate(),
|
||||
endDate: dayjs().add(1, "d").endOf("d").toDate(),
|
||||
});
|
||||
|
||||
const { handleSubmit, setValue, getValues, control } = useForm<BookingRedirectForm>({
|
||||
defaultValues: {
|
||||
startDate: dateRange[0]?.toISOString(),
|
||||
endDate: dateRange[1]?.toISOString(),
|
||||
dateRange: {
|
||||
startDate: dateRange.startDate,
|
||||
endDate: dateRange.endDate,
|
||||
},
|
||||
toTeamUserId: null,
|
||||
},
|
||||
});
|
||||
@@ -74,75 +84,81 @@ const OutOfOfficeSection = () => {
|
||||
setValue("toTeamUserId", null);
|
||||
setSelectedMember(null);
|
||||
})}>
|
||||
<div className="border-subtle flex flex-col rounded-b-lg border border-t-0 p-6 px-6 py-8 text-sm">
|
||||
<div className="border-subtle flex flex-col border border-b-0 border-t-0 p-6 px-6 py-8 text-sm">
|
||||
{/* Add startDate and end date inputs */}
|
||||
<div className="border-subtle mt-2 rounded-lg border bg-gray-50 p-6 dark:bg-transparent">
|
||||
{/* Add toggle to enable/disable redirect */}
|
||||
<div className="flex flex-row">
|
||||
<Switch
|
||||
disabled={!hasTeamPlan}
|
||||
data-testid="profile-redirect-switch"
|
||||
checked={profileRedirect}
|
||||
id="profile-redirect-switch"
|
||||
onCheckedChange={(state) => {
|
||||
setProfileRedirect(state);
|
||||
}}
|
||||
label={hasTeamPlan ? t("redirect_team_enabled") : t("redirect_team_disabled")}
|
||||
/>
|
||||
{!hasTeamPlan && (
|
||||
<div className="mx-2" data-testid="upgrade-team-badge">
|
||||
<UpgradeTeamsBadge />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 flex flex-row">
|
||||
{profileRedirect && (
|
||||
<div className="mr-2 w-1/2 lg:w-1/3">
|
||||
<p className="text-emphasis block text-sm font-medium">{t("team_member")}</p>
|
||||
<Select
|
||||
className="mt-1 h-4 max-w-[350px] text-white"
|
||||
name="toTeamUsername"
|
||||
data-testid="team_username_select"
|
||||
value={selectedMember}
|
||||
placeholder={t("select_team_member")}
|
||||
isSearchable
|
||||
innerClassNames={{
|
||||
control: "h-[38px]",
|
||||
}}
|
||||
options={memberListOptions}
|
||||
onChange={(selectedOption) => {
|
||||
if (selectedOption?.value) {
|
||||
setSelectedMember(selectedOption);
|
||||
setValue("toTeamUserId", selectedOption?.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-1/2 lg:w-1/3">
|
||||
<p className="text-emphasis mb-1 block text-sm font-medium">{t("time_range")}</p>
|
||||
|
||||
<OutOfOfficeDateRangePicker
|
||||
dateRange={dateRange}
|
||||
setValue={setValue}
|
||||
setDateRange={setDateRange}
|
||||
{/* Add toggle to enable/disable redirect */}
|
||||
<div className="flex flex-row">
|
||||
<Switch
|
||||
disabled={!hasTeamPlan}
|
||||
data-testid="profile-redirect-switch"
|
||||
checked={profileRedirect}
|
||||
id="profile-redirect-switch"
|
||||
onCheckedChange={(state) => {
|
||||
setProfileRedirect(state);
|
||||
}}
|
||||
label={hasTeamPlan ? t("redirect_team_enabled") : t("redirect_team_disabled")}
|
||||
/>
|
||||
{!hasTeamPlan && (
|
||||
<div className="mx-2" data-testid="upgrade-team-badge">
|
||||
<UpgradeTeamsBadge />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 flex flex-row">
|
||||
{profileRedirect && (
|
||||
<div className="mr-2 w-1/2 lg:w-1/3">
|
||||
<p className="text-emphasis block text-sm font-medium">{t("team_member")}</p>
|
||||
<Select
|
||||
className="mt-1 h-4 max-w-[350px] text-white"
|
||||
name="toTeamUsername"
|
||||
data-testid="team_username_select"
|
||||
value={selectedMember}
|
||||
placeholder={t("select_team_member")}
|
||||
isSearchable
|
||||
innerClassNames={{
|
||||
control: "h-[38px]",
|
||||
}}
|
||||
options={memberListOptions}
|
||||
onChange={(selectedOption) => {
|
||||
if (selectedOption?.value) {
|
||||
setSelectedMember(selectedOption);
|
||||
setValue("toTeamUserId", selectedOption?.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-7">
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={createOutOfOfficeEntry.isLoading}
|
||||
data-testid="create-entry-ooo-redirect">
|
||||
{t("create_entry")}
|
||||
</Button>
|
||||
)}
|
||||
<div className="w-1/2 lg:w-1/3">
|
||||
<p className="text-emphasis mb-1 block text-sm font-medium">{t("time_range")}</p>
|
||||
<Controller
|
||||
name="dateRange"
|
||||
control={control}
|
||||
defaultValue={dateRange}
|
||||
render={() => (
|
||||
<DateRangePicker
|
||||
startDate={getValues("dateRange").startDate}
|
||||
endDate={getValues("dateRange").endDate}
|
||||
onDatesChange={({ startDate, endDate }) => {
|
||||
setValue("dateRange", {
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<OutOfOfficeEntriesList />
|
||||
</div>
|
||||
<SectionBottomActions className="mb-6" align="end">
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={createOutOfOfficeEntry.isLoading}
|
||||
data-testid="create-entry-ooo-redirect">
|
||||
{t("create_entry")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
@@ -166,10 +182,10 @@ const OutOfOfficeEntriesList = () => {
|
||||
return (
|
||||
<div className="border-subtle mt-6 rounded-lg border">
|
||||
<TableNew className="border-0">
|
||||
<TableHeader>
|
||||
<TableHeader className="md:z-1">
|
||||
<TableRow>
|
||||
<TableHead className="rounded-tl-lg font-normal capitalize">{t("time_range")}</TableHead>
|
||||
<TableHead className="font-normal">{t("username")}</TableHead>
|
||||
<TableHead className="font-normal">{t("redirect_to")}</TableHead>
|
||||
|
||||
<TableHead className="rounded-tr-lg font-normal">{t("action")}</TableHead>
|
||||
</TableRow>
|
||||
@@ -230,6 +246,7 @@ const OutOfOfficePage = () => {
|
||||
<Meta title={t("out_of_office")} description={t("out_of_office_description")} borderInShellHeader />
|
||||
<ShellMain>
|
||||
<OutOfOfficeSection />
|
||||
<OutOfOfficeEntriesList />
|
||||
</ShellMain>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2222,5 +2222,6 @@
|
||||
"add_a_redirect": "Add a redirect",
|
||||
"create_entry": "Create entry",
|
||||
"time_range": "Time range",
|
||||
"redirect_to": "Redirect to",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@ type TBookingRedirect = {
|
||||
};
|
||||
|
||||
export const outOfOfficeCreate = async ({ ctx, input }: TBookingRedirect) => {
|
||||
if (!input.startDate || !input.endDate) {
|
||||
const { startDate, endDate } = input.dateRange;
|
||||
if (!startDate || !endDate) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "start_date_and_end_date_required" });
|
||||
}
|
||||
|
||||
const inputStartTime = dayjs(input.startDate).startOf("day");
|
||||
const inputEndTime = dayjs(input.endDate).endOf("day");
|
||||
const inputStartTime = dayjs(startDate).startOf("day");
|
||||
const inputEndTime = dayjs(endDate).endOf("day");
|
||||
const offset = dayjs(inputStartTime).utcOffset();
|
||||
|
||||
// If start date is after end date throw error
|
||||
@@ -137,8 +138,8 @@ export const outOfOfficeCreate = async ({ ctx, input }: TBookingRedirect) => {
|
||||
const createdRedirect = await prisma.outOfOfficeEntry.create({
|
||||
data: {
|
||||
uuid: uuidv4(),
|
||||
start: dayjs(input.startDate).startOf("day").toISOString(),
|
||||
end: dayjs(input.endDate).endOf("day").toISOString(),
|
||||
start: dayjs(startDate).startOf("day").toISOString(),
|
||||
end: dayjs(endDate).endOf("day").toISOString(),
|
||||
userId: ctx.user.id,
|
||||
toUserId: toUserId,
|
||||
createdAt: new Date(),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZOutOfOfficeInputSchema = z.object({
|
||||
startDate: z.string(),
|
||||
endDate: z.string(),
|
||||
dateRange: z.object({
|
||||
startDate: z.date(),
|
||||
endDate: z.date(),
|
||||
}),
|
||||
toTeamUserId: z.number().nullable(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user