const { useState, useMemo, useEffect, useRef } = React;
/* ============================================================
ICONS — minimal stroke set, no external deps
============================================================ */
const I = {
dot: (p) => ,
search: (p) => ,
bell: (p) => ,
mail: (p) => ,
cog: (p) => ,
home: (p) => ,
cal: (p) => ,
book: (p) => ,
list: (p) => ,
cash: (p) => ,
user: (p) => ,
mega: (p) => ,
file: (p) => ,
edit: (p) => ,
arrow: (p) => ,
ext: (p) => ,
chev: (p) => ,
pin: (p) => ,
check: (p) => ,
out: (p) => ,
shield: (p) => ,
help: (p) => ,
star: (p) => ,
menu: (p) => ,
x: (p) => ,
};
/* ============================================================
DATA — fully anonymized, generic "School"
============================================================ */
const me = {
name: 'Name Surname',
album: '00000',
programs: ['Computer Science, BEng', 'sem. 6'],
email: 'name.surname@school.edu',
initials: 'NS',
};
const upcoming = [
{
when: 'Day after tomorrow · Thursday, May 28',
blocks: [
{ time: '12:15–14:00', code: 'STAT-DC', name: 'Mathematical Statistics', type: 'lab', room: 'B-204', online: 'Teams', teacher: 'Dr. K. Miller' },
],
},
{
when: 'Friday · May 29',
blocks: [
{ time: '08:00–09:30', code: 'ELEC-DB', name: 'Electronics Laboratory', type: 'lab', room: 'C-117', online: null, teacher: 'J. Sawyer, MSc' },
{ time: '11:45–13:15', code: 'PY-2', name: 'Python — Data Mining', type: 'lab', room: 'A-301', online: 'Teams', teacher: 'Dr. M. Collins' },
],
},
{
when: 'Thursday · June 11',
blocks: [
{ time: '10:15–12:00', code: 'STAT-DC', name: 'Mathematical Statistics', type: 'lecture', room: 'Aula A', online: 'Teams', teacher: 'Prof. P. Lawson' },
{ time: '12:00–13:30', code: 'STAT-DC', name: 'Mathematical Statistics · 2nd midterm (25/26)', type: 'midterm', room: '—', online: 'Inspera', teacher: 'Dr. K. Miller' },
{ time: '12:15–14:00', code: 'STAT-DC', name: 'Mathematical Statistics', type: 'lab', room: 'B-204', online: 'Teams', teacher: 'Dr. K. Miller' },
],
},
{
when: 'Tuesday · June 16',
blocks: [
{ time: '09:00–10:30', code: 'OR-DC', name: 'Operations Research — final test', type: 'midterm', room: 'D-22', online: null, teacher: 'Dr. B. Jenkins' },
],
},
];
const announcements = [
{ course: 'Programming Lab 2 — Python (Data Mining)', subject: 'Running late', body: 'Good morning, my sincere apologies — I will be about 15 minutes late today.', when: 'today · 08:17', author: 'Dr. M. Collins', unread: true },
{ course: 'Programming Lab 2 — Python (Data Mining)', subject: 'Project — summary as requested', body: 'Dear students, for the Python 2 — Data Mining course you are required to prepare a final project as a notebook, e.g. Jupyter Notebook or Google Colab. (…)', when: 'May 24 · 12:22', author: 'Dr. M. Collins', unread: true },
{ course: 'Programming Lab 2 — Python (Data Mining)', subject: 'Action needed — project groups', body: 'Please fill in the group assignment table by May 25. Four people are still missing.', when: 'May 19 · 10:15', author: 'Dr. M. Collins', unread: false },
{ course: 'Operations Research & Logistics', subject: 'Materials for lecture 11', body: 'I have uploaded the presentation and two exercise files to the documents section.', when: 'May 18 · 09:01', author: 'Dr. B. Jenkins', unread: false },
];
const fileGroups = [
{
course: 'Applied Electronics Laboratory',
items: [
{ name: 'Distance-sensor-HC-SR04-1.ino', when: 'today · 17:23', kind: 'ino' },
{ name: 'Motion-sensor-test-PIR-HC-SR501.ino', when: 'today · 17:23', kind: 'ino' },
{ name: 'ELEC-DB-group-Lab-12-surname-name-id.docx', when: 'today · 17:23', kind: 'docx' },
{ name: 'ELEC-DB-Lab-12.pptx', when: 'today · 17:23', kind: 'pptx' },
{ name: 'Programmable-LEDs-Task-1.ino', when: 'May 18 · 14:53', kind: 'ino' },
],
},
{
course: 'Programming Lab 2 — Python (Data Mining)',
items: [
{ name: 'Python_data_mining_LAB_12_2.pptx', when: 'today · 10:22', kind: 'pptx' },
{ name: 'Python_data_mining_LAB_12_1.pdf', when: 'today · 10:21', kind: 'pdf' },
{ name: 'Python_data_mining_LAB_11.pdf', when: 'today · 09:20', kind: 'pdf' },
{ name: '3classificationFinal.py', when: 'May 19 · 09:46', kind: 'py' },
{ name: '2classificationTest.py', when: 'May 19 · 09:46', kind: 'py' },
],
},
{
course: 'Operations Research & Logistics',
items: [
{ name: 'Lecture 11.pdf', when: 'yesterday · 10:56', kind: 'pdf' },
{ name: 'Lecture 11.pdf', when: 'yesterday · 10:55', kind: 'pdf' },
],
},
];
const activity = [
{ browser: 'Chrome 148', host: '178.42.0.10 · home-isp.example.net', when: 'May 26 · 21:10', status: 'ok' },
{ browser: 'Chrome 148', host: 'lab392.school.edu', when: 'May 26 · 13:34', status: 'ok' },
{ browser: 'Chrome 148', host: 'lab392.school.edu', when: 'May 26 · 09:45', status: 'ok' },
{ browser: 'Chrome 143 · Android', host: 'mobile-5453.carrier.example.net', when: 'May 26 · 09:28', status: 'ok' },
{ browser: 'Chrome 148', host: 'lab392.school.edu', when: 'May 25 · 14:25', status: 'ok' },
{ browser: 'Chrome 148', host: '178.42.0.10 · home-isp.example.net', when: 'May 25 · 11:43', status: 'ok' },
{ browser: 'Chrome 148', host: '178.42.0.10 · home-isp.example.net', when: 'May 25 · 11:43', status: 'bad' },
{ browser: 'Chrome 136 · Android', host: '178.42.0.10 · home-isp.example.net', when: 'May 25 · 10:05', status: 'ok' },
];
const events = [
{ date: 'Jun 13', time: '10:00', title: 'School Open Day — main campus', tag: 'soon' },
{ date: 'Jun 10', time: '18:00', title: 'Print Design in the 21st Century — online webinar', tag: 'webinar' },
{ date: 'Jun 03', time: '17:30', title: 'AI Student Club meetup', tag: 'student club' },
];
const news = [
{ kind: 'Accreditation', title: 'Graphic Design program receives a positive accreditation review' },
{ kind: 'Partnership', title: 'School joins a creative-campus program — a new era of digital education' },
{ kind: 'Timetable', title: 'Timetable search for other groups is now open — check their schedules' },
];
const navMain = [
{ id: 'dashboard', label: 'Dashboard', icon: 'home', badge: null },
{ id: 'timetable', label: 'Timetable', icon: 'cal', badge: '3' },
{ id: 'grades', label: 'Grades', icon: 'list', badge: null },
{ id: 'courses', label: 'Courses', icon: 'book', badge: null },
{ id: 'finance', label: 'Finance', icon: 'cash', badge: null },
];
const navAccount = [
{ id: 'profile', label: 'Profile', icon: 'user' },
{ id: 'announcements', label: 'Announcements', icon: 'mega', badge: '2' },
{ id: 'documents', label: 'Documents', icon: 'file' },
{ id: 'enrollment', label: 'Enrollment', icon: 'edit' },
];
/* ============================================================
PRIMITIVES
============================================================ */
const Pill = ({ children, tone='neutral', size='sm', ...rest }) => {
const map = {
neutral: { bg:'#F2E7D9', fg:'#5A4A42', bd:'#E8DAC6' },
accent: { bg:'var(--accent-soft)', fg:'var(--accent-ink)', bd:'transparent' },
warn: { bg:'var(--warn-soft)', fg:'var(--warn)', bd:'transparent' },
info: { bg:'var(--info-soft)', fg:'var(--info)', bd:'transparent' },
ink: { bg:'var(--ink)', fg:'#F6EDE4', bd:'transparent' },
ghost: { bg:'transparent', fg:'var(--ink-2)', bd:'var(--line)' },
};
const t = map[tone] || map.neutral;
const sz = size==='xs' ? { padding:'1px 7px', fontSize:10.5, letterSpacing:'.04em' } : { padding:'2px 9px', fontSize:11.5, letterSpacing:'.02em' };
return {children} ;
};
const Card = ({ children, style, pad=20, className, ...rest }) => (
);
const SectionLabel = ({ children, action, icon }) => (
{icon && {icon} }
{children}
{action}
);
const Avatar = ({ initials='NS', size=36 }) => (
{initials}
);
const btnPrimary = {
display:'inline-flex', alignItems:'center', gap:7,
padding:'9px 13px', borderRadius:10,
background:'var(--ink)', color:'#F6EDE4',
fontSize:13, fontWeight:500
};
const btnGhost = {
display:'inline-flex', alignItems:'center', gap:7,
padding:'9px 13px', borderRadius:10,
background:'#FFF', color:'var(--ink)',
border:'1px solid var(--line)',
fontSize:13, fontWeight:500
};
/* ============================================================
SIDEBAR
============================================================ */
function Sidebar({ active, onSelect, open, onClose }) {
return (
{/* Brand */}
{/* Search */}
Search…
⌘K
{/* NAV */}
{navMain.map(n => onSelect(n.id)} {...n} />)}
{navAccount.map(n => onSelect(n.id)} {...n} />)}
{/* User card */}
);
}
function NavGroup({ title, children }) {
return (
);
}
function NavItem({ active, icon, label, badge, onClick }) {
const Icon = I[icon] || I.dot;
return (
{ if(!active) e.currentTarget.style.background='#FFFBF5'; }}
onMouseLeave={e=> { if(!active) e.currentTarget.style.background='transparent'; }}>
{label}
{badge && {badge} }
);
}
/* ============================================================
TOPBAR
============================================================ */
function TopBar({ active, onMenu, onSelect }) {
const labels = {
dashboard:'Dashboard', timetable:'Timetable', grades:'Grades', courses:'Courses',
finance:'Finance', profile:'Profile', announcements:'Announcements',
documents:'Documents', enrollment:'Enrollment'
};
const [open, setOpen] = useState(null); // 'mail' | 'bell' | 'settings' | 'user'
const wrapRef = useRef(null);
useEffect(() => {
const close = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(null); };
document.addEventListener('pointerdown', close);
return () => document.removeEventListener('pointerdown', close);
}, []);
const toggle = (k) => setOpen(o => o===k ? null : k);
const go = (id) => { setOpen(null); onSelect && onSelect(id); };
return (
School
/
Student portal
/
{labels[active] || 'Dashboard'}
toggle('mail')}>
{open==='mail' && (
go('announcements')}>
{[
{ from:'Dr. M. Collins', subject:'Running late', when:'08:17', unread:true },
{ from:'Dean\u2019s Office', subject:'Student ID renewal confirmation', when:'yesterday', unread:true },
{ from:'Dr. K. Miller', subject:'Midterm scope — chapters 5–8', when:'May 24', unread:false },
{ from:'Library', subject:'Return reminder: 2 items due Friday', when:'May 23', unread:false },
].map((m, i) => (
go('announcements')} style={{ display:'grid', gridTemplateColumns:'auto 1fr auto', gap:10, alignItems:'center', padding:'9px 12px', width:'100%', textAlign:'left', borderRadius:8 }}
onMouseEnter={e=>e.currentTarget.style.background='var(--surface-2)'}
onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
{m.from}
{m.subject}
{m.when}
))}
)}
toggle('bell')}>
{open==='bell' && (
go('dashboard')}>
{[
{ icon:, text:'2 new announcements in Python — Data Mining', when:'today', tone:'var(--warn-soft)', fg:'var(--warn)' },
{ icon:, text:'Midterm scheduled: Mathematical Statistics · Jun 11', when:'yesterday', tone:'var(--info-soft)', fg:'var(--info)' },
{ icon:, text:'5 new files in Applied Electronics Laboratory', when:'May 26', tone:'var(--green-soft)', fg:'var(--green)' },
].map((n, i) => (
go('dashboard')} style={{ display:'grid', gridTemplateColumns:'auto 1fr', gap:10, alignItems:'flex-start', padding:'9px 12px', width:'100%', textAlign:'left', borderRadius:8 }}
onMouseEnter={e=>e.currentTarget.style.background='var(--surface-2)'}
onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
{n.icon}
{n.text}
{n.when}
))}
)}
toggle('settings')}>
{open==='settings' && (
} label="Account settings" onClick={()=>go('profile')}/>
} label="Notification preferences" onClick={()=>go('profile')}/>
} label="Password & security" onClick={()=>go('profile')}/>
} label="Language — English" hint="EN" onClick={()=>setOpen(null)}/>
)}
toggle('user')} style={{ display:'flex', alignItems:'center', gap:9, padding:'6px 10px', borderRadius:10, border:`1px solid ${open==='user'?'var(--accent)':'var(--line)'}`, background:'#FFF' }}>
{me.name.split(' ')[0]}
{me.programs[1]}
{open==='user' && (
} label="My profile" onClick={()=>go('profile')}/>
} label="My grades" onClick={()=>go('grades')}/>
} label="Billing" onClick={()=>go('finance')}/>
} label="Sign out" danger onClick={()=>setOpen(null)}/>
)}
);
}
function Dropdown({ title, children, footer, onFooter }) {
return (
{title &&
{title}
}
{children}
{footer && (
{footer}
)}
);
}
function MenuRow({ icon, label, hint, danger, onClick }) {
return (
e.currentTarget.style.background='var(--surface-2)'}
onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
{icon}
{label}
{hint && {hint} }
);
}
function IconBtn({ children, badge, title, active, onClick }) {
return (
{ if(!active) e.currentTarget.style.background='#F2E7D9'; }}
onMouseLeave={e=>{ if(!active) e.currentTarget.style.background='transparent'; }}>
{children}
{badge && {badge} }
);
}
/* ============================================================
HERO — greeting + next class
============================================================ */
function Hero() {
const greeting = useMemo(()=> {
const h=new Date().getHours();
if (h<12) return 'Good morning';
if (h<18) return 'Good afternoon';
return 'Good evening';
}, []);
const today = new Date().toLocaleDateString('en-US', { weekday:'long', month:'long', day:'numeric' });
return (
{/* Greeting */}
{today}
·
Even week
{greeting}, Name.
You have 2 new announcements from your lecturers and one midterm next week. Enrollment for the winter semester opens June 2 .
Open weekly timetable
Enroll in courses
Documents 5
{/* Next class card */}
Mathematical Statistics
lab · Dr. K. Miller
Join on Teams
Details
);
}
function MetaCell({ k, v, sub }) {
return (
);
}
/* ============================================================
SCHEDULE
============================================================ */
function Schedule({ initialTab='online', dense=false }) {
const [tab, setTab] = useState(initialTab);
const filtered = useMemo(()=> {
return upcoming.map(d => ({
...d,
blocks: d.blocks.filter(b => {
if (tab==='week') return true;
if (tab==='online') return !!b.online;
if (tab==='exams') return b.type==='midterm';
return true;
})
})).filter(d => d.blocks.length>0);
}, [tab]);
return (
}
action={
{[['online','Online classes'],['week','Full week'],['exams','Exams']].map(([k,l])=>(
setTab(k)} style={{
padding:'4px 10px', fontSize:12, fontWeight: tab===k?600:450, borderRadius:6,
background: tab===k ? '#FFF' : 'transparent',
color: tab===k ? 'var(--ink)' : 'var(--muted)',
boxShadow: tab===k ? '0 1px 2px rgba(60,20,20,.06)' : 'none',
minHeight:28
}}>{l}
))}
}
>{dense ? 'Weekly plan' : 'Upcoming classes'}
{filtered.length === 0 && (
Nothing in this view — switch to Full week to see all classes.
)}
{filtered.map((d, i) => (
{d.when}
{d.blocks.map((b, j) => )}
))}
Online class times are set by the lecturer and may differ from the timetable.
Open full timetable
);
}
function ClassRow({ time, code, name, type, room, online, teacher }) {
const onlineMap = {
'Teams': { bg:'oklch(0.94 0.04 270)', fg:'oklch(0.42 0.13 270)', label:'Teams' },
'Inspera': { bg:'oklch(0.95 0.05 50)', fg:'oklch(0.5 0.13 50)', label:'Inspera' },
};
const o = online ? onlineMap[online] : null;
const typeTone = type==='midterm' ? 'warn' : type==='lecture' ? 'info' : 'neutral';
return (
e.currentTarget.style.background='#FCEFE0'}
onMouseLeave={e=>e.currentTarget.style.background='var(--surface-2)'}>
{time.split('–')[0]}
{time.split('–')[1]}
{o ? (
{o.label}
) : (
in person
)}
);
}
/* ============================================================
ANNOUNCEMENTS
============================================================ */
function Announcements() {
const [filter, setFilter] = useState('all');
return (
}
action={
setFilter('all')} style={{ color: filter==='all'?'var(--ink)':'var(--muted)', fontWeight: filter==='all'?600:450, minHeight:28 }}>All
·
setFilter('unread')} style={{ color: filter==='unread'?'var(--ink)':'var(--muted)', fontWeight: filter==='unread'?600:450, minHeight:28 }}>Unread 2
}
>Latest announcements
{announcements
.filter(a => filter==='all' || a.unread)
.map((a, i) => (
{a.course}
{a.subject}
{a.body}
{a.author} · {a.when}
))}
);
}
/* ============================================================
FILES
============================================================ */
const fileColors = {
pdf: { bg:'oklch(0.95 0.04 25)', fg:'oklch(0.45 0.15 25)' },
pptx: { bg:'oklch(0.95 0.05 50)', fg:'oklch(0.45 0.14 50)' },
docx: { bg:'oklch(0.95 0.04 240)', fg:'oklch(0.42 0.13 240)' },
ino: { bg:'oklch(0.95 0.05 200)', fg:'oklch(0.42 0.12 200)' },
py: { bg:'oklch(0.95 0.04 145)', fg:'oklch(0.4 0.1 145)' },
};
function FileGroup({ group, defaultOpen=false }) {
const [open, setOpen] = useState(defaultOpen);
return (
setOpen(o=>!o)} style={{
display:'flex', alignItems:'center', gap:9, padding:'12px 4px',
width:'100%', textAlign:'left', minHeight:44
}}>
{group.course}
{group.items.length} files
{open && (
)}
);
}
function FileIcon({ kind }) {
const c = fileColors[kind] || { bg:'#F2E7D9', fg:'#6B5D52' };
return (
.{kind}
);
}
function Files() {
return (
}
action={Go to documents }
>Latest files
{fileGroups.map((g, i) => )}
);
}
/* ============================================================
ACTIVITY
============================================================ */
function Activity() {
const [showAll, setShowAll] = useState(false);
const visible = showAll ? activity : activity.slice(0,5);
return (
}
action={setShowAll(a=>!a)} style={{ fontSize:12.5, fontWeight:500, color:'var(--accent-ink)', minHeight:28 }}>{showAll?'Show less':'Show all'} }
>Account activity
{visible.map((a, i) => (
{a.browser} · {a.when}
{a.host}
{a.status==='bad' ? 'failed' : 'signed in'}
))}
);
}
/* ============================================================
NEWS, EVENTS, SOCIAL
============================================================ */
function NewsBoard() {
return (
}
action={All news }
>School news
{/* Events */}
Events
{events.map((e, i) => (
{e.date.split(' ')[1]}
{e.date.split(' ')[0]}
{e.title}
{e.time} · {e.tag}
))}
School notices
{/* Social */}
);
}
function SocialGrid() {
const tiles = [
{ tag:'IG', seed:'campus-lecture', text:'Explore the Computer Science specializations' },
{ tag:'IG', seed:'laptop-code', text:'If you are into more than just graphics…' },
{ tag:'IG', seed:'design-studio', text:'Positive accreditation review for Graphic Design' },
{ tag:'IG', seed:'open-day', text:'Come to the Open Day and see for yourself…' },
{ tag:'TT', seed:'students-talk', text:'Like taking the lead? Hear from our graduates.' },
{ tag:'IG', seed:'gallery-night', text:'Reminder: exhibition opening tomorrow evening' },
];
return (
);
}
/* ============================================================
CONTACT FOOTER
============================================================ */
function ContactFooter() {
const items = [
{ tag:'ACCOUNT', label:'Locked or inactive account', value:'passwords@school.edu', icon: },
{ tag:'E-MAIL', label:'Mailbox issues', value:'mail-support@school.edu', icon: },
{ tag:'DEAN\u2019S OFFICE', label:'Graphic Design · Computer Science', value:'cs-office@school.edu', icon: },
{ tag:'DEAN\u2019S OFFICE', label:'IT Management · Management', value:'mgmt-office@school.edu', icon: },
];
return (
}>Contact — when something breaks
);
}
/* ============================================================
PAGE HEADER
============================================================ */
function PageHeader({ title, eyebrow, lead, action }) {
return (
{eyebrow}
{title}
{lead &&
{lead}
}
{action}
);
}
/* Share with views file */
Object.assign(window, {
I, me, upcoming, announcements, fileGroups, activity, events, news,
navMain, navAccount,
Pill, Card, SectionLabel, Avatar, btnPrimary, btnGhost,
Sidebar, TopBar, Hero, Schedule, ClassRow, Announcements, Files, FileGroup,
Activity, NewsBoard, ContactFooter, PageHeader,
});