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 { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants"; import type { DateRange } from "@calcom/lib/date-ranges"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { MembershipRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc"; import { Button, ButtonGroup, DataTable } from "@calcom/ui"; import { UserAvatar } from "@calcom/web/components/ui/avatar/UserAvatar"; import { UpgradeTip } from "../../tips/UpgradeTip"; import { TBContext, createTimezoneBuddyStore } from "../store"; import { AvailabilityEditSheet } from "./AvailabilityEditSheet"; import { TimeDial } from "./TimeDial"; export interface SliderUser { id: number; username: string | null; name: string | null; organizationId: number; email: string; timeZone: string; role: MembershipRole; defaultScheduleId: number | null; dateRanges: DateRange[]; } function UpgradeTeamTip() { const { t } = useLocale(); return ( }> <> ); } export function AvailabilitySliderTable() { const { t } = useLocale(); const tableContainerRef = useRef(null); const [browsingDate, setBrowsingDate] = useState(dayjs()); const [editSheetOpen, setEditSheetOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const { data, isLoading, fetchNextPage, isFetching } = trpc.viewer.availability.listTeam.useInfiniteQuery( { limit: 10, loggedInUsersTz: dayjs.tz.guess() || "Europe/London", 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, name, organizationId } = 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]); // This means they are not apart of any teams so we show the upgrade tip if (!flatData.length) return ; return ( <>
{ setEditSheetOpen(true); setSelectedUser(row.original); }} data={flatData} isLoading={isLoading} // tableOverlay={} onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)} />
{selectedUser && editSheetOpen ? ( { setEditSheetOpen(e); setSelectedUser(null); // We need to clear the user here or else the sheet will not re-render when opening a new user }} selectedUser={selectedUser} /> ) : null}
); }