Replace src folder with new version
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
import {configureStore} from '@reduxjs/toolkit';
|
||||
import { demoApi } from './services/demo.service';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
[demoApi.reducerPath]: demoApi.reducer},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(demoApi.middleware),
|
||||
});
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
@@ -1,16 +0,0 @@
|
||||
// import { createApi } from '@reduxjs/toolkit/query'
|
||||
import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
|
||||
export const demoApi = createApi({
|
||||
reducerPath: 'demoApi',
|
||||
baseQuery: fetchBaseQuery({ baseUrl: "https://jsonplaceholder.typicode.com" }),
|
||||
endpoints: (builder) => ({
|
||||
// GET example
|
||||
getPosts: builder.query<any[], void>({
|
||||
query: () => "/posts",
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const { useGetPostsQuery } = demoApi;
|
||||
@@ -1,186 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||
// import heroBannerImage from 'figma:asset/1bb9c22c86c0892d4716564b7135835f04869298.png';
|
||||
const heroBannerImage = 'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80';
|
||||
|
||||
interface HeroSectionProps {
|
||||
showAnnouncementBar?: boolean;
|
||||
announcementText?: string;
|
||||
announcementCTA?: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
ctaText?: string;
|
||||
onAnnouncementClick?: () => void;
|
||||
onCTAClick?: () => void;
|
||||
showFeatures?: boolean;
|
||||
}
|
||||
|
||||
export function HeroSection({
|
||||
showAnnouncementBar = true,
|
||||
announcementText = "Join Our Upcoming Leadership Webinars - Transform Your Leadership Journey",
|
||||
announcementCTA = "Enroll Now",
|
||||
title = "Empowering Future-Ready Leaders",
|
||||
subtitle = "Build confidence, agility, and clarity for today's complex challenges.",
|
||||
ctaText = "Build Your Leadership Pipeline",
|
||||
onAnnouncementClick,
|
||||
onCTAClick,
|
||||
showFeatures = true
|
||||
}: HeroSectionProps) {
|
||||
|
||||
const handleAnnouncementClick = () => {
|
||||
if (onAnnouncementClick) {
|
||||
onAnnouncementClick();
|
||||
} else {
|
||||
window.location.href = '/webinars?view=individual';
|
||||
}
|
||||
};
|
||||
|
||||
const handleCTAClick = () => {
|
||||
if (onCTAClick) {
|
||||
onCTAClick();
|
||||
} else {
|
||||
window.location.href = '/dashboard?view=individual';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
{/* Top Announcement Bar */}
|
||||
{showAnnouncementBar && (
|
||||
<div className="bg-[#F8C301] text-[#26231A] py-3 px-4">
|
||||
<div className="container mx-auto px-4 lg:px-8">
|
||||
<div className="flex items-center justify-center gap-4 text-center">
|
||||
<span className="text-base font-medium">
|
||||
{announcementText}
|
||||
</span>
|
||||
<Button
|
||||
onClick={handleAnnouncementClick}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[#26231A] hover:bg-[#26231A]/10 font-medium text-base h-auto py-1 px-3"
|
||||
>
|
||||
{announcementCTA}
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Hero Section */}
|
||||
<div className="relative min-h-[80vh] flex items-center justify-center overflow-hidden">
|
||||
{/* Background Image */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
<ImageWithFallback
|
||||
src={heroBannerImage}
|
||||
alt="Leadership workshop with diverse team members collaborating with colorful sticky notes on a wall"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Overlay for better text readability */}
|
||||
<div className="absolute inset-0 bg-black/40"></div>
|
||||
</div>
|
||||
|
||||
{/* Hero Content */}
|
||||
<div className="relative z-10 container mx-auto px-4 lg:px-8 pt-20 pb-32">
|
||||
<div className="max-w-4xl">
|
||||
<div className="text-white space-y-8">
|
||||
{/* Main Heading */}
|
||||
<h1 className="text-5xl lg:text-6xl font-bold leading-tight">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
{/* Subtext */}
|
||||
<p className="text-xl lg:text-2xl text-white/90 leading-relaxed max-w-2xl">
|
||||
{subtitle}
|
||||
</p>
|
||||
|
||||
{/* CTA Button */}
|
||||
<div className="pt-4">
|
||||
<Button
|
||||
onClick={handleCTAClick}
|
||||
size="lg"
|
||||
className="bg-[#04045B] hover:bg-[#04045B]/90 text-white text-lg px-8 py-4 min-h-[60px] font-medium"
|
||||
>
|
||||
{ctaText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Feature Navigation */}
|
||||
{showFeatures && (
|
||||
<div className="absolute bottom-0 left-0 right-0 z-10">
|
||||
<div className="container mx-auto px-4 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 py-12">
|
||||
{/* Feature 01 */}
|
||||
<div className="text-white space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-[#F8C301] text-xl font-bold">01</div>
|
||||
<div className="h-px bg-[#F8C301] flex-1"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-2">Leadership Is Learning. We Teach It Right.</h3>
|
||||
<p className="text-white/80 text-base leading-relaxed">
|
||||
Master proven methodologies and frameworks that transform managers into exceptional leaders.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature 02 */}
|
||||
<div className="text-white space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-[#F8C301] text-xl font-bold">02</div>
|
||||
<div className="h-px bg-[#F8C301] flex-1"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-2">Turn Managers Into Impactful Leaders</h3>
|
||||
<p className="text-white/80 text-base leading-relaxed">
|
||||
Develop strategic thinking, emotional intelligence, and decision-making capabilities.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature 03 */}
|
||||
<div className="text-white space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-[#F8C301] text-xl font-bold">03</div>
|
||||
<div className="h-px bg-[#F8C301] flex-1"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-2">Struggling with Managerial Gaps?</h3>
|
||||
<p className="text-white/80 text-base leading-relaxed">
|
||||
Bridge the gap between individual contribution and effective team leadership.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Controls */}
|
||||
<div className="absolute bottom-6 right-6 flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-12 h-12 rounded-full bg-white/10 hover:bg-white/20 text-white border border-white/20"
|
||||
aria-label="Previous slide"
|
||||
>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="w-12 h-12 rounded-full bg-white/10 hover:bg-white/20 text-white border border-white/20"
|
||||
aria-label="Next slide"
|
||||
>
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,712 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
||||
import { Badge } from './ui/badge';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuLabel,
|
||||
} from './ui/dropdown-menu';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from './ui/sheet';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
|
||||
// import { navigate } from './Router';
|
||||
import { useAuth } from './AuthContext';
|
||||
// import klcLogo from 'figma:asset/209958db0c439ec78be82ab4f3e335a6aed5de89.png';
|
||||
// import exampleImage from 'figma:asset/6cae567b6bf6a44cb03b767e4308c4c705340d08.png';
|
||||
const klcLogo = 'https://res.cloudinary.com/dt3k2apqd/image/upload/v1697045531/Kautilya_Leadership_Centre_Logo_hor_uxh0v4.png';
|
||||
const exampleImage = 'https://images.unsplash.com/photo-1508214751196-bcfd4ca60f91?w=150&h=150&fit=crop&crop=face';
|
||||
import {
|
||||
Menu,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
ShoppingCart,
|
||||
Search,
|
||||
Building2,
|
||||
User,
|
||||
Settings,
|
||||
LogOut,
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
Target,
|
||||
Award,
|
||||
Lightbulb,
|
||||
GraduationCap,
|
||||
BookOpen,
|
||||
Video,
|
||||
FileText,
|
||||
Eye,
|
||||
Heart,
|
||||
MapPin,
|
||||
Calendar,
|
||||
Play,
|
||||
Home,
|
||||
Check,
|
||||
ArrowRight
|
||||
} from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
const navigate = useNavigate();
|
||||
interface NavigationProps {
|
||||
currentPage?: string;
|
||||
}
|
||||
|
||||
export function Navigation({ currentPage }: NavigationProps) {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [expandedMobileSection, setExpandedMobileSection] = useState<string | null>(null);
|
||||
|
||||
const { user, login, signOut, isAuthenticated } = useAuth();
|
||||
|
||||
// Determine user type from URL or user data
|
||||
const getQueryParam = (param: string) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(param);
|
||||
};
|
||||
|
||||
const isIndividualUser = getQueryParam('view') === 'individual' ||
|
||||
(!getQueryParam('view') && currentPage?.includes('/dashboard')) ||
|
||||
(!getQueryParam('view') && currentPage?.includes('/library')) ||
|
||||
(!getQueryParam('view') && currentPage?.includes('/course')) ||
|
||||
(!getQueryParam('view') && currentPage?.includes('/settings')) ||
|
||||
(!getQueryParam('view') && currentPage?.includes('/surveys')) ||
|
||||
(!getQueryParam('view') && currentPage?.includes('/webinars')) ||
|
||||
(!getQueryParam('view') && currentPage?.includes('/leaderboard'));
|
||||
|
||||
const isCorporateUser = getQueryParam('view') === 'corporate';
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 10);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as Element;
|
||||
if (!target.closest('[data-dropdown]')) {
|
||||
setActiveDropdown(null);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
return () => document.removeEventListener('click', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const handleDropdownToggle = (dropdown: string) => {
|
||||
setActiveDropdown(activeDropdown === dropdown ? null : dropdown);
|
||||
};
|
||||
|
||||
const handleMobileToggle = (section: string) => {
|
||||
setExpandedMobileSection(expandedMobileSection === section ? null : section);
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
navigate('/auth'); // Route to login selection page
|
||||
setIsMobileMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleSignup = () => {
|
||||
navigate('/signup');
|
||||
setIsMobileMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
signOut();
|
||||
navigate('/');
|
||||
setActiveDropdown(null);
|
||||
setIsMobileMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleAccountSignIn = (accountType: 'individual' | 'corporate') => {
|
||||
// Navigate to appropriate sign-in page for the account type
|
||||
if (accountType === 'individual') {
|
||||
navigate('/login');
|
||||
} else {
|
||||
navigate('/corporate/login');
|
||||
}
|
||||
setActiveDropdown(null);
|
||||
setIsMobileMenuOpen(false);
|
||||
};
|
||||
|
||||
const navigationItems = [
|
||||
{
|
||||
title: 'About Us',
|
||||
href: '/about-us/our-vision',
|
||||
items: [
|
||||
{ title: 'Our Vision', href: '/about-us/our-vision', icon: Eye },
|
||||
{ title: 'Our Team', href: '/about-us/our-team', icon: Users },
|
||||
{ title: 'Our Impact', href: '/about-us/our-impact', icon: Target },
|
||||
{ title: 'Our Expertise', href: '/about-us/our-expertise', icon: Award }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Programmes',
|
||||
href: '/programmes',
|
||||
items: [
|
||||
{ title: 'Programme Catalogue', href: '/programmes', icon: BookOpen },
|
||||
{ title: 'Executive Leadership', href: '/programmes/executive-leadership', icon: Award },
|
||||
{ title: 'Team Leadership', href: '/programmes/team-leadership', icon: Users },
|
||||
{ title: 'Innovation Leadership', href: '/programmes/innovation-leadership', icon: Lightbulb },
|
||||
{ title: 'Leadership Online', href: '/programmes/leadership-online', icon: Play }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Services',
|
||||
href: '/services/leadership-development',
|
||||
items: [
|
||||
{ title: 'Leadership Development', href: '/services/leadership-development', icon: Target },
|
||||
{ title: 'Management Development', href: '/services/management-development', icon: Users },
|
||||
{ title: 'Executive Coaching', href: '/services/executive-coaching', icon: Award },
|
||||
{ title: 'Culture & Competence', href: '/services/culture-competence', icon: Heart },
|
||||
{ title: 'Consulting', href: '/services/consulting', icon: Lightbulb },
|
||||
{ title: 'Learning Facility', href: '/services/learning-facility', icon: MapPin }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Learning',
|
||||
href: '/learning/articles',
|
||||
items: [
|
||||
{ title: 'Articles', href: '/learning/articles', icon: FileText },
|
||||
{ title: 'Blog', href: '/learning/blog', icon: BookOpen },
|
||||
{ title: 'Resources', href: '/learning/resources', icon: BookOpen },
|
||||
{ title: 'Webinars', href: '/individual-webinars', icon: Video }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const learnerMenuItems = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: isIndividualUser ? '/dashboard?view=individual' : '/dashboard?view=corporate',
|
||||
icon: LayoutDashboard,
|
||||
description: isIndividualUser ? 'Your learning overview' : 'Team management hub'
|
||||
},
|
||||
{
|
||||
title: 'Library',
|
||||
href: isIndividualUser ? '/library?view=individual' : '/library?view=corporate',
|
||||
icon: BookOpen,
|
||||
description: isIndividualUser ? 'Browse courses' : 'Assigned courses'
|
||||
},
|
||||
{
|
||||
title: 'Course Timeline',
|
||||
href: isIndividualUser ? '/course?view=individual' : '/course?view=corporate',
|
||||
icon: Calendar,
|
||||
description: isIndividualUser ? 'Your learning path' : 'Team progress'
|
||||
},
|
||||
{
|
||||
title: 'Surveys & Assessments',
|
||||
href: isIndividualUser ? '/surveys?view=individual' : '/surveys?view=corporate',
|
||||
icon: FileText,
|
||||
description: isIndividualUser ? 'Complete assessments' : 'Team evaluations'
|
||||
},
|
||||
{
|
||||
title: 'Live Webinars',
|
||||
href: isIndividualUser ? '/webinars?view=individual' : '/webinars?view=corporate',
|
||||
icon: Video,
|
||||
description: isIndividualUser ? 'Join sessions' : 'Corporate events'
|
||||
},
|
||||
{
|
||||
title: 'Leaderboard',
|
||||
href: isIndividualUser ? '/leaderboard?view=individual' : '/leaderboard?view=corporate',
|
||||
icon: Award,
|
||||
description: isIndividualUser ? 'Your achievements' : 'Team rankings'
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
href: isIndividualUser ? '/settings?view=individual' : '/settings?view=corporate',
|
||||
icon: Settings,
|
||||
description: isIndividualUser ? 'Account preferences' : 'Admin settings'
|
||||
}
|
||||
];
|
||||
|
||||
// Mock data for demonstration - replace with actual user data
|
||||
const currentUser = {
|
||||
name: 'Priya Sharma',
|
||||
email: 'priya.sharma@example.com',
|
||||
avatar: "https://images.unsplash.com/photo-1494790108755-2616b612b786?w=150&h=150&fit=crop&crop=face",
|
||||
organization: 'TechCorp Inc.',
|
||||
role: 'Marketing Team Member'
|
||||
};
|
||||
|
||||
const availableAccounts = [
|
||||
{
|
||||
type: 'individual',
|
||||
isActive: isIndividualUser,
|
||||
title: 'Personal Learning',
|
||||
subtitle: 'Access your individual learning portal',
|
||||
icon: User,
|
||||
user: currentUser
|
||||
},
|
||||
{
|
||||
type: 'corporate',
|
||||
isActive: isCorporateUser,
|
||||
title: 'Corporate Learning',
|
||||
subtitle: 'Enterprise team development portal',
|
||||
icon: Building2,
|
||||
user: currentUser,
|
||||
organization: 'TechCorp Inc.'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<nav className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
|
||||
isScrolled ? 'bg-white/95 backdrop-blur-md shadow-sm' : 'bg-white'
|
||||
}`}>
|
||||
<div className="w-full px-4 lg:px-8">
|
||||
<div className="flex items-center justify-between h-[70px]">
|
||||
{/* Logo */}
|
||||
<div className="flex-shrink-0">
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="flex items-center space-x-2 focus:outline-none focus:ring-2 focus:ring-primary rounded-lg p-1 hover:bg-gray-50 transition-colors"
|
||||
aria-label="Go to KLC homepage"
|
||||
>
|
||||
<img
|
||||
src={klcLogo}
|
||||
alt="Kautilya Leadership Centre"
|
||||
className="h-12 w-auto object-contain"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden lg:flex items-center space-x-8">
|
||||
{navigationItems.map((item) => (
|
||||
<div key={item.title} className="relative" data-dropdown>
|
||||
<button
|
||||
onClick={() => handleDropdownToggle(item.title)}
|
||||
className="flex items-center space-x-1 text-[16px] text-foreground hover:text-primary transition-colors py-2 px-3 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
aria-expanded={activeDropdown === item.title}
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span>{item.title}</span>
|
||||
<ChevronDown className={`h-4 w-4 transition-transform ${
|
||||
activeDropdown === item.title ? 'rotate-180' : ''
|
||||
}`} />
|
||||
</button>
|
||||
|
||||
{activeDropdown === item.title && (
|
||||
<div className="absolute top-full left-0 mt-2 w-64 bg-white rounded-lg shadow-lg border border-gray-200 py-2 z-50">
|
||||
{item.items.map((subItem) => {
|
||||
const IconComponent = subItem.icon;
|
||||
return (
|
||||
<button
|
||||
key={subItem.title}
|
||||
onClick={() => {
|
||||
navigate(subItem.href);
|
||||
setActiveDropdown(null);
|
||||
}}
|
||||
className="w-full flex items-center space-x-3 px-4 py-3 text-[16px] text-gray-700 hover:bg-gray-50 hover:text-primary transition-colors text-left focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
>
|
||||
<IconComponent className="h-4 w-4 flex-shrink-0" />
|
||||
<span>{subItem.title}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/contact')}
|
||||
className="text-[16px] text-foreground hover:text-primary transition-colors py-2 px-3 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
>
|
||||
Contact
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Right Section - Desktop */}
|
||||
<div className="hidden lg:flex items-center space-x-4">
|
||||
{!isAuthenticated ? (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={handleLogin}
|
||||
className="text-[16px] min-h-[44px]"
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSignup}
|
||||
className="text-[16px] min-h-[44px] bg-primary hover:bg-primary/90 text-primary-foreground"
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Redesigned User Profile Dropdown */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex items-center gap-3 h-auto p-2 hover:bg-gray-50 transition-all duration-200 rounded-lg min-h-[44px]"
|
||||
aria-label="Open user menu"
|
||||
>
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage
|
||||
src={currentUser.avatar}
|
||||
alt={currentUser.name}
|
||||
/>
|
||||
<AvatarFallback className="bg-primary/10 text-primary text-sm">
|
||||
{currentUser.name.split(' ').map(n => n[0]).join('')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col items-start min-w-0">
|
||||
<span className="text-[16px] font-medium text-gray-900 truncate">
|
||||
{currentUser.name}
|
||||
</span>
|
||||
<span className="text-[14px] text-gray-600 truncate">
|
||||
{isIndividualUser ? 'Individual Account' : 'Corporate Account'}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDown className="h-4 w-4 text-gray-500 flex-shrink-0" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-80 p-0" align="end" forceMount>
|
||||
{/* Header Section */}
|
||||
<div className="p-4 border-b border-gray-100 bg-gray-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-12 w-12">
|
||||
<AvatarImage
|
||||
src={currentUser.avatar}
|
||||
alt={currentUser.name}
|
||||
/>
|
||||
<AvatarFallback className="bg-primary/10 text-primary text-lg">
|
||||
{currentUser.name.split(' ').map(n => n[0]).join('')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-[16px] font-semibold text-gray-900 truncate">
|
||||
{currentUser.name}
|
||||
</p>
|
||||
<ChevronDown className="h-4 w-4 text-gray-400 flex-shrink-0" />
|
||||
</div>
|
||||
<p className="text-[14px] text-gray-600 truncate">
|
||||
{isIndividualUser ? 'Individual Account' : 'Corporate Account'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Account Switching Section */}
|
||||
<div className="p-4 border-b border-gray-100">
|
||||
<h4 className="text-[14px] font-medium text-gray-900 mb-3">Switch Account</h4>
|
||||
<div className="space-y-2">
|
||||
{availableAccounts.map((account) => {
|
||||
const IconComponent = account.icon;
|
||||
return (
|
||||
<div
|
||||
key={account.type}
|
||||
className={`flex items-center gap-3 p-3 rounded-lg border transition-all duration-200 ${
|
||||
account.isActive
|
||||
? 'bg-green-50 border-green-200'
|
||||
: 'bg-gray-50 border-gray-200 hover:bg-gray-100 cursor-pointer'
|
||||
}`}
|
||||
onClick={() => !account.isActive && handleAccountSignIn(account.type as 'individual' | 'corporate')}
|
||||
>
|
||||
<div className="relative">
|
||||
{account.type === 'individual' ? (
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage
|
||||
src={currentUser.avatar}
|
||||
alt={currentUser.name}
|
||||
/>
|
||||
<AvatarFallback className="bg-blue-100 text-blue-700 text-sm">
|
||||
<User className="h-4 w-4" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
) : (
|
||||
<div className="h-8 w-8 bg-purple-100 rounded-full flex items-center justify-center">
|
||||
<Building2 className="h-4 w-4 text-purple-700" />
|
||||
</div>
|
||||
)}
|
||||
{account.isActive && (
|
||||
<div className="absolute -top-1 -right-1 w-4 h-4 bg-green-500 rounded-full flex items-center justify-center">
|
||||
<Check className="h-2.5 w-2.5 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[14px] font-medium text-gray-900 truncate">
|
||||
{account.title}
|
||||
</p>
|
||||
<p className="text-[14px] text-gray-600 truncate">
|
||||
{account.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
{!account.isActive && (
|
||||
<ArrowRight className="h-4 w-4 text-gray-400 flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{/* Settings and Logout */}
|
||||
<div className="p-2">
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-3 px-3 py-2 cursor-pointer rounded-md hover:bg-gray-50 focus:bg-gray-50 min-h-[44px]"
|
||||
onClick={() => navigate(learnerMenuItems[learnerMenuItems.length - 1].href)}
|
||||
>
|
||||
<Settings className="h-5 w-5 text-gray-500" />
|
||||
<span className="text-[16px] font-medium text-gray-900">Settings</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="flex items-center gap-3 px-3 py-2 cursor-pointer rounded-md text-red-600 hover:bg-red-50 focus:bg-red-50 min-h-[44px]"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut className="h-5 w-5" />
|
||||
<span className="text-[16px] font-medium">Sign Out</span>
|
||||
</DropdownMenuItem>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<div className="lg:hidden">
|
||||
<Sheet open={isMobileMenuOpen} onOpenChange={setIsMobileMenuOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10"
|
||||
aria-label="Open mobile menu"
|
||||
>
|
||||
<Menu className="h-6 w-6" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="right" className="w-full sm:w-96 p-0">
|
||||
<SheetHeader className="p-6 border-b border-gray-200">
|
||||
<SheetTitle className="text-left flex items-center gap-3">
|
||||
<img
|
||||
src={klcLogo}
|
||||
alt="KLC"
|
||||
className="h-8 w-auto object-contain"
|
||||
/>
|
||||
<span className="text-lg font-semibold">Menu</span>
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
<div className="flex flex-col h-full">
|
||||
{/* User Section for Mobile */}
|
||||
{isAuthenticated && (
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Avatar className="h-12 w-12">
|
||||
<AvatarImage
|
||||
src={currentUser.avatar}
|
||||
alt={currentUser.name}
|
||||
/>
|
||||
<AvatarFallback className="bg-primary/10 text-primary">
|
||||
{currentUser.name.split(' ').map(n => n[0]).join('')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[16px] font-semibold text-gray-900 truncate">
|
||||
{currentUser.name}
|
||||
</p>
|
||||
<p className="text-[14px] text-gray-600 truncate">
|
||||
{isIndividualUser ? 'Individual Account' : 'Corporate Account'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Account Switching */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-[14px] font-medium text-gray-900">Switch Account</h4>
|
||||
<div className="space-y-2">
|
||||
{availableAccounts.map((account) => (
|
||||
<button
|
||||
key={account.type}
|
||||
onClick={() => !account.isActive && handleAccountSignIn(account.type as 'individual' | 'corporate')}
|
||||
disabled={account.isActive}
|
||||
className={`w-full flex items-center gap-3 p-3 rounded-lg border text-left transition-all duration-200 ${
|
||||
account.isActive
|
||||
? 'bg-green-50 border-green-200'
|
||||
: 'bg-gray-50 border-gray-200 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<div className="relative">
|
||||
{account.type === 'individual' ? (
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage
|
||||
src={currentUser.avatar}
|
||||
alt={currentUser.name}
|
||||
/>
|
||||
<AvatarFallback className="bg-blue-100 text-blue-700 text-sm">
|
||||
<User className="h-4 w-4" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
) : (
|
||||
<div className="h-8 w-8 bg-purple-100 rounded-full flex items-center justify-center">
|
||||
<Building2 className="h-4 w-4 text-purple-700" />
|
||||
</div>
|
||||
)}
|
||||
{account.isActive && (
|
||||
<div className="absolute -top-1 -right-1 w-4 h-4 bg-green-500 rounded-full flex items-center justify-center">
|
||||
<Check className="h-2.5 w-2.5 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[14px] font-medium text-gray-900 truncate">
|
||||
{account.title}
|
||||
</p>
|
||||
<p className="text-[14px] text-gray-600 truncate">
|
||||
{account.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
{!account.isActive && (
|
||||
<ArrowRight className="h-4 w-4 text-gray-400 flex-shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Navigation Items */}
|
||||
<div className="flex-1 overflow-y-auto py-4">
|
||||
{/* Learner Portal Items (if authenticated) */}
|
||||
{isAuthenticated && (isIndividualUser || isCorporateUser) && (
|
||||
<div className="px-4 mb-6">
|
||||
<h3 className="text-[14px] font-medium text-gray-900 mb-3">
|
||||
{isIndividualUser ? 'Personal Learning' : 'Corporate Learning'}
|
||||
</h3>
|
||||
<div className="space-y-1">
|
||||
{learnerMenuItems.map((item) => {
|
||||
const IconComponent = item.icon;
|
||||
return (
|
||||
<button
|
||||
key={item.title}
|
||||
onClick={() => {
|
||||
navigate(item.href);
|
||||
setIsMobileMenuOpen(false);
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 text-left text-[16px] text-gray-700 hover:bg-gray-50 hover:text-primary rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary min-h-[44px]"
|
||||
>
|
||||
<IconComponent className="h-5 w-5 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium">{item.title}</div>
|
||||
<div className="text-[14px] text-gray-500 truncate">{item.description}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Public Navigation Items */}
|
||||
<div className="px-4">
|
||||
{navigationItems.map((item) => (
|
||||
<Collapsible key={item.title}>
|
||||
<CollapsibleTrigger
|
||||
onClick={() => handleMobileToggle(item.title)}
|
||||
className="w-full flex items-center justify-between p-3 text-[16px] text-gray-900 hover:bg-gray-50 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary min-h-[44px]"
|
||||
>
|
||||
<span className="font-medium">{item.title}</span>
|
||||
<ChevronRight className={`h-4 w-4 transition-transform ${
|
||||
expandedMobileSection === item.title ? 'rotate-90' : ''
|
||||
}`} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="px-4 pb-2">
|
||||
<div className="space-y-1">
|
||||
{item.items.map((subItem) => {
|
||||
const IconComponent = subItem.icon;
|
||||
return (
|
||||
<button
|
||||
key={subItem.title}
|
||||
onClick={() => {
|
||||
navigate(subItem.href);
|
||||
setIsMobileMenuOpen(false);
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 text-left text-[16px] text-gray-600 hover:bg-gray-50 hover:text-primary rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-primary min-h-[44px]"
|
||||
>
|
||||
<IconComponent className="h-4 w-4 flex-shrink-0" />
|
||||
<span>{subItem.title}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate('/contact');
|
||||
setIsMobileMenuOpen(false);
|
||||
}}
|
||||
className="w-full flex items-center p-3 text-[16px] text-gray-900 hover:bg-gray-50 rounded-lg transition-colors text-left focus:outline-none focus:ring-2 focus:ring-primary min-h-[44px]"
|
||||
>
|
||||
<span className="font-medium">Contact</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Authentication Actions */}
|
||||
{!isAuthenticated && (
|
||||
<div className="p-4 border-t border-gray-200 space-y-2">
|
||||
<Button
|
||||
onClick={handleLogin}
|
||||
variant="outline"
|
||||
className="w-full text-[16px] min-h-[44px]"
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSignup}
|
||||
className="w-full text-[16px] min-h-[44px] bg-primary hover:bg-primary/90 text-primary-foreground"
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile Logout */}
|
||||
{isAuthenticated && (
|
||||
<div className="p-4 border-t border-gray-200">
|
||||
<Button
|
||||
onClick={handleLogout}
|
||||
variant="outline"
|
||||
className="w-full text-[16px] min-h-[44px] text-red-600 border-red-200 hover:bg-red-50"
|
||||
>
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -1,595 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
// import { Progress } from '../ui/progress';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
BookOpen,
|
||||
Clock,
|
||||
Trophy,
|
||||
Target,
|
||||
TrendingUp,
|
||||
Award,
|
||||
CheckCircle,
|
||||
Calendar,
|
||||
Users,
|
||||
Star,
|
||||
Play,
|
||||
Bookmark,
|
||||
Search,
|
||||
Zap,
|
||||
ArrowRight,
|
||||
BarChart3,
|
||||
Brain,
|
||||
Sparkles,
|
||||
Flame,
|
||||
ChevronRight,
|
||||
Gauge,
|
||||
Activity,
|
||||
ChevronLeft,
|
||||
ChevronDown,
|
||||
MoreHorizontal
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
Legend
|
||||
} from 'recharts';
|
||||
import { Course } from '../../pages/learner/data/libraryData';
|
||||
|
||||
interface LibraryStatsProps {
|
||||
courses: Course[];
|
||||
userType: 'individual' | 'corporate';
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function LibraryStats({ courses, userType }: LibraryStatsProps) {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [selectedTimeRange, setSelectedTimeRange] = useState('December');
|
||||
|
||||
// Calculate statistics
|
||||
const stats = {
|
||||
totalCourses: courses.length,
|
||||
completedCourses: courses.filter(c => c.status === 'completed').length,
|
||||
inProgressCourses: courses.filter(c => c.status === 'in-progress').length,
|
||||
bookmarkedCourses: courses.filter(c => c.status === 'bookmarked').length,
|
||||
assignedCourses: userType === 'corporate' ? courses.filter(c => c.organizationAssigned).length : 0,
|
||||
overdueCourses: userType === 'corporate' ? courses.filter(c => c.status === 'not-started').length : undefined,
|
||||
completionRate: courses.length > 0 ? (courses.filter(c => c.status === 'completed').length / courses.length) * 100 : 0,
|
||||
averageRating: courses.length > 0 ? courses.reduce((sum, c) => sum + c.rating, 0) / courses.length : 0,
|
||||
totalHours: courses.reduce((sum, c) => sum + parseFloat(c.duration.replace(/[^\d.]/g, '')), 0),
|
||||
certificatesEarned: courses.filter(c => c.status === 'completed').length
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsVisible(true), 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Performance chart data (bar chart showing progress over time)
|
||||
const performanceData = [
|
||||
{ name: 'Jan', value: 1, color: '#04045B' },
|
||||
{ name: 'Feb', value: 2, color: '#04045B' },
|
||||
{ name: 'Mar', value: 4, color: '#04045B' },
|
||||
{ name: 'Apr', value: 6, color: '#04045B' },
|
||||
{ name: 'May', value: 7, color: '#04045B' },
|
||||
{ name: 'Jun', value: 8, color: '#04045B' }
|
||||
];
|
||||
|
||||
|
||||
|
||||
// Time learning data (stacked bar chart)
|
||||
const timeLearningData = [
|
||||
{ date: 'Apr 18', performance: 2, consistency: 1, unknown: 0.5 },
|
||||
{ date: 'Apr 19', performance: 3, consistency: 2, unknown: 1 },
|
||||
{ date: 'Apr 20', performance: 1.5, consistency: 2.5, unknown: 0.5 },
|
||||
{ date: 'Apr 21', performance: 4, consistency: 1, unknown: 1 },
|
||||
{ date: 'Apr 22', performance: 2, consistency: 3, unknown: 0.5 },
|
||||
{ date: 'Apr 23', performance: 3.5, consistency: 1.5, unknown: 1 },
|
||||
{ date: 'Apr 24', performance: 2.5, consistency: 2, unknown: 1.5 }
|
||||
];
|
||||
|
||||
// Course list with progress
|
||||
const courseList = [
|
||||
{ name: 'Introduction to Strategic Leadership', lessons: '24/30 Lessons', progress: 80, color: '#04045B' },
|
||||
{ name: 'English for Effective Communication', lessons: '18/25 Lessons', progress: 72, color: '#10B981' },
|
||||
{ name: 'Introduction to Team Management', lessons: '14/20 Lessons', progress: 70, color: '#F8C301' },
|
||||
{ name: 'Introduction to Digital Leadership', lessons: '8/15 Lessons', progress: 53, color: '#6366F1' }
|
||||
];
|
||||
|
||||
// Daily activity timeline
|
||||
const dailyActivities = [
|
||||
{ time: '07:00', course: 'Introduction to Strategic Leadership', type: 'Google Meeting', color: '#F8C301', instructor: 'A' },
|
||||
{ time: '08:00', course: '', type: '', color: '', instructor: '' },
|
||||
{ time: '09:00', course: 'English for Effective Communication', type: 'Google Meeting', color: '#3B82F6', instructor: 'E' },
|
||||
{ time: '10:00', course: '', type: '', color: '', instructor: '' },
|
||||
{ time: '11:00', course: '', type: '', color: '', instructor: '' },
|
||||
{ time: '12:00', course: 'Introduction to Digital Leadership', type: 'Google Meeting', color: '#6366F1', instructor: 'I' },
|
||||
{ time: '01:00', course: '', type: '', color: '', instructor: '' }
|
||||
];
|
||||
|
||||
// Month navigation functionality
|
||||
const months = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
|
||||
const navigateMonth = (direction: 'prev' | 'next') => {
|
||||
const currentIndex = months.findIndex(month => month === selectedTimeRange);
|
||||
let newIndex;
|
||||
|
||||
if (direction === 'prev') {
|
||||
newIndex = currentIndex <= 0 ? months.length - 1 : currentIndex - 1;
|
||||
} else {
|
||||
newIndex = currentIndex >= months.length - 1 ? 0 : currentIndex + 1;
|
||||
}
|
||||
|
||||
setSelectedTimeRange(months[newIndex]);
|
||||
};
|
||||
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Throttle wheel events to prevent rapid scrolling
|
||||
const now = Date.now();
|
||||
const lastWheelTime = (e.currentTarget as any)._lastWheelTime || 0;
|
||||
|
||||
if (now - lastWheelTime < 200) return; // 200ms throttle
|
||||
|
||||
(e.currentTarget as any)._lastWheelTime = now;
|
||||
|
||||
if (e.deltaY > 0) {
|
||||
navigateMonth('next');
|
||||
} else {
|
||||
navigateMonth('prev');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`space-y-4 transition-all duration-500 ${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'}`}>
|
||||
{/* Dashboard Grid Layout - Matching Reference Image */}
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
|
||||
{/* Top Row */}
|
||||
|
||||
{/* Simplified Performance Chart */}
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Card className="border-0 shadow-md bg-white h-full">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827]">
|
||||
Performance
|
||||
</CardTitle>
|
||||
<MoreHorizontal className="h-4 w-4 text-[#6B7280]" />
|
||||
</div>
|
||||
<div className="text-[14px] text-[#6B7280]">
|
||||
{stats.completedCourses} Course Completed
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-0 pb-4">
|
||||
<ResponsiveContainer width="100%" height={140}>
|
||||
<BarChart data={performanceData} margin={{ top: 15, right: 15, left: 15, bottom: 15 }}>
|
||||
<defs>
|
||||
<filter id="performanceShadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="2" floodColor="#04045B" floodOpacity="0.3"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" opacity={0.4} />
|
||||
<Bar
|
||||
dataKey="value"
|
||||
fill="#04045B"
|
||||
radius={[4, 4, 0, 0]}
|
||||
maxBarSize={24}
|
||||
filter="url(#performanceShadow)"
|
||||
className="hover:opacity-80 transition-opacity duration-200"
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 13, fill: '#6B7280', fontWeight: 500 }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 11, fill: '#9CA3AF' }}
|
||||
width={25}
|
||||
/>
|
||||
<Tooltip
|
||||
cursor={false}
|
||||
contentStyle={{
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid #E5E7EB',
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||
padding: '12px'
|
||||
}}
|
||||
formatter={(value) => [`${value} courses`, 'Completed']}
|
||||
labelStyle={{ color: '#111827', fontWeight: 600, marginBottom: '4px' }}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Simplified Time Learning Chart */}
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Card className="border-0 shadow-md bg-white h-full">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827] mb-1">
|
||||
Time spend on learning
|
||||
</CardTitle>
|
||||
<div className="text-[14px] text-[#6B7280]">
|
||||
4 Course Completed
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer select-none hover:bg-gray-50 rounded-lg px-3 py-2 transition-colors duration-200"
|
||||
onWheel={handleWheel}
|
||||
title="Scroll to change month or click arrows"
|
||||
>
|
||||
<button
|
||||
onClick={() => navigateMonth('prev')}
|
||||
className="h-4 w-4 text-[#6B7280] hover:text-[#04045B] transition-colors duration-200 flex items-center justify-center"
|
||||
aria-label="Previous month"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</button>
|
||||
<span className="text-[14px] font-medium text-[#111827] min-w-[70px] text-center transition-all duration-200">
|
||||
{selectedTimeRange}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => navigateMonth('next')}
|
||||
className="h-4 w-4 text-[#6B7280] hover:text-[#04045B] transition-colors duration-200 flex items-center justify-center"
|
||||
aria-label="Next month"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-0 pb-4">
|
||||
<div className="mb-4">
|
||||
<ResponsiveContainer width="100%" height={120}>
|
||||
<BarChart data={timeLearningData} margin={{ top: 10, right: 10, left: 10, bottom: 10 }}>
|
||||
<defs>
|
||||
<filter id="stackShadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="1" stdDeviation="1" floodColor="#000000" floodOpacity="0.2"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#E5E7EB" opacity={0.3} />
|
||||
<Bar
|
||||
dataKey="performance"
|
||||
stackId="a"
|
||||
fill="#04045B"
|
||||
radius={[0, 0, 0, 0]}
|
||||
maxBarSize={28}
|
||||
filter="url(#stackShadow)"
|
||||
/>
|
||||
<Bar
|
||||
dataKey="consistency"
|
||||
stackId="a"
|
||||
fill="#6366F1"
|
||||
radius={[0, 0, 0, 0]}
|
||||
maxBarSize={28}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="unknown"
|
||||
stackId="a"
|
||||
fill="#E5E7EB"
|
||||
radius={[3, 3, 0, 0]}
|
||||
maxBarSize={28}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 12, fill: '#6B7280', fontWeight: 500 }}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fontSize: 10, fill: '#9CA3AF' }}
|
||||
width={25}
|
||||
/>
|
||||
<Tooltip
|
||||
cursor={false}
|
||||
contentStyle={{
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid #E5E7EB',
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||
padding: '12px'
|
||||
}}
|
||||
labelStyle={{ color: '#111827', fontWeight: 600, marginBottom: '4px' }}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Legend */}
|
||||
<div className="flex items-center justify-center gap-6 text-[14px]">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-sm bg-[#04045B] shadow-sm"></div>
|
||||
<span className="text-[#6B7280] font-medium">Performance</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-sm bg-[#6366F1] shadow-sm"></div>
|
||||
<span className="text-[#6B7280] font-medium">Consistency</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-sm bg-[#E5E7EB] shadow-sm"></div>
|
||||
<span className="text-[#6B7280] font-medium">Other</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Bottom Row */}
|
||||
|
||||
{/* Enhanced Your Courses (7 columns) */}
|
||||
<div className="col-span-12 lg:col-span-7">
|
||||
<Card className="border-0 shadow-md bg-white h-full">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827]">
|
||||
Your Courses
|
||||
</CardTitle>
|
||||
<div className="text-[14px] text-[#6B7280]">
|
||||
{courseList.length} Course{courseList.length !== 1 ? 's' : ''} • {courseList.filter(c => c.progress === 100).length} Completed
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-[14px] text-[#6B7280] hover:text-[#04045B] h-auto p-2 rounded-lg transition-colors"
|
||||
onClick={() => window.location.href = '/library?view=individual'}
|
||||
>
|
||||
<span className="mr-1">View All</span>
|
||||
<ArrowRight className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-0 pb-4">
|
||||
<div className="space-y-3">
|
||||
{courseList.map((course, index) => {
|
||||
const isCompleted = course.progress === 100;
|
||||
const isInProgress = course.progress > 0 && course.progress < 100;
|
||||
const isNotStarted = course.progress === 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="group relative flex items-center gap-4 p-3 hover:bg-gray-50 hover:shadow-sm rounded-lg transition-all duration-200 cursor-pointer border border-transparent hover:border-gray-200"
|
||||
onClick={() => window.location.href = '/course?view=individual'}
|
||||
>
|
||||
{/* Enhanced Course Avatar */}
|
||||
<div className="relative flex-shrink-0">
|
||||
<div
|
||||
className="w-8 h-8 rounded-lg flex items-center justify-center text-white text-[14px] font-semibold shadow-sm"
|
||||
style={{ backgroundColor: course.color }}
|
||||
>
|
||||
{course.name.charAt(0)}
|
||||
</div>
|
||||
{/* Status Indicator */}
|
||||
{isCompleted && (
|
||||
<div className="absolute -top-1 -right-1 w-3 h-3 bg-[#21A36A] rounded-full flex items-center justify-center">
|
||||
<CheckCircle className="w-2 h-2 text-white" />
|
||||
</div>
|
||||
)}
|
||||
{isInProgress && (
|
||||
<div className="absolute -top-1 -right-1 w-3 h-3 bg-[#F8C301] rounded-full animate-pulse"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Course Information */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between gap-2 mb-1">
|
||||
<h4 className="text-[16px] font-medium text-[#111827] truncate group-hover:text-[#04045B] transition-colors">
|
||||
{course.name}
|
||||
</h4>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
<span className="text-[14px] font-medium text-[#111827]">
|
||||
{course.progress}%
|
||||
</span>
|
||||
{isCompleted && (
|
||||
<Badge variant="outline" className="text-[12px] bg-[#21A36A]/10 text-[#21A36A] border-[#21A36A]/20 px-2 py-0.5">
|
||||
Complete
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<span className="text-[14px] text-[#6B7280]">
|
||||
{course.lessons}
|
||||
</span>
|
||||
<span className="text-[14px] text-[#6B7280]">
|
||||
• {isCompleted ? 'Completed' : isInProgress ? 'In Progress' : 'Not Started'}
|
||||
</span>
|
||||
{isInProgress && (
|
||||
<span className="text-[14px] text-[#F8C301] font-medium">
|
||||
• Continue Learning
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Enhanced Progress Bar */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1 bg-gray-100 rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
className="h-2 rounded-full transition-all duration-700 ease-out relative"
|
||||
style={{
|
||||
width: `${course.progress}%`,
|
||||
backgroundColor: course.color
|
||||
}}
|
||||
>
|
||||
{/* Progress bar shine effect */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-pulse"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Button */}
|
||||
<div className="flex-shrink-0">
|
||||
{isCompleted ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 px-3 text-[14px] border-[#21A36A]/20 text-[#21A36A] hover:bg-[#21A36A]/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.location.href = '/course?view=individual';
|
||||
}}
|
||||
>
|
||||
<Award className="w-3 h-3 mr-1" />
|
||||
Review
|
||||
</Button>
|
||||
) : isInProgress ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 px-3 text-[14px] border-[#F8C301]/30 text-[#04045B] hover:bg-[#F8C301]/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.location.href = '/course?view=individual';
|
||||
}}
|
||||
>
|
||||
<Play className="w-3 h-3 mr-1 stroke-[#04045B]" />
|
||||
Continue
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 px-3 text-[14px] border-[#04045B]/20 text-[#04045B] hover:bg-[#04045B]/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.location.href = '/course?view=individual';
|
||||
}}
|
||||
>
|
||||
<BookOpen className="w-3 h-3 mr-1" />
|
||||
Start
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hover Arrow */}
|
||||
<div className="flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||
<ArrowRight className="h-4 w-4 text-[#6B7280]" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Quick Action Footer */}
|
||||
<div className="pt-2 mt-4 border-t border-gray-100">
|
||||
<div className="flex items-center justify-between text-[14px]">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-[#21A36A] rounded-full"></div>
|
||||
<span className="text-[#6B7280]">Completed</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-[#F8C301] rounded-full"></div>
|
||||
<span className="text-[#6B7280]">In Progress</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-gray-300 rounded-full"></div>
|
||||
<span className="text-[#6B7280]">Not Started</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[14px] text-[#6B7280] hover:text-[#04045B] h-auto p-1"
|
||||
onClick={() => window.location.href = '/library?view=individual'}
|
||||
>
|
||||
<Target className="w-3 h-3 mr-1" />
|
||||
Browse Library
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Daily Activity (5 columns) */}
|
||||
<div className="col-span-12 lg:col-span-5">
|
||||
<Card className="border-0 shadow-md bg-white h-full">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827]">
|
||||
Daily activity
|
||||
</CardTitle>
|
||||
<div className="text-[14px] text-[#6B7280]">
|
||||
Today Apr 24
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-0 pb-4">
|
||||
<div className="space-y-2">
|
||||
{dailyActivities.map((activity, index) => (
|
||||
<div key={index} className="flex items-center gap-3">
|
||||
<div className="text-[12px] text-[#6B7280] w-10 text-right">
|
||||
{activity.time}
|
||||
</div>
|
||||
<div className="w-px h-8 bg-gray-200 relative">
|
||||
{activity.course && (
|
||||
<div className="absolute -left-1 top-1/2 transform -translate-y-1/2 w-2 h-2 rounded-full" style={{ backgroundColor: activity.color }}></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
{activity.course ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-[14px] font-medium text-[#111827] truncate">
|
||||
{activity.course}
|
||||
</div>
|
||||
<div className="text-[12px] text-[#6B7280]">
|
||||
{activity.type}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-6 h-6 rounded-full flex items-center justify-center text-white text-[10px] font-semibold flex-shrink-0" style={{ backgroundColor: activity.color }}>
|
||||
{activity.instructor}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-8"></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
src/main.tsx
17
src/main.tsx
@@ -1,12 +1,7 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App.tsx";
|
||||
import "./styles/globals.css";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./Redux/Store.tsx";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
);
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
|
||||
@@ -1,794 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { LearnerLayout } from '../components/learner/LearnerLayout';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Input } from '../components/ui/input';
|
||||
import { Badge } from '../components/ui/badge';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '../components/ui/avatar';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
|
||||
import { Progress } from '../components/ui/progress';
|
||||
import { Separator } from '../components/ui/separator';
|
||||
import { motion } from 'motion/react';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Trophy,
|
||||
Medal,
|
||||
Award,
|
||||
Target,
|
||||
TrendingUp,
|
||||
Users,
|
||||
Download,
|
||||
Filter,
|
||||
Search,
|
||||
Crown,
|
||||
Star,
|
||||
Gift,
|
||||
Zap,
|
||||
ChevronRight,
|
||||
Calendar,
|
||||
Clock,
|
||||
BookOpen,
|
||||
CheckCircle,
|
||||
BarChart3,
|
||||
PieChart,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
Minus,
|
||||
Settings,
|
||||
Flag,
|
||||
Sparkles
|
||||
} from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Mock data for leaderboard
|
||||
const mockLeaderboard = [
|
||||
{
|
||||
id: 1,
|
||||
rank: 1,
|
||||
name: "Alexandra Chen",
|
||||
title: "Senior Director",
|
||||
department: "Operations",
|
||||
avatar: "https://images.unsplash.com/photo-1494790108755-2616b612b786?w=150&h=150&fit=crop&crop=face",
|
||||
totalPoints: 2847,
|
||||
monthlyPoints: 485,
|
||||
coursesCompleted: 12,
|
||||
streakDays: 28,
|
||||
level: "Expert",
|
||||
progress: 85,
|
||||
trend: "up",
|
||||
badges: ["Leadership Excellence", "Team Builder", "Innovation Champion"],
|
||||
lastActive: "2 hours ago",
|
||||
completionRate: 94
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
rank: 2,
|
||||
name: "Michael Rodriguez",
|
||||
title: "VP Engineering",
|
||||
department: "Technology",
|
||||
avatar: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=150&h=150&fit=crop&crop=face",
|
||||
totalPoints: 2756,
|
||||
monthlyPoints: 423,
|
||||
coursesCompleted: 11,
|
||||
streakDays: 22,
|
||||
level: "Expert",
|
||||
progress: 76,
|
||||
trend: "up",
|
||||
badges: ["Tech Leader", "Mentor", "Strategic Thinker"],
|
||||
lastActive: "4 hours ago",
|
||||
completionRate: 89
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
rank: 3,
|
||||
name: "Sarah Johnson",
|
||||
title: "Director of Sales",
|
||||
department: "Sales",
|
||||
avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150&h=150&fit=crop&crop=face",
|
||||
totalPoints: 2634,
|
||||
monthlyPoints: 398,
|
||||
coursesCompleted: 10,
|
||||
streakDays: 19,
|
||||
level: "Advanced",
|
||||
progress: 68,
|
||||
trend: "up",
|
||||
badges: ["Sales Champion", "Customer Focus", "Results Driver"],
|
||||
lastActive: "1 hour ago",
|
||||
completionRate: 91
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
rank: 4,
|
||||
name: "David Kim",
|
||||
title: "Marketing Manager",
|
||||
department: "Marketing",
|
||||
avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop&crop=face",
|
||||
totalPoints: 2521,
|
||||
monthlyPoints: 367,
|
||||
coursesCompleted: 9,
|
||||
streakDays: 15,
|
||||
level: "Advanced",
|
||||
progress: 52,
|
||||
trend: "stable",
|
||||
badges: ["Creative Leader", "Brand Builder", "Analytics Pro"],
|
||||
lastActive: "6 hours ago",
|
||||
completionRate: 87
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
rank: 5,
|
||||
name: "Emily Watson",
|
||||
title: "HR Director",
|
||||
department: "Human Resources",
|
||||
avatar: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=150&h=150&fit=crop&crop=face",
|
||||
totalPoints: 2398,
|
||||
monthlyPoints: 334,
|
||||
coursesCompleted: 8,
|
||||
streakDays: 12,
|
||||
level: "Advanced",
|
||||
progress: 45,
|
||||
trend: "down",
|
||||
badges: ["People Leader", "Culture Champion", "Diversity Advocate"],
|
||||
lastActive: "3 hours ago",
|
||||
completionRate: 92
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
rank: 6,
|
||||
name: "James Thompson",
|
||||
title: "Finance Manager",
|
||||
department: "Finance",
|
||||
avatar: "https://images.unsplash.com/photo-1556157382-97eda2d62296?w=150&h=150&fit=crop&crop=face",
|
||||
totalPoints: 2267,
|
||||
monthlyPoints: 298,
|
||||
coursesCompleted: 7,
|
||||
streakDays: 8,
|
||||
level: "Intermediate",
|
||||
progress: 34,
|
||||
trend: "up",
|
||||
badges: ["Financial Acumen", "Risk Manager", "Process Optimizer"],
|
||||
lastActive: "5 hours ago",
|
||||
completionRate: 85
|
||||
}
|
||||
];
|
||||
|
||||
// Animated number counter component
|
||||
const AnimatedNumber = ({ value, duration = 1000 }: { value: number; duration?: number }) => {
|
||||
const [displayValue, setDisplayValue] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let startTime: number;
|
||||
let animationFrame: number;
|
||||
|
||||
const animate = (timestamp: number) => {
|
||||
if (!startTime) startTime = timestamp;
|
||||
const progress = Math.min((timestamp - startTime) / duration, 1);
|
||||
|
||||
// Easing function for smooth animation
|
||||
const easeOut = 1 - Math.pow(1 - progress, 3);
|
||||
setDisplayValue(Math.floor(value * easeOut));
|
||||
|
||||
if (progress < 1) {
|
||||
animationFrame = requestAnimationFrame(animate);
|
||||
}
|
||||
};
|
||||
|
||||
animationFrame = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
if (animationFrame) {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
}
|
||||
};
|
||||
}, [value, duration]);
|
||||
|
||||
return <span>{displayValue.toLocaleString()}</span>;
|
||||
};
|
||||
|
||||
export function CorporateLeaderboard() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedDepartment, setSelectedDepartment] = useState('all');
|
||||
const [selectedLevel, setSelectedLevel] = useState('all');
|
||||
const [sortBy, setSortBy] = useState('rank');
|
||||
const [timeFilter, setTimeFilter] = useState('month');
|
||||
|
||||
// Mock corporate user data matching dashboard pattern
|
||||
const user = {
|
||||
name: "Sarah Johnson",
|
||||
email: "sarah.johnson@company.com",
|
||||
avatar: "https://images.unsplash.com/photo-1494790108755-2616b612b786?w=150&h=150&fit=crop&crop=face",
|
||||
organization: "TCS",
|
||||
orgLogo: "https://images.unsplash.com/photo-1560179707-f14e90ef3623?w=32&h=32&fit=crop",
|
||||
role: "Senior Manager",
|
||||
cohort: "Leadership 2024"
|
||||
};
|
||||
|
||||
const getRankIcon = (rank: number) => {
|
||||
switch (rank) {
|
||||
case 1:
|
||||
return <Trophy className="h-8 w-8 text-[#F8C301]" />;
|
||||
case 2:
|
||||
return <Medal className="h-8 w-8 text-primary" />;
|
||||
case 3:
|
||||
return <Award className="h-8 w-8 text-[#26231A]" />;
|
||||
default:
|
||||
return <Target className="h-8 w-8 text-muted-foreground" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getTrendIcon = (trend: string) => {
|
||||
switch (trend) {
|
||||
case 'up':
|
||||
return <ArrowUp className="h-4 w-4 text-success" />;
|
||||
case 'down':
|
||||
return <ArrowDown className="h-4 w-4 text-destructive" />;
|
||||
default:
|
||||
return <Minus className="h-4 w-4 text-muted-foreground" />;
|
||||
}
|
||||
};
|
||||
|
||||
// Filter leaderboard data
|
||||
const filteredLeaderboard = mockLeaderboard.filter(user => {
|
||||
const matchesSearch = user.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
user.department.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
user.title.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
|
||||
const matchesDepartment = selectedDepartment === 'all' || user.department === selectedDepartment;
|
||||
const matchesLevel = selectedLevel === 'all' || user.level === selectedLevel;
|
||||
|
||||
return matchesSearch && matchesDepartment && matchesLevel;
|
||||
});
|
||||
|
||||
// Get unique departments and levels for filters
|
||||
const departments = Array.from(new Set(mockLeaderboard.map(user => user.department)));
|
||||
const levels = Array.from(new Set(mockLeaderboard.map(user => user.level)));
|
||||
|
||||
return (
|
||||
<LearnerLayout currentPage="/leaderboard" userType="corporate" user={user}>
|
||||
<div className="p-6 space-y-8">
|
||||
{/* KPI Cards - Matching Dashboard Design with Brand Colors */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<Card className="hover:shadow-lg transition-shadow duration-200">
|
||||
<CardContent className="p-6 text-center">
|
||||
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Users className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-foreground mb-1">128</p>
|
||||
<p className="text-base text-muted-foreground">Active Learners</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow duration-200">
|
||||
<CardContent className="p-6 text-center">
|
||||
<div className="w-12 h-12 bg-success/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<BookOpen className="h-6 w-6 text-success" />
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-foreground mb-1">1,247</p>
|
||||
<p className="text-base text-muted-foreground">Courses Completed</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow duration-200">
|
||||
<CardContent className="p-6 text-center">
|
||||
<div className="w-12 h-12 bg-[#F8C301]/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Trophy className="h-6 w-6 text-[#26231A]" />
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-foreground mb-1">89%</p>
|
||||
<p className="text-base text-muted-foreground">Avg Completion</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="hover:shadow-lg transition-shadow duration-200">
|
||||
<CardContent className="p-6 text-center">
|
||||
<div className="w-12 h-12 bg-[#26231A]/10 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<TrendingUp className="h-6 w-6 text-[#26231A]" />
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-foreground mb-1">+23%</p>
|
||||
<p className="text-base text-muted-foreground">This Month</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Time Period Filter - Enhanced Design */}
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-1 text-foreground">Performance Tracking</h2>
|
||||
<p className="text-base text-muted-foreground">View leaderboard data across different time periods</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={timeFilter === 'week' ? 'default' : 'outline'}
|
||||
onClick={() => setTimeFilter('week')}
|
||||
className="text-base h-11"
|
||||
>
|
||||
This Week
|
||||
</Button>
|
||||
<Button
|
||||
variant={timeFilter === 'month' ? 'default' : 'outline'}
|
||||
onClick={() => setTimeFilter('month')}
|
||||
className="text-base h-11"
|
||||
>
|
||||
This Month
|
||||
</Button>
|
||||
<Button
|
||||
variant={timeFilter === 'quarter' ? 'default' : 'outline'}
|
||||
onClick={() => setTimeFilter('quarter')}
|
||||
className="text-base h-11"
|
||||
>
|
||||
This Quarter
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Top Performers - Enhanced with KLC Branding */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-[#F8C301]/10 rounded-full flex items-center justify-center">
|
||||
<Crown className="h-5 w-5 text-[#26231A]" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-xl text-foreground">Top Performers This Month</CardTitle>
|
||||
<CardDescription className="text-base">
|
||||
Celebrating our highest achievers and their outstanding dedication
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{filteredLeaderboard.slice(0, 3).map((user, index) => {
|
||||
const isFirst = index === 0;
|
||||
const isSecond = index === 1;
|
||||
const isThird = index === 2;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={user.id}
|
||||
initial={{ opacity: 0, y: 30, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
transition={{
|
||||
duration: 0.6,
|
||||
delay: index * 0.15,
|
||||
ease: "easeOut"
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
y: -5,
|
||||
transition: { duration: 0.2 }
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<Card
|
||||
className={`relative overflow-hidden transition-all duration-300 hover:shadow-xl cursor-pointer group ${
|
||||
isFirst ? 'border-2 border-[#F8C301] bg-gradient-to-br from-[#F8C301]/5 to-[#F8C301]/10' :
|
||||
isSecond ? 'border-2 border-primary bg-gradient-to-br from-primary/5 to-primary/10' :
|
||||
'border-2 border-[#26231A] bg-gradient-to-br from-[#26231A]/5 to-[#26231A]/10'
|
||||
} ${isFirst ? 'lg:scale-105 lg:-translate-y-2' : ''}`}
|
||||
>
|
||||
{/* Subtle Metallic Shimmer Effect for Top 3 */}
|
||||
<div
|
||||
className={`absolute inset-0 opacity-0 group-hover:opacity-60 transition-opacity duration-1000 pointer-events-none shimmer-effect ${
|
||||
isFirst ? 'shimmer-gold' : isSecond ? 'shimmer-silver' : 'shimmer-bronze'
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Rank Ribbon with KLC Colors */}
|
||||
<motion.div
|
||||
className={`absolute top-0 right-0 px-3 py-1 rounded-bl-lg text-white text-sm font-medium ${
|
||||
isFirst ? 'bg-[#F8C301] text-[#26231A]' :
|
||||
isSecond ? 'bg-primary text-white' :
|
||||
'bg-[#26231A] text-white'
|
||||
}`}
|
||||
initial={{ x: 20, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
transition={{ delay: index * 0.15 + 0.3 }}
|
||||
>
|
||||
#{user.rank}
|
||||
</motion.div>
|
||||
|
||||
{/* Winner Crown for First Place */}
|
||||
{isFirst && (
|
||||
<motion.div
|
||||
className="absolute top-4 left-4"
|
||||
initial={{ rotate: -10, scale: 0 }}
|
||||
animate={{
|
||||
rotate: 0,
|
||||
scale: 1,
|
||||
y: [0, -2, 0],
|
||||
rotateZ: [0, 2, -2, 0]
|
||||
}}
|
||||
transition={{
|
||||
delay: 0.5,
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 10,
|
||||
y: {
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
},
|
||||
rotateZ: {
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Crown className="h-6 w-6 text-[#F8C301]" />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<CardContent className="p-8 text-center">
|
||||
{/* Rank Icon */}
|
||||
<motion.div
|
||||
className="flex justify-center mb-6"
|
||||
initial={{ scale: 0, rotate: -180 }}
|
||||
animate={{ scale: 1, rotate: 0 }}
|
||||
transition={{
|
||||
delay: index * 0.15 + 0.2,
|
||||
type: "spring",
|
||||
stiffness: 150
|
||||
}}
|
||||
>
|
||||
<div className={`p-4 rounded-full ${
|
||||
isFirst ? 'bg-[#F8C301]/10' : isSecond ? 'bg-primary/10' : 'bg-[#26231A]/10'
|
||||
}`}>
|
||||
{getRankIcon(user.rank)}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* User Avatar with Enhanced Styling */}
|
||||
<motion.div
|
||||
className="relative mb-6"
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{
|
||||
delay: index * 0.15 + 0.4,
|
||||
type: "spring",
|
||||
stiffness: 200
|
||||
}}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
>
|
||||
<Avatar className="w-24 h-24 mx-auto border-4 border-white shadow-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="text-xl">
|
||||
{user.name.split(' ').map(n => n[0]).join('')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{/* Trend Indicator */}
|
||||
<motion.div
|
||||
className="absolute -bottom-2 -right-2 p-1 bg-white rounded-full shadow-md"
|
||||
initial={{ scale: 0 }}
|
||||
animate={{
|
||||
scale: [1, 1.1, 1]
|
||||
}}
|
||||
transition={{
|
||||
delay: index * 0.15 + 0.6,
|
||||
scale: {
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
}
|
||||
}}
|
||||
>
|
||||
{getTrendIcon(user.trend)}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* User Information */}
|
||||
<motion.div
|
||||
className="mb-6"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.15 + 0.5 }}
|
||||
>
|
||||
<h3 className="text-xl mb-2 text-foreground">{user.name}</h3>
|
||||
<p className="text-base text-muted-foreground mb-1">{user.title}</p>
|
||||
<p className="text-base text-muted-foreground">{user.department}</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Performance Stats */}
|
||||
<motion.div
|
||||
className="grid grid-cols-3 gap-4 mb-6 p-4 bg-white/50 rounded-lg backdrop-blur-sm"
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: index * 0.15 + 0.7 }}
|
||||
>
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-foreground">
|
||||
<AnimatedNumber value={user.totalPoints} duration={1500} />
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Points</p>
|
||||
</div>
|
||||
<div className="text-center border-x border-border/50">
|
||||
<p className="text-2xl font-bold text-foreground">
|
||||
<AnimatedNumber value={user.coursesCompleted} duration={1200} />
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Courses</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-2xl font-bold text-foreground">
|
||||
<AnimatedNumber value={user.streakDays} duration={1000} />
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Day Streak</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Level Progress */}
|
||||
<motion.div
|
||||
className="mb-6"
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.15 + 0.8 }}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-base text-muted-foreground">Level Progress</span>
|
||||
<Badge variant="outline" className="text-sm font-medium">
|
||||
{user.level}
|
||||
</Badge>
|
||||
</div>
|
||||
<Progress value={user.progress} className="h-2" />
|
||||
<p className="text-sm text-muted-foreground mt-1">{user.progress}% to next level</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Achievement Badges */}
|
||||
<motion.div
|
||||
className="space-y-3"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.15 + 0.9 }}
|
||||
>
|
||||
<p className="text-base text-muted-foreground">Recent Achievements</p>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{user.badges.slice(0, 3).map((badge, badgeIndex) => (
|
||||
<motion.div
|
||||
key={badge}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{
|
||||
delay: index * 0.15 + 1 + (badgeIndex * 0.1)
|
||||
}}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-sm px-3 py-1 ${
|
||||
badgeIndex === 0 ? 'border-[#F8C301]/30 bg-[#F8C301]/5 text-[#26231A]' :
|
||||
badgeIndex === 1 ? 'border-primary/30 bg-primary/5 text-primary' :
|
||||
'border-success/30 bg-success/5 text-success'
|
||||
}`}
|
||||
>
|
||||
{badge}
|
||||
</Badge>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Monthly Performance Indicator */}
|
||||
<motion.div
|
||||
className="mt-6 pt-4 border-t border-border/50"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.15 + 1.1 }}
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<TrendingUp className="h-4 w-4 text-success" />
|
||||
<span className="text-base text-success font-medium">
|
||||
+<AnimatedNumber value={user.monthlyPoints} duration={1800} /> this month
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Search and Filters - Matching Dashboard Design */}
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
{/* Search Bar */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search by name, department, or title..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 text-base h-11"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Filters Row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Select value={selectedDepartment} onValueChange={setSelectedDepartment}>
|
||||
<SelectTrigger className="text-base h-11">
|
||||
<SelectValue placeholder="All Departments" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Departments</SelectItem>
|
||||
{departments.map(dept => (
|
||||
<SelectItem key={dept} value={dept}>{dept}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={selectedLevel} onValueChange={setSelectedLevel}>
|
||||
<SelectTrigger className="text-base h-11">
|
||||
<SelectValue placeholder="All Levels" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Levels</SelectItem>
|
||||
{levels.map(level => (
|
||||
<SelectItem key={level} value={level}>{level}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={sortBy} onValueChange={setSortBy}>
|
||||
<SelectTrigger className="text-base h-11">
|
||||
<SelectValue placeholder="Sort By" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="rank">Rank</SelectItem>
|
||||
<SelectItem value="points">Total Points</SelectItem>
|
||||
<SelectItem value="courses">Courses Completed</SelectItem>
|
||||
<SelectItem value="streak">Learning Streak</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-base h-11"
|
||||
onClick={() => {
|
||||
setSearchQuery('');
|
||||
setSelectedDepartment('all');
|
||||
setSelectedLevel('all');
|
||||
}}
|
||||
>
|
||||
Clear Filters
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Full Leaderboard Table - Enhanced Design */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-primary/10 rounded-full flex items-center justify-center">
|
||||
<Trophy className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-xl text-foreground">Complete Leaderboard</CardTitle>
|
||||
<p className="text-base text-muted-foreground mt-1">
|
||||
View all team members and their performance metrics
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-base">
|
||||
{filteredLeaderboard.length} {filteredLeaderboard.length === 1 ? 'learner' : 'learners'}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4 !mt-0 !pt-0">
|
||||
{filteredLeaderboard.map((user, index) => (
|
||||
<motion.div
|
||||
key={user.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="group cursor-pointer p-4 rounded-lg border border-border hover:border-primary/30 hover:bg-muted/30 transition-all duration-200"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Rank */}
|
||||
<div className="flex items-center justify-center w-12 h-12 rounded-full bg-muted flex-shrink-0">
|
||||
<div className="flex items-center justify-center w-8 h-8">
|
||||
{user.rank <= 3 ? getRankIcon(user.rank) : (
|
||||
<span className="text-lg font-bold text-foreground">#{user.rank}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Avatar and User Info */}
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<Avatar className="w-10 h-10 flex-shrink-0">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="text-sm bg-primary/10 text-primary">
|
||||
{user.name.split(' ').map(n => n[0]).join('')}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-base font-medium text-foreground group-hover:text-primary transition-colors truncate">
|
||||
{user.name}
|
||||
</p>
|
||||
{getTrendIcon(user.trend)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>{user.title}</span>
|
||||
<span>•</span>
|
||||
<span>{user.department}</span>
|
||||
<span>•</span>
|
||||
<Badge variant="outline" className="text-sm">
|
||||
{user.level}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Performance Stats */}
|
||||
<div className="hidden md:flex items-center gap-6 text-sm">
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.totalPoints.toLocaleString()}</p>
|
||||
<p className="text-muted-foreground">Points</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.coursesCompleted}</p>
|
||||
<p className="text-muted-foreground">Courses</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.streakDays}</p>
|
||||
<p className="text-muted-foreground">Streak</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.completionRate}%</p>
|
||||
<p className="text-muted-foreground">Complete</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action */}
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground group-hover:text-primary group-hover:translate-x-1 transition-all flex-shrink-0" />
|
||||
</div>
|
||||
|
||||
{/* Mobile Stats */}
|
||||
<div className="md:hidden mt-4 grid grid-cols-4 gap-3 text-sm">
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.totalPoints.toLocaleString()}</p>
|
||||
<p className="text-muted-foreground">Points</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.coursesCompleted}</p>
|
||||
<p className="text-muted-foreground">Courses</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.streakDays}</p>
|
||||
<p className="text-muted-foreground">Streak</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="font-bold text-foreground">{user.completionRate}%</p>
|
||||
<p className="text-muted-foreground">Complete</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</LearnerLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,954 +0,0 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { LearnerLayout } from '../components/learner/LearnerLayout';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Input } from '../components/ui/input';
|
||||
import { Label } from '../components/ui/label';
|
||||
import { Textarea } from '../components/ui/textarea';
|
||||
import { RadioGroup, RadioGroupItem } from '../components/ui/radio-group';
|
||||
import { Checkbox } from '../components/ui/checkbox';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
|
||||
import { Badge } from '../components/ui/badge';
|
||||
import { Progress } from '../components/ui/progress';
|
||||
import { Alert, AlertDescription } from '../components/ui/alert';
|
||||
import { Separator } from '../components/ui/separator';
|
||||
import {
|
||||
FileText,
|
||||
Clock,
|
||||
Users,
|
||||
ChevronRight,
|
||||
Star,
|
||||
TrendingUp,
|
||||
BarChart3,
|
||||
MessageSquare,
|
||||
AlertCircle,
|
||||
CheckCircle,
|
||||
Calendar,
|
||||
Target,
|
||||
Zap,
|
||||
ThumbsUp,
|
||||
ThumbsDown,
|
||||
Send,
|
||||
Eye,
|
||||
Award,
|
||||
Lightbulb,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
MapPin,
|
||||
Globe,
|
||||
ExternalLink
|
||||
} from 'lucide-react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
|
||||
// import exampleImage from 'figma:asset/c501c3d3f3a828828d4cb2dadb9558b43986718f.png';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
const exampleImage = 'https://via.placeholder.com/600x400?text=Example+Image';
|
||||
|
||||
interface SurveyProps {
|
||||
userType: 'individual' | 'corporate';
|
||||
}
|
||||
|
||||
interface Survey {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
type: 'self-assessment' | 'feedback' | 'evaluation';
|
||||
duration: string;
|
||||
questions: number;
|
||||
status: 'available' | 'in-progress' | 'completed' | 'pending';
|
||||
dueDate?: string;
|
||||
completedDate?: string;
|
||||
score?: number;
|
||||
feedback?: string;
|
||||
participants?: number;
|
||||
responses?: number;
|
||||
certificateEarned?: boolean;
|
||||
}
|
||||
|
||||
interface LearningInsights {
|
||||
averageScore: number;
|
||||
totalSurveys: number;
|
||||
completedSurveys: number;
|
||||
strengths: string[];
|
||||
developmentAreas: string[];
|
||||
nextRecommendations: string[];
|
||||
progressTrend: 'improving' | 'stable' | 'needs-attention';
|
||||
}
|
||||
|
||||
// Mock data for academic dashboard
|
||||
const dashboardData = {
|
||||
satisfactionScore: {
|
||||
current: 78,
|
||||
previous: 74,
|
||||
trend: 5.4,
|
||||
data: [
|
||||
{ date: 'Week 2', score: 70, previousScore: 68 },
|
||||
{ date: 'Week 4', score: 75, previousScore: 72 },
|
||||
{ date: 'Week 6', score: 78, previousScore: 74 },
|
||||
{ date: 'Week 8', score: 82, previousScore: 77 },
|
||||
{ date: 'Week 10', score: 80, previousScore: 76 },
|
||||
{ date: 'Week 12', score: 78, previousScore: 74 }
|
||||
]
|
||||
},
|
||||
netPromoterScore: {
|
||||
current: 82,
|
||||
previous: 78,
|
||||
trend: 5.1,
|
||||
promoters: 85,
|
||||
passives: 12,
|
||||
detractors: 8,
|
||||
data: [
|
||||
{ date: 'Week 2', promoters: 78, passives: 15, detractors: 12 },
|
||||
{ date: 'Week 4', promoters: 80, passives: 14, detractors: 10 },
|
||||
{ date: 'Week 6', promoters: 82, passives: 13, detractors: 9 },
|
||||
{ date: 'Week 8', promoters: 85, passives: 12, detractors: 8 },
|
||||
{ date: 'Week 10', promoters: 87, passives: 11, detractors: 7 },
|
||||
{ date: 'Week 12', promoters: 85, passives: 12, detractors: 8 }
|
||||
]
|
||||
},
|
||||
responsesFeedback: [
|
||||
{
|
||||
id: 1,
|
||||
user: 'Jennifer Schmidt',
|
||||
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=40&h=40&fit=crop&crop=face',
|
||||
feedback: 'The midterm exam format was fair and comprehensive. The case study questions really tested our understanding.',
|
||||
timeAgo: '2 days ago',
|
||||
rating: 5
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: 'Michael Kim',
|
||||
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=40&h=40&fit=crop&crop=face',
|
||||
feedback: 'Appreciate the detailed feedback on my leadership assessment. The rubric was clearly explained.',
|
||||
timeAgo: '1 week ago',
|
||||
rating: 4
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
user: 'Maria Rodriguez',
|
||||
avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=40&h=40&fit=crop&crop=face',
|
||||
feedback: 'The group project evaluation process was transparent. Would like more peer review opportunities.',
|
||||
timeAgo: '1 week ago',
|
||||
rating: 4
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
user: 'David Thompson',
|
||||
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=40&h=40&fit=crop&crop=face',
|
||||
feedback: 'Research paper guidelines were comprehensive. The academic writing workshop really helped prepare us.',
|
||||
timeAgo: '2 weeks ago',
|
||||
rating: 5
|
||||
}
|
||||
],
|
||||
learnersByLocation: [
|
||||
{ country: 'United States', count: 145, flag: '🇺🇸', activity: 'Taking Assessment', coords: { x: 25, y: 35 }, status: 'active' },
|
||||
{ country: 'United Kingdom', count: 98, flag: '🇬🇧', activity: 'Reviewing Results', coords: { x: 48, y: 28 }, status: 'active' },
|
||||
{ country: 'Germany', count: 87, flag: '🇩🇪', activity: 'Course Progress', coords: { x: 52, y: 30 }, status: 'studying' },
|
||||
{ country: 'Singapore', count: 76, flag: '🇸🇬', activity: 'Live Session', coords: { x: 75, y: 52 }, status: 'online' },
|
||||
{ country: 'Australia', count: 65, flag: '🇦🇺', activity: 'Assignment Due', coords: { x: 82, y: 72 }, status: 'pending' },
|
||||
{ country: 'Canada', count: 54, flag: '🇨🇦', activity: 'Group Project', coords: { x: 22, y: 25 }, status: 'collaboration' },
|
||||
{ country: 'India', count: 43, flag: '🇮🇳', activity: 'Webinar Prep', coords: { x: 68, y: 45 }, status: 'preparing' },
|
||||
{ country: 'Japan', count: 38, flag: '🇯🇵', activity: 'Peer Review', coords: { x: 82, y: 40 }, status: 'reviewing' }
|
||||
]
|
||||
};
|
||||
|
||||
// Mock data for academic assessments and insights
|
||||
const mockData = {
|
||||
insights: {
|
||||
averageScore: 81,
|
||||
totalSurveys: 8,
|
||||
completedSurveys: 5,
|
||||
strengths: [
|
||||
'Theoretical Understanding',
|
||||
'Case Study Analysis',
|
||||
'Academic Writing',
|
||||
'Critical Thinking'
|
||||
],
|
||||
developmentAreas: [
|
||||
'Quantitative Analysis',
|
||||
'Research Methodology',
|
||||
'Group Presentation Skills'
|
||||
],
|
||||
nextRecommendations: [
|
||||
'Focus on statistical analysis methods for your final research paper',
|
||||
'Attend the academic writing workshop to improve citation and structure',
|
||||
'Review leadership frameworks for the comprehensive final examination'
|
||||
],
|
||||
progressTrend: 'improving' as const
|
||||
},
|
||||
availableSurveys: [
|
||||
{
|
||||
id: 'exam-1',
|
||||
title: 'Midterm Examination',
|
||||
description: 'Comprehensive examination covering weeks 1-7 course material including leadership theories and frameworks',
|
||||
category: 'Formal Assessment',
|
||||
type: 'evaluation' as const,
|
||||
duration: '120 min',
|
||||
questions: 60,
|
||||
status: 'available' as const,
|
||||
dueDate: '2024-03-15'
|
||||
},
|
||||
{
|
||||
id: 'assignment-2',
|
||||
title: 'Research Paper Draft Submission',
|
||||
description: 'Submit first draft of your 3000-word research paper on organizational leadership challenges',
|
||||
category: 'Research Assignment',
|
||||
type: 'self-assessment' as const,
|
||||
duration: '2-3 weeks',
|
||||
questions: 1,
|
||||
status: 'in-progress' as const,
|
||||
dueDate: '2024-03-22'
|
||||
},
|
||||
{
|
||||
id: 'quiz-3',
|
||||
title: 'Weekly Quiz: Communication Theory',
|
||||
description: 'Assessment on communication models and leadership communication strategies from Week 8',
|
||||
category: 'Weekly Assessment',
|
||||
type: 'evaluation' as const,
|
||||
duration: '30 min',
|
||||
questions: 25,
|
||||
status: 'pending' as const,
|
||||
dueDate: '2024-03-18'
|
||||
}
|
||||
],
|
||||
completedSurveys: [
|
||||
{
|
||||
id: 'completed-1',
|
||||
title: 'Leadership Theory Examination',
|
||||
description: 'Comprehensive assessment of foundational leadership theories and their practical applications',
|
||||
category: 'Course Examination',
|
||||
type: 'evaluation' as const,
|
||||
duration: '90 min',
|
||||
questions: 45,
|
||||
status: 'completed' as const,
|
||||
completedDate: '2024-02-20',
|
||||
score: 87,
|
||||
feedback: 'Excellent grasp of theoretical concepts. Strong application to case studies. Consider exploring modern leadership paradigms.',
|
||||
participants: 32,
|
||||
responses: 32,
|
||||
certificateEarned: false
|
||||
},
|
||||
{
|
||||
id: 'completed-2',
|
||||
title: 'Group Project Evaluation',
|
||||
description: 'Collaborative assessment of team leadership dynamics and strategic planning exercise',
|
||||
category: 'Group Assignment',
|
||||
type: 'evaluation' as const,
|
||||
duration: '3 weeks',
|
||||
questions: 5,
|
||||
status: 'completed' as const,
|
||||
completedDate: '2024-02-28',
|
||||
score: 83,
|
||||
feedback: 'Strong collaborative approach and well-structured presentation. Peer evaluations were consistently positive.',
|
||||
participants: 6,
|
||||
responses: 6,
|
||||
certificateEarned: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Survey Card Component (Enhanced)
|
||||
function SurveyCard({
|
||||
survey,
|
||||
userType,
|
||||
onStart,
|
||||
onContinue,
|
||||
onView
|
||||
}: {
|
||||
survey: Survey;
|
||||
userType: 'individual' | 'corporate';
|
||||
onStart: (id: string) => void;
|
||||
onContinue: (id: string) => void;
|
||||
onView: (id: string) => void;
|
||||
}) {
|
||||
const getStatusInfo = (status: string) => {
|
||||
switch (status) {
|
||||
case 'available':
|
||||
return {
|
||||
label: 'AVAILABLE',
|
||||
className: 'inline-flex items-center px-2 py-1 text-[12px] bg-primary/10 text-primary rounded-md border border-primary/20'
|
||||
};
|
||||
case 'in-progress':
|
||||
return {
|
||||
label: 'IN PROGRESS',
|
||||
className: 'inline-flex items-center px-2 py-1 text-[12px] bg-orange-100 text-orange-700 rounded-md border border-orange-200'
|
||||
};
|
||||
case 'pending':
|
||||
return {
|
||||
label: 'PENDING',
|
||||
className: 'inline-flex items-center px-2 py-1 text-[12px] bg-yellow-100 text-yellow-700 rounded-md border border-yellow-200'
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getActionButton = () => {
|
||||
switch (survey.status) {
|
||||
case 'available':
|
||||
return (
|
||||
<Button
|
||||
onClick={() => onStart(survey.id)}
|
||||
className="text-[16px] min-h-[44px] w-full transition-colors duration-200"
|
||||
>
|
||||
Begin Assessment
|
||||
<ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
);
|
||||
case 'in-progress':
|
||||
return (
|
||||
<Button
|
||||
onClick={() => onContinue(survey.id)}
|
||||
variant="outline"
|
||||
className="text-[16px] min-h-[44px] w-full transition-colors duration-200"
|
||||
>
|
||||
Continue
|
||||
<ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
);
|
||||
case 'pending':
|
||||
return (
|
||||
<Button
|
||||
disabled
|
||||
variant="outline"
|
||||
className="text-[16px] min-h-[44px] w-full"
|
||||
>
|
||||
Pending Approval
|
||||
</Button>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const statusInfo = getStatusInfo(survey.status);
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col h-full hover:shadow-lg transition-all duration-300 border-gray-200 shadow-md">
|
||||
<CardHeader className="flex-shrink-0 pb-3">
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{statusInfo && (
|
||||
<span className={statusInfo.className}>
|
||||
{statusInfo.label}
|
||||
</span>
|
||||
)}
|
||||
<span className="inline-flex items-center px-2 py-1 text-[12px] bg-muted text-muted-foreground rounded-md">
|
||||
{survey.type.replace('-', ' ').toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-[18px] leading-tight mb-2">{survey.title}</CardTitle>
|
||||
<CardDescription className="text-[16px] line-clamp-2">
|
||||
{survey.description}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="flex-1 flex flex-col justify-between pt-0 space-y-3">
|
||||
<div className="space-y-2">
|
||||
<div className="grid grid-cols-1 gap-2 text-[16px]">
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="truncate">{survey.duration}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<MessageSquare className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="truncate">{survey.questions} questions</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="truncate">{survey.category}</span>
|
||||
</div>
|
||||
{survey.dueDate && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="truncate">Due {new Date(survey.dueDate).toLocaleDateString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
{getActionButton()}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function Surveys({ userType = 'individual' }: SurveyProps) {
|
||||
const [activeTab, setActiveTab] = useState<'available' | 'completed'>('available');
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Mock user data for LearnerLayout - matching Library page pattern
|
||||
const user = {
|
||||
name: "Priya Sharma",
|
||||
email: userType === 'corporate' ? "priya.sharma@company.com" : "priya.sharma@example.com",
|
||||
avatar: "https://images.unsplash.com/photo-1494790108755-2616b612b786?w=150&h=150&fit=crop&crop=face",
|
||||
...(userType === 'corporate' && {
|
||||
organization: "TCS",
|
||||
orgLogo: "https://images.unsplash.com/photo-1560179707-f14e90ef3623?w=32&h=32&fit=crop",
|
||||
role: "Senior Manager",
|
||||
cohort: "Leadership 2024"
|
||||
})
|
||||
};
|
||||
|
||||
const handleStartSurvey = (surveyId: string) => {
|
||||
console.log('Starting survey:', surveyId);
|
||||
// Implementation for starting survey
|
||||
};
|
||||
|
||||
const handleContinueSurvey = (surveyId: string) => {
|
||||
console.log('Continuing survey:', surveyId);
|
||||
// Implementation for continuing survey
|
||||
};
|
||||
|
||||
const handleViewResults = (surveyId: string) => {
|
||||
console.log('Viewing results for survey:', surveyId);
|
||||
// Implementation for viewing survey results
|
||||
};
|
||||
|
||||
return (
|
||||
<LearnerLayout
|
||||
userType={userType}
|
||||
currentPage="/surveys"
|
||||
user={user}
|
||||
>
|
||||
{/* Enhanced Page Header */}
|
||||
<div className="bg-background border-b border-border mb-3">
|
||||
<div className="w-full max-w-none px-2 sm:px-4 lg:px-6">
|
||||
<div className="py-8">
|
||||
<div className="animate-fade-in" style={{ animationDelay: '0.1s' }}>
|
||||
<h1 className="text-[36px] mb-4 relative">
|
||||
Course Assessments & Examinations
|
||||
<div className="absolute -bottom-2 left-0 w-32 h-1 bg-gradient-to-r from-brand-gold to-yellow-400 rounded-full opacity-60"></div>
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-[16px] text-neutral-700 leading-relaxed animate-fade-in" style={{ animationDelay: '0.2s' }}>
|
||||
{userType === 'corporate'
|
||||
? 'Complete course examinations, assignments, and academic assessments for your leadership development program'
|
||||
: 'Complete course examinations, assignments, and academic assessments for your leadership studies'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content with Dashboard Layout */}
|
||||
<div className="w-full max-w-none px-2 sm:px-4 lg:px-6 pb-8">
|
||||
<div className="space-y-6">
|
||||
{/* Dashboard Layout - 2x2 Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Satisfaction Score Chart - Top Left */}
|
||||
<Card className="bg-white border-gray-200 shadow-lg hover:shadow-xl transition-all duration-300 rounded-lg">
|
||||
<CardHeader className="pb-4 border-b border-gray-100">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827]">
|
||||
Course Performance Average
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[14px] text-[#6B7280]">Last 30 days</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-[32px] font-bold text-[#111827]">
|
||||
{dashboardData.satisfactionScore.current}%
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<ArrowUp className="h-4 w-4 text-green-500" />
|
||||
<span className="text-[14px] text-red-500 font-medium">
|
||||
{Math.abs(dashboardData.satisfactionScore.trend)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[14px] text-[#6B7280] mb-4">
|
||||
Previous semester average
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="h-48">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={dashboardData.satisfactionScore.data}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tick={{ fontSize: 12, fill: '#6B7280' }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
tick={{ fontSize: 12, fill: '#6B7280' }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="score"
|
||||
stroke="#3b82f6"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#3b82f6', strokeWidth: 2, r: 4 }}
|
||||
activeDot={{ r: 6 }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="previousScore"
|
||||
stroke="#9ca3af"
|
||||
strokeWidth={2}
|
||||
strokeDasharray="5 5"
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Net Promoter Score Chart - Top Right */}
|
||||
<Card className="bg-white border-gray-200 shadow-lg hover:shadow-xl transition-all duration-300 rounded-lg">
|
||||
<CardHeader className="pb-4 border-b border-gray-100">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827]">
|
||||
Academic Progress Tracking
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-[#10b981] rounded-full"></div>
|
||||
<span className="text-[12px] text-[#6B7280]">Excellent (A)</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-[#f59e0b] rounded-full"></div>
|
||||
<span className="text-[12px] text-[#6B7280]">Good (B)</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-[#ef4444] rounded-full"></div>
|
||||
<span className="text-[12px] text-[#6B7280]">Needs Improvement</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-[32px] font-bold text-[#111827]">
|
||||
{dashboardData.netPromoterScore.current}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<ArrowUp className="h-4 w-4 text-green-500" />
|
||||
<span className="text-[14px] text-green-500 font-medium">
|
||||
{dashboardData.netPromoterScore.trend}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[14px] text-[#6B7280] mb-4">
|
||||
Current semester
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="h-48">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={dashboardData.netPromoterScore.data}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tick={{ fontSize: 12, fill: '#6B7280' }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
tick={{ fontSize: 12, fill: '#6B7280' }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="promoters"
|
||||
stroke="#10b981"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#10b981', strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="passives"
|
||||
stroke="#f59e0b"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#f59e0b', strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="detractors"
|
||||
stroke="#ef4444"
|
||||
strokeWidth={2}
|
||||
dot={{ fill: '#ef4444', strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Responses from Users - Bottom Left */}
|
||||
<Card className="bg-white border-gray-200 shadow-lg hover:shadow-xl transition-all duration-300 rounded-lg">
|
||||
<CardHeader className="pb-4 border-b border-gray-100">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827]">
|
||||
Academic Feedback & Reviews
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[14px] text-[#6B7280] hover:text-[#111827] hover:bg-gray-100 rounded-md h-8 px-2"
|
||||
>
|
||||
View All
|
||||
<ExternalLink className="h-3 w-3 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4">
|
||||
<div className="space-y-4">
|
||||
{dashboardData.responsesFeedback.map((response) => (
|
||||
<div key={response.id} className="flex items-start gap-3">
|
||||
<img
|
||||
src={response.avatar}
|
||||
alt={response.user}
|
||||
className="w-8 h-8 rounded-full object-cover"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-[14px] font-medium text-[#111827]">{response.user}</span>
|
||||
<div className="flex items-center gap-1">
|
||||
{Array.from({ length: response.rating }).map((_, i) => (
|
||||
<Star key={i} className="h-3 w-3 fill-yellow-400 text-yellow-400" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[14px] text-[#6B7280] line-clamp-2 mb-1">
|
||||
{response.feedback}
|
||||
</p>
|
||||
<span className="text-[12px] text-[#9CA3AF]">{response.timeAgo}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Global Learning Activity Heatmap - Bottom Right */}
|
||||
<Card className="bg-white border-gray-200 shadow-lg hover:shadow-xl transition-all duration-300 rounded-lg">
|
||||
<CardHeader className="pb-4 border-b border-gray-100">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-[16px] font-semibold text-[#111827]">
|
||||
Global Learning Activity
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[14px] text-[#6B7280] hover:text-[#111827] hover:bg-gray-100 rounded-md h-8 px-2"
|
||||
>
|
||||
View All
|
||||
<ExternalLink className="h-3 w-3 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4">
|
||||
{/* Interactive World Map Heatmap */}
|
||||
<div className="mb-6">
|
||||
<div className="relative w-full h-40 bg-gradient-to-br from-slate-50 to-blue-50 rounded-lg border border-gray-200 overflow-hidden">
|
||||
{/* World map background */}
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
<svg viewBox="0 0 100 60" className="w-full h-full">
|
||||
{/* Simplified world continents */}
|
||||
<path d="M15 25 L35 20 L40 35 L25 40 Z" fill="#94a3b8" opacity="0.3" />
|
||||
<path d="M45 20 L65 18 L70 35 L50 38 Z" fill="#94a3b8" opacity="0.3" />
|
||||
<path d="M70 25 L85 22 L90 45 L75 48 Z" fill="#94a3b8" opacity="0.3" />
|
||||
<path d="M20 45 L35 42 L40 55 L25 58 Z" fill="#94a3b8" opacity="0.3" />
|
||||
<path d="M75 50 L88 48 L92 58 L78 60 Z" fill="#94a3b8" opacity="0.3" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Activity points on map */}
|
||||
{dashboardData.learnersByLocation.map((location, index) => {
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'active': return 'bg-green-500';
|
||||
case 'studying': return 'bg-blue-500';
|
||||
case 'online': return 'bg-purple-500';
|
||||
case 'pending': return 'bg-orange-500';
|
||||
case 'collaboration': return 'bg-yellow-500';
|
||||
case 'preparing': return 'bg-indigo-500';
|
||||
case 'reviewing': return 'bg-pink-500';
|
||||
default: return 'bg-gray-500';
|
||||
}
|
||||
};
|
||||
|
||||
const getActivitySize = (count: number) => {
|
||||
if (count > 100) return 'w-4 h-4';
|
||||
if (count > 50) return 'w-3 h-3';
|
||||
return 'w-2 h-2';
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={location.country}
|
||||
className={`absolute ${getStatusColor(location.status)} ${getActivitySize(location.count)} rounded-full animate-pulse cursor-pointer group`}
|
||||
style={{
|
||||
left: `${location.coords.x}%`,
|
||||
top: `${location.coords.y}%`,
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}}
|
||||
title={`${location.country}: ${location.count} students - ${location.activity}`}
|
||||
>
|
||||
{/* Pulse ring */}
|
||||
<div className={`absolute inset-0 ${getStatusColor(location.status)} rounded-full animate-ping opacity-30`}></div>
|
||||
|
||||
{/* Hover tooltip */}
|
||||
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-black text-white text-[10px] rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap z-10">
|
||||
<div className="font-medium">{location.country}</div>
|
||||
<div className="text-white/80">{location.count} students</div>
|
||||
<div className="text-white/80">{location.activity}</div>
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-2 border-transparent border-t-black"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Activity Legend */}
|
||||
<div className="mb-4">
|
||||
<h4 className="text-[14px] font-medium text-[#111827] mb-3">Live Activity Status</h4>
|
||||
<div className="grid grid-cols-2 gap-2 text-[12px]">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span className="text-[#6B7280]">Taking Assessment</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<span className="text-[#6B7280]">Course Progress</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-purple-500 rounded-full"></div>
|
||||
<span className="text-[#6B7280]">Live Session</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-orange-500 rounded-full"></div>
|
||||
<span className="text-[#6B7280]">Assignment Due</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top Countries List */}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-[14px] font-medium text-[#111827] mb-2">Top Active Regions</h4>
|
||||
{dashboardData.learnersByLocation.slice(0, 4).map((country, index) => (
|
||||
<div key={country.country} className="flex items-center justify-between py-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[14px]">{country.flag}</span>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[13px] font-medium text-[#111827]">{country.country}</span>
|
||||
<span className="text-[11px] text-[#6B7280]">{country.activity}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-[13px] font-semibold text-[#111827]">{country.count}</div>
|
||||
<div className={`text-[10px] px-1.5 py-0.5 rounded-full text-white ${
|
||||
country.status === 'active' ? 'bg-green-500' :
|
||||
country.status === 'studying' ? 'bg-blue-500' :
|
||||
country.status === 'online' ? 'bg-purple-500' :
|
||||
country.status === 'pending' ? 'bg-orange-500' :
|
||||
'bg-gray-500'
|
||||
}`}>
|
||||
{country.status}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Survey Tabs */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||
{/* Enhanced Segmented Control */}
|
||||
<div className="w-full bg-gradient-to-r from-yellow-50 to-amber-50 rounded-full p-1.5 shadow-sm">
|
||||
<div className="flex items-center space-x-1">
|
||||
{[
|
||||
{
|
||||
value: 'available',
|
||||
label: `Available Surveys (${mockData.availableSurveys.filter(s => ['available', 'in-progress', 'pending'].includes(s.status)).length})`,
|
||||
icon: FileText
|
||||
},
|
||||
{
|
||||
value: 'completed',
|
||||
label: `Completed (${mockData.completedSurveys.length})`,
|
||||
icon: CheckCircle
|
||||
}
|
||||
].map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
return (
|
||||
<button
|
||||
key={tab.value}
|
||||
onClick={() => setActiveTab(tab.value as 'available' | 'completed')}
|
||||
className={`flex-1 px-4 py-2.5 text-[16px] font-medium rounded-full transition-all duration-300 ease-in-out focus:outline-none focus:ring-0 active:outline-none flex items-center justify-center gap-2 hover:scale-105 ${
|
||||
activeTab === tab.value
|
||||
? 'bg-white text-gray-900 shadow-lg transform'
|
||||
: 'text-gray-700 hover:text-gray-900 hover:bg-white/60'
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
<span>{tab.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Available Surveys */}
|
||||
<TabsContent value="available" className="space-y-6">
|
||||
{mockData.availableSurveys.filter(s => ['available', 'in-progress', 'pending'].includes(s.status)).length === 0 ? (
|
||||
<Card className="shadow-lg border-gray-200">
|
||||
<CardContent className="p-12 text-center">
|
||||
<MessageSquare className="h-16 w-16 text-muted-foreground mx-auto mb-4" />
|
||||
<h3 className="text-[20px] font-medium mb-2">No Available Surveys</h3>
|
||||
<p className="text-[16px] text-muted-foreground">
|
||||
All surveys have been completed. Check back later for new assessments.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-6">
|
||||
{mockData.availableSurveys
|
||||
.filter(s => ['available', 'in-progress', 'pending'].includes(s.status))
|
||||
.map((survey) => (
|
||||
<SurveyCard
|
||||
key={survey.id}
|
||||
survey={survey}
|
||||
userType={userType}
|
||||
onStart={handleStartSurvey}
|
||||
onContinue={handleContinueSurvey}
|
||||
onView={handleViewResults}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Completed Surveys */}
|
||||
<TabsContent value="completed" className="space-y-6">
|
||||
{mockData.completedSurveys.length === 0 ? (
|
||||
<Card className="shadow-lg border-gray-200">
|
||||
<CardContent className="p-12 text-center">
|
||||
<CheckCircle className="h-16 w-16 text-muted-foreground mx-auto mb-4" />
|
||||
<h3 className="text-[20px] font-medium mb-2">No Completed Surveys</h3>
|
||||
<p className="text-[16px] text-muted-foreground">
|
||||
Complete your first survey to see results and insights here.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-6">
|
||||
{mockData.completedSurveys.map((survey) => (
|
||||
<Card key={survey.id} className="flex flex-col shadow-lg border-gray-200 hover:shadow-xl transition-all duration-300 transform hover:scale-[1.02]">
|
||||
<CardHeader className="flex-shrink-0 pb-3">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="text-[12px] bg-green-50 text-green-700 border-green-200">
|
||||
COMPLETED
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-[12px] bg-muted text-muted-foreground">
|
||||
{survey.type.replace('-', ' ').toUpperCase()}
|
||||
</Badge>
|
||||
</div>
|
||||
{survey.certificateEarned && (
|
||||
<Award className="h-5 w-5 text-yellow-500" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-[18px] leading-tight mb-2">{survey.title}</CardTitle>
|
||||
<CardDescription className="text-[16px] line-clamp-2">
|
||||
{survey.description}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="flex-1 flex flex-col justify-between pt-0 space-y-3">
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="text-center p-3 bg-gray-50 rounded-lg">
|
||||
<div className="text-[20px] font-bold text-primary">{survey.score}%</div>
|
||||
<div className="text-[12px] text-muted-foreground">Score</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 rounded-lg">
|
||||
<div className="text-[20px] font-bold text-primary">{survey.questions}</div>
|
||||
<div className="text-[12px] text-muted-foreground">Questions</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-[14px]">
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
<span>Completed on {survey.completedDate ? new Date(survey.completedDate).toLocaleDateString() : 'N/A'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
<span>{survey.participants} participants</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Target className="h-4 w-4 text-muted-foreground" />
|
||||
<span>{survey.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{survey.feedback && (
|
||||
<div className="p-3 bg-blue-50 rounded-lg">
|
||||
<p className="text-[14px] text-blue-800 italic">"{survey.feedback}"</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={() => handleViewResults(survey.id)}
|
||||
variant="outline"
|
||||
className="text-[16px] min-h-[44px] w-full"
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
View Results
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</LearnerLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
import React from "react";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "../../components/ui/card";
|
||||
import { Trophy } from "lucide-react";
|
||||
import { ImageWithFallback } from "../../components/figma/ImageWithFallback";
|
||||
|
||||
const platinumTrophy = 'https://via.placeholder.com/150?text=Platinum+Trophy';
|
||||
const goldTrophy = 'https://via.placeholder.com/150?text=Gold+Trophy';
|
||||
const silverTrophy = 'https://via.placeholder.com/150?text=Silver+Trophy';
|
||||
const bronzeTrophy = 'https://via.placeholder.com/150?text=Bronze+Trophy';
|
||||
|
||||
// Achievement Badges component
|
||||
export function AchievementBadges() {
|
||||
const badges = [
|
||||
{
|
||||
id: 'bronze',
|
||||
title: 'Bronze Trophy',
|
||||
description: 'Complete 3 foundational leadership courses and score 80%+ in all assessments',
|
||||
exp: 20,
|
||||
unlocked: true,
|
||||
bgColor: 'from-orange-50 to-orange-100',
|
||||
borderColor: 'border-orange-200',
|
||||
trophyImage: bronzeTrophy
|
||||
},
|
||||
{
|
||||
id: 'silver',
|
||||
title: 'Silver Trophy',
|
||||
description: 'Master 5 advanced leadership modules and lead a successful team project',
|
||||
exp: 40,
|
||||
unlocked: true,
|
||||
bgColor: 'from-gray-50 to-gray-100',
|
||||
borderColor: 'border-gray-300',
|
||||
trophyImage: silverTrophy
|
||||
},
|
||||
{
|
||||
id: 'gold',
|
||||
title: 'Gold Trophy',
|
||||
description: 'Achieve executive leadership certification and mentor 3 junior leaders',
|
||||
exp: 60,
|
||||
unlocked: true,
|
||||
bgColor: 'from-yellow-50 to-yellow-100',
|
||||
borderColor: 'border-yellow-300',
|
||||
trophyImage: goldTrophy
|
||||
},
|
||||
{
|
||||
id: 'platinum',
|
||||
title: 'Platinum Trophy',
|
||||
description: 'Complete all specialization tracks and become a certified KLC ambassador',
|
||||
exp: 100,
|
||||
unlocked: false,
|
||||
bgColor: 'from-gray-50 to-gray-100',
|
||||
borderColor: 'border-gray-200',
|
||||
trophyImage: platinumTrophy
|
||||
}
|
||||
];
|
||||
|
||||
const unlockedCount = badges.filter(badge => badge.unlocked).length;
|
||||
const totalCount = badges.length;
|
||||
|
||||
return (
|
||||
<Card className="border-0 shadow-xl bg-gradient-to-br from-white to-gray-50/50">
|
||||
<CardHeader className="pb-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-brand-gold to-amber-500 rounded-lg flex items-center justify-center">
|
||||
<Trophy className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<CardTitle className="text-xl font-semibold text-gray-900">
|
||||
Achievement Badges
|
||||
</CardTitle>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2 bg-gray-50 border border-gray-200 rounded-full px-3 py-1">
|
||||
<Trophy className="h-3 w-3 text-gray-600" />
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
{unlockedCount}/{totalCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 mb-3">
|
||||
Unlock trophies by completing challenges and competitions
|
||||
</p>
|
||||
|
||||
<div className="max-w-full">
|
||||
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-brand-gold to-amber-500 rounded-full transition-all duration-700 ease-out"
|
||||
style={{ width: `${(unlockedCount / totalCount) * 100}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="px-4 pb-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{badges.map((badge) => (
|
||||
<div
|
||||
key={badge.id}
|
||||
className={`relative flex flex-col h-[18rem] p-4 rounded-lg border-2 ${badge.borderColor} bg-gradient-to-br ${badge.bgColor} transition-all duration-300 hover:shadow-lg hover:scale-[1.02] ${
|
||||
badge.unlocked ? 'hover:border-brand-gold/50 ring-2 ring-white shadow-md' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="absolute top-3 right-3">
|
||||
<div className={`px-2 py-1 rounded-full text-xs font-semibold ${
|
||||
badge.unlocked
|
||||
? 'bg-green-100 text-green-800 border-2 border-green-700'
|
||||
: 'bg-gray-100 text-gray-500 border border-gray-200'
|
||||
}`}>
|
||||
+{badge.exp} EXP
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mb-4 mt-2">
|
||||
<div className="relative">
|
||||
<div className="w-24 h-24 flex items-center justify-center">
|
||||
<ImageWithFallback
|
||||
src={badge.trophyImage}
|
||||
alt={`${badge.title} trophy`}
|
||||
className={`w-full h-full object-contain drop-shadow-lg ${
|
||||
!badge.unlocked ? 'opacity-40' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center flex-grow flex flex-col justify-center mb-4">
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2">
|
||||
{badge.title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 leading-relaxed">
|
||||
{badge.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mt-auto mb-3">
|
||||
<div className={`px-3 py-1 rounded-full text-xs font-semibold min-w-[80px] text-center flex items-center justify-center ${
|
||||
badge.unlocked
|
||||
? 'bg-green-100 text-green-800 border-2 border-green-700'
|
||||
: 'bg-gray-100 text-gray-500 border border-gray-200'
|
||||
}`}>
|
||||
{badge.unlocked ? 'UNLOCKED' : 'LOCKED'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Clock, Users } from "lucide-react";
|
||||
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { ImageWithFallback } from "../../components/figma/ImageWithFallback";
|
||||
|
||||
// Based on your recent activity recommendations component - redesigned to match provided image
|
||||
export function ActivityRecommendations({ userType = 'individual' }: { userType?: 'individual' | 'corporate' }) {
|
||||
const recommendations = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Emotional Intelligence Mastery",
|
||||
description: "Develop self-awareness and interpersonal skills to become a more effective leader.",
|
||||
duration: "5 hours",
|
||||
difficulty: "Intermediate",
|
||||
badgeText: "Intermediate",
|
||||
badgeColor: "bg-yellow-100 text-yellow-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1552664730-d307ca884978?w=400&h=240&fit=crop",
|
||||
recentLearners: 248
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Strategic Thinking Framework",
|
||||
description: "Learn to analyze complex business scenarios and make data-driven decisions.",
|
||||
duration: "4 hours",
|
||||
difficulty: "Advanced",
|
||||
badgeText: "Advanced",
|
||||
badgeColor: "bg-red-100 text-red-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=240&fit=crop",
|
||||
recentLearners: 189
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Team Leadership Excellence",
|
||||
description: "Master the art of motivating and guiding high-performance teams.",
|
||||
duration: "8 hours",
|
||||
difficulty: "Intermediate",
|
||||
badgeText: "Intermediate",
|
||||
badgeColor: "bg-yellow-100 text-yellow-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1600880292203-757bb62b4baf?w=400&h=240&fit=crop",
|
||||
recentLearners: 156
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Change Management Strategies",
|
||||
description: "Navigate organizational transformation with confidence and clarity.",
|
||||
duration: "3 hours",
|
||||
difficulty: "Beginner",
|
||||
badgeText: "Beginner",
|
||||
badgeColor: "bg-green-100 text-green-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1542744173-8e7e53415bb0?w=400&h=240&fit=crop",
|
||||
recentLearners: 134
|
||||
}
|
||||
];
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mb-2">
|
||||
Based on your recent activity, here are your recommendations
|
||||
</h2>
|
||||
<p className="text-base text-gray-600">
|
||||
Personalized course suggestions to accelerate your leadership journey
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/library?view=${userType}`)}
|
||||
className="text-brand-navy hover:bg-brand-navy/10 border-brand-navy/20 text-base px-4 py-2 h-auto font-medium"
|
||||
>
|
||||
View All Courses
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{recommendations.map((course) => (
|
||||
<Card
|
||||
key={course.id}
|
||||
className="group cursor-pointer hover:shadow-lg transition-all duration-300 border border-gray-200 overflow-hidden"
|
||||
>
|
||||
{/* Course Thumbnail */}
|
||||
<div className="relative h-40 bg-gray-200 overflow-hidden">
|
||||
<ImageWithFallback
|
||||
src={course.thumbnail}
|
||||
alt={course.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute top-3 left-3">
|
||||
<Badge className={`${course.badgeColor} text-xs font-medium`}>
|
||||
{course.badgeText}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent className="px-4 pb-4">
|
||||
{/* Course Title */}
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2 line-clamp-2 group-hover:text-brand-navy transition-colors">
|
||||
{course.title}
|
||||
</h3>
|
||||
|
||||
{/* Course Description */}
|
||||
<p className="text-sm text-gray-600 leading-relaxed mb-4 line-clamp-2">
|
||||
{course.description}
|
||||
</p>
|
||||
|
||||
{/* Duration and Recent Learners (Balanced) */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
{/* Duration */}
|
||||
<div className="flex items-center gap-2 w-1/2">
|
||||
<Clock className="h-4 w-4 text-gray-500 flex-shrink-0" />
|
||||
<span className="text-sm text-gray-600">{course.duration}</span>
|
||||
</div>
|
||||
|
||||
{/* Recent Learners */}
|
||||
<div className="flex items-center gap-2 w-1/2 justify-end">
|
||||
<Users className="h-4 w-4 text-gray-500 flex-shrink-0" />
|
||||
<span className="text-sm text-gray-600">{course.recentLearners}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enroll Button */}
|
||||
<Button
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=${course.id}`)}
|
||||
className="w-full text-base min-h-[48px] bg-brand-navy hover:bg-brand-navy/90 text-white font-medium"
|
||||
>
|
||||
Enroll Now
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { WifiOff, Wifi, RefreshCw } from "lucide-react";
|
||||
import { Alert, AlertDescription } from "../../components/ui/alert";
|
||||
import { Button } from "../../components/ui/button";
|
||||
|
||||
|
||||
// Connection status component
|
||||
export function ConnectionStatus() {
|
||||
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOnline = () => setIsOnline(true);
|
||||
const handleOffline = () => setIsOnline(false);
|
||||
|
||||
window.addEventListener('online', handleOnline);
|
||||
window.addEventListener('offline', handleOffline);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', handleOnline);
|
||||
window.removeEventListener('offline', handleOffline);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleRetry = async () => {
|
||||
setIsLoading(true);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
setIsLoading(false);
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
if (isOnline) return null;
|
||||
|
||||
return (
|
||||
<Alert className="mb-8 border-destructive/30 bg-gradient-to-r from-destructive/5 to-destructive/10 backdrop-blur-sm shadow-lg">
|
||||
<WifiOff className="h-5 w-5 text-destructive" />
|
||||
<AlertDescription className="flex items-center justify-between text-base">
|
||||
<span className="font-medium">You're currently offline. Some features may not be available.</span>
|
||||
<Button
|
||||
onClick={handleRetry}
|
||||
disabled={isLoading}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="ml-4 text-base min-h-[44px] border-destructive/30 text-destructive hover:bg-destructive hover:text-destructive-foreground transition-all duration-200"
|
||||
>
|
||||
{isLoading ? (
|
||||
<RefreshCw className="h-4 w-4 animate-spin mr-2" />
|
||||
) : (
|
||||
<Wifi className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
Retry
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Play, FastForward, CheckCircle, ChevronRight, BookOpen } from "lucide-react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card";
|
||||
import CurrentLearningHeaderIcon from "../../imports/CurrentLearningHeaderIcon-4285-106";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Button } from "../../components/ui/button";
|
||||
|
||||
export function CurrentLearningProgress({ userType = 'individual' }: { userType?: 'individual' | 'corporate' }) {
|
||||
const currentCourse = {
|
||||
title: "Strategic Leadership Excellence Program",
|
||||
rating: 4.9,
|
||||
provider: "Dr. Rajesh Sharma, KLC",
|
||||
dueDate: "Aug 30, 2025",
|
||||
totalModules: 8,
|
||||
currentModule: {
|
||||
title: "Building High-Performance Teams",
|
||||
number: 6,
|
||||
progress: 65,
|
||||
lessonsRemaining: 3
|
||||
},
|
||||
nextModule: {
|
||||
title: "Leadership Communication Mastery",
|
||||
number: 7
|
||||
}
|
||||
};
|
||||
|
||||
// Sample module data to match the image layout
|
||||
const moduleList = [
|
||||
{ title: "Leadership Fundamentals", duration: "1h 15m", completed: true },
|
||||
{ title: "Emotional Intelligence for Leaders", duration: "1h 30m", completed: true },
|
||||
{ title: "Strategic Decision Making", duration: "1h", completed: true },
|
||||
{ title: "Conflict Resolution & Negotiation", duration: "50m", completed: true },
|
||||
{ title: "Change Management Principles", duration: "40m", completed: true },
|
||||
{ title: "Building High-Performance Teams", duration: "1h 20m", completed: false, current: true },
|
||||
{ title: "Leadership Communication Mastery", duration: "1h 45m", completed: false, upcoming: true },
|
||||
{ title: "Innovation & Strategic Thinking", duration: "2h", completed: false }
|
||||
];
|
||||
|
||||
const completedModules = moduleList.filter(module => module.completed).length;
|
||||
const totalModules = moduleList.length;
|
||||
const overallProgress = Math.round((completedModules / totalModules) * 100);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Card className="h-fit bg-white shadow-md border border-gray-200 rounded-lg overflow-hidden">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-gradient-to-br from-brand-navy to-brand-navy/90 rounded-lg shadow-lg">
|
||||
<div className="h-6 w-6">
|
||||
<CurrentLearningHeaderIcon />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-xl font-semibold text-gray-900">
|
||||
Current Learning Progress
|
||||
</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="px-6 pb-6">
|
||||
{/* Course Header */}
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-2">
|
||||
{currentCourse.title}
|
||||
</h2>
|
||||
<p className="text-base text-gray-600">
|
||||
with {currentCourse.provider}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Badge className="bg-brand-navy/5 text-brand-navy border border-brand-navy px-3 py-1.5 text-sm font-medium mb-2">
|
||||
In Progress
|
||||
</Badge>
|
||||
<p className="text-sm text-gray-600">2h 30m remaining</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overall Progress */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-medium text-gray-700">Overall Progress</span>
|
||||
<span className="text-sm font-medium text-gray-700">Modules</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-2xl font-bold text-gray-900">{overallProgress}%</span>
|
||||
<span className="text-2xl font-bold text-gray-900">{completedModules}/{totalModules}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="text-sm text-gray-500">Started</span>
|
||||
<span className="text-sm text-gray-500">Completed</span>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-3 mb-2">
|
||||
<div
|
||||
className="bg-brand-navy h-3 rounded-full transition-all duration-300"
|
||||
style={{ width: `${overallProgress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current and Up Next Modules */}
|
||||
<div className="grid grid-cols-2 gap-6 mb-4">
|
||||
{/* Current Module */}
|
||||
<div className="bg-brand-gold/10 border border-brand-gold/30 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="w-6 h-6 bg-brand-gold rounded flex items-center justify-center">
|
||||
<Play className="h-3 w-3 text-brand-charcoal" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-brand-charcoal uppercase tracking-wide">Current</span>
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-3">
|
||||
{currentCourse.currentModule.title}
|
||||
</h3>
|
||||
<Button
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=strategic-leadership&module=${currentCourse.currentModule.number}`)}
|
||||
className="w-full text-base min-h-[48px] bg-brand-gold hover:bg-brand-gold/90 text-brand-charcoal font-medium"
|
||||
>
|
||||
Continue Module →
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Up Next Module */}
|
||||
<div className="bg-brand-navy/10 border border-brand-navy/30 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="w-6 h-6 bg-brand-navy rounded flex items-center justify-center">
|
||||
<FastForward className="h-3 w-3 text-white" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-brand-navy uppercase tracking-wide">Up Next</span>
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-3">
|
||||
{currentCourse.nextModule.title}
|
||||
</h3>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=strategic-leadership&module=${currentCourse.nextModule.number}`)}
|
||||
className="w-full text-base min-h-[48px] border-brand-navy text-brand-navy hover:bg-brand-navy/10 font-medium"
|
||||
>
|
||||
Preview Module →
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Course Modules List */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-base font-semibold text-gray-900">Course Modules</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=strategic-leadership`)}
|
||||
className="text-sm text-brand-navy hover:text-brand-navy hover:bg-brand-navy/10 p-0 h-auto font-medium"
|
||||
>
|
||||
View All →
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{moduleList.slice(0, 5).map((module, index) => (
|
||||
<div key={index} className="flex items-center justify-between py-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-5 h-5 flex items-center justify-center">
|
||||
{module.completed ? (
|
||||
<CheckCircle className="h-5 w-5 text-brand-gold" />
|
||||
) : (
|
||||
<div className="w-4 h-4 border-2 border-gray-300 rounded-full"></div>
|
||||
)}
|
||||
</div>
|
||||
<span className={`text-sm ${module.completed ? 'text-gray-900' : 'text-gray-600'}`}>
|
||||
{module.title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500">{module.duration}</span>
|
||||
<ChevronRight className="h-4 w-4 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=strategic-leadership`)}
|
||||
className="text-base text-gray-600 hover:text-gray-900 hover:bg-gray-50 p-0 min-h-[48px] font-medium flex items-center gap-1"
|
||||
>
|
||||
<BookOpen className="h-4 w-4" />
|
||||
View Full Course
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=strategic-leadership&module=${currentCourse.currentModule.number}`)}
|
||||
className="text-base min-h-[48px] bg-brand-navy hover:bg-brand-navy/90 text-white font-medium px-6"
|
||||
>
|
||||
<Play className="h-4 w-4 mr-2" />
|
||||
Continue Learning
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { LearnerLayout } from '../../components/learner/LearnerLayout';
|
||||
import { RecentActivity } from './RecentActivity';
|
||||
import { WelcomeMessage } from './WelcomeMessage';
|
||||
import { CurrentLearningProgress } from './CurrentLearningProgress';
|
||||
import { ConnectionStatus } from './ConnectionStatus';
|
||||
import { WeeklyProgressTracker } from './WeeklyProgressTracker';
|
||||
import { AchievementBadges } from './AchievementBadges';
|
||||
import { GlobalLeaderboard } from './GlobalLeaderboard';
|
||||
import { ActivityRecommendations } from './ActivityRecommendations';
|
||||
import { RecentlyViewed } from './RecentlyViewed';
|
||||
import { WhatOthersLearning } from './WhatOthersLearning';
|
||||
|
||||
|
||||
|
||||
interface DashboardProps {
|
||||
userType?: 'individual' | 'corporate';
|
||||
}
|
||||
|
||||
// Main Dashboard component
|
||||
export default function Dashboard({ userType = 'individual' }: DashboardProps) {
|
||||
const user = { name: 'Alex', email: 'alex@example.com' };
|
||||
|
||||
return (
|
||||
<LearnerLayout currentPage="/dashboard" userType={userType}>
|
||||
<div className="container mx-auto px-4 lg:px-8 py-8 space-y-8">
|
||||
<ConnectionStatus />
|
||||
|
||||
{/* Welcome Section */}
|
||||
<div className="mb-8">
|
||||
<WelcomeMessage user={user} />
|
||||
<p className="text-lg text-gray-600 leading-relaxed">
|
||||
Continue your leadership development journey with our personalized recommendations and track your progress
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Weekly Progress Tracker */}
|
||||
<WeeklyProgressTracker />
|
||||
|
||||
{/* Current Learning Progress */}
|
||||
<CurrentLearningProgress userType={userType} />
|
||||
|
||||
{/* Achievement Badges */}
|
||||
<AchievementBadges />
|
||||
|
||||
{/* Global Leaderboard (left, prominent) and Recent Activity (right) Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2">
|
||||
<GlobalLeaderboard />
|
||||
</div>
|
||||
<div className="lg:col-span-1">
|
||||
<RecentActivity />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Based on your activity recommendations - NO TOP PADDING */}
|
||||
<div>
|
||||
<ActivityRecommendations userType={userType} />
|
||||
</div>
|
||||
|
||||
{/* Recently Viewed section - NO TOP PADDING */}
|
||||
<div>
|
||||
<RecentlyViewed userType={userType} />
|
||||
</div>
|
||||
|
||||
{/* What are others learning section - NO TOP PADDING */}
|
||||
<div>
|
||||
<WhatOthersLearning userType={userType} />
|
||||
</div>
|
||||
</div>
|
||||
</LearnerLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Trophy } from "lucide-react";
|
||||
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "../../components/ui/card";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { ScrollArea } from "../../components/ui/scroll-area";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "../../components/ui/avatar";
|
||||
|
||||
// Global Leaderboard component - increased height to match Recent Activity
|
||||
export function GlobalLeaderboard() {
|
||||
const leaderboardData = [
|
||||
{
|
||||
rank: 1,
|
||||
name: "Sarah Chen",
|
||||
points: 2450,
|
||||
avatar: "https://images.unsplash.com/photo-1494790108755-2616b612b5c8?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "gold",
|
||||
change: "+15"
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
name: "Marcus Johnson",
|
||||
points: 2380,
|
||||
avatar: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "silver",
|
||||
change: "+8"
|
||||
},
|
||||
{
|
||||
rank: 3,
|
||||
name: "Emily Rodriguez",
|
||||
points: 2320,
|
||||
avatar: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "bronze",
|
||||
change: "+12"
|
||||
},
|
||||
{
|
||||
rank: 4,
|
||||
name: "David Kim",
|
||||
points: 2180,
|
||||
avatar: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "regular",
|
||||
change: "+5"
|
||||
},
|
||||
{
|
||||
rank: 5,
|
||||
name: "Lisa Thompson",
|
||||
points: 2140,
|
||||
avatar: "https://images.unsplash.com/photo-1517841905240-472988babdf9?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "regular",
|
||||
change: "+20"
|
||||
},
|
||||
{
|
||||
rank: 6,
|
||||
name: "Alex Patel",
|
||||
points: 2090,
|
||||
avatar: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "regular",
|
||||
change: "+3"
|
||||
},
|
||||
{
|
||||
rank: 7,
|
||||
name: "Your Position",
|
||||
points: 1950,
|
||||
avatar: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "user",
|
||||
change: "+18",
|
||||
isCurrentUser: true
|
||||
},
|
||||
{
|
||||
rank: 8,
|
||||
name: "Jennifer Lee",
|
||||
points: 1890,
|
||||
avatar: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=80&h=80&fit=crop&crop=face",
|
||||
badge: "regular",
|
||||
change: "+7"
|
||||
}
|
||||
];
|
||||
|
||||
const getBadgeColor = (badge: string) => {
|
||||
switch (badge) {
|
||||
case 'gold': return 'bg-gradient-to-r from-yellow-400 to-yellow-500 text-white';
|
||||
case 'silver': return 'bg-gradient-to-r from-gray-300 to-gray-400 text-gray-800';
|
||||
case 'bronze': return 'bg-gradient-to-r from-orange-400 to-orange-500 text-white';
|
||||
case 'user': return 'bg-gradient-to-r from-brand-navy to-brand-navy/90 text-white';
|
||||
default: return 'bg-gray-100 text-gray-600';
|
||||
}
|
||||
};
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Card className="h-96 flex flex-col border-0 shadow-lg bg-white">
|
||||
<CardHeader className="pb-3 flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-brand-gold to-amber-500 rounded-lg flex items-center justify-center">
|
||||
<Trophy className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<CardTitle className="text-lg font-semibold text-gray-900">
|
||||
Global Leaderboard
|
||||
</CardTitle>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigate('/leaderboard?view=individual')}
|
||||
className="text-brand-navy hover:bg-brand-navy/10 border-brand-navy/20 text-sm px-3 py-1.5 h-auto font-medium"
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Top learners this month
|
||||
</p>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="px-6 pb-4 flex-1 min-h-0">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="space-y-2 pr-2">
|
||||
{leaderboardData.map((user) => (
|
||||
<div
|
||||
key={user.rank}
|
||||
className={`flex items-center gap-3 p-3 rounded-lg transition-all duration-200 hover:shadow-sm ${
|
||||
user.isCurrentUser
|
||||
? 'bg-gradient-to-r from-brand-navy/5 to-brand-navy/10 border border-brand-navy/20'
|
||||
: 'bg-gray-50 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
{/* Rank */}
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${getBadgeColor(user.badge)}`}>
|
||||
{user.rank}
|
||||
</div>
|
||||
|
||||
{/* Avatar */}
|
||||
<Avatar className="h-8 w-8 flex-shrink-0">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="text-xs">{user.name.split(' ').map(n => n[0]).join('')}</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
{/* User Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className={`text-sm font-semibold leading-tight ${
|
||||
user.isCurrentUser ? 'text-brand-navy' : 'text-gray-900'
|
||||
}`}>
|
||||
{user.name}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-600 font-medium">
|
||||
{user.points.toLocaleString()} points
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Change */}
|
||||
<div className="text-xs font-semibold text-green-600 flex-shrink-0">
|
||||
{user.change}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
import {
|
||||
CheckCircle,
|
||||
Video,
|
||||
FileText,
|
||||
Award,
|
||||
PlayCircle,
|
||||
MessageSquare,
|
||||
BookOpen,
|
||||
Users,
|
||||
Activity
|
||||
} from "lucide-react";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { ScrollArea } from "../../components/ui/scroll-area";
|
||||
// import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
|
||||
// Recent Activity component - increased height to match Global Leaderboard
|
||||
export function RecentActivity() {
|
||||
const activities = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'course_completed',
|
||||
title: 'Completed "Strategic Decision Making"',
|
||||
description: 'Module 3 of 4 • Leadership Fundamentals',
|
||||
timestamp: '2 hours ago',
|
||||
icon: CheckCircle,
|
||||
iconColor: 'text-green-600',
|
||||
iconBg: 'bg-green-100'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'webinar_joined',
|
||||
title: 'Attended Live Webinar',
|
||||
description: 'Digital Transformation for Leaders',
|
||||
timestamp: '5 hours ago',
|
||||
icon: Video,
|
||||
iconColor: 'text-blue-600',
|
||||
iconBg: 'bg-blue-100'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'assessment_completed',
|
||||
title: 'Assessment Completed',
|
||||
description: 'Leadership Style Assessment • Score: 92%',
|
||||
timestamp: '1 day ago',
|
||||
icon: FileText,
|
||||
iconColor: 'text-purple-600',
|
||||
iconBg: 'bg-purple-100'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'certificate_earned',
|
||||
title: 'Certificate Earned',
|
||||
description: 'Leadership Foundation Certification',
|
||||
timestamp: '2 days ago',
|
||||
icon: Award,
|
||||
iconColor: 'text-brand-gold',
|
||||
iconBg: 'bg-yellow-100'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'course_started',
|
||||
title: 'Started New Course',
|
||||
description: 'Innovation Leadership Track',
|
||||
timestamp: '3 days ago',
|
||||
icon: PlayCircle,
|
||||
iconColor: 'text-green-600',
|
||||
iconBg: 'bg-green-100'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: 'discussion_posted',
|
||||
title: 'Discussion Contribution',
|
||||
description: 'Team Management Best Practices',
|
||||
timestamp: '4 days ago',
|
||||
icon: MessageSquare,
|
||||
iconColor: 'text-orange-600',
|
||||
iconBg: 'bg-orange-100'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
type: 'resource_downloaded',
|
||||
title: 'Resource Downloaded',
|
||||
description: 'Leadership Skills Handbook PDF',
|
||||
timestamp: '5 days ago',
|
||||
icon: BookOpen,
|
||||
iconColor: 'text-gray-600',
|
||||
iconBg: 'bg-gray-100'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
type: 'peer_connection',
|
||||
title: 'New Connection',
|
||||
description: 'Connected with 3 peer learners',
|
||||
timestamp: '1 week ago',
|
||||
icon: Users,
|
||||
iconColor: 'text-brand-navy',
|
||||
iconBg: 'bg-blue-100'
|
||||
}
|
||||
];
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Card className="h-96 flex flex-col border-0 shadow-lg bg-white">
|
||||
<CardHeader className="pb-3 flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-brand-navy to-brand-navy/90 rounded-lg flex items-center justify-center">
|
||||
<Activity className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<CardTitle className="text-lg font-semibold text-gray-900">
|
||||
Recent Activity
|
||||
</CardTitle>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => navigate('/dashboard?view=individual&tab=activity')}
|
||||
className="text-brand-navy hover:bg-brand-navy/10 border-brand-navy/20 text-base px-3 py-1.5 min-h-[48px] font-medium"
|
||||
>
|
||||
View All
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Your recent learning activities
|
||||
</p>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="px-6 pb-4 flex-1 min-h-0">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="space-y-2 pr-2">
|
||||
{activities.map((activity) => {
|
||||
const Icon = activity.icon;
|
||||
return (
|
||||
<div
|
||||
key={activity.id}
|
||||
className="flex items-start gap-3 p-3 rounded-lg bg-gray-50 hover:bg-gray-100 transition-all duration-200"
|
||||
>
|
||||
{/* Activity Icon */}
|
||||
<div className={`p-2 rounded-lg ${activity.iconBg} flex-shrink-0`}>
|
||||
<Icon className={`h-3 w-3 ${activity.iconColor}`} />
|
||||
</div>
|
||||
|
||||
{/* Activity Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-sm font-semibold text-gray-900 mb-1 leading-tight">
|
||||
{activity.title}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-600 mb-1 leading-relaxed">
|
||||
{activity.description}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 font-medium">
|
||||
{activity.timestamp}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Box, Clock, Users } from "lucide-react";
|
||||
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { ImageWithFallback } from "../../components/figma/ImageWithFallback";
|
||||
|
||||
// Recently Viewed component - redesigned to match course card layout
|
||||
export function RecentlyViewed({ userType = 'individual' }: { userType?: 'individual' | 'corporate' }) {
|
||||
const recentItems = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Strategic Decision Making",
|
||||
description: "Master critical thinking and decision-making frameworks for effective leadership.",
|
||||
type: "Course",
|
||||
progress: 75,
|
||||
lastAccessed: "2 hours ago",
|
||||
thumbnail: "https://images.unsplash.com/photo-1664575602276-acd073f104c1?w=400&h=240&fit=crop",
|
||||
duration: "4.2 hours",
|
||||
badgeText: "In Progress",
|
||||
badgeColor: "bg-blue-100 text-blue-800",
|
||||
recentLearners: 312
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Digital Transformation for Leaders",
|
||||
description: "Navigate the digital landscape and lead technological change in organizations.",
|
||||
type: "Webinar",
|
||||
progress: 100,
|
||||
lastAccessed: "5 hours ago",
|
||||
thumbnail: "https://images.unsplash.com/photo-1551434678-e076c223a692?w=400&h=240&fit=crop",
|
||||
duration: "1.5 hours",
|
||||
badgeText: "Completed",
|
||||
badgeColor: "bg-green-100 text-green-800",
|
||||
recentLearners: 189
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Leadership Style Assessment",
|
||||
description: "Discover your leadership style and develop targeted improvement strategies.",
|
||||
type: "Assessment",
|
||||
progress: 100,
|
||||
lastAccessed: "1 day ago",
|
||||
thumbnail: "https://images.unsplash.com/photo-1450101499163-c8848c66ca85?w=400&h=240&fit=crop",
|
||||
duration: "30 minutes",
|
||||
badgeText: "Completed",
|
||||
badgeColor: "bg-green-100 text-green-800",
|
||||
recentLearners: 276
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Team Management Fundamentals",
|
||||
description: "Build essential skills for managing and developing high-performing teams.",
|
||||
type: "Course",
|
||||
progress: 45,
|
||||
lastAccessed: "2 days ago",
|
||||
thumbnail: "https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=400&h=240&fit=crop",
|
||||
duration: "6.1 hours",
|
||||
badgeText: "In Progress",
|
||||
badgeColor: "bg-blue-100 text-blue-800",
|
||||
recentLearners: 198
|
||||
}
|
||||
];
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mb-2">
|
||||
Recently Viewed
|
||||
</h2>
|
||||
<p className="text-base text-gray-600">
|
||||
Continue where you left off or revisit completed content
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/library?view=${userType}&filter=recent`)}
|
||||
className="text-brand-navy hover:bg-brand-navy/10 border-brand-navy/20 text-base px-4 py-2 h-auto font-medium"
|
||||
>
|
||||
View History
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{recentItems.map((item) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
className="group cursor-pointer hover:shadow-lg transition-all duration-300 border border-gray-200 overflow-hidden flex flex-col"
|
||||
>
|
||||
{/* Course Thumbnail */}
|
||||
<div className="relative h-40 bg-gray-200 overflow-hidden">
|
||||
<ImageWithFallback
|
||||
src={item.thumbnail}
|
||||
alt={item.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute top-3 left-3">
|
||||
<Badge className={`${item.badgeColor} text-xs font-medium`}>
|
||||
{item.badgeText}
|
||||
</Badge>
|
||||
</div>
|
||||
{/* Progress overlay for in-progress items */}
|
||||
{item.progress > 0 && item.progress < 100 && (
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-black/50 text-white text-xs p-2">
|
||||
Progress: {item.progress}%
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Make content take full space and push button down */}
|
||||
<CardContent className="px-4 pb-4 flex flex-col flex-1 justify-between">
|
||||
<div>
|
||||
{/* Course Title */}
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2 line-clamp-2 group-hover:text-brand-navy transition-colors">
|
||||
{item.title}
|
||||
</h3>
|
||||
|
||||
{/* Course Description */}
|
||||
<p className="text-sm text-gray-600 leading-relaxed mb-4 line-clamp-2">
|
||||
{item.description}
|
||||
</p>
|
||||
|
||||
{/* Duration and Recent Learners */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="h-4 w-4 text-gray-500" />
|
||||
<span className="text-sm text-gray-600">{item.duration}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Users className="h-4 w-4 text-gray-500" />
|
||||
<span className="text-sm text-gray-600">{item.recentLearners}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Button fixed at bottom */}
|
||||
<Button
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=${item.id}`)}
|
||||
className="w-full text-base min-h-[48px] bg-brand-navy hover:bg-brand-navy/90 text-white font-medium mt-auto"
|
||||
>
|
||||
{item.progress === 100 ? 'Review' : item.progress > 0 ? 'Continue' : 'Start'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../../components/ui/dialog";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { Button } from "../../components/ui/button";
|
||||
|
||||
// Weekly Progress Goal Setting Modal component
|
||||
export function WeeklyProgressModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
weeklyGoal,
|
||||
setWeeklyGoal,
|
||||
completedDays,
|
||||
setCompletedDays,
|
||||
}: {
|
||||
isOpen?: boolean;
|
||||
onClose?: () => void;
|
||||
weeklyGoal?: number;
|
||||
setWeeklyGoal?: (goal: number) => void;
|
||||
completedDays?: number[];
|
||||
setCompletedDays?: (days: number[]) => void;
|
||||
}) {
|
||||
const [selectedDays, setSelectedDays] = useState<number[]>(completedDays ?? []);
|
||||
|
||||
const daysOfWeek = [
|
||||
{ label: "Monday", index: 0 },
|
||||
{ label: "Tuesday", index: 1 },
|
||||
{ label: "Wednesday", index: 2 },
|
||||
{ label: "Thursday", index: 3 },
|
||||
{ label: "Friday", index: 4 },
|
||||
{ label: "Saturday", index: 5 },
|
||||
{ label: "Sunday", index: 6 },
|
||||
];
|
||||
|
||||
const toggleDay = (dayIndex: number) => {
|
||||
setSelectedDays((prev) =>
|
||||
prev.includes(dayIndex)
|
||||
? prev.filter((d) => d !== dayIndex)
|
||||
: [...prev, dayIndex]
|
||||
);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
setCompletedDays?.(selectedDays);
|
||||
setWeeklyGoal?.(selectedDays.length);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setSelectedDays(completedDays ?? []);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-sm">
|
||||
<DialogHeader className="text-left">
|
||||
<DialogTitle className="text-xl font-semibold text-gray-900">
|
||||
Set your weekly learning goal
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-base text-gray-600 leading-relaxed mt-2">
|
||||
Consistency is key to success! Choose your learning days so we can
|
||||
estimate your course completion dates and schedule assignment
|
||||
deadlines, helping you stay on track.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4">
|
||||
<div>
|
||||
<h3 className="text-base font-medium text-gray-700 mb-4">
|
||||
Choose your learning days
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
The days you select will apply to all your courses on KLC
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{daysOfWeek.map((day) => (
|
||||
<div key={day.index} className="flex items-center space-x-3">
|
||||
<Checkbox
|
||||
id={`day-${day.index}`}
|
||||
checked={selectedDays.includes(day.index)}
|
||||
onCheckedChange={() => toggleDay(day.index)}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
<label
|
||||
htmlFor={`day-${day.index}`}
|
||||
className="text-base text-gray-700 cursor-pointer flex-1"
|
||||
>
|
||||
{day.label}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4 border-t border-gray-200">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
className="flex-1 text-base min-h-[48px] bg-brand-navy hover:bg-brand-navy/90 text-white font-medium"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
className="flex-1 text-base min-h-[48px] border-gray-300 text-gray-700 hover:bg-gray-50 font-medium"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { BookOpen, Clock } from "lucide-react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../components/ui/card";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { WeeklyProgressModal } from "./WeeklyProgressModal";
|
||||
|
||||
// Weekly Progress component based on the provided design
|
||||
export function WeeklyProgressTracker() {
|
||||
const [weeklyGoal, setWeeklyGoal] = useState("Learn 1 day a week on KLC");
|
||||
const [completedDays, setCompletedDays] = useState([0, 1, 2]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const kpiData = [
|
||||
{
|
||||
id: 'courses',
|
||||
value: 8,
|
||||
label: "Courses Completed",
|
||||
change: "+2 this month",
|
||||
icon: BookOpen,
|
||||
iconBg: "bg-gradient-to-br from-emerald-500 to-emerald-600"
|
||||
},
|
||||
{
|
||||
id: 'hours',
|
||||
value: 47,
|
||||
label: "Learning Hours",
|
||||
change: "+8 this week",
|
||||
icon: Clock,
|
||||
iconBg: "bg-gradient-to-br from-blue-500 to-blue-600"
|
||||
}
|
||||
];
|
||||
|
||||
const daysOfWeek = [
|
||||
{ label: 'M', full: 'Monday', index: 0 },
|
||||
{ label: 'T', full: 'Tuesday', index: 1 },
|
||||
{ label: 'W', full: 'Wednesday', index: 2 },
|
||||
{ label: 'T', full: 'Thursday', index: 3 },
|
||||
{ label: 'F', full: 'Friday', index: 4 },
|
||||
{ label: 'S', full: 'Saturday', index: 5 },
|
||||
{ label: 'S', full: 'Sunday', index: 6 }
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 items-stretch">
|
||||
{kpiData.map((data) => {
|
||||
const Icon = data.icon;
|
||||
return (
|
||||
<Card key={data.id} className="border-0 shadow-xl bg-gradient-to-br from-brand-navy/3 to-brand-navy/8 backdrop-blur-sm flex flex-col h-full">
|
||||
<CardContent className="p-6 pb-0 flex-1 flex flex-col justify-between">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-base font-semibold text-gray-700">
|
||||
{data.label}
|
||||
</div>
|
||||
<div className={`p-3 ${data.iconBg} rounded-lg shadow-lg`}>
|
||||
<Icon className="h-6 w-6 text-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-4xl font-bold bg-gradient-to-r from-gray-800 to-gray-600 bg-clip-text text-transparent">
|
||||
{data.value}{data.id === 'hours' ? 'h' : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-base text-gray-600 font-medium mt-auto">
|
||||
{data.change}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
|
||||
<Card className="border-0 shadow-xl bg-gradient-to-br from-white via-brand-navy/3 to-brand-navy/8 backdrop-blur-sm flex flex-col h-full">
|
||||
<CardHeader className="pb-0 flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base font-semibold text-gray-900">
|
||||
Weekly Learning Goal
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
className="text-brand-navy hover:bg-brand-navy/10 text-base font-medium px-3 py-1 min-h-[48px]"
|
||||
>
|
||||
Update goal
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-2 pb-4 px-6 flex-1 flex flex-col justify-between">
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-gray-600">
|
||||
{weeklyGoal}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
{daysOfWeek.map((day) => (
|
||||
<div key={day.index} className="flex flex-col items-center">
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium transition-all duration-200 ${
|
||||
completedDays.includes(day.index)
|
||||
? 'bg-green-500 text-white shadow-md'
|
||||
: 'bg-gray-200 text-gray-600'
|
||||
}`}>
|
||||
{day.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-500">
|
||||
{completedDays.length} days completed
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<WeeklyProgressModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
weeklyGoal={1}
|
||||
setWeeklyGoal={() => {}}
|
||||
completedDays={completedDays}
|
||||
setCompletedDays={setCompletedDays}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
// Welcome Message component with simple visible emoji
|
||||
export function WelcomeMessage({ user }: { user: any }) {
|
||||
const [showEmoji, setShowEmoji] = useState(true);
|
||||
const emojiChar = '👋';
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setShowEmoji(false);
|
||||
}, 5000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="text-4xl font-bold mb-3">
|
||||
<span className="bg-gradient-to-r from-gray-800 via-gray-700 to-gray-600 bg-clip-text text-transparent">
|
||||
Welcome back, Priya Sharma!
|
||||
</span>
|
||||
{showEmoji && (
|
||||
<span
|
||||
className="text-yellow-500 ml-2 inline-block animate-wave-emoji"
|
||||
style={{
|
||||
fontSize: '2.5rem',
|
||||
fontFamily: 'Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, sans-serif',
|
||||
backgroundColor: 'transparent',
|
||||
color: '#EAB308'
|
||||
}}
|
||||
>
|
||||
{emojiChar}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Clock, Users } from "lucide-react";
|
||||
|
||||
import { Card, CardContent } from "../../components/ui/card";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { ImageWithFallback } from "../../components/figma/ImageWithFallback";
|
||||
|
||||
// What are others learning component - redesigned to match course card layout
|
||||
export function WhatOthersLearning({ userType = 'individual' }: { userType?: 'individual' | 'corporate' }) {
|
||||
const trendingContent = [
|
||||
{
|
||||
id: 1,
|
||||
title: "AI in Leadership Decision Making",
|
||||
description: "Explore how artificial intelligence is reshaping leadership strategies and decision-making processes.",
|
||||
trend: "Hot",
|
||||
enrollments: 2340,
|
||||
duration: "5.2 hours",
|
||||
badgeColor: "bg-red-100 text-red-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1485827404703-89b55fcc595e?w=400&h=240&fit=crop",
|
||||
recentLearners: 423
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Remote Team Management",
|
||||
description: "Master the art of leading distributed teams effectively in the modern workplace.",
|
||||
trend: "Trending",
|
||||
enrollments: 1890,
|
||||
duration: "3 sessions",
|
||||
badgeColor: "bg-orange-100 text-orange-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1556761175-b413da4baf72?w=400&h=240&fit=crop",
|
||||
recentLearners: 267
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Sustainable Leadership Practices",
|
||||
description: "Build leadership approaches that create lasting organizational change and impact.",
|
||||
trend: "Rising",
|
||||
enrollments: 1560,
|
||||
duration: "1 day",
|
||||
badgeColor: "bg-green-100 text-green-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1542744094-3a31f272c490?w=400&h=240&fit=crop",
|
||||
recentLearners: 198
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Data-Driven Leadership",
|
||||
description: "Learn to make strategic decisions using data analytics and business insights.",
|
||||
trend: "Popular",
|
||||
enrollments: 2100,
|
||||
duration: "4.8 hours",
|
||||
badgeColor: "bg-blue-100 text-blue-800",
|
||||
thumbnail: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=400&h=240&fit=crop",
|
||||
recentLearners: 356
|
||||
}
|
||||
];
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mb-2">
|
||||
What are others learning
|
||||
</h2>
|
||||
<p className="text-base text-gray-600">
|
||||
Trending courses and popular content among your peers
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/library?view=${userType}&filter=trending`)}
|
||||
className="text-brand-navy hover:bg-brand-navy/10 border-brand-navy/20 text-base px-4 py-2 h-auto font-medium"
|
||||
>
|
||||
View Trending
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{trendingContent.map((content) => (
|
||||
<Card
|
||||
key={content.id}
|
||||
className="group cursor-pointer hover:shadow-lg transition-all duration-300 border border-gray-200 overflow-hidden flex flex-col"
|
||||
>
|
||||
{/* Course Thumbnail */}
|
||||
<div className="relative h-40 bg-gray-200 overflow-hidden">
|
||||
<ImageWithFallback
|
||||
src={content.thumbnail}
|
||||
alt={content.title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute top-3 left-3">
|
||||
<Badge className={`${content.badgeColor} text-xs font-medium`}>
|
||||
{content.trend}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Make content flex column so button sticks to bottom */}
|
||||
<CardContent className="px-4 pb-4 flex flex-col flex-1 justify-between">
|
||||
<div>
|
||||
{/* Course Title */}
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2 line-clamp-2 group-hover:text-brand-navy transition-colors">
|
||||
{content.title}
|
||||
</h3>
|
||||
|
||||
{/* Course Description */}
|
||||
<p className="text-sm text-gray-600 leading-relaxed mb-4 line-clamp-2">
|
||||
{content.description}
|
||||
</p>
|
||||
|
||||
{/* Duration and Recent Learners */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="h-4 w-4 text-gray-500" />
|
||||
<span className="text-sm text-gray-600">{content.duration}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Users className="h-4 w-4 text-gray-500" />
|
||||
<span className="text-sm text-gray-600">{content.recentLearners}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Enroll Button at bottom */}
|
||||
<Button
|
||||
onClick={() => navigate(`/course?view=${userType}&courseId=${content.id}`)}
|
||||
className="w-full text-base min-h-[48px] bg-brand-navy hover:bg-brand-navy/90 text-white font-medium mt-auto"
|
||||
>
|
||||
Enroll Now
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user