import React, { useState, useMemo, useRef, useEffect } from ‘react’;
import { MOCK_PROPERTIES } from ‘./data’;
import { Property, AIReport, VisualOptimization, PropertyUnit } from ‘./types’;
import { PropertyCard } from ‘./components/PropertyCard’;
import { ReportView } from ‘./components/ReportView’;
import { AnalyticsOverview } from ‘./components/AnalyticsOverview’;
import { generatePropertyReport } from ‘./services/geminiService’;
import { ImageEditModal } from ‘./components/ImageEditModal’;
import { AddPropertyModal } from ‘./components/AddPropertyModal’;
import { EditPropertyModal } from ‘./components/EditPropertyModal’;
import { AddUnitModal } from ‘./components/AddUnitModal’;
import { EditUnitModal } from ‘./components/EditUnitModal’;
import { TourManagementModal } from ‘./components/TourManagementModal’;
const App: React.FC = () => {
const [properties, setProperties] = useState(MOCK_PROPERTIES);
const [selectedProperty, setSelectedProperty] = useState(null);
const [report, setReport] = useState(null);
const [activeOptimization, setActiveOptimization] = useState(null);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [isEditPropertyModalOpen, setIsEditPropertyModalOpen] = useState(false);
const [propertyToEdit, setPropertyToEdit] = useState(null);
const [isAddUnitModalOpen, setIsAddUnitModalOpen] = useState(false);
const [unitToEdit, setUnitToEdit] = useState(null);
const [unitForTour, setUnitForTour] = useState(null);
const [loading, setLoading] = useState(false);
// User Menu State
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
const userMenuRef = useRef(null);
// Global UI Scaling State
const [uiScale, setUiScale] = useState(1); // 1 = 100% / 16px base
// Filter and Sort states for units
const [unitSortFilter, setUnitSortFilter] = useState(‘default’);
// Apply UI scaling globally by modifying root font size
useEffect(() => {
document.documentElement.style.fontSize = `${uiScale * 100}%`;
}, [uiScale]);
// Handle clicking outside the user menu to close it
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
setIsUserMenuOpen(false);
}
};
document.addEventListener(‘mousedown’, handleClickOutside);
return () => document.removeEventListener(‘mousedown’, handleClickOutside);
}, []);
const handleGenerateReport = async () => {
if (!selectedProperty) return;
setLoading(true);
try {
const result = await generatePropertyReport(selectedProperty);
setReport(result);
} catch (error) {
console.error(”Failed to generate report:”, error);
alert(”Failed to connect to AI Strategic Engine. Please try again.”);
} finally {
setLoading(false);
}
};
const handleBack = () => {
setSelectedProperty(null);
setReport(null);
setUnitSortFilter(‘default’);
};
const handleOpenEditModal = (p: Property) => {
setPropertyToEdit(p);
setIsEditPropertyModalOpen(true);
};
const handleOptimizationTrigger = (optimization: VisualOptimization) => {
setActiveOptimization(optimization);
};
const handleAddProperty = (newProperty: Property) => {
setProperties([…properties, newProperty]);
setIsAddModalOpen(false);
};
const handleUpdateProperty = (updatedProperty: Property) => {
setProperties(prev => prev.map(p => p.id === updatedProperty.id ? updatedProperty : p));
if (selectedProperty?.id === updatedProperty.id) {
setSelectedProperty(updatedProperty);
}
setIsEditPropertyModalOpen(false);
setPropertyToEdit(null);
};
const handleDeleteProperty = (propertyId: string) => {
setProperties(prev => prev.filter(p => p.id !== propertyId));
if (selectedProperty?.id === propertyId) {
setSelectedProperty(null);
}
setIsEditPropertyModalOpen(false);
setPropertyToEdit(null);
};
const handleAddUnit = (newUnit: PropertyUnit) => {
if (!selectedProperty) return;
const updatedProperty = {
…selectedProperty,
units: […selectedProperty.units, newUnit]
};
setProperties(prev => prev.map(p =>
p.id === selectedProperty.id ? updatedProperty : p
));
setSelectedProperty(updatedProperty);
setIsAddUnitModalOpen(false);
};
const handleUpdateUnit = (updatedUnit: PropertyUnit) => {
if (!selectedProperty) return;
const updatedProperty = {
…selectedProperty,
units: selectedProperty.units.map(u => u.id === updatedUnit.id ? updatedUnit : u)
};
setProperties(prev => prev.map(p =>
p.id === selectedProperty.id ? updatedProperty : p
));
setSelectedProperty(updatedProperty);
setUnitToEdit(null);
};
const handleUpdateUnitTour = (url: string) => {
if (!selectedProperty || !unitForTour) return;
const updatedUnit = { …unitForTour, tourUrl: url };
const updatedProperty = {
…selectedProperty,
units: selectedProperty.units.map(u => u.id === unitForTour.id ? updatedUnit : u)
};
setProperties(prev => prev.map(p =>
p.id === selectedProperty.id ? updatedProperty : p
));
setSelectedProperty(updatedProperty);
setUnitForTour(null);
};
// Compute the processed units list based on Sort & Filter selection
const processedUnits = useMemo(() => {
if (!selectedProperty) return [];
let units = […selectedProperty.units];
// Filter Logic
if (unitSortFilter === ‘available’) units = units.filter(u => u.availability === ‘Available’);
if (unitSortFilter === ‘offer’) units = units.filter(u => u.availability === ‘Under Offer’);
if (unitSortFilter === ‘leased’) units = units.filter(u => u.availability === ‘Leased’);
// Sort Logic
units.sort((a, b) => {
switch (unitSortFilter) {
case ‘floor’:
return a.floor – b.floor;
case ‘size’:
return b.sizeSqM – a.sizeSqM;
case ‘price’:
return b.pricePerMonth – a.pricePerMonth;
case ‘alphabet’:
return a.name.localeCompare(b.name);
default:
return 0;
}
});
return units;
}, [selectedProperty, unitSortFilter]);
return (
setActiveOptimization(null)}
onUpdate={(newImageUrl) => {
setProperties(prev => prev.map(p =>
p.id === selectedProperty.id ? { …p, image: newImageUrl } : p
));
setSelectedProperty({ …selectedProperty, image: newImageUrl });
setActiveOptimization(null);
}}
/>
)}
{isAddModalOpen && (
setIsAddModalOpen(false)}
onAdd={handleAddProperty}
/>
)}
{isEditPropertyModalOpen && propertyToEdit && (
setIsEditPropertyModalOpen(false)}
onUpdate={handleUpdateProperty}
onDelete={handleDeleteProperty}
/>
)}
{isAddUnitModalOpen && (
setIsAddUnitModalOpen(false)}
onAdd={handleAddUnit}
/>
)}
{unitToEdit && (
setUnitToEdit(null)}
onUpdate={handleUpdateUnit}
/>
)}
{unitForTour && (
setUnitForTour(null)}
onUpdate={handleUpdateUnitTour}
/>
)}
Strategic Advisor
>
Commercial Portfolio
{isUserMenuOpen && (
Johnathan Pierce
Chief Asset Manager
{ label: ‘Profile Settings’, icon: },
{ label: ‘Subscription Plan’, icon: },
{ label: ‘Team Management’, icon: },
{ label: ‘API & Integrations’, icon: },
].map((item, idx) => (
))}
{Math.round(uiScale * 100)}%
{ label: ‘S’, val: 0.85 },
{ label: ‘M’, val: 1.0 },
{ label: ‘L’, val: 1.15 },
{ label: ‘XL’, val: 1.3 }
].map((s) => (
))}
)}
{!selectedProperty ? (
My Portfolio
Analyze high-value assets, upload professional visuals, and accelerate leasing speed with strategic advisor insights.
))}
Add Property Template
Create a new digital property asset
) : (
{selectedProperty.name}
{selectedProperty.address}
Simulating Outcomes…
) : (
Generate Insight Report
)}
{report ? (
setReport(null)}
onOptimizeVisual={handleOptimizationTrigger}
/>
) : (
Asset Inventory Management
>
Sort & Filter: All
By Floor (Low-High)
By Size (Large-Small)
By Rent (High-Low)
By Alphabetical (A-Z)
Status: Available
Status: Under Offer
Status: Leased
Add Unit
{processedUnits.length === 0 ? (
{selectedProperty.units.length === 0 ? ”No units added to this asset yet.” : ”No units match your current filter.”}
{selectedProperty.units.length > 0 && (
)}
) : (
{unit.name}
{unit.availability}
Floor {unit.floor}
{unit.sizeSqM.toLocaleString()} m²
{a}
))}
${unit.pricePerMonth.toLocaleString()}
))}
)}
)}
)}
);
};
export default App;

