feat(services): F005 – complete service layer

New domain types:
- shortlist.ts: Shortlist, ShortlistItem, CreateShortlistInput
- review.ts: ReviewQueueItem, ReviewPriority, ReviewQueueStatus

New mock data:
- shortlists.ts: 2 sample shortlists
- reviewQueue.ts: 3 pending review items

New providers (interface + mockup):
- IShortlistProvider / MockupShortlistProvider (with 150ms latency)
- IReviewProvider / MockupReviewProvider (priority-sorted, with latency)
- IDashboardProvider / MockupDashboardProvider (computed from all mock data)

New services:
- reviewService: getQueue, approve, reject, assign
- shortlistService: CRUD + addItem/removeItem
- authService: wraps sessionStore, swappable for real auth
- dashboardService: getStats(organizationId?)

Enhancements:
- types.ts: typed ServiceErrorCode + ServiceError, Pagination alias
- aiService.ts: openRouterAIService stub (throws ai_generation_failed)
- lib/mockUtils.ts: mockDelay(ms) utility

New hooks:
- useUnifiedResults(needId?): joins matches+properties+signals → UnifiedMatchResult[]
- useReviewQueue: + useApproveReviewItem + useRejectReviewItem
- useMatchDetail, usePropertyDetail, useNeedProfiles aliases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-15 11:12:06 +02:00
parent 0b874865ba
commit 5ef8b9d69d
25 changed files with 636 additions and 7 deletions
+42
View File
@@ -0,0 +1,42 @@
import type { IReviewProvider, ReviewFilters } from './IReviewProvider'
import type { ReviewQueueItem } from '../domain/review'
import { mockReviewQueue } from '../mock-data/reviewQueue'
import { mockDelay } from '../lib/mockUtils'
const store: ReviewQueueItem[] = [...mockReviewQueue]
const priorityOrder: Record<string, number> = { HIGH: 0, MEDIUM: 1, LOW: 2 }
export const MockupReviewProvider: IReviewProvider = {
async getQueue(filters?: ReviewFilters) {
await mockDelay()
let results = [...store]
if (filters?.priority) results = results.filter(r => r.priority === filters.priority)
if (filters?.status) results = results.filter(r => r.status === filters.status)
if (filters?.assignedTo) results = results.filter(r => r.assignedTo === filters.assignedTo)
if (filters?.organizationId) results = results.filter(r => r.organizationId === filters.organizationId)
return results.sort((a, b) => (priorityOrder[a.priority] ?? 9) - (priorityOrder[b.priority] ?? 9))
},
async getById(id) {
await mockDelay()
return store.find(r => r.id === id) ?? null
},
async approve(id, _reviewedBy, notes?) {
await mockDelay()
const idx = store.findIndex(r => r.id === id)
store[idx] = { ...store[idx], status: 'COMPLETED', notes, updatedAt: new Date().toISOString() }
return store[idx]
},
async reject(id, _reviewedBy, notes?) {
await mockDelay()
const idx = store.findIndex(r => r.id === id)
store[idx] = { ...store[idx], status: 'COMPLETED', notes, updatedAt: new Date().toISOString() }
return store[idx]
},
async assign(id, assignTo) {
await mockDelay()
const idx = store.findIndex(r => r.id === id)
store[idx] = { ...store[idx], assignedTo: assignTo, status: 'IN_REVIEW', updatedAt: new Date().toISOString() }
return store[idx]
},
}