/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useMemo } from 'react'; import { User, Booking } from '../types'; import { Users, Search, Mail, Calendar, Activity } from 'lucide-react'; interface UserDirectoryProps { users: User[]; currentUser: User; bookings: Booking[]; } // Deterministic accent so a given user always renders the same colour. const AVATAR_COLORS = [ 'from-emerald-500 to-teal-600', 'from-cyan-500 to-blue-600', 'from-indigo-500 to-violet-600', 'from-amber-500 to-orange-600', 'from-rose-500 to-pink-600', 'from-fuchsia-500 to-purple-600', ]; function colorFor(id: string): string { let hash = 0; for (let i = 0; i < id.length; i++) hash = (hash * 31 + id.charCodeAt(i)) >>> 0; return AVATAR_COLORS[hash % AVATAR_COLORS.length]; } function initials(name: string): string { const parts = name.trim().split(/\s+/); if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase(); return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); } export default function UserDirectory({ users, currentUser, bookings }: UserDirectoryProps) { const [search, setSearch] = useState(''); const bookingCount = useMemo(() => { const map = new Map(); bookings.forEach(b => map.set(b.userId, (map.get(b.userId) ?? 0) + 1)); return map; }, [bookings]); const activeCount = useMemo(() => { const map = new Map(); bookings .filter(b => b.status === 'active' || b.status === 'upcoming') .forEach(b => map.set(b.userId, (map.get(b.userId) ?? 0) + 1)); return map; }, [bookings]); const filtered = useMemo(() => { const q = search.toLowerCase(); const sorted = [...users].sort((a, b) => a.name.localeCompare(b.name)); if (!q) return sorted; return sorted.filter(u => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q) || u.role.toLowerCase().includes(q) ); }, [users, search]); return (
{/* Header banner */}
TEAM

Registered Operators

Everyone with an account on this box. booking counts come straight from the shared reservation pool - no shadow IT here.

{users.length} registered {bookings.length} total bookings {bookings.filter(b => b.status === 'active' || b.status === 'upcoming').length} active / upcoming
{/* Search */}
setSearch(e.target.value)} className="w-full bg-[#1E293B] text-slate-100 border border-slate-800 rounded-xl pl-9 pr-4 py-2 text-xs focus:outline-none focus:border-emerald-600" />
{/* User grid */} {filtered.length === 0 ? (

No operators match your search.

) : (
{filtered.map(user => { const isMe = user.id === currentUser.id; const total = bookingCount.get(user.id) ?? 0; const active = activeCount.get(user.id) ?? 0; return (
{isMe && ( You )}
{initials(user.name)}

{user.name}

{user.email}
Operator
{total} {active}
); })}
)}
); }