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
@@ -5,13 +5,13 @@ import { useLocation } from 'react-router'
import { useShallow } from 'zustand/react/shallow'
import { useAssistantStore } from '../../stores/assistantStore'
import { useSessionStore } from '../../stores/sessionStore'
import { aiAssistantService } from '../../services/aiAssistantService'
import { useAssistantSuggestions, useAssistantAnswer } from '../../hooks/useAssistant'
import { AssistantContextSummary } from './AssistantContextSummary'
import { AssistantMessageList } from './AssistantMessageList'
import { AssistantPromptSuggestions } from './AssistantPromptSuggestions'
import { AssistantLoadingState } from './AssistantLoadingState'
import { AssistantErrorState } from './AssistantErrorState'
import type { AssistantContext, SuggestedQuestion } from '../../domain/assistant'
import type { AssistantContext } from '../../domain/assistant'
import type { WorkspaceType } from '../../domain/enums'
function resolveWorkspace(pathname: string): WorkspaceType | null {
@@ -27,24 +27,13 @@ export function GlobalAIAssistantDrawer() {
const { currentUser } = useSessionStore()
const location = useLocation()
const [suggestions, setSuggestions] = useState<SuggestedQuestion[]>([])
const [inputText, setInputText] = useState('')
const scrollRef = useRef<HTMLDivElement>(null)
// Build context from route when drawer opens
useEffect(() => {
if (!isOpen) return
const ctx: AssistantContext = {
currentRoute: location.pathname,
workspace: resolveWorkspace(location.pathname),
userRole: currentUser?.role ?? 'VIEWER',
organizationId: currentUser?.organizationId ?? '',
}
setContext(ctx)
aiAssistantService.getSuggestions(ctx).then(setSuggestions)
}, [isOpen, location.pathname])
const answerQuestion = useAssistantAnswer()
const { data: suggestions = [] } = useAssistantSuggestions(isOpen ? context : null)
// Refresh suggestions when route changes while open
// Build context from route when drawer opens or route changes while open
useEffect(() => {
if (!isOpen) return
const ctx: AssistantContext = {
@@ -54,8 +43,7 @@ export function GlobalAIAssistantDrawer() {
organizationId: currentUser?.organizationId ?? '',
}
setContext(ctx)
aiAssistantService.getSuggestions(ctx).then(setSuggestions)
}, [location.pathname])
}, [isOpen, location.pathname])
// Auto-scroll on new messages
useEffect(() => {
@@ -64,7 +52,7 @@ export function GlobalAIAssistantDrawer() {
}
}, [messages, isLoading])
const handleQuestion = useCallback(async (question: string) => {
const handleQuestion = useCallback((question: string) => {
if (!question.trim() || isLoading) return
setInputText('')
setError(null)
@@ -78,26 +66,32 @@ export function GlobalAIAssistantDrawer() {
addMessage(userMsg)
setLoading(true)
try {
const ctx = context ?? {
currentRoute: location.pathname,
workspace: resolveWorkspace(location.pathname),
userRole: currentUser?.role ?? 'VIEWER',
organizationId: currentUser?.organizationId ?? '',
}
const answer = await aiAssistantService.answerQuestion(ctx, question)
addMessage({
id: crypto.randomUUID(),
role: 'assistant',
createdAt: new Date().toISOString(),
...answer,
})
} catch {
setError('Antwort konnte nicht generiert werden. Bitte erneut versuchen.')
} finally {
setLoading(false)
const ctx = context ?? {
currentRoute: location.pathname,
workspace: resolveWorkspace(location.pathname),
userRole: currentUser?.role ?? 'VIEWER',
organizationId: currentUser?.organizationId ?? '',
}
}, [context, isLoading, location.pathname, currentUser, addMessage, setLoading, setError])
answerQuestion.mutate(
{ context: ctx, question },
{
onSuccess: (answer) => {
addMessage({
id: crypto.randomUUID(),
role: 'assistant',
createdAt: new Date().toISOString(),
...answer,
})
setLoading(false)
},
onError: () => {
setError('Antwort konnte nicht generiert werden. Bitte erneut versuchen.')
setLoading(false)
},
},
)
}, [context, isLoading, location.pathname, currentUser, addMessage, setLoading, setError, answerQuestion])
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
@@ -108,10 +102,6 @@ export function GlobalAIAssistantDrawer() {
const handleClear = () => {
clearConversation()
setSuggestions([])
if (context) {
aiAssistantService.getSuggestions(context).then(setSuggestions)
}
}
const showSuggestions = suggestions.length > 0 && messages.length === 0