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:
Benjamin Sutter
2026-05-24 14:56:32 +02:00
parent f487435a94
commit e36c5bc979
33 changed files with 666 additions and 407 deletions
+42 -55
View File
@@ -1,6 +1,5 @@
import { useState } from 'react'
import { useNavigate } from 'react-router'
import { useQueryClient } from '@tanstack/react-query'
import {
Alert,
Box,
@@ -17,8 +16,7 @@ import {
Typography,
} from '@mui/material'
import { Plus, Trash2 } from 'lucide-react'
import { useProperties } from '../../hooks/useProperties'
import { propertyService } from '../../services/propertyService'
import { useProperties, useUpdateProperty, useRemoveProperty } from '../../hooks/useProperties'
import type { Property } from '../../domain/property'
const ASSET_LABELS: Record<string, string> = {
@@ -37,41 +35,36 @@ function formatDate(iso?: string) {
export default function MyListings() {
const navigate = useNavigate()
const qc = useQueryClient()
const { data: listings = [], isLoading } = useProperties({ sourceType: 'DIRECT' })
const updateProperty = useUpdateProperty()
const removeProperty = useRemoveProperty()
const [togglingId, setTogglingId] = useState<string | null>(null)
const [deletingId, setDeletingId] = useState<string | null>(null)
const [confirmDelete, setConfirmDelete] = useState<Property | null>(null)
const [actionError, setActionError] = useState<string | null>(null)
async function handleToggleStatus(p: Property) {
setTogglingId(p.id)
setActionError(null)
try {
const next = p.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'
await propertyService.update(p.id, { status: next })
qc.invalidateQueries({ queryKey: ['properties'] })
} catch {
setActionError('Status konnte nicht geändert werden.')
} finally {
setTogglingId(null)
}
function handleToggleStatus(p: Property) {
const next = p.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'
updateProperty.mutate(
{ id: p.id, input: { status: next } },
{
onError: () => {
setActionError('Status konnte nicht geändert werden.')
},
},
)
}
async function handleDelete(p: Property) {
setDeletingId(p.id)
setActionError(null)
try {
await propertyService.remove(p.id)
qc.invalidateQueries({ queryKey: ['properties'] })
} catch {
setActionError('Inserat konnte nicht gelöscht werden.')
} finally {
setDeletingId(null)
setConfirmDelete(null)
}
function handleDelete(p: Property) {
removeProperty.mutate(p.id, {
onSuccess: () => {
setConfirmDelete(null)
},
onError: () => {
setActionError('Inserat konnte nicht gelöscht werden.')
setConfirmDelete(null)
},
})
}
return (
@@ -184,35 +177,29 @@ export default function MyListings() {
{/* Status toggle */}
<Box>
{togglingId === p.id ? (
<CircularProgress size={16} />
) : (
<Tooltip title={p.status === 'ACTIVE' ? 'Deaktivieren' : 'Aktivieren'}>
<Switch
size="small"
checked={p.status === 'ACTIVE'}
onChange={() => handleToggleStatus(p)}
sx={{ '& .MuiSwitch-thumb': { width: 14, height: 14 } }}
/>
</Tooltip>
)}
<Tooltip title={p.status === 'ACTIVE' ? 'Deaktivieren' : 'Aktivieren'}>
<Switch
size="small"
checked={p.status === 'ACTIVE'}
onChange={() => handleToggleStatus(p)}
disabled={updateProperty.isPending}
sx={{ '& .MuiSwitch-thumb': { width: 14, height: 14 } }}
/>
</Tooltip>
</Box>
{/* Delete */}
<Box>
{deletingId === p.id ? (
<CircularProgress size={16} />
) : (
<Tooltip title="Inserat löschen">
<IconButton
size="small"
onClick={() => setConfirmDelete(p)}
sx={{ color: '#94a3b8', '&:hover': { color: '#ef4444' } }}
>
<Trash2 size={15} />
</IconButton>
</Tooltip>
)}
<Tooltip title="Inserat löschen">
<IconButton
size="small"
onClick={() => setConfirmDelete(p)}
disabled={removeProperty.isPending}
sx={{ color: '#94a3b8', '&:hover': { color: '#ef4444' } }}
>
<Trash2 size={15} />
</IconButton>
</Tooltip>
</Box>
</Box>
)