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:
@@ -1,6 +1,7 @@
|
||||
import { Box, Button, Chip, IconButton, Tooltip, Typography } from '@mui/material'
|
||||
import { X, AlertTriangle, BookmarkCheck, ExternalLink, Kanban } from 'lucide-react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { usePipelineItems } from '../../hooks/usePipeline'
|
||||
import { usePipelineStore } from '../../stores/pipelineStore'
|
||||
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
||||
|
||||
@@ -20,7 +21,8 @@ interface Props {
|
||||
|
||||
export function CompareColumnHeader({ item, onRemove }: Props) {
|
||||
const navigate = useNavigate()
|
||||
const { items: pipelineItems, openSavedDialog } = usePipelineStore()
|
||||
const { data: pipelineItems = [] } = usePipelineItems()
|
||||
const { openSavedDialog } = usePipelineStore()
|
||||
|
||||
const meta = TYPE_META[item.resultType] ?? { label: item.resultType, color: '#64748b' }
|
||||
const prop = item.resultType !== 'FUTURE_AVAILABILITY' ? (item as any).property : null
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
AlertTriangle, ChevronRight, CheckCircle, ExternalLink, FileText,
|
||||
MapPin, MessageSquare, Sparkles, StickyNote, X,
|
||||
} from 'lucide-react'
|
||||
import { usePipelineStore } from '../../stores/pipelineStore'
|
||||
import { useMoveStage, useUpdateNotes, useLoseItem } from '../../hooks/usePipeline'
|
||||
import type { PipelineItem } from '../../domain/pipeline'
|
||||
import { STAGES, NEXT_STAGE, MOCK_DOCS } from './pipelineConstants'
|
||||
import { scoreColor, detailPath, getKiInsight } from './pipelineUtils'
|
||||
@@ -16,7 +16,9 @@ import { scoreColor, detailPath, getKiInsight } from './pipelineUtils'
|
||||
|
||||
export function DetailPanel({ item, onClose }: { item: PipelineItem; onClose: () => void }) {
|
||||
const navigate = useNavigate()
|
||||
const { moveStage, updateNotes, loseItem } = usePipelineStore()
|
||||
const { mutate: moveStage } = useMoveStage()
|
||||
const { mutate: updateNotes } = useUpdateNotes()
|
||||
const { mutate: loseItem } = useLoseItem()
|
||||
const path = detailPath(item)
|
||||
const [notes, setNotes] = useState(item.notes ?? '')
|
||||
const stageConfig = STAGES.find(s => s.key === item.stage)!
|
||||
@@ -128,7 +130,7 @@ export function DetailPanel({ item, onClose }: { item: PipelineItem; onClose: ()
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
{nextStage && (
|
||||
<Button size="small" variant="contained" endIcon={<ChevronRight size={14} />}
|
||||
onClick={() => moveStage(item.id, nextStage.key)}
|
||||
onClick={() => moveStage({ id: item.id, stage: nextStage.key })}
|
||||
sx={{ bgcolor: stageConfig.color, '&:hover': { filter: 'brightness(0.9)' }, fontSize: '0.75rem', py: 0.5 }}
|
||||
>
|
||||
{nextStage.label}
|
||||
@@ -157,7 +159,7 @@ export function DetailPanel({ item, onClose }: { item: PipelineItem; onClose: ()
|
||||
placeholder="Notiz hinzufügen…"
|
||||
value={notes}
|
||||
onChange={e => setNotes(e.target.value)}
|
||||
onBlur={() => updateNotes(item.id, notes)}
|
||||
onBlur={() => updateNotes({ id: item.id, notes })}
|
||||
sx={{ '& .MuiOutlinedInput-root': { fontSize: '0.8rem', borderRadius: 1.5 } }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -5,29 +5,22 @@ import {
|
||||
} from '@mui/material'
|
||||
import { Bookmark } from 'lucide-react'
|
||||
import { usePipelineStore } from '../../stores/pipelineStore'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
import { useAddToPipeline } from '../../hooks/usePipeline'
|
||||
|
||||
export function AddToPipelineDialog() {
|
||||
const { dialogOpen, pendingItem, closeSavedDialog, confirmSaved } = usePipelineStore()
|
||||
const showToast = useToastStore((s) => s.showToast)
|
||||
const { dialogOpen, pendingItem, closeSavedDialog } = usePipelineStore()
|
||||
const { mutate: addToPipeline, isPending } = useAddToPipeline()
|
||||
const [note, setNote] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
function handleClose() {
|
||||
closeSavedDialog()
|
||||
setNote('')
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
setSaving(true)
|
||||
const result = confirmSaved(note.trim() || undefined)
|
||||
setSaving(false)
|
||||
function handleConfirm() {
|
||||
if (!pendingItem) return
|
||||
addToPipeline({ pending: pendingItem, notes: note.trim() || undefined })
|
||||
setNote('')
|
||||
if (result === 'duplicate') {
|
||||
showToast('Bereits in der Pipeline vorhanden.', 'warning')
|
||||
} else {
|
||||
showToast('Zur Pipeline (Gemerkt) hinzugefügt.')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -59,12 +52,12 @@ export function AddToPipelineDialog() {
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={handleClose} disabled={saving}>Abbrechen</Button>
|
||||
<Button onClick={handleClose} disabled={isPending}>Abbrechen</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleConfirm}
|
||||
disabled={saving}
|
||||
endIcon={saving ? <CircularProgress size={14} color="inherit" /> : undefined}
|
||||
disabled={isPending}
|
||||
endIcon={isPending ? <CircularProgress size={14} color="inherit" /> : undefined}
|
||||
sx={{ bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' } }}
|
||||
>
|
||||
Merken
|
||||
|
||||
Reference in New Issue
Block a user