added prompt and user page

This commit is contained in:
Ida Dittrich 2025-08-22 12:00:05 +02:00
parent 43951c280a
commit 9d5308206e
43 changed files with 1275 additions and 852 deletions

BIN
README.md

Binary file not shown.

View file

@ -1,4 +1,4 @@
import React from 'react'; // import React from 'react';
import { FormGenerator } from '../FormGenerator'; import { FormGenerator } from '../FormGenerator';
import styles from './ConnectionsTable.module.css'; import styles from './ConnectionsTable.module.css';
import { ConnectionsTableProps } from './connectionsInterfaces'; import { ConnectionsTableProps } from './connectionsInterfaces';
@ -11,7 +11,9 @@ export function ConnectionsTable({
isLoading = false, isLoading = false,
isConnecting = false, isConnecting = false,
isDisconnecting = false, isDisconnecting = false,
onRowSelect onRowSelect,
onDelete,
onDeleteMultiple
}: ConnectionsTableProps) { }: ConnectionsTableProps) {
const { t } = useLanguage(); const { t } = useLanguage();
@ -30,8 +32,10 @@ export function ConnectionsTable({
resizable={true} resizable={true}
pagination={true} pagination={true}
pageSize={10} pageSize={10}
selectable={false} selectable={true}
onRowSelect={onRowSelect} onRowSelect={onRowSelect}
onDelete={onDelete}
onDeleteMultiple={onDeleteMultiple}
actions={actions} actions={actions}
className={styles.connectionsTable} className={styles.connectionsTable}
/> />

View file

@ -16,6 +16,8 @@ export interface ConnectionsTableProps {
isConnecting?: boolean; isConnecting?: boolean;
isDisconnecting?: boolean; isDisconnecting?: boolean;
onRowSelect?: (selectedRows: Connection[]) => void; onRowSelect?: (selectedRows: Connection[]) => void;
onDelete?: (connection: Connection) => Promise<void>;
onDeleteMultiple?: (connections: Connection[]) => Promise<void>;
} }
export interface ConnectionEditModalProps { export interface ConnectionEditModalProps {
@ -49,6 +51,7 @@ export interface ConnectionHandlers {
handleConnect: (connection: Connection) => Promise<void>; handleConnect: (connection: Connection) => Promise<void>;
handleDisconnect: (connection: Connection) => Promise<void>; handleDisconnect: (connection: Connection) => Promise<void>;
handleDelete: (connection: Connection) => Promise<void>; handleDelete: (connection: Connection) => Promise<void>;
handleDeleteMultiple: (connections: Connection[]) => Promise<void>;
handleConnectOrDisconnect: (connection: Connection) => Promise<void>; handleConnectOrDisconnect: (connection: Connection) => Promise<void>;
handleEditConnection: (connection: Connection) => Promise<void>; handleEditConnection: (connection: Connection) => Promise<void>;
handleSaveConnection: (updatedConnection: Connection) => Promise<void>; handleSaveConnection: (updatedConnection: Connection) => Promise<void>;

View file

@ -254,6 +254,20 @@ export function useConnectionsLogic(): ConnectionsLogicReturn {
} }
}; };
const handleDeleteMultiple = async (connections: Connection[]) => {
const confirmMessage = t('connections.confirm_delete_multiple', 'Are you sure you want to delete {count} connections?').replace('{count}', connections.length.toString());
if (window.confirm(confirmMessage)) {
try {
// Delete all connections in parallel
await Promise.all(connections.map(connection => deleteConnection(connection.id)));
await fetchConnections();
} catch (error) {
console.error('Error deleting multiple connections:', error);
}
}
};
const handleConnectOrDisconnect = async (connection: Connection) => { const handleConnectOrDisconnect = async (connection: Connection) => {
if (connection.status === 'active') { if (connection.status === 'active') {
await handleDisconnect(connection); await handleDisconnect(connection);
@ -331,6 +345,7 @@ export function useConnectionsLogic(): ConnectionsLogicReturn {
handleConnect, handleConnect,
handleDisconnect, handleDisconnect,
handleDelete, handleDelete,
handleDeleteMultiple,
handleConnectOrDisconnect, handleConnectOrDisconnect,
handleEditConnection, handleEditConnection,
handleSaveConnection, handleSaveConnection,

View file

@ -23,10 +23,11 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
/> />
</div> </div>
{/* Top Right: File Preview */} {/*Top Right: File Preview (disabled for now)
<div className={`${styles.quadrant} ${styles.file_preview_quadrant}`}> <div className={`${styles.quadrant} ${styles.file_preview_quadrant}`}>
<FilePreview selectedFile={selectedFile} /> <FilePreview selectedFile={selectedFile} />
</div> </div>
*/}
{/* Bottom Left: Input Area */} {/* Bottom Left: Input Area */}
<div className={`${styles.quadrant} ${styles.input_quadrant}`}> <div className={`${styles.quadrant} ${styles.input_quadrant}`}>

View file

@ -110,6 +110,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
)} )}
</div> </div>
<div className={messageStyles.message_document_actions}> <div className={messageStyles.message_document_actions}>
{/* Preview and Download buttons disabled for now
<button <button
onClick={(e) => handlePreview(doc, e)} onClick={(e) => handlePreview(doc, e)}
className={messageStyles.message_document_action_button} className={messageStyles.message_document_action_button}
@ -129,6 +130,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
<FaDownload size={16} /> <FaDownload size={16} />
</div> </div>
</button> </button>
*/}
</div> </div>
</div> </div>
))} ))}

View file

@ -397,7 +397,7 @@ export function FormGenerator<T extends Record<string, any>>({
title={t('formgen.delete.multiple', `Delete ${selectedRows.size} selected items`)} title={t('formgen.delete.multiple', `Delete ${selectedRows.size} selected items`)}
> >
<span className={styles.deleteIcon}></span> <span className={styles.deleteIcon}></span>
{t('formgen.delete.multiple', `Delete All (${selectedRows.size})`)} {t('formgen.delete.multiple', `Delete ${selectedRows.size} selected items`).replace('{count}', selectedRows.size.toString())}
</button> </button>
)} )}

View file

@ -1,57 +0,0 @@
.memberItem {
display: flex;
align-items: center;
height: 70px;
padding: 0px 16px;
justify-content: space-between;
color: var(--color-text);
font-family: var(--font-family);
}
.userProfile {
margin-right: 12px;
}
.profileIcon {
font-size: 36px;
color: var(--color-gray-disabled);
}
.userInfo {
display: grid;
grid-template-columns: 200px 250px 100px;
gap: 16px;
flex-grow: 1;
align-items: center;
}
.userName {
margin: 0;
font-size: 14px;
color: var(--color-text);
font-family: var(--font-family);
}
.userEmail,
.userRole {
margin: 0;
font-size: 12px;
font-weight: light;
color: var(--color-gray);
font-family: var(--font-family);
}
.actions {
display: flex;
align-items: center;
justify-content: center;
}
.editBtn:hover {
color: var(--color-secondary);
}
.deleteBtn:hover {
color: var(--color-red);
}

View file

@ -1,32 +0,0 @@
import { FaUserCircle } from "react-icons/fa";
import styles from "./MitgliederItem.module.css";
import MitgliederItemDelete from "./MitgliederItemDelete/MitgliederItemDelete";
import MitgliederItemEdit from "./MitgliederItemEdit/MitgliederItemEdit";
import { User } from "../../hooks/useUsers";
type MitgliederItemProps = {
user: User;
refetchUsers: () => Promise<void>;
totalUsers: number;
};
const MitgliederItem = ({ user, refetchUsers, totalUsers }: MitgliederItemProps) => {
return (
<li className={styles.memberItem}>
<div className={styles.userProfile}>
<FaUserCircle className={styles.profileIcon} />
</div>
<div className={styles.userInfo}>
<span className={styles.userName}>{user.fullName || user.username}</span>
<span className={styles.userEmail}>{user.email}</span>
<span className={styles.userRole}>{user.privilege}</span>
</div>
<div className={styles.actions}>
<MitgliederItemEdit user={user} refetchUsers={refetchUsers} />
<MitgliederItemDelete user={user} refetchUsers={refetchUsers} isDisabled={totalUsers <= 1} />
</div>
</li>
);
};
export default MitgliederItem;

View file

@ -1,92 +0,0 @@
.popupHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-top:10px;
margin-left: 10px;
margin-right: 10px;
margin-bottom: 5px;
width: 300px;
height: 20px;
color: gray;
font-size: 10px;
}
.closeButton {
display: flex;
align-items: center;
background: none;
border: none;
cursor: pointer;
width: 20px; /* Increased button size */
height: 20px;
line-height: 0;
padding: 0;
margin:0;
}
.closeIcon {
width: 100%; /* Make the icon fill the button's width */
height: 100%;
padding: 0;
margin:0;
display: block;
}
.horizontalLineLight {
width: 100%;
background-color: #F1F1F1;
height: 1px;
}
.userInfo {
display: grid;
grid-column: 1;
padding: none;
margin: none;
align-items: center;
justify-content: center; /* Center horizontally */
}
.userInfoParagraph{
display: flex;
color: gray;
font-size: 10px;
margin:0px;
justify-content: center;
gap: 10px;
}
.userInfo h2{
display: flex;
justify-content: center;
margin: 0;
margin-top:10px;
justify-content: center;
}
.submitButtonDiv {
margin: 10px;
display: flex; /* Use Flexbox */
justify-content: center; /* Center horizontally */
align-items: center;
}
.submitButton {
background: none;
border: 1px solid black;
border-radius: 5px;
padding: 5px 20px; /* Optional: Adds some padding for better button size */
cursor: pointer;
width: 80%
}
.submitButton:hover {
background: rgb(168, 18, 18);
border: 1px solid rgb(168, 18, 18);
border-radius: 5px;
padding: 5px 20px; /* Optional: Adds some padding for better button size */
cursor: pointer;
color: white;
transition: 0.2s;
}

