cal.pub0.org/packages/features/schedules/components/DateOverrideList.tsx

123 lines
3.9 KiB
TypeScript
Raw Normal View History

import type { UseFieldArrayRemove } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import type { TimeRange, WorkingHours } from "@calcom/types/schedule";
import { Button, DialogTrigger, Tooltip } from "@calcom/ui";
import { Edit2, Trash2 } from "@calcom/ui/components/icon";
import DateOverrideInputDialog from "./DateOverrideInputDialog";
const sortByDate = (a: { ranges: TimeRange[]; id: string }, b: { ranges: TimeRange[]; id: string }) => {
return a.ranges[0].start > b.ranges[0].start ? 1 : -1;
};
const useSettings = () => {
const { data } = useMeQuery();
return {
hour12: data?.timeFormat === 12,
timeZone: data?.timeZone,
};
};
const DateOverrideList = ({
items,
remove,
replace,
workingHours,
excludedDates = [],
}: {
remove: UseFieldArrayRemove;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
replace: any;
items: { ranges: TimeRange[]; id: string }[];
workingHours: WorkingHours[];
excludedDates?: string[];
}) => {
const { t, i18n } = useLocale();
const { hour12 } = useSettings();
const unsortedFieldArrayMap = items.reduce(
(map: { [id: string]: number }, { id }, index) => ({ ...map, [id]: index }),
{}
);
if (!items.length) {
return <></>;
}
const timeSpan = ({ start, end }: TimeRange) => {
return (
new Intl.DateTimeFormat(i18n.language, { hour: "numeric", minute: "numeric", hour12 }).format(
new Date(start.toISOString().slice(0, -1))
) +
" - " +
new Intl.DateTimeFormat(i18n.language, { hour: "numeric", minute: "numeric", hour12 }).format(
new Date(end.toISOString().slice(0, -1))
)
);
};
return (
Stripe add the ability to place hold on cards (#8022) * Add payment option to schema * Add payment option to Stripe zod * Set payment option on event type * Create manual payment intent in Stripe * Set payment option from Stripe app * Add payment option to DB * Pass React.ReactNode to checkbox * Create uncaptured payment intent * WIP * Capture card in setup intent * Show charge card option * Charge card from booking page * Bug fixes * Clean up * Clean up app card * Add no-show fee messaging on booking page * Send payment email on payment & add price * Fix messaging * Create no show fee charged email * Send charge fee collected email * Disable submit on card failure * Clean up * Serverside prevent charging card again if already charged * Only confirm booking if paid for * Type fixes * More type fixes * More type fixes * Type fix * Type fixes * UI changes * Payment component rework * Update apps/web/public/static/locales/en/common.json Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update apps/web/public/static/locales/en/common.json Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update apps/web/components/dialog/ChargeCardDialog.tsx Co-authored-by: Alex van Andel <me@alexvanandel.com> * Update packages/trpc/server/routers/viewer/payments.tsx Co-authored-by: Alex van Andel <me@alexvanandel.com> * Revert GTM config * Adjust payment option dropdown * Show alert when seats are set * Small bug fixes * Create collect card method * clean up * Prevent seats & charge no-show fee to be enabled together * Do not charge no-show fee on unconfirmed bookings * Add check to collect card method * Webhook send request emails * Fix some dark mode colours * Change awaiting payment language * Type fixes * Set height of Select and TextField both to 38px to fix alignment * Fix message seats & payment error message * Type fix --------- Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-04-11 21:44:14 +00:00
<ul className="border-subtle rounded border" data-testid="date-overrides-list">
{items.sort(sortByDate).map((item) => (
<li key={item.id} className="border-subtle flex justify-between border-b px-5 py-4 last:border-b-0">
<div>
<h3 className="text-emphasis text-sm">
{new Intl.DateTimeFormat("en-GB", {
weekday: "short",
month: "long",
day: "numeric",
timeZone: "UTC",
}).format(item.ranges[0].start)}
</h3>
{item.ranges[0].start.valueOf() - item.ranges[0].end.valueOf() === 0 ? (
<p className="text-subtle text-xs">{t("unavailable")}</p>
) : (
item.ranges.map((range, i) => (
<p key={i} className="text-subtle text-xs">
{timeSpan(range)}
</p>
))
)}
</div>
<div className="flex flex-row-reverse gap-5 space-x-2 rtl:space-x-reverse">
<DateOverrideInputDialog
excludedDates={excludedDates}
workingHours={workingHours}
value={item.ranges}
onChange={(ranges) => {
// update has very weird side-effects with sorting.
replace([...items.filter((currentItem) => currentItem.id !== item.id), { ranges }]);
delete unsortedFieldArrayMap[item.id];
}}
Trigger={
<DialogTrigger asChild>
<Button
tooltip={t("edit")}
className="text-default"
color="minimal"
variant="icon"
StartIcon={Edit2}
/>
</DialogTrigger>
}
/>
<Tooltip content="Delete">
<Button
className="text-default"
color="destructive"
variant="icon"
StartIcon={Trash2}
onClick={() => remove(unsortedFieldArrayMap[item.id])}
/>
</Tooltip>
</div>
</li>
))}
</ul>
);
};
export default DateOverrideList;