ui-nyla/src/components/FlowEditor/editor/TemplatePicker.tsx
2026-04-11 19:44:52 +02:00

155 lines
5.5 KiB
TypeScript

/**
* TemplatePicker - modal to browse and select a workflow template for creating a new workflow.
*/
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { FaSpinner } from 'react-icons/fa';
import {
fetchTemplates,
type AutoWorkflowTemplate,
type AutoTemplateScope,
type ApiRequestFunction,
} from '../../../api/workflowApi';
import styles from './Automation2FlowEditor.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext';
interface TemplatePickerProps {
open: boolean;
onClose: () => void;
onSelect: (templateId: string) => void;
instanceId: string;
request: ApiRequestFunction;
}
export const TemplatePicker: React.FC<TemplatePickerProps> = ({
open,
onClose,
onSelect,
instanceId,
request,
}) => {
const { t } = useLanguage();
const scopeLabels = useMemo(
() =>
({
all: t('Alle'),
user: t('Meine'),
instance: t('Instanz'),
mandate: t('Mandant'),
system: t('System'),
}) as Record<AutoTemplateScope | 'all', string>,
[t]
);
const [templates, setTemplates] = useState<AutoWorkflowTemplate[]>([]);
const [loading, setLoading] = useState(false);
const [activeScope, setActiveScope] = useState<AutoTemplateScope | 'all'>('all');
const [copying, setCopying] = useState<string | null>(null);
const _load = useCallback(async () => {
if (!instanceId || !open) return;
setLoading(true);
try {
const scope = activeScope === 'all' ? undefined : activeScope;
const result = await fetchTemplates(request, instanceId, scope);
setTemplates(Array.isArray(result) ? result : result.items);
} catch {
setTemplates([]);
} finally {
setLoading(false);
}
}, [instanceId, request, open, activeScope]);
useEffect(() => {
_load();
}, [_load]);
const _handleSelect = useCallback(
async (templateId: string) => {
setCopying(templateId);
try {
await onSelect(templateId);
} finally {
setCopying(null);
}
},
[onSelect]
);
if (!open) return null;
return (
<div className={styles.workflowModalBackdrop} role="dialog" aria-modal="true" aria-labelledby="tpl-picker-title">
<div className={styles.workflowModal} style={{ maxWidth: 600, maxHeight: '80vh', display: 'flex', flexDirection: 'column' }}>
<h3 id="tpl-picker-title" className={styles.workflowModalTitle}>
{t('Neu aus Vorlage')}
</h3>
<p className={styles.workflowModalHint}>
{t('Wählen Sie eine Vorlage, um einen neuen Workflow zu erstellen.')}
</p>
<div style={{ display: 'flex', gap: 6, marginBottom: 12, flexWrap: 'wrap' }}>
{(['all', 'user', 'instance', 'mandate', 'system'] as const).map((s) => (
<button
key={s}
type="button"
className={activeScope === s ? styles.workflowModalBtnPrimary : styles.workflowModalBtnSecondary}
onClick={() => setActiveScope(s)}
style={{ fontSize: '0.8rem', padding: '4px 10px' }}
>
{scopeLabels[s]}
</button>
))}
</div>
<div style={{ flex: 1, overflowY: 'auto', minHeight: 120 }}>
{loading ? (
<div style={{ textAlign: 'center', padding: 24 }}>
<FaSpinner className={styles.spinner} />
</div>
) : templates.length === 0 ? (
<div style={{ textAlign: 'center', padding: 24, color: 'var(--text-secondary, #888)' }}>
{t('Keine Vorlagen gefunden.')}
</div>
) : (
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.85rem' }}>
<thead>
<tr style={{ borderBottom: '2px solid var(--border-color, #e0e0e0)', textAlign: 'left' }}>
<th style={{ padding: '6px 8px' }}>{t('Name')}</th>
<th style={{ padding: '6px 8px', width: 80 }}>{t('Scope')}</th>
<th style={{ padding: '6px 8px', width: 100 }}></th>
</tr>
</thead>
<tbody>
{templates.map((tpl) => (
<tr key={tpl.id} style={{ borderBottom: '1px solid var(--border-color, #eee)' }}>
<td style={{ padding: '8px' }}>{tpl.label}</td>
<td style={{ padding: '8px', fontSize: '0.8rem', color: 'var(--text-secondary, #888)' }}>
{scopeLabels[(tpl.templateScope as AutoTemplateScope) || 'user']}
</td>
<td style={{ padding: '8px', textAlign: 'right' }}>
<button
type="button"
className={styles.workflowModalBtnPrimary}
style={{ fontSize: '0.8rem', padding: '4px 10px' }}
onClick={() => _handleSelect(tpl.id)}
disabled={copying !== null}
>
{copying === tpl.id ? <FaSpinner className={styles.spinner} /> : t('Übernehmen')}
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
<div className={styles.workflowModalActions} style={{ marginTop: 12 }}>
<button type="button" className={styles.workflowModalBtnSecondary} onClick={onClose}>
{t('Abbrechen')}
</button>
</div>
</div>
</div>
);
};