diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 7352f9c..30221f4 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -11,10 +11,10 @@ export const routes: Routes = [ // { path: 'utilities', loadChildren: () => import('./demo/components/utilities/utilities.module').then(m => m.UtilitiesModule) }, // { path: 'documentation', loadChildren: () => import('./demo/components/documentation/documentation.module').then(m => m.DocumentationModule) }, // { path: 'blocks', loadChildren: () => import('./demo/components/primeblocks/primeblocks.module').then(m => m.PrimeBlocksModule) }, - // { path: 'pages', loadChildren: () => import('./demo/components/pages/pages.module').then(m => m.PagesModule) } + { path: 'pages', loadChildren: () => import('../views/pages/pages.routes')}, ] }, - { path: 'auth', loadChildren: () => import('../views/pages/auth/routes')}, + { path: 'auth', loadChildren: () => import('../views/pages/auth/auth.routes')}, { path: '**', redirectTo: '/notfound' }, ]; diff --git a/src/views/pages/auth/access.ts b/src/views/pages/auth/access.ts index ba32339..4c7fa55 100644 --- a/src/views/pages/auth/access.ts +++ b/src/views/pages/auth/access.ts @@ -4,7 +4,6 @@ import {RouterModule} from '@angular/router'; import {RippleModule} from 'primeng/ripple'; @Component({ - selector: 'app-access', standalone: true, imports: [ ButtonModule, diff --git a/src/views/pages/auth/routes.ts b/src/views/pages/auth/auth.routes.ts similarity index 100% rename from src/views/pages/auth/routes.ts rename to src/views/pages/auth/auth.routes.ts diff --git a/src/views/pages/auth/error.ts b/src/views/pages/auth/error.ts index a5d203c..db5b40e 100644 --- a/src/views/pages/auth/error.ts +++ b/src/views/pages/auth/error.ts @@ -4,7 +4,6 @@ import {RippleModule} from 'primeng/ripple'; import {RouterModule} from '@angular/router'; @Component({ - selector: 'app-error', imports: [ButtonModule, RippleModule, RouterModule], standalone: true, template: ` diff --git a/src/views/pages/auth/login.ts b/src/views/pages/auth/login.ts index 98ae791..7481093 100644 --- a/src/views/pages/auth/login.ts +++ b/src/views/pages/auth/login.ts @@ -10,7 +10,6 @@ import {LayoutService} from '../../../app/layout/service/app.layout.service'; @Component({ - selector: 'app-login', standalone: true, imports: [ ButtonModule, diff --git a/src/views/pages/crud.ts b/src/views/pages/crud.ts new file mode 100644 index 0000000..06f0aa7 --- /dev/null +++ b/src/views/pages/crud.ts @@ -0,0 +1,376 @@ +import {Component, inject, OnInit} from '@angular/core'; +import { MessageService } from 'primeng/api'; +import {Table, TableModule} from 'primeng/table'; +import {CommonModule} from '@angular/common'; +import {FileUploadModule} from 'primeng/fileupload'; +import {FormsModule} from '@angular/forms'; +import {ButtonModule} from 'primeng/button'; +import {RippleModule} from 'primeng/ripple'; +import {ToastModule} from 'primeng/toast'; +import {ToolbarModule} from 'primeng/toolbar'; +import {RatingModule} from 'primeng/rating'; +import {InputTextModule} from 'primeng/inputtext'; +import {TextareaModule} from 'primeng/textarea'; +import {SelectModule} from 'primeng/select'; +import {RadioButtonModule} from 'primeng/radiobutton'; +import {InputNumberModule} from 'primeng/inputnumber'; +import {DialogModule} from 'primeng/dialog'; +import {Product} from '../../app/demo/api/product'; +import {ProductService} from '../../app/demo/service/product.service'; + +@Component({ + standalone: true, + imports: [ + CommonModule, + TableModule, + FileUploadModule, + FormsModule, + ButtonModule, + RippleModule, + ToastModule, + ToolbarModule, + RatingModule, + InputTextModule, + TextareaModule, + SelectModule, + RadioButtonModule, + InputNumberModule, + DialogModule + ], + template: `
+
+
+ + + +
+ + +
+
+ + + + + +
+ + + +
+
Manage Products
+ + + + +
+
+ + + + + + Code + Name + Image + Price + Category + Reviews + Status + + + + + + + + + Code + {{product.code || product.id}} + + + Name + {{product.name}} + + Image + + + + Price + {{product.price | currency:'USD'}} + + + Category + {{product.category}} + + Reviews + + + Status + {{product.inventoryStatus}} + + +
+ + +
+ + +
+
+
+ + + + +
+ + + Name is required. +
+
+ + +
+
+ + + + {{product.inventoryStatus}} + + + {{option.label}} + + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+ + +
+
+
+ + + + + +
+ + +
+ + Are you sure you want to delete {{product.name}}? +
+ + + + +
+ + +
+ + Are you sure you want to delete selected products? +
+ + + + +
+
+
+ `, + providers: [MessageService] +}) +export class Crud implements OnInit { + + productDialog: boolean = false; + + deleteProductDialog: boolean = false; + + deleteProductsDialog: boolean = false; + + products: Product[] = []; + + product: Product = {}; + + selectedProducts: Product[] = []; + + submitted: boolean = false; + + cols: any[] = []; + + statuses: any[] = []; + + rowsPerPageOptions = [5, 10, 20]; + + private messageService = inject(MessageService); + + private productService = inject(ProductService); + + ngOnInit() { + this.productService.getProducts().then(data => this.products = data); + + this.cols = [ + { field: 'product', header: 'Product' }, + { field: 'price', header: 'Price' }, + { field: 'category', header: 'Category' }, + { field: 'rating', header: 'Reviews' }, + { field: 'inventoryStatus', header: 'Status' } + ]; + + this.statuses = [ + { label: 'INSTOCK', value: 'instock' }, + { label: 'LOWSTOCK', value: 'lowstock' }, + { label: 'OUTOFSTOCK', value: 'outofstock' } + ]; + } + + openNew() { + this.product = {}; + this.submitted = false; + this.productDialog = true; + } + + deleteSelectedProducts() { + this.deleteProductsDialog = true; + } + + editProduct(product: Product) { + this.product = { ...product }; + this.productDialog = true; + } + + deleteProduct(product: Product) { + this.deleteProductDialog = true; + this.product = { ...product }; + } + + confirmDeleteSelected() { + this.deleteProductsDialog = false; + this.products = this.products.filter(val => !this.selectedProducts.includes(val)); + this.messageService.add({ severity: 'success', summary: 'Successful', detail: 'Products Deleted', life: 3000 }); + this.selectedProducts = []; + } + + confirmDelete() { + this.deleteProductDialog = false; + this.products = this.products.filter(val => val.id !== this.product.id); + this.messageService.add({ severity: 'success', summary: 'Successful', detail: 'Product Deleted', life: 3000 }); + this.product = {}; + } + + hideDialog() { + this.productDialog = false; + this.submitted = false; + } + + saveProduct() { + this.submitted = true; + + if (this.product.name?.trim()) { + if (this.product.id) { + // @ts-ignore + this.product.inventoryStatus = this.product.inventoryStatus.value ? this.product.inventoryStatus.value : this.product.inventoryStatus; + this.products[this.findIndexById(this.product.id)] = this.product; + this.messageService.add({ severity: 'success', summary: 'Successful', detail: 'Product Updated', life: 3000 }); + } else { + this.product.id = this.createId(); + this.product.code = this.createId(); + this.product.image = 'product-placeholder.svg'; + // @ts-ignore + this.product.inventoryStatus = this.product.inventoryStatus ? this.product.inventoryStatus.value : 'INSTOCK'; + this.products.push(this.product); + this.messageService.add({ severity: 'success', summary: 'Successful', detail: 'Product Created', life: 3000 }); + } + + this.products = [...this.products]; + this.productDialog = false; + this.product = {}; + } + } + + findIndexById(id: string): number { + let index = -1; + for (let i = 0; i < this.products.length; i++) { + if (this.products[i].id === id) { + index = i; + break; + } + } + + return index; + } + + createId(): string { + let id = ''; + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 5; i++) { + id += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return id; + } + + onGlobalFilter(table: Table, event: Event) { + table.filterGlobal((event.target as HTMLInputElement).value, 'contains'); + } +} diff --git a/src/views/pages/documentation.ts b/src/views/pages/documentation.ts new file mode 100644 index 0000000..907d6dc --- /dev/null +++ b/src/views/pages/documentation.ts @@ -0,0 +1,159 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + template: ` +
+
+

Documentation

+

Getting Started

+

Sakai is an application template for Angular and is distributed as a CLI project. Current versions is Angular + v19 with PrimeNG v19. In case CLI is not installed already, use the command below to set it up.

+ +
npm install -g @angular/cli
+ +

Once CLI is ready in your system, extract the contents of the zip file distribution, cd to the directory, + install the libraries from npm and then execute "ng serve" to run the application in your local + environment.

+ +
cd sakai
+npm install
+ng serve
+ +

The application should run at http://localhost:4200/, you may now start + with the development of your application.

+ +
Important CLI Commands
+

Following commands are derived from CLI.

+ +
Run 'ng serve' for a dev server. Navigate to \`http://localhost:4200/\`. The app will automatically reload if you change any of the source files.
+
+Run 'ng generate component component-name' to generate a new component. You can also use \`ng generate directive/pipe/service/class/module\`.
+
+Run 'ng build' to build the project. The build artifacts will be stored in the \`dist/\` directory. Use the \`-prod\` flag for a production build.
+
+Run 'ng test' to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+Run 'ng e2e' to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+Run 'ng help' for more options.
+ +

Structure

+

Sakai consists of 3 main parts; the application layout, layout assets and PrimeNG component theme assets. + Layout is placed inside the src/app/layout folder, + and the assets are in the src/assets/layout folder. +

+ +
Default Configuration
+

Initial layout configuration can be defined at the main app component by injecting the LayoutService, this step is optional and only necessary when + customizing the defaults. Note that theme and + scale are not reactive since theme is configured outside of + Angular at index.html by default and + initial scale is defined with the $scale at layout.scss. When default theme or scale is changed at their files initially, + it is required to configure the layout service with the matching values + to avoid sync issues.

+ +
import { Component, OnInit } from '@angular/core';
+import { PrimeNGConfig } from 'primeng/api';
+import { LayoutService, AppConfig } from './layout/service/app.layout.service';
+
+@Component({
+    selector: 'app-root',
+    templateUrl: './app.component.html'
+})
+export class AppComponent implements OnInit {
+
+    constructor(private primengConfig: PrimeNGConfig, private layoutService: LayoutService) { }
+
+    ngOnInit(): void {
+        this.primengConfig.ripple = true;       //enables core ripple functionality
+
+        //optional configuration with the default configuration
+        const config: AppConfig = {
+            ripple: false,                      //toggles ripple on and off
+            inputStyle: 'outlined',             //default style for input elements
+            menuMode: 'static',                 //layout mode of the menu, valid values are "static" and "overlay"
+            colorScheme: 'light',               //color scheme of the template, valid values are "light" and "dark"
+            theme: 'lara-light-indigo',         //default component theme for PrimeNG
+            scale: 14                           //size of the body font size to scale the whole application
+        };
+        this.layoutService.config.set(config);
+    }
+
+}
+ +
Menu
+

Menu is a separate component defined in src/app/layout/app.menu.component.ts + file and based on PrimeNG MenuModel API. In order to define the menuitems, + navigate to this file and define your own model as a nested structure.

+ +
import { OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+
+@Component({
+    selector: 'app-menu',
+    templateUrl: './app.menu.component.html'
+})
+export class AppMenuComponent implements OnInit {
+
+    model: any[] = [];
+
+    ngOnInit() {
+        this.model = [
+            {
+                label: 'Home',
+                items: [
+                    {
+                        label: 'Dashboard',
+                        icon: 'pi pi-fw pi-home',
+                        routerLink: ['/']
+                    }
+                ]
+            },
+            //...
+        ];
+    }
+}
+ +

Integration with Existing Angular CLI Projects

+

Sakai structure is designed in a modular way so that it can easily be integrated with your existing + application. We've created a short tutorial with details.

+ +
+ +
+ +

Theme

+

Sakai provides 34 PrimeNG themes out of the box. Setup of a theme is simple by including the css of theme + to your bundle that are located inside assets/layout/styles/theme/ + folder such as assets/layout/styles/theme/lara-light-indigo/theme.css.

+ +

Another alternative would be creating dynamic bundles, please see the video + tutorial for an example.

+
+
+ `, + styles: `@media screen and (max-width: 991px) { + .video-container { + position: relative; + width: 100%; + height: 0; + padding-bottom: 56.25%; + + iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + } + }` +}) +export class Documentation { } diff --git a/src/views/pages/empty.ts b/src/views/pages/empty.ts new file mode 100644 index 0000000..9aab1cd --- /dev/null +++ b/src/views/pages/empty.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + template: `
+
+
+

Empty Page

+

This is your empty page template to start building beautiful applications.

+
+
+
` +}) +export class Empty { } diff --git a/src/views/pages/landing.ts b/src/views/pages/landing.ts new file mode 100644 index 0000000..bb66673 --- /dev/null +++ b/src/views/pages/landing.ts @@ -0,0 +1,379 @@ +import {Component, inject} from '@angular/core'; +import {Router, RouterModule} from '@angular/router'; +import {RippleModule} from 'primeng/ripple'; +import {StyleClassModule} from 'primeng/styleclass'; +import {ButtonModule} from 'primeng/button'; +import {DividerModule} from 'primeng/divider'; +import {LayoutService} from '../../app/layout/service/app.layout.service'; + +@Component({ + standalone: true, + imports: [RouterModule, RippleModule, StyleClassModule, ButtonModule, DividerModule], + template:`
+
+ + +
+
+

Eu sem integereget magna fermentum

+

Sed blandit libero volutpat sed cras. Fames ac turpis egestas integer. Placerat in egestas erat...

+ +
+
+ Hero Image +
+
+ +
+
+
+

Marvelous Features

+ Placerat in egestas erat... +
+ +
+
+
+
+ +
+
Easy to Use
+ Posuere morbi leo urna molestie. +
+
+
+ +
+
+
+
+ +
+
Fresh Design
+ Semper risus in hendrerit. +
+
+
+ +
+
+
+
+ +
+
Well Documented
+ Non arcu risus quis varius quam quisque. +
+
+
+ +
+
+
+
+ +
+
Responsive Layout
+ Nulla malesuada pellentesque elit. +
+
+
+ +
+
+
+
+ +
+
Clean Code
+ Condimentum lacinia quis vel eros. +
+
+
+ +
+
+
+
+ +
+
Dark Mode
+ Convallis tellus id interdum velit laoreet. +
+
+
+ +
+
+
+
+ +
+
Ready to Use
+ Mauris sit amet massa vitae. +
+
+
+ +
+
+
+
+ +
+
Modern Practices
+ Elementum nibh tellus molestie nunc non. +
+
+
+ +
+
+
+
+ +
+
Privacy
+ Neque egestas congue quisque. +
+
+
+ +
+
+

Joséphine Miller

+ Peak Interactive +

“Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.”

+ Company logo + +
+
+
+
+ +
+
+

Powerful Everywhere

+ Amet consectetur adipiscing elit... +
+ +
+
+ mockup mobile +
+ +
+
+ +
+

Congue Quisque Egestas

+ Lectus arcu bibendum at varius vel pharetra vel turpis nunc. Eget aliquet nibh praesent tristique magna sit amet purus gravida. Sit amet mattis vulputate enim nulla aliquet. +
+
+ +
+
+
+ +
+

Celerisque Eu Ultrices

+ Adipiscing commodo elit at imperdiet dui. Viverra nibh cras pulvinar mattis nunc sed blandit libero. Suspendisse in est ante in. Mauris pharetra et ultrices neque ornare aenean euismod elementum nisi. +
+ +
+ mockup +
+
+
+ +
+
+

Matchless Pricing

+ Amet consectetur adipiscing elit... +
+ +
+
+
+

Free

+ free +
+ $0 + per month + +
+ +
    +
  • + + Responsive Layout +
  • +
  • + + Unlimited Push Messages +
  • +
  • + + 50 Support Ticket +
  • +
  • + + Free Shipping +
  • +
+
+
+ +
+
+

Startup

+ startup +
+ $1 + per month + +
+ +
    +
  • + + Responsive Layout +
  • +
  • + + Unlimited Push Messages +
  • +
  • + + 50 Support Ticket +
  • +
  • + + Free Shipping +
  • +
+
+
+ +
+
+

Enterprise

+ enterprise +
+ $999 + per month + +
+ +
    +
  • + + Responsive Layout +
  • +
  • + + Unlimited Push Messages +
  • +
  • + + 50 Support Ticket +
  • +
  • + + Free Shipping +
  • +
+
+
+
+
+ +
+
+ + +
+
+ + +
+

Resources

+ Get Started + Learn + Case Studies +
+ +
+

Community

+ Discord + Events + FAQ + Blog +
+ + +
+
+
+
+
+
`, +}) +export class Landing { + layoutService = inject(LayoutService); + + router = inject(Router); +} diff --git a/src/views/pages/notfound.ts b/src/views/pages/notfound.ts new file mode 100644 index 0000000..29b4a97 --- /dev/null +++ b/src/views/pages/notfound.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; +import {RouterModule} from '@angular/router'; + +@Component({ + standalone: true, + imports: [RouterModule], + template: `
+
+ Sakai logo +
+ +
+
+
`, +}) +export class Notfound { } diff --git a/src/views/pages/pages.routes.ts b/src/views/pages/pages.routes.ts new file mode 100644 index 0000000..4be35c6 --- /dev/null +++ b/src/views/pages/pages.routes.ts @@ -0,0 +1,15 @@ +import { Routes } from '@angular/router'; +import {Documentation} from './documentation'; +import {Crud} from './crud'; +import {Landing} from './landing'; +import {Empty} from './empty'; +import {Notfound} from './notfound'; + +export default [ + { path: 'documentation', component: Documentation}, + { path: 'crud', component: Crud}, + { path: 'landing', component: Landing}, + { path: 'empty', component: Empty}, + { path: 'notfound', component: Notfound}, + { path: '**', redirectTo: '/notfound' } +] as Routes;