diff --git a/src/app/admin/matrix-management/detail/page.tsx b/src/app/admin/matrix-management/detail/page.tsx new file mode 100644 index 0000000..29627da --- /dev/null +++ b/src/app/admin/matrix-management/detail/page.tsx @@ -0,0 +1,307 @@ +'use client' + +import React, { useEffect, useMemo, useState } from 'react' +import { useSearchParams, useRouter } from 'next/navigation' +import PageLayout from '../../../components/PageLayout' +import { ArrowLeftIcon, MagnifyingGlassIcon, PlusIcon, UserIcon, BuildingOffice2Icon } from '@heroicons/react/24/outline' + +type UserType = 'personal' | 'company' +type MatrixUser = { + id: string | number + name: string + email: string + type: UserType + level: number // 0 for top-node, then 1..N +} + +const LEVEL_CAP = (level: number) => Math.pow(5, level) // L1=5, L2=25, ... + +// Dummy users to pick from in the modal +const DUMMY_POOL: Array> = [ + { id: 101, name: 'Alice Johnson', email: 'alice@example.com', type: 'personal' }, + { id: 102, name: 'Beta GmbH', email: 'office@beta-gmbh.de', type: 'company' }, + { id: 103, name: 'Carlos Diaz', email: 'carlos@sample.io', type: 'personal' }, + { id: 104, name: 'Delta Solutions AG', email: 'contact@delta.ag', type: 'company' }, + { id: 105, name: 'Emily Nguyen', email: 'emily.ng@ex.com', type: 'personal' }, + { id: 106, name: 'Foxtrot LLC', email: 'hello@foxtrot.llc', type: 'company' }, + { id: 107, name: 'Grace Blake', email: 'grace@ex.com', type: 'personal' }, + { id: 108, name: 'Hestia Corp', email: 'hq@hestia.io', type: 'company' }, + { id: 109, name: 'Ivan Petrov', email: 'ivan@ex.com', type: 'personal' }, + { id: 110, name: 'Juno Partners', email: 'team@juno.partners', type: 'company' }, +] + +export default function MatrixDetailPage() { + const sp = useSearchParams() + const router = useRouter() + + const matrixId = sp.get('id') || 'm-1' + const matrixName = sp.get('name') || 'Unnamed Matrix' + const topNodeEmail = sp.get('top') || 'top@example.com' + + // Build initial dummy matrix users + const [users, setUsers] = useState(() => { + // Level 0 = top node from URL + const initial: MatrixUser[] = [ + { id: 'top', name: 'Top Node', email: topNodeEmail, type: 'personal', level: 0 }, + ] + // Fill some demo users across levels + const seed: Omit[] = DUMMY_POOL.slice(0, 12) + let remaining = [...seed] + let level = 1 + while (remaining.length > 0 && level <= 3) { + const cap = LEVEL_CAP(level) + const take = Math.min(remaining.length, Math.max(1, Math.min(cap, 6))) // keep demo compact + initial.push(...remaining.splice(0, take).map(u => ({ ...u, level }))) + level++ + } + return initial + }) + + // Modal state + const [open, setOpen] = useState(false) + const [query, setQuery] = useState('') + const [typeFilter, setTypeFilter] = useState<'all' | UserType>('all') + + // Available candidates = pool minus already added ids + const availableUsers = useMemo(() => { + const picked = new Set(users.map(u => String(u.id))) + return DUMMY_POOL.filter(u => !picked.has(String(u.id))) + }, [users]) + + const filteredCandidates = useMemo(() => { + const q = query.trim().toLowerCase() + return availableUsers.filter(u => { + if (typeFilter !== 'all' && u.type !== typeFilter) return false + if (!q) return true + return u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q) + }) + }, [availableUsers, query, typeFilter]) + + // Counts per level and next available level logic + const byLevel = useMemo(() => { + const map = new Map() + users.forEach(u => { + const arr = map.get(u.level) || [] + arr.push(u) + map.set(u.level, arr) + }) + return map + }, [users]) + + const nextAvailableLevel = () => { + // Start from level 1 upwards; find first with space; else go to next new level + let lvl = 1 + while (true) { + const current = byLevel.get(lvl)?.length || 0 + if (current < LEVEL_CAP(lvl)) return lvl + lvl += 1 + if (lvl > 8) return lvl // safety ceiling in demo + } + } + + const addToMatrix = (u: Omit) => { + setUsers(prev => [...prev, { ...u, level: nextAvailableLevel() }]) + } + + // Simple chip for user + const UserChip = ({ u }: { u: MatrixUser }) => ( +
+ {u.type === 'company' ? : } + {u.name} +
+ ) + + // Compact grid for a level + const LevelSection = ({ level }: { level: number }) => { + const cap = level === 0 ? 1 : LEVEL_CAP(level) + const list = byLevel.get(level) || [] + const pct = Math.min(100, Math.round((list.length / cap) * 100)) + const title = + level === 0 ? 'Level 0 (Top node)' : `Level ${level} — ${list.length} / ${cap}` + + return ( +
+
+

{title}

+ {pct}% +
+ + {/* Progress */} +
+
+
+
+
+ + {/* Users */} +
+ {list.length === 0 ? ( +
No users in this level yet.
+ ) : ( +
+ {list.slice(0, 30).map(u => )} + {list.length > 30 && ( + + +{list.length - 30} more + + )} +
+ )} +
+
+ ) + } + + return ( + +
+
+ {/* Header / Overview */} +
+
+ +

{matrixName}

+

+ Top node: {topNodeEmail} +

+
+ + +
+ + {/* Levels display */} +
+ + + + + {/* Add more levels visually if needed */} +
+ + {/* Full users list by level */} +
+
+

All users in this matrix

+

Grouped by levels (power of five structure).

+
+
+ {[...byLevel.keys()].sort((a, b) => a - b).map(lvl => { + const list = byLevel.get(lvl) || [] + return ( +
+
+ {lvl === 0 ? 'Level 0 (Top node)' : `Level ${lvl}`} • {list.length} user(s) +
+
+ {list.map(u => ( +
+
+
{u.name}
+ + {u.type === 'company' ? 'Company' : 'Personal'} + +
+
{u.email}
+
+ ))} +
+
+ ) + })} +
+
+
+
+ + {/* Add Users Modal */} + {open && ( +
+
setOpen(false)} /> +
+
+
+

Add users to “{matrixName}”

+ +
+ +
+
+ + { setQuery(e.target.value) }} + placeholder="Search name or email…" + className="w-full rounded-md border border-gray-300 pl-8 pr-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-gray-900 placeholder:text-gray-700 placeholder:opacity-100" + /> +
+ +
+ Candidates: {filteredCandidates.length} +
+
+ +
+ {filteredCandidates.length === 0 ? ( +
No users match your filters.
+ ) : ( +
    + {filteredCandidates.map(u => ( +
  • +
    +
    {u.name}
    +
    {u.email}
    +
    +
    + + {u.type === 'company' ? 'Company' : 'Personal'} + + +
    +
  • + ))} +
+ )} +
+ +
+ +
+
+
+
+ )} + + ) +} diff --git a/src/app/admin/matrix-management/page.tsx b/src/app/admin/matrix-management/page.tsx index b8c9163..5f0ed61 100644 --- a/src/app/admin/matrix-management/page.tsx +++ b/src/app/admin/matrix-management/page.tsx @@ -316,7 +316,14 @@ export default function MatrixManagementPage() {