+ +
+
+

Dashboard Overview

+

Welcome back! Here's what's happening today.

+
+ +
+ + +
+
+
+
+
+
+

Loading dashboard data...

+
+ + +
+ + {{ error }} + +
+ + +
+ + +
+
+
+ +
+
+

{{ stats.events.upcoming }}

+

Upcoming Events

+
+
+ +
+
+ +
+
+

{{ stats.careers.pendingApplications + stats.education.pendingApplications }}

+

Pending Reviews

+
+
+ +
+
+ +
+
+

{{ stats.careers.activeJobs }}

+

Active Jobs

+
+
+ +
+
+ +
+
+

{{ stats.education.activeCourses }}

+

Active Courses

+
+
+
+ + +
+ + +
+
+
+ + Events +
+
+
+
+ Total Events + {{ stats.events.total }} +
+
+ Upcoming + {{ stats.events.upcoming }} +
+
+ Active + {{ stats.events.active }} +
+
+
+ + +
+
+
+ + Blogs +
+
+
+
+ Total Posts + {{ stats.blogs.total }} +
+
+ Published + {{ stats.blogs.published }} +
+
+ Drafts + {{ stats.blogs.drafts }} +
+
+
+ + +
+
+
+ + Careers +
+
+
+
+ Active Jobs + {{ stats.careers.activeJobs }} +
+
+ Applications + {{ stats.careers.totalApplications }} +
+
+ Pending + {{ stats.careers.pendingApplications }} +
+
+
+ + +
+
+
+ + Education +
+
+
+
+ Active Courses + {{ stats.education.activeCourses }} +
+
+ Enrollments + {{ stats.education.totalApplications }} +
+
+ Pending + {{ stats.education.pendingApplications }} +
+
+
+ + +
+
+
+ + Professors +
+
+
+
+ Total + {{ stats.professors.total }} +
+
+ Active + {{ stats.professors.active }} +
+
+ Status + Active +
+
+
+ + +
+
+
+ + Testimonials +
+
+
+
+ Total + {{ stats.testimonials.total }} +
+
+ Active + {{ stats.testimonials.active }} +
+
+ Status + Published +
+
+
+ + +
+
+
+ + Milestones +
+
+
+
+ Total + {{ stats.milestones.total }} +
+
+ Active + {{ stats.milestones.active }} +
+
+ Status + Tracking +
+
+
+ +
+ + +
+
+

+ + Recent Activities +

+
+
+
+
+ +
+
+

{{ activity.type }}

+

{{ activity.description }}

+ {{ getTimeAgo(activity.date) }} +
+
+ +
+
+ +
+

No recent activities

+
+
+
-
-
-

Welcome, CMC!