View file

@ -1,88 +0,0 @@
import { IoCloseOutline } from "react-icons/io5";
import styles from "./DeletePopUp.module.css";
import { User, useOrgUsers } from "../../../hooks/useUsers";
import { useState } from "react";
type DeletePopUpProps = {
closePopup: () => void;
refetchUsers: () => Promise<void>;
user: User;
};
const DeletePopUp = ({ closePopup, refetchUsers, user }: DeletePopUpProps) => {
const { deleteUser } = useOrgUsers();
const [isDeleting, setIsDeleting] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleDelete = async () => {
setIsDeleting(true);
setError(null);
try {
await deleteUser(user.id);
// Refresh the users list and close the popup
await refetchUsers();
closePopup();
} catch (error: any) {
setError(error.message || "Failed to delete user");
setIsDeleting(false);
}
};
return (
<div style={popupStyles.overlay}>
<div style={popupStyles.popup}>
<div className={styles.popupHeader}>
<p>Delete User...</p>
<button
className={styles.closeButton}
onClick={closePopup}><IoCloseOutline className={styles.closeIcon}/>
</button>
</div>
<div className={styles.horizontalLineLight}></div>
{error && <div className={styles.errorMessage}>{error}</div>}
<div className={styles.userInfo}>
<h2>{user.fullName || user.username}</h2>
<div className={styles.userInfoParagraph}>
<p>Email: {user.email}</p>
<p>Privilege: {user.privilege}</p>
</div>
</div>
<div className={styles.horizontalLineLight}></div>
<div className={styles.submitButtonDiv}>
<button
className={styles.submitButton}
onClick={handleDelete}
disabled={isDeleting}>
{isDeleting ? "Deleting..." : "Delete"}
</button>
</div>
</div>
</div>
)
}
const popupStyles: { overlay: React.CSSProperties; popup: React.CSSProperties } = {
overlay: {
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
},
popup: {
backgroundColor: "#fff",
borderRadius: "8px",
boxShadow: "0 5px 15px rgba(0,0,0,0.3)",
position: "relative"
}
};
export default DeletePopUp;

View file

