rallly/components/poll/manage-poll.tsx
2022-04-16 10:26:53 +01:00

249 lines
7.5 KiB
TypeScript

import { Placement } from "@popperjs/core";
import { format } from "date-fns";
import { Trans, useTranslation } from "next-i18next";
import * as React from "react";
import { decodeDateOption, encodeDateOption } from "utils/date-time-utils";
import Button from "@/components/button";
import Cog from "@/components/icons/cog.svg";
import LockClosed from "@/components/icons/lock-closed.svg";
import LockOpen from "@/components/icons/lock-open.svg";
import Pencil from "@/components/icons/pencil-alt.svg";
import Save from "@/components/icons/save.svg";
import Table from "@/components/icons/table.svg";
import Dropdown, { DropdownItem } from "../dropdown";
import { PollDetailsForm } from "../forms";
import { useModal } from "../modal";
import { useModalContext } from "../modal/modal-provider";
import { usePoll } from "../use-poll";
import { useUpdatePollMutation } from "./mutations";
const PollOptionsForm = React.lazy(() => import("../forms/poll-options-form"));
const ManagePoll: React.VoidFunctionComponent<{
targetTimeZone: string;
placement?: Placement;
}> = ({ targetTimeZone, placement }) => {
const { t } = useTranslation("app");
const poll = usePoll();
const modalContext = useModalContext();
const { mutate: updatePollMutation, isLoading: isUpdating } =
useUpdatePollMutation();
const [
changeOptionsModalContextHolder,
openChangeOptionsModal,
closeChangeOptionsModal,
] = useModal({
overlayClosable: true,
okText: "Save",
okButtonProps: {
form: "pollOptions",
htmlType: "submit",
loading: isUpdating,
},
cancelText: "Cancel",
content: (
<React.Suspense fallback={null}>
<PollOptionsForm
name="pollOptions"
title={poll.title}
defaultValues={{
navigationDate: poll.options[0].value.split("/")[0],
options: poll.options.map((option) => {
const [start, end] = option.value.split("/");
return end
? {
type: "timeSlot",
start,
end,
}
: {
type: "date",
date:
start.indexOf("T") === -1
? start
: // legacy polls
new Date(start).toISOString().substring(0, 10),
};
}),
timeZone: poll.timeZone ?? "",
}}
onSubmit={(data) => {
const encodedOptions = data.options.map(encodeDateOption);
const optionsToDelete = poll.options.filter((option) => {
return !encodedOptions.includes(option.value);
});
const optionsToAdd = encodedOptions.filter(
(encodedOption) =>
!poll.options.find((o) => o.value === encodedOption),
);
const onOk = () => {
updatePollMutation(
{
timeZone: data.timeZone,
optionsToDelete: optionsToDelete.map(({ id }) => id),
optionsToAdd,
},
{
onSuccess: () => closeChangeOptionsModal(),
},
);
};
const optionsToDeleteThatHaveVotes = optionsToDelete.filter(
(option) => option.votes.length > 0,
);
if (optionsToDeleteThatHaveVotes.length > 0) {
modalContext.render({
title: "Are you sure?",
description: (
<Trans
t={t}
i18nKey="deletingOptionsWarning"
components={{ b: <strong /> }}
/>
),
onOk,
okButtonProps: {
type: "danger",
},
okText: "Delete",
cancelText: "Cancel",
});
} else {
onOk();
}
}}
/>
</React.Suspense>
),
});
const [
changePollDetailsModalContextHolder,
openChangePollDetailsModa,
closePollDetailsModal,
] = useModal({
overlayClosable: true,
okText: "Save changes",
okButtonProps: {
form: "updateDetails",
loading: isUpdating,
htmlType: "submit",
},
cancelText: "Cancel",
content: (
<PollDetailsForm
name="updateDetails"
defaultValues={{
title: poll.title,
location: poll.location ?? "",
description: poll.description ?? "",
}}
className="p-4"
onSubmit={(data) => {
//submit
updatePollMutation(data, { onSuccess: closePollDetailsModal });
}}
/>
),
});
return (
<div>
{changeOptionsModalContextHolder}
{changePollDetailsModalContextHolder}
<Dropdown
placement={placement}
trigger={<Button icon={<Cog />}>Manage</Button>}
>
<DropdownItem
icon={Pencil}
label="Edit details"
onClick={openChangePollDetailsModa}
/>
{!poll.legacy ? (
<DropdownItem
icon={Table}
label="Edit options"
onClick={openChangeOptionsModal}
/>
) : null}
<DropdownItem
icon={Save}
label="Export to CSV"
onClick={() => {
const header = [
t("participantCount", {
count: poll.participants.length,
}),
...poll.options.map((option) => {
const decodedOption = decodeDateOption(
option.value,
poll.timeZone,
targetTimeZone,
);
const day = `${decodedOption.dow} ${decodedOption.day} ${decodedOption.month}`;
return decodedOption.type === "date"
? day
: `${day} ${decodedOption.startTime} - ${decodedOption.endTime}`;
}),
].join(",");
const rows = poll.participants.map((participant) => {
return [
participant.name,
...poll.options.map((option) => {
if (
participant.votes.some((vote) => {
return vote.optionId === option.id;
})
) {
return "Yes";
}
return "No";
}),
].join(",");
});
const csv = `data:text/csv;charset=utf-8,${[header, ...rows].join(
"\r\n",
)}`;
const encodedCsv = encodeURI(csv);
var link = document.createElement("a");
link.setAttribute("href", encodedCsv);
link.setAttribute(
"download",
`${poll.title.replace(/\s/g, "_")}-${format(
Date.now(),
"yyyyMMddhhmm",
)}`,
);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}}
/>
{poll.closed ? (
<DropdownItem
icon={LockOpen}
label="Unlock poll"
onClick={() => updatePollMutation({ closed: false })}
/>
) : (
<DropdownItem
icon={LockClosed}
label="Lock poll"
onClick={() => updatePollMutation({ closed: true })}
/>
)}
</Dropdown>
</div>
);
};
export default ManagePoll;