feat: unit-level matching — each floor lettable independently or as bundle

- PropertyUnit: add isFlexible, minLettableSqm, offeredSqm fields
- New UnitBundle + UnitNeedMatch domain types
- unitMatchService: match individual units or bundles against LatentNeeds
  (exact, partial/Teilfläche, bundle match types with area scoring)
- Mock units updated: prop-001/007/012/013 free units flagged as flexible
  with minimum lettable areas
- UnitStructurePanel replaces old Stockwerkstruktur table:
  - Checkboxes on free units (when ≥2 exist) for bundle selection
  - Inline match pills per free unit (top need + score)
  - Expand row to see all matching needs with area details
  - 'Teilfläche möglich' chip for flexible units with min sqm
  - Bundle panel appears when 2+ units selected: shows combined sqm,
    bundle matches, and note about remaining area after contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-19 20:24:17 +02:00
parent 7cda66f98a
commit f0e58f5f7a
4 changed files with 387 additions and 37 deletions
+4 -4
View File
@@ -47,7 +47,7 @@ export const mockProperties: Property[] = [
units: [
{ id: 'unit-001-1', floorLevel: 1, unitLabel: 'Nord', areaSqm: 280, available: false, rentPricePerSqm: 456, currentTenant: 'MediaGroup Schweiz AG', leaseTerm: '5 Jahre', leaseEndDate: '2025-08-31' },
{ id: 'unit-001-2', floorLevel: 2, unitLabel: 'Süd', areaSqm: 310, available: false, rentPricePerSqm: 456, currentTenant: 'MediaGroup Schweiz AG', leaseTerm: '5 Jahre', leaseEndDate: '2025-08-31' },
{ id: 'unit-001-3', floorLevel: 3, unitLabel: 'West', areaSqm: 260, available: true, rentPricePerSqm: 480 },
{ id: 'unit-001-3', floorLevel: 3, unitLabel: 'West', areaSqm: 260, available: true, rentPricePerSqm: 480, isFlexible: true, minLettableSqm: 120 },
],
importedFrom: 'SAP RE-FX',
importedAt: '2025-01-15T08:00:00Z',
@@ -151,7 +151,7 @@ export const mockProperties: Property[] = [
units: [
{ id: 'unit-007-1', floorLevel: 0, unitLabel: 'EG Ost', areaSqm: 180, available: false, rentPricePerSqm: 420, currentTenant: 'Consulting Partners AG', leaseTerm: '4 Jahre', leaseEndDate: '2025-09-30' },
{ id: 'unit-007-2', floorLevel: 1, unitLabel: '1.OG', areaSqm: 260, available: false, rentPricePerSqm: 432, currentTenant: 'Consulting Partners AG', leaseTerm: '4 Jahre', leaseEndDate: '2025-09-30' },
{ id: 'unit-007-3', floorLevel: 2, unitLabel: '2.OG West', areaSqm: 280, available: true, rentPricePerSqm: 445 },
{ id: 'unit-007-3', floorLevel: 2, unitLabel: '2.OG West', areaSqm: 280, available: true, rentPricePerSqm: 445, isFlexible: true, minLettableSqm: 150 },
],
importedFrom: 'SAP RE-FX',
importedAt: '2025-01-15T08:00:00Z',
@@ -456,8 +456,8 @@ export const mockProperties: Property[] = [
units: [
{ id: 'unit-013-1', floorLevel: 0, unitLabel: 'EG Laden', areaSqm: 320, available: false, rentPricePerSqm: 600, currentTenant: 'Design Studio Zürich GmbH', leaseTerm: '4 Jahre', leaseEndDate: '2025-10-31' },
{ id: 'unit-013-2', floorLevel: 1, unitLabel: '1.OG Büro A', areaSqm: 480, available: false, rentPricePerSqm: 540, currentTenant: 'Design Studio Zürich GmbH', leaseTerm: '4 Jahre', leaseEndDate: '2025-10-31' },
{ id: 'unit-013-3', floorLevel: 1, unitLabel: '1.OG Büro B', areaSqm: 280, available: true, rentPricePerSqm: 540 },
{ id: 'unit-013-4', floorLevel: 2, unitLabel: '2.OG', areaSqm: 220, available: true, rentPricePerSqm: 520 },
{ id: 'unit-013-3', floorLevel: 1, unitLabel: '1.OG Büro B', areaSqm: 280, available: true, rentPricePerSqm: 540, isFlexible: true, minLettableSqm: 140 },
{ id: 'unit-013-4', floorLevel: 2, unitLabel: '2.OG', areaSqm: 220, available: true, rentPricePerSqm: 520, isFlexible: true, minLettableSqm: 100 },
],
importedFrom: 'SAP RE-FX',
importedAt: '2025-01-15T08:00:00Z',