testing fixes, udb source handling fixes
This commit is contained in:
parent
74d0ce429a
commit
4c959538ac
30 changed files with 1039 additions and 443 deletions
|
|
@ -620,7 +620,7 @@ export default function FolderTree({
|
|||
expandedIds: externalExpandedIds, onToggleExpand,
|
||||
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
||||
onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh, onDownloadFolder,
|
||||
onScopeChange, onNeutralizeToggle,
|
||||
onScopeChange, onNeutralizeToggle, onFolderNeutralizeToggle, onSendToChat,
|
||||
}: FolderTreeProps) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ export const UserSection: React.FC = () => {
|
|||
|
||||
{/* Legal Modal */}
|
||||
{showLegalModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowLegalModal(false)}>
|
||||
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2>{t('Legal notices')}</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
.wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.select {
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font-size: 0.8rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
padding: 0.15rem 0.3rem;
|
||||
border-radius: 4px;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.select:hover,
|
||||
.select:focus {
|
||||
opacity: 1;
|
||||
outline: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import { FaGlobe } from 'react-icons/fa';
|
||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||
import styles from './LanguageSelector.module.css';
|
||||
|
||||
export function LanguageSelector() {
|
||||
const { currentLanguage, setLanguage, availableLanguages } = useLanguage();
|
||||
|
||||
if (availableLanguages.length <= 1) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<FaGlobe className={styles.icon} />
|
||||
<select
|
||||
className={styles.select}
|
||||
value={currentLanguage}
|
||||
onChange={(e) => setLanguage(e.target.value as typeof currentLanguage)}
|
||||
>
|
||||
{availableLanguages.map((lang) => (
|
||||
<option key={lang.code} value={lang.code}>
|
||||
{lang.label || lang.code.toUpperCase()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LanguageSelector;
|
||||
2
src/components/UiComponents/LanguageSelector/index.ts
Normal file
2
src/components/UiComponents/LanguageSelector/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { LanguageSelector } from './LanguageSelector';
|
||||
export { default } from './LanguageSelector';
|
||||
|
|
@ -23,6 +23,8 @@ export interface PopupProps {
|
|||
className?: string;
|
||||
size?: 'small' | 'medium' | 'large' | 'fullscreen';
|
||||
closable?: boolean;
|
||||
closeOnBackdropClick?: boolean;
|
||||
closeOnEscape?: boolean;
|
||||
actions?: PopupAction[];
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +38,8 @@ export function Popup({
|
|||
className = '',
|
||||
size = 'medium',
|
||||
closable = true,
|
||||
closeOnBackdropClick = false,
|
||||
closeOnEscape = true,
|
||||
actions = []
|
||||
}: PopupProps) {
|
||||
const { t } = useLanguage();
|
||||
|
|
@ -43,7 +47,7 @@ export function Popup({
|
|||
// Handle escape key
|
||||
React.useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape' && closable) {
|
||||
if (e.key === 'Escape' && closable && closeOnEscape) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
|
@ -58,13 +62,13 @@ export function Popup({
|
|||
document.removeEventListener('keydown', handleEscape);
|
||||
document.body.style.overflow = 'unset';
|
||||
};
|
||||
}, [isOpen, closable, onClose]);
|
||||
}, [isOpen, closable, closeOnEscape, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
// Handle backdrop click
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget && closable) {
|
||||
if (e.target === e.currentTarget && closable && closeOnBackdropClick) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -43,6 +43,7 @@ interface UnifiedDataBarProps {
|
|||
onSourcesChanged?: () => void;
|
||||
onSendToChat_Files?: (items: AddToChat_FileItem[]) => void;
|
||||
onSendToChat_FeatureSource?: (params: AddToChat_FeatureSource) => void;
|
||||
onAttachDataSource?: (dsId: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
|||
onSourcesChanged,
|
||||
onSendToChat_Files,
|
||||
onSendToChat_FeatureSource,
|
||||
onAttachDataSource,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
|
|
@ -121,6 +123,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
|||
context={context}
|
||||
onSourcesChanged={onSourcesChanged}
|
||||
onSendToChat_FeatureSource={onSendToChat_FeatureSource}
|
||||
onAttachDataSource={onAttachDataSource}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export { default as UnifiedDataBar } from './UnifiedDataBar';
|
||||
export type { UdbContext, UdbTab } from './UnifiedDataBar';
|
||||
export type { UdbContext, UdbTab, AddToChat_FileItem, AddToChat_FeatureSource } from './UnifiedDataBar';
|
||||
export { useUdlContext } from './useUdlContext';
|
||||
|
|
|
|||
|
|
@ -815,8 +815,8 @@ export const ComplianceAuditPage: React.FC = () => {
|
|||
|
||||
{/* ── Content View Modal ── */}
|
||||
{contentModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setContentModal(null)}>
|
||||
<div className={styles.modalContainer} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modalContainer}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h3 className={styles.modalTitle}>{t('AI-Audit Inhalt')}</h3>
|
||||
<div className={styles.modalMeta}>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { PENDING_INVITATION_KEY } from './InvitePage';
|
|||
import OnboardingWizard from '../components/OnboardingWizard';
|
||||
|
||||
import styles from './Login.module.css';
|
||||
|
||||
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||
|
||||
import { useLanguage } from '../providers/language/LanguageContext';
|
||||
|
||||
|
|
@ -131,6 +131,9 @@ function Login() {
|
|||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.mainContent}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className={styles.logo}>
|
||||
<img
|
||||
src="/logos/poweron-logo.png"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
|||
import styles from './PasswordResetRequest.module.css';
|
||||
import { usePasswordResetRequest } from '../hooks/useAuthentication';
|
||||
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
||||
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||
|
||||
import { useLanguage } from '../providers/language/LanguageContext';
|
||||
|
||||
|
|
@ -57,6 +58,9 @@ function PasswordResetRequest() {
|
|||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.mainContent}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className={styles.logo}>
|
||||
<img
|
||||
src="/logos/poweron-logo.png"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import styles from './Register.module.css';
|
|||
import { useRegister, useMsalRegister, useUsernameAvailability } from '../hooks/useAuthentication';
|
||||
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
||||
import { PENDING_INVITATION_KEY } from './InvitePage';
|
||||
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||
|
||||
import { useLanguage } from '../providers/language/LanguageContext';
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ interface RegisterFormData {
|
|||
}
|
||||
|
||||
function Register() {
|
||||
const { t } = useLanguage();
|
||||
const { t, currentLanguage } = useLanguage();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { register, error: registerError, isLoading } = useRegister();
|
||||
|
|
@ -91,7 +92,7 @@ function Register() {
|
|||
return;
|
||||
}
|
||||
|
||||
await register({ ...formData, registrationType: 'personal' });
|
||||
await register({ ...formData, language: currentLanguage, registrationType: 'personal' });
|
||||
|
||||
let message = t('Registrierung erfolgreich! Bitte prüfen Sie Ihre E-Mail (auch den Spam-Ordner) für den Link zum Setzen Ihres Passworts.');
|
||||
if (hasPendingInvitation) {
|
||||
|
|
@ -125,6 +126,9 @@ function Register() {
|
|||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.mainContent}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className={styles.logo}>
|
||||
<img
|
||||
src="/logos/poweron-logo.png"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
|||
import styles from './Reset.module.css';
|
||||
import { usePasswordReset } from '../hooks/useAuthentication';
|
||||
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
||||
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||
|
||||
import { useLanguage } from '../providers/language/LanguageContext';
|
||||
|
||||
|
|
@ -98,6 +99,9 @@ function Reset() {
|
|||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.mainContent}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className={styles.logo}>
|
||||
<img
|
||||
src="/logos/poweron-logo.png"
|
||||
|
|
@ -138,6 +142,9 @@ function Reset() {
|
|||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.mainContent}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className={styles.logo}>
|
||||
<img
|
||||
src="/logos/poweron-logo.png"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FaCogs, FaComments, FaHeadset, FaProjectDiagram } from 'react-icons/fa';
|
||||
import { FaCogs, FaComments, FaHeadset, FaProjectDiagram, FaShieldAlt } from 'react-icons/fa';
|
||||
import { useLanguage } from '../providers/language/LanguageContext';
|
||||
import { useStore } from '../hooks/useStore';
|
||||
import type { StoreFeature, UserMandate } from '../api/storeApi';
|
||||
|
|
@ -18,6 +18,7 @@ const FEATURE_ICONS: Record<string, React.ReactNode> = {
|
|||
teamsbot: <FaHeadset />,
|
||||
workspace: <FaComments />,
|
||||
commcoach: <FaComments />,
|
||||
trustee: <FaShieldAlt />,
|
||||
};
|
||||
|
||||
/** Fallback when GET /store/features omits description (German i18n keys). */
|
||||
|
|
@ -27,6 +28,7 @@ const STORE_FEATURE_DESCRIPTION_FALLBACK: Record<string, string> = {
|
|||
teamsbot: 'Integriere einen AI-Bot in deine Microsoft Teams Meetings und Channels.',
|
||||
workspace: 'Nutze den gemeinsamen AI Workspace: Chats, Tools und Kontext pro Instanz.',
|
||||
commcoach: 'CommCoach: Kommunikation trainieren mit KI-gestütztem Coaching und Feedback.',
|
||||
trustee: 'Trustee: Intelligentes Dokumentenmanagement mit KI-gestützter Analyse und Verarbeitung.',
|
||||
};
|
||||
|
||||
function _storeCardDescription(feature: StoreFeature): string {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import { FaPlus, FaSync, FaCube, FaBuilding, FaCogs, FaEdit } from 'react-icons/
|
|||
import { useToast } from '../../contexts/ToastContext';
|
||||
import api from '../../api';
|
||||
import { ChatbotConfigSection } from './ChatbotConfigSection';
|
||||
import { DropdownSelect } from '../../components/UiComponents/DropdownSelect';
|
||||
import { TextField } from '../../components/UiComponents/TextField';
|
||||
import styles from './Admin.module.css';
|
||||
|
||||
|
|
@ -512,8 +511,8 @@ export const AdminFeatureAccessPage: React.FC = () => {
|
|||
|
||||
{/* Create Instance Modal */}
|
||||
{showCreateModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Neue Feature-Instanz erstellen')}</h2>
|
||||
<button
|
||||
|
|
@ -533,35 +532,38 @@ export const AdminFeatureAccessPage: React.FC = () => {
|
|||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{/* Feature Code Selector - Required for chatbot config */}
|
||||
{/* Feature Code Selector — buttons instead of dropdown */}
|
||||
<div className={styles.configField} style={{ marginBottom: '1.5rem', paddingBottom: '1rem', borderBottom: '1px solid var(--border-color)' }}>
|
||||
<label className={styles.configLabel} style={{ fontWeight: 600 }}>
|
||||
{t('Feature auswählen')}: <span style={{ color: 'var(--error-color)' }}>*</span>
|
||||
</label>
|
||||
<DropdownSelect
|
||||
items={features.map(f => ({
|
||||
id: f.code,
|
||||
label: f.label || f.code,
|
||||
value: f.code
|
||||
}))}
|
||||
selectedItemId={createFeatureCode}
|
||||
onSelect={(item) => {
|
||||
const selectedCode = item?.value || '';
|
||||
setCreateFeatureCode(selectedCode);
|
||||
// Reset chatbot config when switching
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', marginTop: '0.5rem' }}>
|
||||
{features.map(f => (
|
||||
<button
|
||||
key={f.code}
|
||||
type="button"
|
||||
className={styles.secondaryButton}
|
||||
style={{
|
||||
padding: '0.5rem 1rem',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: createFeatureCode === f.code ? 600 : 400,
|
||||
background: createFeatureCode === f.code ? 'var(--primary-color)' : undefined,
|
||||
color: createFeatureCode === f.code ? '#fff' : undefined,
|
||||
borderColor: createFeatureCode === f.code ? 'var(--primary-color)' : undefined,
|
||||
}}
|
||||
onClick={() => {
|
||||
setCreateFeatureCode(f.code);
|
||||
setChatbotConnectors(['preprocessor']);
|
||||
setChatbotSystemPrompt('');
|
||||
setChatbotEnableWebResearch(true);
|
||||
setChatbotAllowedProviders([]);
|
||||
}}
|
||||
placeholder={t('Feature-Auswahl erforderlich')}
|
||||
className={styles.configSelect}
|
||||
/>
|
||||
{!createFeatureCode && (
|
||||
<p style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginTop: '0.5rem' }}>
|
||||
{t('Bitte wählen Sie ein Feature aus, um fortzufahren.')}
|
||||
</p>
|
||||
)}
|
||||
>
|
||||
{f.label || f.code}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chatbot Configuration Title - Show when chatbot is selected */}
|
||||
|
|
@ -634,8 +636,8 @@ export const AdminFeatureAccessPage: React.FC = () => {
|
|||
|
||||
{/* Edit Instance Modal */}
|
||||
{showEditModal && editingInstance && (
|
||||
<div className={styles.modalOverlay} onClick={() => { setShowEditModal(false); setEditingInstance(null); }}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Feature-Instanz bearbeiten')}</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -561,8 +561,8 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => {
|
|||
|
||||
{/* Add User Modal */}
|
||||
{showAddModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowAddModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Benutzer zur Feature-Instanz hinzufügen')}</h2>
|
||||
<button
|
||||
|
|
@ -594,8 +594,8 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => {
|
|||
|
||||
{/* Edit Roles Modal */}
|
||||
{editingUser && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>
|
||||
{t('Rollen bearbeiten')}: {editingUser.username}
|
||||
|
|
|
|||
|
|
@ -397,8 +397,8 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
|||
|
||||
{/* Create Role Modal */}
|
||||
{showCreateModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Neue Feature-Rolle erstellen')}</h2>
|
||||
<button
|
||||
|
|
@ -430,8 +430,8 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
|||
|
||||
{/* Edit Role Modal */}
|
||||
{editingRole && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingRole(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Feature-Rolle bearbeiten')}</h2>
|
||||
<button
|
||||
|
|
@ -462,8 +462,8 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
|||
|
||||
{/* Permissions Modal */}
|
||||
{permissionsRole && (
|
||||
<div className={styles.modalOverlay} onClick={() => setPermissionsRole(null)}>
|
||||
<div className={styles.modal} style={{ maxWidth: '900px', width: '90%' }} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal} style={{ maxWidth: '900px', width: '90%' }}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>
|
||||
<FaShieldAlt style={{ marginRight: 8 }} />
|
||||
|
|
|
|||
|
|
@ -372,8 +372,8 @@ export const AdminInvitationsPage: React.FC = () => {
|
|||
|
||||
{/* Create Invitation Modal */}
|
||||
{showCreateModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Neue Einladung erstellen')}</h2>
|
||||
<button
|
||||
|
|
@ -411,8 +411,8 @@ export const AdminInvitationsPage: React.FC = () => {
|
|||
|
||||
{/* URL Display Modal */}
|
||||
{showUrlModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowUrlModal(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Einladungs-Link')}</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -434,8 +434,8 @@ export const AdminMandateRolesPage: React.FC = () => {
|
|||
|
||||
{/* Create Role Modal */}
|
||||
{showCreateModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Neue Rolle erstellen')}</h2>
|
||||
<button
|
||||
|
|
@ -468,8 +468,8 @@ export const AdminMandateRolesPage: React.FC = () => {
|
|||
|
||||
{/* Edit Role Modal */}
|
||||
{editingRole && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingRole(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>
|
||||
{t('Rolle bearbeiten')}: {editingRole.roleLabel}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Admin page for managing Mandates (tenants) using FormGeneratorTable.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAdminMandates, useMandateFormAttributes, type Mandate } from '../../hooks/useMandates';
|
||||
import { useApiRequest } from '../../hooks/useApi';
|
||||
|
|
@ -16,8 +16,9 @@ import {
|
|||
import { useToast } from '../../contexts/ToastContext';
|
||||
import { usePrompt } from '../../hooks/usePrompt';
|
||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||||
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
|
||||
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
||||
import { FaPlus, FaSync, FaUsers, FaLock, FaSkullCrossbones } from 'react-icons/fa';
|
||||
import { getUserDataCache } from '../../utils/userCache';
|
||||
import styles from './Admin.module.css';
|
||||
|
||||
import { useLanguage } from '../../providers/language/LanguageContext';
|
||||
|
|
@ -59,6 +60,17 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
const [editingFormData, setEditingFormData] = useState<Record<string, unknown> | null>(null);
|
||||
const [editingBillingWarning, setEditingBillingWarning] = useState<string | null>(null);
|
||||
|
||||
const isSysAdmin = getUserDataCache()?.isSysAdmin === true;
|
||||
|
||||
// MandateAdmin: only label + billing fields editable; rest readonly
|
||||
const _MANDATE_ADMIN_EDITABLE = new Set(['label', 'warningThresholdPercent', 'notifyOnWarning', 'notifyEmails']);
|
||||
const editFormAttrs: AttributeDefinition[] = useMemo(() => {
|
||||
if (isSysAdmin) return formAttributesWithBilling;
|
||||
return formAttributesWithBilling.map(attr =>
|
||||
_MANDATE_ADMIN_EDITABLE.has(attr.name) ? attr : { ...attr, editable: false, readonly: true }
|
||||
);
|
||||
}, [formAttributesWithBilling, isSysAdmin]);
|
||||
|
||||
// Check if user can create
|
||||
const canCreate = permissions?.create !== 'n';
|
||||
const canUpdate = permissions?.update !== 'n';
|
||||
|
|
@ -106,7 +118,10 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
const mandateId = String(editingFormData.id);
|
||||
const { mandatePayload, billingUpdate } = splitMandateAndBillingFromForm(data);
|
||||
const mandateOk = await handleUpdate(mandateId, mandatePayload as Partial<Mandate>);
|
||||
if (!mandateOk) return;
|
||||
if (!mandateOk) {
|
||||
showWarning(t('Fehler'), t('Mandant konnte nicht gespeichert werden. Fehlende Berechtigung oder Serverfehler.'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateSettingsAdmin(request, mandateId, billingUpdate);
|
||||
showSuccess(t('Gespeichert'), t('Mandant und Abrechnung aktualisiert.'));
|
||||
|
|
@ -253,8 +268,8 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
|
||||
{/* Create Modal */}
|
||||
{showCreateModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Neuer Mandant')}</h2>
|
||||
<button
|
||||
|
|
@ -293,14 +308,8 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
|
||||
{/* Edit Modal */}
|
||||
{editingFormData && (
|
||||
<div
|
||||
className={styles.modalOverlay}
|
||||
onClick={() => {
|
||||
setEditingFormData(null);
|
||||
setEditingBillingWarning(null);
|
||||
}}
|
||||
>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Mandant bearbeiten')}</h2>
|
||||
<button
|
||||
|
|
@ -338,7 +347,7 @@ export const AdminMandatesPage: React.FC = () => {
|
|||
</div>
|
||||
) : (
|
||||
<FormGeneratorForm
|
||||
attributes={formAttributesWithBilling}
|
||||
attributes={editFormAttrs}
|
||||
data={editingFormData}
|
||||
mode="edit"
|
||||
onSubmit={handleEditSubmit}
|
||||
|
|
|
|||
|
|
@ -375,8 +375,8 @@ export const AdminUserMandatesPage: React.FC = () => {
|
|||
|
||||
{/* Add User Modal */}
|
||||
{showAddModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowAddModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Benutzer zum Mandanten hinzufügen')}</h2>
|
||||
<button
|
||||
|
|
@ -411,8 +411,8 @@ export const AdminUserMandatesPage: React.FC = () => {
|
|||
|
||||
{/* Edit Roles Modal */}
|
||||
{editingUser && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>
|
||||
{t('Rollen bearbeiten')}: {editingUser.username}
|
||||
|
|
|
|||
|
|
@ -230,8 +230,8 @@ export const AdminUsersPage: React.FC = () => {
|
|||
|
||||
{/* Create Modal */}
|
||||
{showCreateModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Neuer Benutzer')}</h2>
|
||||
<button
|
||||
|
|
@ -264,8 +264,8 @@ export const AdminUsersPage: React.FC = () => {
|
|||
|
||||
{/* Edit Modal */}
|
||||
{editingUser && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Benutzer bearbeiten')}</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -311,8 +311,8 @@ export const InstanceDetailModal: React.FC<InstanceDetailModalProps> = ({ instan
|
|||
</div>
|
||||
|
||||
{showAddModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowAddModal(false)}>
|
||||
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Benutzer hinzufügen')}</h2>
|
||||
<button type="button" className={styles.modalClose} onClick={() => setShowAddModal(false)}>
|
||||
|
|
@ -340,8 +340,8 @@ export const InstanceDetailModal: React.FC<InstanceDetailModalProps> = ({ instan
|
|||
)}
|
||||
|
||||
{editingUser && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
||||
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>
|
||||
{t('Rollen')}: {editingUser.username}
|
||||
|
|
|
|||
|
|
@ -365,8 +365,8 @@ export const ConnectionsPage: React.FC = () => {
|
|||
|
||||
{/* Edit Modal */}
|
||||
{editingConnection && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingConnection(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Verbindung bearbeiten')}</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -511,8 +511,8 @@ export const FilesPage: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{editingFile && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingFile(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Datei bearbeiten')}</h2>
|
||||
<button className={styles.modalClose} onClick={() => setEditingFile(null)}>✕</button>
|
||||
|
|
|
|||
|
|
@ -230,8 +230,8 @@ export const PromptsPage: React.FC = () => {
|
|||
|
||||
{/* Create Modal */}
|
||||
{showCreateModal && (
|
||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Neuer Prompt')}</h2>
|
||||
<button
|
||||
|
|
@ -264,8 +264,8 @@ export const PromptsPage: React.FC = () => {
|
|||
|
||||
{/* Edit Modal */}
|
||||
{editingPrompt && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingPrompt(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Prompt bearbeiten')}</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -249,8 +249,8 @@ export const TrusteePositionDocumentsView: React.FC = () => {
|
|||
|
||||
{/* Edit Modal */}
|
||||
{editingLink && (
|
||||
<div className={styles.modalOverlay} onClick={() => setEditingLink(null)}>
|
||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2 className={styles.modalTitle}>{t('Verknüpfung bearbeiten')}</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ interface WorkspaceInputProps {
|
|||
onProviderSelectionChange?: (selection: ProviderSelection) => void;
|
||||
isMobile?: boolean;
|
||||
onTreeItemsDrop?: (items: TreeItemDrop[]) => void;
|
||||
onFeatureSourceDrop?: (params: { featureInstanceId: string; featureCode: string; tableName?: string; objectKey: string; label: string; fieldName?: string }) => void;
|
||||
onDataSourceDrop?: (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => void;
|
||||
pendingAttachDsId?: string;
|
||||
onPendingAttachDsConsumed?: () => void;
|
||||
onPasteAsFile?: (file: File) => void;
|
||||
draftAppend?: string;
|
||||
onDraftAppendConsumed?: () => void;
|
||||
|
|
@ -75,6 +79,10 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
|||
onProviderSelectionChange,
|
||||
isMobile = false,
|
||||
onTreeItemsDrop,
|
||||
onFeatureSourceDrop,
|
||||
onDataSourceDrop,
|
||||
pendingAttachDsId,
|
||||
onPendingAttachDsConsumed,
|
||||
onPasteAsFile,
|
||||
draftAppend,
|
||||
onDraftAppendConsumed,
|
||||
|
|
@ -101,6 +109,15 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
|||
}
|
||||
}, [draftAppend, onDraftAppendConsumed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pendingAttachDsId) {
|
||||
setAttachedDataSourceIds(prev =>
|
||||
prev.includes(pendingAttachDsId) ? prev : [...prev, pendingAttachDsId],
|
||||
);
|
||||
onPendingAttachDsConsumed?.();
|
||||
}
|
||||
}, [pendingAttachDsId, onPendingAttachDsConsumed]);
|
||||
|
||||
const promptBeforeVoiceRef = useRef('');
|
||||
const finalizedTextRef = useRef('');
|
||||
const currentInterimRef = useRef('');
|
||||
|
|
@ -142,7 +159,6 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
|||
onSend(trimmed, allFileIds, attachedDataSourceIds, attachedFeatureDataSourceIds, options);
|
||||
setPrompt('');
|
||||
setShowAutocomplete(false);
|
||||
setShowSourcePicker(false);
|
||||
setAttachedFileIds([]);
|
||||
}, [prompt, isProcessing, _extractFileRefs, attachedFileIds, attachedDataSourceIds, attachedFeatureDataSourceIds, neutralizeActive, onSend]);
|
||||
|
||||
|
|
@ -197,14 +213,6 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
|||
setAttachedDataSourceIds(prev => prev.filter(id => id !== dsId));
|
||||
}, []);
|
||||
|
||||
const [showSourcePicker, setShowSourcePicker] = useState(false);
|
||||
|
||||
const _toggleDataSource = useCallback((dsId: string) => {
|
||||
setAttachedDataSourceIds(prev =>
|
||||
prev.includes(dsId) ? prev.filter(id => id !== dsId) : [...prev, dsId],
|
||||
);
|
||||
}, []);
|
||||
|
||||
const _toggleFeatureDataSource = useCallback((fdsId: string) => {
|
||||
setAttachedFeatureDataSourceIds(prev =>
|
||||
prev.includes(fdsId) ? prev.filter(id => id !== fdsId) : [...prev, fdsId],
|
||||
|
|
@ -288,7 +296,9 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
|||
const _handlePromptDragOver = useCallback((e: React.DragEvent) => {
|
||||
if (
|
||||
e.dataTransfer.types.includes('application/tree-items') ||
|
||||
e.dataTransfer.types.includes('application/chat-id')
|
||||
e.dataTransfer.types.includes('application/chat-id') ||
|
||||
e.dataTransfer.types.includes('application/feature-source') ||
|
||||
e.dataTransfer.types.includes('application/datasource')
|
||||
) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
|
|
@ -311,6 +321,24 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
|||
return;
|
||||
}
|
||||
|
||||
const featureSourceJson = e.dataTransfer.getData('application/feature-source');
|
||||
if (featureSourceJson && onFeatureSourceDrop) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const params = JSON.parse(featureSourceJson);
|
||||
onFeatureSourceDrop(params);
|
||||
return;
|
||||
}
|
||||
|
||||
const dataSourceJson = e.dataTransfer.getData('application/datasource');
|
||||
if (dataSourceJson && onDataSourceDrop) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const params = JSON.parse(dataSourceJson);
|
||||
onDataSourceDrop(params);
|
||||
return;
|
||||
}
|
||||
|
||||
const treeItemsJson = e.dataTransfer.getData('application/tree-items');
|
||||
if (treeItemsJson && onTreeItemsDrop) {
|
||||
e.preventDefault();
|
||||
|
|
@ -318,7 +346,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
|||
const items: TreeItemDrop[] = JSON.parse(treeItemsJson);
|
||||
onTreeItemsDrop(items);
|
||||
}
|
||||
}, [onTreeItemsDrop]);
|
||||
}, [onTreeItemsDrop, onFeatureSourceDrop, onDataSourceDrop]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -308,6 +308,30 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
}
|
||||
}, [instanceId, workspace]);
|
||||
|
||||
const [pendingAttachDsId, setPendingAttachDsId] = useState<string>('');
|
||||
const _handleAttachDataSource = useCallback((dsId: string) => {
|
||||
setPendingAttachDsId(dsId);
|
||||
}, []);
|
||||
|
||||
const _handleDataSourceDrop = useCallback(async (params: { connectionId: string; sourceType: string; path: string; label: string; displayPath?: string }) => {
|
||||
try {
|
||||
const res = await api.post(`/api/workspace/${instanceId}/datasources`, {
|
||||
connectionId: params.connectionId,
|
||||
sourceType: params.sourceType,
|
||||
path: params.path,
|
||||
label: params.label,
|
||||
displayPath: params.displayPath || params.label,
|
||||
});
|
||||
const newId = res.data?.id || res.data?.dataSource?.id;
|
||||
if (newId) {
|
||||
setPendingAttachDsId(newId);
|
||||
workspace.refreshDataSources();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to drop data source to chat:', err);
|
||||
}
|
||||
}, [instanceId, workspace]);
|
||||
|
||||
const _leftPanelBody = (
|
||||
<UnifiedDataBar
|
||||
context={_udbContext}
|
||||
|
|
@ -322,6 +346,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
onSourcesChanged={_handleSourcesChanged}
|
||||
onSendToChat_Files={_handleSendToChat_Files}
|
||||
onSendToChat_FeatureSource={_handleSendToChat_FeatureSource}
|
||||
onAttachDataSource={_handleAttachDataSource}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
@ -492,6 +517,10 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
onProviderSelectionChange={setProviderSelection}
|
||||
isMobile={isMobile}
|
||||
onTreeItemsDrop={_handleTreeItemsDrop}
|
||||
onFeatureSourceDrop={_handleSendToChat_FeatureSource}
|
||||
onDataSourceDrop={_handleDataSourceDrop}
|
||||
pendingAttachDsId={pendingAttachDsId}
|
||||
onPendingAttachDsConsumed={() => setPendingAttachDsId('')}
|
||||
onPasteAsFile={_uploadAndAttach}
|
||||
draftAppend={draftAppend}
|
||||
onDraftAppendConsumed={() => setDraftAppend('')}
|
||||
|
|
|
|||
Loading…
Reference in a new issue