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 styles from './ConnectionsTable.module.css';
|
||||
import { ConnectionsTableProps } from './connectionsInterfaces';
|
||||
|
|
@ -11,7 +11,9 @@ export function ConnectionsTable({
|
|||
isLoading = false,
|
||||
isConnecting = false,
|
||||
isDisconnecting = false,
|
||||
onRowSelect
|
||||
onRowSelect,
|
||||
onDelete,
|
||||
onDeleteMultiple
|
||||
}: ConnectionsTableProps) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
|
|
@ -30,8 +32,10 @@ export function ConnectionsTable({
|
|||
resizable={true}
|
||||
pagination={true}
|
||||
pageSize={10}
|
||||
selectable={false}
|
||||
selectable={true}
|
||||
onRowSelect={onRowSelect}
|
||||
onDelete={onDelete}
|
||||
onDeleteMultiple={onDeleteMultiple}
|
||||
actions={actions}
|
||||
className={styles.connectionsTable}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export interface ConnectionsTableProps {
|
|||
isConnecting?: boolean;
|
||||
isDisconnecting?: boolean;
|
||||
onRowSelect?: (selectedRows: Connection[]) => void;
|
||||
onDelete?: (connection: Connection) => Promise<void>;
|
||||
onDeleteMultiple?: (connections: Connection[]) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface ConnectionEditModalProps {
|
||||
|
|
@ -49,6 +51,7 @@ export interface ConnectionHandlers {
|
|||
handleConnect: (connection: Connection) => Promise<void>;
|
||||
handleDisconnect: (connection: Connection) => Promise<void>;
|
||||
handleDelete: (connection: Connection) => Promise<void>;
|
||||
handleDeleteMultiple: (connections: Connection[]) => Promise<void>;
|
||||
handleConnectOrDisconnect: (connection: Connection) => Promise<void>;
|
||||
handleEditConnection: (connection: 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) => {
|
||||
if (connection.status === 'active') {
|
||||
await handleDisconnect(connection);
|
||||
|
|
@ -331,6 +345,7 @@ export function useConnectionsLogic(): ConnectionsLogicReturn {
|
|||
handleConnect,
|
||||
handleDisconnect,
|
||||
handleDelete,
|
||||
handleDeleteMultiple,
|
||||
handleConnectOrDisconnect,
|
||||
handleEditConnection,
|
||||
handleSaveConnection,
|
||||
|
|
|
|||
|
|
@ -23,10 +23,11 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Top Right: File Preview */}
|
||||
{/*Top Right: File Preview (disabled for now)
|
||||
<div className={`${styles.quadrant} ${styles.file_preview_quadrant}`}>
|
||||
<FilePreview selectedFile={selectedFile} />
|
||||
</div>
|
||||
*/}
|
||||
|
||||
{/* Bottom Left: Input Area */}
|
||||
<div className={`${styles.quadrant} ${styles.input_quadrant}`}>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
|
|||
)}
|
||||
</div>
|
||||
<div className={messageStyles.message_document_actions}>
|
||||
{/* Preview and Download buttons disabled for now
|
||||
<button
|
||||
onClick={(e) => handlePreview(doc, e)}
|
||||
className={messageStyles.message_document_action_button}
|
||||
|
|
@ -129,6 +130,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, onFilePreview }) =>
|
|||
<FaDownload size={16} />
|
||||
</div>
|
||||
</button>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ export function FormGenerator<T extends Record<string, any>>({
|
|||
title={t('formgen.delete.multiple', `Delete ${selectedRows.size} selected items`)}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { GoGear } from "react-icons/go";
|
||||
import { FaPlug, FaRegFileAlt, FaShare } from "react-icons/fa";
|
||||
import { LuMessageSquareText } from "react-icons/lu";
|
||||
|
||||
// Lazy load components for better performance
|
||||
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 Einstellungen = lazy(() => import('../../pages/Home/Einstellungen'));
|
||||
const TestSharepoint = lazy(() => import('../../pages/Home/TestSharepoint'));
|
||||
const Prompts = lazy(() => import('../../pages/Home/Prompts'));
|
||||
|
||||
// Page configuration with caching and lifecycle settings
|
||||
export const pageConfigs: PageConfig[] = [
|
||||
|
|
@ -61,6 +63,28 @@ export const pageConfigs: PageConfig[] = [
|
|||
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',
|
||||
component: TeamBereich,
|
||||
|
|
@ -103,7 +127,7 @@ export const pageConfigs: PageConfig[] = [
|
|||
preload: true, // Preload workflows for better performance
|
||||
moduleEnabled: true,
|
||||
// Sidebar properties
|
||||
id: '4',
|
||||
id: '6',
|
||||
name: 'Workflows',
|
||||
icon: LuWorkflow,
|
||||
order: 3,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,35 @@
|
|||
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 */
|
||||
.label {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ import styles from './EditForm.module.css';
|
|||
export interface EditFieldConfig {
|
||||
key: string;
|
||||
label: string;
|
||||
type: 'string' | 'email' | 'date' | 'enum' | 'boolean' | 'readonly';
|
||||
type: 'string' | 'email' | 'date' | 'enum' | 'boolean' | 'readonly' | 'textarea';
|
||||
editable: boolean;
|
||||
required?: boolean;
|
||||
options?: string[]; // For enum types
|
||||
formatter?: (value: any) => string; // For display formatting
|
||||
validator?: (value: any) => string | null; // Returns error message or null
|
||||
placeholder?: string;
|
||||
minRows?: number; // For textarea types
|
||||
maxRows?: number; // For textarea types
|
||||
}
|
||||
|
||||
// EditForm props
|
||||
|
|
@ -46,7 +48,29 @@ export function EditForm<T extends Record<string, any>>({
|
|||
setEditedData({ ...data });
|
||||
setErrors({});
|
||||
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
|
||||
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
|
||||
const inputType = field.type === 'email' ? 'email' :
|
||||
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 = () => {
|
||||
return <Sidebar data={useSidebarFromPageConfigs()} />;
|
||||
const sidebarData = useSidebarFromPageConfigs();
|
||||
return <Sidebar data={sidebarData} />;
|
||||
};
|
||||
|
||||
export default SidebarWithData;
|
||||
|
|
@ -4,7 +4,23 @@ import api from '../api';
|
|||
// Generic API error handling
|
||||
export function formatApiError(error: any, defaultMessage: string): string {
|
||||
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) {
|
||||
return 'Keine Antwort vom Server erhalten';
|
||||
} else {
|
||||
|
|
@ -48,7 +64,7 @@ export function useApiRequest<RequestData = any, ResponseData = any>() {
|
|||
} catch (error: any) {
|
||||
const errorMessage = formatApiError(error, `Fehler bei ${method.toUpperCase()} ${url}`);
|
||||
setError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
throw new Error(String(errorMessage)); // Ensure it's a string
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,16 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useApiRequest } from './useApi';
|
||||
|
||||
// Prompt interfaces
|
||||
// Prompt interface based on the API response
|
||||
export interface Prompt {
|
||||
id: string;
|
||||
mandateId: string;
|
||||
name: string;
|
||||
content: string;
|
||||
createdAt?: string;
|
||||
isShared?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// User interface for sharing
|
||||
export interface User {
|
||||
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() {
|
||||
// Prompts list hook
|
||||
export function usePrompts() {
|
||||
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
||||
const { request, isLoading: loading, error } = useApiRequest<null, Prompt[]>();
|
||||
|
||||
|
|
@ -51,77 +34,16 @@ export function useSimplePrompts() {
|
|||
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
|
||||
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 [updatingPrompts, setUpdatingPrompts] = useState<Set<number>>(new Set());
|
||||
const [sharingPrompts, setSharingPrompts] = useState<Set<number>>(new Set());
|
||||
const { request, error: apiError, isLoading } = useApiRequest();
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
const [createError, setCreateError] = 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);
|
||||
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);
|
||||
setCreatingPrompt(true);
|
||||
|
||||
|
|
@ -166,67 +88,32 @@ export function usePromptOperations() {
|
|||
}
|
||||
};
|
||||
|
||||
const handlePromptUpdate = async (promptId: number, promptData: { name?: string; content?: string }) => {
|
||||
setUpdateError(null);
|
||||
setUpdatingPrompts(prev => new Set(prev).add(promptId));
|
||||
const handlePromptUpdate = async (promptId: string, updateData: { id: string; name: string; content: string; mandateId: string }) => {
|
||||
setUpdateError(null); // Reuse update error state for update operations
|
||||
|
||||
try {
|
||||
const updatedPrompt = await request({
|
||||
url: `/api/prompts/${promptId}`,
|
||||
method: 'put',
|
||||
data: promptData
|
||||
data: updateData
|
||||
});
|
||||
|
||||
return { success: true, promptData: updatedPrompt };
|
||||
} catch (error: any) {
|
||||
setUpdateError(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 {
|
||||
deletingPrompts,
|
||||
creatingPrompt,
|
||||
updatingPrompts,
|
||||
sharingPrompts,
|
||||
deleteError,
|
||||
createError,
|
||||
updateError,
|
||||
shareError,
|
||||
handlePromptDelete,
|
||||
handlePromptCreate,
|
||||
handlePromptUpdate,
|
||||
handlePromptShare,
|
||||
isLoading
|
||||
};
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ export function useOrgUsers() {
|
|||
const fetchUsers = async () => {
|
||||
try {
|
||||
const data = await request({
|
||||
url: '/api/users',
|
||||
url: '/api/users/',
|
||||
method: 'get'
|
||||
});
|
||||
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({
|
||||
url: `/api/users/${userId}`,
|
||||
method: 'put',
|
||||
|
|
@ -120,7 +120,7 @@ export function useOrgUsers() {
|
|||
await fetchUsers(); // Refresh the list after update
|
||||
};
|
||||
|
||||
const deleteUser = async (userId: number) => {
|
||||
const deleteUser = async (userId: string) => {
|
||||
await request({
|
||||
url: `/api/users/${userId}`,
|
||||
method: 'delete'
|
||||
|
|
@ -128,7 +128,7 @@ export function useOrgUsers() {
|
|||
await fetchUsers(); // Refresh the list after deletion
|
||||
};
|
||||
|
||||
const getUser = async (userId: number): Promise<User> => {
|
||||
const getUser = async (userId: string): Promise<User> => {
|
||||
return await request({
|
||||
url: `/api/users/${userId}`,
|
||||
method: 'get'
|
||||
|
|
@ -154,14 +154,14 @@ export function useOrgUsers() {
|
|||
export function useUser() {
|
||||
const { request, isLoading, error } = useApiRequest();
|
||||
|
||||
const getUser = async (userId: number): Promise<User> => {
|
||||
const getUser = async (userId: string): Promise<User> => {
|
||||
return await request({
|
||||
url: `/api/users/${userId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
const updateUser = async (userId: number, userData: UserUpdateData): Promise<User> => {
|
||||
const updateUser = async (userId: string, userData: UserUpdateData): Promise<User> => {
|
||||
return await request({
|
||||
url: `/api/users/${userId}`,
|
||||
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({
|
||||
url: `/api/users/${userId}`,
|
||||
method: 'delete'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export default {
|
|||
'nav.connections': 'Verbindungen',
|
||||
'nav.workflows': 'Workflows',
|
||||
'nav.settings': 'Einstellungen',
|
||||
'nav.testSharepoint': 'SharePoint Test',
|
||||
|
||||
// Settings page
|
||||
'settings.title': 'Einstellungen',
|
||||
|
|
@ -35,6 +36,7 @@ export default {
|
|||
'common.delete': 'Löschen',
|
||||
'common.edit': 'Bearbeiten',
|
||||
'common.close': 'Schließen',
|
||||
'common.retry': 'Wiederholen',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Anmelden',
|
||||
|
|
@ -108,6 +110,7 @@ export default {
|
|||
'connections.not_available': 'Nicht verfügbar',
|
||||
'connections.invalid_date': 'Ungültiges Datum',
|
||||
'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
|
||||
'connections.field.service': 'Service',
|
||||
|
|
@ -388,6 +391,51 @@ export default {
|
|||
'formgen.pagination.prev': 'Vorherige Seite',
|
||||
'formgen.pagination.next': 'Nächste 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.title': 'SharePoint Test',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export default {
|
|||
'nav.workflows': 'Workflows',
|
||||
'nav.connections': 'Connections',
|
||||
'nav.settings': 'Settings',
|
||||
'nav.testSharepoint': 'Test SharePoint',
|
||||
|
||||
// Settings page
|
||||
'settings.title': 'Settings',
|
||||
|
|
@ -35,6 +36,7 @@ export default {
|
|||
'common.delete': 'Delete',
|
||||
'common.edit': 'Edit',
|
||||
'common.close': 'Close',
|
||||
'common.retry': 'Retry',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Login',
|
||||
|
|
@ -108,6 +110,7 @@ export default {
|
|||
'connections.not_available': 'N/A',
|
||||
'connections.invalid_date': 'Invalid Date',
|
||||
'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
|
||||
'connections.field.service': 'Service',
|
||||
|
|
@ -337,10 +340,7 @@ export default {
|
|||
'files.type.audio': 'Audio',
|
||||
'files.type.file': 'File',
|
||||
|
||||
// File Sources
|
||||
'files.source.uploaded': 'Uploaded',
|
||||
'files.source.created': 'AI Created',
|
||||
'files.source.shared': 'Shared',
|
||||
|
||||
|
||||
// File Actions
|
||||
'files.action.download': 'Download',
|
||||
|
|
@ -394,6 +394,48 @@ export default {
|
|||
'formgen.pagination.prev': 'Previous page',
|
||||
'formgen.pagination.next': 'Next 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.title': 'SharePoint Test',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export default {
|
|||
'nav.workflows': 'Workflows',
|
||||
'nav.connections': 'Connections',
|
||||
'nav.settings': 'Paramètres',
|
||||
'nav.testSharepoint': 'Test SharePoint',
|
||||
|
||||
// Settings page
|
||||
'settings.title': 'Paramètres',
|
||||
|
|
@ -35,6 +36,7 @@ export default {
|
|||
'common.delete': 'Supprimer',
|
||||
'common.edit': 'Modifier',
|
||||
'common.close': 'Fermer',
|
||||
'common.retry': 'Réessayer',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Se connecter',
|
||||
|
|
@ -108,6 +110,7 @@ export default {
|
|||
'connections.not_available': 'N/D',
|
||||
'connections.invalid_date': 'Date invalide',
|
||||
'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
|
||||
'connections.field.service': 'Service',
|
||||
|
|
@ -394,6 +397,48 @@ export default {
|
|||
'formgen.pagination.prev': 'Page précédente',
|
||||
'formgen.pagination.next': 'Page suivante',
|
||||
'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.title': 'Test SharePoint',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import { AuthProvider } from './auth/authProvider.tsx';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<AuthProvider>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import styles from './HomeStyles/Connections.module.css';
|
||||
import { useState } from 'react';
|
||||
// import styles from './HomeStyles/Connections.module.css';
|
||||
import sharedStyles from '../../components/PageManager/pages.module.css';
|
||||
import { IoIosLink } from 'react-icons/io';
|
||||
import {
|
||||
|
|
@ -30,11 +30,13 @@ function Connections() {
|
|||
tableActions,
|
||||
handleCreateConnection,
|
||||
handleSaveConnection,
|
||||
handleCancelEdit
|
||||
handleCancelEdit,
|
||||
handleDelete,
|
||||
handleDeleteMultiple
|
||||
} = useConnectionsLogic();
|
||||
|
||||
// Local state for selected connections (if needed)
|
||||
const [selectedConnections, setSelectedConnections] = useState<Connection[]>([]);
|
||||
const [, setSelectedConnections] = useState<Connection[]>([]);
|
||||
|
||||
return (
|
||||
<div className={sharedStyles.pageContainer}>
|
||||
|
|
@ -79,6 +81,8 @@ function Connections() {
|
|||
actions={tableActions}
|
||||
isLoading={isLoading}
|
||||
onRowSelect={setSelectedConnections}
|
||||
onDelete={handleDelete}
|
||||
onDeleteMultiple={handleDeleteMultiple}
|
||||
/>
|
||||
</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 {
|
||||
display: flex;
|
||||
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 sharedStyles from '../../components/PageManager/pages.module.css';
|
||||
|
||||
import MitgliederItem from '../../components/Mitglieder/MitgliederItem';
|
||||
import { IoPersonAddSharp } from "react-icons/io5";
|
||||
import { useOrgUsers } from '../../hooks/useUsers';
|
||||
import MitgliederTable from '../../components/Mitglieder/MitgliederTable';
|
||||
|
||||
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 (
|
||||
<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>
|
||||
<div className={sharedStyles.horizontalDivider}></div>
|
||||
<button className={`${sharedStyles.secondaryButton} ${styles.mitglieder_hinzufügen_button}`}>
|
||||
<IoPersonAddSharp className={sharedStyles.buttonIcon} />
|
||||
Mitglied hinzufügen
|
||||
</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 className={sharedStyles.pageContent}>
|
||||
<MitgliederTable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue