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,
|
expandedIds: externalExpandedIds, onToggleExpand,
|
||||||
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
onCreateFolder, onRenameFolder, onDeleteFolder, onMoveFolder, onMoveFolders, onMoveFile, onMoveFiles,
|
||||||
onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh, onDownloadFolder,
|
onRenameFile, onDeleteFile, onDeleteFiles, onDeleteFolders, onRefresh, onDownloadFolder,
|
||||||
onScopeChange, onNeutralizeToggle,
|
onScopeChange, onNeutralizeToggle, onFolderNeutralizeToggle, onSendToChat,
|
||||||
}: FolderTreeProps) {
|
}: FolderTreeProps) {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,8 @@ export const UserSection: React.FC = () => {
|
||||||
|
|
||||||
{/* Legal Modal */}
|
{/* Legal Modal */}
|
||||||
{showLegalModal && (
|
{showLegalModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowLegalModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2>{t('Legal notices')}</h2>
|
<h2>{t('Legal notices')}</h2>
|
||||||
<button
|
<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;
|
className?: string;
|
||||||
size?: 'small' | 'medium' | 'large' | 'fullscreen';
|
size?: 'small' | 'medium' | 'large' | 'fullscreen';
|
||||||
closable?: boolean;
|
closable?: boolean;
|
||||||
|
closeOnBackdropClick?: boolean;
|
||||||
|
closeOnEscape?: boolean;
|
||||||
actions?: PopupAction[];
|
actions?: PopupAction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,6 +38,8 @@ export function Popup({
|
||||||
className = '',
|
className = '',
|
||||||
size = 'medium',
|
size = 'medium',
|
||||||
closable = true,
|
closable = true,
|
||||||
|
closeOnBackdropClick = false,
|
||||||
|
closeOnEscape = true,
|
||||||
actions = []
|
actions = []
|
||||||
}: PopupProps) {
|
}: PopupProps) {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
@ -43,7 +47,7 @@ export function Popup({
|
||||||
// Handle escape key
|
// Handle escape key
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape' && closable) {
|
if (e.key === 'Escape' && closable && closeOnEscape) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -58,13 +62,13 @@ export function Popup({
|
||||||
document.removeEventListener('keydown', handleEscape);
|
document.removeEventListener('keydown', handleEscape);
|
||||||
document.body.style.overflow = 'unset';
|
document.body.style.overflow = 'unset';
|
||||||
};
|
};
|
||||||
}, [isOpen, closable, onClose]);
|
}, [isOpen, closable, closeOnEscape, onClose]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
// Handle backdrop click
|
// Handle backdrop click
|
||||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||||
if (e.target === e.currentTarget && closable) {
|
if (e.target === e.currentTarget && closable && closeOnBackdropClick) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -43,6 +43,7 @@ interface UnifiedDataBarProps {
|
||||||
onSourcesChanged?: () => void;
|
onSourcesChanged?: () => void;
|
||||||
onSendToChat_Files?: (items: AddToChat_FileItem[]) => void;
|
onSendToChat_Files?: (items: AddToChat_FileItem[]) => void;
|
||||||
onSendToChat_FeatureSource?: (params: AddToChat_FeatureSource) => void;
|
onSendToChat_FeatureSource?: (params: AddToChat_FeatureSource) => void;
|
||||||
|
onAttachDataSource?: (dsId: string) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,6 +71,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
||||||
onSourcesChanged,
|
onSourcesChanged,
|
||||||
onSendToChat_Files,
|
onSendToChat_Files,
|
||||||
onSendToChat_FeatureSource,
|
onSendToChat_FeatureSource,
|
||||||
|
onAttachDataSource,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
@ -121,6 +123,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
||||||
context={context}
|
context={context}
|
||||||
onSourcesChanged={onSourcesChanged}
|
onSourcesChanged={onSourcesChanged}
|
||||||
onSendToChat_FeatureSource={onSendToChat_FeatureSource}
|
onSendToChat_FeatureSource={onSendToChat_FeatureSource}
|
||||||
|
onAttachDataSource={onAttachDataSource}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export { default as UnifiedDataBar } from './UnifiedDataBar';
|
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';
|
export { useUdlContext } from './useUdlContext';
|
||||||
|
|
|
||||||
|
|
@ -815,8 +815,8 @@ export const ComplianceAuditPage: React.FC = () => {
|
||||||
|
|
||||||
{/* ── Content View Modal ── */}
|
{/* ── Content View Modal ── */}
|
||||||
{contentModal && (
|
{contentModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setContentModal(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modalContainer} onClick={e => e.stopPropagation()}>
|
<div className={styles.modalContainer}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h3 className={styles.modalTitle}>{t('AI-Audit Inhalt')}</h3>
|
<h3 className={styles.modalTitle}>{t('AI-Audit Inhalt')}</h3>
|
||||||
<div className={styles.modalMeta}>
|
<div className={styles.modalMeta}>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { PENDING_INVITATION_KEY } from './InvitePage';
|
||||||
import OnboardingWizard from '../components/OnboardingWizard';
|
import OnboardingWizard from '../components/OnboardingWizard';
|
||||||
|
|
||||||
import styles from './Login.module.css';
|
import styles from './Login.module.css';
|
||||||
|
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||||
|
|
||||||
import { useLanguage } from '../providers/language/LanguageContext';
|
import { useLanguage } from '../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -131,6 +131,9 @@ function Login() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.mainContent}>
|
<div className={styles.mainContent}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||||
|
<LanguageSelector />
|
||||||
|
</div>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<img
|
<img
|
||||||
src="/logos/poweron-logo.png"
|
src="/logos/poweron-logo.png"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
||||||
import styles from './PasswordResetRequest.module.css';
|
import styles from './PasswordResetRequest.module.css';
|
||||||
import { usePasswordResetRequest } from '../hooks/useAuthentication';
|
import { usePasswordResetRequest } from '../hooks/useAuthentication';
|
||||||
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
||||||
|
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||||
|
|
||||||
import { useLanguage } from '../providers/language/LanguageContext';
|
import { useLanguage } from '../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -57,6 +58,9 @@ function PasswordResetRequest() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.mainContent}>
|
<div className={styles.mainContent}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||||
|
<LanguageSelector />
|
||||||
|
</div>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<img
|
<img
|
||||||
src="/logos/poweron-logo.png"
|
src="/logos/poweron-logo.png"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import styles from './Register.module.css';
|
||||||
import { useRegister, useMsalRegister, useUsernameAvailability } from '../hooks/useAuthentication';
|
import { useRegister, useMsalRegister, useUsernameAvailability } from '../hooks/useAuthentication';
|
||||||
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
||||||
import { PENDING_INVITATION_KEY } from './InvitePage';
|
import { PENDING_INVITATION_KEY } from './InvitePage';
|
||||||
|
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||||
|
|
||||||
import { useLanguage } from '../providers/language/LanguageContext';
|
import { useLanguage } from '../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ interface RegisterFormData {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Register() {
|
function Register() {
|
||||||
const { t } = useLanguage();
|
const { t, currentLanguage } = useLanguage();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { register, error: registerError, isLoading } = useRegister();
|
const { register, error: registerError, isLoading } = useRegister();
|
||||||
|
|
@ -91,7 +92,7 @@ function Register() {
|
||||||
return;
|
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.');
|
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) {
|
if (hasPendingInvitation) {
|
||||||
|
|
@ -125,6 +126,9 @@ function Register() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.mainContent}>
|
<div className={styles.mainContent}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||||
|
<LanguageSelector />
|
||||||
|
</div>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<img
|
<img
|
||||||
src="/logos/poweron-logo.png"
|
src="/logos/poweron-logo.png"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import styles from './Reset.module.css';
|
import styles from './Reset.module.css';
|
||||||
import { usePasswordReset } from '../hooks/useAuthentication';
|
import { usePasswordReset } from '../hooks/useAuthentication';
|
||||||
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
import { generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
||||||
|
import { LanguageSelector } from '../components/UiComponents/LanguageSelector';
|
||||||
|
|
||||||
import { useLanguage } from '../providers/language/LanguageContext';
|
import { useLanguage } from '../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -98,6 +99,9 @@ function Reset() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.mainContent}>
|
<div className={styles.mainContent}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||||
|
<LanguageSelector />
|
||||||
|
</div>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<img
|
<img
|
||||||
src="/logos/poweron-logo.png"
|
src="/logos/poweron-logo.png"
|
||||||
|
|
@ -138,6 +142,9 @@ function Reset() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.mainContent}>
|
<div className={styles.mainContent}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '-1.5rem' }}>
|
||||||
|
<LanguageSelector />
|
||||||
|
</div>
|
||||||
<div className={styles.logo}>
|
<div className={styles.logo}>
|
||||||
<img
|
<img
|
||||||
src="/logos/poweron-logo.png"
|
src="/logos/poweron-logo.png"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
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 { useLanguage } from '../providers/language/LanguageContext';
|
||||||
import { useStore } from '../hooks/useStore';
|
import { useStore } from '../hooks/useStore';
|
||||||
import type { StoreFeature, UserMandate } from '../api/storeApi';
|
import type { StoreFeature, UserMandate } from '../api/storeApi';
|
||||||
|
|
@ -18,6 +18,7 @@ const FEATURE_ICONS: Record<string, React.ReactNode> = {
|
||||||
teamsbot: <FaHeadset />,
|
teamsbot: <FaHeadset />,
|
||||||
workspace: <FaComments />,
|
workspace: <FaComments />,
|
||||||
commcoach: <FaComments />,
|
commcoach: <FaComments />,
|
||||||
|
trustee: <FaShieldAlt />,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Fallback when GET /store/features omits description (German i18n keys). */
|
/** 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.',
|
teamsbot: 'Integriere einen AI-Bot in deine Microsoft Teams Meetings und Channels.',
|
||||||
workspace: 'Nutze den gemeinsamen AI Workspace: Chats, Tools und Kontext pro Instanz.',
|
workspace: 'Nutze den gemeinsamen AI Workspace: Chats, Tools und Kontext pro Instanz.',
|
||||||
commcoach: 'CommCoach: Kommunikation trainieren mit KI-gestütztem Coaching und Feedback.',
|
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 {
|
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 { useToast } from '../../contexts/ToastContext';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import { ChatbotConfigSection } from './ChatbotConfigSection';
|
import { ChatbotConfigSection } from './ChatbotConfigSection';
|
||||||
import { DropdownSelect } from '../../components/UiComponents/DropdownSelect';
|
|
||||||
import { TextField } from '../../components/UiComponents/TextField';
|
import { TextField } from '../../components/UiComponents/TextField';
|
||||||
import styles from './Admin.module.css';
|
import styles from './Admin.module.css';
|
||||||
|
|
||||||
|
|
@ -512,8 +511,8 @@ export const AdminFeatureAccessPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Create Instance Modal */}
|
{/* Create Instance Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Neue Feature-Instanz erstellen')}</h2>
|
<h2 className={styles.modalTitle}>{t('Neue Feature-Instanz erstellen')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -533,35 +532,38 @@ export const AdminFeatureAccessPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<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)' }}>
|
<div className={styles.configField} style={{ marginBottom: '1.5rem', paddingBottom: '1rem', borderBottom: '1px solid var(--border-color)' }}>
|
||||||
<label className={styles.configLabel} style={{ fontWeight: 600 }}>
|
<label className={styles.configLabel} style={{ fontWeight: 600 }}>
|
||||||
{t('Feature auswählen')}: <span style={{ color: 'var(--error-color)' }}>*</span>
|
{t('Feature auswählen')}: <span style={{ color: 'var(--error-color)' }}>*</span>
|
||||||
</label>
|
</label>
|
||||||
<DropdownSelect
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', marginTop: '0.5rem' }}>
|
||||||
items={features.map(f => ({
|
{features.map(f => (
|
||||||
id: f.code,
|
<button
|
||||||
label: f.label || f.code,
|
key={f.code}
|
||||||
value: f.code
|
type="button"
|
||||||
}))}
|
className={styles.secondaryButton}
|
||||||
selectedItemId={createFeatureCode}
|
style={{
|
||||||
onSelect={(item) => {
|
padding: '0.5rem 1rem',
|
||||||
const selectedCode = item?.value || '';
|
borderRadius: '6px',
|
||||||
setCreateFeatureCode(selectedCode);
|
cursor: 'pointer',
|
||||||
// Reset chatbot config when switching
|
fontWeight: createFeatureCode === f.code ? 600 : 400,
|
||||||
setChatbotConnectors(['preprocessor']);
|
background: createFeatureCode === f.code ? 'var(--primary-color)' : undefined,
|
||||||
setChatbotSystemPrompt('');
|
color: createFeatureCode === f.code ? '#fff' : undefined,
|
||||||
setChatbotEnableWebResearch(true);
|
borderColor: createFeatureCode === f.code ? 'var(--primary-color)' : undefined,
|
||||||
setChatbotAllowedProviders([]);
|
}}
|
||||||
}}
|
onClick={() => {
|
||||||
placeholder={t('Feature-Auswahl erforderlich')}
|
setCreateFeatureCode(f.code);
|
||||||
className={styles.configSelect}
|
setChatbotConnectors(['preprocessor']);
|
||||||
/>
|
setChatbotSystemPrompt('');
|
||||||
{!createFeatureCode && (
|
setChatbotEnableWebResearch(true);
|
||||||
<p style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginTop: '0.5rem' }}>
|
setChatbotAllowedProviders([]);
|
||||||
{t('Bitte wählen Sie ein Feature aus, um fortzufahren.')}
|
}}
|
||||||
</p>
|
>
|
||||||
)}
|
{f.label || f.code}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Chatbot Configuration Title - Show when chatbot is selected */}
|
{/* Chatbot Configuration Title - Show when chatbot is selected */}
|
||||||
|
|
@ -634,8 +636,8 @@ export const AdminFeatureAccessPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Instance Modal */}
|
{/* Edit Instance Modal */}
|
||||||
{showEditModal && editingInstance && (
|
{showEditModal && editingInstance && (
|
||||||
<div className={styles.modalOverlay} onClick={() => { setShowEditModal(false); setEditingInstance(null); }}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Feature-Instanz bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Feature-Instanz bearbeiten')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -561,8 +561,8 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Add User Modal */}
|
{/* Add User Modal */}
|
||||||
{showAddModal && (
|
{showAddModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowAddModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Benutzer zur Feature-Instanz hinzufügen')}</h2>
|
<h2 className={styles.modalTitle}>{t('Benutzer zur Feature-Instanz hinzufügen')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -594,8 +594,8 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Roles Modal */}
|
{/* Edit Roles Modal */}
|
||||||
{editingUser && (
|
{editingUser && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>
|
<h2 className={styles.modalTitle}>
|
||||||
{t('Rollen bearbeiten')}: {editingUser.username}
|
{t('Rollen bearbeiten')}: {editingUser.username}
|
||||||
|
|
|
||||||
|
|
@ -397,8 +397,8 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Create Role Modal */}
|
{/* Create Role Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Neue Feature-Rolle erstellen')}</h2>
|
<h2 className={styles.modalTitle}>{t('Neue Feature-Rolle erstellen')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -430,8 +430,8 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Role Modal */}
|
{/* Edit Role Modal */}
|
||||||
{editingRole && (
|
{editingRole && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingRole(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Feature-Rolle bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Feature-Rolle bearbeiten')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -462,8 +462,8 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Permissions Modal */}
|
{/* Permissions Modal */}
|
||||||
{permissionsRole && (
|
{permissionsRole && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setPermissionsRole(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} style={{ maxWidth: '900px', width: '90%' }} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal} style={{ maxWidth: '900px', width: '90%' }}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>
|
<h2 className={styles.modalTitle}>
|
||||||
<FaShieldAlt style={{ marginRight: 8 }} />
|
<FaShieldAlt style={{ marginRight: 8 }} />
|
||||||
|
|
|
||||||
|
|
@ -372,8 +372,8 @@ export const AdminInvitationsPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Create Invitation Modal */}
|
{/* Create Invitation Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Neue Einladung erstellen')}</h2>
|
<h2 className={styles.modalTitle}>{t('Neue Einladung erstellen')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -411,8 +411,8 @@ export const AdminInvitationsPage: React.FC = () => {
|
||||||
|
|
||||||
{/* URL Display Modal */}
|
{/* URL Display Modal */}
|
||||||
{showUrlModal && (
|
{showUrlModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowUrlModal(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Einladungs-Link')}</h2>
|
<h2 className={styles.modalTitle}>{t('Einladungs-Link')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -434,8 +434,8 @@ export const AdminMandateRolesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Create Role Modal */}
|
{/* Create Role Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Neue Rolle erstellen')}</h2>
|
<h2 className={styles.modalTitle}>{t('Neue Rolle erstellen')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -468,8 +468,8 @@ export const AdminMandateRolesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Role Modal */}
|
{/* Edit Role Modal */}
|
||||||
{editingRole && (
|
{editingRole && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingRole(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>
|
<h2 className={styles.modalTitle}>
|
||||||
{t('Rolle bearbeiten')}: {editingRole.roleLabel}
|
{t('Rolle bearbeiten')}: {editingRole.roleLabel}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Admin page for managing Mandates (tenants) using FormGeneratorTable.
|
* 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 { useNavigate } from 'react-router-dom';
|
||||||
import { useAdminMandates, useMandateFormAttributes, type Mandate } from '../../hooks/useMandates';
|
import { useAdminMandates, useMandateFormAttributes, type Mandate } from '../../hooks/useMandates';
|
||||||
import { useApiRequest } from '../../hooks/useApi';
|
import { useApiRequest } from '../../hooks/useApi';
|
||||||
|
|
@ -16,8 +16,9 @@ import {
|
||||||
import { useToast } from '../../contexts/ToastContext';
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import { usePrompt } from '../../hooks/usePrompt';
|
import { usePrompt } from '../../hooks/usePrompt';
|
||||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
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 { FaPlus, FaSync, FaUsers, FaLock, FaSkullCrossbones } from 'react-icons/fa';
|
||||||
|
import { getUserDataCache } from '../../utils/userCache';
|
||||||
import styles from './Admin.module.css';
|
import styles from './Admin.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../providers/language/LanguageContext';
|
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 [editingFormData, setEditingFormData] = useState<Record<string, unknown> | null>(null);
|
||||||
const [editingBillingWarning, setEditingBillingWarning] = useState<string | 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
|
// Check if user can create
|
||||||
const canCreate = permissions?.create !== 'n';
|
const canCreate = permissions?.create !== 'n';
|
||||||
const canUpdate = permissions?.update !== 'n';
|
const canUpdate = permissions?.update !== 'n';
|
||||||
|
|
@ -106,7 +118,10 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
const mandateId = String(editingFormData.id);
|
const mandateId = String(editingFormData.id);
|
||||||
const { mandatePayload, billingUpdate } = splitMandateAndBillingFromForm(data);
|
const { mandatePayload, billingUpdate } = splitMandateAndBillingFromForm(data);
|
||||||
const mandateOk = await handleUpdate(mandateId, mandatePayload as Partial<Mandate>);
|
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 {
|
try {
|
||||||
await updateSettingsAdmin(request, mandateId, billingUpdate);
|
await updateSettingsAdmin(request, mandateId, billingUpdate);
|
||||||
showSuccess(t('Gespeichert'), t('Mandant und Abrechnung aktualisiert.'));
|
showSuccess(t('Gespeichert'), t('Mandant und Abrechnung aktualisiert.'));
|
||||||
|
|
@ -253,8 +268,8 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Create Modal */}
|
{/* Create Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Neuer Mandant')}</h2>
|
<h2 className={styles.modalTitle}>{t('Neuer Mandant')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -293,14 +308,8 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Modal */}
|
{/* Edit Modal */}
|
||||||
{editingFormData && (
|
{editingFormData && (
|
||||||
<div
|
<div className={styles.modalOverlay}>
|
||||||
className={styles.modalOverlay}
|
<div className={styles.modal}>
|
||||||
onClick={() => {
|
|
||||||
setEditingFormData(null);
|
|
||||||
setEditingBillingWarning(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Mandant bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Mandant bearbeiten')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -338,7 +347,7 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<FormGeneratorForm
|
<FormGeneratorForm
|
||||||
attributes={formAttributesWithBilling}
|
attributes={editFormAttrs}
|
||||||
data={editingFormData}
|
data={editingFormData}
|
||||||
mode="edit"
|
mode="edit"
|
||||||
onSubmit={handleEditSubmit}
|
onSubmit={handleEditSubmit}
|
||||||
|
|
|
||||||
|
|
@ -375,8 +375,8 @@ export const AdminUserMandatesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Add User Modal */}
|
{/* Add User Modal */}
|
||||||
{showAddModal && (
|
{showAddModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowAddModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Benutzer zum Mandanten hinzufügen')}</h2>
|
<h2 className={styles.modalTitle}>{t('Benutzer zum Mandanten hinzufügen')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -411,8 +411,8 @@ export const AdminUserMandatesPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Roles Modal */}
|
{/* Edit Roles Modal */}
|
||||||
{editingUser && (
|
{editingUser && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>
|
<h2 className={styles.modalTitle}>
|
||||||
{t('Rollen bearbeiten')}: {editingUser.username}
|
{t('Rollen bearbeiten')}: {editingUser.username}
|
||||||
|
|
|
||||||
|
|
@ -230,8 +230,8 @@ export const AdminUsersPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Create Modal */}
|
{/* Create Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Neuer Benutzer')}</h2>
|
<h2 className={styles.modalTitle}>{t('Neuer Benutzer')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -264,8 +264,8 @@ export const AdminUsersPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Modal */}
|
{/* Edit Modal */}
|
||||||
{editingUser && (
|
{editingUser && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Benutzer bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Benutzer bearbeiten')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -311,8 +311,8 @@ export const InstanceDetailModal: React.FC<InstanceDetailModalProps> = ({ instan
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showAddModal && (
|
{showAddModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowAddModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Benutzer hinzufügen')}</h2>
|
<h2 className={styles.modalTitle}>{t('Benutzer hinzufügen')}</h2>
|
||||||
<button type="button" className={styles.modalClose} onClick={() => setShowAddModal(false)}>
|
<button type="button" className={styles.modalClose} onClick={() => setShowAddModal(false)}>
|
||||||
|
|
@ -340,8 +340,8 @@ export const InstanceDetailModal: React.FC<InstanceDetailModalProps> = ({ instan
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editingUser && (
|
{editingUser && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingUser(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>
|
<h2 className={styles.modalTitle}>
|
||||||
{t('Rollen')}: {editingUser.username}
|
{t('Rollen')}: {editingUser.username}
|
||||||
|
|
|
||||||
|
|
@ -365,8 +365,8 @@ export const ConnectionsPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Modal */}
|
{/* Edit Modal */}
|
||||||
{editingConnection && (
|
{editingConnection && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingConnection(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Verbindung bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Verbindung bearbeiten')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -511,8 +511,8 @@ export const FilesPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{editingFile && (
|
{editingFile && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingFile(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Datei bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Datei bearbeiten')}</h2>
|
||||||
<button className={styles.modalClose} onClick={() => setEditingFile(null)}>✕</button>
|
<button className={styles.modalClose} onClick={() => setEditingFile(null)}>✕</button>
|
||||||
|
|
|
||||||
|
|
@ -230,8 +230,8 @@ export const PromptsPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Create Modal */}
|
{/* Create Modal */}
|
||||||
{showCreateModal && (
|
{showCreateModal && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Neuer Prompt')}</h2>
|
<h2 className={styles.modalTitle}>{t('Neuer Prompt')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
@ -264,8 +264,8 @@ export const PromptsPage: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Modal */}
|
{/* Edit Modal */}
|
||||||
{editingPrompt && (
|
{editingPrompt && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingPrompt(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Prompt bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Prompt bearbeiten')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -249,8 +249,8 @@ export const TrusteePositionDocumentsView: React.FC = () => {
|
||||||
|
|
||||||
{/* Edit Modal */}
|
{/* Edit Modal */}
|
||||||
{editingLink && (
|
{editingLink && (
|
||||||
<div className={styles.modalOverlay} onClick={() => setEditingLink(null)}>
|
<div className={styles.modalOverlay}>
|
||||||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
<div className={styles.modal}>
|
||||||
<div className={styles.modalHeader}>
|
<div className={styles.modalHeader}>
|
||||||
<h2 className={styles.modalTitle}>{t('Verknüpfung bearbeiten')}</h2>
|
<h2 className={styles.modalTitle}>{t('Verknüpfung bearbeiten')}</h2>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,10 @@ interface WorkspaceInputProps {
|
||||||
onProviderSelectionChange?: (selection: ProviderSelection) => void;
|
onProviderSelectionChange?: (selection: ProviderSelection) => void;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
onTreeItemsDrop?: (items: TreeItemDrop[]) => void;
|
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;
|
onPasteAsFile?: (file: File) => void;
|
||||||
draftAppend?: string;
|
draftAppend?: string;
|
||||||
onDraftAppendConsumed?: () => void;
|
onDraftAppendConsumed?: () => void;
|
||||||
|
|
@ -75,6 +79,10 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
||||||
onProviderSelectionChange,
|
onProviderSelectionChange,
|
||||||
isMobile = false,
|
isMobile = false,
|
||||||
onTreeItemsDrop,
|
onTreeItemsDrop,
|
||||||
|
onFeatureSourceDrop,
|
||||||
|
onDataSourceDrop,
|
||||||
|
pendingAttachDsId,
|
||||||
|
onPendingAttachDsConsumed,
|
||||||
onPasteAsFile,
|
onPasteAsFile,
|
||||||
draftAppend,
|
draftAppend,
|
||||||
onDraftAppendConsumed,
|
onDraftAppendConsumed,
|
||||||
|
|
@ -101,6 +109,15 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
||||||
}
|
}
|
||||||
}, [draftAppend, onDraftAppendConsumed]);
|
}, [draftAppend, onDraftAppendConsumed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pendingAttachDsId) {
|
||||||
|
setAttachedDataSourceIds(prev =>
|
||||||
|
prev.includes(pendingAttachDsId) ? prev : [...prev, pendingAttachDsId],
|
||||||
|
);
|
||||||
|
onPendingAttachDsConsumed?.();
|
||||||
|
}
|
||||||
|
}, [pendingAttachDsId, onPendingAttachDsConsumed]);
|
||||||
|
|
||||||
const promptBeforeVoiceRef = useRef('');
|
const promptBeforeVoiceRef = useRef('');
|
||||||
const finalizedTextRef = useRef('');
|
const finalizedTextRef = useRef('');
|
||||||
const currentInterimRef = useRef('');
|
const currentInterimRef = useRef('');
|
||||||
|
|
@ -142,7 +159,6 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
||||||
onSend(trimmed, allFileIds, attachedDataSourceIds, attachedFeatureDataSourceIds, options);
|
onSend(trimmed, allFileIds, attachedDataSourceIds, attachedFeatureDataSourceIds, options);
|
||||||
setPrompt('');
|
setPrompt('');
|
||||||
setShowAutocomplete(false);
|
setShowAutocomplete(false);
|
||||||
setShowSourcePicker(false);
|
|
||||||
setAttachedFileIds([]);
|
setAttachedFileIds([]);
|
||||||
}, [prompt, isProcessing, _extractFileRefs, attachedFileIds, attachedDataSourceIds, attachedFeatureDataSourceIds, neutralizeActive, onSend]);
|
}, [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));
|
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) => {
|
const _toggleFeatureDataSource = useCallback((fdsId: string) => {
|
||||||
setAttachedFeatureDataSourceIds(prev =>
|
setAttachedFeatureDataSourceIds(prev =>
|
||||||
prev.includes(fdsId) ? prev.filter(id => id !== fdsId) : [...prev, fdsId],
|
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) => {
|
const _handlePromptDragOver = useCallback((e: React.DragEvent) => {
|
||||||
if (
|
if (
|
||||||
e.dataTransfer.types.includes('application/tree-items') ||
|
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.preventDefault();
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
|
@ -311,6 +321,24 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
||||||
return;
|
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');
|
const treeItemsJson = e.dataTransfer.getData('application/tree-items');
|
||||||
if (treeItemsJson && onTreeItemsDrop) {
|
if (treeItemsJson && onTreeItemsDrop) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -318,7 +346,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({ instanceId: _ins
|
||||||
const items: TreeItemDrop[] = JSON.parse(treeItemsJson);
|
const items: TreeItemDrop[] = JSON.parse(treeItemsJson);
|
||||||
onTreeItemsDrop(items);
|
onTreeItemsDrop(items);
|
||||||
}
|
}
|
||||||
}, [onTreeItemsDrop]);
|
}, [onTreeItemsDrop, onFeatureSourceDrop, onDataSourceDrop]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,30 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
||||||
}
|
}
|
||||||
}, [instanceId, workspace]);
|
}, [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 = (
|
const _leftPanelBody = (
|
||||||
<UnifiedDataBar
|
<UnifiedDataBar
|
||||||
context={_udbContext}
|
context={_udbContext}
|
||||||
|
|
@ -322,6 +346,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
||||||
onSourcesChanged={_handleSourcesChanged}
|
onSourcesChanged={_handleSourcesChanged}
|
||||||
onSendToChat_Files={_handleSendToChat_Files}
|
onSendToChat_Files={_handleSendToChat_Files}
|
||||||
onSendToChat_FeatureSource={_handleSendToChat_FeatureSource}
|
onSendToChat_FeatureSource={_handleSendToChat_FeatureSource}
|
||||||
|
onAttachDataSource={_handleAttachDataSource}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -492,6 +517,10 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
||||||
onProviderSelectionChange={setProviderSelection}
|
onProviderSelectionChange={setProviderSelection}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
onTreeItemsDrop={_handleTreeItemsDrop}
|
onTreeItemsDrop={_handleTreeItemsDrop}
|
||||||
|
onFeatureSourceDrop={_handleSendToChat_FeatureSource}
|
||||||
|
onDataSourceDrop={_handleDataSourceDrop}
|
||||||
|
pendingAttachDsId={pendingAttachDsId}
|
||||||
|
onPendingAttachDsConsumed={() => setPendingAttachDsId('')}
|
||||||
onPasteAsFile={_uploadAndAttach}
|
onPasteAsFile={_uploadAndAttach}
|
||||||
draftAppend={draftAppend}
|
draftAppend={draftAppend}
|
||||||
onDraftAppendConsumed={() => setDraftAppend('')}
|
onDraftAppendConsumed={() => setDraftAppend('')}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue