Version 1.0 upload
This commit is contained in:
parent
2eff273486
commit
35378d17d0
511
docs/CONTRACT.md
511
docs/CONTRACT.md
@ -1,483 +1,58 @@
|
||||
# Contract
|
||||
# Contract Proposal: Learner Management System (LMS)
|
||||
|
||||
## Project Agreement: Proof-of-Concept Development
|
||||
## **Short Version**
|
||||
|
||||
This document outlines the informal agreement and objectives for the development of a Learner Management System (LMS) Frontend proof-of-concept. This project is undertaken to evaluate the feasibility and benefits of a modular, responsive LMS frontend architecture using Vanilla Typescript, Vite, and Bootstrap 5.3.
|
||||
**1. SDLC (Software Development Life Cycle) Used:**
|
||||
|
||||
**Project Scope:**
|
||||
We will employ an **Agile methodology**, specifically an iterative and incremental approach. This allows for flexibility and adaptation throughout the project lifecycle, ensuring the LMS evolves to meet your needs effectively. Agile is chosen for its responsiveness to change, emphasis on collaboration, and delivery of working software in short cycles.
|
||||
|
||||
The scope of this proof-of-concept is limited to the development of a functional frontend prototype demonstrating core LMS user interface elements and basic workflows. It specifically includes:
|
||||
**2. Advantages:**
|
||||
|
||||
* **User Authentication:** Login and registration pages with mocked backend authentication.
|
||||
* **Dashboard:** A customizable dashboard layout showcasing widget integration and user-specific content.
|
||||
* **User Profile:** A basic profile page displaying user information and allowing access to account settings.
|
||||
* **Account Settings:** A modal-based interface for viewing and potentially modifying user account details (functionality limited in proof-of-concept).
|
||||
* **Admin Interface (Simplified):** A rudimentary admin page demonstrating a three-column layout and widgets for student and teacher counts.
|
||||
* **Navigation:** Topbar navigation for accessing different sections of the LMS (Dashboard, Classrooms - placeholder, Settings, Admin, Profile).
|
||||
* **Widget System:** Implementation of a functional widget system with 'default' and 'icon' sizes, and example widgets for login, buttons, and placeholder content.
|
||||
* **Layout Modules:** Creation of Centered, Three-Column, and Split-Column layout modules for page structure.
|
||||
* **Responsive Design:** Ensuring responsiveness across desktop and mobile devices using Bootstrap 5.3.
|
||||
* **Modern Technology Stack:** Utilizes cutting-edge technologies for performance, scalability, and maintainability.
|
||||
* **User-Centric Design:** Intuitive and responsive interface ensuring ease of use for all users (learners, instructors, administrators).
|
||||
* **Modular and Scalable Architecture:** Designed for future expansion and integration of new features and modules.
|
||||
* **Robust Project Management:** Experienced team employing Agile practices for timely delivery and effective communication.
|
||||
|
||||
**Project Objectives:**
|
||||
**3. Disadvantages:**
|
||||
|
||||
1. **Technical Feasibility:** To validate the technical viability of using Vanilla Typescript, Vite, and Bootstrap 5.3 for building a complex LMS frontend.
|
||||
2. **Architecture Validation:** To demonstrate the effectiveness of a modular, widget-based architecture for LMS frontend development, focusing on reusability and maintainability.
|
||||
3. **User Interface Prototype:** To create an interactive UI prototype that showcases core LMS workflows and user experience concepts.
|
||||
4. **Backend Integration Foundation:** To establish a clear API structure and frontend architecture that facilitates future integration with a backend system.
|
||||
5. **Performance Evaluation:** To assess the performance characteristics of the frontend built with the chosen technologies, particularly in terms of development speed and runtime performance.
|
||||
* **Evolving Requirements:** Agile's flexibility can sometimes lead to scope creep if not managed effectively. We will mitigate this with clear communication and prioritized feature implementation.
|
||||
* **Initial Setup Complexity:** Setting up a comprehensive LMS requires initial effort in data migration and system configuration. We will provide thorough onboarding and support to ease this transition.
|
||||
|
||||
**Stakeholders:**
|
||||
**4. Video Explanation:**
|
||||
|
||||
* **Project Lead & Developer:** [Your Name/Team Name] - Responsible for all aspects of frontend development, documentation, and project delivery for the proof-of-concept.
|
||||
* **[Optional: Internal Reviewer/Stakeholder Name]:** [Their Role/Department] - (If applicable) Responsible for reviewing the proof-of-concept and providing feedback on its functionality and alignment with potential LMS requirements.
|
||||
A concise video will demonstrate the LMS in action. Key points include:
|
||||
|
||||
**Timeline:**
|
||||
* System Overview: High-level tour of the platform and its modules.
|
||||
* User Roles: Demonstration of learner, instructor, and administrator interfaces.
|
||||
* Core Workflows: Showcase of essential features like course enrollment, content delivery, and progress tracking.
|
||||
|
||||
* **Project Start Date:** 2023-10-26 (Example Date)
|
||||
* **Proof-of-Concept Completion Date:** 2023-11-10 (Example Date) - Approximately 2 weeks for initial proof-of-concept development and documentation.
|
||||
**5. Wireframe:**
|
||||
|
||||
**Deliverables:**
|
||||
The LMS features a clean and structured wireframe:
|
||||
|
||||
1. **Functional Frontend Codebase:** A Git repository containing all source code for the LMS frontend proof-of-concept, implemented using Vanilla Typescript, Vite, and Bootstrap 5.3.
|
||||
2. **Project Documentation:** Comprehensive documentation including:
|
||||
* README.md: Project overview, setup instructions, and features.
|
||||
* CONTRIBUTION.md: Guidelines for contributions (during open-source phase).
|
||||
* This Contract document (outlining scope and objectives).
|
||||
* SDLC description.
|
||||
* Advantages and Disadvantages analysis.
|
||||
* [Optional: Link to Video Demonstration].
|
||||
* [Optional: Wireframe and UI Design information].
|
||||
* [Optional: Gantt Chart/Timeline information].
|
||||
3. **[Optional: Video Demonstration]:** A short video demonstrating the key features and functionalities of the frontend prototype.
|
||||
* **Header:** Top navigation with logo, user profile, and main menu links.
|
||||
* **Sidebar:** Contextual navigation within modules for quick access to features and settings.
|
||||
* **Main Content Area:** Dynamic space for dashboards, course content, user lists, and interactive elements.
|
||||
* **Footer:** Essential links, copyright information, and support contact details.
|
||||
|
||||
**Limitations of Proof-of-Concept:**
|
||||
**6. Timeframe (Gantt Chart):**
|
||||
|
||||
* **Backend Simulation:** All backend interactions are simulated using mocked API calls. No real backend database or server-side logic is implemented.
|
||||
* **Security:** Security considerations are basic and not production-ready. Frontend password hashing is for demonstration only and should not be considered secure for production use.
|
||||
* **Feature Completeness:** The proof-of-concept implements a limited set of core LMS features. Many advanced functionalities are intentionally excluded to focus on core architecture and UI demonstration.
|
||||
* **Scalability and Performance (Initial Assessment):** While Vite and Vanilla Typescript are chosen for performance, a full-scale scalability and performance analysis would require a backend system and more extensive testing.
|
||||
|
||||
**Informal Agreement:**
|
||||
|
||||
This document serves as an informal agreement outlining the understanding and expectations for this proof-of-concept project. It is not intended to be a legally binding contract but rather a shared understanding of the project's scope, objectives, and deliverables.
|
||||
|
||||
*Replace example dates, names, and roles with your specific project details.*
|
||||
|
||||
## Software Development Life Cycle (SDLC): Iterative and Agile-Inspired Prototype Development
|
||||
|
||||
For the development of this LMS Frontend proof-of-concept, we employed an **Iterative and Agile-Inspired** SDLC approach, tailored for rapid prototyping and exploration within a limited timeframe. This methodology prioritized flexibility, working software, and continuous feedback over rigid planning and extensive documentation.
|
||||
|
||||
**Iterative Development Cycles:**
|
||||
|
||||
The project was structured into short, iterative cycles (informally resembling sprints, though not formally defined as such). Each iteration focused on developing a specific set of features or modules:
|
||||
|
||||
1. **Iteration 1: Core Project Setup and Layout Foundation (Approx. 2 days):**
|
||||
* Vite project initialization with Vanilla Typescript template.
|
||||
* Bootstrap 5.3 integration and basic global styling.
|
||||
* Creation of `Widget` interface and initial placeholder widgets (ButtonWidget, LoginWidget).
|
||||
* Implementation of Layout Modules (CenteredLayout, ThreeColumnLayout).
|
||||
* Basic routing setup in `main.ts` and creation of LoginPage and RegisterPage.
|
||||
|
||||
2. **Iteration 2: Navigation and Dashboard Structure (Approx. 3 days):**
|
||||
* Development of TopbarModule with basic navigation and user dropdown.
|
||||
* Creation of DashboardPage and SplitColumnLayout.
|
||||
* Implementation of placeholder widgets for dashboard content (e.g., "Dashboard Main Content Widget").
|
||||
* Mock API setup in `api.ts` for login and user retrieval.
|
||||
* Authentication utility (`auth.ts`) for token management.
|
||||
|
||||
3. **Iteration 3: Profile and Admin Page Prototyping (Approx. 2 days):**
|
||||
* Creation of ProfilePage and SettingsPage using SplitColumnLayout.
|
||||
* Implementation of basic ProfileInfoWidget (placeholder).
|
||||
* Creation of AdminPage using ThreeColumnLayout.
|
||||
* Placeholder widgets for Admin page (StudentCountWidget, TeacherCountWidget).
|
||||
|
||||
4. **Iteration 4: Refinement, Documentation, and Demo (Approx. 3 days):**
|
||||
* Refinement of UI and widget implementations based on internal review and testing.
|
||||
* Implementation of RegisterWidget and ButtonWidget for page navigation.
|
||||
* Creation of ClassroomsPage (placeholder modal implementation).
|
||||
* Comprehensive documentation: README.md, CONTRIBUTION.md, Contract, SDLC, Advantages/Disadvantages, etc.
|
||||
* [Optional: Video demonstration recording and editing].
|
||||
|
||||
**Agile-Inspired Principles:**
|
||||
|
||||
* **Working Prototype over Comprehensive Planning:** The primary focus was on delivering a functional, interactive prototype. Detailed upfront planning was minimized in favor of iterative development and emergent design.
|
||||
* **Customer/Stakeholder Collaboration (Internal):** While no external client was involved, internal review and feedback were sought after each iteration to guide development and ensure alignment with project objectives.
|
||||
* **Responding to Change:** The iterative approach allowed for flexibility to adapt to new insights or minor requirement changes during development. If a widget idea proved less effective, it could be adjusted or replaced in subsequent iterations.
|
||||
* **Individuals and Interactions:** Emphasis was placed on developer autonomy and efficient communication within the small development team (or individual developer).
|
||||
* **Informal Communication:** Communication was primarily informal and direct, utilizing quick discussions and code reviews rather than extensive formal documentation or meetings.
|
||||
|
||||
**Tools and Practices:**
|
||||
|
||||
* **Vite Hot Module Replacement (HMR):** Leveraged for rapid feedback and development iteration.
|
||||
* **Git Version Control:** Used for code management, version tracking, and collaboration.
|
||||
* **Informal Code Reviews:** Quick reviews of code changes were conducted to maintain code quality and share knowledge.
|
||||
* **Issue Tracking (Optional):** For larger teams, a simple issue tracker (e.g., GitHub Issues) could be used to manage tasks and track bugs, although for a small proof-of-concept, this might have been less formal.
|
||||
|
||||
**Justification for Agile-Inspired Approach:**
|
||||
|
||||
This lightweight, iterative approach was deemed most suitable for a proof-of-concept project due to:
|
||||
|
||||
* **Speed and Agility:** The need for rapid prototyping and quick iteration cycles to validate the core concepts within a short timeframe.
|
||||
* **Exploratory Nature:** The project was somewhat exploratory in nature, allowing for adjustments and refinements as development progressed.
|
||||
* **Small Team Size:** Agile principles are often effective for smaller, self-organizing teams.
|
||||
|
||||
For a full-scale LMS project, transitioning to a more structured Agile methodology (like Scrum or Kanban) would be recommended to manage complexity, larger teams, and deliver a production-ready system with defined sprints, user stories, and more formal processes.
|
||||
|
||||
## Software Development Life Cycle (SDLC): Iterative and Agile-Inspired Prototype Development
|
||||
|
||||
For the development of this LMS Frontend proof-of-concept, we employed an **Iterative and Agile-Inspired** SDLC approach, tailored for rapid prototyping and exploration within a limited timeframe. This methodology prioritized flexibility, working software, and continuous feedback over rigid planning and extensive documentation.
|
||||
|
||||
**Iterative Development Cycles:**
|
||||
|
||||
The project was structured into short, iterative cycles (informally resembling sprints, though not formally defined as such). Each iteration focused on developing a specific set of features or modules:
|
||||
|
||||
1. **Iteration 1: Core Project Setup and Layout Foundation (Approx. 2 days):**
|
||||
* Vite project initialization with Vanilla Typescript template.
|
||||
* Bootstrap 5.3 integration and basic global styling.
|
||||
* Creation of `Widget` interface and initial placeholder widgets (ButtonWidget, LoginWidget).
|
||||
* Implementation of Layout Modules (CenteredLayout, ThreeColumnLayout).
|
||||
* Basic routing setup in `main.ts` and creation of LoginPage and RegisterPage.
|
||||
|
||||
2. **Iteration 2: Navigation and Dashboard Structure (Approx. 3 days):**
|
||||
* Development of TopbarModule with basic navigation and user dropdown.
|
||||
* Creation of DashboardPage and SplitColumnLayout.
|
||||
* Implementation of placeholder widgets for dashboard content (e.g., "Dashboard Main Content Widget").
|
||||
* Mock API setup in `api.ts` for login and user retrieval.
|
||||
* Authentication utility (`auth.ts`) for token management.
|
||||
|
||||
3. **Iteration 3: Profile and Admin Page Prototyping (Approx. 2 days):**
|
||||
* Creation of ProfilePage and SettingsPage using SplitColumnLayout.
|
||||
* Implementation of basic ProfileInfoWidget (placeholder).
|
||||
* Creation of AdminPage using ThreeColumnLayout.
|
||||
* Placeholder widgets for Admin page (StudentCountWidget, TeacherCountWidget).
|
||||
|
||||
4. **Iteration 4: Refinement, Documentation, and Demo (Approx. 3 days):**
|
||||
* Refinement of UI and widget implementations based on internal review and testing.
|
||||
* Implementation of RegisterWidget and ButtonWidget for page navigation.
|
||||
* Creation of ClassroomsPage (placeholder modal implementation).
|
||||
* Comprehensive documentation: README.md, CONTRIBUTION.md, Contract, SDLC, Advantages/Disadvantages, etc.
|
||||
* [Optional: Video demonstration recording and editing].
|
||||
|
||||
**Agile-Inspired Principles:**
|
||||
|
||||
* **Working Prototype over Comprehensive Planning:** The primary focus was on delivering a functional, interactive prototype. Detailed upfront planning was minimized in favor of iterative development and emergent design.
|
||||
* **Customer/Stakeholder Collaboration (Internal):** While no external client was involved, internal review and feedback were sought after each iteration to guide development and ensure alignment with project objectives.
|
||||
* **Responding to Change:** The iterative approach allowed for flexibility to adapt to new insights or minor requirement changes during development. If a widget idea proved less effective, it could be adjusted or replaced in subsequent iterations.
|
||||
* **Individuals and Interactions:** Emphasis was placed on developer autonomy and efficient communication within the small development team (or individual developer).
|
||||
* **Informal Communication:** Communication was primarily informal and direct, utilizing quick discussions and code reviews rather than extensive formal documentation or meetings.
|
||||
|
||||
**Tools and Practices:**
|
||||
|
||||
* **Vite Hot Module Replacement (HMR):** Leveraged for rapid feedback and development iteration.
|
||||
* **Git Version Control:** Used for code management, version tracking, and collaboration.
|
||||
* **Informal Code Reviews:** Quick reviews of code changes were conducted to maintain code quality and share knowledge.
|
||||
* **Issue Tracking (Optional):** For larger teams, a simple issue tracker (e.g., GitHub Issues) could be used to manage tasks and track bugs, although for a small proof-of-concept, this might have been less formal.
|
||||
|
||||
**Justification for Agile-Inspired Approach:**
|
||||
|
||||
This lightweight, iterative approach was deemed most suitable for a proof-of-concept project due to:
|
||||
|
||||
* **Speed and Agility:** The need for rapid prototyping and quick iteration cycles to validate the core concepts within a short timeframe.
|
||||
* **Exploratory Nature:** The project was somewhat exploratory in nature, allowing for adjustments and refinements as development progressed.
|
||||
* **Small Team Size:** Agile principles are often effective for smaller, self-organizing teams.
|
||||
|
||||
For a full-scale LMS project, transitioning to a more structured Agile methodology (like Scrum or Kanban) would be recommended to manage complexity, larger teams, and deliver a production-ready system with defined sprints, user stories, and more formal processes.
|
||||
|
||||
## Advantages of the LMS Frontend Architecture: Detailed Analysis
|
||||
|
||||
The LMS Frontend architecture, designed with Vanilla Typescript, Vite, and Bootstrap 5.3, presents a compelling set of advantages, particularly for a project focused on performance, modularity, and maintainability. Let's delve into each advantage with more detail:
|
||||
|
||||
* **Exceptional Modularity and Widget Reusability:**
|
||||
* **Granular Widget Design:** Widgets are designed as self-contained units of functionality, encapsulating UI, logic, and styling. This promotes a highly modular architecture. For instance, the `LoginWidget` can be reused on different login pages or even embedded within other widgets if needed, without code duplication.
|
||||
* **Layout Modules as Structure Providers:** Layout modules (CenteredLayout, ThreeColumnLayout, SplitColumnLayout) act as structural templates. They decouple the overall page layout from the widgets themselves. This means you can easily rearrange widgets or change the page structure without modifying widget code, enhancing reusability of both widgets and layouts.
|
||||
* **Easy Feature Extension and Maintenance:** Adding a new feature often involves creating new widgets or modules and integrating them into existing layouts. Modifying or maintaining existing features is simplified as changes are typically localized within specific widgets or modules, reducing the risk of cascading effects and making debugging more straightforward.
|
||||
* **Example: Dashboard Customization:** The widget system allows for easy dashboard customization. Users (or admins) could potentially drag and drop widgets to personalize their dashboard layout, selecting from a library of available widgets. This flexibility is directly enabled by the modularity of the widget architecture.
|
||||
|
||||
* **Superior Responsiveness and Mobile-First Approach with Bootstrap 5.3:**
|
||||
* **Bootstrap Grid System:** Bootstrap's powerful grid system is extensively utilized in layout modules and widget design. This ensures that the frontend automatically adapts to different screen sizes and resolutions, providing a consistent user experience across desktops, tablets, and smartphones.
|
||||
* **Pre-built Responsive Components:** Bootstrap offers a rich library of pre-built, responsive UI components (navbars, buttons, forms, modals, etc.). Using these components significantly reduces the effort required to build a responsive UI, compared to writing custom CSS for every element.
|
||||
* **Mobile-First Philosophy:** Bootstrap 5.3 is inherently mobile-first. This means styles are initially designed for smaller screens and then progressively enhanced for larger screens. This approach ensures that the mobile experience is prioritized, which is crucial for modern web applications.
|
||||
* **Reduced Development Time for Responsiveness:** By leveraging Bootstrap's responsiveness features, development time is significantly reduced as developers can focus on functionality and widget logic rather than spending extensive time on cross-device compatibility and CSS media queries.
|
||||
|
||||
* **High Performance and Fast Development Cycle with Vite:**
|
||||
* **Instant Server Start and Hot Module Replacement (HMR):** Vite's near-instant server start and incredibly fast HMR drastically improve developer productivity. Code changes are reflected in the browser almost instantaneously, leading to a much faster feedback loop and quicker iteration during development. This speed is a significant advantage, especially for frontend development where visual feedback is essential.
|
||||
* **Optimized Production Builds:** Vite uses Rollup for production builds, resulting in highly optimized and performant bundles. This translates to faster page load times and a smoother user experience for the end-users of the LMS.
|
||||
* **Typescript and Modern JavaScript Support:** Vite has excellent built-in support for Typescript and modern JavaScript features. This allows developers to leverage the benefits of Typescript's static typing and modern language features without complex configuration, further enhancing development speed and code quality.
|
||||
* **Faster Build Times Compared to Traditional Bundlers:** Vite's build process is significantly faster than traditional bundlers like Webpack, especially for larger projects. This speed advantage translates to less waiting time during development and deployment. For instance, initial builds and rebuilds can be several times faster with Vite compared to more complex Webpack setups.
|
||||
|
||||
* **Enhanced Maintainability and Scalability through Typescript and Modularity:**
|
||||
* **Static Typing for Error Prevention:** Typescript's static typing system catches type-related errors during development, *before* runtime. This significantly reduces runtime bugs and makes the codebase more robust and maintainable in the long run. Type safety is particularly beneficial in larger projects where code complexity increases.
|
||||
* **Improved Code Readability and Understandability:** Typescript's type annotations and interfaces make the codebase more self-documenting and easier to understand for developers, both new and experienced. This is crucial for team collaboration and long-term project maintainability.
|
||||
* **Refactoring Confidence:** Typescript's type system provides confidence when refactoring code. If you change the structure of a widget or module, the Typescript compiler will help identify all the places where changes are needed due to type mismatches, making refactoring safer and less error-prone.
|
||||
* **Modularity for Scalable Growth:** The modular widget architecture naturally supports scalability. As the LMS needs to grow and incorporate new features, new widgets and modules can be added without significantly impacting existing code. This modularity makes the frontend more adaptable to evolving requirements and future expansion.
|
||||
|
||||
* **Clean and Understandable Vanilla Typescript Codebase:**
|
||||
* **Reduced Abstraction Layer:** Vanilla Typescript, by avoiding large frameworks, reduces the abstraction layer between the developer and the underlying browser APIs. This can lead to a more direct understanding of the code and how it interacts with the browser.
|
||||
* **Smaller Bundle Size Potential (Framework Overhead Avoidance):** While careful code optimization is always necessary, avoiding large framework dependencies can potentially lead to smaller bundle sizes and faster initial load times, especially for simpler frontend applications or proof-of-concepts.
|
||||
* **Deeper Understanding of Frontend Fundamentals:** Working with Vanilla Typescript encourages a deeper understanding of core frontend concepts (DOM manipulation, event handling, etc.) as developers are more directly involved in these aspects compared to framework-centric development where frameworks often abstract away many of these details.
|
||||
|
||||
* **Benefit of Bootstrap Ecosystem and Large Community Support:**
|
||||
* **Extensive UI Component Library:** Bootstrap provides a vast library of well-designed and tested UI components (buttons, forms, navigation, modals, carousels, etc.). This reduces the need to build UI elements from scratch and ensures consistency in visual design and user interactions across the LMS.
|
||||
* **Comprehensive Documentation and Community Support:** Bootstrap has excellent documentation and a large, active community. This makes it easy to find solutions to common problems, get help with implementation, and access a wealth of online resources and tutorials.
|
||||
* **Rapid Prototyping with Pre-built Styles and Utilities:** Bootstrap's pre-defined styles and utility classes (spacing, typography, colors, etc.) enable rapid prototyping. Developers can quickly style and layout UI elements using Bootstrap classes without writing extensive custom CSS.
|
||||
|
||||
* **Solid Foundation for Seamless Backend Integration:**
|
||||
* **Clear API Structure Definition:** The global API structure defined in `api.ts` provides a clear contract for communication between the frontend and backend. This structured approach facilitates backend development, regardless of the backend technology chosen, as the frontend API expectations are well-defined.
|
||||
* **Decoupled Frontend and Backend Development:** The separation of concerns between frontend and backend, enforced by the API structure, allows for parallel development of the frontend and backend. Frontend developers can work against the defined API, using mock API calls initially, while backend developers can focus on implementing the actual API endpoints and data logic.
|
||||
* **Flexibility in Backend Technology Choice:** The frontend architecture is technology-agnostic in terms of the backend. The backend can be implemented using Node.js, Python, Java, PHP, or any other suitable backend technology, as long as it adheres to the defined API contract.
|
||||
|
||||
*Replace or adjust these detailed advantages based on your project's specific strengths and outcomes.*
|
||||
|
||||
## Disadvantages and Limitations of the LMS Frontend: Detailed Analysis and Mitigation Strategies
|
||||
|
||||
While the LMS frontend architecture offers significant advantages, it's crucial to acknowledge its disadvantages and limitations, especially in the context of a proof-of-concept and potential future full-scale development. Let's examine these in detail, along with potential mitigation strategies:
|
||||
|
||||
* **Complexity Management in Vanilla Typescript: State Management, Routing, and Advanced UI Interactions:**
|
||||
* **Challenge:** Vanilla Typescript lacks built-in solutions for complex state management, routing, and advanced UI interactions that are readily available in frameworks like React, Angular, or Vue.js. As the LMS frontend grows in complexity, managing application state, handling navigation between pages, and implementing sophisticated UI components can become more challenging and require more manual code.
|
||||
* **Detailed Explanation:** Frameworks provide structured approaches to state management (e.g., React Context, Redux, Vuex), routing (React Router, Angular Router, Vue Router), and component lifecycle management. In Vanilla Typescript, developers need to implement these functionalities themselves or rely on smaller, external libraries. This can lead to more boilerplate code and potentially more complex custom solutions as the application scales.
|
||||
* **Mitigation Strategies for Future Development:**
|
||||
* **State Management Libraries:** Consider integrating lightweight state management libraries specifically designed for Vanilla Javascript or Typescript if the application state becomes complex. Libraries like `nanostores`, `zustand` (Vanilla version), or even a custom-built lightweight state management solution could be explored.
|
||||
* **Routing Libraries:** For more complex routing needs beyond simple hash-based routing, consider integrating a lightweight routing library for Vanilla Javascript. Libraries exist that offer client-side routing capabilities without the full overhead of framework routers.
|
||||
* **Component Composition Patterns:** Adopt robust component composition patterns in Vanilla Typescript to manage UI complexity. Focus on creating small, reusable, and composable components to build more complex UI structures.
|
||||
* **Framework Transition (Consideration for Full-Scale):** If the LMS evolves into a large, feature-rich application, a strategic transition to a framework like React, Vue.js, or Angular might become necessary to leverage their built-in features for managing complexity and scalability more effectively. This decision should be based on a careful assessment of project requirements and long-term maintainability needs.
|
||||
|
||||
* **Frontend Password Hashing: Security Vulnerability (Proof-of-Concept Limitation):**
|
||||
* **Challenge:** The inclusion of `argon2-browser` for frontend password hashing, while demonstrating the *concept* of Argon2id, introduces a significant security vulnerability if implemented in a production system.
|
||||
* **Detailed Explanation:** Frontend password hashing is inherently less secure because:
|
||||
* **Algorithm Exposure:** The hashing algorithm (Argon2id) and potentially the salt are exposed in the client-side Javascript code, making it easier for attackers to analyze and potentially exploit weaknesses.
|
||||
* **Client-Side Computation Limitation:** Frontend hashing relies on the user's browser and device for computation. Attackers can potentially bypass or manipulate client-side hashing.
|
||||
* **HTTPS Still Required:** Even with frontend hashing, HTTPS is still absolutely essential to protect passwords in transit from the client to the server.
|
||||
* **Mitigation Strategy (Production Imperative):**
|
||||
* **Backend Password Hashing (Mandatory):** **Password hashing MUST be implemented on the backend server.** The frontend should send the user's password over HTTPS to the backend. The backend should then generate a unique salt for each user, hash the password using Argon2id (or another strong hashing algorithm), and store the hashed password and salt securely in the database. Password verification should also be performed on the backend.
|
||||
* **Frontend Role: Secure Transmission Only:** The frontend's role in password handling should be limited to securely transmitting the password (over HTTPS) to the backend for secure processing.
|
||||
|
||||
* **Mocked API: Lack of Real Backend Functionality and Data Persistence:**
|
||||
* **Challenge:** The current reliance on mocked API calls means the frontend is isolated from a real backend. Key LMS functionalities requiring data persistence, server-side logic, and real-time data updates are not fully functional.
|
||||
* **Detailed Explanation:** Mocked API calls are useful for frontend development in isolation, but they do not represent the complexities of a real-world LMS. Features like user accounts, data storage, dynamic content, and server-side security are not implemented in this proof-of-concept.
|
||||
* **Mitigation Strategy (Backend Development):**
|
||||
* **Backend API Implementation:** The next crucial step is to develop a robust backend API that implements the actual LMS functionalities. This API should handle user authentication, data management (students, teachers, courses, enrollments, etc.), and all server-side logic.
|
||||
* **Database Integration:** A database system (e.g., PostgreSQL, MySQL, MongoDB) needs to be integrated with the backend to store and manage LMS data persistently.
|
||||
* **API Endpoint Implementation:** Develop API endpoints corresponding to the frontend's API calls in `api.ts`. These endpoints should handle requests, interact with the database, and return appropriate responses.
|
||||
* **Authentication and Authorization Implementation (Backend):** Implement secure authentication and authorization mechanisms on the backend to protect API endpoints and sensitive data.
|
||||
|
||||
* **Limited Feature Set: Proof-of-Concept Scope Constraint:**
|
||||
* **Challenge:** The proof-of-concept intentionally implements only a core subset of LMS features. Many essential functionalities are missing.
|
||||
* **Detailed Explanation:** The focus was on demonstrating the architecture and UI framework, not on building a fully featured LMS. Features like course management, assignment submission, grading, communication tools, detailed reporting, and many other LMS functionalities are not included in this proof-of-concept.
|
||||
* **Mitigation Strategy (Iterative Feature Expansion):**
|
||||
* **Prioritize Feature Development:** For a full-scale LMS, prioritize feature development based on user needs and requirements. Create a roadmap for iterative feature implementation.
|
||||
* **Widget-Based Feature Expansion:** Leverage the widget architecture to develop new widgets and modules that implement the missing LMS features. This modular approach will facilitate incremental feature addition and maintainability.
|
||||
* **User Feedback and Iteration:** Incorporate user feedback throughout the development process to guide feature prioritization and ensure that the LMS meets user needs effectively.
|
||||
|
||||
* **Styling and UI Polish: Potential for Enhanced Visual Design:**
|
||||
* **Challenge:** While Bootstrap provides a solid foundation, the current UI styling might lack the visual polish and branding expected of a production LMS.
|
||||
* **Detailed Explanation:** Bootstrap provides a consistent and functional UI, but it can sometimes result in a generic "Bootstrap look." Custom styling and UI/UX design are often needed to create a visually appealing and branded user interface.
|
||||
* **Mitigation Strategy (UI/UX Design and Custom Styling):**
|
||||
* **UI/UX Design Phase:** Invest in a dedicated UI/UX design phase to create visually appealing and user-friendly designs for the LMS.
|
||||
* **Custom CSS and Theming:** Develop custom CSS stylesheets and potentially a theming system to override or extend Bootstrap's default styles and create a unique visual identity for the LMS.
|
||||
* **Branding Integration:** Incorporate branding elements (logos, color palettes, typography) into the UI design to create a branded LMS experience.
|
||||
* **User Testing for UI/UX:** Conduct user testing to gather feedback on the UI/UX design and iterate on the design based on user input.
|
||||
|
||||
* **Testing and Quality Assurance: Proof-of-Concept Level (Basic):**
|
||||
* **Challenge:** Testing in the proof-of-concept phase is likely to be basic and may not be comprehensive enough for a production system.
|
||||
* **Detailed Explanation:** Proof-of-concepts often prioritize rapid development and feature demonstration over rigorous testing. Comprehensive testing is essential for ensuring the quality, stability, and reliability of a production LMS.
|
||||
* **Mitigation Strategy (Comprehensive Testing Strategy):**
|
||||
* **Implement a Testing Strategy:** Develop a comprehensive testing strategy that includes unit tests, integration tests, and end-to-end (E2E) tests.
|
||||
* **Testing Frameworks and Tools:** Utilize testing frameworks and tools appropriate for Vanilla Typescript and frontend testing (e.g., Jest, Mocha, Chai, Cypress, Playwright).
|
||||
* **Automated Testing:** Automate testing processes to ensure consistent and repeatable testing. Integrate automated testing into a CI/CD pipeline for continuous quality assurance.
|
||||
* **Performance Testing and Security Testing:** Include performance testing and security testing in the overall testing strategy to identify and address performance bottlenecks and security vulnerabilities.
|
||||
|
||||
* **Modal Module: Initial Implementation and Further Refinement Needed:**
|
||||
* **Challenge:** The Modal Module, while conceptualized, might not be fully implemented or robust in the initial proof-of-concept.
|
||||
* **Detailed Explanation:** Reusable modal components are important for consistent UI patterns and code maintainability. If the Modal Module is not fully developed, modal implementation across the LMS might be less consistent and more code duplication could occur.
|
||||
* **Mitigation Strategy (Modal Module Development and Refinement):**
|
||||
* **Complete Modal Module Implementation:** Prioritize the full implementation of the Modal Module to create a reusable and flexible component for displaying modals across the LMS.
|
||||
* **Modal Component API Design:** Design a clear and flexible API for the Modal Module that allows for easy customization of modal content, actions, and styling.
|
||||
* **Accessibility Considerations:** Ensure that the Modal Module is implemented with accessibility in mind (ARIA attributes, keyboard navigation, etc.) to provide an inclusive user experience.
|
||||
|
||||
By acknowledging these disadvantages and actively planning mitigation strategies, the LMS frontend project can be strategically developed beyond the proof-of-concept stage towards a robust and production-ready application. Honest assessment of limitations is crucial for guiding future development efforts.
|
||||
|
||||
## Video Demonstration: LMS Frontend Proof-of-Concept - Feature Walkthrough and Architecture Overview
|
||||
|
||||
[**Link to Video Demonstration: [Insert YouTube, Vimeo, Loom Link Here]**]
|
||||
|
||||
**Video Title:** LMS Frontend Proof-of-Concept Demonstration - Vanilla Typescript, Vite, Bootstrap 5.3
|
||||
|
||||
**Video Description:**
|
||||
|
||||
This video provides a comprehensive walkthrough and demonstration of the Learner Management System (LMS) Frontend proof-of-concept, built using Vanilla Typescript, Vite, and Bootstrap 5.3. It aims to showcase the core features, modular architecture, and responsive design implemented in this prototype.
|
||||
|
||||
**Key Video Sections and Demonstrated Features:**
|
||||
|
||||
1. **Introduction and Project Overview (0:00 - 1:30):**
|
||||
* Brief introduction to the project goals and objectives of the LMS Frontend proof-of-concept.
|
||||
* Overview of the technologies used: Vanilla Typescript, Vite, Bootstrap 5.3, and pnpm.
|
||||
* Highlighting the focus on modularity, responsiveness, and a widget-based architecture.
|
||||
|
||||
2. **Login and Registration Flow (1:30 - 3:00):**
|
||||
* Demonstration of the Login Page using the CenteredLayout and LoginWidget.
|
||||
* Showcasing the mocked login functionality using the `api.ts` mock API.
|
||||
* Briefly displaying the Register Page and its intended message (department head contact for registration).
|
||||
* Navigation between Login and Register pages using ButtonWidgets.
|
||||
|
||||
3. **Dashboard and Widget System (3:00 - 5:30):**
|
||||
* Walkthrough of the Dashboard Page and SplitColumnLayout.
|
||||
* Demonstration of widget integration within the layout.
|
||||
* Explanation of the widget system and the concept of 'default' and 'icon' widget sizes.
|
||||
* Showcasing example widgets on the dashboard (e.g., placeholder "Dashboard Main Content Widget," "Icon Widget 1").
|
||||
* Illustrating how widgets can be added and rearranged within the layout (conceptually, even if drag-and-drop is not implemented in the proof-of-concept).
|
||||
|
||||
4. **Topbar Navigation and User Profile (5:30 - 7:00):**
|
||||
* Exploration of the TopbarModule and its components.
|
||||
* Demonstration of the menu items: Dashboard, Classrooms (placeholder modal), Settings, Admin (if applicable), Profile.
|
||||
* Showcasing the user profile dropdown menu with options: "ID," "Profile," "Account settings," and "Log out."
|
||||
* Demonstration of the "Log out" functionality and redirection to the Login page.
|
||||
|
||||
5. **Profile Page and Account Settings (7:00 - 8:30):**
|
||||
* Navigation to the Profile Page and its SplitColumnLayout.
|
||||
* Displaying the ProfileInfoWidget (placeholder in proof-of-concept).
|
||||
* Conceptual demonstration of how the ProfileInfoWidget would display user data and profile picture in a real implementation.
|
||||
* Briefly showing the intended Account Settings modal (even if the modal functionality is a placeholder in the proof-of-concept).
|
||||
* Navigation back to the Dashboard.
|
||||
|
||||
6. **Admin Page and Three-Column Layout (8:30 - 9:30):**
|
||||
* Accessing the Admin Page (if applicable based on user role - mocked in proof-of-concept).
|
||||
* Showcasing the ThreeColumnLayout used for the Admin Page.
|
||||
* Demonstration of placeholder widgets in the Admin Page (StudentCountWidget, TeacherCountWidget).
|
||||
* Illustrating how the three-column layout can be used to organize admin-related widgets.
|
||||
|
||||
7. **Classrooms Page (Placeholder Modal) (9:30 - 10:00):**
|
||||
* Clicking on the "Classrooms" menu item in the Topbar.
|
||||
* Demonstration of the placeholder modal indicating "Classrooms is currently not yet ready for production."
|
||||
* Explaining the intended future implementation of the Classrooms feature and its planned integration into the LMS.
|
||||
|
||||
8. **Responsive Design Demonstration (10:00 - 11:00):**
|
||||
* Resizing the browser window to showcase the responsive behavior of the LMS frontend.
|
||||
* Demonstrating how the layout adapts to different screen sizes (desktop, tablet, mobile).
|
||||
* Highlighting the role of Bootstrap 5.3 in achieving responsiveness without extensive custom CSS.
|
||||
|
||||
9. **Architecture Overview and Conclusion (11:00 - 12:00):**
|
||||
* Briefly summarizing the key architectural components: Widgets, Layout Modules, API Structure, TopbarModule, ModalModule.
|
||||
* Reiterating the advantages of the chosen architecture and technologies.
|
||||
* Acknowledging the limitations of the proof-of-concept (mocked backend, limited features).
|
||||
* Concluding remarks and future directions for the LMS project.
|
||||
|
||||
**Video Takeaways:**
|
||||
|
||||
This video provides a visual and interactive understanding of the LMS Frontend proof-of-concept. It effectively communicates the project's architecture, core features, and the benefits of using Vanilla Typescript, Vite, and Bootstrap 5.3 for building a modular and responsive LMS frontend. The video serves as a valuable resource for understanding the project's current state and its potential for future development into a full-scale LMS application.
|
||||
|
||||
*Adjust timestamps, feature descriptions, and video takeaways to accurately reflect your actual video content.* If you don't have a video, remove the link and modify the text to explain *what a video would cover* if one were to be created, focusing on the same key areas.
|
||||
|
||||
## Wireframes and UI Design: Figma Project for LMS Frontend
|
||||
|
||||
Wireframes and UI design mockups for the LMS Frontend proof-of-concept were created using **Figma**. The Figma project serves as the primary visual guide for the frontend's structure, layout, and initial UI concepts.
|
||||
|
||||
**Figma Project Link:** [**Insert Figma Project Shareable Link Here**]
|
||||
|
||||
**Figma Project Structure and Key Screens:**
|
||||
|
||||
The Figma project is organized to represent the main pages and key UI elements of the LMS Frontend. Key screens and sections within the Figma project include:
|
||||
|
||||
* **Login and Registration Flows:**
|
||||
* **Login Page Wireframe:** Detailed wireframe of the Login Page, showing the layout of the LoginWidget, input fields (username, password), login button, and "Register" button. Focus on the CenteredLayout and widget placement.
|
||||
* **Register Page Wireframe:** Wireframe of the Register Page, displaying the RegisterWidget's informational text and the "Back to Login" button, also within the CenteredLayout.
|
||||
* **Error States (Login):** Wireframes illustrating potential error states for the Login Page (e.g., invalid credentials error message).
|
||||
|
||||
* **Dashboard Layout and Widget Placement:**
|
||||
* **Dashboard Page Wireframe (Split Column Layout):** Wireframe of the Dashboard Page, showcasing the SplitColumnLayout with a sidebar and main content area.
|
||||
* **Widget Placement Examples:** Illustrations of how various widgets (placeholder widgets in the proof-of-concept) would be positioned within the Dashboard layout, demonstrating both 'default' and 'icon' widget sizes.
|
||||
* **Topbar Integration:** Wireframe showing the placement of the TopbarModule at the top of the Dashboard Page, including navigation links and the user profile dropdown.
|
||||
|
||||
* **User Profile and Account Settings:**
|
||||
* **Profile Page Wireframe (Split Column Layout):** Wireframe of the Profile Page, again using the SplitColumnLayout.
|
||||
* **ProfileInfoWidget Wireframe:** Detailed wireframe of the ProfileInfoWidget, showing the intended layout for user profile picture, name, ID, and birthday information. Placement of the kebab menu icon for account settings access.
|
||||
* **Account Settings Modal Wireframe:** Wireframe of the Account Settings Modal, outlining sections for profile picture update, student details (name, birthday - read-only), and password change form. Placement of form fields, labels, and action buttons.
|
||||
|
||||
* **Admin Page Layout and Widgets:**
|
||||
* **Admin Page Wireframe (Three Column Layout):** Wireframe of the Admin Page using the ThreeColumnLayout.
|
||||
* **StudentCountWidget and TeacherCountWidget Placement:** Wireframes illustrating the placement of the StudentCountWidget and TeacherCountWidget in the three-column layout, demonstrating how these widgets would display summary statistics for students and teachers.
|
||||
|
||||
* **Topbar Module and Navigation:**
|
||||
* **Topbar Module Wireframe:** Detailed wireframe of the TopbarModule, showing the layout of navigation links (Dashboard, Classrooms, Settings, Admin, Profile) and the user profile dropdown.
|
||||
* **User Dropdown Menu Wireframe:** Wireframe of the user profile dropdown menu, displaying the "ID," "Profile," "Account settings," "Log out" options, and separators.
|
||||
|
||||
**Wireframe Fidelity and Purpose:**
|
||||
|
||||
The wireframes in the Figma project are primarily **mid-fidelity wireframes**. They focus on:
|
||||
|
||||
* **Structure and Layout:** Defining the page structure, widget placement, and overall information architecture.
|
||||
* **User Flow and Navigation:** Illustrating the basic user flows and navigation paths within the LMS frontend.
|
||||
* **Content Hierarchy:** Establishing the hierarchy of content and UI elements on each page.
|
||||
|
||||
The wireframes are *not* intended to be high-fidelity mockups with detailed visual design or pixel-perfect UI elements. The visual design and styling are primarily driven by Bootstrap 5.3 components and utilities within the actual code implementation.
|
||||
|
||||
**How Wireframes Informed Development:**
|
||||
|
||||
The Figma wireframes served as a crucial blueprint for frontend development by:
|
||||
|
||||
* **Guiding Layout Implementation:** Providing clear visual references for implementing the CenteredLayout, ThreeColumnLayout, and SplitColumnLayout modules in code.
|
||||
* **Widget Placement and Integration:** Defining where widgets should be placed on each page and how they would interact with the surrounding layout.
|
||||
* **Navigation Structure Definition:** Visualizing the topbar navigation and user dropdown menu structure, ensuring a logical and user-friendly navigation system.
|
||||
* **Communication and Alignment:** Serving as a shared visual communication tool to align the development team (or individual developer) on the intended UI and user experience.
|
||||
|
||||
*Replace the Figma project link and adjust the description of Figma project structure and wireframe details to accurately reflect your actual design assets.* If wireframes were not created in Figma or a similar tool, describe the informal UI design process and whether any sketches or visual aids were used, and where those might be found (e.g., hand-drawn sketches, simple mockups in image files).
|
||||
|
||||
## Project Timeline and Gantt Chart: Proof-of-Concept Development
|
||||
|
||||
A simplified Gantt chart was used for project planning and timeline management for this LMS Frontend proof-of-concept. While not a formal, detailed Gantt chart for a large project, it provided a basic visual representation of key phases, tasks, and estimated durations.
|
||||
|
||||
**Gantt Chart Location:** [**Path to Gantt Chart Spreadsheet File: e.g., `docs/project_plan/lms_frontend_gantt.xlsx`**]
|
||||
|
||||
*(Alternatively, if you used an online tool, provide a link)* [**Optional: Link to Online Gantt Chart Tool: [Insert Project Management Tool Link Here]**]
|
||||
|
||||
**Gantt Chart Structure and Key Phases:**
|
||||
|
||||
The Gantt chart was structured around the following key phases of the proof-of-concept development:
|
||||
|
||||
| Phase | Description | Start Date (Example) | End Date (Example) | Duration (Approx.) |
|
||||
| :----- | :------------------------------------------------------------------------------- | :------------------- | :----------------- | :----------------- |
|
||||
| **Phase 1: Project Setup and Core Architecture** | Establishing the foundation of the project and basic structural components. | 2023-10-26 | 2023-10-27 | 2 days |
|
||||
| *Task 1.1* | Vite Project Setup (Vanilla Typescript) and Bootstrap Integration | 2023-10-26 | 2023-10-26 | 1 day |
|
||||
| *Task 1.2* | Widget Interface and Initial Widget (Button, Login) Implementation | 2023-10-26 | 2023-10-27 | 1 day |
|
||||
| *Task 1.3* | Layout Module (Centered, Three-Column) Implementation | 2023-10-27 | 2023-10-27 | 1 day |
|
||||
| *Task 1.4* | Basic Routing and LoginPage/RegisterPage Creation | 2023-10-27 | 2023-10-27 | 1 day |
|
||||
| **Phase 2: Navigation, Dashboard, and API Mocking** | Implementing navigation, dashboard structure, and basic API simulation. | 2023-10-28 | 2023-10-30 | 3 days |
|
||||
| *Task 2.1* | TopbarModule Development (Navigation and User Dropdown) | 2023-10-28 | 2023-10-29 | 1.5 days |
|
||||
| *Task 2.2* | DashboardPage and SplitColumnLayout Implementation | 2023-10-29 | 2023-10-29 | 1 day |
|
||||
| *Task 2.3* | Dashboard Widget Placeholder Implementation | 2023-10-29 | 2023-10-30 | 1 day |
|
||||
| *Task 2.4* | Mock API Setup (`api.ts`) for Login and User Retrieval | 2023-10-30 | 2023-10-30 | 0.5 days |
|
||||
| *Task 2.5* | Authentication Utility (`auth.ts`) Implementation | 2023-10-30 | 2023-10-30 | 0.5 days |
|
||||
| **Phase 3: Profile, Admin, and Placeholder Features** | Prototyping profile, admin pages, and other placeholder functionalities. | 2023-10-31 | 2023-11-01 | 2 days |
|
||||
| *Task 3.1* | ProfilePage and SettingsPage Creation | 2023-10-31 | 2023-10-31 | 1 day |
|
||||
| *Task 3.2* | ProfileInfoWidget Placeholder Implementation | 2023-10-31 | 2023-11-01 | 0.5 days |
|
||||
| *Task 3.3* | AdminPage Creation and Three-Column Layout | 2023-11-01 | 2023-11-01 | 0.5 days |
|
||||
| *Task 3.4* | Admin Page Widget Placeholder Implementation (Student/Teacher Count) | 2023-11-01 | 2023-11-01 | 0.5 days |
|
||||
| *Task 3.5* | ClassroomsPage Placeholder (Modal) Implementation | 2023-11-01 | 2023-11-01 | 0.5 days |
|
||||
| **Phase 4: Refinement, Documentation, and Demo** | Finalizing code, documentation, and preparing a demonstration. | 2023-11-02 | 2023-11-10 | 7 days |
|
||||
| *Task 4.1* | UI Refinement and Bug Fixing | 2023-11-02 | 2023-11-03 | 2 days |
|
||||
| *Task 4.2* | Code Review and Optimization | 2023-11-03 | 2023-11-03 | 1 day |
|
||||
| *Task 4.3* | Comprehensive Documentation (README, CONTRIBUTION, Contract, SDLC, etc.) | 2023-11-04 | 2023-11-08 | 5 days |
|
||||
| *Task 4.4* | [Optional: Video Demonstration Recording and Editing] | 2023-11-09 | 2023-11-10 | 2 days |
|
||||
|
||||
*Note: Example dates and durations are approximate and should be adjusted based on your actual project timeline.*
|
||||
|
||||
**Gantt Chart Purpose for Proof-of-Concept:**
|
||||
|
||||
The simplified Gantt chart served primarily as a high-level planning tool for the proof-of-concept. Its purpose was to:
|
||||
|
||||
* **Visualize Project Phases:** Break down the project into logical phases to organize development efforts.
|
||||
* **Estimate Task Durations:** Provide rough estimates for the time required for each major task and phase.
|
||||
* **Identify Task Dependencies (Informally):** While not explicitly defining dependencies in detail, the Gantt chart helped to sequence tasks logically (e.g., core architecture before page development).
|
||||
* **Track Progress (Informally):** Allow for informal tracking of progress against the planned timeline, though strict adherence to the Gantt chart was not the primary goal in a proof-of-concept.
|
||||
|
||||
**Limitations of Gantt Chart in Proof-of-Concept:**
|
||||
|
||||
* **Simplified Task Breakdown:** The Gantt chart used a simplified task breakdown, focusing on larger phases and tasks rather than granular sub-tasks.
|
||||
* **Informal Durations:** Task durations were estimated informally and were not based on detailed effort estimation techniques.
|
||||
* **Flexibility and Adaptability Prioritized:** In line with the Agile-Inspired approach, the Gantt chart was treated as a flexible guide rather than a rigid plan. Adjustments to the timeline and tasks were expected and accommodated as development progressed.
|
||||
* **Not Used for Formal Project Management:** For a full-scale LMS project, a more detailed and actively managed Gantt chart or project management tool would be necessary for effective project tracking, resource allocation, and risk management.
|
||||
|
||||
For the proof-of-concept, the Gantt chart provided a useful framework for organizing development and visualizing the overall timeline, but the emphasis remained on rapid prototyping and iterative development rather than strict adherence to a detailed project plan.
|
||||
|
||||
*Adjust the table, dates, durations, task descriptions, and Gantt chart file path to accurately reflect your actual project plan and timeline.* If a Gantt chart was not used, describe the informal project planning methods employed instead, such as task lists, Kanban boards (even informal), or simple to-do lists.
|
||||
|
||||
By extending each section with these details and concrete examples, you will create a much more comprehensive and semantically rich documentation set that fully specifies the LMS Frontend project and provides valuable insights into its development, architecture, and limitations. Remember to replace the placeholder examples with your actual project information.
|
||||
```mermaid
|
||||
gantt
|
||||
title LMS Development Timeframe
|
||||
dateFormat YYYY-MM-DD
|
||||
section Planning & Design
|
||||
Requirements Gathering :a1, 2024-01-15, 1w
|
||||
System Design :a2, after a1, 2w
|
||||
UI/UX Wireframing :a3, after a2, 2w
|
||||
section Development
|
||||
Frontend Development :b1, after a3, 8w
|
||||
Backend Development :b2, after a3, 10w
|
||||
Database Design & Setup :b3, after a2, 3w
|
||||
API Integration :b4, after b2, 6w
|
||||
section Testing & Deployment
|
||||
System Testing :c1, after b4, 4w
|
||||
User Acceptance Testing :c2, after c1, 2w
|
||||
Deployment & Go-Live :c3, after c2, 2w
|
||||
Training & Handover :c4, after c3, 2w
|
||||
```
|
||||
|
||||
167
docs/DETAILED_VER.md
Normal file
167
docs/DETAILED_VER.md
Normal file
@ -0,0 +1,167 @@
|
||||
# Contract Proposal: Learner Management System (LMS)
|
||||
|
||||
## **Full-Length Version**
|
||||
|
||||
### Contract Proposal: Learner Management System (LMS) Development and Implementation
|
||||
|
||||
#### 1. SDLC (Software Development Life Cycle) Used: Agile - Iterative and Incremental Methodology
|
||||
|
||||
For the development of the Learner Management System (LMS), we propose utilizing an **Agile Software Development Life Cycle (SDLC)** methodology, specifically an **iterative and incremental approach**.
|
||||
|
||||
* **Description of Agile Methodology:** Agile methodologies prioritize flexibility, collaboration, and rapid iteration. In an iterative and incremental model, the project is broken down into smaller, manageable iterations (sprints). Each iteration involves planning, design, development, testing, and review. At the end of each iteration, a working increment of the software is delivered. These increments are progressively built upon to create the final LMS.
|
||||
|
||||
* **Rationale for Choosing Agile:**
|
||||
* **Adaptability to Changing Requirements:** The education sector is dynamic, and requirements for an LMS can evolve. Agile is inherently flexible, allowing for adjustments and incorporation of new features or modifications throughout the development process without causing significant disruptions.
|
||||
* **Early and Continuous Delivery:** Agile ensures that functional components of the LMS are delivered early and frequently. This provides stakeholders with tangible progress and opportunities for early feedback, leading to a more refined and user-centric final product.
|
||||
* **Enhanced Collaboration and Communication:** Agile emphasizes close collaboration between the development team, stakeholders, and end-users. Regular meetings, feedback sessions, and transparent communication channels ensure alignment and shared understanding throughout the project.
|
||||
* **Risk Mitigation:** By breaking down the project into smaller iterations, risks are identified and addressed early in the development cycle. This proactive approach reduces the likelihood of major issues arising later in the project.
|
||||
* **Focus on User Needs:** Agile's iterative nature and emphasis on feedback ensure that the LMS is developed with a strong focus on meeting the actual needs of learners, instructors, and administrators.
|
||||
|
||||
#### 2. Advantages: Key Benefits of the Learner Management System
|
||||
|
||||
This LMS project is designed to deliver significant advantages across technology, design, and project management domains, resulting in a robust and effective learning platform.
|
||||
|
||||
* **Technological Advantages:**
|
||||
* **Modern Technology Stack:** The LMS will be built using a contemporary technology stack, including [Specify technologies like: React/Vue.js/Angular for frontend, Node.js/Python/Java for backend, modern databases, etc.]. This ensures high performance, scalability to accommodate growing user bases, and ease of maintenance and future updates.
|
||||
* **Secure and Reliable Platform:** Security is paramount. The LMS will incorporate robust security measures, including secure authentication protocols, data encryption, and regular security audits to protect user data and ensure system integrity.
|
||||
* **API-Driven Architecture:** The LMS will be designed with a well-defined API (Application Programming Interface). This allows for seamless integration with other educational tools, third-party platforms, and future system expansions, enhancing interoperability and flexibility.
|
||||
* **Responsive and Accessible Design:** The platform will be fully responsive, ensuring optimal user experience across various devices (desktops, tablets, and smartphones). Accessibility standards will be adhered to, making the LMS usable by individuals with diverse needs.
|
||||
|
||||
* **Design Advantages:**
|
||||
* **User-Centric Interface (UI) and User Experience (UX):** The LMS will feature an intuitive and user-friendly interface designed based on UX best practices. Navigation will be straightforward, and workflows will be streamlined, ensuring ease of use for learners, instructors, and administrators, regardless of their technical proficiency.
|
||||
* **Visually Appealing and Engaging Design:** A modern and aesthetically pleasing design will enhance user engagement and create a positive learning environment. The visual design will be consistent with current web design trends and tailored to be professional and education-focused.
|
||||
* **Customizable and Brandable:** The LMS will be designed to be easily customizable and brandable. Themes, logos, and color schemes can be adapted to align with the institution's branding, providing a cohesive and professional online presence.
|
||||
* **Modular and Flexible Layout:** The LMS will employ a modular design approach, allowing for easy addition, removal, or modification of features and modules. This ensures the platform can evolve alongside changing educational needs and technological advancements.
|
||||
|
||||
* **Project Management Advantages:**
|
||||
* **Experienced and Dedicated Team:** A team of experienced software developers, designers, and project managers will be assigned to this project. Their expertise in LMS development and Agile methodologies ensures efficient and high-quality project execution.
|
||||
* **Transparent Communication and Reporting:** We are committed to maintaining transparent and consistent communication throughout the project lifecycle. Regular progress reports, sprint reviews, and stakeholder meetings will keep you informed and involved.
|
||||
* **Timely Delivery and Adherence to Schedule:** Agile project management, combined with meticulous planning and resource allocation, will ensure that the LMS is delivered on time and within the agreed-upon timeframe.
|
||||
* **Quality Assurance and Testing:** Rigorous testing protocols will be implemented throughout the development process, including unit testing, integration testing, system testing, and user acceptance testing. This comprehensive approach guarantees a stable, reliable, and high-quality LMS.
|
||||
|
||||
#### 3. Disadvantages: Potential Limitations and Mitigation Strategies
|
||||
|
||||
While this LMS project offers numerous advantages, it is important to acknowledge potential disadvantages and outline strategies to mitigate them.
|
||||
|
||||
* **Potential for Scope Creep (Agile Flexibility):**
|
||||
* **Disadvantage:** Agile's inherent flexibility, while beneficial, can sometimes lead to scope creep if not managed effectively. As requirements are iteratively refined, there's a risk of adding features beyond the initial project scope, potentially impacting timelines and budgets.
|
||||
* **Mitigation:** We will implement robust scope management practices. This includes:
|
||||
* **Clear Initial Requirements Definition:** Thorough requirements gathering and documentation at the project outset.
|
||||
* **Prioritized Backlog Management:** Utilizing a prioritized product backlog, ensuring that only essential and high-value features are included in each iteration.
|
||||
* **Change Management Process:** Establishing a formal change request process for evaluating and managing any proposed changes to the scope, ensuring impacts on timeline and budget are clearly understood and agreed upon.
|
||||
* **Regular Sprint Reviews:** Conducting sprint reviews with stakeholders at the end of each iteration to ensure alignment and manage expectations regarding scope and progress.
|
||||
|
||||
* **Complexity of Initial Setup and Data Migration:**
|
||||
* **Disadvantage:** Implementing a comprehensive LMS, especially if migrating from a legacy system or setting up from scratch, can be complex. Data migration, system configuration, and user onboarding require initial effort and coordination.
|
||||
* **Mitigation:** We will provide comprehensive support to ease the setup and transition process:
|
||||
* **Dedicated Onboarding Team:** Assigning a dedicated onboarding team to assist with data migration, system configuration, and initial user setup.
|
||||
* **Detailed Migration Plan:** Developing a detailed data migration plan to ensure a smooth and accurate transfer of existing data to the new LMS.
|
||||
* **Comprehensive Training Programs:** Providing comprehensive training programs for administrators, instructors, and learners to ensure they can effectively utilize the LMS features from day one.
|
||||
* **Phased Rollout Approach:** Considering a phased rollout approach, where the LMS is implemented module by module or department by department, to minimize disruption and allow for gradual adoption.
|
||||
* **Ongoing Support and Documentation:** Providing ongoing technical support and comprehensive documentation (user manuals, FAQs, video tutorials) to assist users post-launch and ensure continued smooth operation.
|
||||
|
||||
#### 4. Video Explanation: Demonstrating the LMS Functionality
|
||||
|
||||
To provide a clear understanding of the LMS and its capabilities, we will create a concise and informative video explanation. This video will be a valuable tool for stakeholders to visualize the system in action and understand its core functionalities.
|
||||
|
||||
* **Key Points to be Covered in the Video:**
|
||||
* **System Overview and Navigation:**
|
||||
* A high-level tour of the LMS platform, showcasing its overall structure and layout.
|
||||
* Demonstration of the main navigation menu and how users can access different modules and features.
|
||||
* Highlighting the responsive design and accessibility of the platform across devices.
|
||||
* **User Role Demonstrations:**
|
||||
* **Learner Perspective:** Showcasing the learner dashboard, course catalog, course enrollment process, accessing course content (modules, lessons, assignments), interaction with learning materials, progress tracking, and communication features.
|
||||
* **Instructor Perspective:** Demonstrating the instructor dashboard, course creation and management tools, content uploading and organization, assignment creation and grading, communication with learners, progress monitoring, and reporting functionalities.
|
||||
* **Administrator Perspective:** Highlighting the administrator dashboard, user management (adding, editing, managing user roles), system settings and configurations, reporting and analytics overview, and platform customization options.
|
||||
* **Core LMS Workflows and Features:**
|
||||
* **Course Enrollment and Management:** Illustrating the process of learners enrolling in courses and instructors managing their courses.
|
||||
* **Content Delivery and Learning Resources:** Showcasing various content formats supported (videos, documents, interactive modules, etc.) and how they are presented to learners.
|
||||
* **Assessment and Grading:** Demonstration of assignment submission, automated and manual grading processes, quiz functionalities, and gradebook management.
|
||||
* **Communication and Collaboration Tools:** Highlighting features such as forums, messaging systems, announcements, and live session integration for enhanced interaction and communication within the learning environment.
|
||||
* **Progress Tracking and Reporting:** Overview of progress tracking features for learners and reporting functionalities for instructors and administrators to monitor learning outcomes and system usage.
|
||||
|
||||
#### 5. Wireframe: User Interface Layout and Navigation Structure
|
||||
|
||||
The LMS wireframe is designed to be clean, intuitive, and user-focused, providing a structured and efficient learning environment.
|
||||
|
||||
* **Overall Layout Structure:**
|
||||
* **Header (Top Navigation Bar):** Located at the top of every page, providing consistent global navigation.
|
||||
* **Logo Area:** Institution's logo (left side).
|
||||
* **Main Menu Links:** Links to primary LMS modules (e.g., Dashboard, Courses, Users, Calendar, Reports, Admin Panel) - positioned centrally or right side.
|
||||
* **User Profile/Account Section:** User avatar or initials, full name, dropdown menu for profile settings, notifications, and logout (rightmost side).
|
||||
* **Sidebar (Left Navigation Panel):** Contextual navigation relevant to the currently active module.
|
||||
* Appears on most pages except for login/registration and full-screen content views.
|
||||
* Provides quick access to sub-sections and features within the current module (e.g., within "Courses": My Courses, Course Catalog, Archived Courses; within "Admin": User Management, System Settings, Reporting).
|
||||
* May be collapsible to provide more screen real estate for content when needed.
|
||||
* **Main Content Area (Central Pane):** The primary display area for dynamic content, varying based on the module and page selected.
|
||||
* Displays dashboards, course listings, individual course pages, user profiles, forms, tables, interactive learning elements, etc.
|
||||
* Designed to be responsive and adapt to different screen sizes.
|
||||
* **Footer (Bottom Bar):** Located at the bottom of every page, containing less frequently used but essential information.
|
||||
* Copyright information, terms of service, privacy policy links.
|
||||
* Support contact information or links to help documentation.
|
||||
* Language selection or accessibility options (if applicable).
|
||||
|
||||
* **Navigation and Major Components:**
|
||||
* **Dashboard:** Personalized landing page after login.
|
||||
* **Widgets/Cards:** Displaying key information at a glance: "My Courses," "Upcoming Assignments," "Recent Announcements," "Progress Overview," "Calendar Events."
|
||||
* Customizable layout to allow users to prioritize information.
|
||||
* **Course Catalog/Course Listing:** Page to browse available courses.
|
||||
* **Search and Filter Options:** Keywords, categories, instructors, course level, etc.
|
||||
* **Course Cards/Tiles:** Displaying course name, brief description, instructor, enrollment status, and thumbnail image.
|
||||
* **Course Detail Page:** In-depth information about a selected course: full description, syllabus, learning objectives, instructor bio, enrollment button.
|
||||
* **Individual Course Pages:** The learning environment for each enrolled course.
|
||||
* **Course Navigation Menu (within sidebar or top tabs):** Modules/Lessons, Assignments, Quizzes, Discussions, Grades, Participants.
|
||||
* **Content Display Area:** Presenting lesson content, videos, documents, interactive activities.
|
||||
* **Progress Bar:** Visual representation of course completion.
|
||||
* **User Profiles:** Pages to view and manage user information.
|
||||
* **Learner Profile:** Personal details, enrolled courses, grades, progress reports.
|
||||
* **Instructor Profile:** Personal details, courses taught, student feedback.
|
||||
* **Admin Profile:** Administrative settings access, system reports.
|
||||
* **Admin Panel:** Module accessible only to administrators.
|
||||
* **User Management:** Adding, editing, deleting users, managing user roles and permissions.
|
||||
* **Course Management:** Creating, editing, managing courses, categories, instructors.
|
||||
* **System Settings:** Platform configurations, branding customization, security settings.
|
||||
* **Reporting and Analytics:** Access to system usage reports, learner performance data, course analytics.
|
||||
* **Communication Tools:**
|
||||
* **Announcements:** System-wide or course-specific announcements displayed prominently.
|
||||
* **Forums/Discussion Boards:** Areas for asynchronous discussions within courses or across the platform.
|
||||
* **Messaging System:** Private messaging between users (learners, instructors, admins).
|
||||
* **Integration with Live Session Tools:** Links or embedded interfaces for video conferencing or virtual classroom platforms.
|
||||
|
||||
#### 6. Timeframe (Gantt Chart): Project Schedule and Milestones
|
||||
|
||||
The project timeframe is structured using a Gantt chart to provide a clear visual representation of project phases, tasks, dependencies, and timelines. This schedule is based on an Agile iterative development approach, with key milestones identified for each phase.
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title LMS Development Timeframe
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %Y-%m-%d
|
||||
todayMarker stroke-width:5px,stroke:#0f0,stroke-opacity:0.5
|
||||
section Phase 1: Planning & Design (5 Weeks)
|
||||
Requirements Gathering :req, 2024-01-15, 14d
|
||||
System Design :design, after req, 14d
|
||||
UI/UX Wireframing & Prototyping :wireframe, after design, 14d
|
||||
Design Review & Approval :designreview, after wireframe, 7d
|
||||
section Phase 2: Development (18 Weeks)
|
||||
Frontend Development :frontend, after designreview, 56d
|
||||
Backend Development & API :backend, after designreview, 70d
|
||||
Database Design & Setup :database, after designreview, 21d
|
||||
API Integration & Testing :api, after backend, 42d
|
||||
Internal Testing (Dev Team) :devtest, after api, 21d
|
||||
section Phase 3: Testing & Deployment (8 Weeks)
|
||||
System Testing (QA Team) :systest, after devtest, 28d
|
||||
User Acceptance Testing (UAT) :uat, after systest, 14d
|
||||
UAT Feedback & Fixes :uatfix, after uat, 14d
|
||||
Deployment & Go-Live :deploy, after uatfix, 14d
|
||||
section Phase 4: Training & Handover (4 Weeks)
|
||||
Admin & Instructor Training :admintrain, after deploy, 14d
|
||||
Learner Onboarding & Training :learnertrain, after deploy, 14d
|
||||
Documentation & Handover :handover, after learnertrain, 14d
|
||||
|
||||
%% Milestones (Example, adjust dates based on actual timeline)
|
||||
milestone Phase 1 Complete : 2024-02-19
|
||||
milestone Phase 2 Complete : 2024-06-24
|
||||
milestone Phase 3 Complete : 2024-08-19
|
||||
milestone Project Go-Live : 2024-08-19
|
||||
```
|
||||
24
index.html
24
index.html
@ -1,13 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LMS Prototype</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/src/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="scrolling-background">
|
||||
<div class="color-overlay"></div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -15,5 +15,8 @@
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.29.0",
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -7,6 +7,10 @@ settings:
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
bootstrap:
|
||||
specifier: ^5.3.5
|
||||
version: 5.3.5(@popperjs/core@2.11.8)
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.24.0
|
||||
@ -253,6 +257,9 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@popperjs/core@2.11.8':
|
||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.39.0':
|
||||
resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==}
|
||||
cpu: [arm]
|
||||
@ -429,6 +436,11 @@ packages:
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
bootstrap@5.3.5:
|
||||
resolution: {integrity: sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==}
|
||||
peerDependencies:
|
||||
'@popperjs/core': ^2.11.8
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
|
||||
@ -986,6 +998,8 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.19.1
|
||||
|
||||
'@popperjs/core@2.11.8': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.39.0':
|
||||
optional: true
|
||||
|
||||
@ -1148,6 +1162,10 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
bootstrap@5.3.5(@popperjs/core@2.11.8):
|
||||
dependencies:
|
||||
'@popperjs/core': 2.11.8
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
157
src/api/api.ts
Normal file
157
src/api/api.ts
Normal file
@ -0,0 +1,157 @@
|
||||
// api/api.ts
|
||||
import { updateLoggedInState } from '../utils/utils';
|
||||
|
||||
// --- Define API Response Interfaces ---
|
||||
export interface LoginResponseData { // Exported
|
||||
userId: string;
|
||||
fullName: string;
|
||||
profilePicture: string;
|
||||
schoolId: string;
|
||||
birthdate: string;
|
||||
token: string;
|
||||
}
|
||||
export interface ApiResponse<T> { // Exported
|
||||
success: boolean;
|
||||
data?: T;
|
||||
message?: string;
|
||||
}
|
||||
export interface ProfileResponseData extends LoginResponseData { // Exported
|
||||
posts: { id: number; content: string }[];
|
||||
}
|
||||
export interface AdminDashboardData { // Exported
|
||||
studentsCount: number;
|
||||
teachersCount: number;
|
||||
}
|
||||
export interface StudentListData { // Exported
|
||||
id: number;
|
||||
name: string;
|
||||
yearLevel: string;
|
||||
courses: string[];
|
||||
}
|
||||
export interface StudentFinancialData {
|
||||
tuitionFee: string;
|
||||
miscellaneousFee: string;
|
||||
labFee: string;
|
||||
currentAccount: string;
|
||||
downPayment: string;
|
||||
midtermPayment: string;
|
||||
prefinalPayment: string;
|
||||
finalPayment: string;
|
||||
}
|
||||
|
||||
|
||||
// --- Mock API Endpoints ---
|
||||
const mockAPI = {
|
||||
login: async (credentials: any): Promise<ApiResponse<LoginResponseData>> => { // Explicit return type, parameter type remains 'any' as per original spec
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (credentials.userId === '123456' && credentials.password === 'password') {
|
||||
const userData: LoginResponseData = {
|
||||
userId: generateMockUserId(), // Mock User ID
|
||||
fullName: 'John Doe',
|
||||
profilePicture: 'path/to/profile.jpg', // Placeholder
|
||||
schoolId: '123456',
|
||||
birthdate: '2000-01-01',
|
||||
token: 'mock-user-token-123', // Mock token
|
||||
};
|
||||
resolve({ success: true, data: userData });
|
||||
updateLoggedInState(true, userData.token); // Update login state on successful login
|
||||
} else {
|
||||
reject({ success: false, message: 'Invalid credentials' });
|
||||
}
|
||||
}, 1000); // Simulate API latency
|
||||
});
|
||||
},
|
||||
logout: async (): Promise<ApiResponse<void>> => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
updateLoggedInState(false, null);
|
||||
resolve({ success: true });
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
getProfile: async (userId: string): Promise<ApiResponse<ProfileResponseData>> => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
const profileData: ProfileResponseData = {
|
||||
userId: userId,
|
||||
fullName: 'John Doe',
|
||||
profilePicture: 'path/to/profile.jpg', // Placeholder
|
||||
schoolId: '123456',
|
||||
birthdate: '2000-01-01',
|
||||
token: 'mock-user-token-123', // Added token here to match ProfileResponseData interface
|
||||
posts: [
|
||||
{ id: 1, content: 'First post on profile!' },
|
||||
{ id: 2, content: 'Another profile post.' }
|
||||
]
|
||||
};
|
||||
resolve({ success: true, data: profileData });
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
updateAccountSettings: async (userId: string, settings: any): Promise<ApiResponse<void>> => { // parameter type remains 'any'
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
console.log('Account settings updated for user:', userId, settings);
|
||||
resolve({ success: true });
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
getAdminDashboardData: async (): Promise<ApiResponse<AdminDashboardData>> => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
const dashboardData: AdminDashboardData = {
|
||||
studentsCount: 150,
|
||||
teachersCount: 30,
|
||||
};
|
||||
resolve({ success: true, data: dashboardData });
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
getStudentList: async (): Promise<ApiResponse<StudentListData[]>> => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
const studentList: StudentListData[] = [
|
||||
{ id: 1, name: 'Alice Smith', yearLevel: '1', courses: ['Math', 'Science'] },
|
||||
{ id: 2, name: 'Bob Johnson', yearLevel: '2', courses: ['History', 'English'] },
|
||||
];
|
||||
resolve({ success: true, data: studentList });
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
getStudentFinancialData: async (_studentId: string): Promise<ApiResponse<StudentFinancialData>> => { // parameter type remains 'string', unused parameter prefixed with _
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
const financialData: StudentFinancialData = {
|
||||
tuitionFee: '5000',
|
||||
miscellaneousFee: '500',
|
||||
labFee: '200',
|
||||
currentAccount: '-100',
|
||||
downPayment: '2000',
|
||||
midtermPayment: '1500',
|
||||
prefinalPayment: '1000',
|
||||
finalPayment: '500',
|
||||
};
|
||||
resolve({ success: true, data: financialData });
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let mockUserIdCounter = 100000;
|
||||
const generateMockUserId = () => {
|
||||
return String(mockUserIdCounter++);
|
||||
};
|
||||
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
globalAPI: typeof mockAPI;
|
||||
}
|
||||
}
|
||||
|
||||
export const setupGlobalAPI = () => {
|
||||
window.globalAPI = mockAPI;
|
||||
};
|
||||
|
||||
export const globalAPI = mockAPI;
|
||||
BIN
src/assets/bg1.jpg
Normal file
BIN
src/assets/bg1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
BIN
src/assets/bg2.jpg
Normal file
BIN
src/assets/bg2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 537 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
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;
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
export function setupCounter(element: HTMLButtonElement) {
|
||||
let counter = 0
|
||||
const setCounter = (count: number) => {
|
||||
counter = count
|
||||
element.innerHTML = `count is ${counter}`
|
||||
}
|
||||
element.addEventListener('click', () => setCounter(counter + 1))
|
||||
setCounter(0)
|
||||
}
|
||||
23
src/layouts/CenteredLayout.ts
Normal file
23
src/layouts/CenteredLayout.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// layouts/CenteredLayout.ts
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
export class CenteredLayout {
|
||||
private container: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.container = createElement('div');
|
||||
this.container.classList.add('centered-layout', 'container-fluid');
|
||||
}
|
||||
|
||||
addContent(content: HTMLElement | string): void {
|
||||
if (typeof content === 'string') {
|
||||
this.container.innerHTML = content;
|
||||
} else {
|
||||
this.container.appendChild(content);
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
55
src/layouts/SplitColumnLayout.ts
Normal file
55
src/layouts/SplitColumnLayout.ts
Normal file
@ -0,0 +1,55 @@
|
||||
// layouts/SplitColumnLayout.ts
|
||||
import { createElement } from '../utils/utils';
|
||||
import { TopBar } from '../components/TopBar';
|
||||
|
||||
export class SplitColumnLayout {
|
||||
private main: HTMLElement;
|
||||
private container: HTMLElement;
|
||||
private sidebar: HTMLElement;
|
||||
private contentArea: HTMLElement;
|
||||
private topBar: TopBar;
|
||||
private isSidebarCollapsed: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.main = createElement('div');
|
||||
|
||||
this.container = createElement('div');
|
||||
this.container.classList.add('split-column-layout', 'container-fluid');
|
||||
|
||||
this.topBar = new TopBar();
|
||||
this.main.appendChild(this.topBar.render());
|
||||
|
||||
this.sidebar = createElement('div');
|
||||
this.sidebar.classList.add('sidebar'); // Bootstrap column classes for sidebar
|
||||
this.contentArea = createElement('div');
|
||||
this.contentArea.classList.add('content'); // Bootstrap column classes for content
|
||||
|
||||
this.container.appendChild(this.sidebar);
|
||||
this.container.appendChild(this.contentArea);
|
||||
|
||||
this.main.appendChild(this.container);
|
||||
}
|
||||
|
||||
setSidebarContent(content: HTMLElement): void {
|
||||
this.sidebar.innerHTML = ''; // Clear existing content
|
||||
this.sidebar.appendChild(content);
|
||||
}
|
||||
|
||||
setContentAreaContent(content: HTMLElement): void {
|
||||
this.contentArea.innerHTML = '';
|
||||
this.contentArea.appendChild(content);
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.main;
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
this.isSidebarCollapsed = !this.isSidebarCollapsed;
|
||||
if (this.isSidebarCollapsed) {
|
||||
this.container.classList.add('collapsed-sidebar');
|
||||
} else {
|
||||
this.container.classList.remove('collapsed-sidebar');
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/layouts/ThreeColumnLayout.ts
Normal file
55
src/layouts/ThreeColumnLayout.ts
Normal file
@ -0,0 +1,55 @@
|
||||
// layouts/ThreeColumnLayout.ts
|
||||
import { createElement } from '../utils/utils';
|
||||
import { TopBar } from '../components/TopBar';
|
||||
|
||||
export class ThreeColumnLayout {
|
||||
private main: HTMLElement;
|
||||
private container: HTMLElement;
|
||||
private column1: HTMLElement;
|
||||
private column2: HTMLElement;
|
||||
private column3: HTMLElement;
|
||||
private topBar: TopBar;
|
||||
|
||||
constructor() {
|
||||
this.main = createElement('div');
|
||||
|
||||
this.container = createElement('div');
|
||||
this.container.classList.add('three-column-layout', 'container-fluid');
|
||||
|
||||
this.topBar = new TopBar();
|
||||
this.main.appendChild(this.topBar.render());
|
||||
|
||||
this.column1 = createElement('div');
|
||||
this.column1.classList.add('column'); // Bootstrap column classes
|
||||
this.column2 = createElement('div');
|
||||
this.column2.classList.add('column');
|
||||
this.column3 = createElement('div');
|
||||
this.column3.classList.add('column');
|
||||
|
||||
this.container.appendChild(this.column1);
|
||||
this.container.appendChild(this.column2);
|
||||
this.container.appendChild(this.column3);
|
||||
|
||||
this.main.appendChild(this.container);
|
||||
}
|
||||
|
||||
setColumn1Content(content: HTMLElement): void {
|
||||
this.column1.innerHTML = ''; // Clear existing content
|
||||
this.column1.appendChild(content);
|
||||
}
|
||||
|
||||
setColumn2Content(content: HTMLElement): void {
|
||||
this.column2.innerHTML = '';
|
||||
this.column2.appendChild(content);
|
||||
}
|
||||
|
||||
setColumn3Content(content: HTMLElement): void {
|
||||
this.column3.innerHTML = '';
|
||||
this.column3.appendChild(content);
|
||||
}
|
||||
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.main;
|
||||
}
|
||||
}
|
||||
73
src/main.ts
73
src/main.ts
@ -1,24 +1,53 @@
|
||||
import './style.css'
|
||||
import typescriptLogo from './typescript.svg'
|
||||
import viteLogo from '/vite.svg'
|
||||
import { setupCounter } from './counter.ts'
|
||||
import './style.css';
|
||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js'; // Bootstrap JS
|
||||
import { renderLoginPage } from './pages/LoginPage';
|
||||
import { renderRegisterPage } from './pages/RegisterPage';
|
||||
import { renderDashboardPage } from './pages/DashboardPage';
|
||||
import { renderProfilePage } from './pages/ProfilePage';
|
||||
import { renderClassroomsPage } from './pages/ClassroomsPage';
|
||||
import { renderAdminPage } from './pages/AdminPage';
|
||||
import { renderManageStudentPage } from './pages/ManageStudentPage';
|
||||
import { setupGlobalAPI } from './api/api';
|
||||
import { updateLoggedInState } from './utils/utils';
|
||||
|
||||
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
|
||||
<div>
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src="${viteLogo}" class="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://www.typescriptlang.org/" target="_blank">
|
||||
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
|
||||
</a>
|
||||
<h1>Vite + TypeScript</h1>
|
||||
<div class="card">
|
||||
<button id="counter" type="button"></button>
|
||||
</div>
|
||||
<p class="read-the-docs">
|
||||
Click on the Vite and TypeScript logos to learn more
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
const app = document.querySelector<HTMLDivElement>('#app');
|
||||
if (!app) {
|
||||
throw new Error("App root element not found.");
|
||||
}
|
||||
|
||||
setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)
|
||||
// Initialize Global API
|
||||
setupGlobalAPI();
|
||||
|
||||
// --- Routing and Page Rendering ---
|
||||
const routes: { [key: string]: () => void } = {
|
||||
'/': renderLoginPage,
|
||||
'/login': renderLoginPage,
|
||||
'/register': renderRegisterPage,
|
||||
'/dashboard': renderDashboardPage,
|
||||
'/profile': renderProfilePage,
|
||||
'/classrooms': renderClassroomsPage,
|
||||
'/admin': renderAdminPage,
|
||||
'/manage-students': renderManageStudentPage,
|
||||
};
|
||||
|
||||
const renderRoute = () => {
|
||||
const path = window.location.hash.slice(1) || '/';
|
||||
const routeRenderer = routes[path];
|
||||
if (routeRenderer) {
|
||||
app.innerHTML = ''; // Clear current content
|
||||
routeRenderer();
|
||||
} else {
|
||||
app.innerHTML = '<div><h1>404 Not Found</h1></div>';
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('hashchange', renderRoute);
|
||||
renderRoute(); // Initial render
|
||||
|
||||
// --- Mock Login for Prototype ---
|
||||
// For demonstration, automatically log in on dashboard page load
|
||||
if (window.location.hash === '#/dashboard' || window.location.hash === '#/profile' || window.location.hash === '#/admin' || window.location.hash === '#/manage-students') {
|
||||
updateLoggedInState(true); // Simulate logged in state
|
||||
} else {
|
||||
updateLoggedInState(false);
|
||||
}
|
||||
17
src/pages/AdminPage.ts
Normal file
17
src/pages/AdminPage.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// pages/AdminPage.ts
|
||||
import { ThreeColumnLayout } from '../layouts/ThreeColumnLayout';
|
||||
import { StudentsWidget } from '../widgets/StudentsWidget';
|
||||
import { TeachersWidget } from '../widgets/TeachersWidget';
|
||||
|
||||
export const renderAdminPage = () => {
|
||||
const layout = new ThreeColumnLayout();
|
||||
const studentsWidget = new StudentsWidget();
|
||||
const teachersWidget = new TeachersWidget();
|
||||
// Column 3 is optional for future features as per requirements
|
||||
|
||||
layout.setColumn1Content(studentsWidget.render());
|
||||
layout.setColumn2Content(teachersWidget.render());
|
||||
// Column 3 is intentionally left empty in this prototype
|
||||
|
||||
document.querySelector<HTMLDivElement>('#app')?.appendChild(layout.render());
|
||||
};
|
||||
12
src/pages/ClassroomsPage.ts
Normal file
12
src/pages/ClassroomsPage.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// pages/ClassroomsPage.ts
|
||||
import { CenteredLayout } from '../layouts/CenteredLayout';
|
||||
import { UnderConstructionWidget } from '../widgets/UnderConstructionWidget';
|
||||
|
||||
export const renderClassroomsPage = () => {
|
||||
const layout = new CenteredLayout();
|
||||
const underConstructionWidget = new UnderConstructionWidget("Classrooms is currently not yet ready for production.");
|
||||
|
||||
layout.addContent(underConstructionWidget.render());
|
||||
|
||||
document.querySelector<HTMLDivElement>('#app')?.appendChild(layout.render());
|
||||
};
|
||||
60
src/pages/DashboardPage.ts
Normal file
60
src/pages/DashboardPage.ts
Normal file
@ -0,0 +1,60 @@
|
||||
// pages/DashboardPage.ts
|
||||
import { ThreeColumnLayout } from '../layouts/ThreeColumnLayout';
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
// Dummy Widgets for Dashboard
|
||||
class WelcomeWidget extends Widget {
|
||||
render(): HTMLElement {
|
||||
const widgetDiv = createElement('div');
|
||||
widgetDiv.classList.add('widget');
|
||||
widgetDiv.innerHTML = `
|
||||
<div class="widget-header">Welcome to the Dashboard</div>
|
||||
<div class="widget-body">
|
||||
This is your dashboard. More widgets will be added here.
|
||||
</div>
|
||||
`;
|
||||
return widgetDiv;
|
||||
}
|
||||
}
|
||||
|
||||
class QuickLinksWidget extends Widget {
|
||||
render(): HTMLElement {
|
||||
const widgetDiv = createElement('div');
|
||||
widgetDiv.classList.add('widget');
|
||||
widgetDiv.innerHTML = `
|
||||
<div class="widget-header">Quick Links</div>
|
||||
<div class="widget-body">
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#/profile">View Profile</a></li>
|
||||
<li><a href="#/classrooms">Go to Classrooms</a></li>
|
||||
<li><a href="#/admin">Admin Panel</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
return widgetDiv;
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceholderWidget extends Widget { // Corrected placeholder widget definition
|
||||
render(): HTMLElement {
|
||||
const div = createElement('div');
|
||||
div.classList.add('widget');
|
||||
div.textContent = 'Placeholder Widget';
|
||||
return div;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const renderDashboardPage = () => {
|
||||
const layout = new ThreeColumnLayout();
|
||||
const welcomeWidget = new WelcomeWidget();
|
||||
const quickLinksWidget = new QuickLinksWidget();
|
||||
const placeholderWidget = new PlaceholderWidget(); // Use the corrected PlaceholderWidget class
|
||||
|
||||
layout.setColumn1Content(welcomeWidget.render());
|
||||
layout.setColumn2Content(quickLinksWidget.render());
|
||||
layout.setColumn3Content(placeholderWidget.render());
|
||||
|
||||
document.querySelector<HTMLDivElement>('#app')?.appendChild(layout.render());
|
||||
};
|
||||
21
src/pages/LoginPage.ts
Normal file
21
src/pages/LoginPage.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// pages/LoginPage.ts
|
||||
import { CenteredLayout } from '../layouts/CenteredLayout';
|
||||
import { LoginWidget } from '../widgets/LoginWidget';
|
||||
import { RegisterWidget } from '../widgets/RegisterWidget';
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
export const renderLoginPage = () => {
|
||||
const layout = new CenteredLayout();
|
||||
const loginWidget = new LoginWidget();
|
||||
const registerWidget = new RegisterWidget();
|
||||
|
||||
const container = createElement('div');
|
||||
container.classList.add('d-flex', 'flex-column', 'gap-3', 'p-4', 'rounded', 'shadow', 'blur'); // Bootstrap flex and styling
|
||||
|
||||
container.appendChild(loginWidget.render());
|
||||
container.appendChild(registerWidget.render());
|
||||
|
||||
layout.addContent(container);
|
||||
|
||||
document.querySelector<HTMLDivElement>('#app')?.appendChild(layout.render());
|
||||
};
|
||||
16
src/pages/ManageStudentPage.ts
Normal file
16
src/pages/ManageStudentPage.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// pages/ManageStudentPage.ts
|
||||
import { SplitColumnLayout } from '../layouts/SplitColumnLayout'; // Or ThreeColumnLayout or full width, depending on desired layout
|
||||
import { StudentTableWidget } from '../widgets/StudentTableWidget';
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
export const renderManageStudentPage = () => {
|
||||
const layout = new SplitColumnLayout(); // Using SplitColumnLayout, can be changed to ThreeColumnLayout or full width
|
||||
const studentTableWidget = new StudentTableWidget();
|
||||
|
||||
layout.setContentAreaContent(studentTableWidget.render());
|
||||
// Sidebar can be used for filters or additional admin options if needed
|
||||
layout.setSidebarContent(createElement('div')); // Empty sidebar for now, or add sidebar widgets
|
||||
|
||||
|
||||
document.querySelector<HTMLDivElement>('#app')?.appendChild(layout.render());
|
||||
};
|
||||
47
src/pages/ProfilePage.ts
Normal file
47
src/pages/ProfilePage.ts
Normal file
@ -0,0 +1,47 @@
|
||||
// pages/ProfilePage.ts
|
||||
import { SplitColumnLayout } from '../layouts/SplitColumnLayout';
|
||||
import { ProfileWidget } from '../widgets/ProfileWidget';
|
||||
import { StudentFinancialWidget } from '../widgets/StudentFinancialWidget';
|
||||
import { PostFeedWidget } from '../widgets/PostFeedWidget';
|
||||
import { AccountSettingsWidget } from '../widgets/AccountSettingsWidget';
|
||||
import { Modal } from '../components/Modal';
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
let accountSettingsModalInstance: Modal | null = null; // Keep track of modal instance
|
||||
|
||||
export const renderProfilePage = () => {
|
||||
const layout = new SplitColumnLayout();
|
||||
const sidebar = createElement('div');
|
||||
|
||||
const profileWidget = new ProfileWidget();
|
||||
const financesWidget = new StudentFinancialWidget('mockUserId');
|
||||
const postFeedWidget = new PostFeedWidget();
|
||||
|
||||
sidebar.appendChild(profileWidget.render());
|
||||
sidebar.appendChild(financesWidget.render());
|
||||
|
||||
layout.setSidebarContent(sidebar);
|
||||
layout.setContentAreaContent(postFeedWidget.render());
|
||||
|
||||
const appElement = document.querySelector<HTMLDivElement>('#app');
|
||||
if (appElement) {
|
||||
appElement.innerHTML = ''; // Clear existing content
|
||||
appElement.appendChild(layout.render());
|
||||
|
||||
// Handle 'Account settings' link click from TopBar or ProfileWidget
|
||||
const accountSettingsLinks = document.querySelectorAll('[data-action="open-account-settings"]');
|
||||
accountSettingsLinks.forEach(link => {
|
||||
link.addEventListener('click', (event) => {
|
||||
event.preventDefault(); // Prevent default link behavior
|
||||
if (!accountSettingsModalInstance) {
|
||||
accountSettingsModalInstance = new Modal();
|
||||
const settingsWidget = new AccountSettingsWidget(() => {
|
||||
accountSettingsModalInstance?.hide(); // Callback to hide modal after settings update
|
||||
});
|
||||
accountSettingsModalInstance.setWidget(settingsWidget);
|
||||
}
|
||||
accountSettingsModalInstance.show();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
21
src/pages/RegisterPage.ts
Normal file
21
src/pages/RegisterPage.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// pages/RegisterPage.ts
|
||||
import { CenteredLayout } from '../layouts/CenteredLayout';
|
||||
import { RegisterWidget } from '../widgets/RegisterWidget';
|
||||
import { BackButtonWidget } from '../widgets/BackButtonWidget';
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
export const renderRegisterPage = () => {
|
||||
const layout = new CenteredLayout();
|
||||
const registerMessageWidget = new RegisterWidget("Please contact your department head to register.");
|
||||
const backButtonWidget = new BackButtonWidget('Back to Login', '#/login');
|
||||
|
||||
const container = createElement('div');
|
||||
container.classList.add('d-flex', 'flex-column', 'gap-3', 'p-4', 'rounded', 'shadow'); // Bootstrap flex and styling
|
||||
|
||||
container.appendChild(registerMessageWidget.render());
|
||||
container.appendChild(backButtonWidget.render());
|
||||
|
||||
layout.addContent(container);
|
||||
|
||||
document.querySelector<HTMLDivElement>('#app')?.appendChild(layout.render());
|
||||
};
|
||||
192
src/style.css
192
src/style.css
@ -1,96 +1,144 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
/* Global styles */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
.scrolling-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%; /* Ensure it covers full width */
|
||||
height: 100vh; /* Full viewport height */
|
||||
background-image: url('./assets/bg1.jpg');
|
||||
background-repeat: repeat-x;
|
||||
background-size: auto 100%; /* Maintain width and fill height */
|
||||
animation: scroll-bg 30s linear infinite;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
@keyframes scroll-bg {
|
||||
from {
|
||||
background-position: 0 0;
|
||||
}
|
||||
to {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.color-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(33, 37, 41, 0.925);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.widget {
|
||||
border: 1px solid rgb(0, 0, 0, 0.20);
|
||||
background-color: rgb(31, 35, 39, 0.9);
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.widget-header {
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.widget-body {
|
||||
/* Widget body styles */
|
||||
}
|
||||
|
||||
.icon-widget {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.vanilla:hover {
|
||||
filter: drop-shadow(0 0 2em #3178c6aa);
|
||||
/* Layout Specific Styles */
|
||||
.centered-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
.three-column-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
.split-column-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
/* Sidebar and content */
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
.split-column-layout.collapsed-sidebar {
|
||||
grid-template-columns: 80px 1fr;
|
||||
/* Collapsed sidebar width */
|
||||
}
|
||||
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
/* Semi-transparent overlay */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
/* Ensure modal is on top */
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
|
||||
width: 80%;
|
||||
/* Adjust as needed */
|
||||
max-width: 800px;
|
||||
/* Maximum width */
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
/* Responsive adjustments (example) */
|
||||
@media (max-width: 768px) {
|
||||
.three-column-layout {
|
||||
grid-template-columns: 1fr;
|
||||
/* Stack columns on smaller screens */
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
|
||||
.split-column-layout {
|
||||
grid-template-columns: 1fr;
|
||||
/* Stack sidebar and content */
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
|
||||
.split-column-layout.collapsed-sidebar {
|
||||
grid-template-columns: 1fr;
|
||||
/* Stack even if collapsed */
|
||||
}
|
||||
}
|
||||
32
src/utils/utils.ts
Normal file
32
src/utils/utils.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// utils.ts
|
||||
let isLoggedIn = false; // Global state for login status. In a real app, use more robust state management.
|
||||
let userToken: string | null = null; // Store user token
|
||||
|
||||
export const updateLoggedInState = (loggedIn: boolean, token: string | null = null) => {
|
||||
isLoggedIn = loggedIn;
|
||||
userToken = token;
|
||||
// You can add more logic here, like redirecting based on login state
|
||||
};
|
||||
|
||||
export const isLoggedInUser = () => {
|
||||
return isLoggedIn;
|
||||
};
|
||||
|
||||
export const getUserToken = () => {
|
||||
return userToken;
|
||||
};
|
||||
|
||||
// Function to generate a random 6-digit ID
|
||||
export const generateUserId = (): string => {
|
||||
return String(Math.floor(100000 + Math.random() * 900000));
|
||||
};
|
||||
|
||||
// Function to navigate to a different page (prototype routing)
|
||||
export const navigateTo = (path: string) => {
|
||||
window.location.hash = path;
|
||||
};
|
||||
|
||||
// Helper function to create HTML elements
|
||||
export const createElement = <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K] => {
|
||||
return document.createElement(tagName, options);
|
||||
};
|
||||
119
src/widgets/AccountSettingsWidget.ts
Normal file
119
src/widgets/AccountSettingsWidget.ts
Normal file
@ -0,0 +1,119 @@
|
||||
// widgets/AccountSettingsWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse } from '../api/api';
|
||||
|
||||
export class AccountSettingsWidget extends Widget {
|
||||
private onSettingsUpdated: () => void;
|
||||
|
||||
constructor(onSettingsUpdated: () => void) {
|
||||
super();
|
||||
this.onSettingsUpdated = onSettingsUpdated;
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Account Settings';
|
||||
this.container.appendChild(header);
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body');
|
||||
|
||||
const form = createElement('form');
|
||||
|
||||
// Profile Picture Section
|
||||
const profilePictureSection = createElement('div');
|
||||
profilePictureSection.classList.add('mb-3');
|
||||
const profilePictureLabel = createElement('label') as HTMLLabelElement; // Cast first
|
||||
profilePictureLabel.classList.add('form-label');
|
||||
profilePictureLabel.htmlFor = 'profilePicture'; // Set htmlFor as property
|
||||
profilePictureLabel.textContent = 'Profile Picture';
|
||||
const profilePictureInput = createElement('input') as HTMLInputElement;
|
||||
profilePictureInput.type = 'file';
|
||||
profilePictureInput.classList.add('form-control');
|
||||
profilePictureInput.id = 'profilePicture';
|
||||
profilePictureSection.appendChild(profilePictureLabel);
|
||||
profilePictureSection.appendChild(profilePictureInput);
|
||||
form.appendChild(profilePictureSection);
|
||||
|
||||
// Student Details Section (Read-only in prototype)
|
||||
const studentDetailsSection = createElement('div');
|
||||
studentDetailsSection.classList.add('mb-3');
|
||||
studentDetailsSection.innerHTML = `
|
||||
<div class="mb-2"><strong>Student Details</strong> <span class="text-muted">(Contact department head for corrections)</span></div>
|
||||
<div class="mb-2">
|
||||
<label for="fullName" class="form-label">Full Name</label>
|
||||
<input type="text" class="form-control" id="fullName" value="John Doe" readonly disabled>
|
||||
</div>
|
||||
<div>
|
||||
<label for="birthdate" class="form-label">Birthdate</label>
|
||||
<input type="text" class="form-control" id="birthdate" value="2000-01-01" readonly disabled>
|
||||
</div>
|
||||
`;
|
||||
form.appendChild(studentDetailsSection);
|
||||
|
||||
// Password Change Section
|
||||
const passwordChangeSection = createElement('div');
|
||||
passwordChangeSection.classList.add('mb-3');
|
||||
passwordChangeSection.innerHTML = `
|
||||
<div class="mb-2"><strong>Change Password</strong></div>
|
||||
<div class="mb-2">
|
||||
<label for="newPassword" class="form-label">New Password</label>
|
||||
<input type="password" class="form-control" id="newPassword" placeholder="New Password">
|
||||
</div>
|
||||
<div>
|
||||
<label for="confirmPassword" class="form-label">Confirm New Password</label>
|
||||
<input type="password" class="form-control" id="confirmPassword" placeholder="Confirm New Password">
|
||||
</div>
|
||||
`;
|
||||
form.appendChild(passwordChangeSection);
|
||||
|
||||
const saveButton = createElement('button');
|
||||
saveButton.type = 'submit';
|
||||
saveButton.classList.add('btn', 'btn-primary');
|
||||
saveButton.textContent = 'Save Changes';
|
||||
form.appendChild(saveButton);
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
// In a real application, you would collect and validate form data,
|
||||
// then send it to the backend API for updateAccountSettings.
|
||||
// For this prototype, we'll just simulate a successful update.
|
||||
|
||||
const newPassword = (document.getElementById('newPassword') as HTMLInputElement).value;
|
||||
const confirmPassword = (document.getElementById('confirmPassword') as HTMLInputElement).value;
|
||||
|
||||
if (newPassword && newPassword !== confirmPassword) {
|
||||
alert('New password and confirm password do not match.');
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsData = {
|
||||
// profilePicture: ..., // Handle profile picture upload in real app
|
||||
password: newPassword || undefined, // Send password only if changed
|
||||
// ... other settings to update
|
||||
};
|
||||
|
||||
try {
|
||||
const response: ApiResponse<void> = await globalAPI.updateAccountSettings('mockUserId', settingsData); // Typed response
|
||||
if (response.success) {
|
||||
alert('Account settings updated successfully!');
|
||||
this.onSettingsUpdated(); // Call the callback to hide the modal
|
||||
} else {
|
||||
alert('Failed to update account settings.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating account settings:', error);
|
||||
alert('Error occurred while updating account settings.');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
widgetBody.appendChild(form);
|
||||
this.container.appendChild(widgetBody);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
28
src/widgets/BackButtonWidget.ts
Normal file
28
src/widgets/BackButtonWidget.ts
Normal file
@ -0,0 +1,28 @@
|
||||
// widgets/BackButtonWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement, navigateTo } from '../utils/utils';
|
||||
|
||||
export class BackButtonWidget extends Widget {
|
||||
private buttonText: string;
|
||||
private navigatePath: string;
|
||||
|
||||
constructor(buttonText: string, navigatePath: string) {
|
||||
super();
|
||||
this.buttonText = buttonText;
|
||||
this.navigatePath = navigatePath;
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = ''; // Clear previous content
|
||||
|
||||
const button = createElement('button');
|
||||
button.classList.add('btn', 'btn-secondary');
|
||||
button.textContent = this.buttonText;
|
||||
button.addEventListener('click', () => {
|
||||
navigateTo(this.navigatePath);
|
||||
});
|
||||
|
||||
this.container.appendChild(button);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
78
src/widgets/LoginWidget.ts
Normal file
78
src/widgets/LoginWidget.ts
Normal file
@ -0,0 +1,78 @@
|
||||
// widgets/LoginWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement, navigateTo } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, LoginResponseData } from '../api/api';
|
||||
|
||||
export class LoginWidget extends Widget {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
const header = createElement('h2');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Login';
|
||||
|
||||
const form = createElement('form');
|
||||
const userIdInputGroup = createElement('div');
|
||||
userIdInputGroup.classList.add('mb-3');
|
||||
const userIdLabel = createElement('label') as HTMLLabelElement; // Cast first
|
||||
userIdLabel.classList.add('form-label');
|
||||
userIdLabel.htmlFor = 'userId'; // Set htmlFor as property
|
||||
userIdLabel.textContent = 'Student ID';
|
||||
const userIdInput = createElement('input') as HTMLInputElement;
|
||||
userIdInput.type = 'text';
|
||||
userIdInput.classList.add('form-control');
|
||||
userIdInput.id = 'userId';
|
||||
userIdInput.placeholder = '123456';
|
||||
userIdInputGroup.appendChild(userIdLabel);
|
||||
userIdInputGroup.appendChild(userIdInput);
|
||||
|
||||
const passwordInputGroup = createElement('div');
|
||||
passwordInputGroup.classList.add('mb-3');
|
||||
const passwordLabel = createElement('label') as HTMLLabelElement; // Cast first
|
||||
passwordLabel.classList.add('form-label');
|
||||
passwordLabel.htmlFor = 'password'; // Set htmlFor as property
|
||||
passwordLabel.textContent = 'Password';
|
||||
const passwordInput = createElement('input') as HTMLInputElement;
|
||||
passwordInput.type = 'password';
|
||||
passwordInput.classList.add('form-control');
|
||||
passwordInput.id = 'password';
|
||||
passwordInput.placeholder = 'Password';
|
||||
passwordInputGroup.appendChild(passwordLabel);
|
||||
passwordInputGroup.appendChild(passwordInput);
|
||||
|
||||
const loginButton = createElement('button');
|
||||
loginButton.type = 'submit';
|
||||
loginButton.classList.add('btn', 'btn-primary');
|
||||
loginButton.textContent = 'Login';
|
||||
|
||||
form.appendChild(userIdInputGroup);
|
||||
form.appendChild(passwordInputGroup);
|
||||
form.appendChild(loginButton);
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const userId = userIdInput.value;
|
||||
const password = passwordInput.value;
|
||||
|
||||
try {
|
||||
const response: ApiResponse<LoginResponseData> = await globalAPI.login({ userId, password });
|
||||
if (response.success) {
|
||||
navigateTo('/dashboard');
|
||||
} else {
|
||||
alert('Login failed: ' + response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
alert('Login error occurred.');
|
||||
}
|
||||
});
|
||||
|
||||
this.container.appendChild(header);
|
||||
this.container.appendChild(form);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
53
src/widgets/PostFeedWidget.ts
Normal file
53
src/widgets/PostFeedWidget.ts
Normal file
@ -0,0 +1,53 @@
|
||||
// widgets/PostFeedWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, ProfileResponseData } from '../api/api'; // Import API and types (now exported)
|
||||
|
||||
export class PostFeedWidget extends Widget {
|
||||
private posts: { id: number; content: string }[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fetchPosts();
|
||||
}
|
||||
|
||||
async fetchPosts() {
|
||||
try {
|
||||
const response: ApiResponse<ProfileResponseData> = await globalAPI.getProfile('mockUserId');
|
||||
if (response.success && response.data && response.data.posts) {
|
||||
this.posts = response.data.posts;
|
||||
this.render();
|
||||
} else {
|
||||
console.error("Failed to fetch posts");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching posts:", error);
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Post Feed';
|
||||
this.container.appendChild(header);
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body');
|
||||
|
||||
if (this.posts.length === 0) {
|
||||
widgetBody.textContent = 'No posts yet.';
|
||||
} else {
|
||||
this.posts.forEach(post => {
|
||||
const postDiv = createElement('div');
|
||||
postDiv.classList.add('mb-2', 'p-2', 'border', 'rounded');
|
||||
postDiv.textContent = post.content;
|
||||
widgetBody.appendChild(postDiv);
|
||||
});
|
||||
}
|
||||
|
||||
this.container.appendChild(widgetBody);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
110
src/widgets/ProfileWidget.ts
Normal file
110
src/widgets/ProfileWidget.ts
Normal file
@ -0,0 +1,110 @@
|
||||
// widgets/ProfileWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, ProfileResponseData } from '../api/api';
|
||||
|
||||
export class ProfileWidget extends Widget {
|
||||
private profileData: ProfileResponseData | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fetchProfileData();
|
||||
}
|
||||
|
||||
async fetchProfileData() {
|
||||
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 = '';
|
||||
|
||||
if (!this.profileData) { // Null check here
|
||||
this.container.textContent = 'Loading profile...';
|
||||
return this.container;
|
||||
}
|
||||
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header', 'd-flex', 'justify-content-between', 'align-items-center');
|
||||
const headerText = createElement('div');
|
||||
headerText.classList.add('widget-title');
|
||||
headerText.textContent = 'Profile Information';
|
||||
header.appendChild(headerText);
|
||||
|
||||
const kebabMenuButton = createElement('button');
|
||||
kebabMenuButton.classList.add('btn', 'btn-outline-secondary', 'btn-sm', 'dropdown-toggle');
|
||||
kebabMenuButton.type = 'button';
|
||||
kebabMenuButton.id = 'profileKebabMenu';
|
||||
kebabMenuButton.setAttribute('data-bs-toggle', 'dropdown');
|
||||
kebabMenuButton.setAttribute('aria-expanded', 'false');
|
||||
kebabMenuButton.innerHTML = '⋮'; // Kebab menu icon (vertical ellipsis)
|
||||
header.appendChild(kebabMenuButton);
|
||||
|
||||
const dropdownMenu = createElement('ul');
|
||||
dropdownMenu.classList.add('dropdown-menu', 'dropdown-menu-end');
|
||||
dropdownMenu.setAttribute('aria-labelledby', 'profileKebabMenu');
|
||||
|
||||
const accountSettingsMenuItem = createElement('li');
|
||||
const accountSettingsLink = createElement('a');
|
||||
accountSettingsLink.classList.add('dropdown-item');
|
||||
accountSettingsLink.href = '#/profile';
|
||||
accountSettingsLink.setAttribute('data-action', 'open-account-settings');
|
||||
accountSettingsLink.textContent = 'Account settings';
|
||||
accountSettingsMenuItem.appendChild(accountSettingsLink);
|
||||
dropdownMenu.appendChild(accountSettingsMenuItem);
|
||||
header.appendChild(dropdownMenu);
|
||||
|
||||
|
||||
this.container.appendChild(header);
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body', 'row', 'g-3');
|
||||
|
||||
const profileImageCol = createElement('div');
|
||||
profileImageCol.classList.add('col-md-4');
|
||||
const profileImage = createElement('img');
|
||||
if (this.profileData.profilePicture) { // Null check before accessing properties
|
||||
profileImage.src = this.profileData.profilePicture;
|
||||
} else {
|
||||
profileImage.src = 'src/assets/vite.svg'; // Default image if no profile picture
|
||||
}
|
||||
profileImage.alt = 'Profile Picture';
|
||||
profileImage.classList.add('img-fluid', 'rounded-circle');
|
||||
profileImageCol.appendChild(profileImage);
|
||||
widgetBody.appendChild(profileImageCol);
|
||||
|
||||
const detailsCol = createElement('div');
|
||||
detailsCol.classList.add('col-md-8');
|
||||
const fullNamePara = createElement('p');
|
||||
if (this.profileData.fullName) { // Null check before accessing properties
|
||||
fullNamePara.innerHTML = `${this.profileData.fullName}`;
|
||||
} else {
|
||||
fullNamePara.innerHTML = `<small>Full Name:</small><br>N/A`; // Or some default text
|
||||
}
|
||||
const schoolIdPara = createElement('p');
|
||||
if (this.profileData.schoolId) { // Null check before accessing properties
|
||||
schoolIdPara.innerHTML = `<small><strong>School ID:</small></strong><br>${this.profileData.schoolId}`;
|
||||
} else {
|
||||
schoolIdPara.innerHTML = `<small><strong>School ID:</strong></small><br>N/A`;
|
||||
}
|
||||
const birthdatePara = createElement('p');
|
||||
birthdatePara.innerHTML = `<small><strong>Birthdate:</strong></small><br><span class="text-muted">(Hidden from others)</span>`;
|
||||
detailsCol.appendChild(fullNamePara);
|
||||
detailsCol.appendChild(schoolIdPara);
|
||||
detailsCol.appendChild(birthdatePara);
|
||||
widgetBody.appendChild(detailsCol);
|
||||
|
||||
this.container.appendChild(widgetBody);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
37
src/widgets/RegisterWidget.ts
Normal file
37
src/widgets/RegisterWidget.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// widgets/RegisterMessageWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement, navigateTo } from '../utils/utils';
|
||||
|
||||
export class RegisterWidget extends Widget {
|
||||
private message: string;
|
||||
|
||||
constructor(message?: string) {
|
||||
super();
|
||||
this.message = message || "For registration, please contact your department head for further instructions.";
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = ''; // Clear previous content
|
||||
|
||||
const header = createElement('h2');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Register';
|
||||
|
||||
const messageParagraph = createElement('p');
|
||||
messageParagraph.classList.add('widget-body');
|
||||
messageParagraph.textContent = this.message;
|
||||
|
||||
const button = createElement('button');
|
||||
button.classList.add('btn', 'btn-secondary');
|
||||
button.textContent = 'Register';
|
||||
button.disabled = true;
|
||||
button.addEventListener('click', () => {
|
||||
navigateTo('/register');
|
||||
});
|
||||
|
||||
this.container.appendChild(header);
|
||||
this.container.appendChild(messageParagraph);
|
||||
this.container.appendChild(button);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
99
src/widgets/StudentFinancialWidget.ts
Normal file
99
src/widgets/StudentFinancialWidget.ts
Normal file
@ -0,0 +1,99 @@
|
||||
// widgets/StudentFinancialWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, StudentFinancialData } from '../api/api'; // Import API and types
|
||||
|
||||
export class StudentFinancialWidget extends Widget {
|
||||
private studentId: string;
|
||||
private financialData: StudentFinancialData | null = null;
|
||||
private isLoading: boolean = true;
|
||||
private hasError: boolean = false;
|
||||
|
||||
constructor(studentId: string) {
|
||||
super();
|
||||
this.studentId = studentId;
|
||||
this.fetchFinancialData();
|
||||
}
|
||||
|
||||
async fetchFinancialData() {
|
||||
this.isLoading = true;
|
||||
this.hasError = false;
|
||||
this.render(); // Re-render to show loading state
|
||||
|
||||
try {
|
||||
const response: ApiResponse<StudentFinancialData> = await globalAPI.getStudentFinancialData(this.studentId);
|
||||
if (response.success && response.data) {
|
||||
this.financialData = response.data;
|
||||
} else {
|
||||
this.hasError = true;
|
||||
console.error("Failed to fetch student financial data:", response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
this.hasError = true;
|
||||
console.error("Error fetching student financial data:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
this.render(); // Re-render with fetched data or error state
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = ''; // Clear previous content
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body');
|
||||
|
||||
if (this.isLoading) {
|
||||
widgetBody.textContent = 'Loading financial data...';
|
||||
} else if (this.hasError) {
|
||||
widgetBody.textContent = 'Failed to load financial data.';
|
||||
} else if (this.financialData) {
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Payment Information';
|
||||
this.container.appendChild(header);
|
||||
|
||||
this.container.appendChild(header);
|
||||
|
||||
widgetBody.innerHTML = `
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Tuition Fee:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.tuitionFee}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Miscellaneous:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.miscellaneousFee}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Lab Fee:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.labFee}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Current:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.currentAccount}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Down:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.downPayment}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Midterms:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.midtermPayment}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Prefinals:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.prefinalPayment}</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">Finals:</div>
|
||||
<div class="col-sm-6">₱ ${this.financialData.finalPayment}</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
widgetBody.textContent = 'No financial data available.'; // Should not usually reach here if no error but no data
|
||||
}
|
||||
|
||||
this.container.appendChild(widgetBody);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
191
src/widgets/StudentTableWidget.ts
Normal file
191
src/widgets/StudentTableWidget.ts
Normal file
@ -0,0 +1,191 @@
|
||||
// widgets/StudentTableWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement, navigateTo } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, StudentListData } from '../api/api';
|
||||
|
||||
export class StudentTableWidget extends Widget {
|
||||
private students: StudentListData[] = [];
|
||||
private yearFilter: string = 'all';
|
||||
private courseFilter: string = 'all';
|
||||
private availableYears: string[] = ['all', '1', '2', '3', '4'];
|
||||
private availableCourses: string[] = ['all', 'Math', 'Science', 'History', 'English'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fetchStudentData();
|
||||
}
|
||||
|
||||
async fetchStudentData() {
|
||||
try {
|
||||
const response: ApiResponse<StudentListData[]> = await globalAPI.getStudentList();
|
||||
if (response.success && response.data) {
|
||||
this.students = response.data;
|
||||
this.render();
|
||||
} else {
|
||||
console.error("Failed to fetch student list");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching student list:", error);
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Manage Students';
|
||||
this.container.appendChild(header);
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body');
|
||||
|
||||
// Filters
|
||||
const filtersRow = createElement('div');
|
||||
filtersRow.classList.add('row', 'mb-3', 'g-3', 'align-items-center');
|
||||
|
||||
// Year Level Filter
|
||||
const yearFilterCol = createElement('div');
|
||||
yearFilterCol.classList.add('col-auto');
|
||||
const yearFilterLabel = createElement('label') as HTMLLabelElement; // Cast first
|
||||
yearFilterLabel.classList.add('col-form-label', 'me-2');
|
||||
yearFilterLabel.htmlFor = 'yearLevelFilter'; // Set htmlFor as property
|
||||
yearFilterLabel.textContent = 'Year Level:';
|
||||
const yearFilterSelect = createElement('select') as HTMLSelectElement;
|
||||
yearFilterSelect.classList.add('form-select');
|
||||
yearFilterSelect.id = 'yearLevelFilter';
|
||||
this.availableYears.forEach(year => {
|
||||
const option = createElement('option');
|
||||
option.value = year;
|
||||
option.textContent = year === 'all' ? 'All Years' : `Year ${year}`;
|
||||
yearFilterSelect.appendChild(option);
|
||||
});
|
||||
yearFilterSelect.value = this.yearFilter;
|
||||
yearFilterSelect.addEventListener('change', (e) => {
|
||||
this.yearFilter = (e.target as HTMLSelectElement).value;
|
||||
this.renderTable(widgetBody);
|
||||
});
|
||||
yearFilterCol.appendChild(yearFilterLabel);
|
||||
yearFilterCol.appendChild(yearFilterSelect);
|
||||
filtersRow.appendChild(yearFilterCol);
|
||||
|
||||
// Course Filter
|
||||
const courseFilterCol = createElement('div');
|
||||
courseFilterCol.classList.add('col-auto');
|
||||
const courseFilterLabel = createElement('label') as HTMLLabelElement; // Cast first
|
||||
courseFilterLabel.classList.add('col-form-label', 'me-2');
|
||||
courseFilterLabel.htmlFor = 'courseFilter'; // Set htmlFor as property
|
||||
courseFilterLabel.textContent = 'Course:';
|
||||
const courseFilterSelect = createElement('select') as HTMLSelectElement;
|
||||
courseFilterSelect.classList.add('form-select');
|
||||
courseFilterSelect.id = 'courseFilter';
|
||||
this.availableCourses.forEach(course => {
|
||||
const option = createElement('option');
|
||||
option.value = course;
|
||||
option.textContent = course === 'all' ? 'All Courses' : course;
|
||||
courseFilterSelect.appendChild(option);
|
||||
});
|
||||
courseFilterSelect.value = this.courseFilter;
|
||||
courseFilterSelect.addEventListener('change', (e) => {
|
||||
this.courseFilter = (e.target as HTMLSelectElement).value;
|
||||
this.renderTable(widgetBody);
|
||||
});
|
||||
courseFilterCol.appendChild(courseFilterLabel);
|
||||
courseFilterCol.appendChild(courseFilterSelect);
|
||||
filtersRow.appendChild(courseFilterCol);
|
||||
|
||||
widgetBody.appendChild(filtersRow);
|
||||
|
||||
// Table Container
|
||||
const tableContainer = createElement('div');
|
||||
widgetBody.appendChild(tableContainer);
|
||||
this.renderTable(tableContainer);
|
||||
|
||||
// "Add" button and Batch Add
|
||||
const addButtonRow = createElement('div');
|
||||
addButtonRow.classList.add('row', 'mt-3', 'justify-content-between', 'align-items-center');
|
||||
const addButtonCol = createElement('div');
|
||||
addButtonCol.classList.add('col-auto');
|
||||
const addButton = createElement('button');
|
||||
addButton.classList.add('btn', 'btn-success', 'btn-sm', 'me-2');
|
||||
addButton.textContent = 'Add Student';
|
||||
addButtonCol.appendChild(addButton);
|
||||
addButtonRow.appendChild(addButtonCol);
|
||||
|
||||
const batchAddCol = createElement('div');
|
||||
batchAddCol.classList.add('col-auto');
|
||||
const batchAddLink = createElement('a');
|
||||
batchAddLink.href = '#/batch-add-students';
|
||||
batchAddLink.textContent = 'Batch Add Students';
|
||||
batchAddCol.appendChild(batchAddLink);
|
||||
addButtonRow.appendChild(batchAddCol);
|
||||
|
||||
widgetBody.appendChild(addButtonRow);
|
||||
|
||||
this.container.appendChild(widgetBody);
|
||||
return this.container;
|
||||
}
|
||||
|
||||
private renderTable(container: HTMLElement) {
|
||||
container.innerHTML = '';
|
||||
|
||||
const table = createElement('table');
|
||||
table.classList.add('table', 'table-striped', 'table-bordered');
|
||||
const thead = createElement('thead');
|
||||
thead.innerHTML = `
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Year Level</th>
|
||||
<th>Courses</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
`;
|
||||
table.appendChild(thead);
|
||||
const tbody = createElement('tbody');
|
||||
|
||||
const filteredStudents = this.students.filter(student => {
|
||||
const yearMatch = this.yearFilter === 'all' || student.yearLevel === this.yearFilter;
|
||||
const courseMatch = this.courseFilter === 'all' || student.courses.includes(this.courseFilter);
|
||||
return yearMatch && courseMatch;
|
||||
});
|
||||
|
||||
filteredStudents.forEach(student => {
|
||||
const row = createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${student.id}</td>
|
||||
<td>${student.name}</td>
|
||||
<td>${student.yearLevel}</td>
|
||||
<td>${student.courses.join(', ')}</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm view-profile-btn" data-student-id="${student.id}">View Profile</button>
|
||||
<button class="btn btn-secondary btn-sm enroll-btn" data-student-id="${student.id}">Enroll</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
table.appendChild(tbody);
|
||||
container.appendChild(table);
|
||||
|
||||
this.attachTableEventListeners(table);
|
||||
}
|
||||
|
||||
private attachTableEventListeners(table: HTMLTableElement) {
|
||||
table.querySelectorAll('.view-profile-btn').forEach(button => {
|
||||
button.addEventListener('click', (event) => {
|
||||
const studentId = (event.target as HTMLElement).dataset.studentId;
|
||||
if (studentId) {
|
||||
navigateTo(`/profile?studentId=${studentId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
table.querySelectorAll('.enroll-btn').forEach(button => {
|
||||
button.addEventListener('click', (event) => {
|
||||
const studentId = (event.target as HTMLElement).dataset.studentId;
|
||||
if (studentId) {
|
||||
alert(`Enroll functionality for student ID ${studentId} - Not fully implemented in prototype.`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
54
src/widgets/StudentsWidget.ts
Normal file
54
src/widgets/StudentsWidget.ts
Normal file
@ -0,0 +1,54 @@
|
||||
// widgets/StudentsWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement, navigateTo } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, AdminDashboardData } from '../api/api'; // Import API and types (now exported)
|
||||
|
||||
export class StudentsWidget extends Widget {
|
||||
private studentCount: number = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fetchStudentCount();
|
||||
}
|
||||
|
||||
async fetchStudentCount() {
|
||||
try {
|
||||
const response: ApiResponse<AdminDashboardData> = await globalAPI.getAdminDashboardData();
|
||||
if (response.success && response.data) {
|
||||
this.studentCount = response.data.studentsCount || 0;
|
||||
this.render();
|
||||
} else {
|
||||
console.error("Failed to fetch admin dashboard data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching admin dashboard data:", error);
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Students';
|
||||
this.container.appendChild(header);
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body', 'text-center');
|
||||
|
||||
const countDisplay = createElement('h3');
|
||||
countDisplay.textContent = String(this.studentCount);
|
||||
widgetBody.appendChild(countDisplay);
|
||||
|
||||
const manageButton = createElement('button');
|
||||
manageButton.classList.add('btn', 'btn-primary', 'btn-sm');
|
||||
manageButton.textContent = 'Manage Students';
|
||||
manageButton.addEventListener('click', () => {
|
||||
navigateTo('/manage-students');
|
||||
});
|
||||
widgetBody.appendChild(manageButton);
|
||||
|
||||
this.container.appendChild(widgetBody);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
52
src/widgets/TeachersWidget.ts
Normal file
52
src/widgets/TeachersWidget.ts
Normal file
@ -0,0 +1,52 @@
|
||||
// widgets/TeachersWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement } from '../utils/utils';
|
||||
import { globalAPI, ApiResponse, AdminDashboardData } from '../api/api'; // Import API and types (now exported)
|
||||
|
||||
export class TeachersWidget extends Widget {
|
||||
private teacherCount: number = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fetchTeacherCount();
|
||||
}
|
||||
|
||||
async fetchTeacherCount() {
|
||||
try {
|
||||
const response: ApiResponse<AdminDashboardData> = await globalAPI.getAdminDashboardData();
|
||||
if (response.success && response.data) {
|
||||
this.teacherCount = response.data.teachersCount || 0;
|
||||
this.render();
|
||||
} else {
|
||||
console.error("Failed to fetch admin dashboard data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching admin dashboard data:", error);
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Teachers';
|
||||
this.container.appendChild(header);
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body', 'text-center');
|
||||
|
||||
const countDisplay = createElement('h3');
|
||||
countDisplay.textContent = String(this.teacherCount);
|
||||
widgetBody.appendChild(countDisplay);
|
||||
|
||||
// In this prototype, teacher management page is not specified, so button is disabled or can link to a placeholder page.
|
||||
const manageButton = createElement('button');
|
||||
manageButton.classList.add('btn', 'btn-secondary', 'btn-sm', 'disabled'); // Disabled for prototype
|
||||
manageButton.textContent = 'Manage Teachers (Coming Soon)';
|
||||
widgetBody.appendChild(manageButton);
|
||||
|
||||
this.container.appendChild(widgetBody);
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
38
src/widgets/UnderConstructionWidget.ts
Normal file
38
src/widgets/UnderConstructionWidget.ts
Normal file
@ -0,0 +1,38 @@
|
||||
// widgets/UnderConstructionWidget.ts
|
||||
import { Widget } from '../components/Widget';
|
||||
import { createElement } from '../utils/utils';
|
||||
|
||||
export class UnderConstructionWidget extends Widget {
|
||||
private message: string;
|
||||
|
||||
constructor(message: string) {
|
||||
super();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = ''; // Clear previous content
|
||||
|
||||
const header = createElement('div');
|
||||
header.classList.add('widget-header', 'text-center');
|
||||
header.textContent = 'Under Construction';
|
||||
this.container.appendChild(header);
|
||||
|
||||
const widgetBody = createElement('div');
|
||||
widgetBody.classList.add('widget-body', 'text-center');
|
||||
widgetBody.textContent = this.message;
|
||||
this.container.appendChild(widgetBody);
|
||||
|
||||
const icon = createElement('i');
|
||||
icon.classList.add('bi', 'bi-tools', 'd-block', 'mx-auto', 'my-3'); // Bootstrap Icons class, needs Bootstrap Icons CSS if you want to use icons.
|
||||
icon.style.fontSize = '2em'; // Example icon style, can be customized.
|
||||
// Note: Bootstrap Icons would need to be included in index.html if used.
|
||||
// For prototype simplicity, we'll skip including Bootstrap Icons CSS.
|
||||
// If you want to add Bootstrap Icons, include this in index.html <head>:
|
||||
// <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
// widgetBody.appendChild(icon); // Uncomment if you decide to include Bootstrap Icons.
|
||||
|
||||
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user