File size: 4,998 Bytes
cd99321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { useState, useMemo, useEffect, useRef } from 'react'
import { useTripStore } from '../../store/tripStore'
import { useCanDo } from '../../store/permissionsStore'
import { useToast } from '../shared/Toast'
import { useTranslation } from '../../i18n'
import apiClient from '../../api/client'
import { formatDate as fmtDate } from '../../utils/formatters'
import type { TodoItem } from '../../types'
import type { FilterType, Member } from './todoListModel'

/**
 * Todo list logic — store actions, member load, the filter/selection/add-new
 * view state and the derived buckets (filtered list + counts) + handlers.
 * TodoListPanel stays a layout component that renders the sidebar, the rows
 * (TodoRow) and the detail/new panes from this state.
 */
export function useTodoList(tripId: number, items: TodoItem[], addItemSignal: number) {
  const { addTodoItem, updateTodoItem, deleteTodoItem, toggleTodoItem } = useTripStore()
  const trip = useTripStore((s) => s.trip)
  const can = useCanDo()
  const canEdit = can('packing_edit', trip)
  const toast = useToast()
  const { t, locale } = useTranslation()
  const formatDate = (d: string) => fmtDate(d, locale) || d

  const [isMobile, setIsMobile] = useState(() => window.innerWidth < 768)
  useEffect(() => {
    const mq = window.matchMedia('(max-width: 767px)')
    const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches)
    mq.addEventListener('change', handler)
    return () => mq.removeEventListener('change', handler)
  }, [])

  const [filter, setFilter] = useState<FilterType>('all')
  const [selectedId, setSelectedId] = useState<number | null>(null)
  const [isAddingNew, setIsAddingNew] = useState(false)
  const lastHandledAddSignal = useRef(addItemSignal)

  useEffect(() => {
    if (addItemSignal !== lastHandledAddSignal.current && addItemSignal > 0) {
      setSelectedId(null)
      setIsAddingNew(true)
    }
    lastHandledAddSignal.current = addItemSignal
  }, [addItemSignal])
  const [sortByPrio, setSortByPrio] = useState(false)
  const [addingCategory, setAddingCategory] = useState(false)
  const [newCategoryName, setNewCategoryName] = useState('')
  const [members, setMembers] = useState<Member[]>([])
  const [currentUserId, setCurrentUserId] = useState<number | null>(null)

  useEffect(() => {
    apiClient.get(`/trips/${tripId}/members`).then(r => {
      const owner = r.data?.owner
      const mems = r.data?.members || []
      const all = owner ? [owner, ...mems] : mems
      setMembers(all)
      setCurrentUserId(r.data?.current_user_id || null)
    }).catch(() => {})
  }, [tripId])

  const categories = useMemo(() => {
    const cats = new Set<string>()
    items.forEach(i => { if (i.category) cats.add(i.category) })
    return Array.from(cats).sort()
  }, [items])

  const today = new Date().toISOString().split('T')[0]

  const filtered = useMemo(() => {
    let result: TodoItem[]
    if (filter === 'all') result = items.filter(i => !i.checked)
    else if (filter === 'done') result = items.filter(i => !!i.checked)
    else if (filter === 'my') result = items.filter(i => !i.checked && i.assigned_user_id === currentUserId)
    else if (filter === 'overdue') result = items.filter(i => !i.checked && i.due_date && i.due_date < today)
    else result = items.filter(i => i.category === filter)
    if (sortByPrio) result = [...result].sort((a, b) => {
      const ap = a.priority || 99
      const bp = b.priority || 99
      return ap - bp
    })
    return result
  }, [items, filter, currentUserId, today, sortByPrio])

  const selectedItem = items.find(i => i.id === selectedId) || null
  const totalCount = items.length
  const doneCount = items.filter(i => !!i.checked).length
  const overdueCount = items.filter(i => !i.checked && i.due_date && i.due_date < today).length
  const myCount = currentUserId ? items.filter(i => !i.checked && i.assigned_user_id === currentUserId).length : 0

  const addCategory = () => {
    const name = newCategoryName.trim()
    if (!name || categories.includes(name)) { setAddingCategory(false); setNewCategoryName(''); return }
    addTodoItem(tripId, { name: t('todo.newItem'), category: name } as any)
      .then(() => { setAddingCategory(false); setNewCategoryName(''); setFilter(name) })
      .catch(err => toast.error(err instanceof Error ? err.message : t('common.error')))
  }

  // Get category count (non-done items)
  const catCount = (cat: string) => items.filter(i => i.category === cat && !i.checked).length

  return {
    canEdit, t, formatDate, toggleTodoItem,
    isMobile, filter, setFilter, selectedId, setSelectedId,
    isAddingNew, setIsAddingNew, sortByPrio, setSortByPrio,
    addingCategory, setAddingCategory, newCategoryName, setNewCategoryName,
    members, categories, today, filtered, selectedItem,
    totalCount, doneCount, overdueCount, myCount,
    addCategory, catCount,
    // exposed for completeness (DetailPane/NewTaskPane already get their own)
    updateTodoItem, deleteTodoItem,
  }
}