frontend_nyla/src/components/Dateien/DateienHinzufügen/DateienSelector.tsx

310 lines
No EOL
12 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { IoClose, IoCheckbox, IoSquareOutline, IoCloudUploadOutline } from 'react-icons/io5';
import { FaFile } from 'react-icons/fa';
import { useUserFiles, UserFile, FileInfo } from '../../../hooks/useFiles';
import DateienUploadTool from './DateienUploadTool';
import DateienAll from '../DateienAll';
import DateienUploads from '../DateienUploads';
import DateienCreated from '../DateienCreated';
import DateienShared from '../DateienShared';
import styles from './DateienSelector.module.css';
type FileListType = 'all' | 'uploads' | 'created' | 'shared';
interface DateienSelectorProps {
isOpen: boolean;
onClose: () => void;
onFilesSelected: (files: FileInfo[]) => void;
allowMultiple?: boolean;
}
const DateienSelector: React.FC<DateienSelectorProps> = ({
isOpen,
onClose,
onFilesSelected,
allowMultiple = true
}) => {
const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
const [activeTab, setActiveTab] = useState<FileListType>('all');
const [showUploadTool, setShowUploadTool] = useState(false);
const { files, loading, error, refetch } = useUserFiles();
// Filter files based on source
const getFilteredFiles = (files: UserFile[], type: FileListType): UserFile[] => {
switch (type) {
case 'uploads':
return files.filter(file => file.source === 'user_uploaded');
case 'created':
return files.filter(file => file.source === 'agent_created');
case 'shared':
return files.filter(file => file.source === 'shared_with_me');
case 'all':
default:
return files;
}
};
const filteredFiles = getFilteredFiles(files, activeTab);
// Reset selection when tab changes
useEffect(() => {
setSelectedFiles(new Set());
}, [activeTab]);
// Reset state when modal closes
useEffect(() => {
if (!isOpen) {
setSelectedFiles(new Set());
setShowUploadTool(false);
}
}, [isOpen]);
const handleFileSelect = (fileId: number) => {
if (allowMultiple) {
setSelectedFiles(prev => {
const newSet = new Set(prev);
if (newSet.has(fileId)) {
newSet.delete(fileId);
} else {
newSet.add(fileId);
}
return newSet;
});
} else {
setSelectedFiles(new Set([fileId]));
}
};
const handleSelectAll = () => {
if (selectedFiles.size === filteredFiles.length) {
setSelectedFiles(new Set());
} else {
setSelectedFiles(new Set(filteredFiles.map(file => file.id)));
}
};
const handleConfirmSelection = () => {
const selectedFileObjects: FileInfo[] = files
.filter(file => selectedFiles.has(file.id))
.map(file => ({
id: file.id,
name: file.file_name,
mimeType: deriveMimeTypeFromAction(file.action),
size: file.size,
creationDate: file.created_at,
source: file.source
}));
onFilesSelected(selectedFileObjects);
onClose();
};
// Helper function to derive MIME type from action (reverse mapping)
const deriveMimeTypeFromAction = (action: string): string => {
switch (action.toLowerCase()) {
case 'bild':
return 'image/jpeg'; // Default image type
case 'pdf':
return 'application/pdf';
case 'dokument':
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
case 'tabelle':
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
case 'text':
return 'text/plain';
case 'video':
return 'video/mp4'; // Default video type
case 'audio':
return 'audio/mpeg'; // Default audio type
default:
return 'application/octet-stream'; // Default binary type
}
};
const handleFileUpload = (file: File) => {
// Refresh file list after upload
refetch();
setShowUploadTool(false);
};
const renderFileListComponent = () => {
const commonProps = {
files: filteredFiles,
onFileDeleted: refetch
};
switch (activeTab) {
case 'uploads':
return <DateienUploads {...commonProps} />;
case 'created':
return <DateienCreated {...commonProps} />;
case 'shared':
return <DateienShared {...commonProps} />;
case 'all':
default:
return <DateienAll {...commonProps} />;
}
};
const getTabLabel = (type: FileListType) => {
const counts = {
all: files.length,
uploads: files.filter(f => f.source === 'user_uploaded').length,
created: files.filter(f => f.source === 'agent_created').length,
shared: files.filter(f => f.source === 'shared_with_me').length
};
const labels = {
all: 'Alle Dateien',
uploads: 'Hochgeladen',
created: 'KI-erstellt',
shared: 'Geteilt'
};
return `${labels[type]} (${counts[type]})`;
};
if (!isOpen) return null;
if (showUploadTool) {
return (
<DateienUploadTool
isOpen={true}
onClose={() => setShowUploadTool(false)}
onFileUpload={handleFileUpload}
/>
);
}
return (
<div className={styles.overlay}>
<motion.div
className={styles.modal}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.3 }}
>
<div className={styles.header}>
<h2>Dateien auswählen</h2>
<button className={styles.closeButton} onClick={onClose}>
<IoClose />
</button>
</div>
<div className={styles.content}>
{/* Tab Navigation */}
<div className={styles.tabNavigation}>
{(['all', 'uploads', 'created', 'shared'] as FileListType[]).map(tab => (
<button
key={tab}
className={`${styles.tabButton} ${activeTab === tab ? styles.active : ''}`}
onClick={() => setActiveTab(tab)}
>
{getTabLabel(tab)}
</button>
))}
</div>
{/* Action Bar */}
<div className={styles.actionBar}>
<div className={styles.selectionControls}>
{allowMultiple && filteredFiles.length > 0 && (
<button
className={styles.selectAllButton}
onClick={handleSelectAll}
>
{selectedFiles.size === filteredFiles.length ? (
<>
<IoCheckbox /> Alle abwählen
</>
) : (
<>
<IoSquareOutline /> Alle auswählen
</>
)}
</button>
)}
{selectedFiles.size > 0 && (
<span className={styles.selectionCount}>
{selectedFiles.size} Datei{selectedFiles.size !== 1 ? 'en' : ''} ausgewählt
</span>
)}
</div>
<button
className={styles.uploadButton}
onClick={() => setShowUploadTool(true)}
>
<IoCloudUploadOutline />
Neue Datei hochladen
</button>
</div>
{/* File List */}
<div className={styles.fileListContainer}>
{loading ? (
<div className={styles.loading}>Dateien werden geladen...</div>
) : error ? (
<div className={styles.error}>Fehler beim Laden der Dateien: {error}</div>
) : filteredFiles.length === 0 ? (
<div className={styles.noFiles}>Keine Dateien in dieser Kategorie gefunden.</div>
) : (
<div className={styles.selectableFileList}>
{filteredFiles.map(file => (
<div
key={file.id}
className={`${styles.selectableFileItem} ${
selectedFiles.has(file.id) ? styles.selected : ''
}`}
onClick={() => handleFileSelect(file.id)}
>
<div className={styles.fileCheckbox}>
{selectedFiles.has(file.id) ? (
<IoCheckbox className={styles.checkedIcon} />
) : (
<IoSquareOutline className={styles.uncheckedIcon} />
)}
</div>
<div className={styles.fileIcon}>
<FaFile />
</div>
<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'}
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
{/* Footer */}
<div className={styles.footer}>
<button
className={styles.cancelButton}
onClick={onClose}
>
Abbrechen
</button>
<button
className={styles.confirmButton}
onClick={handleConfirmSelection}
disabled={selectedFiles.size === 0}
>
{selectedFiles.size === 0
? 'Dateien auswählen'
: `${selectedFiles.size} Datei${selectedFiles.size !== 1 ? 'en' : ''} hinzufügen`
}
</button>
</div>
</motion.div>
</div>
);
};
export default DateienSelector;