diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..1348ba4a
--- /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:4200",
+ "webRoot": "${workspaceFolder}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/angular.json b/angular.json
index f98c9205..8d5cec9e 100644
--- a/angular.json
+++ b/angular.json
@@ -10,13 +10,14 @@
"prefix": "app",
"schematics": {
"@schematics/angular:component": {
- "styleext": "scss"
+ "style": "scss"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
+ "sourceMap": true,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
@@ -31,7 +32,12 @@
],
"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",
+ "node_modules/ngx-toastr/toastr.css",
+ "node_modules/ngx-spinner/animations/ball-scale-multiple.css"
+
],
"scripts": [
"node_modules/chart.js/dist/Chart.min.js",
@@ -40,7 +46,8 @@
},
"configurations": {
"production": {
- "optimization": {
+ "optimization":
+ {
"scripts": true,
"styles": {
"minify": false,
@@ -49,7 +56,7 @@
"fonts": true
},
"outputHashing": "all",
- "sourceMap": false,
+ "sourceMap": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
@@ -62,34 +69,26 @@
]
},
"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
}
}
},
"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 +96,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
- "browserTarget": "argon-dashboard-angular:build"
+ "buildTarget": "argon-dashboard-angular:build"
}
},
"test": {
@@ -159,10 +158,9 @@
}
}
},
- "defaultProject": "argon-dashboard-angular",
"schematics": {
"@schematics/angular:component": {
- "styleext": "scss"
+ "style": "scss"
}
},
"cli": {
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/package.json b/package.json
index 89497054..58421e9b 100644
--- a/package.json
+++ b/package.json
@@ -5,47 +5,57 @@
"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",
- "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": {
- "@angular/animations": "^14.2.0",
- "@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/google-maps": "^14.2.0",
- "@angular/localize": "^14.2.0",
- "@angular/material": "^14.2.0",
- "@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",
+ "@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/fire": "^18.0.1",
+ "@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": "4.6.1",
+ "bootstrap": "^4.6.2",
"chart.js": "2.9.4",
"clipboard": "2.0.10",
+ "firebase": "^10.13.1",
+ "lodash": "^4.17.21",
"ngx-clipboard": "15.0.1",
- "ngx-toastr": "14.2.2",
+ "ngx-spinner": "^17.0.0",
+ "ngx-toastr": "^19.0.0",
"nouislider": "15.5.1",
- "rxjs": "~7.5.0",
- "zone.js": "~0.11.4",
- "web-animations-js": "2.3.2"
+ "rxjs": "~7.8.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": "^14.2.7",
- "@angular/cli": "~14.2.7",
- "@angular/compiler-cli": "^14.2.0",
- "@angular/language-service": "14.2.0",
+ "@angular-devkit/build-angular": "^18.2.2",
+ "@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",
- "codelyzer": "6.0.2",
+ "@types/lodash": "^4.17.10",
+ "@types/node": "^22.5.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 +64,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": "^5.5.4"
}
}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 0a8b2ec9..71cbcbbb 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -6,5 +6,5 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
- title = 'argon-dashboard-angular';
+ title = 'Blu Inventory';
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 80a35bab..f8dc9c68 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,17 +1,25 @@
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { NgModule } from '@angular/core';
-import { FormsModule } from '@angular/forms';
-import { HttpClientModule } from '@angular/common/http';
-import { RouterModule } from '@angular/router';
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core";
+import { FormsModule } from "@angular/forms";
+import { HttpClientModule } from "@angular/common/http";
+import { RouterModule } from "@angular/router";
-import { AppComponent } from './app.component';
-import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.component';
-import { AuthLayoutComponent } from './layouts/auth-layout/auth-layout.component';
+import { AppComponent } from "./app.component";
+import { AdminLayoutComponent } from "./layouts/admin-layout/admin-layout.component";
+import { AuthLayoutComponent } from "./layouts/auth-layout/auth-layout.component";
-import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
-import { AppRoutingModule } from './app.routing';
-import { ComponentsModule } from './components/components.module';
+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";
+import { StudentLayoutComponent } from "./layouts/student-layout/student-layout.component";
+import { CustodianLayoutComponent } from "./layouts/custodian-layout/custodian-layout.component";
+import { NgxSpinnerModule } from "ngx-spinner";import { AccountantLayoutComponent } from './layouts/accountant-layout/accountant-layout.component';
@NgModule({
@@ -22,14 +30,23 @@ import { ComponentsModule } from './components/components.module';
ComponentsModule,
NgbModule,
RouterModule,
- AppRoutingModule
+ AppRoutingModule,
+ AngularFireModule.initializeApp(environment.firebaseConfig),
+ AngularFireAuthModule,
+ AngularFirestoreModule,
+ ToastrModule.forRoot(),
+ NgxSpinnerModule.forRoot(),
],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [
AppComponent,
AdminLayoutComponent,
- AuthLayoutComponent
+ AuthLayoutComponent,
+ StudentLayoutComponent,
+ CustodianLayoutComponent,
+ AccountantLayoutComponent,
],
providers: [],
- bootstrap: [AppComponent]
+ bootstrap: [AppComponent],
})
-export class AppModule { }
+export class AppModule {}
diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts
index 29a17f41..8a150011 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';
@@ -5,33 +6,78 @@ 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';
+import { LoginGuard } from './guards/login.guard';
+import { ItemComponent } from './components/cards/item/item.component';
+import { CustodianLayoutComponent } from './layouts/custodian-layout/custodian-layout.component';
+import { CheckoutComponent } from './pages/student/checkout/checkout.component';
+import { AccountantLayoutComponent } from './layouts/accountant-layout/accountant-layout.component';
const routes: Routes =[
{
path: '',
- redirectTo: 'dashboard',
+ redirectTo: 'auth/login',
pathMatch: 'full',
}, {
- path: '',
+ path: 'admin',
component: AdminLayoutComponent,
+ canActivate: [AuthGuard],
children: [
{
path: '',
loadChildren: () => import('src/app/layouts/admin-layout/admin-layout.module').then(m => m.AdminLayoutModule)
}
]
- }, {
- path: '',
+ },
+ {
+ 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: '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: 'accountant',
+ component: AccountantLayoutComponent,
+ canActivate: [AuthGuard], // Add role guard to check if the user is a student
+ children: [
+ {
+ path: '',
+ loadChildren: () => import('./layouts/accountant-layout/accountant-layout.module').then(m => m.AccountantLayoutModule)
+ }
+ ]
+ },
+ {
+ path: 'auth',
component: AuthLayoutComponent,
+ canActivate: [LoginGuard],
children: [
{
path: '',
loadChildren: () => import('src/app/layouts/auth-layout/auth-layout.module').then(m => m.AuthLayoutModule)
- }
+ },
]
- }, {
+ },
+
+ {
path: '**',
- redirectTo: 'dashboard'
+ redirectTo: '/dashboard'
}
];
@@ -40,7 +86,7 @@ const routes: Routes =[
CommonModule,
BrowserModule,
RouterModule.forRoot(routes,{
- useHash: true
+ useHash: false
})
],
exports: [
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..e30391a7
--- /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..de6b8f12
--- /dev/null
+++ b/src/app/components/cards/display-product/display-product.component.scss
@@ -0,0 +1,10 @@
+.card-style{
+ height: 30rem;
+ }
+
+ .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..27262ce1
--- /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();
+
+ onSelect() {
+ this.select.emit(this.product);
+ }
+
+ ngOnInit(): void {
+ console.log(this.product)
+ }
+
+}
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..468a2495
--- /dev/null
+++ b/src/app/components/cards/item/item.component.html
@@ -0,0 +1,85 @@
+
+
+
+
+
![Product image]()
+
+
+
+
+
{{ product.name }}
+
+
+
+
+ {{ variant.name }}
+
+
+
+
+
₱ {{selectedVariant.price}}
+
+
+
+
+
+
+
+ {{ size.size }}
+
+
+
+
+
+
+
Out of stock
+ 0" class="text-dark">Available: {{ selectedSetSize.quantity }}
+
+
+
+
+
+
+
+
+
+
+
+
+
Out of stock
+ 0" class="text-dark">Available: {{ selectedVariant.quantity }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..6284a281
--- /dev/null
+++ b/src/app/components/cards/item/item.component.scss
@@ -0,0 +1,148 @@
+/* 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{
+ 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;
+ }
+
+ .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.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..216e73f0
--- /dev/null
+++ b/src/app/components/cards/item/item.component.ts
@@ -0,0 +1,235 @@
+import { ProductsService } from 'src/app/services/products.service';
+import { Inventory, Variant, Size } from './../../../models/inventory.model';
+import { AfterViewInit, Component, Input, OnInit, SimpleChanges, ViewChild } from '@angular/core';
+import { ActivatedRoute, Router } 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';
+import { ToastService } from '../../modal/toast/toast.service';
+import { ToastComponent } from '../../modal/toast/toast.component';
+
+@Component({
+ selector: 'app-item',
+ templateUrl: './item.component.html',
+ styleUrls: ['./item.component.scss']
+})
+export class ItemComponent implements OnInit, AfterViewInit {
+ product: Product;
+ inventory: Inventory;
+ variants: Variant[] = [];
+ sizesForSet: Size[] = [];
+ selectedVariant: Variant | null = null;
+ selectedSetSize: Size | null = null;
+ maxQuantity = 0;
+ quantity = 1;
+ selectedItem: CartItem[] = [];
+ @ViewChild(ToastComponent) toastComponent!: ToastComponent;
+
+ constructor(
+ private inventoryService: InventoryService,
+ private route: ActivatedRoute,
+ private productService: ProductsService,
+ private shoppingCartService: ShoppingCartService,
+ private authService: AuthService,
+ private router: Router,
+ private toastService: ToastService,
+ ) {}
+
+ ngOnInit(): void {
+ this.getProductCodeFromRoute();
+ }
+
+ ngAfterViewInit() {
+ this.toastService.registerToast(this.toastComponent);
+ }
+
+ getProductCodeFromRoute(): void {
+ this.route.params.subscribe(params => {
+ const code = params['code'];
+ if (code) {
+ this.getProductByCode(code)
+ this.getInventoryItemByCode(code); // Fetch the product by code
+ }
+ });
+ }
+
+ getProductByCode(code: string): void {
+ this.productService.getProductByCode(code).subscribe(data => {
+ this.product = data;
+ });
+ }
+
+ getInventoryItemByCode(code: string){
+ this.inventoryService.getInventoryByProductCode(code).subscribe(data => {
+ if (data) {
+ this.inventory = data;
+ console.log(this.inventory)
+ this.getInventoryItems();
+ console.log(this.inventory)
+ 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.');
+ }
+ });
+ }
+
+ getInventoryItems() {
+ if (this.inventory.variants) {
+ this.variants = [...this.inventory.variants];
+
+ // Check if any variant has sizes
+ const hasSizes = this.variants.some(variant => variant.sizes && variant.sizes.length > 0);
+
+ if (this.inventory.isSet && hasSizes) {
+ this.createSizesForSet();
+ this.variants.push({
+ code: 'SET',
+ name: 'Set' ,
+ price: this.product.price,
+ sizes: this.sizesForSet
+ } as Variant);
+ } else if (this.inventory.isSet && !hasSizes) {
+ const minQuantity = Math.min(...this.variants.map(variant => variant.quantity || Infinity));
+
+ this.variants.push({
+ code: 'SET',
+ name: 'Set',
+ price: this.product.price,
+ quantity: minQuantity === Infinity ? 0 : minQuantity
+ } as Variant);
+ }
+ }
+ }
+
+
+ createSizesForSet() {
+ if (this.inventory.isSet && this.inventory.variants) {
+ const sizeMap: { [sizeName: string]: number } = {};
+
+ 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;
+ if(this.maxQuantity==0){
+ this.quantity = 0
+ }else{
+ this.quantity = 1;
+ }
+ }
+
+ 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;
+ }
+ }else{
+ this.selectSetSize(this.selectedVariant.sizes[0])
+ }
+ }
+
+ increaseQuantity(): void {
+ if (this.quantity < this.maxQuantity) {
+ this.quantity++;
+ }
+ }
+
+ decreaseQuantity(): void {
+ if (this.quantity > 1) {
+ this.quantity--;
+ }
+ }
+
+ addToCart(): void {
+ if (this.selectedVariant && this.maxQuantity>0) {
+ const cartItem: CartItem = {
+ cartID: this.generateUniqueCartID(),
+ idNo: this.authService.getUserIdNo(), // Replace with actual user ID
+ orderDate: new Date().toISOString(), // Current date
+ productCode: this.product.code,
+ variantCode: this.selectedVariant.code,
+ price: this.selectedVariant.price,
+ quantity: this.quantity,
+ totalPrice: this.selectedVariant.price * this.quantity,
+ imgURL: this.selectedVariant.imgURL || '',
+ size: this.selectedSetSize ? this.selectedSetSize.size : '' ,
+ name: this.selectedVariant.name === "Set" ? "Set - " + this.product.name : this.selectedVariant.name,
+ productName: this.product.name
+ };
+
+ this.shoppingCartService.addToCart(cartItem);
+ const message = cartItem.size
+ ? `${cartItem.name} size ${cartItem.size} successfully added to cart!`
+ : `${cartItem.name} successfully added to cart!`;
+ this.toastService.showToast(message, 'success');
+
+ } else {
+ const message = this.selectedSetSize
+ ? `${this.selectedVariant.name} size ${this.selectedSetSize.size} is not available!`
+ : `${this.selectedVariant.name} is not available!`;
+ this.toastService.showToast(message, 'error');
+ }
+ }
+
+ generateUniqueCartID(): number {
+ return Date.now() + Math.floor(Math.random() * 1000);
+ }
+
+ proceedToCheckOut(){
+ if (this.product && this.selectedVariant && this.maxQuantity>0) {
+ const cartItem: CartItem = {
+ // cartID: this.generateUniqueCartID(),
+ idNo: this.authService.getUserIdNo(), // Replace with actual user ID
+ orderDate: new Date().toISOString(), // Current date
+ productCode: this.product.code,
+ variantCode: this.selectedVariant.code,
+ price: this.selectedVariant.price,
+ quantity: this.quantity,
+ totalPrice: this.selectedVariant.price * this.quantity,
+ imgURL: this.selectedVariant.imgURL || '',
+ size: this.selectedSetSize ? this.selectedSetSize.size : '' ,
+ name: this.selectedVariant.name === "Set" ? "Set - " + this.product.name : this.selectedVariant.name,
+ productName: this.product.name
+ };
+ this.selectedItem.push(cartItem)
+ sessionStorage.setItem('selectedItems', JSON.stringify(this.selectedItem));
+
+ this.router.navigate([`/student/products/${this.product.code}/checkout`]);
+ }else{
+ this.toastService.showToast("Selected product is not available at the moment", 'error');
+ }
+ }
+
+}
+
diff --git a/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.html b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.html
new file mode 100644
index 00000000..88937546
--- /dev/null
+++ b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
![Product image]()
+
+
+
{{cartItem.name}}
+
+
+
+
+
+
+
+
+
+
+
+
₱{{cartItem.totalPrice}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.scss b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.scss
new file mode 100644
index 00000000..138536ce
--- /dev/null
+++ b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.scss
@@ -0,0 +1,4 @@
+.border-select {
+ border: 4px solid !important;
+ border-color: #0d93aa !important;
+}
\ No newline at end of file
diff --git a/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.spec.ts b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.spec.ts
new file mode 100644
index 00000000..9d168607
--- /dev/null
+++ b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ShoppingcartItemComponent } from './shoppingcart-item.component';
+
+describe('ShoppingcartItemComponent', () => {
+ let component: ShoppingcartItemComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ShoppingcartItemComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ShoppingcartItemComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.ts b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.ts
new file mode 100644
index 00000000..ea843b6f
--- /dev/null
+++ b/src/app/components/cards/shoppingcart-item/shoppingcart-item.component.ts
@@ -0,0 +1,55 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { CartItem } from 'src/app/models/shoppingcart.model';
+
+@Component({
+ selector: 'app-shoppingcart-item',
+ templateUrl: './shoppingcart-item.component.html',
+ styleUrl: './shoppingcart-item.component.scss'
+})
+
+export class ShoppingcartItemComponent implements OnInit {
+ @Input() cartItem: CartItem;
+ @Output() selectionChange = new EventEmitter();
+ @Output() remove = new EventEmitter();
+
+ ngOnInit(): void {
+ }
+
+
+ onCheckboxChange(): void {
+ this.selectionChange.emit(this.cartItem);
+ }
+
+ decreaseQuantity(): void {
+ if (this.cartItem.quantity > 1) {
+ this.cartItem.quantity--;
+ this.updateTotalPrice();
+ }
+ }
+
+
+ increaseQuantity(): void {
+ if (this.cartItem.quantity < this.cartItem.maxQuantity) {
+ this.cartItem.quantity++;
+ this.updateTotalPrice();
+ }
+ }
+
+ updateTotalPrice(): void {
+ this.cartItem.totalPrice = this.cartItem.quantity * this.cartItem.price;
+ }
+
+ removeItem(): void {
+ this.remove.emit(this.cartItem);
+ }
+
+ toggleCheckbox(event: MouseEvent): void {
+ // Prevent toggling if clicking directly on the checkbox
+ const target = event.target as HTMLElement;
+ if (target.tagName !== 'INPUT') {
+ this.cartItem.selected = !this.cartItem.selected;
+ }
+ this.selectionChange.emit(this.cartItem);
+ }
+
+}
diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts
index b1add21a..80695de5 100644
--- a/src/app/components/components.module.ts
+++ b/src/app/components/components.module.ts
@@ -5,22 +5,42 @@ 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';
+import { TableComponent } from './table/table.component';
+import { DisplayProductComponent } from './cards/display-product/display-product.component';
+import { ShoppingcartItemComponent } from './cards/shoppingcart-item/shoppingcart-item.component';
+import { FormsModule } from '@angular/forms';
+import { PromptDialogComponent } from './modal/prompt-dialog/prompt-dialog.component';
+import { ToastComponent } from './modal/toast/toast.component';
@NgModule({
imports: [
CommonModule,
RouterModule,
- NgbModule
+ NgbModule,
+ FormsModule,
],
declarations: [
FooterComponent,
NavbarComponent,
- SidebarComponent
+ SidebarComponent,
+ ItemComponent,
+ DisplayProductComponent,
+ TableComponent,
+ ShoppingcartItemComponent,
+ PromptDialogComponent,
+ ToastComponent
],
exports: [
FooterComponent,
NavbarComponent,
- SidebarComponent
+ SidebarComponent,
+ ItemComponent,
+ DisplayProductComponent,
+ TableComponent,
+ ShoppingcartItemComponent,
+ PromptDialogComponent,
+ ToastComponent
]
})
export class ComponentsModule { }
diff --git a/src/app/components/modal/prompt-dialog/prompt-dialog.component.html b/src/app/components/modal/prompt-dialog/prompt-dialog.component.html
new file mode 100644
index 00000000..9a43c337
--- /dev/null
+++ b/src/app/components/modal/prompt-dialog/prompt-dialog.component.html
@@ -0,0 +1,42 @@
+
+
+
\ No newline at end of file
diff --git a/src/app/components/modal/prompt-dialog/prompt-dialog.component.scss b/src/app/components/modal/prompt-dialog/prompt-dialog.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/modal/prompt-dialog/prompt-dialog.component.spec.ts b/src/app/components/modal/prompt-dialog/prompt-dialog.component.spec.ts
new file mode 100644
index 00000000..69066ae7
--- /dev/null
+++ b/src/app/components/modal/prompt-dialog/prompt-dialog.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PromptDialogComponent } from './prompt-dialog.component';
+
+describe('PromptDialogComponent', () => {
+ let component: PromptDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [PromptDialogComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(PromptDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/modal/prompt-dialog/prompt-dialog.component.ts b/src/app/components/modal/prompt-dialog/prompt-dialog.component.ts
new file mode 100644
index 00000000..67c4b036
--- /dev/null
+++ b/src/app/components/modal/prompt-dialog/prompt-dialog.component.ts
@@ -0,0 +1,66 @@
+import { Component, ElementRef, EventEmitter, Input, Output, Renderer2, ViewChild } from '@angular/core';
+
+@Component({
+ selector: 'app-prompt-dialog',
+ templateUrl: './prompt-dialog.component.html',
+ styleUrl: './prompt-dialog.component.scss'
+})
+export class PromptDialogComponent {
+
+ @ViewChild('modal') modalElement!: ElementRef;
+ @Input() title: string = '';
+ @Input() message: string = '';
+ @Input() confirmButtonLabel: string = '';
+ @Input() cancelButtonLabel: string = '';
+ @Output() confirmActionButtonClick = new EventEmitter();
+ @Output() cancelActionButtonClick = new EventEmitter();
+ @Input() buttonColorClass: string = "info"
+ private backdropElement: HTMLElement;
+
+ constructor(private renderer: Renderer2) {
+ // Create the backdrop element and configure its styles
+ this.backdropElement = this.renderer.createElement('div');
+ this.renderer.addClass(this.backdropElement, 'modal-backdrop');
+ this.renderer.addClass(this.backdropElement, 'fade');
+ }
+
+ open() {
+ // Show the modal with fade effect
+ this.renderer.addClass(this.modalElement.nativeElement, 'show');
+ this.modalElement.nativeElement.style.display = 'block';
+ document.body.classList.add('modal-open');
+
+ // Append the backdrop element to the body with fade in
+ this.renderer.appendChild(document.body, this.backdropElement);
+ setTimeout(() => {
+ this.renderer.addClass(this.backdropElement, 'show');
+ }, 10); // Small delay to trigger CSS transition
+ }
+
+ close() {
+ // Remove the modal's show class and hide it
+ this.renderer.removeClass(this.modalElement.nativeElement, 'show');
+ setTimeout(() => {
+ this.modalElement.nativeElement.style.display = 'none';
+ }, 150); // Delay to match CSS fade out duration
+
+ // Remove the modal-open class from the body
+ document.body.classList.remove('modal-open');
+
+ // Fade out and remove the backdrop
+ this.renderer.removeClass(this.backdropElement, 'show');
+ setTimeout(() => {
+ this.renderer.removeChild(document.body, this.backdropElement);
+ }, 150); // Same delay as modal fade out
+ }
+
+ onComfirmButtonActionClick(): void {
+ this.confirmActionButtonClick.emit();
+ this.close();
+ }
+
+ onCancelButtonActionClick(): void {
+ this.cancelActionButtonClick.emit();
+ this.close();
+ }
+}
diff --git a/src/app/components/modal/toast/toast.component.html b/src/app/components/modal/toast/toast.component.html
new file mode 100644
index 00000000..88777187
--- /dev/null
+++ b/src/app/components/modal/toast/toast.component.html
@@ -0,0 +1,7 @@
+
+ {{ message }}
+
\ No newline at end of file
diff --git a/src/app/components/modal/toast/toast.component.scss b/src/app/components/modal/toast/toast.component.scss
new file mode 100644
index 00000000..5b744508
--- /dev/null
+++ b/src/app/components/modal/toast/toast.component.scss
@@ -0,0 +1,9 @@
+:host {
+ position: fixed; /* Ensures the toast is fixed to the viewport */
+ top: 100px; /* Distance from the top of the viewport */
+ left: 50%; /* Center horizontally */
+ transform: translateX(-50%); /* Offset by half its width to center it */
+ z-index: 1050; /* Bootstrap default for modals */
+ pointer-events: none;
+ width: 30%; /* Disable pointer events on the toast host */
+}
\ No newline at end of file
diff --git a/src/app/components/modal/toast/toast.component.spec.ts b/src/app/components/modal/toast/toast.component.spec.ts
new file mode 100644
index 00000000..f9c33e87
--- /dev/null
+++ b/src/app/components/modal/toast/toast.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ToastComponent } from './toast.component';
+
+describe('ToastComponent', () => {
+ let component: ToastComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ToastComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ToastComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/modal/toast/toast.component.ts b/src/app/components/modal/toast/toast.component.ts
new file mode 100644
index 00000000..fe68a917
--- /dev/null
+++ b/src/app/components/modal/toast/toast.component.ts
@@ -0,0 +1,29 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'app-toast',
+ templateUrl: './toast.component.html',
+ styleUrl: './toast.component.scss'
+})
+export class ToastComponent {
+ @Input() message: string = ''
+ @Input() show: boolean = false;
+ @Input() type: 'success' | 'error' | 'info' = 'success'; // You can extend this as needed
+ timeout: any;
+
+ display(message: string, type: 'success' | 'error' | 'info', duration: number = 3000): void {
+ this.message = message;
+ this.type = type;
+ this.show = true;
+
+ clearTimeout(this.timeout);
+ this.timeout = setTimeout(() => {
+ this.hide();
+ }, duration);
+ }
+
+ hide(): void {
+ this.show = false;
+ }
+
+}
diff --git a/src/app/components/modal/toast/toast.service.spec.ts b/src/app/components/modal/toast/toast.service.spec.ts
new file mode 100644
index 00000000..e0413db8
--- /dev/null
+++ b/src/app/components/modal/toast/toast.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { ToastService } from './toast.service';
+
+describe('ToastService', () => {
+ let service: ToastService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(ToastService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/components/modal/toast/toast.service.ts b/src/app/components/modal/toast/toast.service.ts
new file mode 100644
index 00000000..97a31372
--- /dev/null
+++ b/src/app/components/modal/toast/toast.service.ts
@@ -0,0 +1,22 @@
+import { Injectable } from '@angular/core';
+import { ToastComponent } from './toast.component';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ToastService {
+
+ constructor() { }
+
+ private toastComponent: ToastComponent | undefined;
+
+ public registerToast(toast: ToastComponent): void {
+ this.toastComponent = toast;
+ }
+
+ public showToast(message: string, type: 'success' | 'error' | 'info', duration: number = 3000): void {
+ if (this.toastComponent) {
+ this.toastComponent.display(message, type, duration);
+ }
+ }
+}
diff --git a/src/app/components/navbar/navbar.component.html b/src/app/components/navbar/navbar.component.html
index 533724cf..3919f91e 100644
--- a/src/app/components/navbar/navbar.component.html
+++ b/src/app/components/navbar/navbar.component.html
@@ -19,11 +19,12 @@
@@ -31,11 +32,11 @@
-
+
My profile
-
+
-
+
Logout
diff --git a/src/app/components/navbar/navbar.component.ts b/src/app/components/navbar/navbar.component.ts
index e4dd9fb8..fa84faea 100644
--- a/src/app/components/navbar/navbar.component.ts
+++ b/src/app/components/navbar/navbar.component.ts
@@ -1,7 +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 { ActivatedRoute, Router } from '@angular/router';
+import { AuthService } from 'src/app/services/auth.service';
+import { RouteInfo } from 'src/app/models/util/routes.model';
@Component({
selector: 'app-navbar',
@@ -9,28 +11,60 @@ import { Router } from '@angular/router';
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
+ // @Input() routes: RouteInfo[];
+
public focus;
public listTitles: any[];
public location: Location;
- constructor(location: Location, private element: ElementRef, private router: Router) {
+ public urlParent: string = '';
+ public fullName: string ='';
+ public initials: string='';
+
+ constructor(location: Location, private element: ElementRef, private router: Router, private authService: AuthService, private activatedRoute: ActivatedRoute) {
this.location = location;
}
+
ngOnInit() {
- this.listTitles = ROUTES.filter(listTitle => listTitle);
+
+ this.fullName = this.authService.getUserFullName();
+ this.initials = this.authService.getUserInitials();
+ this.urlParent = this.activatedRoute.parent.toString();
+ console.log(this.urlParent);
}
- getTitle(){
- var titlee = this.location.prepareExternalUrl(this.location.path());
- if(titlee.charAt(0) === '#'){
- titlee = titlee.slice( 1 );
- }
+
+ // getTitle(){
+ // 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 title; // default is Dashboard
+ // }
- for(var item = 0; item < this.listTitles.length; item++){
- if(this.listTitles[item].path === titlee){
- return this.listTitles[item].title;
- }
+ getTitle() {
+ const titlee = this.location.prepareExternalUrl(this.location.path()); // Get the URL path
+ const titleArr = titlee.split("/").filter(item => item);
+
+ let title = "Dashboard";
+
+ if (titleArr.length >= 2) {
+ title = `${titleArr[1]}`; // Start with the third element (e.g., "products")
+ for (let i = 2; i < titleArr.length; i++) {
+ title += ` > ${titleArr[i]}`; // Append the remaining parts (e.g., "wae")
+ }
+ } else if (titleArr.length === 2) {
+ title = titleArr[1];
}
- return 'Dashboard';
+
+ return title;
+ }
+
+
+ logout(){
+ this.authService.logout();
+ this.router.navigate(['/auth/login'])
}
}
diff --git a/src/app/components/sidebar/sidebar.component.html b/src/app/components/sidebar/sidebar.component.html
index b8a9dc3c..4add5096 100644
--- a/src/app/components/sidebar/sidebar.component.html
+++ b/src/app/components/sidebar/sidebar.component.html
@@ -7,7 +7,7 @@
-
+
@@ -25,8 +25,9 @@
-
@@ -38,7 +39,7 @@
Welcome!
My profile
-
+
@@ -65,7 +66,7 @@ Welcome!
@@ -99,9 +100,9 @@
Welcome!
-
Documentation
+
-
diff --git a/src/app/components/sidebar/sidebar.component.ts b/src/app/components/sidebar/sidebar.component.ts
index e8e392eb..374be350 100644
--- a/src/app/components/sidebar/sidebar.component.ts
+++ b/src/app/components/sidebar/sidebar.component.ts
@@ -1,20 +1,22 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
+import { RouteInfo } from 'src/app/models/util/routes.model';
+import { AuthService } from 'src/app/services/auth.service';
-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: '/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({
@@ -26,13 +28,22 @@ export class SidebarComponent implements OnInit {
public menuItems: any[];
public isCollapsed = true;
+ @Input() routes : RouteInfo[];
+ userInitials: string='';
- constructor(private router: Router) { }
+ constructor(
+ private router: Router,
+ private authService: AuthService
+ ) { }
+
ngOnInit() {
- this.menuItems = ROUTES.filter(menuItem => menuItem);
+ this.userInitials = this.authService.getUserInitials();
+ if (this.routes) {
+ this.menuItems = this.routes.filter(menuItem => menuItem);
+ }
this.router.events.subscribe((event) => {
this.isCollapsed = true;
- });
+ });
}
}
diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html
new file mode 100644
index 00000000..dc1ad9c5
--- /dev/null
+++ b/src/app/components/table/table.component.html
@@ -0,0 +1,193 @@
+
+
+
+
+
\ 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..ec82b40c
--- /dev/null
+++ b/src/app/components/table/table.component.ts
@@ -0,0 +1,119 @@
+import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
+import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
+import { TableColumn, ColumnType } from 'src/app/models/util/table.model';
+
+@Component({
+ selector: 'app-table',
+ templateUrl: './table.component.html',
+ styleUrl: './table.component.scss'
+})
+export class TableComponent {
+
+ constructor(private sanitizer: DomSanitizer) {}
+
+ //add and row action buttons
+ @Input() useBasicTable: boolean = true;
+ @Input() theme: string = "primary" // css purpose only
+
+ // bottom action button
+ @Input() showBottomActionButton: boolean = false;
+ @Input() bottomActionButtonTitle: string;
+ @Output() bottomActionButtonClick = new EventEmitter();
+ @Input() bottomActionHTML: string = "";
+ sanitizedBottomActionDescription: SafeHtml;
+
+ @Input() title: string = 'Table Title'
+ @Input() dataColumns: TableColumn[]
+ @Input() data: any[]
+
+ @Output() saveRecords = new EventEmitter();
+ @Output() deleteRecord = new EventEmitter();
+ @Output() imageSelected = new EventEmitter<{ record: any; file: File; imgPreviewURL: string }>();
+
+ ColumnType = ColumnType
+ showAddRow = false
+ hasChanges = false
+ newRecords: any[] = []; // array to hold multiple new rows
+
+ ngOnInit() {
+ console.log(this.data);
+ this.sanitizedBottomActionDescription = this.sanitizer.bypassSecurityTrustHtml(this.bottomActionHTML);
+ }
+
+ // Method to validate records
+ validateRecord(record: any): boolean {
+ for (const col of this.dataColumns) {
+ if (col.required && !record[col.field]) {
+ return false; // Return false if any required field is empty
+ }
+ }
+ return true; // All required fields are filled
+ }
+
+ // Method to add a new empty row
+ addRow() {
+ // Add a new row for input
+ const newRow = {}; // Initialize based on your model
+ this.newRecords.push(newRow);
+ this.showAddRow = true; // Optionally show the input row
+ this.hasChanges = true;
+ }
+
+ // Method to remove a specific new row
+ removeNewRow(row: any) {
+ this.newRecords = this.newRecords.filter(r => r !== row); // Filter out the specific row
+ if (this.newRecords.length === 0) {
+ this.hasChanges = false; // Reset flag if there are no new rows
+ }
+ }
+
+ // Method to edit a record
+ editRow(item: any) {
+ // Set the row to edit mode
+ item.isEditing = true;
+ this.hasChanges = true;
+ }
+
+ // Method to remove a record
+ removeRow(item: any) {
+ this.deleteRecord.emit(item);
+ this.data = this.data.filter(i => i !== item); // Remove the item from data
+ // this.hasChanges = true; // Set the flag to true when an item is deleted
+ }
+
+ // Method to save all new records
+ saveAll() {
+ // Combine new records and modified existing records
+ const recordsToSave = [...this.newRecords, ...this.data.filter(item => item.isEditing)];
+
+ // Validate records before saving
+ const allValid = recordsToSave.every(record => this.validateRecord(record));
+
+ if (allValid) {
+ console.log(recordsToSave); // Log the records to be saved
+ this.saveRecords.emit(recordsToSave); // Emit new records to be saved in the parent component
+ this.newRecords = []; // Clear the array after saving
+ this.hasChanges = false; // Reset the changes flag
+ } else {
+ alert('Please fill all required fields.'); // Show a feedback alert if validation fails
+ }
+ }
+
+
+ onImageChange(event: any, record: any) {
+ debugger;
+ const file = event.target.files[0];
+ if (file) {
+ const imgPreviewURL = URL.createObjectURL(file);
+ // Update the record with the new preview URL
+ record.imgPreviewURL = imgPreviewURL;
+ // Emit the selected file along with its preview URL if needed
+ this.imageSelected.emit({ record, file, imgPreviewURL });
+ }
+ }
+
+ onBottomActionButtonClick(): void {
+ this.bottomActionButtonClick.emit(); // Emit the event
+ }
+
+}
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..a43f93f1
--- /dev/null
+++ b/src/app/guards/login.guard.ts
@@ -0,0 +1,26 @@
+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(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;
+ }
+
+}
diff --git a/src/app/layouts/accountant-layout/accountant-layout.component.html b/src/app/layouts/accountant-layout/accountant-layout.component.html
new file mode 100644
index 00000000..d52d2bb5
--- /dev/null
+++ b/src/app/layouts/accountant-layout/accountant-layout.component.html
@@ -0,0 +1,18 @@
+
+
+
\ No newline at end of file
diff --git a/src/app/layouts/accountant-layout/accountant-layout.component.scss b/src/app/layouts/accountant-layout/accountant-layout.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/layouts/accountant-layout/accountant-layout.component.spec.ts b/src/app/layouts/accountant-layout/accountant-layout.component.spec.ts
new file mode 100644
index 00000000..70a2c00d
--- /dev/null
+++ b/src/app/layouts/accountant-layout/accountant-layout.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AccountantLayoutComponent } from './accountant-layout.component';
+
+describe('AccountantLayoutComponent', () => {
+ let component: AccountantLayoutComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AccountantLayoutComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(AccountantLayoutComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/layouts/accountant-layout/accountant-layout.component.ts b/src/app/layouts/accountant-layout/accountant-layout.component.ts
new file mode 100644
index 00000000..c5a943ad
--- /dev/null
+++ b/src/app/layouts/accountant-layout/accountant-layout.component.ts
@@ -0,0 +1,19 @@
+import { Component, OnInit } from '@angular/core';
+import { RouteInfo } from 'src/app/models/util/routes.model';
+import { ACCOUNTANT_ROUTES } from '../routes';
+
+@Component({
+ selector: 'app-accountant-layout',
+ templateUrl: './accountant-layout.component.html',
+ styleUrl: './accountant-layout.component.scss'
+})
+export class AccountantLayoutComponent implements OnInit{
+
+ accountantRouteInfo: RouteInfo[] = ACCOUNTANT_ROUTES;
+
+ constructor() { }
+
+ ngOnInit() {
+
+ }
+}
diff --git a/src/app/layouts/accountant-layout/accountant-layout.module.ts b/src/app/layouts/accountant-layout/accountant-layout.module.ts
new file mode 100644
index 00000000..07ab0908
--- /dev/null
+++ b/src/app/layouts/accountant-layout/accountant-layout.module.ts
@@ -0,0 +1,28 @@
+import { AccountantLayoutRoutes } from './accountant-layout.routing';
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+import { HttpClientModule } from '@angular/common/http';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { ClipboardModule } from 'ngx-clipboard';
+import { ComponentsModule } from 'src/app/components/components.module';
+import { OrderConfirmationComponent } from 'src/app/pages/accountant/order-confirmation/order-confirmation.component';
+
+
+@NgModule({
+ declarations: [
+ OrderConfirmationComponent
+ ],
+ imports: [
+ CommonModule,
+ ComponentsModule,
+ RouterModule.forChild(AccountantLayoutRoutes),
+ FormsModule,
+ HttpClientModule,
+ NgbModule,
+ ClipboardModule
+ ]
+})
+export class AccountantLayoutModule { }
diff --git a/src/app/layouts/accountant-layout/accountant-layout.routing.ts b/src/app/layouts/accountant-layout/accountant-layout.routing.ts
new file mode 100644
index 00000000..1b1b250b
--- /dev/null
+++ b/src/app/layouts/accountant-layout/accountant-layout.routing.ts
@@ -0,0 +1,24 @@
+
+import { Routes } from '@angular/router';
+import { ItemComponent } from 'src/app/components/cards/item/item.component';
+import { DashboardComponent } from 'src/app/pages/dashboard/dashboard.component';
+import { CheckoutComponent } from 'src/app/pages/student/checkout/checkout.component';
+import { ProductsComponent } from 'src/app/pages/student/products/products.component';
+import { OrderDetailsComponent } from 'src/app/pages/student/transactions/order-details/order-details.component';
+import { TransactionsComponent } from 'src/app/pages/student/transactions/transactions.component';
+import { ShoppingcartComponent } from 'src/app/pages/student/shoppingcart/shoppingcart.component';
+import { UserProfileComponent } from 'src/app/pages/user-profile/user-profile.component';
+import { OrderConfirmationComponent } from 'src/app/pages/accountant/order-confirmation/order-confirmation.component';
+
+export const AccountantLayoutRoutes: Routes = [
+ { path: 'products', component: ProductsComponent },
+ { path: 'products/:code', component: ItemComponent },
+ { path: 'dashboard', component: DashboardComponent },
+ { path: 'user-profile', component: UserProfileComponent },
+ { path: 'transactions', component: TransactionsComponent },
+ { path: 'transactions/:transactionId/order-confirmation', component: OrderConfirmationComponent},
+ { path: 'products', component: ProductsComponent },
+ { path: 'shoppingcart', component: ShoppingcartComponent },
+ { path: 'checkout', component: CheckoutComponent },
+ { path: 'products/:code/checkout', component: CheckoutComponent },
+];
\ No newline at end of file
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..7f50de29 100644
--- a/src/app/layouts/admin-layout/admin-layout.component.ts
+++ b/src/app/layouts/admin-layout/admin-layout.component.ts
@@ -1,4 +1,7 @@
import { Component, OnInit } from '@angular/core';
+import { AdminLayoutRoutes } from './admin-layout.routing';
+import { RouteInfo } from 'src/app/models/util/routes.model';
+import { ADMIN_ROUTES } from '../routes';
@Component({
selector: 'app-admin-layout',
@@ -6,6 +9,7 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./admin-layout.component.scss']
})
export class AdminLayoutComponent implements OnInit {
+ adminRoutes:RouteInfo[] = ADMIN_ROUTES;
constructor() { }
diff --git a/src/app/layouts/admin-layout/admin-layout.module.ts b/src/app/layouts/admin-layout/admin-layout.module.ts
index 5c7b0c84..39e2095b 100644
--- a/src/app/layouts/admin-layout/admin-layout.module.ts
+++ b/src/app/layouts/admin-layout/admin-layout.module.ts
@@ -21,7 +21,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
FormsModule,
HttpClientModule,
NgbModule,
- ClipboardModule
+ ClipboardModule,
],
declarations: [
DashboardComponent,
diff --git a/src/app/layouts/auth-layout/auth-layout.component.html b/src/app/layouts/auth-layout/auth-layout.component.html
index f8473126..6c4e1ea4 100644
--- a/src/app/layouts/auth-layout/auth-layout.component.html
+++ b/src/app/layouts/auth-layout/auth-layout.component.html
@@ -1,9 +1,9 @@
-