Version 1.0 upload
This commit is contained in:
30
src/components/CompositeWidget.ts
Normal file
30
src/components/CompositeWidget.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// components/MergedWidget.ts
|
||||
import { Widget } from './Widget';
|
||||
|
||||
export class MergedWidget extends Widget {
|
||||
private children: Widget[] = [];
|
||||
|
||||
constructor(sizeType: 'default' | 'icon' = 'default') {
|
||||
super(sizeType);
|
||||
}
|
||||
|
||||
addWidget(widget: Widget): void {
|
||||
this.children.push(widget);
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
// Clear current container contents (in case render is called more than once)
|
||||
this.container.innerHTML = '';
|
||||
|
||||
for (const widget of this.children) {
|
||||
const rendered = widget.render();
|
||||
|
||||
// Move child nodes (not the container itself)
|
||||
while (rendered.firstChild) {
|
||||
this.container.appendChild(rendered.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
56
src/components/Modal.ts
Normal file
56
src/components/Modal.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// components/Modal.ts
|
||||
import { createElement } from '../utils/utils';
|
||||
import { Widget } from './Widget';
|
||||
|
||||
export class Modal {
|
||||
private modalOverlay: HTMLDivElement;
|
||||
private modalContent: HTMLDivElement;
|
||||
private widget: Widget | null = null;
|
||||
private isVisible: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.modalOverlay = createElement('div') as HTMLDivElement;
|
||||
this.modalOverlay.classList.add('modal-overlay');
|
||||
this.modalOverlay.style.display = 'none'; // Initially hidden
|
||||
this.modalOverlay.addEventListener('click', (event) => {
|
||||
if (event.target === this.modalOverlay) {
|
||||
this.hide(); // Close modal if clicked outside content
|
||||
}
|
||||
});
|
||||
|
||||
this.modalContent = createElement('div') as HTMLDivElement;
|
||||
this.modalContent.classList.add('modal-content');
|
||||
this.modalOverlay.appendChild(this.modalContent);
|
||||
document.body.appendChild(this.modalOverlay); // Append to body once
|
||||
}
|
||||
|
||||
setWidget(widget: Widget): void {
|
||||
this.widget = widget;
|
||||
this.modalContent.innerHTML = ''; // Clear previous content
|
||||
this.modalContent.appendChild(widget.render());
|
||||
}
|
||||
|
||||
show(): void {
|
||||
if (this.widget) {
|
||||
this.modalOverlay.style.display = 'flex';
|
||||
this.isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.modalOverlay.style.display = 'none';
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
isVisibleModal(): boolean {
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
getContainer(): HTMLDivElement {
|
||||
return this.modalOverlay;
|
||||
}
|
||||
|
||||
getModalContentContainer(): HTMLDivElement {
|
||||
return this.modalContent;
|
||||
}
|
||||
}
|
||||
176
src/components/TopBar.ts
Normal file
176
src/components/TopBar.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
// components/TopBar.ts
|
||||
import { createElement, navigateTo, isLoggedInUser } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, ProfileResponseData } from '../api/api'; // Import API and types (now exported)
|
||||
|
||||
export class TopBar {
|
||||
private container: HTMLElement;
|
||||
private profileDropdownVisible: boolean = false;
|
||||
private profileData: ProfileResponseData | null = null;
|
||||
|
||||
constructor() {
|
||||
this.container = createElement('nav');
|
||||
this.container.classList.add('top-bar', 'navbar', 'navbar-expand-lg', 'navbar-light', 'bg-darker', 'mb-3', 'px-4');
|
||||
this.fetchProfileData();
|
||||
}
|
||||
|
||||
async fetchProfileData() {
|
||||
if (isLoggedInUser()) {
|
||||
try {
|
||||
const response: ApiResponse<ProfileResponseData> = await globalAPI.getProfile('mockUserId');
|
||||
if (response.success && response.data) {
|
||||
this.profileData = response.data;
|
||||
this.render();
|
||||
} else {
|
||||
console.error("Failed to fetch profile data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching profile data:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
const navbarBrand = createElement('a');
|
||||
navbarBrand.classList.add('navbar-brand');
|
||||
navbarBrand.href = '#/dashboard';
|
||||
navbarBrand.textContent = 'LMS';
|
||||
|
||||
const navbarToggler = createElement('button');
|
||||
navbarToggler.classList.add('navbar-toggler');
|
||||
navbarToggler.type = 'button';
|
||||
navbarToggler.setAttribute('data-bs-toggle', 'collapse');
|
||||
navbarToggler.setAttribute('data-bs-target', '#navbarNav');
|
||||
navbarToggler.setAttribute('aria-controls', 'navbarNav');
|
||||
navbarToggler.setAttribute('aria-expanded', 'false');
|
||||
navbarToggler.setAttribute('aria-label', 'Toggle navigation');
|
||||
navbarToggler.innerHTML = '<span class="navbar-toggler-icon"></span>';
|
||||
|
||||
const navbarCollapse = createElement('div');
|
||||
navbarCollapse.classList.add('collapse', 'navbar-collapse');
|
||||
navbarCollapse.id = 'navbarNav';
|
||||
|
||||
const menuPages = createElement('ul');
|
||||
menuPages.classList.add('navbar-nav', 'me-auto', 'mb-2', 'mb-lg-0');
|
||||
|
||||
const dashboardMenuItem = createElement('li');
|
||||
dashboardMenuItem.classList.add('nav-item');
|
||||
const dashboardLink = createElement('a');
|
||||
dashboardLink.classList.add('nav-link');
|
||||
dashboardLink.href = '#/dashboard';
|
||||
dashboardLink.textContent = 'Dashboard';
|
||||
dashboardMenuItem.appendChild(dashboardLink);
|
||||
menuPages.appendChild(dashboardMenuItem);
|
||||
|
||||
const classroomsMenuItem = createElement('li');
|
||||
classroomsMenuItem.classList.add('nav-item');
|
||||
const classroomsLink = createElement('a');
|
||||
classroomsLink.classList.add('nav-link');
|
||||
classroomsLink.href = '#/classrooms';
|
||||
classroomsLink.textContent = 'Classrooms';
|
||||
classroomsMenuItem.appendChild(classroomsLink);
|
||||
menuPages.appendChild(classroomsMenuItem);
|
||||
|
||||
const adminMenuItem = createElement('li');
|
||||
adminMenuItem.classList.add('nav-item');
|
||||
const adminLink = createElement('a');
|
||||
adminLink.classList.add('nav-link');
|
||||
adminLink.href = '#/admin';
|
||||
adminLink.textContent = 'Admin';
|
||||
adminMenuItem.appendChild(adminLink);
|
||||
menuPages.appendChild(adminMenuItem);
|
||||
|
||||
|
||||
const profileSection = createElement('div');
|
||||
profileSection.classList.add('d-flex', 'align-items-center', 'ms-auto');
|
||||
|
||||
if (this.profileData) {
|
||||
const profileButton = createElement('button');
|
||||
profileButton.classList.add('btn', 'btn-dark', 'dropdown-toggle');
|
||||
profileButton.type = 'button';
|
||||
profileButton.id = 'profileDropdownButton';
|
||||
profileButton.setAttribute('data-bs-toggle', 'dropdown');
|
||||
profileButton.setAttribute('aria-expanded', String(this.profileDropdownVisible));
|
||||
|
||||
const profileImage = createElement('img'); // Placeholder image, replace with actual profile picture logic
|
||||
profileImage.src = this.profileData.profilePicture || 'src/assets/vite.svg'; // Default placeholder if no picture
|
||||
profileImage.alt = 'Profile Picture';
|
||||
profileImage.style.width = '30px';
|
||||
profileImage.style.height = '30px';
|
||||
profileImage.style.borderRadius = '50%';
|
||||
profileImage.style.marginRight = '5px';
|
||||
|
||||
const fullNameSpan = createElement('span');
|
||||
fullNameSpan.textContent = this.profileData.fullName;
|
||||
|
||||
profileButton.appendChild(profileImage);
|
||||
profileButton.appendChild(fullNameSpan);
|
||||
|
||||
const dropdownMenu = createElement('ul');
|
||||
dropdownMenu.classList.add('dropdown-menu', 'dropdown-menu-end');
|
||||
dropdownMenu.setAttribute('aria-labelledby', 'profileDropdownButton');
|
||||
|
||||
const userIdItem = createElement('li');
|
||||
userIdItem.innerHTML = `<span class="dropdown-item-text">ID: ${this.profileData.schoolId}</span>`;
|
||||
dropdownMenu.appendChild(userIdItem);
|
||||
|
||||
const divider = createElement('li');
|
||||
divider.innerHTML = '<hr class="dropdown-divider">';
|
||||
dropdownMenu.appendChild(divider);
|
||||
|
||||
const profileMenuItem = createElement('li');
|
||||
const profileLink = createElement('a');
|
||||
profileLink.classList.add('dropdown-item');
|
||||
profileLink.href = '#/profile';
|
||||
profileLink.textContent = 'Profile';
|
||||
profileMenuItem.appendChild(profileLink);
|
||||
dropdownMenu.appendChild(profileMenuItem);
|
||||
|
||||
const accountSettingsMenuItem = createElement('li');
|
||||
const accountSettingsLink = createElement('a');
|
||||
accountSettingsLink.classList.add('dropdown-item');
|
||||
accountSettingsLink.href = '#/profile'; // Same profile page, modal will be triggered there
|
||||
accountSettingsLink.setAttribute('data-action', 'open-account-settings'); // Custom attribute to trigger modal
|
||||
accountSettingsLink.textContent = 'Account settings';
|
||||
accountSettingsMenuItem.appendChild(accountSettingsLink);
|
||||
dropdownMenu.appendChild(accountSettingsMenuItem);
|
||||
|
||||
const logoutMenuItem = createElement('li');
|
||||
const logoutButton = createElement('button');
|
||||
logoutButton.classList.add('dropdown-item');
|
||||
logoutButton.textContent = 'Log out';
|
||||
logoutButton.addEventListener('click', async () => {
|
||||
await globalAPI.logout();
|
||||
navigateTo('/login'); // Redirect to login page after logout
|
||||
});
|
||||
logoutMenuItem.appendChild(logoutButton);
|
||||
dropdownMenu.appendChild(logoutMenuItem);
|
||||
|
||||
profileSection.appendChild(profileButton);
|
||||
profileSection.appendChild(dropdownMenu);
|
||||
} else {
|
||||
// Fallback if profile data is not loaded (or not logged in, though TopBar is for logged-in users)
|
||||
const loginLink = createElement('a');
|
||||
loginLink.classList.add('btn', 'btn-primary');
|
||||
loginLink.href = '#/login';
|
||||
loginLink.textContent = 'Login';
|
||||
profileSection.appendChild(loginLink);
|
||||
}
|
||||
|
||||
|
||||
navbarCollapse.appendChild(menuPages);
|
||||
navbarCollapse.appendChild(profileSection);
|
||||
|
||||
this.container.appendChild(navbarBrand);
|
||||
this.container.appendChild(navbarToggler);
|
||||
this.container.appendChild(navbarCollapse);
|
||||
|
||||
|
||||
return this.container;
|
||||
}
|
||||
|
||||
getContainer(): HTMLElement {
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
26
src/components/Widget.ts
Normal file
26
src/components/Widget.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
// components/Widget.ts
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
export abstract class Widget {
|
||||
protected container: HTMLElement;
|
||||
protected sizeType: 'default' | 'icon';
|
||||
|
||||
constructor(sizeType: 'default' | 'icon' = 'default') {
|
||||
this.container = createElement('div');
|
||||
this.container.classList.add('widget');
|
||||
this.sizeType = sizeType;
|
||||
if (sizeType === 'icon') {
|
||||
this.container.classList.add('icon-widget');
|
||||
}
|
||||
}
|
||||
|
||||
abstract render(): HTMLElement;
|
||||
|
||||
getSizeType(): 'default' | 'icon' {
|
||||
return this.sizeType;
|
||||
}
|
||||
|
||||
getContainer(): HTMLElement {
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user