// 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;