added multilanguage setting
This commit is contained in:
parent
8ca6f28bcd
commit
7f9f5b0539
31 changed files with 1049 additions and 230 deletions
41
src/App.tsx
41
src/App.tsx
|
|
@ -9,6 +9,7 @@ import Register from './pages/Register';
|
|||
|
||||
import { AuthProvider } from './auth/authProvider';
|
||||
import { ProtectedRoute } from './auth/ProtectedRoute';
|
||||
import { LanguageProvider } from './contexts/LanguageContext';
|
||||
import Home from './pages/Home';
|
||||
import Dateien from './pages/Dateien/Dateien';
|
||||
import TeamBereich from './pages/TeamBereich/TeamBereich';
|
||||
|
|
@ -33,25 +34,27 @@ function App() {
|
|||
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
|
||||
}, []);
|
||||
return (
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* Public route */}
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Home />
|
||||
</ProtectedRoute>
|
||||
}>
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="dateien" element={<Dateien />} />
|
||||
<Route path="team-bereich" element={<TeamBereich />} />
|
||||
<Route path="einstellungen" element={<Einstellungen />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
<LanguageProvider>
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* Public route */}
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Home />
|
||||
</ProtectedRoute>
|
||||
}>
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="dateien" element={<Dateien />} />
|
||||
<Route path="team-bereich" element={<TeamBereich />} />
|
||||
<Route path="einstellungen" element={<Einstellungen />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
</LanguageProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||
import { BsArrowsAngleExpand, BsArrowsAngleContract } from "react-icons/bs";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Prompt } from "../../../hooks/usePrompts";
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
|
||||
import DashboardChatArea from './DashboardChatArea/DashboardChatArea';
|
||||
import DashboardChatHistory from './DashboardChatHistory/DashboardChatHistory';
|
||||
|
|
@ -27,12 +28,13 @@ const DashboardChat: React.FC<DashboardChatProps> = ({
|
|||
onWorkflowCompletedChange,
|
||||
onWorkflowResume
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState("Chatbereich");
|
||||
const { t } = useLanguage();
|
||||
const [activeTab, setActiveTab] = useState(t('dashboard.chat.area'));
|
||||
const [resumeWorkflowId, setResumeWorkflowId] = useState<string | null>(null);
|
||||
|
||||
const handleWorkflowResume = (workflowId: string) => {
|
||||
// Switch to Chat Area tab first
|
||||
setActiveTab("Chatbereich");
|
||||
setActiveTab(t('dashboard.chat.area'));
|
||||
// Set the workflow ID to resume
|
||||
setResumeWorkflowId(workflowId);
|
||||
// Then call the parent's resume handler
|
||||
|
|
@ -57,7 +59,7 @@ const DashboardChat: React.FC<DashboardChatProps> = ({
|
|||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<div className={styles.chat_button_div}>
|
||||
{["Chatbereich", "Workflow-Verlauf"].map((tab) => (
|
||||
{[t('dashboard.chat.area'), t('dashboard.chat.history')].map((tab) => (
|
||||
<div key={tab} className={styles.buttonWrapper}>
|
||||
<motion.button
|
||||
key={tab}
|
||||
|
|
@ -116,7 +118,7 @@ const DashboardChat: React.FC<DashboardChatProps> = ({
|
|||
}}
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
{activeTab === "Chatbereich" ? (
|
||||
{activeTab === t('dashboard.chat.area') ? (
|
||||
<DashboardChatArea
|
||||
selectedPrompt={selectedPrompt}
|
||||
onPromptUsed={onPromptUsed}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { WorkflowStatusDisplayProps } from "./dashboardChatAreaTypes";
|
||||
import { useLanguage } from "../../../../contexts/LanguageContext";
|
||||
import styles from './DashboardChatArea.module.css';
|
||||
|
||||
const WorkflowStatusDisplay: React.FC<WorkflowStatusDisplayProps> = ({
|
||||
|
|
@ -11,6 +12,7 @@ const WorkflowStatusDisplay: React.FC<WorkflowStatusDisplayProps> = ({
|
|||
handleRetry,
|
||||
shouldShowRetryButton
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{currentWorkflowId && shouldShowRetryButton() && (
|
||||
|
|
@ -23,13 +25,13 @@ const WorkflowStatusDisplay: React.FC<WorkflowStatusDisplayProps> = ({
|
|||
>
|
||||
<div className={styles.retry_container}>
|
||||
<span className={styles.failed_message}>
|
||||
Workflow failed.
|
||||
{t('chat.workflow_failed')}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleRetry}
|
||||
className={styles.retry_button}
|
||||
>
|
||||
Nochmal versuchen
|
||||
{t('chat.retry_workflow')}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { DashboardChatAreaProps } from "./dashboardChatAreaTypes";
|
||||
import { useChatLogic } from "./dashboardChatAreaLogic";
|
||||
import { useLanguage } from "../../../../contexts/LanguageContext";
|
||||
import MessageList from "./DashboardChatAreaMessageList";
|
||||
import ChatInput from "./DashboardChatAreaInput";
|
||||
import styles from './DashboardChatArea.module.css';
|
||||
|
|
@ -53,6 +54,8 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
|
|||
resumeWorkflowId
|
||||
});
|
||||
|
||||
const { t } = useLanguage();
|
||||
|
||||
// Notify parent component when workflow completion status changes
|
||||
useEffect(() => {
|
||||
if (onWorkflowCompletedChange) {
|
||||
|
|
@ -60,7 +63,7 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
|
|||
}
|
||||
}, [workflowCompleted, onWorkflowCompletedChange]);
|
||||
|
||||
const placeholder = workflowCompleted ? "Gespräch fortsetzen..." : "Nachricht eingeben...";
|
||||
const placeholder = workflowCompleted ? t('chat.continue_conversation') : t('chat.enter_message');
|
||||
|
||||
return (
|
||||
<div className={styles.chat_area}>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { FaStop } from "react-icons/fa";
|
|||
import { IoAttach, IoClose } from "react-icons/io5";
|
||||
import { ChatInputProps } from "./dashboardChatAreaTypes";
|
||||
import { FileInfo } from "../../../../hooks/useFiles";
|
||||
import { useLanguage } from "../../../../contexts/LanguageContext";
|
||||
import DateienSelector from "../../../Dateien/DateienHinzufügen/DateienSelector";
|
||||
import styles from './DashboardChatArea.module.css';
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|||
onFileRemove,
|
||||
onFilesSelect
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
|
||||
|
|
@ -169,7 +171,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|||
<button
|
||||
className={styles.attached_file_remove}
|
||||
onClick={() => handleFileRemove(file.id)}
|
||||
title="Datei entfernen"
|
||||
title={t('chat.remove_file')}
|
||||
>
|
||||
<IoClose size={12} />
|
||||
</button>
|
||||
|
|
@ -203,7 +205,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|||
disabled={isDisabled || isWorkflowRunning}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
title="Datei anhängen"
|
||||
title={t('chat.attach_file')}
|
||||
>
|
||||
<IoAttach size={26} />
|
||||
</motion.button>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { MdOutlineRemoveRedEye } from "react-icons/md";
|
|||
import { Message, Document } from "./dashboardChatAreaTypes";
|
||||
import FilePreviewPopup from "./FilePreviewPopup";
|
||||
import { useFileDownload } from "../../../../hooks/useWorkflows";
|
||||
import { useLanguage } from "../../../../contexts/LanguageContext";
|
||||
import styles from './DashboardChatArea.module.css';
|
||||
|
||||
interface MessageItemProps {
|
||||
|
|
@ -62,6 +63,7 @@ const getFileIcon = (type?: string, ext?: string): string => {
|
|||
};
|
||||
|
||||
const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
|
||||
const { t } = useLanguage();
|
||||
const [previewDocument, setPreviewDocument] = useState<Document | null>(null);
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||
const { downloadFile, isDownloading, error: downloadError } = useFileDownload();
|
||||
|
|
@ -119,7 +121,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
|
|||
className={`${styles.message} ${styles[`message_${message.role}`]}`}
|
||||
>
|
||||
<div className={styles.message_role}>
|
||||
{message.role === 'user' ? 'You' : message.agentName}
|
||||
{message.role === 'user' ? t('chat.you') : message.agentName}
|
||||
</div>
|
||||
<div className={styles.message_content}>
|
||||
{message.content}
|
||||
|
|
@ -132,7 +134,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
|
|||
key={document.id || docIndex}
|
||||
className={styles.document_item}
|
||||
onClick={() => handleDocumentClick(document)}
|
||||
title={`Click to open ${document.name}`}
|
||||
title={`${t('chat.click_to_open')} ${document.name}`}
|
||||
>
|
||||
<span className={styles.document_icon}>
|
||||
{getFileIcon(document.type, document.ext)}
|
||||
|
|
@ -153,14 +155,14 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
|
|||
<button
|
||||
className={styles.document_action_button}
|
||||
onClick={(e) => handlePreview(document, e)}
|
||||
title="Preview document"
|
||||
title={t('chat.preview_document')}
|
||||
>
|
||||
<MdOutlineRemoveRedEye />
|
||||
</button>
|
||||
<button
|
||||
className={styles.document_action_button}
|
||||
onClick={(e) => handleDownload(document, e)}
|
||||
title="Download document"
|
||||
title={t('chat.download_document')}
|
||||
>
|
||||
<FaDownload />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { motion } from "framer-motion";
|
|||
import { MessageListProps } from "./dashboardChatAreaTypes";
|
||||
import MessageItem from "./DashboardChatAreaMessageItem";
|
||||
import WorkflowStatusDisplay from "./DashbaordChatAreaStatusDisplay";
|
||||
import { useLanguage } from "../../../../contexts/LanguageContext";
|
||||
import styles from './DashboardChatArea.module.css';
|
||||
|
||||
const MessageList: React.FC<MessageListProps> = ({
|
||||
|
|
@ -19,6 +20,8 @@ const MessageList: React.FC<MessageListProps> = ({
|
|||
handleRetry,
|
||||
shouldShowRetryButton
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={styles.chat_messages}
|
||||
|
|
@ -29,22 +32,22 @@ const MessageList: React.FC<MessageListProps> = ({
|
|||
<div className={styles.messages_container}>
|
||||
{startingWorkflow && (
|
||||
<div className={styles.loading_message}>
|
||||
<p>{workflowCompleted && currentWorkflowId ? 'Sending follow-up message...' : 'Sending message...'}</p>
|
||||
<p>{workflowCompleted && currentWorkflowId ? t('chat.sending_followup', 'Sending follow-up message...') : t('chat.sending_message', 'Sending message...')}</p>
|
||||
</div>
|
||||
)}
|
||||
{startError && (
|
||||
<div className={styles.error_message}>
|
||||
<p>Error: {startError}</p>
|
||||
<p>{t('chat.error_prefix', 'Error:')} {startError}</p>
|
||||
</div>
|
||||
)}
|
||||
{messagesError && (
|
||||
<div className={styles.error_message}>
|
||||
<p>Error loading messages: {messagesError}</p>
|
||||
<p>{t('chat.error_loading_messages', 'Error loading messages:')} {messagesError}</p>
|
||||
</div>
|
||||
)}
|
||||
{currentWorkflowId && messagesLoading && messages.length === 0 && (
|
||||
<div className={styles.loading_message}>
|
||||
<p>Loading workflow messages...</p>
|
||||
<p>{t('chat.loading_workflow_messages', 'Loading workflow messages...')}</p>
|
||||
</div>
|
||||
)}
|
||||
{messages.length > 0 ? (
|
||||
|
|
@ -56,7 +59,7 @@ const MessageList: React.FC<MessageListProps> = ({
|
|||
/>
|
||||
))
|
||||
) : !currentWorkflowId ? (
|
||||
<p className={styles.placeholder_text}>Beginne ein Gespräch, indem du eine Nachricht eingibst, eine Vorlage auswählst oder einen vorherigen Workflow fortsetzt …</p>
|
||||
<p className={styles.placeholder_text}>{t('chat.start_conversation', 'Start a conversation by entering a message, selecting a template, or continuing a previous workflow...')}</p>
|
||||
) : null}
|
||||
|
||||
{/* Spacer to push workflow status to bottom when there are fewer messages */}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
background-color: var(--color-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -134,7 +134,7 @@
|
|||
.text_preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-surface);
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-gray-disabled);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
|
|
@ -185,14 +185,14 @@
|
|||
.text_numbered {
|
||||
margin: 8px 0;
|
||||
padding-left: 8px;
|
||||
color: var(--color-gray);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.text_bullet {
|
||||
margin: 4px 0;
|
||||
padding-left: 8px;
|
||||
color: var(--color-gray);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +204,7 @@
|
|||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
color: var(--color-gray);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.code_preview {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import ReactMarkdown from 'react-markdown';
|
|||
import { MdClose } from "react-icons/md";
|
||||
import { Document } from "./dashboardChatAreaTypes";
|
||||
import { useFilePreview } from "../../../../hooks/useWorkflows";
|
||||
import { useLanguage } from "../../../../contexts/LanguageContext";
|
||||
import styles from './FilePreviewPopup.module.css';
|
||||
|
||||
interface FilePreviewPopupProps {
|
||||
|
|
@ -12,6 +13,7 @@ interface FilePreviewPopupProps {
|
|||
}
|
||||
|
||||
const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, onClose }) => {
|
||||
const { t } = useLanguage();
|
||||
const { previewContent, fileMetadata, isLoading, error, fetchPreview, clearPreview } = useFilePreview();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -38,13 +40,13 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
|
|||
|
||||
const getPreviewComponent = () => {
|
||||
if (isLoading) {
|
||||
return <div className={styles.loading}>Loading preview...</div>;
|
||||
return <div className={styles.loading}>{t('file_preview.loading')}</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={styles.error}>
|
||||
<div>Error: {error}</div>
|
||||
<div>{t('file_preview.error')}: {error}</div>
|
||||
{fileMetadata && (
|
||||
<div style={{ marginTop: '10px', fontSize: '12px', opacity: 0.7 }}>
|
||||
Debug: {JSON.stringify(fileMetadata, null, 2)}
|
||||
|
|
@ -57,7 +59,7 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
|
|||
if (!previewContent) {
|
||||
return (
|
||||
<div className={styles.no_preview}>
|
||||
<div>No preview available</div>
|
||||
<div>{t('file_preview.no_preview')}</div>
|
||||
{fileMetadata && (
|
||||
<div style={{ marginTop: '10px', fontSize: '12px', opacity: 0.7 }}>
|
||||
Available metadata: {Object.keys(fileMetadata).join(', ')}
|
||||
|
|
@ -165,7 +167,7 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
|
|||
return (
|
||||
<div className={styles.python_code_preview}>
|
||||
<div className={styles.code_header}>
|
||||
<span className={styles.code_language}>Python</span>
|
||||
<span className={styles.code_language}>{t('file_preview.python')}</span>
|
||||
<span className={styles.code_filename}>
|
||||
{document.ext ? `${document.name}.${document.ext}` : document.name}
|
||||
</span>
|
||||
|
|
@ -229,7 +231,7 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
|
|||
<button
|
||||
className={styles.close_button}
|
||||
onClick={onClose}
|
||||
title="Close preview"
|
||||
title={t('file_preview.close_preview')}
|
||||
>
|
||||
<MdClose />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useWorkflows } from "../../../../hooks/useWorkflows";
|
||||
import { useLanguage } from "../../../../contexts/LanguageContext";
|
||||
import DashboardChatHistoryItem from "./DashboardChatHistoryItem";
|
||||
|
||||
import styles from './DashboardChatHistory.module.css';
|
||||
|
|
@ -11,6 +12,7 @@ interface DashboardChatHistoryProps {
|
|||
|
||||
const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowResume }) => {
|
||||
const { workflows, loading, error, refetch } = useWorkflows();
|
||||
const { t } = useLanguage();
|
||||
|
||||
const handleWorkflowResume = (workflowId: string) => {
|
||||
if (onWorkflowResume) {
|
||||
|
|
@ -28,7 +30,7 @@ const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowR
|
|||
transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<div className={styles.loadingContainer}>
|
||||
<div className={styles.loadingText}>Workflows werden geladen...</div>
|
||||
<div className={styles.loadingText}>{t('chat_history.loading', 'Loading workflows...')}</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
|
@ -43,12 +45,12 @@ const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowR
|
|||
transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<div className={styles.errorContainer}>
|
||||
<div className={styles.errorText}>Fehler beim Laden der Workflows: {error}</div>
|
||||
<div className={styles.errorText}>{t('chat_history.error_loading', 'Error loading workflows:')} {error}</div>
|
||||
<button
|
||||
onClick={refetch}
|
||||
className={styles.retryButton}
|
||||
>
|
||||
Try Again
|
||||
{t('chat_history.try_again', 'Try Again')}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
@ -64,16 +66,16 @@ const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowR
|
|||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<h2 className={styles.history_title}>Workflow-Verlauf</h2>
|
||||
<h2 className={styles.history_title}>{t('chat_history.title', 'Workflow History')}</h2>
|
||||
<div className={styles.workflowCount}>
|
||||
{workflows.length} {workflows.length === 1 ? 'Workflow' : 'Workflows'}
|
||||
{workflows.length} {workflows.length === 1 ? t('chat_history.workflow_count', 'Workflow') : t('chat_history.workflow_count_plural', 'Workflows')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.scrollableContent}>
|
||||
{workflows.length === 0 ? (
|
||||
<div className={styles.emptyState}>
|
||||
Keine Workflows verfügbar
|
||||
{t('chat_history.empty_state', 'No workflows available')}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.workflowsList}>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FaArrowRight } from 'react-icons/fa';
|
|||
import { AiOutlineDelete } from 'react-icons/ai';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useWorkflowOperations, useWorkflowMessages, Workflow } from '../../../../hooks/useWorkflows';
|
||||
import { useLanguage } from '../../../../contexts/LanguageContext';
|
||||
import styles from './DashboardChatHistoryItem.module.css';
|
||||
|
||||
interface DashboardChatHistoryItemProps {
|
||||
|
|
@ -14,15 +15,17 @@ interface DashboardChatHistoryItemProps {
|
|||
function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardChatHistoryItemProps) {
|
||||
const { deleteWorkflow, deletingWorkflows } = useWorkflowOperations();
|
||||
const { messages } = useWorkflowMessages(workflow.id);
|
||||
const { t } = useLanguage();
|
||||
|
||||
const isDeleting = deletingWorkflows.has(workflow.id);
|
||||
|
||||
// Get the first user message as preview
|
||||
const firstUserMessage = messages.find(msg => msg.role === 'user');
|
||||
const messagePreview = firstUserMessage?.content || 'No message content available';
|
||||
const messagePreview = firstUserMessage?.content || t('chat_history.no_message_content', 'No message content available');
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (window.confirm(`Are you sure you want to delete workflow "${workflow.id.substring(0, 8)}..."?`)) {
|
||||
const confirmMessage = t('chat_history.confirm_delete', 'Are you sure you want to delete workflow "{id}..."?').replace('{id}', workflow.id.substring(0, 8));
|
||||
if (window.confirm(confirmMessage)) {
|
||||
const success = await deleteWorkflow(workflow.id);
|
||||
if (success && onDelete) {
|
||||
onDelete();
|
||||
|
|
@ -35,7 +38,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
};
|
||||
|
||||
const formatDate = (dateString?: string) => {
|
||||
if (!dateString) return 'Unknown date';
|
||||
if (!dateString) return t('chat_history.unknown_date', 'Unknown date');
|
||||
try {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
|
|
@ -45,7 +48,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
minute: '2-digit'
|
||||
});
|
||||
} catch (error) {
|
||||
return 'Invalid date';
|
||||
return t('chat_history.invalid_date', 'Invalid date');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -86,6 +89,11 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
return statusLower === 'running' || statusLower === 'processing';
|
||||
};
|
||||
|
||||
const getTranslatedStatus = (status: string): string => {
|
||||
const statusKey = `status.${status.toLowerCase()}`;
|
||||
return t(statusKey, status.toUpperCase());
|
||||
};
|
||||
|
||||
const renderStatusIndicator = () => {
|
||||
if (isRunning(workflow.status)) {
|
||||
return (
|
||||
|
|
@ -118,7 +126,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
backgroundColor: getStatusBackgroundColor(workflow.status)
|
||||
}}
|
||||
>
|
||||
{workflow.status.toUpperCase()}
|
||||
{getTranslatedStatus(workflow.status)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
@ -143,19 +151,19 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
{renderStatusIndicator()}
|
||||
{workflow.currentRound && (
|
||||
<span className={styles.workflowRound}>
|
||||
Round {workflow.currentRound}
|
||||
{t('chat_history.round', 'Round')} {workflow.currentRound}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.workflowDates}>
|
||||
{workflow.startedAt && (
|
||||
<p className={styles.workflowDate}>
|
||||
Started: {formatDate(workflow.startedAt)}
|
||||
{t('chat_history.started', 'Started:')} {formatDate(workflow.startedAt)}
|
||||
</p>
|
||||
)}
|
||||
{workflow.lastActivity && (
|
||||
<p className={styles.workflowDate}>
|
||||
Last Activity: {formatDate(workflow.lastActivity)}
|
||||
{t('chat_history.last_activity', 'Last Activity:')} {formatDate(workflow.lastActivity)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -175,7 +183,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
<button
|
||||
onClick={handleResume}
|
||||
className={`${styles.actionButton} ${styles.resumeButton}`}
|
||||
title="Resume workflow"
|
||||
title={t('chat_history.resume_tooltip', 'Resume workflow')}
|
||||
>
|
||||
<FaArrowRight size={16} />
|
||||
</button>
|
||||
|
|
@ -184,7 +192,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
className={`${styles.actionButton} ${styles.deleteButton}`}
|
||||
title="Delete workflow"
|
||||
title={t('chat_history.delete_tooltip', 'Delete workflow')}
|
||||
>
|
||||
<AiOutlineDelete size={16} />
|
||||
</button>
|
||||
|
|
@ -193,7 +201,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
|
|||
|
||||
{isDeleting && (
|
||||
<div className={styles.deletingMessage}>
|
||||
Deleting workflow...
|
||||
{t('chat_history.deleting', 'Deleting workflow...')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { motion } from "framer-motion";
|
|||
|
||||
import styles from './DashboardLog.module.css';
|
||||
import { useApiRequest } from '../../../hooks/useApi';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
|
||||
interface DashboardLogProps {
|
||||
isExpanded: boolean;
|
||||
|
|
@ -11,6 +12,7 @@ interface DashboardLogProps {
|
|||
}
|
||||
|
||||
const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, workflowCompleted = false }) => {
|
||||
const { t } = useLanguage();
|
||||
const [logs, setLogs] = useState<any[]>([]);
|
||||
const [isPolling, setIsPolling] = useState(false);
|
||||
const [logsError, setLogsError] = useState<string | null>(null);
|
||||
|
|
@ -33,7 +35,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
|
|||
setLogsError(null);
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching logs:', error);
|
||||
setLogsError(error.message || 'Failed to fetch logs');
|
||||
setLogsError(error.message || t('dashboard.log.fetch_failed'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -108,7 +110,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
|
|||
return (
|
||||
<div className={styles.console_placeholder}>
|
||||
<span className={styles.console_prompt}>$</span>
|
||||
<span className={styles.console_text}>No workflow selected</span>
|
||||
<span className={styles.console_text}>{t('dashboard.log.no_workflow')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -117,7 +119,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
|
|||
return (
|
||||
<div className={styles.console_placeholder}>
|
||||
<span className={styles.console_prompt}>$</span>
|
||||
<span className={styles.console_text}>Loading logs...</span>
|
||||
<span className={styles.console_text}>{t('dashboard.log.loading')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -126,15 +128,15 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
|
|||
return (
|
||||
<div className={styles.console_placeholder}>
|
||||
<span className={styles.console_prompt}>$</span>
|
||||
<span className={styles.console_text}>Error loading logs: {logsError}</span>
|
||||
<span className={styles.console_text}>{t('dashboard.log.error')}: {logsError}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (logs.length === 0) {
|
||||
const statusText = workflowCompleted
|
||||
? "No logs available for this workflow"
|
||||
: "Workflow running... Waiting for logs...";
|
||||
? t('dashboard.log.no_logs')
|
||||
: t('dashboard.log.waiting');
|
||||
|
||||
return (
|
||||
<div className={styles.console_placeholder}>
|
||||
|
|
@ -152,7 +154,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
|
|||
<span className={styles.log_timestamp}>
|
||||
{log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : ''}
|
||||
</span>
|
||||
<span className={styles.log_level}>[{log.level || 'INFO'}]</span>
|
||||
<span className={styles.log_level}>[{log.level || t('dashboard.log.level.info')}]</span>
|
||||
<span className={styles.log_message}>{log.message || log.content || JSON.stringify(log)}</span>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -177,7 +179,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
|
|||
whileHover={{ scale: 1.05 }}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
>
|
||||
Log {workflowId && `- Workflow ${workflowId.substring(0, 8)}...`}
|
||||
{t('dashboard.log.title')} {workflowId && `- ${t('dashboard.log.workflow')} ${workflowId.substring(0, 8)}...`}
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className={styles.horizontalLine}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { MdExpandMore, MdExpandLess } from "react-icons/md";
|
|||
import DashboardPromptSettings from './DashboardPromptSettings/DashboardPromptSettings';
|
||||
import DashboardPromptSet from './DashboardPromptSet/DashboardPromptSet';
|
||||
import { Prompt } from '../../../hooks/usePrompts';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
|
||||
import styles from './DashboardPrompt.module.css';
|
||||
|
||||
|
|
@ -19,7 +20,8 @@ const DashboardPrompt: React.FC<DashboardPromptProps> = ({
|
|||
isCollapsed,
|
||||
onToggleCollapse
|
||||
}) => {
|
||||
const [activeTab, setActiveTab] = useState("Prompt Vorlage");
|
||||
const { t } = useLanguage();
|
||||
const [activeTab, setActiveTab] = useState(t('dashboard.prompt.template'));
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -27,19 +29,19 @@ const DashboardPrompt: React.FC<DashboardPromptProps> = ({
|
|||
const promptId = searchParams.get('promptId');
|
||||
|
||||
if (expandedPrompt) {
|
||||
setActiveTab("Prompt Vorlage");
|
||||
setActiveTab(t('dashboard.prompt.template'));
|
||||
} else if (promptId) {
|
||||
setActiveTab("Einstellungen");
|
||||
setActiveTab(t('dashboard.prompt.settings'));
|
||||
}
|
||||
}, [searchParams]);
|
||||
}, [searchParams, t]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.dashboard_prompt} ${isCollapsed ? styles.collapsed : ''}`}>
|
||||
<div className={ styles.prompt_header }>
|
||||
<div className={ styles.prompt_button_div }>
|
||||
{[
|
||||
"Prompt Vorlage",
|
||||
"Einstellungen"
|
||||
t('dashboard.prompt.template'),
|
||||
t('dashboard.prompt.settings')
|
||||
].map((tab) => (
|
||||
<div key={tab} className={styles.buttonWrapper}>
|
||||
<button
|
||||
|
|
@ -83,7 +85,7 @@ const DashboardPrompt: React.FC<DashboardPromptProps> = ({
|
|||
}}
|
||||
>
|
||||
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
|
||||
{activeTab === "Prompt Vorlage" ? (
|
||||
{activeTab === t('dashboard.prompt.template') ? (
|
||||
<DashboardPromptSet onPromptRun={onPromptRun} />
|
||||
) : (
|
||||
<DashboardPromptSettings />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { usePrompts, usePromptOperations, Prompt } from '../../../../hooks/usePrompts';
|
||||
import { useLanguage } from '../../../../contexts/LanguageContext';
|
||||
import DashboardPromptSetItem from './DashboardPromptSetItem';
|
||||
import DashboardPromptSetModal from './DashboardPromptSetModal';
|
||||
import styles from './DashboardPromptSet.module.css';
|
||||
|
|
@ -10,6 +11,7 @@ interface DashboardPromptSetProps {
|
|||
}
|
||||
|
||||
function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
|
||||
const { t } = useLanguage();
|
||||
const { prompts, loading, error, refetch } = usePrompts();
|
||||
const { handlePromptCreate, creatingPrompt } = usePromptOperations();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
|
@ -27,7 +29,7 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
|
|||
if (loading) {
|
||||
return (
|
||||
<div className={styles.loadingContainer}>
|
||||
<div className={styles.loadingText}>Prompts werden geladen...</div>
|
||||
<div className={styles.loadingText}>{t('promptset.loading')}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -35,12 +37,12 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
|
|||
if (error) {
|
||||
return (
|
||||
<div className={styles.errorContainer}>
|
||||
<div className={styles.errorText}>Fehler beim Laden der Prompts: {error}</div>
|
||||
<div className={styles.errorText}>{t('promptset.error.loading')}: {error}</div>
|
||||
<button
|
||||
onClick={refetch}
|
||||
className={styles.retryButton}
|
||||
>
|
||||
Erneut versuchen
|
||||
{t('promptset.retry')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -52,11 +54,11 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
|
|||
<div className={styles.headerButtons}>
|
||||
<button className={styles.addButton} onClick={() => setIsModalOpen(true)}>
|
||||
<FaPlus />
|
||||
Neuer Prompt
|
||||
{t('promptset.new_prompt')}
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.promptCount}>
|
||||
{prompts.length} {prompts.length === 1 ? 'Prompt' : 'Prompts'}
|
||||
{prompts.length} {prompts.length === 1 ? t('promptset.prompt_count') : t('promptset.prompt_count_plural')}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -64,7 +66,7 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
|
|||
<div className={styles.scrollableContent}>
|
||||
{prompts.length === 0 ? (
|
||||
<div className={styles.emptyState}>
|
||||
Keine Prompts verfügbar
|
||||
{t('promptset.no_prompts')}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.promptsList}>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FaArrowRight } from 'react-icons/fa';
|
|||
import { AiOutlineDelete } from 'react-icons/ai';
|
||||
import { BsShareFill } from 'react-icons/bs';
|
||||
import { usePromptOperations, Prompt } from '../../../../hooks/usePrompts';
|
||||
import { useLanguage } from '../../../../contexts/LanguageContext';
|
||||
import styles from './DashboardPromptSetItem.module.css';
|
||||
|
||||
interface DashboardPromptSetItemProps {
|
||||
|
|
@ -12,6 +13,7 @@ interface DashboardPromptSetItemProps {
|
|||
}
|
||||
|
||||
function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetItemProps) {
|
||||
const { t } = useLanguage();
|
||||
const { handlePromptDelete, deletingPrompts, deleteError } = usePromptOperations();
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
|
@ -54,7 +56,7 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
|
|||
</h3>
|
||||
{prompt.createdAt && (
|
||||
<p className={styles.promptDate}>
|
||||
Erstellt: {new Date(prompt.createdAt).toLocaleDateString('de-DE')}
|
||||
{t('promptset.created')}: {new Date(prompt.createdAt).toLocaleDateString('de-DE')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -70,7 +72,7 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
|
|||
<button
|
||||
onClick={handleRun}
|
||||
className={`${styles.actionButton} ${styles.runButton}`}
|
||||
title="Prompt ausführen"
|
||||
title={t('promptset.run_tooltip')}
|
||||
>
|
||||
<FaArrowRight size={16} />
|
||||
</button>
|
||||
|
|
@ -78,7 +80,7 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
|
|||
<button
|
||||
onClick={handleShare}
|
||||
className={`${styles.actionButton} ${styles.shareButton}`}
|
||||
title="Prompt teilen"
|
||||
title={t('promptset.share_tooltip')}
|
||||
>
|
||||
<BsShareFill size={16} />
|
||||
</button>
|
||||
|
|
@ -87,25 +89,25 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
|
|||
onClick={handleDeleteClick}
|
||||
disabled={isDeleting}
|
||||
className={`${styles.actionButton} ${styles.deleteButton} ${showDeleteConfirm ? styles.confirm : ''}`}
|
||||
title={showDeleteConfirm ? "Klicken Sie erneut zum Bestätigen" : "Prompt löschen"}
|
||||
title={showDeleteConfirm ? t('promptset.confirm_delete') : t('promptset.delete_tooltip')}
|
||||
onBlur={handleCancelDelete}
|
||||
>
|
||||
<AiOutlineDelete size={16} />
|
||||
{isDeleting && <span className={styles.actionText}>Löschen...</span>}
|
||||
{showDeleteConfirm && <span className={styles.actionText}>Zum Bestätigen klicken</span>}
|
||||
{isDeleting && <span className={styles.actionText}>{t('promptset.deleting')}</span>}
|
||||
{showDeleteConfirm && <span className={styles.actionText}>{t('promptset.confirm_click')}</span>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{deleteError && (
|
||||
<div className={styles.errorMessage}>
|
||||
Fehler beim Löschen: {deleteError}
|
||||
{t('promptset.delete_error')}: {deleteError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDeleting && (
|
||||
<div className={styles.deletingMessage}>
|
||||
Prompt wird gelöscht...
|
||||
{t('promptset.deleting_message')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { useLanguage } from '../../../../contexts/LanguageContext';
|
||||
import styles from './DashboardPromptSetModal.module.css';
|
||||
|
||||
interface DashboardPromptSetModalProps {
|
||||
|
|
@ -10,6 +11,7 @@ interface DashboardPromptSetModalProps {
|
|||
}
|
||||
|
||||
function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false }: DashboardPromptSetModalProps) {
|
||||
const { t } = useLanguage();
|
||||
const [name, setName] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
|
@ -18,12 +20,12 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
|
|||
e.preventDefault();
|
||||
|
||||
if (!name.trim()) {
|
||||
setError('Name ist erforderlich');
|
||||
setError(t('modal.name_required'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content.trim()) {
|
||||
setError('Inhalt ist erforderlich');
|
||||
setError(t('modal.content_required'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +38,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
|
|||
setContent('');
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Fehler beim Erstellen des Prompts');
|
||||
setError(err.message || t('modal.create_error'));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -53,7 +55,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
|
|||
<div className={styles.overlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.header}>
|
||||
<h2 className={styles.title}>Neuen Prompt erstellen</h2>
|
||||
<h2 className={styles.title}>{t('modal.create_prompt')}</h2>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className={styles.closeButton}
|
||||
|
|
@ -66,7 +68,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
|
|||
<form onSubmit={handleSubmit} className={styles.form}>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="promptName" className={styles.label}>
|
||||
Name *
|
||||
{t('modal.name_label')} *
|
||||
</label>
|
||||
<input
|
||||
id="promptName"
|
||||
|
|
@ -74,7 +76,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
|
|||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className={styles.input}
|
||||
placeholder="Geben Sie einen Namen für den Prompt ein"
|
||||
placeholder={t('modal.name_placeholder')}
|
||||
disabled={isLoading}
|
||||
maxLength={100}
|
||||
/>
|
||||
|
|
@ -82,14 +84,14 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
|
|||
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="promptContent" className={styles.label}>
|
||||
Inhalt *
|
||||
{t('modal.content_label')} *
|
||||
</label>
|
||||
<textarea
|
||||
id="promptContent"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
className={styles.textarea}
|
||||
placeholder="Geben Sie den Inhalt des Prompts ein"
|
||||
placeholder={t('modal.content_placeholder')}
|
||||
disabled={isLoading}
|
||||
rows={8}
|
||||
/>
|
||||
|
|
@ -108,14 +110,14 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
|
|||
className={styles.cancelButton}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Abbrechen
|
||||
{t('modal.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.submitButton}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Erstellen...' : 'Prompt erstellen'}
|
||||
{isLoading ? t('modal.creating') : t('modal.create')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import React from 'react';
|
||||
import { useLanguage } from '../../../../contexts/LanguageContext';
|
||||
import styles from './DashboardPromptSettings.module.css';
|
||||
|
||||
function DashboardPromptSettings() {
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<h1 className={styles.title}>Dashboard Prompt Settings</h1>
|
||||
<p>Settings content will be added here in future updates.</p>
|
||||
<h1 className={styles.title}>{t('prompt_settings.title')}</h1>
|
||||
<p>{t('prompt_settings.content_placeholder')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
|||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import DateienItem from './DateienItem';
|
||||
import { UserFile } from '../../hooks/useFiles';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import styles from './DateienLists.module.css';
|
||||
|
||||
// Sort types
|
||||
|
|
@ -16,6 +17,7 @@ interface DateienAllProps {
|
|||
}
|
||||
|
||||
const DateienAll: React.FC<DateienAllProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
|
||||
const { t } = useLanguage();
|
||||
const [sortField, setSortField] = useState<SortField>('created_at');
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
|
||||
|
||||
|
|
@ -72,7 +74,7 @@ const DateienAll: React.FC<DateienAllProps> = ({ files, onFileDeleted, onOptimis
|
|||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<p>Keine Dateien gefunden.</p>
|
||||
<p>{t('files.no_files', 'No files found.')}</p>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -87,19 +89,19 @@ const DateienAll: React.FC<DateienAllProps> = ({ files, onFileDeleted, onOptimis
|
|||
{/* Table Headers */}
|
||||
<div className={styles.tableHeader}>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
|
||||
<span>Name</span>
|
||||
<span>{t('files.header.name', 'Name')}</span>
|
||||
{renderSortIcon('file_name')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('action')}>
|
||||
<span>Typ</span>
|
||||
<span>{t('files.header.type', 'Type')}</span>
|
||||
{renderSortIcon('action')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('size')}>
|
||||
<span>Größe</span>
|
||||
<span>{t('files.header.size', 'Size')}</span>
|
||||
{renderSortIcon('size')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
|
||||
<span>Datum</span>
|
||||
<span>{t('files.header.date', 'Date')}</span>
|
||||
{renderSortIcon('created_at')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
|||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import DateienItem from './DateienItem';
|
||||
import { UserFile } from '../../hooks/useFiles';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import styles from './DateienLists.module.css';
|
||||
|
||||
// Sort types
|
||||
|
|
@ -16,6 +17,7 @@ interface DateienCreatedProps {
|
|||
}
|
||||
|
||||
const DateienCreated: React.FC<DateienCreatedProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
|
||||
const { t } = useLanguage();
|
||||
const [sortField, setSortField] = useState<SortField>('created_at');
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
|
||||
|
||||
|
|
@ -72,7 +74,7 @@ const DateienCreated: React.FC<DateienCreatedProps> = ({ files, onFileDeleted, o
|
|||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<p>Keine von der KI erstellten Dateien gefunden.</p>
|
||||
<p>{t('files.no_ai_files', 'No AI-created files found.')}</p>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -87,19 +89,19 @@ const DateienCreated: React.FC<DateienCreatedProps> = ({ files, onFileDeleted, o
|
|||
{/* Table Headers */}
|
||||
<div className={styles.tableHeader}>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
|
||||
<span>Name</span>
|
||||
<span>{t('files.header.name', 'Name')}</span>
|
||||
{renderSortIcon('file_name')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('action')}>
|
||||
<span>Typ</span>
|
||||
<span>{t('files.header.type', 'Type')}</span>
|
||||
{renderSortIcon('action')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('size')}>
|
||||
<span>Größe</span>
|
||||
<span>{t('files.header.size', 'Size')}</span>
|
||||
{renderSortIcon('size')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
|
||||
<span>Datum</span>
|
||||
<span>{t('files.header.date', 'Date')}</span>
|
||||
{renderSortIcon('created_at')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -218,22 +218,22 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
color: #9ca3af;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.checkedIcon {
|
||||
color: var(--Brand-Green-Green, #3A8088);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.uncheckedIcon {
|
||||
color: #d1d5db;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.fileIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
color: #6b7280;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.fileInfo {
|
||||
|
|
@ -243,7 +243,7 @@
|
|||
|
||||
.fileName {
|
||||
font-weight: 500;
|
||||
color: #111827;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
@ -252,7 +252,7 @@
|
|||
|
||||
.fileDetails {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
|
@ -261,17 +261,17 @@
|
|||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
background-color: #f9fafb;
|
||||
border-top: 1px solid var(--color-gray-disabled);
|
||||
background-color: var(--color-bg);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cancelButton {
|
||||
padding: 10px 16px;
|
||||
border: 1px solid #d1d5db;
|
||||
border: 1px solid var(--color-red);
|
||||
border-radius: 8px;
|
||||
background-color: #ffffff;
|
||||
color: #374151;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
|
|
@ -279,16 +279,16 @@
|
|||
}
|
||||
|
||||
.cancelButton:hover {
|
||||
background-color: #f9fafb;
|
||||
border-color: #9ca3af;
|
||||
background-color: var(--color-red);
|
||||
border-color: var(--color-red);
|
||||
}
|
||||
|
||||
.confirmButton {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: var(--Brand-Green-Green, #3A8088);
|
||||
color: white;
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-bg);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
|
|
@ -296,11 +296,11 @@
|
|||
}
|
||||
|
||||
.confirmButton:hover:not(:disabled) {
|
||||
background-color: #2d6b73;
|
||||
background-color: var(--color-secondary-hover);
|
||||
}
|
||||
|
||||
.confirmButton:disabled {
|
||||
background-color: #9ca3af;
|
||||
background-color: var(--color-secondary-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import DateienAll from '../DateienAll';
|
|||
import DateienUploads from '../DateienUploads';
|
||||
import DateienCreated from '../DateienCreated';
|
||||
import DateienShared from '../DateienShared';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
import styles from './DateienSelector.module.css';
|
||||
|
||||
type FileListType = 'all' | 'uploads' | 'created' | 'shared';
|
||||
|
|
@ -25,6 +26,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
onFilesSelected,
|
||||
allowMultiple = true
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
|
||||
const [activeTab, setActiveTab] = useState<FileListType>('all');
|
||||
const [showUploadTool, setShowUploadTool] = useState(false);
|
||||
|
|
@ -156,10 +158,10 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
};
|
||||
|
||||
const labels = {
|
||||
all: 'Alle Dateien',
|
||||
uploads: 'Hochgeladen',
|
||||
created: 'KI-erstellt',
|
||||
shared: 'Geteilt'
|
||||
all: t('files.selector.tab.all', 'All files'),
|
||||
uploads: t('files.selector.tab.uploads', 'Uploaded'),
|
||||
created: t('files.selector.tab.created', 'AI-created'),
|
||||
shared: t('files.selector.tab.shared', 'Shared')
|
||||
};
|
||||
|
||||
return `${labels[type]} (${counts[type]})`;
|
||||
|
|
@ -187,7 +189,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<h2>Dateien auswählen</h2>
|
||||
<h2>{t('files.selector.title', 'Select files')}</h2>
|
||||
<button className={styles.closeButton} onClick={onClose}>
|
||||
<IoClose />
|
||||
</button>
|
||||
|
|
@ -217,18 +219,18 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
>
|
||||
{selectedFiles.size === filteredFiles.length ? (
|
||||
<>
|
||||
<IoCheckbox /> Alle abwählen
|
||||
<IoCheckbox /> {t('files.selector.deselect_all', 'Deselect all')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IoSquareOutline /> Alle auswählen
|
||||
<IoSquareOutline /> {t('files.selector.select_all', 'Select all')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{selectedFiles.size > 0 && (
|
||||
<span className={styles.selectionCount}>
|
||||
{selectedFiles.size} Datei{selectedFiles.size !== 1 ? 'en' : ''} ausgewählt
|
||||
{selectedFiles.size} {selectedFiles.size === 1 ? t('files.selector.file_selected', 'File') : t('files.selector.files_selected', 'Files')} {t('files.selector.selected_suffix', 'selected')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -238,18 +240,18 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
onClick={() => setShowUploadTool(true)}
|
||||
>
|
||||
<IoCloudUploadOutline />
|
||||
Neue Datei hochladen
|
||||
{t('files.selector.upload_new', 'Upload new file')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* File List */}
|
||||
<div className={styles.fileListContainer}>
|
||||
{loading ? (
|
||||
<div className={styles.loading}>Dateien werden geladen...</div>
|
||||
<div className={styles.loading}>{t('files.selector.loading', 'Loading files...')}</div>
|
||||
) : error ? (
|
||||
<div className={styles.error}>Fehler beim Laden der Dateien: {error}</div>
|
||||
<div className={styles.error}>{t('files.selector.error_loading', 'Error loading files:')} {error}</div>
|
||||
) : filteredFiles.length === 0 ? (
|
||||
<div className={styles.noFiles}>Keine Dateien in dieser Kategorie gefunden.</div>
|
||||
<div className={styles.noFiles}>{t('files.no_files', 'No files found.')}</div>
|
||||
) : (
|
||||
<div className={styles.selectableFileList}>
|
||||
{filteredFiles.map(file => (
|
||||
|
|
@ -273,7 +275,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
<div className={styles.fileInfo}>
|
||||
<div className={styles.fileName}>{file.file_name}</div>
|
||||
<div className={styles.fileDetails}>
|
||||
{file.action} • {file.size ? `${Math.round(file.size / 1024)} KB` : 'Unbekannte Größe'}
|
||||
{file.action} • {file.size ? `${Math.round(file.size / 1024)} KB` : t('files.unknown_size', 'Unknown Size')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -289,7 +291,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
className={styles.cancelButton}
|
||||
onClick={onClose}
|
||||
>
|
||||
Abbrechen
|
||||
{t('common.cancel', 'Cancel')}
|
||||
</button>
|
||||
<button
|
||||
className={styles.confirmButton}
|
||||
|
|
@ -297,8 +299,8 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
|
|||
disabled={selectedFiles.size === 0}
|
||||
>
|
||||
{selectedFiles.size === 0
|
||||
? 'Dateien auswählen'
|
||||
: `${selectedFiles.size} Datei${selectedFiles.size !== 1 ? 'en' : ''} hinzufügen`
|
||||
? t('files.selector.title', 'Select files')
|
||||
: `${selectedFiles.size} ${selectedFiles.size === 1 ? t('files.selector.file_selected', 'File') : t('files.selector.files_selected', 'Files')} ${t('common.save', 'Save')}`
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import styles from './DateienUploadTool.module.css';
|
|||
import { IoCloudUploadOutline } from "react-icons/io5";
|
||||
import { IoClose } from "react-icons/io5";
|
||||
import { useFileOperations } from '../../../hooks/useFiles';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
|
||||
interface DateienUploadToolProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -12,6 +13,7 @@ interface DateienUploadToolProps {
|
|||
}
|
||||
|
||||
function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolProps) {
|
||||
const { t } = useLanguage();
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [uploadStatus, setUploadStatus] = useState<{ success: boolean; message: string } | null>(null);
|
||||
|
|
@ -41,7 +43,7 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
|
|||
if (result.success) {
|
||||
setUploadStatus({
|
||||
success: true,
|
||||
message: 'Datei erfolgreich hochgeladen!'
|
||||
message: t('files.upload.success', 'File uploaded successfully!')
|
||||
});
|
||||
onFileUpload(selectedFile);
|
||||
setSelectedFile(null);
|
||||
|
|
@ -52,13 +54,13 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
|
|||
} else {
|
||||
setUploadStatus({
|
||||
success: false,
|
||||
message: uploadError || 'Beim Hochladen ist ein Fehler aufgetreten.'
|
||||
message: uploadError || t('files.upload.error', 'An error occurred while uploading.')
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setUploadStatus({
|
||||
success: false,
|
||||
message: 'Beim Hochladen ist ein unerwarteter Fehler aufgetreten.'
|
||||
message: t('files.upload.unexpected_error', 'An unexpected error occurred while uploading.')
|
||||
});
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
|
|
@ -72,7 +74,7 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
|
|||
<div className={styles.overlay}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalHeader}>
|
||||
<h2>Datei hochladen</h2>
|
||||
<h2>{t('files.upload.title', 'Upload file')}</h2>
|
||||
<button className={styles.closeButton} onClick={onClose} disabled={isUploading}>
|
||||
<IoClose />
|
||||
</button>
|
||||
|
|
@ -91,15 +93,15 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
|
|||
<input {...getInputProps()} disabled={isUploading} />
|
||||
<IoCloudUploadOutline className={styles.uploadIcon} />
|
||||
{isDragActive ? (
|
||||
<p>Datei hier ablegen...</p>
|
||||
<p>{t('files.upload.drop_here', 'Drop file here...')}</p>
|
||||
) : isUploading ? (
|
||||
<p>Lädt hoch...</p>
|
||||
<p>{t('files.upload.uploading', 'Uploading...')}</p>
|
||||
) : (
|
||||
<div className={styles.dropzoneText}>
|
||||
<p>Dateien hierher ziehen</p>
|
||||
<p>oder</p>
|
||||
<p>{t('files.upload.drag_files', 'Drag files here')}</p>
|
||||
<p>{t('files.upload.or', 'or')}</p>
|
||||
<button className={styles.browseButton} disabled={isUploading}>
|
||||
Durchsuchen
|
||||
{t('files.upload.browse', 'Browse')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -107,13 +109,13 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
|
|||
|
||||
{selectedFile && !isUploading && !uploadStatus?.success && (
|
||||
<div className={styles.selectedFile}>
|
||||
<p>Ausgewählte Datei: {selectedFile.name}</p>
|
||||
<p>{t('files.upload.selected_file', 'Selected file:')} {selectedFile.name}</p>
|
||||
<button
|
||||
className={styles.uploadButton}
|
||||
onClick={handleUpload}
|
||||
disabled={isUploading}
|
||||
>
|
||||
{isUploading ? 'Wird hochgeladen...' : 'Hochladen'}
|
||||
{isUploading ? t('files.upload.uploading_button', 'Uploading...') : t('files.upload.upload_button', 'Upload')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useState } from "react";
|
|||
import { useFileOperations } from "../../hooks/useFiles";
|
||||
import FilePreviewPopup from "../Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup";
|
||||
import { Document } from "../Dashboard/DashboardChat/DashboardChatArea/dashboardChatAreaTypes";
|
||||
import { useLanguage } from "../../contexts/LanguageContext";
|
||||
|
||||
type DateienItemProps = {
|
||||
file: {
|
||||
|
|
@ -19,39 +20,8 @@ type DateienItemProps = {
|
|||
onOptimisticDelete?: () => void; // New prop for immediate UI update
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a file size in bytes to a human-readable string (KB, MB, etc.)
|
||||
*/
|
||||
const formatFileSize = (bytes?: number): string => {
|
||||
if (bytes === undefined || bytes === null) return 'Unbekannte Größe';
|
||||
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
|
||||
if (i === 0) return `${bytes} ${sizes[i]}`;
|
||||
|
||||
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the file source for display
|
||||
*/
|
||||
const formatFileSource = (source?: string): string => {
|
||||
switch (source) {
|
||||
case 'user_uploaded':
|
||||
return 'Hochgeladen';
|
||||
case 'agent_created':
|
||||
return 'KI-erstellt';
|
||||
case 'shared_with_me':
|
||||
return 'Geteilt';
|
||||
default:
|
||||
return 'Unbekannt';
|
||||
}
|
||||
};
|
||||
|
||||
const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) => {
|
||||
const { t } = useLanguage();
|
||||
const { downloadingFiles, deletingFiles, handleFileDownload, handleFileDelete } = useFileOperations();
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [previewDocument, setPreviewDocument] = useState<Document | null>(null);
|
||||
|
|
@ -59,13 +29,45 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
|
|||
const isDownloading = downloadingFiles.has(file.id);
|
||||
const isDeleting = deletingFiles.has(file.id);
|
||||
|
||||
/**
|
||||
* Formats a file size in bytes to a human-readable string (KB, MB, etc.)
|
||||
*/
|
||||
const formatFileSize = (bytes?: number): string => {
|
||||
if (bytes === undefined || bytes === null) return t('files.unknown_size', 'Unknown Size');
|
||||
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
|
||||
if (i === 0) return `${bytes} ${sizes[i]}`;
|
||||
|
||||
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats the file source for display
|
||||
*/
|
||||
const formatFileSource = (source?: string): string => {
|
||||
switch (source) {
|
||||
case 'user_uploaded':
|
||||
return t('files.source.uploaded', 'Uploaded');
|
||||
case 'agent_created':
|
||||
return t('files.source.ai_created', 'AI-created');
|
||||
case 'shared_with_me':
|
||||
return t('files.source.shared', 'Shared');
|
||||
default:
|
||||
return t('files.source.unknown', 'Unknown');
|
||||
}
|
||||
};
|
||||
|
||||
// Format the date properly
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
// Check if date is valid
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Unbekanntes Datum';
|
||||
return t('files.unknown_date', 'Unknown Date');
|
||||
}
|
||||
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
|
|
@ -75,7 +77,7 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
|
|||
});
|
||||
} catch (e) {
|
||||
console.error('Error formatting date:', e);
|
||||
return 'Unbekanntes Datum';
|
||||
return t('files.unknown_date', 'Unknown Date');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -153,7 +155,7 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
|
|||
className={styles.previewButton}
|
||||
onClick={handlePreview}
|
||||
disabled={isDownloading || isDeleting}
|
||||
title="Preview file"
|
||||
title={t('files.preview_tooltip', 'Preview file')}
|
||||
>
|
||||
<MdOutlineRemoveRedEye className={styles.actionIcon} />
|
||||
</button>
|
||||
|
|
@ -161,21 +163,21 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
|
|||
className={`${styles.downloadButton} ${isDownloading ? styles.downloading : ''}`}
|
||||
onClick={() => handleFileDownload(file.id, file.file_name)}
|
||||
disabled={isDownloading || isDeleting}
|
||||
title="Download file"
|
||||
title={t('files.download_tooltip', 'Download file')}
|
||||
>
|
||||
<FaDownload className={styles.actionIcon} />
|
||||
{isDownloading && <span className={styles.actionText}>Laden...</span>}
|
||||
{isDownloading && <span className={styles.actionText}>{t('files.downloading', 'Downloading...')}</span>}
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.deleteButton} ${isDeleting ? styles.deleting : ''} ${showDeleteConfirm ? styles.confirm : ''}`}
|
||||
onClick={handleDeleteClick}
|
||||
disabled={isDownloading || isDeleting}
|
||||
title={showDeleteConfirm ? "Click again to confirm deletion" : "Delete file"}
|
||||
title={showDeleteConfirm ? t('files.delete_confirm_tooltip', 'Click again to confirm deletion') : t('files.delete_tooltip', 'Delete file')}
|
||||
onBlur={handleCancelDelete}
|
||||
>
|
||||
<FaTrash className={styles.actionIcon} />
|
||||
{isDeleting && <span className={styles.actionText}>Löschen...</span>}
|
||||
{showDeleteConfirm && <span className={styles.actionText}>Zum Bestätigen klicken...</span>}
|
||||
{isDeleting && <span className={styles.actionText}>{t('files.deleting', 'Deleting...')}</span>}
|
||||
{showDeleteConfirm && <span className={styles.actionText}>{t('files.delete_confirm', 'Click to confirm...')}</span>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
|||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import DateienItem from './DateienItem';
|
||||
import { UserFile } from '../../hooks/useFiles';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import styles from './DateienLists.module.css';
|
||||
|
||||
// Sort types
|
||||
|
|
@ -16,6 +17,7 @@ interface DateienSharedProps {
|
|||
}
|
||||
|
||||
const DateienShared: React.FC<DateienSharedProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
|
||||
const { t } = useLanguage();
|
||||
const [sortField, setSortField] = useState<SortField>('created_at');
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
|
||||
|
||||
|
|
@ -72,7 +74,7 @@ const DateienShared: React.FC<DateienSharedProps> = ({ files, onFileDeleted, onO
|
|||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<p>Keine mit Ihnen geteilten Dateien gefunden.</p>
|
||||
<p>{t('files.no_shared_files', 'No shared files found.')}</p>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -87,19 +89,19 @@ const DateienShared: React.FC<DateienSharedProps> = ({ files, onFileDeleted, onO
|
|||
{/* Table Headers */}
|
||||
<div className={styles.tableHeader}>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
|
||||
<span>Name</span>
|
||||
<span>{t('files.header.name', 'Name')}</span>
|
||||
{renderSortIcon('file_name')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('action')}>
|
||||
<span>Typ</span>
|
||||
<span>{t('files.header.type', 'Type')}</span>
|
||||
{renderSortIcon('action')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('size')}>
|
||||
<span>Größe</span>
|
||||
<span>{t('files.header.size', 'Size')}</span>
|
||||
{renderSortIcon('size')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
|
||||
<span>Datum</span>
|
||||
<span>{t('files.header.date', 'Date')}</span>
|
||||
{renderSortIcon('created_at')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
|||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import DateienItem from './DateienItem';
|
||||
import { UserFile } from '../../hooks/useFiles';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
import styles from './DateienLists.module.css';
|
||||
|
||||
// Sort types
|
||||
|
|
@ -16,6 +17,7 @@ interface DateienUploadsProps {
|
|||
}
|
||||
|
||||
const DateienUploads: React.FC<DateienUploadsProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
|
||||
const { t } = useLanguage();
|
||||
const [sortField, setSortField] = useState<SortField>('created_at');
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
|
||||
|
||||
|
|
@ -72,7 +74,7 @@ const DateienUploads: React.FC<DateienUploadsProps> = ({ files, onFileDeleted, o
|
|||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<p>Keine hochgeladenen Dateien gefunden.</p>
|
||||
<p>{t('files.no_uploaded_files', 'No uploaded files found.')}</p>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -87,19 +89,19 @@ const DateienUploads: React.FC<DateienUploadsProps> = ({ files, onFileDeleted, o
|
|||
{/* Table Headers */}
|
||||
<div className={styles.tableHeader}>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
|
||||
<span>Name</span>
|
||||
<span>{t('files.header.name', 'Name')}</span>
|
||||
{renderSortIcon('file_name')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('action')}>
|
||||
<span>Typ</span>
|
||||
<span>{t('files.header.type', 'Type')}</span>
|
||||
{renderSortIcon('action')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('size')}>
|
||||
<span>Größe</span>
|
||||
<span>{t('files.header.size', 'Size')}</span>
|
||||
{renderSortIcon('size')}
|
||||
</div>
|
||||
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
|
||||
<span>Datum</span>
|
||||
<span>{t('files.header.date', 'Date')}</span>
|
||||
{renderSortIcon('created_at')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,24 +8,27 @@ import { FaRegFileAlt } from "react-icons/fa";
|
|||
import { TbLogs } from "react-icons/tb";
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
|
||||
const useSidebarData = () => {
|
||||
const { t } = useLanguage();
|
||||
|
||||
return useMemo(() => [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Team-Bereich',
|
||||
name: t('nav.team'),
|
||||
link: '/team-bereich',
|
||||
icon: MdOutlineWorkOutline,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Aktivitätszentrum',
|
||||
name: t('nav.dashboard'),
|
||||
link: '/dashboard',
|
||||
icon: LuTicket,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Dateien',
|
||||
name: t('nav.files'),
|
||||
link: '/dateien',
|
||||
icon: FaRegFileAlt,
|
||||
},
|
||||
|
|
@ -37,7 +40,7 @@ const useSidebarData = () => {
|
|||
},*/
|
||||
{
|
||||
id: '7',
|
||||
name: 'Einstellungen',
|
||||
name: t('nav.settings'),
|
||||
link: '/einstellungen',
|
||||
icon: GoGear,
|
||||
},
|
||||
|
|
@ -47,7 +50,7 @@ const useSidebarData = () => {
|
|||
link: '',
|
||||
icon: BiInfoSquare,
|
||||
},*/
|
||||
], []);
|
||||
], [t]);
|
||||
}
|
||||
|
||||
export default useSidebarData;
|
||||
67
src/contexts/LanguageContext.tsx
Normal file
67
src/contexts/LanguageContext.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import { Language, translations } from './languageContextData';
|
||||
|
||||
// Re-export Language type for convenience
|
||||
export type { Language };
|
||||
|
||||
interface LanguageContextType {
|
||||
currentLanguage: Language;
|
||||
setLanguage: (language: Language) => void;
|
||||
t: (key: string, fallback?: string) => string;
|
||||
}
|
||||
|
||||
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
|
||||
|
||||
interface LanguageProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const LanguageProvider: React.FC<LanguageProviderProps> = ({ children }) => {
|
||||
const [currentLanguage, setCurrentLanguage] = useState<Language>('de');
|
||||
|
||||
// Load saved language preference on mount
|
||||
useEffect(() => {
|
||||
const savedLanguage = localStorage.getItem('language') as Language;
|
||||
if (savedLanguage && ['de', 'en', 'fr'].includes(savedLanguage)) {
|
||||
setCurrentLanguage(savedLanguage);
|
||||
} else {
|
||||
// Detect browser language
|
||||
const browserLang = navigator.language.split('-')[0] as Language;
|
||||
if (['de', 'en', 'fr'].includes(browserLang)) {
|
||||
setCurrentLanguage(browserLang);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setLanguage = (language: Language) => {
|
||||
setCurrentLanguage(language);
|
||||
localStorage.setItem('language', language);
|
||||
};
|
||||
|
||||
const t = (key: string, fallback?: string): string => {
|
||||
const translation = translations[currentLanguage]?.[key] || translations['de'][key] || fallback || key;
|
||||
return translation;
|
||||
};
|
||||
|
||||
const contextValue: LanguageContextType = {
|
||||
currentLanguage,
|
||||
setLanguage,
|
||||
t
|
||||
};
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useLanguage = (): LanguageContextType => {
|
||||
const context = useContext(LanguageContext);
|
||||
if (!context) {
|
||||
throw new Error('useLanguage must be used within a LanguageProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
626
src/contexts/languageContextData.ts
Normal file
626
src/contexts/languageContextData.ts
Normal file
|
|
@ -0,0 +1,626 @@
|
|||
// Language type definition
|
||||
export type Language = 'de' | 'en' | 'fr';
|
||||
|
||||
// Translation keys and their values for each language
|
||||
export type TranslationKeys = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export type Translations = {
|
||||
[K in Language]: TranslationKeys;
|
||||
};
|
||||
|
||||
export const translations: Translations = {
|
||||
de: {
|
||||
// Navigation
|
||||
'nav.dashboard': 'Aktivitätszentrum',
|
||||
'nav.files': 'Dateien',
|
||||
'nav.team': 'Team-Bereich',
|
||||
'nav.settings': 'Einstellungen',
|
||||
|
||||
// Settings page
|
||||
'settings.title': 'Einstellungen',
|
||||
'settings.appearance': 'Darstellung',
|
||||
'settings.language': 'Sprache',
|
||||
'settings.about': 'Über',
|
||||
'settings.version': 'Version',
|
||||
'settings.theme': 'Theme',
|
||||
'settings.theme.description': 'Wechseln Sie zwischen hellem und dunklem Modus',
|
||||
'settings.language.description': 'Wählen Sie Ihre bevorzugte Sprache',
|
||||
'settings.theme.light': 'Hell',
|
||||
'settings.theme.dark': 'Dunkel',
|
||||
'settings.theme.toggle.light': 'Zu hellem Modus wechseln',
|
||||
'settings.theme.toggle.dark': 'Zu dunklem Modus wechseln',
|
||||
|
||||
// Languages
|
||||
'language.german': 'Deutsch',
|
||||
'language.english': 'English',
|
||||
'language.french': 'Français',
|
||||
|
||||
// Common
|
||||
'common.loading': 'Laden...',
|
||||
'common.error': 'Fehler',
|
||||
'common.success': 'Erfolgreich',
|
||||
'common.cancel': 'Abbrechen',
|
||||
'common.save': 'Speichern',
|
||||
'common.delete': 'Löschen',
|
||||
'common.edit': 'Bearbeiten',
|
||||
'common.close': 'Schließen',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Anmelden',
|
||||
'auth.register': 'Registrieren',
|
||||
'auth.logout': 'Abmelden',
|
||||
'auth.email': 'E-Mail',
|
||||
'auth.password': 'Passwort',
|
||||
|
||||
// Dashboard
|
||||
'dashboard.prompt.template': 'Prompt Vorlage',
|
||||
'dashboard.prompt.settings': 'Einstellungen',
|
||||
'dashboard.chat.area': 'Chatbereich',
|
||||
'dashboard.chat.history': 'Workflow-Verlauf',
|
||||
'dashboard.log.title': 'Log',
|
||||
'dashboard.log.workflow': 'Workflow',
|
||||
'dashboard.log.no_workflow': 'Kein Workflow ausgewählt',
|
||||
'dashboard.log.loading': 'Logs werden geladen...',
|
||||
'dashboard.log.error': 'Fehler beim Laden der Logs',
|
||||
'dashboard.log.no_logs': 'Keine Logs für diesen Workflow verfügbar',
|
||||
'dashboard.log.waiting': 'Workflow läuft... Warte auf Logs...',
|
||||
'dashboard.log.fetch_failed': 'Logs konnten nicht geladen werden',
|
||||
'dashboard.log.level.info': 'INFO',
|
||||
|
||||
// Prompt Set
|
||||
'promptset.loading': 'Prompts werden geladen...',
|
||||
'promptset.error.loading': 'Fehler beim Laden der Prompts',
|
||||
'promptset.retry': 'Erneut versuchen',
|
||||
'promptset.new_prompt': 'Neuer Prompt',
|
||||
'promptset.prompt_count': 'Prompt',
|
||||
'promptset.prompt_count_plural': 'Prompts',
|
||||
'promptset.no_prompts': 'Keine Prompts verfügbar',
|
||||
'promptset.created': 'Erstellt',
|
||||
'promptset.run_tooltip': 'Prompt ausführen',
|
||||
'promptset.share_tooltip': 'Prompt teilen',
|
||||
'promptset.delete_tooltip': 'Prompt löschen',
|
||||
'promptset.confirm_delete': 'Klicken Sie erneut zum Bestätigen',
|
||||
'promptset.deleting': 'Löschen...',
|
||||
'promptset.confirm_click': 'Zum Bestätigen klicken',
|
||||
'promptset.delete_error': 'Fehler beim Löschen',
|
||||
'promptset.deleting_message': 'Prompt wird gelöscht...',
|
||||
|
||||
// Prompt Modal
|
||||
'modal.create_prompt': 'Neuen Prompt erstellen',
|
||||
'modal.name_required': 'Name ist erforderlich',
|
||||
'modal.content_required': 'Inhalt ist erforderlich',
|
||||
'modal.create_error': 'Fehler beim Erstellen des Prompts',
|
||||
'modal.name_label': 'Name',
|
||||
'modal.content_label': 'Inhalt',
|
||||
'modal.name_placeholder': 'Geben Sie einen Namen für den Prompt ein',
|
||||
'modal.content_placeholder': 'Geben Sie den Inhalt des Prompts ein',
|
||||
'modal.cancel': 'Abbrechen',
|
||||
'modal.creating': 'Erstellen...',
|
||||
'modal.create': 'Prompt erstellen',
|
||||
|
||||
// Prompt Settings
|
||||
'prompt_settings.title': 'Prompt Einstellungen',
|
||||
'prompt_settings.content_placeholder': 'Einstellungen werden in zukünftigen Updates hinzugefügt.',
|
||||
|
||||
// Chat Area
|
||||
'chat.continue_conversation': 'Gespräch fortsetzen...',
|
||||
'chat.enter_message': 'Nachricht eingeben...',
|
||||
'chat.remove_file': 'Datei entfernen',
|
||||
'chat.attach_file': 'Datei anhängen',
|
||||
'chat.you': 'You',
|
||||
'chat.click_to_open': 'Klicken Sie, um zu öffnen',
|
||||
'chat.preview_document': 'Dokument vorschauen',
|
||||
'chat.download_document': 'Dokument herunterladen',
|
||||
'chat.workflow_failed': 'Workflow fehlgeschlagen.',
|
||||
'chat.retry_workflow': 'Nochmal versuchen',
|
||||
'chat.sending_followup': 'Folgenachricht wird gesendet...',
|
||||
'chat.sending_message': 'Nachricht wird gesendet...',
|
||||
'chat.error_prefix': 'Fehler:',
|
||||
'chat.error_loading_messages': 'Fehler beim Laden der Nachrichten:',
|
||||
'chat.loading_workflow_messages': 'Workflow-Nachrichten werden geladen...',
|
||||
'chat.start_conversation': 'Beginne ein Gespräch, indem du eine Nachricht eingibst, eine Vorlage auswählst oder einen vorherigen Workflow fortsetzt …',
|
||||
|
||||
// File Preview
|
||||
'file_preview.loading': 'Vorschau wird geladen...',
|
||||
'file_preview.error': 'Fehler',
|
||||
'file_preview.no_preview': 'Keine Vorschau verfügbar',
|
||||
'file_preview.close_preview': 'Vorschau schließen',
|
||||
'file_preview.python': 'Python',
|
||||
|
||||
// Chat History
|
||||
'chat_history.loading': 'Workflows werden geladen...',
|
||||
'chat_history.error_loading': 'Fehler beim Laden der Workflows:',
|
||||
'chat_history.try_again': 'Nochmal versuchen',
|
||||
'chat_history.title': 'Workflow-Verlauf',
|
||||
'chat_history.workflow_count': 'Workflow',
|
||||
'chat_history.workflow_count_plural': 'Workflows',
|
||||
'chat_history.empty_state': 'Keine Workflows verfügbar',
|
||||
'chat_history.confirm_delete': 'Sind Sie sicher, dass Sie Workflow "{id}..." löschen möchten?',
|
||||
'chat_history.no_message_content': 'Kein Nachrichteninhalt verfügbar',
|
||||
'chat_history.unknown_date': 'Unbekanntes Datum',
|
||||
'chat_history.invalid_date': 'Ungültiges Datum',
|
||||
'chat_history.started': 'Gestartet:',
|
||||
'chat_history.last_activity': 'Letzte Aktivität:',
|
||||
'chat_history.round': 'Runde',
|
||||
'chat_history.resume_tooltip': 'Workflow fortsetzen',
|
||||
'chat_history.delete_tooltip': 'Workflow löschen',
|
||||
'chat_history.deleting': 'Workflow wird gelöscht...',
|
||||
|
||||
// Workflow Status
|
||||
'status.error': 'FEHLER',
|
||||
'status.failed': 'FEHLGESCHLAGEN',
|
||||
'status.stopped': 'GESTOPPT',
|
||||
'status.cancelled': 'ABGEBROCHEN',
|
||||
'status.running': 'LÄUFT',
|
||||
'status.processing': 'VERARBEITUNG',
|
||||
'status.completed': 'ABGESCHLOSSEN',
|
||||
'status.pending': 'WARTEND',
|
||||
|
||||
// Files
|
||||
'files.unknown_size': 'Unbekannte Größe',
|
||||
'files.unknown_date': 'Unbekanntes Datum',
|
||||
'files.source.uploaded': 'Hochgeladen',
|
||||
'files.source.ai_created': 'KI-erstellt',
|
||||
'files.source.shared': 'Geteilt',
|
||||
'files.source.unknown': 'Unbekannt',
|
||||
'files.preview_tooltip': 'Datei vorschauen',
|
||||
'files.download_tooltip': 'Datei herunterladen',
|
||||
'files.delete_tooltip': 'Datei löschen',
|
||||
'files.delete_confirm_tooltip': 'Klicken Sie erneut zum Bestätigen der Löschung',
|
||||
'files.downloading': 'Laden...',
|
||||
'files.deleting': 'Löschen...',
|
||||
'files.delete_confirm': 'Zum Bestätigen klicken...',
|
||||
'files.no_files': 'Keine Dateien gefunden.',
|
||||
'files.no_shared_files': 'Keine mit Ihnen geteilten Dateien gefunden.',
|
||||
'files.no_ai_files': 'Keine von der KI erstellten Dateien gefunden.',
|
||||
'files.no_uploaded_files': 'Keine hochgeladenen Dateien gefunden.',
|
||||
'files.header.name': 'Name',
|
||||
'files.header.type': 'Typ',
|
||||
'files.header.size': 'Größe',
|
||||
'files.header.date': 'Datum',
|
||||
'files.selector.title': 'Dateien auswählen',
|
||||
'files.selector.tab.all': 'Alle Dateien',
|
||||
'files.selector.tab.uploads': 'Hochgeladen',
|
||||
'files.selector.tab.created': 'KI-erstellt',
|
||||
'files.selector.tab.shared': 'Geteilt',
|
||||
'files.selector.select_all': 'Alle auswählen',
|
||||
'files.selector.deselect_all': 'Alle abwählen',
|
||||
'files.selector.file_selected': 'Datei',
|
||||
'files.selector.files_selected': 'Dateien',
|
||||
'files.selector.selected_suffix': 'ausgewählt',
|
||||
'files.selector.upload_new': 'Neue Datei hochladen',
|
||||
'files.selector.loading': 'Dateien werden geladen...',
|
||||
'files.selector.error_loading': 'Fehler beim Laden der Dateien:',
|
||||
'files.upload.title': 'Datei hochladen',
|
||||
'files.upload.drop_here': 'Datei hier ablegen...',
|
||||
'files.upload.uploading': 'Lädt hoch...',
|
||||
'files.upload.drag_files': 'Dateien hierher ziehen',
|
||||
'files.upload.or': 'oder',
|
||||
'files.upload.browse': 'Durchsuchen',
|
||||
'files.upload.selected_file': 'Ausgewählte Datei:',
|
||||
'files.upload.upload_button': 'Hochladen',
|
||||
'files.upload.uploading_button': 'Wird hochgeladen...',
|
||||
'files.upload.success': 'Datei erfolgreich hochgeladen!',
|
||||
'files.upload.error': 'Beim Hochladen ist ein Fehler aufgetreten.',
|
||||
'files.upload.unexpected_error': 'Beim Hochladen ist ein unerwarteter Fehler aufgetreten.',
|
||||
|
||||
// Files Page
|
||||
'files.page.tab.all': 'Alle Dateien',
|
||||
'files.page.tab.uploads': 'Meine Uploads',
|
||||
'files.page.tab.created': 'Erstellte Dateien',
|
||||
'files.page.tab.shared': 'Geteilte Dateien',
|
||||
'files.page.add_file': 'Datei hinzufügen',
|
||||
'files.page.loading': 'Dateien werden geladen...',
|
||||
'files.page.error': 'Fehler:',
|
||||
},
|
||||
en: {
|
||||
// Navigation
|
||||
'nav.dashboard': 'Activity Center',
|
||||
'nav.files': 'Files',
|
||||
'nav.team': 'Team Area',
|
||||
'nav.settings': 'Settings',
|
||||
|
||||
// Settings page
|
||||
'settings.title': 'Settings',
|
||||
'settings.appearance': 'Appearance',
|
||||
'settings.language': 'Language',
|
||||
'settings.about': 'About',
|
||||
'settings.version': 'Version',
|
||||
'settings.theme': 'Theme',
|
||||
'settings.theme.description': 'Switch between light and dark mode',
|
||||
'settings.language.description': 'Choose your preferred language',
|
||||
'settings.theme.light': 'Light',
|
||||
'settings.theme.dark': 'Dark',
|
||||
'settings.theme.toggle.light': 'Switch to light mode',
|
||||
'settings.theme.toggle.dark': 'Switch to dark mode',
|
||||
|
||||
// Languages
|
||||
'language.german': 'Deutsch',
|
||||
'language.english': 'English',
|
||||
'language.french': 'Français',
|
||||
|
||||
// Common
|
||||
'common.loading': 'Loading...',
|
||||
'common.error': 'Error',
|
||||
'common.success': 'Success',
|
||||
'common.cancel': 'Cancel',
|
||||
'common.save': 'Save',
|
||||
'common.delete': 'Delete',
|
||||
'common.edit': 'Edit',
|
||||
'common.close': 'Close',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Login',
|
||||
'auth.register': 'Register',
|
||||
'auth.logout': 'Logout',
|
||||
'auth.email': 'Email',
|
||||
'auth.password': 'Password',
|
||||
|
||||
// Dashboard
|
||||
'dashboard.prompt.template': 'Prompt Template',
|
||||
'dashboard.prompt.settings': 'Settings',
|
||||
'dashboard.chat.area': 'Chat Area',
|
||||
'dashboard.chat.history': 'Workflow History',
|
||||
'dashboard.log.title': 'Log',
|
||||
'dashboard.log.workflow': 'Workflow',
|
||||
'dashboard.log.no_workflow': 'No workflow selected',
|
||||
'dashboard.log.loading': 'Loading logs...',
|
||||
'dashboard.log.error': 'Error loading logs',
|
||||
'dashboard.log.no_logs': 'No logs available for this workflow',
|
||||
'dashboard.log.waiting': 'Workflow running... Waiting for logs...',
|
||||
'dashboard.log.fetch_failed': 'Failed to fetch logs',
|
||||
'dashboard.log.level.info': 'INFO',
|
||||
|
||||
// Prompt Set
|
||||
'promptset.loading': 'Loading prompts...',
|
||||
'promptset.error.loading': 'Error loading prompts',
|
||||
'promptset.retry': 'Try again',
|
||||
'promptset.new_prompt': 'New Prompt',
|
||||
'promptset.prompt_count': 'Prompt',
|
||||
'promptset.prompt_count_plural': 'Prompts',
|
||||
'promptset.no_prompts': 'No prompts available',
|
||||
'promptset.created': 'Created',
|
||||
'promptset.run_tooltip': 'Run prompt',
|
||||
'promptset.share_tooltip': 'Share prompt',
|
||||
'promptset.delete_tooltip': 'Delete prompt',
|
||||
'promptset.confirm_delete': 'Click again to confirm',
|
||||
'promptset.deleting': 'Deleting...',
|
||||
'promptset.confirm_click': 'Click to confirm',
|
||||
'promptset.delete_error': 'Error deleting',
|
||||
'promptset.deleting_message': 'Deleting prompt...',
|
||||
|
||||
// Prompt Modal
|
||||
'modal.create_prompt': 'Create New Prompt',
|
||||
'modal.name_required': 'Name is required',
|
||||
'modal.content_required': 'Content is required',
|
||||
'modal.create_error': 'Error creating prompt',
|
||||
'modal.name_label': 'Name',
|
||||
'modal.content_label': 'Content',
|
||||
'modal.name_placeholder': 'Enter a name for the prompt',
|
||||
'modal.content_placeholder': 'Enter the prompt content',
|
||||
'modal.cancel': 'Cancel',
|
||||
'modal.creating': 'Creating...',
|
||||
'modal.create': 'Create Prompt',
|
||||
|
||||
// Prompt Settings
|
||||
'prompt_settings.title': 'Prompt Settings',
|
||||
'prompt_settings.content_placeholder': 'Settings content will be added here in future updates.',
|
||||
|
||||
// Chat Area
|
||||
'chat.continue_conversation': 'Continue conversation...',
|
||||
'chat.enter_message': 'Enter message...',
|
||||
'chat.remove_file': 'Remove file',
|
||||
'chat.attach_file': 'Attach file',
|
||||
'chat.you': 'You',
|
||||
'chat.click_to_open': 'Click to open',
|
||||
'chat.preview_document': 'Preview document',
|
||||
'chat.download_document': 'Download document',
|
||||
'chat.workflow_failed': 'Workflow failed.',
|
||||
'chat.retry_workflow': 'Try again',
|
||||
'chat.sending_followup': 'Sending follow-up message...',
|
||||
'chat.sending_message': 'Sending message...',
|
||||
'chat.error_prefix': 'Error:',
|
||||
'chat.error_loading_messages': 'Error loading messages:',
|
||||
'chat.loading_workflow_messages': 'Loading workflow messages...',
|
||||
'chat.start_conversation': 'Start a conversation by entering a message, selecting a template, or continuing a previous workflow...',
|
||||
|
||||
// File Preview
|
||||
'file_preview.loading': 'Loading preview...',
|
||||
'file_preview.error': 'Error',
|
||||
'file_preview.no_preview': 'No preview available',
|
||||
'file_preview.close_preview': 'Close preview',
|
||||
'file_preview.python': 'Python',
|
||||
|
||||
// Chat History
|
||||
'chat_history.loading': 'Loading workflows...',
|
||||
'chat_history.error_loading': 'Error loading workflows:',
|
||||
'chat_history.try_again': 'Try Again',
|
||||
'chat_history.title': 'Workflow History',
|
||||
'chat_history.workflow_count': 'Workflow',
|
||||
'chat_history.workflow_count_plural': 'Workflows',
|
||||
'chat_history.empty_state': 'No workflows available',
|
||||
'chat_history.confirm_delete': 'Are you sure you want to delete workflow "{id}..."?',
|
||||
'chat_history.no_message_content': 'No message content available',
|
||||
'chat_history.unknown_date': 'Unknown date',
|
||||
'chat_history.invalid_date': 'Invalid date',
|
||||
'chat_history.started': 'Started:',
|
||||
'chat_history.last_activity': 'Last Activity:',
|
||||
'chat_history.round': 'Round',
|
||||
'chat_history.resume_tooltip': 'Resume workflow',
|
||||
'chat_history.delete_tooltip': 'Delete workflow',
|
||||
'chat_history.deleting': 'Deleting workflow...',
|
||||
|
||||
// Workflow Status
|
||||
'status.error': 'ERROR',
|
||||
'status.failed': 'FAILED',
|
||||
'status.stopped': 'STOPPED',
|
||||
'status.cancelled': 'CANCELLED',
|
||||
'status.running': 'RUNNING',
|
||||
'status.processing': 'PROCESSING',
|
||||
'status.completed': 'COMPLETED',
|
||||
'status.pending': 'PENDING',
|
||||
|
||||
// Files
|
||||
'files.unknown_size': 'Unknown Size',
|
||||
'files.unknown_date': 'Unknown Date',
|
||||
'files.source.uploaded': 'Uploaded',
|
||||
'files.source.ai_created': 'AI-created',
|
||||
'files.source.shared': 'Shared',
|
||||
'files.source.unknown': 'Unknown',
|
||||
'files.preview_tooltip': 'Preview file',
|
||||
'files.download_tooltip': 'Download file',
|
||||
'files.delete_tooltip': 'Delete file',
|
||||
'files.delete_confirm_tooltip': 'Click again to confirm deletion',
|
||||
'files.downloading': 'Downloading...',
|
||||
'files.deleting': 'Deleting...',
|
||||
'files.delete_confirm': 'Click to confirm...',
|
||||
'files.no_files': 'No files found.',
|
||||
'files.no_shared_files': 'No shared files found.',
|
||||
'files.no_ai_files': 'No AI-created files found.',
|
||||
'files.no_uploaded_files': 'No uploaded files found.',
|
||||
'files.header.name': 'Name',
|
||||
'files.header.type': 'Type',
|
||||
'files.header.size': 'Size',
|
||||
'files.header.date': 'Date',
|
||||
'files.selector.title': 'Select files',
|
||||
'files.selector.tab.all': 'All files',
|
||||
'files.selector.tab.uploads': 'Uploaded',
|
||||
'files.selector.tab.created': 'AI-created',
|
||||
'files.selector.tab.shared': 'Shared',
|
||||
'files.selector.select_all': 'Select all',
|
||||
'files.selector.deselect_all': 'Deselect all',
|
||||
'files.selector.file_selected': 'File',
|
||||
'files.selector.files_selected': 'Files',
|
||||
'files.selector.selected_suffix': 'selected',
|
||||
'files.selector.upload_new': 'Upload new file',
|
||||
'files.selector.loading': 'Loading files...',
|
||||
'files.selector.error_loading': 'Error loading files:',
|
||||
'files.upload.title': 'Upload file',
|
||||
'files.upload.drop_here': 'Drop file here...',
|
||||
'files.upload.uploading': 'Uploading...',
|
||||
'files.upload.drag_files': 'Drag files here',
|
||||
'files.upload.or': 'or',
|
||||
'files.upload.browse': 'Browse',
|
||||
'files.upload.selected_file': 'Selected file:',
|
||||
'files.upload.upload_button': 'Upload',
|
||||
'files.upload.uploading_button': 'Uploading...',
|
||||
'files.upload.success': 'File uploaded successfully!',
|
||||
'files.upload.error': 'An error occurred while uploading.',
|
||||
'files.upload.unexpected_error': 'An unexpected error occurred while uploading.',
|
||||
|
||||
// Files Page
|
||||
'files.page.tab.all': 'All Files',
|
||||
'files.page.tab.uploads': 'My Uploads',
|
||||
'files.page.tab.created': 'Created Files',
|
||||
'files.page.tab.shared': 'Shared Files',
|
||||
'files.page.add_file': 'Add File',
|
||||
'files.page.loading': 'Loading files...',
|
||||
'files.page.error': 'Error:',
|
||||
},
|
||||
fr: {
|
||||
// Navigation
|
||||
'nav.dashboard': 'Centre d\'activité',
|
||||
'nav.files': 'Fichiers',
|
||||
'nav.team': 'Espace équipe',
|
||||
'nav.settings': 'Paramètres',
|
||||
|
||||
// Settings page
|
||||
'settings.title': 'Paramètres',
|
||||
'settings.appearance': 'Apparence',
|
||||
'settings.language': 'Langue',
|
||||
'settings.about': 'À propos',
|
||||
'settings.version': 'Version',
|
||||
'settings.theme': 'Thème',
|
||||
'settings.theme.description': 'Basculer entre le mode clair et sombre',
|
||||
'settings.language.description': 'Choisissez votre langue préférée',
|
||||
'settings.theme.light': 'Clair',
|
||||
'settings.theme.dark': 'Sombre',
|
||||
'settings.theme.toggle.light': 'Passer en mode clair',
|
||||
'settings.theme.toggle.dark': 'Passer en mode sombre',
|
||||
|
||||
// Languages
|
||||
'language.german': 'Deutsch',
|
||||
'language.english': 'English',
|
||||
'language.french': 'Français',
|
||||
|
||||
// Common
|
||||
'common.loading': 'Chargement...',
|
||||
'common.error': 'Erreur',
|
||||
'common.success': 'Succès',
|
||||
'common.cancel': 'Annuler',
|
||||
'common.save': 'Enregistrer',
|
||||
'common.delete': 'Supprimer',
|
||||
'common.edit': 'Modifier',
|
||||
'common.close': 'Fermer',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Se connecter',
|
||||
'auth.register': 'S\'inscrire',
|
||||
'auth.logout': 'Se déconnecter',
|
||||
'auth.email': 'E-mail',
|
||||
'auth.password': 'Mot de passe',
|
||||
|
||||
// Dashboard
|
||||
'dashboard.prompt.template': 'Modèle de prompt',
|
||||
'dashboard.prompt.settings': 'Paramètres',
|
||||
'dashboard.chat.area': 'Zone de chat',
|
||||
'dashboard.chat.history': 'Historique des workflows',
|
||||
'dashboard.log.title': 'Journal',
|
||||
'dashboard.log.workflow': 'Workflow',
|
||||
'dashboard.log.no_workflow': 'Aucun workflow sélectionné',
|
||||
'dashboard.log.loading': 'Chargement des logs...',
|
||||
'dashboard.log.error': 'Erreur lors du chargement des logs',
|
||||
'dashboard.log.no_logs': 'Aucun log disponible pour ce workflow',
|
||||
'dashboard.log.waiting': 'Workflow en cours... En attente des logs...',
|
||||
'dashboard.log.fetch_failed': 'Échec du chargement des logs',
|
||||
'dashboard.log.level.info': 'INFO',
|
||||
|
||||
// Prompt Set
|
||||
'promptset.loading': 'Chargement des prompts...',
|
||||
'promptset.error.loading': 'Erreur lors du chargement des prompts',
|
||||
'promptset.retry': 'Réessayer',
|
||||
'promptset.new_prompt': 'Nouveau prompt',
|
||||
'promptset.prompt_count': 'Prompt',
|
||||
'promptset.prompt_count_plural': 'Prompts',
|
||||
'promptset.no_prompts': 'Aucun prompt disponible',
|
||||
'promptset.created': 'Créé',
|
||||
'promptset.run_tooltip': 'Exécuter le prompt',
|
||||
'promptset.share_tooltip': 'Partager le prompt',
|
||||
'promptset.delete_tooltip': 'Supprimer le prompt',
|
||||
'promptset.confirm_delete': 'Cliquez à nouveau pour confirmer',
|
||||
'promptset.deleting': 'Suppression...',
|
||||
'promptset.confirm_click': 'Cliquez pour confirmer',
|
||||
'promptset.delete_error': 'Erreur lors de la suppression',
|
||||
'promptset.deleting_message': 'Suppression du prompt...',
|
||||
|
||||
// Prompt Modal
|
||||
'modal.create_prompt': 'Créer un nouveau prompt',
|
||||
'modal.name_required': 'Le nom est requis',
|
||||
'modal.content_required': 'Le contenu est requis',
|
||||
'modal.create_error': 'Erreur lors de la création du prompt',
|
||||
'modal.name_label': 'Nom',
|
||||
'modal.content_label': 'Contenu',
|
||||
'modal.name_placeholder': 'Entrez un nom pour le prompt',
|
||||
'modal.content_placeholder': 'Entrez le contenu du prompt',
|
||||
'modal.cancel': 'Annuler',
|
||||
'modal.creating': 'Création...',
|
||||
'modal.create': 'Créer le prompt',
|
||||
|
||||
// Prompt Settings
|
||||
'prompt_settings.title': 'Paramètres de prompt',
|
||||
'prompt_settings.content_placeholder': 'Le contenu des paramètres sera ajouté dans les futures mises à jour.',
|
||||
|
||||
// Chat Area
|
||||
'chat.continue_conversation': 'Continuer la conversation...',
|
||||
'chat.enter_message': 'Entrez votre message...',
|
||||
'chat.remove_file': 'Supprimer le fichier',
|
||||
'chat.attach_file': 'Joindre un fichier',
|
||||
'chat.you': 'Vous',
|
||||
'chat.click_to_open': 'Cliquez pour ouvrir',
|
||||
'chat.preview_document': 'Aperçu du document',
|
||||
'chat.download_document': 'Télécharger le document',
|
||||
'chat.workflow_failed': 'Échec du workflow.',
|
||||
'chat.retry_workflow': 'Réessayer',
|
||||
'chat.sending_followup': 'Envoi du message de suivi...',
|
||||
'chat.sending_message': 'Envoi du message...',
|
||||
'chat.error_prefix': 'Erreur:',
|
||||
'chat.error_loading_messages': 'Erreur lors du chargement des messages:',
|
||||
'chat.loading_workflow_messages': 'Chargement des messages de workflow...',
|
||||
'chat.start_conversation': 'Commencez une conversation en entrant un message, en sélectionnant un modèle ou en continuant un workflow précédent...',
|
||||
|
||||
// File Preview
|
||||
'file_preview.loading': 'Chargement de l\'aperçu...',
|
||||
'file_preview.error': 'Erreur',
|
||||
'file_preview.no_preview': 'Aucun aperçu disponible',
|
||||
'file_preview.close_preview': 'Fermer l\'aperçu',
|
||||
'file_preview.python': 'Python',
|
||||
|
||||
// Chat History
|
||||
'chat_history.loading': 'Chargement des workflows...',
|
||||
'chat_history.error_loading': 'Erreur lors du chargement des workflows:',
|
||||
'chat_history.try_again': 'Réessayer',
|
||||
'chat_history.title': 'Historique des workflows',
|
||||
'chat_history.workflow_count': 'Workflow',
|
||||
'chat_history.workflow_count_plural': 'Workflows',
|
||||
'chat_history.empty_state': 'Aucun workflow disponible',
|
||||
'chat_history.confirm_delete': 'Êtes-vous sûr de vouloir supprimer le workflow "{id}..."?',
|
||||
'chat_history.no_message_content': 'Aucun contenu de message disponible',
|
||||
'chat_history.unknown_date': 'Date inconnue',
|
||||
'chat_history.invalid_date': 'Date invalide',
|
||||
'chat_history.started': 'Démarré:',
|
||||
'chat_history.last_activity': 'Dernière activité:',
|
||||
'chat_history.round': 'Tour',
|
||||
'chat_history.resume_tooltip': 'Reprendre le workflow',
|
||||
'chat_history.delete_tooltip': 'Supprimer le workflow',
|
||||
'chat_history.deleting': 'Suppression du workflow...',
|
||||
|
||||
// Workflow Status
|
||||
'status.error': 'ERREUR',
|
||||
'status.failed': 'ÉCHEC',
|
||||
'status.stopped': 'ARRÊTÉ',
|
||||
'status.cancelled': 'ANNULÉ',
|
||||
'status.running': 'EN COURS',
|
||||
'status.processing': 'TRAITEMENT',
|
||||
'status.completed': 'TERMINÉ',
|
||||
'status.pending': 'EN ATTENTE',
|
||||
|
||||
// Files
|
||||
'files.unknown_size': 'Taille inconnue',
|
||||
'files.unknown_date': 'Date inconnue',
|
||||
'files.source.uploaded': 'Téléchargé',
|
||||
'files.source.ai_created': 'Créé par IA',
|
||||
'files.source.shared': 'Partagé',
|
||||
'files.source.unknown': 'Inconnu',
|
||||
'files.preview_tooltip': 'Aperçu du fichier',
|
||||
'files.download_tooltip': 'Télécharger le fichier',
|
||||
'files.delete_tooltip': 'Supprimer le fichier',
|
||||
'files.delete_confirm_tooltip': 'Cliquez à nouveau pour confirmer la suppression',
|
||||
'files.downloading': 'Téléchargement...',
|
||||
'files.deleting': 'Suppression...',
|
||||
'files.delete_confirm': 'Cliquez pour confirmer...',
|
||||
'files.no_files': 'Aucun fichier trouvé.',
|
||||
'files.no_shared_files': 'Aucun fichier partagé trouvé.',
|
||||
'files.no_ai_files': 'Aucun fichier créé par IA trouvé.',
|
||||
'files.no_uploaded_files': 'Aucun fichier téléchargé trouvé.',
|
||||
'files.header.name': 'Nom',
|
||||
'files.header.type': 'Type',
|
||||
'files.header.size': 'Taille',
|
||||
'files.header.date': 'Date',
|
||||
'files.selector.title': 'Sélectionner des fichiers',
|
||||
'files.selector.tab.all': 'Tous les fichiers',
|
||||
'files.selector.tab.uploads': 'Téléchargés',
|
||||
'files.selector.tab.created': 'Créés par IA',
|
||||
'files.selector.tab.shared': 'Partagés',
|
||||
'files.selector.select_all': 'Tout sélectionner',
|
||||
'files.selector.deselect_all': 'Tout désélectionner',
|
||||
'files.selector.file_selected': 'Fichier',
|
||||
'files.selector.files_selected': 'Fichiers',
|
||||
'files.selector.selected_suffix': 'sélectionné(s)',
|
||||
'files.selector.upload_new': 'Télécharger un nouveau fichier',
|
||||
'files.selector.loading': 'Chargement des fichiers...',
|
||||
'files.selector.error_loading': 'Erreur lors du chargement des fichiers:',
|
||||
'files.upload.title': 'Télécharger un fichier',
|
||||
'files.upload.drop_here': 'Déposer le fichier ici...',
|
||||
'files.upload.uploading': 'Téléchargement...',
|
||||
'files.upload.drag_files': 'Glisser les fichiers ici',
|
||||
'files.upload.or': 'ou',
|
||||
'files.upload.browse': 'Parcourir',
|
||||
'files.upload.selected_file': 'Fichier sélectionné:',
|
||||
'files.upload.upload_button': 'Télécharger',
|
||||
'files.upload.uploading_button': 'Téléchargement...',
|
||||
'files.upload.success': 'Fichier téléchargé avec succès!',
|
||||
'files.upload.error': 'Une erreur s\'est produite lors du téléchargement.',
|
||||
'files.upload.unexpected_error': 'Une erreur inattendue s\'est produite lors du téléchargement.',
|
||||
|
||||
// Files Page
|
||||
'files.page.tab.all': 'Tous les fichiers',
|
||||
'files.page.tab.uploads': 'Mes téléchargements',
|
||||
'files.page.tab.created': 'Fichiers créés',
|
||||
'files.page.tab.shared': 'Fichiers partagés',
|
||||
'files.page.add_file': 'Ajouter un fichier',
|
||||
'files.page.loading': 'Chargement des fichiers...',
|
||||
'files.page.error': 'Erreur:',
|
||||
}
|
||||
};
|
||||
|
|
@ -8,22 +8,24 @@ import DateienShared from '../../components/Dateien/DateienShared';
|
|||
import { useState } from 'react';
|
||||
import { useUserFiles, useFileOperations } from '../../hooks/useFiles';
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { useLanguage } from '../../contexts/LanguageContext';
|
||||
|
||||
// Tab types
|
||||
type TabType = 'alle' | 'uploads' | 'erstellt' | 'geteilt';
|
||||
|
||||
function Dateien() {
|
||||
const { t } = useLanguage();
|
||||
const { files, loading, error, refetch, removeFileOptimistically } = useUserFiles();
|
||||
const [isUploadOpen, setIsUploadOpen] = useState(false);
|
||||
const { uploadError, downloadError, deleteError } = useFileOperations();
|
||||
const [activeTab, setActiveTab] = useState<TabType>('alle');
|
||||
|
||||
// Tab configuration
|
||||
// Tab configuration with translations
|
||||
const tabs = [
|
||||
{ key: 'alle' as TabType, label: 'Alle Dateien' },
|
||||
{ key: 'uploads' as TabType, label: 'Meine Uploads' },
|
||||
{ key: 'erstellt' as TabType, label: 'Erstellte Dateien' },
|
||||
{ key: 'geteilt' as TabType, label: 'Geteilte Dateien' }
|
||||
{ key: 'alle' as TabType, label: t('files.page.tab.all', 'All Files') },
|
||||
{ key: 'uploads' as TabType, label: t('files.page.tab.uploads', 'My Uploads') },
|
||||
{ key: 'erstellt' as TabType, label: t('files.page.tab.created', 'Created Files') },
|
||||
{ key: 'geteilt' as TabType, label: t('files.page.tab.shared', 'Shared Files') }
|
||||
];
|
||||
|
||||
// Single function to handle file refresh
|
||||
|
|
@ -112,7 +114,7 @@ function Dateien() {
|
|||
onClick={() => setIsUploadOpen(true)}
|
||||
>
|
||||
<IoAddCircleOutline className={styles.add_icon}/>
|
||||
Datei hinzufügen
|
||||
{t('files.page.add_file', 'Add File')}
|
||||
</button>
|
||||
</motion.div>
|
||||
<div className={styles.horizontalLineLight}></div>
|
||||
|
|
@ -130,8 +132,8 @@ function Dateien() {
|
|||
</p>
|
||||
)}
|
||||
|
||||
{loading && <p>Loading files...</p>}
|
||||
{error && <p>Error: {error}</p>}
|
||||
{loading && <p>{t('files.page.loading', 'Loading files...')}</p>}
|
||||
{error && <p>{t('files.page.error', 'Error:')} {error}</p>}
|
||||
|
||||
{!loading && !error && (
|
||||
<motion.div
|
||||
|
|
|
|||
|
|
@ -148,6 +148,40 @@
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.languageSelect {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
border-radius: 25px;
|
||||
border: 2px solid var(--color-gray-disabled);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-family: var(--font-family);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
min-width: 140px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.languageSelect:hover {
|
||||
border-color: var(--color-secondary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(63, 81, 181, 0.15);
|
||||
}
|
||||
|
||||
.languageSelect:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(63, 81, 181, 0.1);
|
||||
}
|
||||
|
||||
.languageSelect option {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.einstellungenContainer {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import styles from './Einstellungen.module.css';
|
||||
import { useLanguage, Language } from '../../contexts/LanguageContext';
|
||||
|
||||
function Einstellungen() {
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
const { currentLanguage, setLanguage, t } = useLanguage();
|
||||
|
||||
// Sync component state with current theme on mount
|
||||
useEffect(() => {
|
||||
|
|
@ -29,27 +31,36 @@ function Einstellungen() {
|
|||
localStorage.setItem('theme', newIsDarkMode ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
const getLanguageLabel = (lang: Language): string => {
|
||||
switch (lang) {
|
||||
case 'de': return t('language.german');
|
||||
case 'en': return t('language.english');
|
||||
case 'fr': return t('language.french');
|
||||
default: return lang;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.einstellungenContainer}>
|
||||
<div className={styles.contentWrapper}>
|
||||
<div className={styles.settingsCard}>
|
||||
<h1 className={styles.title}>Einstellungen</h1>
|
||||
<h1 className={styles.title}>{t('settings.title')}</h1>
|
||||
|
||||
<div className={styles.settingsSection}>
|
||||
<h2 className={styles.sectionTitle}>Darstellung</h2>
|
||||
<h2 className={styles.sectionTitle}>{t('settings.appearance')}</h2>
|
||||
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingInfo}>
|
||||
<span className={styles.settingLabel}>Theme</span>
|
||||
<span className={styles.settingLabel}>{t('settings.theme')}</span>
|
||||
<span className={styles.settingDescription}>
|
||||
Wechseln Sie zwischen hellem und dunklem Modus
|
||||
{t('settings.theme.description')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`${styles.themeToggle} ${isDarkMode ? styles.dark : styles.light}`}
|
||||
onClick={toggleTheme}
|
||||
aria-label={isDarkMode ? 'Zu hellem Modus wechseln' : 'Zu dunklem Modus wechseln'}
|
||||
aria-label={isDarkMode ? t('settings.theme.toggle.light') : t('settings.theme.toggle.dark')}
|
||||
>
|
||||
<div className={styles.toggleSlider}>
|
||||
<div className={styles.toggleIcon}>
|
||||
|
|
@ -57,17 +68,37 @@ function Einstellungen() {
|
|||
</div>
|
||||
</div>
|
||||
<span className={styles.toggleLabel}>
|
||||
{isDarkMode ? 'Dunkel' : 'Hell'}
|
||||
{isDarkMode ? t('settings.theme.dark') : t('settings.theme.light')}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingInfo}>
|
||||
<span className={styles.settingLabel}>{t('settings.language')}</span>
|
||||
<span className={styles.settingDescription}>
|
||||
{t('settings.language.description')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<select
|
||||
className={styles.languageSelect}
|
||||
value={currentLanguage}
|
||||
onChange={(e) => setLanguage(e.target.value as Language)}
|
||||
aria-label={t('settings.language')}
|
||||
>
|
||||
<option value="de">{getLanguageLabel('de')}</option>
|
||||
<option value="en">{getLanguageLabel('en')}</option>
|
||||
<option value="fr">{getLanguageLabel('fr')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.settingsSection}>
|
||||
<h2 className={styles.sectionTitle}>Über</h2>
|
||||
<h2 className={styles.sectionTitle}>{t('settings.about')}</h2>
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingInfo}>
|
||||
<span className={styles.settingLabel}>Version</span>
|
||||
<span className={styles.settingLabel}>{t('settings.version')}</span>
|
||||
<span className={styles.settingDescription}>1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue