// Table View — Tablo Görünümü const TVIcons = window.Icons; const TVStatusPill = window.UI.StatusPill; const TVPriority = window.UI.Priority; const TVTag = window.UI.Tag; const TV_STATUS_META = window.UI.STATUS_META; const TV_PRIO_META = window.UI.PRIORITY_META; const { isOverdue: tvOverdue } = window.PMStore; function fmtMinsTv(mins) { if (!mins) return null; const h = Math.floor(mins / 60), m = mins % 60; return m > 0 ? `${h}s ${m}dk` : `${h}s`; } function fmtDateTv(dateStr) { if (!dateStr) return null; const d = new Date(dateStr + 'T00:00:00'); return d.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }); } const TV_STATUS_ORDER = ['todo', 'doing', 'review', 'done']; const TV_PRIO_ORDER = ['high', 'medium', 'low']; function TableView({ state, onSelectTask, onUpdateTask, onAddTask }) { const [sortBy, setSortBy] = React.useState('due'); const [sortDir, setSortDir] = React.useState('asc'); const [filterProj, setFilterProj] = React.useState('all'); const [filterStatus, setFilterStatus] = React.useState('all'); const [selected, setSelected] = React.useState(new Set()); const [search, setSearch] = React.useState(''); const [groupBy, setGroupBy] = React.useState('none'); const [openCell, setOpenCell] = React.useState(null); // {id, field} const projects = state.projects || []; const projById = Object.fromEntries(projects.map(p => [p.id, p])); // ── Filtrele ────────────────────────────────────────────── let tasks = (state.tasks || []).filter(t => !t.archived); if (filterProj !== 'all') tasks = tasks.filter(t => t.projectId === filterProj); if (filterStatus !== 'all') tasks = tasks.filter(t => t.status === filterStatus); if (search.trim()) { const q = search.toLowerCase(); tasks = tasks.filter(t => t.title.toLowerCase().includes(q)); } // ── Sırala ─────────────────────────────────────────────── tasks = [...tasks].sort((a, b) => { let va, vb; if (sortBy === 'title') { va = a.title || ''; vb = b.title || ''; } if (sortBy === 'status') { va = TV_STATUS_ORDER.indexOf(a.status); vb = TV_STATUS_ORDER.indexOf(b.status); } if (sortBy === 'priority') { va = TV_PRIO_ORDER.indexOf(a.priority || 'medium'); vb = TV_PRIO_ORDER.indexOf(b.priority || 'medium'); } if (sortBy === 'due') { va = a.due || '9999'; vb = b.due || '9999'; } if (sortBy === 'firm') { va = projById[a.projectId]?.name || ''; vb = projById[b.projectId]?.name || ''; } if (sortBy === 'time') { va = (a.timeLog||[]).reduce((s,e)=>s+(e.duration||0),0); vb = (b.timeLog||[]).reduce((s,e)=>s+(e.duration||0),0); } if (va == null) va = ''; if (vb == null) vb = ''; const cmp = typeof va === 'string' ? va.localeCompare(vb, 'tr') : va - vb; return sortDir === 'asc' ? cmp : -cmp; }); const handleSort = (col) => { if (sortBy === col) setSortDir(d => d === 'asc' ? 'desc' : 'asc'); else { setSortBy(col); setSortDir('asc'); } }; // ── Seçim ──────────────────────────────────────────────── const toggleSel = (id, e) => { e.stopPropagation(); const s = new Set(selected); s.has(id) ? s.delete(id) : s.add(id); setSelected(s); }; const selAll = () => setSelected( selected.size === tasks.length ? new Set() : new Set(tasks.map(t => t.id)) ); const bulkSetStatus = (status) => { [...selected].forEach(id => onUpdateTask(id, { status })); setSelected(new Set()); }; // ── Gruplama ───────────────────────────────────────────── let groups = []; if (groupBy === 'firm') { const map = {}; tasks.forEach(t => { const key = t.projectId || '__none'; if (!map[key]) map[key] = []; map[key].push(t); }); groups = Object.entries(map).map(([key, ts]) => ({ label: key === '__none' ? 'Firma yok' : (projById[key]?.name || key), color: projById[key]?.color, tasks: ts, })); } else if (groupBy === 'status') { TV_STATUS_ORDER.forEach(sid => { const m = TV_STATUS_META[sid]; const ts = tasks.filter(t => t.status === sid); if (ts.length > 0) groups.push({ label: m?.label || sid, color: m?.fg, tasks: ts }); }); } else { groups = [{ label: null, tasks }]; } const colCount = 7 - (groupBy === 'firm' ? 1 : 0) - (groupBy === 'status' ? 1 : 0); const hasFilters = filterProj !== 'all' || filterStatus !== 'all' || !!search.trim(); // ── Sütun başlığı ───────────────────────────────────────── const SortTH = ({ id, label, style }) => { const active = sortBy === id; return ( handleSort(id)} > {label} {active ? (sortDir === 'asc' ? ' ↑' : ' ↓') : ''} ); }; // ── Satır render ────────────────────────────────────────── const renderRow = (t) => { const proj = projById[t.projectId]; const ov = tvOverdue(t.due, t.status); const timeMins = (t.timeLog||[]).reduce((s,e) => s + (e.duration||0), 0); const taskTags = (t.tags||[]).map(tid => (state.tags||[]).find(x=>x.id===tid)).filter(Boolean); const isSel = selected.has(t.id); const isDone = t.status === 'done'; const isStatusOpen = openCell?.id === t.id && openCell?.field === 'status'; const isPriorityOpen = openCell?.id === t.id && openCell?.field === 'priority'; return ( { setOpenCell(null); onSelectTask(t.id); }} > {/* ─ Checkbox ─ */} toggleSel(t.id, e)}>
{isSel && }
{/* ─ Başlık ─ */} {t.title} {(t.subtasks?.length > 0) && ( {t.subtasks.filter(s=>s.done).length}/{t.subtasks.length} )} {/* ─ Firma ─ */} {groupBy !== 'firm' && ( e.stopPropagation()}> {proj ? ( {proj.name} ) : } )} {/* ─ Durum ─ */} {groupBy !== 'status' && ( { e.stopPropagation(); setOpenCell(isStatusOpen ? null : {id:t.id,field:'status'}); }} >
{isStatusOpen && (
e.stopPropagation()}> {TV_STATUS_ORDER.map(sid => { const m = TV_STATUS_META[sid]; return ( ); })}
)}
)} {/* ─ Öncelik ─ */} { e.stopPropagation(); setOpenCell(isPriorityOpen ? null : {id:t.id,field:'priority'}); }} >
{isPriorityOpen && (
e.stopPropagation()}> {TV_PRIO_ORDER.map(pid => { const m = TV_PRIO_META[pid]; const pColors = { high:'var(--p-high)', medium:'var(--p-med)', low:'var(--p-low)' }; return ( ); })}
)}
{/* ─ Teslim tarihi ─ */} e.stopPropagation()}> {/* ─ Süre ─ */} {timeMins > 0 ? {fmtMinsTv(timeMins)} : } {/* ─ Etiketler ─ */} e.stopPropagation()}>
{taskTags.slice(0, 2).map(tg => )} {taskTags.length > 2 && ( +{taskTags.length - 2} )}
{/* ─ Aç ─ */} { e.stopPropagation(); onSelectTask(t.id); }}> ); }; // ───────────────────────────────────────────────────────── return (
setOpenCell(null)}> {/* ── Araç Çubuğu ── */}
setSearch(e.target.value)} /> {search && ( )}
{hasFilters && ( )}
{tasks.length} görev
{/* ── Toplu işlem çubuğu ── */} {selected.size > 0 && (
{selected.size} görev seçildi
Durum değiştir: {TV_STATUS_ORDER.map(sid => { const m = TV_STATUS_META[sid]; return ( ); })}
)} {/* ── Tablo kartı ── */}
{/* Check-all */} {groupBy !== 'firm' && } {groupBy !== 'status' && } {groups.map((group, gi) => ( {group.label && ( )} {group.tasks.map(t => renderRow(t))} ))} {/* Yeni görev satırı */}
0?' checked':''}`} style={{ width: 16, height: 16 }} onClick={selAll} > {selected.size===tasks.length&&tasks.length>0 && }
Etiket
{group.label} {group.tasks.length}
{/* Boş durum */} {tasks.length === 0 && (

Görev bulunamadı

{hasFilters ? 'Filtreleri değiştir veya temizle' : 'Yeni görev ekleyerek başla'}
)}
); } window.TableView = TableView;