Files
cal.diy/packages/features/timezone-buddy/components/AvailabilitySliderTable.tsx
T
+15 67bb2ed798 feat: Availability slider for orgs (#10740)
* Init + get timezone + offset data agh

* Add 12/24h mode - style correctly

* User users timezone + working hours. Still some stuff to figure out

* Multiple working hours

* move calc to once per day

* Demo with two users and differnt timezones

* availabillity control tab via search params

* WIP hover overlay

* THIS WORKS ISH

* fix: multiple duration getSchedule calls [CAL-2336] (#10709)

Co-authored-by: Omar López <zomars@me.com>

* New Crowdin translations by Github Action

* fix: If the input type "Name" is selected, the label can't be changed from our default label "Your Name" (#10618)

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

* New Crowdin translations by Github Action

* test: Create unit tests for react components in packages/ui/components/form/step (#10442)

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* feat: element call app added (#10585)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* New Crowdin translations by Github Action

* New Crowdin translations by Github Action

* fix: e2e test for rescheduling overlapping time (#10721)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* feat: mailhog fixture (#10606)

* feat: mailhog fixture

* fix: nodemailer to dispatch emails with e2e env

* fix: remove space from email subject

* feat: fixture getFirstEventAsOwner

* feat: assert email subjects

* fix: and enable dynamic booking test (#10642)

* fix and enable dynamic booking test

* remove page pause

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>

* fix: Broken team events if a user with the same name exists (#10724)

* fix: Broken team events if a user with the same name exists

* Fix tests + fix usernameList optionality

* Try to list calendars, if not continue (#10720)

Co-authored-by: Omar López <zomars@me.com>

* v3.1.9

* link to org settings (#10718)

* feat: app paypal payment (#8797)

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>

* fix: RTL issues on booking pages + email confirmation (#10526)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* fix: merge conflict

* Fixing org slug (#10538)

* fix: paypal build fixes

* Fix avatar for org in Shell top (#10712)

* feat: add range of dates for availability over-ride (#10462)

* feat: add range of dates for availability over-ride

* chore: changed range select to multiple select

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>

* fix: border issue for time slots (#10577)

Co-authored-by: Raghul D <v-raghuld@microsoft.com>

* style: Fix text wrapping issue in button (#10725)

Co-authored-by: Omar López <zomars@me.com>

* New Crowdin translations by Github Action

* fix: App Install Dropdown Sort Properly [CAL-2285] (#10672)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* fix: link escaping in booking page (#10360)

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* chore: fix refund i18n message (#10731)

* chore: remove tailwind-scrollbar warning (#10523)

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>

* New Crowdin translations by Github Action

* chore: Simplified date overrides (#10728)

* chore: Simplified date overrides

* Fixed a test that had a date override that wasn't at midnight utc

* Wrote test that showed a fixed Europe/Brussels

* Lint fix

* New Crowdin translations by Github Action

* Fix offset time + fetching correct dates

* Deal with awkward minute offsets

* remove store overhead

* Format H based on tz

* Cleanup store logic

* Cleanup

* Remove comments

* Remove comments

* Remove yarn.lock

* Dark mode & v-align text fixes

* Move ButtonGroup to the left to prevent chevron from jumping

* Shift based on timezone (non-hour) and have 15min granularity in hour display background color

---------

Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Zain Gulbaz <zaingulbaz8@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Suyash Srivastava <suyashsrivastava5053@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Pradumn Kumar <pradumn@tealfeed.com>
Co-authored-by: Raghul <123321540+Raghul18@users.noreply.github.com>
Co-authored-by: Raghul D <v-raghuld@microsoft.com>
Co-authored-by: ABDERRAHMANI IDRISSI HAMZA <97639117+idrissi-hamza@users.noreply.github.com>
Co-authored-by: Nafees Nazik <84864519+G3root@users.noreply.github.com>
Co-authored-by: Danila <daniil.demidovich@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-08-18 16:32:24 +00:00

164 lines
5.7 KiB
TypeScript

import type { ColumnDef } from "@tanstack/react-table";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { useMemo, useRef, useCallback, useEffect, useState } from "react";
import dayjs from "@calcom/dayjs";
import type { DateRange } from "@calcom/lib/date-ranges";
import type { MembershipRole } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc";
import { Avatar, Button, ButtonGroup, DataTable } from "@calcom/ui";
import { TBContext, createTimezoneBuddyStore } from "../store";
import { TimeDial } from "./TimeDial";
export interface SliderUser {
id: number;
username: string | null;
email: string;
timeZone: string;
role: MembershipRole;
dateRanges: DateRange[];
}
export function AvailabilitySliderTable() {
const tableContainerRef = useRef<HTMLDivElement>(null);
const [browsingDate, setBrowsingDate] = useState(dayjs());
const { data, isLoading, fetchNextPage, isFetching } = trpc.viewer.availability.listTeam.useInfiniteQuery(
{
limit: 10,
loggedInUsersTz: dayjs.tz.guess(),
startDate: browsingDate.startOf("day").toISOString(),
endDate: browsingDate.endOf("day").toISOString(),
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
keepPreviousData: true,
}
);
const memorisedColumns = useMemo(() => {
const cols: ColumnDef<SliderUser>[] = [
{
id: "member",
accessorFn: (data) => data.email,
header: "Member",
cell: ({ row }) => {
const { username, email, timeZone } = row.original;
return (
<div className="max-w-64 flex flex-shrink-0 items-center gap-2 overflow-hidden">
<Avatar
size="sm"
alt={username || email}
imageSrc={"/" + username + "/avatar.png"}
gravatarFallbackMd5="fallback"
/>
<div className="">
<div className="text-emphasis max-w-64 truncate text-sm font-medium" title={email}>
{username || "No username"}
</div>
<div className="text-subtle text-xs leading-none">{timeZone}</div>
</div>
</div>
);
},
},
{
id: "timezone",
accessorFn: (data) => data.timeZone,
header: "Timezone",
cell: ({ row }) => {
const { timeZone } = row.original;
const timeRaw = dayjs().tz(timeZone);
const time = timeRaw.format("HH:mm");
const utcOffsetInMinutes = timeRaw.utcOffset();
const hours = Math.abs(Math.floor(utcOffsetInMinutes / 60));
const minutes = Math.abs(utcOffsetInMinutes % 60);
const offsetFormatted = `${utcOffsetInMinutes < 0 ? "-" : "+"}${hours
.toString()
.padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
return (
<div className="flex flex-col">
<span className="text-default text-sm font-medium">{time}</span>
<span className="text-subtle text-xs leading-none">GMT {offsetFormatted}</span>
</div>
);
},
},
{
id: "slider",
header: () => {
return (
<div className="flex items-center">
<ButtonGroup containerProps={{ className: "space-x-0" }}>
<Button
color="minimal"
variant="icon"
StartIcon={ChevronLeftIcon}
onClick={() => setBrowsingDate(browsingDate.subtract(1, "day"))}
/>
<Button
onClick={() => setBrowsingDate(browsingDate.add(1, "day"))}
color="minimal"
StartIcon={ChevronRightIcon}
variant="icon"
/>
</ButtonGroup>
<span>{browsingDate.format("DD dddd MMM, YYYY")}</span>
</div>
);
},
cell: ({ row }) => {
const { timeZone, dateRanges } = row.original;
// return <pre>{JSON.stringify(dateRanges, null, 2)}</pre>;
return <TimeDial timezone={timeZone} dateRanges={dateRanges} />;
},
},
];
return cols;
}, [browsingDate]);
//we must flatten the array of arrays from the useInfiniteQuery hook
const flatData = useMemo(() => data?.pages?.flatMap((page) => page.rows) ?? [], [data]) as SliderUser[];
const totalDBRowCount = data?.pages?.[0]?.meta?.totalRowCount ?? 0;
const totalFetched = flatData.length;
//called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table
const fetchMoreOnBottomReached = useCallback(
(containerRefElement?: HTMLDivElement | null) => {
if (containerRefElement) {
const { scrollHeight, scrollTop, clientHeight } = containerRefElement;
//once the user has scrolled within 300px of the bottom of the table, fetch more data if there is any
if (scrollHeight - scrollTop - clientHeight < 300 && !isFetching && totalFetched < totalDBRowCount) {
fetchNextPage();
}
}
},
[fetchNextPage, isFetching, totalFetched, totalDBRowCount]
);
useEffect(() => {
fetchMoreOnBottomReached(tableContainerRef.current);
}, [fetchMoreOnBottomReached]);
return (
<TBContext.Provider
value={createTimezoneBuddyStore({
browsingDate: browsingDate.toDate(),
})}>
<div className="relative">
<DataTable
tableContainerRef={tableContainerRef}
columns={memorisedColumns}
data={flatData}
isLoading={isLoading}
// tableOverlay={<HoverOverview />}
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}
/>
</div>
</TBContext.Provider>
);
}