import type { Table } from "@tanstack/react-table"; import { createContext, useContext, useState, useMemo, type PropsWithChildren } from "react"; import type { Dispatch, SetStateAction } from "react"; import { DataTableSelectionBar, type ColumnFilter } from "@calcom/features/data-table"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { Attribute as _Attribute, AttributeOption } from "@calcom/prisma/client"; import { trpc } from "@calcom/trpc/react"; import classNames from "@calcom/ui/classNames"; import { Alert } from "@calcom/ui/components/alert"; import { Button } from "@calcom/ui/components/button"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@calcom/ui/components/command"; import { Input } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; import { Popover, PopoverContent, PopoverTrigger } from "@calcom/ui/components/popover"; import { showToast } from "@calcom/ui/components/toast"; import type { UserTableUser } from "../types"; interface Props { table: Table; filters: ColumnFilter[]; } type Attribute = _Attribute & { options: AttributeOption[] }; type AttributesContextType = { selectedAttribute: string | undefined; setSelectedAttribute: Dispatch>; foundAttributeInCache: Attribute | undefined; selectedAttributeOptions: string[]; setSelectedAttributeOptions: Dispatch>; attributes: Attribute[] | undefined; }; const AttributesContext = createContext(null); function AttributesProvider({ children }: PropsWithChildren) { const { data: attributes } = trpc.viewer.attributes.list.useQuery(); const [selectedAttribute, setSelectedAttribute] = useState(); const [selectedAttributeOptions, setSelectedAttributeOptions] = useState([]); const foundAttributeInCache = useMemo( () => attributes?.find((attr) => attr.id === selectedAttribute), [selectedAttribute, attributes] ); const value: AttributesContextType = { selectedAttribute, setSelectedAttribute, selectedAttributeOptions, setSelectedAttributeOptions, foundAttributeInCache, attributes, }; return {children}; } function useAttributes() { const context = useContext(AttributesContext); if (!context) { throw new Error("useAttributes must be used within an AttributesProvider"); } return context; } function getTranslateableStringFromType(type: string) { switch (type) { case "SINGLE_SELECT": return "single_select"; case "MULTI_SELECT": return "multi_select"; case "TEXT": return "text"; case "NUMBER": return "number"; default: return undefined; } } function SelectedAttributeToAssign() { const { t } = useLocale(); const { foundAttributeInCache: foundAttribute, selectedAttributeOptions, setSelectedAttributeOptions, } = useAttributes(); if (!foundAttribute) { return null; } const translateableType = getTranslateableStringFromType(foundAttribute.type); const isSelectable = foundAttribute.type === "SINGLE_SELECT" || foundAttribute.type === "MULTI_SELECT"; return (
{foundAttribute.name} {translateableType && ({t(translateableType)})}
{isSelectable ? ( <> {foundAttribute.options.map((option) => { return ( { if (foundAttribute.type === "SINGLE_SELECT") { setSelectedAttributeOptions([option.id]); } else { setSelectedAttributeOptions((prev: string[]) => { if (prev.includes(option.id)) { return prev.filter((id: string) => id !== option.id); } return [...prev, option.id]; }); } }}> {option.value}
{selectedAttributeOptions?.includes(option.id) ? ( ) : null}
); })} ) : ( <> { // trigger onBlur so it's set as Apply is pressed (but not onChange) which triggers // a re-render which also loses focus. setSelectedAttributeOptions([e.target.value]); }} /> )}
); } function Content({ showMultiSelectWarning }: { showMultiSelectWarning: boolean }) { const { t } = useLocale(); const { selectedAttribute, setSelectedAttribute, selectedAttributeOptions, setSelectedAttributeOptions, attributes, foundAttributeInCache, } = useAttributes(); if (!selectedAttribute) { return ( <> No attributes found {attributes && attributes.map((option) => { return ( { setSelectedAttribute(option.id); setSelectedAttributeOptions([]); }}> {option.name}
); })}
); } if (showMultiSelectWarning) { return (
); } if (selectedAttribute) { return ; } return null; } function MassAssignAttributesBulkActionComponent({ table, filters }: Props) { const { selectedAttribute, setSelectedAttribute, selectedAttributeOptions, setSelectedAttributeOptions, foundAttributeInCache, } = useAttributes(); const [showMultiSelectWarning, setShowMultiSelectWarning] = useState(false); const { t } = useLocale(); const utils = trpc.useUtils(); const bulkAssignAttributes = trpc.viewer.attributes.bulkAssignAttributes.useMutation({ onSuccess: (success) => { setSelectedAttribute(undefined); setSelectedAttributeOptions([]); utils.viewer.organizations.listMembers.invalidate(); showToast(success.message, "success"); }, onError: (error) => { showToast(`Error assigning attributes: ${error.message}`, "error"); }, }); return ( { if (!open) { setSelectedAttribute(undefined); setSelectedAttributeOptions([]); setShowMultiSelectWarning(false); } }}> {t("add_attributes")} {/* We dont really use shadows much - but its needed here */}
{selectedAttribute ? ( <> ) : null}
); } export function MassAssignAttributesBulkAction({ table, filters }: Props) { return ( ); }