mirror of
https://github.com/lukevella/rallly.git
synced 2025-05-16 02:16:48 +02:00
✨ Improvements to table component (#809)
This commit is contained in:
parent
b727c3f5e5
commit
83ad12b884
18 changed files with 711 additions and 618 deletions
|
@ -64,8 +64,6 @@
|
||||||
"smoothscroll-polyfill": "^0.4.4",
|
"smoothscroll-polyfill": "^0.4.4",
|
||||||
"spacetime": "^7.1.4",
|
"spacetime": "^7.1.4",
|
||||||
"superjson": "^1.12.2",
|
"superjson": "^1.12.2",
|
||||||
"tailwindcss": "^3.2.4",
|
|
||||||
"tailwindcss-animate": "^1.0.5",
|
|
||||||
"timezone-soft": "^1.4.1",
|
"timezone-soft": "^1.4.1",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
|
|
|
@ -10,23 +10,16 @@ export const DateIconInner = (props: {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-14 overflow-hidden rounded-md border bg-white text-center text-slate-800",
|
"inline-flex h-12 w-12 flex-col overflow-hidden rounded-md border bg-gray-50 text-center text-slate-800",
|
||||||
props.className,
|
props.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="h-4 border-b border-slate-200 bg-slate-50 text-xs leading-4">
|
<div className="text-muted-foreground border-b border-gray-200 text-xs font-normal leading-4">
|
||||||
{props.dow}
|
{props.dow}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-10 items-center justify-center">
|
<div className="flex grow items-center justify-center bg-white text-lg font-semibold leading-none tracking-tight">
|
||||||
<div>
|
|
||||||
<div className="my-px text-lg font-bold leading-none">
|
|
||||||
{props.day}
|
{props.day}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs font-bold uppercase tracking-wider">
|
|
||||||
{props.month}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { Trans } from "next-i18next";
|
||||||
const FeedbackButton = () => {
|
const FeedbackButton = () => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu modal={false}>
|
<DropdownMenu modal={false}>
|
||||||
<DropdownMenuTrigger className="shadow-huge fixed bottom-8 right-6 hidden h-12 w-12 items-center justify-center rounded-full bg-gray-800 hover:bg-gray-700 active:shadow-none sm:inline-flex">
|
<DropdownMenuTrigger className="shadow-huge fixed bottom-8 right-6 z-20 hidden h-12 w-12 items-center justify-center rounded-full bg-gray-800 hover:bg-gray-700 active:shadow-none sm:inline-flex">
|
||||||
<MegaphoneIcon className="h-5 text-white" />
|
<MegaphoneIcon className="h-5 text-white" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent sideOffset={10} align="end">
|
<DropdownMenuContent sideOffset={10} align="end">
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { keyBy } from "lodash";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { usePermissions } from "@/contexts/permissions";
|
|
||||||
import {
|
import {
|
||||||
decodeOptions,
|
decodeOptions,
|
||||||
ParsedDateOption,
|
ParsedDateOption,
|
||||||
|
@ -16,10 +15,8 @@ import { GetPollApiResponse } from "@/utils/trpc/types";
|
||||||
import ErrorPage from "./error-page";
|
import ErrorPage from "./error-page";
|
||||||
import { useParticipants } from "./participants-provider";
|
import { useParticipants } from "./participants-provider";
|
||||||
import { useRequiredContext } from "./use-required-context";
|
import { useRequiredContext } from "./use-required-context";
|
||||||
import { useUser } from "./user-provider";
|
|
||||||
|
|
||||||
type PollContextValue = {
|
type PollContextValue = {
|
||||||
userAlreadyVoted: boolean;
|
|
||||||
poll: GetPollApiResponse;
|
poll: GetPollApiResponse;
|
||||||
urlId: string;
|
urlId: string;
|
||||||
admin: boolean;
|
admin: boolean;
|
||||||
|
@ -51,9 +48,6 @@ export const PollContextProvider: React.FunctionComponent<{
|
||||||
}> = ({ poll, urlId, admin, children }) => {
|
}> = ({ poll, urlId, admin, children }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { participants } = useParticipants();
|
const { participants } = useParticipants();
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const { canEditParticipant } = usePermissions();
|
|
||||||
|
|
||||||
const getScore = React.useCallback(
|
const getScore = React.useCallback(
|
||||||
(optionId: string) => {
|
(optionId: string) => {
|
||||||
|
@ -95,11 +89,6 @@ export const PollContextProvider: React.FunctionComponent<{
|
||||||
return participant;
|
return participant;
|
||||||
};
|
};
|
||||||
|
|
||||||
const userAlreadyVoted =
|
|
||||||
user && participants
|
|
||||||
? participants.some((participant) => canEditParticipant(participant.id))
|
|
||||||
: false;
|
|
||||||
|
|
||||||
const optionIds = poll.options.map(({ id }) => id);
|
const optionIds = poll.options.map(({ id }) => id);
|
||||||
|
|
||||||
const participantById = keyBy(
|
const participantById = keyBy(
|
||||||
|
@ -119,7 +108,6 @@ export const PollContextProvider: React.FunctionComponent<{
|
||||||
|
|
||||||
return {
|
return {
|
||||||
optionIds,
|
optionIds,
|
||||||
userAlreadyVoted,
|
|
||||||
poll,
|
poll,
|
||||||
urlId,
|
urlId,
|
||||||
admin,
|
admin,
|
||||||
|
@ -137,7 +125,7 @@ export const PollContextProvider: React.FunctionComponent<{
|
||||||
},
|
},
|
||||||
getScore,
|
getScore,
|
||||||
};
|
};
|
||||||
}, [admin, canEditParticipant, getScore, participants, poll, urlId, user]);
|
}, [admin, getScore, participants, poll, urlId]);
|
||||||
|
|
||||||
if (poll.deleted) {
|
if (poll.deleted) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,17 +4,16 @@ import {
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
Users2Icon,
|
Users2Icon,
|
||||||
} from "@rallly/icons";
|
} from "@rallly/icons";
|
||||||
|
import { cn } from "@rallly/ui";
|
||||||
import { Button } from "@rallly/ui/button";
|
import { Button } from "@rallly/ui/button";
|
||||||
import clsx from "clsx";
|
|
||||||
import { Trans, useTranslation } from "next-i18next";
|
import { Trans, useTranslation } from "next-i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useMeasure, useUpdateEffect } from "react-use";
|
import { useScroll } from "react-use";
|
||||||
|
|
||||||
import { TimesShownIn } from "@/components/clock";
|
import { TimesShownIn } from "@/components/clock";
|
||||||
|
import { useVotingForm, VotingForm } from "@/components/poll/voting-form";
|
||||||
import { usePermissions } from "@/contexts/permissions";
|
import { usePermissions } from "@/contexts/permissions";
|
||||||
import { useRole } from "@/contexts/role";
|
|
||||||
|
|
||||||
import { useNewParticipantModal } from "../new-participant-modal";
|
|
||||||
import {
|
import {
|
||||||
useParticipants,
|
useParticipants,
|
||||||
useVisibleParticipants,
|
useVisibleParticipants,
|
||||||
|
@ -22,126 +21,84 @@ import {
|
||||||
import { usePoll } from "../poll-context";
|
import { usePoll } from "../poll-context";
|
||||||
import ParticipantRow from "./desktop-poll/participant-row";
|
import ParticipantRow from "./desktop-poll/participant-row";
|
||||||
import ParticipantRowForm from "./desktop-poll/participant-row-form";
|
import ParticipantRowForm from "./desktop-poll/participant-row-form";
|
||||||
import { PollContext } from "./desktop-poll/poll-context";
|
|
||||||
import PollHeader from "./desktop-poll/poll-header";
|
import PollHeader from "./desktop-poll/poll-header";
|
||||||
import {
|
|
||||||
useAddParticipantMutation,
|
|
||||||
useUpdateParticipantMutation,
|
|
||||||
} from "./mutations";
|
|
||||||
|
|
||||||
const minSidebarWidth = 200;
|
const useIsOverflowing = <E extends Element | null>(
|
||||||
|
ref: React.RefObject<E>,
|
||||||
|
) => {
|
||||||
|
const [isOverflowing, setIsOverflowing] = React.useState(false);
|
||||||
|
|
||||||
const Poll: React.FunctionComponent = () => {
|
React.useEffect(() => {
|
||||||
|
const checkOverflow = () => {
|
||||||
|
if (ref.current) {
|
||||||
|
const element = ref.current;
|
||||||
|
const overflowX = element.scrollWidth > element.clientWidth;
|
||||||
|
const overflowY = element.scrollHeight > element.clientHeight;
|
||||||
|
|
||||||
|
setIsOverflowing(overflowX || overflowY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ref.current) {
|
||||||
|
const resizeObserver = new ResizeObserver(checkOverflow);
|
||||||
|
resizeObserver.observe(ref.current);
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
checkOverflow();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return isOverflowing;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DesktopPoll: React.FunctionComponent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { poll, userAlreadyVoted } = usePoll();
|
const { poll } = usePoll();
|
||||||
|
|
||||||
const { participants } = useParticipants();
|
const { participants } = useParticipants();
|
||||||
|
|
||||||
const [ref, { width }] = useMeasure<HTMLDivElement>();
|
const votingForm = useVotingForm();
|
||||||
|
|
||||||
const [editingParticipantId, setEditingParticipantId] = React.useState<
|
const mode = votingForm.watch("mode");
|
||||||
string | null
|
|
||||||
>(null);
|
|
||||||
|
|
||||||
const columnWidth = 80;
|
|
||||||
|
|
||||||
const numberOfVisibleColumns = Math.min(
|
|
||||||
poll.options.length,
|
|
||||||
Math.floor((width - minSidebarWidth) / columnWidth),
|
|
||||||
);
|
|
||||||
|
|
||||||
const sidebarWidth = Math.min(
|
|
||||||
width - numberOfVisibleColumns * columnWidth,
|
|
||||||
275,
|
|
||||||
);
|
|
||||||
|
|
||||||
const availableSpace = Math.min(
|
|
||||||
numberOfVisibleColumns * columnWidth,
|
|
||||||
poll.options.length * columnWidth,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [activeOptionId, setActiveOptionId] = React.useState<string | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [scrollPosition, setScrollPosition] = React.useState(0);
|
|
||||||
|
|
||||||
const maxScrollPosition =
|
|
||||||
columnWidth * poll.options.length - columnWidth * numberOfVisibleColumns;
|
|
||||||
|
|
||||||
const { canAddNewParticipant } = usePermissions();
|
const { canAddNewParticipant } = usePermissions();
|
||||||
|
|
||||||
const role = useRole();
|
|
||||||
const [shouldShowNewParticipantForm, setShouldShowNewParticipantForm] =
|
|
||||||
React.useState(
|
|
||||||
canAddNewParticipant && !userAlreadyVoted && role === "participant",
|
|
||||||
);
|
|
||||||
|
|
||||||
const pollWidth = sidebarWidth + poll.options.length * columnWidth;
|
|
||||||
const addParticipant = useAddParticipantMutation();
|
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
|
||||||
if (!canAddNewParticipant) {
|
|
||||||
setShouldShowNewParticipantForm(false);
|
|
||||||
}
|
|
||||||
}, [canAddNewParticipant]);
|
|
||||||
|
|
||||||
const goToNextPage = () => {
|
const goToNextPage = () => {
|
||||||
setScrollPosition(
|
if (scrollRef.current) {
|
||||||
Math.min(
|
scrollRef.current.scrollLeft += 220;
|
||||||
maxScrollPosition,
|
}
|
||||||
scrollPosition + numberOfVisibleColumns * columnWidth,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToPreviousPage = () => {
|
const goToPreviousPage = () => {
|
||||||
setScrollPosition(
|
if (scrollRef.current) {
|
||||||
Math.max(0, scrollPosition - numberOfVisibleColumns * columnWidth),
|
scrollRef.current.scrollLeft -= 220;
|
||||||
);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateParticipant = useUpdateParticipantMutation();
|
|
||||||
const showNewParticipantModal = useNewParticipantModal();
|
|
||||||
|
|
||||||
const visibleParticipants = useVisibleParticipants();
|
const visibleParticipants = useVisibleParticipants();
|
||||||
|
|
||||||
|
const scrollRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const isOverflowing = useIsOverflowing(scrollRef);
|
||||||
|
|
||||||
|
const { x } = useScroll(scrollRef);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PollContext.Provider
|
<div className="flex flex-col">
|
||||||
value={{
|
|
||||||
activeOptionId,
|
|
||||||
setActiveOptionId,
|
|
||||||
scrollPosition,
|
|
||||||
setScrollPosition,
|
|
||||||
columnWidth,
|
|
||||||
sidebarWidth,
|
|
||||||
goToNextPage,
|
|
||||||
goToPreviousPage,
|
|
||||||
numberOfColumns: numberOfVisibleColumns,
|
|
||||||
availableSpace,
|
|
||||||
maxScrollPosition,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
"relative min-w-full max-w-full duration-300",
|
|
||||||
width === 0 ? "invisible" : "visible",
|
|
||||||
)} // Don't add styles like border, margin, padding – that can mess up the sizing calculations
|
|
||||||
style={{ width: pollWidth }}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col overflow-hidden">
|
|
||||||
<div className="flex h-14 shrink-0 items-center justify-between rounded-t-md border-b bg-gradient-to-b from-gray-50 to-gray-100/50 py-3 px-4">
|
<div className="flex h-14 shrink-0 items-center justify-between rounded-t-md border-b bg-gradient-to-b from-gray-50 to-gray-100/50 py-3 px-4">
|
||||||
<div>
|
<div>
|
||||||
{shouldShowNewParticipantForm || editingParticipantId ? (
|
{mode !== "view" ? (
|
||||||
<div className="px-1">
|
<div>
|
||||||
<Trans
|
<Trans
|
||||||
t={t}
|
t={t}
|
||||||
i18nKey="saveInstruction"
|
i18nKey="saveInstruction"
|
||||||
values={{
|
values={{
|
||||||
action: shouldShowNewParticipantForm
|
action: mode === "new" ? t("continue") : t("save"),
|
||||||
? t("continue")
|
|
||||||
: t("save"),
|
|
||||||
}}
|
}}
|
||||||
components={{ b: <strong /> }}
|
components={{ b: <strong /> }}
|
||||||
/>
|
/>
|
||||||
|
@ -160,8 +117,7 @@ const Poll: React.FunctionComponent = () => {
|
||||||
data-testid="add-participant-button"
|
data-testid="add-participant-button"
|
||||||
icon={PlusIcon}
|
icon={PlusIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditingParticipantId(null);
|
votingForm.newParticipant();
|
||||||
setShouldShowNewParticipantForm(true);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -172,16 +128,17 @@ const Poll: React.FunctionComponent = () => {
|
||||||
<div className="font-semibold">
|
<div className="font-semibold">
|
||||||
{t("optionCount", { count: poll.options.length })}
|
{t("optionCount", { count: poll.options.length })}
|
||||||
</div>
|
</div>
|
||||||
{maxScrollPosition > 0 ? (
|
{isOverflowing ? (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button disabled={x === 0} onClick={goToPreviousPage}>
|
||||||
onClick={goToPreviousPage}
|
|
||||||
disabled={scrollPosition === 0}
|
|
||||||
>
|
|
||||||
<ArrowLeftIcon className="h-4 w-4" />
|
<ArrowLeftIcon className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={scrollPosition === maxScrollPosition}
|
disabled={Boolean(
|
||||||
|
scrollRef.current &&
|
||||||
|
x + scrollRef.current.offsetWidth >=
|
||||||
|
scrollRef.current.scrollWidth,
|
||||||
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
goToNextPage();
|
goToNextPage();
|
||||||
}}
|
}}
|
||||||
|
@ -197,94 +154,95 @@ const Poll: React.FunctionComponent = () => {
|
||||||
<TimesShownIn />
|
<TimesShownIn />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div>
|
<div className="relative">
|
||||||
<div className="flex py-3">
|
|
||||||
<div
|
<div
|
||||||
className="flex shrink-0 items-end pl-4 pr-2 font-medium"
|
aria-hidden="true"
|
||||||
style={{ width: sidebarWidth }}
|
className={cn(
|
||||||
>
|
"pointer-events-none absolute left-[240px] top-0 bottom-2 z-30 w-4 border-l bg-gradient-to-r from-gray-200/50 via-transparent to-transparent transition-opacity",
|
||||||
<div className="font-semibold text-gray-800"></div>
|
x > 0 ? "opacity-100" : "opacity-0",
|
||||||
</div>
|
)}
|
||||||
<PollHeader />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
{shouldShowNewParticipantForm && !editingParticipantId ? (
|
|
||||||
<ParticipantRowForm
|
|
||||||
className="mb-2 shrink-0"
|
|
||||||
onSubmit={async ({ votes }) => {
|
|
||||||
showNewParticipantModal({
|
|
||||||
votes,
|
|
||||||
onSubmit: () => {
|
|
||||||
setShouldShowNewParticipantForm(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
ref={scrollRef}
|
||||||
|
className="scrollbar-thin hover:scrollbar-thumb-gray-400 scrollbar-thumb-gray-300 scrollbar-track-gray-100 relative z-10 flex-grow overflow-auto scroll-smooth pr-3 pb-3"
|
||||||
|
>
|
||||||
|
<table className="w-full table-auto border-separate border-spacing-0 ">
|
||||||
|
<thead>
|
||||||
|
<PollHeader />
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{mode === "new" ? (
|
||||||
|
<>
|
||||||
|
<ParticipantRowForm />
|
||||||
|
<tr aria-hidden="true">
|
||||||
|
<td colSpan={poll.options.length + 1} className="py-2" />
|
||||||
|
</tr>
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{visibleParticipants.length > 0 ? (
|
{visibleParticipants.length > 0
|
||||||
<div className="py-2">
|
? visibleParticipants.map((participant, i) => {
|
||||||
{visibleParticipants.map((participant, i) => {
|
|
||||||
return (
|
return (
|
||||||
<ParticipantRow
|
<ParticipantRow
|
||||||
key={i}
|
key={i}
|
||||||
participant={participant}
|
participant={participant}
|
||||||
editMode={editingParticipantId === participant.id}
|
editMode={
|
||||||
|
votingForm.watch("participantId") === participant.id
|
||||||
|
}
|
||||||
onChangeEditMode={(isEditing) => {
|
onChangeEditMode={(isEditing) => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
setShouldShowNewParticipantForm(false);
|
votingForm.setEditingParticipantId(participant.id);
|
||||||
setEditingParticipantId(participant.id);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onSubmit={async ({ votes }) => {
|
|
||||||
await updateParticipant.mutateAsync({
|
|
||||||
participantId: participant.id,
|
|
||||||
pollId: poll.id,
|
|
||||||
votes,
|
|
||||||
});
|
|
||||||
setEditingParticipantId(null);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})
|
||||||
</div>
|
: null}
|
||||||
) : null}
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{mode !== "view" ? (
|
||||||
{shouldShowNewParticipantForm || editingParticipantId ? (
|
|
||||||
<div className="flex shrink-0 items-center border-t bg-gray-50">
|
<div className="flex shrink-0 items-center border-t bg-gray-50">
|
||||||
<div className="flex w-full items-center justify-between gap-3 p-3">
|
<div className="flex w-full items-center justify-between gap-3 p-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (editingParticipantId) {
|
votingForm.cancel();
|
||||||
setEditingParticipantId(null);
|
|
||||||
} else {
|
|
||||||
setShouldShowNewParticipantForm(false);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("cancel")}
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
|
{mode === "new" ? (
|
||||||
<Button
|
<Button
|
||||||
key="submit"
|
form="voting-form"
|
||||||
form="participant-row-form"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
loading={
|
loading={votingForm.formState.isSubmitting}
|
||||||
addParticipant.isLoading || updateParticipant.isLoading
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{shouldShowNewParticipantForm ? t("continue") : t("save")}
|
{t("continue")}
|
||||||
</Button>
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
form="voting-form"
|
||||||
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
loading={votingForm.formState.isSubmitting}
|
||||||
|
>
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</PollContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(Poll);
|
const WrappedDesktopPoll = () => {
|
||||||
|
return (
|
||||||
|
<VotingForm>
|
||||||
|
<DesktopPoll />
|
||||||
|
</VotingForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WrappedDesktopPoll;
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import clsx from "clsx";
|
|
||||||
import { AnimatePresence, m } from "framer-motion";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { usePollContext } from "./poll-context";
|
|
||||||
|
|
||||||
const ControlledScrollArea: React.FunctionComponent<{
|
|
||||||
children?: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
}> = ({ className, children }) => {
|
|
||||||
const { availableSpace, scrollPosition } = usePollContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={clsx("min-w-0 overflow-hidden", className)}
|
|
||||||
style={{ width: availableSpace, maxWidth: availableSpace }}
|
|
||||||
>
|
|
||||||
<AnimatePresence initial={false}>
|
|
||||||
<m.div
|
|
||||||
className="flex h-full"
|
|
||||||
transition={{
|
|
||||||
type: "spring",
|
|
||||||
mass: 0.4,
|
|
||||||
}}
|
|
||||||
initial={{ x: 0 }}
|
|
||||||
animate={{ x: scrollPosition * -1 }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</m.div>
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ControlledScrollArea;
|
|
|
@ -1,45 +1,31 @@
|
||||||
import clsx from "clsx";
|
import { cn } from "@rallly/ui";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
|
import { useVotingForm } from "@/components/poll/voting-form";
|
||||||
|
|
||||||
import { usePoll } from "../../poll-context";
|
import { usePoll } from "../../poll-context";
|
||||||
import { normalizeVotes } from "../mutations";
|
|
||||||
import { ParticipantForm, ParticipantFormSubmitted } from "../types";
|
|
||||||
import UserAvatar, { YouAvatar } from "../user-avatar";
|
import UserAvatar, { YouAvatar } from "../user-avatar";
|
||||||
import { VoteSelector } from "../vote-selector";
|
import { VoteSelector } from "../vote-selector";
|
||||||
import ControlledScrollArea from "./controlled-scroll-area";
|
|
||||||
import { usePollContext } from "./poll-context";
|
|
||||||
|
|
||||||
export interface ParticipantRowFormProps {
|
export interface ParticipantRowFormProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
defaultValues?: Partial<ParticipantForm>;
|
|
||||||
onSubmit: (data: ParticipantFormSubmitted) => Promise<void>;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
isYou?: boolean;
|
isYou?: boolean;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ParticipantRowForm: React.ForwardRefRenderFunction<
|
const ParticipantRowForm = ({
|
||||||
HTMLFormElement,
|
name,
|
||||||
ParticipantRowFormProps
|
isYou,
|
||||||
> = ({ defaultValues, onSubmit, name, isYou, className, onCancel }, ref) => {
|
className,
|
||||||
|
onCancel,
|
||||||
|
}: ParticipantRowFormProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
|
||||||
columnWidth,
|
|
||||||
scrollPosition,
|
|
||||||
sidebarWidth,
|
|
||||||
numberOfColumns,
|
|
||||||
goToNextPage,
|
|
||||||
} = usePollContext();
|
|
||||||
|
|
||||||
const { optionIds } = usePoll();
|
const { optionIds } = usePoll();
|
||||||
const { handleSubmit, control } = useForm({
|
const form = useVotingForm();
|
||||||
defaultValues: {
|
|
||||||
votes: [],
|
|
||||||
...defaultValues,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.addEventListener("keydown", (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
|
@ -49,79 +35,38 @@ const ParticipantRowForm: React.ForwardRefRenderFunction<
|
||||||
});
|
});
|
||||||
}, [onCancel]);
|
}, [onCancel]);
|
||||||
|
|
||||||
const isColumnVisible = (index: number) => {
|
|
||||||
return scrollPosition + numberOfColumns * columnWidth > columnWidth * index;
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkboxRefs = React.useRef<Array<HTMLButtonElement | null>>([]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<tr className={cn(className)}>
|
||||||
id="participant-row-form"
|
<td className="sticky left-0 z-10 bg-white pl-4 pr-4">
|
||||||
ref={ref}
|
<div className="flex items-center">
|
||||||
onSubmit={handleSubmit(async ({ votes }) => {
|
|
||||||
await onSubmit({
|
|
||||||
votes: normalizeVotes(optionIds, votes),
|
|
||||||
});
|
|
||||||
})}
|
|
||||||
className={clsx("flex h-12 shrink-0", className)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center px-5" style={{ width: sidebarWidth }}>
|
|
||||||
{name ? (
|
{name ? (
|
||||||
<UserAvatar name={name ?? t("you")} isYou={isYou} showName={true} />
|
<UserAvatar name={name ?? t("you")} isYou={isYou} showName={true} />
|
||||||
) : (
|
) : (
|
||||||
<YouAvatar />
|
<YouAvatar />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</td>
|
||||||
|
{optionIds.map((optionId, i) => {
|
||||||
|
return (
|
||||||
|
<td key={optionId} className="h-12 bg-white p-1">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={form.control}
|
||||||
name="votes"
|
name={`votes.${i}`}
|
||||||
render={({ field }) => {
|
render={({ field }) => (
|
||||||
return (
|
|
||||||
<ControlledScrollArea>
|
|
||||||
{optionIds.map((optionId, index) => {
|
|
||||||
const value = field.value[index];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={optionId}
|
|
||||||
className="flex shrink-0 items-center justify-center p-1"
|
|
||||||
style={{ width: columnWidth }}
|
|
||||||
>
|
|
||||||
<VoteSelector
|
<VoteSelector
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
value={value?.type}
|
value={field.value.type}
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (
|
|
||||||
e.code === "Tab" &&
|
|
||||||
index < optionIds.length - 1 &&
|
|
||||||
!isColumnVisible(index + 1)
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
|
||||||
goToNextPage();
|
|
||||||
setTimeout(() => {
|
|
||||||
checkboxRefs.current[index + 1]?.focus();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onChange={(vote) => {
|
onChange={(vote) => {
|
||||||
const newValue = [...field.value];
|
field.onChange({ optionId, type: vote });
|
||||||
newValue[index] = { optionId, type: vote };
|
|
||||||
field.onChange(newValue);
|
|
||||||
}}
|
|
||||||
ref={(el) => {
|
|
||||||
checkboxRefs.current[index] = el;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ControlledScrollArea>
|
</tr>
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.forwardRef(ParticipantRowForm);
|
export default ParticipantRowForm;
|
||||||
|
|
|
@ -9,61 +9,43 @@ import { usePoll } from "@/components/poll-context";
|
||||||
import { useUser } from "@/components/user-provider";
|
import { useUser } from "@/components/user-provider";
|
||||||
import { usePermissions } from "@/contexts/permissions";
|
import { usePermissions } from "@/contexts/permissions";
|
||||||
|
|
||||||
import { ParticipantFormSubmitted } from "../types";
|
|
||||||
import UserAvatar from "../user-avatar";
|
import UserAvatar from "../user-avatar";
|
||||||
import VoteIcon from "../vote-icon";
|
import VoteIcon from "../vote-icon";
|
||||||
import ControlledScrollArea from "./controlled-scroll-area";
|
|
||||||
import ParticipantRowForm from "./participant-row-form";
|
import ParticipantRowForm from "./participant-row-form";
|
||||||
import { usePollContext } from "./poll-context";
|
|
||||||
|
|
||||||
export interface ParticipantRowProps {
|
export interface ParticipantRowProps {
|
||||||
participant: Participant & { votes: Vote[] };
|
participant: Participant & { votes: Vote[] };
|
||||||
className?: string;
|
className?: string;
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
onChangeEditMode?: (editMode: boolean) => void;
|
onChangeEditMode?: (editMode: boolean) => void;
|
||||||
onSubmit?: (data: ParticipantFormSubmitted) => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ParticipantRowView: React.FunctionComponent<{
|
export const ParticipantRowView: React.FunctionComponent<{
|
||||||
name: string;
|
name: string;
|
||||||
action?: React.ReactNode;
|
action?: React.ReactNode;
|
||||||
votes: Array<VoteType | undefined>;
|
votes: Array<VoteType | undefined>;
|
||||||
columnWidth: number;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
sidebarWidth: number;
|
|
||||||
isYou?: boolean;
|
isYou?: boolean;
|
||||||
participantId: string;
|
participantId: string;
|
||||||
}> = ({
|
}> = ({ name, action, votes, className, isYou, participantId }) => {
|
||||||
name,
|
|
||||||
action,
|
|
||||||
votes,
|
|
||||||
className,
|
|
||||||
sidebarWidth,
|
|
||||||
columnWidth,
|
|
||||||
isYou,
|
|
||||||
participantId,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<tr
|
||||||
data-testid="participant-row"
|
data-testid="participant-row"
|
||||||
data-participantid={participantId}
|
data-participantid={participantId}
|
||||||
className={clsx("flex h-12 items-center", className)}
|
className={clsx(className)}
|
||||||
>
|
>
|
||||||
<div
|
<td
|
||||||
className="flex h-full shrink-0 items-center justify-between gap-2 px-3 sm:pl-5"
|
style={{ minWidth: 240, maxWidth: 240 }}
|
||||||
style={{ width: sidebarWidth }}
|
className="sticky left-0 z-10 bg-white px-4"
|
||||||
>
|
>
|
||||||
|
<div className="flex max-w-full items-center justify-between gap-x-4 ">
|
||||||
<UserAvatar name={name} showName={true} isYou={isYou} />
|
<UserAvatar name={name} showName={true} isYou={isYou} />
|
||||||
{action}
|
{action}
|
||||||
</div>
|
</div>
|
||||||
<ControlledScrollArea className="h-full">
|
</td>
|
||||||
{votes.map((vote, i) => {
|
{votes.map((vote, i) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<td key={i} className={clsx("h-12 p-1")}>
|
||||||
key={i}
|
|
||||||
className={clsx("relative flex h-full shrink-0 p-1")}
|
|
||||||
style={{ width: columnWidth }}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex h-full w-full items-center justify-center rounded border bg-gray-50",
|
"flex h-full w-full items-center justify-center rounded border bg-gray-50",
|
||||||
|
@ -71,23 +53,19 @@ export const ParticipantRowView: React.FunctionComponent<{
|
||||||
>
|
>
|
||||||
<VoteIcon type={vote} />
|
<VoteIcon type={vote} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ControlledScrollArea>
|
</tr>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ParticipantRow: React.FunctionComponent<ParticipantRowProps> = ({
|
const ParticipantRow: React.FunctionComponent<ParticipantRowProps> = ({
|
||||||
participant,
|
participant,
|
||||||
editMode,
|
editMode,
|
||||||
onSubmit,
|
|
||||||
className,
|
className,
|
||||||
onChangeEditMode,
|
onChangeEditMode,
|
||||||
}) => {
|
}) => {
|
||||||
const { columnWidth, sidebarWidth } = usePollContext();
|
|
||||||
|
|
||||||
const { user, ownsObject } = useUser();
|
const { user, ownsObject } = useUser();
|
||||||
const { getVote, optionIds } = usePoll();
|
const { getVote, optionIds } = usePoll();
|
||||||
|
|
||||||
|
@ -100,27 +78,14 @@ const ParticipantRow: React.FunctionComponent<ParticipantRowProps> = ({
|
||||||
return (
|
return (
|
||||||
<ParticipantRowForm
|
<ParticipantRowForm
|
||||||
name={participant.name}
|
name={participant.name}
|
||||||
defaultValues={{
|
|
||||||
votes: optionIds.map((optionId) => {
|
|
||||||
const type = getVote(participant.id, optionId);
|
|
||||||
return type ? { optionId, type } : undefined;
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
isYou={isYou}
|
isYou={isYou}
|
||||||
onSubmit={async ({ votes }) => {
|
|
||||||
await onSubmit?.({ votes });
|
|
||||||
onChangeEditMode?.(false);
|
|
||||||
}}
|
|
||||||
onCancel={() => onChangeEditMode?.(false)}
|
onCancel={() => onChangeEditMode?.(false)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<ParticipantRowView
|
<ParticipantRowView
|
||||||
sidebarWidth={sidebarWidth}
|
|
||||||
columnWidth={columnWidth}
|
|
||||||
className={className}
|
className={className}
|
||||||
name={participant.name}
|
name={participant.name}
|
||||||
votes={optionIds.map((optionId) => {
|
votes={optionIds.map((optionId) => {
|
||||||
|
@ -140,7 +105,6 @@ const ParticipantRow: React.FunctionComponent<ParticipantRowProps> = ({
|
||||||
}
|
}
|
||||||
isYou={isYou}
|
isYou={isYou}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import noop from "lodash/noop";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export const PollContext = React.createContext<{
|
|
||||||
activeOptionId: string | null;
|
|
||||||
setActiveOptionId: (optionId: string | null) => void;
|
|
||||||
scrollPosition: number;
|
|
||||||
maxScrollPosition: number;
|
|
||||||
setScrollPosition: (position: number) => void;
|
|
||||||
columnWidth: number;
|
|
||||||
sidebarWidth: number;
|
|
||||||
numberOfColumns: number;
|
|
||||||
availableSpace: number | string;
|
|
||||||
goToNextPage: () => void;
|
|
||||||
goToPreviousPage: () => void;
|
|
||||||
}>({
|
|
||||||
activeOptionId: null,
|
|
||||||
setActiveOptionId: noop,
|
|
||||||
scrollPosition: 0,
|
|
||||||
maxScrollPosition: 100,
|
|
||||||
setScrollPosition: noop,
|
|
||||||
columnWidth: 100,
|
|
||||||
sidebarWidth: 200,
|
|
||||||
numberOfColumns: 0,
|
|
||||||
availableSpace: "auto",
|
|
||||||
goToNextPage: noop,
|
|
||||||
goToPreviousPage: noop,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const usePollContext = () => React.useContext(PollContext);
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { cn } from "@rallly/ui";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
@ -5,8 +6,6 @@ import { DateIconInner } from "@/components/date-icon";
|
||||||
import { useOptions } from "@/components/poll-context";
|
import { useOptions } from "@/components/poll-context";
|
||||||
|
|
||||||
import { ConnectedScoreSummary } from "../score-summary";
|
import { ConnectedScoreSummary } from "../score-summary";
|
||||||
import ControlledScrollArea from "./controlled-scroll-area";
|
|
||||||
import { usePollContext } from "./poll-context";
|
|
||||||
|
|
||||||
const TimeRange: React.FunctionComponent<{
|
const TimeRange: React.FunctionComponent<{
|
||||||
start: string;
|
start: string;
|
||||||
|
@ -16,43 +15,145 @@ const TimeRange: React.FunctionComponent<{
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"relative -mr-2 inline-block pr-2 text-right text-xs font-semibold after:absolute after:right-0 after:top-2 after:h-4 after:w-1 after:border-b after:border-r after:border-t after:border-gray-300 after:content-['']",
|
"relative -mr-2 inline-block whitespace-nowrap pr-2 text-right text-xs font-normal after:absolute after:right-0 after:top-2 after:h-4 after:w-1 after:border-b after:border-r after:border-t after:border-gray-300 after:content-['']",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div>{start}</div>
|
<div className="font-medium tabular-nums">{start}</div>
|
||||||
<div className="text-gray-500">{end}</div>
|
<div className="text-muted-foreground tabular-nums">{end}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TimelineRow = ({
|
||||||
|
children,
|
||||||
|
top,
|
||||||
|
}: React.PropsWithChildren<{ top?: number }>) => {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
style={{ minWidth: 240, top }}
|
||||||
|
className="sticky left-0 z-30 bg-white pl-4 pr-4"
|
||||||
|
></th>
|
||||||
|
{children}
|
||||||
|
<th className="w-full" />
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const monthRowHeight = 48;
|
||||||
|
const dayRowHeight = 64;
|
||||||
|
|
||||||
|
const scoreRowTop = monthRowHeight + dayRowHeight;
|
||||||
|
|
||||||
|
const Trail = ({ end }: { end?: boolean }) => {
|
||||||
|
return end ? (
|
||||||
|
<div aria-hidden="true" className="absolute top-6 left-0 z-10 h-full w-1/2">
|
||||||
|
<div className="h-px bg-gray-200" />
|
||||||
|
<div className="absolute right-0 top-0 h-5 w-px bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn("absolute top-6 left-0 z-10 h-full w-full")}
|
||||||
|
>
|
||||||
|
<div className="h-px bg-gray-200" />
|
||||||
|
<div className={cn("absolute right-1/2 top-0 h-2 w-px bg-gray-200")} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PollHeader: React.FunctionComponent = () => {
|
const PollHeader: React.FunctionComponent = () => {
|
||||||
const { options } = useOptions();
|
const { options } = useOptions();
|
||||||
const { setActiveOptionId, columnWidth } = usePollContext();
|
|
||||||
return (
|
return (
|
||||||
<ControlledScrollArea>
|
<>
|
||||||
{options.map((option) => {
|
<TimelineRow top={0}>
|
||||||
|
{options.map((option, i) => {
|
||||||
|
const firstOfMonth =
|
||||||
|
i === 0 || options[i - 1]?.month !== option.month;
|
||||||
|
const lastOfMonth = options[i + 1]?.month !== option.month;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<th
|
||||||
key={option.optionId}
|
key={option.optionId}
|
||||||
className="flex shrink-0 flex-col items-center gap-y-3"
|
style={{ height: monthRowHeight }}
|
||||||
style={{ width: columnWidth }}
|
className={cn(
|
||||||
onMouseOver={() => setActiveOptionId(option.optionId)}
|
"sticky top-0 space-y-3 bg-white",
|
||||||
onMouseOut={() => setActiveOptionId(null)}
|
firstOfMonth ? "left-[240px] z-20" : "z-10",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
{firstOfMonth ? null : <Trail end={lastOfMonth} />}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-5 px-2 py-0.5 text-sm font-semibold",
|
||||||
|
firstOfMonth ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{option.month}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TimelineRow>
|
||||||
|
<TimelineRow top={monthRowHeight}>
|
||||||
|
{options.map((option, i) => {
|
||||||
|
const firstOfDay =
|
||||||
|
i === 0 ||
|
||||||
|
options[i - 1]?.day !== option.day ||
|
||||||
|
options[i - 1]?.month !== option.month;
|
||||||
|
const lastOfDay =
|
||||||
|
options[i + 1]?.day !== option.day ||
|
||||||
|
options[i + 1]?.month !== option.month;
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={option.optionId}
|
||||||
|
style={{
|
||||||
|
minWidth: 80,
|
||||||
|
width: 80,
|
||||||
|
maxWidth: 90,
|
||||||
|
height: dayRowHeight,
|
||||||
|
// could enable this to make the date column sticky
|
||||||
|
left: firstOfDay ? 240 : 0,
|
||||||
|
top: monthRowHeight,
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"sticky space-y-2 align-top",
|
||||||
|
firstOfDay
|
||||||
|
? "z-20 bg-gradient-to-r from-transparent to-white"
|
||||||
|
: "z-10 bg-white",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{firstOfDay ? null : <Trail end={lastOfDay} />}
|
||||||
<DateIconInner
|
<DateIconInner
|
||||||
|
className={firstOfDay ? "opacity-100" : "opacity-0"}
|
||||||
day={option.day}
|
day={option.day}
|
||||||
dow={option.dow}
|
dow={option.dow}
|
||||||
month={option.month}
|
|
||||||
/>
|
/>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TimelineRow>
|
||||||
|
<TimelineRow top={scoreRowTop}>
|
||||||
|
{options.map((option) => {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
key={option.optionId}
|
||||||
|
style={{ minWidth: 80, maxWidth: 90, top: scoreRowTop }}
|
||||||
|
className="sticky z-20 space-y-2 bg-white py-2"
|
||||||
|
>
|
||||||
{option.type === "timeSlot" ? (
|
{option.type === "timeSlot" ? (
|
||||||
<TimeRange start={option.startTime} end={option.endTime} />
|
<TimeRange start={option.startTime} end={option.endTime} />
|
||||||
) : null}
|
) : null}
|
||||||
|
<div>
|
||||||
<ConnectedScoreSummary optionId={option.optionId} />
|
<ConnectedScoreSummary optionId={option.optionId} />
|
||||||
</div>
|
</div>
|
||||||
|
</th>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ControlledScrollArea>
|
</TimelineRow>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,7 @@ if (typeof window !== "undefined") {
|
||||||
const MobilePoll: React.FunctionComponent = () => {
|
const MobilePoll: React.FunctionComponent = () => {
|
||||||
const pollContext = usePoll();
|
const pollContext = usePoll();
|
||||||
|
|
||||||
const {
|
const { poll, admin, getParticipantById, optionIds, getVote } = pollContext;
|
||||||
poll,
|
|
||||||
admin,
|
|
||||||
getParticipantById,
|
|
||||||
optionIds,
|
|
||||||
getVote,
|
|
||||||
userAlreadyVoted,
|
|
||||||
} = pollContext;
|
|
||||||
|
|
||||||
const { options } = useOptions();
|
const { options } = useOptions();
|
||||||
const { participants } = useParticipants();
|
const { participants } = useParticipants();
|
||||||
|
@ -70,7 +63,7 @@ const MobilePoll: React.FunctionComponent = () => {
|
||||||
const { canEditParticipant, canAddNewParticipant } = usePermissions();
|
const { canEditParticipant, canAddNewParticipant } = usePermissions();
|
||||||
|
|
||||||
const [isEditing, setIsEditing] = React.useState(
|
const [isEditing, setIsEditing] = React.useState(
|
||||||
canAddNewParticipant && !userAlreadyVoted,
|
canAddNewParticipant && !participants.some((p) => canEditParticipant(p.id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
|
|
|
@ -190,7 +190,7 @@ const PollOption: React.FunctionComponent<PollOptionProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx("space-y-4 overflow-hidden p-3", {
|
className={clsx("space-y-4 overflow-hidden px-4 py-3", {
|
||||||
"bg-gray-500/5": editable && active,
|
"bg-gray-500/5": editable && active,
|
||||||
})}
|
})}
|
||||||
onTouchStart={() => setActive(editable)}
|
onTouchStart={() => setActive(editable)}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const ScoreSummary: React.FunctionComponent<PopularityScoreProps> =
|
||||||
<div
|
<div
|
||||||
data-testid="popularity-score"
|
data-testid="popularity-score"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex select-none items-center gap-1 rounded-full border py-0.5 px-2 text-xs tabular-nums",
|
"relative inline-flex select-none items-center gap-1 rounded-full border py-0.5 px-2 text-xs font-normal tabular-nums",
|
||||||
highlight
|
highlight
|
||||||
? "border-green-500 text-green-500"
|
? "border-green-500 text-green-500"
|
||||||
: "border-transparent text-gray-600",
|
: "border-transparent text-gray-600",
|
||||||
|
|
|
@ -4,7 +4,7 @@ export interface ParticipantForm {
|
||||||
votes: Array<
|
votes: Array<
|
||||||
| {
|
| {
|
||||||
optionId: string;
|
optionId: string;
|
||||||
type: VoteType;
|
type?: VoteType;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>;
|
>;
|
||||||
|
|
138
apps/web/src/components/poll/voting-form.tsx
Normal file
138
apps/web/src/components/poll/voting-form.tsx
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { useNewParticipantModal } from "@/components/new-participant-modal";
|
||||||
|
import { useParticipants } from "@/components/participants-provider";
|
||||||
|
import {
|
||||||
|
normalizeVotes,
|
||||||
|
useUpdateParticipantMutation,
|
||||||
|
} from "@/components/poll/mutations";
|
||||||
|
import { usePermissions } from "@/contexts/permissions";
|
||||||
|
import { usePoll } from "@/contexts/poll";
|
||||||
|
import { useRole } from "@/contexts/role";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
mode: z.enum(["new", "edit", "view"]),
|
||||||
|
participantId: z.string().optional(),
|
||||||
|
votes: z.array(
|
||||||
|
z.object({
|
||||||
|
optionId: z.string(),
|
||||||
|
type: z.enum(["yes", "no", "ifNeedBe"]).optional(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
type VotingFormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const useVotingForm = () => {
|
||||||
|
const { options } = usePoll();
|
||||||
|
const { participants } = useParticipants();
|
||||||
|
const form = useFormContext<VotingFormValues>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...form,
|
||||||
|
newParticipant: () => {
|
||||||
|
form.reset({
|
||||||
|
mode: "new",
|
||||||
|
participantId: undefined,
|
||||||
|
votes: options.map((option) => ({
|
||||||
|
optionId: option.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setEditingParticipantId: (newParticipantId: string) => {
|
||||||
|
const participant = participants.find((p) => p.id === newParticipantId);
|
||||||
|
if (participant) {
|
||||||
|
form.reset({
|
||||||
|
mode: "edit",
|
||||||
|
participantId: newParticipantId,
|
||||||
|
votes: options.map((option) => ({
|
||||||
|
optionId: option.id,
|
||||||
|
type: participant.votes.find((vote) => vote.optionId === option.id)
|
||||||
|
?.type,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("Participant not found");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel: () =>
|
||||||
|
form.reset({
|
||||||
|
mode: "view",
|
||||||
|
participantId: undefined,
|
||||||
|
votes: options.map((option) => ({
|
||||||
|
optionId: option.id,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VotingForm = ({ children }: React.PropsWithChildren) => {
|
||||||
|
const { id: pollId, options } = usePoll();
|
||||||
|
const showNewParticipantModal = useNewParticipantModal();
|
||||||
|
const updateParticipant = useUpdateParticipantMutation();
|
||||||
|
const { participants } = useParticipants();
|
||||||
|
|
||||||
|
const { canAddNewParticipant, canEditParticipant } = usePermissions();
|
||||||
|
const userAlreadyVoted = participants.some((participant) =>
|
||||||
|
canEditParticipant(participant.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
const role = useRole();
|
||||||
|
|
||||||
|
const form = useForm<VotingFormValues>({
|
||||||
|
defaultValues: {
|
||||||
|
mode:
|
||||||
|
canAddNewParticipant && !userAlreadyVoted && role === "participant"
|
||||||
|
? "new"
|
||||||
|
: "view",
|
||||||
|
votes: options.map((option) => ({
|
||||||
|
optionId: option.id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<form
|
||||||
|
id="voting-form"
|
||||||
|
onSubmit={form.handleSubmit(async (data) => {
|
||||||
|
const optionIds = options.map((option) => option.id);
|
||||||
|
|
||||||
|
if (data.participantId) {
|
||||||
|
// update participant
|
||||||
|
|
||||||
|
await updateParticipant.mutateAsync({
|
||||||
|
participantId: data.participantId,
|
||||||
|
pollId,
|
||||||
|
votes: normalizeVotes(optionIds, data.votes),
|
||||||
|
});
|
||||||
|
|
||||||
|
form.reset({
|
||||||
|
participantId: undefined,
|
||||||
|
votes: options.map((option) => ({
|
||||||
|
optionId: option.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// new participant
|
||||||
|
showNewParticipantModal({
|
||||||
|
votes: normalizeVotes(optionIds, data.votes),
|
||||||
|
onSubmit: async () => {
|
||||||
|
form.reset({
|
||||||
|
mode: "view",
|
||||||
|
participantId: undefined,
|
||||||
|
votes: options.map((option) => ({
|
||||||
|
optionId: option.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -20,7 +20,7 @@ async function main() {
|
||||||
const polls = await Promise.all(
|
const polls = await Promise.all(
|
||||||
Array.from({ length: 20 }).map(async (_, i) => {
|
Array.from({ length: 20 }).map(async (_, i) => {
|
||||||
// create some polls with no duration (all day) and some with a random duration.
|
// create some polls with no duration (all day) and some with a random duration.
|
||||||
const duration = i % 5 === 0 ? 15 * randInt(8) : 0;
|
const duration = i % 2 === 0 ? 15 * randInt(8) : 0;
|
||||||
const poll = await prisma.poll.create({
|
const poll = await prisma.poll.create({
|
||||||
include: {
|
include: {
|
||||||
participants: true,
|
participants: true,
|
||||||
|
@ -43,7 +43,7 @@ async function main() {
|
||||||
.betweens(
|
.betweens(
|
||||||
Date.now(),
|
Date.now(),
|
||||||
Date.now() + 1000 * 60 * 60 * 24 * 30,
|
Date.now() + 1000 * 60 * 60 * 24 * 30,
|
||||||
randInt(16, 1),
|
randInt(30, 1),
|
||||||
)
|
)
|
||||||
.map((date) => {
|
.map((date) => {
|
||||||
// rounded to nearest 15 minutes
|
// rounded to nearest 15 minutes
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
"main": "tailwind.config.js",
|
"main": "tailwind.config.js",
|
||||||
"types": "tailwind.config.d.ts",
|
"types": "tailwind.config.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tailwindcss": "^3.2.7"
|
"tailwindcss": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tailwind-scrollbar": "^3.0.4"
|
"tailwind-scrollbar": "^3.0.4",
|
||||||
|
"tailwindcss-animate": "^1.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
225
yarn.lock
225
yarn.lock
|
@ -2,6 +2,11 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@alloc/quick-lru@^5.2.0":
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
||||||
|
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
||||||
|
|
||||||
"@ampproject/remapping@^2.2.0":
|
"@ampproject/remapping@^2.2.0":
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz"
|
resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz"
|
||||||
|
@ -4058,15 +4063,6 @@ acorn-jsx@^5.3.1:
|
||||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||||
|
|
||||||
acorn-node@^1.8.2:
|
|
||||||
version "1.8.2"
|
|
||||||
resolved "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz"
|
|
||||||
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
|
|
||||||
dependencies:
|
|
||||||
acorn "^7.0.0"
|
|
||||||
acorn-walk "^7.0.0"
|
|
||||||
xtend "^4.0.2"
|
|
||||||
|
|
||||||
acorn-private-class-elements@^0.2.7:
|
acorn-private-class-elements@^0.2.7:
|
||||||
version "0.2.7"
|
version "0.2.7"
|
||||||
resolved "https://registry.npmjs.org/acorn-private-class-elements/-/acorn-private-class-elements-0.2.7.tgz"
|
resolved "https://registry.npmjs.org/acorn-private-class-elements/-/acorn-private-class-elements-0.2.7.tgz"
|
||||||
|
@ -4095,17 +4091,12 @@ acorn-static-class-features@^0.2.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn-private-class-elements "^0.2.7"
|
acorn-private-class-elements "^0.2.7"
|
||||||
|
|
||||||
acorn-walk@^7.0.0:
|
|
||||||
version "7.2.0"
|
|
||||||
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz"
|
|
||||||
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
|
||||||
|
|
||||||
acorn-walk@^8.0.0, acorn-walk@^8.1.1:
|
acorn-walk@^8.0.0, acorn-walk@^8.1.1:
|
||||||
version "8.2.0"
|
version "8.2.0"
|
||||||
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz"
|
resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz"
|
||||||
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
||||||
|
|
||||||
acorn@^7.0.0, acorn@^7.4.0:
|
acorn@^7.4.0:
|
||||||
version "7.4.1"
|
version "7.4.1"
|
||||||
resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
|
resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
|
||||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||||
|
@ -4175,6 +4166,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert "^2.0.1"
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
|
any-promise@^1.0.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||||
|
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
|
||||||
|
|
||||||
any@^1.0.0:
|
any@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/any/-/any-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/any/-/any-1.0.0.tgz"
|
||||||
|
@ -4752,7 +4748,7 @@ color-name@1.1.3:
|
||||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
||||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||||
|
|
||||||
color-name@^1.1.4, color-name@~1.1.4:
|
color-name@~1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
@ -4777,6 +4773,11 @@ commander@^2.19.0:
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
||||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||||
|
|
||||||
|
commander@^4.0.0:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||||
|
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||||
|
|
||||||
commander@^5.0.0:
|
commander@^5.0.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz"
|
||||||
|
@ -5065,11 +5066,6 @@ define-properties@^1.1.3, define-properties@^1.1.4:
|
||||||
has-property-descriptors "^1.0.0"
|
has-property-descriptors "^1.0.0"
|
||||||
object-keys "^1.1.1"
|
object-keys "^1.1.1"
|
||||||
|
|
||||||
defined@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz"
|
|
||||||
integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==
|
|
||||||
|
|
||||||
deprecation@^2.0.0, deprecation@^2.3.1:
|
deprecation@^2.0.0, deprecation@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz"
|
resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz"
|
||||||
|
@ -5092,15 +5088,6 @@ detect-package-manager@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
execa "^5.1.1"
|
execa "^5.1.1"
|
||||||
|
|
||||||
detective@^5.2.1:
|
|
||||||
version "5.2.1"
|
|
||||||
resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz"
|
|
||||||
integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==
|
|
||||||
dependencies:
|
|
||||||
acorn-node "^1.8.2"
|
|
||||||
defined "^1.0.0"
|
|
||||||
minimist "^1.2.6"
|
|
||||||
|
|
||||||
didyoumean@^1.2.2:
|
didyoumean@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
|
resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
|
||||||
|
@ -6070,6 +6057,18 @@ glob-stream@^6.1.0:
|
||||||
to-absolute-glob "^2.0.0"
|
to-absolute-glob "^2.0.0"
|
||||||
unique-stream "^2.0.2"
|
unique-stream "^2.0.2"
|
||||||
|
|
||||||
|
glob@7.1.6:
|
||||||
|
version "7.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||||
|
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||||
|
dependencies:
|
||||||
|
fs.realpath "^1.0.0"
|
||||||
|
inflight "^1.0.4"
|
||||||
|
inherits "2"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
once "^1.3.0"
|
||||||
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
glob@7.1.7:
|
glob@7.1.7:
|
||||||
version "7.1.7"
|
version "7.1.7"
|
||||||
resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz"
|
resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz"
|
||||||
|
@ -6693,6 +6692,13 @@ is-core-module@^2.10.0, is-core-module@^2.11.0, is-core-module@^2.9.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
|
|
||||||
|
is-core-module@^2.13.0:
|
||||||
|
version "2.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
|
||||||
|
integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==
|
||||||
|
dependencies:
|
||||||
|
has "^1.0.3"
|
||||||
|
|
||||||
is-date-object@^1.0.1, is-date-object@^1.0.5:
|
is-date-object@^1.0.1, is-date-object@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz"
|
||||||
|
@ -6943,6 +6949,11 @@ isobject@^3.0.1:
|
||||||
resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz"
|
resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz"
|
||||||
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
|
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
|
||||||
|
|
||||||
|
jiti@^1.18.2:
|
||||||
|
version "1.19.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1"
|
||||||
|
integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==
|
||||||
|
|
||||||
jju@^1.4.0:
|
jju@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz"
|
||||||
|
@ -7153,11 +7164,16 @@ lie@3.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
immediate "~3.0.5"
|
immediate "~3.0.5"
|
||||||
|
|
||||||
lilconfig@^2.0.5, lilconfig@^2.0.6:
|
lilconfig@^2.0.5:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz"
|
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz"
|
||||||
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
|
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
|
||||||
|
|
||||||
|
lilconfig@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||||
|
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
|
||||||
|
|
||||||
lines-and-columns@^1.1.6:
|
lines-and-columns@^1.1.6:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
|
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
|
||||||
|
@ -7705,6 +7721,15 @@ ms@^2.1.1:
|
||||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
|
mz@^2.7.0:
|
||||||
|
version "2.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
|
||||||
|
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
object-assign "^4.0.1"
|
||||||
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
nano-css@^5.3.1:
|
nano-css@^5.3.1:
|
||||||
version "5.3.5"
|
version "5.3.5"
|
||||||
resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.5.tgz"
|
resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.5.tgz"
|
||||||
|
@ -7724,7 +7749,7 @@ nanoclone@^0.2.1:
|
||||||
resolved "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz"
|
resolved "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz"
|
||||||
integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==
|
integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==
|
||||||
|
|
||||||
nanoid@^3.1.23:
|
nanoid@^3.1.23, nanoid@^3.3.6:
|
||||||
version "3.3.6"
|
version "3.3.6"
|
||||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz"
|
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz"
|
||||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||||
|
@ -7872,7 +7897,7 @@ nth-check@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase "^1.0.0"
|
boolbase "^1.0.0"
|
||||||
|
|
||||||
object-assign@^4.1.1:
|
object-assign@^4.0.1, object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
|
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
|
||||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||||
|
@ -8171,41 +8196,46 @@ pify@^4.0.1:
|
||||||
resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz"
|
resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz"
|
||||||
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
|
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
|
||||||
|
|
||||||
|
pirates@^4.0.1:
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
|
||||||
|
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
|
||||||
|
|
||||||
playwright-core@1.35.1:
|
playwright-core@1.35.1:
|
||||||
version "1.35.1"
|
version "1.35.1"
|
||||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.1.tgz#52c1e6ffaa6a8c29de1a5bdf8cce0ce290ffb81d"
|
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.1.tgz#52c1e6ffaa6a8c29de1a5bdf8cce0ce290ffb81d"
|
||||||
integrity sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==
|
integrity sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==
|
||||||
|
|
||||||
postcss-import@^14.1.0:
|
postcss-import@^15.1.0:
|
||||||
version "14.1.0"
|
version "15.1.0"
|
||||||
resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz"
|
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"
|
||||||
integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==
|
integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss-value-parser "^4.0.0"
|
postcss-value-parser "^4.0.0"
|
||||||
read-cache "^1.0.0"
|
read-cache "^1.0.0"
|
||||||
resolve "^1.1.7"
|
resolve "^1.1.7"
|
||||||
|
|
||||||
postcss-js@^4.0.0:
|
postcss-js@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz"
|
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
|
||||||
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
|
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
|
||||||
dependencies:
|
dependencies:
|
||||||
camelcase-css "^2.0.1"
|
camelcase-css "^2.0.1"
|
||||||
|
|
||||||
postcss-load-config@^3.1.4:
|
postcss-load-config@^4.0.1:
|
||||||
version "3.1.4"
|
version "4.0.1"
|
||||||
resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz"
|
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd"
|
||||||
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
|
integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==
|
||||||
dependencies:
|
dependencies:
|
||||||
lilconfig "^2.0.5"
|
lilconfig "^2.0.5"
|
||||||
yaml "^1.10.2"
|
yaml "^2.1.1"
|
||||||
|
|
||||||
postcss-nested@6.0.0:
|
postcss-nested@^6.0.1:
|
||||||
version "6.0.0"
|
version "6.0.1"
|
||||||
resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz"
|
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c"
|
||||||
integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==
|
integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss-selector-parser "^6.0.10"
|
postcss-selector-parser "^6.0.11"
|
||||||
|
|
||||||
postcss-selector-parser@6.0.10:
|
postcss-selector-parser@6.0.10:
|
||||||
version "6.0.10"
|
version "6.0.10"
|
||||||
|
@ -8215,7 +8245,7 @@ postcss-selector-parser@6.0.10:
|
||||||
cssesc "^3.0.0"
|
cssesc "^3.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11:
|
postcss-selector-parser@^6.0.11:
|
||||||
version "6.0.11"
|
version "6.0.11"
|
||||||
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz"
|
resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz"
|
||||||
integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==
|
integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==
|
||||||
|
@ -8237,7 +8267,7 @@ postcss@8.4.14:
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
postcss@^8.0.9, postcss@^8.4.21:
|
postcss@^8.4.21:
|
||||||
version "8.4.21"
|
version "8.4.21"
|
||||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz"
|
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz"
|
||||||
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
|
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
|
||||||
|
@ -8246,6 +8276,15 @@ postcss@^8.0.9, postcss@^8.4.21:
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
|
postcss@^8.4.23:
|
||||||
|
version "8.4.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
|
||||||
|
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.6"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
posthog-js@^1.57.2:
|
posthog-js@^1.57.2:
|
||||||
version "1.57.2"
|
version "1.57.2"
|
||||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.57.2.tgz#131fb93e2ad099baff4317f3d91a4d6c96a08e7f"
|
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.57.2.tgz#131fb93e2ad099baff4317f3d91a4d6c96a08e7f"
|
||||||
|
@ -8373,11 +8412,6 @@ queue-microtask@^1.2.2:
|
||||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
quick-lru@^5.1.1:
|
|
||||||
version "5.1.1"
|
|
||||||
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
|
|
||||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
|
||||||
|
|
||||||
react-big-calendar@^1.8.1:
|
react-big-calendar@^1.8.1:
|
||||||
version "1.8.1"
|
version "1.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-big-calendar/-/react-big-calendar-1.8.1.tgz#07886a66086fcae16934572c5ace8c4c433dbbed"
|
resolved "https://registry.yarnpkg.com/react-big-calendar/-/react-big-calendar-1.8.1.tgz#07886a66086fcae16934572c5ace8c4c433dbbed"
|
||||||
|
@ -8794,6 +8828,15 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.22.
|
||||||
path-parse "^1.0.7"
|
path-parse "^1.0.7"
|
||||||
supports-preserve-symlinks-flag "^1.0.0"
|
supports-preserve-symlinks-flag "^1.0.0"
|
||||||
|
|
||||||
|
resolve@^1.22.2:
|
||||||
|
version "1.22.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34"
|
||||||
|
integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==
|
||||||
|
dependencies:
|
||||||
|
is-core-module "^2.13.0"
|
||||||
|
path-parse "^1.0.7"
|
||||||
|
supports-preserve-symlinks-flag "^1.0.0"
|
||||||
|
|
||||||
resolve@^2.0.0-next.4:
|
resolve@^2.0.0-next.4:
|
||||||
version "2.0.0-next.4"
|
version "2.0.0-next.4"
|
||||||
resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz"
|
resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz"
|
||||||
|
@ -9288,6 +9331,19 @@ stylis@^4.0.6:
|
||||||
resolved "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz"
|
resolved "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz"
|
||||||
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
|
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
|
||||||
|
|
||||||
|
sucrase@^3.32.0:
|
||||||
|
version "3.34.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f"
|
||||||
|
integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/gen-mapping" "^0.3.2"
|
||||||
|
commander "^4.0.0"
|
||||||
|
glob "7.1.6"
|
||||||
|
lines-and-columns "^1.1.6"
|
||||||
|
mz "^2.7.0"
|
||||||
|
pirates "^4.0.1"
|
||||||
|
ts-interface-checker "^0.1.9"
|
||||||
|
|
||||||
superjson@^1.12.2:
|
superjson@^1.12.2:
|
||||||
version "1.12.2"
|
version "1.12.2"
|
||||||
resolved "https://registry.npmjs.org/superjson/-/superjson-1.12.2.tgz"
|
resolved "https://registry.npmjs.org/superjson/-/superjson-1.12.2.tgz"
|
||||||
|
@ -9371,34 +9427,33 @@ tailwindcss-animate@^1.0.5:
|
||||||
resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.5.tgz"
|
||||||
integrity sha512-UU3qrOJ4lFQABY+MVADmBm+0KW3xZyhMdRvejwtXqYOL7YjHYxmuREFAZdmVG5LPe5E9CAst846SLC4j5I3dcw==
|
integrity sha512-UU3qrOJ4lFQABY+MVADmBm+0KW3xZyhMdRvejwtXqYOL7YjHYxmuREFAZdmVG5LPe5E9CAst846SLC4j5I3dcw==
|
||||||
|
|
||||||
tailwindcss@^3.2.4, tailwindcss@^3.2.7:
|
tailwindcss@^3.3.3:
|
||||||
version "3.2.7"
|
version "3.3.3"
|
||||||
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz"
|
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
|
||||||
integrity sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==
|
integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@alloc/quick-lru" "^5.2.0"
|
||||||
arg "^5.0.2"
|
arg "^5.0.2"
|
||||||
chokidar "^3.5.3"
|
chokidar "^3.5.3"
|
||||||
color-name "^1.1.4"
|
|
||||||
detective "^5.2.1"
|
|
||||||
didyoumean "^1.2.2"
|
didyoumean "^1.2.2"
|
||||||
dlv "^1.1.3"
|
dlv "^1.1.3"
|
||||||
fast-glob "^3.2.12"
|
fast-glob "^3.2.12"
|
||||||
glob-parent "^6.0.2"
|
glob-parent "^6.0.2"
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
lilconfig "^2.0.6"
|
jiti "^1.18.2"
|
||||||
|
lilconfig "^2.1.0"
|
||||||
micromatch "^4.0.5"
|
micromatch "^4.0.5"
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
object-hash "^3.0.0"
|
object-hash "^3.0.0"
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
postcss "^8.0.9"
|
postcss "^8.4.23"
|
||||||
postcss-import "^14.1.0"
|
postcss-import "^15.1.0"
|
||||||
postcss-js "^4.0.0"
|
postcss-js "^4.0.1"
|
||||||
postcss-load-config "^3.1.4"
|
postcss-load-config "^4.0.1"
|
||||||
postcss-nested "6.0.0"
|
postcss-nested "^6.0.1"
|
||||||
postcss-selector-parser "^6.0.11"
|
postcss-selector-parser "^6.0.11"
|
||||||
postcss-value-parser "^4.2.0"
|
resolve "^1.22.2"
|
||||||
quick-lru "^5.1.1"
|
sucrase "^3.32.0"
|
||||||
resolve "^1.22.1"
|
|
||||||
|
|
||||||
tapable@^2.2.0:
|
tapable@^2.2.0:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
|
@ -9410,6 +9465,20 @@ text-table@^0.2.0:
|
||||||
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
||||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||||
|
|
||||||
|
thenify-all@^1.0.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||||
|
integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
|
||||||
|
dependencies:
|
||||||
|
thenify ">= 3.1.0 < 4"
|
||||||
|
|
||||||
|
"thenify@>= 3.1.0 < 4":
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
|
||||||
|
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
|
||||||
|
dependencies:
|
||||||
|
any-promise "^1.0.0"
|
||||||
|
|
||||||
throttle-debounce@^3.0.1:
|
throttle-debounce@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz"
|
resolved "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz"
|
||||||
|
@ -9532,6 +9601,11 @@ ts-easing@^0.2.0:
|
||||||
resolved "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz"
|
resolved "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz"
|
||||||
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
|
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
|
||||||
|
|
||||||
|
ts-interface-checker@^0.1.9:
|
||||||
|
version "0.1.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
|
||||||
|
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||||
|
|
||||||
ts-node@^10.9.1:
|
ts-node@^10.9.1:
|
||||||
version "10.9.1"
|
version "10.9.1"
|
||||||
resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz"
|
resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz"
|
||||||
|
@ -10137,7 +10211,7 @@ ws@^7.3.1:
|
||||||
resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz"
|
resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz"
|
||||||
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
||||||
|
|
||||||
xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
|
xtend@~4.0.0, xtend@~4.0.1:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
|
resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
|
||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
@ -10157,11 +10231,16 @@ yallist@^4.0.0:
|
||||||
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
|
||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
yaml@^1.10.0, yaml@^1.10.2:
|
yaml@^1.10.0:
|
||||||
version "1.10.2"
|
version "1.10.2"
|
||||||
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
|
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
|
||||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||||
|
|
||||||
|
yaml@^2.1.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
|
||||||
|
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
||||||
|
|
||||||
yn@3.1.1:
|
yn@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue