import { useMemo, useState } from "react"; import { createPortal } from "react-dom"; import type { ApiSettings } from "../../../types"; import Button from "../../../components/Button"; import DataGrid, { type DataGridColumn } from "../../../components/table/DataGrid"; import ToggleSwitch from "../../../components/ToggleSwitch"; import { getBool, getText } from "../utils/draftEditor"; import { createAttachmentRule, nextAttachmentLabel, summarizeAttachmentRules, type AttachmentBasePath, type AttachmentRule } from "../utils/attachments"; import ManagedFileChooser, { type ManagedAttachmentSelection } from "./ManagedFileChooser"; export type { AttachmentBasePath, AttachmentRule } from "../utils/attachments"; type AttachmentRulesOverlayProps = { title: string; rules: AttachmentRule[]; settings: ApiSettings; campaignId: string; disabled?: boolean; buttonLabel?: string; emptyText?: string; basePaths?: AttachmentBasePath[]; onChange: (rules: AttachmentRule[]) => void; }; type AttachmentRulesTableProps = { rules: AttachmentRule[]; settings: ApiSettings; campaignId: string; disabled?: boolean; emptyText?: string; basePaths?: AttachmentBasePath[]; id?: string; showAddButton?: boolean; activeChooserRuleIndex?: number | null; onOpenFileChooser?: (ruleIndex: number) => void; onChange: (rules: AttachmentRule[]) => void; }; type FileChooserState = { ruleIndex: number; basePath: AttachmentBasePath | null; }; export default function AttachmentRulesOverlay({ title, rules, settings, campaignId, disabled = false, buttonLabel, emptyText = "No attachment files or matching rules configured yet.", basePaths = [], onChange }: AttachmentRulesOverlayProps) { const [open, setOpen] = useState(false); const summary = useMemo(() => summarizeAttachmentRules(rules), [rules]); const label = buttonLabel ?? `direct: ${summary.direct} / rules: ${summary.rules}`; function closeOverlay() { setOpen(false); } function addOverlayRule() { onChange([...rules, createAttachmentRule(basePaths[0]?.path ?? "", nextAttachmentLabel(rules), basePaths[0]?.id ?? "")]); } const dialog = open ? createPortal(

{title}

, document.body ) : null; return ( <> {dialog} ); } export function AttachmentRulesTable({ showAddButton = true, onChange, ...tableProps }: AttachmentRulesTableProps) { function addRule() { onChange([ ...tableProps.rules, createAttachmentRule(tableProps.basePaths?.[0]?.path ?? "", nextAttachmentLabel(tableProps.rules), tableProps.basePaths?.[0]?.id ?? "") ]); } return (
{showAddButton && (
)}
); } export function AttachmentRulesDataGrid({ rules, settings, campaignId, disabled = false, emptyText = "No attachment files or matching rules configured yet.", basePaths = [], id = "attachment-rules", activeChooserRuleIndex = null, onOpenFileChooser, onChange }: AttachmentRulesTableProps) { const [fileChooser, setFileChooser] = useState(null); function patchRule(index: number, patch: Partial) { onChange(rules.map((rule, currentIndex) => currentIndex === index ? { ...rule, ...patch } : rule)); } function removeRule(index: number) { setFileChooser(null); onChange(rules.filter((_, currentIndex) => currentIndex !== index)); } function openFileChooser(ruleIndex: number) { if (onOpenFileChooser) { onOpenFileChooser(ruleIndex); return; } const rule = rules[ruleIndex] ?? {}; const currentPath = getText(rule, "base_dir", basePaths[0]?.path ?? ""); const currentBasePathId = getText(rule, "base_path_id"); const basePath = basePaths.find((item) => item.id === currentBasePathId) ?? basePaths.find((item) => item.path === currentPath) ?? basePaths[0] ?? null; setFileChooser({ ruleIndex, basePath }); } function selectAttachment(selection: ManagedAttachmentSelection) { if (!fileChooser) return; const currentRule = rules[fileChooser.ruleIndex] ?? {}; patchRule(fileChooser.ruleIndex, { base_path_id: fileChooser.basePath?.id ?? "", base_dir: fileChooser.basePath?.path ?? (selection.folderPath || "."), file_filter: selection.fileFilter, type: selection.selectionType === "file" ? "direct" : "pattern", include_subdirs: false, label: getText(currentRule, "label") || `Attachment ${fileChooser.ruleIndex + 1}` }); setFileChooser(null); } return ( <> String(rule.id ?? index)} emptyText={emptyText} className="attachment-rules-table-wrap attachment-rules-table" rowClassName={(_rule, index) => (activeChooserRuleIndex ?? fileChooser?.ruleIndex) === index ? "is-choosing-file" : undefined} /> {fileChooser && ( setFileChooser(null)} onSelectAttachment={selectAttachment} /> )} ); } type AttachmentRuleColumnContext = { disabled: boolean; basePaths: AttachmentBasePath[]; activeChooserRuleIndex: number | null; patchRule: (index: number, patch: Partial) => void; openFileChooser: (ruleIndex: number) => void; removeRule: (index: number) => void; }; function attachmentRuleColumns({ disabled, basePaths, activeChooserRuleIndex: _activeChooserRuleIndex, patchRule, openFileChooser, removeRule }: AttachmentRuleColumnContext): DataGridColumn[] { return [ { id: "label", header: "Label", width: 190, resizable: true, sortable: true, filterable: true, sticky: "start", render: (rule, index) => patchRule(index, { label: event.target.value })} />, value: (rule) => getText(rule, "label") }, { id: "base_path", header: "Base path", width: 250, sortable: true, filterable: true, render: (rule, index) => { const currentBasePathValue = getText(rule, "base_dir", basePaths[0]?.path ?? ""); const currentBasePathId = getText(rule, "base_path_id"); const selectedBasePath = basePaths.find((basePath) => basePath.id === currentBasePathId) ?? basePaths.find((basePath) => basePath.path === currentBasePathValue) ?? basePaths[0]; return basePaths.length > 0 ? ( ) : ( ); }, value: (rule) => getText(rule, "base_dir", basePaths[0]?.path ?? "") }, { id: "file_filter", header: "File / pattern", width: "minmax(260px, 1fr)", resizable: true, sortable: true, filterable: true, render: (rule, index) => (
!disabled && openFileChooser(index)} onKeyDown={(event) => { if (!disabled && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); openFileChooser(index); } }} />
), value: (rule) => getText(rule, "file_filter") }, { id: "required", header: "Required", width: 175, sortable: true, filterable: true, render: (rule, index) => patchRule(index, { required: checked })} />, value: (rule) => getBool(rule, "required", true) ? "required" : "optional" }, { id: "actions", header: "", width: 145, sticky: "end", render: (_rule, index) => } ]; }