import { useState, useCallback, useRef, useEffect, useMemo } from "react"; import { Popover, Portal, TextInput, Button, Group, Stack, Divider, UnstyledButton, Text, ScrollArea, } from "@mantine/core"; import { IconPlus, IconChevronRight } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; import { BasePropertyType, IBaseProperty, TypeOptions, } from "@/features/base/types/base.types"; import { useCreatePropertyMutation } from "@/features/base/queries/base-property-query"; import { PropertyTypePicker, propertyTypes } from "./property-type-picker"; import { PropertyOptions } from "./property-options"; import classes from "@/features/base/styles/grid.module.css"; type CreatePropertyPopoverProps = { baseId: string; onPropertyCreated?: () => void; }; type Panel = "typePicker" | "configure" | "confirmDiscard"; const noop = () => {}; // Keep in sync with the switch cases in PropertyOptions const typesWithOptions = new Set([ "select", "multiSelect", "status", "number", "date", "person", ]); export function CreatePropertyPopover({ baseId, onPropertyCreated }: CreatePropertyPopoverProps) { const { t } = useTranslation(); const [opened, setOpened] = useState(false); const [panel, setPanel] = useState("typePicker"); const [selectedType, setSelectedType] = useState(null); const [name, setName] = useState(""); const [typeOptions, setTypeOptions] = useState>({}); const nameInputRef = useRef(null); const createPropertyMutation = useCreatePropertyMutation(); const selectedTypeDef = useMemo( () => propertyTypes.find((pt) => pt.type === selectedType), [selectedType], ); const selectedTypeLabel = selectedTypeDef ? t(selectedTypeDef.labelKey) : ""; const selectedTypeIcon = selectedTypeDef?.icon; const hasContent = useMemo(() => { return name.trim().length > 0 || Object.keys(typeOptions).length > 0; }, [name, typeOptions]); const resetState = useCallback(() => { setPanel("typePicker"); setSelectedType(null); setName(""); setTypeOptions({}); }, []); const handleOpen = useCallback(() => { resetState(); setOpened(true); }, [resetState]); const handleClose = useCallback(() => { setOpened(false); resetState(); }, [resetState]); const attemptClose = useCallback(() => { if (panel === "configure" && hasContent) { setPanel("confirmDiscard"); } else { handleClose(); } }, [panel, hasContent, handleClose]); const handleConfirmDiscard = useCallback(() => { handleClose(); }, [handleClose]); const handleCancelDiscard = useCallback(() => { setPanel("configure"); }, []); const handleTypeSelect = useCallback((type: BasePropertyType) => { setSelectedType(type); setTypeOptions({}); setPanel("configure"); }, []); useEffect(() => { if (panel === "configure") { setTimeout(() => nameInputRef.current?.focus(), 0); } }, [panel]); const handleCreate = useCallback(() => { if (!selectedType) return; const finalName = name.trim() || selectedTypeLabel; createPropertyMutation.mutate( { baseId, name: finalName, type: selectedType, typeOptions: Object.keys(typeOptions).length > 0 ? typeOptions as TypeOptions : undefined, }, { onSuccess: () => { onPropertyCreated?.(); }, }, ); handleClose(); }, [selectedType, name, selectedTypeLabel, typeOptions, baseId, createPropertyMutation, handleClose, onPropertyCreated]); const handleBackToTypePicker = useCallback(() => { setPanel("typePicker"); setTypeOptions({}); }, []); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); if (panel === "confirmDiscard") { handleCancelDiscard(); } else if (panel === "configure") { handleBackToTypePicker(); } else { handleClose(); } } }, [panel, handleBackToTypePicker, handleClose, handleCancelDiscard], ); const handleNameKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); handleCreate(); } }, [handleCreate], ); const handleOptionsUpdate = useCallback( (newTypeOptions: Record) => { setTypeOptions(newTypeOptions); }, [], ); const syntheticProperty: IBaseProperty = useMemo(() => ({ id: "", baseId, name: name || "", type: selectedType ?? "text", position: "", typeOptions: typeOptions as TypeOptions, isPrimary: false, workspaceId: "", createdAt: "", updatedAt: "", }), [baseId, name, selectedType, typeOptions]); const TypeIcon = selectedTypeIcon; const showOptions = selectedType && typesWithOptions.has(selectedType); return ( <> {opened && (
)}
e.stopPropagation()} onKeyDown={handleKeyDown} style={{ zIndex: 300 }} > {panel === "typePicker" && ( )} {(panel === "configure" || panel === "confirmDiscard") && ( setName(e.currentTarget.value)} onKeyDown={handleNameKeyDown} mb="xs" /> {TypeIcon && } {selectedTypeLabel} {showOptions && ( <> )} )} {panel === "confirmDiscard" && ( {t("Unsaved changes")} {t("You have unsaved changes. Do you want to discard them?")} )}
); }