'use client' import { useEffect, useMemo, useState } from 'react' import InviteModal from '@/components/team/InviteModal' import EditMemberModal from '@/components/team/EditMemberModal' import PermissionsMatrix from '@/components/team/PermissionsMatrix' import { EMPLOYEE_TOKEN_KEY } from '@/lib/api' import { useTeam, TeamMember, InvitePayload } from '@/hooks/useTeam' import { useDashboardI18n } from '@/components/I18nProvider' function initials(m: TeamMember) { return (m.firstName[0] ?? '') + (m.lastName[0] ?? '') } function timeAgo(dateStr: string | null, language: 'en' | 'fr' | 'ar') { if (!dateStr) { return language === 'fr' ? 'Jamais' : language === 'ar' ? 'أبداً' : 'Never' } const diff = Date.now() - new Date(dateStr).getTime() const mins = Math.floor(diff / 60000) if (mins < 2) { return language === 'fr' ? "À l'instant" : language === 'ar' ? 'الآن' : 'Just now' } if (mins < 60) { return language === 'fr' ? `il y a ${mins} min` : language === 'ar' ? `منذ ${mins} دقيقة` : `${mins}m ago` } const hrs = Math.floor(mins / 60) if (hrs < 24) { return language === 'fr' ? `il y a ${hrs}h` : language === 'ar' ? `منذ ${hrs} ساعة` : `${hrs}h ago` } const days = Math.floor(hrs / 24) if (days < 30) { return language === 'fr' ? `il y a ${days}j` : language === 'ar' ? `منذ ${days} يوم` : `${days}d ago` } const locale = language === 'fr' ? 'fr' : language === 'ar' ? 'ar' : 'en' return new Date(dateStr).toLocaleDateString(locale, { month: 'short', day: 'numeric' }) } const AVATAR_BG: string[] = [ 'bg-violet-100 dark:bg-violet-900/40 text-violet-700 dark:text-violet-300', 'bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300', 'bg-teal-100 dark:bg-teal-900/40 text-teal-700 dark:text-teal-300', 'bg-orange-100 dark:bg-orange-900/40 text-orange-700 dark:text-orange-300', 'bg-pink-100 dark:bg-pink-900/40 text-pink-700 dark:text-pink-300', ] function avatarColor(id: string) { const hash = id.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0) return AVATAR_BG[hash % AVATAR_BG.length] } const roleLabels: Record> = { OWNER: { en: 'Owner', fr: 'Propriétaire', ar: 'مالك' }, MANAGER: { en: 'Manager', fr: 'Manager', ar: 'مدير' }, AGENT: { en: 'Agent', fr: 'Agent', ar: 'وكيل' }, } function RoleBadge({ role, language }: { role: string; language: 'en' | 'fr' | 'ar' }) { const styles: Record = { OWNER: 'bg-violet-50 dark:bg-violet-950/40 text-violet-700 dark:text-violet-300 border-violet-100 dark:border-violet-900/50', MANAGER: 'bg-blue-50 dark:bg-blue-950/40 text-blue-700 dark:text-blue-300 border-blue-100 dark:border-blue-900/50', AGENT: 'bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 border-zinc-200 dark:border-zinc-700', } const label = roleLabels[role]?.[language] ?? (role.charAt(0) + role.slice(1).toLowerCase()) return ( {label} ) } function StatusBadge({ member, language }: { member: TeamMember; language: 'en' | 'fr' | 'ar' }) { if (member.invitationStatus === 'pending') { return ( {language === 'fr' ? 'Invitation en attente' : language === 'ar' ? 'دعوة قيد الانتظار' : 'Pending invite'} ) } if (!member.isActive) { return ( {language === 'fr' ? 'Désactivé' : language === 'ar' ? 'معطل' : 'Deactivated'} ) } return ( {language === 'fr' ? 'Actif' : language === 'ar' ? 'نشط' : 'Active'} ) } export default function TeamPage() { const { language } = useDashboardI18n() const copy = { en: { title: 'Team', subtitle: 'Manage your employees and their access levels', inviteMember: 'Invite member', totalMembers: 'Total members', active: 'Active', pendingInvite: 'Pending invite', deactivated: 'Deactivated', search: 'Search by name or email…', allRoles: 'All roles', owner: 'Owner', manager: 'Manager', agent: 'Agent', allStatuses: 'All statuses', pending: 'Pending', loadingTeam: 'Loading team…', noMembers: 'No members match your filters', member: 'Member', role: 'Role', status: 'Status', lastActive: 'Last active', manage: 'Manage', inviteSent: 'Invite sent to', roleUpdated: 'Role updated', memberDeactivated: 'Member deactivated', memberReactivated: 'Member reactivated', memberRemoved: 'Member removed', you: '(you)', }, fr: { title: 'Équipe', subtitle: "Gérez vos employés et leurs niveaux d'accès", inviteMember: 'Inviter un membre', totalMembers: 'Total membres', active: 'Actif', pendingInvite: 'Invitation en attente', deactivated: 'Désactivé', search: 'Rechercher par nom ou email…', allRoles: 'Tous les rôles', owner: 'Propriétaire', manager: 'Manager', agent: 'Agent', allStatuses: 'Tous les statuts', pending: 'En attente', loadingTeam: "Chargement de l'équipe…", noMembers: 'Aucun membre ne correspond aux filtres', member: 'Membre', role: 'Rôle', status: 'Statut', lastActive: 'Dernière activité', manage: 'Gérer', inviteSent: 'Invitation envoyée à', roleUpdated: 'Rôle mis à jour', memberDeactivated: 'Membre désactivé', memberReactivated: 'Membre réactivé', memberRemoved: 'Membre supprimé', you: '(vous)', }, ar: { title: 'الفريق', subtitle: 'إدارة الموظفين ومستويات الوصول الخاصة بهم', inviteMember: 'دعوة عضو', totalMembers: 'إجمالي الأعضاء', active: 'نشط', pendingInvite: 'دعوة قيد الانتظار', deactivated: 'معطل', search: 'ابحث بالاسم أو البريد الإلكتروني…', allRoles: 'كل الأدوار', owner: 'مالك', manager: 'مدير', agent: 'وكيل', allStatuses: 'كل الحالات', pending: 'قيد الانتظار', loadingTeam: 'جارٍ تحميل الفريق…', noMembers: 'لا يوجد أعضاء مطابقون للفلاتر', member: 'العضو', role: 'الدور', status: 'الحالة', lastActive: 'آخر نشاط', manage: 'إدارة', inviteSent: 'تم إرسال الدعوة إلى', roleUpdated: 'تم تحديث الدور', memberDeactivated: 'تم تعطيل العضو', memberReactivated: 'تمت إعادة تفعيل العضو', memberRemoved: 'تم حذف العضو', you: '(أنت)', }, }[language] const { members, stats, loading, error, invite, updateRole, deactivate, reactivate, remove } = useTeam() const [currentEmployeeId, setCurrentEmployeeId] = useState(null) const [search, setSearch] = useState('') const [roleFilter, setRoleFilter] = useState('') const [statusFilter, setStatusFilter] = useState('') const [inviteOpen, setInviteOpen] = useState(false) const [editTarget, setEditTarget] = useState(null) const [toast, setToast] = useState(null) useEffect(() => { const token = window.localStorage.getItem(EMPLOYEE_TOKEN_KEY) if (!token) return try { const encoded = token.split('.')[1] ?? '' const normalized = encoded.replace(/-/g, '+').replace(/_/g, '/') const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=') const payload = JSON.parse(atob(padded)) setCurrentEmployeeId(typeof payload?.sub === 'string' ? payload.sub : null) } catch { setCurrentEmployeeId(null) } }, []) const currentEmployee = members.find((member) => member.id === currentEmployeeId) const isOwner = currentEmployee?.role === 'OWNER' function showToast(msg: string) { setToast(msg) setTimeout(() => setToast(null), 3000) } const filtered = useMemo(() => { return members.filter((m) => { const q = search.toLowerCase() const matchQ = !q || `${m.firstName} ${m.lastName}`.toLowerCase().includes(q) || m.email.toLowerCase().includes(q) const matchRole = !roleFilter || m.role === roleFilter const matchStatus = !statusFilter || (statusFilter === 'active' && m.isActive && m.invitationStatus === 'accepted') || (statusFilter === 'pending' && m.invitationStatus === 'pending') || (statusFilter === 'inactive' && !m.isActive && m.invitationStatus === 'accepted') return matchQ && matchRole && matchStatus }) }, [members, search, roleFilter, statusFilter]) const handleInvite = async (payload: InvitePayload) => { await invite(payload) showToast(`${copy.inviteSent} ${payload.email}`) } const handleUpdateRole = async (id: string, role: 'MANAGER' | 'AGENT') => { await updateRole(id, role) showToast(copy.roleUpdated) } const handleDeactivate = async (id: string) => { await deactivate(id) showToast(copy.memberDeactivated) } const handleReactivate = async (id: string) => { await reactivate(id) showToast(copy.memberReactivated) } const handleRemove = async (id: string) => { await remove(id) showToast(copy.memberRemoved) } return (

{copy.title}

{copy.subtitle}

{isOwner && ( )}
{[ { label: copy.totalMembers, value: stats.total }, { label: copy.active, value: stats.active }, { label: copy.pendingInvite, value: stats.pending }, { label: copy.deactivated, value: stats.inactive }, ].map((s) => (
{s.label} {s.value}
))}
setSearch(e.target.value)} className="w-full pl-8 pr-3 py-2 text-sm border border-zinc-200 dark:border-zinc-700 rounded-lg bg-white dark:bg-zinc-900 text-zinc-900 dark:text-white placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-zinc-900 dark:focus:ring-white focus:border-transparent" />
{loading ? (
{copy.loadingTeam}
) : error ? (
{error}
) : filtered.length === 0 ? (

{copy.noMembers}

) : ( {filtered.map((member) => ( ))}
{copy.member} {copy.role} {copy.status} {copy.lastActive}
{initials(member)}
{member.firstName} {member.lastName} {member.id === currentEmployeeId && ( {copy.you} )}
{member.email}
{member.invitationStatus === 'pending' ? '—' : timeAgo(member.lastActiveAt, language)} {isOwner && member.role !== 'OWNER' && ( )}
)}
setInviteOpen(false)} onInvite={handleInvite} /> setEditTarget(null)} onUpdateRole={handleUpdateRole} onDeactivate={handleDeactivate} onReactivate={handleReactivate} onRemove={handleRemove} /> {toast && (
{toast}
)}
) }