frontend_nyla/src/pages/basedata/PromptsPage.tsx
ValueOn AG 3bcf4e4d9e fixes
2026-03-23 00:27:56 +01:00

310 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* PromptsPage
*
* Page for managing prompt templates using FormGeneratorTable.
* Follows the pattern established in AdminUsersPage/WorkflowsPage.
*/
import React, { useState, useMemo, useEffect } from 'react';
import { usePrompts, usePromptOperations } from '../../hooks/usePrompts';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
import { FaSync, FaPlus } from 'react-icons/fa';
import styles from '../admin/Admin.module.css';
interface Prompt {
id: string;
name: string;
content: string;
[key: string]: any;
}
export const PromptsPage: React.FC = () => {
// Data hook
const {
prompts,
attributes,
permissions,
pagination,
loading,
error,
refetch,
fetchPromptById,
updateOptimistically,
} = usePrompts();
// Operations hook
const {
handlePromptCreate,
handlePromptUpdate,
handlePromptDelete,
handleInlineUpdate,
deletingPrompts,
} = usePromptOperations();
const [showCreateModal, setShowCreateModal] = useState(false);
const [editingPrompt, setEditingPrompt] = useState<Prompt | null>(null);
// Initial fetch
useEffect(() => {
refetch();
}, []);
// Generate columns from attributes - exclude ID fields from display
const columns = useMemo(() => {
// Fields to hide in table view
const hiddenColumns = ['id', 'mandateId', '_createdAt', '_modifiedAt', '_hideDelete', '_permissions'];
const cols = (attributes || [])
.filter(attr => !hiddenColumns.includes(attr.name))
.map(attr => ({
key: attr.name,
label: attr.label || attr.name,
type: attr.type as any,
sortable: attr.sortable !== false,
filterable: attr.filterable !== false,
searchable: attr.searchable !== false,
width: attr.name === 'content' ? 300 : attr.width || 150,
minWidth: attr.minWidth || 100,
maxWidth: attr.name === 'content' ? 500 : attr.maxWidth || 400,
fkSource: (attr as any).fkSource,
fkDisplayField: (attr as any).fkDisplayField,
}));
// Add _createdBy column with FK resolution to show username
cols.push({
key: '_createdBy',
label: 'Created By',
type: 'text' as any,
sortable: true,
filterable: false,
searchable: false,
width: 150,
minWidth: 100,
maxWidth: 250,
fkSource: '/api/users/',
fkDisplayField: 'username',
});
return cols;
}, [attributes]);
// Check permissions
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
// Handle edit click
const handleEditClick = async (prompt: Prompt) => {
const fullPrompt = await fetchPromptById(prompt.id);
if (fullPrompt) {
setEditingPrompt(fullPrompt as Prompt);
}
};
// Handle create submit
const handleCreateSubmit = async (data: Partial<Prompt>) => {
const result = await handlePromptCreate({
name: data.name || '',
content: data.content || ''
});
if (result?.success) {
setShowCreateModal(false);
refetch();
}
};
// Handle edit submit
const handleEditSubmit = async (data: Partial<Prompt>) => {
if (!editingPrompt) return;
const result = await handlePromptUpdate(editingPrompt.id, {
name: data.name || editingPrompt.name,
content: data.content || editingPrompt.content
});
if (result.success) {
setEditingPrompt(null);
refetch();
}
};
// Handle duplicate prompt
const handleDuplicate = async (prompt: Prompt) => {
const result = await handlePromptCreate({
name: `Kopie von ${prompt.name || 'Prompt'}`,
content: prompt.content || ''
});
if (result?.success) {
refetch();
}
};
// Handle delete single prompt (confirmation handled by DeleteActionButton)
const handleDelete = async (prompt: Prompt) => {
const success = await handlePromptDelete(prompt.id);
if (success) {
refetch();
}
};
// Form attributes for create/edit modal
const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'isSystem', '_createdBy', '_createdAt', '_modifiedAt', '_hideDelete', '_permissions'];
return (attributes || [])
.filter(attr => !excludedFields.includes(attr.name));
}, [attributes]);
if (error) {
return (
<div className={styles.adminPage}>
<div className={styles.errorContainer}>
<span className={styles.errorIcon}></span>
<p className={styles.errorMessage}>Fehler beim Laden der Prompts: {error}</p>
<button className={styles.secondaryButton} onClick={() => refetch()}>
<FaSync /> Erneut versuchen
</button>
</div>
</div>
);
}
return (
<div className={`${styles.adminPage} ${styles.adminPageFill}`}>
<div className={styles.pageHeader}>
<div>
<h1 className={styles.pageTitle}>Prompts</h1>
<p className={styles.pageSubtitle}>Prompt-Templates verwalten</p>
</div>
<div className={styles.headerActions}>
<button
className={styles.secondaryButton}
onClick={() => refetch()}
disabled={loading}
>
<FaSync className={loading ? 'spinning' : ''} /> Aktualisieren
</button>
{canCreate && (
<button
className={styles.primaryButton}
onClick={() => setShowCreateModal(true)}
>
<FaPlus /> Neuer Prompt
</button>
)}
</div>
</div>
<div className={styles.tableContainer}>
<FormGeneratorTable
data={prompts}
columns={columns}
apiEndpoint="/api/prompts"
loading={loading}
pagination={true}
pageSize={25}
searchable={true}
filterable={true}
sortable={true}
selectable={false}
actionButtons={[
...(canCreate ? [{
type: 'copy' as const,
title: 'Duplizieren',
onAction: handleDuplicate,
}] : []),
...(canUpdate ? [{
type: 'edit' as const,
onAction: handleEditClick,
title: 'Bearbeiten',
}] : []),
...(canDelete ? [{
type: 'delete' as const,
title: 'Löschen',
loading: (row: Prompt) => deletingPrompts.has(row.id),
}] : []),
]}
onDelete={handleDelete}
hookData={{
refetch,
permissions,
pagination,
handleDelete: handlePromptDelete,
handleInlineUpdate,
updateOptimistically,
}}
emptyMessage="Keine Prompts gefunden"
/>
</div>
{/* Create Modal */}
{showCreateModal && (
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
<div className={styles.modal} onClick={e => e.stopPropagation()}>
<div className={styles.modalHeader}>
<h2 className={styles.modalTitle}>Neuer Prompt</h2>
<button
className={styles.modalClose}
onClick={() => setShowCreateModal(false)}
>
</button>
</div>
<div className={styles.modalContent}>
{formAttributes.length === 0 ? (
<div className={styles.loadingContainer}>
<div className={styles.spinner} />
<span>Lade Formular...</span>
</div>
) : (
<FormGeneratorForm
attributes={formAttributes}
mode="create"
onSubmit={handleCreateSubmit}
onCancel={() => setShowCreateModal(false)}
submitButtonText="Erstellen"
cancelButtonText="Abbrechen"
/>
)}
</div>
</div>
</div>
)}
{/* Edit Modal */}
{editingPrompt && (
<div className={styles.modalOverlay} onClick={() => setEditingPrompt(null)}>
<div className={styles.modal} onClick={e => e.stopPropagation()}>
<div className={styles.modalHeader}>
<h2 className={styles.modalTitle}>Prompt bearbeiten</h2>
<button
className={styles.modalClose}
onClick={() => setEditingPrompt(null)}
>
</button>
</div>
<div className={styles.modalContent}>
{formAttributes.length === 0 ? (
<div className={styles.loadingContainer}>
<div className={styles.spinner} />
<span>Lade Formular...</span>
</div>
) : (
<FormGeneratorForm
attributes={formAttributes}
data={editingPrompt}
mode="edit"
onSubmit={handleEditSubmit}
onCancel={() => setEditingPrompt(null)}
submitButtonText="Speichern"
cancelButtonText="Abbrechen"
/>
)}
</div>
</div>
</div>
)}
</div>
);
};
export default PromptsPage;