feat: clickable NeedMatchCard in Matchability tab navigates to Anfragencenter
Clicking a need card in the Matchability tab pre-selects the property and navigates to Anfragencenter → Latente Anfragen tab. Angebot creation lives here (demand-side), not in Marktsignale. NeedMatchCard gains onClick prop with navy hover + shadow treatment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import type { PropertyNeedMatch } from '../../domain/match'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
match: PropertyNeedMatch
|
match: PropertyNeedMatch
|
||||||
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function ScoreBadge({ score }: { score: number }) {
|
function ScoreBadge({ score }: { score: number }) {
|
||||||
@@ -43,17 +44,19 @@ function InfoRow({ icon, label, value }: { icon: React.ReactNode; label: string;
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NeedMatchCard({ match }: Props) {
|
export function NeedMatchCard({ match, onClick }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
elevation={0}
|
elevation={0}
|
||||||
|
onClick={onClick}
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
mb: 1.5,
|
mb: 1.5,
|
||||||
border: '1px solid #e2e8f0',
|
border: '1px solid #e2e8f0',
|
||||||
borderRadius: 1.5,
|
borderRadius: 1.5,
|
||||||
'&:hover': { borderColor: '#cbd5e1', bgcolor: '#fafafa' },
|
cursor: onClick ? 'pointer' : 'default',
|
||||||
transition: 'border-color 0.15s, background-color 0.15s',
|
'&:hover': onClick ? { borderColor: '#1e3a5f', bgcolor: '#f8fafc', boxShadow: '0 2px 8px rgba(30,58,95,0.08)' } : { borderColor: '#cbd5e1', bgcolor: '#fafafa' },
|
||||||
|
transition: 'border-color 0.15s, background-color 0.15s, box-shadow 0.15s',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import { Edit2, Save, X } from 'lucide-react'
|
import { Edit2, Save, X } from 'lucide-react'
|
||||||
|
import { useOfferWizardStore } from '../../stores/offerWizardStore'
|
||||||
import { PropertyMap } from '../shared'
|
import { PropertyMap } from '../shared'
|
||||||
import { NeedMatchCard } from './NeedMatchCard'
|
import { NeedMatchCard } from './NeedMatchCard'
|
||||||
import { PropertyMarketSignalsTab } from './PropertyMarketSignalsTab'
|
import { PropertyMarketSignalsTab } from './PropertyMarketSignalsTab'
|
||||||
@@ -238,6 +240,8 @@ function OverviewPanel({ p, editing, draft, onDraftChange }: OverviewPanelProps)
|
|||||||
function MatchabilityTabPanel({ propertyId }: { propertyId: string }) {
|
function MatchabilityTabPanel({ propertyId }: { propertyId: string }) {
|
||||||
const [matches, setMatches] = useState<PropertyNeedMatch[]>([])
|
const [matches, setMatches] = useState<PropertyNeedMatch[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const setSelectedProperties = useOfferWizardStore(s => s.setSelectedProperties)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
@@ -248,6 +252,11 @@ function MatchabilityTabPanel({ propertyId }: { propertyId: string }) {
|
|||||||
return () => { cancelled = true }
|
return () => { cancelled = true }
|
||||||
}, [propertyId])
|
}, [propertyId])
|
||||||
|
|
||||||
|
function handleNeedCardClick() {
|
||||||
|
setSelectedProperties([propertyId])
|
||||||
|
navigate('/supply/anfragen', { state: { tab: 1 } })
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3, display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ p: 3, display: 'flex', justifyContent: 'center' }}>
|
||||||
@@ -263,7 +272,7 @@ function MatchabilityTabPanel({ propertyId }: { propertyId: string }) {
|
|||||||
Matchability
|
Matchability
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
Bedarfsprofile mit ≥ 80% Match-Score für dieses Objekt
|
Bedarfsprofile mit ≥ 80% Match-Score — Karte klicken um Angebot zu erstellen
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{matches.length === 0 ? (
|
{matches.length === 0 ? (
|
||||||
@@ -271,7 +280,7 @@ function MatchabilityTabPanel({ propertyId }: { propertyId: string }) {
|
|||||||
Keine Bedarfsprofile mit ≥ 80% Match-Score für dieses Objekt gefunden.
|
Keine Bedarfsprofile mit ≥ 80% Match-Score für dieses Objekt gefunden.
|
||||||
</Alert>
|
</Alert>
|
||||||
) : (
|
) : (
|
||||||
matches.map(m => <NeedMatchCard key={m.matchId} match={m} />)
|
matches.map(m => <NeedMatchCard key={m.matchId} match={m} onClick={handleNeedCardClick} />)
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
|
||||||
import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogContent, DialogTitle, LinearProgress, Typography } from '@mui/material'
|
import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogContent, DialogTitle, LinearProgress, Typography } from '@mui/material'
|
||||||
import { BarChart2, Building2, CheckCircle, Download, FileText, Lightbulb, Send, TrendingUp } from 'lucide-react'
|
import { BarChart2, Building2, CheckCircle, Download, FileText, Lightbulb, TrendingUp } from 'lucide-react'
|
||||||
import { marketReportService } from '../../services/marketReportService'
|
import { marketReportService } from '../../services/marketReportService'
|
||||||
import { useOfferWizardStore } from '../../stores/offerWizardStore'
|
|
||||||
import type { MarketReport, PropertyMarketSignal, SignalCategory } from '../../domain/marketReport'
|
import type { MarketReport, PropertyMarketSignal, SignalCategory } from '../../domain/marketReport'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -222,13 +220,6 @@ export function PropertyMarketSignalsTab({ propertyId }: Props) {
|
|||||||
const [report, setReport] = useState<MarketReport | null>(null)
|
const [report, setReport] = useState<MarketReport | null>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [dialogOpen, setDialogOpen] = useState(false)
|
const [dialogOpen, setDialogOpen] = useState(false)
|
||||||
const navigate = useNavigate()
|
|
||||||
const setSelectedProperties = useOfferWizardStore(s => s.setSelectedProperties)
|
|
||||||
|
|
||||||
function handleAngebotErstellen() {
|
|
||||||
setSelectedProperties([propertyId])
|
|
||||||
navigate('/supply/anfragen', { state: { tab: 1 } })
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
@@ -260,26 +251,15 @@ export function PropertyMarketSignalsTab({ propertyId }: Props) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Button
|
||||||
<Button
|
variant="outlined"
|
||||||
variant="contained"
|
size="small"
|
||||||
size="small"
|
startIcon={<FileText size={14} />}
|
||||||
startIcon={<Send size={14} />}
|
onClick={() => setDialogOpen(true)}
|
||||||
onClick={handleAngebotErstellen}
|
sx={{ textTransform: 'none', fontSize: '0.8125rem' }}
|
||||||
sx={{ textTransform: 'none', fontSize: '0.8125rem', bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#16304d' } }}
|
>
|
||||||
>
|
Bericht erstellen
|
||||||
Angebot erstellen
|
</Button>
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
startIcon={<FileText size={14} />}
|
|
||||||
onClick={() => setDialogOpen(true)}
|
|
||||||
sx={{ textTransform: 'none', fontSize: '0.8125rem' }}
|
|
||||||
>
|
|
||||||
Bericht erstellen
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{!report ? (
|
{!report ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user