Spaces:
Running
Running
| "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> | |
| ); | |
| } | |