feat: Inserat erstellen — direct listing flow for units and standalone

- New /supply/new-listing page (Path A: pre-filled from portfolio unit,
  Path B: standalone blank form); creates EXTERNAL_MARKET + sourceType DIRECT
- "+ Inserat" button on available units in PropertyDetailView navigates
  with prefilled address/area/rent
- Nav entry "Neues Inserat" added to supply sidebar
- SourceProvenancePanel hidden for DIRECT source listings (no external source)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-21 21:10:41 +02:00
parent 531c88cfb6
commit 67d3d06a53
5 changed files with 338 additions and 0 deletions
+2
View File
@@ -39,6 +39,7 @@ import {
Menu,
Kanban,
BellRing,
Plus,
} from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import { OrganizationContextBadge } from './OrganizationContextBadge'
@@ -88,6 +89,7 @@ const WORKSPACE_CONFIG: Record<WorkspaceType, WorkspaceConfig> = {
{ path: '/supply/anfragen', label: 'Anfragencenter', icon: MessageSquare },
{ path: '/supply/data-quality', label: 'Datenpflege', icon: CheckSquare },
{ path: '/supply/market-intelligence', label: 'Markt Intelligence', icon: Radar },
{ path: '/supply/new-listing', label: 'Neues Inserat', icon: Plus },
],
},
[WorkspaceType.DEMAND]: {
@@ -13,6 +13,7 @@ interface Props {
export function SourceProvenancePanel({ property }: Props) {
if (!property) return null
if (property.sourceType === 'DIRECT' || property.sourceMeta?.sourceType === 'DIRECT') return null
const freshness = property.dataQuality?.freshness
const freshnessMeta = freshness ? (FRESHNESS_META[freshness] ?? null) : null
@@ -106,6 +106,7 @@ function MatchPill({ m }: { m: UnitNeedMatch }) {
}
function UnitStructurePanel({ p }: { p: Property }) {
const navigate = useNavigate()
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [expandedUnit, setExpandedUnit] = useState<string | null>(null)
@@ -204,6 +205,28 @@ function UnitStructurePanel({ p }: { p: Property }) {
{u.isFlexible && (
<Chip label="Teilfläche möglich" size="small" sx={{ height: 16, fontSize: '0.58rem', bgcolor: '#eff6ff', color: '#1d4ed8', border: '1px solid #bfdbfe' }} />
)}
<Button
size="small"
variant="text"
sx={{ height: 16, fontSize: '0.58rem', p: 0, minWidth: 0, color: '#7c3aed', textTransform: 'none', lineHeight: 1 }}
onClick={() => navigate('/supply/new-listing', {
state: {
prefill: {
assetType: p.assetType,
street: p.address?.street,
houseNumber: p.address?.houseNumber,
postalCode: p.address?.postalCode,
city: p.address?.city,
areaSqm: u.offeredSqm ?? u.areaSqm,
rentPricePerSqm: u.rentPricePerSqm ?? p.rentPricePerSqm,
unitLabel: u.unitLabel,
propertyId: p.id,
},
},
})}
>
+ Inserat
</Button>
</>
) : (
<Typography variant="caption" sx={{ color: '#64748b' }} noWrap>{u.currentTenant ?? 'Vermietet'}</Typography>