Compare commits
30 Commits
99a2bc20de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fed37c189 | |||
| d5d11f7d70 | |||
| 13fd32d038 | |||
| 160a92eceb | |||
| a5e8ff49d1 | |||
| 00a7e8c488 | |||
| 1445ec83db | |||
| 5505adc040 | |||
| 673f8bb24f | |||
| 28435c2f3b | |||
| 6ffdbeadb5 | |||
| f816d3eef4 | |||
| 64f30aa363 | |||
| 2a8d75294f | |||
| 16a706e366 | |||
| ff9fdc7982 | |||
| 4923ccf30c | |||
| ae285ad84e | |||
| 8178806075 | |||
| 2b059d089a | |||
| 3522448a7f | |||
| cb82f80e8e | |||
| d4ae3569ed | |||
| e030c11894 | |||
| 6c23b77056 | |||
| 58f7f09fbd | |||
| 31ecf0d022 | |||
| 043c4d5aa1 | |||
| 35378d17d0 | |||
| 2eff273486 |
143
README.md
143
README.md
@@ -1,38 +1,40 @@
|
||||
# Learner Management System Frontend
|
||||
|
||||
This is the frontend for a modular and responsive Learner Management System (LMS), built with Vanilla Typescript, Vite, and Bootstrap v5.3. It utilizes a widget-based system and a global API structure designed to interact with a backend server.
|
||||
This is the frontend for a modular and responsive Learner Management System (LMS), built with Vanilla Typescript, Vite, and Bootstrap v5.3. It utilizes a widget-based system and interacts with a dedicated backend server via a RESTful API using PAKE SRP for authentication.
|
||||
|
||||
## Features
|
||||
|
||||
* **Modular Architecture:** Organized into modules for easy maintainability and scalability.
|
||||
* **Responsive Design:** Built with Bootstrap 5.3 to ensure responsiveness across various devices.
|
||||
* **Widget System:** Extensible widget system with two size types (default full-width and icon-type for sidebar).
|
||||
* **Global API System:** Clearly defined API structure for communication with a backend (currently mocked).
|
||||
* **Layout Modules:** Pre-built layouts (Centered, Three-Column, Split-Column) for different page structures.
|
||||
* **Page Components:** Includes pre-built pages for Login, Register, Dashboard, Settings, Admin, Profile, and Manage Students.
|
||||
* **Topbar Module:** Navigation and user profile dropdown.
|
||||
* **Modal Module:** (To be implemented) For reusable modal components.
|
||||
* **Basic Authentication Flow:** Login and logout functionality with token-based session management (localStorage).
|
||||
* **Modular Architecture:** Organized into distinct modules (widgets, layouts, pages, API) for maintainability and scalability.
|
||||
* **Responsive Design:** Uses Bootstrap 5.3 for a consistent experience across desktops, tablets, and mobiles.
|
||||
* **Widget System:** Extensible widgets with `default` (full-width) and `icon` (sidebar) sizes.
|
||||
* **API Integration:** Connects to a live backend API (default: `http://localhost:8080/api`) for data fetching and actions.
|
||||
* **Secure Authentication:** Implements Password-Authenticated Key Exchange (PAKE SRP) for secure login via the `thin-srp` library.
|
||||
* **Token-Based Sessions:** Manages user sessions using tokens obtained from the backend upon successful login (stored in localStorage by default).
|
||||
* **Layout Modules:** Provides pre-built layouts: `CenteredLayout`, `ThreeColumnLayout`, `SplitColumnLayout`.
|
||||
* **Core Pages:** Includes pages for Login, Register (info only), Dashboard, Profile, Account Settings (Modal), Admin Dashboard, Manage Students.
|
||||
* **Topbar Module:** Features main navigation and a user profile dropdown with account actions.
|
||||
* **Modal Module:** Provides a reusable container for modal dialogs (e.g., Account Settings, notifications).
|
||||
|
||||
## Technologies Used
|
||||
|
||||
* **Vanilla Typescript:** For a type-safe and maintainable codebase.
|
||||
* **Vite:** For fast and efficient development and build process.
|
||||
* **Bootstrap v5.3:** For responsive layout and styling.
|
||||
* **pnpm:** Package manager for efficient dependency management.
|
||||
* **argon2-browser:** (For demonstration purposes - **Backend Hashing Recommended**) For frontend password hashing demonstration (Argon2id). **Important:** In a production environment, password hashing should be performed on the backend for security reasons.
|
||||
* **Vanilla Typescript:** For a robust, type-safe, and maintainable codebase without framework overhead.
|
||||
* **Vite:** For an extremely fast development server and optimized build process.
|
||||
* **Bootstrap v5.3:** For responsive layout, styling components, and utility classes.
|
||||
* **pnpm:** Efficient package manager for dependency management.
|
||||
* **thin-srp:** JavaScript client library for Secure Remote Password (SRP) protocol implementation.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* **Node.js** (>= 18 recommended)
|
||||
* **pnpm** (Install globally: `npm install -g pnpm`)
|
||||
* **Running Backend:** The LMS backend service must be running (see backend README) and accessible (defaults to `http://localhost:8080`).
|
||||
|
||||
## Installation and Setup
|
||||
|
||||
1. **Clone the repository:**
|
||||
|
||||
```bash
|
||||
git clone <repository_url>
|
||||
git clone <your-frontend-repository-url>
|
||||
cd lms-frontend
|
||||
```
|
||||
|
||||
@@ -42,85 +44,86 @@ This is the frontend for a modular and responsive Learner Management System (LMS
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. **Start the development server:**
|
||||
3. **Configure API Base URL (Optional):**
|
||||
* If your backend runs on a different address or port, update the `API_BASE_URL` constant in `src/api/api.ts`.
|
||||
|
||||
4. **Start the development server:**
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
This will start the Vite development server. Open your browser and navigate to the address provided in the console (usually `http://localhost:5173/`).
|
||||
This will start the Vite development server. Open your browser and navigate to the address provided (usually `http://localhost:5173`).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```bash
|
||||
lms-frontend/
|
||||
├── index.html # Main HTML entry point
|
||||
├── pnpm-lock.yaml # pnpm lock file for dependency management
|
||||
├── pnpm-workspace.yaml # pnpm workspace configuration
|
||||
├── public/ # Public assets (images, etc.)
|
||||
├── package.json # Project dependencies and scripts
|
||||
├── pnpm-lock.yaml # pnpm lock file
|
||||
├── public/ # Static assets served directly
|
||||
├── README.md # This README file
|
||||
├── src/ # Source code directory
|
||||
│ ├── api/ # API interaction functions (mocked in api.ts)
|
||||
│ ├── assets/ # Static assets (images, icons)
|
||||
│ ├── components/ # Reusable components
|
||||
│ │ ├── layouts/ # Page layout components (CenteredLayout, ThreeColumnLayout, SplitColumnLayout)
|
||||
│ │ ├── modules/ # Modules (TopbarModule, ModalModule - to be implemented)
|
||||
│ │ ├── widgets/ # Widget components (LoginWidget, ButtonWidget, etc.)
|
||||
│ ├── main.ts # Main entry point for the application
|
||||
│ ├── pages/ # Page components (LoginPage, DashboardPage, etc.)
|
||||
│ ├── styles/ # Global styles and Bootstrap import (index.css)
|
||||
│ ├── types/ # Typescript interfaces (Widget.ts, User.ts)
|
||||
│ ├── utils/ # Utility functions (api.ts, auth.ts)
|
||||
│ ├── vite-env.d.ts # Vite environment declaration
|
||||
├── tsconfig.json # Typescript configuration
|
||||
├── vite.config.ts # Vite configuration
|
||||
│ ├── api/
|
||||
│ │ └── api.ts # Functions for interacting with the backend API (SRP, data fetching)
|
||||
│ ├── assets/ # Static assets processed by Vite (images, icons)
|
||||
│ ├── components/
|
||||
│ │ ├── layouts/ # Page layout components
|
||||
│ │ ├── modules/ # Larger UI modules (TopbarModule, ModalModule)
|
||||
│ │ └── widgets/ # Reusable UI widgets
|
||||
│ ├── main.ts # Application entry point, initializes router/app state
|
||||
│ ├── pages/ # Page-level components/logic
|
||||
│ ├── styles/
|
||||
│ │ └── index.css # Global styles, Bootstrap import
|
||||
│ ├── types/ # TypeScript interfaces and type definitions
|
||||
│ ├── utils/
|
||||
│ │ └── utils.ts # Utility functions (auth state management, storage)
|
||||
│ └── vite-env.d.ts # Vite environment type declarations
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── tsconfig.node.json # TypeScript configuration for Node contexts (e.g., Vite config)
|
||||
└── vite.config.ts # Vite build tool configuration
|
||||
```
|
||||
|
||||
## Widget System
|
||||
|
||||
The frontend is built around a widget system. Widgets are independent, reusable components that display specific information or functionality.
|
||||
|
||||
* **Widget Sizes:**
|
||||
* `default`: Full column width, suitable for most widgets.
|
||||
* `icon`: Smaller size, designed for use in collapsed sidebars or icon-based menus.
|
||||
|
||||
* **Widget Components:** Located in `src/components/widgets/`. Examples include:
|
||||
* `LoginWidget`: Login form.
|
||||
* `RegisterWidget`: Registration information display.
|
||||
* `ButtonWidget`: Reusable button component.
|
||||
* `StudentCountWidget`, `TeacherCountWidget`, `ProfileInfoWidget`, `PostFeedWidget`, `StudentTableWidget`, `TuitionFeeWidget`: Placeholder widgets to be implemented.
|
||||
The UI is composed of reusable widgets found in `src/components/widgets/`. Each widget encapsulates specific functionality or displays data. They support different sizes (`default`, `icon`) for adaptability within various layouts.
|
||||
|
||||
## API System
|
||||
|
||||
The frontend is designed to interact with a backend through a global API system defined in `src/utils/api.ts`.
|
||||
Defined in `src/api/api.ts`, this module handles all communication with the backend API.
|
||||
|
||||
* **Current Implementation:** The `api.ts` file currently contains **mocked API calls** for demonstration purposes. It simulates API responses using timeouts and hardcoded data.
|
||||
* **Backend Integration:** To connect to a real backend, you will need to replace the mocked API calls in `api.ts` with actual `fetch` requests to your backend endpoints.
|
||||
* **Expected Backend Endpoints (Example):**
|
||||
* `POST /api/login`: User login.
|
||||
* `GET /api/user`: Get user data (requires authentication).
|
||||
* `POST /api/logout`: User logout.
|
||||
* `/api/students`, `/api/teachers`, `/api/admin/students`, etc.: Endpoints for managing students, teachers, and other LMS data.
|
||||
* **Live Interaction:** Functions use `fetch` to make requests to the running backend (default: `http://localhost:8080/api`).
|
||||
* **SRP Flow:** The `login` function implements the two-step SRP authentication handshake with the backend.
|
||||
* **Authenticated Requests:** Other API functions automatically include the stored authentication token in the `Authorization: Bearer <token>` header for protected endpoints.
|
||||
* **Error Handling:** Includes basic error handling and detection of unauthorized (401) responses.
|
||||
* **Key Endpoints Used:**
|
||||
* `POST /api/auth/srp/start`
|
||||
* `POST /api/auth/srp/verify`
|
||||
* `POST /api/auth/logout`
|
||||
* `GET /api/profile/{user_id}`
|
||||
* `PUT /api/profile/settings`
|
||||
* `GET /api/admin/dashboard`
|
||||
* `GET /api/admin/students`
|
||||
* `GET /api/admin/students/{student_id}/financials`
|
||||
|
||||
## Backend Considerations
|
||||
## Backend Interaction Notes
|
||||
|
||||
* **Password Hashing (Backend Recommended):** While `argon2-browser` is included for frontend password hashing demonstration, **it is strongly recommended to implement password hashing (using Argon2id or similar) and salting on the backend for enhanced security.** The frontend should send passwords securely (HTTPS) to the backend, and the backend should handle the hashing and verification process.
|
||||
* **Authentication and Authorization:** The backend should implement robust authentication (e.g., JWT or session-based) and authorization mechanisms to secure API endpoints and protect sensitive data.
|
||||
* **Database:** The database schema outlined in the initial prompt should be implemented on the backend to store user data, course information, enrollments, etc. (Refer to the initial prompt for database schema details).
|
||||
* **Backend Technology:** You can choose any suitable backend technology (Node.js, Python, Java, PHP, etc.) to build the API endpoints and connect to the database.
|
||||
* **Authentication:** The backend handles the secure verification of passwords using SRP. The frontend never stores or hashes the raw password itself after the initial SRP calculation during login.
|
||||
* **Authorization:** Access to specific API endpoints (e.g., admin routes) is controlled by the backend based on the user's role/permissions associated with their session token.
|
||||
* **Data Source:** All dynamic data (user info, student lists, etc.) is fetched from the backend, which interacts with the MariaDB database.
|
||||
|
||||
## Further Development
|
||||
|
||||
* **Implement Modal Module:** Create a reusable `ModalModule` component in `src/components/modules/ModalModule.ts` to handle modals for various purposes (e.g., classrooms under construction, account settings).
|
||||
* **Complete Widget Implementations:** Implement the remaining placeholder widgets in `src/components/widgets/` (e.g., `StudentCountWidget`, `TeacherCountWidget`, `ProfileInfoWidget`, `PostFeedWidget`, `StudentTableWidget`, `TuitionFeeWidget`) to display actual data from the backend.
|
||||
* **Account Settings Modal:** Develop the Account Settings Modal to allow users to edit their profile information and change passwords.
|
||||
* **Admin Page Functionality:** Implement the functionality for the Admin page, including student and teacher management, using appropriate widgets and API calls.
|
||||
* **Student Table Widget:** Integrate a table library (e.g., Tabulator, DataTables) into the `StudentTableWidget` for enhanced table features like sorting, filtering, and pagination.
|
||||
* **Classrooms Page/Modal:** Implement the Classrooms feature, potentially starting with a modal indicating "Under Construction" as initially requested, and then expanding to a full Classrooms page with relevant widgets.
|
||||
* **Form Validation:** Add client-side form validation to login, registration (if implemented), and account settings forms for better user experience.
|
||||
* **Error Handling:** Improve error handling in API calls and display user-friendly error messages.
|
||||
* **Styling and UI Polish:** Enhance the visual design and user interface with custom CSS and Bootstrap components to create a polished and professional LMS frontend.
|
||||
* **Backend Integration:** Replace the mocked API calls with real API calls to your backend endpoints to connect the frontend to the backend data and functionality.
|
||||
* **Complete Widget Functionality:** Ensure all widgets fetch and display real data from the backend API.
|
||||
* **Admin Page Enhancements:** Implement full CRUD operations for student/teacher management, filtering, batch actions, and enrollment assignments.
|
||||
* **Student Table Widget:** Integrate a robust table library (e.g., Tabulator, TanStack Table) for improved sorting, filtering, and pagination.
|
||||
* **Classrooms Module:** Build out the Classrooms feature beyond the placeholder modal.
|
||||
* **Robust Error Handling:** Implement more comprehensive error handling and user feedback mechanisms throughout the UI.
|
||||
* **Client-Side Validation:** Add more input validation for forms (e.g., account settings).
|
||||
* **UI/UX Polish:** Refine styling, transitions, and interactions for a smoother user experience.
|
||||
* **Testing:** Implement unit and potentially end-to-end tests.
|
||||
* **State Management:** For larger applications, consider introducing a dedicated state management library (like Zustand, Pinia, Redux Toolkit) instead of relying solely on `utils.ts`.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -128,4 +131,4 @@ The frontend is designed to interact with a backend through a global API system
|
||||
|
||||
## License
|
||||
|
||||
[View the License](./LICENSE)
|
||||
[View MIT License](./LICENSE)
|
||||
788
docs/CONTRACT.md
788
docs/CONTRACT.md
@@ -1,483 +1,467 @@
|
||||
# Contract
|
||||
# **SOFTWARE DEVELOPMENT AND IMPLEMENTATION AGREEMENT**
|
||||
|
||||
## Project Agreement: Proof-of-Concept Development
|
||||
**Parties:**
|
||||
(Term: 3-5 Years, To be finalized based on Support/Maintenance period post-delivery)
|
||||
|
||||
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.
|
||||
This Software Development and Implementation Agreement (the "**Agreement**") is entered into effective as of **[Date - e.g., August 5, 2024]** (the "**Effective Date**"), by and between:
|
||||
|
||||
**Project Scope:**
|
||||
**CellTech**, a Partnership organized and existing under the laws of the Republic of the Philippines, with its principal place of business at Funda Dalipe, San Jose, Antique (hereinafter referred to as "**Developer**"), and online at https://gitea.opossum-arcturus.ts.net/CellTech.
|
||||
|
||||
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:
|
||||
AND
|
||||
|
||||
* **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.
|
||||
**Western Institute of Technology**, a Partnership organized and existing under the laws of the Republic of the Philippines, with its principal place of business at Lapaz, Iloilo City (hereinafter referred to as "**Client**").
|
||||
|
||||
**Project Objectives:**
|
||||
(Developer and Client may be referred to individually as a "**Party**" and collectively as the "**Parties**").
|
||||
|
||||
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.
|
||||
**Recitals:**
|
||||
|
||||
**Stakeholders:**
|
||||
A. Client requires the design, development, implementation, testing, and delivery of a custom Learner Management System (hereinafter referred to as the "**System**" or "**LMS**") as further specified herein, to meet its operational and educational requirements.
|
||||
|
||||
* **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.
|
||||
B. Developer represents that it possesses the necessary expertise, personnel, skills, and resources to develop and deliver the System in accordance with the specifications, timelines, and conditions outlined in this Agreement.
|
||||
|
||||
**Timeline:**
|
||||
C. Client desires to engage Developer to perform such software development and implementation services ("**Services**"), and Developer desires to provide such Services to Client, subject to the terms and conditions set forth in this Agreement.
|
||||
|
||||
* **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.
|
||||
**Agreement:**
|
||||
|
||||
**Deliverables:**
|
||||
**NOW, THEREFORE**, in consideration of the mutual covenants, promises, and agreements contained herein, and other good and valuable consideration, the receipt and sufficiency of which are hereby acknowledged, the Parties hereto agree as follows:
|
||||
|
||||
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.
|
||||
## ARTICLE 1: DEFINITIONS
|
||||
|
||||
**Limitations of Proof-of-Concept:**
|
||||
For the purposes of this Agreement, the following terms shall have the meanings ascribed to them below:
|
||||
|
||||
* **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.
|
||||
1.1 **"Acceptance Criteria"** means the specific, measurable criteria defined in this Agreement or mutually agreed upon test plans, which the System must meet to be formally accepted by the Client.
|
||||
|
||||
**Informal Agreement:**
|
||||
1.2 **"Agile"** refers to the iterative and incremental software development methodology described in Section 2.2.
|
||||
|
||||
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.
|
||||
1.3 **"Confidential Information"** has the meaning set forth in Article 10.
|
||||
|
||||
*Replace example dates, names, and roles with your specific project details.*
|
||||
1.4 **"Deliverables"** means the specific items, including software code, documentation, reports, and training materials, that Developer is obligated to provide to Client under this Agreement, as further detailed in Section 4.3.
|
||||
|
||||
## Software Development Life Cycle (SDLC): Iterative and Agile-Inspired Prototype Development
|
||||
1.5 **"Intellectual Property"** means any and all patents, copyrights, trademarks, trade secrets, database rights, design rights, and other proprietary rights, whether registered or unregistered.
|
||||
|
||||
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.
|
||||
1.6 **"LMS"** or **"System"** means the custom Learner Management System software to be developed and implemented by Developer for Client pursuant to this Agreement.
|
||||
|
||||
**Iterative Development Cycles:**
|
||||
1.7 **"OPAQUE"** refers to the Oblivious Pseudo-Random Function (OPRF) based Asymmetric Password-Authenticated Key Exchange protocol intended for user authentication within the System.
|
||||
|
||||
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.8 **"PAKE"** means Password-Authenticated Key Exchange, a class of cryptographic protocols allowing two parties to establish a shared cryptographic key based on a user's password without transmitting the password itself. OPAQUE is a type of PAKE protocol.
|
||||
|
||||
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.
|
||||
1.9 **"Services"** means the software design, development, implementation, testing, training, documentation, and delivery services related to the System to be performed by Developer under this Agreement.
|
||||
|
||||
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.
|
||||
1.10 **"Sprint"** means a time-boxed iteration (typically 2-4 weeks) within the Agile development process during which a defined amount of work is completed and made ready for review.
|
||||
|
||||
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).
|
||||
1.11 **"Source Code"** means the human-readable programming language instructions used to create the System software.
|
||||
|
||||
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].
|
||||
1.12 **"UAT"** means User Acceptance Testing, the process by which Client validates that the System meets the agreed-upon requirements and Acceptance Criteria.
|
||||
|
||||
**Agile-Inspired Principles:**
|
||||
## ARTICLE 2: SCOPE OF SERVICES
|
||||
|
||||
* **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.
|
||||
2.1 **General Scope:** Developer shall perform the Services necessary to design, develop, test, implement, and deliver the System as described in this Agreement and its Exhibits. The System is intended to function as a comprehensive Learner Management System providing functionalities for learners, instructors, and administrators of Western Institute of Technology.
|
||||
|
||||
**Tools and Practices:**
|
||||
2.2 **Development Methodology:** Developer shall utilize an Agile (Iterative and Incremental) Software Development Life Cycle (SDLC) methodology for the performance of the Services.
|
||||
|
||||
* **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.
|
||||
> a. **Process:** The project will be broken down into Sprints. Each Sprint will generally include planning, design, development, testing, and stakeholder review, aiming to produce a potentially shippable increment of the System. Sprint duration will be mutually agreed upon, typically **[e.g., two (2)]** weeks.
|
||||
>
|
||||
> b. **Rationale:** This methodology is selected to provide adaptability to evolving requirements, facilitate early and frequent feedback from Client, foster collaboration and transparency, manage risks effectively, and maintain focus on user needs.
|
||||
>
|
||||
> c. **Project Management:** Developer shall employ project management practices consistent with the Agile methodology. Developer shall provide Client with **[e.g., weekly progress summaries via email and bi-weekly Sprint review meetings]**. Project tracking and backlog management will utilize **[Specify Tool, e.g., Trello, Jira, Asana, GitHub Projects - requires agreement]**, to which Client representatives will be granted appropriate access.
|
||||
|
||||
**Justification for Agile-Inspired Approach:**
|
||||
2.3 **Key System Features and Characteristics:** The System developed under this Agreement shall aim to possess the following features and characteristics:
|
||||
|
||||
This lightweight, iterative approach was deemed most suitable for a proof-of-concept project due to:
|
||||
> a. **Technology Stack:**
|
||||
>
|
||||
> > i. Frontend: Vite utilizing Vanilla TypeScript and Bootstrap v5.3.
|
||||
> >
|
||||
> > ii. Backend: Rust utilizing the Actix framework.
|
||||
> >
|
||||
> > iii. Database: MariaDB (version 10 or later compatible version).
|
||||
> >
|
||||
> > iv. Deployment Environment: Containerized using Docker and orchestrated via Docker Compose.
|
||||
>
|
||||
> b. **Performance & Scalability:** The architecture is designed for efficient performance under expected load conditions (to be reasonably defined) and to accommodate future growth in user base and data volume anticipated by the Client over the Agreement term.
|
||||
>
|
||||
> c. **Security:** Development practices will incorporate security considerations, including secure authentication mechanisms (specifically, the **OPAQUE** PAKE protocol), secure session management, input validation, protection against common web vulnerabilities (e.g., Cross-Site Scripting, SQL Injection), and adherence to standard secure coding practices. The Parties shall mutually agree upon any specific additional security standards or penetration testing requirements, if necessary.
|
||||
>
|
||||
> d. **User Interface (UI) and User Experience (UX):** The System shall feature an intuitive, responsive user interface adaptable to various screen sizes (desktop, tablet, mobile) and designed according to the principles outlined in Section 3.1 and Exhibit A. Adherence to Web Content Accessibility Guidelines (WCAG) **[Specify required level, e.g., 2.1 Level AA]** will be pursued where reasonably practicable within the scope and budget.
|
||||
>
|
||||
> e. **Modularity:** The System architecture, including the frontend widget system and backend service structure, promotes modularity to facilitate future enhancements, maintenance, and potential integrations.
|
||||
|
||||
* **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.
|
||||
2.4 **Video Demonstration:** As part of the Deliverables, Developer shall provide Client with a video demonstration (e.g., screen recording with narration) showcasing core System functionalities, user roles (learner, instructor, administrator), and key workflows, substantially covering the items listed below:
|
||||
|
||||
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.
|
||||
> a. Platform Navigation and Layout.
|
||||
>
|
||||
> b. Key User Journeys (Learner Course Interaction, Instructor Course Management, Administrator User/Course Management).
|
||||
>
|
||||
> c. Core Feature Highlights (Dashboard, Profiles, Course Interaction, Admin Interfaces).
|
||||
>
|
||||
> d. Interface Responsiveness.
|
||||
|
||||
## Software Development Life Cycle (SDLC): Iterative and Agile-Inspired Prototype Development
|
||||
2.5 **Excluded Services:** Unless otherwise explicitly agreed upon in a written Change Order (Article 6), the Services under this Agreement **do not** include:
|
||||
> a. Ongoing hosting services, server maintenance, or domain name registration/renewal fees beyond the initial deployment and stabilization period defined in Phase 4 of Exhibit C.
|
||||
>
|
||||
> b. Creation or curation of educational content (course materials, quizzes, etc.) to be loaded into the LMS.
|
||||
>
|
||||
> c. Extensive data migration services from legacy systems beyond [Define scope, e.g., "basic assistance with data mapping and import validation for user data provided in a pre-agreed format" or "data migration services as detailed in a separate Statement of Work"]. Client is responsible for data extraction and cleansing from source systems unless otherwise agreed.
|
||||
>
|
||||
> d. Procurement or management of hardware infrastructure required by Client outside the scope of the development and deployment process.
|
||||
>
|
||||
> e. Licenses for any third-party software required by Client for its own operations that may interact with the LMS, unless such software is directly embedded by Developer as part of the System Deliverable and its licensing terms are passed through.
|
||||
|
||||
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.
|
||||
## ARTICLE 3: SYSTEM SPECIFICATIONS AND ARCHITECTURE
|
||||
|
||||
**Iterative Development Cycles:**
|
||||
3.1 **User Interface (UI) Layout and Flow:** The general layout principles and high-level component relationships for the System's user interface are conceptually depicted in **Exhibit A (User Interface Flow Diagram)**, incorporated herein by reference. Key layout components include a persistent Header, a contextual Sidebar (where applicable), a Main Content Area, and a Footer. Specific screen designs and detailed UI specifications will be developed and refined during the Sprints, subject to Client review and feedback during Sprint Reviews.
|
||||
|
||||
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:
|
||||
3.2 **Backend Architecture:** The high-level backend architecture, illustrating the interaction between the Actix framework, MariaDB database, OPAQUE authentication flow, and other core components within the planned containerized environment, is conceptually depicted in **Exhibit B (Backend Architecture Diagram)**, incorporated herein by reference.
|
||||
|
||||
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.
|
||||
3.3 **Technical Specifications:** Detailed technical specifications, including specific API endpoint definitions, data model schemas, and performance guidelines, may be documented collaboratively by the Parties during the project lifecycle, potentially in a shared repository or document store ([Specify if needed, e.g., "maintained in the project's shared Confluence space"]), and referenced herein upon mutual agreement. Initial database schema is based on requirements outlined during project initiation.
|
||||
|
||||
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.
|
||||
## ARTICLE 4: PROJECT TIMELINE AND DELIVERABLES
|
||||
|
||||
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.1 **Estimated Timeline:** The estimated timeline for the completion of the Services is approximately **Thirty-Five (35) weeks**, commencing from the agreed-upon project start date. A detailed breakdown of phases, estimated task durations, and dependencies is illustrated in the Gantt chart provided as **Exhibit C (Project Timeline Gantt Chart)**, incorporated herein by reference.
|
||||
|
||||
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.2 **Acknowledgement of Estimates:** Client acknowledges that the timeline provided in Exhibit C is an estimate based on the initial Scope of Services, assumptions about requirements stability, and the Agile methodology. Timelines may be adjusted based on the outcomes of Sprints, the complexity of features prioritized, Client feedback responsiveness, unforeseen technical challenges, or mutually agreed-upon Change Requests processed via the Change Management procedure (Article 6). Developer shall promptly communicate any anticipated significant deviations from the estimated timeline.
|
||||
|
||||
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.
|
||||
4.3 **Key Deliverables:** Major deliverables under this Agreement include:
|
||||
|
||||
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.
|
||||
> a. Access to functional System increments for review and feedback at the conclusion of relevant Sprints (typically via a staging environment).
|
||||
>
|
||||
> b. The final, deployed System software meeting the Acceptance Criteria (Article 5), delivered to the agreed production environment.
|
||||
>
|
||||
> c. The Video Demonstration (as per Section 2.4).
|
||||
>
|
||||
> d. System Documentation, comprising:
|
||||
> > i. User Manuals: Guides for administrators, instructors, and learners covering core functionalities.
|
||||
> >
|
||||
> > ii. Technical Documentation: Including deployment instructions, system architecture overview, API endpoint documentation (e.g., generated OpenAPI spec), and database schema description, sufficient to enable technically skilled personnel to operate, maintain, and potentially extend the System. The specific level of detail shall be **[e.g., "standard industry practice for systems of similar complexity" or specify further detail if required]**.
|
||||
>
|
||||
> e. Source Code for the custom-developed portions of the System delivered under this Agreement, provided via **[Specify method, e.g., access to a Git repository, digital media transfer]**. Source code escrow requirements, if any, must be separately agreed upon in writing.
|
||||
>
|
||||
> f. Training materials and delivery of training sessions as specified in Section 4.4.
|
||||
|
||||
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:**
|
||||
4.4 **Training and Handover:** Upon successful deployment (Go-Live milestone in Exhibit C), Developer shall provide:
|
||||
|
||||
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.
|
||||
> a. Training for Client's designated administrators and instructors, covering system administration, course management, user management, and key instructional features. This training shall consist of **[Specify format, duration, number of sessions, e.g., "up to two (2) remote training sessions, each lasting approximately three (3) hours, for a maximum of ten (10) Client attendees per session"]**.
|
||||
>
|
||||
> b. Onboarding materials suitable for learners, such as a concise user guide document and potentially referencing the Video Demonstration.
|
||||
>
|
||||
> c. A formal handover meeting and documentation transfer concluding the development and deployment phases outlined in Exhibit C.
|
||||
|
||||
*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.
|
||||
## ARTICLE 5: TESTING AND ACCEPTANCE
|
||||
|
||||
## Wireframes and UI Design: Figma Project for LMS Frontend
|
||||
5.1 **Testing:** Developer shall perform internal testing throughout the development process, appropriate to the Agile methodology. This includes developer testing (e.g., unit tests where applicable), integration testing of components, and functional testing against requirements defined for each Sprint.
|
||||
|
||||
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.
|
||||
5.2 **User Acceptance Testing (UAT):** Client shall be responsible for conducting UAT. Developer shall notify Client when specific features, modules, or System increments are ready for UAT (typically at the end of Sprints or designated UAT phases as shown in Exhibit C). Client shall perform UAT in accordance with mutually agreed-upon test plans or use cases based on the Acceptance Criteria. Client shall have **[Specify duration, e.g., ten (10) business days]** from such notification to conduct UAT for the provided increment and report any identified material defects or non-conformities ("**Defects**") to Developer in writing via the agreed project tracking tool ([Tool name from 2.2.c]). Failure to report Defects within the specified period may be deemed acceptance of that increment for the purpose of proceeding with development, without prejudice to addressing Defects identified later within the Warranty Period.
|
||||
|
||||
**Figma Project Link:** [**Insert Figma Project Shareable Link Here**]
|
||||
5.3 **Acceptance Criteria:** The System (or relevant increment) shall be deemed formally accepted by Client upon the earliest occurrence of:
|
||||
> (a) Client providing Developer with written notice of acceptance; or
|
||||
>
|
||||
> (b) Client using the delivered System or increment in a live production environment for its intended operational purposes (excluding UAT activities); or
|
||||
>
|
||||
> (c) The expiration of the final UAT period for the complete System (as per the timeline in Exhibit C) without Client providing written notice of material Defects that prevent acceptance according to the agreed Acceptance Criteria.
|
||||
>
|
||||
> Acceptance Criteria shall primarily be based on the functional requirements defined in the project backlog and specifications developed during the Sprints, demonstrating that the System operates substantially as intended.
|
||||
|
||||
**Figma Project Structure and Key Screens:**
|
||||
5.4 **Defect Resolution:** Developer shall use commercially reasonable efforts to correct any material Defects identified during UAT and properly reported by Client within the agreed timeframe. Defect prioritization and resolution timelines will be managed as part of the Agile backlog grooming and sprint planning process. Resolution of minor defects or cosmetic issues may be deferred to subsequent Sprints or the Warranty Period by mutual agreement.
|
||||
|
||||
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:
|
||||
## ARTICLE 6: CHANGE MANAGEMENT
|
||||
|
||||
* **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).
|
||||
6.1 **Change Request Process:** Both Parties acknowledge that requirements may evolve. Any proposed change to the agreed-upon Scope of Services, features, specifications, or Deliverables after the initial baseline established during planning ("**Change Request**") must be submitted in writing by either Party to the other Party's designated project contact.
|
||||
|
||||
* **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.
|
||||
6.2 **Impact Assessment:** Upon receipt of a Change Request, Developer shall promptly assess its potential impact on the project scope, technical feasibility, estimated timeline, resource allocation, and overall project cost. Developer will provide Client with a written impact analysis, including any proposed adjustments to fees or schedule, within **[e.g., five (5) business days]**, or a longer period if mutually agreed for complex requests.
|
||||
|
||||
* **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.
|
||||
6.3 **Approval:** Developer shall not proceed with implementing any Change Request until both Parties have formally agreed in writing (e.g., through a signed Change Order document referencing this Agreement) to the Change Request itself, its assessed impact, and any associated adjustments to the Agreement's terms, including fees and timeline. Approved Change Orders shall become part of this Agreement.
|
||||
|
||||
* **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.
|
||||
## ARTICLE 7: PROJECT CONSIDERATIONS AND RISK MITIGATION
|
||||
|
||||
* **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.
|
||||
7.1 **Potential Scope Evolution:** Client acknowledges that the flexibility inherent in the Agile process necessitates active participation and decisive feedback from Client representatives to manage scope effectively. Both Parties agree to utilize the Change Management process (Article 6) diligently to ensure that scope adjustments are intentional, documented, and their impacts understood and agreed upon, thereby mitigating risks to project timelines and budget.
|
||||
|
||||
**Wireframe Fidelity and Purpose:**
|
||||
7.2 **Initial Setup and Data Migration:**
|
||||
|
||||
The wireframes in the Figma project are primarily **mid-fidelity wireframes**. They focus on:
|
||||
> a. Client Responsibilities: Client is responsible for providing necessary access to its infrastructure (if applicable), timely feedback, subject matter expertise, and ensuring user readiness for the System implementation. Client shall designate key personnel to participate in project meetings, reviews, and UAT.
|
||||
>
|
||||
> b. Data Migration: **[Select ONE option based on agreement, requires confirmation]:**
|
||||
> * **Option 1 (Migration Not Included/Basic Assistance):** Data migration from Client's existing systems is not included in the scope of Services under the base Fees. Developer may provide reasonable assistance with defining data formats or validating imported data, subject to separate agreement or Change Order if significant effort is required. Client is primarily responsible for extracting, cleansing, formatting, and importing its data.
|
||||
> * **Option 2 (Migration Included - Define Scope):** Data migration services for [Specify data types, e.g., user profiles, basic course structures] from [Specify source system(s)] are included in the Scope of Services. A detailed Data Migration Plan outlining responsibilities, formats, timelines, and validation procedures shall be developed collaboratively by the Parties early in the project. Client remains responsible for the accuracy and completeness of source data provided. Complexities discovered during migration may necessitate a Change Request.
|
||||
>
|
||||
> c. Support and Training: Developer commits to providing the onboarding support, training, and documentation outlined in Articles 2, 4, and associated Exhibits to mitigate challenges associated with system transition and user adoption.
|
||||
|
||||
* **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.
|
||||
## ARTICLE 8: FEES AND PAYMENT SCHEDULE
|
||||
|
||||
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.
|
||||
**8.1 Fees and Payment Terms:** **The total fees, billing rates (if applicable), invoicing procedures, and payment schedule for the Services rendered under this Agreement shall be detailed in a separate Payment Schedule document (designated as Exhibit D), which shall be mutually agreed upon by the Parties in writing and incorporated herein by reference upon execution.** Exhibit D shall specify currency (Philippine Peso - PHP, unless otherwise agreed), payment milestones or frequency, and payment terms (e.g., Net 30 days from invoice date).
|
||||
|
||||
**How Wireframes Informed Development:**
|
||||
8.2 **Expenses:** Unless otherwise specified in Exhibit D, Developer shall bear its own general overhead costs. Any direct, out-of-pocket expenses reasonably incurred by Developer specifically for the project (e.g., pre-approved travel, specific third-party software licenses necessary for the deliverable) shall be reimbursable by Client only if pre-approved by Client in writing. Invoices for approved expenses shall include supporting documentation.
|
||||
|
||||
The Figma wireframes served as a crucial blueprint for frontend development by:
|
||||
8.3 **Taxes:** Each Party shall be responsible for its own taxes imposed by relevant authorities. Fees specified in Exhibit D are exclusive of any applicable Value Added Tax (VAT) or other sales taxes, which, if applicable, shall be added to Developer's invoices and paid by Client.
|
||||
|
||||
* **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.
|
||||
## ARTICLE 9: INTELLECTUAL PROPERTY RIGHTS
|
||||
|
||||
*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).
|
||||
9.1 **Ownership of Custom Developed System:** Subject to Client's full payment of all fees due under this Agreement and Developer's reservation of rights in Pre-Existing IP (Section 9.2), Developer hereby assigns to Client all right, title, and interest in and to the custom Source Code and associated Deliverables specifically created by Developer for Client under this Agreement (the "**Custom Developed IP**").
|
||||
|
||||
## Project Timeline and Gantt Chart: Proof-of-Concept Development
|
||||
9.2 **Developer's Pre-Existing Intellectual Property:** Developer shall retain all right, title, and interest in and to any software, code, libraries, tools, methodologies, know-how, or other intellectual property owned or licensed by Developer prior to the Effective Date or developed independently of this Agreement ("**Developer Pre-Existing IP**"), even if incorporated into the System or used in performing the Services.
|
||||
|
||||
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.
|
||||
9.3 **License to Developer Pre-Existing IP:** To the extent Developer Pre-Existing IP is incorporated into the System Deliverables, Developer hereby grants Client a perpetual, irrevocable, non-exclusive, royalty-free, worldwide license to use, reproduce, modify, and create derivative works of such Developer Pre-Existing IP solely as necessary for Client to use, operate, maintain, and enhance the System for its internal educational and administrative purposes. This license is non-transferable except to a successor of Client's entire business related to the System.
|
||||
|
||||
**Gantt Chart Location:** [**Path to Gantt Chart Spreadsheet File: e.g., `docs/project_plan/lms_frontend_gantt.xlsx`**]
|
||||
9.4 **Third-Party Materials:** Any third-party software or materials, including open-source software components, incorporated into the System shall be subject to the terms and conditions of their respective licenses. Developer shall identify any significant third-party components and their licenses to Client upon request or as part of the documentation Deliverable. Client's use of the System is subject to compliance with such third-party licenses.
|
||||
|
||||
*(Alternatively, if you used an online tool, provide a link)* [**Optional: Link to Online Gantt Chart Tool: [Insert Project Management Tool Link Here]**]
|
||||
## ARTICLE 10: CONFIDENTIALITY
|
||||
|
||||
**Gantt Chart Structure and Key Phases:**
|
||||
10.1 **Definition:** "**Confidential Information**" means any non-public information disclosed by one Party ("**Disclosing Party**") to the other Party ("**Receiving Party**") under this Agreement, whether orally or in writing, that is designated as confidential or that reasonably should be understood to be confidential given the nature of the information and the circumstances of disclosure. This includes, but is not limited to, business plans, financial information, customer lists, personnel information, technical data, trade secrets, know-how, source code (for Developer's pre-existing IP), and the terms of this Agreement.
|
||||
|
||||
The Gantt chart was structured around the following key phases of the proof-of-concept development:
|
||||
10.2 **Obligations:** The Receiving Party agrees to: (a) use the Confidential Information solely for the purpose of performing its obligations or exercising its rights under this Agreement; (b) not disclose the Confidential Information to any third party without the prior written consent of the Disclosing Party, except to its employees, contractors, or legal/financial advisors who have a need to know and are bound by confidentiality obligations at least as restrictive as those herein; and (c) protect the Confidential Information from unauthorized use or disclosure using at least the same degree of care that it uses to protect its own confidential information of like importance, but not less than a reasonable degree of care.
|
||||
|
||||
| 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 |
|
||||
10.3 **Exclusions:** The obligations under this Article shall not apply to information that the Receiving Party can demonstrate: (a) was already lawfully known to the Receiving Party at the time of disclosure, free of any obligation of confidentiality; (b) is or becomes publicly known through no wrongful act of the Receiving Party; (c) is rightfully received from a third party without restriction and without breach of this Agreement; or (d) was independently developed by the Receiving Party without reference to or use of the Disclosing Party's Confidential Information.
|
||||
|
||||
*Note: Example dates and durations are approximate and should be adjusted based on your actual project timeline.*
|
||||
10.4 **Required Disclosure:** If the Receiving Party is compelled by law, regulation, or court order to disclose any Confidential Information, it shall provide the Disclosing Party with prompt prior written notice (to the extent legally permissible) to allow the Disclosing Party an opportunity to seek a protective order or other appropriate remedy.
|
||||
|
||||
**Gantt Chart Purpose for Proof-of-Concept:**
|
||||
10.5 **Duration:** The confidentiality obligations set forth herein shall survive the termination or expiration of this Agreement for a period of **[e.g., five (5)]** years. Obligations related to trade secrets shall survive indefinitely as long as they remain trade secrets under applicable law.
|
||||
|
||||
The simplified Gantt chart served primarily as a high-level planning tool for the proof-of-concept. Its purpose was to:
|
||||
## ARTICLE 11: WARRANTIES AND DISCLAIMERS
|
||||
|
||||
* **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.
|
||||
11.1 **Developer Warranties:** Developer warrants to Client that:
|
||||
> a. **Performance Warranty:** For a period of **[Specify duration, e.g., ninety (90) days]** following the date of final Acceptance of the complete System by Client ("**Warranty Period**"), the System will perform substantially in accordance with the material functional specifications agreed upon by the Parties under this Agreement when operated in the intended environment. Client's sole and exclusive remedy, and Developer's entire liability, for breach of this warranty shall be for Developer to use commercially reasonable efforts to correct or provide a workaround for any reproducible, material non-conformity reported by Client in writing during the Warranty Period. This warranty does not cover defects arising from misuse, modification by Client or third parties not authorized by Developer, accidents, or failure to operate the System in accordance with documentation or specified requirements.
|
||||
>
|
||||
> b. **Authority:** Developer has the full right, power, and authority to enter into this Agreement and perform its obligations hereunder.
|
||||
>
|
||||
> c. **Service Performance:** The Services will be performed in a professional and workmanlike manner, consistent with generally accepted industry standards.
|
||||
>
|
||||
> d. **Non-Infringement:** To Developer's knowledge, the Custom Developed IP delivered under this Agreement does not infringe upon any valid patent, copyright, or trade secret of any third party existing under the laws of the Republic of the Philippines as of the Effective Date. Developer makes no warranty regarding infringement related to Developer Pre-Existing IP or any third-party materials.
|
||||
|
||||
**Limitations of Gantt Chart in Proof-of-Concept:**
|
||||
11.2 **Disclaimers:** **EXCEPT FOR THE EXPRESS WARRANTIES SET FORTH IN SECTION 11.1, THE SYSTEM, SERVICES, AND DELIVERABLES ARE PROVIDED "AS IS." DEVELOPER HEREBY DISCLAIMS ALL OTHER WARRANTIES, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT WITH RESPECT TO THE SYSTEM AS A WHOLE OR ANY THIRD-PARTY COMPONENTS. DEVELOPER DOES NOT WARRANT THAT THE SYSTEM WILL BE ERROR-FREE, UNINTERRUPTED, OR MEET ALL OF CLIENT'S REQUIREMENTS.**
|
||||
|
||||
* **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.
|
||||
## ARTICLE 12: LIMITATION OF LIABILITY
|
||||
|
||||
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.
|
||||
12.1 **Exclusion of Indirect Damages:** **IN NO EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER PARTY OR ANY THIRD PARTY FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, OR EXEMPLARY DAMAGES (INCLUDING, BUT NOT LIMITED TO, LOST PROFITS, LOST REVENUE, LOST DATA, LOSS OF GOODWILL, OR BUSINESS INTERRUPTION) ARISING OUT OF OR IN CONNECTION WITH THIS AGREEMENT OR THE USE OR PERFORMANCE OF THE SYSTEM OR SERVICES, REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT, OR OTHERWISE), EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.**
|
||||
|
||||
*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.
|
||||
12.2 **Cap on Direct Damages:** **EXCEPT FOR LIABILITY ARISING FROM A PARTY'S BREACH OF ITS CONFIDENTIALITY OBLIGATIONS (ARTICLE 10), INDEMNIFICATION OBLIGATIONS (IF ANY - NOT CURRENTLY INCLUDED, CONSIDER ADDING), OR INFRINGEMENT OF THE OTHER PARTY'S INTELLECTUAL PROPERTY RIGHTS, EACH PARTY'S TOTAL AGGREGATE LIABILITY TO THE OTHER PARTY FOR ALL CLAIMS FOR DIRECT DAMAGES ARISING OUT OF OR RELATING TO THIS AGREEMENT, WHETHER IN CONTRACT, TORT, OR OTHERWISE, SHALL NOT EXCEED THE TOTAL AMOUNT OF FEES ACTUALLY PAID BY CLIENT TO DEVELOPER UNDER THIS AGREEMENT DURING THE TWELVE (12) MONTH PERIOD PRECEDING THE EVENT GIVING RISE TO THE CLAIM.** *(Note: This cap is often heavily negotiated. Alternatives include a fixed amount or excluding certain types of direct damages).*
|
||||
|
||||
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.
|
||||
12.3 **Basis of Bargain:** The Parties acknowledge that the limitations of liability and disclaimers of warranties set forth in this Agreement reflect the agreed-upon allocation of risk between the Parties and form an essential basis of the bargain, without which Developer would not have entered into this Agreement on the terms provided.
|
||||
|
||||
## ARTICLE 13: TERM AND TERMINATION
|
||||
|
||||
13.1 **Term:** This Agreement shall commence on the Effective Date and shall continue until all Services are completed, the System is Accepted, final payment has been made, and the Warranty Period has expired, unless terminated earlier pursuant to the terms of this Article 13. The initial intended development and deployment term is estimated in Exhibit C. The overall Agreement duration might extend based on mutually agreed support terms beyond the initial delivery (as suggested by the "3-5 Year Term" note, which requires separate definition, likely in a Support Agreement).
|
||||
|
||||
13.2 **Termination for Cause:** Either Party may terminate this Agreement upon written notice if the other Party materially breaches any provision of this Agreement and fails to cure such breach within **[Specify cure period, e.g., thirty (30) calendar days]** after receiving written notice specifying the breach in reasonable detail. Material breaches include, but are not limited to, failure to make timely payments (by Client) or failure to meet key milestones or Deliverable requirements after reasonable opportunity to cure (by Developer).
|
||||
|
||||
13.3 **Termination for Convenience:** **[Choose ONE option or delete if not applicable]:**
|
||||
* **Option A (Client Only):** Client may terminate this Agreement without cause at any time upon **[Specify notice period, e.g., thirty (30) days']** prior written notice to Developer.
|
||||
* **Option B (Mutual):** Either Party may terminate this Agreement without cause at any time upon **[Specify notice period, e.g., sixty (60) days']** prior written notice to the other Party.
|
||||
* **Option C (No Convenience Termination):** (No clause added).
|
||||
|
||||
13.4 **Effect of Termination:** Upon termination or expiration of this Agreement for any reason:
|
||||
> a. Developer shall cease performing Services and shall promptly deliver to Client all completed Deliverables and work-in-progress, including Source Code for Custom Developed IP up to the date of termination.
|
||||
>
|
||||
> b. Client shall pay Developer for all Services performed and accepted Deliverables provided up to the effective date of termination, plus any pre-approved, non-cancelable expenses incurred. If termination is by Client for convenience (if Option A or B is chosen), Client shall also pay **[Specify additional amount, e.g., "a termination fee equivalent to X% of the remaining estimated project fees" or "for the work completed during the notice period"]**.
|
||||
>
|
||||
> c. Each Party shall promptly return or, at the Disclosing Party's request, destroy all Confidential Information of the other Party in its possession or control.
|
||||
>
|
||||
> d. Any provisions of this Agreement that by their nature should survive termination (including, but not limited to, Articles 9, 10, 11.2, 12, 14, and payment obligations accrued prior to termination) shall survive.
|
||||
|
||||
## ARTICLE 14: MISCELLANEOUS
|
||||
|
||||
14.1 **Governing Law:** This Agreement and any disputes arising out of or relating to it shall be governed by and construed in accordance with the laws of the **Republic of the Philippines**, without regard to its conflict of laws principles.
|
||||
|
||||
14.2 **Dispute Resolution:** The Parties agree to attempt to resolve any dispute, controversy, or claim arising out of or relating to this Agreement amicably through good faith negotiation between authorized representatives. If negotiation fails within **[e.g., thirty (30) days]**, the Parties agree **[Choose ONE: e.g., "to submit the dispute to mediation under the rules of [Specify Mediation Body in the Philippines] before resorting to litigation" OR "that the exclusive venue for any legal action shall be the competent courts of [Specify City, e.g., Iloilo City], Philippines"]**.
|
||||
|
||||
14.3 **Notices:** All notices, requests, demands, and other communications required or permitted under this Agreement shall be in writing and shall be deemed effectively given: (a) upon personal delivery; (b) upon transmission by electronic mail to the addresses specified below (provided confirmation of receipt is obtained); or (c) three (3) business days after deposit with a reputable overnight courier or registered mail, postage prepaid, addressed to the Parties at their respective addresses first set forth above, or to such other address as a Party may designate by notice.
|
||||
> **To Developer:** Attn: [Name/Title], Email: [Email Address]
|
||||
> **To Client:** Attn: [Name/Title], Email: [Email Address]
|
||||
|
||||
14.4 **Entire Agreement:** This Agreement, including all Exhibits attached hereto (which are incorporated herein by reference), constitutes the entire agreement between the Parties with respect to the subject matter hereof and supersedes all prior or contemporaneous proposals, understandings, representations, warranties, covenants, and agreements, whether written or oral, relating thereto.
|
||||
|
||||
14.5 **Amendments:** No amendment, modification, or waiver of any provision of this Agreement shall be effective unless in writing and signed by duly authorized representatives of both Parties.
|
||||
|
||||
14.6 **Assignment:** Neither Party may assign or transfer this Agreement or any of its rights or obligations hereunder, without the prior written consent of the other Party, which consent shall not be unreasonably withheld or delayed. Notwithstanding the foregoing, either Party may assign this Agreement without consent in connection with a merger, acquisition, or sale of all or substantially all of its assets related to this Agreement, provided the assignee agrees in writing to be bound by the terms hereof.
|
||||
|
||||
14.7 **Severability:** If any provision of this Agreement is held by a court of competent jurisdiction to be invalid, illegal, or unenforceable, the validity, legality, and enforceability of the remaining provisions shall not in any way be affected or impaired thereby, and such provision shall be deemed modified to the minimum extent necessary to make it valid, legal, and enforceable.
|
||||
|
||||
14.8 **Force Majeure:** Neither Party shall be liable for any failure or delay in performing its obligations hereunder (except for payment obligations) if such failure or delay is caused by circumstances beyond its reasonable control, including but not limited to acts of God, war, terrorism, strikes, labor disputes, pandemics, epidemics, government orders, fire, flood, earthquake, or internet service provider failures ("**Force Majeure Event**"). The affected Party shall provide prompt notice to the other Party and shall use reasonable efforts to resume performance as soon as practicable. If a Force Majeure Event continues for more than **[e.g., sixty (60) days]**, the non-affected Party may terminate this Agreement upon written notice.
|
||||
|
||||
14.9 **Relationship of Parties:** Developer is an independent contractor, and nothing in this Agreement shall be construed as creating an employment, partnership, joint venture, or agency relationship between Developer and Client. Neither Party has the authority to bind the other Party in any respect.
|
||||
|
||||
## ARTICLE 15: EXHIBITS
|
||||
|
||||
The following Exhibits are attached hereto and incorporated by reference into this Agreement:
|
||||
|
||||
* **Exhibit A:** User Interface Flow Diagram
|
||||
* **Exhibit B:** Backend Architecture Diagram
|
||||
* **Exhibit C:** Project Timeline Gantt Chart
|
||||
* **Exhibit D:** Payment Schedule *(To be mutually agreed upon and attached)*
|
||||
|
||||
**IN WITNESS WHEREOF,** the Parties hereto have caused this Software Development and Implementation Agreement to be executed by their duly authorized representatives as of the Effective Date.
|
||||
|
||||
**[CLIENT: Western Institute of Technology]**
|
||||
|
||||
By: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|
||||
Name: Mark Glen C. Guides
|
||||
Title: [Client Representative Title]
|
||||
Date: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|
||||
|
||||
**[DEVELOPER: Cell Tech]**
|
||||
|
||||
By: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|
||||
Name: Jose Daniel G. Percy
|
||||
Title: [Partner / Authorized Representative]
|
||||
Date: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|
||||
|
||||
By: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|
||||
Name: Rekcel M. Endencia
|
||||
Title: [Partner / Authorized Representative]
|
||||
Date: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|
||||
|
||||
---
|
||||
|
||||
### EXHIBIT A: User Interface Flow Diagram
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
theme: neo-dark
|
||||
---
|
||||
flowchart LR
|
||||
subgraph Layouts["Layouts"]
|
||||
Centered["🎯 Centered Layout (Login, Register)"]
|
||||
ThreeCol["🏛️ Three-Column Layout (Admin Dash)"]
|
||||
SplitCol["| Sidebar | Content | (Split Column Layout)"]
|
||||
end
|
||||
Header["🧭 Header (Logo, Menu, User Profile Dropdown)"]
|
||||
Sidebar["📚 Sidebar (Optional, Collapsible)"]
|
||||
MainContent["🖥️ Main Content Area (Hosts Widgets/Pages)"]
|
||||
Footer["📎 Footer (Links, Copyright)"]
|
||||
Modal["P M Modal Container"]
|
||||
LoginPage["🔑 Login Page"]
|
||||
LoginWidget["🧩 Login Widget (SRP)"]
|
||||
RegisterButtonWidget["🧩 Button Widget -> Register"]
|
||||
RegisterPage["✍️ Register Page"]
|
||||
RegisterInfoWidget["🧩 Register Info Widget"]
|
||||
LoginButtonWidget["🧩 Button Widget -> Login"]
|
||||
DashboardPage["📊 Dashboard Page"]
|
||||
VariousWidgets["🧩 Various Dashboard Widgets"]
|
||||
ProfilePage["👤 Profile Page"]
|
||||
ProfileInfoWidget["🧩 Profile Info Widget (Pic, Name, ID, DOB)"]
|
||||
PostFeedWidget["🧩 Post Feed Widget"]
|
||||
AdminPage["⚙️ Admin Dashboard"]
|
||||
StudentsWidget["🧩 Students Widget"]
|
||||
TeachersWidget["🧩 Teachers Widget"]
|
||||
ManageStudentsPage["📋 Manage Students Page"]
|
||||
StudentTableWidget["🧩 Student Table Widget"]
|
||||
LoginPage --> Centered
|
||||
RegisterPage --> Centered
|
||||
DashboardPage --> SplitCol
|
||||
ProfilePage --> SplitCol
|
||||
AdminPage --> ThreeCol
|
||||
ManageStudentsPage --> SplitCol
|
||||
LoginPage --> LoginWidget
|
||||
LoginPage --> RegisterButtonWidget
|
||||
RegisterPage --> RegisterInfoWidget
|
||||
RegisterPage --> LoginButtonWidget
|
||||
DashboardPage --> VariousWidgets
|
||||
ProfilePage --> ProfileInfoWidget
|
||||
ProfilePage --> PostFeedWidget
|
||||
AdminPage --> StudentsWidget
|
||||
AdminPage --> TeachersWidget
|
||||
ManageStudentsPage --> StudentTableWidget
|
||||
Header --> Sidebar
|
||||
Sidebar --> MainContent
|
||||
MainContent --> Footer
|
||||
UserProfile["User Profile"] --> Modal
|
||||
Modal --> AccountSettingsWidget["🧩 Account Settings Widget"]
|
||||
MenuLinks["Menu Links"] --> Modal
|
||||
Modal --> UnderConstructionWidget["🧩 'Under Construction' Widget (e.g., Classrooms)"]
|
||||
|
||||
```
|
||||
|
||||
*End of Exhibit A*
|
||||
|
||||
---
|
||||
|
||||
### EXHIBIT B: Backend Architecture Diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph A["🌐 Client (Browser)"]
|
||||
direction LR
|
||||
Frontend["Vite App"]
|
||||
end
|
||||
|
||||
subgraph B["🐳 Docker Environment"]
|
||||
direction TB
|
||||
subgraph C["🚦 Actix Backend Container (lms-backend)"]
|
||||
direction TB
|
||||
ActixServer["🚀 Actix HTTP Server"] --> Middleware["🛡️ Middleware (CORS, Log, Auth*)"]
|
||||
Middleware --> Router["🗺️ Router (handlers.rs)"]
|
||||
Router --> Handlers["⚙️ Route Handlers"]
|
||||
Handlers -- Uses --> DBPool["💾 DB Pool (SQLx)"]
|
||||
Handlers -- Uses --> Models["📝 Data Models (models.rs)"]
|
||||
Handlers -- Uses --> Errors["❗ Error Handling (errors.rs)"]
|
||||
Handlers -- Uses --> AppState["🧠 App State (Temp SRP/Session*)"]
|
||||
Handlers -- Calls --> DBModule["🗃️ DB Logic (db.rs)"]
|
||||
DBModule -- Uses --> DBPool
|
||||
DBModule -- Uses --> Models
|
||||
end
|
||||
subgraph D["🗄️ MariaDB Container (db)"]
|
||||
MariaDB["MariaDB Server"] -- Stores --> LMSData["LMS Database (lms_db)"]
|
||||
end
|
||||
C -- Connects via Network --> D
|
||||
end
|
||||
|
||||
A --> B
|
||||
```
|
||||
<!--
|
||||
note for C "(*) Auth Middleware & Production-Ready AppState (e.g., Redis/DB Session) are TODOs - INTERNAL NOTE"
|
||||
note for D "Data persisted via Docker Volume - INTERNAL NOTE"
|
||||
-->
|
||||
|
||||
*End of Exhibit B*
|
||||
|
||||
---
|
||||
|
||||
### EXHIBIT C: Project Timeline Gantt Chart
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title LMS Development Timeframe (Estimated)
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %Y-%m-%d
|
||||
todayMarker stroke-width:3px,stroke:#E67E22,stroke-opacity:0.8
|
||||
%% Define Sections based on Phases
|
||||
section Phase 1: Planning & Design (Approx. 5 Weeks)
|
||||
Requirements Gathering & Analysis :req, 2024-01-15, 10d
|
||||
System Architecture Design :design, after req, 10d
|
||||
UI/UX Wireframing & Prototyping :wireframe, after design, 10d
|
||||
Tech Stack Finalization & Setup :setup, after design, 5d
|
||||
Phase 1 Review & Approval :p1review, after wireframe, 5d
|
||||
|
||||
section Phase 2: Core Development (Approx. 18 Weeks)
|
||||
Database Schema Implementation :dbdev, after setup, 15d
|
||||
Backend Core & Auth Dev (Rust) :backend, after setup, 50d
|
||||
Frontend Foundational Setup :frontend_setup, after setup, 15d
|
||||
API Development & Integration :api, after backend, 30d
|
||||
Frontend UI Development :frontend_ui, after frontend_setup, 40d
|
||||
Widget System Implementation :frontend_widget, after frontend_setup, 20d
|
||||
Module Development (Iterative) :modules, after api, 60d
|
||||
Initial Dev Testing & Integration :devtest, after modules, 15d
|
||||
|
||||
section Phase 3: Testing & Deployment (Approx. 8 Weeks)
|
||||
Comprehensive QA Testing :qatest, after devtest, 20d
|
||||
User Acceptance Testing (UAT) :uat, after qatest, 10d
|
||||
Feedback Implementation & Bug Fixing :fixes, after uat, 10d
|
||||
Deployment Preparation :deployprep, after fixes, 5d
|
||||
Production Deployment & Go-Live :deploy, after deployprep, 5d
|
||||
|
||||
section Phase 4: Post-Launch (Approx. 4 Weeks)
|
||||
System Monitoring & Stabilization :monitor, after deploy, 10d
|
||||
Admin & Instructor Training :admintrain, after deploy, 10d
|
||||
Learner Onboarding Materials :learnertrain, after admintrain, 10d
|
||||
Final Documentation & Handover :handover, after admintrain, 10d
|
||||
|
||||
%% Milestones (Aligned with phase ends where logical)
|
||||
milestone Phase 1 Complete : 2025-02-16
|
||||
milestone Core Backend Complete : 2025-04-19
|
||||
milestone Core Frontend Complete : 2025-05-31
|
||||
milestone Development Complete : 2025-07-26
|
||||
milestone Testing Complete : 2025-08-23
|
||||
milestone Project Go-Live : 2025-08-30
|
||||
milestone Project Handover : 2025-09-27
|
||||
```
|
||||
|
||||
*Note: Dates are estimates and subject to refinement based on detailed sprint planning and potential scope adjustments defined via the Change Management process (Article 6).*
|
||||
*End of Exhibit C*
|
||||
|
||||
12
eslint.config.js
Normal file
12
eslint.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from "eslint/config";
|
||||
import globals from "globals";
|
||||
import js from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
|
||||
export default defineConfig([
|
||||
{ files: ["**/*.{js,mjs,cjs,ts}"] },
|
||||
{ files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: globals.browser } },
|
||||
{ files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] },
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
31
index.html
31
index.html
@@ -1,13 +1,20 @@
|
||||
<!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>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</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="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||
<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>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,7 +9,14 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.24.0",
|
||||
"eslint": "^9.24.0",
|
||||
"globals": "^16.0.0",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.29.0",
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
1561
pnpm-lock.yaml
generated
Normal file
1561
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
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/after-sunset-minimal-4k-zm-3840x2400.jpg
Normal file
BIN
src/assets/after-sunset-minimal-4k-zm-3840x2400.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1011 KiB |
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 |
BIN
src/assets/images.jpg
Normal file
BIN
src/assets/images.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
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;
|
||||
}
|
||||
}
|
||||
167
src/components/TopBar.ts
Normal file
167
src/components/TopBar.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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 menuItems: Array<HTMLElement>;
|
||||
private profileDropdownVisible: boolean = false;
|
||||
private profileData: ProfileResponseData | null = null;
|
||||
|
||||
constructor() {
|
||||
this.container = createElement('nav');
|
||||
this.menuItems = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addMenuItem(text: string, href: string) {
|
||||
const menuItem = createElement('li');
|
||||
menuItem.classList.add('nav-item');
|
||||
const itemLink = createElement('a');
|
||||
itemLink.classList.add('nav-link');
|
||||
|
||||
itemLink.href = href;
|
||||
itemLink.textContent = text;
|
||||
|
||||
menuItem.appendChild(itemLink);
|
||||
this.menuItems.push(menuItem)
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
for (const item of this.menuItems) {
|
||||
menuPages.appendChild(item);
|
||||
}
|
||||
|
||||
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());
|
||||
};
|
||||
59
src/pages/DashboardPage.ts
Normal file
59
src/pages/DashboardPage.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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', 'welcome-widget');
|
||||
widgetDiv.innerHTML = `
|
||||
<div class="widget-header centered-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', 'quick-links-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 {
|
||||
render(): HTMLElement {
|
||||
const div = createElement('div');
|
||||
div.classList.add('widget', 'placeholder-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();
|
||||
|
||||
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());
|
||||
};
|
||||
259
src/style.css
259
src/style.css
@@ -1,96 +1,185 @@
|
||||
: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;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
@keyframes scroll-bg {
|
||||
from {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
to {
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
.color-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(33, 37, 41, 0.925);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.scrolling-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-image: url('./assets/after-sunset-minimal-4k-zm-3840x2400.jpg');
|
||||
background-repeat: repeat-x;
|
||||
background-size: auto 100%;
|
||||
animation: scroll-bg 30s linear infinite;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
/* --- Widget Styling --- */
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.centered-header {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.widget-header {
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.icon-widget {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* --- Layout Specific Styles --- */
|
||||
.centered-layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.three-column-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.split-column-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 250px 1fr;
|
||||
/* Sidebar and content */
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
|
||||
width: 80%;
|
||||
/* Adjust as needed */
|
||||
max-width: 800px;
|
||||
/* Maximum width */
|
||||
}
|
||||
|
||||
/* --- Responsive adjustments (example) --- */
|
||||
@media (max-width: 768px) {
|
||||
.three-column-layout {
|
||||
grid-template-columns: 1fr;
|
||||
/* Stack columns on smaller screens */
|
||||
}
|
||||
|
||||
.split-column-layout {
|
||||
grid-template-columns: 1fr;
|
||||
/* Stack sidebar and content */
|
||||
}
|
||||
|
||||
.split-column-layout.collapsed-sidebar {
|
||||
grid-template-columns: 1fr;
|
||||
/* Stack even if collapsed */
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Custom styles --- */
|
||||
|
||||
.login-container {
|
||||
background-image: url('./assets/images.jpg');
|
||||
/* Replace with your logo path */
|
||||
background-repeat: no-repeat;
|
||||
background-position: center top;
|
||||
/* Center the logo at the top */
|
||||
background-size: 80x 80px;
|
||||
/* Adjust the size of the logo */
|
||||
padding-top: 60px;
|
||||
/* Add padding to create space for the logo */
|
||||
text-align: center;
|
||||
/* Center text and form elements */
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
button {
|
||||
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;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
display: block;
|
||||
margin: 0 auto 20px;
|
||||
/* Center the logo and add space below */
|
||||
max-width: 100px;
|
||||
/* Adjust the size as needed */
|
||||
width: 80px;
|
||||
/* Set the width of the logo */
|
||||
height: 80px;
|
||||
/* Set the height of the logo */
|
||||
border-radius: 50%;
|
||||
/* Make the logo circular */
|
||||
background-image: url('./assets/images.jpg');
|
||||
/* Replace with your logo path */
|
||||
background-size: cover;
|
||||
/* Cover the entire area */
|
||||
margin: 0 auto 20px;
|
||||
/* Center the logo and add space below */
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
86
src/widgets/LoginWidget.ts
Normal file
86
src/widgets/LoginWidget.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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 = '';
|
||||
|
||||
// Create a logo container
|
||||
const logoContainer = createElement('div') as HTMLElement;
|
||||
logoContainer.classList.add('logo'); // Add class for circular logo
|
||||
this.container.appendChild(logoContainer);
|
||||
|
||||
// Create header
|
||||
const header = createElement('h2');
|
||||
header.classList.add('widget-header');
|
||||
header.textContent = 'Login';
|
||||
this.container.appendChild(header);
|
||||
|
||||
// Create form
|
||||
const form = createElement('form');
|
||||
|
||||
// User ID input group
|
||||
const userIdInputGroup = this.createInputGroup('userId', 'Student ID', 'text', 'Student ID');
|
||||
form.appendChild(userIdInputGroup);
|
||||
|
||||
// Password input group
|
||||
const passwordInputGroup = this.createInputGroup('password', 'Password', 'password', 'Password');
|
||||
form.appendChild(passwordInputGroup);
|
||||
|
||||
// Login button
|
||||
const loginButton = createElement('button');
|
||||
loginButton.type = 'submit';
|
||||
loginButton.classList.add('btn', 'btn-primary');
|
||||
loginButton.textContent = 'Login';
|
||||
form.appendChild(loginButton);
|
||||
|
||||
// Form submission handler
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const userId = (userIdInputGroup.querySelector('input') as HTMLInputElement).value;
|
||||
const password = (passwordInputGroup.querySelector('input') as HTMLInputElement).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(form);
|
||||
return this.container;
|
||||
}
|
||||
|
||||
private createInputGroup(id: string, labelText: string, inputType: string, placeholder: string): HTMLElement {
|
||||
const inputGroup = createElement('div');
|
||||
inputGroup.classList.add('mb-3');
|
||||
|
||||
const label = createElement('label') as HTMLLabelElement;
|
||||
label.classList.add('form-label');
|
||||
label.htmlFor = id;
|
||||
label.textContent = labelText;
|
||||
|
||||
const input = createElement('input') as HTMLInputElement;
|
||||
input.type = inputType;
|
||||
input.classList.add('form-control');
|
||||
input.id = id;
|
||||
input.placeholder = placeholder;
|
||||
|
||||
inputGroup.appendChild(label);
|
||||
inputGroup.appendChild(input);
|
||||
|
||||
return inputGroup;
|
||||
}
|
||||
}
|
||||
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 || "";
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user