// Team panel: project members, workload, recent activity const TEAMIcons = window.Icons; function TeamView({ state, currentUser, onOpenMembers }) { const [membersMap, setMembersMap] = React.useState({}); const [loading, setLoading] = React.useState(false); const [loadedKey, setLoadedKey] = React.useState(''); const projects = state.projects || []; const tasks = state.tasks || []; const projById = React.useMemo(() => Object.fromEntries(projects.map(p => [p.id, p])), [projects]); // Tüm kendi (shared olmayan, arşivlenmemiş) projeler — memberCount'a bakma, // her zaman API'den sorgula ki accept sonrası sayfa güncel kalsın const sharedProjects = React.useMemo( () => projects.filter(p => !p._shared && !p.archived), [projects] ); const sharedKey = sharedProjects.map(p => p.id).join(','); React.useEffect(() => { if (!sharedProjects.length || sharedKey === loadedKey) return; setLoading(true); Promise.all( sharedProjects.map(p => fetch(`./api/invite.php?action=members&projectKey=${encodeURIComponent(p.id)}`, { credentials: 'include' }) .then(r => r.json()) .then(d => [p.id, d.ok ? { members: d.members || [], pending: d.pending || [] } : { members: [], pending: [] }]) .catch(() => [p.id, { members: [], pending: [] }]) ) ).then(results => { setMembersMap(Object.fromEntries(results)); setLoadedKey(sharedKey); setLoading(false); }); }, [sharedKey]); // Unique members aggregated across all shared projects const allMembers = React.useMemo(() => { const list = []; const seen = new Set(); Object.entries(membersMap).forEach(([projectId, d]) => { (d.members || []).forEach(m => { if (seen.has(m.id)) return; seen.add(m.id); const memberProjectIds = sharedProjects .filter(p => (membersMap[p.id]?.members || []).some(mm => mm.id === m.id)) .map(p => p.id); list.push({ ...m, projectIds: memberProjectIds, activeCount: tasks.filter(t => memberProjectIds.includes(t.projectId) && t.status !== 'done').length, doneCount: tasks.filter(t => memberProjectIds.includes(t.projectId) && t.status === 'done').length, }); }); }); return list; }, [membersMap, tasks, sharedProjects]); // Recent activity — tasks updated in last 7 days const recentActivity = React.useMemo(() => { const cut = new Date(); cut.setDate(cut.getDate() - 7); return [...tasks] .filter(t => t.updatedAt && new Date(t.updatedAt) > cut) .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)) .slice(0, 8); }, [tasks]); const ownerActiveCount = tasks.filter(t => t.status !== 'done').length; const ownerDoneCount = tasks.filter(t => t.status === 'done').length; // API yüklendikten sonra hiç üye yoksa boş durum göster const hasAnyMembers = allMembers.length > 0 || Object.values(membersMap).some(d => (d.pending || []).length > 0); if (!loading && sharedKey === loadedKey && !hasAnyMembers) { return (

Henüz takım üyeniz yok

Projenizi ekibinizle paylaşarak birlikte çalışmaya başlayın.

Kenar çubuğunda proje üzerine gelince 👥 simgesine tıklayarak davet oluşturabilirsiniz.

); } // Yükleniyor if (loading || sharedKey !== loadedKey) { return (
Ekip bilgileri yükleniyor...
); } // ── Main content ───────────────────────────────────────── return (

Ekip

{allMembers.length} üye · {sharedProjects.filter(p => (membersMap[p.id]?.members?.length || 0) + (membersMap[p.id]?.pending?.length || 0) > 0).length} paylaşılan proje
{/* ── Member list ── */}
Üyeler
{/* Owner row */} Sahip} activeCount={ownerActiveCount} doneCount={ownerDoneCount} /> {loading &&
Yükleniyor...
} {!loading && allMembers.length === 0 && (
Henüz kabul edilmiş üye yok. Davet bağlantısını paylaşın.
)} {!loading && allMembers.map(m => ( {m.role === 'editor' ? 'Editör' : 'Görüntüleyici'} } activeCount={m.activeCount} doneCount={m.doneCount} projects={m.projectIds.map(pid => projById[pid]).filter(Boolean)} /> ))}
{/* ── Shared projects — sadece gerçek üyesi/bekleyeni olanlar ── */}
Paylaşılan Projeler
{sharedProjects.filter(p => { const mc = membersMap[p.id]?.members?.length || 0; const pc = membersMap[p.id]?.pending?.length || 0; return mc > 0 || pc > 0; }).map(p => { const memberCount = membersMap[p.id]?.members?.length || 0; const pending = membersMap[p.id]?.pending?.length || 0; const pTasks = tasks.filter(t => t.projectId === p.id); const pDone = pTasks.filter(t => t.status === 'done').length; const pct = pTasks.length ? Math.round(pDone / pTasks.length * 100) : 0; return (
{(p.name || '?').charAt(0).toUpperCase()}
{p.name}
{memberCount} üye {pending > 0 ? `· ${pending} bekleyen` : ''}
{pct}%
); })} {sharedProjects.every(p => !(membersMap[p.id]?.members?.length || membersMap[p.id]?.pending?.length)) && (
Henüz paylaşılan proje yok
)}
{/* ── Workload chart ── */} {allMembers.length > 0 && (
İş Yükü Dağılımı
{/* Owner row */} m.activeCount), 1)} /> {allMembers.map(m => ( mm.activeCount), 1)} /> ))}
)} {/* ── Recent activity ── */}
Son Aktiviteler
{recentActivity.length === 0 ? (
Bu hafta güncelleme yok
) : (
{recentActivity.map(t => { const proj = projById[t.projectId]; const statusLabels = { todo: 'Yapılacak', doing: 'Devam Ediyor', review: 'İncelemede', done: 'Tamamlandı' }; const dateStr = t.updatedAt ? new Date(t.updatedAt).toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' }) : ''; return (
{t.title}
{proj?.name} · {statusLabels[t.status] || t.status}
{dateStr}
); })}
)}
); } function MemberRow({ name, email, avatarUrl, roleBadge, activeCount, doneCount, projects }) { const initials = window.projectAbbr ? window.projectAbbr(name || 'U') : (name || 'U').slice(0, 2).toUpperCase(); return (
{avatarUrl ? :
{initials}
}
{name} {roleBadge}
{email}
{projects && projects.length > 0 && (
{projects.map(p => ( {p.name} ))}
)}
{activeCount} Aktif
{doneCount} Bitti
); } function WorkloadRow({ name, active, done, maxActive }) { const pct = maxActive > 0 ? Math.round((active / maxActive) * 100) : 0; const tone = pct > 80 ? 'busy' : pct > 50 ? 'balanced' : 'light'; return (
{name}
{active} aktif · {done} bitti
); } window.TeamView = TeamView;