- +
\ No newline at end of file diff --git a/support-portal-frontend/src/app/component/home/home.component.ts b/support-portal-frontend/src/app/component/home/home.component.ts index 007fef0..a5413b7 100644 --- a/support-portal-frontend/src/app/component/home/home.component.ts +++ b/support-portal-frontend/src/app/component/home/home.component.ts @@ -1,4 +1,46 @@ import { Component, OnInit } from '@angular/core'; +import { forkJoin } from 'rxjs'; +import { EventService } from '../../service/event.service'; +import { BlogService } from '../../service/blog.service'; +import { CareerService } from '../../service/career.service'; +import { EducationService } from '../../service/education.service'; +import { MilestoneService } from '../../service/milestone.service'; +import { DashboardService } from '../../service/dashboard.service'; + +interface DashboardStats { + events: { + total: number; + upcoming: number; + active: number; + }; + blogs: { + total: number; + published: number; + drafts: number; + }; + careers: { + activeJobs: number; + totalApplications: number; + pendingApplications: number; + }; + education: { + activeCourses: number; + totalApplications: number; + pendingApplications: number; + }; + professors: { + total: number; + active: number; + }; + testimonials: { + total: number; + active: number; + }; + milestones: { + total: number; + active: number; + }; +} @Component({ selector: 'app-home', @@ -6,10 +48,166 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { + stats: DashboardStats = { + events: { total: 0, upcoming: 0, active: 0 }, + blogs: { total: 0, published: 0, drafts: 0 }, + careers: { activeJobs: 0, totalApplications: 0, pendingApplications: 0 }, + education: { activeCourses: 0, totalApplications: 0, pendingApplications: 0 }, + professors: { total: 0, active: 0 }, + testimonials: { total: 0, active: 0 }, + milestones: { total: 0, active: 0 } + }; - constructor() { } + loading = true; + error: string | null = null; + + recentActivities: any[] = []; + + constructor( + private eventService: EventService, + private blogService: BlogService, + private careerService: CareerService, + private educationService: EducationService, + private milestoneService: MilestoneService, + private dashboardService: DashboardService + ) { } ngOnInit(): void { + this.loadDashboardData(); } -} + loadDashboardData(): void { + this.loading = true; + this.error = null; + + forkJoin({ + events: this.eventService.getEvents(), + blogs: this.blogService.getBlogs(), + jobs: this.careerService.getAllJobs(), + jobApplications: this.careerService.getAllApplications(), + courses: this.educationService.getAllCourses(), + courseApplications: this.educationService.getAllApplications(), + milestones: this.milestoneService.getAllMilestones(), + professors: this.dashboardService.getProfessors(), + testimonials: this.dashboardService.getTestimonials() + }).subscribe({ + next: (data) => { + this.calculateStats(data); + this.generateRecentActivities(data); + this.loading = false; + }, + error: (error) => { + console.error('Error loading dashboard data:', error); + this.error = 'Failed to load dashboard data. Please try again.'; + this.loading = false; + } + }); + } + + calculateStats(data: any): void { + // Events stats + this.stats.events.total = data.events?.length || 0; + const now = new Date(); + this.stats.events.upcoming = data.events?.filter((e: any) => + new Date(e.eventDate) > now + ).length || 0; + this.stats.events.active = data.events?.filter((e: any) => e.isActive).length || 0; + + // Blogs stats + this.stats.blogs.total = data.blogs?.length || 0; + this.stats.blogs.published = data.blogs?.filter((b: any) => b.posted).length || 0; + this.stats.blogs.drafts = data.blogs?.filter((b: any) => !b.posted).length || 0; + + // Careers stats + this.stats.careers.activeJobs = data.jobs?.filter((j: any) => j.isActive).length || 0; + this.stats.careers.totalApplications = data.jobApplications?.length || 0; + this.stats.careers.pendingApplications = data.jobApplications?.filter( + (a: any) => a.status === 'PENDING' || !a.status + ).length || 0; + + // Education stats + this.stats.education.activeCourses = data.courses?.filter((c: any) => c.isActive).length || 0; + this.stats.education.totalApplications = data.courseApplications?.length || 0; + this.stats.education.pendingApplications = data.courseApplications?.filter( + (a: any) => a.status === 'PENDING' || !a.status + ).length || 0; + + // Milestones stats + this.stats.milestones.total = data.milestones?.length || 0; + this.stats.milestones.active = data.milestones?.filter((m: any) => m.isActive).length || 0; + + // Professors stats + this.stats.professors.total = data.professors?.length || 0; + this.stats.professors.active = data.professors?.filter( + (p: any) => p.status === 'ACTIVE' + ).length || 0; + + // Testimonials stats + this.stats.testimonials.total = data.testimonials?.length || 0; + this.stats.testimonials.active = data.testimonials?.filter((t: any) => t.isActive).length || 0; + } + + generateRecentActivities(data: any): void { + this.recentActivities = []; + + // Add recent job applications + if (data.jobApplications && data.jobApplications.length > 0) { + const recentJobApps = data.jobApplications + .sort((a: any, b: any) => new Date(b.createdDate).getTime() - new Date(a.createdDate).getTime()) + .slice(0, 3); + + recentJobApps.forEach((app: any) => { + this.recentActivities.push({ + type: 'Job Application', + description: `New application from ${app.fullName} for ${app.job?.title || 'Unknown Position'}`, + date: app.createdDate, + icon: 'briefcase', + color: 'primary' + }); + }); + } + + // Add recent course applications + if (data.courseApplications && data.courseApplications.length > 0) { + const recentCourseApps = data.courseApplications + .sort((a: any, b: any) => new Date(b.createdDate).getTime() - new Date(a.createdDate).getTime()) + .slice(0, 3); + + recentCourseApps.forEach((app: any) => { + this.recentActivities.push({ + type: 'Course Application', + description: `New enrollment from ${app.fullName} for ${app.course?.title || 'Unknown Course'}`, + date: app.createdDate, + icon: 'book', + color: 'success' + }); + }); + } + + // Sort all activities by date and limit to 5 + this.recentActivities = this.recentActivities + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + .slice(0, 5); + } + + getTimeAgo(date: string): string { + const now = new Date(); + const past = new Date(date); + const diffInMs = now.getTime() - past.getTime(); + const diffInMinutes = Math.floor(diffInMs / 60000); + const diffInHours = Math.floor(diffInMs / 3600000); + const diffInDays = Math.floor(diffInMs / 86400000); + + if (diffInMinutes < 60) { + return `${diffInMinutes} minute${diffInMinutes !== 1 ? 's' : ''} ago`; + } else if (diffInHours < 24) { + return `${diffInHours} hour${diffInHours !== 1 ? 's' : ''} ago`; + } else { + return `${diffInDays} day${diffInDays !== 1 ? 's' : ''} ago`; + } + } + + refreshDashboard(): void { + this.loadDashboardData(); + } +} \ No newline at end of file diff --git a/support-portal-frontend/src/app/component/login/login.component.css b/support-portal-frontend/src/app/component/login/login.component.css index bae92cb..080ecc5 100644 --- a/support-portal-frontend/src/app/component/login/login.component.css +++ b/support-portal-frontend/src/app/component/login/login.component.css @@ -1,69 +1,330 @@ -body, -html { - margin: 0; - padding: 0; - height: 100%; - background: #60a3bc !important; -} -.user_card { - height: 500px; - width: 600px; - margin-top: auto; - margin-bottom: auto; - background: #afbfd8; - position: relative; +/* Login Layout */ +.login-layout { + min-height: 100vh; display: flex; + align-items: center; justify-content: center; - flex-direction: column; - padding: 10px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0a, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - border-radius: 5px; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + padding: 20px; + position: relative; + overflow: hidden; +} -} -.brand_logo_container { +.login-layout::before { + content: ''; position: absolute; - height: 170px; - width: 170px; - top: -75px; + top: -50%; + right: -20%; + width: 600px; + height: 600px; + background: radial-gradient(circle, rgba(230, 72, 56, 0.05) 0%, transparent 70%); border-radius: 50%; - background: #60a3bc; - padding: 10px; - text-align: center; + pointer-events: none; } -.brand_logo { - height: 150px; - width: 150px; + +.login-layout::after { + content: ''; + position: absolute; + bottom: -50%; + left: -20%; + width: 500px; + height: 500px; + background: radial-gradient(circle, rgba(1, 32, 104, 0.04) 0%, transparent 70%); border-radius: 50%; - border: 2px solid white; + pointer-events: none; } -.form_container { - margin-top: 100px; -} -.login_btn { + +/* Login Container */ +.login-container { + position: relative; + z-index: 10; width: 100%; - background: #1B4F72!important; - color: white !important; + max-width: 380px; + animation: fadeInUp 0.6s ease; } -.login_btn:focus { - box-shadow: none !important; - outline: 0px !important; + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } } -.login_container { - padding: 0 2rem; + +/* Login Card */ +.login-card { + background: #ffffff; + border-radius: 16px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + overflow: hidden; + border: 1px solid #e5e7eb; } -.input-group-text { - background: #1B4F72!important; - color: white !important; - border: 0 !important; - border-radius: 0.25rem 0 0 0.25rem !important; + +/* Login Header */ +.login-header { + text-align: center; + padding: 32px 32px 20px; + background: #ffffff; + border-bottom: 3px solid #e64838; } -.input_user, -.input_pass:focus { - box-shadow: none !important; - outline: 0px !important; + +.logo-wrapper { + margin-bottom: 16px; } -.custom-checkbox .custom-control-input:checked~.custom-control-label::before { - background-color: #1B4F72!important; + +.logo-image { + width: 60px; + height: 60px; + object-fit: contain; + filter: drop-shadow(0 2px 8px rgba(1, 32, 104, 0.15)); } + +.login-title { + font-size: 22px; + font-weight: 700; + color: #012068; + margin: 0 0 6px 0; + letter-spacing: -0.5px; +} + +.login-subtitle { + font-size: 14px; + color: #6b7280; + margin: 0; +} + +/* Login Form */ +.login-form { + padding: 24px 32px 28px; + background: #ffffff; +} + +/* Form Group */ +.form-group { + margin-bottom: 18px; +} + +.form-group:last-of-type { + margin-bottom: 24px; +} + +.form-group label { + display: block; + font-size: 13px; + font-weight: 600; + color: #012068; + margin-bottom: 6px; +} + +/* Input Wrapper */ +.input-wrapper { + position: relative; +} + +.form-input { + width: 100%; + padding: 11px 14px 11px 38px; + border: 1.5px solid #d1d5db; + border-radius: 8px; + font-size: 14px; + color: #1a1a1a; + transition: all 0.3s ease; + background: #f9fafb; +} + +.form-input:focus { + outline: none; + border-color: #012068; + box-shadow: 0 0 0 3px rgba(1, 32, 104, 0.1); + background: #ffffff; +} + +.form-input::placeholder { + color: #9ca3af; +} + +.input-icon { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: #9ca3af; + font-size: 14px; + pointer-events: none; +} + +.form-input:focus + .input-icon { + color: #012068; +} + +.input-error { + border-color: #e64838; +} + +.input-error:focus { + border-color: #e64838; + box-shadow: 0 0 0 3px rgba(230, 72, 56, 0.1); +} + +/* Error Message */ +.error-message { + display: flex; + align-items: center; + gap: 6px; + margin-top: 6px; + font-size: 12px; + color: #e64838; +} + +.error-message i { + font-size: 12px; + flex-shrink: 0; +} + +/* Login Button */ +.btn-login { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 20px; + background: linear-gradient(135deg, #e64838 0%, #d63527 100%); + color: #ffffff; + border: none; + border-radius: 8px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(230, 72, 56, 0.25); +} + +.btn-login:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(230, 72, 56, 0.35); + background: linear-gradient(135deg, #d63527 0%, #c62e1f 100%); +} + +.btn-login:active:not(:disabled) { + transform: translateY(0); +} + +.btn-login:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +.btn-login i { + font-size: 16px; +} + +/* Login Footer */ +.login-footer { + padding: 20px 32px 24px; + text-align: center; + background: #f9fafb; + border-top: 1px solid #e5e7eb; +} + +.footer-text { + font-size: 13px; + color: #6b7280; + margin: 0; +} + +.footer-link { + color: #e64838; + font-weight: 600; + text-decoration: none; + margin-left: 4px; + transition: color 0.2s ease; +} + +.footer-link:hover { + color: #d63527; + text-decoration: underline; +} + +/* Spinner Animation */ +.fa-spin { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* Responsive Design */ +@media (max-width: 576px) { + .login-container { + max-width: 100%; + } + + .login-header { + padding: 28px 24px 18px; + } + + .logo-image { + width: 55px; + height: 55px; + } + + .login-title { + font-size: 20px; + } + + .login-subtitle { + font-size: 13px; + } + + .login-form, + .login-footer { + padding-left: 24px; + padding-right: 24px; + } + + .form-input { + padding: 10px 12px 10px 36px; + } + + .btn-login { + padding: 11px 20px; + } +} + +@media (max-width: 400px) { + .login-card { + border-radius: 14px; + } + + .logo-image { + width: 50px; + height: 50px; + } + + .login-title { + font-size: 19px; + } +} + +/* Print Styles */ +@media print { + .login-layout { + background: #ffffff; + } + + .login-layout::before, + .login-layout::after { + display: none; + } +} \ No newline at end of file diff --git a/support-portal-frontend/src/app/component/login/login.component.html b/support-portal-frontend/src/app/component/login/login.component.html index b9c298c..7dafd9c 100644 --- a/support-portal-frontend/src/app/component/login/login.component.html +++ b/support-portal-frontend/src/app/component/login/login.component.html @@ -1,46 +1,80 @@ -
-
-
-
-
-

User Management Portal

+