205 lines
6.8 KiB
TypeScript
205 lines
6.8 KiB
TypeScript
/**
|
||
* RealEstateProjectsView
|
||
*
|
||
* Projekt-Verwaltung für eine Real Estate/PEK-Instanz.
|
||
* Verwendet FormGeneratorTable analog zu TrusteeDocumentsView.
|
||
*/
|
||
|
||
import React, { useState, useMemo, useEffect } from 'react';
|
||
import {
|
||
useRealEstateProjects,
|
||
useRealEstateProjectOperations,
|
||
type RealEstateProject,
|
||
} from '../../../hooks/useRealEstate';
|
||
import { useInstanceId } from '../../../hooks/useCurrentInstance';
|
||
import { FormGeneratorTable } from '../../../components/FormGenerator/FormGeneratorTable';
|
||
import { FormGeneratorForm } from '../../../components/FormGenerator/FormGeneratorForm';
|
||
import { FaSync, FaBuilding } from 'react-icons/fa';
|
||
import styles from '../../admin/Admin.module.css';
|
||
|
||
export const RealEstateProjectsView: React.FC = () => {
|
||
const instanceId = useInstanceId();
|
||
|
||
const {
|
||
items: projects,
|
||
attributes,
|
||
permissions,
|
||
pagination,
|
||
loading,
|
||
error,
|
||
refetch,
|
||
fetchById,
|
||
updateOptimistically,
|
||
removeOptimistically,
|
||
} = useRealEstateProjects();
|
||
|
||
const {
|
||
handleDelete,
|
||
handleCreate,
|
||
handleUpdate,
|
||
deletingItems,
|
||
} = useRealEstateProjectOperations();
|
||
|
||
const [editingProject, setEditingProject] = useState<RealEstateProject | null>(null);
|
||
const [isCreateMode, setIsCreateMode] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (instanceId) refetch();
|
||
}, [instanceId, refetch]);
|
||
|
||
const columns = useMemo(() => {
|
||
return (attributes || []).map(attr => ({
|
||
key: attr.name,
|
||
label: attr.label || attr.name,
|
||
type: (attr.type || 'string') as 'string' | 'number' | 'date' | 'boolean',
|
||
sortable: attr.sortable !== false,
|
||
filterable: attr.filterable !== false,
|
||
searchable: attr.searchable !== false,
|
||
width: attr.width || 150,
|
||
minWidth: attr.minWidth || 100,
|
||
maxWidth: attr.maxWidth || 400,
|
||
}));
|
||
}, [attributes]);
|
||
|
||
const canCreate = permissions?.create !== 'n';
|
||
const canUpdate = permissions?.update !== 'n';
|
||
const canDelete = permissions?.delete !== 'n';
|
||
|
||
const handleEditClick = async (project: RealEstateProject) => {
|
||
const full = await fetchById(project.id);
|
||
if (full) {
|
||
setEditingProject(full);
|
||
setIsCreateMode(false);
|
||
}
|
||
};
|
||
|
||
const handleCreateClick = () => {
|
||
setEditingProject(null);
|
||
setIsCreateMode(true);
|
||
};
|
||
|
||
const handleFormSubmit = async (data: Partial<RealEstateProject>) => {
|
||
if (isCreateMode) {
|
||
const result = await handleCreate(data);
|
||
if (result.success) {
|
||
setIsCreateMode(false);
|
||
refetch();
|
||
}
|
||
} else if (editingProject) {
|
||
const result = await handleUpdate(editingProject.id, data);
|
||
if (result.success) {
|
||
setEditingProject(null);
|
||
refetch();
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleDeleteProject = async (project: RealEstateProject) => {
|
||
removeOptimistically(project.id);
|
||
const success = await handleDelete(project.id);
|
||
if (!success) refetch();
|
||
};
|
||
|
||
const handleCloseModal = () => {
|
||
setEditingProject(null);
|
||
setIsCreateMode(false);
|
||
};
|
||
|
||
const formAttributes = useMemo(() => {
|
||
const excluded = ['id', 'mandateId', 'instanceId', '_createdBy', '_createdAt', '_modifiedAt', '_modifiedBy'];
|
||
return (attributes || []).filter(attr => !excluded.includes(attr.name));
|
||
}, [attributes]);
|
||
|
||
const handleInlineUpdate = async (itemId: string, updateData: Partial<RealEstateProject>, row: RealEstateProject) => {
|
||
updateOptimistically(itemId, updateData);
|
||
const result = await handleUpdate(itemId, { ...row, ...updateData });
|
||
if (!result.success) refetch();
|
||
};
|
||
|
||
if (error) {
|
||
return (
|
||
<div className={styles.adminPage}>
|
||
<div className={styles.errorContainer}>
|
||
<span className={styles.errorIcon}>⚠️</span>
|
||
<p className={styles.errorMessage}>Fehler beim Laden der Projekte: {error}</p>
|
||
<button className={styles.secondaryButton} onClick={() => refetch()}>
|
||
<FaSync /> Erneut versuchen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className={`${styles.adminPage} ${styles.adminPageFill}`}>
|
||
<div className={styles.pageHeader}>
|
||
<div>
|
||
<p className={styles.pageSubtitle}>Projekte verwalten</p>
|
||
</div>
|
||
<div className={styles.headerActions}>
|
||
<button className={styles.secondaryButton} onClick={() => refetch()} disabled={loading}>
|
||
<FaSync className={loading ? 'spinning' : ''} /> Aktualisieren
|
||
</button>
|
||
{canCreate && (
|
||
<button className={styles.primaryButton} onClick={handleCreateClick}>
|
||
+ Neues Projekt
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className={styles.tableContainer}>
|
||
<FormGeneratorTable
|
||
data={projects}
|
||
columns={columns}
|
||
loading={loading}
|
||
pagination={true}
|
||
pageSize={25}
|
||
searchable={true}
|
||
filterable={true}
|
||
sortable={true}
|
||
selectable={false}
|
||
actionButtons={[
|
||
...(canUpdate ? [{ type: 'edit' as const, onAction: handleEditClick, title: 'Bearbeiten' }] : []),
|
||
...(canDelete ? [{ type: 'delete' as const, title: 'Löschen', loading: (row: RealEstateProject) => deletingItems.has(row.id) }] : []),
|
||
]}
|
||
onDelete={handleDeleteProject}
|
||
hookData={{ refetch, permissions, pagination, handleDelete, handleInlineUpdate, updateOptimistically }}
|
||
emptyMessage="Keine Projekte gefunden"
|
||
/>
|
||
</div>
|
||
|
||
{(editingProject || isCreateMode) && (
|
||
<div className={styles.modalOverlay} onClick={handleCloseModal}>
|
||
<div className={styles.modal} onClick={e => e.stopPropagation()}>
|
||
<div className={styles.modalHeader}>
|
||
<h2 className={styles.modalTitle}>{isCreateMode ? 'Neues Projekt' : 'Projekt bearbeiten'}</h2>
|
||
<button className={styles.modalClose} onClick={handleCloseModal}>✕</button>
|
||
</div>
|
||
<div className={styles.modalContent}>
|
||
{formAttributes.length === 0 ? (
|
||
<div className={styles.loadingContainer}>
|
||
<div className={styles.spinner} />
|
||
<span>Lade Formular...</span>
|
||
</div>
|
||
) : (
|
||
<FormGeneratorForm
|
||
attributes={formAttributes}
|
||
data={editingProject || {}}
|
||
mode={isCreateMode ? 'create' : 'edit'}
|
||
onSubmit={handleFormSubmit}
|
||
onCancel={handleCloseModal}
|
||
submitButtonText={isCreateMode ? 'Erstellen' : 'Speichern'}
|
||
cancelButtonText="Abbrechen"
|
||
instanceId={instanceId}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default RealEstateProjectsView;
|