feat: saved profiles — per-profile notifications, real match scores, action buttons

- Add per-profile notification config (toggle, score threshold, email) with Accordion UI in SavedNeedDetail
- NotificationButton: badge only lights up for unseen matches, dismisses on open (localStorage), uses per-profile minScore
- SavedNeedDetail: replace deterministicMatchScore with real match data (useMatchesByNeed), sort by score desc, deduplicate by property
- SavedProfilesTab & SavedNeedCard: top score bar now shows best real match score instead of criteria confidence
- "Treffer" count uses per-profile notificationConfig.minScore (≥80 default) consistently across card, panel, and notification badge
- Extract PropertyMatchRow to own file with Anfrage/Merken/Vergleichen/Details→/Zur Einheit→ action buttons
- Details → navigates to /demand/results/:matchId using real match ID format (m__propId__prop__needId)
- Add InquiryQuickDialog + AddToPipelineDialog to AISearch.tsx so dialogs work from SavedProfilesTab
- Org-isolation: useNeeds filters by organizationId, org-mobimo gets 4 own search profiles
- Mock data: all 18 org-wincasa need names changed to descriptive profile names; 4 new org-mobimo needs added
- useUpdateNeed mutation hook for updating need fields (notificationConfig etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-06-19 20:53:49 +02:00
parent 6aa4f96bd2
commit 7a0909e36a
10 changed files with 1131 additions and 110 deletions
+25 -3
View File
@@ -1,6 +1,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { needService } from '../services/needService'
import type { CreateNeedInput } from '../domain/need'
import { useSessionStore } from '../stores/sessionStore'
import type { CreateNeedInput, UpdateNeedInput } from '../domain/need'
interface UseNeedsOptions {
refetchOnMount?: boolean | 'always'
@@ -8,9 +9,10 @@ interface UseNeedsOptions {
}
export function useNeeds(options?: UseNeedsOptions) {
const orgId = useSessionStore(s => s.currentUser?.organizationId)
return useQuery({
queryKey: ['needs'],
queryFn: () => needService.getAll(),
queryKey: ['needs', orgId],
queryFn: () => needService.getAll({ organizationId: orgId }),
select: (res) => res.data ?? [],
...options,
})
@@ -36,3 +38,23 @@ export function useCreateNeed() {
},
})
}
export function useUpdateNeed() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, ...data }: { id: string } & UpdateNeedInput) => needService.update(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['needs'] })
},
})
}
export function useArchiveNeed() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (id: string) => needService.update(id, { status: 'INACTIVE' }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['needs'] })
},
})
}