import { useMemo, useState } from "react"; import { createPortal } from "react-dom"; import type { ApiSettings } from "../../../types"; import { Button } from "@govoplan/core-webui"; import { Dialog } from "@govoplan/core-webui"; import { usePlatformUiCapability, type FilesFileExplorerUiCapability, type FilesManagedAttachmentSelection } from "@govoplan/core-webui"; import DataGrid, { DataGridEmptyAction, DataGridRowActions, type DataGridColumn } from "../../../components/table/DataGrid"; import { ToggleSwitch } from "@govoplan/core-webui"; import { getBool, getText } from "../utils/draftEditor"; import { attachmentRuleZipSelection, createAttachmentRule, nextAttachmentLabel, summarizeAttachmentRules, type AttachmentBasePath, type AttachmentRule, type AttachmentZipCollection } from "../utils/attachments"; import { insertAfter, moveArrayItem, i18nMessage } from "@govoplan/core-webui"; import { asRecord } from "../utils/campaignView"; import { renderTemplatePreviewText } from "../utils/templatePlaceholders"; 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[]; zipConfig?: AttachmentZipCollection; filesModuleInstalled?: boolean; previewContext?: Record; onChange: (rules: AttachmentRule[]) => void; }; type AttachmentRulesTableProps = { rules: AttachmentRule[]; settings: ApiSettings; campaignId: string; disabled?: boolean; emptyText?: string; basePaths?: AttachmentBasePath[]; id?: string; activeChooserRuleIndex?: number | null; zipConfig?: AttachmentZipCollection; filesModuleInstalled?: boolean; previewContext?: Record; 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 = "i18n:govoplan-campaign.no_attachment_files_or_matching_rules_configured.c6948b49", basePaths = [], zipConfig = { enabled: false, archives: [] }, filesModuleInstalled = false, previewContext, onChange }: AttachmentRulesOverlayProps) { const [open, setOpen] = useState(false); const [draftRules, setDraftRules] = useState([]); const summary = useMemo(() => summarizeAttachmentRules(rules), [rules]); const label = buttonLabel ?? `direct: ${summary.direct} / rules: ${summary.rules}`; function openOverlay() { setDraftRules(cloneAttachmentRules(rules)); setOpen(true); } function cancelOverlay() { setOpen(false); } function saveOverlay() { if (disabled) return; onChange(draftRules); setOpen(false); } const dialog = open ? createPortal( }> , document.body ) : null; return ( <> {dialog} ); } function cloneAttachmentRules(rules: AttachmentRule[]): AttachmentRule[] { return rules.map((rule) => ({ ...rule, ...(rule.zip && typeof rule.zip === "object" ? { zip: { ...asRecord(rule.zip) } } : {}) })); } export function AttachmentRulesTable({ onChange, ...tableProps }: AttachmentRulesTableProps) { return (
); } export function AttachmentRulesDataGrid({ rules, settings, campaignId, disabled = false, emptyText = "i18n:govoplan-campaign.no_attachment_files_or_matching_rules_configured.c6948b49", basePaths = [], id = "attachment-rules", activeChooserRuleIndex = null, zipConfig = { enabled: false, archives: [] }, filesModuleInstalled = false, previewContext, onOpenFileChooser, onChange }: AttachmentRulesTableProps) { const [fileChooser, setFileChooser] = useState(null); const filesFileExplorer = usePlatformUiCapability("files.fileExplorer"); const ManagedFileChooser = filesModuleInstalled ? filesFileExplorer?.ManagedFileChooser : null; const managedFilesAvailable = Boolean(ManagedFileChooser); function patchRule(index: number, patch: Partial) { onChange(rules.map((rule, currentIndex) => currentIndex === index ? { ...rule, ...patch } : rule)); } function addRule(afterIndex = rules.length - 1) { if (disabled || basePaths.length === 0) return; const nextRule = createAttachmentRule(basePaths[0].path, nextAttachmentLabel(rules), basePaths[0].id); onChange(insertAfter(rules, afterIndex, nextRule)); } function removeRule(index: number) { setFileChooser(null); onChange(rules.filter((_, currentIndex) => currentIndex !== index)); } function moveRule(index: number, targetIndex: number) { if (disabled || index === targetIndex) return; setFileChooser(null); onChange(moveArrayItem(rules, index, targetIndex)); } function openFileChooser(ruleIndex: number) { if (!managedFilesAvailable) return; if (onOpenFileChooser) { onOpenFileChooser(ruleIndex); return; } const rule = rules[ruleIndex] ?? {}; const currentPath = getText(rule, "base_dir"); const currentBasePathId = getText(rule, "base_path_id"); const explicitlyReferenced = Boolean(currentBasePathId || currentPath); const basePath = basePaths.find((item) => item.id === currentBasePathId) ?? basePaths.find((item) => item.path === currentPath) ?? ( !explicitlyReferenced ? basePaths[0] : undefined) ?? null; if (!basePath) return; setFileChooser({ ruleIndex, basePath }); } function selectAttachment(selection: FilesManagedAttachmentSelection) { 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={basePaths.length === 0 ? "i18n:govoplan-campaign.no_attachment_source_is_enabled_for_individual_a.818a2820" : emptyText} emptyAction={ addRule(-1)} disabled={disabled || basePaths.length === 0} label="i18n:govoplan-campaign.add_first_attachment.025fbf31" />} className="attachment-rules-table-wrap attachment-rules-table" rowClassName={(_rule, index) => (activeChooserRuleIndex ?? fileChooser?.ruleIndex) === index ? "is-choosing-file" : undefined} /> {fileChooser && ManagedFileChooser && renderTemplatePreviewText(pattern, context, false)} onClose={() => setFileChooser(null)} onSelectAttachment={selectAttachment} /> } ); } type AttachmentRuleColumnContext = { disabled: boolean; rules: AttachmentRule[]; basePaths: AttachmentBasePath[]; zipConfig: AttachmentZipCollection; filesModuleInstalled: boolean; activeChooserRuleIndex: number | null; patchRule: (index: number, patch: Partial) => void; addRule: (afterIndex?: number) => void; moveRule: (index: number, targetIndex: number) => void; openFileChooser: (ruleIndex: number) => void; removeRule: (index: number) => void; }; function attachmentRuleColumns({ disabled, rules, basePaths, zipConfig, filesModuleInstalled, activeChooserRuleIndex: _activeChooserRuleIndex, patchRule, addRule, moveRule, openFileChooser, removeRule }: AttachmentRuleColumnContext): DataGridColumn[] { return [ { id: "label", header: "i18n:govoplan-campaign.label.74341e3c", 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: "i18n:govoplan-campaign.base_path.6a4867ca", width: 250, sortable: true, filterable: true, columnType: "from-list", list: { options: basePaths.map((basePath) => ({ value: basePath.path, label: basePath.name || basePath.path })) }, render: (rule, index) => { const currentBasePathValue = getText(rule, "base_dir"); const currentBasePathId = getText(rule, "base_path_id"); const explicitlyReferenced = Boolean(currentBasePathId || currentBasePathValue); const selectedBasePath = basePaths.find((basePath) => basePath.id === currentBasePathId) ?? basePaths.find((basePath) => basePath.path === currentBasePathValue) ?? ( !explicitlyReferenced ? basePaths[0] : undefined); return ( ); }, value: (rule) => getText(rule, "base_dir", basePaths[0]?.path ?? "") }, { id: "file_filter", header: "i18n:govoplan-campaign.file_pattern.86320b07", width: "minmax(260px, 1fr)", resizable: true, sortable: true, filterable: true, render: (rule, index) =>
{ if (!filesModuleInstalled) patchRule(index, { file_filter: event.target.value }); }} onClick={() => filesModuleInstalled && !disabled && openFileChooser(index)} onKeyDown={(event) => { if (filesModuleInstalled && !disabled && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); openFileChooser(index); } }} /> {filesModuleInstalled && }
, value: (rule) => getText(rule, "file_filter") }, { id: "message_filename_template", header: "i18n:govoplan-campaign.message_filename_template.0b7ac934", width: 230, resizable: true, sortable: true, filterable: true, render: (rule, index) => patchRule(index, { message_filename_template: event.target.value })} />, value: (rule) => getText(rule, "message_filename_template") }, { id: "zip_entry_name_template", header: "i18n:govoplan-campaign.zip_entry_name_template.83772a73", width: 230, resizable: true, sortable: true, filterable: true, render: (rule, index) => patchRule(index, { zip_entry_name_template: event.target.value })} />, value: (rule) => getText(rule, "zip_entry_name_template") }, ...(zipConfig.enabled ? [{ id: "zip", header: "i18n:govoplan-campaign.zip_archive.5a2430dd", width: 240, sortable: true, filterable: true, columnType: "from-list", list: { options: [ { value: "exclude", label: "i18n:govoplan-campaign.exclude_from_zip.a6422da1" }, { value: "inherit", label: "i18n:govoplan-campaign.campaign_standard.c88ce44c" }, ...zipConfig.archives.map((archive) => ({ value: archive.id, label: archive.name }))] }, render: (rule: AttachmentRule, index: number) => { const selection = attachmentRuleZipSelection(rule); return ( ); }, value: (rule: AttachmentRule) => attachmentRuleZipSelection(rule) } satisfies DataGridColumn] : []), { id: "required", header: "i18n:govoplan-campaign.required.eed6bfb4", width: 175, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "required", label: "i18n:govoplan-campaign.required.eed6bfb4" }, { value: "optional", label: "i18n:govoplan-campaign.optional.0c6c4102" }] }, render: (rule, index) => patchRule(index, { required: checked })} />, value: (rule) => getBool(rule, "required", true) ? "required" : "optional" }, { id: "actions", header: "i18n:govoplan-campaign.actions.c3cd636a", width: 180, sticky: "end", render: (_rule, index) => addRule(index)} onRemove={() => removeRule(index)} onMoveUp={index > 0 ? () => moveRule(index, index - 1) : undefined} onMoveDown={index < rules.length - 1 ? () => moveRule(index, index + 1) : undefined} addLabel="i18n:govoplan-campaign.add_attachment_below.c7b66ffd" removeLabel="i18n:govoplan-campaign.remove_attachment.0fcc6594" moveUpLabel="i18n:govoplan-campaign.move_attachment_up.28614804" moveDownLabel="i18n:govoplan-campaign.move_attachment_down.a4d6604f" /> }]; }