91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
import { Link, useParams } from "react-router";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Transition } from "@headlessui/react";
|
|
import { Fragment, useState } from "react";
|
|
import { useOuterClick } from "~/utils/shared/KeypressUtils";
|
|
import { useAppData } from "~/utils/data/useAppData";
|
|
import UrlUtils from "~/utils/app/UrlUtils";
|
|
import { EntitySimple } from "~/utils/db/entities/entities.db.server";
|
|
import { getEntityPermission, getUserHasPermission } from "~/utils/helpers/PermissionsHelper";
|
|
import { PlusIcon } from "~/components/ui/icons/vendor";
|
|
|
|
interface Props {
|
|
entities: EntitySimple[];
|
|
className?: string;
|
|
}
|
|
|
|
export function quickActionEntityPath(params: { tenant?: string; group?: string }, entitySlug: string) {
|
|
const path = params.group ? `g/${params.group}/${entitySlug}/new` : `${entitySlug}/new`;
|
|
return UrlUtils.currentTenantUrl(params, path);
|
|
}
|
|
|
|
export default function QuickActionsButton({ entities, className }: Props) {
|
|
const { t } = useTranslation();
|
|
const params = useParams();
|
|
const appData = useAppData();
|
|
|
|
const [opened, setOpened] = useState(false);
|
|
const creatableEntities = entities.filter((entity) => getUserHasPermission(appData, getEntityPermission(entity, "create")));
|
|
const hasQuickActions = creatableEntities.length > 0;
|
|
|
|
const clickOutside = useOuterClick(() => setOpened(false));
|
|
return (
|
|
<span className={className} ref={clickOutside}>
|
|
{hasQuickActions && (
|
|
<div className="relative">
|
|
<div className="inline-flex divide-x divide-gray-300 rounded-sm shadow-none">
|
|
<div className="relative z-0 inline-flex rounded-full text-sm shadow-none">
|
|
<button
|
|
onClick={() => setOpened(!opened)}
|
|
type="button"
|
|
className="text-muted-foreground bg-secondary hover:bg-secondary/90 border-border relative inline-flex items-center rounded-full border p-2 font-medium shadow-inner focus:z-10 focus:outline-hidden focus:ring-2 focus:ring-offset-2"
|
|
aria-haspopup="listbox"
|
|
aria-expanded="true"
|
|
aria-label={t("shared.new")}
|
|
>
|
|
<span className="sr-only">{t("shared.new")}</span>
|
|
<PlusIcon className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<Transition
|
|
as={Fragment}
|
|
show={opened}
|
|
enter="transition ease-out duration-100"
|
|
enterFrom="transform opacity-0 scale-95"
|
|
enterTo="transform opacity-100 scale-100"
|
|
leave="transition ease-in duration-75"
|
|
leaveFrom="transform opacity-100 scale-100"
|
|
leaveTo="transform opacity-0 scale-95"
|
|
>
|
|
<ul
|
|
className="divide-border bg-background absolute right-0 z-40 mt-2 w-72 origin-top-right divide-y overflow-hidden rounded-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-hidden"
|
|
tabIndex={-1}
|
|
aria-labelledby="listbox-label"
|
|
>
|
|
{creatableEntities.map((entity) => {
|
|
return (
|
|
<li key={entity.name} className="text-foreground relative cursor-default select-none text-sm" id="listbox-option-0">
|
|
<Link
|
|
to={quickActionEntityPath(params, entity.slug)}
|
|
onClick={() => setOpened(false)}
|
|
className="hover:bg-secondary flex flex-col p-4"
|
|
>
|
|
<div className="flex justify-between">
|
|
<p className="font-semibold">
|
|
{t("shared.new")} {t(entity.title)}
|
|
</p>
|
|
</div>
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</Transition>
|
|
</div>
|
|
)}
|
|
</span>
|
|
);
|
|
}
|