Worked example: If 96 chapters are completed out of 120 total, then \(P_{\text{complete}}=80\%\). At 8 chapters/week, remaining effort is \((120-96)/8=3\) weeks.
π§ Admin Dashboard
Welcome to the IoT Class admin portal. Manage users, content, and view platform analytics.
Admin Access Required
This page is only accessible to administrators. If youβre not an admin and believe you should have access, please contact the site administrator.
Show code
Auth =window.Auth|| {};IoTClassAPI =window.IoTClassAPI|| {};// Colorscolors = ({navy:'#2C3E50',teal:'#16A085',orange:'#E67E22',gray:'#7F8C8D',lightGray:'#ECF0F1',green:'#27AE60',red:'#E74C3C',blue:'#3498DB'})// Check authentication and admin statuscurrentUser = {const user = Auth.getCurrentUser();if (!user) {returnnull; }// Check if user is admin using Auth moduleconst isAdmin = Auth.isUserAdmin();return {...user,isAdmin: isAdmin };}// Load platform statistics using SupabaseplatformStats = {if (!currentUser ||!currentUser.isAdmin) {returnnull; }try {// Get all users via Supabase Admin APIconst allUsers =await IoTClassAPI.Admin.getAllUsers();// Get all progress records via Supabaseconst allProgress =await IoTClassAPI.Admin.getAllProgress();// Get all badges awarded via Supabaseconst allBadges =await IoTClassAPI.Admin.getAllBadges();// Get all knowledge check results via Supabaseconst allKnowledgeChecks =await IoTClassAPI.Admin.getAllKnowledgeChecks();// Calculate statisticsconst totalUsers = allUsers.length;const totalXP = allUsers.reduce((sum, user) => sum + (user.total_xp||0),0);const totalBadges = allBadges.length;const totalChaptersCompleted = allProgress.filter(p => p.completed).length;const totalKnowledgeChecks = allKnowledgeChecks.length;// Average XP per userconst avgXPPerUser = totalUsers >0?Math.round(totalXP / totalUsers) :0;// Users registered in last 7 daysconst sevenDaysAgo =newDate(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() -7);const newUsersLast7Days = allUsers.filter(user => {const joinDate =newDate(user.created_at);return joinDate >= sevenDaysAgo; }).length;// Active users (activity in last 7 days)const activeUsers = allUsers.filter(user => {if (!user.updated_at) returnfalse;const lastActive =newDate(user.updated_at);return lastActive >= sevenDaysAgo; }).length;// Most popular chapters (by completion count)const chapterCompletions = {}; allProgress.forEach(p => {const chapter = p.chapter_path||'unknown'; chapterCompletions[chapter] = (chapterCompletions[chapter] ||0) +1; });const popularChapters =Object.entries(chapterCompletions).map(([chapter, count]) => ({ chapter, count })).sort((a, b) => b.count- a.count).slice(0,10);// Recent user registrations (last 10)const recentUsers = [...allUsers].sort((a, b) =>newDate(b.created_at) -newDate(a.created_at)).slice(0,10);// Badge distributionconst badgeTypes = {}; allBadges.forEach(b => {const badgeName = b.badge_name||'Unknown'; badgeTypes[badgeName] = (badgeTypes[badgeName] ||0) +1; });return { totalUsers, totalXP, totalBadges, totalChaptersCompleted, totalKnowledgeChecks, avgXPPerUser, newUsersLast7Days, activeUsers, popularChapters, recentUsers, badgeTypes }; } catch (error) {console.error('Error loading platform stats:', error);returnnull; }}
Show code
// Render access denied if not adminhtml`${!Auth.isUserAuthenticated() ||!currentUser?.isAdmin?` <div class="access-denied"> <div class="access-denied-icon">π</div> <h2 style="color: ${colors.navy}; margin-bottom: 16px;">Access Denied</h2> <p style="color: ${colors.gray}; margin-bottom: 24px;"> You must be an administrator to access this page. </p>${!Auth.isUserAuthenticated() ?` <button onclick="Auth.login()" style=" padding: 12px 32px; background: ${colors.teal}; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 600; " > <i class="fab fa-github"></i> Sign in with GitHub </button> `:''} </div>`:''}`