Create FRONTEND_DESIGN_RULES.md as the single source of truth for all dashboard UI conventions, including component library (Syncfusion first), component defaults table, layout structure, buttons, dialogs, badges, toasts, form fields, tabs, statistics cards, warnings, color palette, CSS files, loading states, locale rules, and icon conventions (TentTree for skip-holidays events). Move embedded design rules from ACADEMIC_PERIODS_CRUD_BUILD_PLAN.md to the new file and replace with a reference link for maintainability. Update copilot-instructions.md to point to FRONTEND_DESIGN_RULES.md and remove redundant Syncfusion/Tailwind prose from the Theming section. Add reference blockquote to README.md under Frontend Features directing readers to FRONTEND_DESIGN_RULES.md. Bug fix: Presentation events now reliably persist page_progress and auto_progress flags across create, update, and detached occurrence flows so display settings survive round-trips to the API. Files changed: - Created: FRONTEND_DESIGN_RULES.md (15 sections, 340+ lines) - Modified: ACADEMIC_PERIODS_CRUD_BUILD_PLAN.md (extract rules, consolidate) - Modified: .github/copilot-instructions.md (link to new rules file) - Modified: README.md (reference blockquote)
151 lines
4.6 KiB
TypeScript
151 lines
4.6 KiB
TypeScript
import React, { useMemo, useState } from 'react';
|
|
import { useAuth } from '../useAuth';
|
|
import { DialogComponent } from '@syncfusion/ej2-react-popups';
|
|
import {
|
|
FileManagerComponent,
|
|
Inject,
|
|
NavigationPane,
|
|
DetailsView,
|
|
Toolbar,
|
|
} from '@syncfusion/ej2-react-filemanager';
|
|
|
|
const hostUrl = '/api/eventmedia/filemanager/';
|
|
|
|
type CustomSelectUploadEventModalProps = {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
onSelect: (file: { id: string; path: string; name: string }) => void; // name ergänzt
|
|
selectedFileId?: string | null;
|
|
};
|
|
|
|
const CustomSelectUploadEventModal: React.FC<CustomSelectUploadEventModalProps> = props => {
|
|
const { open, onClose, onSelect } = props;
|
|
const { user } = useAuth();
|
|
const isSuperadmin = useMemo(() => user?.role === 'superadmin', [user]);
|
|
|
|
const [selectedFile, setSelectedFile] = useState<{
|
|
id: string;
|
|
path: string;
|
|
name: string;
|
|
} | null>(null);
|
|
const [selectionError, setSelectionError] = useState<string>('');
|
|
|
|
// Callback für Dateiauswahl
|
|
interface FileSelectEventArgs {
|
|
fileDetails: {
|
|
name: string;
|
|
isFile: boolean;
|
|
size: number;
|
|
// weitere Felder falls benötigt
|
|
};
|
|
}
|
|
|
|
const handleFileSelect = async (args: FileSelectEventArgs) => {
|
|
if (args.fileDetails.isFile && args.fileDetails.size > 0) {
|
|
const filename = args.fileDetails.name;
|
|
setSelectionError('');
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`/api/eventmedia/find_by_filename?filename=${encodeURIComponent(filename)}`
|
|
);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setSelectedFile({ id: data.id, path: data.file_path, name: filename });
|
|
} else {
|
|
setSelectedFile(null);
|
|
setSelectionError('Datei ist noch nicht als Medium registriert. Bitte erneut hochladen oder Metadaten prüfen.');
|
|
}
|
|
} catch (e) {
|
|
console.error('Error fetching file details:', e);
|
|
setSelectedFile(null);
|
|
setSelectionError('Medium-ID konnte nicht geladen werden. Bitte erneut versuchen.');
|
|
}
|
|
}
|
|
};
|
|
|
|
// Button-Handler
|
|
const handleSelectClick = () => {
|
|
if (selectedFile) {
|
|
onSelect(selectedFile);
|
|
}
|
|
};
|
|
|
|
type FileItem = { name: string; isFile: boolean };
|
|
type ReadSuccessArgs = { action: string; result?: { files?: FileItem[] } };
|
|
type FileOpenArgs = { fileDetails?: FileItem; cancel?: boolean };
|
|
|
|
const handleSuccess = (args: ReadSuccessArgs) => {
|
|
if (isSuperadmin) return;
|
|
if (args && args.action === 'read' && args.result && Array.isArray(args.result.files)) {
|
|
args.result.files = args.result.files.filter((f: FileItem) => !(f.name === 'converted' && !f.isFile));
|
|
}
|
|
};
|
|
|
|
const handleFileOpen = (args: FileOpenArgs) => {
|
|
if (!isSuperadmin && args && args.fileDetails && args.fileDetails.name === 'converted' && !args.fileDetails.isFile) {
|
|
args.cancel = true;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<DialogComponent
|
|
target="#root"
|
|
visible={open}
|
|
width="700px"
|
|
header="Medium auswählen/hochladen"
|
|
showCloseIcon={true}
|
|
close={onClose}
|
|
isModal={true}
|
|
footerTemplate={() => (
|
|
<div className="flex gap-2 justify-end">
|
|
<button className="e-btn" onClick={onClose}>
|
|
Abbrechen
|
|
</button>
|
|
<button className="e-btn e-primary" disabled={!selectedFile} onClick={handleSelectClick}>
|
|
Auswählen
|
|
</button>
|
|
</div>
|
|
)}
|
|
>
|
|
<FileManagerComponent
|
|
cssClass="e-bigger media-icons-xl"
|
|
success={handleSuccess}
|
|
fileOpen={handleFileOpen}
|
|
ajaxSettings={{
|
|
url: hostUrl + 'operations',
|
|
getImageUrl: hostUrl + 'get-image',
|
|
uploadUrl: hostUrl + 'upload',
|
|
downloadUrl: hostUrl + 'download',
|
|
}}
|
|
toolbarSettings={{
|
|
items: [
|
|
'NewFolder',
|
|
'Upload',
|
|
'Download',
|
|
'Rename',
|
|
'Delete',
|
|
'SortBy',
|
|
'Refresh',
|
|
'Details',
|
|
],
|
|
}}
|
|
contextMenuSettings={{
|
|
file: ['Open', '|', 'Download', '|', 'Rename', 'Delete', '|', 'Details'],
|
|
folder: ['Open', '|', 'Rename', 'Delete', '|', 'Details'],
|
|
layout: ['SortBy', 'Refresh', '|', 'View', 'Details'],
|
|
}}
|
|
allowMultiSelection={false}
|
|
fileSelect={handleFileSelect}
|
|
>
|
|
<Inject services={[NavigationPane, DetailsView, Toolbar]} />
|
|
</FileManagerComponent>
|
|
{selectionError && (
|
|
<div style={{ marginTop: 10, color: '#b71c1c', fontSize: 13 }}>{selectionError}</div>
|
|
)}
|
|
</DialogComponent>
|
|
);
|
|
};
|
|
|
|
export default CustomSelectUploadEventModal;
|