From 4088b45da58b77daef32dc7c5649e0a84180904e Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Mon, 2 Sep 2024 22:27:17 +0800 Subject: [PATCH 01/48] Config --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 89497054..996bf843 100644 --- a/package.json +++ b/package.json @@ -27,25 +27,26 @@ "@angular/router": "^14.2.0", "@ng-bootstrap/ng-bootstrap": "12.0.1", "@popperjs/core": "^2.11.4", - "bootstrap": "4.6.1", + "bootstrap": "^5.3.3", "chart.js": "2.9.4", "clipboard": "2.0.10", "ngx-clipboard": "15.0.1", "ngx-toastr": "14.2.2", "nouislider": "15.5.1", "rxjs": "~7.5.0", - "zone.js": "~0.11.4", - "web-animations-js": "2.3.2" + "web-animations-js": "2.3.2", + "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "^14.2.7", + "@angular-devkit/build-angular": "^18.2.2", "@angular/cli": "~14.2.7", "@angular/compiler-cli": "^14.2.0", "@angular/language-service": "14.2.0", "@types/jasmine": "~4.0.0", "@types/jasminewd2": "~2.0.10", "@types/node": "^17.0.21", - "codelyzer": "6.0.2", + "codelyzer": "^6.0.2", + "cross-env": "^7.0.3", "jasmine-core": "~4.4.0", "jasmine-spec-reporter": "~7.0.0", "karma": "~6.4.0", @@ -54,9 +55,8 @@ "karma-coverage-istanbul-reporter": "~3.0.3", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", - "protractor": "7.0.0", + "protractor": "^7.0.0", "ts-node": "~10.9.1", - "typescript": "~4.7.2", - "cross-env": "^7.0.3" + "typescript": "~4.7.2" } } From 8d73ec342d6e4b051789ac769d7cb6805198dee0 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Mon, 2 Sep 2024 22:31:24 +0800 Subject: [PATCH 02/48] config 2 --- package.json | 30 +++++++++++++++--------------- src/test.ts | 6 ------ tsconfig.json | 5 +++-- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 996bf843..0166644b 100644 --- a/package.json +++ b/package.json @@ -12,19 +12,19 @@ }, "private": true, "dependencies": { - "@angular/animations": "^14.2.0", + "@angular/animations": "^15.2.10", "@angular/cdk": "^14.2.0", - "@angular/common": "^14.2.0", - "@angular/compiler": "^14.2.0", - "@angular/core": "^14.2.0", - "@angular/elements": "^14.2.0", - "@angular/forms": "^14.2.0", + "@angular/common": "^15.2.10", + "@angular/compiler": "^15.2.10", + "@angular/core": "^15.2.10", + "@angular/elements": "^15.2.10", + "@angular/forms": "^15.2.10", "@angular/google-maps": "^14.2.0", - "@angular/localize": "^14.2.0", + "@angular/localize": "^15.2.10", "@angular/material": "^14.2.0", - "@angular/platform-browser": "^14.2.0", - "@angular/platform-browser-dynamic": "^14.2.0", - "@angular/router": "^14.2.0", + "@angular/platform-browser": "^15.2.10", + "@angular/platform-browser-dynamic": "^15.2.10", + "@angular/router": "^15.2.10", "@ng-bootstrap/ng-bootstrap": "12.0.1", "@popperjs/core": "^2.11.4", "bootstrap": "^5.3.3", @@ -39,9 +39,9 @@ }, "devDependencies": { "@angular-devkit/build-angular": "^18.2.2", - "@angular/cli": "~14.2.7", - "@angular/compiler-cli": "^14.2.0", - "@angular/language-service": "14.2.0", + "@angular/cli": "~15.2.11", + "@angular/compiler-cli": "^15.2.10", + "@angular/language-service": "15.2.10", "@types/jasmine": "~4.0.0", "@types/jasminewd2": "~2.0.10", "@types/node": "^17.0.21", @@ -57,6 +57,6 @@ "karma-jasmine-html-reporter": "~2.0.0", "protractor": "^7.0.0", "ts-node": "~10.9.1", - "typescript": "~4.7.2" + "typescript": "~4.9.5" } -} +} \ No newline at end of file diff --git a/src/test.ts b/src/test.ts index 16317897..06aa8e41 100644 --- a/src/test.ts +++ b/src/test.ts @@ -7,14 +7,8 @@ import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -declare const require: any; - // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); -// Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); -// And load the modules. -context.keys().map(context); diff --git a/tsconfig.json b/tsconfig.json index e13990db..f4f0089a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,11 +11,12 @@ "typeRoots": [ "node_modules/@types" ], - "target": "es2020", + "target": "ES2022", "module": "es2020", "lib": [ "es2020", "dom" - ] + ], + "useDefineForClassFields": false } } From 814fa0326782782516c6b9b7832abe723bfd07c5 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Mon, 2 Sep 2024 22:33:58 +0800 Subject: [PATCH 03/48] config 3 --- angular.json | 1 - package.json | 28 ++++++++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/angular.json b/angular.json index f98c9205..01388ba3 100644 --- a/angular.json +++ b/angular.json @@ -159,7 +159,6 @@ } } }, - "defaultProject": "argon-dashboard-angular", "schematics": { "@schematics/angular:component": { "styleext": "scss" diff --git a/package.json b/package.json index 0166644b..de0e4a55 100644 --- a/package.json +++ b/package.json @@ -12,19 +12,19 @@ }, "private": true, "dependencies": { - "@angular/animations": "^15.2.10", + "@angular/animations": "^16.2.12", "@angular/cdk": "^14.2.0", - "@angular/common": "^15.2.10", - "@angular/compiler": "^15.2.10", - "@angular/core": "^15.2.10", - "@angular/elements": "^15.2.10", - "@angular/forms": "^15.2.10", + "@angular/common": "^16.2.12", + "@angular/compiler": "^16.2.12", + "@angular/core": "^16.2.12", + "@angular/elements": "^16.2.12", + "@angular/forms": "^16.2.12", "@angular/google-maps": "^14.2.0", - "@angular/localize": "^15.2.10", + "@angular/localize": "^16.2.12", "@angular/material": "^14.2.0", - "@angular/platform-browser": "^15.2.10", - "@angular/platform-browser-dynamic": "^15.2.10", - "@angular/router": "^15.2.10", + "@angular/platform-browser": "^16.2.12", + "@angular/platform-browser-dynamic": "^16.2.12", + "@angular/router": "^16.2.12", "@ng-bootstrap/ng-bootstrap": "12.0.1", "@popperjs/core": "^2.11.4", "bootstrap": "^5.3.3", @@ -35,13 +35,13 @@ "nouislider": "15.5.1", "rxjs": "~7.5.0", "web-animations-js": "2.3.2", - "zone.js": "~0.11.4" + "zone.js": "~0.13.3" }, "devDependencies": { "@angular-devkit/build-angular": "^18.2.2", - "@angular/cli": "~15.2.11", - "@angular/compiler-cli": "^15.2.10", - "@angular/language-service": "15.2.10", + "@angular/cli": "~16.2.15", + "@angular/compiler-cli": "^16.2.12", + "@angular/language-service": "16.2.12", "@types/jasmine": "~4.0.0", "@types/jasminewd2": "~2.0.10", "@types/node": "^17.0.21", From 39c4fa4c317dbd42a21563bf663b433ec30ddbc3 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Mon, 2 Sep 2024 22:36:49 +0800 Subject: [PATCH 04/48] config #4 --- angular.json | 8 ++++---- package.json | 30 +++++++++++++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/angular.json b/angular.json index 01388ba3..66c4600a 100644 --- a/angular.json +++ b/angular.json @@ -82,14 +82,14 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "argon-dashboard-angular:build" + "buildTarget": "argon-dashboard-angular:build" }, "configurations": { "production": { - "browserTarget": "argon-dashboard-angular:build:production" + "buildTarget": "argon-dashboard-angular:build:production" }, "development": { - "browserTarget": "argon-dashboard-angular:build:development" + "buildTarget": "argon-dashboard-angular:build:development" } }, "defaultConfiguration": "development" @@ -97,7 +97,7 @@ "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { - "browserTarget": "argon-dashboard-angular:build" + "buildTarget": "argon-dashboard-angular:build" } }, "test": { diff --git a/package.json b/package.json index de0e4a55..fc5f72d4 100644 --- a/package.json +++ b/package.json @@ -12,19 +12,19 @@ }, "private": true, "dependencies": { - "@angular/animations": "^16.2.12", + "@angular/animations": "^17.3.12", "@angular/cdk": "^14.2.0", - "@angular/common": "^16.2.12", - "@angular/compiler": "^16.2.12", - "@angular/core": "^16.2.12", - "@angular/elements": "^16.2.12", - "@angular/forms": "^16.2.12", + "@angular/common": "^17.3.12", + "@angular/compiler": "^17.3.12", + "@angular/core": "^17.3.12", + "@angular/elements": "^17.3.12", + "@angular/forms": "^17.3.12", "@angular/google-maps": "^14.2.0", - "@angular/localize": "^16.2.12", + "@angular/localize": "^17.3.12", "@angular/material": "^14.2.0", - "@angular/platform-browser": "^16.2.12", - "@angular/platform-browser-dynamic": "^16.2.12", - "@angular/router": "^16.2.12", + "@angular/platform-browser": "^17.3.12", + "@angular/platform-browser-dynamic": "^17.3.12", + "@angular/router": "^17.3.12", "@ng-bootstrap/ng-bootstrap": "12.0.1", "@popperjs/core": "^2.11.4", "bootstrap": "^5.3.3", @@ -35,13 +35,13 @@ "nouislider": "15.5.1", "rxjs": "~7.5.0", "web-animations-js": "2.3.2", - "zone.js": "~0.13.3" + "zone.js": "~0.14.10" }, "devDependencies": { "@angular-devkit/build-angular": "^18.2.2", - "@angular/cli": "~16.2.15", - "@angular/compiler-cli": "^16.2.12", - "@angular/language-service": "16.2.12", + "@angular/cli": "~17.3.9", + "@angular/compiler-cli": "^17.3.12", + "@angular/language-service": "17.3.12", "@types/jasmine": "~4.0.0", "@types/jasminewd2": "~2.0.10", "@types/node": "^17.0.21", @@ -57,6 +57,6 @@ "karma-jasmine-html-reporter": "~2.0.0", "protractor": "^7.0.0", "ts-node": "~10.9.1", - "typescript": "~4.9.5" + "typescript": "~5.4.5" } } \ No newline at end of file From a4aa02569f728e690b37317ebc6d4739594a034c Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Mon, 2 Sep 2024 23:47:37 +0800 Subject: [PATCH 05/48] config #5 --- package.json | 44 ++++++++++++++------------ src/assets/scss/custom/_variables.scss | 6 ++++ src/polyfills.ts | 2 +- src/test.ts | 2 +- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index fc5f72d4..efad2e65 100644 --- a/package.json +++ b/package.json @@ -12,39 +12,41 @@ }, "private": true, "dependencies": { - "@angular/animations": "^17.3.12", - "@angular/cdk": "^14.2.0", - "@angular/common": "^17.3.12", - "@angular/compiler": "^17.3.12", - "@angular/core": "^17.3.12", - "@angular/elements": "^17.3.12", - "@angular/forms": "^17.3.12", - "@angular/google-maps": "^14.2.0", - "@angular/localize": "^17.3.12", - "@angular/material": "^14.2.0", - "@angular/platform-browser": "^17.3.12", - "@angular/platform-browser-dynamic": "^17.3.12", - "@angular/router": "^17.3.12", - "@ng-bootstrap/ng-bootstrap": "12.0.1", + "@angular/animations": "^18.2.2", + "@angular/cdk": "^18.2.2", + "@angular/common": "^18.2.2", + "@angular/compiler": "^18.2.2", + "@angular/core": "^18.2.2", + "@angular/elements": "^18.2.2", + "@angular/forms": "^18.2.2", + "@angular/google-maps": "^18.2.2", + "@angular/localize": "^18.2.2", + "@angular/material": "^18.2.2", + "@angular/platform-browser": "^18.2.2", + "@angular/platform-browser-dynamic": "^18.2.2", + "@angular/router": "^18.2.2", + "@ng-bootstrap/ng-bootstrap": "^17.0.1", "@popperjs/core": "^2.11.4", - "bootstrap": "^5.3.3", + "bootstrap": "^4.3.0", "chart.js": "2.9.4", "clipboard": "2.0.10", "ngx-clipboard": "15.0.1", "ngx-toastr": "14.2.2", "nouislider": "15.5.1", "rxjs": "~7.5.0", + "sass": "^1.77.8", + "sass-loader": "^16.0.1", "web-animations-js": "2.3.2", "zone.js": "~0.14.10" }, "devDependencies": { "@angular-devkit/build-angular": "^18.2.2", - "@angular/cli": "~17.3.9", - "@angular/compiler-cli": "^17.3.12", - "@angular/language-service": "17.3.12", + "@angular/cli": "~18.2.2", + "@angular/compiler-cli": "^18.2.2", + "@angular/language-service": "18.2.2", "@types/jasmine": "~4.0.0", "@types/jasminewd2": "~2.0.10", - "@types/node": "^17.0.21", + "@types/node": "^22.5.2", "codelyzer": "^6.0.2", "cross-env": "^7.0.3", "jasmine-core": "~4.4.0", @@ -57,6 +59,6 @@ "karma-jasmine-html-reporter": "~2.0.0", "protractor": "^7.0.0", "ts-node": "~10.9.1", - "typescript": "~5.4.5" + "typescript": "^5.5.4" } -} \ No newline at end of file +} diff --git a/src/assets/scss/custom/_variables.scss b/src/assets/scss/custom/_variables.scss index 0dad85c5..95c53860 100644 --- a/src/assets/scss/custom/_variables.scss +++ b/src/assets/scss/custom/_variables.scss @@ -108,6 +108,12 @@ $theme-colors: map-merge(( "darker": $darker ), $theme-colors); +// Define the theme-color function (if not already defined by your theme) +@function theme-color($color-name) { + @return map-get($theme-colors, $color-name); +} + + $brand-colors: () !default; $brand-colors: map-merge(( "facebook": $facebook, diff --git a/src/polyfills.ts b/src/polyfills.ts index dc522cfa..18d7bf1c 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -46,7 +46,7 @@ import '@angular/localize/init'; /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone'; // Included with Angular CLI. +import 'zone.js'; // Included with Angular CLI. diff --git a/src/test.ts b/src/test.ts index 06aa8e41..11a3d3cc 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,6 +1,6 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files -import 'zone.js/dist/zone-testing'; +import 'zone.js'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, From 716242ed037c2907a5b824ac537392e98ea3fa33 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Sat, 7 Sep 2024 13:22:48 +0800 Subject: [PATCH 06/48] App authentication and firebase setup --- angular.json | 3 +- package.json | 6 ++- src/app/app.module.ts | 11 ++++- src/app/app.routing.ts | 2 +- .../pages/dashboard/dashboard.component.html | 2 +- src/app/pages/icons/icons.component.html | 2 +- src/app/pages/login/login.component.html | 21 ++++++-- src/app/pages/login/login.component.ts | 45 ++++++++++++++++- src/app/pages/maps/maps.component.html | 2 +- .../pages/register/register.component.html | 46 +++++++++++------ src/app/pages/register/register.component.ts | 49 ++++++++++++++++++- src/app/pages/tables/tables.component.html | 2 +- .../user-profile/user-profile.component.html | 2 +- src/app/services/firestore.service.ts | 23 +++++++++ src/environments/environment.ts | 11 ++++- 15 files changed, 195 insertions(+), 32 deletions(-) create mode 100644 src/app/services/firestore.service.ts diff --git a/angular.json b/angular.json index 66c4600a..f2360de0 100644 --- a/angular.json +++ b/angular.json @@ -17,6 +17,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { + "sourceMap": true, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", @@ -49,7 +50,7 @@ "fonts": true }, "outputHashing": "all", - "sourceMap": false, + "sourceMap": true, "namedChunks": false, "extractLicenses": true, "vendorChunk": false, diff --git a/package.json b/package.json index efad2e65..97cb9b08 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@angular/compiler": "^18.2.2", "@angular/core": "^18.2.2", "@angular/elements": "^18.2.2", + "@angular/fire": "^18.0.1", "@angular/forms": "^18.2.2", "@angular/google-maps": "^18.2.2", "@angular/localize": "^18.2.2", @@ -30,10 +31,11 @@ "bootstrap": "^4.3.0", "chart.js": "2.9.4", "clipboard": "2.0.10", + "firebase": "^10.13.1", "ngx-clipboard": "15.0.1", - "ngx-toastr": "14.2.2", + "ngx-toastr": "^19.0.0", "nouislider": "15.5.1", - "rxjs": "~7.5.0", + "rxjs": "~7.8.0", "sass": "^1.77.8", "sass-loader": "^16.0.1", "web-animations-js": "2.3.2", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 80a35bab..031e737b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -12,6 +12,11 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { AppRoutingModule } from './app.routing'; import { ComponentsModule } from './components/components.module'; +import { environment } from '../environments/environment'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireAuthModule } from '@angular/fire/compat/auth'; +import { AngularFirestoreModule } from '@angular/fire/compat/firestore'; +import { ToastrModule } from 'ngx-toastr'; @NgModule({ @@ -22,7 +27,11 @@ import { ComponentsModule } from './components/components.module'; ComponentsModule, NgbModule, RouterModule, - AppRoutingModule + AppRoutingModule, + AngularFireModule.initializeApp(environment.firebaseConfig), + AngularFireAuthModule, + AngularFirestoreModule , + ToastrModule.forRoot() ], declarations: [ AppComponent, diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 29a17f41..adaa0c82 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -9,7 +9,7 @@ import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component const routes: Routes =[ { path: '', - redirectTo: 'dashboard', + redirectTo: 'login', pathMatch: 'full', }, { path: '', diff --git a/src/app/pages/dashboard/dashboard.component.html b/src/app/pages/dashboard/dashboard.component.html index e9f86f57..5aecd30f 100644 --- a/src/app/pages/dashboard/dashboard.component.html +++ b/src/app/pages/dashboard/dashboard.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/icons/icons.component.html b/src/app/pages/icons/icons.component.html index ab8bfe8f..6b6901b8 100644 --- a/src/app/pages/icons/icons.component.html +++ b/src/app/pages/icons/icons.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/login/login.component.html b/src/app/pages/login/login.component.html index 617f5074..7932cad8 100644 --- a/src/app/pages/login/login.component.html +++ b/src/app/pages/login/login.component.html @@ -1,4 +1,4 @@ -
+
@@ -43,7 +43,12 @@

Welcome!

- +
@@ -51,7 +56,12 @@

Welcome!

- +
@@ -61,7 +71,10 @@

Welcome!

- + +
+
+ {{ errorMessage }}
diff --git a/src/app/pages/login/login.component.ts b/src/app/pages/login/login.component.ts index b1504f1b..3c422183 100644 --- a/src/app/pages/login/login.component.ts +++ b/src/app/pages/login/login.component.ts @@ -1,4 +1,12 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; +import { AngularFirestore } from '@angular/fire/compat/firestore'; // Firestore service +import { Router } from '@angular/router'; // For navigation + +interface User { + idNo: string; + password: string; + // Add other fields as needed +} @Component({ selector: 'app-login', @@ -6,11 +14,46 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; styleUrls: ['./login.component.scss'] }) export class LoginComponent implements OnInit, OnDestroy { - constructor() {} + idNo: string = ''; // ID number input field + password: string = ''; // Password input field + errorMessage: string = ''; // Error message field + + constructor( + private firestore: AngularFirestore, // Firestore service injection + private router: Router // Router service injection + ) {} ngOnInit() { } + ngOnDestroy() { } + login() { + // Fetch the user by idNo from Firestore + this.firestore.collection('Users', ref => ref.where('idNo', '==', this.idNo)) + .get() + .subscribe({ + next: (snapshot) => { + if (!snapshot.empty) { + const user = snapshot.docs[0].data() as User; + + if (user.password === this.password) { + this.errorMessage = ''; + this.router.navigate(['/dashboard']); + } else { + + this.errorMessage = 'Invalid password'; + } + } else { + + this.errorMessage = 'User not found'; + } + }, + error: (error) => { + console.error("Error during login:", error); + this.errorMessage = 'Login failed'; + } + }); + } } diff --git a/src/app/pages/maps/maps.component.html b/src/app/pages/maps/maps.component.html index 71bbb70f..577a152d 100644 --- a/src/app/pages/maps/maps.component.html +++ b/src/app/pages/maps/maps.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/register/register.component.html b/src/app/pages/register/register.component.html index 965900a2..5b10be4c 100644 --- a/src/app/pages/register/register.component.html +++ b/src/app/pages/register/register.component.html @@ -1,10 +1,10 @@ -
+

Welcome!

-

Use these awesome forms to login or create new account in your project for free.

+

