refactor: move PipelineItems from Zustand to Provider→Service→React Query

PipelineItems are domain data and must not live in Zustand. Moves the
full stack to the correct layer: MockupPipelineProvider (localStorage
persistence + seed fallback) → pipelineService → usePipeline hooks
(useQuery for reads, useMutation for writes with cache invalidation).

pipelineStore is now UI-only: dialogOpen, pendingItem, openSavedDialog,
closeSavedDialog. All consumers updated to use the new hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 12:32:57 +02:00
parent 0582031930
commit 723f553939
12 changed files with 303 additions and 115 deletions
+85
View File
@@ -0,0 +1,85 @@
import { MockupPipelineProvider } from '../provider/MockupPipelineProvider'
import type { PipelineItem, PipelineStage, PendingPipelineItem } from '../domain/pipeline'
import type { ListResponse, ItemResponse } from './types'
import { throwServiceError } from './errors'
const provider = MockupPipelineProvider
export const pipelineService = {
async getAll(): Promise<ListResponse<PipelineItem>> {
try {
const data = await provider.getAll()
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
} catch (err) {
throwServiceError(err)
}
},
async confirmSaved(pending: PendingPipelineItem, notes?: string): Promise<ItemResponse<'added' | 'duplicate'>> {
try {
const items = await provider.getAll()
const alreadyExists = items.some(
i => i.id === pending.resultId || (pending.propertyId && i.propertyId === pending.propertyId)
)
if (alreadyExists) return { data: 'duplicate' }
const newItem: PipelineItem = {
id: pending.resultId,
matchId: pending.resultId,
title: pending.title,
location: pending.location ?? '',
matchScore: pending.matchScore,
resultType: pending.resultType,
stage: 'SAVED',
propertyId: pending.propertyId,
unitId: pending.unitId,
propertyAddress: pending.propertyAddress,
areaLabel: pending.areaLabel,
rentLabel: pending.rentLabel,
availabilityLabel: pending.availabilityLabel,
notes: notes || undefined,
addedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
await provider.add(newItem)
return { data: 'added' }
} catch (err) {
throwServiceError(err)
}
},
async moveStage(id: string, stage: PipelineStage): Promise<ItemResponse<void>> {
try {
await provider.moveStage(id, stage)
return { data: undefined }
} catch (err) {
throwServiceError(err)
}
},
async updateNotes(id: string, notes: string): Promise<ItemResponse<void>> {
try {
await provider.updateNotes(id, notes)
return { data: undefined }
} catch (err) {
throwServiceError(err)
}
},
async loseItem(id: string): Promise<ItemResponse<void>> {
try {
await provider.loseItem(id)
return { data: undefined }
} catch (err) {
throwServiceError(err)
}
},
async linkInquiry(id: string, inquiryId: string): Promise<ItemResponse<void>> {
try {
await provider.linkInquiry(id, inquiryId)
return { data: undefined }
} catch (err) {
throwServiceError(err)
}
},
}