added prompt and user page
This commit is contained in:
parent
43951c280a
commit
9d5308206e
43 changed files with 1275 additions and 852 deletions
BIN
README.md
BIN
README.md
Binary file not shown.
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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}`}>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
.editBtn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #444;
|
|
||||||
height: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editBtn:hover {
|
|
||||||
color: #3d8df5;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
64
src/components/Mitglieder/MitgliederTable.module.css
Normal file
64
src/components/Mitglieder/MitgliederTable.module.css
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
50
src/components/Mitglieder/MitgliederTable.tsx
Normal file
50
src/components/Mitglieder/MitgliederTable.tsx
Normal 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;
|
||||||
3
src/components/Mitglieder/index.ts
Normal file
3
src/components/Mitglieder/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as MitgliederTable } from './MitgliederTable';
|
||||||
|
export { useMitgliederLogic } from './mitgliederLogic';
|
||||||
|
export type * from './mitgliederTypes';
|
||||||
107
src/components/Mitglieder/mitgliederLogic.tsx
Normal file
107
src/components/Mitglieder/mitgliederLogic.tsx
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
44
src/components/Mitglieder/mitgliederTypes.ts
Normal file
44
src/components/Mitglieder/mitgliederTypes.ts
Normal 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[];
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
76
src/components/Prompts/PromptsTable.module.css
Normal file
76
src/components/Prompts/PromptsTable.module.css
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/components/Prompts/PromptsTable.tsx
Normal file
82
src/components/Prompts/PromptsTable.tsx
Normal 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;
|
||||||
3
src/components/Prompts/index.ts
Normal file
3
src/components/Prompts/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as PromptsTable } from './PromptsTable';
|
||||||
|
export { usePromptsLogic } from './promptsLogic';
|
||||||
|
export type * from './promptsTypes';
|
||||||
301
src/components/Prompts/promptsLogic.tsx
Normal file
301
src/components/Prompts/promptsLogic.tsx
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
66
src/components/Prompts/promptsTypes.ts
Normal file
66
src/components/Prompts/promptsTypes.ts
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
0
src/pages/Home/HomeStyles/Prompts.module.css
Normal file
0
src/pages/Home/HomeStyles/Prompts.module.css
Normal file
139
src/pages/Home/Prompts.tsx
Normal file
139
src/pages/Home/Prompts.tsx
Normal 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;
|
||||||
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue