frontend_nyla/src/pages/views/realestate/RealEstateParcelsView.tsx
2026-04-26 22:53:39 +02:00

253 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* RealEstateParcelsView
*
* Parzellen-Verwaltung für eine Real Estate/PEK-Instanz.
* Verwendet FormGeneratorTable analog zu TrusteeDocumentsView.
*/
import React, { useState, useMemo, useEffect } from 'react';
import {
useRealEstateParcels,
useRealEstateParcelOperations,
type RealEstateParcel,
} from '../../../hooks/useRealEstate';
import { useInstanceId } from '../../../hooks/useCurrentInstance';
import { FormGeneratorTable } from '../../../components/FormGenerator/FormGeneratorTable';
import { FormGeneratorForm } from '../../../components/FormGenerator/FormGeneratorForm';
import { FaSync } from 'react-icons/fa';
import styles from '../../admin/Admin.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext';
import { resolveColumnTypes } from '../../../utils/columnTypeResolver';
export const RealEstateParcelsView: React.FC = () => {
const { t } = useLanguage();
const instanceId = useInstanceId();
const {
items: parcels,
attributes,
permissions,
pagination,
loading,
error,
refetch,
fetchById,
updateOptimistically,
removeOptimistically,
} = useRealEstateParcels();
const {
handleDelete,
handleCreate,
handleUpdate,
deletingItems,
} = useRealEstateParcelOperations();
const [editingParcel, setEditingParcel] = useState<RealEstateParcel | null>(null);
const [isCreateMode, setIsCreateMode] = useState(false);
useEffect(() => {
if (instanceId) {
refetch();
}
}, [instanceId, refetch]);
const columns = useMemo(() => {
const raw = (attributes || []).map(attr => ({
key: attr.name,
label: attr.label || attr.name,
sortable: attr.sortable !== false,
filterable: attr.filterable !== false,
searchable: attr.searchable !== false,
width: attr.width || 150,
minWidth: attr.minWidth || 100,
maxWidth: attr.maxWidth || 400,
displayField: (attr as any).displayField,
}));
return resolveColumnTypes(raw, attributes || []);
}, [attributes]);
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
const handleEditClick = async (parcel: RealEstateParcel) => {
const full = await fetchById(parcel.id);
if (full) {
setEditingParcel(full);
setIsCreateMode(false);
}
};
const handleCreateClick = () => {
setEditingParcel(null);
setIsCreateMode(true);
};
const handleFormSubmit = async (data: Partial<RealEstateParcel>) => {
if (isCreateMode) {
const result = await handleCreate(data);
if (result.success) {
setIsCreateMode(false);
refetch();
}
} else if (editingParcel) {
const result = await handleUpdate(editingParcel.id, data);
if (result.success) {
setEditingParcel(null);
refetch();
}
}
};
const handleDeleteParcel = async (parcel: RealEstateParcel) => {
removeOptimistically(parcel.id);
const success = await handleDelete(parcel.id);
if (!success) {
refetch();
}
};
const handleCloseModal = () => {
setEditingParcel(null);
setIsCreateMode(false);
};
const formAttributes = useMemo(() => {
const excluded = ['id', 'mandateId', 'instanceId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'sysModifiedBy'];
return (attributes || []).filter(attr => !excluded.includes(attr.name));
}, [attributes]);
const handleInlineUpdate = async (
itemId: string,
updateData: Partial<RealEstateParcel>,
row: RealEstateParcel
) => {
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 Parzellen: {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}>{t('Parzellen 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}>
+ Neue Parzelle
</button>
)}
</div>
</div>
<div className={styles.tableContainer}>
<FormGeneratorTable
data={parcels}
columns={columns}
loading={loading}
pagination={true}
pageSize={25}
searchable={true}
filterable={true}
sortable={true}
selectable={true}
apiEndpoint={instanceId ? `/api/realestate/${instanceId}/parcels` : undefined}
actionButtons={[
...(canUpdate
? [
{
type: 'edit' as const,
onAction: handleEditClick,
title: t('Bearbeiten'),
},
]
: []),
...(canDelete
? [
{
type: 'delete' as const,
title: t('Löschen'),
loading: (row: RealEstateParcel) => deletingItems.has(row.id),
},
]
: []),
]}
onDelete={handleDeleteParcel}
hookData={{
refetch,
permissions,
pagination,
handleDelete,
handleInlineUpdate,
updateOptimistically,
}}
emptyMessage={t('Keine Parzellen gefunden')}
/>
</div>
{(editingParcel || isCreateMode) && (
<div className={styles.modalOverlay}>
<div className={styles.modal}>
<div className={styles.modalHeader}>
<h2 className={styles.modalTitle}>
{isCreateMode ? t('Neue Parzelle') : t('Parzelle 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>{t('Formular laden')}</span>
</div>
) : (
<FormGeneratorForm
attributes={formAttributes}
data={editingParcel || {}}
mode={isCreateMode ? 'create' : 'edit'}
onSubmit={handleFormSubmit}
onCancel={handleCloseModal}
submitButtonText={isCreateMode ? t('Erstellen') : t('Speichern')}
cancelButtonText={t('Abbrechen')}
instanceId={instanceId}
/>
)}
</div>
</div>
</div>
)}
</div>
);
};
export default RealEstateParcelsView;