Use these awesome forms to login or create a new account in your project for free.

@@ -38,13 +38,21 @@

Welcome!

Or sign up with credentials
-
+
- + +
+
+
+
+
+ +
+
@@ -52,7 +60,15 @@

Welcome!

- + +
+
+
+
+
+ +
+
@@ -60,22 +76,22 @@

Welcome!

- +
-
password strength: strong
-
-
-
- - +
+
+
+
+
+
+ Passwords do not match +
- +
diff --git a/src/app/pages/register/register.component.ts b/src/app/pages/register/register.component.ts index 21cfa54e..0c28c827 100644 --- a/src/app/pages/register/register.component.ts +++ b/src/app/pages/register/register.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { FirestoreService } from '../../services/firestore.service'; +import { NgForm } from '@angular/forms'; @Component({ selector: 'app-register', @@ -6,10 +8,55 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./register.component.scss'] }) export class RegisterComponent implements OnInit { + user = { + idNo: '', + password: '', + name: '', + email: '', + phone: '' + }; + errorMessage = ''; + confirmPassword: string = ''; + passwordMismatch: boolean = false; - constructor() { } + constructor(private firestoreService: FirestoreService) { + } ngOnInit() { } + register() { + if (this.validateForm()) { + this.firestoreService.addUser(this.user) + .then(() => { + // Registration successful + console.log('User registered successfully'); + this.user = { idNo: '', password: '', name: '', email: '', phone: '' }; // Clear form + }) + .catch((error) => { + // Handle error + console.error('Error registering user:', error); + this.errorMessage = 'Registration failed'; + }); + } else { + this.errorMessage = 'Please fill in all fields'; + } + } + + validateForm(): boolean { + return Object.values(this.user).every(field => field.trim() !== ''); + } + + onSubmit(form: NgForm){ + if (this.user.password !== this.confirmPassword) { + this.passwordMismatch = true; + this.errorMessage = 'Passwords do not match'; + } else { + this.passwordMismatch = false; + if (form.valid) { + this.register() + } + } + } + } diff --git a/src/app/pages/tables/tables.component.html b/src/app/pages/tables/tables.component.html index e2efaf4c..7acbed95 100644 --- a/src/app/pages/tables/tables.component.html +++ b/src/app/pages/tables/tables.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/user-profile/user-profile.component.html b/src/app/pages/user-profile/user-profile.component.html index dcd5aa5a..bad493a1 100644 --- a/src/app/pages/user-profile/user-profile.component.html +++ b/src/app/pages/user-profile/user-profile.component.html @@ -1,6 +1,6 @@
- +
diff --git a/src/app/services/firestore.service.ts b/src/app/services/firestore.service.ts new file mode 100644 index 00000000..53af39c1 --- /dev/null +++ b/src/app/services/firestore.service.ts @@ -0,0 +1,23 @@ +// src/app/services/firestore.service.ts +import { Injectable } from '@angular/core'; +import { AngularFirestore } from '@angular/fire/compat/firestore'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class FirestoreService { + + constructor(private firestore: AngularFirestore) { } + + // Method to add user to Firestore + addUser(user: any): Promise { + const userId = this.firestore.createId(); // Generate unique ID + return this.firestore.collection('Users').doc(userId).set(user); + } + + // Method to get a user by idNo + getUserByIdNo(idNo: string): Observable { + return this.firestore.collection('users', ref => ref.where('idNo', '==', idNo)).valueChanges(); + } +} diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7b4f817a..7a708d02 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,16 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + firebaseConfig : { + apiKey: "AIzaSyAor0vsOYA3yiXEFSNL_VyftuTZJTk4a60", + authDomain: "inventoryapp-2162b.firebaseapp.com", + projectId: "inventoryapp-2162b", + storageBucket: "inventoryapp-2162b.appspot.com", + messagingSenderId: "386387705752", + appId: "1:386387705752:web:325a619af00ebe2f8cf5ed", + measurementId: "G-3S6K26FVBL" + } }; /* From 688929a3b1df6509dfd75241413682aceb0ada7b Mon Sep 17 00:00:00 2001 From: Kyra Antonio Date: Sat, 7 Sep 2024 16:23:17 +0800 Subject: [PATCH 07/48] Config for angular 14 - base branch --- angular.json | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/angular.json b/angular.json index f98c9205..9ae836ed 100644 --- a/angular.json +++ b/angular.json @@ -31,7 +31,9 @@ ], "styles": [ "src/styles.scss", - "src/assets/scss/argon.scss" + "src/assets/scss/argon.scss", + "src/assets/vendor/nucleo/css/nucleo.css", + "src/assets/vendor/@fortawesome/fontawesome-free/css/all.min.css" ], "scripts": [ "node_modules/chart.js/dist/Chart.min.js", diff --git a/package.json b/package.json index 89497054..42a0db26 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", - "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm start" + "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install --force && npm start" }, "private": true, "dependencies": { From 0672fa902e61507cd3e5f667d5c0563633896f09 Mon Sep 17 00:00:00 2001 From: Kyra Antonio Date: Sat, 7 Sep 2024 16:27:22 +0800 Subject: [PATCH 08/48] ng generate fix --- angular.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/angular.json b/angular.json index 9ae836ed..56c8adde 100644 --- a/angular.json +++ b/angular.json @@ -10,7 +10,7 @@ "prefix": "app", "schematics": { "@schematics/angular:component": { - "styleext": "scss" + "style": "scss" } }, "architect": { @@ -164,7 +164,7 @@ "defaultProject": "argon-dashboard-angular", "schematics": { "@schematics/angular:component": { - "styleext": "scss" + "style": "scss" } }, "cli": { From 44e6d3f4d5dbcfcc69447cd677c00d1f35d9532a Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Sun, 8 Sep 2024 12:47:39 +0800 Subject: [PATCH 09/48] Login with user role --- package.json | 12 +++-- src/app/app.module.ts | 11 +++- src/app/app.routing.ts | 2 +- src/app/models/user.model.ts | 6 +++ .../pages/dashboard/dashboard.component.ts | 8 +++ src/app/pages/login/login.component.html | 24 +++++++-- src/app/pages/login/login.component.ts | 42 +++++++++++++--- src/app/services/auth.service.ts | 50 +++++++++++++++++++ src/app/services/register.services.ts | 23 +++++++++ src/environments/environment.ts | 12 ++++- 10 files changed, 170 insertions(+), 20 deletions(-) create mode 100644 src/app/models/user.model.ts create mode 100644 src/app/services/auth.service.ts create mode 100644 src/app/services/register.services.ts diff --git a/package.json b/package.json index 42a0db26..6487f66c 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@angular/compiler": "^14.2.0", "@angular/core": "^14.2.0", "@angular/elements": "^14.2.0", + "@angular/fire": "^7.6.1", "@angular/forms": "^14.2.0", "@angular/google-maps": "^14.2.0", "@angular/localize": "^14.2.0", @@ -25,17 +26,18 @@ "@angular/platform-browser": "^14.2.0", "@angular/platform-browser-dynamic": "^14.2.0", "@angular/router": "^14.2.0", - "@ng-bootstrap/ng-bootstrap": "12.0.1", + "@ng-bootstrap/ng-bootstrap": "^13.0.0", "@popperjs/core": "^2.11.4", "bootstrap": "4.6.1", "chart.js": "2.9.4", "clipboard": "2.0.10", + "firebase": "^10.13.1", "ngx-clipboard": "15.0.1", "ngx-toastr": "14.2.2", "nouislider": "15.5.1", "rxjs": "~7.5.0", - "zone.js": "~0.11.4", - "web-animations-js": "2.3.2" + "web-animations-js": "2.3.2", + "zone.js": "~0.11.4" }, "devDependencies": { "@angular-devkit/build-angular": "^14.2.7", @@ -46,6 +48,7 @@ "@types/jasminewd2": "~2.0.10", "@types/node": "^17.0.21", "codelyzer": "6.0.2", + "cross-env": "^7.0.3", "jasmine-core": "~4.4.0", "jasmine-spec-reporter": "~7.0.0", "karma": "~6.4.0", @@ -56,7 +59,6 @@ "karma-jasmine-html-reporter": "~2.0.0", "protractor": "7.0.0", "ts-node": "~10.9.1", - "typescript": "~4.7.2", - "cross-env": "^7.0.3" + "typescript": "~4.7.2" } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 80a35bab..031e737b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -12,6 +12,11 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { AppRoutingModule } from './app.routing'; import { ComponentsModule } from './components/components.module'; +import { environment } from '../environments/environment'; +import { AngularFireModule } from '@angular/fire/compat'; +import { AngularFireAuthModule } from '@angular/fire/compat/auth'; +import { AngularFirestoreModule } from '@angular/fire/compat/firestore'; +import { ToastrModule } from 'ngx-toastr'; @NgModule({ @@ -22,7 +27,11 @@ import { ComponentsModule } from './components/components.module'; ComponentsModule, NgbModule, RouterModule, - AppRoutingModule + AppRoutingModule, + AngularFireModule.initializeApp(environment.firebaseConfig), + AngularFireAuthModule, + AngularFirestoreModule , + ToastrModule.forRoot() ], declarations: [ AppComponent, diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 29a17f41..adaa0c82 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -9,7 +9,7 @@ import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component const routes: Routes =[ { path: '', - redirectTo: 'dashboard', + redirectTo: 'login', pathMatch: 'full', }, { path: '', diff --git a/src/app/models/user.model.ts b/src/app/models/user.model.ts new file mode 100644 index 00000000..276a9dce --- /dev/null +++ b/src/app/models/user.model.ts @@ -0,0 +1,6 @@ +export interface User { + idNo: string; + password: string; + role: string; + // Add other properties as needed + } \ No newline at end of file diff --git a/src/app/pages/dashboard/dashboard.component.ts b/src/app/pages/dashboard/dashboard.component.ts index f1850ae0..0b81209a 100644 --- a/src/app/pages/dashboard/dashboard.component.ts +++ b/src/app/pages/dashboard/dashboard.component.ts @@ -1,3 +1,4 @@ +import { AuthService } from 'src/app/services/auth.service'; import { Component, OnInit } from '@angular/core'; import Chart from 'chart.js'; @@ -21,6 +22,13 @@ export class DashboardComponent implements OnInit { public salesChart; public clicked: boolean = true; public clicked1: boolean = false; + userRole: string | null = null; + + constructor(private authService: AuthService) { + this.userRole = this.authService.getUserRole(); + console.log("test get user on dashboard", this.userRole) + } + ngOnInit() { diff --git a/src/app/pages/login/login.component.html b/src/app/pages/login/login.component.html index 617f5074..f790d2f2 100644 --- a/src/app/pages/login/login.component.html +++ b/src/app/pages/login/login.component.html @@ -43,7 +43,12 @@

Welcome!

- +
@@ -51,17 +56,25 @@

Welcome!

- +
- -
- + +
+
+ {{ errorMessage }}
@@ -77,3 +90,4 @@

Welcome!

+ diff --git a/src/app/pages/login/login.component.ts b/src/app/pages/login/login.component.ts index b1504f1b..1ddde478 100644 --- a/src/app/pages/login/login.component.ts +++ b/src/app/pages/login/login.component.ts @@ -1,16 +1,44 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from 'src/app/services/auth.service'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'] }) -export class LoginComponent implements OnInit, OnDestroy { - constructor() {} +export class LoginComponent implements OnInit { + idNo: string = ''; + password: string = ''; + errorMessage: string = ''; - ngOnInit() { - } - ngOnDestroy() { - } + constructor( + private authService: AuthService, + private router: Router + ) {} + ngOnInit() {} + + async login() { + try { + // Call the login method and wait for it to complete + const success = await this.authService.login(this.idNo, this.password); + if (success) { + // Retrieve the role after successful login + const role = this.authService.getUserRole(); + console.log('User role:', role); + // Redirect based on role + if (role === 'custodian') { + this.router.navigate(['/dashboard']); // Adjust as needed + } else { + this.router.navigate(['/dashboard']); // Adjust as needed + } + } else { + this.errorMessage = 'Login failed'; + } + } catch (error) { + console.error('Login error:', error); + this.errorMessage = 'An error occurred during login'; + } + } } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 00000000..fd1bbcad --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { AngularFirestore } from '@angular/fire/compat/firestore'; +import { BehaviorSubject, Observable } from 'rxjs'; +// import { User } from './user.model'; // Import the User interface +import { User } from '../models/user.model'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private userSubject: BehaviorSubject = new BehaviorSubject(null); + public user$: Observable = this.userSubject.asObservable(); + + constructor(private firestore: AngularFirestore) {} + + // Method to login the user + login(idNo: string, password: string): Promise { + return new Promise((resolve, reject) => { + this.firestore.collection('Users', ref => ref.where('idNo', '==', idNo)) + .get() + .subscribe(snapshot => { + if (!snapshot.empty) { + const user = snapshot.docs[0].data() as User; + if (user.password === password) { + this.userSubject.next(user); + resolve(true); // Login successful + } else { + resolve(false); // Incorrect password + } + } else { + resolve(false); // User not found + } + }, error => { + console.error("Error during login:", error); + reject(false); // Login failed + }); + }); + } + + getUserRole(): string | null { + const user = this.userSubject.value; + return user ? user.role || null : null; + } + + + // Method to log out + logout() { + this.userSubject.next(null); // Clear user data + } +} diff --git a/src/app/services/register.services.ts b/src/app/services/register.services.ts new file mode 100644 index 00000000..71226c15 --- /dev/null +++ b/src/app/services/register.services.ts @@ -0,0 +1,23 @@ +// src/app/services/firestore.service.ts +import { Injectable } from '@angular/core'; +import { AngularFirestore } from '@angular/fire/compat/firestore'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class RegisterService { + + constructor(private firestore: AngularFirestore) { } + + // Method to add user to Firestore + addUser(user: any): Promise { + const userId = this.firestore.createId(); // Generate unique ID + return this.firestore.collection('users').doc(userId).set(user); + } + + // Method to get a user by idNo + getUserByIdNo(idNo: string): Observable { + return this.firestore.collection('users', ref => ref.where('idNo', '==', idNo)).valueChanges(); + } +} diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7b4f817a..bb60e95c 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,17 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + firebaseConfig : { + apiKey: "AIzaSyAor0vsOYA3yiXEFSNL_VyftuTZJTk4a60", + authDomain: "inventoryapp-2162b.firebaseapp.com", + projectId: "inventoryapp-2162b", + storageBucket: "inventoryapp-2162b.appspot.com", + messagingSenderId: "386387705752", + appId: "1:386387705752:web:325a619af00ebe2f8cf5ed", + measurementId: "G-3S6K26FVBL" + } + }; /* From 5423a381802c4bf6c31c1cb22b88bd551ef1217b Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Thu, 12 Sep 2024 14:48:49 +0800 Subject: [PATCH 10/48] basic-navigation --- src/app/app.routing.ts | 13 +++--- .../components/navbar/navbar.component.html | 2 +- src/app/components/navbar/navbar.component.ts | 8 +++- .../components/sidebar/sidebar.component.ts | 14 +++--- src/app/guards/auth.guard.ts | 44 +++++++++++++++++++ src/app/pages/login/login.component.ts | 26 ++++++++--- src/app/services/auth.service.ts | 15 ++++++- src/app/services/navigation.service.ts | 36 +++++++++++++++ 8 files changed, 137 insertions(+), 21 deletions(-) create mode 100644 src/app/guards/auth.guard.ts create mode 100644 src/app/services/navigation.service.ts diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index adaa0c82..ce710ec8 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -5,15 +5,17 @@ import { Routes, RouterModule } from '@angular/router'; import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.component'; import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component'; +import { AuthGuard } from './guards/auth.guard'; const routes: Routes =[ { path: '', - redirectTo: 'login', + redirectTo: 'auth/login', pathMatch: 'full', }, { - path: '', + path: 'admin', component: AdminLayoutComponent, + canActivate: [AuthGuard], children: [ { path: '', @@ -21,8 +23,9 @@ const routes: Routes =[ } ] }, { - path: '', + path: 'auth', component: AuthLayoutComponent, + // canActivate: [AuthGuard], children: [ { path: '', @@ -31,7 +34,7 @@ const routes: Routes =[ ] }, { path: '**', - redirectTo: 'dashboard' + redirectTo: 'auth/login' } ]; @@ -40,7 +43,7 @@ const routes: Routes =[ CommonModule, BrowserModule, RouterModule.forRoot(routes,{ - useHash: true + useHash: false }) ], exports: [ diff --git a/src/app/components/navbar/navbar.component.html b/src/app/components/navbar/navbar.component.html index 533724cf..3a80971e 100644 --- a/src/app/components/navbar/navbar.component.html +++ b/src/app/components/navbar/navbar.component.html @@ -48,7 +48,7 @@
Welcome!
Support - + Logout diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts index e4dd9fb8..a61b6f40 100644 --- a/src/app/components/navbar/navbar.component.ts +++ b/src/app/components/navbar/navbar.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit, ElementRef } from '@angular/core'; import { ROUTES } from '../sidebar/sidebar.component'; import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; import { Router } from '@angular/router'; +import { AuthService } from 'src/app/services/auth.service'; @Component({ selector: 'app-navbar', @@ -12,7 +13,7 @@ export class NavbarComponent implements OnInit { public focus; public listTitles: any[]; public location: Location; - constructor(location: Location, private element: ElementRef, private router: Router) { + constructor(location: Location, private element: ElementRef, private router: Router, private authService: AuthService) { this.location = location; } @@ -33,4 +34,9 @@ export class NavbarComponent implements OnInit { return 'Dashboard'; } + logout(){ + this.authService.logout(); + this.router.navigate(['auth/login']) + } + } diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts index e8e392eb..dde41c78 100644 --- a/src/app/components/sidebar/sidebar.component.ts +++ b/src/app/components/sidebar/sidebar.component.ts @@ -8,13 +8,13 @@ declare interface RouteInfo { class: string; } export const ROUTES: RouteInfo[] = [ - { path: '/dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, - { path: '/icons', title: 'Icons', icon:'ni-planet text-blue', class: '' }, - { path: '/maps', title: 'Maps', icon:'ni-pin-3 text-orange', class: '' }, - { path: '/user-profile', title: 'User profile', icon:'ni-single-02 text-yellow', class: '' }, - { path: '/tables', title: 'Tables', icon:'ni-bullet-list-67 text-red', class: '' }, - { path: '/login', title: 'Login', icon:'ni-key-25 text-info', class: '' }, - { path: '/register', title: 'Register', icon:'ni-circle-08 text-pink', class: '' } + { path: '/admin/dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, + { path: '/admin/icons', title: 'Icons', icon:'ni-planet text-blue', class: '' }, + { path: '/admin/maps', title: 'Maps', icon:'ni-pin-3 text-orange', class: '' }, + { path: '/admin/user-profile', title: 'User profile', icon:'ni-single-02 text-yellow', class: '' }, + { path: '/admin/tables', title: 'Tables', icon:'ni-bullet-list-67 text-red', class: '' }, + { path: '/admin/login', title: 'Login', icon:'ni-key-25 text-info', class: '' }, + { path: '/admin/register', title: 'Register', icon:'ni-circle-08 text-pink', class: '' } ]; @Component({ diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts new file mode 100644 index 00000000..6de08d69 --- /dev/null +++ b/src/app/guards/auth.guard.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; +import { AuthService } from '../services/auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot): boolean { + const isLoggedIn = this.authService.isLoggedIn(); + const userRole = this.authService.getUserRole(); + const routePath = route.routeConfig?.path; + + if (isLoggedIn) { + // User is logged in + if (routePath === 'auth/login') { + // Redirect logged-in users away from the login page + this.router.navigate([`/${userRole}/dashboard`]); + return false; // Prevent further navigation + } + + // Allow access to routes based on user role + if (routePath === userRole || routePath === `${userRole}/dashboard` || routePath === '${userRole}/icons') { + return true; + } + + // Redirect to the respective dashboard if unauthorized + // this.router.navigate([`/${userRole}/dashboard`]); + return false; // Prevent access to the current route + } else { + // User is not logged in + if (routePath !== 'auth/login') { + // Redirect to the login page if not logged in and trying to access protected routes + this.router.navigate(['/auth/login']); + return false; // Prevent access to the current route + } + + // Allow access to the login page + return true; + } + } +} diff --git a/src/app/pages/login/login.component.ts b/src/app/pages/login/login.component.ts index 1ddde478..b3ff7956 100644 --- a/src/app/pages/login/login.component.ts +++ b/src/app/pages/login/login.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from 'src/app/services/auth.service'; +import { NavigationService } from 'src/app/services/navigation.service'; @Component({ selector: 'app-login', @@ -14,7 +15,8 @@ export class LoginComponent implements OnInit { constructor( private authService: AuthService, - private router: Router + private router: Router, + private navigationService: NavigationService ) {} ngOnInit() {} @@ -28,11 +30,7 @@ export class LoginComponent implements OnInit { const role = this.authService.getUserRole(); console.log('User role:', role); // Redirect based on role - if (role === 'custodian') { - this.router.navigate(['/dashboard']); // Adjust as needed - } else { - this.router.navigate(['/dashboard']); // Adjust as needed - } + this.navigationService.redirectBasedOnRole(role) } else { this.errorMessage = 'Login failed'; } @@ -41,4 +39,20 @@ export class LoginComponent implements OnInit { this.errorMessage = 'An error occurred during login'; } } + + private redirectBasedOnRole(role: string | null) { + switch (role) { + case 'admin': + this.router.navigate(['/admin/dashboard']); + break; + case 'student': + this.router.navigate(['/student/dashboard']); + break; + case 'custodian': + this.router.navigate(['/custodian/dashboard']); + break; + default: + this.router.navigate(['/auth/login']); + } + } } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index fd1bbcad..998ffd12 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -23,6 +23,7 @@ export class AuthService { const user = snapshot.docs[0].data() as User; if (user.password === password) { this.userSubject.next(user); + this.saveUserToLocalStorage(user); resolve(true); // Login successful } else { resolve(false); // Incorrect password @@ -41,10 +42,22 @@ export class AuthService { const user = this.userSubject.value; return user ? user.role || null : null; } - + + private saveUserToLocalStorage(user: User | null) { + if (user) { + localStorage.setItem('user', JSON.stringify(user)); + } else { + localStorage.removeItem('user'); + } + } + + isLoggedIn(): boolean { + return !!localStorage.getItem('user'); + } // Method to log out logout() { this.userSubject.next(null); // Clear user data + this.saveUserToLocalStorage(null); // Remove user from localStorage } } diff --git a/src/app/services/navigation.service.ts b/src/app/services/navigation.service.ts new file mode 100644 index 00000000..0f5cc1d0 --- /dev/null +++ b/src/app/services/navigation.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class NavigationService { + + constructor(private authService: AuthService, private router: Router) {} + + // Redirect based on the user's role + redirectBasedOnRole(role: string | null) { + switch (role) { + case 'admin': + this.router.navigate(['/admin/dashboard']); + break; + case 'student': + this.router.navigate(['/student/dashboard']); + break; + case 'custodian': + this.router.navigate(['/custodian/dashboard']); + break; + default: + this.router.navigate(['/auth/login']); + } + } + + // Redirect user if already logged in + redirectIfLoggedIn() { + if (this.authService.isLoggedIn()) { + const role = this.authService.getUserRole(); + this.redirectBasedOnRole(role); + } + } +} From 675d8151f0af1ebf3ad76d8183fab15bf5f3056e Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Thu, 12 Sep 2024 15:07:50 +0800 Subject: [PATCH 11/48] admin-routing --- .../components/sidebar/sidebar.component.ts | 23 +++++++++++-------- .../admin-layout/admin-layout.component.html | 2 +- .../admin-layout/admin-layout.component.ts | 10 ++++++++ src/app/models/routes.model.ts | 6 +++++ 4 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 src/app/models/routes.model.ts diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts index dde41c78..fcf9216a 100644 --- a/src/app/components/sidebar/sidebar.component.ts +++ b/src/app/components/sidebar/sidebar.component.ts @@ -1,12 +1,13 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { RouteInfo } from 'src/app/models/routes.model'; -declare interface RouteInfo { - path: string; - title: string; - icon: string; - class: string; -} +// declare interface RouteInfo { +// path: string; +// title: string; +// icon: string; +// class: string; +// } export const ROUTES: RouteInfo[] = [ { path: '/admin/dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, { path: '/admin/icons', title: 'Icons', icon:'ni-planet text-blue', class: '' }, @@ -26,13 +27,17 @@ export class SidebarComponent implements OnInit { public menuItems: any[]; public isCollapsed = true; + @Input() routes : RouteInfo[]; constructor(private router: Router) { } + ngOnInit() { - this.menuItems = ROUTES.filter(menuItem => menuItem); + if (this.routes) { + this.menuItems = this.routes.filter(menuItem => menuItem); + } this.router.events.subscribe((event) => { this.isCollapsed = true; - }); + }); } } diff --git a/src/app/layouts/admin-layout/admin-layout.component.html b/src/app/layouts/admin-layout/admin-layout.component.html index fe755a73..401d6503 100644 --- a/src/app/layouts/admin-layout/admin-layout.component.html +++ b/src/app/layouts/admin-layout/admin-layout.component.html @@ -1,5 +1,5 @@ - +
diff --git a/src/app/layouts/admin-layout/admin-layout.component.ts b/src/app/layouts/admin-layout/admin-layout.component.ts index ed43be94..db739c6f 100644 --- a/src/app/layouts/admin-layout/admin-layout.component.ts +++ b/src/app/layouts/admin-layout/admin-layout.component.ts @@ -1,4 +1,6 @@ import { Component, OnInit } from '@angular/core'; +import { AdminLayoutRoutes } from './admin-layout.routing'; +import { RouteInfo } from 'src/app/models/routes.model'; @Component({ selector: 'app-admin-layout', @@ -6,10 +8,18 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./admin-layout.component.scss'] }) export class AdminLayoutComponent implements OnInit { + adminRoutes:RouteInfo[] = []; constructor() { } ngOnInit() { + this.adminRoutes = [ + { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, + { path: 'user-profile', title: 'User Profile', icon: 'ni-single-02 text-yellow', class: '' }, + { path: 'tables', title: 'Tables', icon: 'ni-bullet-list-67 text-red', class: '' }, + { path: 'icons', title: 'Icons', icon: 'ni-planet text-blue', class: '' }, + { path: 'maps', title: 'Maps', icon: 'ni-pin-3 text-orange', class: '' } + ]; } } diff --git a/src/app/models/routes.model.ts b/src/app/models/routes.model.ts new file mode 100644 index 00000000..21bbea46 --- /dev/null +++ b/src/app/models/routes.model.ts @@ -0,0 +1,6 @@ +export interface RouteInfo { + path: string; + title: string; + icon: string; + class: string; +} \ No newline at end of file From 06b2a2b340cd721a9be82b3eebf74945b4416452 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Thu, 12 Sep 2024 17:53:58 +0800 Subject: [PATCH 12/48] add student module and logout --- src/app/app.module.ts | 4 ++- src/app/app.routing.ts | 12 +++++++++ src/app/components/navbar/navbar.component.ts | 2 +- src/app/guards/auth.guard.ts | 24 +++++++++--------- .../student-layout.component.html | 11 ++++++++ .../student-layout.component.scss | 0 .../student-layout.component.spec.ts | 23 +++++++++++++++++ .../student-layout.component.ts | 25 +++++++++++++++++++ .../student-layout/student-layout.module.ts | 24 ++++++++++++++++++ .../student-layout/student-layout.routing.ts | 11 ++++++++ 10 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 src/app/layouts/student-layout/student-layout.component.html create mode 100644 src/app/layouts/student-layout/student-layout.component.scss create mode 100644 src/app/layouts/student-layout/student-layout.component.spec.ts create mode 100644 src/app/layouts/student-layout/student-layout.component.ts create mode 100644 src/app/layouts/student-layout/student-layout.module.ts create mode 100644 src/app/layouts/student-layout/student-layout.routing.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 031e737b..cdcd31ae 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -17,6 +17,7 @@ import { AngularFireModule } from '@angular/fire/compat'; import { AngularFireAuthModule } from '@angular/fire/compat/auth'; import { AngularFirestoreModule } from '@angular/fire/compat/firestore'; import { ToastrModule } from 'ngx-toastr'; +import { StudentLayoutComponent } from './layouts/student-layout/student-layout.component'; @NgModule({ @@ -36,7 +37,8 @@ import { ToastrModule } from 'ngx-toastr'; declarations: [ AppComponent, AdminLayoutComponent, - AuthLayoutComponent + AuthLayoutComponent, + StudentLayoutComponent ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index ce710ec8..842d05c6 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -6,6 +6,7 @@ import { Routes, RouterModule } from '@angular/router'; import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.component'; import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component'; import { AuthGuard } from './guards/auth.guard'; +import { StudentLayoutComponent } from './layouts/student-layout/student-layout.component'; const routes: Routes =[ { @@ -22,6 +23,17 @@ const routes: Routes =[ loadChildren: () => import('src/app/layouts/admin-layout/admin-layout.module').then(m => m.AdminLayoutModule) } ] + }, + { + path: 'student', + component: StudentLayoutComponent, + canActivate: [AuthGuard], // Add role guard to check if the user is a student + children: [ + { + path: '', + loadChildren: () => import('./layouts/student-layout/student-layout.module').then(m => m.StudentLayoutModule) + } + ] }, { path: 'auth', component: AuthLayoutComponent, diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts index a61b6f40..a6836027 100644 --- a/src/app/components/navbar/navbar.component.ts +++ b/src/app/components/navbar/navbar.component.ts @@ -36,7 +36,7 @@ export class NavbarComponent implements OnInit { logout(){ this.authService.logout(); - this.router.navigate(['auth/login']) + this.router.navigate(['/auth/login']) } } diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts index 6de08d69..476fc639 100644 --- a/src/app/guards/auth.guard.ts +++ b/src/app/guards/auth.guard.ts @@ -13,31 +13,31 @@ export class AuthGuard implements CanActivate { const userRole = this.authService.getUserRole(); const routePath = route.routeConfig?.path; + console.log('isLoggedIn:', isLoggedIn); + console.log('userRole:', userRole); + console.log('routePath:', routePath); + if (isLoggedIn) { - // User is logged in + // Redirect logged-in users away from the login page if (routePath === 'auth/login') { - // Redirect logged-in users away from the login page this.router.navigate([`/${userRole}/dashboard`]); - return false; // Prevent further navigation + return false; } - + // Allow access to routes based on user role - if (routePath === userRole || routePath === `${userRole}/dashboard` || routePath === '${userRole}/icons') { + if (routePath === userRole || routePath === `${userRole}/dashboard` || routePath === `${userRole}/icons`) { return true; } // Redirect to the respective dashboard if unauthorized - // this.router.navigate([`/${userRole}/dashboard`]); - return false; // Prevent access to the current route + this.router.navigate([`/${userRole}/dashboard`]); + return false; } else { - // User is not logged in + // Redirect to the login page if not logged in and trying to access protected routes if (routePath !== 'auth/login') { - // Redirect to the login page if not logged in and trying to access protected routes this.router.navigate(['/auth/login']); - return false; // Prevent access to the current route + return false; } - - // Allow access to the login page return true; } } diff --git a/src/app/layouts/student-layout/student-layout.component.html b/src/app/layouts/student-layout/student-layout.component.html new file mode 100644 index 00000000..64424358 --- /dev/null +++ b/src/app/layouts/student-layout/student-layout.component.html @@ -0,0 +1,11 @@ + + +
+ + + + +
+ +
+
diff --git a/src/app/layouts/student-layout/student-layout.component.scss b/src/app/layouts/student-layout/student-layout.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/layouts/student-layout/student-layout.component.spec.ts b/src/app/layouts/student-layout/student-layout.component.spec.ts new file mode 100644 index 00000000..ca62e76b --- /dev/null +++ b/src/app/layouts/student-layout/student-layout.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StudentLayoutComponent } from './student-layout.component'; + +describe('StudentLayoutComponent', () => { + let component: StudentLayoutComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ StudentLayoutComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(StudentLayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/layouts/student-layout/student-layout.component.ts b/src/app/layouts/student-layout/student-layout.component.ts new file mode 100644 index 00000000..4e7c8704 --- /dev/null +++ b/src/app/layouts/student-layout/student-layout.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { RouteInfo } from 'src/app/models/routes.model'; + +@Component({ + selector: 'app-student-layout', + templateUrl: './student-layout.component.html', + styleUrls: ['./student-layout.component.scss'] +}) +export class StudentLayoutComponent implements OnInit { + studentRoutes: RouteInfo[] = [] + + constructor() { } + + ngOnInit() { + this.studentRoutes = [ + { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, + { path: 'user-profile', title: 'User Profile', icon: 'ni-single-02 text-yellow', class: '' }, + { path: 'tables', title: 'Tables', icon: 'ni-bullet-list-67 text-red', class: '' }, + { path: 'icons', title: 'Icons', icon: 'ni-planet text-blue', class: '' }, + { path: 'maps', title: 'Maps', icon: 'ni-pin-3 text-orange', class: '' } + ]; + } + + +} diff --git a/src/app/layouts/student-layout/student-layout.module.ts b/src/app/layouts/student-layout/student-layout.module.ts new file mode 100644 index 00000000..976124e5 --- /dev/null +++ b/src/app/layouts/student-layout/student-layout.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { StudentLayoutRoutes } from './student-layout.routing'; +import { RouterModule } from '@angular/router'; +import { StudentLayoutComponent } from './student-layout.component'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ClipboardModule } from 'ngx-clipboard'; + + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + RouterModule.forChild(StudentLayoutRoutes), + FormsModule, + HttpClientModule, + NgbModule, + ClipboardModule + ] +}) +export class StudentLayoutModule { } diff --git a/src/app/layouts/student-layout/student-layout.routing.ts b/src/app/layouts/student-layout/student-layout.routing.ts new file mode 100644 index 00000000..0ea72c39 --- /dev/null +++ b/src/app/layouts/student-layout/student-layout.routing.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { StudentLayoutComponent } from './student-layout.component'; +import { DashboardComponent } from 'src/app/pages/dashboard/dashboard.component'; +import { UserProfileComponent } from 'src/app/pages/user-profile/user-profile.component'; + +export const StudentLayoutRoutes: Routes = [ + { path: 'dashboard', component: DashboardComponent }, + { path: 'user-profile', component: UserProfileComponent }, + +]; \ No newline at end of file From bc8c486ec596c8c1573934a3adeb5312e2ae2d0b Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Thu, 12 Sep 2024 22:02:40 +0800 Subject: [PATCH 13/48] routing and products template --- .../components/cards/item/item.component.html | 8 +++++ .../components/cards/item/item.component.scss | 0 .../cards/item/item.component.spec.ts | 23 ++++++++++++++ .../components/cards/item/item.component.ts | 15 ++++++++++ src/app/components/components.module.ts | 7 +++-- src/app/components/navbar/navbar.component.ts | 30 +++++++++++-------- .../admin-layout/admin-layout.component.ts | 10 ++----- src/app/layouts/routes.ts | 17 +++++++++++ .../student-layout.component.html | 7 +++++ .../student-layout.component.ts | 11 ++----- .../student-layout/student-layout.module.ts | 8 +++-- .../student-layout/student-layout.routing.ts | 4 ++- .../student/products/products.component.html | 2 ++ .../student/products/products.component.scss | 0 .../products/products.component.spec.ts | 23 ++++++++++++++ .../student/products/products.component.ts | 14 +++++++++ 16 files changed, 145 insertions(+), 34 deletions(-) create mode 100644 src/app/components/cards/item/item.component.html create mode 100644 src/app/components/cards/item/item.component.scss create mode 100644 src/app/components/cards/item/item.component.spec.ts create mode 100644 src/app/components/cards/item/item.component.ts create mode 100644 src/app/layouts/routes.ts create mode 100644 src/app/pages/student/products/products.component.html create mode 100644 src/app/pages/student/products/products.component.scss create mode 100644 src/app/pages/student/products/products.component.spec.ts create mode 100644 src/app/pages/student/products/products.component.ts diff --git a/src/app/components/cards/item/item.component.html b/src/app/components/cards/item/item.component.html new file mode 100644 index 00000000..02dd2f08 --- /dev/null +++ b/src/app/components/cards/item/item.component.html @@ -0,0 +1,8 @@ +
+ Card image cap +
+
Card title
+

Some quick example text to build on the card title and make up the bulk of the card's content.

+ Go somewhere +
+
\ No newline at end of file diff --git a/src/app/components/cards/item/item.component.scss b/src/app/components/cards/item/item.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/cards/item/item.component.spec.ts b/src/app/components/cards/item/item.component.spec.ts new file mode 100644 index 00000000..03c464ad --- /dev/null +++ b/src/app/components/cards/item/item.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ItemComponent } from './item.component'; + +describe('ItemComponent', () => { + let component: ItemComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ItemComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/cards/item/item.component.ts b/src/app/components/cards/item/item.component.ts new file mode 100644 index 00000000..e0f2de5d --- /dev/null +++ b/src/app/components/cards/item/item.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-item', + templateUrl: './item.component.html', + styleUrls: ['./item.component.scss'] +}) +export class ItemComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index b1add21a..ca3ea568 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -5,6 +5,7 @@ import { NavbarComponent } from './navbar/navbar.component'; import { FooterComponent } from './footer/footer.component'; import { RouterModule } from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ItemComponent } from './cards/item/item.component'; @NgModule({ imports: [ @@ -15,12 +16,14 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; declarations: [ FooterComponent, NavbarComponent, - SidebarComponent + SidebarComponent, + ItemComponent ], exports: [ FooterComponent, NavbarComponent, - SidebarComponent + SidebarComponent, + ItemComponent ] }) export class ComponentsModule { } diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts index a6836027..f20d3bd5 100644 --- a/src/app/components/navbar/navbar.component.ts +++ b/src/app/components/navbar/navbar.component.ts @@ -1,8 +1,9 @@ -import { Component, OnInit, ElementRef } from '@angular/core'; -import { ROUTES } from '../sidebar/sidebar.component'; +import { Component, OnInit, ElementRef, Input } from '@angular/core'; +// import { ROUTES } from '../sidebar/sidebar.component'; import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; import { Router } from '@angular/router'; import { AuthService } from 'src/app/services/auth.service'; +import { RouteInfo } from 'src/app/models/routes.model'; @Component({ selector: 'app-navbar', @@ -10,6 +11,8 @@ import { AuthService } from 'src/app/services/auth.service'; styleUrls: ['./navbar.component.scss'] }) export class NavbarComponent implements OnInit { + // @Input() routes: RouteInfo[]; + public focus; public listTitles: any[]; public location: Location; @@ -18,20 +21,21 @@ export class NavbarComponent implements OnInit { } ngOnInit() { - this.listTitles = ROUTES.filter(listTitle => listTitle); + // if (this.routes) { + // this.listTitles = this.routes.filter(listTitle => listTitle); + // } else { + // this.listTitles = ROUTES.filter(listTitle => listTitle); + // } + } getTitle(){ - var titlee = this.location.prepareExternalUrl(this.location.path()); - if(titlee.charAt(0) === '#'){ - titlee = titlee.slice( 1 ); - } - - for(var item = 0; item < this.listTitles.length; item++){ - if(this.listTitles[item].path === titlee){ - return this.listTitles[item].title; - } + const titlee = this.location.prepareExternalUrl(this.location.path()); //eg. /student/dashboard + const titleArr = titlee.split("/"); + let title = "Dashboard"; + if (titleArr.length > 0) { + title = titleArr.pop(); } - return 'Dashboard'; + return title; // default is Dashboard } logout(){ diff --git a/src/app/layouts/admin-layout/admin-layout.component.ts b/src/app/layouts/admin-layout/admin-layout.component.ts index db739c6f..1b6b22b2 100644 --- a/src/app/layouts/admin-layout/admin-layout.component.ts +++ b/src/app/layouts/admin-layout/admin-layout.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { AdminLayoutRoutes } from './admin-layout.routing'; import { RouteInfo } from 'src/app/models/routes.model'; +import { ADMIN_ROUTES } from '../routes'; @Component({ selector: 'app-admin-layout', @@ -8,18 +9,11 @@ import { RouteInfo } from 'src/app/models/routes.model'; styleUrls: ['./admin-layout.component.scss'] }) export class AdminLayoutComponent implements OnInit { - adminRoutes:RouteInfo[] = []; + adminRoutes:RouteInfo[] = ADMIN_ROUTES; constructor() { } ngOnInit() { - this.adminRoutes = [ - { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, - { path: 'user-profile', title: 'User Profile', icon: 'ni-single-02 text-yellow', class: '' }, - { path: 'tables', title: 'Tables', icon: 'ni-bullet-list-67 text-red', class: '' }, - { path: 'icons', title: 'Icons', icon: 'ni-planet text-blue', class: '' }, - { path: 'maps', title: 'Maps', icon: 'ni-pin-3 text-orange', class: '' } - ]; } } diff --git a/src/app/layouts/routes.ts b/src/app/layouts/routes.ts new file mode 100644 index 00000000..eab5c1b1 --- /dev/null +++ b/src/app/layouts/routes.ts @@ -0,0 +1,17 @@ +import { RouteInfo } from "../models/routes.model"; + +export const ADMIN_ROUTES: RouteInfo[] = [ + { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, + { path: 'user-profile', title: 'User Profile', icon: 'ni-single-02 text-yellow', class: '' }, + { path: 'tables', title: 'Tables', icon: 'ni-bullet-list-67 text-red', class: '' }, + { path: 'icons', title: 'Icons', icon: 'ni-planet text-blue', class: '' }, + { path: 'maps', title: 'Maps', icon: 'ni-pin-3 text-orange', class: '' } +]; + +export const STUDENT_ROUTES: RouteInfo[] = [ + { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, + { path: 'user-profile', title: 'User Profile', icon: 'ni-single-02 text-yellow', class: '' }, + { path: 'products', title: 'Products', icon: 'ni-bullet-list-67 text-red', class: '' }, + { path: 'icons', title: 'Icons', icon: 'ni-planet text-blue', class: '' }, + { path: 'maps', title: 'Maps', icon: 'ni-pin-3 text-orange', class: '' } +]; diff --git a/src/app/layouts/student-layout/student-layout.component.html b/src/app/layouts/student-layout/student-layout.component.html index 64424358..7768729b 100644 --- a/src/app/layouts/student-layout/student-layout.component.html +++ b/src/app/layouts/student-layout/student-layout.component.html @@ -1,6 +1,13 @@
+ +
+
+
+
+
+
diff --git a/src/app/layouts/student-layout/student-layout.component.ts b/src/app/layouts/student-layout/student-layout.component.ts index 4e7c8704..398b0186 100644 --- a/src/app/layouts/student-layout/student-layout.component.ts +++ b/src/app/layouts/student-layout/student-layout.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { RouteInfo } from 'src/app/models/routes.model'; +import { STUDENT_ROUTES } from '../routes'; @Component({ selector: 'app-student-layout', @@ -7,18 +8,12 @@ import { RouteInfo } from 'src/app/models/routes.model'; styleUrls: ['./student-layout.component.scss'] }) export class StudentLayoutComponent implements OnInit { - studentRoutes: RouteInfo[] = [] + studentRoutes: RouteInfo[] = STUDENT_ROUTES; constructor() { } ngOnInit() { - this.studentRoutes = [ - { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, - { path: 'user-profile', title: 'User Profile', icon: 'ni-single-02 text-yellow', class: '' }, - { path: 'tables', title: 'Tables', icon: 'ni-bullet-list-67 text-red', class: '' }, - { path: 'icons', title: 'Icons', icon: 'ni-planet text-blue', class: '' }, - { path: 'maps', title: 'Maps', icon: 'ni-pin-3 text-orange', class: '' } - ]; + } diff --git a/src/app/layouts/student-layout/student-layout.module.ts b/src/app/layouts/student-layout/student-layout.module.ts index 976124e5..ca919691 100644 --- a/src/app/layouts/student-layout/student-layout.module.ts +++ b/src/app/layouts/student-layout/student-layout.module.ts @@ -3,17 +3,21 @@ import { CommonModule } from '@angular/common'; import { StudentLayoutRoutes } from './student-layout.routing'; import { RouterModule } from '@angular/router'; -import { StudentLayoutComponent } from './student-layout.component'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { ClipboardModule } from 'ngx-clipboard'; +import { ProductsComponent } from 'src/app/pages/student/products/products.component'; +import { ComponentsModule } from 'src/app/components/components.module'; @NgModule({ - declarations: [], + declarations: [ + ProductsComponent + ], imports: [ CommonModule, + ComponentsModule, RouterModule.forChild(StudentLayoutRoutes), FormsModule, HttpClientModule, diff --git a/src/app/layouts/student-layout/student-layout.routing.ts b/src/app/layouts/student-layout/student-layout.routing.ts index 0ea72c39..8e6cb248 100644 --- a/src/app/layouts/student-layout/student-layout.routing.ts +++ b/src/app/layouts/student-layout/student-layout.routing.ts @@ -1,11 +1,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { StudentLayoutComponent } from './student-layout.component'; import { DashboardComponent } from 'src/app/pages/dashboard/dashboard.component'; +import { ProductsComponent } from 'src/app/pages/student/products/products.component'; import { UserProfileComponent } from 'src/app/pages/user-profile/user-profile.component'; export const StudentLayoutRoutes: Routes = [ { path: 'dashboard', component: DashboardComponent }, { path: 'user-profile', component: UserProfileComponent }, + { path: 'products', component: ProductsComponent }, + ]; \ No newline at end of file diff --git a/src/app/pages/student/products/products.component.html b/src/app/pages/student/products/products.component.html new file mode 100644 index 00000000..7520beda --- /dev/null +++ b/src/app/pages/student/products/products.component.html @@ -0,0 +1,2 @@ +

products works!

+ \ No newline at end of file diff --git a/src/app/pages/student/products/products.component.scss b/src/app/pages/student/products/products.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/pages/student/products/products.component.spec.ts b/src/app/pages/student/products/products.component.spec.ts new file mode 100644 index 00000000..623e5fef --- /dev/null +++ b/src/app/pages/student/products/products.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductsComponent } from './products.component'; + +describe('ProductsComponent', () => { + let component: ProductsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProductsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/student/products/products.component.ts b/src/app/pages/student/products/products.component.ts new file mode 100644 index 00000000..8c2c5fcf --- /dev/null +++ b/src/app/pages/student/products/products.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-products', + templateUrl: './products.component.html', + styleUrls: ['./products.component.scss'] +}) +export class ProductsComponent implements OnInit { + constructor() { } + + ngOnInit(): void { + } + +} From 6a4cd853b50972ad53bdcf6b04edc72557b2130c Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Thu, 12 Sep 2024 22:10:48 +0800 Subject: [PATCH 14/48] loginguard --- src/app/guards/auth.guard.ts | 71 ++++++++++++++++++++++++++++++ src/app/guards/login.guard.spec.ts | 16 +++++++ src/app/guards/login.guard.ts | 30 +++++++++++++ src/app/services/auth.service.ts | 65 +++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 src/app/guards/auth.guard.ts create mode 100644 src/app/guards/login.guard.spec.ts create mode 100644 src/app/guards/login.guard.ts create mode 100644 src/app/services/auth.service.ts diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts new file mode 100644 index 00000000..f278858d --- /dev/null +++ b/src/app/guards/auth.guard.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthService } from '../services/auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot): boolean { + const isLoggedIn = this.authService.isLoggedIn(); + const userRole = this.authService.getUserRole(); + const routePath = route.routeConfig?.path; + + if (routePath === 'logout') { + // Handle logout + this.authService.logout(); + this.router.navigate(['/auth/login']); + return false; // Prevent further navigation + } + + if (isLoggedIn) { + // Redirect logged-in users away from login page + if (routePath === 'auth/login') { + this.router.navigate([`/${userRole}/dashboard`]); + return false; + } + + // Allow access to routes based on user role + if (routePath === userRole) { + return true; + } + + // Redirect to the respective dashboard if unauthorized + this.router.navigate([`/${userRole}/dashboard`]); + return false; + } else { + // Redirect not-logged-in users to the login page + if (routePath !== 'auth/login') { + this.router.navigate(['/auth/login']); + } + return false; + } + + // const userRole = this.authService.getUserRole(); // Retrieve the user role + // const isLoggedIn = this.authService.isLoggedIn(); // Check if the user is logged in + + // if (isLoggedIn) { + // // If the user is logged in, check their role and redirect if necessary + // if (route.routeConfig?.path === 'student' && userRole === 'student') { + // return true; // Allow access to student route + // } else if (userRole === 'admin') { + // this.router.navigate(['/admin/dashboard']); // Redirect admin to admin dashboard + // return false; // Block the current navigation + // } else if (userRole === 'custodian') { + // this.router.navigate(['/custodian/dashboard']); // Redirect custodian to custodian dashboard + // return false; // Block the current navigation + // } else { + // // Handle any other roles if necessary + // return false; + // } + // } else { + // // If not logged in, redirect to the login page + // this.router.navigate(['/auth/login']); + // return false; // Block access to the route + // } + } + +} diff --git a/src/app/guards/login.guard.spec.ts b/src/app/guards/login.guard.spec.ts new file mode 100644 index 00000000..38aefa04 --- /dev/null +++ b/src/app/guards/login.guard.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { LoginGuard } from './login.guard'; + +describe('LoginGuard', () => { + let guard: LoginGuard; + + beforeEach(() => { + TestBed.configureTestingModule({}); + guard = TestBed.inject(LoginGuard); + }); + + it('should be created', () => { + expect(guard).toBeTruthy(); + }); +}); diff --git a/src/app/guards/login.guard.ts b/src/app/guards/login.guard.ts new file mode 100644 index 00000000..14ead011 --- /dev/null +++ b/src/app/guards/login.guard.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthService } from '../services/auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class LoginGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router){ + + } + + canActivate(): boolean { + if (this.authService.isLoggedIn()) { + // If the user is logged in, redirect them to their dashboard + const role = this.authService.getUserRole(); + if (role === 'admin') { + this.router.navigate(['/admin/dashboard']); + } else if (role === 'student') { + this.router.navigate(['/student/dashboard']); + } else if (role === 'custodian') { + this.router.navigate(['/custodian/dashboard']); + } + return false; // Prevent navigation to the login page + } + return true; // Allow navigation to the login page if not logged in + } + +} diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 00000000..6bab64b0 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,65 @@ +import { User } from './../models/user.model'; +import { Injectable } from '@angular/core'; +import { AngularFirestore } from '@angular/fire/compat/firestore'; +import { BehaviorSubject, Observable } from 'rxjs'; + + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private userSubject: BehaviorSubject = new BehaviorSubject(null); + public user$: Observable = this.userSubject.asObservable(); + + constructor(private firestore: AngularFirestore) { + // Initialize user data from localStorage if available + const storedUser = localStorage.getItem('authUser'); + if (storedUser) { + this.userSubject.next(JSON.parse(storedUser)); + } + } + + // Method to login the user + login(idNo: string, password: string): Promise { + return new Promise((resolve, reject) => { + this.firestore.collection('Users', ref => ref.where('idNo', '==', idNo)) + .get() + .subscribe(snapshot => { + if (!snapshot.empty) { + const user = snapshot.docs[0].data() as User; + if (user.password === password) { + + // Store user in localStorage + localStorage.setItem('authUser', JSON.stringify(user)); + + this.userSubject.next(user); + resolve(true); // Login successful + } else { + resolve(false); // Incorrect password + } + } else { + resolve(false); // User not found + } + }, error => { + console.error("Error during login:", error); + reject(false); // Login failed + }); + }); + } + + getUserRole(): string | null { + const user = this.userSubject.value; + return user ? user.role || null : null; + } + + // Method to check if user is logged in + isLoggedIn(): boolean { + return this.userSubject.value !== null; + } + + // Method to log out + logout() { + localStorage.removeItem('authUser'); // Clear user data from localStorage + this.userSubject.next(null); // Clear user data + } +} From 94afdbf278b5cb26c31fa65aa63b78eded348684 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Thu, 12 Sep 2024 22:25:53 +0800 Subject: [PATCH 15/48] dupe --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index b634e3bd..7ed4e28a 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "chart.js": "2.9.4", "clipboard": "2.0.10", "firebase": "^10.13.1", - "firebase": "^10.13.1", "ngx-clipboard": "15.0.1", "ngx-toastr": "^19.0.0", "nouislider": "15.5.1", From 32c66e24ec0fe9a9ca0d0629fa6cb2e297a2e7cb Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Fri, 13 Sep 2024 10:13:34 +0800 Subject: [PATCH 16/48] change header color --- src/app/layouts/student-layout/student-layout.component.html | 2 +- src/app/pages/dashboard/dashboard.component.html | 2 +- src/app/pages/icons/icons.component.html | 2 +- src/app/pages/login/login.component.html | 2 +- src/app/pages/maps/maps.component.html | 2 +- src/app/pages/register/register.component.html | 2 +- src/app/pages/tables/tables.component.html | 2 +- src/app/pages/user-profile/user-profile.component.html | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/layouts/student-layout/student-layout.component.html b/src/app/layouts/student-layout/student-layout.component.html index 7768729b..3b878db6 100644 --- a/src/app/layouts/student-layout/student-layout.component.html +++ b/src/app/layouts/student-layout/student-layout.component.html @@ -2,7 +2,7 @@
-
+
diff --git a/src/app/pages/dashboard/dashboard.component.html b/src/app/pages/dashboard/dashboard.component.html index 5aecd30f..788f6ee1 100644 --- a/src/app/pages/dashboard/dashboard.component.html +++ b/src/app/pages/dashboard/dashboard.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/icons/icons.component.html b/src/app/pages/icons/icons.component.html index 6b6901b8..5d58d248 100644 --- a/src/app/pages/icons/icons.component.html +++ b/src/app/pages/icons/icons.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/login/login.component.html b/src/app/pages/login/login.component.html index 73004535..01d53183 100644 --- a/src/app/pages/login/login.component.html +++ b/src/app/pages/login/login.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/maps/maps.component.html b/src/app/pages/maps/maps.component.html index 577a152d..81929f1d 100644 --- a/src/app/pages/maps/maps.component.html +++ b/src/app/pages/maps/maps.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/register/register.component.html b/src/app/pages/register/register.component.html index 5b10be4c..324199c1 100644 --- a/src/app/pages/register/register.component.html +++ b/src/app/pages/register/register.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/tables/tables.component.html b/src/app/pages/tables/tables.component.html index 7acbed95..b631247a 100644 --- a/src/app/pages/tables/tables.component.html +++ b/src/app/pages/tables/tables.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/pages/user-profile/user-profile.component.html b/src/app/pages/user-profile/user-profile.component.html index bad493a1..d781b44f 100644 --- a/src/app/pages/user-profile/user-profile.component.html +++ b/src/app/pages/user-profile/user-profile.component.html @@ -1,6 +1,6 @@
- +
From a236286a0c32edbc890c997ba77e65158f73aca9 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Fri, 13 Sep 2024 11:05:17 +0800 Subject: [PATCH 17/48] fix user login and enter button on login --- src/app/app.routing.ts | 6 ++++-- src/app/guards/login.guard.ts | 22 +++++++++------------- src/app/pages/login/login.component.html | 12 +++++++----- src/app/services/auth.service.ts | 7 ++++++- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 842d05c6..aaf5103f 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -7,10 +7,12 @@ import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.compon import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component'; import { AuthGuard } from './guards/auth.guard'; import { StudentLayoutComponent } from './layouts/student-layout/student-layout.component'; +import { LoginGuard } from './guards/login.guard'; const routes: Routes =[ { path: '', + canActivate: [LoginGuard], redirectTo: 'auth/login', pathMatch: 'full', }, { @@ -37,7 +39,7 @@ const routes: Routes =[ }, { path: 'auth', component: AuthLayoutComponent, - // canActivate: [AuthGuard], + canActivate: [LoginGuard], children: [ { path: '', @@ -46,7 +48,7 @@ const routes: Routes =[ ] }, { path: '**', - redirectTo: 'auth/login' + redirectTo: '/dashboard' } ]; diff --git a/src/app/guards/login.guard.ts b/src/app/guards/login.guard.ts index 14ead011..a43f93f1 100644 --- a/src/app/guards/login.guard.ts +++ b/src/app/guards/login.guard.ts @@ -11,20 +11,16 @@ export class LoginGuard implements CanActivate { } - canActivate(): boolean { - if (this.authService.isLoggedIn()) { - // If the user is logged in, redirect them to their dashboard - const role = this.authService.getUserRole(); - if (role === 'admin') { - this.router.navigate(['/admin/dashboard']); - } else if (role === 'student') { - this.router.navigate(['/student/dashboard']); - } else if (role === 'custodian') { - this.router.navigate(['/custodian/dashboard']); - } - return false; // Prevent navigation to the login page + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + const isLoggedIn = this.authService.isLoggedIn(); + const role = this.authService.getUserRole() + + // Check if user is trying to access the login page and is already logged in + if (isLoggedIn && state.url === '/auth/login') { + this.router.navigate([`/${role}/dashboard`]); + return false; } - return true; // Allow navigation to the login page if not logged in + return true; } } diff --git a/src/app/pages/login/login.component.html b/src/app/pages/login/login.component.html index 01d53183..d69ca714 100644 --- a/src/app/pages/login/login.component.html +++ b/src/app/pages/login/login.component.html @@ -15,6 +15,7 @@

Welcome!

+
@@ -37,7 +38,7 @@

Welcome!

Or sign in with credentials
-
+
@@ -48,7 +49,8 @@

Welcome!

placeholder="Email" type="email" [(ngModel)]="idNo" - name="email"> + name="email" + required>
@@ -61,7 +63,8 @@

Welcome!

placeholder="Password" type="password" [(ngModel)]="password" - name="password"> + name="password" + required>
@@ -71,7 +74,7 @@

Welcome!

- +
{{ errorMessage }} @@ -90,4 +93,3 @@

Welcome!

- diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 998ffd12..137403af 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -39,10 +39,15 @@ export class AuthService { } getUserRole(): string | null { - const user = this.userSubject.value; + const user = this.userSubject.value || this.getUserFromLocalStorage(); return user ? user.role || null : null; } + private getUserFromLocalStorage(): User | null { + const userJson = localStorage.getItem('user'); + return userJson ? JSON.parse(userJson) : null; + } + private saveUserToLocalStorage(user: User | null) { if (user) { localStorage.setItem('user', JSON.stringify(user)); From ce47cc4fdcfa839732d8c8710c10f933c0f4895e Mon Sep 17 00:00:00 2001 From: Kyra Antonio Date: Mon, 16 Sep 2024 22:49:45 +0800 Subject: [PATCH 18/48] fix angular devtools --- angular.json | 34 ++++++++++++++++++---------- package.json | 2 ++ src/app/app.routing.ts | 1 - src/environments/environment.prod.ts | 2 +- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/angular.json b/angular.json index 727a4238..40b20783 100644 --- a/angular.json +++ b/angular.json @@ -43,7 +43,8 @@ }, "configurations": { "production": { - "optimization": { + "optimization": + { "scripts": true, "styles": { "minify": false, @@ -64,21 +65,30 @@ } ] }, + // "development": { + // "vendorChunk": true, + // "extractLicenses": false, + // "sourceMap": true, + // "namedChunks": true, + // "buildOptimizer": false, + // "optimization": + // { + // "scripts": true, + // "styles": { + // "minify": false, + // "inlineCritical": true + // }, + // "fonts": true + // }, + // "outputHashing": "all" + // } "development": { + "buildOptimizer": false, + "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, - "namedChunks": true, - "buildOptimizer": false, - "optimization": { - "scripts": true, - "styles": { - "minify": false, - "inlineCritical": true - }, - "fonts": true - }, - "outputHashing": "all" + "namedChunks": true } } }, diff --git a/package.json b/package.json index 7ed4e28a..34e8ca14 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "ng": "ng", "start": "ng serve", "build": "cross-env CI=false ng build", + "build:prod": "ng build --prod", + "build:dev": "ng build --configuration development", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index aaf5103f..df1faae0 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -12,7 +12,6 @@ import { LoginGuard } from './guards/login.guard'; const routes: Routes =[ { path: '', - canActivate: [LoginGuard], redirectTo: 'auth/login', pathMatch: 'full', }, { diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3612073b..ffe8aed7 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,3 +1,3 @@ export const environment = { - production: true + production: false }; From 9c59f50bf3bcd75391c077a68c1e8b84ce09884c Mon Sep 17 00:00:00 2001 From: Kyra Antonio Date: Tue, 17 Sep 2024 00:51:50 +0800 Subject: [PATCH 19/48] custodian: base (routes, table component, products page) --- src/app/app.module.ts | 4 +- src/app/app.routing.ts | 16 +- src/app/components/components.module.ts | 7 +- src/app/components/table/table.component.html | 345 ++++++++++++++++++ src/app/components/table/table.component.scss | 0 .../components/table/table.component.spec.ts | 23 ++ src/app/components/table/table.component.ts | 10 + .../custodian-layout.component.html | 18 + .../custodian-layout.component.scss | 0 .../custodian-layout.component.spec.ts | 23 ++ .../custodian-layout.component.ts | 18 + .../custodian-layout.module.ts | 29 ++ .../custodian-layout.routing.ts | 13 + src/app/layouts/routes.ts | 7 + .../products/products.component.html | 1 + .../products/products.component.scss | 0 .../products/products.component.spec.ts | 23 ++ .../custodian/products/products.component.ts | 10 + 18 files changed, 543 insertions(+), 4 deletions(-) create mode 100644 src/app/components/table/table.component.html create mode 100644 src/app/components/table/table.component.scss create mode 100644 src/app/components/table/table.component.spec.ts create mode 100644 src/app/components/table/table.component.ts create mode 100644 src/app/layouts/custodian-layout/custodian-layout.component.html create mode 100644 src/app/layouts/custodian-layout/custodian-layout.component.scss create mode 100644 src/app/layouts/custodian-layout/custodian-layout.component.spec.ts create mode 100644 src/app/layouts/custodian-layout/custodian-layout.component.ts create mode 100644 src/app/layouts/custodian-layout/custodian-layout.module.ts create mode 100644 src/app/layouts/custodian-layout/custodian-layout.routing.ts create mode 100644 src/app/pages/custodian/products/products.component.html create mode 100644 src/app/pages/custodian/products/products.component.scss create mode 100644 src/app/pages/custodian/products/products.component.spec.ts create mode 100644 src/app/pages/custodian/products/products.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index cdcd31ae..2f20140a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -18,6 +18,7 @@ import { AngularFireAuthModule } from '@angular/fire/compat/auth'; import { AngularFirestoreModule } from '@angular/fire/compat/firestore'; import { ToastrModule } from 'ngx-toastr'; import { StudentLayoutComponent } from './layouts/student-layout/student-layout.component'; +import { CustodianLayoutComponent } from './layouts/custodian-layout/custodian-layout.component'; @NgModule({ @@ -38,7 +39,8 @@ import { StudentLayoutComponent } from './layouts/student-layout/student-layout. AppComponent, AdminLayoutComponent, AuthLayoutComponent, - StudentLayoutComponent + StudentLayoutComponent, + CustodianLayoutComponent ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index df1faae0..f28b1e82 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -1,3 +1,4 @@ +import { CustodianLayoutModule } from './layouts/custodian-layout/custodian-layout.module'; import { NgModule } from '@angular/core'; import { CommonModule, } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; @@ -8,6 +9,7 @@ import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component import { AuthGuard } from './guards/auth.guard'; import { StudentLayoutComponent } from './layouts/student-layout/student-layout.component'; import { LoginGuard } from './guards/login.guard'; +import { CustodianLayoutComponent } from './layouts/custodian-layout/custodian-layout.component'; const routes: Routes =[ { @@ -35,7 +37,19 @@ const routes: Routes =[ loadChildren: () => import('./layouts/student-layout/student-layout.module').then(m => m.StudentLayoutModule) } ] - }, { + }, + { + path: 'custodian', + component: CustodianLayoutComponent, + canActivate: [AuthGuard], // Add role guard to check if the user is a student + children: [ + { + path: '', + loadChildren: () => import('./layouts/custodian-layout/custodian-layout.module').then(m => m.CustodianLayoutModule) + } + ] + }, + { path: 'auth', component: AuthLayoutComponent, canActivate: [LoginGuard], diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index ca3ea568..576c025b 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -6,6 +6,7 @@ import { FooterComponent } from './footer/footer.component'; import { RouterModule } from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { ItemComponent } from './cards/item/item.component'; +import { TableComponent } from './table/table.component'; @NgModule({ imports: [ @@ -17,13 +18,15 @@ import { ItemComponent } from './cards/item/item.component'; FooterComponent, NavbarComponent, SidebarComponent, - ItemComponent + ItemComponent, + TableComponent ], exports: [ FooterComponent, NavbarComponent, SidebarComponent, - ItemComponent + ItemComponent, + TableComponent ] }) export class ComponentsModule { } diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html new file mode 100644 index 00000000..c7272797 --- /dev/null +++ b/src/app/components/table/table.component.html @@ -0,0 +1,345 @@ + +
+ +
+
+
+
+

{{ title }}

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProjectBudgetStatusUsersCompletion
+
+ + Image placeholder + +
+ Argon Design System +
+
+
+ $2,500 USD + + + pending + + + + +
+ 60% +
+
+
+
+
+
+
+ +
+
+ + Image placeholder + +
+ Angular Now UI Kit PRO +
+
+
+ $1,800 USD + + + completed + + + + +
+ 100% +
+
+
+
+
+
+
+ +
+
+ + Image placeholder + +
+ Black Dashboard +
+
+
+ $3,150 USD + + + delayed + + + + +
+ 72% +
+
+
+
+
+
+
+ +
+
+ + Image placeholder + +
+ React Material Dashboard +
+
+
+ $4,400 USD + + + on schedule + + + + +
+ 90% +
+
+
+
+
+
+
+ +
+
+ + Image placeholder + +
+ Vue Paper UI Kit PRO +
+
+
+ $2,200 USD + + + completed + + + + +
+ 100% +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ \ No newline at end of file diff --git a/src/app/components/table/table.component.scss b/src/app/components/table/table.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/table/table.component.spec.ts b/src/app/components/table/table.component.spec.ts new file mode 100644 index 00000000..3c9a5e48 --- /dev/null +++ b/src/app/components/table/table.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableComponent } from './table.component'; + +describe('TableComponent', () => { + let component: TableComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TableComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts new file mode 100644 index 00000000..d8cbe3d7 --- /dev/null +++ b/src/app/components/table/table.component.ts @@ -0,0 +1,10 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-table', + templateUrl: './table.component.html', + styleUrl: './table.component.scss' +}) +export class TableComponent { + @Input() title: string = 'Table Title' +} diff --git a/src/app/layouts/custodian-layout/custodian-layout.component.html b/src/app/layouts/custodian-layout/custodian-layout.component.html new file mode 100644 index 00000000..f9e7b69e --- /dev/null +++ b/src/app/layouts/custodian-layout/custodian-layout.component.html @@ -0,0 +1,18 @@ + + +
+ +
+
+
+
+
+
+ + + + +
+ +
+
\ No newline at end of file diff --git a/src/app/layouts/custodian-layout/custodian-layout.component.scss b/src/app/layouts/custodian-layout/custodian-layout.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/layouts/custodian-layout/custodian-layout.component.spec.ts b/src/app/layouts/custodian-layout/custodian-layout.component.spec.ts new file mode 100644 index 00000000..710a24cc --- /dev/null +++ b/src/app/layouts/custodian-layout/custodian-layout.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CustodianLayoutComponent } from './custodian-layout.component'; + +describe('CustodianLayoutComponent', () => { + let component: CustodianLayoutComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CustodianLayoutComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CustodianLayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/layouts/custodian-layout/custodian-layout.component.ts b/src/app/layouts/custodian-layout/custodian-layout.component.ts new file mode 100644 index 00000000..a86e2e9a --- /dev/null +++ b/src/app/layouts/custodian-layout/custodian-layout.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; +import { CUSTODIAN_ROUTES } from '../routes'; +import { RouteInfo } from 'src/app/models/routes.model'; + +@Component({ + selector: 'app-custodian-layout', + templateUrl: './custodian-layout.component.html', + styleUrl: './custodian-layout.component.scss' +}) + +export class CustodianLayoutComponent implements OnInit{ + custodianRoutes: RouteInfo[] = CUSTODIAN_ROUTES; + + ngOnInit(): void { + throw new Error('Method not implemented.'); + } + +} diff --git a/src/app/layouts/custodian-layout/custodian-layout.module.ts b/src/app/layouts/custodian-layout/custodian-layout.module.ts new file mode 100644 index 00000000..f9ba0588 --- /dev/null +++ b/src/app/layouts/custodian-layout/custodian-layout.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ComponentsModule } from 'src/app/components/components.module'; +import { ProductsComponent } from 'src/app/pages/custodian/products/products.component'; +import { CustodianLayoutRoutes } from './custodian-layout.routing'; +import { ClipboardModule } from 'ngx-clipboard'; +import { RouterModule } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + + + +@NgModule({ + declarations: [ + ProductsComponent + ], + imports: [ + CommonModule, + ComponentsModule, + RouterModule.forChild(CustodianLayoutRoutes), + FormsModule, + HttpClientModule, + NgbModule, + ClipboardModule + ] +}) + +export class CustodianLayoutModule { } diff --git a/src/app/layouts/custodian-layout/custodian-layout.routing.ts b/src/app/layouts/custodian-layout/custodian-layout.routing.ts new file mode 100644 index 00000000..7abc37fc --- /dev/null +++ b/src/app/layouts/custodian-layout/custodian-layout.routing.ts @@ -0,0 +1,13 @@ +import { Routes } from '@angular/router'; +import { DashboardComponent } from 'src/app/pages/dashboard/dashboard.component'; +import { ProductsComponent } from 'src/app/pages/custodian/products/products.component'; +import { UserProfileComponent } from 'src/app/pages/user-profile/user-profile.component'; + +export const CustodianLayoutRoutes: Routes = [ + { path: 'dashboard', component: DashboardComponent }, + { path: 'orders', component: DashboardComponent }, + { path: 'transactions', component: UserProfileComponent }, + { path: 'products', component: ProductsComponent }, + + +]; \ No newline at end of file diff --git a/src/app/layouts/routes.ts b/src/app/layouts/routes.ts index eab5c1b1..37416056 100644 --- a/src/app/layouts/routes.ts +++ b/src/app/layouts/routes.ts @@ -15,3 +15,10 @@ export const STUDENT_ROUTES: RouteInfo[] = [ { path: 'icons', title: 'Icons', icon: 'ni-planet text-blue', class: '' }, { path: 'maps', title: 'Maps', icon: 'ni-pin-3 text-orange', class: '' } ]; + +export const CUSTODIAN_ROUTES: RouteInfo[] = [ + { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, + { path: 'orders', title: 'Orders', icon: 'ni-basket text-yellow', class: '' }, + { path: 'transactions', title: 'Transactions', icon: 'ni-single-copy-04 text-blue', class: '' }, + { path: 'products', title: 'Products', icon: 'ni-app text-red', class: '' }, +]; \ No newline at end of file diff --git a/src/app/pages/custodian/products/products.component.html b/src/app/pages/custodian/products/products.component.html new file mode 100644 index 00000000..7ec6d6cb --- /dev/null +++ b/src/app/pages/custodian/products/products.component.html @@ -0,0 +1 @@ + diff --git a/src/app/pages/custodian/products/products.component.scss b/src/app/pages/custodian/products/products.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/pages/custodian/products/products.component.spec.ts b/src/app/pages/custodian/products/products.component.spec.ts new file mode 100644 index 00000000..3a184dc5 --- /dev/null +++ b/src/app/pages/custodian/products/products.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductsComponent } from './products.component'; + +describe('ProductsComponent', () => { + let component: ProductsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/custodian/products/products.component.ts b/src/app/pages/custodian/products/products.component.ts new file mode 100644 index 00000000..9e630af8 --- /dev/null +++ b/src/app/pages/custodian/products/products.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-products', + templateUrl: './products.component.html', + styleUrl: './products.component.scss' +}) +export class ProductsComponent { + +} From 59756d456c86f2bb8c95e3cb3ac7273bb5882df4 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Tue, 17 Sep 2024 00:53:27 +0800 Subject: [PATCH 20/48] student-products-display --- .../components/cards/item/item.component.html | 101 ++++++++++++- .../components/cards/item/item.component.scss | 125 ++++++++++++++++ .../components/cards/item/item.component.ts | 140 +++++++++++++++++- .../student-layout.component.html | 6 +- src/app/models/shoppingcart.model.ts | 9 ++ .../student/products/products.component.html | 9 +- .../student/products/products.component.scss | 9 ++ .../student/products/products.component.ts | 10 +- src/app/services/products.service.ts | 37 +++++ src/app/services/shoppingcart.service.ts | 44 ++++++ 10 files changed, 473 insertions(+), 17 deletions(-) create mode 100644 src/app/models/shoppingcart.model.ts create mode 100644 src/app/services/products.service.ts create mode 100644 src/app/services/shoppingcart.service.ts diff --git a/src/app/components/cards/item/item.component.html b/src/app/components/cards/item/item.component.html index 02dd2f08..ab4ccbd6 100644 --- a/src/app/components/cards/item/item.component.html +++ b/src/app/components/cards/item/item.component.html @@ -1,8 +1,93 @@ -
- Card image cap -
-
Card title
-

Some quick example text to build on the card title and make up the bulk of the card's content.

- Go somewhere -
-
\ No newline at end of file +
+ Product image + + +
+
{{ product.name }}
+
+ + +
+
+ {{ variantItem.name }} +
+
+ + +
+ +

Price: {{ selectedVariant.price | currency }}

+ + +
+
+ {{ size.name }} +
+
+ + +
+

Out of stock

+

Available: {{ selectedSize.quantity }}

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

Price: {{ totalSetPrice | currency }}

+
+
+ {{ size.name }} +
+
+ + +
+

Out of stock

+

Available: {{ selectedSize.quantity }}

+
+ + +
+ + + +
+
+ + +
+ + +
+
diff --git a/src/app/components/cards/item/item.component.scss b/src/app/components/cards/item/item.component.scss index e69de29b..d40213e1 100644 --- a/src/app/components/cards/item/item.component.scss +++ b/src/app/components/cards/item/item.component.scss @@ -0,0 +1,125 @@ +/* Style for the variant squares */ +.variant-square { + width: 100px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid #007bff; /* Border color */ + margin: 5px; + cursor: pointer; + font-size: 14px; + text-align: center; + background-color: #f8f9fa; /* Background color */ + transition: background-color 0.3s, border-color 0.3s; + border-radius: 5px; + color: black; + } + + .variant-square:hover { + background-color: #e9ecef; /* Hover background color */ + } + + .variant-square.selected { + background-color: #007bff; /* Selected background color */ + color: white; + border-color: #0056b3; /* Selected border color */ + border-radius: 5px; + } + + /* Existing size square styles */ + .size-square { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid #007bff; /* Border color */ + margin: 5px; + cursor: pointer; + font-size: 14px; + text-align: center; + background-color: #f8f9fa; /* Background color */ + transition: background-color 0.3s, border-color 0.3s; + } + + + .size-square.selected { + background-color: #007bff; /* Selected background color */ + color: white; + border-color: #0056b3; /* Selected border color */ + } + + .size-square.out-of-stock { + background-color: #d6d6d6; /* Out of stock background color */ + cursor: not-allowed; + color: #6c757d; + border-color: #6c757d; /* Out of stock border color */ + } + + .quantity-container { + display: flex; + align-items: center; + justify-content: center; + margin: 10px; + } + + .quantity-input { + width: 60px; + text-align: center; + margin: 0 10px; + } + + .quantity-button { + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + background-color: aquamarine; + } + + .quantity-button:disabled{ + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + background-color: gray; + } + + .buy-buttons{ + display: flex; + justify-content: center; + align-items: center; + padding: 1rem; + gap: 2px; + + #addToCart{ + background-color: aqua; + border: aqua; + border-radius: 5px; + display: inline-block; + white-space: nowrap; /* Prevent text from wrapping */ + overflow: hidden; + font-size: 12px; + text-align: center; + padding: 10px; + } + #purchase{ + background-color:#007bff; + border: #007bff; + margin: 0.5rem; + border-radius: 5px; + display: inline-block; + font-size: 12px; + } + } + + .card-style{ + height: 500px; + max-height: 500px; + overflow-y: scroll; + } + + \ No newline at end of file diff --git a/src/app/components/cards/item/item.component.ts b/src/app/components/cards/item/item.component.ts index e0f2de5d..5d6b6fe4 100644 --- a/src/app/components/cards/item/item.component.ts +++ b/src/app/components/cards/item/item.component.ts @@ -1,4 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { CartItem } from 'src/app/models/shoppingcart.model'; +import { ShoppingCartService } from 'src/app/services/shoppingcart.service'; @Component({ selector: 'app-item', @@ -6,10 +8,144 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./item.component.scss'] }) export class ItemComponent implements OnInit { + @Input() product: any; // Product input from the parent component + selectedVariant: any = null; // Stores the selected variant + selectedSize: any = null; // Stores the selected size for individual variants + selectedSetSize: any = null; // Stores the selected size for sets + variantsWithSet: any[] = []; // Stores the variants including the "Set" option + availableSetSizes: any[] = []; + totalSetPrice: number = 0; + quantity: number = 1; // Initial quantity + maxQuantity: number = 0; // Max quantity based on available stock - constructor() { } + constructor(private shoppingcartService: ShoppingCartService){ + + } ngOnInit(): void { + if (this.product && this.product.Variants) { + this.initializeVariants(); + } + } + + // Initialize the variants and add the "Set" option if isSet is true + initializeVariants(): void { + this.variantsWithSet = [...this.product.Variants]; + + if (this.product.isSet) { + // Add a special "Set" option + this.variantsWithSet.unshift({ name: 'Set' }); + } + } + + // Function to handle variant selection + selectVariant(variant: any): void { + this.selectedVariant = variant; + + if (variant.name === 'Set') { + // For "Set" variants, calculate available sizes and total price + this.calculateSetSizes(); + this.calculateSetPrice(); + this.selectedSize = null; // Ensure we don't select sizes for individual variants + this.selectedSetSize = null; // Ensure we reset the set size + this.maxQuantity = 0; + } else if(variant.sizes && variant.sizes.length > 0) { + // For individual variants, set the sizes and price + this.availableSetSizes = []; // Clear set sizes to avoid conflicts + this.selectedSize = null; // Reset the selected size + this.maxQuantity = 0; // Reset the max quantity + this.quantity = 1; // Reset quantity to 1 + } else{ + this.maxQuantity = variant.quantity; // Set max quantity based on the variant + this.quantity = 1; // Reset quantity to 1 + this.selectedSize = null; // No size selection needed + } + } + + // Function to handle individual variant size selection + selectSize(size: any): void { + this.selectedSize = size; + this.maxQuantity = size.quantity; // Set the max quantity based on size's available quantity + this.quantity = 1; // Reset the quantity to 1 when a new size is selected + } + + // Function to handle set size selection + selectSetSize(size: any): void { + this.selectedSetSize = size; + this.maxQuantity = size.quantity; // Set the max quantity based on size's available quantity + this.quantity = 1; // Reset the quantity to 1 when a new size is selected + } + + // Calculate the available sizes for the "Set" option + calculateSetSizes(): void { + const sizeAvailability: { [key: string]: { name: string, available: boolean, quantity: number } } = {}; + + // Loop through all variants and their sizes + this.product.Variants.forEach(variant => { + variant.sizes.forEach(size => { + if (!sizeAvailability[size.name]) { + // Initialize size availability + sizeAvailability[size.name] = { name: size.name, available: size.quantity > 0, quantity: size.quantity }; + } + + // If any variant has 0 quantity for this size, mark it as unavailable + if (size.quantity === 0) { + sizeAvailability[size.name].available = false; + } + + // Update quantity to reflect the smallest available quantity + sizeAvailability[size.name].quantity = Math.min(sizeAvailability[size.name].quantity, size.quantity); + }); + }); + + // Convert the sizeAvailability object to an array of sizes + this.availableSetSizes = Object.values(sizeAvailability); + } + + // Calculate the total price for the "Set" + calculateSetPrice(): void { + this.totalSetPrice = this.product.Variants.reduce((total: number, variant: any) => { + return total + (variant.price || 0); + }, 0); + } + + // Increase quantity, respecting the max quantity for the selected size + increaseQuantity(): void { + if (this.quantity < this.maxQuantity) { + this.quantity++; + } + } + + // Decrease quantity, but not below 1 + decreaseQuantity(): void { + if (this.quantity > 1) { + this.quantity--; + } + } + + addToCart(): void { + const selectedItemPrice = this.selectedVariant + ? (this.selectedVariant.name === 'Set' ? this.totalSetPrice : this.selectedVariant.price) + : this.product.price; + + const cartItem: CartItem = { + productCode: this.product.code, + name: this.product.name, + size: this.selectedSize ? this.selectedSize.name : "", + quantity: this.quantity, + price: selectedItemPrice, + total: selectedItemPrice * this.quantity, + variantName: this.selectedVariant.name + }; + + // Add the selected product to the cart using the shopping cart service + this.shoppingcartService.addToCart(cartItem) + .then(() => { + console.log('Product added to cart'); + }) + .catch((error) => { + console.error('Error adding product to cart:', error); + }); } } diff --git a/src/app/layouts/student-layout/student-layout.component.html b/src/app/layouts/student-layout/student-layout.component.html index 3b878db6..f495a455 100644 --- a/src/app/layouts/student-layout/student-layout.component.html +++ b/src/app/layouts/student-layout/student-layout.component.html @@ -2,7 +2,7 @@
-
+
@@ -12,7 +12,7 @@ -
+
diff --git a/src/app/models/shoppingcart.model.ts b/src/app/models/shoppingcart.model.ts new file mode 100644 index 00000000..44fea387 --- /dev/null +++ b/src/app/models/shoppingcart.model.ts @@ -0,0 +1,9 @@ +export interface CartItem { + productCode: string; + name: string; + size?: string; + quantity: number; + price: number; + total: number; + variantName: string; + } \ No newline at end of file diff --git a/src/app/pages/student/products/products.component.html b/src/app/pages/student/products/products.component.html index 7520beda..b95967e5 100644 --- a/src/app/pages/student/products/products.component.html +++ b/src/app/pages/student/products/products.component.html @@ -1,2 +1,7 @@ -

products works!

- \ No newline at end of file +

No products available.

+
+ +
+
+ +
\ No newline at end of file diff --git a/src/app/pages/student/products/products.component.scss b/src/app/pages/student/products/products.component.scss index e69de29b..511c6a0d 100644 --- a/src/app/pages/student/products/products.component.scss +++ b/src/app/pages/student/products/products.component.scss @@ -0,0 +1,9 @@ +.products-container { + display: flex; /* Use flexbox for horizontal layout */ + flex-wrap: wrap; /* Allow items to wrap onto multiple lines if needed */ + gap:10px; /* Optional: Add spacing between items */ + padding: 10px; + } + + /* Style for each item to control its size */ + \ No newline at end of file diff --git a/src/app/pages/student/products/products.component.ts b/src/app/pages/student/products/products.component.ts index 8c2c5fcf..cd4e6be5 100644 --- a/src/app/pages/student/products/products.component.ts +++ b/src/app/pages/student/products/products.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { ProductsService } from 'src/app/services/products.service'; @Component({ selector: 'app-products', @@ -6,9 +7,14 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { - constructor() { } + products: any[] = []; + + constructor(private productService: ProductsService) { } ngOnInit(): void { + this.productService.getProducts().subscribe(data => { + this.products = data; + console.log(this.products) + }); } - } diff --git a/src/app/services/products.service.ts b/src/app/services/products.service.ts new file mode 100644 index 00000000..515ea97f --- /dev/null +++ b/src/app/services/products.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { AngularFirestore } from '@angular/fire/compat/firestore'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ProductsService { + + constructor(private firestore: AngularFirestore) { } + + // Method to get all products + getProducts(): Observable { + return this.firestore.collection('Products').valueChanges(); + } + + // Method to get a specific product by ID + getProductById(productId: string): Observable { + return this.firestore.collection('Products').doc(productId).valueChanges(); + } + + // Method to add a product + addProduct(product: any): Promise { + const id = this.firestore.createId(); + return this.firestore.collection('Products').doc(id).set(product); + } + + // Method to update a product + updateProduct(productId: string, product: any): Promise { + return this.firestore.collection('Products').doc(productId).update(product); + } + + // Method to delete a product + deleteProduct(productId: string): Promise { + return this.firestore.collection('Products').doc(productId).delete(); + } +} diff --git a/src/app/services/shoppingcart.service.ts b/src/app/services/shoppingcart.service.ts new file mode 100644 index 00000000..75a09b3f --- /dev/null +++ b/src/app/services/shoppingcart.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { AngularFirestore, DocumentReference } from '@angular/fire/compat/firestore'; +import { CartItem } from '../models/shoppingcart.model'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ShoppingCartService { + + constructor(private firestore: AngularFirestore) { } + + // Method to add a product to the shopping cart + addToCart(cartItem: CartItem): Promise { + return this.firestore.collection('ShoppingCart').add(cartItem) + .then((docRef: DocumentReference) => { + console.log('Product added to cart successfully with ID:', docRef.id); + return docRef; + }) + .catch((error) => { + console.error('Error adding product to cart:', error); + throw error; + }); + } + + // Method to retrieve the cart items + getCartItems(): Observable { + return this.firestore.collection('ShoppingCart').valueChanges(); + } + + // Method to remove an item from the cart by its document ID + removeFromCart(cartItemId: string): Promise { + return this.firestore.collection('ShoppingCart').doc(cartItemId).delete() + .then(() => console.log('Item removed from cart')) + .catch((error) => console.error('Error removing item from cart:', error)); + } + + // Method to update quantity or other fields of an item in the cart + updateCartItem(cartItemId: string, updatedFields: Partial): Promise { + return this.firestore.collection('ShoppingCart').doc(cartItemId).update(updatedFields) + .then(() => console.log('Cart item updated')) + .catch((error) => console.error('Error updating cart item:', error)); + } +} From 761c927c5f8ee81109255907ed3220830f8aa922 Mon Sep 17 00:00:00 2001 From: Kyra Antonio Date: Tue, 17 Sep 2024 22:19:34 +0800 Subject: [PATCH 21/48] custodian: models, product table display --- src/app/components/navbar/navbar.component.ts | 2 +- .../components/sidebar/sidebar.component.ts | 2 +- src/app/components/table/table.component.html | 251 ++---------------- src/app/components/table/table.component.ts | 16 +- .../admin-layout/admin-layout.component.ts | 2 +- .../custodian-layout.component.ts | 4 +- .../custodian-layout.module.ts | 1 + src/app/layouts/routes.ts | 2 +- .../student-layout.component.ts | 2 +- src/app/models/product.model.ts | 19 ++ src/app/models/{ => util}/routes.model.ts | 0 src/app/models/util/table.model.ts | 21 ++ .../products/products.component.html | 7 +- .../custodian/products/products.component.ts | 27 +- .../student/products/products.component.ts | 1 + src/app/services/products.service.ts | 3 +- src/app/services/util/table.service.ts | 70 +++++ 17 files changed, 184 insertions(+), 246 deletions(-) create mode 100644 src/app/models/product.model.ts rename src/app/models/{ => util}/routes.model.ts (100%) create mode 100644 src/app/models/util/table.model.ts create mode 100644 src/app/services/util/table.service.ts diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts index f20d3bd5..dbf2d085 100644 --- a/src/app/components/navbar/navbar.component.ts +++ b/src/app/components/navbar/navbar.component.ts @@ -3,7 +3,7 @@ import { Component, OnInit, ElementRef, Input } from '@angular/core'; import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; import { Router } from '@angular/router'; import { AuthService } from 'src/app/services/auth.service'; -import { RouteInfo } from 'src/app/models/routes.model'; +import { RouteInfo } from 'src/app/models/util/routes.model'; @Component({ selector: 'app-navbar', diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts index fcf9216a..7f143f21 100644 --- a/src/app/components/sidebar/sidebar.component.ts +++ b/src/app/components/sidebar/sidebar.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { RouteInfo } from 'src/app/models/routes.model'; +import { RouteInfo } from 'src/app/models/util/routes.model'; // declare interface RouteInfo { // path: string; diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index c7272797..f86412b1 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -11,131 +11,28 @@

{{ title }}

- + + @for (col of dataColumns; track $index) { + + } + - - - - - - - - - - - - - - - - + @for (item of data; track $index) { + + @for (col of dataColumns; track $index) { + + + + } + + } - - - - - - - - - - - - - - - -
Project {{ col.Header || col.field }}
-
- - Image placeholder - -
- Argon Design System -
-
-
- $2,500 USD - - - pending - - - - -
- 60% -
-
-
-
-
-
-
- -
-
- - Image placeholder - -
- Angular Now UI Kit PRO -
-
-
- $1,800 USD - - - completed - - - - -
- 100% -
-
-
-
-
-
-
- -
{{ item[col.field] }}
@@ -148,7 +45,7 @@

{{ title }}

- $3,150 USD + 3,150 USD @@ -194,122 +91,6 @@

{{ title }}

-
- - Image placeholder - -
- React Material Dashboard -
-
-
- $4,400 USD - - - on schedule - - - - -
- 90% -
-
-
-
-
-
-
- -
-
- - Image placeholder - -
- Vue Paper UI Kit PRO -
-
-
- $2,200 USD - - - completed - - - - -
- 100% -
-
-
-
-
-
-
- -
diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index d8cbe3d7..743fe394 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -1,4 +1,5 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, SimpleChanges } from '@angular/core'; +import { TableColumn, ColumnType } from 'src/app/models/util/table.model'; @Component({ selector: 'app-table', @@ -7,4 +8,17 @@ import { Component, Input } from '@angular/core'; }) export class TableComponent { @Input() title: string = 'Table Title' + @Input() dataColumns: TableColumn[] + @Input() data: any[] + + // displayedData: any[] + + // ngOnChanges(changes: SimpleChanges): void { + // if (changes['data']) { + // this.displayedData = changes['data'].currentValue; + // } + // } + ngOnInit() { + console.log(this.data); + } } diff --git a/src/app/layouts/admin-layout/admin-layout.component.ts b/src/app/layouts/admin-layout/admin-layout.component.ts index 1b6b22b2..7f50de29 100644 --- a/src/app/layouts/admin-layout/admin-layout.component.ts +++ b/src/app/layouts/admin-layout/admin-layout.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { AdminLayoutRoutes } from './admin-layout.routing'; -import { RouteInfo } from 'src/app/models/routes.model'; +import { RouteInfo } from 'src/app/models/util/routes.model'; import { ADMIN_ROUTES } from '../routes'; @Component({ diff --git a/src/app/layouts/custodian-layout/custodian-layout.component.ts b/src/app/layouts/custodian-layout/custodian-layout.component.ts index a86e2e9a..3d755304 100644 --- a/src/app/layouts/custodian-layout/custodian-layout.component.ts +++ b/src/app/layouts/custodian-layout/custodian-layout.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { CUSTODIAN_ROUTES } from '../routes'; -import { RouteInfo } from 'src/app/models/routes.model'; +import { RouteInfo } from 'src/app/models/util/routes.model'; @Component({ selector: 'app-custodian-layout', @@ -12,7 +12,7 @@ export class CustodianLayoutComponent implements OnInit{ custodianRoutes: RouteInfo[] = CUSTODIAN_ROUTES; ngOnInit(): void { - throw new Error('Method not implemented.'); + // throw new Error('Method not implemented.'); } } diff --git a/src/app/layouts/custodian-layout/custodian-layout.module.ts b/src/app/layouts/custodian-layout/custodian-layout.module.ts index f9ba0588..85392920 100644 --- a/src/app/layouts/custodian-layout/custodian-layout.module.ts +++ b/src/app/layouts/custodian-layout/custodian-layout.module.ts @@ -8,6 +8,7 @@ import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TableComponent } from 'src/app/components/table/table.component'; diff --git a/src/app/layouts/routes.ts b/src/app/layouts/routes.ts index 37416056..34c1a5b9 100644 --- a/src/app/layouts/routes.ts +++ b/src/app/layouts/routes.ts @@ -1,4 +1,4 @@ -import { RouteInfo } from "../models/routes.model"; +import { RouteInfo } from "../models/util/routes.model"; export const ADMIN_ROUTES: RouteInfo[] = [ { path: 'dashboard', title: 'Dashboard', icon: 'ni-tv-2 text-primary', class: '' }, diff --git a/src/app/layouts/student-layout/student-layout.component.ts b/src/app/layouts/student-layout/student-layout.component.ts index 398b0186..2998327a 100644 --- a/src/app/layouts/student-layout/student-layout.component.ts +++ b/src/app/layouts/student-layout/student-layout.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { RouteInfo } from 'src/app/models/routes.model'; +import { RouteInfo } from 'src/app/models/util/routes.model'; import { STUDENT_ROUTES } from '../routes'; @Component({ diff --git a/src/app/models/product.model.ts b/src/app/models/product.model.ts new file mode 100644 index 00000000..628d4d90 --- /dev/null +++ b/src/app/models/product.model.ts @@ -0,0 +1,19 @@ +export interface Product { + imageUrl?: string, // not required + code: string, + name: string, + Variants: Variant[], + isSet: boolean +} + +export interface Variant { + name: string, + price: number, + quantity?: number, + size?: Size[] +} + +export interface Size { + name: string, + quantity: number +} \ No newline at end of file diff --git a/src/app/models/routes.model.ts b/src/app/models/util/routes.model.ts similarity index 100% rename from src/app/models/routes.model.ts rename to src/app/models/util/routes.model.ts diff --git a/src/app/models/util/table.model.ts b/src/app/models/util/table.model.ts new file mode 100644 index 00000000..763b28b3 --- /dev/null +++ b/src/app/models/util/table.model.ts @@ -0,0 +1,21 @@ +export enum ColumnType { + hidden = 'hidden', + custom = 'custom', + string = 'string', + text = 'text', + number = 'number', + date = 'date', + range = 'range', + checkbox = 'checkbox', + dropDown = 'dropdown', +} + +export interface TableColumn { + field: string; // keyof T; // it's not keyof T, because it can be dot-notation like user.location.id + header?: string; + type?: ColumnType; + hidden?: boolean; + editable?: boolean; + sortable?: boolean; // Defaults to true + filterable?: boolean; // Defaults to true +} \ No newline at end of file diff --git a/src/app/pages/custodian/products/products.component.html b/src/app/pages/custodian/products/products.component.html index 7ec6d6cb..8c767775 100644 --- a/src/app/pages/custodian/products/products.component.html +++ b/src/app/pages/custodian/products/products.component.html @@ -1 +1,6 @@ - + + diff --git a/src/app/pages/custodian/products/products.component.ts b/src/app/pages/custodian/products/products.component.ts index 9e630af8..7d01f50c 100644 --- a/src/app/pages/custodian/products/products.component.ts +++ b/src/app/pages/custodian/products/products.component.ts @@ -1,4 +1,8 @@ -import { Component } from '@angular/core'; +import { ProductsService } from 'src/app/services/products.service'; +import { Component, OnInit } from '@angular/core'; +import { TableColumn } from 'src/app/models/util/table.model'; +import { Product } from 'src/app/models/product.model'; +import { TableService } from 'src/app/services/util/table.service'; @Component({ selector: 'app-products', @@ -7,4 +11,25 @@ import { Component } from '@angular/core'; }) export class ProductsComponent { + dataColumns: TableColumn[]; + products: Product[]; + + constructor(private productService: ProductsService, private tableService: TableService) { + } + + ngOnInit() { + this.productService.getProducts().subscribe(data => { + this.products = data; + this.dataColumns = this.tableService.generateDataColumns(this.products); + + console.log(data); + console.log(this.dataColumns); + }); + + if (this.products) { + } + console.log(this.products); + + } + } diff --git a/src/app/pages/student/products/products.component.ts b/src/app/pages/student/products/products.component.ts index cd4e6be5..6d516142 100644 --- a/src/app/pages/student/products/products.component.ts +++ b/src/app/pages/student/products/products.component.ts @@ -16,5 +16,6 @@ export class ProductsComponent implements OnInit { this.products = data; console.log(this.products) }); + } } diff --git a/src/app/services/products.service.ts b/src/app/services/products.service.ts index 515ea97f..ef5dd7ba 100644 --- a/src/app/services/products.service.ts +++ b/src/app/services/products.service.ts @@ -1,6 +1,7 @@ -import { Injectable } from '@angular/core'; +import { Injectable, signal } from '@angular/core'; import { AngularFirestore } from '@angular/fire/compat/firestore'; import { Observable } from 'rxjs'; +import { Product } from '../models/product.model'; @Injectable({ providedIn: 'root' diff --git a/src/app/services/util/table.service.ts b/src/app/services/util/table.service.ts new file mode 100644 index 00000000..49f5cf10 --- /dev/null +++ b/src/app/services/util/table.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@angular/core'; +import { ColumnType, TableColumn } from 'src/app/models/util/table.model'; + +@Injectable({ + providedIn: 'root' +}) +export class TableService { + + constructor() { } + + generateDataColumns(data: any[]): TableColumn[] { + if (data.length === 0) return []; + + const columns = this.extractColumns(data); + return columns; + } + + private extractColumns(data: any[]): TableColumn[] { + const columnsSet = new Set(); + + data.forEach(item => { + this.collectFields(item, columnsSet); + }); + + return Array.from(columnsSet) + .filter(field => !field.includes('.')) // Exclude fields with a dot + .map(field => ({ + field, + header: this.capitalizeFirstLetter(field), + type: this.detectFieldType(data[0][field]), + sortable: true, + filterable: true + })); + } + + private collectFields(obj: any, columnsSet: Set, parentKey: string = ''): void { + if (typeof obj !== 'object' || obj === null) return; + + Object.keys(obj).forEach(key => { + const fullKey = parentKey ? `${parentKey}.${key}` : key; + columnsSet.add(fullKey); + + if (Array.isArray(obj[key])) { + obj[key].forEach(item => this.collectFields(item, columnsSet, fullKey)); + } else if (typeof obj[key] === 'object') { + this.collectFields(obj[key], columnsSet, fullKey); + } + }); + } + + detectFieldType(value: any): ColumnType { + if (Array.isArray(value)) { + return ColumnType.custom; // You might use 'custom' for arrays, or define another type + } else if (typeof value === 'object' && value !== null) { + return ColumnType.custom; // You might use 'custom' for objects, or define another type + } else if (typeof value === 'boolean') { + return ColumnType.checkbox; + } else if (typeof value === 'number') { + return ColumnType.number; + } else if (value instanceof Date) { + return ColumnType.date; // Assuming you handle date fields this way + } else { + return ColumnType.string; // Default to string + } + } + // Helper function to capitalize the first letter of the field name for display purposes + capitalizeFirstLetter(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); + } +} From 95c71ac2ec9e1b430a4949c67541dfcd752ea9fc Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Tue, 17 Sep 2024 22:43:27 +0800 Subject: [PATCH 22/48] fix product display and add to cart function --- src/app/app.routing.ts | 8 +- .../display-product.component.html | 12 ++ .../display-product.component.scss | 12 ++ .../display-product.component.spec.ts | 23 +++ .../display-product.component.ts | 23 +++ .../components/cards/item/item.component.html | 156 +++++++++--------- .../components/cards/item/item.component.scss | 24 ++- .../components/cards/item/item.component.ts | 93 ++++++----- src/app/components/components.module.ts | 7 +- src/app/components/navbar/navbar.component.ts | 8 +- src/app/models/orders.model.ts | 12 ++ src/app/models/shoppingcart.model.ts | 1 + .../student/products/products.component.html | 19 ++- .../student/products/products.component.ts | 48 +++++- src/app/services/auth.service.ts | 5 + src/app/services/products.service.ts | 8 +- 16 files changed, 322 insertions(+), 137 deletions(-) create mode 100644 src/app/components/cards/display-product/display-product.component.html create mode 100644 src/app/components/cards/display-product/display-product.component.scss create mode 100644 src/app/components/cards/display-product/display-product.component.spec.ts create mode 100644 src/app/components/cards/display-product/display-product.component.ts create mode 100644 src/app/models/orders.model.ts diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index df1faae0..1e672f99 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -8,6 +8,7 @@ import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component import { AuthGuard } from './guards/auth.guard'; import { StudentLayoutComponent } from './layouts/student-layout/student-layout.component'; import { LoginGuard } from './guards/login.guard'; +import { ItemComponent } from './components/cards/item/item.component'; const routes: Routes =[ { @@ -43,9 +44,12 @@ const routes: Routes =[ { path: '', loadChildren: () => import('src/app/layouts/auth-layout/auth-layout.module').then(m => m.AuthLayoutModule) - } + }, + { path: 'products/:code', component: ItemComponent }, ] - }, { + }, + + { path: '**', redirectTo: '/dashboard' } diff --git a/src/app/components/cards/display-product/display-product.component.html b/src/app/components/cards/display-product/display-product.component.html new file mode 100644 index 00000000..8a799eb0 --- /dev/null +++ b/src/app/components/cards/display-product/display-product.component.html @@ -0,0 +1,12 @@ + +
+
+
+ Product image +
+
+
{{ product.name }}
+

{{product.price}}

+
+
+
diff --git a/src/app/components/cards/display-product/display-product.component.scss b/src/app/components/cards/display-product/display-product.component.scss new file mode 100644 index 00000000..5cbef445 --- /dev/null +++ b/src/app/components/cards/display-product/display-product.component.scss @@ -0,0 +1,12 @@ +.card-style{ + height: 400px; + min-height: 400px; + overflow: hidden; + } + + .img-container{ + display: flex; + justify-content: center; + border-color: aqua; + border: 1px; + } diff --git a/src/app/components/cards/display-product/display-product.component.spec.ts b/src/app/components/cards/display-product/display-product.component.spec.ts new file mode 100644 index 00000000..5c039c02 --- /dev/null +++ b/src/app/components/cards/display-product/display-product.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DisplayProductComponent } from './display-product.component'; + +describe('DisplayProductComponent', () => { + let component: DisplayProductComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DisplayProductComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DisplayProductComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/cards/display-product/display-product.component.ts b/src/app/components/cards/display-product/display-product.component.ts new file mode 100644 index 00000000..a6760503 --- /dev/null +++ b/src/app/components/cards/display-product/display-product.component.ts @@ -0,0 +1,23 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +@Component({ + selector: 'app-display-product', + templateUrl: './display-product.component.html', + styleUrl: './display-product.component.scss' +}) +export class DisplayProductComponent implements OnInit{ + + + + @Input() product: any; + @Output() select = new EventEmitter(); + + // Emit the selected product when clicked + onSelect() { + this.select.emit(this.product); + } + + ngOnInit(): void { + } + +} diff --git a/src/app/components/cards/item/item.component.html b/src/app/components/cards/item/item.component.html index ab4ccbd6..57f35b5e 100644 --- a/src/app/components/cards/item/item.component.html +++ b/src/app/components/cards/item/item.component.html @@ -1,93 +1,101 @@ -
- Product image +
+
+
+ Product image +
+ + - -
-
{{ product.name }}
-
- -
-
- {{ variantItem.name }} + +
+ +
+
{{ product.name }}
-
- -
- -

Price: {{ selectedVariant.price | currency }}

- - -
-
- {{ size.name }} + +
+
+ {{ variantItem.name }}
- -
-

Out of stock

-

Available: {{ selectedSize.quantity }}

-
+ +
+ +

Price: {{ selectedVariant.price | currency }}

- -
- - - -
-
+ +
+
+ {{ size.name }} +
+
- -
- - -
- - - -
-
+ +
+

Out of stock

+

Available: {{ selectedSize.quantity }}

+
- -
-

Price: {{ totalSetPrice | currency }}

-
-
- {{ size.name }} + +
+ + +
- -
-

Out of stock

-

Available: {{ selectedSize.quantity }}

+ +
+
+ + + +
- -
- - - + +
+

Price: {{ totalSetPrice | currency }}

+
+
+ {{ size.name }} +
+
+ + +
+

Out of stock

+

Available: {{ selectedSize.quantity }}

+
+ + +
+ + + +
-
- -
- - + +
+ + +
+
diff --git a/src/app/components/cards/item/item.component.scss b/src/app/components/cards/item/item.component.scss index d40213e1..a2089e71 100644 --- a/src/app/components/cards/item/item.component.scss +++ b/src/app/components/cards/item/item.component.scss @@ -117,9 +117,27 @@ } .card-style{ - height: 500px; - max-height: 500px; - overflow-y: scroll; + display: flex; + width: 100%; + border: 1px solid #ddd; + border-radius: 5px; + overflow: hidden; + } + + + .card-img-top { + width: 100%; + height: auto; + object-fit: cover; + } + + .card-title{ + font-size: x-large; } + .box{ + box-sizing: border-box; + border: 3px solid transparent; + background-clip:padding-box; + } \ No newline at end of file diff --git a/src/app/components/cards/item/item.component.ts b/src/app/components/cards/item/item.component.ts index 5d6b6fe4..c73cb709 100644 --- a/src/app/components/cards/item/item.component.ts +++ b/src/app/components/cards/item/item.component.ts @@ -1,5 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { CartItem } from 'src/app/models/shoppingcart.model'; +import { AuthService } from 'src/app/services/auth.service'; import { ShoppingCartService } from 'src/app/services/shoppingcart.service'; @Component({ @@ -8,115 +10,121 @@ import { ShoppingCartService } from 'src/app/services/shoppingcart.service'; styleUrls: ['./item.component.scss'] }) export class ItemComponent implements OnInit { - @Input() product: any; // Product input from the parent component - selectedVariant: any = null; // Stores the selected variant - selectedSize: any = null; // Stores the selected size for individual variants - selectedSetSize: any = null; // Stores the selected size for sets - variantsWithSet: any[] = []; // Stores the variants including the "Set" option + @Input() product: any; + selectedVariant: any = null; + selectedSize: any = null; + selectedSetSize: any = null; + variantsWithSet: any[] = []; availableSetSizes: any[] = []; totalSetPrice: number = 0; - quantity: number = 1; // Initial quantity - maxQuantity: number = 0; // Max quantity based on available stock - - constructor(private shoppingcartService: ShoppingCartService){ - + quantity: number = 1; + maxQuantity: number = 0; + productCode: string | null = null; + + constructor( + private shoppingcartService: ShoppingCartService, + private route: ActivatedRoute, + private authService: AuthService){ } ngOnInit(): void { if (this.product && this.product.Variants) { this.initializeVariants(); + if (this.variantsWithSet.length > 0) { + this.selectVariant(this.variantsWithSet[0]); + + // If the first variant has sizes, auto-select the first size + if (this.selectedVariant && this.selectedVariant.sizes && this.selectedVariant.sizes.length > 0) { + this.selectSize(this.selectedVariant.sizes[0]); + } + } } } - // Initialize the variants and add the "Set" option if isSet is true initializeVariants(): void { this.variantsWithSet = [...this.product.Variants]; if (this.product.isSet) { - // Add a special "Set" option this.variantsWithSet.unshift({ name: 'Set' }); } } - // Function to handle variant selection selectVariant(variant: any): void { this.selectedVariant = variant; if (variant.name === 'Set') { - // For "Set" variants, calculate available sizes and total price this.calculateSetSizes(); this.calculateSetPrice(); - this.selectedSize = null; // Ensure we don't select sizes for individual variants - this.selectedSetSize = null; // Ensure we reset the set size + this.selectedSize = null; + this.selectedSetSize = null; this.maxQuantity = 0; } else if(variant.sizes && variant.sizes.length > 0) { - // For individual variants, set the sizes and price - this.availableSetSizes = []; // Clear set sizes to avoid conflicts - this.selectedSize = null; // Reset the selected size - this.maxQuantity = 0; // Reset the max quantity - this.quantity = 1; // Reset quantity to 1 + this.availableSetSizes = []; + this.selectedSize = null; + this.maxQuantity = 0; + this.quantity = 1; } else{ - this.maxQuantity = variant.quantity; // Set max quantity based on the variant - this.quantity = 1; // Reset quantity to 1 - this.selectedSize = null; // No size selection needed + this.maxQuantity = variant.quantity; + this.quantity = 1; + this.selectedSize = null; } } - // Function to handle individual variant size selection + selectSize(size: any): void { this.selectedSize = size; - this.maxQuantity = size.quantity; // Set the max quantity based on size's available quantity - this.quantity = 1; // Reset the quantity to 1 when a new size is selected + this.maxQuantity = size.quantity; + this.quantity = 1; } - // Function to handle set size selection + selectSetSize(size: any): void { this.selectedSetSize = size; - this.maxQuantity = size.quantity; // Set the max quantity based on size's available quantity - this.quantity = 1; // Reset the quantity to 1 when a new size is selected + this.maxQuantity = size.quantity; + this.quantity = 1; } - // Calculate the available sizes for the "Set" option + calculateSetSizes(): void { const sizeAvailability: { [key: string]: { name: string, available: boolean, quantity: number } } = {}; - // Loop through all variants and their sizes + this.product.Variants.forEach(variant => { variant.sizes.forEach(size => { if (!sizeAvailability[size.name]) { - // Initialize size availability + sizeAvailability[size.name] = { name: size.name, available: size.quantity > 0, quantity: size.quantity }; } - // If any variant has 0 quantity for this size, mark it as unavailable + if (size.quantity === 0) { sizeAvailability[size.name].available = false; } - // Update quantity to reflect the smallest available quantity + sizeAvailability[size.name].quantity = Math.min(sizeAvailability[size.name].quantity, size.quantity); }); }); - // Convert the sizeAvailability object to an array of sizes + this.availableSetSizes = Object.values(sizeAvailability); } - // Calculate the total price for the "Set" + calculateSetPrice(): void { this.totalSetPrice = this.product.Variants.reduce((total: number, variant: any) => { return total + (variant.price || 0); }, 0); } - // Increase quantity, respecting the max quantity for the selected size + increaseQuantity(): void { if (this.quantity < this.maxQuantity) { this.quantity++; } } - // Decrease quantity, but not below 1 + decreaseQuantity(): void { if (this.quantity > 1) { this.quantity--; @@ -127,7 +135,7 @@ export class ItemComponent implements OnInit { const selectedItemPrice = this.selectedVariant ? (this.selectedVariant.name === 'Set' ? this.totalSetPrice : this.selectedVariant.price) : this.product.price; - + const cartItem: CartItem = { productCode: this.product.code, name: this.product.name, @@ -135,10 +143,11 @@ export class ItemComponent implements OnInit { quantity: this.quantity, price: selectedItemPrice, total: selectedItemPrice * this.quantity, - variantName: this.selectedVariant.name + variantName: this.selectedVariant.name, + idNo: this.authService.getUserIdNo() }; - // Add the selected product to the cart using the shopping cart service + this.shoppingcartService.addToCart(cartItem) .then(() => { console.log('Product added to cart'); diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index ca3ea568..164cf7d4 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -6,6 +6,7 @@ import { FooterComponent } from './footer/footer.component'; import { RouterModule } from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { ItemComponent } from './cards/item/item.component'; +import { DisplayProductComponent } from './cards/display-product/display-product.component'; @NgModule({ imports: [ @@ -17,13 +18,15 @@ import { ItemComponent } from './cards/item/item.component'; FooterComponent, NavbarComponent, SidebarComponent, - ItemComponent + ItemComponent, + DisplayProductComponent ], exports: [ FooterComponent, NavbarComponent, SidebarComponent, - ItemComponent + ItemComponent, + DisplayProductComponent ] }) export class ComponentsModule { } diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts index f20d3bd5..c2cf75bc 100644 --- a/src/app/components/navbar/navbar.component.ts +++ b/src/app/components/navbar/navbar.component.ts @@ -21,13 +21,9 @@ export class NavbarComponent implements OnInit { } ngOnInit() { - // if (this.routes) { - // this.listTitles = this.routes.filter(listTitle => listTitle); - // } else { - // this.listTitles = ROUTES.filter(listTitle => listTitle); - // } + + } - } getTitle(){ const titlee = this.location.prepareExternalUrl(this.location.path()); //eg. /student/dashboard const titleArr = titlee.split("/"); diff --git a/src/app/models/orders.model.ts b/src/app/models/orders.model.ts new file mode 100644 index 00000000..272d7ee8 --- /dev/null +++ b/src/app/models/orders.model.ts @@ -0,0 +1,12 @@ +export interface Orders { + productCode: string; + name: string; + size?: string; + quantity: number; + price: number; + total: number; + variantName: string; + idNo?: string; + date: Date; + status: string; + } \ No newline at end of file diff --git a/src/app/models/shoppingcart.model.ts b/src/app/models/shoppingcart.model.ts index 44fea387..7e533652 100644 --- a/src/app/models/shoppingcart.model.ts +++ b/src/app/models/shoppingcart.model.ts @@ -6,4 +6,5 @@ export interface CartItem { price: number; total: number; variantName: string; + idNo?: string; } \ No newline at end of file diff --git a/src/app/pages/student/products/products.component.html b/src/app/pages/student/products/products.component.html index b95967e5..09f08c72 100644 --- a/src/app/pages/student/products/products.component.html +++ b/src/app/pages/student/products/products.component.html @@ -1,7 +1,16 @@

No products available.

-
- + + +
+ + +
+ + +
+ +
-
- -
\ No newline at end of file diff --git a/src/app/pages/student/products/products.component.ts b/src/app/pages/student/products/products.component.ts index cd4e6be5..7db30e2f 100644 --- a/src/app/pages/student/products/products.component.ts +++ b/src/app/pages/student/products/products.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; import { ProductsService } from 'src/app/services/products.service'; @Component({ @@ -8,13 +9,56 @@ import { ProductsService } from 'src/app/services/products.service'; }) export class ProductsComponent implements OnInit { products: any[] = []; + selectedProduct: any = null; // Stores the selected product for detailed view - constructor(private productService: ProductsService) { } + constructor( + private productService: ProductsService, + private router: Router, + private route: ActivatedRoute + ) { } ngOnInit(): void { + // Load the list of products + this.loadProducts(); + + // Subscribe to query params to handle changes + this.route.queryParams.subscribe(params => { + const code = params['code']; + if (code) { + this.getProductByCode(code); + + } else { + this.selectedProduct = null; // Reset selectedProduct if code is not present + } + }); + } + + // Method to load products + loadProducts(): void { this.productService.getProducts().subscribe(data => { this.products = data; - console.log(this.products) + }); + } + + // Method to get a product by its code + getProductByCode(code: string): void { + this.productService.getProductByCode(code).subscribe(product => { + this.selectedProduct = product; + }); + } + + // Method to handle product selection + selectProduct(product: any): void { + this.router.navigate(['/student/products'], { + queryParams: { code: product.code }, + queryParamsHandling: 'merge' // Use 'merge' to keep existing query params if any + }); + } + + // Method to go back to the product list + backToProducts(): void { + this.router.navigate(['/student/products'], { + queryParams: {} // Clear the query parameters }); } } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 137403af..5cfebb85 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -43,6 +43,11 @@ export class AuthService { return user ? user.role || null : null; } + getUserIdNo(): string | null { + const user = this.userSubject.value || this.getUserFromLocalStorage(); + return user ? user.idNo || null : null; + } + private getUserFromLocalStorage(): User | null { const userJson = localStorage.getItem('user'); return userJson ? JSON.parse(userJson) : null; diff --git a/src/app/services/products.service.ts b/src/app/services/products.service.ts index 515ea97f..a52c9848 100644 --- a/src/app/services/products.service.ts +++ b/src/app/services/products.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { AngularFirestore } from '@angular/fire/compat/firestore'; -import { Observable } from 'rxjs'; +import { map, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' @@ -19,6 +19,12 @@ export class ProductsService { return this.firestore.collection('Products').doc(productId).valueChanges(); } + getProductByCode(productCode: string): Observable { + return this.firestore.collection('Products', ref => ref.where('code', '==', productCode)).valueChanges().pipe( + map(products => products.length > 0 ? products[0] : null) + ); + } + // Method to add a product addProduct(product: any): Promise { const id = this.firestore.createId(); From 713d332607797e4824ed947bb9244dc814a6442e Mon Sep 17 00:00:00 2001 From: mattsayco Date: Tue, 17 Sep 2024 23:46:29 +0800 Subject: [PATCH 23/48] Added Registration --- .vscode/launch.json | 15 ++++++ angular.json | 3 +- docs/documentation.html | 4 +- .../components/navbar/navbar.component.html | 2 +- src/app/components/navbar/navbar.component.ts | 7 ++- .../auth-layout/auth-layout.component.html | 4 +- .../pages/register/register.component.html | 14 +++-- src/app/pages/register/register.component.ts | 14 +++-- .../user-profile/user-profile.component.html | 22 ++++---- .../user-profile/user-profile.component.ts | 9 +++- src/app/services/auth.service.ts | 53 ++++++++++++++----- src/index.html | 2 +- 12 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..2ba986f6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/angular.json b/angular.json index 727a4238..5fd81550 100644 --- a/angular.json +++ b/angular.json @@ -34,7 +34,8 @@ "src/styles.scss", "src/assets/scss/argon.scss", "src/assets/vendor/nucleo/css/nucleo.css", - "src/assets/vendor/@fortawesome/fontawesome-free/css/all.min.css" + "src/assets/vendor/@fortawesome/fontawesome-free/css/all.min.css", + "node_modules/ngx-toastr/toastr.css" ], "scripts": [ "node_modules/chart.js/dist/Chart.min.js", diff --git a/docs/documentation.html b/docs/documentation.html index 8f2405eb..5dd70e15 100644 --- a/docs/documentation.html +++ b/docs/documentation.html @@ -15,10 +15,10 @@ - + /> -->
Welcome!
- + My profile diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts index f20d3bd5..d38c936a 100644 --- a/src/app/components/navbar/navbar.component.ts +++ b/src/app/components/navbar/navbar.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, ElementRef, Input } from '@angular/core'; // import { ROUTES } from '../sidebar/sidebar.component'; import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { AuthService } from 'src/app/services/auth.service'; import { RouteInfo } from 'src/app/models/routes.model'; @@ -16,7 +16,8 @@ export class NavbarComponent implements OnInit { public focus; public listTitles: any[]; public location: Location; - constructor(location: Location, private element: ElementRef, private router: Router, private authService: AuthService) { + public urlParent: string = ''; + constructor(location: Location, private element: ElementRef, private router: Router, private authService: AuthService, private activatedRoute: ActivatedRoute) { this.location = location; } @@ -26,6 +27,8 @@ export class NavbarComponent implements OnInit { // } else { // this.listTitles = ROUTES.filter(listTitle => listTitle); // } + this.urlParent = this.activatedRoute.parent.toString(); + console.log(this.urlParent); } getTitle(){ diff --git a/src/app/layouts/auth-layout/auth-layout.component.html b/src/app/layouts/auth-layout/auth-layout.component.html index f8473126..b61fa2d2 100644 --- a/src/app/layouts/auth-layout/auth-layout.component.html +++ b/src/app/layouts/auth-layout/auth-layout.component.html @@ -34,13 +34,13 @@
+
+
+
+
+ +
+
@@ -87,9 +95,9 @@

Welcome!

-
+
diff --git a/src/app/pages/register/register.component.ts b/src/app/pages/register/register.component.ts index 0c28c827..283c43f7 100644 --- a/src/app/pages/register/register.component.ts +++ b/src/app/pages/register/register.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { FirestoreService } from '../../services/firestore.service'; import { NgForm } from '@angular/forms'; +import { ToastrService } from 'ngx-toastr'; @Component({ selector: 'app-register', @@ -11,7 +12,8 @@ export class RegisterComponent implements OnInit { user = { idNo: '', password: '', - name: '', + firstName: '', + lastName: '', email: '', phone: '' }; @@ -19,7 +21,7 @@ export class RegisterComponent implements OnInit { confirmPassword: string = ''; passwordMismatch: boolean = false; - constructor(private firestoreService: FirestoreService) { + constructor(private firestoreService: FirestoreService, private toastr: ToastrService) { } ngOnInit() { @@ -30,16 +32,20 @@ export class RegisterComponent implements OnInit { this.firestoreService.addUser(this.user) .then(() => { // Registration successful + this.toastr.success("User registered successfully"); console.log('User registered successfully'); - this.user = { idNo: '', password: '', name: '', email: '', phone: '' }; // Clear form + this.user = { idNo: '', password: '', firstName: '', lastName: '', email: '', phone: '' }; // Clear form + this.confirmPassword = ''; }) .catch((error) => { // Handle error console.error('Error registering user:', error); this.errorMessage = 'Registration failed'; + this.toastr.error(this.errorMessage); }); } else { this.errorMessage = 'Please fill in all fields'; + this.toastr.error(this.errorMessage); } } @@ -48,9 +54,11 @@ export class RegisterComponent implements OnInit { } onSubmit(form: NgForm){ + console.log(form); if (this.user.password !== this.confirmPassword) { this.passwordMismatch = true; this.errorMessage = 'Passwords do not match'; + this.toastr.error(this.errorMessage); } else { this.passwordMismatch = false; if (form.valid) { diff --git a/src/app/pages/user-profile/user-profile.component.html b/src/app/pages/user-profile/user-profile.component.html index d781b44f..2c9b6170 100644 --- a/src/app/pages/user-profile/user-profile.component.html +++ b/src/app/pages/user-profile/user-profile.component.html @@ -5,7 +5,7 @@
-

Hello Jesse

+

Hello {{user.firstName}} {{user.lastName}}

This is your profile page. You can see the progress you've made with your work and manage your projects or assigned tasks

Edit profile
@@ -52,7 +52,7 @@

Hello Jesse

- Jessica Jones, 27 + {{user.firstName}} {{user.lastName}}, 27

Bucharest, Romania @@ -90,13 +90,13 @@
User information
- +
- +
@@ -104,13 +104,13 @@
User information
- +
- +
@@ -123,7 +123,7 @@
Contact information
- +
@@ -131,19 +131,19 @@
Contact information
- +
- +
- +
@@ -154,7 +154,7 @@
About me
- +
diff --git a/src/app/pages/user-profile/user-profile.component.ts b/src/app/pages/user-profile/user-profile.component.ts index 2856d870..4cef2e23 100644 --- a/src/app/pages/user-profile/user-profile.component.ts +++ b/src/app/pages/user-profile/user-profile.component.ts @@ -1,4 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { AuthService } from 'src/app/services/auth.service'; +import { FirestoreService } from 'src/app/services/firestore.service'; @Component({ selector: 'app-user-profile', @@ -6,10 +9,12 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./user-profile.component.scss'] }) export class UserProfileComponent implements OnInit { - - constructor() { } + public user: any; + constructor(private firestoreService: FirestoreService, private toastr: ToastrService, private authService: AuthService) { } ngOnInit() { + this.authService.user$.subscribe(user => this.user = user); + console.log(this.user); } } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 137403af..551978f7 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -18,23 +18,50 @@ export class AuthService { return new Promise((resolve, reject) => { this.firestore.collection('Users', ref => ref.where('idNo', '==', idNo)) .get() - .subscribe(snapshot => { - if (!snapshot.empty) { - const user = snapshot.docs[0].data() as User; - if (user.password === password) { - this.userSubject.next(user); - this.saveUserToLocalStorage(user); - resolve(true); // Login successful + .subscribe({ + next: (snapshot) => { + debugger; + if (!snapshot.empty) { + const user = snapshot.docs[0].data() as User; + if (user.password === password) { + this.userSubject.next(user); + this.saveUserToLocalStorage(user); + debugger; + this.user$ = this.userSubject.asObservable(); + resolve(true); // Login successful + } else { + resolve(false); // Incorrect password + } } else { - resolve(false); // Incorrect password + resolve(false); // User not found } - } else { - resolve(false); // User not found + }, + error: (error) => { + console.error("Error during login:", error); + reject(false); // Login failed } - }, error => { - console.error("Error during login:", error); - reject(false); // Login failed }); + + + // .subscribe(snapshot => { + // if (!snapshot.empty) { + // const user = snapshot.docs[0].data() as User; + // if (user.password === password) { + // this.userSubject.next(user); + // this.saveUserToLocalStorage(user); + // debugger; + // this.user$ = this.userSubject.asObservable(); + // resolve(true); // Login successful + // } else { + // resolve(false); // Incorrect password + // } + // } else { + // resolve(false); // User not found + // } + // }, error => { + // console.error("Error during login:", error); + // reject(false); // Login failed + // }); }); } diff --git a/src/index.html b/src/index.html index 8672d2ea..cc097e1a 100644 --- a/src/index.html +++ b/src/index.html @@ -27,7 +27,7 @@ - + From 3ba26c441cb40d4177b7167c0c77cbbec65a3585 Mon Sep 17 00:00:00 2001 From: mattsayco Date: Wed, 18 Sep 2024 00:19:03 +0800 Subject: [PATCH 24/48] Fixed registration to default role to student --- docs/documentation.html | 4 ++-- src/app/models/user.model.ts | 4 ++++ src/app/pages/register/register.component.ts | 20 ++++++++------------ src/index.html | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/documentation.html b/docs/documentation.html index 5dd70e15..8f2405eb 100644 --- a/docs/documentation.html +++ b/docs/documentation.html @@ -15,10 +15,10 @@ - + /> { // Handle error @@ -53,8 +50,7 @@ export class RegisterComponent implements OnInit { return Object.values(this.user).every(field => field.trim() !== ''); } - onSubmit(form: NgForm){ - console.log(form); + onSubmit(form: NgForm){ if (this.user.password !== this.confirmPassword) { this.passwordMismatch = true; this.errorMessage = 'Passwords do not match'; diff --git a/src/index.html b/src/index.html index cc097e1a..8672d2ea 100644 --- a/src/index.html +++ b/src/index.html @@ -27,7 +27,7 @@ - + From b393f3f29395ef30dc2a937ce90ffa67e86da403 Mon Sep 17 00:00:00 2001 From: mattsayco Date: Wed, 18 Sep 2024 23:22:23 +0800 Subject: [PATCH 25/48] Added functionality to user profile --- .../components/navbar/navbar.component.html | 2 +- .../user-profile/user-profile.component.html | 76 ++++++++++++++----- .../user-profile/user-profile.component.ts | 54 ++++++++++++- src/app/services/auth.service.ts | 34 +++------ src/app/services/firestore.service.ts | 13 +++- 5 files changed, 133 insertions(+), 46 deletions(-) diff --git a/src/app/components/navbar/navbar.component.html b/src/app/components/navbar/navbar.component.html index e40d8bab..d429be54 100644 --- a/src/app/components/navbar/navbar.component.html +++ b/src/app/components/navbar/navbar.component.html @@ -31,7 +31,7 @@ - + My profile diff --git a/src/app/pages/user-profile/user-profile.component.html b/src/app/pages/user-profile/user-profile.component.html index 2c9b6170..56d4b9b0 100644 --- a/src/app/pages/user-profile/user-profile.component.html +++ b/src/app/pages/user-profile/user-profile.component.html @@ -5,16 +5,16 @@
-

Hello {{user.firstName}} {{user.lastName}}

-

This is your profile page. You can see the progress you've made with your work and manage your projects or assigned tasks

- Edit profile +

Hello, {{user.firstName}} {{user.lastName}}

+ +
-
+ + + +
@@ -83,41 +85,75 @@

My account

-
+
User information
+
+
+
+ + +
+
+
- - + +
- - + +
- - + +
- - + +
+
+
+ @if(isChangePassword) { +
+
+
+ + +
+
+
+
+ + +
+
+
+ } +
+
+ +
+
+
+ +
-
+ -
Contact information
+ -
About me
+
diff --git a/src/app/pages/user-profile/user-profile.component.ts b/src/app/pages/user-profile/user-profile.component.ts index 4cef2e23..cc31f332 100644 --- a/src/app/pages/user-profile/user-profile.component.ts +++ b/src/app/pages/user-profile/user-profile.component.ts @@ -1,5 +1,7 @@ import { Component, OnInit } from '@angular/core'; +import { NgForm } from '@angular/forms'; import { ToastrService } from 'ngx-toastr'; +import { User } from 'src/app/models/user.model'; import { AuthService } from 'src/app/services/auth.service'; import { FirestoreService } from 'src/app/services/firestore.service'; @@ -9,12 +11,62 @@ import { FirestoreService } from 'src/app/services/firestore.service'; styleUrls: ['./user-profile.component.scss'] }) export class UserProfileComponent implements OnInit { - public user: any; + user: User; + isChangePassword: boolean = false; + confirmPassword: string = ''; + errorMessage: string = '' constructor(private firestoreService: FirestoreService, private toastr: ToastrService, private authService: AuthService) { } ngOnInit() { this.authService.user$.subscribe(user => this.user = user); + // for testing + this.user = JSON.parse(localStorage.getItem('user')); console.log(this.user); } + saveProfile() { + if (this.validateForm()) { + this.firestoreService.updateUser(this.user) + .then(() => { + // Registration successful + this.toastr.success("User profile updated"); + console.log('User profile updated'); + this.confirmPassword = ''; + }) + .catch((error) => { + // Handle error + console.error('Error updating user:', error); + this.errorMessage = 'Update failed failed'; + this.toastr.error(this.errorMessage); + }); + } else { + this.errorMessage = 'Please fill in all fields'; + this.toastr.error(this.errorMessage); + } + } + + validateForm(): boolean { + return Object.values(this.user).every(field => field.trim() !== ''); + } + + //#region Events + onChangePassword(){ + this.isChangePassword = !this.isChangePassword; + this.user.password = ''; + this.confirmPassword = ''; + } + + onSubmit(form: NgForm){ + console.log("submitted"); + if ((this.user.password !== this.confirmPassword) && this.isChangePassword) { + this.errorMessage = 'Passwords do not match'; + this.toastr.error(this.errorMessage); + } else { + if (form.valid) { + this.saveProfile() + } + } + } + //#endregion + } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 551978f7..82241f75 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -20,14 +20,15 @@ export class AuthService { .get() .subscribe({ next: (snapshot) => { - debugger; + console.log("test"); if (!snapshot.empty) { const user = snapshot.docs[0].data() as User; if (user.password === password) { this.userSubject.next(user); this.saveUserToLocalStorage(user); - debugger; + this.saveUserDocIdToLocalStorage(snapshot.docs[0].id); this.user$ = this.userSubject.asObservable(); + resolve(true); // Login successful } else { resolve(false); // Incorrect password @@ -41,27 +42,6 @@ export class AuthService { reject(false); // Login failed } }); - - - // .subscribe(snapshot => { - // if (!snapshot.empty) { - // const user = snapshot.docs[0].data() as User; - // if (user.password === password) { - // this.userSubject.next(user); - // this.saveUserToLocalStorage(user); - // debugger; - // this.user$ = this.userSubject.asObservable(); - // resolve(true); // Login successful - // } else { - // resolve(false); // Incorrect password - // } - // } else { - // resolve(false); // User not found - // } - // }, error => { - // console.error("Error during login:", error); - // reject(false); // Login failed - // }); }); } @@ -83,6 +63,14 @@ export class AuthService { } } + private saveUserDocIdToLocalStorage(userDocId: string) { + if(userDocId) { + localStorage.setItem('userDocId', userDocId); + } else { + localStorage.removeItem('userDocId'); + } + } + isLoggedIn(): boolean { return !!localStorage.getItem('user'); } diff --git a/src/app/services/firestore.service.ts b/src/app/services/firestore.service.ts index 53af39c1..9f3a29d1 100644 --- a/src/app/services/firestore.service.ts +++ b/src/app/services/firestore.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { AngularFirestore } from '@angular/fire/compat/firestore'; import { Observable } from 'rxjs'; +import { User } from '../models/user.model'; @Injectable({ providedIn: 'root' @@ -15,9 +16,19 @@ export class FirestoreService { const userId = this.firestore.createId(); // Generate unique ID return this.firestore.collection('Users').doc(userId).set(user); } +// to be continued + updateUser(user: User): Promise { + console.log("test update"); + console.log("to update") + return this.firestore.collection('Users').doc(this.getUserDocId()).update(user); + } // Method to get a user by idNo getUserByIdNo(idNo: string): Observable { - return this.firestore.collection('users', ref => ref.where('idNo', '==', idNo)).valueChanges(); + return this.firestore.collection('Users', ref => ref.where('idNo', '==', idNo)).valueChanges(); + } + + getUserDocId(): string { + return localStorage.getItem('userDocId') } } From b6e2be1dd0a3e76d6f9a98125a377fb352b60fd7 Mon Sep 17 00:00:00 2001 From: redmanuel1 Date: Thu, 19 Sep 2024 14:26:02 +0800 Subject: [PATCH 26/48] display products, variants, sizes and add blu logo --- docs/argon.css | 8 +- .../display-product.component.html | 10 +- .../display-product.component.scss | 4 +- .../display-product.component.ts | 1 - .../components/cards/item/item.component.html | 139 ++++++------- .../components/cards/item/item.component.scss | 5 + .../components/cards/item/item.component.ts | 196 ++++++++---------- .../components/sidebar/sidebar.component.html | 2 +- src/app/models/inventory.model.ts | 23 ++ src/app/models/product.model.ts | 3 +- .../student/products/products.component.ts | 19 +- src/app/services/inventory.service.ts | 34 +++ src/app/services/products.service.ts | 5 +- src/assets/img/icons/common/default_img.png | Bin 0 -> 215017 bytes src/assets/img/logo/b_logo.png | Bin 0 -> 66111 bytes src/assets/img/logo/blu_logo.png | Bin 0 -> 308114 bytes src/assets/img/logo/blu_logo2.png | Bin 0 -> 79014 bytes src/assets/img/logo/desktop.ini | 2 + 18 files changed, 237 insertions(+), 214 deletions(-) create mode 100644 src/app/models/inventory.model.ts create mode 100644 src/app/services/inventory.service.ts create mode 100644 src/assets/img/icons/common/default_img.png create mode 100644 src/assets/img/logo/b_logo.png create mode 100644 src/assets/img/logo/blu_logo.png create mode 100644 src/assets/img/logo/blu_logo2.png create mode 100644 src/assets/img/logo/desktop.ini diff --git a/docs/argon.css b/docs/argon.css index 9ddd4bd1..ff9989c0 100644 --- a/docs/argon.css +++ b/docs/argon.css @@ -3575,7 +3575,8 @@ fieldset:disabled a.btn background-color: transparent; background-image: none; } -.btn-outline-info:hover +.btn-outline-info:hover, +.btn-outline-info.active { color: #fff; border-color: #11cdef; @@ -6254,6 +6255,11 @@ input[type='button'].btn-block background-color: #0da5c0; } +/* .badge-info.active { + color: #fff !important; + background-color: #0da5c0 !important; +} */ + .badge-warning { color: #ff3709; diff --git a/src/app/components/cards/display-product/display-product.component.html b/src/app/components/cards/display-product/display-product.component.html index 8a799eb0..64e6da4a 100644 --- a/src/app/components/cards/display-product/display-product.component.html +++ b/src/app/components/cards/display-product/display-product.component.html @@ -2,11 +2,11 @@
- Product image + Product image +
+
+

{{ product.name }}

+
{{ product.price }}
-
-
{{ product.name }}
-

{{product.price}}

-
diff --git a/src/app/components/cards/display-product/display-product.component.scss b/src/app/components/cards/display-product/display-product.component.scss index 5cbef445..c0757734 100644 --- a/src/app/components/cards/display-product/display-product.component.scss +++ b/src/app/components/cards/display-product/display-product.component.scss @@ -1,7 +1,7 @@ .card-style{ - height: 400px; + height: 550px; min-height: 400px; - overflow: hidden; + overflow: y; } .img-container{ diff --git a/src/app/components/cards/display-product/display-product.component.ts b/src/app/components/cards/display-product/display-product.component.ts index a6760503..0d28980e 100644 --- a/src/app/components/cards/display-product/display-product.component.ts +++ b/src/app/components/cards/display-product/display-product.component.ts @@ -12,7 +12,6 @@ export class DisplayProductComponent implements OnInit{ @Input() product: any; @Output() select = new EventEmitter(); - // Emit the selected product when clicked onSelect() { this.select.emit(this.product); } diff --git a/src/app/components/cards/item/item.component.html b/src/app/components/cards/item/item.component.html index 57f35b5e..87858904 100644 --- a/src/app/components/cards/item/item.component.html +++ b/src/app/components/cards/item/item.component.html @@ -1,101 +1,84 @@ -
+
-
- Product image +
+ Product image
- - - - -
- -
-
{{ product.name }}
-
- - -
-
- {{ variantItem.name }} +
+
+
{{ product.name }}
-
- -
- -

Price: {{ selectedVariant.price | currency }}

- - -
-
- {{ size.name }} +
+
+ {{ variant.name }}
- -
-

Out of stock

-

Available: {{ selectedSize.quantity }}

+
+

{{selectedVariant.price}}

- -
- - - + + +
+ + + {{ size.size }} + +
-
- -
-
- - - + +
+

Out of stock

+

Available: {{ selectedSetSize.quantity }}

-
- -
-

Price: {{ totalSetPrice | currency }}

-
-
- {{ size.name }} -
+ +
+ + +
- -
-

Out of stock

-

Available: {{ selectedSize.quantity }}

+ + +
+

Out of stock

+

Available: {{ selectedVariant.quantity }}

- -
- - - + +
+ + +
-
+ +
+ + +
+ + - -
- - + + + +
-
diff --git a/src/app/components/cards/item/item.component.scss b/src/app/components/cards/item/item.component.scss index a2089e71..6284a281 100644 --- a/src/app/components/cards/item/item.component.scss +++ b/src/app/components/cards/item/item.component.scss @@ -140,4 +140,9 @@ border: 3px solid transparent; background-clip:padding-box; } + + .badge-info.active{ + color: #fff !important; + background-color: #0da5c0 !important; + } \ No newline at end of file diff --git a/src/app/components/cards/item/item.component.ts b/src/app/components/cards/item/item.component.ts index c73cb709..7db1e691 100644 --- a/src/app/components/cards/item/item.component.ts +++ b/src/app/components/cards/item/item.component.ts @@ -1,7 +1,10 @@ +import { Inventory, Variant, Size } from './../../../models/inventory.model'; import { Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { Product } from 'src/app/models/product.model'; import { CartItem } from 'src/app/models/shoppingcart.model'; import { AuthService } from 'src/app/services/auth.service'; +import { InventoryService } from 'src/app/services/inventory.service'; import { ShoppingCartService } from 'src/app/services/shoppingcart.service'; @Component({ @@ -10,114 +13,108 @@ import { ShoppingCartService } from 'src/app/services/shoppingcart.service'; styleUrls: ['./item.component.scss'] }) export class ItemComponent implements OnInit { - @Input() product: any; - selectedVariant: any = null; - selectedSize: any = null; - selectedSetSize: any = null; - variantsWithSet: any[] = []; - availableSetSizes: any[] = []; - totalSetPrice: number = 0; - quantity: number = 1; - maxQuantity: number = 0; - productCode: string | null = null; + @Input() product: Product; + inventory: Inventory; + variants: Variant[] = []; + sizesForSet: Size[] = []; + selectedVariant: Variant | null = null; + selectedSetSize: Size | null = null; + maxQuantity = 0; + quantity = 1; constructor( - private shoppingcartService: ShoppingCartService, - private route: ActivatedRoute, - private authService: AuthService){ - } + private inventoryService: InventoryService + ) {} ngOnInit(): void { - if (this.product && this.product.Variants) { - this.initializeVariants(); - if (this.variantsWithSet.length > 0) { - this.selectVariant(this.variantsWithSet[0]); - - // If the first variant has sizes, auto-select the first size - if (this.selectedVariant && this.selectedVariant.sizes && this.selectedVariant.sizes.length > 0) { - this.selectSize(this.selectedVariant.sizes[0]); + this.inventoryService.getInventoryByProductCode(this.product.code).subscribe(data => { + if (data) { + this.inventory = data; + console.log(this.inventory); + this.getInventoryItems(); + if (this.variants.length > 0) { + this.selectVariant(this.variants[0]) + if (this.selectedVariant.sizes && this.selectedVariant.sizes.length > 0) { + this.selectSetSize(this.selectedVariant.sizes[0]); + } } + } else { + console.warn('No inventory found.'); } - } - } - - initializeVariants(): void { - this.variantsWithSet = [...this.product.Variants]; - - if (this.product.isSet) { - this.variantsWithSet.unshift({ name: 'Set' }); - } + }); } - selectVariant(variant: any): void { - this.selectedVariant = variant; - - if (variant.name === 'Set') { - this.calculateSetSizes(); - this.calculateSetPrice(); - this.selectedSize = null; - this.selectedSetSize = null; - this.maxQuantity = 0; - } else if(variant.sizes && variant.sizes.length > 0) { - this.availableSetSizes = []; - this.selectedSize = null; - this.maxQuantity = 0; - this.quantity = 1; - } else{ - this.maxQuantity = variant.quantity; - this.quantity = 1; - this.selectedSize = null; + getInventoryItems() { + if (this.inventory.variants) { + if (this.inventory.isSet) { + this.createSizesForSet(); + this.variants = [ + ...this.inventory.variants, + { + code: 'SET', + name: 'Set', + price: this.product.price, + sizes: this.sizesForSet + } as Variant + ]; + } else { + this.variants = [...this.inventory.variants]; + } } } + createSizesForSet() { + if (this.inventory.isSet && this.inventory.variants) { + const sizeMap: { [sizeName: string]: number } = {}; - selectSize(size: any): void { - this.selectedSize = size; - this.maxQuantity = size.quantity; - this.quantity = 1; + this.inventory.variants.forEach(variant => { + variant.sizes?.forEach(size => { + const sizeName = size.size; + const quantity = size.quantity === undefined || size.quantity === null ? 0 : size.quantity; + + + if (sizeMap[sizeName] === undefined) { + sizeMap[sizeName] = quantity; + } else { + sizeMap[sizeName] = Math.min(sizeMap[sizeName], quantity); + } + }); + }); + + + this.sizesForSet = Object.keys(sizeMap).map(sizeName => ({ + size: sizeName, + quantity: sizeMap[sizeName] + })) as Size[]; + } } - selectSetSize(size: any): void { this.selectedSetSize = size; this.maxQuantity = size.quantity; - this.quantity = 1; - } - - - calculateSetSizes(): void { - const sizeAvailability: { [key: string]: { name: string, available: boolean, quantity: number } } = {}; - - - this.product.Variants.forEach(variant => { - variant.sizes.forEach(size => { - if (!sizeAvailability[size.name]) { - - sizeAvailability[size.name] = { name: size.name, available: size.quantity > 0, quantity: size.quantity }; - } - + if(this.maxQuantity==0){ + this.quantity = 0 + }else{ + this.quantity = 1; + } - if (size.quantity === 0) { - sizeAvailability[size.name].available = false; - } + + } - - sizeAvailability[size.name].quantity = Math.min(sizeAvailability[size.name].quantity, size.quantity); - }); - }); - - this.availableSetSizes = Object.values(sizeAvailability); - } - - - calculateSetPrice(): void { - this.totalSetPrice = this.product.Variants.reduce((total: number, variant: any) => { - return total + (variant.price || 0); - }, 0); + selectVariant(variant: Variant) { + this.selectedVariant = variant; + console.log("selectedVariant", this.selectedVariant) + if(!this.selectedSetSize){ + this.maxQuantity = variant.quantity; + if(this.maxQuantity==0){ + this.quantity = 0 + }else{ + this.quantity = 1; + } + } } - increaseQuantity(): void { if (this.quantity < this.maxQuantity) { this.quantity++; @@ -130,31 +127,4 @@ export class ItemComponent implements OnInit { this.quantity--; } } - - addToCart(): void { - const selectedItemPrice = this.selectedVariant - ? (this.selectedVariant.name === 'Set' ? this.totalSetPrice : this.selectedVariant.price) - : this.product.price; - - const cartItem: CartItem = { - productCode: this.product.code, - name: this.product.name, - size: this.selectedSize ? this.selectedSize.name : "", - quantity: this.quantity, - price: selectedItemPrice, - total: selectedItemPrice * this.quantity, - variantName: this.selectedVariant.name, - idNo: this.authService.getUserIdNo() - }; - - - this.shoppingcartService.addToCart(cartItem) - .then(() => { - console.log('Product added to cart'); - }) - .catch((error) => { - console.error('Error adding product to cart:', error); - }); - } - } diff --git a/src/app/components/sidebar/sidebar.component.html b/src/app/components/sidebar/sidebar.component.html index b8a9dc3c..7ad8ad13 100644 --- a/src/app/components/sidebar/sidebar.component.html +++ b/src/app/components/sidebar/sidebar.component.html @@ -7,7 +7,7 @@ - ... + ...