File size: 5,603 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
115
116
117
118
119
import { CheckSquare, Square, ChevronRight, Flag, Calendar } from 'lucide-react'
import type { TodoItem } from '../../types'
import { katColor, PRIO_CONFIG, type Member } from './todoListModel'

/** A single task row in the todo list. Pure presentation; all behaviour is
 *  delegated to onSelect/onToggle so TodoListPanel stays a layout component. */
export default function TodoRow({ item, members, categories, today, isSelected, canEdit, formatDate, onSelect, onToggle }: {
  item: TodoItem
  members: Member[]
  categories: string[]
  today: string
  isSelected: boolean
  canEdit: boolean
  formatDate: (d: string) => string
  onSelect: (id: number | null) => void
  onToggle: (id: number, checked: boolean) => void
}) {
  const done = !!item.checked
  const assignedUser = members.find(m => m.id === item.assigned_user_id)
  const isOverdue = item.due_date && !done && item.due_date < today
  const catColor = item.category ? katColor(item.category, categories) : null

  return (
    <div key={item.id}
      onClick={() => onSelect(isSelected ? null : item.id)}
      style={{
        display: 'flex', alignItems: 'center', gap: 10, padding: '10px 20px',
        borderBottom: '1px solid var(--border-faint)', cursor: 'pointer',
        background: isSelected ? 'var(--bg-hover)' : 'transparent',
        transition: 'background 0.1s',
      }}
      onMouseEnter={e => { if (!isSelected) e.currentTarget.style.background = 'rgba(0,0,0,0.02)' }}
      onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = 'transparent' }}>

      {/* Checkbox */}
      <button onClick={e => { e.stopPropagation(); if (canEdit) onToggle(item.id, !done) }}
        style={{ background: 'none', border: 'none', cursor: canEdit ? 'pointer' : 'default', padding: 0, flexShrink: 0,
          color: done ? '#22c55e' : 'var(--border-primary)' }}>
        {done ? <CheckSquare size={18} /> : <Square size={18} />}
      </button>

      {/* Content */}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontSize: 14, color: done ? 'var(--text-faint)' : 'var(--text-primary)',
          textDecoration: done ? 'line-through' : 'none', lineHeight: 1.4,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
        }}>
          {item.name}
        </div>
        {/* Description preview */}
        {item.description && (
          <div style={{ fontSize: 12, color: 'var(--text-faint)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', lineHeight: 1.4 }}>
            {item.description}
          </div>
        )}
        {/* Inline badges */}
        {(item.priority || item.due_date || catColor || assignedUser) && (
        <div style={{ display: 'flex', gap: 5, marginTop: 5, flexWrap: 'wrap' }}>
          {item.priority > 0 && PRIO_CONFIG[item.priority] && (
            <span style={{
              fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 3,
              padding: '2px 7px', borderRadius: 5, fontWeight: 600,
              color: PRIO_CONFIG[item.priority].color,
              background: `${PRIO_CONFIG[item.priority].color}10`,
              border: `1px solid ${PRIO_CONFIG[item.priority].color}25`,
            }}>
              <Flag size={9} />{PRIO_CONFIG[item.priority].label}
            </span>
          )}
          {item.due_date && (
            <span style={{
              fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 3,
              padding: '2px 7px', borderRadius: 5, fontWeight: 500,
              color: isOverdue ? '#ef4444' : 'var(--text-secondary)',
              background: isOverdue ? 'rgba(239,68,68,0.08)' : 'var(--bg-hover)',
              border: `1px solid ${isOverdue ? 'rgba(239,68,68,0.15)' : 'var(--border-faint)'}`,
            }}>
              <Calendar size={9} />{formatDate(item.due_date)}
            </span>
          )}
          {catColor && (
            <span style={{
              fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 4,
              padding: '2px 7px', borderRadius: 5, fontWeight: 500,
              color: 'var(--text-secondary)', background: 'var(--bg-hover)',
              border: '1px solid var(--border-faint)',
            }}>
              <span style={{ width: 6, height: 6, borderRadius: '50%', background: catColor, flexShrink: 0 }} />
              {item.category}
            </span>
          )}
          {assignedUser && (
            <span style={{
              fontSize: 10, display: 'inline-flex', alignItems: 'center', gap: 4,
              padding: '2px 7px', borderRadius: 5, fontWeight: 500,
              color: 'var(--text-secondary)', background: 'var(--bg-hover)',
              border: '1px solid var(--border-faint)',
            }}>
              {assignedUser.avatar ? (
                <img src={`/uploads/avatars/${assignedUser.avatar}`} style={{ width: 13, height: 13, borderRadius: '50%', objectFit: 'cover' }} alt="" />
              ) : (
                <span style={{ width: 13, height: 13, borderRadius: '50%', background: 'var(--border-primary)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, color: 'var(--text-faint)', fontWeight: 700 }}>
                  {assignedUser.username.charAt(0).toUpperCase()}
                </span>
              )}
              {assignedUser.username}
            </span>
          )}
        </div>
        )}
      </div>

      {/* Chevron */}
      <ChevronRight size={16} color="var(--text-faint)" style={{ flexShrink: 0, opacity: 0.4 }} />
    </div>
  )
}