275 lines
13 KiB
TypeScript
275 lines
13 KiB
TypeScript
import React from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { FaTimes, FaTrash } from 'react-icons/fa';
|
|
import styles from './ParcelInfoPanel.module.css';
|
|
|
|
export interface ParcelInfoPanelProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
parcels: any[];
|
|
onRemoveParcel?: (parcelId: string) => void;
|
|
adjacentParcels?: any[];
|
|
}
|
|
|
|
const ParcelInfoPanel: React.FC<ParcelInfoPanelProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
parcels,
|
|
onRemoveParcel,
|
|
adjacentParcels = []
|
|
}) => {
|
|
if (!parcels || parcels.length === 0) return null;
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<>
|
|
{/* Backdrop */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
className={styles.backdrop}
|
|
onClick={onClose}
|
|
/>
|
|
|
|
{/* Panel */}
|
|
<motion.div
|
|
initial={{ x: '100%' }}
|
|
animate={{ x: 0 }}
|
|
exit={{ x: '100%' }}
|
|
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
|
|
className={styles.panel}
|
|
>
|
|
<div className={styles.header}>
|
|
<h2 className={styles.title}>Parzellen-Informationen ({parcels.length})</h2>
|
|
<button className={styles.closeButton} onClick={onClose}>
|
|
<FaTimes />
|
|
</button>
|
|
</div>
|
|
|
|
<div className={styles.content}>
|
|
{/* Selected Parcels List */}
|
|
<div className={styles.parcelsList}>
|
|
{parcels.map((parcelData, index) => (
|
|
<section key={parcelData.parcel.id || index} className={styles.section}>
|
|
<div className={styles.sectionHeader}>
|
|
<h3 className={styles.sectionTitle}>
|
|
Parzelle {index + 1}: {parcelData.parcel.number || parcelData.parcel.id || 'Unbekannt'}
|
|
</h3>
|
|
{onRemoveParcel && (
|
|
<button
|
|
className={styles.removeButton}
|
|
onClick={() => onRemoveParcel(parcelData.parcel.id)}
|
|
title="Parzelle entfernen"
|
|
>
|
|
<FaTrash />
|
|
</button>
|
|
)}
|
|
</div>
|
|
<div className={styles.infoGrid}>
|
|
{parcelData.parcel.id && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>ID:</span>
|
|
<span className={styles.value}>{parcelData.parcel.id}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.number && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Nummer:</span>
|
|
<span className={styles.value}>{parcelData.parcel.number}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.name && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Name:</span>
|
|
<span className={styles.value}>{parcelData.parcel.name}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.egrid && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>EGRID:</span>
|
|
<span className={styles.value}>{parcelData.parcel.egrid}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.identnd && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>IdentND:</span>
|
|
<span className={styles.value}>{parcelData.parcel.identnd}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.address && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Adresse:</span>
|
|
<span className={styles.value}>{parcelData.parcel.address}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.canton && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Kanton:</span>
|
|
<span className={styles.value}>{parcelData.parcel.canton}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.municipality_name && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Gemeinde:</span>
|
|
<span className={styles.value}>{parcelData.parcel.municipality_name}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.municipality_code && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Gemeinde-Code:</span>
|
|
<span className={styles.value}>{parcelData.parcel.municipality_code}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.area_m2 !== undefined && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Fläche:</span>
|
|
<span className={styles.value}>
|
|
{parcelData.parcel.area_m2.toFixed(2)} m²
|
|
{parcelData.parcel.area_m2 >= 10000 && (
|
|
<span className={styles.subValue}>
|
|
{' '}({(parcelData.parcel.area_m2 / 10000).toFixed(2)} ha)
|
|
</span>
|
|
)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.realestate_type && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Grundstückstyp:</span>
|
|
<span className={styles.value}>{parcelData.parcel.realestate_type}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.bauzone && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Bauzone:</span>
|
|
<span className={styles.value}>{parcelData.parcel.bauzone}</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.zone && Array.isArray(parcelData.parcel.zone) && parcelData.parcel.zone.length > 0 && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Zone:</span>
|
|
<span className={styles.value}>
|
|
{parcelData.parcel.zone.length} Zone{parcelData.parcel.zone.length !== 1 ? 'n' : ''} gefunden
|
|
{(() => {
|
|
// Extract zone types from zone array
|
|
const zoneTypes = parcelData.parcel.zone
|
|
.map((z: any) => {
|
|
const attrs = z.attributes || {};
|
|
return attrs.typ || attrs.zone_typ || attrs.bauzone || attrs.zone || attrs.label || null;
|
|
})
|
|
.filter((t: string | null) => t !== null);
|
|
|
|
if (zoneTypes.length > 0) {
|
|
return (
|
|
<span className={styles.subValue}>
|
|
{' '}({zoneTypes.join(', ')})
|
|
</span>
|
|
);
|
|
}
|
|
return null;
|
|
})()}
|
|
{import.meta.env.DEV && (
|
|
<details className={styles.zoneDetails}>
|
|
<summary className={styles.zoneSummary}>Details anzeigen</summary>
|
|
<pre className={styles.zoneData}>
|
|
{JSON.stringify(parcelData.parcel.zone, null, 2)}
|
|
</pre>
|
|
</details>
|
|
)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.centroid && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Zentrum (LV95):</span>
|
|
<span className={styles.value}>
|
|
{parcelData.parcel.centroid.x.toFixed(2)}, {parcelData.parcel.centroid.y.toFixed(2)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
{parcelData.parcel.geoportal_url && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Geoportal:</span>
|
|
<a
|
|
href={parcelData.parcel.geoportal_url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={styles.link}
|
|
>
|
|
Link öffnen
|
|
</a>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Map View Info for this parcel */}
|
|
{parcelData.map_view && (
|
|
<div className={styles.mapViewSection}>
|
|
<h4 className={styles.subSectionTitle}>Kartenansicht</h4>
|
|
<div className={styles.infoGrid}>
|
|
{parcelData.map_view.center && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Zentrum:</span>
|
|
<span className={styles.value}>
|
|
{parcelData.map_view.center.x.toFixed(2)}, {parcelData.map_view.center.y.toFixed(2)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
{parcelData.map_view.zoom_bounds && (
|
|
<>
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Bounds Min:</span>
|
|
<span className={styles.value}>
|
|
{parcelData.map_view.zoom_bounds.min_x.toFixed(2)}, {parcelData.map_view.zoom_bounds.min_y.toFixed(2)}
|
|
</span>
|
|
</div>
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.label}>Bounds Max:</span>
|
|
<span className={styles.value}>
|
|
{parcelData.map_view.zoom_bounds.max_x.toFixed(2)}, {parcelData.map_view.zoom_bounds.max_y.toFixed(2)}
|
|
</span>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</section>
|
|
))}
|
|
</div>
|
|
|
|
{/* Adjacent Parcels */}
|
|
{adjacentParcels.length > 0 && (
|
|
<section className={styles.section}>
|
|
<h3 className={styles.sectionTitle}>
|
|
Angrenzende Parzellen ({adjacentParcels.length})
|
|
</h3>
|
|
<div className={styles.adjacentList}>
|
|
{adjacentParcels.map((adjacent, index) => (
|
|
<div key={adjacent.id || index} className={styles.adjacentItem}>
|
|
<div className={styles.adjacentHeader}>
|
|
<span className={styles.adjacentNumber}>
|
|
{adjacent.number || adjacent.id}
|
|
</span>
|
|
{adjacent.egrid && (
|
|
<span className={styles.adjacentEgrid}>{adjacent.egrid}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
};
|
|
|
|
export default ParcelInfoPanel;
|
|
|