import { useState, useMemo } from “react”; const COLORS = [’#e8c84a’,’#4a9ee8’,’#e84a7a’,’#4ae89a’,’#e87a4a’,’#ae4ae8’,’#4ae8d4’,’#e8ae4a’]; function uid() { return Math.random().toString(36).substr(2, 9); } function getColor(name) { let h = 0; for (let c of (name||’?’)) h = (h * 31 + c.charCodeAt(0)) % COLORS.length; return COLORS[h]; } function initials(name) { return (name||’?’).split(’ ‘).filter(Boolean).slice(0,2).map(w=>w[0].toUpperCase()).join(’’); } const DEMO_M1=uid(), DEMO_M2=uid(), DEMO_M3=uid(); const DEMO_P1=uid(), DEMO_P2=uid(); const DEMO_T1=uid(), DEMO_T2=uid(), DEMO_T3=uid(), DEMO_T4=uid(); const INIT = { members: [ {id:DEMO_M1,name:‘Kovács Péter’,role:‘Fejlesztő’,email:‘peter@ceg.hu’,phone:’+36 30 123 4567’,status:‘active’,note:’’}, {id:DEMO_M2,name:‘Nagy Anna’,role:‘Designer’,email:‘anna@ceg.hu’,phone:’+36 70 234 5678’,status:‘active’,note:’’}, {id:DEMO_M3,name:‘Szabó Gábor’,role:‘Projektmenedzser’,email:‘gabor@ceg.hu’,phone:’’,status:‘active’,note:’’}, ], projects: [ {id:DEMO_P1,name:‘Weboldal újratervezés’,client:‘ABC Kft.’,owner:DEMO_M3,deadline:‘2025-06-30’,status:‘doing’,note:’’}, {id:DEMO_P2,name:‘Mobilapp fejlesztés’,client:‘XYZ Zrt.’,owner:DEMO_M1,deadline:‘2025-09-15’,status:‘todo’,note:’’}, ], tasks: [ {id:DEMO_T1,name:‘Főoldal dizájn’,project:DEMO_P1,owner:DEMO_M2,priority:‘high’,deadline:‘2025-04-10’,status:‘doing’,desc:‘Wireframe és végleges dizájn’}, {id:DEMO_T2,name:‘Backend API’,project:DEMO_P1,owner:DEMO_M1,priority:‘high’,deadline:‘2025-04-20’,status:‘todo’,desc:‘REST API végpontok’}, {id:DEMO_T3,name:‘Projekt kickoff’,project:DEMO_P2,owner:DEMO_M3,priority:‘med’,deadline:‘2025-04-05’,status:‘done’,desc:‘Kickoff meeting’}, {id:DEMO_T4,name:‘UX kutatás’,project:DEMO_P2,owner:DEMO_M2,priority:‘low’,deadline:‘2025-05-01’,status:‘todo’,desc:’’}, ], work: [ {id:uid(),date:‘2025-03-20’,member:DEMO_M1,project:DEMO_P1,task:DEMO_T2,hours:4,desc:‘Adatbázis séma tervezése’}, {id:uid(),date:‘2025-03-20’,member:DEMO_M2,project:DEMO_P1,task:DEMO_T1,hours:6,desc:‘Főoldal wireframe és moodboard’}, {id:uid(),date:‘2025-03-21’,member:DEMO_M3,project:DEMO_P2,task:DEMO_T3,hours:2,desc:‘Kickoff meeting levezetése’}, {id:uid(),date:‘2025-03-21’,member:DEMO_M1,project:DEMO_P1,task:DEMO_T2,hours:3,desc:‘Auth végpontok elkészítve’}, {id:uid(),date:‘2025-03-22’,member:DEMO_M2,project:DEMO_P2,task:DEMO_T4,hours:3.5,desc:‘Felhasználói interjúk tervezése’}, ], }; const STATUS_MAP = {todo:{cls:‘todo’,lbl:‘Teendő’},doing:{cls:‘doing’,lbl:‘Folyamatban’},done:{cls:‘done’,lbl:‘Kész’},blocked:{cls:‘blocked’,lbl:‘Blokkolt’}}; const PRIO_MAP = {low:{cls:‘low’,lbl:‘Alacsony’},med:{cls:‘med’,lbl:‘Közepes’},high:{cls:‘high’,lbl:‘Magas’}}; function Avatar({name,size=26}){ const col=getColor(name); return
{initials(name)}
; } function Badge({type,val}){ const map = type===‘status’?STATUS_MAP:PRIO_MAP; const {cls,lbl}=map[val]||{cls:‘todo’,lbl:val}; const colors={todo:[’#6b7394’,’#6b739422’],doing:[’#4a9ee8’,’#4a9ee822’],done:[’#4ae89a’,’#4ae89a22’],blocked:[’#e84a7a’,’#e84a7a22’],low:[’#4ae89a’,’#4ae89a22’],med:[’#e8c84a’,’#e8c84a22’],high:[’#e84a7a’,’#e84a7a22’]}; const [fg,bg]=colors[cls]||[’#6b7394’,’#6b739422’]; return {lbl}; } const EMPTY_MEMBER={name:’’,role:’’,email:’’,phone:’’,status:‘active’,note:’’}; const EMPTY_PROJECT={name:’’,client:’’,owner:’’,deadline:’’,status:‘todo’,note:’’}; const EMPTY_TASK={name:’’,project:’’,owner:’’,priority:‘med’,deadline:’’,status:‘todo’,desc:’’}; const EMPTY_WORK={date:new Date().toISOString().split(‘T’)[0],member:’’,project:’’,task:’’,hours:’’,desc:’’}; function Modal({title,onClose,onSave,children}){ return (
{if(e.target===e.currentTarget)onClose()}} style={{position:‘fixed’,inset:0,background:‘rgba(0,0,0,0.75)’,backdropFilter:‘blur(4px)’,zIndex:200,display:‘flex’,alignItems:‘center’,justifyContent:‘center’,padding:20}}>
{title}
{children}
); } const inp = {background:’#1a1e2a’,border:‘1px solid #252a38’,borderRadius:3,color:’#e8eaf0’,fontFamily:‘monospace’,fontSize:12,padding:‘9px 12px’,width:‘100%’,outline:‘none’,boxSizing:‘border-box’}; const lbl = {fontSize:10,letterSpacing:‘1.5px’,textTransform:‘uppercase’,color:’#6b7394’,display:‘block’,marginBottom:4}; const primaryBtn = {background:’#e8c84a’,color:’#0d0f14’,border:‘none’,borderRadius:3,padding:‘9px 18px’,fontFamily:‘monospace’,fontSize:11,letterSpacing:1,textTransform:‘uppercase’,cursor:‘pointer’,fontWeight:500}; const ghostBtn = {background:’#13161e’,color:’#6b7394’,border:‘1px solid #252a38’,borderRadius:3,padding:‘9px 18px’,fontFamily:‘monospace’,fontSize:11,letterSpacing:1,textTransform:‘uppercase’,cursor:‘pointer’}; const iconBtn = (danger=false) => ({background:‘none’,border:`1px solid ${danger?'#e84a7a':'#252a38'}`,borderRadius:3,color:danger?’#e84a7a’:’#6b7394’,width:28,height:28,cursor:‘pointer’,display:‘flex’,alignItems:‘center’,justifyContent:‘center’,fontSize:13}); function FG({label,full,children}){ return
{children}
; } export default function App() { const [members, setMembers] = useState(INIT.members); const [projects, setProjects] = useState(INIT.projects); const [tasks, setTasks] = useState(INIT.tasks); const [work, setWork] = useState(INIT.work); const [tab, setTab] = useState(‘dashboard’); const [modal, setModal] = useState(null); // {type, data, editId} const [taskFilter, setTaskFilter] = useState(‘all’); const [search, setSearch] = useState({members:’’,projects:’’,tasks:’’,work:’’}); const memberName = id => members.find(m=>m.id===id)?.name||’—’; const projectName = id => projects.find(p=>p.id===id)?.name||’—’; const taskName = id => tasks.find(t=>t.id===id)?.name||’—’; const memberHours = id => work.filter(w=>w.member===id).reduce((s,w)=>s+Number(w.hours),0); const memberTaskCount = id => tasks.filter(t=>t.owner===id).length; const projectHours = id => work.filter(w=>w.project===id).reduce((s,w)=>s+Number(w.hours),0); const projectTaskCount = id => tasks.filter(t=>t.project===id).length; const maxH = useMemo(()=>Math.max(…members.map(m=>memberHours(m.id)),1),[members,work]); const totalHours = work.reduce((s,w)=>s+Number(w.hours),0); function openModal(type, editId=null) { const defaults = {member:EMPTY_MEMBER,project:EMPTY_PROJECT,task:EMPTY_TASK,work:EMPTY_WORK}; let data; if(editId){ if(type===‘member’) data={…members.find(m=>m.id===editId)}; if(type===‘project’) data={…projects.find(p=>p.id===editId)}; if(type===‘task’) data={…tasks.find(t=>t.id===editId)}; if(type===‘work’) data={…work.find(w=>w.id===editId)}; } else { data={…defaults[type]}; } setModal({type,data,editId}); } function saveModal() { const {type,data,editId}=modal; const obj={…data,id:editId||uid()}; if(type===‘member’){ if(!obj.name||!obj.role){alert(‘Név és szerepkör kötelező!’);return;} setMembers(p=>editId?p.map(x=>x.id===editId?obj:x):[…p,obj]); } if(type===‘project’){ if(!obj.name){alert(‘Projekt neve kötelező!’);return;} setProjects(p=>editId?p.map(x=>x.id===editId?obj:x):[…p,obj]); } if(type===‘task’){ if(!obj.name){alert(‘Feladat neve kötelező!’);return;} setTasks(p=>editId?p.map(x=>x.id===editId?obj:x):[…p,obj]); } if(type===‘work’){ if(!obj.date||!obj.member||!obj.hours){alert(‘Dátum, csapattag és óra kötelező!’);return;} setWork(p=>editId?p.map(x=>x.id===editId?obj:x):[…p,obj]); } setModal(null); } function del(type,id){ if(!confirm(‘Biztosan törlöd?’))return; if(type===‘member’) setMembers(p=>p.filter(x=>x.id!==id)); if(type===‘project’) setProjects(p=>p.filter(x=>x.id!==id)); if(type===‘task’) setTasks(p=>p.filter(x=>x.id!==id)); if(type===‘work’) setWork(p=>p.filter(x=>x.id!==id)); } function upd(field,val){ setModal(m=>({…m,data:{…m.data,[field]:val}})); } const TABS=[{id:‘dashboard’,lbl:‘📊 Áttekintés’},{id:‘members’,lbl:‘👥 Csapattagok’},{id:‘projects’,lbl:‘📁 Projektek’},{id:‘tasks’,lbl:‘✅ Feladatok’},{id:‘worklog’,lbl:‘⏱ Munkaidő’}]; const s={ wrap:{background:’#0d0f14’,minHeight:‘100vh’,color:’#e8eaf0’,fontFamily:‘monospace’,position:‘relative’}, grid:{position:‘fixed’,inset:0,backgroundImage:‘linear-gradient(rgba(232,200,74,0.03) 1px,transparent 1px),linear-gradient(90deg,rgba(232,200,74,0.03) 1px,transparent 1px)’,backgroundSize:‘40px 40px’,pointerEvents:‘none’}, inner:{position:‘relative’,zIndex:1,maxWidth:1300,margin:‘0 auto’,padding:‘0 20px 60px’}, header:{display:‘flex’,alignItems:‘flex-end’,justifyContent:‘space-between’,padding:‘28px 0 22px’,borderBottom:‘1px solid #252a38’,marginBottom:24,gap:16,flexWrap:‘wrap’}, h1:{fontFamily:‘sans-serif’,fontWeight:800,fontSize:28,letterSpacing:-1,color:’#e8eaf0’,lineHeight:1}, accent:{color:’#e8c84a’}, sub:{fontSize:10,color:’#6b7394’,letterSpacing:2,textTransform:‘uppercase’,marginTop:5}, pill:{background:’#13161e’,border:‘1px solid #252a38’,borderRadius:4,padding:‘8px 16px’,textAlign:‘center’}, pillVal:{fontFamily:‘sans-serif’,fontSize:20,fontWeight:700,color:’#e8c84a’}, pillLbl:{fontSize:9,color:’#6b7394’,textTransform:‘uppercase’,letterSpacing:1.5,marginTop:2}, tabs:{display:‘flex’,gap:2,borderBottom:‘1px solid #252a38’,marginBottom:24}, tabBtn:(active)=>({background:‘none’,border:‘none’,color:active?’#e8c84a’:’#6b7394’,fontFamily:‘monospace’,fontSize:11,letterSpacing:1.5,textTransform:‘uppercase’,padding:‘11px 18px’,cursor:‘pointer’,borderBottom:active?‘2px solid #e8c84a’:‘2px solid transparent’,transition:‘color .2s’}), tblWrap:{overflowX:‘auto’,border:‘1px solid #252a38’,borderRadius:6}, th:{background:’#1a1e2a’,color:’#6b7394’,fontSize:9,letterSpacing:1.5,textTransform:‘uppercase’,padding:‘11px 14px’,textAlign:‘left’,borderBottom:‘1px solid #252a38’,whiteSpace:‘nowrap’}, td:{padding:‘11px 14px’,verticalAlign:‘middle’,borderBottom:‘1px solid #252a38’,fontSize:12}, toolbar:{display:‘flex’,gap:10,marginBottom:16,alignItems:‘center’,flexWrap:‘wrap’}, searchBox:{background:’#13161e’,border:‘1px solid #252a38’,borderRadius:3,color:’#e8eaf0’,fontFamily:‘monospace’,fontSize:12,padding:‘8px 12px’,width:200,outline:‘none’,marginLeft:‘auto’}, empty:{textAlign:‘center’,padding:‘40px 20px’,color:’#3d4460’,fontSize:12,letterSpacing:1}, dashGrid:{display:‘grid’,gridTemplateColumns:‘repeat(auto-fit,minmax(180px,1fr))’,gap:14,marginBottom:24}, dashCard:(color)=>({background:’#13161e’,border:‘1px solid #252a38’,borderRadius:6,padding:‘18px 20px’,borderLeft:`3px solid ${color}`}), cardVal:(color)=>({fontFamily:‘sans-serif’,fontSize:34,fontWeight:800,color,lineHeight:1,marginBottom:3}), cardLbl:{fontSize:9,color:’#6b7394’,textTransform:‘uppercase’,letterSpacing:1.5,marginBottom:6}, cardSub:{fontSize:10,color:’#6b7394’}, filterRow:{display:‘flex’,gap:8,marginBottom:12,flexWrap:‘wrap’}, chip:(active)=>({background:active?’#e8c84a’:’#13161e’,border:`1px solid ${active?'#e8c84a':'#252a38'}`,borderRadius:20,fontFamily:‘monospace’,fontSize:10,letterSpacing:1,textTransform:‘uppercase’,color:active?’#0d0f14’:’#6b7394’,padding:‘5px 14px’,cursor:‘pointer’}), }; // MEMBER MODAL function MemberModal(){ const d=modal.data; return setModal(null)} onSave={saveModal}>
upd(‘name’,e.target.value)} placeholder=“pl. Kovács Péter”/> upd(‘role’,e.target.value)} placeholder=“pl. Fejlesztő”/> upd(‘email’,e.target.value)} placeholder=“email@ceg.hu”/> upd(‘phone’,e.target.value)} placeholder=”+36 30 …”/> upd(‘note’,e.target.value)} placeholder=“Opcionális”/>
; } function ProjectModal(){ const d=modal.data; return setModal(null)} onSave={saveModal}>
upd(‘name’,e.target.value)} placeholder=“pl. Weboldal fejlesztés”/> upd(‘client’,e.target.value)} placeholder=“pl. ABC Kft.”/> upd(‘deadline’,e.target.value)}/> upd(‘note’,e.target.value)} placeholder=“Opcionális”/>
; } function TaskModal(){ const d=modal.data; return setModal(null)} onSave={saveModal}>
upd(‘name’,e.target.value)} placeholder=“pl. Főoldal design”/> upd(‘deadline’,e.target.value)}/>