refactor(arch): eliminate direct service calls in components — route all through hooks
New hook files: - useAuth.ts: useLogin, useSwitchDemoRole, useSwitchOrganization - useAI.ts: useParseNeed, useGenerateOfferEmail, useGenerateDecisionBrief, useParseListingText - useAssistant.ts: useAssistantSuggestions (useQuery), useAssistantAnswer (useMutation) - useOfferReport.ts: useOfferReportByInquiry, useCreateOfferReport, useUpdateOfferReport, useGenerateOfferReportPdf - useInquiryReport.ts: useInquiryReportByInquiry, useCreateInquiryReport, useUpdateInquiryReport, useFinalizeInquiryReport - useMarketReport.ts: useMarketReport - useWeighting.ts: useDefaultWeights (synchronous wrapper) - useUnitMatches.ts: useUnitMatchesMap, useUnitBundle, useBundleMatches (useMemo wrappers) Extended hooks: useProperties (add update/create/remove mutations), useNeeds (add useCreateNeed), useMatches (add useNeedMatchesForProperty, useAdditionalMatchesForInquiry), useReviewQueue (add useCreateReviewTask) Updated 21 components/pages: all direct service imports replaced with hooks. Deliberate exception: getRecommendedActions in DataQuality.tsx (pure sync utility, no provider access). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
@@ -16,8 +15,8 @@ import {
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { ArrowLeft, CheckCircle, ImagePlus, Sparkles, X } from 'lucide-react'
|
||||
import { propertyService } from '../../services/propertyService'
|
||||
import { parseListingText } from '../../services/aiService'
|
||||
import { useCreateProperty } from '../../hooks/useProperties'
|
||||
import { useParseListingText } from '../../hooks/useAI'
|
||||
import {
|
||||
ASSET_TYPE_LABELS,
|
||||
SOFT_FACTORS,
|
||||
@@ -29,10 +28,12 @@ import { buildCreatePropertyInput } from './newListingMapper'
|
||||
|
||||
export default function NewListing() {
|
||||
const navigate = useNavigate()
|
||||
const qc = useQueryClient()
|
||||
const { state } = useLocation() as { state: LocationState | null }
|
||||
const pre = state?.prefill ?? {}
|
||||
|
||||
const createProperty = useCreateProperty()
|
||||
const parseListingMutation = useParseListingText()
|
||||
|
||||
// Core fields
|
||||
const [assetType, setAssetType] = useState(pre.assetType ?? 'OFFICE')
|
||||
const [street, setStreet] = useState(pre.street ?? '')
|
||||
@@ -64,34 +65,33 @@ export default function NewListing() {
|
||||
|
||||
// AI
|
||||
const [aiText, setAiText] = useState('')
|
||||
const [aiParsing, setAiParsing] = useState(false)
|
||||
const [aiApplied, setAiApplied] = useState(false)
|
||||
|
||||
// Submit
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [created, setCreated] = useState(false)
|
||||
|
||||
const aiParsing = parseListingMutation.isPending
|
||||
const submitting = createProperty.isPending
|
||||
|
||||
const isPrefilled = !!pre.propertyId
|
||||
|
||||
async function handleAiParse() {
|
||||
function handleAiParse() {
|
||||
if (!aiText.trim()) return
|
||||
setAiParsing(true)
|
||||
try {
|
||||
const parsed = await parseListingText(aiText)
|
||||
if (parsed.assetType) setAssetType(parsed.assetType)
|
||||
if (parsed.areaSqm) setAreaSqm(String(parsed.areaSqm))
|
||||
if (parsed.rentPerSqm) setRentPerSqm(String(parsed.rentPerSqm))
|
||||
if (parsed.city && !city) setCity(parsed.city)
|
||||
if (parsed.fitOut) setFitOut(parsed.fitOut)
|
||||
if (parsed.parking) setParking(String(parsed.parking))
|
||||
if (parsed.softLevels) {
|
||||
setSoftLevels(prev => ({ ...prev, ...parsed.softLevels }))
|
||||
}
|
||||
setAiApplied(true)
|
||||
} finally {
|
||||
setAiParsing(false)
|
||||
}
|
||||
parseListingMutation.mutate(aiText, {
|
||||
onSuccess: (parsed) => {
|
||||
if (parsed.assetType) setAssetType(parsed.assetType)
|
||||
if (parsed.areaSqm) setAreaSqm(String(parsed.areaSqm))
|
||||
if (parsed.rentPerSqm) setRentPerSqm(String(parsed.rentPerSqm))
|
||||
if (parsed.city && !city) setCity(parsed.city)
|
||||
if (parsed.fitOut) setFitOut(parsed.fitOut)
|
||||
if (parsed.parking) setParking(String(parsed.parking))
|
||||
if (parsed.softLevels) {
|
||||
setSoftLevels(prev => ({ ...prev, ...parsed.softLevels }))
|
||||
}
|
||||
setAiApplied(true)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function addImage() {
|
||||
@@ -111,11 +111,10 @@ export default function NewListing() {
|
||||
return null
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
function handleSubmit() {
|
||||
const err = validate()
|
||||
if (err) { setError(err); return }
|
||||
setError(null)
|
||||
setSubmitting(true)
|
||||
|
||||
const input = buildCreatePropertyInput({
|
||||
assetType,
|
||||
@@ -135,15 +134,14 @@ export default function NewListing() {
|
||||
images,
|
||||
})
|
||||
|
||||
try {
|
||||
await propertyService.create(input)
|
||||
qc.invalidateQueries({ queryKey: ['properties'] })
|
||||
setCreated(true)
|
||||
} catch {
|
||||
setError('Fehler beim Erstellen des Inserats. Bitte erneut versuchen.')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
createProperty.mutate(input, {
|
||||
onSuccess: () => {
|
||||
setCreated(true)
|
||||
},
|
||||
onError: () => {
|
||||
setError('Fehler beim Erstellen des Inserats. Bitte erneut versuchen.')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
|
||||
Reference in New Issue
Block a user