@ -1,33 +0,0 @@
.deleteBtn {
background: none;
border: none;
cursor: pointer;
font-size: 18px;
color: #444;
height: 100;
}
.deleteBtn:hover {
color: var(--Brand-Green-Green, #3A8088);
}
.deleteBtn.disabled {
cursor: not-allowed;
opacity: 0.5;
background: none;
border: none;
cursor: pointer;
font-size: 18px;
color: #444;
}
.deleteBtn.disabled:hover {
color: #444;
}
.deleteBtnContainer {
display: flex;
align-items: center;
justify-content: center;
}

View file

@ -1,37 +0,0 @@
import { FaTrash } from "react-icons/fa";
import styles from "./MitgliederItemDelete.module.css";
import { useState } from "react";
import DeletePopUp from "./DeletePopUp";
import { User } from "../../../hooks/useUsers";
type MitgliederItemDeleteProps = {
user: User;
refetchUsers: () => Promise<void>;
isDisabled?: boolean;
};
const MitgliederItemDelete = ({ user, refetchUsers, isDisabled = false }: MitgliederItemDeleteProps) => {
const [deleteWindow, setDeleteWindow] = useState(false);
return (
<div className={styles.deleteBtnContainer}>
<button
onClick={() => !isDisabled && setDeleteWindow(true)}
className={`${styles.deleteBtn} ${isDisabled ? styles.disabled : ''}`}
disabled={isDisabled}
title={isDisabled ? "Cannot delete the only remaining user" : "Delete user"}>
<FaTrash />
</button>
{deleteWindow && (
<DeletePopUp
user={user}
closePopup={() => setDeleteWindow(false)}
refetchUsers={refetchUsers}
/>
)}
</div>
);
};
export default MitgliederItemDelete;

View file

@ -1,89 +0,0 @@
.popupHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
}
.popupHeader p {
font-size: 1.2rem;
font-weight: 600;
margin: 0;
}
.closeButton {
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.closeIcon {
font-size: 1.5rem;
color: #666;
}
.horizontalLineLight {
height: 1px;
background-color: #e0e0e0;
width: 100%;
margin: 0;
}
.form {
box-sizing: border-box;
max-width: 100%;
padding: 20px;
}
.formGroup {
margin-bottom: 15px;
box-sizing: border-box;
}
.formGroup label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.formGroup input,
.formGroup select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
}
.submitButtonDiv {
display: flex;
justify-content: flex-end;
padding: 15px 0 10px;
}
.submitButton {
background-color: #4c9aff;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
.submitButton:hover {
background-color: #3d8df5;
}
.userInfo h2 {
margin-top: 0;
margin-bottom: 10px;
}

View file

@ -1,152 +0,0 @@
import { IoCloseOutline } from "react-icons/io5";
import { useState } from "react";
import styles from "./EditPopUp.module.css";
import { User, useOrgUsers } from "../../../hooks/useUsers";
type EditPopUpProps = {
closePopup: () => void;
refetchUsers: () => Promise<void>;
user: User;
};
const EditPopUp = ({ closePopup, refetchUsers, user }: EditPopUpProps) => {
const { updateUser } = useOrgUsers();
const [formData, setFormData] = useState({
fullName: user.fullName || "",
email: user.email || "",
privilege: user.privilege || "",
username: user.username || ""
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
await updateUser(user.id, {
fullName: formData.fullName,
email: formData.email,
privilege: formData.privilege,
username: formData.username
});
// Refresh the users list and close the popup
await refetchUsers();
closePopup();
} catch (error: any) {
setError(error.message || "Failed to update user");
} finally {
setIsSubmitting(false);
}
};
return (
<div style={popupStyles.overlay}>
<div style={popupStyles.popup}>
<div className={styles.popupHeader}>
<p>Edit User</p>
<button
className={styles.closeButton}
onClick={closePopup}><IoCloseOutline className={styles.closeIcon}/>
</button>
</div>
<div className={styles.horizontalLineLight}></div>
{error && <div className={styles.errorMessage}>{error}</div>}
<form onSubmit={handleSubmit} className={styles.form}>
<div className={styles.formGroup}>
<label htmlFor="fullName">Name:</label>
<input
type="text"
id="fullName"
name="fullName"
value={formData.fullName}
onChange={handleChange}
required
/>
</div>
<div className={styles.formGroup}>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</div>
<div className={styles.formGroup}>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
required
/>
</div>
<div className={styles.formGroup}>
<label htmlFor="privilege">Privilege:</label>
<select
id="privilege"
name="privilege"
value={formData.privilege}
onChange={handleChange}
required
>
<option value="admin">admin</option>
<option value="user">user</option>
<option value="sysadmin">sysadmin</option>
</select>
</div>
<div className={styles.horizontalLineLight}></div>
<div className={styles.submitButtonDiv}>
<button
type="submit"
className={styles.submitButton}
disabled={isSubmitting}>
{isSubmitting ? "Updating..." : "Update"}
</button>
</div>
</form>
</div>
</div>
);
};
const popupStyles: { overlay: React.CSSProperties; popup: React.CSSProperties } = {
overlay: {
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0,0,0,0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
},
popup: {
backgroundColor: "#fff",
borderRadius: "8px",
boxShadow: "0 5px 15px rgba(0,0,0,0.3)",
position: "relative",
width: "400px",
maxWidth: "90%"
}
};
export default EditPopUp;

View file

@ -1,12 +0,0 @@
.editBtn {
background: none;
border: none;
cursor: pointer;
font-size: 18px;
color: #444;
height: 100;
}
.editBtn:hover {
color: #3d8df5;
}

View file

@ -1,36 +0,0 @@
import { FaEdit } from "react-icons/fa";
import { useState } from "react";
import styles from "./MitgliederItemEdit.module.css";
import EditPopUp from "./EditPopUp";
import { User } from "../../../hooks/useUsers";
type MitgliederItemEditProps = {
user: User;
refetchUsers: () => Promise<void>;
};
const MitgliederItemEdit = ({ user, refetchUsers }: MitgliederItemEditProps) => {
const [editWindow, setEditWindow] = useState(false);
return (
<div>
<button
onClick={() => setEditWindow(true)}
className={styles.editBtn}
title="Edit user"
>
<FaEdit />
</button>
{editWindow && (
<EditPopUp
user={user}
closePopup={() => setEditWindow(false)}
refetchUsers={refetchUsers}
/>
)}
</div>
);
};
export default MitgliederItemEdit;

View file

@ -0,0 +1,64 @@
/* Main table container */
.mitgliederTable {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 20px;
}
/* FormGenerator container */
.mitgliederFormGenerator {
flex: 1;
min-height: 0;
}
/* Error state styling */
.errorState {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
gap: 20px;
color: var(--color-text);
font-family: var(--font-family);
text-align: center;
}
.retryButton {
padding: 10px 20px;
border: 1px solid var(--color-primary);
border-radius: 20px;
background: var(--color-bg);
color: var(--color-text);
cursor: pointer;
transition: all 0.2s ease;
font-family: var(--font-family);
}
.retryButton:hover {
background: var(--color-primary);
color: var(--color-bg);
}
/* User-specific formatting */
.userName {
font-weight: 500;
color: var(--color-text);
}
.userFullName {
color: var(--color-text);
}
.userEmail {
color: var(--color-gray);
font-size: 0.9em;
}
.userLanguage {
color: var(--color-text);
font-size: 0.85em;
}

View file

@ -0,0 +1,50 @@
import { FormGenerator } from '../FormGenerator/FormGenerator';
import { useMitgliederLogic } from './mitgliederLogic';
import { MitgliederTableProps } from './mitgliederTypes';
import { useLanguage } from '../../contexts/LanguageContext';
import styles from './MitgliederTable.module.css';
function MitgliederTable({ className = '' }: MitgliederTableProps) {
const { t } = useLanguage();
const {
users,
loading,
error,
columns,
actions,
refetch
} = useMitgliederLogic();
if (error) {
return (
<div className={styles.errorState}>
<p>{t('users.error.loading', 'Error loading users:')} {error}</p>
<button onClick={refetch} className={styles.retryButton}>
{t('common.retry', 'Retry')}
</button>
</div>
);
}
return (
<div className={`${styles.mitgliederTable} ${className}`}>
<FormGenerator
data={users}
columns={columns}
title={t('users.title', 'Users')}
searchable={true}
filterable={true}
sortable={true}
resizable={true}
pagination={true}
pageSize={10}
selectable={false}
loading={loading}
actions={actions}
className={styles.mitgliederFormGenerator}
/>
</div>
);
}
export default MitgliederTable;

View file

@ -0,0 +1,3 @@
export { default as MitgliederTable } from './MitgliederTable';
export { useMitgliederLogic } from './mitgliederLogic';
export type * from './mitgliederTypes';

View file

@ -0,0 +1,107 @@
import React, { useMemo } from 'react';
import { useOrgUsers, User } from '../../hooks/useUsers';
import { useLanguage } from '../../contexts/LanguageContext';
import type {
MitgliederLogicReturn,
UserActionConfig,
UserColumnConfig
} from './mitgliederTypes';
export function useMitgliederLogic(): MitgliederLogicReturn {
const { users, loading, error, refetch, deleteUser } = useOrgUsers();
const { t } = useLanguage();
// Configure columns for the users table
const columns: UserColumnConfig[] = useMemo(() => [
{
key: 'username',
label: t('users.column.username', 'Username'),
type: 'string',
width: 150,
minWidth: 120,
maxWidth: 200,
sortable: true,
filterable: true,
searchable: true,
formatter: (value: string | undefined) => (
<span className="userName">
{value || t('users.noUsername', 'No Username')}
</span>
)
},
{
key: 'fullName',
label: t('users.column.name', 'Name'),
type: 'string',
width: 200,
minWidth: 150,
maxWidth: 300,
sortable: true,
filterable: true,
searchable: true,
formatter: (value: string | undefined) => (
<span className="userFullName">
{value || t('users.noName', 'No Name')}
</span>
)
},
{
key: 'email',
label: t('users.column.email', 'Email'),
type: 'string',
width: 250,
minWidth: 200,
maxWidth: 350,
sortable: true,
filterable: true,
searchable: true,
formatter: (value: string | undefined) => (
<span className="userEmail">
{value || t('users.noEmail', 'No Email')}
</span>
)
},
{
key: 'language',
label: t('users.column.language', 'Language'),
type: 'enum',
width: 120,
minWidth: 100,
maxWidth: 150,
sortable: true,
filterable: true,
filterOptions: ['en', 'de', 'fr'],
formatter: (value: string | undefined) => {
const languageMap: Record<string, string> = {
'en': t('language.english', 'English'),
'de': t('language.german', 'Deutsch'),
'fr': t('language.french', 'Français')
};
return (
<span className="userLanguage">
{value ? languageMap[value] || value : t('users.noLanguage', 'No Language')}
</span>
);
}
}
], [t]);
// Configure action buttons (empty for now)
const actions: UserActionConfig[] = useMemo(() => [], []);
return {
// Data
users,
loading,
error,
// Refetch function
refetch,
// Additional data for rendering
columns,
actions
};
}

View file

@ -0,0 +1,44 @@
import React from 'react';
import { User } from '../../hooks/useUsers';
// Props for the MitgliederTable component
export interface MitgliederTableProps {
className?: string;
}
// Action configuration for user actions
export interface UserActionConfig {
label: string;
icon: (row: User) => React.ReactElement;
onClick: (row: User) => void;
}
// Column configuration for the users table
export interface UserColumnConfig {
key: string;
label: string;
type: 'string' | 'number' | 'date' | 'boolean' | 'enum';
width: number;
minWidth: number;
maxWidth: number;
sortable: boolean;
filterable: boolean;
searchable?: boolean;
filterOptions?: string[];
formatter: (value: any, row?: any) => React.ReactElement | string;
}
// Return type for the mitglieder logic hook
export interface MitgliederLogicReturn {
// Data
users: User[];
loading: boolean;
error: string | null;
// Refetch function
refetch: () => Promise<void>;
// Additional data for rendering
columns: UserColumnConfig[];
actions: UserActionConfig[];
}

View file

@ -6,6 +6,7 @@ import { MdOutlineWorkOutline } from 'react-icons/md';
import { LuWorkflow, LuTicket } from "react-icons/lu"; import { LuWorkflow, LuTicket } from "react-icons/lu";
import { GoGear } from "react-icons/go"; import { GoGear } from "react-icons/go";
import { FaPlug, FaRegFileAlt, FaShare } from "react-icons/fa"; import { FaPlug, FaRegFileAlt, FaShare } from "react-icons/fa";
import { LuMessageSquareText } from "react-icons/lu";
// Lazy load components for better performance // Lazy load components for better performance
const Dashboard = lazy(() => import('../../pages/Home/Dashboard')); const Dashboard = lazy(() => import('../../pages/Home/Dashboard'));
@ -15,6 +16,7 @@ const Connections = lazy(() => import('../../pages/Home/Connections'));
const Workflows = lazy(() => import('../../pages/Home/Workflows')); const Workflows = lazy(() => import('../../pages/Home/Workflows'));
const Einstellungen = lazy(() => import('../../pages/Home/Einstellungen')); const Einstellungen = lazy(() => import('../../pages/Home/Einstellungen'));
const TestSharepoint = lazy(() => import('../../pages/Home/TestSharepoint')); const TestSharepoint = lazy(() => import('../../pages/Home/TestSharepoint'));
const Prompts = lazy(() => import('../../pages/Home/Prompts'));
// Page configuration with caching and lifecycle settings // Page configuration with caching and lifecycle settings
export const pageConfigs: PageConfig[] = [ export const pageConfigs: PageConfig[] = [
@ -61,6 +63,28 @@ export const pageConfigs: PageConfig[] = [
if (import.meta.env.DEV) console.log('Dateien unloaded - cleanup file references'); if (import.meta.env.DEV) console.log('Dateien unloaded - cleanup file references');
} }
}, },
{
path: 'prompts',
component: Prompts,
persistent: false,
preload: true,
moduleEnabled: true,
// Sidebar properties
id: '4',
name: 'Prompts',
icon: LuMessageSquareText ,
order: 3,
showInSidebar: true,
onActivate: async () => {
if (import.meta.env.DEV) console.log('Prompts activated');
},
onLoad: async () => {
if (import.meta.env.DEV) console.log('Prompts loaded - can initialize prompts here');
},
onUnload: async () => {
if (import.meta.env.DEV) console.log('Prompts unloaded - cleanup prompts references');
}
},
{ {
path: 'team-bereich', path: 'team-bereich',
component: TeamBereich, component: TeamBereich,
@ -103,7 +127,7 @@ export const pageConfigs: PageConfig[] = [
preload: true, // Preload workflows for better performance preload: true, // Preload workflows for better performance
moduleEnabled: true, moduleEnabled: true,
// Sidebar properties // Sidebar properties
id: '4', id: '6',
name: 'Workflows', name: 'Workflows',
icon: LuWorkflow, icon: LuWorkflow,
order: 3, order: 3,

View file

@ -44,6 +44,35 @@
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1); box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
} }
/* Textarea styling */
.fieldTextarea {
width: 100%;
padding: 12px 12px 8px 12px;
border: 1px solid var(--color-primary);
border-radius: 25px;
font-size: 14px;
transition: all 0.2s ease;
background-color: var(--color-bg);
box-sizing: border-box;
color: var(--color-text);
font-family: inherit;
line-height: 1.5;
overflow-y: auto;
resize: vertical;
min-height: 4em;
max-height: 8em;
}
.fieldTextarea:focus {
outline: none;
border-color: var(--color-secondary);
}
.fieldTextarea.fieldError {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
/* Floating label styles */ /* Floating label styles */
.label { .label {
position: absolute; position: absolute;

View file

@ -5,13 +5,15 @@ import styles from './EditForm.module.css';
export interface EditFieldConfig { export interface EditFieldConfig {
key: string; key: string;
label: string; label: string;
type: 'string' | 'email' | 'date' | 'enum' | 'boolean' | 'readonly'; type: 'string' | 'email' | 'date' | 'enum' | 'boolean' | 'readonly' | 'textarea';
editable: boolean; editable: boolean;
required?: boolean; required?: boolean;
options?: string[]; // For enum types options?: string[]; // For enum types
formatter?: (value: any) => string; // For display formatting formatter?: (value: any) => string; // For display formatting
validator?: (value: any) => string | null; // Returns error message or null validator?: (value: any) => string | null; // Returns error message or null
placeholder?: string; placeholder?: string;
minRows?: number; // For textarea types
maxRows?: number; // For textarea types
} }
// EditForm props // EditForm props
@ -46,7 +48,29 @@ export function EditForm<T extends Record<string, any>>({
setEditedData({ ...data }); setEditedData({ ...data });
setErrors({}); setErrors({});
setFieldFocused({}); setFieldFocused({});
}, [data]);
// Initialize textarea heights for textarea fields
setTimeout(() => {
fields.forEach(field => {
if (field.type === 'textarea') {
const textarea = document.querySelector(`textarea[name="${field.key}"]`) as HTMLTextAreaElement;
if (textarea) {
const minRows = field.minRows || 4;
const maxRows = field.maxRows || 8;
textarea.style.height = 'auto';
const newHeight = Math.max(
minRows * 1.5 * 16,
Math.min(
textarea.scrollHeight,
maxRows * 1.5 * 16
)
);
textarea.style.height = `${newHeight}px`;
}
}
});
}, 0);
}, [data, fields]);
// Handle field focus // Handle field focus
const handleFieldFocus = (fieldKey: string, focused: boolean) => { const handleFieldFocus = (fieldKey: string, focused: boolean) => {
@ -194,6 +218,44 @@ export function EditForm<T extends Record<string, any>>({
); );
} }
// Handle textarea type
if (field.type === 'textarea') {
const minRows = field.minRows || 4;
const maxRows = field.maxRows || 8;
return (
<div className={styles.floatingLabelInput} key={field.key}>
<textarea
name={field.key}
value={value || ''}
onChange={(e) => {
handleFieldChange(field.key, e.target.value);
// Auto-resize textarea
const textarea = e.target;
textarea.style.height = 'auto';
const newHeight = Math.max(
minRows * 1.5 * 16, // minRows * line-height * font-size
Math.min(
textarea.scrollHeight,
maxRows * 1.5 * 16 // maxRows * line-height * font-size
)
);
textarea.style.height = `${newHeight}px`;
}}
onFocus={() => handleFieldFocus(field.key, true)}
onBlur={() => handleFieldFocus(field.key, false)}
className={`${styles.fieldTextarea} ${hasError ? styles.fieldError : ''}`}
rows={minRows}
/>
<label className={getLabelClass(field.key, value)}>
{field.label}
{field.required && <span className={styles.required}>*</span>}
</label>
{hasError && <span className={styles.errorText}>{hasError}</span>}
</div>
);
}
// Default to text input for string, email, date types // Default to text input for string, email, date types
const inputType = field.type === 'email' ? 'email' : const inputType = field.type === 'email' ? 'email' :
field.type === 'date' ? 'date' : 'text'; field.type === 'date' ? 'date' : 'text';

View file

@ -0,0 +1,76 @@
.promptsTable {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.promptsFormGenerator {
flex: 1;
height: 100%;
}
/* Error state styling */
.errorState {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;
color: var(--color-error, #dc3545);
background-color: var(--color-error-bg, #f8d7da);
border: 1px solid var(--color-error-border, #f5c6cb);
border-radius: 8px;
margin: 1rem;
}
.retryButton {
padding: 0.5rem 1rem;
background-color: var(--color-primary, #007bff);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-top: 1rem;
transition: background-color 0.2s ease;
}
.retryButton:hover {
background-color: var(--color-primary-dark, #0056b3);
}
/* Prompt-specific styling */
.promptName {
font-weight: 500;
color: var(--color-text);
}
.promptContent {
color: var(--color-text);
line-height: 1.4;
max-width: 100%;
word-wrap: break-word;
}
/* Responsive design */
@media (max-width: 768px) {
.promptName {
font-size: 0.9em;
}
.promptContent {
font-size: 0.85em;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.promptName {
color: var(--color-text);
}
.promptContent {
color: var(--color-text);
}
}

View file

@ -0,0 +1,82 @@
import { FormGenerator } from '../FormGenerator/FormGenerator';
import { Popup } from '../Popup/Popup';
import { EditForm } from '../Popup/EditForm';
import { usePromptsLogic } from './promptsLogic';
import { PromptsTableProps } from './promptsTypes';
import { useLanguage } from '../../contexts/LanguageContext';
import styles from './PromptsTable.module.css';
function PromptsTable({ className = '' }: PromptsTableProps) {
const { t } = useLanguage();
const {
prompts,
loading,
error,
columns,
actions,
handleDeleteSingle,
handleDeleteMultiple,
editModalOpen,
editingPrompt,
editPromptFields,
handleSavePrompt,
handleCancelEdit,
refetch
} = usePromptsLogic();
if (error) {
return (
<div className={styles.errorState}>
<p>{t('prompts.error.loading', 'Error loading prompts:')} {error}</p>
<button onClick={refetch} className={styles.retryButton}>
{t('common.retry', 'Retry')}
</button>
</div>
);
}
return (
<div className={`${styles.promptsTable} ${className}`}>
<FormGenerator
data={prompts}
columns={columns}
title={t('prompts.title', 'Prompts')}
searchable={true}
filterable={true}
sortable={true}
resizable={true}
pagination={true}
pageSize={10}
selectable={true}
loading={loading}
actions={actions}
onDelete={handleDeleteSingle}
onDeleteMultiple={handleDeleteMultiple}
className={styles.promptsFormGenerator}
/>
{/* Edit Modal */}
<Popup
isOpen={editModalOpen}
title={t('prompts.modal.edit.title', 'Edit Prompt')}
onClose={handleCancelEdit}
size="large"
>
{editingPrompt && (
<EditForm
data={editingPrompt}
fields={editPromptFields}
onSave={handleSavePrompt}
onCancel={handleCancelEdit}
saveButtonText={t('prompts.modal.edit.save', 'Save Changes')}
cancelButtonText={t('common.cancel', 'Cancel')}
/>
)}
</Popup>
</div>
);
}
export default PromptsTable;

View file

@ -0,0 +1,3 @@
export { default as PromptsTable } from './PromptsTable';
export { usePromptsLogic } from './promptsLogic';
export type * from './promptsTypes';

View file

@ -0,0 +1,301 @@
import React, { useState, useEffect, useMemo } from 'react';
import { IoIosTrash, IoIosCopy } from 'react-icons/io';
import { MdModeEdit } from 'react-icons/md';
import { usePrompts, usePromptOperations, Prompt } from '../../hooks/usePrompts';
import { useLanguage } from '../../contexts/LanguageContext';
import type { EditFieldConfig } from '../Popup/EditForm';
import type {
PromptsLogicReturn,
PromptActionConfig,
PromptColumnConfig
} from './promptsTypes';
export function usePromptsLogic(): PromptsLogicReturn {
const { prompts, loading, error, refetch } = usePrompts();
const { t } = useLanguage();
const {
handlePromptDelete,
handlePromptUpdate,
deletingPrompts
} = usePromptOperations();
// Edit modal state
const [editModalOpen, setEditModalOpen] = useState(false);
const [editingPrompt, setEditingPrompt] = useState<Prompt | null>(null);
// Configure edit fields for prompt editing
const editPromptFields: EditFieldConfig[] = useMemo(() => [
{
key: 'name',
label: t('prompts.field.name', 'Prompt Name'),
type: 'string',
editable: true,
required: true,
validator: (value: string) => {
if (!value || value.trim() === '') {
return t('prompts.validation.nameRequired', 'Prompt name cannot be empty');
}
if (value.length > 100) {
return t('prompts.validation.nameTooLong', 'Prompt name cannot exceed 100 characters');
}
return null;
}
},
{
key: 'content',
label: t('prompts.field.content', 'Prompt Content'),
type: 'textarea',
editable: true,
required: true,
minRows: 4,
maxRows: 8,
validator: (value: string) => {
if (!value || value.trim() === '') {
return t('prompts.validation.contentRequired', 'Prompt content cannot be empty');
}
if (value.length > 10000) {
return t('prompts.validation.contentTooLong', 'Prompt content cannot exceed 10,000 characters');
}
return null;
}
}
], [t]);
// Configure columns for the prompts table
const columns: PromptColumnConfig[] = useMemo(() => [
{
key: 'name',
label: t('prompts.column.name', 'Name'),
type: 'string',
width: 200,
minWidth: 150,
maxWidth: 300,
sortable: true,
filterable: true,
searchable: true,
formatter: (value: string | undefined) => (
<span className="promptName">
{value || t('prompts.unnamed', 'Unnamed')}
</span>
)
},
{
key: 'content',
label: t('prompts.column.content', 'Content'),
type: 'string',
width: 400,
minWidth: 200,
maxWidth: 600,
sortable: true,
filterable: true,
searchable: true,
formatter: (value: string | undefined) => (
<span className="promptContent" title={value}>
{value && value.length > 100 ? `${value.substring(0, 100)}...` : value || '-'}
</span>
)
}
], [t]);
// Handle prompt actions
const handleDeletePrompt = async (prompt: Prompt) => {
const promptName = prompt.name || prompt.id;
if (window.confirm(t('prompts.delete.confirm', 'Are you sure you want to delete "{name}"?').replace('{name}', promptName))) {
const success = await handlePromptDelete(prompt.id);
if (success) {
refetch(); // Refresh the prompts list
}
}
};
// Handle single prompt deletion for bulk delete
const handleDeleteSingle = async (prompt: Prompt) => {
const promptName = prompt.name || prompt.id;
if (window.confirm(t('prompts.delete.confirm', 'Are you sure you want to delete "{name}"?').replace('{name}', promptName))) {
const success = await handlePromptDelete(prompt.id);
if (success) {
refetch(); // Refresh the prompts list
} else {
console.error('Delete failed for prompt:', prompt.id);
}
}
};
// Handle multiple prompt deletion
const handleDeleteMultiple = async (promptsToDelete: Prompt[]) => {
const promptCount = promptsToDelete.length;
if (window.confirm(t('prompts.delete.confirmMultiple', 'Are you sure you want to delete {count} prompts?').replace('{count}', promptCount.toString()))) {
// Start all delete operations simultaneously
const deletePromises = promptsToDelete.map(async (prompt) => {
try {
const success = await handlePromptDelete(prompt.id);
return { promptId: prompt.id, success };
} catch (error) {
console.error('Failed to delete prompt:', prompt.id, error);
return { promptId: prompt.id, success: false };
}
});
// Wait for all deletions to complete
const results = await Promise.all(deletePromises);
// Check if any deletions failed
const failedDeletions = results.filter(result => !result.success);
if (failedDeletions.length > 0) {
console.error('Some prompt deletions failed:', failedDeletions);
}
// Refresh the prompt list regardless of individual failures
refetch();
}
};
// Handle edit prompt
const handleEditPrompt = (prompt: Prompt) => {
setEditingPrompt(prompt);
setEditModalOpen(true);
};
// Handle save prompt
const handleSavePrompt = async (updatedPrompt: Prompt) => {
if (!editingPrompt) return;
try {
// Call API to update prompt - backend requires full Prompt object
const updateData = {
id: editingPrompt.id,
name: updatedPrompt.name,
content: updatedPrompt.content,
mandateId: editingPrompt.mandateId
};
const result = await handlePromptUpdate(editingPrompt.id, updateData);
if (result.success) {
// Close modal
setEditModalOpen(false);
setEditingPrompt(null);
// Refresh prompt list
await refetch();
// Notify other components that prompts have been updated
window.dispatchEvent(new CustomEvent('promptUpdated', {
detail: { promptId: editingPrompt.id, newName: updatedPrompt.name }
}));
} else {
console.error('Failed to update prompt:', result.error);
// TODO: Show error message to user
}
} catch (error) {
console.error('Failed to update prompt:', error);
// TODO: Show error message to user
}
};
// Handle cancel edit
const handleCancelEdit = () => {
setEditModalOpen(false);
setEditingPrompt(null);
};
// Handle copy prompt
const handleCopyPrompt = async (prompt: Prompt) => {
try {
// Use the modern Clipboard API if available
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(prompt.content);
} else {
// Fallback for older browsers or non-secure contexts
const textArea = document.createElement('textarea');
textArea.value = prompt.content;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
// Show success feedback
const promptName = prompt.name || t('prompts.unnamed', 'Unnamed');
console.log(`Copied content of "${promptName}" to clipboard`);
// Optional: You could add a toast notification here in the future
// showToast(t('prompts.copy.success', 'Prompt content copied to clipboard'), 'success');
} catch (error) {
console.error('Failed to copy prompt content:', error);
// Optional: You could add a toast notification here in the future
// showToast(t('prompts.copy.error', 'Failed to copy prompt content'), 'error');
}
};
// Configure action buttons
const actions: PromptActionConfig[] = useMemo(() => [
{
label: t('prompts.action.edit', 'Edit'),
icon: (_row: Prompt) => {
return <MdModeEdit />;
},
onClick: (row: Prompt) => {
handleEditPrompt(row);
}
},
{
label: t('prompts.action.copy', 'Copy'),
icon: (_row: Prompt) => {
return <IoIosCopy />;
},
onClick: (row: Prompt) => {
handleCopyPrompt(row);
}
},
{
label: t('prompts.action.delete', 'Delete'),
icon: (_row: Prompt) => {
return <IoIosTrash />;
},
onClick: (row: Prompt) => {
if (!deletingPrompts.has(row.id)) {
handleDeletePrompt(row);
}
}
},
], [t, deletingPrompts, handleDeletePrompt, handleEditPrompt, handleCopyPrompt]);
return {
// Data
prompts,
loading,
error,
// Actions
handleDeleteSingle,
handleDeleteMultiple,
handleEditPrompt,
handleCopyPrompt,
// Refetch function
refetch,
// Additional data for rendering
columns,
actions,
// Edit modal state
editModalOpen,
editingPrompt,
editPromptFields,
// Edit modal actions
handleSavePrompt,
handleCancelEdit
};
}

View file

@ -0,0 +1,66 @@
import React from 'react';
// Prompt interface based on the API response
export interface Prompt {
id: string;
mandateId: string;
content: string;
name: string;
}
// Props for the PromptsTable component
export interface PromptsTableProps {
className?: string;
}
// Action configuration for prompt actions
export interface PromptActionConfig {
label: string;
icon: (row: Prompt) => React.ReactElement;
onClick: (row: Prompt) => void;
}
// Column configuration for the prompts table
export interface PromptColumnConfig {
key: string;
label: string;
type: 'string' | 'number' | 'date' | 'boolean' | 'enum';
width: number;
minWidth: number;
maxWidth: number;
sortable: boolean;
filterable: boolean;
searchable?: boolean;
filterOptions?: string[];
formatter: (value: any, row?: any) => React.ReactElement | string;
}
// Return type for the prompts logic hook
export interface PromptsLogicReturn {
// Data
prompts: Prompt[];
loading: boolean;
error: string | null;
// Actions
handleDeleteSingle: (prompt: Prompt) => Promise<void>;
handleDeleteMultiple: (prompts: Prompt[]) => Promise<void>;
handleEditPrompt: (prompt: Prompt) => void;
handleCopyPrompt: (prompt: Prompt) => void;
// Refetch function
refetch: () => Promise<void>;
// Additional data for rendering
columns: PromptColumnConfig[];
actions: PromptActionConfig[];
// Edit modal state
editModalOpen: boolean;
editingPrompt: Prompt | null;
editPromptFields: any[];
// Edit modal actions
handleSavePrompt: (updatedPrompt: Prompt) => Promise<void>;
handleCancelEdit: () => void;
}

View file

@ -62,7 +62,8 @@ const Sidebar: React.FC<SidebarProps> = ({ data }) => {
} }
const SidebarWithData: React.FC = () => { const SidebarWithData: React.FC = () => {
return <Sidebar data={useSidebarFromPageConfigs()} />; const sidebarData = useSidebarFromPageConfigs();
return <Sidebar data={sidebarData} />;
}; };
export default SidebarWithData; export default SidebarWithData;

View file

@ -4,7 +4,23 @@ import api from '../api';
// Generic API error handling // Generic API error handling
export function formatApiError(error: any, defaultMessage: string): string { export function formatApiError(error: any, defaultMessage: string): string {
if (error.response) { if (error.response) {
return error.response.data?.detail || error.response.data?.message || defaultMessage; const data = error.response.data;
// Handle FastAPI validation errors (detail is an array)
if (data?.detail && Array.isArray(data.detail)) {
return data.detail.map((err: any) => {
if (typeof err === 'string') return err;
if (err.msg) return `${err.loc?.join('.') || 'field'}: ${err.msg}`;
return JSON.stringify(err);
}).join(', ');
}
// Handle other error formats
if (typeof data?.detail === 'string') return data.detail;
if (typeof data?.message === 'string') return data.message;
if (typeof data === 'string') return data;
return defaultMessage;
} else if (error.request) { } else if (error.request) {
return 'Keine Antwort vom Server erhalten'; return 'Keine Antwort vom Server erhalten';
} else { } else {
@ -48,7 +64,7 @@ export function useApiRequest<RequestData = any, ResponseData = any>() {
} catch (error: any) { } catch (error: any) {
const errorMessage = formatApiError(error, `Fehler bei ${method.toUpperCase()} ${url}`); const errorMessage = formatApiError(error, `Fehler bei ${method.toUpperCase()} ${url}`);
setError(errorMessage); setError(errorMessage);
throw new Error(errorMessage); throw new Error(String(errorMessage)); // Ensure it's a string
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }

View file

@ -1,33 +1,16 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
// Prompt interfaces // Prompt interface based on the API response
export interface Prompt { export interface Prompt {
id: string; id: string;
mandateId: string; mandateId: string;
name: string;
content: string; content: string;
createdAt?: string; name: string;
isShared?: boolean;
} }
// User interface for sharing // Prompts list hook
export interface User { export function usePrompts() {
id: number;
username: string;
fullName?: string;
email?: string;
}
// Share request interface
export interface ShareRequest {
userIds: number[];
message?: string;
title?: string;
}
// Simple prompts list hook for dropdown
export function useSimplePrompts() {
const [prompts, setPrompts] = useState<Prompt[]>([]); const [prompts, setPrompts] = useState<Prompt[]>([]);
const { request, isLoading: loading, error } = useApiRequest<null, Prompt[]>(); const { request, isLoading: loading, error } = useApiRequest<null, Prompt[]>();
@ -51,77 +34,16 @@ export function useSimplePrompts() {
return { prompts, loading, error, refetch: fetchPrompts }; return { prompts, loading, error, refetch: fetchPrompts };
} }
// Prompts list hook
export function usePrompts() {
const [prompts, setPrompts] = useState<Prompt[]>([]);
const { request, isLoading: loading, error } = useApiRequest<null, any>();
const fetchPrompts = async () => {
try {
const data = await request({
url: '/api/prompts/all-with-shared',
method: 'get'
});
// Combine owned and shared prompts into a single list
const allPrompts = [
...(data.owned || []),
...(data.sharedWithMe || [])
];
setPrompts(allPrompts);
} catch (error) {
// Error is already handled by useApiRequest
}
};
useEffect(() => {
fetchPrompts();
}, []);
return { prompts, loading, error, refetch: fetchPrompts };
}
// Users hook for sharing functionality
export function useUsers() {
const [users, setUsers] = useState<User[]>([]);
const { request, isLoading: loading, error } = useApiRequest<null, User[]>();
const fetchUsers = async () => {
try {
const data = await request({
url: '/api/users',
method: 'get'
});
// Filter out users that shouldn't be shown for sharing
const shareableUsers = data.filter(user => !(user as any).disabled);
setUsers(shareableUsers);
} catch (error) {
// Error is already handled by useApiRequest
}
};
useEffect(() => {
fetchUsers();
}, []);
return { users, loading, error, refetch: fetchUsers };
}
// Prompt operations hook // Prompt operations hook
export function usePromptOperations() { export function usePromptOperations() {
const [deletingPrompts, setDeletingPrompts] = useState<Set<number>>(new Set()); const [deletingPrompts, setDeletingPrompts] = useState<Set<string>>(new Set());
const [creatingPrompt, setCreatingPrompt] = useState(false); const [creatingPrompt, setCreatingPrompt] = useState(false);
const [updatingPrompts, setUpdatingPrompts] = useState<Set<number>>(new Set());
const [sharingPrompts, setSharingPrompts] = useState<Set<number>>(new Set());
const { request, error: apiError, isLoading } = useApiRequest(); const { request, error: apiError, isLoading } = useApiRequest();
const [deleteError, setDeleteError] = useState<string | null>(null); const [deleteError, setDeleteError] = useState<string | null>(null);
const [createError, setCreateError] = useState<string | null>(null); const [createError, setCreateError] = useState<string | null>(null);
const [updateError, setUpdateError] = useState<string | null>(null); const [updateError, setUpdateError] = useState<string | null>(null);
const [shareError, setShareError] = useState<string | null>(null);
const handlePromptDelete = async (promptId: number) => { const handlePromptDelete = async (promptId: string) => {
setDeleteError(null); setDeleteError(null);
setDeletingPrompts(prev => new Set(prev).add(promptId)); setDeletingPrompts(prev => new Set(prev).add(promptId));
@ -146,7 +68,7 @@ export function usePromptOperations() {
} }
}; };
const handlePromptCreate = async (promptData: { name: string; content: string }) => { const handlePromptCreate = async (promptData: { name: string; content: string; mandateId: string }) => {
setCreateError(null); setCreateError(null);
setCreatingPrompt(true); setCreatingPrompt(true);
@ -166,67 +88,32 @@ export function usePromptOperations() {
} }
}; };
const handlePromptUpdate = async (promptId: number, promptData: { name?: string; content?: string }) => { const handlePromptUpdate = async (promptId: string, updateData: { id: string; name: string; content: string; mandateId: string }) => {
setUpdateError(null); setUpdateError(null); // Reuse update error state for update operations
setUpdatingPrompts(prev => new Set(prev).add(promptId));
try { try {
const updatedPrompt = await request({ const updatedPrompt = await request({
url: `/api/prompts/${promptId}`, url: `/api/prompts/${promptId}`,
method: 'put', method: 'put',
data: promptData data: updateData
}); });
return { success: true, promptData: updatedPrompt }; return { success: true, promptData: updatedPrompt };
} catch (error: any) { } catch (error: any) {
setUpdateError(error.message); setUpdateError(error.message);
return { success: false, error: error.message }; return { success: false, error: error.message };
} finally {
setUpdatingPrompts(prev => {
const newSet = new Set(prev);
newSet.delete(promptId);
return newSet;
});
}
};
const handlePromptShare = async (promptId: number, shareData: ShareRequest) => {
setShareError(null);
setSharingPrompts(prev => new Set(prev).add(promptId));
try {
const shareResult = await request({
url: `/api/prompts/${promptId}/share`,
method: 'post',
data: shareData
});
return { success: true, shareData: shareResult };
} catch (error: any) {
setShareError(error.message);
return { success: false, error: error.message };
} finally {
setSharingPrompts(prev => {
const newSet = new Set(prev);
newSet.delete(promptId);
return newSet;
});
} }
}; };
return { return {
deletingPrompts, deletingPrompts,
creatingPrompt, creatingPrompt,
updatingPrompts,
sharingPrompts,
deleteError, deleteError,
createError, createError,
updateError, updateError,
shareError,
handlePromptDelete, handlePromptDelete,
handlePromptCreate, handlePromptCreate,
handlePromptUpdate, handlePromptUpdate,
handlePromptShare,
isLoading isLoading
}; };
} }

View file

@ -102,7 +102,7 @@ export function useOrgUsers() {
const fetchUsers = async () => { const fetchUsers = async () => {
try { try {
const data = await request({ const data = await request({
url: '/api/users', url: '/api/users/',
method: 'get' method: 'get'
}); });
setUsers(data); setUsers(data);
@ -111,7 +111,7 @@ export function useOrgUsers() {
} }
}; };
const updateUser = async (userId: number, userData: UserUpdateData) => { const updateUser = async (userId: string, userData: UserUpdateData) => {
await request({ await request({
url: `/api/users/${userId}`, url: `/api/users/${userId}`,
method: 'put', method: 'put',
@ -120,7 +120,7 @@ export function useOrgUsers() {
await fetchUsers(); // Refresh the list after update await fetchUsers(); // Refresh the list after update
}; };
const deleteUser = async (userId: number) => { const deleteUser = async (userId: string) => {
await request({ await request({
url: `/api/users/${userId}`, url: `/api/users/${userId}`,
method: 'delete' method: 'delete'
@ -128,7 +128,7 @@ export function useOrgUsers() {
await fetchUsers(); // Refresh the list after deletion await fetchUsers(); // Refresh the list after deletion
}; };
const getUser = async (userId: number): Promise<User> => { const getUser = async (userId: string): Promise<User> => {
return await request({ return await request({
url: `/api/users/${userId}`, url: `/api/users/${userId}`,
method: 'get' method: 'get'
@ -154,14 +154,14 @@ export function useOrgUsers() {
export function useUser() { export function useUser() {
const { request, isLoading, error } = useApiRequest(); const { request, isLoading, error } = useApiRequest();
const getUser = async (userId: number): Promise<User> => { const getUser = async (userId: string): Promise<User> => {
return await request({ return await request({
url: `/api/users/${userId}`, url: `/api/users/${userId}`,
method: 'get' method: 'get'
}); });
}; };
const updateUser = async (userId: number, userData: UserUpdateData): Promise<User> => { const updateUser = async (userId: string, userData: UserUpdateData): Promise<User> => {
return await request({ return await request({
url: `/api/users/${userId}`, url: `/api/users/${userId}`,
method: 'put', method: 'put',
@ -169,7 +169,7 @@ export function useUser() {
}); });
}; };
const deleteUser = async (userId: number): Promise<void> => { const deleteUser = async (userId: string): Promise<void> => {
await request({ await request({
url: `/api/users/${userId}`, url: `/api/users/${userId}`,
method: 'delete' method: 'delete'

View file

@ -6,6 +6,7 @@ export default {
'nav.connections': 'Verbindungen', 'nav.connections': 'Verbindungen',
'nav.workflows': 'Workflows', 'nav.workflows': 'Workflows',
'nav.settings': 'Einstellungen', 'nav.settings': 'Einstellungen',
'nav.testSharepoint': 'SharePoint Test',
// Settings page // Settings page
'settings.title': 'Einstellungen', 'settings.title': 'Einstellungen',
@ -35,6 +36,7 @@ export default {
'common.delete': 'Löschen', 'common.delete': 'Löschen',
'common.edit': 'Bearbeiten', 'common.edit': 'Bearbeiten',
'common.close': 'Schließen', 'common.close': 'Schließen',
'common.retry': 'Wiederholen',
// Auth // Auth
'auth.login': 'Anmelden', 'auth.login': 'Anmelden',
@ -108,6 +110,7 @@ export default {
'connections.not_available': 'Nicht verfügbar', 'connections.not_available': 'Nicht verfügbar',
'connections.invalid_date': 'Ungültiges Datum', 'connections.invalid_date': 'Ungültiges Datum',
'connections.confirm_delete': 'Sind Sie sicher, dass Sie die {service} Verbindung löschen möchten?', 'connections.confirm_delete': 'Sind Sie sicher, dass Sie die {service} Verbindung löschen möchten?',
'connections.confirm_delete_multiple': 'Sind Sie sicher, dass Sie {count} Verbindungen löschen möchten?',
// Connection Fields // Connection Fields
'connections.field.service': 'Service', 'connections.field.service': 'Service',
@ -388,6 +391,51 @@ export default {
'formgen.pagination.prev': 'Vorherige Seite', 'formgen.pagination.prev': 'Vorherige Seite',
'formgen.pagination.next': 'Nächste Seite', 'formgen.pagination.next': 'Nächste Seite',
'formgen.pagination.last': 'Letzte Seite', 'formgen.pagination.last': 'Letzte Seite',
'formgen.delete.multiple': 'Löschen ({count})',
'formgen.delete.single': 'Löschen',
'formgen.delete.confirm': 'Sind Sie sicher, dass Sie die {count} ausgewählten Elemente löschen möchten?',
'formgen.delete.confirm_multiple': 'Sind Sie sicher, dass Sie die {count} ausgewählten Elemente löschen möchten?',
'formgen.delete.confirm_single': 'Sind Sie sicher, dass Sie das ausgewählte Element löschen möchten?',
// Prompts
'prompts.title': 'Prompts',
'prompts.addNew': 'Prompt hinzufügen',
'prompts.creating': 'Erstellen...',
'prompts.column.name': 'Name',
'prompts.column.content': 'Inhalt',
'prompts.unnamed': 'Unbenannt',
'prompts.action.edit': 'Bearbeiten',
'prompts.action.copy': 'Kopieren',
'prompts.action.delete': 'Löschen',
'prompts.delete.confirm': 'Sind Sie sicher, dass Sie "{name}" löschen möchten?',
'prompts.delete.confirmMultiple': 'Sind Sie sicher, dass Sie {count} Prompts löschen möchten?',
'prompts.field.name': 'Prompt-Name',
'prompts.field.content': 'Prompt-Inhalt',
'prompts.validation.nameRequired': 'Prompt-Name darf nicht leer sein',
'prompts.validation.nameTooLong': 'Prompt-Name darf 100 Zeichen nicht überschreiten',
'prompts.validation.contentRequired': 'Prompt-Inhalt darf nicht leer sein',
'prompts.validation.contentTooLong': 'Prompt-Inhalt darf 10.000 Zeichen nicht überschreiten',
'prompts.error.loading': 'Fehler beim Laden der Prompts:',
'prompts.modal.edit.title': 'Prompt bearbeiten',
'prompts.modal.edit.save': 'Änderungen speichern',
'prompts.modal.create.title': 'Neuen Prompt erstellen',
'prompts.modal.create.save': 'Prompt erstellen',
// Users/Members
'users.title': 'Benutzer',
'users.column.username': 'Benutzername',
'users.column.name': 'Name',
'users.column.email': 'E-Mail',
'users.column.language': 'Sprache',
'users.noUsername': 'Kein Benutzername',
'users.noName': 'Kein Name',
'users.noEmail': 'Keine E-Mail',
'users.noLanguage': 'Keine Sprache',
'users.action.edit': 'Bearbeiten',
'users.action.delete': 'Löschen',
'users.delete.confirm': 'Sind Sie sicher, dass Sie "{name}" löschen möchten?',
'users.delete.confirmMultiple': 'Sind Sie sicher, dass Sie {count} Benutzer löschen möchten?',
'users.error.loading': 'Fehler beim Laden der Benutzer:',
// SharePoint Test // SharePoint Test
'sharepoint.title': 'SharePoint Test', 'sharepoint.title': 'SharePoint Test',

View file

@ -6,6 +6,7 @@ export default {
'nav.workflows': 'Workflows', 'nav.workflows': 'Workflows',
'nav.connections': 'Connections', 'nav.connections': 'Connections',
'nav.settings': 'Settings', 'nav.settings': 'Settings',
'nav.testSharepoint': 'Test SharePoint',
// Settings page // Settings page
'settings.title': 'Settings', 'settings.title': 'Settings',
@ -35,6 +36,7 @@ export default {
'common.delete': 'Delete', 'common.delete': 'Delete',
'common.edit': 'Edit', 'common.edit': 'Edit',
'common.close': 'Close', 'common.close': 'Close',
'common.retry': 'Retry',
// Auth // Auth
'auth.login': 'Login', 'auth.login': 'Login',
@ -108,6 +110,7 @@ export default {
'connections.not_available': 'N/A', 'connections.not_available': 'N/A',
'connections.invalid_date': 'Invalid Date', 'connections.invalid_date': 'Invalid Date',
'connections.confirm_delete': 'Are you sure you want to delete the {service} connection?', 'connections.confirm_delete': 'Are you sure you want to delete the {service} connection?',
'connections.confirm_delete_multiple': 'Are you sure you want to delete {count} connections?',
// Connection Fields // Connection Fields
'connections.field.service': 'Service', 'connections.field.service': 'Service',
@ -337,10 +340,7 @@ export default {
'files.type.audio': 'Audio', 'files.type.audio': 'Audio',
'files.type.file': 'File', 'files.type.file': 'File',
// File Sources
'files.source.uploaded': 'Uploaded',
'files.source.created': 'AI Created',
'files.source.shared': 'Shared',
// File Actions // File Actions
'files.action.download': 'Download', 'files.action.download': 'Download',
@ -394,6 +394,48 @@ export default {
'formgen.pagination.prev': 'Previous page', 'formgen.pagination.prev': 'Previous page',
'formgen.pagination.next': 'Next page', 'formgen.pagination.next': 'Next page',
'formgen.pagination.last': 'Last page', 'formgen.pagination.last': 'Last page',
'formgen.delete.multiple': 'Delete ({count})',
'formgen.delete.confirm_multiple': 'Are you sure you want to delete the {count} selected items?',
// Prompts
'prompts.title': 'Prompts',
'prompts.addNew': 'Add Prompt',
'prompts.creating': 'Creating...',
'prompts.column.name': 'Name',
'prompts.column.content': 'Content',
'prompts.unnamed': 'Unnamed',
'prompts.action.edit': 'Edit',
'prompts.action.copy': 'Copy',
'prompts.action.delete': 'Delete',
'prompts.delete.confirm': 'Are you sure you want to delete "{name}"?',
'prompts.delete.confirmMultiple': 'Are you sure you want to delete {count} prompts?',
'prompts.field.name': 'Prompt Name',
'prompts.field.content': 'Prompt Content',
'prompts.validation.nameRequired': 'Prompt name cannot be empty',
'prompts.validation.nameTooLong': 'Prompt name cannot exceed 100 characters',
'prompts.validation.contentRequired': 'Prompt content cannot be empty',
'prompts.validation.contentTooLong': 'Prompt content cannot exceed 10,000 characters',
'prompts.error.loading': 'Error loading prompts:',
'prompts.modal.edit.title': 'Edit Prompt',
'prompts.modal.edit.save': 'Save Changes',
'prompts.modal.create.title': 'Create New Prompt',
'prompts.modal.create.save': 'Create Prompt',
// Users/Members
'users.title': 'Users',
'users.column.username': 'Username',
'users.column.name': 'Name',
'users.column.email': 'Email',
'users.column.language': 'Language',
'users.noUsername': 'No Username',
'users.noName': 'No Name',
'users.noEmail': 'No Email',
'users.noLanguage': 'No Language',
'users.action.edit': 'Edit',
'users.action.delete': 'Delete',
'users.delete.confirm': 'Are you sure you want to delete "{name}"?',
'users.delete.confirmMultiple': 'Are you sure you want to delete {count} users?',
'users.error.loading': 'Error loading users:',
// SharePoint Test // SharePoint Test
'sharepoint.title': 'SharePoint Test', 'sharepoint.title': 'SharePoint Test',

View file

@ -6,6 +6,7 @@ export default {
'nav.workflows': 'Workflows', 'nav.workflows': 'Workflows',
'nav.connections': 'Connections', 'nav.connections': 'Connections',
'nav.settings': 'Paramètres', 'nav.settings': 'Paramètres',
'nav.testSharepoint': 'Test SharePoint',
// Settings page // Settings page
'settings.title': 'Paramètres', 'settings.title': 'Paramètres',
@ -35,6 +36,7 @@ export default {
'common.delete': 'Supprimer', 'common.delete': 'Supprimer',
'common.edit': 'Modifier', 'common.edit': 'Modifier',
'common.close': 'Fermer', 'common.close': 'Fermer',
'common.retry': 'Réessayer',
// Auth // Auth
'auth.login': 'Se connecter', 'auth.login': 'Se connecter',
@ -108,6 +110,7 @@ export default {
'connections.not_available': 'N/D', 'connections.not_available': 'N/D',
'connections.invalid_date': 'Date invalide', 'connections.invalid_date': 'Date invalide',
'connections.confirm_delete': 'Êtes-vous sûr de vouloir supprimer la connexion {service} ?', 'connections.confirm_delete': 'Êtes-vous sûr de vouloir supprimer la connexion {service} ?',
'connections.confirm_delete_multiple': 'Êtes-vous sûr de vouloir supprimer {count} connexions ?',
// Connection Fields // Connection Fields
'connections.field.service': 'Service', 'connections.field.service': 'Service',
@ -394,6 +397,48 @@ export default {
'formgen.pagination.prev': 'Page précédente', 'formgen.pagination.prev': 'Page précédente',
'formgen.pagination.next': 'Page suivante', 'formgen.pagination.next': 'Page suivante',
'formgen.pagination.last': 'Dernière page', 'formgen.pagination.last': 'Dernière page',
'formgen.delete.multiple': 'Supprimer ({count})',
'formgen.delete.confirm_multiple': 'Êtes-vous sûr de vouloir supprimer les {count} éléments sélectionnés ?',
// Prompts
'prompts.title': 'Prompts',
'prompts.addNew': 'Ajouter un prompt',
'prompts.creating': 'Création...',
'prompts.column.name': 'Nom',
'prompts.column.content': 'Contenu',
'prompts.unnamed': 'Sans nom',
'prompts.action.edit': 'Modifier',
'prompts.action.copy': 'Copier',
'prompts.action.delete': 'Supprimer',
'prompts.delete.confirm': 'Êtes-vous sûr de vouloir supprimer "{name}" ?',
'prompts.delete.confirmMultiple': 'Êtes-vous sûr de vouloir supprimer {count} prompts ?',
'prompts.field.name': 'Nom du prompt',
'prompts.field.content': 'Contenu du prompt',
'prompts.validation.nameRequired': 'Le nom du prompt ne peut pas être vide',
'prompts.validation.nameTooLong': 'Le nom du prompt ne peut pas dépasser 100 caractères',
'prompts.validation.contentRequired': 'Le contenu du prompt ne peut pas être vide',
'prompts.validation.contentTooLong': 'Le contenu du prompt ne peut pas dépasser 10 000 caractères',
'prompts.error.loading': 'Erreur lors du chargement des prompts:',
'prompts.modal.edit.title': 'Modifier le prompt',
'prompts.modal.edit.save': 'Enregistrer les modifications',
'prompts.modal.create.title': 'Créer un nouveau prompt',
'prompts.modal.create.save': 'Créer le prompt',
// Users/Members
'users.title': 'Utilisateurs',
'users.column.username': 'Nom d\'utilisateur',
'users.column.name': 'Nom',
'users.column.email': 'E-mail',
'users.column.language': 'Langue',
'users.noUsername': 'Aucun nom d\'utilisateur',
'users.noName': 'Aucun nom',
'users.noEmail': 'Aucun e-mail',
'users.noLanguage': 'Aucune langue',
'users.action.edit': 'Modifier',
'users.action.delete': 'Supprimer',
'users.delete.confirm': 'Êtes-vous sûr de vouloir supprimer "{name}" ?',
'users.delete.confirmMultiple': 'Êtes-vous sûr de vouloir supprimer {count} utilisateurs ?',
'users.error.loading': 'Erreur lors du chargement des utilisateurs:',
// SharePoint Test // SharePoint Test
'sharepoint.title': 'Test SharePoint', 'sharepoint.title': 'Test SharePoint',

View file

@ -1,12 +1,9 @@
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import App from './App.tsx' import App from './App.tsx'
import { AuthProvider } from './auth/authProvider.tsx';
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<AuthProvider> <App />
<App />
</AuthProvider>
</StrictMode>, </StrictMode>,
) )

View file

@ -1,5 +1,5 @@
import React, { useState } from 'react'; import { useState } from 'react';
import styles from './HomeStyles/Connections.module.css'; // import styles from './HomeStyles/Connections.module.css';
import sharedStyles from '../../components/PageManager/pages.module.css'; import sharedStyles from '../../components/PageManager/pages.module.css';
import { IoIosLink } from 'react-icons/io'; import { IoIosLink } from 'react-icons/io';
import { import {
@ -30,11 +30,13 @@ function Connections() {
tableActions, tableActions,
handleCreateConnection, handleCreateConnection,
handleSaveConnection, handleSaveConnection,
handleCancelEdit handleCancelEdit,
handleDelete,
handleDeleteMultiple
} = useConnectionsLogic(); } = useConnectionsLogic();
// Local state for selected connections (if needed) // Local state for selected connections (if needed)
const [selectedConnections, setSelectedConnections] = useState<Connection[]>([]); const [, setSelectedConnections] = useState<Connection[]>([]);
return ( return (
<div className={sharedStyles.pageContainer}> <div className={sharedStyles.pageContainer}>
@ -79,6 +81,8 @@ function Connections() {
actions={tableActions} actions={tableActions}
isLoading={isLoading} isLoading={isLoading}
onRowSelect={setSelectedConnections} onRowSelect={setSelectedConnections}
onDelete={handleDelete}
onDeleteMultiple={handleDeleteMultiple}
/> />
</div> </div>
</div> </div>

View file

@ -1,26 +1,3 @@
/* Dateien Page Styles
*
* This page now uses shared styles from pages.module.css for consistency.
* Only file management-specific styles should be defined here.
*/
/* Remove old styles - now using shared styles */
/* .dateienContainer - now using sharedStyles.pageContainer + sharedStyles.pageCardWithContentPadding */
/* .horizontalLineLight - now using sharedStyles.horizontalDivider */
/* .datei_hinzufügen_button - now using sharedStyles.primaryButton */
/* .add_icon - now using sharedStyles.buttonIcon */
/* Dateien-specific header customization */
.combinedHeader {
/* Additional styling for the header with tabs - extends sharedStyles.pageHeader */
}
/* Additional button customization if needed */
.datei_hinzufügen_button {
/* Any Dateien-specific button customizations go here */
}
/* Tab Navigation Styles */
.tabButtonDiv { .tabButtonDiv {
display: flex; display: flex;
gap: 12px; gap: 12px;

139
src/pages/Home/Prompts.tsx Normal file
View file

@ -0,0 +1,139 @@
import { useState } from 'react';
import { IoMdAdd } from 'react-icons/io';
import { PromptsTable } from '../../components/Prompts';
import { useLanguage } from '../../contexts/LanguageContext';
import { Popup } from '../../components/Popup/Popup';
import { EditForm } from '../../components/Popup/EditForm';
import { usePrompts, usePromptOperations } from '../../hooks/usePrompts';
import type { EditFieldConfig } from '../../components/Popup/EditForm';
import sharedStyles from '../../components/PageManager/pages.module.css';
function Prompts() {
const { t } = useLanguage();
const { prompts } = usePrompts();
const { handlePromptCreate, creatingPrompt } = usePromptOperations();
const [createModalOpen, setCreateModalOpen] = useState(false);
const [tableRefreshKey, setTableRefreshKey] = useState(0);
// Configure fields for creating a new prompt
const createPromptFields: EditFieldConfig[] = [
{
key: 'name',
label: t('prompts.field.name', 'Prompt Name'),
type: 'string',
editable: true,
required: true,
validator: (value: string) => {
if (!value || value.trim() === '') {
return t('prompts.validation.nameRequired', 'Prompt name cannot be empty');
}
if (value.length > 100) {
return t('prompts.validation.nameTooLong', 'Prompt name cannot exceed 100 characters');
}
return null;
}
},
{
key: 'content',
label: t('prompts.field.content', 'Prompt Content'),
type: 'textarea',
editable: true,
required: true,
minRows: 4,
maxRows: 8,
validator: (value: string) => {
if (!value || value.trim() === '') {
return t('prompts.validation.contentRequired', 'Prompt content cannot be empty');
}
if (value.length > 10000) {
return t('prompts.validation.contentTooLong', 'Prompt content cannot exceed 10,000 characters');
}
return null;
}
}
];
const handleCreatePrompt = () => {
setCreateModalOpen(true);
};
const handleSaveNewPrompt = async (newPromptData: any) => {
// Get mandateId from existing prompts or use a default
const mandateId = prompts.length > 0 ? prompts[0].mandateId : '';
if (!mandateId) {
console.error('No mandateId available. Cannot create prompt.');
// TODO: Show error message to user
return;
}
try {
const result = await handlePromptCreate({
name: newPromptData.name,
content: newPromptData.content,
mandateId: mandateId
});
if (result.success) {
// Close modal
setCreateModalOpen(false);
// Force remount PromptsTable to refetch
setTableRefreshKey(prev => prev + 1);
} else {
console.error('Failed to create prompt:', result.error);
// TODO: Show error message to user
}
} catch (error) {
console.error('Failed to create prompt:', error);
// TODO: Show error message to user
}
};
const handleCancelCreate = () => {
setCreateModalOpen(false);
};
return (
<div className={sharedStyles.pageContainer}>
<div className={sharedStyles.pageCard}>
<div className={sharedStyles.pageHeader}>
<h1 className={sharedStyles.pageTitle}>{t('prompts.title', 'Prompts')}</h1>
<button
className={sharedStyles.primaryButton}
onClick={handleCreatePrompt}
disabled={creatingPrompt}
aria-label="Add new prompt"
>
<span className={sharedStyles.buttonIcon}><IoMdAdd /></span>
{creatingPrompt ? t('prompts.creating', 'Creating...') : t('prompts.addNew', 'Add Prompt')}
</button>
</div>
<div className={sharedStyles.pageContent}>
<PromptsTable key={tableRefreshKey} />
</div>
{/* Create Prompt Modal */}
<Popup
isOpen={createModalOpen}
title={t('prompts.modal.create.title', 'Create New Prompt')}
onClose={handleCancelCreate}
size="large"
>
<EditForm
data={{ name: '', content: '' }}
fields={createPromptFields}
onSave={handleSaveNewPrompt}
onCancel={handleCancelCreate}
saveButtonText={t('prompts.modal.create.save', 'Create Prompt')}
cancelButtonText={t('common.cancel', 'Cancel')}
/>
</Popup>
</div>
</div>
);
}
export default Prompts;

View file

@ -1,59 +1,18 @@
import styles from './HomeStyles/TeamBereich.module.css' import styles from './HomeStyles/TeamBereich.module.css'
import sharedStyles from '../../components/PageManager/pages.module.css'; import sharedStyles from '../../components/PageManager/pages.module.css';
import MitgliederItem from '../../components/Mitglieder/MitgliederItem'; import MitgliederTable from '../../components/Mitglieder/MitgliederTable';
import { IoPersonAddSharp } from "react-icons/io5";
import { useOrgUsers } from '../../hooks/useUsers';
function TeamBereich () { function TeamBereich () {
const { users, loading, error, refetch } = useOrgUsers();
if (loading) {
return (
<div className={sharedStyles.pageContainer}>
<div className={`${sharedStyles.pageCard} ${styles.mitgliederContainer}`}>
<h1 className={sharedStyles.pageTitle}>Team-Bereich</h1>
<div className={sharedStyles.horizontalDivider}></div>
<p>Lade Mitglieder...</p>
</div>
</div>
);
}
if (error) {
return (
<div className={sharedStyles.pageContainer}>
<div className={`${sharedStyles.pageCard}`}>
<h1 className={sharedStyles.pageTitle}>Team-Bereich</h1>
<div className={sharedStyles.horizontalDivider}></div>
<p>Fehler beim Laden der Mitglieder: {error}</p>
</div>
</div>
);
}
return ( return (
<div className={sharedStyles.pageContainer}> <div className={sharedStyles.pageContainer}>
<div className={`${sharedStyles.pageCard} ${styles.mitgliederContainer}`}> <div className={sharedStyles.pageCard}>
<div className={sharedStyles.pageHeader}>
<h1 className={sharedStyles.pageTitle}>Team-Bereich</h1> <h1 className={sharedStyles.pageTitle}>Team-Bereich</h1>
<div className={sharedStyles.horizontalDivider}></div> </div>
<button className={`${sharedStyles.secondaryButton} ${styles.mitglieder_hinzufügen_button}`}> <div className={sharedStyles.pageContent}>
<IoPersonAddSharp className={sharedStyles.buttonIcon} /> <MitgliederTable />
Mitglied hinzufügen </div>
</button>
<ul className={styles.membersList}>
{users.map((user) => (
<MitgliederItem
key={user.id}
user={user}
refetchUsers={refetch}
totalUsers={users.length}
/>
))}
</ul>
{users.length === 0 && (
<p>Keine Mitglieder gefunden.</p>
)}
</div> </div>
</div> </div>
); );