From cd220ed282b09e7a9b4b755947699669421e98c3 Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Sun, 24 May 2026 23:08:41 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Suchabo=20inline=20contact=20flow=20?= =?UTF-8?q?=E2=80=94=20chip=20opens=20need=20detail=20+=20OfferWizard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add >80% STRONG matches for all 6 direct listings (match-072–075) - Add 4 new Needs (need-012–015) tailored to each previously unmatched listing - Add 8 new LatentNeeds (lneed-007–014) for all needs in direct listing matches - Add need→latentNeed mapping (src/lib/needToLatentNeedMap.ts) - MyListings: clicking Suchabo chip opens dialog with company name, score, PublicNeedDetail view and OfferWizard — no navigation to Anfragencenter needed Co-Authored-By: Claude Sonnet 4.6 --- src/lib/needToLatentNeedMap.ts | 11 ++ src/mock-data/latentNeeds.ts | 220 ++++++++++++++++++++++++++++++++ src/mock-data/matches.ts | 119 +++++++++++++++++ src/mock-data/needs.ts | 136 ++++++++++++++++++++ src/pages/supply/MyListings.tsx | 190 ++++++++++++++++++++++++++- 5 files changed, 672 insertions(+), 4 deletions(-) create mode 100644 src/lib/needToLatentNeedMap.ts diff --git a/src/lib/needToLatentNeedMap.ts b/src/lib/needToLatentNeedMap.ts new file mode 100644 index 0000000..47ccb94 --- /dev/null +++ b/src/lib/needToLatentNeedMap.ts @@ -0,0 +1,11 @@ +/** Maps a Need id (need-XXX) to its corresponding LatentNeed id (lneed-XXX). */ +export const NEED_TO_LATENT_NEED: Record = { + 'need-001': 'lneed-007', + 'need-002': 'lneed-008', + 'need-004': 'lneed-009', + 'need-005': 'lneed-010', + 'need-012': 'lneed-011', + 'need-013': 'lneed-012', + 'need-014': 'lneed-013', + 'need-015': 'lneed-014', +} diff --git a/src/mock-data/latentNeeds.ts b/src/mock-data/latentNeeds.ts index 0fbd397..7c31b8d 100644 --- a/src/mock-data/latentNeeds.ts +++ b/src/mock-data/latentNeeds.ts @@ -146,6 +146,226 @@ export const mockLatentNeeds: LatentNeed[] = [ createdAt: '2026-05-09T08:15:00Z', }, + // ─── Suchabos linked to direct listing matches ──────────────────────────── + + { + id: 'lneed-007', + title: 'Modernes Büro in Zürich für 600–1000m²', + tenantCompany: 'Innovatech AG', + assetType: AssetType.OFFICE, + desiredLocation: 'Zürich-West / Kreis 5', + sizeRange: { min: 600, max: 1000 }, + budgetRange: { min: 32, max: 45 }, + timing: 'Bezug ab Herbst 2025, flexibel', + mustHaveCriteria: [ + 'ÖV-Anbindung unter 6 Min', + 'Mindestfläche 600m²', + 'Moderner Ausbau', + ], + weightedPreferences: [ + { criterion: 'Lage Zürich-West', weight: 0.35 }, + { criterion: 'Ausbaustandard', weight: 0.25, description: 'Modern, repräsentativ' }, + { criterion: 'Erweiterungsoption', weight: 0.20 }, + { criterion: 'ESG-Standard', weight: 0.10 }, + { criterion: 'Parkplätze', weight: 0.10 }, + ], + aiSummary: 'Technologieunternehmen mit solidem Wachstum sucht Bürofläche in Zürich-West. Hohe Abschlussbereitschaft bei passendem Objekt.', + publicVisibility: true, + status: 'public', + createdAt: '2026-04-15T10:00:00Z', + }, + + { + id: 'lneed-008', + title: 'Logistikfläche Basel-Region 1500–4000m²', + tenantCompany: 'Schweizer Logistik GmbH', + assetType: AssetType.LOGISTICS, + desiredLocation: 'Basel / Muttenz / Pratteln / Reinach BL', + sizeRange: { min: 1500, max: 4000 }, + budgetRange: { min: 12, max: 18 }, + timing: 'Bezug ab September 2025', + mustHaveCriteria: [ + 'Autobahnanschluss unter 5 Min', + 'Rampe / Andienung vorhanden', + 'Hallenhöhe min 6m', + ], + weightedPreferences: [ + { criterion: 'Rampenanzahl', weight: 0.30 }, + { criterion: 'Lkw-Wendekreis', weight: 0.25 }, + { criterion: 'Erweiterungspotenzial', weight: 0.20 }, + { criterion: '24/7-Betrieb', weight: 0.15 }, + { criterion: 'Sprinkleranlage', weight: 0.10 }, + ], + aiSummary: 'Etablierter Logistiker mit klaren Anforderungen. Region Basel priorisiert. Sehr gute Matchbasis mit verfügbaren Hallenflächen.', + publicVisibility: true, + status: 'public', + createdAt: '2026-03-20T14:00:00Z', + }, + + { + id: 'lneed-009', + title: 'Premium-Retailfläche Zürich Innenstadt', + tenantCompany: 'Retailer Zürich AG', + assetType: AssetType.RETAIL, + desiredLocation: 'Zürich Innenstadt / Bahnhofstrasse / Niederdorf', + sizeRange: { min: 200, max: 500 }, + budgetRange: { min: 60, max: 100 }, + timing: 'Bezug ab September 2025, hohe Dringlichkeit', + mustHaveCriteria: [ + 'Laufkundschaft und Passantenfrequenz', + 'Schaufensterfront', + 'Erdgeschoss', + ], + weightedPreferences: [ + { criterion: 'Fussgängerfrequenz', weight: 0.35 }, + { criterion: 'Lagequalität', weight: 0.30, description: 'Zürich Innenstadt' }, + { criterion: 'Sichtbarkeit', weight: 0.20 }, + { criterion: 'Deckenhöhe', weight: 0.10 }, + { criterion: 'Lagerfläche', weight: 0.05 }, + ], + aiSummary: 'Retailer mit starkem Markenbewusstsein sucht Toplage in Zürich. Klare Präferenz Innenstadt, solider Abschluss bei Matching.', + publicVisibility: true, + status: 'public', + createdAt: '2026-03-10T11:00:00Z', + }, + + { + id: 'lneed-010', + title: 'Büro 300–700m² für Tech-Startup Zug/Zürich', + tenantCompany: 'TechStart GmbH', + assetType: AssetType.OFFICE, + desiredLocation: 'Zug / Zürich / Baar', + sizeRange: { min: 300, max: 700 }, + budgetRange: { min: 30, max: 48 }, + timing: 'Bezug ab Oktober 2025, flexibel bis Q2 2026', + mustHaveCriteria: [ + 'Moderner Ausbau', + 'Schnelles Internet / Glasfaser', + 'Fahrradabstellplätze', + ], + weightedPreferences: [ + { criterion: 'Tech-Umfeld / Startup-Nähe', weight: 0.30 }, + { criterion: 'ÖV-Anbindung', weight: 0.25 }, + { criterion: 'Flexibler Grundriss', weight: 0.20 }, + { criterion: 'Steuerumfeld', weight: 0.15, description: 'Zug bevorzugt' }, + { criterion: 'ESG-Qualität', weight: 0.10 }, + ], + aiSummary: 'Wachsendes Tech-Startup mit flexibler Lage zwischen Zug und Zürich. Hohe Bereitschaft bei modernem Objekt mit gutem Preis-Leistungs-Verhältnis.', + publicVisibility: true, + status: 'public', + createdAt: '2026-04-28T13:00:00Z', + }, + + { + id: 'lneed-011', + title: 'Gewerbe-/Lagerfläche Pratteln 400–750m²', + tenantCompany: 'Gewerbe Solutions AG', + assetType: AssetType.LIGHT_INDUSTRIAL, + desiredLocation: 'Pratteln / Muttenz / Schweizerhalle', + sizeRange: { min: 400, max: 750 }, + budgetRange: { min: 10, max: 14 }, + timing: 'Bezug ab April 2026', + mustHaveCriteria: [ + 'Lkw-Zufahrt', + 'Hallenhöhe min 5m', + 'Drehstrom vorhanden', + ], + weightedPreferences: [ + { criterion: 'Autobahnnähe', weight: 0.30 }, + { criterion: 'Aussenanlagefläche', weight: 0.25 }, + { criterion: 'Erweiterungspotenzial', weight: 0.20 }, + { criterion: 'Parkplätze', weight: 0.15 }, + { criterion: 'Büroanteil', weight: 0.10, description: 'Kleines Büro on-site gewünscht' }, + ], + aiSummary: 'Gewerbebetrieb mit klaren Anforderungen sucht Fläche im Raum Pratteln. Gute Ausgangslage für verfügbare Gewerbeliegenschaften.', + publicVisibility: true, + status: 'public', + createdAt: '2026-03-01T09:00:00Z', + }, + + { + id: 'lneed-012', + title: 'Büro 180–320m² Bern Zentrum / Bahnhof', + tenantCompany: 'Bern Consulting AG', + assetType: AssetType.OFFICE, + desiredLocation: 'Bern Bollwerk / Bahnhof / Innenstadt', + sizeRange: { min: 180, max: 320 }, + budgetRange: { min: 20, max: 30 }, + timing: 'Bezug ab Mai 2026', + mustHaveCriteria: [ + 'ÖV-Anbindung unter 5 Min zum Bahnhof', + 'Repräsentative Lage', + 'Konferenzraum', + ], + weightedPreferences: [ + { criterion: 'Bahnhofsnähe', weight: 0.35, description: 'Kundenmeetings erfordern zentrale Lage' }, + { criterion: 'Ausbaustandard', weight: 0.25 }, + { criterion: 'Flexibilität', weight: 0.20 }, + { criterion: 'Natürliches Licht', weight: 0.10 }, + { criterion: 'IT-Infrastruktur', weight: 0.10 }, + ], + aiSummary: 'Beratungsunternehmen sucht repräsentatives Büro nahe Bern HB. Zentrale Lage ist entscheidend für Kundenkontakte. Guter Match mit Berner Citylagen.', + publicVisibility: true, + status: 'public', + createdAt: '2026-04-05T10:00:00Z', + }, + + { + id: 'lneed-013', + title: 'Verkaufsfläche Winterthur Zentrum 150–250m²', + tenantCompany: 'Winterthur Handel GmbH', + assetType: AssetType.RETAIL, + desiredLocation: 'Winterthur Zentrum / Stadthausstrasse / Altstadt', + sizeRange: { min: 150, max: 250 }, + budgetRange: { min: 25, max: 40 }, + timing: 'Bezug ab Juni 2026', + mustHaveCriteria: [ + 'Schaufensterfront', + 'Erdgeschoss', + 'Fussgängerfrequenz', + ], + weightedPreferences: [ + { criterion: 'Lage Innenstadt Winterthur', weight: 0.40 }, + { criterion: 'Schaufenstergrösse', weight: 0.25 }, + { criterion: 'Deckenhöhe', weight: 0.15 }, + { criterion: 'Lagerraum', weight: 0.10 }, + { criterion: 'Barrierefrei', weight: 0.10 }, + ], + aiSummary: 'Lokaler Handelsbetrieb sucht Fläche in Winterthur Innenstadt. Klare Anforderungen, realistisches Budget. Guter Match mit Stadtzentrum-Angeboten.', + publicVisibility: true, + status: 'public', + createdAt: '2026-03-20T11:00:00Z', + }, + + { + id: 'lneed-014', + title: 'Bürofläche 350–600m² Luzern', + tenantCompany: 'Luzern Advisory GmbH', + assetType: AssetType.OFFICE, + desiredLocation: 'Luzern / Luzern-West / Emmen', + sizeRange: { min: 350, max: 600 }, + budgetRange: { min: 18, max: 26 }, + timing: 'Bezug ab Juni 2026, flexibel bis Januar 2027', + mustHaveCriteria: [ + 'ÖV-Anbindung unter 10 Min', + 'Moderner Ausbau', + 'Parkplätze vorhanden', + ], + weightedPreferences: [ + { criterion: 'Standortqualität Luzern', weight: 0.30 }, + { criterion: 'Ausbaustandard', weight: 0.25 }, + { criterion: 'Parkplatzverfügbarkeit', weight: 0.20 }, + { criterion: 'Erweiterungspotenzial', weight: 0.15 }, + { criterion: 'ESG', weight: 0.10 }, + ], + aiSummary: 'Beratungsgesellschaft sucht modernes Büro in Luzern. Gute Balance zwischen Lage, Qualität und Budget. Solides Matchprofil.', + publicVisibility: true, + status: 'public', + createdAt: '2026-04-10T14:00:00Z', + }, + + // ─── Original lneed-006 ──────────────────────────────────────────────────── + { id: 'lneed-006', title: 'Pop-up-Retailfläche temporär 6 Monate', diff --git a/src/mock-data/matches.ts b/src/mock-data/matches.ts index e94efc1..cd1f95c 100644 --- a/src/mock-data/matches.ts +++ b/src/mock-data/matches.ts @@ -1957,4 +1957,123 @@ export const mockMatches: Match[] = [ createdAt: '2026-01-15T09:00:00Z', updatedAt: '2026-01-15T09:00:00Z', }, + + // ─────────────────────────────────────────────────────────────────────────── + // Direct listings — Strong matches (>80%) ensuring all 6 have ≥1 STRONG + // ─────────────────────────────────────────────────────────────────────────── + + { + id: 'match-072', + propertyId: 'prop-d02', + needId: 'need-012', + matchScore: 85, + matchStrength: MatchStrength.STRONG, + status: MatchStatus.PENDING_REVIEW, + scoreBreakdown: { hardMatchScore: 88, softFactorScore: 84, confidenceModifier: 0.90, dataQualityModifier: 0.87, totalScore: 85 }, + positiveFactors: [ + { criterion: 'Standort', weight: 0.20, score: 98, contribution: 19.6, explanation: 'Pratteln — explizit bevorzugte Region für Gewerbe Solutions AG' }, + { criterion: 'Budget', weight: 0.18, score: 100, contribution: 18.0, explanation: 'CHF 145/m²/J deutlich unter Maximum CHF 168/m²/J' }, + { criterion: 'Fläche', weight: 0.22, score: 90, contribution: 19.8, explanation: '580m² liegt ideal im Zielkorridor (400–750m²)' }, + { criterion: 'Lkw-Zufahrt', weight: 0.10, score: 100, contribution: 10.0, explanation: 'Lkw-Zufahrt vorhanden — Pflichtkriterium erfüllt' }, + ], + negativeFactors: [ + { criterion: 'Hallenhöhe', weight: 0.08, score: 70, contribution: 5.6, explanation: 'Angabe Hallenhöhe nicht vollständig dokumentiert' }, + ], + tradeoffs: [ + { criterion: 'Hallenhöhe', concern: 'Angabe Hallenhöhe unvollständig — Besichtigung nötig', severity: 'LOW', mitigation: 'Vor Mietabschluss Vor-Ort-Prüfung empfohlen' }, + ], + explainabilitySummary: 'Ausgezeichneter Standort und Preis-Leistungs-Verhältnis für Gewerbe Solutions AG. Pratteln ist die Wunschregion, Fläche und Budget passen sehr gut.', + confidenceLevel: 0.90, + riskLevel: RiskLevel.LOW, + uncertaintyIndicators: [], + organizationId: 'org-wincasa', + createdAt: '2026-05-10T08:00:00Z', + updatedAt: '2026-05-10T08:00:00Z', + }, + + { + id: 'match-073', + propertyId: 'prop-d03', + needId: 'need-013', + matchScore: 84, + matchStrength: MatchStrength.STRONG, + status: MatchStatus.PENDING_REVIEW, + scoreBreakdown: { hardMatchScore: 87, softFactorScore: 83, confidenceModifier: 0.88, dataQualityModifier: 0.85, totalScore: 84 }, + positiveFactors: [ + { criterion: 'Standort', weight: 0.25, score: 96, contribution: 24.0, explanation: 'Bern Bollwerk — perfekter Fit mit Bern Innenstadt / Bahnhof-Nähe' }, + { criterion: 'Budget', weight: 0.18, score: 100, contribution: 18.0, explanation: 'CHF 295/m²/J unter Maximum CHF 360/m²/J' }, + { criterion: 'Fläche', weight: 0.18, score: 92, contribution: 16.6, explanation: '240m² liegt gut im Korridor (180–320m²)' }, + { criterion: 'ÖV', weight: 0.09, score: 100, contribution: 9.0, explanation: 'Hauptbahnhof Bern < 5 Min zu Fuss' }, + ], + negativeFactors: [ + { criterion: 'Konferenzraum', weight: 0.06, score: 60, contribution: 3.6, explanation: 'Konferenzraumsituation nicht explizit dokumentiert' }, + ], + tradeoffs: [], + explainabilitySummary: 'Starke Übereinstimmung: Bern Bollwerk trifft genau die Anforderungen von Bern Consulting AG — Lage, Preis und Fläche passen.', + confidenceLevel: 0.88, + riskLevel: RiskLevel.LOW, + uncertaintyIndicators: [], + organizationId: 'org-wincasa', + createdAt: '2026-05-10T09:00:00Z', + updatedAt: '2026-05-10T09:00:00Z', + }, + + { + id: 'match-074', + propertyId: 'prop-d04', + needId: 'need-014', + matchScore: 82, + matchStrength: MatchStrength.STRONG, + status: MatchStatus.PENDING_REVIEW, + scoreBreakdown: { hardMatchScore: 86, softFactorScore: 80, confidenceModifier: 0.84, dataQualityModifier: 0.82, totalScore: 82 }, + positiveFactors: [ + { criterion: 'Standort', weight: 0.22, score: 97, contribution: 21.3, explanation: 'Winterthur Stadthausstrasse — Wunschlage für Winterthur Handel GmbH' }, + { criterion: 'Schaufensterfront', weight: 0.12, score: 100, contribution: 12.0, explanation: 'Schaufensterfront vorhanden — Pflichtkriterium erfüllt' }, + { criterion: 'Erdgeschoss', weight: 0.12, score: 100, contribution: 12.0, explanation: 'EG-Lage wie gefordert' }, + { criterion: 'Budget', weight: 0.14, score: 100, contribution: 14.0, explanation: 'CHF 360/m²/J deutlich unter Maximum CHF 480/m²/J' }, + ], + negativeFactors: [ + { criterion: 'Fläche', weight: 0.12, score: 68, contribution: 8.2, explanation: '185m² leicht unter Minimum 150m², aber am unteren Rand' }, + { criterion: 'Inaktiv', weight: 0.04, score: 0, contribution: 0, explanation: 'Inserat derzeit inaktiv — Verfügbarkeit zu klären' }, + ], + tradeoffs: [ + { criterion: 'Status', concern: 'Inserat inaktiv — Verfügbarkeit muss bestätigt werden', severity: 'LOW', mitigation: 'Direkter Kontakt mit Eigentümer empfohlen' }, + ], + explainabilitySummary: 'Winterthur Handel GmbH findet hier genau die gesuchte Retailfläche am richtigen Ort. Status zu klären.', + confidenceLevel: 0.84, + riskLevel: RiskLevel.LOW, + uncertaintyIndicators: ['Inserat inaktiv'], + organizationId: 'org-wincasa', + createdAt: '2026-05-10T10:00:00Z', + updatedAt: '2026-05-10T10:00:00Z', + }, + + { + id: 'match-075', + propertyId: 'prop-d06', + needId: 'need-015', + matchScore: 83, + matchStrength: MatchStrength.STRONG, + status: MatchStatus.PENDING_REVIEW, + scoreBreakdown: { hardMatchScore: 86, softFactorScore: 82, confidenceModifier: 0.86, dataQualityModifier: 0.84, totalScore: 83 }, + positiveFactors: [ + { criterion: 'Standort', weight: 0.22, score: 95, contribution: 20.9, explanation: 'Luzern Zürichstrasse — Wunschlage für Luzern Advisory GmbH' }, + { criterion: 'Fläche', weight: 0.19, score: 96, contribution: 18.2, explanation: '450m² liegt ideal im Korridor (350–600m²)' }, + { criterion: 'Budget', weight: 0.18, score: 100, contribution: 18.0, explanation: 'CHF 260/m²/J klar unter Maximum CHF 312/m²/J' }, + { criterion: 'ÖV', weight: 0.08, score: 88, contribution: 7.0, explanation: 'Gute S-Bahn-Anbindung Luzern Zentrum' }, + ], + negativeFactors: [ + { criterion: 'Inaktiv', weight: 0.04, score: 0, contribution: 0, explanation: 'Inserat derzeit inaktiv — Bezugsdatum offen' }, + ], + tradeoffs: [ + { criterion: 'Status', concern: 'Inserat inaktiv — Aktivierung und Verfügbarkeit zu klären', severity: 'LOW', mitigation: 'Direkte Anfrage an Eigentümer empfohlen' }, + ], + explainabilitySummary: 'Sehr guter Match für Luzern Advisory GmbH: Lage, Fläche und Budget passen ausgezeichnet. Inserat-Status zu klären.', + confidenceLevel: 0.86, + riskLevel: RiskLevel.LOW, + uncertaintyIndicators: ['Inserat inaktiv'], + organizationId: 'org-wincasa', + createdAt: '2026-05-10T11:00:00Z', + updatedAt: '2026-05-10T11:00:00Z', + }, ] diff --git a/src/mock-data/needs.ts b/src/mock-data/needs.ts index 3a88370..48c1801 100644 --- a/src/mock-data/needs.ts +++ b/src/mock-data/needs.ts @@ -437,6 +437,142 @@ export const mockNeeds: Need[] = [ updatedAt: '2025-05-22T10:00:00Z', }, + // --- need-012: Gewerbe Solutions AG — LIGHT_INDUSTRIAL Pratteln --- + { + id: 'need-012', + companyName: 'Gewerbe Solutions AG', + contactName: 'Andreas Weber', + assetType: AssetType.LIGHT_INDUSTRIAL, + requiredArea: { min: 400, max: 750 }, + preferredLocations: ['Pratteln', 'Muttenz', 'Schweizerhalle', 'Liestal'], + excludedLocations: [], + budgetRange: { maxPerSqm: 168, currency: 'CHF' }, + timing: { + earliestMoveIn: '2026-04-01', + latestMoveIn: '2026-10-01', + contractDurationMonths: 48, + flexibleTiming: true, + }, + mustCriteriaText: ['Lkw-Zufahrt', 'Hallenhöhe min 5m', 'Drehstrom vorhanden'], + softFactors: { + requireParking: true, + }, + weightingProfile: { + area: 0.22, location: 0.20, budget: 0.18, timing: 0.12, + prestige: 0.02, accessibility: 0.10, expansionPotential: 0.06, flexibility: 0.03, + visibility: 0.01, footfall: 0.00, talentAccess: 0.03, esg: 0.02, taxEnvironment: 0.01, + }, + confidenceInCriteria: 0.90, + extractedFromText: 'Gewerbe-/Lagerfläche 450–700m² in Pratteln oder Muttenz, Lkw-Zufahrt, max. CHF 168/m²/J.', + organizationId: 'org-wincasa', + createdAt: '2026-03-01T09:00:00Z', + updatedAt: '2026-03-01T09:00:00Z', + }, + + // --- need-013: Bern Consulting AG — OFFICE Bern --- + { + id: 'need-013', + companyName: 'Bern Consulting AG', + contactName: 'Sabine Gerber', + assetType: AssetType.OFFICE, + requiredArea: { min: 180, max: 320 }, + preferredLocations: ['Bern', 'Bern Bollwerk', 'Bern Bahnhof', 'Bern Innenstadt'], + excludedLocations: [], + budgetRange: { maxPerSqm: 360, maxMonthlyTotal: 8500, currency: 'CHF' }, + timing: { + earliestMoveIn: '2026-05-01', + latestMoveIn: '2026-11-01', + contractDurationMonths: 48, + flexibleTiming: true, + }, + mustCriteriaText: ['ÖV-Anbindung < 5 Min', 'Repräsentative Lage', 'Konferenzraum vorhanden'], + softFactors: { + minPrestige: 65, + minAccessibility: 80, + requireParking: false, + maxPublicTransportMinutes: 5, + }, + weightingProfile: { + area: 0.18, location: 0.25, budget: 0.18, timing: 0.11, + prestige: 0.08, accessibility: 0.09, expansionPotential: 0.02, flexibility: 0.03, + visibility: 0.02, footfall: 0.01, talentAccess: 0.05, esg: 0.02, taxEnvironment: 0.00, + }, + confidenceInCriteria: 0.88, + extractedFromText: 'Beratungsunternehmen sucht Bürofläche 200–300m² in Bern Zentrum nahe Bahnhof, max. CHF 360/m²/J.', + organizationId: 'org-wincasa', + createdAt: '2026-04-05T10:00:00Z', + updatedAt: '2026-04-05T10:00:00Z', + }, + + // --- need-014: Winterthur Handel GmbH — RETAIL Winterthur --- + { + id: 'need-014', + companyName: 'Winterthur Handel GmbH', + contactName: 'Pascal Brunner', + assetType: AssetType.RETAIL, + requiredArea: { min: 150, max: 250 }, + preferredLocations: ['Winterthur', 'Winterthur Zentrum', 'Winterthur Stadthausstrasse', 'Winterthur Altstadt'], + excludedLocations: [], + budgetRange: { maxPerSqm: 480, maxMonthlyTotal: 8000, currency: 'CHF' }, + timing: { + earliestMoveIn: '2026-06-01', + latestMoveIn: '2026-12-01', + contractDurationMonths: 36, + flexibleTiming: false, + }, + mustCriteriaText: ['Schaufensterfront', 'Erdgeschoss', 'Gute Fussgängerfrequenz'], + softFactors: { + minPrestige: 70, + requireParking: false, + maxPublicTransportMinutes: 8, + }, + weightingProfile: { + area: 0.12, location: 0.22, budget: 0.14, timing: 0.10, + prestige: 0.06, accessibility: 0.05, expansionPotential: 0.02, flexibility: 0.03, + visibility: 0.12, footfall: 0.12, talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00, + }, + confidenceInCriteria: 0.87, + extractedFromText: 'Handelsbetrieb sucht Verkaufsfläche 160–230m² in Winterthur Zentrum, Schaufenster, max. CHF 480/m²/J.', + organizationId: 'org-wincasa', + createdAt: '2026-03-20T11:00:00Z', + updatedAt: '2026-03-20T11:00:00Z', + }, + + // --- need-015: Luzern Advisory GmbH — OFFICE Luzern --- + { + id: 'need-015', + companyName: 'Luzern Advisory GmbH', + contactName: 'Markus Bucher', + assetType: AssetType.OFFICE, + requiredArea: { min: 350, max: 600 }, + preferredLocations: ['Luzern', 'Luzern Zentrum', 'Luzern-West', 'Emmen'], + excludedLocations: [], + budgetRange: { maxPerSqm: 312, maxMonthlyTotal: 14000, currency: 'CHF' }, + timing: { + earliestMoveIn: '2026-06-01', + latestMoveIn: '2027-01-01', + contractDurationMonths: 60, + flexibleTiming: true, + }, + mustCriteriaText: ['ÖV < 10 Min', 'Moderner Ausbau', 'Parkplätze vorhanden'], + softFactors: { + minPrestige: 60, + minAccessibility: 70, + requireParking: true, + maxPublicTransportMinutes: 10, + }, + weightingProfile: { + area: 0.19, location: 0.22, budget: 0.18, timing: 0.11, + prestige: 0.07, accessibility: 0.08, expansionPotential: 0.03, flexibility: 0.04, + visibility: 0.02, footfall: 0.01, talentAccess: 0.04, esg: 0.02, taxEnvironment: 0.01, + }, + confidenceInCriteria: 0.86, + extractedFromText: 'Beratungsgesellschaft sucht moderne Bürofläche 400–550m² in Luzern, max. CHF 312/m²/J, Parkplätze nötig.', + organizationId: 'org-wincasa', + createdAt: '2026-04-10T14:00:00Z', + updatedAt: '2026-04-10T14:00:00Z', + }, + // --- need-uc-c: Renato's Vermögensverwalter — Use Case C --- { id: 'need-uc-c', diff --git a/src/pages/supply/MyListings.tsx b/src/pages/supply/MyListings.tsx index 92e485e..acd413e 100644 --- a/src/pages/supply/MyListings.tsx +++ b/src/pages/supply/MyListings.tsx @@ -16,11 +16,15 @@ import { Tooltip, Typography, } from '@mui/material' -import { Plus, Trash2 } from 'lucide-react' +import { ArrowLeft, Plus, Trash2 } from 'lucide-react' import { useProperties, useUpdateProperty, useRemoveProperty } from '../../hooks/useProperties' import { useMatchesByProperty } from '../../hooks/useMatches' +import { useLatentNeedById } from '../../hooks/useLatentNeeds' import { PropertyDetailView } from '../../components/supply' +import { PublicNeedDetail, OfferWizard } from '../../components/anfragencenter' +import { NEED_TO_LATENT_NEED } from '../../lib/needToLatentNeedMap' import type { Property } from '../../domain/property' +import type { Match } from '../../domain/match' const ASSET_LABELS: Record = { OFFICE: 'Büro', @@ -44,17 +48,158 @@ function matchColor(score: number): string { return '#dc2626' } +// ─── Suchabo dialog ──────────────────────────────────────────────────────── + +function MatchListItem({ + match, + onSelect, +}: { + match: Match + onSelect: (needId: string) => void +}) { + const latentNeedId = NEED_TO_LATENT_NEED[match.needId] ?? null + const { data: latentNeed } = useLatentNeedById(latentNeedId) + const companyName = latentNeed?.tenantCompany ?? match.needId + + return ( + + + + {companyName} + + {latentNeed && ( + + {latentNeed.desiredLocation} + + )} + + + + {latentNeedId ? ( + + ) : ( + + + + + + )} + + + ) +} + +interface SuchaboDialogProps { + propertyId: string + matches: Match[] + onClose: () => void +} + +function SuchaboDialogContent({ matches }: SuchaboDialogProps) { + const [selectedNeedId, setSelectedNeedId] = useState( + matches.length === 1 ? matches[0].needId : null, + ) + const latentNeedId = selectedNeedId ? (NEED_TO_LATENT_NEED[selectedNeedId] ?? null) : null + const { data: latentNeed, isLoading } = useLatentNeedById(latentNeedId) + + const showList = !selectedNeedId + + if (showList) { + return ( + + {matches.map(m => ( + + ))} + + ) + } + + if (isLoading) { + return ( + + + + ) + } + + if (!latentNeed) { + return ( + + Suchabo nicht gefunden. + + ) + } + + return ( + + {matches.length > 1 && ( + + + + )} + + + ) +} + +// ─── Row ─────────────────────────────────────────────────────────────────── + interface RowProps { p: Property isLast: boolean onDetail: (id: string) => void onToggleStatus: (p: Property) => void onDelete: (p: Property) => void + onChipClick: (propertyId: string, matches: Match[]) => void updatePending: boolean removePending: boolean } -const ListingRow = memo(function ListingRow({ p, isLast, onDetail, onToggleStatus, onDelete, updatePending, removePending }: RowProps) { +const ListingRow = memo(function ListingRow({ + p, isLast, onDetail, onToggleStatus, onDelete, onChipClick, updatePending, removePending, +}: RowProps) { const { data: matches = [] } = useMatchesByProperty(p.id) const topScore = matches.length > 0 ? Math.max(...matches.map(m => m.matchScore)) : 0 @@ -102,17 +247,19 @@ const ListingRow = memo(function ListingRow({ p, isLast, onDetail, onToggleStatu {/* Match count */} e.stopPropagation()}> {matches.length > 0 ? ( - `${m.matchScore}% · need-${m.needId.slice(-3)}`).join('\n')}> + onChipClick(p.id, matches)} sx={{ height: 20, fontSize: '0.65rem', fontWeight: 600, bgcolor: `${matchColor(topScore)}18`, color: matchColor(topScore), - cursor: 'default', + cursor: 'pointer', + '&:hover': { opacity: 0.85 }, }} /> @@ -156,6 +303,8 @@ const ListingRow = memo(function ListingRow({ p, isLast, onDetail, onToggleStatu ) }) +// ─── Page ───────────────────────────────────────────────────────────────── + export default function MyListings() { const navigate = useNavigate() @@ -166,6 +315,10 @@ export default function MyListings() { const [detailId, setDetailId] = useState(null) const [confirmDelete, setConfirmDelete] = useState(null) const [actionError, setActionError] = useState(null) + const [suchaboDialog, setSuchaboDialog] = useState<{ + propertyId: string + matches: Match[] + } | null>(null) function handleToggleStatus(p: Property) { const next = p.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE' @@ -254,6 +407,7 @@ export default function MyListings() { onDetail={setDetailId} onToggleStatus={handleToggleStatus} onDelete={setConfirmDelete} + onChipClick={(pid, m) => setSuchaboDialog({ propertyId: pid, matches: m })} updatePending={updateProperty.isPending} removePending={removeProperty.isPending} /> @@ -273,6 +427,31 @@ export default function MyListings() { )} + {/* Suchabo dialog */} + setSuchaboDialog(null)} + maxWidth="sm" + fullWidth + slotProps={{ paper: { sx: { maxHeight: '85vh' } } }} + > + + Suchabos + {suchaboDialog && ( + + {suchaboDialog.matches.length} Suchabo{suchaboDialog.matches.length !== 1 ? 's' : ''} matchen dieses Inserat + + )} + + {suchaboDialog && ( + setSuchaboDialog(null)} + /> + )} + + {/* Delete confirmation */} setConfirmDelete(null)} maxWidth="xs" fullWidth> Inserat löschen? @@ -295,6 +474,9 @@ export default function MyListings() { + + {/* OfferWizard — triggered by PublicNeedDetail's "Angebot erstellen" button */} + ) }