Update with new components
This commit is contained in:
@ -0,0 +1,481 @@
|
||||
<div class="container mt-4">
|
||||
<!-- Header -->
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h2 class="text-primary">Education & Training Management</h2>
|
||||
<p class="text-muted">Manage courses, programs, upcoming events and applications</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header Actions -->
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<div>
|
||||
<button *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm"
|
||||
class="btn btn-primary mr-2" (click)="showCourseFormModal()">
|
||||
<i class="fa fa-plus"></i> New Course
|
||||
</button>
|
||||
<button *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm"
|
||||
class="btn btn-info" (click)="showUpcomingEventsModal()">
|
||||
<i class="fa fa-calendar"></i> Manage Upcoming Events
|
||||
</button>
|
||||
|
||||
<button *ngIf="showCourseForm" class="btn btn-secondary" (click)="hideCourseForm()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Courses
|
||||
</button>
|
||||
<button *ngIf="showApplications" class="btn btn-secondary" (click)="hideApplications()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Courses
|
||||
</button>
|
||||
<button *ngIf="showUpcomingEvents && !showUpcomingEventForm" class="btn btn-secondary mr-2"
|
||||
(click)="hideUpcomingEventsModal()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Courses
|
||||
</button>
|
||||
<button *ngIf="showUpcomingEvents && !showUpcomingEventForm" class="btn btn-primary"
|
||||
(click)="showUpcomingEventFormModal()">
|
||||
<i class="fa fa-plus"></i> New Event
|
||||
</button>
|
||||
<button *ngIf="showUpcomingEventForm" class="btn btn-secondary" (click)="hideUpcomingEventForm()">
|
||||
<i class="fa fa-arrow-left"></i> Back to Events
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm">
|
||||
<span class="badge badge-info">Total Courses: {{ courses.length }}</span>
|
||||
<span class="badge badge-warning ml-2">Total Applications: {{ applications.length }}</span>
|
||||
<span class="badge badge-success ml-2">Upcoming Events: {{ upcomingEvents.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upcoming Event Form -->
|
||||
<div *ngIf="showUpcomingEventForm" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{{ editingUpcomingEvent ? 'Edit Upcoming Event' : 'Create New Upcoming Event' }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form [formGroup]="upcomingEventForm" (ngSubmit)="saveUpcomingEvent()">
|
||||
<div class="form-group">
|
||||
<label for="eventTitle" class="text-primary">Event Title *</label>
|
||||
<input type="text" id="eventTitle" class="form-control" formControlName="title"
|
||||
placeholder="Enter event title">
|
||||
<div *ngIf="upcomingEventForm.get('title')?.invalid && upcomingEventForm.get('title')?.touched"
|
||||
class="text-danger">
|
||||
Event title is required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="eventDescription" class="text-primary">Event Description *</label>
|
||||
<textarea id="eventDescription" class="form-control" formControlName="description" rows="4"
|
||||
placeholder="Enter event description"></textarea>
|
||||
<div *ngIf="upcomingEventForm.get('description')?.invalid && upcomingEventForm.get('description')?.touched"
|
||||
class="text-danger">
|
||||
Event description is required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="eventSchedule" class="text-primary">Schedule *</label>
|
||||
<input type="text" id="eventSchedule" class="form-control" formControlName="schedule"
|
||||
placeholder="e.g., Q3 2025, Monthly Sessions, Ongoing">
|
||||
<div *ngIf="upcomingEventForm.get('schedule')?.invalid && upcomingEventForm.get('schedule')?.touched"
|
||||
class="text-danger">
|
||||
Schedule is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="eventDate" class="text-primary">Event Date (Optional)</label>
|
||||
<input type="date" id="eventDate" class="form-control" formControlName="eventDate">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="eventIsActive" formControlName="isActive">
|
||||
<label class="form-check-label text-primary" for="eventIsActive">Active (visible on website)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" [disabled]="upcomingEventForm.invalid">
|
||||
<i class="fa fa-save"></i> {{ editingUpcomingEvent ? 'Update Event' : 'Create Event' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" (click)="resetUpcomingEventForm()">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upcoming Events List -->
|
||||
<div *ngIf="showUpcomingEvents && !showUpcomingEventForm">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Upcoming Events Management</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div *ngIf="upcomingEvents.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Description</th>
|
||||
<th>Schedule</th>
|
||||
<th>Event Date</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let event of upcomingEvents">
|
||||
<td>
|
||||
<strong>{{ event.title }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span [title]="event.description">
|
||||
{{ event.description.length > 100 ? (event.description | slice:0:100) + '...' :
|
||||
event.description }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ event.schedule }}</td>
|
||||
<td>{{ event.eventDate ? (event.eventDate | date:'short') : 'N/A' }}</td>
|
||||
<td>
|
||||
<span *ngIf="event.isActive" class="badge badge-success">Active</span>
|
||||
<span *ngIf="!event.isActive" class="badge badge-danger">Inactive</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm mr-2" (click)="editUpcomingEvent(event)">
|
||||
<i class="fa fa-edit"></i> Edit
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" (click)="deleteUpcomingEvent(event)">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="upcomingEvents.length === 0" class="alert alert-info">
|
||||
No upcoming events available. Please create a new event.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Course Form (existing content) -->
|
||||
<div *ngIf="showCourseForm" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{{ editing ? 'Edit Course' : 'Create New Course' }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form [formGroup]="courseForm" (ngSubmit)="saveCourse()">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="title" class="text-primary">Course Title *</label>
|
||||
<input type="text" id="title" class="form-control" formControlName="title"
|
||||
placeholder="Enter course title">
|
||||
<div *ngIf="courseForm.get('title')?.invalid && courseForm.get('title')?.touched"
|
||||
class="text-danger">
|
||||
Course title is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="instructor" class="text-primary">Instructor *</label>
|
||||
<input type="text" id="instructor" class="form-control" formControlName="instructor"
|
||||
placeholder="Enter instructor name">
|
||||
<div *ngIf="courseForm.get('instructor')?.invalid && courseForm.get('instructor')?.touched"
|
||||
class="text-danger">
|
||||
Instructor is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description" class="text-primary">Course Description *</label>
|
||||
<textarea id="description" class="form-control" formControlName="description" rows="4"
|
||||
placeholder="Enter course description"></textarea>
|
||||
<div *ngIf="courseForm.get('description')?.invalid && courseForm.get('description')?.touched"
|
||||
class="text-danger">
|
||||
Course description is required.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="duration" class="text-primary">Duration *</label>
|
||||
<input type="text" id="duration" class="form-control" formControlName="duration"
|
||||
placeholder="e.g., 3 Days, 2 Years">
|
||||
<div *ngIf="courseForm.get('duration')?.invalid && courseForm.get('duration')?.touched"
|
||||
class="text-danger">
|
||||
Duration is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="seats" class="text-primary">Number of Seats *</label>
|
||||
<input type="number" id="seats" class="form-control" formControlName="seats"
|
||||
placeholder="Enter number of seats" min="1">
|
||||
<div *ngIf="courseForm.get('seats')?.invalid && courseForm.get('seats')?.touched"
|
||||
class="text-danger">
|
||||
Number of seats is required and must be at least 1.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="price" class="text-primary">Price</label>
|
||||
<input type="text" id="price" class="form-control" formControlName="price"
|
||||
placeholder="e.g., $15,000 or N/A">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="category" class="text-primary">Category *</label>
|
||||
<select id="category" class="form-control" formControlName="category">
|
||||
<option value="">Select category</option>
|
||||
<option value="Certification">Certification</option>
|
||||
<option value="Training">Training</option>
|
||||
<option value="Workshop">Workshop</option>
|
||||
<option value="Fellowship">Fellowship</option>
|
||||
<option value="Course">Course</option>
|
||||
</select>
|
||||
<div *ngIf="courseForm.get('category')?.invalid && courseForm.get('category')?.touched"
|
||||
class="text-danger">
|
||||
Category is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="level" class="text-primary">Level *</label>
|
||||
<select id="level" class="form-control" formControlName="level">
|
||||
<option value="">Select level</option>
|
||||
<option value="Beginner">Beginner</option>
|
||||
<option value="Intermediate">Intermediate</option>
|
||||
<option value="Advanced">Advanced</option>
|
||||
<option value="Professional">Professional</option>
|
||||
<option value="Post-Doctoral">Post-Doctoral</option>
|
||||
</select>
|
||||
<div *ngIf="courseForm.get('level')?.invalid && courseForm.get('level')?.touched"
|
||||
class="text-danger">
|
||||
Level is required.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label for="startDate" class="text-primary">Start Date</label>
|
||||
<input type="date" id="startDate" class="form-control" formControlName="startDate">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Upload Section -->
|
||||
<div class="form-group">
|
||||
<label class="text-primary">Course Image</label>
|
||||
|
||||
<!-- Image Preview -->
|
||||
<div *ngIf="imagePreview" class="mb-3">
|
||||
<div class="position-relative d-inline-block">
|
||||
<img [src]="getFullImageUrl(imagePreview)" alt="Preview" class="img-thumbnail"
|
||||
style="max-width: 300px; max-height: 200px;">
|
||||
<button type="button" class="btn btn-danger btn-sm position-absolute"
|
||||
style="top: -5px; right: -5px;" (click)="removeImage()">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File Input -->
|
||||
<div class="mb-2">
|
||||
<input type="file" id="imageInput" class="form-control" accept="image/*"
|
||||
(change)="onImageSelected($event)">
|
||||
<small class="form-text text-muted">Supported formats: JPG, PNG, GIF. Max size: 5MB</small>
|
||||
</div>
|
||||
|
||||
<!-- Upload Error -->
|
||||
<div *ngIf="uploadError" class="alert alert-danger alert-sm">
|
||||
{{ uploadError }}
|
||||
</div>
|
||||
|
||||
<!-- Upload Progress -->
|
||||
<div *ngIf="isImageUploading" class="mb-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<small class="text-muted">Uploading image...</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="eligibility" class="text-primary">Eligibility Criteria</label>
|
||||
<textarea id="eligibility" class="form-control" formControlName="eligibility" rows="3"
|
||||
placeholder="Enter eligibility criteria separated by commas"></textarea>
|
||||
<small class="form-text text-muted">Separate multiple criteria with commas</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="objectives" class="text-primary">Learning Objectives</label>
|
||||
<textarea id="objectives" class="form-control" formControlName="objectives" rows="3"
|
||||
placeholder="Enter learning objectives separated by commas"></textarea>
|
||||
<small class="form-text text-muted">Separate multiple objectives with commas</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="isActive" formControlName="isActive">
|
||||
<label class="form-check-label text-primary" for="isActive">Active (visible to students)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" [disabled]="courseForm.invalid || isImageUploading">
|
||||
<i class="fa fa-save"></i> {{ editing ? 'Update Course' : 'Create Course' }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" (click)="resetCourseForm()">
|
||||
<i class="fa fa-times"></i> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Applications View (existing content) -->
|
||||
<div *ngIf="showApplications && selectedCourseForApplications" class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Applications for: {{ selectedCourseForApplications.title }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div *ngIf="applications.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Applicant</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Qualification</th>
|
||||
<th>Experience</th>
|
||||
<th>Status</th>
|
||||
<th>Applied Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let application of applications">
|
||||
<td>{{ application.fullName }}</td>
|
||||
<td>{{ application.email }}</td>
|
||||
<td>{{ application.phone }}</td>
|
||||
<td>{{ application.qualification }}</td>
|
||||
<td>{{ application.experience || 'N/A' }}</td>
|
||||
<td>
|
||||
<span class="badge" [ngClass]="getStatusBadgeClass(application.status || 'pending')">
|
||||
{{ application.status || 'PENDING' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ application.createdDate | date:'short' }}</td>
|
||||
<td>
|
||||
<div class="btn-group dropdown">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle"
|
||||
data-toggle="dropdown">
|
||||
Status
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'PENDING')">Pending</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'REVIEWED')">Reviewed</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'SHORTLISTED')">Shortlisted</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'ACCEPTED')">Accepted</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'ENROLLED')">Enrolled</a>
|
||||
<a class="dropdown-item"
|
||||
(click)="updateApplicationStatus(application, 'REJECTED')">Rejected</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger ml-1" (click)="deleteApplication(application)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="applications.length === 0" class="alert alert-info">
|
||||
No applications found for this course.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Courses List (existing content) -->
|
||||
<div *ngIf="!showCourseForm && !showApplications && !showUpcomingEvents && !showUpcomingEventForm">
|
||||
<div *ngIf="courses.length > 0">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>Course</th>
|
||||
<th>Category</th>
|
||||
<th>Duration</th>
|
||||
<th>Seats</th>
|
||||
<th>Status</th>
|
||||
<th>Applications</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let course of courses">
|
||||
<td>
|
||||
<img *ngIf="course.imageUrl" [src]="getFullImageUrl(course.imageUrl)"
|
||||
alt="{{ course.title }}" class="img-thumbnail"
|
||||
style="width: 60px; height: 60px; object-fit: cover;">
|
||||
<span *ngIf="!course.imageUrl" class="text-muted">No image</span>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ course.title }}</strong>
|
||||
<br>
|
||||
<small class="text-muted">{{ course.instructor }}</small>
|
||||
<br>
|
||||
<small class="text-muted">{{ course.level }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-secondary">{{ course.category }}</span>
|
||||
</td>
|
||||
<td>{{ course.duration }}</td>
|
||||
<td>{{ course.seats }}</td>
|
||||
<td>
|
||||
<span *ngIf="course.isActive" class="badge badge-success">Active</span>
|
||||
<span *ngIf="!course.isActive" class="badge badge-danger">Inactive</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" (click)="viewApplications(course)">
|
||||
View Applications
|
||||
<span class="badge badge-light ml-1">{{ getApplicationCount(course.id) }}</span>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm mr-2" (click)="editCourse(course)">
|
||||
<i class="fa fa-edit"></i> Edit
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" (click)="deleteCourse(course)">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div *ngIf="courses.length === 0" class="alert alert-info">
|
||||
No courses available. Please create a new course.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EducationComponent } from './education.component';
|
||||
|
||||
describe('EducationComponent', () => {
|
||||
let component: EducationComponent;
|
||||
let fixture: ComponentFixture<EducationComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ EducationComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EducationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,385 @@
|
||||
// education.component.ts
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { EducationService, Course, CourseApplication } from '../../service/education.service';
|
||||
import { UpcomingEventsService, UpcomingEvent } from '../../service/upcoming-events.service';
|
||||
import { FileUploadService } from '../../service/file-upload.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-education',
|
||||
templateUrl: './education.component.html',
|
||||
styleUrls: ['./education.component.css'],
|
||||
})
|
||||
export class EducationComponent implements OnInit {
|
||||
courses: Course[] = [];
|
||||
applications: CourseApplication[] = [];
|
||||
upcomingEvents: UpcomingEvent[] = [];
|
||||
selectedCourse: Course | null = null;
|
||||
selectedUpcomingEvent: UpcomingEvent | null = null;
|
||||
courseForm: FormGroup;
|
||||
upcomingEventForm: FormGroup;
|
||||
showCourseForm = false;
|
||||
showUpcomingEventForm = false;
|
||||
editing = false;
|
||||
editingUpcomingEvent = false;
|
||||
showApplications = false;
|
||||
showUpcomingEvents = false;
|
||||
|
||||
// Filter for applications
|
||||
selectedCourseForApplications: Course | null = null;
|
||||
|
||||
// Image upload properties
|
||||
selectedImage: File | null = null;
|
||||
imagePreview: string | null = null;
|
||||
isImageUploading = false;
|
||||
uploadError: string | null = null;
|
||||
|
||||
constructor(
|
||||
private educationService: EducationService,
|
||||
private upcomingEventsService: UpcomingEventsService,
|
||||
private fileUploadService: FileUploadService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.courseForm = this.fb.group({
|
||||
title: ['', Validators.required],
|
||||
description: ['', Validators.required],
|
||||
duration: ['', Validators.required],
|
||||
seats: ['', [Validators.required, Validators.min(1)]],
|
||||
category: ['', Validators.required],
|
||||
level: ['', Validators.required],
|
||||
instructor: ['', Validators.required],
|
||||
price: [''],
|
||||
startDate: [''],
|
||||
eligibility: [''],
|
||||
objectives: [''],
|
||||
imageUrl: [''],
|
||||
isActive: [true]
|
||||
});
|
||||
this.upcomingEventForm = this.fb.group({
|
||||
title: ['', Validators.required],
|
||||
description: ['', Validators.required],
|
||||
schedule: ['', Validators.required],
|
||||
eventDate: [''],
|
||||
isActive: [true]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadCourses();
|
||||
this.loadApplications();
|
||||
this.loadUpcomingEvents();
|
||||
}
|
||||
|
||||
loadCourses() {
|
||||
this.educationService.getAllCourses().subscribe(data => {
|
||||
this.courses = data;
|
||||
});
|
||||
}
|
||||
|
||||
loadApplications() {
|
||||
this.educationService.getAllApplications().subscribe(data => {
|
||||
this.applications = data;
|
||||
});
|
||||
}
|
||||
|
||||
getFullImageUrl(imageUrl: string): string {
|
||||
if (!imageUrl) return '';
|
||||
|
||||
// If it's already a full URL, return as-is
|
||||
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
// Otherwise, prepend your API base URL
|
||||
return environment.apiUrl + imageUrl;
|
||||
}
|
||||
loadUpcomingEvents() {
|
||||
this.upcomingEventsService.getAllUpcomingEvents().subscribe(data => {
|
||||
this.upcomingEvents = data;
|
||||
});
|
||||
}
|
||||
|
||||
// Upcoming Events Management
|
||||
showUpcomingEventsModal() {
|
||||
this.showUpcomingEvents = true;
|
||||
}
|
||||
|
||||
hideUpcomingEventsModal() {
|
||||
this.showUpcomingEvents = false;
|
||||
this.hideUpcomingEventForm();
|
||||
}
|
||||
|
||||
showUpcomingEventFormModal() {
|
||||
this.showUpcomingEventForm = true;
|
||||
this.resetUpcomingEventForm();
|
||||
|
||||
setTimeout(() => {
|
||||
this.upcomingEventForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
hideUpcomingEventForm() {
|
||||
this.showUpcomingEventForm = false;
|
||||
this.resetUpcomingEventForm();
|
||||
}
|
||||
|
||||
editUpcomingEvent(event: UpcomingEvent) {
|
||||
this.selectedUpcomingEvent = event;
|
||||
this.upcomingEventForm.patchValue({
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
schedule: event.schedule,
|
||||
eventDate: event.eventDate || '',
|
||||
isActive: event.isActive
|
||||
});
|
||||
this.editingUpcomingEvent = true;
|
||||
this.showUpcomingEventForm = true;
|
||||
}
|
||||
|
||||
saveUpcomingEvent() {
|
||||
if (this.upcomingEventForm.valid) {
|
||||
const eventData = this.upcomingEventForm.value;
|
||||
eventData.isActive = this.upcomingEventForm.get('isActive')?.value === true;
|
||||
|
||||
if (this.editingUpcomingEvent && this.selectedUpcomingEvent) {
|
||||
this.upcomingEventsService.updateUpcomingEvent(this.selectedUpcomingEvent.id!, eventData).subscribe(() => {
|
||||
this.loadUpcomingEvents();
|
||||
this.hideUpcomingEventForm();
|
||||
});
|
||||
} else {
|
||||
this.upcomingEventsService.createUpcomingEvent(eventData).subscribe(() => {
|
||||
this.loadUpcomingEvents();
|
||||
this.hideUpcomingEventForm();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteUpcomingEvent(event: UpcomingEvent) {
|
||||
if (confirm('Are you sure you want to delete this upcoming event?')) {
|
||||
this.upcomingEventsService.deleteUpcomingEvent(event.id!).subscribe(() => {
|
||||
this.loadUpcomingEvents();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetUpcomingEventForm() {
|
||||
this.upcomingEventForm.reset();
|
||||
this.selectedUpcomingEvent = null;
|
||||
this.editingUpcomingEvent = false;
|
||||
this.upcomingEventForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}
|
||||
|
||||
showCourseFormModal() {
|
||||
this.showCourseForm = true;
|
||||
this.resetCourseForm();
|
||||
|
||||
setTimeout(() => {
|
||||
this.courseForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
hideCourseForm() {
|
||||
this.showCourseForm = false;
|
||||
this.resetCourseForm();
|
||||
}
|
||||
|
||||
editCourse(course: Course) {
|
||||
this.selectedCourse = course;
|
||||
this.courseForm.patchValue({
|
||||
title: course.title,
|
||||
description: course.description,
|
||||
duration: course.duration,
|
||||
seats: course.seats,
|
||||
category: course.category,
|
||||
level: course.level,
|
||||
instructor: course.instructor,
|
||||
price: course.price || '',
|
||||
startDate: course.startDate || '',
|
||||
eligibility: course.eligibility ? course.eligibility.join(', ') : '',
|
||||
objectives: course.objectives ? course.objectives.join(', ') : '',
|
||||
imageUrl: course.imageUrl || '',
|
||||
isActive: course.isActive
|
||||
});
|
||||
|
||||
if (course.imageUrl) {
|
||||
this.imagePreview = course.imageUrl;
|
||||
}
|
||||
|
||||
this.editing = true;
|
||||
this.showCourseForm = true;
|
||||
}
|
||||
|
||||
// Image handling methods
|
||||
onImageSelected(event: any) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
this.selectedImage = file;
|
||||
this.uploadError = null;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: any) => {
|
||||
this.imagePreview = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
uploadImage(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.selectedImage) {
|
||||
resolve('');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isImageUploading = true;
|
||||
this.uploadError = null;
|
||||
|
||||
this.fileUploadService.uploadFile(this.selectedImage).subscribe({
|
||||
next: (response) => {
|
||||
this.isImageUploading = false;
|
||||
this.courseForm.patchValue({ imageUrl: response.url });
|
||||
resolve(response.url);
|
||||
},
|
||||
error: (error) => {
|
||||
this.isImageUploading = false;
|
||||
this.uploadError = error.error?.error || 'Failed to upload image';
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeImage() {
|
||||
this.selectedImage = null;
|
||||
this.imagePreview = null;
|
||||
this.courseForm.patchValue({ imageUrl: '' });
|
||||
this.uploadError = null;
|
||||
|
||||
const fileInput = document.getElementById('imageInput') as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
async saveCourse() {
|
||||
if (this.courseForm.valid) {
|
||||
try {
|
||||
if (this.selectedImage) {
|
||||
await this.uploadImage();
|
||||
}
|
||||
|
||||
const courseData = this.courseForm.value;
|
||||
|
||||
courseData.isActive = this.courseForm.get('isActive')?.value === true;
|
||||
|
||||
courseData.eligibility = courseData.eligibility
|
||||
? courseData.eligibility.split(',').map((item: string) => item.trim()).filter((item: string) => item.length > 0)
|
||||
: [];
|
||||
courseData.objectives = courseData.objectives
|
||||
? courseData.objectives.split(',').map((item: string) => item.trim()).filter((item: string) => item.length > 0)
|
||||
: [];
|
||||
|
||||
if (this.editing && this.selectedCourse) {
|
||||
this.educationService.updateCourse(this.selectedCourse.id!, courseData).subscribe(() => {
|
||||
this.loadCourses();
|
||||
this.hideCourseForm();
|
||||
});
|
||||
} else {
|
||||
this.educationService.createCourse(courseData).subscribe(() => {
|
||||
this.loadCourses();
|
||||
this.hideCourseForm();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving course:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteCourse(course: Course) {
|
||||
if (confirm('Are you sure you want to delete this course?')) {
|
||||
this.educationService.deleteCourse(course.id!).subscribe(() => {
|
||||
this.loadCourses();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetCourseForm() {
|
||||
this.courseForm.reset();
|
||||
this.selectedCourse = null;
|
||||
this.editing = false;
|
||||
this.selectedImage = null;
|
||||
this.imagePreview = null;
|
||||
this.uploadError = null;
|
||||
this.courseForm.patchValue({
|
||||
isActive: true
|
||||
});
|
||||
|
||||
const fileInput = document.getElementById('imageInput') as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Application management
|
||||
viewApplications(course: Course) {
|
||||
this.selectedCourseForApplications = course;
|
||||
this.showApplications = true;
|
||||
this.educationService.getApplicationsByCourseId(course.id!).subscribe(data => {
|
||||
this.applications = data;
|
||||
});
|
||||
}
|
||||
|
||||
hideApplications() {
|
||||
this.showApplications = false;
|
||||
this.selectedCourseForApplications = null;
|
||||
this.loadApplications();
|
||||
}
|
||||
|
||||
updateApplicationStatus(application: CourseApplication, status: string) {
|
||||
this.educationService.updateApplicationStatus(application.id!, status).subscribe(() => {
|
||||
if (this.selectedCourseForApplications) {
|
||||
this.viewApplications(this.selectedCourseForApplications);
|
||||
} else {
|
||||
this.loadApplications();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteApplication(application: CourseApplication) {
|
||||
if (confirm('Are you sure you want to delete this application?')) {
|
||||
this.educationService.deleteApplication(application.id!).subscribe(() => {
|
||||
if (this.selectedCourseForApplications) {
|
||||
this.viewApplications(this.selectedCourseForApplications);
|
||||
} else {
|
||||
this.loadApplications();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatusBadgeClass(status: string): string {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'pending': return 'badge-warning';
|
||||
case 'reviewed': return 'badge-info';
|
||||
case 'shortlisted': return 'badge-primary';
|
||||
case 'accepted': return 'badge-success';
|
||||
case 'enrolled': return 'badge-success';
|
||||
case 'rejected': return 'badge-danger';
|
||||
default: return 'badge-light';
|
||||
}
|
||||
}
|
||||
|
||||
getApplicationCount(courseId?: number): number {
|
||||
if (!courseId) return 0;
|
||||
return this.applications.filter(app => app.course?.id === courseId).length;
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user