NEXUS / app /org-chart /page.tsx
dimensionalpulsar's picture
Upload 32 files
8e723d7 verified
"use client";
import React, { useState, useEffect } from "react";
import { db } from "@/lib/firebase";
import { collection, onSnapshot } from "firebase/firestore";
import { motion } from "framer-motion";
import { User, ShieldCheck, Briefcase } from "lucide-react";
interface Employee {
id: string;
name: string;
position: string;
department: string;
}
export default function OrgChartPage() {
const [employees, setEmployees] = useState<Employee[]>([]);
useEffect(() => {
const unsub = onSnapshot(collection(db, "employees"), (snap) => {
setEmployees(snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Employee)));
});
return () => unsub();
}, []);
return (
<div className="p-10 min-h-screen">
<header className="mb-20 text-center">
<h1 className="text-5xl font-black tracking-widest uppercase mb-4">Structure / Hierarchy</h1>
<p className="text-gray-500 font-bold">Arquitectura Organizacional en Tiempo Real</p>
</header>
{/* CEO Level */}
<div className="flex flex-col items-center">
<OrgNode label="Dirección General" name="CEO Admin" position="Consejo de Administración" type="ceo" />
<div className="w-px h-16 bg-gradient-to-b from-blue-500 to-transparent my-2"></div>
{/* Departments Level */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-20 relative">
{/* Connecting Lines */}
<div className="hidden md:block absolute top-[2.5rem] left-[15%] right-[15%] h-px bg-white/10"></div>
<OrgSection title="Operaciones" icon={<Briefcase size={20} />} employees={employees} />
<OrgSection title="Tecnología" icon={<ShieldCheck size={20} />} employees={employees} />
<OrgSection title="Comercial" icon={<User size={20} />} employees={employees} />
</div>
</div>
</div>
);
}
function OrgSection({ title, icon, employees }: { title: string, icon: React.ReactNode, employees: Employee[] }) {
return (
<div className="flex flex-col items-center">
<div className="bg-white/10 border border-white/20 px-8 py-4 rounded-full flex items-center gap-3 shadow-xl mb-12">
<div className="text-blue-400">{icon}</div>
<span className="font-black uppercase text-xs tracking-widest">{title}</span>
</div>
<div className="space-y-6 flex flex-col items-center">
{employees.length === 0 ? (
<div className="text-gray-600 text-[10px] uppercase font-bold">Sin asignar</div>
) : (
employees.slice(0, 3).map((emp, i) => (
<motion.div
key={emp.id}
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: i * 0.1 }}
>
<OrgNode name={emp.name} position={emp.position} />
</motion.div>
))
)}
</div>
</div>
);
}
function OrgNode({ label, name, position, type }: { label?: string, name: string, position: string, type?: string }) {
return (
<div className={`
relative group p-6 rounded-[2rem] border min-w-[240px] text-center backdrop-blur-3xl shadow-2xl transition-all
${type === 'ceo' ? 'bg-blue-600/20 border-blue-500/40' : 'bg-white/5 border-white/10 hover:border-white/30 hover:bg-white/10'}
`}>
{label && <span className="absolute -top-3 left-1/2 -translate-x-1/2 px-4 py-1 bg-blue-600 text-[8px] font-black uppercase rounded-full shadow-lg">{label}</span>}
<div className={`w-12 h-12 mx-auto mb-4 rounded-2xl flex items-center justify-center ${type === 'ceo' ? 'bg-blue-500 shadow-blue-500/50 shadow-xl' : 'bg-white/10'}`}>
<User size={24} />
</div>
<h4 className="font-bold text-lg mb-1">{name}</h4>
<p className="text-[10px] font-black text-gray-500 uppercase tracking-widest">{position}</p>
</div>
);
}