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(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[] = [ { id: "member", accessorFn: (data) => data.email, header: "Member", cell: ({ row }) => { const { username, email, timeZone } = row.original; return (
{username || "No username"}
{timeZone}
); }, }, { 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 (
{time} GMT {offsetFormatted}
); }, }, { id: "slider", header: () => { return (
); }, cell: ({ row }) => { const { timeZone, dateRanges } = row.original; // return
{JSON.stringify(dateRanges, null, 2)}
; return ; }, }, ]; 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 (
} onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)} />
); }