Initial commit
This commit is contained in:
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
5
.postcssrc.json
Normal file
5
.postcssrc.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"@tailwindcss/postcss": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
.prettierignore
Normal file
14
.prettierignore
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Ignore artifacts:
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
dist
|
||||||
|
out
|
||||||
|
public
|
||||||
|
styles
|
||||||
|
node_modules
|
||||||
|
.vscode
|
||||||
|
.angular
|
||||||
|
*.md
|
||||||
|
*.yml
|
||||||
|
/tsconfig.json
|
||||||
|
*.json
|
||||||
29
.prettierrc.json
Normal file
29
.prettierrc.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"useTabs": false,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 250,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.mts", "*.d.ts"],
|
||||||
|
"options": {
|
||||||
|
"parser": "typescript"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"options": {
|
||||||
|
"parser": "html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.component.html"],
|
||||||
|
"options": {
|
||||||
|
"parser": "angular"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018-2022 PrimeTek
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
59
README.md
Normal file
59
README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Sakai19
|
||||||
|
|
||||||
|
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.5.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
To start a local development server, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate component component-name
|
||||||
|
```
|
||||||
|
|
||||||
|
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the project run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
For end-to-end (e2e) testing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||||
131
angular.json
Normal file
131
angular.json
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"sakai-ng": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/sakai-ng",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"@angular/localize/init"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/assets/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"localize": true
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environments.ts",
|
||||||
|
"with": "src/environments/environments.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"staging": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environments.ts",
|
||||||
|
"with": "src/environments/environments.staging.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"dev-de": {
|
||||||
|
"localize": ["de"]
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"localize": ["en"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "sakai-ng:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "sakai-ng:build:development"
|
||||||
|
},
|
||||||
|
"dev-de": {
|
||||||
|
"buildTarget": "sakai-ng:build:dev-de,development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing",
|
||||||
|
"@angular/localize/init"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/assets/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"sourceLocale": "de-DE",
|
||||||
|
"locales": {
|
||||||
|
"de": {
|
||||||
|
"translation": "src/locale/messages.de.xlf"
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"translation": "src/locale/messages.en.xlf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
|
}
|
||||||
|
}
|
||||||
89
eslint.config.js
Normal file
89
eslint.config.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
export default {
|
||||||
|
root: true,
|
||||||
|
ignorePatterns: ['**/dist/**'],
|
||||||
|
plugins: ['prettier'],
|
||||||
|
extends: ['prettier'],
|
||||||
|
rules: {
|
||||||
|
'padding-line-between-statements': [
|
||||||
|
'error',
|
||||||
|
{ blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
|
||||||
|
{ blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] },
|
||||||
|
{ blankLine: 'any', prev: ['case', 'default'], next: 'break' },
|
||||||
|
{ blankLine: 'any', prev: 'case', next: 'case' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'return' },
|
||||||
|
{ blankLine: 'always', prev: 'block', next: '*' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'block' },
|
||||||
|
{ blankLine: 'always', prev: 'block-like', next: '*' },
|
||||||
|
{ blankLine: 'always', prev: '*', next: 'block-like' },
|
||||||
|
{ blankLine: 'always', prev: ['import'], next: ['const', 'let', 'var'] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.ts'],
|
||||||
|
parserOptions: {
|
||||||
|
project: ['tsconfig.json', 'e2e/tsconfig.json'],
|
||||||
|
createDefaultProgram: true
|
||||||
|
},
|
||||||
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@angular-eslint/recommended', 'plugin:@angular-eslint/template/process-inline-templates', 'prettier'],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'p',
|
||||||
|
style: 'kebab-case'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'p',
|
||||||
|
style: 'camelCase'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-class-suffix': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
suffixes: ['']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@angular-eslint/template/eqeqeq': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allowNullOrUndefined: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@angular-eslint/no-host-metadata-property': 'off',
|
||||||
|
'@angular-eslint/no-output-on-prefix': 'off',
|
||||||
|
'@typescript-eslint/ban-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'off',
|
||||||
|
'arrow-body-style': ['error', 'as-needed'],
|
||||||
|
curly: 0,
|
||||||
|
'@typescript-eslint/member-ordering': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
default: ['public-static-field', 'static-field', 'instance-field', 'public-instance-method', 'public-static-field']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-console': 0,
|
||||||
|
'prefer-const': 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.html'],
|
||||||
|
extends: ['plugin:@angular-eslint/template/recommended', 'prettier'],
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.js'],
|
||||||
|
rules: {
|
||||||
|
parserOptions: {
|
||||||
|
allowImportExportEverywhere: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
19451
package-lock.json
generated
Normal file
19451
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
61
package.json
Normal file
61
package.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"name": "sakai-ng",
|
||||||
|
"version": "20.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"format": "prettier --write \"**/*.{js,mjs,ts,mts,d.ts,html}\" --cache",
|
||||||
|
"test": "ng test"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^20",
|
||||||
|
"@angular/cdk": "^20.2.0",
|
||||||
|
"@angular/common": "^20",
|
||||||
|
"@angular/compiler": "^20",
|
||||||
|
"@angular/core": "^20",
|
||||||
|
"@angular/forms": "^20",
|
||||||
|
"@angular/material": "^20.2.0",
|
||||||
|
"@angular/platform-browser": "^20",
|
||||||
|
"@angular/platform-browser-dynamic": "^20",
|
||||||
|
"@angular/router": "^20",
|
||||||
|
"@primeuix/themes": "^1.2.1",
|
||||||
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
|
"chart.js": "4.4.2",
|
||||||
|
"keycloak-angular": "^20.0.0",
|
||||||
|
"keycloak-js": "^26.2.0",
|
||||||
|
"primeclt": "^0.1.5",
|
||||||
|
"primeicons": "^7.0.0",
|
||||||
|
"primeng": "^20",
|
||||||
|
"quill": "^2.0.3",
|
||||||
|
"rxjs": "~7.8.2",
|
||||||
|
"tailwindcss-primeui": "^0.6.1",
|
||||||
|
"tslib": "^2.8.1",
|
||||||
|
"zone.js": "~0.15.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^20",
|
||||||
|
"@angular/cli": "^20",
|
||||||
|
"@angular/compiler-cli": "^20",
|
||||||
|
"@angular/localize": "^20.2.0",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^9.30.1",
|
||||||
|
"eslint-config-prettier": "^10.1.5",
|
||||||
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
|
"jasmine-core": "~5.8.0",
|
||||||
|
"karma": "~6.4.4",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.1",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"tailwindcss": "^4.1.11",
|
||||||
|
"typescript": "~5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
public/silent-check-sso.html
Normal file
7
public/silent-check-sso.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
parent.postMessage(location.href, location.origin);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10
src/app.component.ts
Normal file
10
src/app.component.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterModule],
|
||||||
|
template: `<router-outlet></router-outlet>`
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
||||||
57
src/app.config.ts
Normal file
57
src/app.config.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
|
||||||
|
import { ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||||
|
import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router';
|
||||||
|
import Aura from '@primeuix/themes/aura';
|
||||||
|
import { providePrimeNG } from 'primeng/config';
|
||||||
|
import { appRoutes } from './app.routes';
|
||||||
|
import { provideKeycloak, withAutoRefreshToken, AutoRefreshTokenService, UserActivityService, includeBearerTokenInterceptor, INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, createInterceptorCondition, IncludeBearerTokenCondition } from 'keycloak-angular';
|
||||||
|
import { environment } from './environments/environments';
|
||||||
|
import { KeycloakOnLoad } from 'keycloak-js';
|
||||||
|
|
||||||
|
const urlCondition = createInterceptorCondition<IncludeBearerTokenCondition>({
|
||||||
|
urlPattern: new RegExp(environment.bearerTokenUrlCondition, 'i')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
// Other providers
|
||||||
|
provideRouter(appRoutes, withInMemoryScrolling({ anchorScrolling: 'enabled', scrollPositionRestoration: 'enabled' }), withEnabledBlockingInitialNavigation()),
|
||||||
|
provideAnimationsAsync(),
|
||||||
|
providePrimeNG({ theme: { preset: Aura, options: { darkModeSelector: '.app-dark' } } }),
|
||||||
|
|
||||||
|
// The official Keycloak Provider
|
||||||
|
// Config see https://www.npmjs.com/package/keycloak-angular#setup
|
||||||
|
provideKeycloak({
|
||||||
|
config: {
|
||||||
|
url: environment.keycloak.config.url,
|
||||||
|
realm: environment.keycloak.config.realm,
|
||||||
|
clientId: environment.keycloak.config.clientId,
|
||||||
|
},
|
||||||
|
initOptions: {
|
||||||
|
onLoad: environment.keycloak.initOptions.onLoad as KeycloakOnLoad,
|
||||||
|
silentCheckSsoRedirectUri: environment.keycloak.initOptions.silentCheckSsoRedirectUri,
|
||||||
|
redirectUri: environment.keycloak.initOptions.redirectUri
|
||||||
|
},
|
||||||
|
features: [
|
||||||
|
withAutoRefreshToken({
|
||||||
|
onInactivityTimeout: 'logout',
|
||||||
|
sessionTimeout: environment.keycloak.sessionTimeout * 60000 // sessionTimeout x 60000ms (1 minute)
|
||||||
|
})
|
||||||
|
],
|
||||||
|
// see https://github.com/mauriciovigolo/keycloak-angular/blob/main/projects/examples/fetch_config/src/app/keycloak.config.ts
|
||||||
|
providers: [
|
||||||
|
AutoRefreshTokenService,
|
||||||
|
UserActivityService,
|
||||||
|
// see https://www.npmjs.com/package/keycloak-angular#httpclient-interceptors
|
||||||
|
{
|
||||||
|
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
||||||
|
useValue: [urlCondition] // <-- Note that multiple conditions might be added.
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
|
||||||
|
// HttpClient MUST come after provideKeycloak
|
||||||
|
provideHttpClient(withFetch(), withInterceptors([includeBearerTokenInterceptor])),
|
||||||
|
]
|
||||||
|
};
|
||||||
62
src/app.routes.ts
Normal file
62
src/app.routes.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { AppLayout } from './app/layout/component/app.layout';
|
||||||
|
import { Dashboard } from './app/pages/dashboard/dashboard';
|
||||||
|
import { Documentation } from './app/pages/documentation/documentation';
|
||||||
|
import { Landing } from './app/pages/landing/landing';
|
||||||
|
import { Notfound } from './app/pages/notfound/notfound';
|
||||||
|
import { Properties } from '@/pages/properties/properties';
|
||||||
|
import { PropertyDetails } from '@/pages/property-details/property-details';
|
||||||
|
import { PropertyManager } from '@/pages/property-manager/property-manager';
|
||||||
|
import { canActivateAuthRole } from '@/guards/auth.guard';
|
||||||
|
import { Projects } from '@/pages/projects/projects';
|
||||||
|
import { ProjectDetails } from '@/pages/project-details/project-details';
|
||||||
|
import { Contacts } from '@/pages/contacts/contacts';
|
||||||
|
|
||||||
|
export const appRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AppLayout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '', component: Dashboard,
|
||||||
|
// data: { role: ['admin', 'can-view-dashboard'] }, canActivate: [canActivateAuthRole]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'projects', component: Projects,
|
||||||
|
data: { role: ['admin', 'can-view-projects'] }, canActivate: [canActivateAuthRole]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'projects/:id', component: ProjectDetails,
|
||||||
|
data: { role: ['admin', 'can-view-projects'] }, canActivate: [canActivateAuthRole]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'contacts', component: Contacts,
|
||||||
|
data: { role: ['admin', 'can-view-contacts'] }, canActivate: [canActivateAuthRole]
|
||||||
|
},
|
||||||
|
|
||||||
|
// admin pages
|
||||||
|
{
|
||||||
|
path: 'properties', component: Properties,
|
||||||
|
data: { role: ['admin', 'can-view-properties'] }, canActivate: [canActivateAuthRole]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'properties/:id', component: PropertyDetails,
|
||||||
|
data: { role: ['admin', 'can-view-properties'] }, canActivate: [canActivateAuthRole]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'admin/properties', component: PropertyManager,
|
||||||
|
data: { role: ['admin', 'can-manage-properties'] }, canActivate: [canActivateAuthRole]
|
||||||
|
},
|
||||||
|
|
||||||
|
{ path: 'uikit', loadChildren: () => import('./app/pages/uikit/uikit.routes') },
|
||||||
|
{ path: 'documentation', component: Documentation },
|
||||||
|
{ path: 'pages', loadChildren: () => import('./app/pages/pages.routes') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ path: 'landing', component: Landing },
|
||||||
|
{ path: 'notfound', component: Notfound },
|
||||||
|
{ path: 'auth', loadChildren: () => import('./app/pages/auth/auth.routes') },
|
||||||
|
{ path: '**', redirectTo: '/notfound' }
|
||||||
|
];
|
||||||
60
src/app/guards/auth.guard.ts
Normal file
60
src/app/guards/auth.guard.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { AuthGuardData, createAuthGuard, KeycloakService } from 'keycloak-angular';
|
||||||
|
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { appConfig } from '../../app.config';
|
||||||
|
import Keycloak from 'keycloak-js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logic below is a simple example, please make it more robust when implementing in your application.
|
||||||
|
*
|
||||||
|
* Reason: isAccessGranted is not validating the resource, since it is merging all roles. Two resources might
|
||||||
|
* have the same role name, and it makes sense to validate it more granular.
|
||||||
|
*/
|
||||||
|
const isAccessAllowed = async (
|
||||||
|
route: ActivatedRouteSnapshot,
|
||||||
|
__: RouterStateSnapshot,
|
||||||
|
authData: AuthGuardData
|
||||||
|
): Promise<boolean | UrlTree> => {
|
||||||
|
const marker_start = '======================= auth.guard >>> =======================';
|
||||||
|
const marker_end = '\n======================= <<< auth.guard =======================';
|
||||||
|
console.debug(marker_start);
|
||||||
|
|
||||||
|
const { authenticated, grantedRoles } = authData;
|
||||||
|
console.debug('authData', authData);
|
||||||
|
// console.debug('authenticated', authenticated);
|
||||||
|
// console.debug('grantedRoles', grantedRoles);
|
||||||
|
// console.debug('grantedRoles - realmRoles', grantedRoles.realmRoles);
|
||||||
|
// console.debug('grantedRoles - resourceRoles', grantedRoles.resourceRoles);
|
||||||
|
|
||||||
|
const requiredRole = route.data['role'];
|
||||||
|
// console.debug('requiredRole', requiredRole);
|
||||||
|
|
||||||
|
if (!requiredRole) {
|
||||||
|
// console.debug('No role required for this route.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = inject(Router);
|
||||||
|
const notAllowed = router.parseUrl('/auth/access');
|
||||||
|
const keycloak = inject(Keycloak);
|
||||||
|
|
||||||
|
if (!authenticated) {
|
||||||
|
console.debug('you are not authenticated. please authenticate first.' + marker_end);
|
||||||
|
// await keycloak.login({ redirectUri: window.location.href });
|
||||||
|
return notAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasRequiredRealmRole = requiredRole.some((role: string) => {
|
||||||
|
return grantedRoles.realmRoles.includes(role);
|
||||||
|
});
|
||||||
|
if (hasRequiredRealmRole) {
|
||||||
|
console.debug('you have the required realm role' + marker_end);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug('you do not have permission to visit this page.' + marker_end);
|
||||||
|
return notAllowed;
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
export const canActivateAuthRole = createAuthGuard<CanActivateFn>(isAccessAllowed);
|
||||||
446
src/app/layout/component/app.configurator.ts
Normal file
446
src/app/layout/component/app.configurator.ts
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
|
import { Component, computed, inject, PLATFORM_ID, signal } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { $t, updatePreset, updateSurfacePalette } from '@primeuix/themes';
|
||||||
|
import Aura from '@primeuix/themes/aura';
|
||||||
|
import Lara from '@primeuix/themes/lara';
|
||||||
|
import Nora from '@primeuix/themes/nora';
|
||||||
|
import { PrimeNG } from 'primeng/config';
|
||||||
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
|
import { LayoutService } from '../service/layout.service';
|
||||||
|
|
||||||
|
const presets = {
|
||||||
|
Aura,
|
||||||
|
Lara,
|
||||||
|
Nora
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
declare type KeyOfType<T> = keyof T extends infer U ? U : never;
|
||||||
|
|
||||||
|
declare type SurfacesType = {
|
||||||
|
name?: string;
|
||||||
|
palette?: {
|
||||||
|
0?: string;
|
||||||
|
50?: string;
|
||||||
|
100?: string;
|
||||||
|
200?: string;
|
||||||
|
300?: string;
|
||||||
|
400?: string;
|
||||||
|
500?: string;
|
||||||
|
600?: string;
|
||||||
|
700?: string;
|
||||||
|
800?: string;
|
||||||
|
900?: string;
|
||||||
|
950?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-configurator',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, FormsModule, SelectButtonModule],
|
||||||
|
template: `
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Primary</span>
|
||||||
|
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
||||||
|
@for (primaryColor of primaryColors(); track primaryColor.name) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
[title]="primaryColor.name"
|
||||||
|
(click)="updateColors($event, 'primary', primaryColor)"
|
||||||
|
[ngClass]="{
|
||||||
|
'outline outline-primary': primaryColor.name === selectedPrimaryColor()
|
||||||
|
}"
|
||||||
|
class="cursor-pointer w-5 h-5 rounded-full flex shrink-0 items-center justify-center outline-offset-1 shadow"
|
||||||
|
[style]="{
|
||||||
|
'background-color': primaryColor?.name === 'noir' ? 'var(--text-color)' : primaryColor?.palette?.['500']
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Surface</span>
|
||||||
|
<div class="pt-2 flex gap-2 flex-wrap justify-start">
|
||||||
|
@for (surface of surfaces; track surface.name) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
[title]="surface.name"
|
||||||
|
(click)="updateColors($event, 'surface', surface)"
|
||||||
|
class="cursor-pointer w-5 h-5 rounded-full flex shrink-0 items-center justify-center p-0 outline-offset-1"
|
||||||
|
[ngClass]="{
|
||||||
|
'outline outline-primary': selectedSurfaceColor() ? selectedSurfaceColor() === surface.name : layoutService.layoutConfig().darkTheme ? surface.name === 'zinc' : surface.name === 'slate'
|
||||||
|
}"
|
||||||
|
[style]="{
|
||||||
|
'background-color': surface?.palette?.['500']
|
||||||
|
}"
|
||||||
|
></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Presets</span>
|
||||||
|
<p-selectbutton [options]="presets" [ngModel]="selectedPreset()" (ngModelChange)="onPresetChange($event)" [allowEmpty]="false" size="small" />
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showMenuModeButton()" class="flex flex-col gap-2">
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Menu Mode</span>
|
||||||
|
<p-selectbutton [ngModel]="menuMode()" (ngModelChange)="onMenuModeChange($event)" [options]="menuModeOptions" [allowEmpty]="false" size="small" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
host: {
|
||||||
|
class: 'hidden absolute top-13 right-0 w-72 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class AppConfigurator {
|
||||||
|
router = inject(Router);
|
||||||
|
|
||||||
|
config: PrimeNG = inject(PrimeNG);
|
||||||
|
|
||||||
|
layoutService: LayoutService = inject(LayoutService);
|
||||||
|
|
||||||
|
platformId = inject(PLATFORM_ID);
|
||||||
|
|
||||||
|
primeng = inject(PrimeNG);
|
||||||
|
|
||||||
|
presets = Object.keys(presets);
|
||||||
|
|
||||||
|
showMenuModeButton = signal(!this.router.url.includes('auth'));
|
||||||
|
|
||||||
|
menuModeOptions = [
|
||||||
|
{ label: 'Static', value: 'static' },
|
||||||
|
{ label: 'Overlay', value: 'overlay' }
|
||||||
|
];
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
this.onPresetChange(this.layoutService.layoutConfig().preset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
surfaces: SurfacesType[] = [
|
||||||
|
{
|
||||||
|
name: 'slate',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f8fafc',
|
||||||
|
100: '#f1f5f9',
|
||||||
|
200: '#e2e8f0',
|
||||||
|
300: '#cbd5e1',
|
||||||
|
400: '#94a3b8',
|
||||||
|
500: '#64748b',
|
||||||
|
600: '#475569',
|
||||||
|
700: '#334155',
|
||||||
|
800: '#1e293b',
|
||||||
|
900: '#0f172a',
|
||||||
|
950: '#020617'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gray',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f9fafb',
|
||||||
|
100: '#f3f4f6',
|
||||||
|
200: '#e5e7eb',
|
||||||
|
300: '#d1d5db',
|
||||||
|
400: '#9ca3af',
|
||||||
|
500: '#6b7280',
|
||||||
|
600: '#4b5563',
|
||||||
|
700: '#374151',
|
||||||
|
800: '#1f2937',
|
||||||
|
900: '#111827',
|
||||||
|
950: '#030712'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zinc',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafafa',
|
||||||
|
100: '#f4f4f5',
|
||||||
|
200: '#e4e4e7',
|
||||||
|
300: '#d4d4d8',
|
||||||
|
400: '#a1a1aa',
|
||||||
|
500: '#71717a',
|
||||||
|
600: '#52525b',
|
||||||
|
700: '#3f3f46',
|
||||||
|
800: '#27272a',
|
||||||
|
900: '#18181b',
|
||||||
|
950: '#09090b'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'neutral',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafafa',
|
||||||
|
100: '#f5f5f5',
|
||||||
|
200: '#e5e5e5',
|
||||||
|
300: '#d4d4d4',
|
||||||
|
400: '#a3a3a3',
|
||||||
|
500: '#737373',
|
||||||
|
600: '#525252',
|
||||||
|
700: '#404040',
|
||||||
|
800: '#262626',
|
||||||
|
900: '#171717',
|
||||||
|
950: '#0a0a0a'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stone',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafaf9',
|
||||||
|
100: '#f5f5f4',
|
||||||
|
200: '#e7e5e4',
|
||||||
|
300: '#d6d3d1',
|
||||||
|
400: '#a8a29e',
|
||||||
|
500: '#78716c',
|
||||||
|
600: '#57534e',
|
||||||
|
700: '#44403c',
|
||||||
|
800: '#292524',
|
||||||
|
900: '#1c1917',
|
||||||
|
950: '#0c0a09'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'soho',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#ececec',
|
||||||
|
100: '#dedfdf',
|
||||||
|
200: '#c4c4c6',
|
||||||
|
300: '#adaeb0',
|
||||||
|
400: '#97979b',
|
||||||
|
500: '#7f8084',
|
||||||
|
600: '#6a6b70',
|
||||||
|
700: '#55565b',
|
||||||
|
800: '#3f4046',
|
||||||
|
900: '#2c2c34',
|
||||||
|
950: '#16161d'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'viva',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f3f3f3',
|
||||||
|
100: '#e7e7e8',
|
||||||
|
200: '#cfd0d0',
|
||||||
|
300: '#b7b8b9',
|
||||||
|
400: '#9fa1a1',
|
||||||
|
500: '#87898a',
|
||||||
|
600: '#6e7173',
|
||||||
|
700: '#565a5b',
|
||||||
|
800: '#3e4244',
|
||||||
|
900: '#262b2c',
|
||||||
|
950: '#0e1315'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ocean',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fbfcfc',
|
||||||
|
100: '#F7F9F8',
|
||||||
|
200: '#EFF3F2',
|
||||||
|
300: '#DADEDD',
|
||||||
|
400: '#B1B7B6',
|
||||||
|
500: '#828787',
|
||||||
|
600: '#5F7274',
|
||||||
|
700: '#415B61',
|
||||||
|
800: '#29444E',
|
||||||
|
900: '#183240',
|
||||||
|
950: '#0c1920'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
selectedPrimaryColor = computed(() => {
|
||||||
|
return this.layoutService.layoutConfig().primary;
|
||||||
|
});
|
||||||
|
|
||||||
|
selectedSurfaceColor = computed(() => this.layoutService.layoutConfig().surface);
|
||||||
|
|
||||||
|
selectedPreset = computed(() => this.layoutService.layoutConfig().preset);
|
||||||
|
|
||||||
|
menuMode = computed(() => this.layoutService.layoutConfig().menuMode);
|
||||||
|
|
||||||
|
primaryColors = computed<SurfacesType[]>(() => {
|
||||||
|
const presetPalette = presets[this.layoutService.layoutConfig().preset as KeyOfType<typeof presets>].primitive;
|
||||||
|
const colors = ['emerald', 'green', 'lime', 'orange', 'amber', 'yellow', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'];
|
||||||
|
const palettes: SurfacesType[] = [{ name: 'noir', palette: {} }];
|
||||||
|
|
||||||
|
colors.forEach((color) => {
|
||||||
|
palettes.push({
|
||||||
|
name: color,
|
||||||
|
palette: presetPalette?.[color as KeyOfType<typeof presetPalette>] as SurfacesType['palette']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return palettes;
|
||||||
|
});
|
||||||
|
|
||||||
|
getPresetExt() {
|
||||||
|
const color: SurfacesType = this.primaryColors().find((c) => c.name === this.selectedPrimaryColor()) || {};
|
||||||
|
const preset = this.layoutService.layoutConfig().preset;
|
||||||
|
|
||||||
|
if (color.name === 'noir') {
|
||||||
|
return {
|
||||||
|
semantic: {
|
||||||
|
primary: {
|
||||||
|
50: '{surface.50}',
|
||||||
|
100: '{surface.100}',
|
||||||
|
200: '{surface.200}',
|
||||||
|
300: '{surface.300}',
|
||||||
|
400: '{surface.400}',
|
||||||
|
500: '{surface.500}',
|
||||||
|
600: '{surface.600}',
|
||||||
|
700: '{surface.700}',
|
||||||
|
800: '{surface.800}',
|
||||||
|
900: '{surface.900}',
|
||||||
|
950: '{surface.950}'
|
||||||
|
},
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.950}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.800}',
|
||||||
|
activeColor: '{primary.700}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.950}',
|
||||||
|
focusBackground: '{primary.700}',
|
||||||
|
color: '#ffffff',
|
||||||
|
focusColor: '#ffffff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.50}',
|
||||||
|
contrastColor: '{primary.950}',
|
||||||
|
hoverColor: '{primary.200}',
|
||||||
|
activeColor: '{primary.300}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.50}',
|
||||||
|
focusBackground: '{primary.300}',
|
||||||
|
color: '{primary.950}',
|
||||||
|
focusColor: '{primary.950}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (preset === 'Nora') {
|
||||||
|
return {
|
||||||
|
semantic: {
|
||||||
|
primary: color.palette,
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.600}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.700}',
|
||||||
|
activeColor: '{primary.800}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.600}',
|
||||||
|
focusBackground: '{primary.700}',
|
||||||
|
color: '#ffffff',
|
||||||
|
focusColor: '#ffffff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.500}',
|
||||||
|
contrastColor: '{surface.900}',
|
||||||
|
hoverColor: '{primary.400}',
|
||||||
|
activeColor: '{primary.300}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.500}',
|
||||||
|
focusBackground: '{primary.400}',
|
||||||
|
color: '{surface.900}',
|
||||||
|
focusColor: '{surface.900}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
semantic: {
|
||||||
|
primary: color.palette,
|
||||||
|
colorScheme: {
|
||||||
|
light: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.500}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.600}',
|
||||||
|
activeColor: '{primary.700}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.50}',
|
||||||
|
focusBackground: '{primary.100}',
|
||||||
|
color: '{primary.700}',
|
||||||
|
focusColor: '{primary.800}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
primary: {
|
||||||
|
color: '{primary.400}',
|
||||||
|
contrastColor: '{surface.900}',
|
||||||
|
hoverColor: '{primary.300}',
|
||||||
|
activeColor: '{primary.200}'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
||||||
|
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
||||||
|
color: 'rgba(255,255,255,.87)',
|
||||||
|
focusColor: 'rgba(255,255,255,.87)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColors(event: any, type: string, color: any) {
|
||||||
|
if (type === 'primary') {
|
||||||
|
this.layoutService.layoutConfig.update((state) => ({ ...state, primary: color.name }));
|
||||||
|
} else if (type === 'surface') {
|
||||||
|
this.layoutService.layoutConfig.update((state) => ({ ...state, surface: color.name }));
|
||||||
|
}
|
||||||
|
this.applyTheme(type, color);
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTheme(type: string, color: any) {
|
||||||
|
if (type === 'primary') {
|
||||||
|
updatePreset(this.getPresetExt());
|
||||||
|
} else if (type === 'surface') {
|
||||||
|
updateSurfacePalette(color.palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPresetChange(event: any) {
|
||||||
|
this.layoutService.layoutConfig.update((state) => ({ ...state, preset: event }));
|
||||||
|
const preset = presets[event as KeyOfType<typeof presets>];
|
||||||
|
const surfacePalette = this.surfaces.find((s) => s.name === this.selectedSurfaceColor())?.palette;
|
||||||
|
$t().preset(preset).preset(this.getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuModeChange(event: string) {
|
||||||
|
this.layoutService.layoutConfig.update((prev) => ({ ...prev, menuMode: event }));
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/app/layout/component/app.floatingconfigurator.ts
Normal file
32
src/app/layout/component/app.floatingconfigurator.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import {Component, computed, inject, input} from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { AppConfigurator } from './app.configurator';
|
||||||
|
import { LayoutService } from '../service/layout.service';
|
||||||
|
import {CommonModule} from "@angular/common";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-floating-configurator',
|
||||||
|
imports: [CommonModule, ButtonModule, StyleClassModule, AppConfigurator],
|
||||||
|
template: `
|
||||||
|
<div class="flex gap-4 top-8 right-8" [ngClass]="{'fixed':float()}">
|
||||||
|
<p-button type="button" (onClick)="toggleDarkMode()" [rounded]="true" [icon]="isDarkTheme() ? 'pi pi-moon' : 'pi pi-sun'" severity="secondary" />
|
||||||
|
<div class="relative">
|
||||||
|
<p-button icon="pi pi-palette" pStyleClass="@next" enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden" leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true" type="button" rounded />
|
||||||
|
<app-configurator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class AppFloatingConfigurator {
|
||||||
|
LayoutService = inject(LayoutService);
|
||||||
|
|
||||||
|
float = input<boolean>(true);
|
||||||
|
|
||||||
|
isDarkTheme = computed(() => this.LayoutService.layoutConfig().darkTheme);
|
||||||
|
|
||||||
|
toggleDarkMode() {
|
||||||
|
this.LayoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme }));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/app/layout/component/app.footer.ts
Normal file
11
src/app/layout/component/app.footer.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-footer',
|
||||||
|
template: `<div class="layout-footer">
|
||||||
|
PROPIFY by
|
||||||
|
<a href="https://itworksonmymachine.de" target="_blank" rel="noopener noreferrer" class="text-primary font-bold hover:underline">itworksonmymachine.de</a>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class AppFooter {}
|
||||||
111
src/app/layout/component/app.layout.ts
Normal file
111
src/app/layout/component/app.layout.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { Component, Renderer2, ViewChild } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
||||||
|
import { filter, Subscription } from 'rxjs';
|
||||||
|
import { AppTopbar } from './app.topbar';
|
||||||
|
import { AppSidebar } from './app.sidebar';
|
||||||
|
import { AppFooter } from './app.footer';
|
||||||
|
import { LayoutService } from '../service/layout.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-layout',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, AppTopbar, AppSidebar, RouterModule, AppFooter],
|
||||||
|
template: `<div class="layout-wrapper" [ngClass]="containerClass">
|
||||||
|
<app-topbar></app-topbar>
|
||||||
|
<app-sidebar></app-sidebar>
|
||||||
|
<div class="layout-main-container">
|
||||||
|
<div class="layout-main">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
<app-footer></app-footer>
|
||||||
|
</div>
|
||||||
|
<div class="layout-mask animate-fadein"></div>
|
||||||
|
</div> `
|
||||||
|
})
|
||||||
|
export class AppLayout {
|
||||||
|
overlayMenuOpenSubscription: Subscription;
|
||||||
|
|
||||||
|
menuOutsideClickListener: any;
|
||||||
|
|
||||||
|
@ViewChild(AppSidebar) appSidebar!: AppSidebar;
|
||||||
|
|
||||||
|
@ViewChild(AppTopbar) appTopBar!: AppTopbar;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public layoutService: LayoutService,
|
||||||
|
public renderer: Renderer2,
|
||||||
|
public router: Router
|
||||||
|
) {
|
||||||
|
this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => {
|
||||||
|
if (!this.menuOutsideClickListener) {
|
||||||
|
this.menuOutsideClickListener = this.renderer.listen('document', 'click', (event) => {
|
||||||
|
if (this.isOutsideClicked(event)) {
|
||||||
|
this.hideMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.layoutService.layoutState().staticMenuMobileActive) {
|
||||||
|
this.blockBodyScroll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(() => {
|
||||||
|
this.hideMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isOutsideClicked(event: MouseEvent) {
|
||||||
|
const sidebarEl = document.querySelector('.layout-sidebar');
|
||||||
|
const topbarEl = document.querySelector('.layout-menu-button');
|
||||||
|
const eventTarget = event.target as Node;
|
||||||
|
|
||||||
|
return !(sidebarEl?.isSameNode(eventTarget) || sidebarEl?.contains(eventTarget) || topbarEl?.isSameNode(eventTarget) || topbarEl?.contains(eventTarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
hideMenu() {
|
||||||
|
this.layoutService.layoutState.update((prev) => ({ ...prev, overlayMenuActive: false, staticMenuMobileActive: false, menuHoverActive: false }));
|
||||||
|
if (this.menuOutsideClickListener) {
|
||||||
|
this.menuOutsideClickListener();
|
||||||
|
this.menuOutsideClickListener = null;
|
||||||
|
}
|
||||||
|
this.unblockBodyScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBodyScroll(): void {
|
||||||
|
if (document.body.classList) {
|
||||||
|
document.body.classList.add('blocked-scroll');
|
||||||
|
} else {
|
||||||
|
document.body.className += ' blocked-scroll';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unblockBodyScroll(): void {
|
||||||
|
if (document.body.classList) {
|
||||||
|
document.body.classList.remove('blocked-scroll');
|
||||||
|
} else {
|
||||||
|
document.body.className = document.body.className.replace(new RegExp('(^|\\b)' + 'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get containerClass() {
|
||||||
|
return {
|
||||||
|
'layout-overlay': this.layoutService.layoutConfig().menuMode === 'overlay',
|
||||||
|
'layout-static': this.layoutService.layoutConfig().menuMode === 'static',
|
||||||
|
'layout-static-inactive': this.layoutService.layoutState().staticMenuDesktopInactive && this.layoutService.layoutConfig().menuMode === 'static',
|
||||||
|
'layout-overlay-active': this.layoutService.layoutState().overlayMenuActive,
|
||||||
|
'layout-mobile-active': this.layoutService.layoutState().staticMenuMobileActive
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.overlayMenuOpenSubscription) {
|
||||||
|
this.overlayMenuOpenSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.menuOutsideClickListener) {
|
||||||
|
this.menuOutsideClickListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
193
src/app/layout/component/app.menu.ts
Normal file
193
src/app/layout/component/app.menu.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { MenuItem } from 'primeng/api';
|
||||||
|
import { AppMenuitem } from './app.menuitem';
|
||||||
|
import Keycloak from 'keycloak-js';
|
||||||
|
import { HasRolePipe } from '@/pipes/has-role-pipe';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-menu',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, AppMenuitem, RouterModule, HasRolePipe],
|
||||||
|
template: `
|
||||||
|
<ul class="layout-menu">
|
||||||
|
@for (rootMenuItem of model; track $index) {
|
||||||
|
<ng-container>
|
||||||
|
@if (!rootMenuItem.separator) {
|
||||||
|
@if (rootMenuItem['roles']) {
|
||||||
|
@if (rootMenuItem['roles'] | hasRole: 'any') {
|
||||||
|
<li app-menuitem [item]="rootMenuItem" [index]="$index" [root]="true"></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if (!rootMenuItem['roles']) {
|
||||||
|
<li app-menuitem [item]="rootMenuItem" [index]="$index" [root]="true"></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (rootMenuItem.separator) {
|
||||||
|
<li class="menu-separator"></li>
|
||||||
|
}
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class AppMenu {
|
||||||
|
model: MenuItem[] = [];
|
||||||
|
protected keycloak = inject(Keycloak);
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.model = [
|
||||||
|
{
|
||||||
|
roles: ['user'],
|
||||||
|
label: 'Home',
|
||||||
|
items: [
|
||||||
|
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: ['/'] },
|
||||||
|
{ label: 'Projekte', icon: 'pi pi-fw pi-list', routerLink: ['/projects'], roles: ['admin', 'can-view-projects'] },
|
||||||
|
{ label: 'Kontakte', icon: 'pi pi-fw pi-id-card', routerLink: ['/contacts'], roles: ['admin', 'can-view-contacts'] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Admin',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
roles: ['admin', 'can-manage-properties'],
|
||||||
|
label: 'Gebäude Verwalten',
|
||||||
|
icon: 'pi pi-fw pi-home',
|
||||||
|
routerLink: ['/admin/properties']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'UI Components',
|
||||||
|
items: [
|
||||||
|
{ label: 'Form Layout', icon: 'pi pi-fw pi-id-card', routerLink: ['/uikit/formlayout'] },
|
||||||
|
{ label: 'Input', icon: 'pi pi-fw pi-check-square', routerLink: ['/uikit/input'] },
|
||||||
|
{ label: 'Button', icon: 'pi pi-fw pi-mobile', class: 'rotated-icon', routerLink: ['/uikit/button'] },
|
||||||
|
{ label: 'Table', icon: 'pi pi-fw pi-table', routerLink: ['/uikit/table'] },
|
||||||
|
{ label: 'Properties', icon: 'pi pi-fw pi-home', routerLink: ['/properties'] },
|
||||||
|
{ label: 'List', icon: 'pi pi-fw pi-list', routerLink: ['/uikit/list'] },
|
||||||
|
{ label: 'Tree', icon: 'pi pi-fw pi-share-alt', routerLink: ['/uikit/tree'] },
|
||||||
|
{ label: 'Panel', icon: 'pi pi-fw pi-tablet', routerLink: ['/uikit/panel'] },
|
||||||
|
{ label: 'Overlay', icon: 'pi pi-fw pi-clone', routerLink: ['/uikit/overlay'] },
|
||||||
|
{ label: 'Media', icon: 'pi pi-fw pi-image', routerLink: ['/uikit/media'] },
|
||||||
|
{ label: 'Menu', icon: 'pi pi-fw pi-bars', routerLink: ['/uikit/menu'] },
|
||||||
|
{ label: 'Message', icon: 'pi pi-fw pi-comment', routerLink: ['/uikit/message'] },
|
||||||
|
{ label: 'File', icon: 'pi pi-fw pi-file', routerLink: ['/uikit/file'] },
|
||||||
|
{ label: 'Chart', icon: 'pi pi-fw pi-chart-bar', routerLink: ['/uikit/charts'] },
|
||||||
|
{ label: 'Timeline', icon: 'pi pi-fw pi-calendar', routerLink: ['/uikit/timeline'] },
|
||||||
|
{ label: 'Misc', icon: 'pi pi-fw pi-circle', routerLink: ['/uikit/misc'] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Pages',
|
||||||
|
icon: 'pi pi-fw pi-briefcase',
|
||||||
|
routerLink: ['/pages'],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Landing',
|
||||||
|
icon: 'pi pi-fw pi-globe',
|
||||||
|
routerLink: ['/landing']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Auth',
|
||||||
|
icon: 'pi pi-fw pi-user',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Login',
|
||||||
|
icon: 'pi pi-fw pi-sign-in',
|
||||||
|
routerLink: ['/auth/login']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Error',
|
||||||
|
icon: 'pi pi-fw pi-times-circle',
|
||||||
|
routerLink: ['/auth/error']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Access Denied',
|
||||||
|
icon: 'pi pi-fw pi-lock',
|
||||||
|
routerLink: ['/auth/access']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Crud',
|
||||||
|
icon: 'pi pi-fw pi-pencil',
|
||||||
|
routerLink: ['/pages/crud']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Not Found',
|
||||||
|
icon: 'pi pi-fw pi-exclamation-circle',
|
||||||
|
routerLink: ['/pages/notfound']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Empty',
|
||||||
|
icon: 'pi pi-fw pi-circle-off',
|
||||||
|
routerLink: ['/pages/empty']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hierarchy',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Submenu 1',
|
||||||
|
icon: 'pi pi-fw pi-bookmark',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Submenu 1.1',
|
||||||
|
icon: 'pi pi-fw pi-bookmark',
|
||||||
|
items: [
|
||||||
|
{ label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-bookmark' },
|
||||||
|
{ label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-bookmark' },
|
||||||
|
{ label: 'Submenu 1.1.3', icon: 'pi pi-fw pi-bookmark' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Submenu 1.2',
|
||||||
|
icon: 'pi pi-fw pi-bookmark',
|
||||||
|
items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-bookmark' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Submenu 2',
|
||||||
|
icon: 'pi pi-fw pi-bookmark',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Submenu 2.1',
|
||||||
|
icon: 'pi pi-fw pi-bookmark',
|
||||||
|
items: [
|
||||||
|
{ label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-bookmark' },
|
||||||
|
{ label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-bookmark' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Submenu 2.2',
|
||||||
|
icon: 'pi pi-fw pi-bookmark',
|
||||||
|
items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-bookmark' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Get Started',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Documentation',
|
||||||
|
icon: 'pi pi-fw pi-book',
|
||||||
|
routerLink: ['/documentation']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View Source',
|
||||||
|
icon: 'pi pi-fw pi-github',
|
||||||
|
url: 'https://github.com/primefaces/sakai-ng',
|
||||||
|
target: '_blank'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/app/layout/component/app.menuitem.ts
Normal file
178
src/app/layout/component/app.menuitem.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import { Component, HostBinding, Input } from '@angular/core';
|
||||||
|
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
||||||
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { filter } from 'rxjs/operators';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { MenuItem } from 'primeng/api';
|
||||||
|
import { LayoutService } from '../service/layout.service';
|
||||||
|
import { HasRolePipe } from '@/pipes/has-role-pipe';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
// eslint-disable-next-line @angular-eslint/component-selector
|
||||||
|
selector: '[app-menuitem]',
|
||||||
|
imports: [CommonModule, RouterModule, RippleModule, HasRolePipe],
|
||||||
|
template: `
|
||||||
|
<ng-container>
|
||||||
|
<div *ngIf="root && item.visible !== false" class="layout-menuitem-root-text">{{ item.label }}</div>
|
||||||
|
<a *ngIf="(!item.routerLink || item.items) && item.visible !== false" [attr.href]="item.url" (click)="itemClick($event)" [ngClass]="item.styleClass" [attr.target]="item.target" tabindex="0" pRipple>
|
||||||
|
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i>
|
||||||
|
<span class="layout-menuitem-text">{{ item.label }}</span>
|
||||||
|
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" *ngIf="item.items"></i>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
*ngIf="item.routerLink && !item.items && item.visible !== false"
|
||||||
|
(click)="itemClick($event)"
|
||||||
|
[ngClass]="item.styleClass"
|
||||||
|
[routerLink]="item.routerLink"
|
||||||
|
routerLinkActive="active-route"
|
||||||
|
[routerLinkActiveOptions]="item.routerLinkActiveOptions || { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' }"
|
||||||
|
[fragment]="item.fragment"
|
||||||
|
[queryParamsHandling]="item.queryParamsHandling"
|
||||||
|
[preserveFragment]="item.preserveFragment"
|
||||||
|
[skipLocationChange]="item.skipLocationChange"
|
||||||
|
[replaceUrl]="item.replaceUrl"
|
||||||
|
[state]="item.state"
|
||||||
|
[queryParams]="item.queryParams"
|
||||||
|
[attr.target]="item.target"
|
||||||
|
tabindex="0"
|
||||||
|
pRipple
|
||||||
|
>
|
||||||
|
<i [ngClass]="item.icon" class="layout-menuitem-icon"></i>
|
||||||
|
<span class="layout-menuitem-text">{{ item.label }}</span>
|
||||||
|
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" *ngIf="item.items"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul *ngIf="item.items && item.visible !== false" [@children]="submenuAnimation">
|
||||||
|
<ng-template ngFor let-child let-i="index" [ngForOf]="item.items">
|
||||||
|
@if (child['roles']) {
|
||||||
|
@if (child['roles'] | hasRole: 'any') {
|
||||||
|
<li app-menuitem [item]="child" [index]="i" [parentKey]="key" [class]="child['badgeClass']"></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if (!child['roles']) {
|
||||||
|
<li app-menuitem [item]="child" [index]="i" [parentKey]="key" [class]="child['badgeClass']"></li>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
</ul>
|
||||||
|
</ng-container>
|
||||||
|
`,
|
||||||
|
animations: [
|
||||||
|
trigger('children', [
|
||||||
|
state(
|
||||||
|
'collapsed',
|
||||||
|
style({
|
||||||
|
height: '0'
|
||||||
|
})
|
||||||
|
),
|
||||||
|
state(
|
||||||
|
'expanded',
|
||||||
|
style({
|
||||||
|
height: '*'
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transition('collapsed <=> expanded', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
|
||||||
|
])
|
||||||
|
],
|
||||||
|
providers: [LayoutService]
|
||||||
|
})
|
||||||
|
export class AppMenuitem {
|
||||||
|
@Input() item!: MenuItem;
|
||||||
|
|
||||||
|
@Input() index!: number;
|
||||||
|
|
||||||
|
@Input() @HostBinding('class.layout-root-menuitem') root!: boolean;
|
||||||
|
|
||||||
|
@Input() parentKey!: string;
|
||||||
|
|
||||||
|
active = false;
|
||||||
|
|
||||||
|
menuSourceSubscription: Subscription;
|
||||||
|
|
||||||
|
menuResetSubscription: Subscription;
|
||||||
|
|
||||||
|
key: string = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public router: Router,
|
||||||
|
private layoutService: LayoutService
|
||||||
|
) {
|
||||||
|
this.menuSourceSubscription = this.layoutService.menuSource$.subscribe((value) => {
|
||||||
|
Promise.resolve(null).then(() => {
|
||||||
|
if (value.routeEvent) {
|
||||||
|
this.active = value.key === this.key || value.key.startsWith(this.key + '-') ? true : false;
|
||||||
|
} else {
|
||||||
|
if (value.key !== this.key && !value.key.startsWith(this.key + '-')) {
|
||||||
|
this.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.menuResetSubscription = this.layoutService.resetSource$.subscribe(() => {
|
||||||
|
this.active = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe((params) => {
|
||||||
|
if (this.item.routerLink) {
|
||||||
|
this.updateActiveStateFromRoute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.key = this.parentKey ? this.parentKey + '-' + this.index : String(this.index);
|
||||||
|
|
||||||
|
if (this.item.routerLink) {
|
||||||
|
this.updateActiveStateFromRoute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActiveStateFromRoute() {
|
||||||
|
let activeRoute = this.router.isActive(this.item.routerLink[0], { paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored' });
|
||||||
|
|
||||||
|
if (activeRoute) {
|
||||||
|
this.layoutService.onMenuStateChange({ key: this.key, routeEvent: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemClick(event: Event) {
|
||||||
|
// avoid processing disabled items
|
||||||
|
if (this.item.disabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute command
|
||||||
|
if (this.item.command) {
|
||||||
|
this.item.command({ originalEvent: event, item: this.item });
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggle active state
|
||||||
|
if (this.item.items) {
|
||||||
|
this.active = !this.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layoutService.onMenuStateChange({ key: this.key });
|
||||||
|
}
|
||||||
|
|
||||||
|
get submenuAnimation() {
|
||||||
|
return this.root ? 'expanded' : this.active ? 'expanded' : 'collapsed';
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('class.active-menuitem')
|
||||||
|
get activeClass() {
|
||||||
|
return this.active && !this.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.menuSourceSubscription) {
|
||||||
|
this.menuSourceSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.menuResetSubscription) {
|
||||||
|
this.menuResetSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/app/layout/component/app.sidebar.ts
Normal file
14
src/app/layout/component/app.sidebar.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Component, ElementRef } from '@angular/core';
|
||||||
|
import { AppMenu } from './app.menu';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sidebar',
|
||||||
|
standalone: true,
|
||||||
|
imports: [AppMenu],
|
||||||
|
template: ` <div class="layout-sidebar">
|
||||||
|
<app-menu></app-menu>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class AppSidebar {
|
||||||
|
constructor(public el: ElementRef) {}
|
||||||
|
}
|
||||||
152
src/app/layout/component/app.topbar.ts
Normal file
152
src/app/layout/component/app.topbar.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { MenuItem } from 'primeng/api';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { AppConfigurator } from './app.configurator';
|
||||||
|
import { LayoutService } from '../service/layout.service';
|
||||||
|
import { Popover } from 'primeng/popover';
|
||||||
|
import Keycloak from 'keycloak-js';
|
||||||
|
import { KEYCLOAK_EVENT_SIGNAL } from 'keycloak-angular';
|
||||||
|
import { Button } from 'primeng/button';
|
||||||
|
import { Tag } from 'primeng/tag';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-topbar',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterModule, CommonModule, StyleClassModule, AppConfigurator, Popover, Button, Tag],
|
||||||
|
template: `
|
||||||
|
<div class="layout-topbar">
|
||||||
|
<div class="layout-topbar-logo-container">
|
||||||
|
<button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
|
||||||
|
<i class="pi pi-bars"></i>
|
||||||
|
</button>
|
||||||
|
<a class="layout-topbar-logo" routerLink="/">
|
||||||
|
<svg viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
<mask id="mask0_1413_1551" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="8"
|
||||||
|
width="54" height="11">
|
||||||
|
<path
|
||||||
|
d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z"
|
||||||
|
fill="var(--primary-color)" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_1413_1551)">
|
||||||
|
<path
|
||||||
|
d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<span>PROPIFY</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
@if (keycloak.authenticated) {
|
||||||
|
<p-tag severity="success" value="Authenticated" />
|
||||||
|
|
||||||
|
@for (item of keycloak.realmAccess?.roles; track $index) {
|
||||||
|
<p-tag severity="info" value="{{ item }}" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if (!keycloak.authenticated) {
|
||||||
|
<p-tag severity="danger" value="Not authenticated" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-topbar-actions">
|
||||||
|
<div class="layout-config-menu">
|
||||||
|
<button type="button" class="layout-topbar-action" (click)="toggleDarkMode()">
|
||||||
|
<i [ngClass]="{ 'pi ': true, 'pi-moon': layoutService.isDarkTheme(), 'pi-sun': !layoutService.isDarkTheme() }"></i>
|
||||||
|
</button>
|
||||||
|
<div class="relative">
|
||||||
|
<button
|
||||||
|
class="layout-topbar-action layout-topbar-action-highlight"
|
||||||
|
pStyleClass="@next"
|
||||||
|
enterFromClass="hidden"
|
||||||
|
enterActiveClass="animate-scalein"
|
||||||
|
leaveToClass="hidden"
|
||||||
|
leaveActiveClass="animate-fadeout"
|
||||||
|
[hideOnOutsideClick]="true"
|
||||||
|
>
|
||||||
|
<i class="pi pi-palette"></i>
|
||||||
|
</button>
|
||||||
|
<app-configurator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="layout-topbar-menu-button layout-topbar-action" pStyleClass="@next"
|
||||||
|
enterFromClass="hidden" enterActiveClass="animate-scalein" leaveToClass="hidden"
|
||||||
|
leaveActiveClass="animate-fadeout" [hideOnOutsideClick]="true">
|
||||||
|
<i class="pi pi-ellipsis-v"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="layout-topbar-menu hidden lg:block">
|
||||||
|
<div class="layout-topbar-menu-content">
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-calendar"></i>
|
||||||
|
<span>Calendar</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-inbox"></i>
|
||||||
|
<span>Messages</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button (click)="userpopover.toggle($event)" type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-user"></i>
|
||||||
|
<span>Profile</span>
|
||||||
|
</button>
|
||||||
|
<p-popover #userpopover>
|
||||||
|
<div class="flex flex-col gap-4 w-[25rem]">
|
||||||
|
@if (loggedIn) {
|
||||||
|
<div>
|
||||||
|
Logged in as <span
|
||||||
|
class="font-medium text-surface-900 dark:text-surface-0 block mb-2">{{ firstName }} {{ lastName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p-button icon="pi pi-sign-out" label="Logout" severity="danger"
|
||||||
|
(click)="logout()" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
<span class="font-medium text-surface-900 dark:text-surface-0 block mb-2">Team Members</span>
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class AppTopbar {
|
||||||
|
items!: MenuItem[];
|
||||||
|
|
||||||
|
protected keycloak = inject(Keycloak);
|
||||||
|
private keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);
|
||||||
|
public loggedIn: boolean = false;
|
||||||
|
public firstName?: string = 'unknown';
|
||||||
|
public lastName?: string = 'unknown';
|
||||||
|
|
||||||
|
constructor(public layoutService: LayoutService) {
|
||||||
|
if (this.keycloak.authenticated) {
|
||||||
|
this.loggedIn = true;
|
||||||
|
|
||||||
|
this.keycloak.loadUserProfile().then((userProfile) => {
|
||||||
|
this.firstName = userProfile.firstName;
|
||||||
|
this.lastName = userProfile.lastName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.keycloak.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDarkMode() {
|
||||||
|
this.layoutService.layoutConfig.update((state) => ({ ...state, darkTheme: !state.darkTheme }));
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/app/layout/service/layout.service.ts
Normal file
178
src/app/layout/service/layout.service.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import { Injectable, effect, signal, computed } from '@angular/core';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
|
export interface layoutConfig {
|
||||||
|
preset?: string;
|
||||||
|
primary?: string;
|
||||||
|
surface?: string | undefined | null;
|
||||||
|
darkTheme?: boolean;
|
||||||
|
menuMode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LayoutState {
|
||||||
|
staticMenuDesktopInactive?: boolean;
|
||||||
|
overlayMenuActive?: boolean;
|
||||||
|
configSidebarVisible?: boolean;
|
||||||
|
staticMenuMobileActive?: boolean;
|
||||||
|
menuHoverActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuChangeEvent {
|
||||||
|
key: string;
|
||||||
|
routeEvent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class LayoutService {
|
||||||
|
_config: layoutConfig = {
|
||||||
|
preset: 'Aura',
|
||||||
|
primary: 'emerald',
|
||||||
|
surface: null,
|
||||||
|
darkTheme: false,
|
||||||
|
menuMode: 'static'
|
||||||
|
};
|
||||||
|
|
||||||
|
_state: LayoutState = {
|
||||||
|
staticMenuDesktopInactive: false,
|
||||||
|
overlayMenuActive: false,
|
||||||
|
configSidebarVisible: false,
|
||||||
|
staticMenuMobileActive: false,
|
||||||
|
menuHoverActive: false
|
||||||
|
};
|
||||||
|
|
||||||
|
layoutConfig = signal<layoutConfig>(this._config);
|
||||||
|
|
||||||
|
layoutState = signal<LayoutState>(this._state);
|
||||||
|
|
||||||
|
private configUpdate = new Subject<layoutConfig>();
|
||||||
|
|
||||||
|
private overlayOpen = new Subject<any>();
|
||||||
|
|
||||||
|
private menuSource = new Subject<MenuChangeEvent>();
|
||||||
|
|
||||||
|
private resetSource = new Subject();
|
||||||
|
|
||||||
|
menuSource$ = this.menuSource.asObservable();
|
||||||
|
|
||||||
|
resetSource$ = this.resetSource.asObservable();
|
||||||
|
|
||||||
|
configUpdate$ = this.configUpdate.asObservable();
|
||||||
|
|
||||||
|
overlayOpen$ = this.overlayOpen.asObservable();
|
||||||
|
|
||||||
|
theme = computed(() => (this.layoutConfig()?.darkTheme ? 'light' : 'dark'));
|
||||||
|
|
||||||
|
isSidebarActive = computed(() => this.layoutState().overlayMenuActive || this.layoutState().staticMenuMobileActive);
|
||||||
|
|
||||||
|
isDarkTheme = computed(() => this.layoutConfig().darkTheme);
|
||||||
|
|
||||||
|
getPrimary = computed(() => this.layoutConfig().primary);
|
||||||
|
|
||||||
|
getSurface = computed(() => this.layoutConfig().surface);
|
||||||
|
|
||||||
|
isOverlay = computed(() => this.layoutConfig().menuMode === 'overlay');
|
||||||
|
|
||||||
|
transitionComplete = signal<boolean>(false);
|
||||||
|
|
||||||
|
private initialized = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
effect(() => {
|
||||||
|
const config = this.layoutConfig();
|
||||||
|
if (config) {
|
||||||
|
this.onConfigUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
effect(() => {
|
||||||
|
const config = this.layoutConfig();
|
||||||
|
|
||||||
|
if (!this.initialized || !config) {
|
||||||
|
this.initialized = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleDarkModeTransition(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDarkModeTransition(config: layoutConfig): void {
|
||||||
|
if ((document as any).startViewTransition) {
|
||||||
|
this.startViewTransition(config);
|
||||||
|
} else {
|
||||||
|
this.toggleDarkMode(config);
|
||||||
|
this.onTransitionEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startViewTransition(config: layoutConfig): void {
|
||||||
|
const transition = (document as any).startViewTransition(() => {
|
||||||
|
this.toggleDarkMode(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
transition.ready
|
||||||
|
.then(() => {
|
||||||
|
this.onTransitionEnd();
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDarkMode(config?: layoutConfig): void {
|
||||||
|
const _config = config || this.layoutConfig();
|
||||||
|
if (_config.darkTheme) {
|
||||||
|
document.documentElement.classList.add('app-dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('app-dark');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTransitionEnd() {
|
||||||
|
this.transitionComplete.set(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.transitionComplete.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuToggle() {
|
||||||
|
if (this.isOverlay()) {
|
||||||
|
this.layoutState.update((prev) => ({ ...prev, overlayMenuActive: !this.layoutState().overlayMenuActive }));
|
||||||
|
|
||||||
|
if (this.layoutState().overlayMenuActive) {
|
||||||
|
this.overlayOpen.next(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isDesktop()) {
|
||||||
|
this.layoutState.update((prev) => ({ ...prev, staticMenuDesktopInactive: !this.layoutState().staticMenuDesktopInactive }));
|
||||||
|
} else {
|
||||||
|
this.layoutState.update((prev) => ({ ...prev, staticMenuMobileActive: !this.layoutState().staticMenuMobileActive }));
|
||||||
|
|
||||||
|
if (this.layoutState().staticMenuMobileActive) {
|
||||||
|
this.overlayOpen.next(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDesktop() {
|
||||||
|
return window.innerWidth > 991;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMobile() {
|
||||||
|
return !this.isDesktop();
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfigUpdate() {
|
||||||
|
this._config = { ...this.layoutConfig() };
|
||||||
|
this.configUpdate.next(this.layoutConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuStateChange(event: MenuChangeEvent) {
|
||||||
|
this.menuSource.next(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.resetSource.next(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/app/pages/auth/access.ts
Normal file
32
src/app/pages/auth/access.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-access',
|
||||||
|
standalone: true,
|
||||||
|
imports: [ButtonModule, RouterModule, RippleModule, AppFloatingConfigurator, ButtonModule],
|
||||||
|
template: ` <app-floating-configurator />
|
||||||
|
<div class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-screen overflow-hidden">
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, rgba(247, 149, 48, 0.4) 10%, rgba(247, 149, 48, 0) 30%)">
|
||||||
|
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20 flex flex-col items-center" style="border-radius: 53px">
|
||||||
|
<div class="gap-4 flex flex-col items-center">
|
||||||
|
<div class="flex justify-center items-center border-2 border-orange-500 rounded-full" style="width: 3.2rem; height: 3.2rem">
|
||||||
|
<i class="text-orange-500 pi pi-fw pi-lock text-2xl!"></i>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-surface-900 dark:text-surface-0 font-bold text-4xl lg:text-5xl mb-2">Access Denied</h1>
|
||||||
|
<span class="text-muted-color mb-8">You do not have the necessary permisions. Please contact admins.</span>
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/auth/asset-access.svg" alt="Access denied" class="mb-8" width="80%" />
|
||||||
|
<div class="col-span-12 mt-8 text-center">
|
||||||
|
<p-button label="Go to Dashboard" routerLink="/" severity="warn" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class Access {}
|
||||||
10
src/app/pages/auth/auth.routes.ts
Normal file
10
src/app/pages/auth/auth.routes.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { Access } from './access';
|
||||||
|
import { Login } from './login';
|
||||||
|
import { Error } from './error';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{ path: 'access', component: Access },
|
||||||
|
{ path: 'error', component: Error },
|
||||||
|
{ path: 'login', component: Login }
|
||||||
|
] as Routes;
|
||||||
32
src/app/pages/auth/error.ts
Normal file
32
src/app/pages/auth/error.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-error',
|
||||||
|
imports: [ButtonModule, RippleModule, RouterModule, AppFloatingConfigurator, ButtonModule],
|
||||||
|
standalone: true,
|
||||||
|
template: ` <app-floating-configurator />
|
||||||
|
<div class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-screen overflow-hidden">
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, rgba(233, 30, 99, 0.4) 10%, rgba(33, 150, 243, 0) 30%)">
|
||||||
|
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20 flex flex-col items-center" style="border-radius: 53px">
|
||||||
|
<div class="gap-4 flex flex-col items-center">
|
||||||
|
<div class="flex justify-center items-center border-2 border-pink-500 rounded-full" style="height: 3.2rem; width: 3.2rem">
|
||||||
|
<i class="pi pi-fw pi-exclamation-circle text-2xl! text-pink-500"></i>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-surface-900 dark:text-surface-0 font-bold text-5xl mb-2">Error Occured</h1>
|
||||||
|
<span class="text-muted-color mb-8">Requested resource is not available.</span>
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/auth/asset-error.svg" alt="Error" class="mb-8" width="80%" />
|
||||||
|
<div class="col-span-12 mt-8 text-center">
|
||||||
|
<p-button label="Go to Dashboard" routerLink="/" severity="danger" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class Error {}
|
||||||
71
src/app/pages/auth/login.ts
Normal file
71
src/app/pages/auth/login.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { PasswordModule } from 'primeng/password';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
standalone: true,
|
||||||
|
imports: [ButtonModule, CheckboxModule, InputTextModule, PasswordModule, FormsModule, RouterModule, RippleModule, AppFloatingConfigurator],
|
||||||
|
template: `
|
||||||
|
<app-floating-configurator />
|
||||||
|
<div class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-screen overflow-hidden">
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
|
||||||
|
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<svg viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-8 w-16 shrink-0 mx-auto">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
<mask id="mask0_1413_1551" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="8" width="54" height="11">
|
||||||
|
<path d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z" fill="var(--primary-color)" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_1413_1551)">
|
||||||
|
<path
|
||||||
|
d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mb-4">Welcome to PrimeLand!</div>
|
||||||
|
<span class="text-muted-color font-medium">Sign in to continue</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="email1" class="block text-surface-900 dark:text-surface-0 text-xl font-medium mb-2">Email</label>
|
||||||
|
<input pInputText id="email1" type="text" placeholder="Email address" class="w-full md:w-120 mb-8" [(ngModel)]="email" />
|
||||||
|
|
||||||
|
<label for="password1" class="block text-surface-900 dark:text-surface-0 font-medium text-xl mb-2">Password</label>
|
||||||
|
<p-password id="password1" [(ngModel)]="password" placeholder="Password" [toggleMask]="true" styleClass="mb-4" [fluid]="true" [feedback]="false"></p-password>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-2 mb-8 gap-8">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p-checkbox [(ngModel)]="checked" id="rememberme1" binary class="mr-2"></p-checkbox>
|
||||||
|
<label for="rememberme1">Remember me</label>
|
||||||
|
</div>
|
||||||
|
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-primary">Forgot password?</span>
|
||||||
|
</div>
|
||||||
|
<p-button label="Sign In" styleClass="w-full" routerLink="/"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class Login {
|
||||||
|
email: string = '';
|
||||||
|
|
||||||
|
password: string = '';
|
||||||
|
|
||||||
|
checked: boolean = false;
|
||||||
|
}
|
||||||
353
src/app/pages/contacts/contacts.html
Normal file
353
src/app/pages/contacts/contacts.html
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 text-left">
|
||||||
|
<div class="mb-5">
|
||||||
|
<h1 i18n>Contacts</h1>
|
||||||
|
<p class="my-4 text-lg text-gray-500">Verwalten Sie Personen und Organisationen in Ihrem Netzwerk</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 text-right">
|
||||||
|
|
||||||
|
<p-button i18n-label label="Exportieren" class="p-button-outlined">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-base">download</span>
|
||||||
|
</span>
|
||||||
|
</p-button>
|
||||||
|
|
||||||
|
<p-button i18n-label label="Kontakt hinzufügen" class="p-button-outlined ml-4">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-base">person_add</span>
|
||||||
|
</span>
|
||||||
|
</p-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-8 mb-8">
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-0w-full max-w-sm">
|
||||||
|
<!-- Search Input -->
|
||||||
|
<p-iconfield class="h-10">
|
||||||
|
<p-inputicon class="pi pi-search" />
|
||||||
|
<input type="text" pInputText class="h-full" placeholder="Projekte durchsuchen..." />
|
||||||
|
</p-iconfield>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-0w-full max-w-sm">
|
||||||
|
<!-- SelectButton -->
|
||||||
|
<p-selectButton
|
||||||
|
[options]="filterOptions"
|
||||||
|
[(ngModel)]="filterBy"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
class="h-10">
|
||||||
|
|
||||||
|
<ng-template let-item pTemplate="item">
|
||||||
|
<span class="inline-flex items-center gap-1 justify-center h-full">
|
||||||
|
<mat-icon>{{ item.icon }}</mat-icon>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-selectButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-8 mb-8">
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p-avatar label="AE" class="mr-2" size="normal" shape="circle" />
|
||||||
|
<span class="font-bold">Amy Elsner</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 text-left">
|
||||||
|
<p-button label="Details" variant="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-right">
|
||||||
|
<p-button variant="outlined" aria-label="E-Mail">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">mail</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Phone" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">phone</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Delete" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">delete</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #icons>
|
||||||
|
<p-chip class="!text-xs">Person</p-chip>
|
||||||
|
</ng-template>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">mail</span>j.boedige@gzm-mainz.de
|
||||||
|
</p>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">phone</span> 0171 3150850
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p-avatar label="AE" class="mr-2" size="normal" shape="circle" />
|
||||||
|
<span class="font-bold">Amy Elsner</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 text-left">
|
||||||
|
<p-button label="Details" variant="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-right">
|
||||||
|
<p-button variant="outlined" aria-label="E-Mail">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">mail</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Phone" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">phone</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Delete" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">delete</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #icons>
|
||||||
|
<p-chip class="!text-xs">Person</p-chip>
|
||||||
|
</ng-template>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">mail</span>j.boedige@gzm-mainz.de
|
||||||
|
</p>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">phone</span> 0171 3150850
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p-avatar label="AE" class="mr-2" size="normal" shape="circle" />
|
||||||
|
<span class="font-bold">Amy Elsner</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 text-left">
|
||||||
|
<p-button label="Details" variant="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-right">
|
||||||
|
<p-button variant="outlined" aria-label="E-Mail">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">mail</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Phone" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">phone</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Delete" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">delete</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #icons>
|
||||||
|
<p-chip class="!text-xs">Person</p-chip>
|
||||||
|
</ng-template>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">mail</span>j.boedige@gzm-mainz.de
|
||||||
|
</p>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">phone</span> 0171 3150850
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p-avatar label="AE" class="mr-2" size="normal" shape="circle" />
|
||||||
|
<span class="font-bold">Amy Elsner</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 text-left">
|
||||||
|
<p-button label="Details" variant="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-right">
|
||||||
|
<p-button variant="outlined" aria-label="E-Mail">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">mail</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Phone" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">phone</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Delete" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">delete</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #icons>
|
||||||
|
<p-chip class="!text-xs">Person</p-chip>
|
||||||
|
</ng-template>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">mail</span>j.boedige@gzm-mainz.de
|
||||||
|
</p>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">phone</span> 0171 3150850
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p-avatar label="AE" class="mr-2" size="normal" shape="circle" />
|
||||||
|
<span class="font-bold">Amy Elsner</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 text-left">
|
||||||
|
<p-button label="Details" variant="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-right">
|
||||||
|
<p-button variant="outlined" aria-label="E-Mail">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">mail</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Phone" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">phone</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
<p-button variant="outlined" aria-label="Delete" class="ml-2">
|
||||||
|
<ng-template #icon>
|
||||||
|
<span class="material-icons !text-base">delete</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #icons>
|
||||||
|
<p-chip class="!text-xs">Person</p-chip>
|
||||||
|
</ng-template>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">mail</span>j.boedige@gzm-mainz.de
|
||||||
|
</p>
|
||||||
|
<p class="flex flex-wrap items-center">
|
||||||
|
<span class="material-icons !text-base mr-2">phone</span> 0171 3150850
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-8">
|
||||||
|
<!-- STATS -->
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-green-100 dark:bg-green-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-green-500">contact_phone</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">1</span>
|
||||||
|
<span class="text-sm text-gray-500" i18n>Contacts total</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-green-100 dark:bg-green-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-green-500">group</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">0</span>
|
||||||
|
<span class="text-sm text-gray-500" i18n>Persons</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-green-100 dark:bg-green-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-green-500">corporate_fare</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">0</span>
|
||||||
|
<span class="text-sm text-gray-500" i18n>Organisations</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-green-100 dark:bg-green-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-green-500">note_alt</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">12</span>
|
||||||
|
<span class="text-sm text-gray-500" i18n>With notes</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
0
src/app/pages/contacts/contacts.scss
Normal file
0
src/app/pages/contacts/contacts.scss
Normal file
23
src/app/pages/contacts/contacts.spec.ts
Normal file
23
src/app/pages/contacts/contacts.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Contacts } from './contacts';
|
||||||
|
|
||||||
|
describe('Contacts', () => {
|
||||||
|
let component: Contacts;
|
||||||
|
let fixture: ComponentFixture<Contacts>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Contacts]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Contacts);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
27
src/app/pages/contacts/contacts.ts
Normal file
27
src/app/pages/contacts/contacts.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Button } from 'primeng/button';
|
||||||
|
import { IconField } from 'primeng/iconfield';
|
||||||
|
import { InputIcon } from 'primeng/inputicon';
|
||||||
|
import { InputText } from 'primeng/inputtext';
|
||||||
|
import { PrimeTemplate } from 'primeng/api';
|
||||||
|
import { SelectButton } from 'primeng/selectbutton';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { Panel } from 'primeng/panel';
|
||||||
|
import { Chip } from 'primeng/chip';
|
||||||
|
import { Avatar } from 'primeng/avatar';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-contacts',
|
||||||
|
imports: [Button, IconField, InputIcon, InputText, MatIcon, PrimeTemplate, SelectButton, FormsModule, Panel, Chip, Avatar],
|
||||||
|
templateUrl: './contacts.html',
|
||||||
|
styleUrl: './contacts.scss'
|
||||||
|
})
|
||||||
|
export class Contacts {
|
||||||
|
protected filterBy = 'all';
|
||||||
|
filterOptions: any[] = [
|
||||||
|
{ label: 'All', value: 'all', icon: 'all_inclusive' },
|
||||||
|
{ label: 'Persons', value: 'persons', icon: 'group' },
|
||||||
|
{ label: 'Organisations', value: 'organisations', icon: 'corporate_fare' }
|
||||||
|
];
|
||||||
|
}
|
||||||
387
src/app/pages/crud/crud.ts
Normal file
387
src/app/pages/crud/crud.ts
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
import { Component, OnInit, signal, ViewChild } from '@angular/core';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { Table, TableModule } from 'primeng/table';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
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 { TagModule } from 'primeng/tag';
|
||||||
|
import { InputIconModule } from 'primeng/inputicon';
|
||||||
|
import { IconFieldModule } from 'primeng/iconfield';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
|
import { Property, ProductService } from '../service/product.service';
|
||||||
|
|
||||||
|
interface Column {
|
||||||
|
field: string;
|
||||||
|
header: string;
|
||||||
|
customExportHeader?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportColumn {
|
||||||
|
title: string;
|
||||||
|
dataKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-crud',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TableModule,
|
||||||
|
FormsModule,
|
||||||
|
ButtonModule,
|
||||||
|
RippleModule,
|
||||||
|
ToastModule,
|
||||||
|
ToolbarModule,
|
||||||
|
RatingModule,
|
||||||
|
InputTextModule,
|
||||||
|
TextareaModule,
|
||||||
|
SelectModule,
|
||||||
|
RadioButtonModule,
|
||||||
|
InputNumberModule,
|
||||||
|
DialogModule,
|
||||||
|
TagModule,
|
||||||
|
InputIconModule,
|
||||||
|
IconFieldModule,
|
||||||
|
ConfirmDialogModule
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<p-toolbar styleClass="mb-6">
|
||||||
|
<ng-template #start>
|
||||||
|
<p-button label="New" icon="pi pi-plus" severity="secondary" class="mr-2" (onClick)="openNew()" />
|
||||||
|
<p-button severity="secondary" label="Delete" icon="pi pi-trash" outlined (onClick)="deleteSelectedProducts()" [disabled]="!selectedProducts || !selectedProducts.length" />
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #end>
|
||||||
|
<p-button label="Export" icon="pi pi-upload" severity="secondary" (onClick)="exportCSV()" />
|
||||||
|
</ng-template>
|
||||||
|
</p-toolbar>
|
||||||
|
|
||||||
|
<p-table
|
||||||
|
#dt
|
||||||
|
[value]="products()"
|
||||||
|
[rows]="10"
|
||||||
|
[columns]="cols"
|
||||||
|
[paginator]="true"
|
||||||
|
[globalFilterFields]="['name', 'country.name', 'representative.name', 'status']"
|
||||||
|
[tableStyle]="{ 'min-width': '75rem' }"
|
||||||
|
[(selection)]="selectedProducts"
|
||||||
|
[rowHover]="true"
|
||||||
|
dataKey="id"
|
||||||
|
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products"
|
||||||
|
[showCurrentPageReport]="true"
|
||||||
|
[rowsPerPageOptions]="[10, 20, 30]"
|
||||||
|
>
|
||||||
|
<ng-template #caption>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h5 class="m-0">Manage Products</h5>
|
||||||
|
<p-iconfield>
|
||||||
|
<p-inputicon styleClass="pi pi-search" />
|
||||||
|
<input pInputText type="text" (input)="onGlobalFilter(dt, $event)" placeholder="Search..." />
|
||||||
|
</p-iconfield>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 3rem">
|
||||||
|
<p-tableHeaderCheckbox />
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 16rem">Code</th>
|
||||||
|
<th pSortableColumn="name" style="min-width:16rem">
|
||||||
|
Name
|
||||||
|
<p-sortIcon field="name" />
|
||||||
|
</th>
|
||||||
|
<th>Image</th>
|
||||||
|
<th pSortableColumn="price" style="min-width: 8rem">
|
||||||
|
Price
|
||||||
|
<p-sortIcon field="price" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="category" style="min-width:10rem">
|
||||||
|
Category
|
||||||
|
<p-sortIcon field="category" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="rating" style="min-width: 12rem">
|
||||||
|
Reviews
|
||||||
|
<p-sortIcon field="rating" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="inventoryStatus" style="min-width: 12rem">
|
||||||
|
Status
|
||||||
|
<p-sortIcon field="inventoryStatus" />
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 12rem"></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-product>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 3rem">
|
||||||
|
<p-tableCheckbox [value]="product" />
|
||||||
|
</td>
|
||||||
|
<td style="min-width: 12rem">{{ product.code }}</td>
|
||||||
|
<td style="min-width: 16rem">{{ product.name }}</td>
|
||||||
|
<td>
|
||||||
|
<img [src]="'https://primefaces.org/cdn/primeng/images/demo/product/' + product.image" [alt]="product.name" style="width: 64px" class="rounded" />
|
||||||
|
</td>
|
||||||
|
<td>{{ product.price | currency: 'USD' }}</td>
|
||||||
|
<td>{{ product.category }}</td>
|
||||||
|
<td>
|
||||||
|
<p-rating [(ngModel)]="product.rating" [readonly]="true" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-tag [value]="product.inventoryStatus" [severity]="getSeverity(product.inventoryStatus)" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-button icon="pi pi-pencil" class="mr-2" [rounded]="true" [outlined]="true" (click)="editProduct(product)" />
|
||||||
|
<p-button icon="pi pi-trash" severity="danger" [rounded]="true" [outlined]="true" (click)="deleteProduct(product)" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
|
||||||
|
<p-dialog [(visible)]="productDialog" [style]="{ width: '450px' }" header="Product Details" [modal]="true">
|
||||||
|
<ng-template #content>
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<img [src]="'https://primefaces.org/cdn/primeng/images/demo/product/' + product.image" [alt]="product.image" class="block m-auto pb-4" *ngIf="product.image" />
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block font-bold mb-3">Name</label>
|
||||||
|
<input type="text" pInputText id="name" [(ngModel)]="product.name" required autofocus fluid />
|
||||||
|
<small class="text-red-500" *ngIf="submitted && !product.name">Name is required.</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block font-bold mb-3">Description</label>
|
||||||
|
<textarea id="description" pTextarea [(ngModel)]="product.description" required rows="3" cols="20" fluid></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="inventoryStatus" class="block font-bold mb-3">Inventory Status</label>
|
||||||
|
<p-select [(ngModel)]="product.inventoryStatus" inputId="inventoryStatus" [options]="statuses" optionLabel="label" optionValue="label" placeholder="Select a Status" fluid />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="block font-bold mb-4">Category</span>
|
||||||
|
<div class="grid grid-cols-12 gap-4">
|
||||||
|
<div class="flex items-center gap-2 col-span-6">
|
||||||
|
<p-radiobutton id="category1" name="category" value="Accessories" [(ngModel)]="product.category" />
|
||||||
|
<label for="category1">Accessories</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 col-span-6">
|
||||||
|
<p-radiobutton id="category2" name="category" value="Clothing" [(ngModel)]="product.category" />
|
||||||
|
<label for="category2">Clothing</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 col-span-6">
|
||||||
|
<p-radiobutton id="category3" name="category" value="Electronics" [(ngModel)]="product.category" />
|
||||||
|
<label for="category3">Electronics</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 col-span-6">
|
||||||
|
<p-radiobutton id="category4" name="category" value="Fitness" [(ngModel)]="product.category" />
|
||||||
|
<label for="category4">Fitness</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-4">
|
||||||
|
<div class="col-span-6">
|
||||||
|
<label for="price" class="block font-bold mb-3">Price</label>
|
||||||
|
<p-inputnumber id="price" [(ngModel)]="product.price" mode="currency" currency="USD" locale="en-US" fluid />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-6">
|
||||||
|
<label for="quantity" class="block font-bold mb-3">Quantity</label>
|
||||||
|
<p-inputnumber id="quantity" [(ngModel)]="product.quantity" fluid />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #footer>
|
||||||
|
<p-button label="Cancel" icon="pi pi-times" text (click)="hideDialog()" />
|
||||||
|
<p-button label="Save" icon="pi pi-check" (click)="saveProduct()" />
|
||||||
|
</ng-template>
|
||||||
|
</p-dialog>
|
||||||
|
|
||||||
|
<p-confirmdialog [style]="{ width: '450px' }" />
|
||||||
|
`,
|
||||||
|
providers: [MessageService, ProductService, ConfirmationService]
|
||||||
|
})
|
||||||
|
export class Crud implements OnInit {
|
||||||
|
productDialog: boolean = false;
|
||||||
|
|
||||||
|
products = signal<Property[]>([]);
|
||||||
|
|
||||||
|
product!: Property;
|
||||||
|
|
||||||
|
selectedProducts!: Property[] | null;
|
||||||
|
|
||||||
|
submitted: boolean = false;
|
||||||
|
|
||||||
|
statuses!: any[];
|
||||||
|
|
||||||
|
@ViewChild('dt') dt!: Table;
|
||||||
|
|
||||||
|
exportColumns!: ExportColumn[];
|
||||||
|
|
||||||
|
cols!: Column[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private productService: ProductService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private confirmationService: ConfirmationService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
exportCSV() {
|
||||||
|
this.dt.exportCSV();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loadDemoData();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDemoData() {
|
||||||
|
this.productService.getProducts().then((data) => {
|
||||||
|
this.products.set(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.statuses = [
|
||||||
|
{ label: 'INSTOCK', value: 'instock' },
|
||||||
|
{ label: 'LOWSTOCK', value: 'lowstock' },
|
||||||
|
{ label: 'OUTOFSTOCK', value: 'outofstock' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.cols = [
|
||||||
|
{ field: 'code', header: 'Code', customExportHeader: 'Product Code' },
|
||||||
|
{ field: 'name', header: 'Name' },
|
||||||
|
{ field: 'image', header: 'Image' },
|
||||||
|
{ field: 'price', header: 'Price' },
|
||||||
|
{ field: 'category', header: 'Category' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.exportColumns = this.cols.map((col) => ({ title: col.header, dataKey: col.field }));
|
||||||
|
}
|
||||||
|
|
||||||
|
onGlobalFilter(table: Table, event: Event) {
|
||||||
|
table.filterGlobal((event.target as HTMLInputElement).value, 'contains');
|
||||||
|
}
|
||||||
|
|
||||||
|
openNew() {
|
||||||
|
this.product = {};
|
||||||
|
this.submitted = false;
|
||||||
|
this.productDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editProduct(product: Property) {
|
||||||
|
this.product = { ...product };
|
||||||
|
this.productDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSelectedProducts() {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
message: 'Are you sure you want to delete the selected products?',
|
||||||
|
header: 'Confirm',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
accept: () => {
|
||||||
|
this.products.set(this.products().filter((val) => !this.selectedProducts?.includes(val)));
|
||||||
|
this.selectedProducts = null;
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Products Deleted',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDialog() {
|
||||||
|
this.productDialog = false;
|
||||||
|
this.submitted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProduct(product: Property) {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
message: 'Are you sure you want to delete ' + product.name + '?',
|
||||||
|
header: 'Confirm',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
accept: () => {
|
||||||
|
this.products.set(this.products().filter((val) => val.id !== product.id));
|
||||||
|
this.product = {};
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Product Deleted',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = '';
|
||||||
|
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeverity(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case 'INSTOCK':
|
||||||
|
return 'success';
|
||||||
|
case 'LOWSTOCK':
|
||||||
|
return 'warn';
|
||||||
|
case 'OUTOFSTOCK':
|
||||||
|
return 'danger';
|
||||||
|
default:
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveProduct() {
|
||||||
|
this.submitted = true;
|
||||||
|
let _products = this.products();
|
||||||
|
if (this.product.name?.trim()) {
|
||||||
|
if (this.product.id) {
|
||||||
|
_products[this.findIndexById(this.product.id)] = this.product;
|
||||||
|
this.products.set([..._products]);
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Product Updated',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.product.id = this.createId();
|
||||||
|
this.product.image = 'product-placeholder.svg';
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Product Created',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
this.products.set([..._products, this.product]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.productDialog = false;
|
||||||
|
this.product = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { MenuModule } from 'primeng/menu';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-activities-widget',
|
||||||
|
imports: [ButtonModule, MenuModule],
|
||||||
|
template: `<div class="card">
|
||||||
|
<div class="flex items-center justify-between mb-6">
|
||||||
|
<div class="font-semibold text-xl" i18n>Letzte Aktivitäten</div>
|
||||||
|
<div>
|
||||||
|
<button pButton type="button" icon="pi pi-ellipsis-v" class="p-button-rounded p-button-text p-button-plain" (click)="menu.toggle($event)"></button>
|
||||||
|
<p-menu #menu [popup]="true" [model]="items"></p-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="block text-muted-color font-medium mb-4" i18n>HEUTE</span>
|
||||||
|
<ul class="space-y-4 mb-10">
|
||||||
|
<li class="flex items-start mb-10">
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center bg-blue-100 dark:bg-blue-400/10 rounded-full mr-4 shrink-0">
|
||||||
|
<i class="pi pi-folder-plus text-xl! text-blue-500"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-gray-800 font-bold">Ereignis "vor Ort Aufnahme " hinzugefügt</p>
|
||||||
|
<p class="text-gray-700 font-medium">Ein neues Ereignis wurde zu Projekt "Heizungsmodernisierung " hinzugefügt</p>
|
||||||
|
<p class="text-gray-500 text-sm">01.09.2025</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="flex items-start mb-10">
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center bg-blue-100 dark:bg-blue-400/10 rounded-full mr-4 shrink-0">
|
||||||
|
<i class="pi pi-folder-plus text-xl! text-blue-500"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-gray-800 font-bold">Ereignis "Antrag Bafa iSFP" hinzugefügt</p>
|
||||||
|
<p class="text-gray-700 font-medium">Ein neues Ereignis wurde zu Projekt "Heizungsmodernisierung " hinzugefügt</p>
|
||||||
|
<p class="text-gray-500 text-sm">01.09.2025</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<span class="block text-muted-color font-medium mb-4" i18n>GESTERN</span>
|
||||||
|
<ul class="p-0 m-0 list-none mb-6">
|
||||||
|
<li class="flex items-start mb-10">
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center bg-blue-100 dark:bg-blue-400/10 rounded-full mr-4 shrink-0">
|
||||||
|
<i class="pi pi-folder-plus text-xl! text-blue-500"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-gray-800 font-bold">Eier gekrault</p>
|
||||||
|
<p class="text-gray-700 font-medium">kwt</p>
|
||||||
|
<p class="text-gray-500 text-sm">01.09.2025</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<span class="block text-muted-color font-medium mb-4" i18n>LETZTE WOCHE</span>
|
||||||
|
<ul class="p-0 m-0 list-none">
|
||||||
|
<li class="flex items-start mb-10">
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center bg-blue-100 dark:bg-blue-400/10 rounded-full mr-4 shrink-0">
|
||||||
|
<i class="pi pi-folder-plus text-xl! text-blue-500"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-gray-800 font-bold">Lorem Ipsum</p>
|
||||||
|
<p class="text-gray-700 font-medium">yadda yadda yadda</p>
|
||||||
|
<p class="text-gray-500 text-sm">01.09.2025</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class ActivitiesWidget {
|
||||||
|
items = [
|
||||||
|
{ label: 'Add New', icon: 'pi pi-fw pi-plus' },
|
||||||
|
{ label: 'Remove', icon: 'pi pi-fw pi-trash' }
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { MenuModule } from 'primeng/menu';
|
||||||
|
import { Panel } from 'primeng/panel';
|
||||||
|
import { ProgressBar } from 'primeng/progressbar';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-current-projects-widget',
|
||||||
|
imports: [CommonModule, ButtonModule, MenuModule, Panel, ProgressBar],
|
||||||
|
template: `
|
||||||
|
<div class="card">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<div class="font-semibold text-xl" i18n>Aktuelle Projekte</div>
|
||||||
|
<div>
|
||||||
|
<button pButton type="button" icon="pi pi-ellipsis-v"
|
||||||
|
class="p-button-rounded p-button-text p-button-plain"
|
||||||
|
(click)="menu.toggle($event)"></button>
|
||||||
|
<p-menu #menu [popup]="true" [model]="items"></p-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p-panel [toggleable]="true">
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-bold">EnB Bvh Rheinallee 191</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-user"></i> Erstellt: 01.09.1025</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-calendar"></i> Bis: 22.12.2026</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #icons>
|
||||||
|
<p-button icon="pi pi-cog" severity="secondary" rounded text (click)="menu.toggle($event)" />
|
||||||
|
<p-menu #menu id="config_menu" [model]="items" [popup]="true" />
|
||||||
|
<p-button i18n-label label="Vorbereitung" variant="text" severity="info" [rounded]="true" size="small"/>
|
||||||
|
</ng-template>
|
||||||
|
<p class="m-0">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-map-marker"></i> Nicht-Wohngebäude mit Mischnutzung</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span i18n>Fortschritt:</span>
|
||||||
|
<p-progress-bar [value]="25"></p-progress-bar>
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class CurrentProjectsWidget {
|
||||||
|
menu = null;
|
||||||
|
|
||||||
|
items = [
|
||||||
|
{ label: 'Add New', icon: 'pi pi-fw pi-plus' },
|
||||||
|
{ label: 'Remove', icon: 'pi pi-fw pi-trash' }
|
||||||
|
];
|
||||||
|
}
|
||||||
47
src/app/pages/dashboard/components/recentsaleswidget.ts
Normal file
47
src/app/pages/dashboard/components/recentsaleswidget.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Property, ProductService } from '../../service/product.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-recent-sales-widget',
|
||||||
|
imports: [CommonModule, TableModule, ButtonModule, RippleModule],
|
||||||
|
template: `<div class="card mb-8!">
|
||||||
|
<div class="font-semibold text-xl mb-4">Recent Sales</div>
|
||||||
|
<p-table [value]="products" [paginator]="true" [rows]="5" responsiveLayout="scroll">
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th>Image</th>
|
||||||
|
<th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
|
||||||
|
<th pSortableColumn="price">Price <p-sortIcon field="price"></p-sortIcon></th>
|
||||||
|
<th>View</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-product>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 15%; min-width: 5rem;">
|
||||||
|
<img src="https://primefaces.org/cdn/primevue/images/product/{{ product.image }}" class="shadow-lg" alt="{{ product.name }}" width="50" />
|
||||||
|
</td>
|
||||||
|
<td style="width: 35%; min-width: 7rem;">{{ product.name }}</td>
|
||||||
|
<td style="width: 35%; min-width: 8rem;">{{ product.price | currency: 'USD' }}</td>
|
||||||
|
<td style="width: 15%;">
|
||||||
|
<button pButton pRipple type="button" icon="pi pi-search" class="p-button p-component p-button-text p-button-icon-only"></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>`,
|
||||||
|
providers: [ProductService]
|
||||||
|
})
|
||||||
|
export class RecentSalesWidget {
|
||||||
|
products!: Property[];
|
||||||
|
|
||||||
|
constructor(private productService: ProductService) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.productService.getProductsSmall().then((data) => (this.products = data));
|
||||||
|
}
|
||||||
|
}
|
||||||
113
src/app/pages/dashboard/components/revenuestreamwidget.ts
Normal file
113
src/app/pages/dashboard/components/revenuestreamwidget.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ChartModule } from 'primeng/chart';
|
||||||
|
import { debounceTime, Subscription } from 'rxjs';
|
||||||
|
import { LayoutService } from '../../../layout/service/layout.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-revenue-stream-widget',
|
||||||
|
imports: [ChartModule],
|
||||||
|
template: `<div class="card mb-8!">
|
||||||
|
<div class="font-semibold text-xl mb-4">Revenue Stream</div>
|
||||||
|
<p-chart type="bar" [data]="chartData" [options]="chartOptions" class="h-100" />
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class RevenueStreamWidget {
|
||||||
|
chartData: any;
|
||||||
|
|
||||||
|
chartOptions: any;
|
||||||
|
|
||||||
|
subscription!: Subscription;
|
||||||
|
|
||||||
|
constructor(public layoutService: LayoutService) {
|
||||||
|
this.subscription = this.layoutService.configUpdate$.pipe(debounceTime(25)).subscribe(() => {
|
||||||
|
this.initChart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.initChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
initChart() {
|
||||||
|
const documentStyle = getComputedStyle(document.documentElement);
|
||||||
|
const textColor = documentStyle.getPropertyValue('--text-color');
|
||||||
|
const borderColor = documentStyle.getPropertyValue('--surface-border');
|
||||||
|
const textMutedColor = documentStyle.getPropertyValue('--text-color-secondary');
|
||||||
|
|
||||||
|
this.chartData = {
|
||||||
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
label: 'Subscriptions',
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-400'),
|
||||||
|
data: [4000, 10000, 15000, 4000],
|
||||||
|
barThickness: 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
label: 'Advertising',
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-300'),
|
||||||
|
data: [2100, 8400, 2400, 7500],
|
||||||
|
barThickness: 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
label: 'Affiliate',
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
data: [4100, 5200, 3400, 7400],
|
||||||
|
borderRadius: {
|
||||||
|
topLeft: 8,
|
||||||
|
topRight: 8,
|
||||||
|
bottomLeft: 0,
|
||||||
|
bottomRight: 0
|
||||||
|
},
|
||||||
|
borderSkipped: false,
|
||||||
|
barThickness: 32
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.chartOptions = {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
aspectRatio: 0.8,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
color: textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
stacked: true,
|
||||||
|
ticks: {
|
||||||
|
color: textMutedColor
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: 'transparent',
|
||||||
|
borderColor: 'transparent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
stacked: true,
|
||||||
|
ticks: {
|
||||||
|
color: textMutedColor
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: borderColor,
|
||||||
|
borderColor: 'transparent',
|
||||||
|
drawTicks: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/app/pages/dashboard/components/statswidget.ts
Normal file
65
src/app/pages/dashboard/components/statswidget.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-stats-widget',
|
||||||
|
imports: [CommonModule],
|
||||||
|
template: `<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
||||||
|
<div class="card mb-0">
|
||||||
|
<div class="flex justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<span class="min-h-10 block text-muted-color font-medium mb-4" i18n>Gebäude verwaltet</span>
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">152</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center bg-blue-100 dark:bg-blue-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
||||||
|
<i class="pi pi-home text-blue-500 text-xl!"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-color" i18n>Gesamt im Portfolio</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
||||||
|
<div class="card mb-0">
|
||||||
|
<div class="flex justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<span class="min-h-10 block text-muted-color font-medium mb-4" i18n>Aktive Projekte</span>
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">12</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center bg-orange-100 dark:bg-orange-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
||||||
|
<i class="pi pi-wrench text-orange-500 text-xl!"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-color" i18n>von 73 insgesamt</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
||||||
|
<div class="card mb-0">
|
||||||
|
<div class="flex justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<span class="min-h-10 block text-muted-color font-medium mb-4" i18n>Kontakte</span>
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">28441</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center bg-cyan-100 dark:bg-cyan-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
||||||
|
<i class="pi pi-users text-cyan-500 text-xl!"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-color">Personen & Unternehmen</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-3">
|
||||||
|
<div class="card mb-0">
|
||||||
|
<div class="flex justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<span class="min-h-10 block text-muted-color font-medium mb-4" i18n>Projekte abgeschlossen</span>
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 font-medium text-xl">152</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center bg-purple-100 dark:bg-purple-400/10 rounded-border" style="width: 2.5rem; height: 2.5rem">
|
||||||
|
<i class="pi pi-list-check text-purple-500 text-xl!"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-muted-color" i18n>Erfolgreich beendet</span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class StatsWidget {}
|
||||||
26
src/app/pages/dashboard/dashboard.ts
Normal file
26
src/app/pages/dashboard/dashboard.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ActivitiesWidget } from './components/activities-widget.component';
|
||||||
|
import { StatsWidget } from './components/statswidget';
|
||||||
|
import { CurrentProjectsWidget } from './components/current-projects-widget.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dashboard',
|
||||||
|
imports: [StatsWidget, CurrentProjectsWidget, ActivitiesWidget],
|
||||||
|
template: `
|
||||||
|
<div class="mb-5">
|
||||||
|
<h1 i18n>Dashboard</h1>
|
||||||
|
<p class="my-4 text-lg text-gray-500">Willkommen zurück! Hier ist ein Überblick über Ihr Portfolio.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-8">
|
||||||
|
<app-stats-widget class="contents" />
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<app-current-projects-widget />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<app-activities-widget />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class Dashboard {}
|
||||||
73
src/app/pages/documentation/documentation.ts
Normal file
73
src/app/pages/documentation/documentation.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-documentation',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
template: `
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-2xl mb-4">Documentation</div>
|
||||||
|
<div class="font-semibold text-xl mb-4">Get Started</div>
|
||||||
|
<p class="text-lg mb-4">Sakai is an application template for Angular and is distributed as a CLI project. Current versions is Angular v20 with PrimeNG v20. In case CLI is not installed already, use the command below to set it up.</p>
|
||||||
|
<pre class="app-code">
|
||||||
|
<code>npm install -g @angular/cli</code></pre>
|
||||||
|
<p class="text-lg mb-4">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<pre class="app-code">
|
||||||
|
<code>git clone https://github.com/primefaces/sakai-ng
|
||||||
|
npm install
|
||||||
|
ng serve</code></pre>
|
||||||
|
|
||||||
|
<p class="text-lg mb-4">The application should run at <i class="bg-highlight px-2 py-1 rounded-border not-italic text-base">http://localhost:4200/</i> to view the application in your local environment.</p>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl mb-4">Structure</div>
|
||||||
|
<p class="text-lg mb-4">Templates consists of a couple folders, demos and layout have been separated so that you can easily identify what is necessary for your application.</p>
|
||||||
|
<ul class="leading-normal list-disc pl-8 text-lg mb-4">
|
||||||
|
<li><span class="text-primary font-medium">src/app/layout</span>: Main layout files, needs to be present.</li>
|
||||||
|
<li><span class="text-primary font-medium">src/app/pages</span>: Demo content like Dashboard.</li>
|
||||||
|
<li><span class="text-primary font-medium">src/assets/demo</span>: Assets used in demos</li>
|
||||||
|
<li><span class="text-primary font-medium">src/assets/layout</span>: SCSS files of the main layout</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl mb-4">Menu</div>
|
||||||
|
<p class="text-lg mb-4">
|
||||||
|
Main menu is defined at <span class="bg-highlight px-2 py-1 rounded-border not-italic text-base">src/app/layout/component/app.menu.ts</span> file. Update the
|
||||||
|
<i class="bg-highlight px-2 py-1 rounded-border not-italic text-base">model</i> property to define your own menu items.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl mb-4">Layout Service</div>
|
||||||
|
<p class="text-lg mb-4">
|
||||||
|
<span class="bg-highlight px-2 py-1 rounded-border not-italic text-base">src/app/layout/service/layout.service.ts</span> is a service that manages layout state changes, including dark mode, PrimeNG theme, menu modes, and states.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl mb-4">Tailwind CSS</div>
|
||||||
|
<p class="text-lg mb-4">The demo pages are developed with Tailwind CSS however the core application shell uses custom CSS.</p>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl mb-4">Variables</div>
|
||||||
|
<p class="text-lg mb-4">
|
||||||
|
CSS variables used in the template are derived from the applied PrimeNG theme. Customize them through the CSS variables in <span class="bg-highlight px-2 py-1 rounded-border not-italic text-base">src/assets/layout/variables</span>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
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 {}
|
||||||
11
src/app/pages/empty/empty.ts
Normal file
11
src/app/pages/empty/empty.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-empty',
|
||||||
|
standalone: true,
|
||||||
|
template: ` <div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Empty Page</div>
|
||||||
|
<p>Use this page to start from scratch and place your custom content.</p>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class Empty {}
|
||||||
139
src/app/pages/landing/components/featureswidget.ts
Normal file
139
src/app/pages/landing/components/featureswidget.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'features-widget',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
template: ` <div id="features" class="py-6 px-6 lg:px-20 mt-8 mx-0 lg:mx-20">
|
||||||
|
<div class="grid grid-cols-12 gap-4 justify-center">
|
||||||
|
<div class="col-span-12 text-center mt-20 mb-6">
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 font-normal mb-2 text-4xl">Marvelous Features</div>
|
||||||
|
<span class="text-muted-color text-2xl">Placerat in egestas erat...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pr-8 lg:pb-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(253, 228, 165, 0.2), rgba(187, 199, 205, 0.2)), linear-gradient(180deg, rgba(253, 228, 165, 0.2), rgba(187, 199, 205, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-yellow-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-users text-2xl! text-yellow-700"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-2 text-surface-900 dark:text-surface-0">Easy to Use</h5>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Posuere morbi leo urna molestie.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pr-8 lg:pb-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(145, 226, 237, 0.2), rgba(251, 199, 145, 0.2)), linear-gradient(180deg, rgba(253, 228, 165, 0.2), rgba(172, 180, 223, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-cyan-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-palette text-2xl! text-cyan-700"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-2 text-surface-900 dark:text-surface-0">Fresh Design</h5>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Semper risus in hendrerit.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pb-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(145, 226, 237, 0.2), rgba(172, 180, 223, 0.2)), linear-gradient(180deg, rgba(172, 180, 223, 0.2), rgba(246, 158, 188, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-indigo-200" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-map text-2xl! text-indigo-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 mb-1 text-surface-900 dark:text-surface-0 text-xl font-semibold">Well Documented</div>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Non arcu risus quis varius quam quisque.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pr-8 lg:pb-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(187, 199, 205, 0.2), rgba(251, 199, 145, 0.2)), linear-gradient(180deg, rgba(253, 228, 165, 0.2), rgba(145, 210, 204, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-slate-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-id-card text-2xl! text-slate-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 mb-1 text-surface-900 dark:text-surface-0 text-xl font-semibold">Responsive Layout</div>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Nulla malesuada pellentesque elit.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pr-8 lg:pb-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(187, 199, 205, 0.2), rgba(246, 158, 188, 0.2)), linear-gradient(180deg, rgba(145, 226, 237, 0.2), rgba(160, 210, 250, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-orange-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-star text-2xl! text-orange-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 mb-1 text-surface-900 dark:text-surface-0 text-xl font-semibold">Clean Code</div>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Condimentum lacinia quis vel eros.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pb-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(251, 199, 145, 0.2), rgba(246, 158, 188, 0.2)), linear-gradient(180deg, rgba(172, 180, 223, 0.2), rgba(212, 162, 221, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-pink-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-moon text-2xl! text-pink-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 mb-1 text-surface-900 dark:text-surface-0 text-xl font-semibold">Dark Mode</div>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Convallis tellus id interdum velit laoreet.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pr-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(145, 210, 204, 0.2), rgba(160, 210, 250, 0.2)), linear-gradient(180deg, rgba(187, 199, 205, 0.2), rgba(145, 210, 204, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-teal-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-shopping-cart text-2xl! text-teal-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 mb-1 text-surface-900 dark:text-surface-0 text-xl font-semibold">Ready to Use</div>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Mauris sit amet massa vitae.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg:pr-8 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(145, 210, 204, 0.2), rgba(212, 162, 221, 0.2)), linear-gradient(180deg, rgba(251, 199, 145, 0.2), rgba(160, 210, 250, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-blue-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-globe text-2xl! text-blue-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 mb-1 text-surface-900 dark:text-surface-0 text-xl font-semibold">Modern Practices</div>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Elementum nibh tellus molestie nunc non.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-12 lg:col-span-4 p-0 lg-4 mt-6 lg:mt-0">
|
||||||
|
<div style="height: 160px; padding: 2px; border-radius: 10px; background: linear-gradient(90deg, rgba(160, 210, 250, 0.2), rgba(212, 162, 221, 0.2)), linear-gradient(180deg, rgba(246, 158, 188, 0.2), rgba(212, 162, 221, 0.2))">
|
||||||
|
<div class="p-4 bg-surface-0 dark:bg-surface-900 h-full" style="border-radius: 8px">
|
||||||
|
<div class="flex items-center justify-center bg-purple-200 mb-4" style="width: 3.5rem; height: 3.5rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-eye text-2xl! text-purple-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 mb-1 text-surface-900 dark:text-surface-0 text-xl font-semibold">Privacy</div>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">Neque egestas congue quisque.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="col-span-12 mt-20 mb-20 p-2 md:p-20"
|
||||||
|
style="border-radius: 20px; background: linear-gradient(0deg, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.6)), radial-gradient(77.36% 256.97% at 77.36% 57.52%, #efe1af 0%, #c3dcfa 100%)"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col justify-center items-center text-center px-4 py-4 md:py-0">
|
||||||
|
<div class="text-gray-900 mb-2 text-3xl font-semibold">Joséphine Miller</div>
|
||||||
|
<span class="text-gray-600 text-2xl">Peak Interactive</span>
|
||||||
|
<p class="text-gray-900 sm:line-height-2 md:line-height-4 text-2xl mt-6" style="max-width: 800px">
|
||||||
|
“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.”
|
||||||
|
</p>
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/landing/peak-logo.svg" class="mt-6" alt="Company logo" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class FeaturesWidget {}
|
||||||
73
src/app/pages/landing/components/footerwidget.ts
Normal file
73
src/app/pages/landing/components/footerwidget.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Router, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'footer-widget',
|
||||||
|
imports: [RouterModule],
|
||||||
|
template: `
|
||||||
|
<div class="py-12 px-12 mx-0 mt-20 lg:mx-20">
|
||||||
|
<div class="grid grid-cols-12 gap-4">
|
||||||
|
<div class="col-span-12 md:col-span-2">
|
||||||
|
<a (click)="router.navigate(['/pages/landing'], { fragment: 'home' })" class="flex flex-wrap items-center justify-center md:justify-start md:mb-0 mb-6 cursor-pointer">
|
||||||
|
<svg viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-14 mr-2">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
<mask id="mask0_1413_1551" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="8" width="54" height="11">
|
||||||
|
<path d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z" fill="var(--primary-color)" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_1413_1551)">
|
||||||
|
<path
|
||||||
|
d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<h4 class="font-medium text-3xl text-surface-900 dark:text-surface-0">SAKAI</h4>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-10">
|
||||||
|
<div class="grid grid-cols-12 gap-8 text-center md:text-left">
|
||||||
|
<div class="col-span-12 md:col-span-3">
|
||||||
|
<h4 class="font-medium text-2xl leading-normal mb-6 text-surface-900 dark:text-surface-0">Company</h4>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">About Us</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">News</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Investor Relations</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Careers</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer text-surface-700 dark:text-surface-100">Media Kit</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-3">
|
||||||
|
<h4 class="font-medium text-2xl leading-normal mb-6 text-surface-900 dark:text-surface-0">Resources</h4>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Get Started</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Learn</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer text-surface-700 dark:text-surface-100">Case Studies</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-3">
|
||||||
|
<h4 class="font-medium text-2xl leading-normal mb-6 text-surface-900 dark:text-surface-0">Community</h4>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Discord</a>
|
||||||
|
<a class="leading-normal text-xl flex items-center cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Events<img src="https://primefaces.org/cdn/templates/sakai/landing/new-badge.svg" alt="badge" class="ml-2" /></a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">FAQ</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer text-surface-700 dark:text-surface-100">Blog</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 md:col-span-3">
|
||||||
|
<h4 class="font-medium text-2xl leading-normal mb-6 text-surface-900 dark:text-surface-0">Legal</h4>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Brand Policy</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer mb-2 text-surface-700 dark:text-surface-100">Privacy Policy</a>
|
||||||
|
<a class="leading-normal text-xl block cursor-pointer text-surface-700 dark:text-surface-100">Terms of Service</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class FooterWidget {
|
||||||
|
constructor(public router: Router) {}
|
||||||
|
}
|
||||||
25
src/app/pages/landing/components/herowidget.ts
Normal file
25
src/app/pages/landing/components/herowidget.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'hero-widget',
|
||||||
|
imports: [ButtonModule, RippleModule],
|
||||||
|
template: `
|
||||||
|
<div
|
||||||
|
id="hero"
|
||||||
|
class="flex flex-col pt-6 px-6 lg:px-20 overflow-hidden"
|
||||||
|
style="background: linear-gradient(0deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), radial-gradient(77.36% 256.97% at 77.36% 57.52%, rgb(238, 239, 175) 0%, rgb(195, 227, 250) 100%); clip-path: ellipse(150% 87% at 93% 13%)"
|
||||||
|
>
|
||||||
|
<div class="mx-6 md:mx-20 mt-0 md:mt-6">
|
||||||
|
<h1 class="text-6xl font-bold text-gray-900 leading-tight dark:!text-gray-700"><span class="font-light block">Eu sem integer</span>eget magna fermentum</h1>
|
||||||
|
<p class="font-normal text-2xl leading-normal md:mt-4 text-gray-700 dark:text-gray-700">Sed blandit libero volutpat sed cras. Fames ac turpis egestas integer. Placerat in egestas erat...</p>
|
||||||
|
<button pButton pRipple [rounded]="true" type="button" label="Get Started" class="text-xl! mt-8 px-4!"></button>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center md:justify-end">
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/landing/screen-1.png" alt="Hero Image" class="w-9/12 md:w-auto" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class HeroWidget {}
|
||||||
46
src/app/pages/landing/components/highlightswidget.ts
Normal file
46
src/app/pages/landing/components/highlightswidget.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'highlights-widget',
|
||||||
|
template: `
|
||||||
|
<div id="highlights" class="py-6 px-6 lg:px-20 mx-0 my-12 lg:mx-20">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 font-normal mb-2 text-4xl">Powerful Everywhere</div>
|
||||||
|
<span class="text-muted-color text-2xl">Amet consectetur adipiscing elit...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-4 mt-20 pb-2 md:pb-20">
|
||||||
|
<div class="flex justify-center col-span-12 lg:col-span-6 bg-purple-100 p-0 order-1 lg:order-0" style="border-radius: 8px">
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/landing/mockup.png" class="w-11/12" alt="mockup mobile" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-6 my-auto flex flex-col lg:items-end text-center lg:text-right gap-4">
|
||||||
|
<div class="flex items-center justify-center bg-purple-200 self-center lg:self-end" style="width: 4.2rem; height: 4.2rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-mobile text-4xl! text-purple-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="leading-none text-surface-900 dark:text-surface-0 text-3xl font-normal">Congue Quisque Egestas</div>
|
||||||
|
<span class="text-surface-700 dark:text-surface-100 text-2xl leading-normal ml-0 md:ml-2" style="max-width: 650px"
|
||||||
|
>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.</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-4 my-20 pt-2 md:pt-20">
|
||||||
|
<div class="col-span-12 lg:col-span-6 my-auto flex flex-col text-center lg:text-left lg:items-start gap-4">
|
||||||
|
<div class="flex items-center justify-center bg-yellow-200 self-center lg:self-start" style="width: 4.2rem; height: 4.2rem; border-radius: 10px">
|
||||||
|
<i class="pi pi-fw pi-desktop text-3xl! text-yellow-700"></i>
|
||||||
|
</div>
|
||||||
|
<div class="leading-none text-surface-900 dark:text-surface-0 text-3xl font-normal">Celerisque Eu Ultrices</div>
|
||||||
|
<span class="text-surface-700 dark:text-surface-100 text-2xl leading-normal mr-0 md:mr-2" style="max-width: 650px"
|
||||||
|
>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.</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end order-1 sm:order-2 col-span-12 lg:col-span-6 bg-yellow-100 p-0" style="border-radius: 8px">
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/landing/mockup-desktop.png" class="w-11/12" alt="mockup" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class HighlightsWidget {}
|
||||||
119
src/app/pages/landing/components/pricingwidget.ts
Normal file
119
src/app/pages/landing/components/pricingwidget.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DividerModule } from 'primeng/divider';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pricing-widget',
|
||||||
|
imports: [DividerModule, ButtonModule, RippleModule],
|
||||||
|
template: `
|
||||||
|
<div id="pricing" class="py-6 px-6 lg:px-20 my-2 md:my-6">
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 font-normal mb-2 text-4xl">Matchless Pricing</div>
|
||||||
|
<span class="text-muted-color text-2xl">Amet consectetur adipiscing elit...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-4 justify-between mt-20 md:mt-0">
|
||||||
|
<div class="col-span-12 lg:col-span-4 p-0 md:p-4">
|
||||||
|
<div class="p-4 flex flex-col border-surface-200 dark:border-surface-600 pricing-card cursor-pointer border-2 hover:border-primary duration-300 transition-all" style="border-radius: 10px">
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 text-center my-8 text-3xl">Free</div>
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/landing/free.svg" class="w-10/12 mx-auto" alt="free" />
|
||||||
|
<div class="my-8 flex flex-col items-center gap-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-5xl font-bold mr-2 text-surface-900 dark:text-surface-0">$0</span>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">per month</span>
|
||||||
|
</div>
|
||||||
|
<button pButton pRipple label="Get Started" class="p-button-rounded border-0 ml-4 font-light leading-tight bg-blue-500 text-white"></button>
|
||||||
|
</div>
|
||||||
|
<p-divider class="w-full bg-surface-200"></p-divider>
|
||||||
|
<ul class="my-8 list-none p-0 flex text-surface-900 dark:text-surface-0 flex-col px-8">
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Responsive Layout</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Unlimited Push Messages</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">50 Support Ticket</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Free Shipping</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-4 p-0 md:p-4 mt-6 md:mt-0">
|
||||||
|
<div class="p-4 flex flex-col border-surface-200 dark:border-surface-600 pricing-card cursor-pointer border-2 hover:border-primary duration-300 transition-all" style="border-radius: 10px">
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 text-center my-8 text-3xl">Startup</div>
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/landing/startup.svg" class="w-10/12 mx-auto" alt="startup" />
|
||||||
|
<div class="my-8 flex flex-col items-center gap-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-5xl font-bold mr-2 text-surface-900 dark:text-surface-0">$1</span>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">per month</span>
|
||||||
|
</div>
|
||||||
|
<button pButton pRipple label="Get Started" class="p-button-rounded border-0 ml-4 font-light leading-tight bg-blue-500 text-white"></button>
|
||||||
|
</div>
|
||||||
|
<p-divider class="w-full bg-surface-200"></p-divider>
|
||||||
|
<ul class="my-8 list-none p-0 flex text-surface-900 dark:text-surface-0 flex-col px-8">
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Responsive Layout</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Unlimited Push Messages</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">50 Support Ticket</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Free Shipping</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-4 p-0 md:p-4 mt-6 md:mt-0">
|
||||||
|
<div class="p-4 flex flex-col border-surface-200 dark:border-surface-600 pricing-card cursor-pointer border-2 hover:border-primary duration-300 transition-all" style="border-radius: 10px">
|
||||||
|
<div class="text-surface-900 dark:text-surface-0 text-center my-8 text-3xl">Enterprise</div>
|
||||||
|
<img src="https://primefaces.org/cdn/templates/sakai/landing/enterprise.svg" class="w-10/12 mx-auto" alt="enterprise" />
|
||||||
|
<div class="my-8 flex flex-col items-center gap-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-5xl font-bold mr-2 text-surface-900 dark:text-surface-0">$5</span>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200">per month</span>
|
||||||
|
</div>
|
||||||
|
<button pButton pRipple label="Try Free" class="p-button-rounded border-0 ml-4 font-light leading-tight bg-blue-500 text-white"></button>
|
||||||
|
</div>
|
||||||
|
<p-divider class="w-full bg-surface-200"></p-divider>
|
||||||
|
<ul class="my-8 list-none p-0 flex text-surface-900 dark:text-surface-0 flex-col px-8">
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Responsive Layout</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Unlimited Push Messages</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">50 Support Ticket</span>
|
||||||
|
</li>
|
||||||
|
<li class="py-2">
|
||||||
|
<i class="pi pi-fw pi-check text-xl text-cyan-500 mr-2"></i>
|
||||||
|
<span class="text-xl leading-normal">Free Shipping</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class PricingWidget {}
|
||||||
68
src/app/pages/landing/components/topbarwidget.component.ts
Normal file
68
src/app/pages/landing/components/topbarwidget.component.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
import { Router, RouterModule } from '@angular/router';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import {AppFloatingConfigurator} from "@/layout/component/app.floatingconfigurator";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'topbar-widget',
|
||||||
|
imports: [RouterModule, StyleClassModule, ButtonModule, RippleModule, AppFloatingConfigurator],
|
||||||
|
template: `<a class="flex items-center" href="#">
|
||||||
|
<svg viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="h-12 mr-2">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
<mask id="mask0_1413_1551" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="8" width="54" height="11">
|
||||||
|
<path d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z" fill="var(--primary-color)" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_1413_1551)">
|
||||||
|
<path
|
||||||
|
d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<span class="text-surface-900 dark:text-surface-0 font-medium text-2xl leading-normal mr-20">SAKAI</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a pButton [text]="true" severity="secondary" [rounded]="true" pRipple class="lg:hidden!" pStyleClass="@next" enterFromClass="hidden" leaveToClass="hidden" [hideOnOutsideClick]="true">
|
||||||
|
<i class="pi pi-bars text-2xl!"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="items-center bg-surface-0 dark:bg-surface-900 grow justify-between hidden lg:flex absolute lg:static w-full left-0 top-full px-12 lg:px-0 z-20 rounded-border">
|
||||||
|
<ul class="list-none p-0 m-0 flex lg:items-center select-none flex-col lg:flex-row cursor-pointer gap-8">
|
||||||
|
<li>
|
||||||
|
<a (click)="router.navigate(['/landing'], { fragment: 'home' })" pRipple class="px-0 py-4 text-surface-900 dark:text-surface-0 font-medium text-xl">
|
||||||
|
<span>Home</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a (click)="router.navigate(['/landing'], { fragment: 'features' })" pRipple class="px-0 py-4 text-surface-900 dark:text-surface-0 font-medium text-xl">
|
||||||
|
<span>Features</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a (click)="router.navigate(['/landing'], { fragment: 'highlights' })" pRipple class="px-0 py-4 text-surface-900 dark:text-surface-0 font-medium text-xl">
|
||||||
|
<span>Highlights</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a (click)="router.navigate(['/landing'], { fragment: 'pricing' })" pRipple class="px-0 py-4 text-surface-900 dark:text-surface-0 font-medium text-xl">
|
||||||
|
<span>Pricing</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="flex border-t lg:border-t-0 border-surface py-4 lg:py-0 mt-4 lg:mt-0 gap-2">
|
||||||
|
<button pButton pRipple label="Login" routerLink="/auth/login" [rounded]="true" [text]="true"></button>
|
||||||
|
<button pButton pRipple label="Register" routerLink="/auth/login" [rounded]="true"></button>
|
||||||
|
<app-floating-configurator [float]="false"/>
|
||||||
|
</div>
|
||||||
|
</div> `
|
||||||
|
})
|
||||||
|
export class TopbarWidget {
|
||||||
|
constructor(public router: Router) {}
|
||||||
|
}
|
||||||
31
src/app/pages/landing/landing.ts
Normal file
31
src/app/pages/landing/landing.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { 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 { TopbarWidget } from './components/topbarwidget.component';
|
||||||
|
import { HeroWidget } from './components/herowidget';
|
||||||
|
import { FeaturesWidget } from './components/featureswidget';
|
||||||
|
import { HighlightsWidget } from './components/highlightswidget';
|
||||||
|
import { PricingWidget } from './components/pricingwidget';
|
||||||
|
import { FooterWidget } from './components/footerwidget';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-landing',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterModule, TopbarWidget, HeroWidget, FeaturesWidget, HighlightsWidget, PricingWidget, FooterWidget, RippleModule, StyleClassModule, ButtonModule, DividerModule],
|
||||||
|
template: `
|
||||||
|
<div class="bg-surface-0 dark:bg-surface-900">
|
||||||
|
<div id="home" class="landing-wrapper overflow-hidden">
|
||||||
|
<topbar-widget class="py-6 px-6 mx-0 md:mx-12 lg:mx-20 lg:px-20 flex items-center justify-between relative lg:static" />
|
||||||
|
<hero-widget />
|
||||||
|
<features-widget />
|
||||||
|
<highlights-widget />
|
||||||
|
<pricing-widget />
|
||||||
|
<footer-widget />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class Landing {}
|
||||||
68
src/app/pages/notfound/notfound.ts
Normal file
68
src/app/pages/notfound/notfound.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { AppFloatingConfigurator } from '../../layout/component/app.floatingconfigurator';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-notfound',
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterModule, AppFloatingConfigurator, ButtonModule],
|
||||||
|
template: ` <app-floating-configurator />
|
||||||
|
<div class="flex items-center justify-center min-h-screen overflow-hidden">
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<svg width="54" height="40" viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-8 w-32 shrink-0">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
<mask id="mask0_1413_1551" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="8" width="54" height="11">
|
||||||
|
<path d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z" fill="var(--primary-color)" />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_1413_1551)">
|
||||||
|
<path
|
||||||
|
d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, color-mix(in srgb, var(--primary-color), transparent 60%) 10%, var(--surface-ground) 30%)">
|
||||||
|
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20 flex flex-col items-center" style="border-radius: 53px">
|
||||||
|
<span class="text-primary font-bold text-3xl">404</span>
|
||||||
|
<h1 class="text-surface-900 dark:text-surface-0 font-bold text-3xl lg:text-5xl mb-2">Not Found</h1>
|
||||||
|
<div class="text-surface-600 dark:text-surface-200 mb-8">Requested resource is not available.</div>
|
||||||
|
<a routerLink="/" class="w-full flex items-center py-8 border-surface-300 dark:border-surface-500 border-b">
|
||||||
|
<span class="flex justify-center items-center border-2 border-primary text-primary rounded-border" style="height: 3.5rem; width: 3.5rem">
|
||||||
|
<i class="pi pi-fw pi-table text-2xl!"></i>
|
||||||
|
</span>
|
||||||
|
<span class="ml-6 flex flex-col">
|
||||||
|
<span class="text-surface-900 dark:text-surface-0 lg:text-xl font-medium mb-0 block">Frequently Asked Questions</span>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200 lg:text-xl">Ultricies mi quis hendrerit dolor.</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a routerLink="/" class="w-full flex items-center py-8 border-surface-300 dark:border-surface-500 border-b">
|
||||||
|
<span class="flex justify-center items-center border-2 border-primary text-primary rounded-border" style="height: 3.5rem; width: 3.5rem">
|
||||||
|
<i class="pi pi-fw pi-question-circle text-2xl!"></i>
|
||||||
|
</span>
|
||||||
|
<span class="ml-6 flex flex-col">
|
||||||
|
<span class="text-surface-900 dark:text-surface-0 lg:text-xl font-medium mb-0">Solution Center</span>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200 lg:text-xl">Phasellus faucibus scelerisque eleifend.</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a routerLink="/" class="w-full flex items-center mb-8 py-8 border-surface-300 dark:border-surface-500 border-b">
|
||||||
|
<span class="flex justify-center items-center border-2 border-primary text-primary rounded-border" style="height: 3.5rem; width: 3.5rem">
|
||||||
|
<i class="pi pi-fw pi-unlock text-2xl!"></i>
|
||||||
|
</span>
|
||||||
|
<span class="ml-6 flex flex-col">
|
||||||
|
<span class="text-surface-900 dark:text-surface-0 lg:text-xl font-medium mb-0">Permission Manager</span>
|
||||||
|
<span class="text-surface-600 dark:text-surface-200 lg:text-xl">Accumsan in nisl nisi scelerisque</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<p-button label="Go to Dashboard" routerLink="/" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class Notfound {}
|
||||||
11
src/app/pages/pages.routes.ts
Normal file
11
src/app/pages/pages.routes.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { Documentation } from './documentation/documentation';
|
||||||
|
import { Crud } from './crud/crud';
|
||||||
|
import { Empty } from './empty/empty';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{ path: 'documentation', component: Documentation },
|
||||||
|
{ path: 'crud', component: Crud },
|
||||||
|
{ path: 'empty', component: Empty },
|
||||||
|
{ path: '**', redirectTo: '/notfound' }
|
||||||
|
] as Routes;
|
||||||
131
src/app/pages/project-details/project-details.html
Normal file
131
src/app/pages/project-details/project-details.html
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="mb-5">
|
||||||
|
<h1 i18n>Projekt Details</h1>
|
||||||
|
<h4>{{ projectDetailsDTO?.name }}</h4>
|
||||||
|
<p class="my-4 text-lg text-gray-500">Erstellt: {{ projectDetailsDTO?.createdAt | date: 'dd.MM.yyyy HH:mm' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p-button label="Bearbeiten" class="p-button-outlined">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-base">edit_document</span>
|
||||||
|
</span>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-8 mb-8">
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-6">
|
||||||
|
<p-panel class="mb-8">
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-md">apartment</span>
|
||||||
|
<span class="font-bold text-2xl">Projektinformationen</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-8">
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-6">
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Projektname</span>
|
||||||
|
<p>{{ projectDetailsDTO?.name }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Status</span>
|
||||||
|
<p>{{ projectDetailsDTO?.status }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Beantragte Summe</span>
|
||||||
|
<p>{{ projectDetailsDTO?.amountRequested | currency: 'EUR' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Startdatum</span>
|
||||||
|
<p><span class="material-icons !text-base">calendar_today</span> {{ projectDetailsDTO?.startDate | date }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-6">
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Vorgangsnummer</span>
|
||||||
|
<p>{{ projectDetailsDTO?.eventNumber }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Projekt Typ</span>
|
||||||
|
<p>{{ projectDetailsDTO?.projectType }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Gebäude</span>
|
||||||
|
<p><span class="material-icons !text-base">location_on</span> {{ projectDetailsDTO?.address }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-8">
|
||||||
|
<span class="font-bold">Enddatum</span>
|
||||||
|
<p><span class="material-icons !text-base">calendar_today</span> {{ projectDetailsDTO?.endDate | date }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p-divider />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">Beschreibung</p>
|
||||||
|
<p class="m-0" [innerHTML]="projectDetailsDTO?.description | safeHtml"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</p-panel>
|
||||||
|
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-md">apartment</span>
|
||||||
|
<span class="font-bold text-2xl">Notizen & Dokumente</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p-panel>
|
||||||
|
Hier können Projektnotizen und Dokumente verwaltet werden.
|
||||||
|
</p-panel>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-6">
|
||||||
|
<div class="card">
|
||||||
|
<p-timeline [value]="projectDetailsDTO?.timelineEvents" align="alternate" class="customized-timeline">
|
||||||
|
<ng-template #marker let-event>
|
||||||
|
<span class="flex w-8 h-8 items-center justify-center text-white rounded-full z-10 shadow-sm" [style]="{ 'background-color': event.timelineEventType.color }">
|
||||||
|
@if (event.timelineEventType.icon != null && event.timelineEventType.icon != "") {
|
||||||
|
<i [class]="event.timelineEventType.icon"></i>
|
||||||
|
}
|
||||||
|
@if (event.timelineEventType.icon == null) {
|
||||||
|
<i class="pi pi-check"></i>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #content let-event>
|
||||||
|
<p-card [header]="event?.title" [subheader]="(event.date | date:'dd.MM.yyyy \'um\' H:mm') ?? undefined">
|
||||||
|
@if (event?.description) {
|
||||||
|
<p>{{ event?.description }}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (event?.documents.length > 0) {
|
||||||
|
<p-button label="Dokument ansehen" size="small" class="p-button-outlined">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-base">search</span>
|
||||||
|
</span>
|
||||||
|
</p-button>
|
||||||
|
}
|
||||||
|
</p-card>
|
||||||
|
</ng-template>
|
||||||
|
</p-timeline>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
0
src/app/pages/project-details/project-details.scss
Normal file
0
src/app/pages/project-details/project-details.scss
Normal file
23
src/app/pages/project-details/project-details.spec.ts
Normal file
23
src/app/pages/project-details/project-details.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProjectDetails } from './project-details';
|
||||||
|
|
||||||
|
describe('ProjectDetails', () => {
|
||||||
|
let component: ProjectDetails;
|
||||||
|
let fixture: ComponentFixture<ProjectDetails>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ProjectDetails]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ProjectDetails);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
74
src/app/pages/project-details/project-details.ts
Normal file
74
src/app/pages/project-details/project-details.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Button } from 'primeng/button';
|
||||||
|
import { Panel } from 'primeng/panel';
|
||||||
|
import { Timeline } from 'primeng/timeline';
|
||||||
|
import { Card } from 'primeng/card';
|
||||||
|
import { Divider } from 'primeng/divider';
|
||||||
|
import { ProjectService } from '@/pages/service/project.service';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { CurrencyPipe, DatePipe } from '@angular/common';
|
||||||
|
import { SafeHtmlPipe } from '@/pipes/safe-html-pipe';
|
||||||
|
import { ProjectType } from '@/pages/service/project-type.service';
|
||||||
|
|
||||||
|
export interface ProjectTimelineEventDTO {
|
||||||
|
id?: string; // UUID
|
||||||
|
date?: Date;
|
||||||
|
description?: string;
|
||||||
|
documents?: string[];
|
||||||
|
|
||||||
|
|
||||||
|
projectEventType?: string; // impact on icon and color
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectDetailsDTO {
|
||||||
|
id?: string; // UUID
|
||||||
|
address: string;
|
||||||
|
amountRequested: number;
|
||||||
|
endDate?: Date;
|
||||||
|
eventNumber?: string;
|
||||||
|
name?: string;
|
||||||
|
projectType?: ProjectType;
|
||||||
|
startDate?: Date;
|
||||||
|
status?: string;
|
||||||
|
timelineEvents: ProjectTimelineEventDTO[];
|
||||||
|
description?: string;
|
||||||
|
createdAt?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-project-details',
|
||||||
|
imports: [Button, Panel, Timeline, Card, Divider, CurrencyPipe, DatePipe, SafeHtmlPipe, SafeHtmlPipe],
|
||||||
|
templateUrl: './project-details.html',
|
||||||
|
styleUrl: './project-details.scss'
|
||||||
|
})
|
||||||
|
export class ProjectDetails implements OnInit {
|
||||||
|
events: any[] = [];
|
||||||
|
projectDetailsDTO: ProjectDetailsDTO | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private projectService: ProjectService,
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.projectService.getProject(this.route.snapshot.paramMap.get('id') ?? '').subscribe((project: ProjectDetailsDTO) => {
|
||||||
|
this.projectDetailsDTO = project;
|
||||||
|
|
||||||
|
console.debug('Edit project ', this.projectDetailsDTO);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.events = [
|
||||||
|
{ title: 'Geplantes Ende', date: '22.12.2026 01:00', description: null, document: null, icon: 'pi pi-shopping-cart', color: '#9C27B0' },
|
||||||
|
{ title: 'Vor-ort-Aufnahme', date: '01.09.2025 17:51', description: 'War ok sind nicht in den Keller gekommen.', document: null, icon: 'pi pi-cog', color: '#673AB7' },
|
||||||
|
{ title: 'Antrag Bafa iSFP', date: '01.09.2025 16:31', description: 'Antragsstellung in Vollmacht Bafa vor Ort Beratung', document: ['/path/to/document.pdf'], icon: 'pi pi-shopping-cart', color: '#FF9800' },
|
||||||
|
{ title: 'Delivevor Ort Aufnahmered', date: '01.09.2025 15:41', description: 'Alles in Ordnung', document: ['/path/to/document.pdf'], icon: 'pi pi-check', color: '#607D8B' },
|
||||||
|
{ title: 'Projekt erstellt', date: '01.09.2025 15:41', description: null, document: null, icon: 'pi pi-check', color: '#607D8B' },
|
||||||
|
{ title: 'Projekt gestartet', date: '11.04.2025 02:00', description: null, document: null, icon: 'pi pi-check', color: '#607D8B' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
497
src/app/pages/projects/projects.html
Normal file
497
src/app/pages/projects/projects.html
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 text-left">
|
||||||
|
<div class="mb-5">
|
||||||
|
<h1>Projekte</h1>
|
||||||
|
<p class="my-4 text-lg text-gray-500">Übersicht und Verwaltung aller Sanierungsprojekte</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 text-right">
|
||||||
|
|
||||||
|
<p-button label="Bericht erstellen" class="p-button-outlined">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-base">analytics</span>
|
||||||
|
</span>
|
||||||
|
</p-button>
|
||||||
|
|
||||||
|
<p-button (click)="showDialog()" label="Neues Projekt" class="p-button-outlined ml-4">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-base">add</span>
|
||||||
|
</span>
|
||||||
|
</p-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<p-dialog header="Neues Projekt" [modal]="true" [(visible)]="addNewProjectDialogVisible">
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="project_name" class="mb-2 font-semibold">Projekt Name</label>
|
||||||
|
<input [(ngModel)]="newProjectName"
|
||||||
|
[required]="true"
|
||||||
|
pInputText
|
||||||
|
id="project_name"
|
||||||
|
class="w-full"
|
||||||
|
autocomplete="on" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="event_number" class="mb-2 font-semibold">Vorgangsnummer</label>
|
||||||
|
<input [(ngModel)]="newProjectEventNumber"
|
||||||
|
pInputText
|
||||||
|
id="event_number"
|
||||||
|
class="w-full"
|
||||||
|
autocomplete="on" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 md:col-span-2 flex flex-col mt-4">
|
||||||
|
<label for="description" class="mb-2 font-semibold">Beschreibung</label>
|
||||||
|
<p-editor [(ngModel)]="newProjectDescription"
|
||||||
|
[style]="{'height':'200px'}"
|
||||||
|
[required]="true"
|
||||||
|
id="description"
|
||||||
|
class="w-full"></p-editor>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 md:col-span-2 flex flex-col">
|
||||||
|
<label for="project_type" class="mb-2 font-semibold">Projekt-Typ</label>
|
||||||
|
<p-select [options]="projectTypeOptionsForNewProject"
|
||||||
|
[(ngModel)]="newProjectProjectType"
|
||||||
|
[required]="true"
|
||||||
|
optionLabel="label" optionValue="value"
|
||||||
|
id="project_type" class="w-full"></p-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="status" class="mb-2 font-semibold">Status</label>
|
||||||
|
<p-select [options]="projectStatusOptionsForNewProject"
|
||||||
|
[(ngModel)]="newProjectStatus"
|
||||||
|
[required]="true"
|
||||||
|
optionLabel="label" optionValue="value"
|
||||||
|
id="status" class="w-full"></p-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="property" class="mb-2 font-semibold">Gebäude</label>
|
||||||
|
<p-select [options]="propertyOptionsForNewProject"
|
||||||
|
[(ngModel)]="newProjectPropertyId"
|
||||||
|
[filter]="true"
|
||||||
|
[showClear]="true"
|
||||||
|
[required]="true"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
filterBy="label"
|
||||||
|
placeholder="Gebäude auswählen" class="w-full">
|
||||||
|
<ng-template #selectedItem let-selectedItem>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div>{{ selectedItem.label }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template let-item #item>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div>{{ item.label }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="project_start_date" class="mb-2 font-semibold">Start</label>
|
||||||
|
<p-datepicker [(ngModel)]="newProjectStartDate"
|
||||||
|
[readonlyInput]="true"
|
||||||
|
[appendTo]="'body'"
|
||||||
|
[required]="true"
|
||||||
|
dateFormat="dd.mm.yy"
|
||||||
|
id="project_start_date" class="w-full"></p-datepicker>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="project_end_date" class="mb-2 font-semibold">Ende</label>
|
||||||
|
<p-datepicker [(ngModel)]="newProjectEndDate"
|
||||||
|
[readonlyInput]="true"
|
||||||
|
[appendTo]="'body'"
|
||||||
|
[required]="true"
|
||||||
|
dateFormat="dd.mm.yy"
|
||||||
|
id="project_end_date" class="w-full"></p-datepicker>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1 md:col-span-2 flex flex-col mt-4">
|
||||||
|
<label for="amount_requested" class="mb-2 font-semibold">Beantragte Summe</label>
|
||||||
|
<p-inputgroup>
|
||||||
|
<p-inputgroup-addon>€</p-inputgroup-addon>
|
||||||
|
<p-inputnumber [(ngModel)]="newProjectAmountRequested"
|
||||||
|
[minFractionDigits]="2"
|
||||||
|
placeholder="0,00"
|
||||||
|
inputId="amount_requested" mode="decimal" locale="de-DE"/>
|
||||||
|
</p-inputgroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<p-button label="Cancel" severity="secondary" (click)="addNewProjectDialogVisible = false" />
|
||||||
|
<p-button label="Save" (click)="createNewProject()" />
|
||||||
|
</div>
|
||||||
|
</p-dialog>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-8 mb-8">
|
||||||
|
<!-- STATS -->
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-0w-full max-w-sm">
|
||||||
|
<!-- Search Input -->
|
||||||
|
<p-iconfield class="h-10">
|
||||||
|
<p-inputicon class="pi pi-search" />
|
||||||
|
<input type="text" pInputText class="h-full" placeholder="Projekte durchsuchen..." />
|
||||||
|
</p-iconfield>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-0w-full max-w-sm">
|
||||||
|
<!-- SelectButton -->
|
||||||
|
<p-selectButton
|
||||||
|
[options]="stateOptions"
|
||||||
|
[(ngModel)]="viewMode"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
class="h-10">
|
||||||
|
|
||||||
|
<ng-template let-item pTemplate="item">
|
||||||
|
<span class="inline-flex items-center gap-1 justify-center h-full">
|
||||||
|
<mat-icon>{{ item.icon }}</mat-icon>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-selectButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-0w-full max-w-sm">
|
||||||
|
<!-- Status Select -->
|
||||||
|
<p-select [options]="statuses" [(ngModel)]="selectedStatus" [filter]="true"
|
||||||
|
optionLabel="label" optionValue="value" filterBy="label" placeholder="Status"
|
||||||
|
class="w-full md:w-70 h-10">
|
||||||
|
<ng-template #selectedItem let-selectedOption>
|
||||||
|
<div class="flex items-center gap-2 h-full">
|
||||||
|
<div>{{ selectedOption.label }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template let-status #item>
|
||||||
|
<div class="flex items-center gap-2 h-full">
|
||||||
|
<div>{{ status.label }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-0w-full max-w-sm">
|
||||||
|
<!-- Measures Select -->
|
||||||
|
<p-select [options]="measures" [(ngModel)]="selectedMeasures" [filter]="true"
|
||||||
|
optionLabel="label" optionValue="value" filterBy="label" placeholder="Maßnahmen"
|
||||||
|
class="w-full md:w-70 h-10">
|
||||||
|
<ng-template #selectedItem let-selectedOption>
|
||||||
|
<div class="flex items-center gap-2 h-full">
|
||||||
|
<div>{{ selectedOption.label }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template let-measures #item>
|
||||||
|
<div class="flex items-center gap-2 h-full">
|
||||||
|
<div>{{ measures.label }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (viewMode === 'kanban') {
|
||||||
|
<div class="grid grid-cols-12 gap-4 mb-8">
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2 border-b border-gray-300 pb-2 w-full">
|
||||||
|
<span class="font-bold">Vorbereitung</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<p class="m-0">
|
||||||
|
<p-panel >
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-bold text-base">EnB Bvh Rheinallee 191</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-4 justify-center">
|
||||||
|
<p-chip label="Vorbereitung" class="!bg-blue-100 !dark:bg-blue-400/10 !text-blue-500 !text-xs"/>
|
||||||
|
<p-chip label="Energieberatung NWG (Modul2)" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
Modul 2
|
||||||
|
</p>
|
||||||
|
<p class="m-0">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-map-marker"></i> Rheinallee 191, Mainz</span>
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-bold text-base">EnB Bvh Rheinallee 191</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-4 justify-center">
|
||||||
|
<p-chip label="Vorbereitung" class="!bg-blue-100 !dark:bg-blue-400/10 !text-blue-500 !text-xs"/>
|
||||||
|
<p-chip label="Energieberatung NWG (Modul2)" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
Modul 2
|
||||||
|
</p>
|
||||||
|
<p class="m-0">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-map-marker"></i> Rheinallee 191, Mainz</span>
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2 border-b border-gray-300 pb-2 w-full">
|
||||||
|
<span class="font-bold">Aktiv</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-bold text-base">EnB Bvh Rheinallee 191</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-4 justify-center">
|
||||||
|
<p-chip label="Aktiv" class="!bg-blue-100 !dark:bg-blue-400/10 !text-blue-500 !text-xs"/>
|
||||||
|
<p-chip label="Energieberatung NWG (Modul2)" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
Modul 2
|
||||||
|
</p>
|
||||||
|
<p class="m-0">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-map-marker"></i> Rheinallee 191, Mainz</span>
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
el2
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2 border-b border-gray-300 pb-2 w-full">
|
||||||
|
<span class="font-bold">Abgeschlossen</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
el1
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
el2
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2 border-b border-gray-300 pb-2 w-full">
|
||||||
|
<span class="font-bold">Problem</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
el1
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="m-0">
|
||||||
|
el2
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (viewMode === 'grid') {
|
||||||
|
<div class="grid grid-cols-12 gap-8 mb-8">
|
||||||
|
|
||||||
|
@for (project of projects; track project.id) {
|
||||||
|
<div class="col-span-12 lg:col-span-6 xl:col-span-6">
|
||||||
|
<p-panel>
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-bold text-base">
|
||||||
|
{{ project.name }} {{ project.property?.street }} {{ project.property?.houseNumber }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #footer>
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-user"></i> Erstellt: {{ project.createdAt | date:'dd.MM.yyyy HH:mm'}}</span>
|
||||||
|
</div>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
|
||||||
|
<p-button (click)="navigateToDetails(project.id)" variant="text" styleClass="p-button-sm">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<span class="material-icons !text-base">search</span>
|
||||||
|
<span class="text-sm">Details</span>
|
||||||
|
</span>
|
||||||
|
</p-button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #icons>
|
||||||
|
<div class="flex flex-col items-end gap-2">
|
||||||
|
@switch (project?.status?.name) {
|
||||||
|
@case ('preparation') {
|
||||||
|
<p-chip label="Vorbereitung" class="!bg-blue-100 !dark:bg-blue-400/10 !text-blue-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('active') {
|
||||||
|
<p-chip label="Aktiv" class="!bg-blue-100 !dark:bg-blue-400/10 !text-blue-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('completed') {
|
||||||
|
<p-chip label="Abgeschlossen" class="!bg-blue-100 !dark:bg-blue-400/10 !text-blue-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('problem') {
|
||||||
|
<p-chip label="Problem" class="!bg-blue-100 !dark:bg-blue-400/10 !text-blue-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@switch (project?.projectType?.name) {
|
||||||
|
@case ('energy_consulting_wg') {
|
||||||
|
<p-chip label="Energieberatung WG (iSFP)" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('energy_consulting_nwg') {
|
||||||
|
<p-chip label="Energieberatung NWG (Modul2)" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('individual_measures') {
|
||||||
|
<p-chip label="Einzelmaßnahmen" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('efficiency_house_renovation') {
|
||||||
|
<p-chip label="Effizienzhaus Sanierung'" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('efficiency_house_new_construction') {
|
||||||
|
<p-chip label="Effizienzhaus Neubau" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('energy_performance_certificate') {
|
||||||
|
<p-chip label="Energieausweis" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('specialist_planning_for_building_physics') {
|
||||||
|
<p-chip label="Fachplanung Bauphysik" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('specialist_planning_for_heating') {
|
||||||
|
<p-chip label="Fachplanung Heizung" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
@case ('certification') {
|
||||||
|
<p-chip label="Zertifizierung" class="!bg-green-100 !dark:bg-green-400/10 !text-green-500 !text-xs"/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<p class="m-0">Module 2</p>
|
||||||
|
<p class="m-0">
|
||||||
|
<span class="text-surface-500 dark:text-surface-400"><i class="pi pi-map-marker"></i> {{ project.property?.street }} {{ project.property?.houseNumber }}, {{ project.property?.zipCode }} {{ project.property?.city }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="m-0" [innerHTML]="project.description | safeHtml"></p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- STATS -->
|
||||||
|
<div class="grid grid-cols-12 gap-8">
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-orange-100 dark:bg-orange-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-orange-500">list</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">{{ projectsStatsCountTotal }}</span>
|
||||||
|
<span class="text-sm text-gray-500">Projekte insgesamt</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-orange-100 dark:bg-orange-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-orange-500">schedule</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">{{ projectsStatsCountActive }}</span>
|
||||||
|
<span class="text-sm text-gray-500">Aktiv</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-green-100 dark:bg-green-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-green-500">checklist</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">{{ projectsStatsCountCompleted }}</span>
|
||||||
|
<span class="text-sm text-gray-500">Abgeschlossen</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-3 xl:col-span-3">
|
||||||
|
<div class="flex items-center p-3 bg-white shadow rounded-lg w-full max-w-sm">
|
||||||
|
<!-- Icon -->
|
||||||
|
<div class="w-1 flex items-center justify-center bg-gray-100 dark:bg-gray-400/10 w-10 h-10 rounded-full">
|
||||||
|
<span class="material-icons text-gray-500">attach_money</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="ml-4 flex flex-col">
|
||||||
|
<span class="text-2xl font-bold text-gray-800 leading-none">{{ projectsStatsAmountRequestedInTotal| currency: 'EUR' }}</span>
|
||||||
|
<span class="text-sm text-gray-500">Beantragte Summe</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
0
src/app/pages/projects/projects.scss
Normal file
0
src/app/pages/projects/projects.scss
Normal file
23
src/app/pages/projects/projects.spec.ts
Normal file
23
src/app/pages/projects/projects.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Projects } from './projects';
|
||||||
|
|
||||||
|
describe('Projects', () => {
|
||||||
|
let component: Projects;
|
||||||
|
let fixture: ComponentFixture<Projects>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Projects]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Projects);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
235
src/app/pages/projects/projects.ts
Normal file
235
src/app/pages/projects/projects.ts
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { IconField } from 'primeng/iconfield';
|
||||||
|
import { InputIcon } from 'primeng/inputicon';
|
||||||
|
import { InputText } from 'primeng/inputtext';
|
||||||
|
import { SelectButton } from 'primeng/selectbutton';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MessageService, PrimeTemplate } from 'primeng/api';
|
||||||
|
import { MatIcon } from '@angular/material/icon';
|
||||||
|
import { Select } from 'primeng/select';
|
||||||
|
import { Panel } from 'primeng/panel';
|
||||||
|
import { Button } from 'primeng/button';
|
||||||
|
import { Chip } from 'primeng/chip';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Dialog } from 'primeng/dialog';
|
||||||
|
import { DatePicker } from 'primeng/datepicker';
|
||||||
|
import { InputGroup } from 'primeng/inputgroup';
|
||||||
|
import { InputGroupAddon } from 'primeng/inputgroupaddon';
|
||||||
|
import { Editor } from 'primeng/editor';
|
||||||
|
import { InputNumber } from 'primeng/inputnumber';
|
||||||
|
import { PropertyService } from '@/pages/service/property.service';
|
||||||
|
import { Project, ProjectService} from '@/pages/service/project.service';
|
||||||
|
import { CurrencyPipe, DatePipe } from '@angular/common';
|
||||||
|
import { SafeHtmlPipe } from '@/pipes/safe-html-pipe';
|
||||||
|
import { ProjectType, ProjectTypeService } from '@/pages/service/project-type.service';
|
||||||
|
import { ProjectStatus, ProjectStatusService } from '@/pages/service/project-status.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-projects',
|
||||||
|
imports: [
|
||||||
|
IconField,
|
||||||
|
InputIcon,
|
||||||
|
InputText,
|
||||||
|
SelectButton,
|
||||||
|
FormsModule,
|
||||||
|
PrimeTemplate,
|
||||||
|
MatIcon,
|
||||||
|
Select,
|
||||||
|
Panel,
|
||||||
|
Button,
|
||||||
|
Chip,
|
||||||
|
Dialog,
|
||||||
|
DatePicker,
|
||||||
|
InputGroup,
|
||||||
|
InputGroupAddon,
|
||||||
|
Editor,
|
||||||
|
InputNumber,
|
||||||
|
DatePipe,
|
||||||
|
SafeHtmlPipe,
|
||||||
|
SafeHtmlPipe,
|
||||||
|
SafeHtmlPipe,
|
||||||
|
SafeHtmlPipe,
|
||||||
|
CurrencyPipe
|
||||||
|
],
|
||||||
|
templateUrl: './projects.html',
|
||||||
|
styleUrl: './projects.scss',
|
||||||
|
providers: [MessageService]
|
||||||
|
})
|
||||||
|
export class Projects implements OnInit {
|
||||||
|
protected viewMode = 'grid';
|
||||||
|
stateOptions: any[] = [
|
||||||
|
{ label: 'Kachel', value: 'grid', icon: 'view_comfy_alt' },
|
||||||
|
{ label: 'Kanban', value: 'kanban', icon: 'view_kanban' }
|
||||||
|
];
|
||||||
|
|
||||||
|
projects: Project[] = [];
|
||||||
|
projectTypes: ProjectType[]= [];
|
||||||
|
projectStatuses: ProjectStatus[]= [];
|
||||||
|
|
||||||
|
statuses: any;
|
||||||
|
measures: any;
|
||||||
|
selectedStatus: any;
|
||||||
|
selectedMeasures: any;
|
||||||
|
addNewProjectDialogVisible: boolean = false;
|
||||||
|
|
||||||
|
// for stats
|
||||||
|
projectsStatsCountTotal: number = 0;
|
||||||
|
projectsStatsCountActive: number = 0;
|
||||||
|
projectsStatsCountCompleted: number = 0;
|
||||||
|
projectsStatsAmountRequestedInTotal: number = 0;
|
||||||
|
|
||||||
|
// for new project dialog
|
||||||
|
projectTypeOptionsForNewProject: any = [];
|
||||||
|
projectStatusOptionsForNewProject: any = [];
|
||||||
|
propertyOptionsForNewProject: any = [];
|
||||||
|
newProjectName: string | undefined;
|
||||||
|
newProjectEventNumber: string | undefined;
|
||||||
|
newProjectDescription: string | undefined;
|
||||||
|
newProjectProjectType: string | undefined;
|
||||||
|
newProjectStatus: string | undefined;
|
||||||
|
newProjectPropertyId: string | undefined;
|
||||||
|
newProjectStartDate: Date | undefined;
|
||||||
|
newProjectEndDate: Date | undefined;
|
||||||
|
newProjectAmountRequested: number | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private propertyService: PropertyService,
|
||||||
|
private projectService: ProjectService,
|
||||||
|
private projectTypeService: ProjectTypeService,
|
||||||
|
private projectStatusService: ProjectStatusService,
|
||||||
|
private messageService: MessageService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.statuses = [{ label: 'Alle', value: 'all' }, ...this.projectStatuses];
|
||||||
|
|
||||||
|
this.measures = [{ label: 'Alle', value: 'all' }, ...this.projectTypes];
|
||||||
|
|
||||||
|
this.projectService.getProjects().subscribe((projects) => {
|
||||||
|
console.debug('Projects', projects);
|
||||||
|
this.projects = projects;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.projectTypeService.getProjectTypes().subscribe((projectTypes) => {
|
||||||
|
console.debug('ProjectTypes', projectTypes);
|
||||||
|
this.projectTypes = projectTypes;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.projectStatusService.getProjectStatuses().subscribe((projectStatuses) => {
|
||||||
|
console.debug('ProjectStatuses', projectStatuses);
|
||||||
|
this.projectStatuses = projectStatuses;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.projectService.getStats().subscribe((stats) => {
|
||||||
|
this.projectsStatsCountTotal = stats.projectsCountTotal ?? 0;
|
||||||
|
this.projectsStatsCountActive = stats.projectsCountActive ?? 0;
|
||||||
|
this.projectsStatsCountCompleted = stats.projectsCountCompleted ?? 0;
|
||||||
|
this.projectsStatsAmountRequestedInTotal = stats.amountRequestedInTotal ?? 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToDetails(id: string | undefined) {
|
||||||
|
this.router.navigate(['/projects', id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMe_getStatusOptionsForNewProject() {
|
||||||
|
return [
|
||||||
|
{ label: 'Vorbereitung', value: 'preparation' },
|
||||||
|
{ label: 'Aktiv', value: 'active' },
|
||||||
|
{ label: 'Abgeschlossen', value: 'completed' },
|
||||||
|
{ label: 'Problem', value: 'problem' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMe_getProjectTypeOptionsForNewProject() {
|
||||||
|
return [
|
||||||
|
{ label: 'Energieberatung WG (iSFP)', value: 'energy_consulting_wg' },
|
||||||
|
{ label: 'Energieberatung NWG (Modul2)', value: 'energy_consulting_nwg' },
|
||||||
|
{ label: 'Einzelmaßnahmen', value: 'individual_measures' },
|
||||||
|
{ label: 'Effizienzhaus Sanierung', value: 'efficiency_house_renovation' },
|
||||||
|
{ label: 'Effizienzhaus Neubau', value: 'efficiency_house_new_construction' },
|
||||||
|
{ label: 'Energieausweis', value: 'energy_performance_certificate' },
|
||||||
|
{ label: 'Fachplanung Bauphysik', value: 'specialist_planning_for_building_physics' },
|
||||||
|
{ label: 'Fachplanung Heizung', value: 'specialist_planning_for_heating' },
|
||||||
|
{ label: 'Zertifizierung', value: 'certification' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog() {
|
||||||
|
this.propertyService.getProperties().subscribe((properties) => {
|
||||||
|
this.propertyOptionsForNewProject = [];
|
||||||
|
properties.forEach((property) => {
|
||||||
|
this.propertyOptionsForNewProject.push({
|
||||||
|
value: property.id,
|
||||||
|
label: property.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.projectTypeOptionsForNewProject = [];
|
||||||
|
this.projectTypes.forEach((projectType) => {
|
||||||
|
this.projectTypeOptionsForNewProject.push({
|
||||||
|
value: projectType.id,
|
||||||
|
label: projectType.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.projectStatusOptionsForNewProject = [];
|
||||||
|
this.projectStatuses.forEach((projectStatus) => {
|
||||||
|
this.projectStatusOptionsForNewProject.push({
|
||||||
|
value: projectStatus.id,
|
||||||
|
label: projectStatus.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.projectStatusOptionsForNewProject = this.projectStatuses;
|
||||||
|
this.projectTypeOptionsForNewProject = this.projectTypes;
|
||||||
|
this.addNewProjectDialogVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewProject() {
|
||||||
|
let newProject = this.projectService.getProjectInstance(
|
||||||
|
this.newProjectName,
|
||||||
|
this.newProjectEventNumber,
|
||||||
|
this.newProjectDescription,
|
||||||
|
this.newProjectProjectType,
|
||||||
|
this.newProjectStatus,
|
||||||
|
this.newProjectPropertyId,
|
||||||
|
this.newProjectStartDate,
|
||||||
|
this.newProjectEndDate,
|
||||||
|
this.newProjectAmountRequested
|
||||||
|
);
|
||||||
|
|
||||||
|
this.projectService.create(newProject).subscribe({
|
||||||
|
next: (arg) => {
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Erfolgreich',
|
||||||
|
detail: 'Projekt erfolgreich angelegt',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
this.projects.push(newProject);
|
||||||
|
this.projectsStatsCountTotal++;
|
||||||
|
this.projectsStatsCountCompleted += this.newProjectStatus == 'completed' ? 1 : 0;
|
||||||
|
this.projectsStatsAmountRequestedInTotal += this.newProjectAmountRequested ?? 0;
|
||||||
|
this.projectsStatsCountActive += this.newProjectStatus == 'active' ? 1 : 0;
|
||||||
|
|
||||||
|
this.addNewProjectDialogVisible = false;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'danger',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: 'Beim Anlegen des Projekts ist ein Fehler aufgetreten.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
console.log('Error while creating project -- Error: ' + err + ' -- Project: ', newProject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
114
src/app/pages/properties/properties.html
Normal file
114
src/app/pages/properties/properties.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Properties</div>
|
||||||
|
<p-table
|
||||||
|
#dt1
|
||||||
|
[value]="properties"
|
||||||
|
dataKey="id"
|
||||||
|
[rows]="10"
|
||||||
|
[loading]="loading"
|
||||||
|
[rowHover]="true"
|
||||||
|
[showGridlines]="true"
|
||||||
|
[paginator]="true"
|
||||||
|
[globalFilterFields]="['street', 'houseNumber', 'zipCode', 'country.name']"
|
||||||
|
responsiveLayout="scroll"
|
||||||
|
[rowsPerPageOptions]="[10, 20, 30]"
|
||||||
|
>
|
||||||
|
<ng-template #caption>
|
||||||
|
<div class="flex justify-between items-center flex-column sm:flex-row">
|
||||||
|
<button pButton label="Clear" class="p-button-outlined mb-2" icon="pi pi-filter-slash" (click)="clear(dt1)"></button>
|
||||||
|
<p-iconfield iconPosition="left" class="ml-auto">
|
||||||
|
<p-inputicon>
|
||||||
|
<i class="pi pi-search"></i>
|
||||||
|
</p-inputicon>
|
||||||
|
<input pInputText type="text" (input)="onGlobalFilter(dt1, $event)" placeholder="Search keyword" />
|
||||||
|
</p-iconfield>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th pSortableColumn="street" style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Street
|
||||||
|
<p-sortIcon field="street" />
|
||||||
|
<p-columnFilter type="text" field="street" display="menu" placeholder="Search by street"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="houseNumber" style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Number
|
||||||
|
<p-sortIcon field="houseNumber" />
|
||||||
|
<p-columnFilter type="text" field="houseNumber" display="menu" placeholder="Search by street"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="zipCode" style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Zip Code
|
||||||
|
<p-sortIcon field="zipCode" />
|
||||||
|
<p-columnFilter type="text" field="zipCode" display="menu" placeholder="Search by street"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="city" style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
City
|
||||||
|
<p-sortIcon field="city" />
|
||||||
|
<p-columnFilter type="text" field="city" display="menu" placeholder="Search by street"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="country.name" style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Country
|
||||||
|
<p-sortIcon field="country.name" />
|
||||||
|
<p-columnFilter type="text" field="country.name" display="menu" placeholder="Search by country"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-property>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ property.street }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ property.houseNumber }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ property.zipCode }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ property.city }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + (property.country.code | lowercase)" width="30" />
|
||||||
|
<span>{{ property.country.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex items-center justify-center gap-2">
|
||||||
|
<a routerLink="/properties/{{ property.id }}">
|
||||||
|
<button
|
||||||
|
pButton
|
||||||
|
pRipple
|
||||||
|
type="button"
|
||||||
|
icon="pi pi-eye"
|
||||||
|
rounded
|
||||||
|
severity="secondary"
|
||||||
|
></button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #emptymessage>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8">No properties found.</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #loadingbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8">Loading properties data. Please wait.</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
7
src/app/pages/properties/properties.scss
Normal file
7
src/app/pages/properties/properties.scss
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.p-datatable-frozen-tbody {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-datatable-scrollable .p-frozen-column {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
23
src/app/pages/properties/properties.spec.ts
Normal file
23
src/app/pages/properties/properties.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Properties } from './properties';
|
||||||
|
|
||||||
|
describe('Properties', () => {
|
||||||
|
let component: Properties;
|
||||||
|
let fixture: ComponentFixture<Properties>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Properties]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Properties);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
84
src/app/pages/properties/properties.ts
Normal file
84
src/app/pages/properties/properties.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { ButtonDirective, ButtonModule } from 'primeng/button';
|
||||||
|
import { CommonModule, CurrencyPipe, DatePipe } from '@angular/common';
|
||||||
|
import { IconField, IconFieldModule } from 'primeng/iconfield';
|
||||||
|
import { InputIcon, InputIconModule } from 'primeng/inputicon';
|
||||||
|
import { InputText, InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { MultiSelect, MultiSelectModule } from 'primeng/multiselect';
|
||||||
|
import { ProgressBar, ProgressBarModule } from 'primeng/progressbar';
|
||||||
|
import { Select, SelectModule } from 'primeng/select';
|
||||||
|
import { Slider, SliderModule } from 'primeng/slider';
|
||||||
|
import { Table, TableModule } from 'primeng/table';
|
||||||
|
import { Tag, TagModule } from 'primeng/tag';
|
||||||
|
import { ToggleButtonModule } from 'primeng/togglebutton';
|
||||||
|
import { ToastModule } from 'primeng/toast';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RatingModule } from 'primeng/rating';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { Property, PropertyService } from '@/pages/service/property.service';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
interface expandedRows {
|
||||||
|
[key: string]: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-properties',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
ButtonDirective,
|
||||||
|
IconField,
|
||||||
|
InputIcon,
|
||||||
|
InputText,
|
||||||
|
TableModule,
|
||||||
|
|
||||||
|
TableModule,
|
||||||
|
MultiSelectModule,
|
||||||
|
SelectModule,
|
||||||
|
InputIconModule,
|
||||||
|
TagModule,
|
||||||
|
InputTextModule,
|
||||||
|
SliderModule,
|
||||||
|
ProgressBarModule,
|
||||||
|
ToggleButtonModule,
|
||||||
|
ToastModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ButtonModule,
|
||||||
|
RatingModule,
|
||||||
|
RippleModule,
|
||||||
|
IconFieldModule,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './properties.html',
|
||||||
|
styleUrl: './properties.scss',
|
||||||
|
providers: [PropertyService]
|
||||||
|
})
|
||||||
|
export class Properties implements OnInit {
|
||||||
|
properties: Property[] = [];
|
||||||
|
|
||||||
|
statuses: any[] = [];
|
||||||
|
|
||||||
|
loading: boolean = true;
|
||||||
|
|
||||||
|
@ViewChild('filter') filter!: ElementRef;
|
||||||
|
|
||||||
|
constructor(private propertyService: PropertyService) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.propertyService.getProperties().subscribe((properties) => {
|
||||||
|
// this.properties = properties;
|
||||||
|
console.log(properties);
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onGlobalFilter(table: Table, event: Event) {
|
||||||
|
table.filterGlobal((event.target as HTMLInputElement).value, 'contains');
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(table: Table) {
|
||||||
|
table.clear();
|
||||||
|
this.filter.nativeElement.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/app/pages/property-details/property-details.html
Normal file
3
src/app/pages/property-details/property-details.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<p>property-details works!</p>
|
||||||
|
|
||||||
|
<h2>Property ID: {{ propertyId() }}</h2>
|
||||||
23
src/app/pages/property-details/property-details.spec.ts
Normal file
23
src/app/pages/property-details/property-details.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PropertyDetails } from './property-details';
|
||||||
|
|
||||||
|
describe('PropertyDetails', () => {
|
||||||
|
let component: PropertyDetails;
|
||||||
|
let fixture: ComponentFixture<PropertyDetails>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [PropertyDetails]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(PropertyDetails);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
19
src/app/pages/property-details/property-details.ts
Normal file
19
src/app/pages/property-details/property-details.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Component, inject, signal } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-property-details',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './property-details.html',
|
||||||
|
styleUrl: './property-details.scss'
|
||||||
|
})
|
||||||
|
export class PropertyDetails {
|
||||||
|
propertyId = signal('');
|
||||||
|
private activatedRoute = inject(ActivatedRoute);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.activatedRoute.params.subscribe(params => {
|
||||||
|
this.propertyId.set(params['id']);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
252
src/app/pages/property-manager/property-manager.html
Normal file
252
src/app/pages/property-manager/property-manager.html
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
<p-toolbar styleClass="mb-6">
|
||||||
|
<ng-template #start>
|
||||||
|
<p-button i18n-label label="Neu" icon="pi pi-plus" severity="secondary" class="mr-2" (onClick)="openNew()" />
|
||||||
|
<p-button i18n-label label="Löschen" icon="pi pi-trash" severity="danger" outlined (onClick)="deleteSelected()" [disabled]="!selectedProperties || !selectedProperties.length" />
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #end>
|
||||||
|
<p-button i18n-label label="Exportieren" icon="pi pi-upload" severity="secondary" (onClick)="exportCSV()" />
|
||||||
|
</ng-template>
|
||||||
|
</p-toolbar>
|
||||||
|
|
||||||
|
<p-table
|
||||||
|
#dt
|
||||||
|
[value]="properties()"
|
||||||
|
[rows]="10"
|
||||||
|
[columns]="cols"
|
||||||
|
[paginator]="true"
|
||||||
|
[globalFilterFields]="['name', 'street', 'houseNumber', 'zipCode', 'city', 'country', 'notes']"
|
||||||
|
[tableStyle]="{ 'min-width': '75rem' }"
|
||||||
|
[(selection)]="selectedProperties"
|
||||||
|
[rowHover]="true"
|
||||||
|
dataKey="id"
|
||||||
|
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} properties"
|
||||||
|
[showCurrentPageReport]="true"
|
||||||
|
[rowsPerPageOptions]="[10, 20, 30]"
|
||||||
|
>
|
||||||
|
<ng-template #caption>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h5 class="m-0" i18n>Verwalte Liegenschaften</h5>
|
||||||
|
<p-iconfield>
|
||||||
|
<p-inputicon styleClass="pi pi-search" />
|
||||||
|
<input pInputText type="text" (input)="onGlobalFilter(dt, $event)" i18n-placeholder placeholder="Suche..." />
|
||||||
|
</p-iconfield>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 3rem">
|
||||||
|
<p-tableHeaderCheckbox />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="nr" style="min-width:8rem" i18n>
|
||||||
|
Nr.
|
||||||
|
<p-sortIcon field="nr" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="name" style="min-width:8rem" i18n>
|
||||||
|
Name
|
||||||
|
<p-sortIcon field="name" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="owner" style="min-width:8rem" i18n>
|
||||||
|
Eigentümer
|
||||||
|
<p-sortIcon field="owner" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="street" style="min-width: 8rem" i18n>
|
||||||
|
Straße
|
||||||
|
<p-sortIcon field="street" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="houseNumber" style="min-width:8rem" i18n>
|
||||||
|
Hausnummer
|
||||||
|
<p-sortIcon field="houseNumber" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="zipCode" style="min-width: 12rem" i18n>
|
||||||
|
Postleitzahl
|
||||||
|
<p-sortIcon field="zipCode" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="city" style="min-width: 12rem" i18n>
|
||||||
|
Stadt
|
||||||
|
<p-sortIcon field="city" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="bundesland" style="min-width:8rem" i18n>
|
||||||
|
Bundesland
|
||||||
|
<p-sortIcon field="bundesland" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="Einheiten" style="min-width:8rem" i18n>
|
||||||
|
Einheiten
|
||||||
|
<p-sortIcon field="Einheiten" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="Status" style="min-width:8rem">
|
||||||
|
Status
|
||||||
|
<p-sortIcon field="Status" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="Projekte" style="min-width:8rem">
|
||||||
|
Projekte
|
||||||
|
<p-sortIcon field="Projekte" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="Labels" style="min-width:8rem">
|
||||||
|
Labels
|
||||||
|
<p-sortIcon field="Labels" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="country.name" style="min-width: 6rem">
|
||||||
|
Land
|
||||||
|
<p-sortIcon field="country.name" />
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 12rem"></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-property>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p-tableCheckbox [value]="property" />
|
||||||
|
</td>
|
||||||
|
<td>Nr...</td>
|
||||||
|
<td>{{ property.name }}</td>
|
||||||
|
<td>Eigentümer...</td>
|
||||||
|
<td>{{ property.street }}</td>
|
||||||
|
<td class="text-right">{{ property.houseNumber }}</td>
|
||||||
|
<td class="text-right">{{ property.zipCode }}</td>
|
||||||
|
<td>{{ property.city }}</td>
|
||||||
|
<td>Bundesland...</td>
|
||||||
|
<td>Einheiten...</td>
|
||||||
|
<td>Status...</td>
|
||||||
|
<td>Projekte...</td>
|
||||||
|
<td>Labels...</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + (property.country | lowercase)" width="30" />
|
||||||
|
<span>{{ property.country?.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<p-button icon="pi pi-pencil" class="mr-2" [rounded]="true" [outlined]="true" (click)="editProperty(property)" />
|
||||||
|
<p-button icon="pi pi-trash" severity="danger" [rounded]="true" [outlined]="true" (click)="deleteProperty(property)" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
|
||||||
|
<p-dialog [(visible)]="propertyDialog" [style]="{ width: '900px' }" i18n-header header="Liegenschaft Details" [modal]="true">
|
||||||
|
<ng-template #content>
|
||||||
|
|
||||||
|
<div class="grid gap-4 max-w-3xl mx-auto p-4">
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="name" class="block font-bold mb-3">Name</label>
|
||||||
|
<input type="text" pInputText id="name" [(ngModel)]="property().name" required autofocus fluid />
|
||||||
|
@if (submitted && !property().name) {
|
||||||
|
<small class="text-red-500">Name is required.</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Street + Housenumber -->
|
||||||
|
<div class="grid gap-4 grid-cols-1 sm:grid-cols-3">
|
||||||
|
<div class="flex flex-col sm:col-span-2">
|
||||||
|
<label for="street" class="block font-bold mb-3">Straße</label>
|
||||||
|
<input type="text" pInputText id="street" [(ngModel)]="property().street" required autofocus fluid />
|
||||||
|
@if (submitted && !property().street) {
|
||||||
|
<small class="text-red-500">Street is required.</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="houseNumber" class="block font-bold mb-3">Hausnummer</label>
|
||||||
|
<input type="text" pInputText id="houseNumber" [(ngModel)]="property().houseNumber" required autofocus fluid />
|
||||||
|
@if (submitted && !property().houseNumber) {
|
||||||
|
<small class="text-red-500">House number is required.</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ZipCode + City -->
|
||||||
|
<div class="grid gap-4 grid-cols-1 sm:grid-cols-3">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="zipCode" class="block font-bold mb-3">Postleitzahl</label>
|
||||||
|
<input type="text" pInputText id="zipCode" [(ngModel)]="property().zipCode" required autofocus fluid />
|
||||||
|
@if (submitted && !property().zipCode) {
|
||||||
|
<small class="text-red-500">Zip code is required.</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col sm:col-span-2">
|
||||||
|
<label for="city" class="block font-bold mb-3">Stadt</label>
|
||||||
|
<input type="text" pInputText id="city" [(ngModel)]="property().city" required autofocus fluid />
|
||||||
|
@if (submitted && !property().city) {
|
||||||
|
<small class="text-red-500">City is required.</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Country -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="country" class="mb-1 font-medium">Land</label>
|
||||||
|
<p-select id="country" [options]="countries" [(ngModel)]="property().country" optionLabel="name" optionValue="code" placeholder="Select a country" class="w-full md:w-56">
|
||||||
|
<ng-template #selectedItem let-selectedOption>
|
||||||
|
@if (selectedOption) {
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png"
|
||||||
|
[class]="'flag flag-' + selectedOption.code.toLowerCase()"
|
||||||
|
style="width: 18px"
|
||||||
|
/>
|
||||||
|
<div>{{ selectedOption.name }}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template let-country #item>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png"
|
||||||
|
[class]="'flag flag-' + country.code.toLowerCase()"
|
||||||
|
style="width: 18px"
|
||||||
|
/>
|
||||||
|
<div>{{ country.name }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #dropdownicon>
|
||||||
|
<i class="pi pi-map"></i>
|
||||||
|
</ng-template>
|
||||||
|
</p-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notes -->
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label for="notes" class="block font-bold mb-3">Bemerkungen</label>
|
||||||
|
<p-editor #notes="ngModel"
|
||||||
|
[(ngModel)]="property().notes"
|
||||||
|
name="notes"
|
||||||
|
[style]="{ height: '320px' }">
|
||||||
|
</p-editor>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Uploads -->
|
||||||
|
@if (property().id) {
|
||||||
|
<div class="flex flex-col">
|
||||||
|
|
||||||
|
<p-fieldset i18n-legend legend="Anhänge" [toggleable]="true">
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2 mb-2">
|
||||||
|
@for (attachment of property().attachments; track attachment.id) {
|
||||||
|
<p-chip label="{{ attachment.fileName }}" icon="pi pi-file">
|
||||||
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" size="small" (click)="confirmRemoveAttachment($event, property(), attachment)"/>
|
||||||
|
</p-chip>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<p-fileupload name="attachments" url="{{ environment.apiBaseUrl }}/properties/{{ property().id }}/upload" (onUpload)="onUpload($event)" [multiple]="true" accept="image/*,.pdf" maxFileSize="1000000000" mode="advanced">
|
||||||
|
<ng-template #empty>
|
||||||
|
<div>Drag and drop files to here to upload.</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-fileupload>
|
||||||
|
</p-fieldset>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #footer>
|
||||||
|
<p-button i18n-label label="Abbrechen" icon="pi pi-times" text (click)="hideDialog()" />
|
||||||
|
<p-button i18n-label label="Speichern" icon="pi pi-check" (click)="saveProperty()" />
|
||||||
|
</ng-template>
|
||||||
|
</p-dialog>
|
||||||
|
|
||||||
|
<p-confirmdialog [style]="{ width: '450px' }" />
|
||||||
23
src/app/pages/property-manager/property-manager.spec.ts
Normal file
23
src/app/pages/property-manager/property-manager.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PropertyManager } from './property-manager';
|
||||||
|
|
||||||
|
describe('PropertyManager', () => {
|
||||||
|
let component: PropertyManager;
|
||||||
|
let fixture: ComponentFixture<PropertyManager>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [PropertyManager]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(PropertyManager);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
292
src/app/pages/property-manager/property-manager.ts
Normal file
292
src/app/pages/property-manager/property-manager.ts
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import { Component, OnInit, signal, ViewChild } from '@angular/core';
|
||||||
|
import { Button } from 'primeng/button';
|
||||||
|
import { ConfirmDialog } from 'primeng/confirmdialog';
|
||||||
|
import { LowerCasePipe } from '@angular/common';
|
||||||
|
import { Dialog } from 'primeng/dialog';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { IconField } from 'primeng/iconfield';
|
||||||
|
import { InputIcon } from 'primeng/inputicon';
|
||||||
|
import { InputText } from 'primeng/inputtext';
|
||||||
|
import { Table, TableModule } from 'primeng/table';
|
||||||
|
import { Toolbar } from 'primeng/toolbar';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { Country, Property, PropertyService } from '@/pages/service/property.service';
|
||||||
|
import { Editor } from 'primeng/editor';
|
||||||
|
import { Select } from 'primeng/select';
|
||||||
|
import { CountryService } from '@/pages/service/country.service';
|
||||||
|
import { FileUpload, FileUploadEvent } from 'primeng/fileupload';
|
||||||
|
import { Fieldset } from 'primeng/fieldset';
|
||||||
|
import { Chip } from 'primeng/chip';
|
||||||
|
import { Attachment, AttachmentService } from '@/pages/service/attachment.service';
|
||||||
|
import { environment } from '../../../environments/environments';
|
||||||
|
|
||||||
|
interface Column {
|
||||||
|
field: string;
|
||||||
|
header: string;
|
||||||
|
customExportHeader?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportColumn {
|
||||||
|
title: string;
|
||||||
|
dataKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-property-manager',
|
||||||
|
imports: [Button, ConfirmDialog, Dialog, FormsModule, IconField, InputIcon, InputText, TableModule, Toolbar, LowerCasePipe, Editor, Select, FileUpload, Fieldset, Chip],
|
||||||
|
templateUrl: './property-manager.html',
|
||||||
|
styleUrl: './property-manager.scss',
|
||||||
|
providers: [MessageService, ConfirmationService, PropertyService, CountryService, AttachmentService]
|
||||||
|
})
|
||||||
|
export class PropertyManager implements OnInit {
|
||||||
|
propertyDialog: boolean = false;
|
||||||
|
|
||||||
|
properties = signal<Property[]>([]);
|
||||||
|
|
||||||
|
property = signal<Property>({});
|
||||||
|
|
||||||
|
selectedProperties!: Property[] | null;
|
||||||
|
|
||||||
|
submitted: boolean = false;
|
||||||
|
|
||||||
|
statuses!: any[];
|
||||||
|
|
||||||
|
@ViewChild('dt') dt!: Table;
|
||||||
|
|
||||||
|
exportColumns!: ExportColumn[];
|
||||||
|
|
||||||
|
cols!: Column[];
|
||||||
|
countries: Country[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private messageService: MessageService,
|
||||||
|
private confirmationService: ConfirmationService,
|
||||||
|
private propertyService: PropertyService,
|
||||||
|
private countryService: CountryService,
|
||||||
|
private attachmentService: AttachmentService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
exportCSV() {
|
||||||
|
// this.dt.exportCSV();
|
||||||
|
console.debug(this, ' -- ', this.dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.selectedProperties = [];
|
||||||
|
|
||||||
|
this.propertyService.getProperties().subscribe((properties) => {
|
||||||
|
console.log('Properties', properties);
|
||||||
|
this.properties.set(properties);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.countryService.getCountries().then((countries) => {
|
||||||
|
this.countries = countries;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDemoData() {
|
||||||
|
this.statuses = [
|
||||||
|
{ label: 'INSTOCK', value: 'instock' },
|
||||||
|
{ label: 'LOWSTOCK', value: 'lowstock' },
|
||||||
|
{ label: 'OUTOFSTOCK', value: 'outofstock' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.cols = [
|
||||||
|
{ field: 'code', header: 'Code', customExportHeader: 'Product Code' },
|
||||||
|
{ field: 'name', header: 'Name' },
|
||||||
|
{ field: 'image', header: 'Image' },
|
||||||
|
{ field: 'price', header: 'Price' },
|
||||||
|
{ field: 'category', header: 'Category' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.exportColumns = this.cols.map((col) => ({ title: col.header, dataKey: col.field }));
|
||||||
|
}
|
||||||
|
|
||||||
|
onGlobalFilter(table: Table, event: Event) {
|
||||||
|
table.filterGlobal((event.target as HTMLInputElement).value, 'contains');
|
||||||
|
}
|
||||||
|
|
||||||
|
openNew() {
|
||||||
|
this.property.set({});
|
||||||
|
this.submitted = false;
|
||||||
|
this.propertyDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
editProperty(property: Property) {
|
||||||
|
this.property.set({ ...property });
|
||||||
|
this.propertyDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSelected() {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
message: 'Are you sure you want to delete the selected properties?',
|
||||||
|
header: 'Confirm',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
accept: () => {
|
||||||
|
console.log('properties to delete', this.selectedProperties);
|
||||||
|
|
||||||
|
if (this.selectedProperties === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.propertyService.deleteProperties(this.selectedProperties).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.properties.set(this.properties().filter((val) => !this.selectedProperties?.includes(val)));
|
||||||
|
this.selectedProperties = null;
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Properties Deleted',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.log('Error while deletting properties -- Error: ' + err + ' -- Properties: ', this.selectedProperties);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDialog() {
|
||||||
|
this.propertyDialog = false;
|
||||||
|
this.submitted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProperty(property: Property) {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
message: 'Are you sure you want to delete ' + property.name + '?',
|
||||||
|
header: 'Confirm',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
accept: () => {
|
||||||
|
this.propertyService.deleteProperty(property).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.properties.set(this.properties().filter((val) => val.id !== property.id));
|
||||||
|
this.property.set({});
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Property Deleted',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.log('Error while updating property -- Error: ' + err + ' -- Property: ', property);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findIndexById(id?: string): number {
|
||||||
|
let index = -1;
|
||||||
|
// console.log("Looking for id " + id);
|
||||||
|
for (let i = 0; i < this.properties().length; i++) {
|
||||||
|
// console.log("current item id: " + this.properties()[i].id)
|
||||||
|
if (this.properties()[i].id === id) {
|
||||||
|
// console.log("Index", this.properties()[i].id, i);
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveProperty() {
|
||||||
|
this.submitted = true;
|
||||||
|
let _property = this.property();
|
||||||
|
let _properties = this.properties();
|
||||||
|
|
||||||
|
console.log('Saving Property', _property);
|
||||||
|
|
||||||
|
if (this.property.name?.trim()) {
|
||||||
|
if (_property.id) {
|
||||||
|
// update
|
||||||
|
this.propertyService.updateProperty(_property).subscribe({
|
||||||
|
next: (arg) => {
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Property Updated',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
_properties[this.findIndexById(_property.id)] = _property;
|
||||||
|
this.properties.set([..._properties]);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.log('Error while updating property -- Error: ' + err + ' -- Property: ', _property);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.propertyService.createProperty(_property).subscribe({
|
||||||
|
next: (arg) => {
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Property Created',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
this.properties.set([..._properties, arg]);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.log('Error while creating property -- Error: ' + err + ' -- Property: ', _property);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.propertyDialog = false;
|
||||||
|
this.property.set({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpload($event: FileUploadEvent) {
|
||||||
|
console.debug(this, $event);
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Success', detail: 'Upload was successful.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmRemoveAttachment(event: Event, property: Property, attachment: Attachment) {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
target: event.target as EventTarget,
|
||||||
|
message: 'Do you want to remvoe this attachment?',
|
||||||
|
header: 'Please confirm',
|
||||||
|
icon: 'pi pi-info-circle',
|
||||||
|
rejectLabel: 'Cancel',
|
||||||
|
rejectButtonProps: {
|
||||||
|
label: 'Cancel',
|
||||||
|
severity: 'secondary',
|
||||||
|
outlined: true,
|
||||||
|
},
|
||||||
|
acceptButtonProps: {
|
||||||
|
label: 'Delete',
|
||||||
|
severity: 'danger',
|
||||||
|
},
|
||||||
|
|
||||||
|
accept: () => {
|
||||||
|
this.attachmentService.deleteAttachment(attachment).subscribe({
|
||||||
|
next: () => {
|
||||||
|
// @ts-ignore
|
||||||
|
property.attachments = property.attachments.filter(a => a.id !== attachment.id);
|
||||||
|
|
||||||
|
this.messageService.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Successful',
|
||||||
|
detail: 'Attachment Deleted',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.log('Error while deleting attachment -- Error: ' + err + ' -- Attachment: ', property);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
reject: () => {
|
||||||
|
console.debug("Deletion aborted.");
|
||||||
|
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly environment = environment;
|
||||||
|
}
|
||||||
16
src/app/pages/service/attachment.service.spec.ts
Normal file
16
src/app/pages/service/attachment.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AttachmentService } from './attachment.service';
|
||||||
|
|
||||||
|
describe('AttachmentService', () => {
|
||||||
|
let service: AttachmentService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(AttachmentService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
29
src/app/pages/service/attachment.service.ts
Normal file
29
src/app/pages/service/attachment.service.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Property } from '@/pages/service/property.service';
|
||||||
|
import { environment } from '../../../environments/environments';
|
||||||
|
|
||||||
|
export interface Attachment {
|
||||||
|
id?: string;
|
||||||
|
storagePath?: string;
|
||||||
|
fileName?: string;
|
||||||
|
createdAt?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AttachmentService {
|
||||||
|
url: string = environment.apiBaseUrl;
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
deleteAttachment(attachment: Attachment): Observable<Attachment> {
|
||||||
|
console.debug(this + " -- args: ", arguments);
|
||||||
|
|
||||||
|
return this.http.delete<Attachment>(this.url + "/attachments/" + attachment.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
255
src/app/pages/service/country.service.ts
Normal file
255
src/app/pages/service/country.service.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CountryService {
|
||||||
|
getData() {
|
||||||
|
return [
|
||||||
|
{ name: 'Afghanistan', code: 'AF' },
|
||||||
|
{ name: 'Albania', code: 'AL' },
|
||||||
|
{ name: 'Algeria', code: 'DZ' },
|
||||||
|
{ name: 'American Samoa', code: 'AS' },
|
||||||
|
{ name: 'Andorra', code: 'AD' },
|
||||||
|
{ name: 'Angola', code: 'AO' },
|
||||||
|
{ name: 'Anguilla', code: 'AI' },
|
||||||
|
{ name: 'Antarctica', code: 'AQ' },
|
||||||
|
{ name: 'Antigua and Barbuda', code: 'AG' },
|
||||||
|
{ name: 'Argentina', code: 'AR' },
|
||||||
|
{ name: 'Armenia', code: 'AM' },
|
||||||
|
{ name: 'Aruba', code: 'AW' },
|
||||||
|
{ name: 'Australia', code: 'AU' },
|
||||||
|
{ name: 'Austria', code: 'AT' },
|
||||||
|
{ name: 'Azerbaijan', code: 'AZ' },
|
||||||
|
{ name: 'Bahamas', code: 'BS' },
|
||||||
|
{ name: 'Bahrain', code: 'BH' },
|
||||||
|
{ name: 'Bangladesh', code: 'BD' },
|
||||||
|
{ name: 'Barbados', code: 'BB' },
|
||||||
|
{ name: 'Belarus', code: 'BY' },
|
||||||
|
{ name: 'Belgium', code: 'BE' },
|
||||||
|
{ name: 'Belize', code: 'BZ' },
|
||||||
|
{ name: 'Benin', code: 'BJ' },
|
||||||
|
{ name: 'Bermuda', code: 'BM' },
|
||||||
|
{ name: 'Bhutan', code: 'BT' },
|
||||||
|
{ name: 'Bolivia', code: 'BO' },
|
||||||
|
{ name: 'Bosnia and Herzegovina', code: 'BA' },
|
||||||
|
{ name: 'Botswana', code: 'BW' },
|
||||||
|
{ name: 'Bouvet Island', code: 'BV' },
|
||||||
|
{ name: 'Brazil', code: 'BR' },
|
||||||
|
{ name: 'British Indian Ocean Territory', code: 'IO' },
|
||||||
|
{ name: 'Brunei Darussalam', code: 'BN' },
|
||||||
|
{ name: 'Bulgaria', code: 'BG' },
|
||||||
|
{ name: 'Burkina Faso', code: 'BF' },
|
||||||
|
{ name: 'Burundi', code: 'BI' },
|
||||||
|
{ name: 'Cambodia', code: 'KH' },
|
||||||
|
{ name: 'Cameroon', code: 'CM' },
|
||||||
|
{ name: 'Canada', code: 'CA' },
|
||||||
|
{ name: 'Cape Verde', code: 'CV' },
|
||||||
|
{ name: 'Cayman Islands', code: 'KY' },
|
||||||
|
{ name: 'Central African Republic', code: 'CF' },
|
||||||
|
{ name: 'Chad', code: 'TD' },
|
||||||
|
{ name: 'Chile', code: 'CL' },
|
||||||
|
{ name: 'China', code: 'CN' },
|
||||||
|
{ name: 'Christmas Island', code: 'CX' },
|
||||||
|
{ name: 'Cocos (Keeling) Islands', code: 'CC' },
|
||||||
|
{ name: 'Colombia', code: 'CO' },
|
||||||
|
{ name: 'Comoros', code: 'KM' },
|
||||||
|
{ name: 'Congo', code: 'CG' },
|
||||||
|
{ name: 'Congo, The Democratic Republic of the', code: 'CD' },
|
||||||
|
{ name: 'Cook Islands', code: 'CK' },
|
||||||
|
{ name: 'Costa Rica', code: 'CR' },
|
||||||
|
{ name: 'Cote D"Ivoire', code: 'CI' },
|
||||||
|
{ name: 'Croatia', code: 'HR' },
|
||||||
|
{ name: 'Cuba', code: 'CU' },
|
||||||
|
{ name: 'Cyprus', code: 'CY' },
|
||||||
|
{ name: 'Czech Republic', code: 'CZ' },
|
||||||
|
{ name: 'Denmark', code: 'DK' },
|
||||||
|
{ name: 'Djibouti', code: 'DJ' },
|
||||||
|
{ name: 'Dominica', code: 'DM' },
|
||||||
|
{ name: 'Dominican Republic', code: 'DO' },
|
||||||
|
{ name: 'Ecuador', code: 'EC' },
|
||||||
|
{ name: 'Egypt', code: 'EG' },
|
||||||
|
{ name: 'El Salvador', code: 'SV' },
|
||||||
|
{ name: 'Equatorial Guinea', code: 'GQ' },
|
||||||
|
{ name: 'Eritrea', code: 'ER' },
|
||||||
|
{ name: 'Estonia', code: 'EE' },
|
||||||
|
{ name: 'Ethiopia', code: 'ET' },
|
||||||
|
{ name: 'Falkland Islands (Malvinas)', code: 'FK' },
|
||||||
|
{ name: 'Faroe Islands', code: 'FO' },
|
||||||
|
{ name: 'Fiji', code: 'FJ' },
|
||||||
|
{ name: 'Finland', code: 'FI' },
|
||||||
|
{ name: 'France', code: 'FR' },
|
||||||
|
{ name: 'French Guiana', code: 'GF' },
|
||||||
|
{ name: 'French Polynesia', code: 'PF' },
|
||||||
|
{ name: 'French Southern Territories', code: 'TF' },
|
||||||
|
{ name: 'Gabon', code: 'GA' },
|
||||||
|
{ name: 'Gambia', code: 'GM' },
|
||||||
|
{ name: 'Georgia', code: 'GE' },
|
||||||
|
{ name: 'Germany', code: 'DE' },
|
||||||
|
{ name: 'Ghana', code: 'GH' },
|
||||||
|
{ name: 'Gibraltar', code: 'GI' },
|
||||||
|
{ name: 'Greece', code: 'GR' },
|
||||||
|
{ name: 'Greenland', code: 'GL' },
|
||||||
|
{ name: 'Grenada', code: 'GD' },
|
||||||
|
{ name: 'Guadeloupe', code: 'GP' },
|
||||||
|
{ name: 'Guam', code: 'GU' },
|
||||||
|
{ name: 'Guatemala', code: 'GT' },
|
||||||
|
{ name: 'Guernsey', code: 'GG' },
|
||||||
|
{ name: 'Guinea', code: 'GN' },
|
||||||
|
{ name: 'Guinea-Bissau', code: 'GW' },
|
||||||
|
{ name: 'Guyana', code: 'GY' },
|
||||||
|
{ name: 'Haiti', code: 'HT' },
|
||||||
|
{ name: 'Heard Island and Mcdonald Islands', code: 'HM' },
|
||||||
|
{ name: 'Holy See (Vatican City State)', code: 'VA' },
|
||||||
|
{ name: 'Honduras', code: 'HN' },
|
||||||
|
{ name: 'Hong Kong', code: 'HK' },
|
||||||
|
{ name: 'Hungary', code: 'HU' },
|
||||||
|
{ name: 'Iceland', code: 'IS' },
|
||||||
|
{ name: 'India', code: 'IN' },
|
||||||
|
{ name: 'Indonesia', code: 'ID' },
|
||||||
|
{ name: 'Iran, Islamic Republic Of', code: 'IR' },
|
||||||
|
{ name: 'Iraq', code: 'IQ' },
|
||||||
|
{ name: 'Ireland', code: 'IE' },
|
||||||
|
{ name: 'Isle of Man', code: 'IM' },
|
||||||
|
{ name: 'Israel', code: 'IL' },
|
||||||
|
{ name: 'Italy', code: 'IT' },
|
||||||
|
{ name: 'Jamaica', code: 'JM' },
|
||||||
|
{ name: 'Japan', code: 'JP' },
|
||||||
|
{ name: 'Jersey', code: 'JE' },
|
||||||
|
{ name: 'Jordan', code: 'JO' },
|
||||||
|
{ name: 'Kazakhstan', code: 'KZ' },
|
||||||
|
{ name: 'Kenya', code: 'KE' },
|
||||||
|
{ name: 'Kiribati', code: 'KI' },
|
||||||
|
{ name: 'Korea, Democratic People"S Republic of', code: 'KP' },
|
||||||
|
{ name: 'Korea, Republic of', code: 'KR' },
|
||||||
|
{ name: 'Kuwait', code: 'KW' },
|
||||||
|
{ name: 'Kyrgyzstan', code: 'KG' },
|
||||||
|
{ name: 'Lao People"S Democratic Republic', code: 'LA' },
|
||||||
|
{ name: 'Latvia', code: 'LV' },
|
||||||
|
{ name: 'Lebanon', code: 'LB' },
|
||||||
|
{ name: 'Lesotho', code: 'LS' },
|
||||||
|
{ name: 'Liberia', code: 'LR' },
|
||||||
|
{ name: 'Libyan Arab Jamahiriya', code: 'LY' },
|
||||||
|
{ name: 'Liechtenstein', code: 'LI' },
|
||||||
|
{ name: 'Lithuania', code: 'LT' },
|
||||||
|
{ name: 'Luxembourg', code: 'LU' },
|
||||||
|
{ name: 'Macao', code: 'MO' },
|
||||||
|
{ name: 'Macedonia, The Former Yugoslav Republic of', code: 'MK' },
|
||||||
|
{ name: 'Madagascar', code: 'MG' },
|
||||||
|
{ name: 'Malawi', code: 'MW' },
|
||||||
|
{ name: 'Malaysia', code: 'MY' },
|
||||||
|
{ name: 'Maldives', code: 'MV' },
|
||||||
|
{ name: 'Mali', code: 'ML' },
|
||||||
|
{ name: 'Malta', code: 'MT' },
|
||||||
|
{ name: 'Marshall Islands', code: 'MH' },
|
||||||
|
{ name: 'Martinique', code: 'MQ' },
|
||||||
|
{ name: 'Mauritania', code: 'MR' },
|
||||||
|
{ name: 'Mauritius', code: 'MU' },
|
||||||
|
{ name: 'Mayotte', code: 'YT' },
|
||||||
|
{ name: 'Mexico', code: 'MX' },
|
||||||
|
{ name: 'Micronesia, Federated States of', code: 'FM' },
|
||||||
|
{ name: 'Moldova, Republic of', code: 'MD' },
|
||||||
|
{ name: 'Monaco', code: 'MC' },
|
||||||
|
{ name: 'Mongolia', code: 'MN' },
|
||||||
|
{ name: 'Montserrat', code: 'MS' },
|
||||||
|
{ name: 'Morocco', code: 'MA' },
|
||||||
|
{ name: 'Mozambique', code: 'MZ' },
|
||||||
|
{ name: 'Myanmar', code: 'MM' },
|
||||||
|
{ name: 'Namibia', code: 'NA' },
|
||||||
|
{ name: 'Nauru', code: 'NR' },
|
||||||
|
{ name: 'Nepal', code: 'NP' },
|
||||||
|
{ name: 'Netherlands', code: 'NL' },
|
||||||
|
{ name: 'Netherlands Antilles', code: 'AN' },
|
||||||
|
{ name: 'New Caledonia', code: 'NC' },
|
||||||
|
{ name: 'New Zealand', code: 'NZ' },
|
||||||
|
{ name: 'Nicaragua', code: 'NI' },
|
||||||
|
{ name: 'Niger', code: 'NE' },
|
||||||
|
{ name: 'Nigeria', code: 'NG' },
|
||||||
|
{ name: 'Niue', code: 'NU' },
|
||||||
|
{ name: 'Norfolk Island', code: 'NF' },
|
||||||
|
{ name: 'Northern Mariana Islands', code: 'MP' },
|
||||||
|
{ name: 'Norway', code: 'NO' },
|
||||||
|
{ name: 'Oman', code: 'OM' },
|
||||||
|
{ name: 'Pakistan', code: 'PK' },
|
||||||
|
{ name: 'Palau', code: 'PW' },
|
||||||
|
{ name: 'Palestinian Territory, Occupied', code: 'PS' },
|
||||||
|
{ name: 'Panama', code: 'PA' },
|
||||||
|
{ name: 'Papua New Guinea', code: 'PG' },
|
||||||
|
{ name: 'Paraguay', code: 'PY' },
|
||||||
|
{ name: 'Peru', code: 'PE' },
|
||||||
|
{ name: 'Philippines', code: 'PH' },
|
||||||
|
{ name: 'Pitcairn', code: 'PN' },
|
||||||
|
{ name: 'Poland', code: 'PL' },
|
||||||
|
{ name: 'Portugal', code: 'PT' },
|
||||||
|
{ name: 'Puerto Rico', code: 'PR' },
|
||||||
|
{ name: 'Qatar', code: 'QA' },
|
||||||
|
{ name: 'Reunion', code: 'RE' },
|
||||||
|
{ name: 'Romania', code: 'RO' },
|
||||||
|
{ name: 'Russian Federation', code: 'RU' },
|
||||||
|
{ name: 'RWANDA', code: 'RW' },
|
||||||
|
{ name: 'Saint Helena', code: 'SH' },
|
||||||
|
{ name: 'Saint Kitts and Nevis', code: 'KN' },
|
||||||
|
{ name: 'Saint Lucia', code: 'LC' },
|
||||||
|
{ name: 'Saint Pierre and Miquelon', code: 'PM' },
|
||||||
|
{ name: 'Saint Vincent and the Grenadines', code: 'VC' },
|
||||||
|
{ name: 'Samoa', code: 'WS' },
|
||||||
|
{ name: 'San Marino', code: 'SM' },
|
||||||
|
{ name: 'Sao Tome and Principe', code: 'ST' },
|
||||||
|
{ name: 'Saudi Arabia', code: 'SA' },
|
||||||
|
{ name: 'Senegal', code: 'SN' },
|
||||||
|
{ name: 'Serbia and Montenegro', code: 'CS' },
|
||||||
|
{ name: 'Seychelles', code: 'SC' },
|
||||||
|
{ name: 'Sierra Leone', code: 'SL' },
|
||||||
|
{ name: 'Singapore', code: 'SG' },
|
||||||
|
{ name: 'Slovakia', code: 'SK' },
|
||||||
|
{ name: 'Slovenia', code: 'SI' },
|
||||||
|
{ name: 'Solomon Islands', code: 'SB' },
|
||||||
|
{ name: 'Somalia', code: 'SO' },
|
||||||
|
{ name: 'South Africa', code: 'ZA' },
|
||||||
|
{ name: 'South Georgia and the South Sandwich Islands', code: 'GS' },
|
||||||
|
{ name: 'Spain', code: 'ES' },
|
||||||
|
{ name: 'Sri Lanka', code: 'LK' },
|
||||||
|
{ name: 'Sudan', code: 'SD' },
|
||||||
|
{ name: 'Suriname', code: 'SR' },
|
||||||
|
{ name: 'Svalbard and Jan Mayen', code: 'SJ' },
|
||||||
|
{ name: 'Swaziland', code: 'SZ' },
|
||||||
|
{ name: 'Sweden', code: 'SE' },
|
||||||
|
{ name: 'Switzerland', code: 'CH' },
|
||||||
|
{ name: 'Syrian Arab Republic', code: 'SY' },
|
||||||
|
{ name: 'Taiwan, Province of China', code: 'TW' },
|
||||||
|
{ name: 'Tajikistan', code: 'TJ' },
|
||||||
|
{ name: 'Tanzania, United Republic of', code: 'TZ' },
|
||||||
|
{ name: 'Thailand', code: 'TH' },
|
||||||
|
{ name: 'Timor-Leste', code: 'TL' },
|
||||||
|
{ name: 'Togo', code: 'TG' },
|
||||||
|
{ name: 'Tokelau', code: 'TK' },
|
||||||
|
{ name: 'Tonga', code: 'TO' },
|
||||||
|
{ name: 'Trinidad and Tobago', code: 'TT' },
|
||||||
|
{ name: 'Tunisia', code: 'TN' },
|
||||||
|
{ name: 'Turkey', code: 'TR' },
|
||||||
|
{ name: 'Turkmenistan', code: 'TM' },
|
||||||
|
{ name: 'Turks and Caicos Islands', code: 'TC' },
|
||||||
|
{ name: 'Tuvalu', code: 'TV' },
|
||||||
|
{ name: 'Uganda', code: 'UG' },
|
||||||
|
{ name: 'Ukraine', code: 'UA' },
|
||||||
|
{ name: 'United Arab Emirates', code: 'AE' },
|
||||||
|
{ name: 'United Kingdom', code: 'GB' },
|
||||||
|
{ name: 'United States', code: 'US' },
|
||||||
|
{ name: 'United States Minor Outlying Islands', code: 'UM' },
|
||||||
|
{ name: 'Uruguay', code: 'UY' },
|
||||||
|
{ name: 'Uzbekistan', code: 'UZ' },
|
||||||
|
{ name: 'Vanuatu', code: 'VU' },
|
||||||
|
{ name: 'Venezuela', code: 'VE' },
|
||||||
|
{ name: 'Viet Nam', code: 'VN' },
|
||||||
|
{ name: 'Virgin Islands, British', code: 'VG' },
|
||||||
|
{ name: 'Virgin Islands, U.S.', code: 'VI' },
|
||||||
|
{ name: 'Wallis and Futuna', code: 'WF' },
|
||||||
|
{ name: 'Western Sahara', code: 'EH' },
|
||||||
|
{ name: 'Yemen', code: 'YE' },
|
||||||
|
{ name: 'Zambia', code: 'ZM' },
|
||||||
|
{ name: 'Zimbabwe', code: 'ZW' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCountries() {
|
||||||
|
return Promise.resolve(this.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
9055
src/app/pages/service/customer.service.ts
Normal file
9055
src/app/pages/service/customer.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
23
src/app/pages/service/icon.service.ts
Normal file
23
src/app/pages/service/icon.service.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class IconService {
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
icons!: any[];
|
||||||
|
|
||||||
|
selectedIcon: any;
|
||||||
|
|
||||||
|
apiUrl = 'assets/demo/data/icons.json';
|
||||||
|
|
||||||
|
getIcons() {
|
||||||
|
return this.http.get(this.apiUrl).pipe(
|
||||||
|
map((response: any) => {
|
||||||
|
this.icons = response.icons;
|
||||||
|
return this.icons;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
816
src/app/pages/service/node.service.ts
Normal file
816
src/app/pages/service/node.service.ts
Normal file
@@ -0,0 +1,816 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { TreeNode } from 'primeng/api';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NodeService {
|
||||||
|
getTreeNodesData() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: '0',
|
||||||
|
label: 'Documents',
|
||||||
|
data: 'Documents Folder',
|
||||||
|
icon: 'pi pi-fw pi-inbox',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '0-0',
|
||||||
|
label: 'Work',
|
||||||
|
data: 'Work Folder',
|
||||||
|
icon: 'pi pi-fw pi-cog',
|
||||||
|
children: [
|
||||||
|
{ key: '0-0-0', label: 'Expenses.doc', icon: 'pi pi-fw pi-file', data: 'Expenses Document' },
|
||||||
|
{ key: '0-0-1', label: 'Resume.doc', icon: 'pi pi-fw pi-file', data: 'Resume Document' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '0-1',
|
||||||
|
label: 'Home',
|
||||||
|
data: 'Home Folder',
|
||||||
|
icon: 'pi pi-fw pi-home',
|
||||||
|
children: [{ key: '0-1-0', label: 'Invoices.txt', icon: 'pi pi-fw pi-file', data: 'Invoices for this month' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'Events',
|
||||||
|
data: 'Events Folder',
|
||||||
|
icon: 'pi pi-fw pi-calendar',
|
||||||
|
children: [
|
||||||
|
{ key: '1-0', label: 'Meeting', icon: 'pi pi-fw pi-calendar-plus', data: 'Meeting' },
|
||||||
|
{ key: '1-1', label: 'Product Launch', icon: 'pi pi-fw pi-calendar-plus', data: 'Product Launch' },
|
||||||
|
{ key: '1-2', label: 'Report Review', icon: 'pi pi-fw pi-calendar-plus', data: 'Report Review' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: 'Movies',
|
||||||
|
data: 'Movies Folder',
|
||||||
|
icon: 'pi pi-fw pi-star-fill',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '2-0',
|
||||||
|
icon: 'pi pi-fw pi-star-fill',
|
||||||
|
label: 'Al Pacino',
|
||||||
|
data: 'Pacino Movies',
|
||||||
|
children: [
|
||||||
|
{ key: '2-0-0', label: 'Scarface', icon: 'pi pi-fw pi-video', data: 'Scarface Movie' },
|
||||||
|
{ key: '2-0-1', label: 'Serpico', icon: 'pi pi-fw pi-video', data: 'Serpico Movie' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2-1',
|
||||||
|
label: 'Robert De Niro',
|
||||||
|
icon: 'pi pi-fw pi-star-fill',
|
||||||
|
data: 'De Niro Movies',
|
||||||
|
children: [
|
||||||
|
{ key: '2-1-0', label: 'Goodfellas', icon: 'pi pi-fw pi-video', data: 'Goodfellas Movie' },
|
||||||
|
{ key: '2-1-1', label: 'Untouchables', icon: 'pi pi-fw pi-video', data: 'Untouchables Movie' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeTableNodesData() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: '0',
|
||||||
|
data: {
|
||||||
|
name: 'Applications',
|
||||||
|
size: '100kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '0-0',
|
||||||
|
data: {
|
||||||
|
name: 'Angular',
|
||||||
|
size: '25kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '0-0-0',
|
||||||
|
data: {
|
||||||
|
name: 'angular.app',
|
||||||
|
size: '10kb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '0-0-1',
|
||||||
|
data: {
|
||||||
|
name: 'native.app',
|
||||||
|
size: '10kb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '0-0-2',
|
||||||
|
data: {
|
||||||
|
name: 'mobile.app',
|
||||||
|
size: '5kb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '0-1',
|
||||||
|
data: {
|
||||||
|
name: 'editor.app',
|
||||||
|
size: '25kb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '0-2',
|
||||||
|
data: {
|
||||||
|
name: 'settings.app',
|
||||||
|
size: '50kb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
data: {
|
||||||
|
name: 'Cloud',
|
||||||
|
size: '20kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '1-0',
|
||||||
|
data: {
|
||||||
|
name: 'backup-1.zip',
|
||||||
|
size: '10kb',
|
||||||
|
type: 'Zip'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '1-1',
|
||||||
|
data: {
|
||||||
|
name: 'backup-2.zip',
|
||||||
|
size: '10kb',
|
||||||
|
type: 'Zip'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
data: {
|
||||||
|
name: 'Desktop',
|
||||||
|
size: '150kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '2-0',
|
||||||
|
data: {
|
||||||
|
name: 'note-meeting.txt',
|
||||||
|
size: '50kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '2-1',
|
||||||
|
data: {
|
||||||
|
name: 'note-todo.txt',
|
||||||
|
size: '100kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
data: {
|
||||||
|
name: 'Documents',
|
||||||
|
size: '75kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '3-0',
|
||||||
|
data: {
|
||||||
|
name: 'Work',
|
||||||
|
size: '55kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '3-0-0',
|
||||||
|
data: {
|
||||||
|
name: 'Expenses.doc',
|
||||||
|
size: '30kb',
|
||||||
|
type: 'Document'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3-0-1',
|
||||||
|
data: {
|
||||||
|
name: 'Resume.doc',
|
||||||
|
size: '25kb',
|
||||||
|
type: 'Resume'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3-1',
|
||||||
|
data: {
|
||||||
|
name: 'Home',
|
||||||
|
size: '20kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '3-1-0',
|
||||||
|
data: {
|
||||||
|
name: 'Invoices',
|
||||||
|
size: '20kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
data: {
|
||||||
|
name: 'Downloads',
|
||||||
|
size: '25kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '4-0',
|
||||||
|
data: {
|
||||||
|
name: 'Spanish',
|
||||||
|
size: '10kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '4-0-0',
|
||||||
|
data: {
|
||||||
|
name: 'tutorial-a1.txt',
|
||||||
|
size: '5kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4-0-1',
|
||||||
|
data: {
|
||||||
|
name: 'tutorial-a2.txt',
|
||||||
|
size: '5kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4-1',
|
||||||
|
data: {
|
||||||
|
name: 'Travel',
|
||||||
|
size: '15kb',
|
||||||
|
type: 'Text'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '4-1-0',
|
||||||
|
data: {
|
||||||
|
name: 'Hotel.pdf',
|
||||||
|
size: '10kb',
|
||||||
|
type: 'PDF'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '4-1-1',
|
||||||
|
data: {
|
||||||
|
name: 'Flight.pdf',
|
||||||
|
size: '5kb',
|
||||||
|
type: 'PDF'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5',
|
||||||
|
data: {
|
||||||
|
name: 'Main',
|
||||||
|
size: '50kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '5-0',
|
||||||
|
data: {
|
||||||
|
name: 'bin',
|
||||||
|
size: '50kb',
|
||||||
|
type: 'Link'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5-1',
|
||||||
|
data: {
|
||||||
|
name: 'etc',
|
||||||
|
size: '100kb',
|
||||||
|
type: 'Link'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '5-2',
|
||||||
|
data: {
|
||||||
|
name: 'var',
|
||||||
|
size: '100kb',
|
||||||
|
type: 'Link'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '6',
|
||||||
|
data: {
|
||||||
|
name: 'Other',
|
||||||
|
size: '5kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '6-0',
|
||||||
|
data: {
|
||||||
|
name: 'todo.txt',
|
||||||
|
size: '3kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '6-1',
|
||||||
|
data: {
|
||||||
|
name: 'logo.png',
|
||||||
|
size: '2kb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '7',
|
||||||
|
data: {
|
||||||
|
name: 'Pictures',
|
||||||
|
size: '150kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '7-0',
|
||||||
|
data: {
|
||||||
|
name: 'barcelona.jpg',
|
||||||
|
size: '90kb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '7-1',
|
||||||
|
data: {
|
||||||
|
name: 'primeng.png',
|
||||||
|
size: '30kb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '7-2',
|
||||||
|
data: {
|
||||||
|
name: 'prime.jpg',
|
||||||
|
size: '30kb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '8',
|
||||||
|
data: {
|
||||||
|
name: 'Videos',
|
||||||
|
size: '1500kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: '8-0',
|
||||||
|
data: {
|
||||||
|
name: 'primefaces.mkv',
|
||||||
|
size: '1000kb',
|
||||||
|
type: 'Video'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '8-1',
|
||||||
|
data: {
|
||||||
|
name: 'intro.avi',
|
||||||
|
size: '500kb',
|
||||||
|
type: 'Video'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getLazyNodesData() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Lazy Node 0',
|
||||||
|
data: 'Node 0',
|
||||||
|
expandedIcon: 'pi pi-folder-open',
|
||||||
|
collapsedIcon: 'pi pi-folder',
|
||||||
|
leaf: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lazy Node 1',
|
||||||
|
data: 'Node 1',
|
||||||
|
expandedIcon: 'pi pi-folder-open',
|
||||||
|
collapsedIcon: 'pi pi-folder',
|
||||||
|
leaf: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lazy Node 1',
|
||||||
|
data: 'Node 2',
|
||||||
|
expandedIcon: 'pi pi-folder-open',
|
||||||
|
collapsedIcon: 'pi pi-folder',
|
||||||
|
leaf: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileSystemNodesData() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Applications',
|
||||||
|
size: '200mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Angular',
|
||||||
|
size: '25mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'angular.app',
|
||||||
|
size: '10mb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'cli.app',
|
||||||
|
size: '10mb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'mobile.app',
|
||||||
|
size: '5mb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'editor.app',
|
||||||
|
size: '25mb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'settings.app',
|
||||||
|
size: '50mb',
|
||||||
|
type: 'Application'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Cloud',
|
||||||
|
size: '20mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'backup-1.zip',
|
||||||
|
size: '10mb',
|
||||||
|
type: 'Zip'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'backup-2.zip',
|
||||||
|
size: '10mb',
|
||||||
|
type: 'Zip'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Desktop',
|
||||||
|
size: '150kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'note-meeting.txt',
|
||||||
|
size: '50kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'note-todo.txt',
|
||||||
|
size: '100kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Documents',
|
||||||
|
size: '75kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Work',
|
||||||
|
size: '55kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Expenses.doc',
|
||||||
|
size: '30kb',
|
||||||
|
type: 'Document'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Resume.doc',
|
||||||
|
size: '25kb',
|
||||||
|
type: 'Resume'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Home',
|
||||||
|
size: '20kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Invoices',
|
||||||
|
size: '20kb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Downloads',
|
||||||
|
size: '25mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Spanish',
|
||||||
|
size: '10mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'tutorial-a1.txt',
|
||||||
|
size: '5mb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'tutorial-a2.txt',
|
||||||
|
size: '5mb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Travel',
|
||||||
|
size: '15mb',
|
||||||
|
type: 'Text'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Hotel.pdf',
|
||||||
|
size: '10mb',
|
||||||
|
type: 'PDF'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Flight.pdf',
|
||||||
|
size: '5mb',
|
||||||
|
type: 'PDF'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Main',
|
||||||
|
size: '50mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'bin',
|
||||||
|
size: '50kb',
|
||||||
|
type: 'Link'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'etc',
|
||||||
|
size: '100kb',
|
||||||
|
type: 'Link'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'var',
|
||||||
|
size: '100kb',
|
||||||
|
type: 'Link'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Other',
|
||||||
|
size: '5mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'todo.txt',
|
||||||
|
size: '3mb',
|
||||||
|
type: 'Text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'logo.png',
|
||||||
|
size: '2mb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Pictures',
|
||||||
|
size: '150kb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'barcelona.jpg',
|
||||||
|
size: '90kb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'primeng.png',
|
||||||
|
size: '30kb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'prime.jpg',
|
||||||
|
size: '30kb',
|
||||||
|
type: 'Picture'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'Videos',
|
||||||
|
size: '1500mb',
|
||||||
|
type: 'Folder'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'primefaces.mkv',
|
||||||
|
size: '1000mb',
|
||||||
|
type: 'Video'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: 'intro.avi',
|
||||||
|
size: '500mb',
|
||||||
|
type: 'Video'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getDynamicTreeNodes(parentCount: number, childrenCount: number): TreeNode[] {
|
||||||
|
const nodes: TreeNode[] = [];
|
||||||
|
|
||||||
|
for (let parentIndex = 0; parentIndex < parentCount; parentIndex++) {
|
||||||
|
const children: TreeNode[] = [];
|
||||||
|
|
||||||
|
for (let childIndex = 0; childIndex < childrenCount; childIndex++) {
|
||||||
|
children.push({
|
||||||
|
key: `${parentIndex}-${childIndex}`,
|
||||||
|
label: `Child ${parentIndex}-${childIndex}`,
|
||||||
|
selectable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.push({
|
||||||
|
key: parentIndex.toString(),
|
||||||
|
label: `Parent ${parentIndex}`,
|
||||||
|
selectable: true,
|
||||||
|
children: children
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLargeTreeNodes() {
|
||||||
|
return Promise.resolve(this.getDynamicTreeNodes(10, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeTableNodes() {
|
||||||
|
return Promise.resolve(this.getTreeTableNodesData());
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeNodes() {
|
||||||
|
return Promise.resolve(this.getTreeNodesData());
|
||||||
|
}
|
||||||
|
|
||||||
|
getFiles() {
|
||||||
|
return Promise.resolve(this.getTreeNodesData());
|
||||||
|
}
|
||||||
|
|
||||||
|
getLazyFiles() {
|
||||||
|
return Promise.resolve(this.getLazyNodesData());
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilesystem() {
|
||||||
|
return Promise.resolve(this.getFileSystemNodesData());
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/app/pages/service/photo.service.ts
Normal file
103
src/app/pages/service/photo.service.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PhotoService {
|
||||||
|
getData() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria1.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria1s.jpg',
|
||||||
|
alt: 'Description for Image 1',
|
||||||
|
title: 'Title 1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria2.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria2s.jpg',
|
||||||
|
alt: 'Description for Image 2',
|
||||||
|
title: 'Title 2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria3.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria3s.jpg',
|
||||||
|
alt: 'Description for Image 3',
|
||||||
|
title: 'Title 3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria4.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria4s.jpg',
|
||||||
|
alt: 'Description for Image 4',
|
||||||
|
title: 'Title 4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria5.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria5s.jpg',
|
||||||
|
alt: 'Description for Image 5',
|
||||||
|
title: 'Title 5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria6.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria6s.jpg',
|
||||||
|
alt: 'Description for Image 6',
|
||||||
|
title: 'Title 6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria7.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria7s.jpg',
|
||||||
|
alt: 'Description for Image 7',
|
||||||
|
title: 'Title 7'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria8.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria8s.jpg',
|
||||||
|
alt: 'Description for Image 8',
|
||||||
|
title: 'Title 8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria9.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria9s.jpg',
|
||||||
|
alt: 'Description for Image 9',
|
||||||
|
title: 'Title 9'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria10.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria10s.jpg',
|
||||||
|
alt: 'Description for Image 10',
|
||||||
|
title: 'Title 10'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria11.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria11s.jpg',
|
||||||
|
alt: 'Description for Image 11',
|
||||||
|
title: 'Title 11'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria12.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria12s.jpg',
|
||||||
|
alt: 'Description for Image 12',
|
||||||
|
title: 'Title 12'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria13.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria13s.jpg',
|
||||||
|
alt: 'Description for Image 13',
|
||||||
|
title: 'Title 13'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria14.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria14s.jpg',
|
||||||
|
alt: 'Description for Image 14',
|
||||||
|
title: 'Title 14'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria15.jpg',
|
||||||
|
thumbnailImageSrc: 'https://primefaces.org/cdn/primeng/images/galleria/galleria15s.jpg',
|
||||||
|
alt: 'Description for Image 15',
|
||||||
|
title: 'Title 15'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getImages() {
|
||||||
|
return Promise.resolve(this.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
1322
src/app/pages/service/product.service.ts
Normal file
1322
src/app/pages/service/product.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
16
src/app/pages/service/project-status.service.spec.ts
Normal file
16
src/app/pages/service/project-status.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProjectStatusService } from './project-status.service';
|
||||||
|
|
||||||
|
describe('ProjectStatusService', () => {
|
||||||
|
let service: ProjectStatusService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ProjectStatusService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
24
src/app/pages/service/project-status.service.ts
Normal file
24
src/app/pages/service/project-status.service.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../environments/environments';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
export interface ProjectStatus {
|
||||||
|
id?: string; // UUID
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ProjectStatusService {
|
||||||
|
apiEndpoint: string = environment.apiBaseUrl + '/project-statuses';
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
getProjectStatuses(): Observable<ProjectStatus[]> {
|
||||||
|
return this.http.get<ProjectStatus[]>(this.apiEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
src/app/pages/service/project-type.service.spec.ts
Normal file
16
src/app/pages/service/project-type.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProjectTypeService } from './project-type.service';
|
||||||
|
|
||||||
|
describe('ProjectTypeService', () => {
|
||||||
|
let service: ProjectTypeService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ProjectTypeService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
24
src/app/pages/service/project-type.service.ts
Normal file
24
src/app/pages/service/project-type.service.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../environments/environments';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
export interface ProjectType {
|
||||||
|
id?: string; // UUID
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ProjectTypeService {
|
||||||
|
apiEndpoint: string = environment.apiBaseUrl + '/project-types';
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
getProjectTypes(): Observable<ProjectType[]> {
|
||||||
|
return this.http.get<ProjectType[]>(this.apiEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
src/app/pages/service/project.service.spec.ts
Normal file
16
src/app/pages/service/project.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProjectService } from './project.service';
|
||||||
|
|
||||||
|
describe('ProjectService', () => {
|
||||||
|
let service: ProjectService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ProjectService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
82
src/app/pages/service/project.service.ts
Normal file
82
src/app/pages/service/project.service.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { environment } from '../../../environments/environments';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Property } from '@/pages/service/property.service';
|
||||||
|
import { ProjectStatus } from '@/pages/service/project-status.service';
|
||||||
|
import { ProjectType } from '@/pages/service/project-type.service';
|
||||||
|
import { ProjectDetailsDTO } from '@/pages/project-details/project-details';
|
||||||
|
|
||||||
|
export interface Project {
|
||||||
|
id?: string; // UUID
|
||||||
|
name?: string;
|
||||||
|
eventNumber?: string;
|
||||||
|
description?: string;
|
||||||
|
projectTypeId?: string;
|
||||||
|
statusId?: string;
|
||||||
|
propertyId?: string;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
amountRequested?: number;
|
||||||
|
createdAt?: Date;
|
||||||
|
property?: Property;
|
||||||
|
status?: ProjectStatus;
|
||||||
|
projectType?: ProjectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectsStats {
|
||||||
|
projectsCountTotal?: number,
|
||||||
|
projectsCountActive?: number,
|
||||||
|
projectsCountCompleted?: number,
|
||||||
|
amountRequestedInTotal?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ProjectService {
|
||||||
|
apiEndpoint: string = environment.apiBaseUrl + '/projects';
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
getProjects(params?: any): Observable<Project[]> {
|
||||||
|
return this.http.get<Project[]>(this.apiEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProject(id: string): Observable<ProjectDetailsDTO> {
|
||||||
|
return this.http.get<ProjectDetailsDTO>(this.apiEndpoint + '/' + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStats(params?: any): Observable<ProjectsStats> {
|
||||||
|
return this.http.get<ProjectsStats>(this.apiEndpoint + '/stats');
|
||||||
|
}
|
||||||
|
|
||||||
|
getProjectInstance(
|
||||||
|
newProjectName?: string,
|
||||||
|
newProjectEventNumber?: string,
|
||||||
|
newProjectDescription?: string,
|
||||||
|
newProjectProjectTypeId?: string,
|
||||||
|
newProjectStatusId?: string,
|
||||||
|
newProjectPropertyId?: string,
|
||||||
|
newProjectStartDate?: Date,
|
||||||
|
newProjectEndDate?: Date,
|
||||||
|
newProjectAmountRequested?: number
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
name: newProjectName,
|
||||||
|
eventNumber: newProjectEventNumber,
|
||||||
|
description: newProjectDescription,
|
||||||
|
projectTypeId: newProjectProjectTypeId,
|
||||||
|
statusId: newProjectStatusId,
|
||||||
|
propertyId: newProjectPropertyId,
|
||||||
|
startDate: newProjectStartDate,
|
||||||
|
endDate: newProjectEndDate,
|
||||||
|
amountRequested: newProjectAmountRequested
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
create(project: Project): Observable<Project> {
|
||||||
|
return this.http.post<Project>(this.apiEndpoint, project);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/app/pages/service/property.service.spec.ts
Normal file
16
src/app/pages/service/property.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PropertyService } from './property.service';
|
||||||
|
|
||||||
|
describe('PropertyService', () => {
|
||||||
|
let service: PropertyService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(PropertyService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
254
src/app/pages/service/property.service.ts
Normal file
254
src/app/pages/service/property.service.ts
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { EMPTY, Observable } from 'rxjs';
|
||||||
|
import { Attachment } from '@/pages/service/attachment.service';
|
||||||
|
import { environment } from '../../../environments/environments';
|
||||||
|
|
||||||
|
export interface BulkDeletePropertyIds {
|
||||||
|
ids: String[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Country {
|
||||||
|
name?: string;
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Property {
|
||||||
|
// Liegenschaft
|
||||||
|
// - ID
|
||||||
|
id?: string ; // UUID
|
||||||
|
|
||||||
|
// - Name / Bezeichnung (z. B. „Wohnanlage Musterstraße“)
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
// - Adresse (Straße, PLZ, Ort, Land)
|
||||||
|
street?: string;
|
||||||
|
houseNumber?: string;
|
||||||
|
zipCode?: string;
|
||||||
|
city?: string;
|
||||||
|
country?: string;
|
||||||
|
|
||||||
|
// - Grundstücksnummer / Flurstück
|
||||||
|
landParcel?: string;
|
||||||
|
|
||||||
|
// - Baujahr
|
||||||
|
dateOfConstruction?: Date;
|
||||||
|
|
||||||
|
// - Grundstücksfläche
|
||||||
|
propertyArea?: number;
|
||||||
|
|
||||||
|
// - Eigentümer (Verknüpfung zur Person)
|
||||||
|
ownerId?: string; // UUID -> Eigentümer
|
||||||
|
|
||||||
|
// - Verwaltungsbeginn (seit wann wird es verwaltet)
|
||||||
|
startOfAdministration?: Date;
|
||||||
|
|
||||||
|
// - Notizen / Besonderheiten
|
||||||
|
notes?: string;
|
||||||
|
|
||||||
|
attachments?: Attachment[];
|
||||||
|
|
||||||
|
// - Dokumente (Grundbuchauszug, Lagepläne, etc.)
|
||||||
|
// siehe PropertyDocuments
|
||||||
|
|
||||||
|
// created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
createdAt?: Date;
|
||||||
|
|
||||||
|
// updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
updatedAt?: Date;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PropertyDocuments {
|
||||||
|
id: string; // UUID
|
||||||
|
name: string; // filename
|
||||||
|
propertyId: string; // UUID: FK -> property.id
|
||||||
|
pathInStorage: string; // Unique path in storage
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Building {
|
||||||
|
// id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
id: string; // UUID
|
||||||
|
|
||||||
|
// liegenschaft_id BIGINT NOT NULL,
|
||||||
|
propertyId: string; // UUID: FK -> property.id
|
||||||
|
|
||||||
|
// bezeichnung VARCHAR(255),
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
// baujahr SMALLINT,
|
||||||
|
dateOfConstruction: Date;
|
||||||
|
|
||||||
|
// bauweise VARCHAR(100),
|
||||||
|
constructionMethod: string;
|
||||||
|
|
||||||
|
// etagen SMALLINT,
|
||||||
|
floors: number | undefined
|
||||||
|
|
||||||
|
// dachform VARCHAR(100),
|
||||||
|
roofShape: string;
|
||||||
|
|
||||||
|
// heizungssystem VARCHAR(100),
|
||||||
|
heatingSystem: string;
|
||||||
|
|
||||||
|
// energieausweis VARCHAR(100), -- z.B. Dateiname oder Referenz
|
||||||
|
pathToEnergyCertificate: string;
|
||||||
|
|
||||||
|
// aufzug BOOLEAN DEFAULT FALSE,
|
||||||
|
elevator: boolean;
|
||||||
|
|
||||||
|
// bemerkungen TEXT,
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
// created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
// updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
// CONSTRAINT fk_gebaeude_liegenschaft FOREIGN KEY (liegenschaft_id)
|
||||||
|
// REFERENCES liegenschaft (id) ON DELETE CASCADE
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Contract {
|
||||||
|
id: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdministrativeUnit {
|
||||||
|
// id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
// gebaeude_id BIGINT NOT NULL,
|
||||||
|
buildingId: string; // UUID: FK -> building.id
|
||||||
|
|
||||||
|
// typ ENUM('Wohnung', 'Gewerbe', 'Stellplatz', 'Keller') NOT NULL,
|
||||||
|
|
||||||
|
// nummer VARCHAR(50), -- z.B. "Whg 3.OG links"
|
||||||
|
number: string;
|
||||||
|
|
||||||
|
// bezeichnung VARCHAR(255),
|
||||||
|
desription: string;
|
||||||
|
|
||||||
|
// flaeche DECIMAL(10,2),
|
||||||
|
area: number;
|
||||||
|
|
||||||
|
// zimmer SMALLINT,
|
||||||
|
rooms: number;
|
||||||
|
|
||||||
|
// ausstattung TEXT,
|
||||||
|
equipment: string;
|
||||||
|
|
||||||
|
// nutzung ENUM('vermietet', 'leerstehend', 'eigennutzung') DEFAULT 'leerstehend',
|
||||||
|
|
||||||
|
// mietstatus ENUM('Miete', 'Eigentum') DEFAULT 'Miete',
|
||||||
|
|
||||||
|
// gemeinschaftsanteil DECIMAL(5,2), -- z.B. % Anteil an NK
|
||||||
|
|
||||||
|
// aktueller_vertrag_id BIGINT, -- FK zu Vertrag (optional)
|
||||||
|
currentContract: string; // UUID: FK -> contract.id
|
||||||
|
|
||||||
|
// bemerkungen TEXT,
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
// created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
// updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
//
|
||||||
|
// CONSTRAINT fk_einheit_gebaeude FOREIGN KEY (gebaeude_id)
|
||||||
|
// REFERENCES gebaeude (id) ON DELETE CASCADE
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnergyCounter {
|
||||||
|
|
||||||
|
// id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
if: string; // UUID
|
||||||
|
|
||||||
|
// einheit_id BIGINT NOT NULL,
|
||||||
|
unitId: string; // UUID: FK -> administrative_unit.id
|
||||||
|
|
||||||
|
// typ ENUM('Strom', 'Wasser', 'Gas', 'Waerme') NOT NULL,
|
||||||
|
|
||||||
|
// zaehlernummer VARCHAR(100) NOT NULL,
|
||||||
|
counterIdentification: string;
|
||||||
|
|
||||||
|
// letzter_ablesewert DECIMAL(10,2),
|
||||||
|
lastReadValue: number;
|
||||||
|
|
||||||
|
// letztes_ablesedatum DATE,
|
||||||
|
lastReadDate: Date;
|
||||||
|
|
||||||
|
// bemerkungen TEXT,
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
// created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
// updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
//
|
||||||
|
// CONSTRAINT fk_zaehler_einheit FOREIGN KEY (einheit_id)
|
||||||
|
// REFERENCES einheit (id) ON DELETE CASCADE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class PropertyService {
|
||||||
|
apiEndpoint: string = environment.apiBaseUrl + '/properties';
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
getProperties(params?: any): Observable<Property[]> {
|
||||||
|
console.debug(this + " -- args: ", arguments);
|
||||||
|
|
||||||
|
return this.http.get<Property[]>(this.apiEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
createProperty(property: Property): Observable<Property> {
|
||||||
|
console.debug(this + " -- args: ", arguments);
|
||||||
|
|
||||||
|
return this.http.post<Property>(this.apiEndpoint, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProperty(property: Property): Observable<Property> {
|
||||||
|
console.debug(this + " -- args: ", arguments);
|
||||||
|
|
||||||
|
return this.http.patch<Property>(this.apiEndpoint + "/" + property.id, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProperty(property: Property): Observable<Property> {
|
||||||
|
console.debug(this + " -- args: ", arguments);
|
||||||
|
|
||||||
|
return this.http.delete<Property>(this.apiEndpoint + "/" + property.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProperties(properties: Property[]): Observable<Property> {
|
||||||
|
console.debug(this + " -- args: ", arguments);
|
||||||
|
|
||||||
|
if (!properties || properties.length === 0) {
|
||||||
|
return EMPTY; // Observable, which is "completed" immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids : BulkDeletePropertyIds = {
|
||||||
|
"ids": []
|
||||||
|
};
|
||||||
|
|
||||||
|
properties.forEach(property => {
|
||||||
|
if (property.id !== undefined) {
|
||||||
|
ids.ids.push(property.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.http.post<Property>(this.apiEndpoint + "/bulk-delete", ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
192
src/app/pages/uikit/buttondemo.ts
Normal file
192
src/app/pages/uikit/buttondemo.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { MenuItem } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { ButtonGroupModule } from 'primeng/buttongroup';
|
||||||
|
import { SplitButtonModule } from 'primeng/splitbutton';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-button-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [ButtonModule, ButtonGroupModule, SplitButtonModule],
|
||||||
|
template: `<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Default</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button label="Submit"></p-button>
|
||||||
|
<p-button label="Disabled" [disabled]="true"></p-button>
|
||||||
|
<p-button label="Link" class="p-button-link" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Severities</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button label="Primary" />
|
||||||
|
<p-button label="Secondary" severity="secondary" />
|
||||||
|
<p-button label="Success" severity="success" />
|
||||||
|
<p-button label="Info" severity="info" />
|
||||||
|
<p-button label="Warn" severity="warn" />
|
||||||
|
<p-button label="Help" severity="help" />
|
||||||
|
<p-button label="Danger" severity="danger" />
|
||||||
|
<p-button label="Contrast" severity="contrast" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Text</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button label="Primary" text />
|
||||||
|
<p-button label="Secondary" severity="secondary" text />
|
||||||
|
<p-button label="Success" severity="success" text />
|
||||||
|
<p-button label="Info" severity="info" text />
|
||||||
|
<p-button label="Warn" severity="warn" text />
|
||||||
|
<p-button label="Help" severity="help" text />
|
||||||
|
<p-button label="Danger" severity="danger" text />
|
||||||
|
<p-button label="Plain" text />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Outlined</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button label="Primary" outlined />
|
||||||
|
<p-button label="Secondary" severity="secondary" outlined />
|
||||||
|
<p-button label="Success" severity="success" outlined />
|
||||||
|
<p-button label="Info" severity="info" outlined />
|
||||||
|
<p-button label="warn" severity="warn" outlined />
|
||||||
|
<p-button label="Help" severity="help" outlined />
|
||||||
|
<p-button label="Danger" severity="danger" outlined />
|
||||||
|
<p-button label="Contrast" severity="contrast" outlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Group</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-buttongroup>
|
||||||
|
<p-button label="Save" icon="pi pi-check" />
|
||||||
|
<p-button label="Delete" icon="pi pi-trash" />
|
||||||
|
<p-button label="Cancel" icon="pi pi-times" />
|
||||||
|
</p-buttongroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">SplitButton</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-splitbutton label="Save" [model]="items"></p-splitbutton>
|
||||||
|
<p-splitbutton label="Save" [model]="items" severity="secondary"></p-splitbutton>
|
||||||
|
<p-splitbutton label="Save" [model]="items" severity="success"></p-splitbutton>
|
||||||
|
<p-splitbutton label="Save" [model]="items" severity="info"></p-splitbutton>
|
||||||
|
<p-splitbutton label="Save" [model]="items" severity="warn"></p-splitbutton>
|
||||||
|
<p-splitbutton label="Save" [model]="items" severity="help"></p-splitbutton>
|
||||||
|
<p-splitbutton label="Save" [model]="items" severity="danger"></p-splitbutton>
|
||||||
|
<p-splitbutton label="Save" [model]="items" severity="contrast"></p-splitbutton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Templating</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button type="button">
|
||||||
|
<img alt="logo" src="https://primefaces.org/cdn/primeng/images/logo.svg" style="width: 1.5rem" />
|
||||||
|
</p-button>
|
||||||
|
<p-button type="button" outlined severity="success">
|
||||||
|
<img alt="logo" src="https://primefaces.org/cdn/primeng/images/logo.svg" style="width: 1.5rem" />
|
||||||
|
<span class="text-bold">PrimeNG</span>
|
||||||
|
</p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Icons</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button icon="pi pi-bookmark"></p-button>
|
||||||
|
<p-button label="Bookmark" icon="pi pi-bookmark"></p-button>
|
||||||
|
<p-button label="Bookmark" icon="pi pi-bookmark" iconPos="right"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Raised</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button label="Primary" raised />
|
||||||
|
<p-button label="Secondary" severity="secondary" raised />
|
||||||
|
<p-button label="Success" severity="success" raised />
|
||||||
|
<p-button label="Info" severity="info" raised />
|
||||||
|
<p-button label="Warn" severity="warn" raised />
|
||||||
|
<p-button label="Help" severity="help" raised />
|
||||||
|
<p-button label="Danger" severity="danger" raised />
|
||||||
|
<p-button label="Contrast" severity="contrast" raised />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Rounded</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button label="Primary" rounded />
|
||||||
|
<p-button label="Secondary" severity="secondary" rounded />
|
||||||
|
<p-button label="Success" severity="success" rounded />
|
||||||
|
<p-button label="Info" severity="info" rounded />
|
||||||
|
<p-button label="Warn" severity="warn" rounded />
|
||||||
|
<p-button label="Help" severity="help" rounded />
|
||||||
|
<p-button label="Danger" severity="danger" rounded />
|
||||||
|
<p-button label="Contrast" severity="contrast" rounded />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Rounded Icons</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button icon="pi pi-check" rounded />
|
||||||
|
<p-button icon="pi pi-bookmark" severity="secondary" rounded />
|
||||||
|
<p-button icon="pi pi-search" severity="success" rounded />
|
||||||
|
<p-button icon="pi pi-user" severity="info" rounded />
|
||||||
|
<p-button icon="pi pi-bell" severity="warn" rounded />
|
||||||
|
<p-button icon="pi pi-heart" severity="help" rounded />
|
||||||
|
<p-button icon="pi pi-times" severity="danger" rounded />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Rounded Text</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button icon="pi pi-check" text raised rounded />
|
||||||
|
<p-button icon="pi pi-bookmark" severity="secondary" text raised rounded />
|
||||||
|
<p-button icon="pi pi-search" severity="success" text raised rounded />
|
||||||
|
<p-button icon="pi pi-user" severity="info" text raised rounded />
|
||||||
|
<p-button icon="pi pi-bell" severity="warn" text raised rounded />
|
||||||
|
<p-button icon="pi pi-heart" severity="help" text raised rounded />
|
||||||
|
<p-button icon="pi pi-times" severity="danger" text raised rounded />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Rounded Outlined</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button icon="pi pi-check" rounded outlined />
|
||||||
|
<p-button icon="pi pi-bookmark" severity="secondary" rounded outlined />
|
||||||
|
<p-button icon="pi pi-search" severity="success" rounded outlined />
|
||||||
|
<p-button icon="pi pi-user" severity="info" rounded outlined />
|
||||||
|
<p-button icon="pi pi-bell" severity="warn" rounded outlined />
|
||||||
|
<p-button icon="pi pi-heart" severity="help" rounded outlined />
|
||||||
|
<p-button icon="pi pi-times" severity="danger" rounded outlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Loading</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button type="button" label="Search" icon="pi pi-search" [loading]="loading[0]" (click)="load(0)" />
|
||||||
|
<p-button type="button" label="Search" icon="pi pi-search" iconPos="right" [loading]="loading[1]" (click)="load(1)" />
|
||||||
|
<p-button type="button" styleClass="h-full" icon="pi pi-search" [loading]="loading[2]" (click)="load(2)" />
|
||||||
|
<p-button type="button" label="Search" [loading]="loading[3]" (click)="load(3)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> `
|
||||||
|
})
|
||||||
|
export class ButtonDemo implements OnInit {
|
||||||
|
items: MenuItem[] = [];
|
||||||
|
|
||||||
|
loading = [false, false, false, false];
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.items = [{ label: 'Update', icon: 'pi pi-refresh' }, { label: 'Delete', icon: 'pi pi-times' }, { label: 'Angular.io', icon: 'pi pi-info', url: 'http://angular.io' }, { separator: true }, { label: 'Setup', icon: 'pi pi-cog' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
load(index: number) {
|
||||||
|
this.loading[index] = true;
|
||||||
|
setTimeout(() => (this.loading[index] = false), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
302
src/app/pages/uikit/chartdemo.ts
Normal file
302
src/app/pages/uikit/chartdemo.ts
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ChartModule } from 'primeng/chart';
|
||||||
|
import { FluidModule } from 'primeng/fluid';
|
||||||
|
import { debounceTime, Subscription } from 'rxjs';
|
||||||
|
import { LayoutService } from '../../layout/service/layout.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, ChartModule, FluidModule],
|
||||||
|
template: `
|
||||||
|
<p-fluid class="grid grid-cols-12 gap-8">
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Linear</div>
|
||||||
|
<p-chart type="line" [data]="lineData" [options]="lineOptions"></p-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Bar</div>
|
||||||
|
<p-chart type="bar" [data]="barData" [options]="barOptions"></p-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<div class="card flex flex-col items-center">
|
||||||
|
<div class="font-semibold text-xl mb-4">Pie</div>
|
||||||
|
<p-chart type="pie" [data]="pieData" [options]="pieOptions"></p-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<div class="card flex flex-col items-center">
|
||||||
|
<div class="font-semibold text-xl mb-4">Doughnut</div>
|
||||||
|
<p-chart type="doughnut" [data]="pieData" [options]="pieOptions"></p-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<div class="card flex flex-col items-center">
|
||||||
|
<div class="font-semibold text-xl mb-4">Polar Area</div>
|
||||||
|
<p-chart type="polarArea" [data]="polarData" [options]="polarOptions"></p-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 xl:col-span-6">
|
||||||
|
<div class="card flex flex-col items-center">
|
||||||
|
<div class="font-semibold text-xl mb-4">Radar</div>
|
||||||
|
<p-chart type="radar" [data]="radarData" [options]="radarOptions"></p-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p-fluid>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class ChartDemo {
|
||||||
|
lineData: any;
|
||||||
|
|
||||||
|
barData: any;
|
||||||
|
|
||||||
|
pieData: any;
|
||||||
|
|
||||||
|
polarData: any;
|
||||||
|
|
||||||
|
radarData: any;
|
||||||
|
|
||||||
|
lineOptions: any;
|
||||||
|
|
||||||
|
barOptions: any;
|
||||||
|
|
||||||
|
pieOptions: any;
|
||||||
|
|
||||||
|
polarOptions: any;
|
||||||
|
|
||||||
|
radarOptions: any;
|
||||||
|
|
||||||
|
subscription: Subscription;
|
||||||
|
constructor(private layoutService: LayoutService) {
|
||||||
|
this.subscription = this.layoutService.configUpdate$.pipe(debounceTime(25)).subscribe(() => {
|
||||||
|
this.initCharts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.initCharts();
|
||||||
|
}
|
||||||
|
|
||||||
|
initCharts() {
|
||||||
|
const documentStyle = getComputedStyle(document.documentElement);
|
||||||
|
const textColor = documentStyle.getPropertyValue('--text-color');
|
||||||
|
const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary');
|
||||||
|
const surfaceBorder = documentStyle.getPropertyValue('--surface-border');
|
||||||
|
|
||||||
|
this.barData = {
|
||||||
|
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'My First dataset',
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-500'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-primary-500'),
|
||||||
|
data: [65, 59, 80, 81, 56, 55, 40]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'My Second dataset',
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
data: [28, 48, 40, 19, 86, 27, 90]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.barOptions = {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
aspectRatio: 0.8,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
color: textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
color: textColorSecondary,
|
||||||
|
font: {
|
||||||
|
weight: 500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false,
|
||||||
|
drawBorder: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
ticks: {
|
||||||
|
color: textColorSecondary
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: surfaceBorder,
|
||||||
|
drawBorder: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pieData = {
|
||||||
|
labels: ['A', 'B', 'C'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [540, 325, 702],
|
||||||
|
backgroundColor: [documentStyle.getPropertyValue('--p-indigo-500'), documentStyle.getPropertyValue('--p-purple-500'), documentStyle.getPropertyValue('--p-teal-500')],
|
||||||
|
hoverBackgroundColor: [documentStyle.getPropertyValue('--p-indigo-400'), documentStyle.getPropertyValue('--p-purple-400'), documentStyle.getPropertyValue('--p-teal-400')]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pieOptions = {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
usePointStyle: true,
|
||||||
|
color: textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.lineData = {
|
||||||
|
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'First Dataset',
|
||||||
|
data: [65, 59, 80, 81, 56, 55, 40],
|
||||||
|
fill: false,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-500'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-primary-500'),
|
||||||
|
tension: 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Second Dataset',
|
||||||
|
data: [28, 48, 40, 19, 86, 27, 90],
|
||||||
|
fill: false,
|
||||||
|
backgroundColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-primary-200'),
|
||||||
|
tension: 0.4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.lineOptions = {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
aspectRatio: 0.8,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
color: textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
color: textColorSecondary
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: surfaceBorder,
|
||||||
|
drawBorder: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
ticks: {
|
||||||
|
color: textColorSecondary
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: surfaceBorder,
|
||||||
|
drawBorder: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.polarData = {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [11, 16, 7, 3],
|
||||||
|
backgroundColor: [documentStyle.getPropertyValue('--p-indigo-500'), documentStyle.getPropertyValue('--p-purple-500'), documentStyle.getPropertyValue('--p-teal-500'), documentStyle.getPropertyValue('--p-orange-500')],
|
||||||
|
label: 'My dataset'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
labels: ['Indigo', 'Purple', 'Teal', 'Orange']
|
||||||
|
};
|
||||||
|
|
||||||
|
this.polarOptions = {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
color: textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
r: {
|
||||||
|
grid: {
|
||||||
|
color: surfaceBorder,
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
display: false,
|
||||||
|
color: textColorSecondary
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.radarData = {
|
||||||
|
labels: ['Eating', 'Drinking', 'Sleeping', 'Designing', 'Coding', 'Cycling', 'Running'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'My First dataset',
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-indigo-400'),
|
||||||
|
pointBackgroundColor: documentStyle.getPropertyValue('--p-indigo-400'),
|
||||||
|
pointBorderColor: documentStyle.getPropertyValue('--p-indigo-400'),
|
||||||
|
pointHoverBackgroundColor: textColor,
|
||||||
|
pointHoverBorderColor: documentStyle.getPropertyValue('--p-indigo-400'),
|
||||||
|
data: [65, 59, 90, 81, 56, 55, 40]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'My Second dataset',
|
||||||
|
borderColor: documentStyle.getPropertyValue('--p-purple-400'),
|
||||||
|
pointBackgroundColor: documentStyle.getPropertyValue('--p-purple-400'),
|
||||||
|
pointBorderColor: documentStyle.getPropertyValue('--p-purple-400'),
|
||||||
|
pointHoverBackgroundColor: textColor,
|
||||||
|
pointHoverBorderColor: documentStyle.getPropertyValue('--p-purple-400'),
|
||||||
|
data: [28, 48, 40, 19, 96, 27, 100]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.radarOptions = {
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
color: textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
r: {
|
||||||
|
pointLabels: {
|
||||||
|
color: textColor
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: surfaceBorder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/app/pages/uikit/filedemo.ts
Normal file
52
src/app/pages/uikit/filedemo.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MessageService } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { FileUploadModule } from 'primeng/fileupload';
|
||||||
|
import { ToastModule } from 'primeng/toast';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-file-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, FileUploadModule, ToastModule, ButtonModule],
|
||||||
|
template: `<p-toast />
|
||||||
|
<div class="grid grid-cols-12 gap-8">
|
||||||
|
<div class="col-span-full lg:col-span-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Advanced</div>
|
||||||
|
<p-fileupload name="demo[]" (onUpload)="onUpload($event)" [multiple]="true" accept="image/*" maxFileSize="1000000" mode="advanced" url="https://www.primefaces.org/cdn/api/upload.php">
|
||||||
|
<ng-template #empty>
|
||||||
|
<div>Drag and drop files to here to upload.</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-fileupload>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-full lg:col-span-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Basic</div>
|
||||||
|
<div class="flex flex-col gap-4 items-center justify-center">
|
||||||
|
<p-fileupload #fu mode="basic" chooseLabel="Choose" chooseIcon="pi pi-upload" name="demo[]" url="https://www.primefaces.org/cdn/api/upload.php" accept="image/*" maxFileSize="1000000" (onUpload)="onUpload($event)" />
|
||||||
|
<p-button label="Upload" (onClick)="fu.upload()" severity="secondary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
providers: [MessageService]
|
||||||
|
})
|
||||||
|
export class FileDemo {
|
||||||
|
uploadedFiles: any[] = [];
|
||||||
|
|
||||||
|
constructor(private messageService: MessageService) {}
|
||||||
|
|
||||||
|
onUpload(event: any) {
|
||||||
|
for (const file of event.files) {
|
||||||
|
this.uploadedFiles.push(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Success', detail: 'File Uploaded' });
|
||||||
|
}
|
||||||
|
|
||||||
|
onBasicUpload() {
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Success', detail: 'File Uploaded with Basic Mode' });
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/app/pages/uikit/formlayoutdemo.ts
Normal file
129
src/app/pages/uikit/formlayoutdemo.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FluidModule } from 'primeng/fluid';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { SelectModule } from 'primeng/select';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { TextareaModule } from 'primeng/textarea';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-formlayout-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [InputTextModule, FluidModule, ButtonModule, SelectModule, FormsModule, TextareaModule],
|
||||||
|
template: `<p-fluid>
|
||||||
|
<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Vertical</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="name1">Name</label>
|
||||||
|
<input pInputText id="name1" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="email1">Email</label>
|
||||||
|
<input pInputText id="email1" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="age1">Age</label>
|
||||||
|
<input pInputText id="age1" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Vertical Grid</div>
|
||||||
|
<div class="flex flex-wrap gap-6">
|
||||||
|
<div class="flex flex-col grow basis-0 gap-2">
|
||||||
|
<label for="name2">Name</label>
|
||||||
|
<input pInputText id="name2" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col grow basis-0 gap-2">
|
||||||
|
<label for="email2">Email</label>
|
||||||
|
<input pInputText id="email2" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Horizontal</div>
|
||||||
|
<div class="grid grid-cols-12 gap-4 grid-cols-12 gap-2">
|
||||||
|
<label for="name3" class="flex items-center col-span-12 mb-2 md:col-span-2 md:mb-0">Name</label>
|
||||||
|
<div class="col-span-12 md:col-span-10">
|
||||||
|
<input pInputText id="name3" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-12 gap-4 grid-cols-12 gap-2">
|
||||||
|
<label for="email3" class="flex items-center col-span-12 mb-2 md:col-span-2 md:mb-0">Email</label>
|
||||||
|
<div class="col-span-12 md:col-span-10">
|
||||||
|
<input pInputText id="email3" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Inline</div>
|
||||||
|
<div class="flex flex-wrap items-start gap-6">
|
||||||
|
<div class="field">
|
||||||
|
<label for="firstname1" class="sr-only">Firstname</label>
|
||||||
|
<input pInputText id="firstname1" type="text" placeholder="Firstname" />
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="lastname1" class="sr-only">Lastname</label>
|
||||||
|
<input pInputText id="lastname1" type="text" placeholder="Lastname" />
|
||||||
|
</div>
|
||||||
|
<p-button label="Submit" [fluid]="false"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Help Text</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input pInputText id="username" type="text" />
|
||||||
|
<small>Enter your username to reset your password.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex mt-8">
|
||||||
|
<div class="card flex flex-col gap-6 w-full">
|
||||||
|
<div class="font-semibold text-xl">Advanced</div>
|
||||||
|
<div class="flex flex-col md:flex-row gap-6">
|
||||||
|
<div class="flex flex-wrap gap-2 w-full">
|
||||||
|
<label for="firstname2">Firstname</label>
|
||||||
|
<input pInputText id="firstname2" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2 w-full">
|
||||||
|
<label for="lastname2">Lastname</label>
|
||||||
|
<input pInputText id="lastname2" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<label for="address">Address</label>
|
||||||
|
<textarea pTextarea id="address" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-6">
|
||||||
|
<div class="flex flex-wrap gap-2 w-full">
|
||||||
|
<label for="state">State</label>
|
||||||
|
<p-select id="state" [(ngModel)]="dropdownItem" [options]="dropdownItems" optionLabel="name" placeholder="Select One" class="w-full"></p-select>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2 w-full">
|
||||||
|
<label for="zip">Zip</label>
|
||||||
|
<input pInputText id="zip" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p-fluid>`
|
||||||
|
})
|
||||||
|
export class FormLayoutDemo {
|
||||||
|
dropdownItems = [
|
||||||
|
{ name: 'Option 1', code: 'Option 1' },
|
||||||
|
{ name: 'Option 2', code: 'Option 2' },
|
||||||
|
{ name: 'Option 3', code: 'Option 3' }
|
||||||
|
];
|
||||||
|
|
||||||
|
dropdownItem = null;
|
||||||
|
}
|
||||||
339
src/app/pages/uikit/inputdemo.ts
Normal file
339
src/app/pages/uikit/inputdemo.ts
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
import { Component, inject, OnInit } from '@angular/core';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
|
import { RadioButtonModule } from 'primeng/radiobutton';
|
||||||
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
|
import { InputGroupModule } from 'primeng/inputgroup';
|
||||||
|
import { FluidModule } from 'primeng/fluid';
|
||||||
|
import { IconFieldModule } from 'primeng/iconfield';
|
||||||
|
import { InputIconModule } from 'primeng/inputicon';
|
||||||
|
import { FloatLabelModule } from 'primeng/floatlabel';
|
||||||
|
import { AutoCompleteCompleteEvent, AutoCompleteModule } from 'primeng/autocomplete';
|
||||||
|
import { InputNumberModule } from 'primeng/inputnumber';
|
||||||
|
import { SliderModule } from 'primeng/slider';
|
||||||
|
import { RatingModule } from 'primeng/rating';
|
||||||
|
import { ColorPickerModule } from 'primeng/colorpicker';
|
||||||
|
import { KnobModule } from 'primeng/knob';
|
||||||
|
import { SelectModule } from 'primeng/select';
|
||||||
|
import { DatePickerModule } from 'primeng/datepicker';
|
||||||
|
import { ToggleSwitchModule } from 'primeng/toggleswitch';
|
||||||
|
import { TreeSelectModule } from 'primeng/treeselect';
|
||||||
|
import { MultiSelectModule } from 'primeng/multiselect';
|
||||||
|
import { ListboxModule } from 'primeng/listbox';
|
||||||
|
import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
|
||||||
|
import { TextareaModule } from 'primeng/textarea';
|
||||||
|
import { ToggleButtonModule } from 'primeng/togglebutton';
|
||||||
|
import { CountryService } from '../service/country.service';
|
||||||
|
import { NodeService } from '../service/node.service';
|
||||||
|
import { TreeNode } from 'primeng/api';
|
||||||
|
import { Country } from '@/pages/service/property.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-input-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
InputTextModule,
|
||||||
|
ButtonModule,
|
||||||
|
CheckboxModule,
|
||||||
|
RadioButtonModule,
|
||||||
|
SelectButtonModule,
|
||||||
|
InputGroupModule,
|
||||||
|
FluidModule,
|
||||||
|
IconFieldModule,
|
||||||
|
InputIconModule,
|
||||||
|
FloatLabelModule,
|
||||||
|
AutoCompleteModule,
|
||||||
|
InputNumberModule,
|
||||||
|
SliderModule,
|
||||||
|
RatingModule,
|
||||||
|
ColorPickerModule,
|
||||||
|
KnobModule,
|
||||||
|
SelectModule,
|
||||||
|
DatePickerModule,
|
||||||
|
ToggleButtonModule,
|
||||||
|
ToggleSwitchModule,
|
||||||
|
TreeSelectModule,
|
||||||
|
MultiSelectModule,
|
||||||
|
ListboxModule,
|
||||||
|
InputGroupAddonModule,
|
||||||
|
TextareaModule
|
||||||
|
],
|
||||||
|
template: ` <p-fluid class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">InputText</div>
|
||||||
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
|
<input pInputText type="text" placeholder="Default" />
|
||||||
|
<input pInputText type="text" placeholder="Disabled" [disabled]="true" />
|
||||||
|
<input pInputText type="text" placeholder="Invalid" class="ng-dirty ng-invalid" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">Icons</div>
|
||||||
|
<p-iconfield>
|
||||||
|
<p-inputicon class="pi pi-user" />
|
||||||
|
<input pInputText type="text" placeholder="Username" />
|
||||||
|
</p-iconfield>
|
||||||
|
<p-iconfield iconPosition="left">
|
||||||
|
<input pInputText type="text" placeholder="Search" />
|
||||||
|
<p-inputicon class="pi pi-search" />
|
||||||
|
</p-iconfield>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">Float Label</div>
|
||||||
|
<p-floatlabel>
|
||||||
|
<input pInputText id="username" type="text" [(ngModel)]="floatValue" />
|
||||||
|
<label for="username">Username</label>
|
||||||
|
</p-floatlabel>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">Textarea</div>
|
||||||
|
<textarea pTextarea placeholder="Your Message" [autoResize]="true" rows="3" cols="30"></textarea>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">AutoComplete</div>
|
||||||
|
<p-autocomplete [(ngModel)]="selectedAutoValue" [suggestions]="autoFilteredValue" optionLabel="name" placeholder="Search" dropdown multiple display="chip" (completeMethod)="filterCountry($event)" />
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">DatePicker</div>
|
||||||
|
<p-datepicker [showIcon]="true" [showButtonBar]="true" [(ngModel)]="calendarValue"></p-datepicker>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">InputNumber</div>
|
||||||
|
<p-inputnumber [(ngModel)]="inputNumberValue" showButtons mode="decimal"></p-inputnumber>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Slider</div>
|
||||||
|
<input pInputText [(ngModel)]="sliderValue" type="number" />
|
||||||
|
<p-slider [(ngModel)]="sliderValue" />
|
||||||
|
|
||||||
|
<div class="flex flex-row mt-6">
|
||||||
|
<div class="flex flex-col gap-4 w-1/2">
|
||||||
|
<div class="font-semibold text-xl">Rating</div>
|
||||||
|
<p-rating [(ngModel)]="ratingValue" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-4 w-1/2">
|
||||||
|
<div class="font-semibold text-xl">ColorPicker</div>
|
||||||
|
<p-colorpicker [style]="{ width: '2rem' }" [(ngModel)]="colorValue" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">Knob</div>
|
||||||
|
<p-knob [(ngModel)]="knobValue" [step]="10" [min]="-50" [max]="50" valueTemplate="{value}%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">RadioButton</div>
|
||||||
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p-radiobutton id="option1" name="option" value="Chicago" [(ngModel)]="radioValue" />
|
||||||
|
<label for="option1" class="leading-none ml-2">Chicago</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p-radiobutton id="option2" name="option" value="Los Angeles" [(ngModel)]="radioValue" />
|
||||||
|
<label for="option2" class="leading-none ml-2">Los Angeles</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p-radiobutton id="option3" name="option" value="New York" [(ngModel)]="radioValue" />
|
||||||
|
<label for="option3" class="leading-none ml-2">New York</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">Checkbox</div>
|
||||||
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p-checkbox id="checkOption1" name="option" value="Chicago" [(ngModel)]="checkboxValue" />
|
||||||
|
<label for="checkOption1" class="ml-2">Chicago</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p-checkbox id="checkOption2" name="option" value="Los Angeles" [(ngModel)]="checkboxValue" />
|
||||||
|
<label for="checkOption2" class="ml-2">Los Angeles</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p-checkbox id="checkOption3" name="option" value="New York" [(ngModel)]="checkboxValue" />
|
||||||
|
<label for="checkOption3" class="ml-2">New York</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">ToggleSwitch</div>
|
||||||
|
<p-toggleswitch [(ngModel)]="switchValue" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">Listbox</div>
|
||||||
|
<p-listbox [(ngModel)]="listboxValue" [options]="listboxValues" optionLabel="name" [filter]="true" />
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">Select</div>
|
||||||
|
<p-select [(ngModel)]="dropdownValue" [options]="dropdownValues" optionLabel="name" placeholder="Select" />
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">MultiSelect</div>
|
||||||
|
<p-multiselect [options]="multiselectCountries" [(ngModel)]="multiselectSelectedCountries" placeholder="Select Countries" optionLabel="name" display="chip" [filter]="true">
|
||||||
|
<ng-template #selecteditems let-countries>
|
||||||
|
@for (country of countries; track country.code) {
|
||||||
|
<div class="inline-flex items-center py-1 px-2 bg-primary text-primary-contrast rounded-border mr-2">
|
||||||
|
<span [class]="'mr-2 flag flag-' + country.code.toLowerCase()" style="width: 18px; height: 12px"></span>
|
||||||
|
<div>{{ country.name }}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #item let-country>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span [class]="'mr-2 flag flag-' + country.code.toLowerCase()" style="width: 18px; height: 12px"></span>
|
||||||
|
<div>{{ country.name }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-multiselect>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">TreeSelect</div>
|
||||||
|
<p-treeselect [(ngModel)]="selectedNode" [options]="treeSelectNodes" placeholder="Select Item"></p-treeselect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card flex flex-col gap-4">
|
||||||
|
<div class="font-semibold text-xl">ToggleButton</div>
|
||||||
|
<p-togglebutton [(ngModel)]="toggleValue" onLabel="Yes" offLabel="No" [style]="{ width: '10em' }" />
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl">SelectButton</div>
|
||||||
|
<p-selectbutton [(ngModel)]="selectButtonValue" [options]="selectButtonValues" optionLabel="name" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p-fluid>
|
||||||
|
|
||||||
|
<p-fluid class="flex mt-8">
|
||||||
|
<div class="card flex flex-col gap-6 w-full">
|
||||||
|
<div class="font-semibold text-xl">InputGroup</div>
|
||||||
|
<div class="flex flex-col md:flex-row gap-6">
|
||||||
|
<p-inputgroup>
|
||||||
|
<p-inputgroup-addon>
|
||||||
|
<i class="pi pi-user"></i>
|
||||||
|
</p-inputgroup-addon>
|
||||||
|
<input pInputText placeholder="Username" />
|
||||||
|
</p-inputgroup>
|
||||||
|
<p-inputgroup>
|
||||||
|
<p-inputgroup-addon>
|
||||||
|
<i class="pi pi-clock"></i>
|
||||||
|
</p-inputgroup-addon>
|
||||||
|
<p-inputgroup-addon>
|
||||||
|
<i class="pi pi-star-fill"></i>
|
||||||
|
</p-inputgroup-addon>
|
||||||
|
<p-inputnumber placeholder="Price" />
|
||||||
|
<p-inputgroup-addon>$</p-inputgroup-addon>
|
||||||
|
<p-inputgroup-addon>.00</p-inputgroup-addon>
|
||||||
|
</p-inputgroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col md:flex-row gap-6">
|
||||||
|
<p-inputgroup>
|
||||||
|
<p-button label="Search" />
|
||||||
|
<input pInputText placeholder="Keyword" />
|
||||||
|
</p-inputgroup>
|
||||||
|
<p-inputgroup>
|
||||||
|
<p-inputgroup-addon>
|
||||||
|
<p-checkbox [(ngModel)]="inputGroupValue" [binary]="true" />
|
||||||
|
</p-inputgroup-addon>
|
||||||
|
<input pInputText placeholder="Confirm" />
|
||||||
|
</p-inputgroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p-fluid>`,
|
||||||
|
providers: [CountryService, NodeService]
|
||||||
|
})
|
||||||
|
export class InputDemo implements OnInit {
|
||||||
|
floatValue: any = null;
|
||||||
|
|
||||||
|
autoValue: any[] | undefined;
|
||||||
|
|
||||||
|
autoFilteredValue: any[] = [];
|
||||||
|
|
||||||
|
selectedAutoValue: any = null;
|
||||||
|
|
||||||
|
calendarValue: any = null;
|
||||||
|
|
||||||
|
inputNumberValue: any = null;
|
||||||
|
|
||||||
|
sliderValue: number = 50;
|
||||||
|
|
||||||
|
ratingValue: any = null;
|
||||||
|
|
||||||
|
colorValue: string = '#1976D2';
|
||||||
|
|
||||||
|
radioValue: any = null;
|
||||||
|
|
||||||
|
checkboxValue: any[] = [];
|
||||||
|
|
||||||
|
switchValue: boolean = false;
|
||||||
|
|
||||||
|
listboxValues: any[] = [
|
||||||
|
{ name: 'New York', code: 'NY' },
|
||||||
|
{ name: 'Rome', code: 'RM' },
|
||||||
|
{ name: 'London', code: 'LDN' },
|
||||||
|
{ name: 'Istanbul', code: 'IST' },
|
||||||
|
{ name: 'Paris', code: 'PRS' }
|
||||||
|
];
|
||||||
|
|
||||||
|
listboxValue: any = null;
|
||||||
|
|
||||||
|
dropdownValues = [
|
||||||
|
{ name: 'New York', code: 'NY' },
|
||||||
|
{ name: 'Rome', code: 'RM' },
|
||||||
|
{ name: 'London', code: 'LDN' },
|
||||||
|
{ name: 'Istanbul', code: 'IST' },
|
||||||
|
{ name: 'Paris', code: 'PRS' }
|
||||||
|
];
|
||||||
|
|
||||||
|
dropdownValue: any = null;
|
||||||
|
|
||||||
|
multiselectCountries: Country[] = [
|
||||||
|
{ name: 'Australia', code: 'AU' },
|
||||||
|
{ name: 'Brazil', code: 'BR' },
|
||||||
|
{ name: 'China', code: 'CN' },
|
||||||
|
{ name: 'Egypt', code: 'EG' },
|
||||||
|
{ name: 'France', code: 'FR' },
|
||||||
|
{ name: 'Germany', code: 'DE' },
|
||||||
|
{ name: 'India', code: 'IN' },
|
||||||
|
{ name: 'Japan', code: 'JP' },
|
||||||
|
{ name: 'Spain', code: 'ES' },
|
||||||
|
{ name: 'United States', code: 'US' }
|
||||||
|
];
|
||||||
|
|
||||||
|
multiselectSelectedCountries!: Country[];
|
||||||
|
|
||||||
|
toggleValue: boolean = false;
|
||||||
|
|
||||||
|
selectButtonValue: any = null;
|
||||||
|
|
||||||
|
selectButtonValues: any = [{ name: 'Option 1' }, { name: 'Option 2' }, { name: 'Option 3' }];
|
||||||
|
|
||||||
|
knobValue: number = 50;
|
||||||
|
|
||||||
|
inputGroupValue: boolean = false;
|
||||||
|
|
||||||
|
treeSelectNodes!: TreeNode[];
|
||||||
|
|
||||||
|
selectedNode: any = null;
|
||||||
|
|
||||||
|
countryService = inject(CountryService);
|
||||||
|
|
||||||
|
nodeService = inject(NodeService);
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.countryService.getCountries().then((countries) => {
|
||||||
|
this.autoValue = countries;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.nodeService.getFiles().then((data) => (this.treeSelectNodes = data));
|
||||||
|
}
|
||||||
|
|
||||||
|
filterCountry(event: AutoCompleteCompleteEvent) {
|
||||||
|
const filtered: any[] = [];
|
||||||
|
const query = event.query;
|
||||||
|
|
||||||
|
for (let i = 0; i < (this.autoValue as any[]).length; i++) {
|
||||||
|
const country = (this.autoValue as any[])[i];
|
||||||
|
if (country.name.toLowerCase().indexOf(query.toLowerCase()) == 0) {
|
||||||
|
filtered.push(country);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.autoFilteredValue = filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
217
src/app/pages/uikit/listdemo.ts
Normal file
217
src/app/pages/uikit/listdemo.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DataViewModule } from 'primeng/dataview';
|
||||||
|
import { OrderListModule } from 'primeng/orderlist';
|
||||||
|
import { PickListModule } from 'primeng/picklist';
|
||||||
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
|
import { TagModule } from 'primeng/tag';
|
||||||
|
import { Property, ProductService } from '../service/product.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-list-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, DataViewModule, FormsModule, SelectButtonModule, PickListModule, OrderListModule, TagModule, ButtonModule],
|
||||||
|
template: ` <div class="flex flex-col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl">DataView</div>
|
||||||
|
<p-dataview [value]="products" [layout]="layout">
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<p-select-button [(ngModel)]="layout" [options]="options" [allowEmpty]="false">
|
||||||
|
<ng-template #item let-option>
|
||||||
|
<i class="pi " [ngClass]="{ 'pi-bars': option === 'list', 'pi-table': option === 'grid' }"></i>
|
||||||
|
</ng-template>
|
||||||
|
</p-select-button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #list let-items>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div *ngFor="let item of items; let i = index">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center p-6 gap-4" [ngClass]="{ 'border-t border-surface': i !== 0 }">
|
||||||
|
<div class="md:w-40 relative">
|
||||||
|
<img class="block xl:block mx-auto rounded w-full" src="https://primefaces.org/cdn/primevue/images/product/{{ item.image }}" [alt]="item.name" />
|
||||||
|
<div class="absolute bg-black/70 rounded-border" [style]="{ left: '4px', top: '4px' }">
|
||||||
|
<p-tag [value]="item.inventoryStatus" [severity]="getSeverity(item)"></p-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col md:flex-row justify-between md:items-center flex-1 gap-6">
|
||||||
|
<div class="flex flex-row md:flex-col justify-between items-start gap-2">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ item.category }}</span>
|
||||||
|
<div class="text-lg font-medium mt-2">{{ item.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-surface-100 p-1" style="border-radius: 30px">
|
||||||
|
<div
|
||||||
|
class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2"
|
||||||
|
style="
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow:
|
||||||
|
0px 1px 2px 0px rgba(0, 0, 0, 0.04),
|
||||||
|
0px 1px 2px 0px rgba(0, 0, 0, 0.06);
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span class="text-surface-900 font-medium text-sm">{{ item.rating }}</span>
|
||||||
|
<i class="pi pi-star-fill text-yellow-500"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col md:items-end gap-8">
|
||||||
|
<span class="text-xl font-semibold">$ {{ item.price }}</span>
|
||||||
|
<div class="flex flex-row-reverse md:flex-row gap-2">
|
||||||
|
<p-button icon="pi pi-heart" styleClass="h-full" [outlined]="true"></p-button>
|
||||||
|
<p-button icon="pi pi-shopping-cart" label="Buy Now" [disabled]="item.inventoryStatus === 'OUTOFSTOCK'" styleClass="flex-auto md:flex-initial whitespace-nowrap"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #grid let-items>
|
||||||
|
<div class="grid grid-cols-12 gap-4">
|
||||||
|
<div *ngFor="let item of items; let i = index" class="col-span-12 sm:col-span-6 lg:col-span-4 p-2">
|
||||||
|
<div class="p-6 border border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 rounded flex flex-col">
|
||||||
|
<div class="relative w-full shadow-sm">
|
||||||
|
<img class="rounded w-full" src="https://primefaces.org/cdn/primevue/images/product/{{ item.image }}" [alt]="item.name" />
|
||||||
|
<div
|
||||||
|
class="absolute bg-black/70 rounded-border"
|
||||||
|
[style]="{
|
||||||
|
left: '4px',
|
||||||
|
top: '4px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<p-tag [value]="item.inventoryStatus" [severity]="getSeverity(item)"></p-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-12">
|
||||||
|
<div class="flex flex-row justify-between items-start gap-2">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ item.category }}</span>
|
||||||
|
<div class="text-lg font-medium mt-1">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-surface-100 p-1" style="border-radius: 30px">
|
||||||
|
<div
|
||||||
|
class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2"
|
||||||
|
style="
|
||||||
|
border-radius: 30px;
|
||||||
|
box-shadow:
|
||||||
|
0px 1px 2px 0px rgba(0, 0, 0, 0.04),
|
||||||
|
0px 1px 2px 0px rgba(0, 0, 0, 0.06);
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span class="text-surface-900 font-medium text-xs">{{ item.rating }}</span>
|
||||||
|
<i class="pi pi-star-fill text-yellow-500"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-6 mt-6">
|
||||||
|
<span class="text-2xl font-semibold">$ {{ item.price }}</span>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p-button icon="pi pi-shopping-cart" label="Buy Now" [disabled]="item.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" styleClass="w-full"></p-button>
|
||||||
|
<p-button icon="pi pi-heart" styleClass="h-full" [outlined]="true"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-dataview>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col lg:flex-row gap-20">
|
||||||
|
<div class="lg:w-2/3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">PickList</div>
|
||||||
|
<p-pick-list [source]="sourceCities" [target]="targetCities" breakpoint="1400px">
|
||||||
|
<ng-template #item let-item>
|
||||||
|
{{ item.name }}
|
||||||
|
</ng-template>
|
||||||
|
</p-pick-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lg:w-1/3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">OrderList</div>
|
||||||
|
<p-orderlist [value]="orderCities" dataKey="id" breakpoint="575px">
|
||||||
|
<ng-template #option let-option>
|
||||||
|
{{ option.name }}
|
||||||
|
</ng-template>
|
||||||
|
</p-orderlist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
styles: `
|
||||||
|
::ng-deep {
|
||||||
|
.p-orderlist-list-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
providers: [ProductService]
|
||||||
|
})
|
||||||
|
export class ListDemo {
|
||||||
|
layout: 'list' | 'grid' = 'list';
|
||||||
|
|
||||||
|
options = ['list', 'grid'];
|
||||||
|
|
||||||
|
products: Property[] = [];
|
||||||
|
|
||||||
|
sourceCities: any[] = [];
|
||||||
|
|
||||||
|
targetCities: any[] = [];
|
||||||
|
|
||||||
|
orderCities: any[] = [];
|
||||||
|
|
||||||
|
constructor(private productService: ProductService) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.productService.getProductsSmall().then((data) => (this.products = data.slice(0, 6)));
|
||||||
|
|
||||||
|
this.sourceCities = [
|
||||||
|
{ name: 'San Francisco', code: 'SF' },
|
||||||
|
{ name: 'London', code: 'LDN' },
|
||||||
|
{ name: 'Paris', code: 'PRS' },
|
||||||
|
{ name: 'Istanbul', code: 'IST' },
|
||||||
|
{ name: 'Berlin', code: 'BRL' },
|
||||||
|
{ name: 'Barcelona', code: 'BRC' },
|
||||||
|
{ name: 'Rome', code: 'RM' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.targetCities = [];
|
||||||
|
|
||||||
|
this.orderCities = [
|
||||||
|
{ name: 'San Francisco', code: 'SF' },
|
||||||
|
{ name: 'London', code: 'LDN' },
|
||||||
|
{ name: 'Paris', code: 'PRS' },
|
||||||
|
{ name: 'Istanbul', code: 'IST' },
|
||||||
|
{ name: 'Berlin', code: 'BRL' },
|
||||||
|
{ name: 'Barcelona', code: 'BRC' },
|
||||||
|
{ name: 'Rome', code: 'RM' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeverity(product: Property) {
|
||||||
|
switch (product.inventoryStatus) {
|
||||||
|
case 'INSTOCK':
|
||||||
|
return 'success';
|
||||||
|
|
||||||
|
case 'LOWSTOCK':
|
||||||
|
return 'warn';
|
||||||
|
|
||||||
|
case 'OUTOFSTOCK':
|
||||||
|
return 'danger';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/app/pages/uikit/mediademo.ts
Normal file
128
src/app/pages/uikit/mediademo.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CarouselModule } from 'primeng/carousel';
|
||||||
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
|
import { ImageModule } from 'primeng/image';
|
||||||
|
import { TagModule } from 'primeng/tag';
|
||||||
|
import { PhotoService } from '../service/photo.service';
|
||||||
|
import { Property, ProductService } from '../service/product.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-media-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, CarouselModule, ButtonModule, GalleriaModule, ImageModule, TagModule],
|
||||||
|
template: `<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Carousel</div>
|
||||||
|
<p-carousel [value]="products" [numVisible]="3" [numScroll]="3" [circular]="false" [responsiveOptions]="carouselResponsiveOptions">
|
||||||
|
<ng-template let-product #item>
|
||||||
|
<div class="border border-surface rounded-border m-2 p-4">
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="relative mx-auto">
|
||||||
|
<img src="https://primefaces.org/cdn/primeng/images/demo/product/{{ product.image }}" [alt]="product.name" class="w-full rounded-border" />
|
||||||
|
<div class="absolute bg-black/70 rounded-border" [ngStyle]="{ 'left.px': 5, 'top.px': 5 }">
|
||||||
|
<p-tag [value]="product.inventoryStatus" [severity]="getSeverity(product.inventoryStatus)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4 font-medium">{{ product.name }}</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="mt-0 font-semibold text-xl">{{ '$' + product.price }}</div>
|
||||||
|
<span>
|
||||||
|
<p-button icon="pi pi-heart" severity="secondary" [outlined]="true" />
|
||||||
|
<p-button icon="pi pi-shopping-cart" styleClass="ml-2" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-carousel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Image</div>
|
||||||
|
<p-image src="https://primefaces.org/cdn/primeng/images/galleria/galleria10.jpg" alt="Image" width="250" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Galleria</div>
|
||||||
|
<p-galleria [value]="images" [responsiveOptions]="galleriaResponsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5">
|
||||||
|
<ng-template #item let-item>
|
||||||
|
<img [src]="item.itemImageSrc" style="width:100%" />
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #thumbnail let-item>
|
||||||
|
<img [src]="item.thumbnailImageSrc" />
|
||||||
|
</ng-template>
|
||||||
|
</p-galleria>
|
||||||
|
</div>`,
|
||||||
|
providers: [ProductService, PhotoService]
|
||||||
|
})
|
||||||
|
export class MediaDemo implements OnInit {
|
||||||
|
products!: Property[];
|
||||||
|
|
||||||
|
images!: any[];
|
||||||
|
|
||||||
|
galleriaResponsiveOptions: any[] = [
|
||||||
|
{
|
||||||
|
breakpoint: '1024px',
|
||||||
|
numVisible: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '960px',
|
||||||
|
numVisible: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '768px',
|
||||||
|
numVisible: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '560px',
|
||||||
|
numVisible: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
carouselResponsiveOptions: any[] = [
|
||||||
|
{
|
||||||
|
breakpoint: '1024px',
|
||||||
|
numVisible: 3,
|
||||||
|
numScroll: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '768px',
|
||||||
|
numVisible: 2,
|
||||||
|
numScroll: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
breakpoint: '560px',
|
||||||
|
numVisible: 1,
|
||||||
|
numScroll: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private productService: ProductService,
|
||||||
|
private photoService: PhotoService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.productService.getProductsSmall().then((products) => {
|
||||||
|
this.products = products;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.photoService.getImages().then((images) => {
|
||||||
|
this.images = images;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeverity(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case 'INSTOCK':
|
||||||
|
return 'success';
|
||||||
|
case 'LOWSTOCK':
|
||||||
|
return 'warn';
|
||||||
|
case 'OUTOFSTOCK':
|
||||||
|
return 'danger';
|
||||||
|
default:
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
540
src/app/pages/uikit/menudemo.ts
Normal file
540
src/app/pages/uikit/menudemo.ts
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { BreadcrumbModule } from 'primeng/breadcrumb';
|
||||||
|
import { TieredMenuModule } from 'primeng/tieredmenu';
|
||||||
|
import { ContextMenuModule } from 'primeng/contextmenu';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { MenuModule } from 'primeng/menu';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { MegaMenuModule } from 'primeng/megamenu';
|
||||||
|
import { PanelMenuModule } from 'primeng/panelmenu';
|
||||||
|
import { TabsModule } from 'primeng/tabs';
|
||||||
|
import { MenubarModule } from 'primeng/menubar';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { StepperModule } from 'primeng/stepper';
|
||||||
|
import { IconField, IconFieldModule } from 'primeng/iconfield';
|
||||||
|
import { InputIcon, InputIconModule } from 'primeng/inputicon';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-menu-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
BreadcrumbModule,
|
||||||
|
TieredMenuModule,
|
||||||
|
IconFieldModule,
|
||||||
|
InputIconModule,
|
||||||
|
MenuModule,
|
||||||
|
ButtonModule,
|
||||||
|
ContextMenuModule,
|
||||||
|
MegaMenuModule,
|
||||||
|
PanelMenuModule,
|
||||||
|
TabsModule,
|
||||||
|
MenubarModule,
|
||||||
|
InputTextModule,
|
||||||
|
TabsModule,
|
||||||
|
StepperModule,
|
||||||
|
TabsModule,
|
||||||
|
IconField,
|
||||||
|
InputIcon
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Menubar</div>
|
||||||
|
<p-menubar [model]="nestedMenuItems">
|
||||||
|
<ng-template #end>
|
||||||
|
<p-iconfield>
|
||||||
|
<p-inputicon class="pi pi-search" />
|
||||||
|
<input type="text" pInputText placeholder="Search" />
|
||||||
|
</p-iconfield>
|
||||||
|
</ng-template>
|
||||||
|
</p-menubar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Breadcrumb</div>
|
||||||
|
<p-breadcrumb [model]="breadcrumbItems" [home]="breadcrumbHome"></p-breadcrumb>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Steps</div>
|
||||||
|
<p-stepper [value]="1">
|
||||||
|
<p-step-list>
|
||||||
|
<p-step [value]="1">Header I</p-step>
|
||||||
|
<p-step [value]="2">Header II</p-step>
|
||||||
|
<p-step [value]="3">Header III</p-step>
|
||||||
|
</p-step-list>
|
||||||
|
</p-stepper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">TabMenu</div>
|
||||||
|
<p-tabs [value]="0">
|
||||||
|
<p-tablist>
|
||||||
|
<p-tab [value]="0">Header I</p-tab>
|
||||||
|
<p-tab [value]="1">Header II</p-tab>
|
||||||
|
<p-tab [value]="2">Header III</p-tab>
|
||||||
|
</p-tablist>
|
||||||
|
</p-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-8 mt-6">
|
||||||
|
<div class="md:w-1/3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Tiered Menu</div>
|
||||||
|
<p-tieredmenu [model]="tieredMenuItems"></p-tieredmenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Plain Menu</div>
|
||||||
|
<p-menu [model]="menuItems"></p-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Overlay Menu</div>
|
||||||
|
<p-menu #menu [popup]="true" [model]="overlayMenuItems"></p-menu>
|
||||||
|
<button type="button" pButton icon="pi pi-chevron-down" label="Options" (click)="menu.toggle($event)" style="width:auto"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" #anchor>
|
||||||
|
<div class="font-semibold text-xl mb-4">Context Menu</div>
|
||||||
|
Right click to display.
|
||||||
|
<p-contextmenu [target]="anchor" [model]="contextMenuItems"></p-contextmenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-8 mt-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">MegaMenu | Horizontal</div>
|
||||||
|
<p-megamenu [model]="megaMenuItems" />
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl mb-4 mt-8">MegaMenu | Vertical</div>
|
||||||
|
<p-megamenu [model]="megaMenuItems" orientation="vertical" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">PanelMenu</div>
|
||||||
|
<p-panelmenu [model]="panelMenuItems" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class MenuDemo {
|
||||||
|
nestedMenuItems = [
|
||||||
|
{
|
||||||
|
label: 'Customers',
|
||||||
|
icon: 'pi pi-fw pi-table',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
icon: 'pi pi-fw pi-user-plus',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Customer',
|
||||||
|
icon: 'pi pi-fw pi-plus'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Duplicate',
|
||||||
|
icon: 'pi pi-fw pi-copy'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
icon: 'pi pi-fw pi-user-edit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Orders',
|
||||||
|
icon: 'pi pi-fw pi-shopping-cart',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
icon: 'pi pi-fw pi-list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search',
|
||||||
|
icon: 'pi pi-fw pi-search'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Shipments',
|
||||||
|
icon: 'pi pi-fw pi-envelope',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Tracker',
|
||||||
|
icon: 'pi pi-fw pi-compass'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Map',
|
||||||
|
icon: 'pi pi-fw pi-map-marker'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Manage',
|
||||||
|
icon: 'pi pi-fw pi-pencil'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Profile',
|
||||||
|
icon: 'pi pi-fw pi-user',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'pi pi-fw pi-cog'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billing',
|
||||||
|
icon: 'pi pi-fw pi-file'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
icon: 'pi pi-fw pi-sign-out'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
breadcrumbHome = { icon: 'pi pi-home', to: '/' };
|
||||||
|
breadcrumbItems = [{ label: 'Computer' }, { label: 'Notebook' }, { label: 'Accessories' }, { label: 'Backpacks' }, { label: 'Item' }];
|
||||||
|
tieredMenuItems = [
|
||||||
|
{
|
||||||
|
label: 'Customers',
|
||||||
|
icon: 'pi pi-fw pi-table',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
icon: 'pi pi-fw pi-user-plus',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Customer',
|
||||||
|
icon: 'pi pi-fw pi-plus'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Duplicate',
|
||||||
|
icon: 'pi pi-fw pi-copy'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
icon: 'pi pi-fw pi-user-edit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Orders',
|
||||||
|
icon: 'pi pi-fw pi-shopping-cart',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
icon: 'pi pi-fw pi-list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search',
|
||||||
|
icon: 'pi pi-fw pi-search'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Shipments',
|
||||||
|
icon: 'pi pi-fw pi-envelope',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Tracker',
|
||||||
|
icon: 'pi pi-fw pi-compass'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Map',
|
||||||
|
icon: 'pi pi-fw pi-map-marker'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Manage',
|
||||||
|
icon: 'pi pi-fw pi-pencil'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Profile',
|
||||||
|
icon: 'pi pi-fw pi-user',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'pi pi-fw pi-cog'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billing',
|
||||||
|
icon: 'pi pi-fw pi-file'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
icon: 'pi pi-fw pi-sign-out'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
overlayMenuItems = [
|
||||||
|
{
|
||||||
|
label: 'Save',
|
||||||
|
icon: 'pi pi-save'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
icon: 'pi pi-refresh'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
icon: 'pi pi-trash'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Home',
|
||||||
|
icon: 'pi pi-home'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
menuItems = [
|
||||||
|
{
|
||||||
|
label: 'Customers',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
icon: 'pi pi-fw pi-plus'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
icon: 'pi pi-fw pi-user-edit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Orders',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
icon: 'pi pi-fw pi-list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search',
|
||||||
|
icon: 'pi pi-fw pi-search'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
contextMenuItems = [
|
||||||
|
{
|
||||||
|
label: 'Save',
|
||||||
|
icon: 'pi pi-save'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
icon: 'pi pi-refresh'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
icon: 'pi pi-trash'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Options',
|
||||||
|
icon: 'pi pi-cog'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
megaMenuItems = [
|
||||||
|
{
|
||||||
|
label: 'Fashion',
|
||||||
|
icon: 'pi pi-fw pi-tag',
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Woman',
|
||||||
|
items: [{ label: 'Woman Item' }, { label: 'Woman Item' }, { label: 'Woman Item' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Men',
|
||||||
|
items: [{ label: 'Men Item' }, { label: 'Men Item' }, { label: 'Men Item' }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Kids',
|
||||||
|
items: [{ label: 'Kids Item' }, { label: 'Kids Item' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Luggage',
|
||||||
|
items: [{ label: 'Luggage Item' }, { label: 'Luggage Item' }, { label: 'Luggage Item' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Electronics',
|
||||||
|
icon: 'pi pi-fw pi-desktop',
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Computer',
|
||||||
|
items: [{ label: 'Computer Item' }, { label: 'Computer Item' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Camcorder',
|
||||||
|
items: [{ label: 'Camcorder Item' }, { label: 'Camcorder Item' }, { label: 'Camcorder Item' }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'TV',
|
||||||
|
items: [{ label: 'TV Item' }, { label: 'TV Item' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Audio',
|
||||||
|
items: [{ label: 'Audio Item' }, { label: 'Audio Item' }, { label: 'Audio Item' }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Sports.7',
|
||||||
|
items: [{ label: 'Sports.7.1' }, { label: 'Sports.7.2' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Furniture',
|
||||||
|
icon: 'pi pi-fw pi-image',
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Living Room',
|
||||||
|
items: [{ label: 'Living Room Item' }, { label: 'Living Room Item' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Kitchen',
|
||||||
|
items: [{ label: 'Kitchen Item' }, { label: 'Kitchen Item' }, { label: 'Kitchen Item' }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Bedroom',
|
||||||
|
items: [{ label: 'Bedroom Item' }, { label: 'Bedroom Item' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Outdoor',
|
||||||
|
items: [{ label: 'Outdoor Item' }, { label: 'Outdoor Item' }, { label: 'Outdoor Item' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Sports',
|
||||||
|
icon: 'pi pi-fw pi-star',
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Basketball',
|
||||||
|
items: [{ label: 'Basketball Item' }, { label: 'Basketball Item' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Football',
|
||||||
|
items: [{ label: 'Football Item' }, { label: 'Football Item' }, { label: 'Football Item' }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Tennis',
|
||||||
|
items: [{ label: 'Tennis Item' }, { label: 'Tennis Item' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
panelMenuItems = [
|
||||||
|
{
|
||||||
|
label: 'Customers',
|
||||||
|
icon: 'pi pi-fw pi-table',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New',
|
||||||
|
icon: 'pi pi-fw pi-user-plus',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Customer',
|
||||||
|
icon: 'pi pi-fw pi-plus'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Duplicate',
|
||||||
|
icon: 'pi pi-fw pi-copy'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
icon: 'pi pi-fw pi-user-edit'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Orders',
|
||||||
|
icon: 'pi pi-fw pi-shopping-cart',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
icon: 'pi pi-fw pi-list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Search',
|
||||||
|
icon: 'pi pi-fw pi-search'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Shipments',
|
||||||
|
icon: 'pi pi-fw pi-envelope',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Tracker',
|
||||||
|
icon: 'pi pi-fw pi-compass'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Map',
|
||||||
|
icon: 'pi pi-fw pi-map-marker'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Manage',
|
||||||
|
icon: 'pi pi-fw pi-pencil'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Profile',
|
||||||
|
icon: 'pi pi-fw pi-user',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'pi pi-fw pi-cog'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billing',
|
||||||
|
icon: 'pi pi-fw pi-file'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
79
src/app/pages/uikit/messagesdemo.ts
Normal file
79
src/app/pages/uikit/messagesdemo.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MessageService, ToastMessageOptions } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { MessageModule } from 'primeng/message';
|
||||||
|
import { ToastModule } from 'primeng/toast';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-messages-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, ToastModule, ButtonModule, InputTextModule, MessageModule, FormsModule],
|
||||||
|
template: `
|
||||||
|
<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Toast</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button (click)="showSuccessViaToast()" label="Success" severity="success" />
|
||||||
|
<p-button (click)="showInfoViaToast()" label="Info" severity="info" />
|
||||||
|
<p-button (click)="showWarnViaToast()" label="Warn" severity="warn" />
|
||||||
|
<p-button (click)="showErrorViaToast()" label="Error" severity="danger" />
|
||||||
|
<p-toast />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold text-xl mt-4 mb-4">Inline</div>
|
||||||
|
<div class="flex flex-col mb-4 gap-1">
|
||||||
|
<input pInputText [(ngModel)]="username" placeholder="Username" aria-label="username" class="ng-dirty ng-invalid" />
|
||||||
|
<p-message severity="error" variant="simple" size="small">Username is required</p-message>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col flex-wrap gap-1">
|
||||||
|
<input pInputText [(ngModel)]="email" placeholder="Email" aria-label="email" class="ng-dirty ng-invalid" />
|
||||||
|
<p-message severity="error" variant="simple" size="small">Email is required</p-message>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Message</div>
|
||||||
|
<div class="flex flex-col gap-4 mb-4">
|
||||||
|
<p-message severity="success">Success Message</p-message>
|
||||||
|
<p-message severity="info">Info Message</p-message>
|
||||||
|
<p-message severity="warn">Warn Message</p-message>
|
||||||
|
<p-message severity="error">Error Message</p-message>
|
||||||
|
<p-message severity="secondary">Secondary Message</p-message>
|
||||||
|
<p-message severity="contrast">Contrast Message</p-message>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
providers: [MessageService]
|
||||||
|
})
|
||||||
|
export class MessagesDemo {
|
||||||
|
msgs: ToastMessageOptions[] | null = [];
|
||||||
|
|
||||||
|
username: string | undefined;
|
||||||
|
|
||||||
|
email: string | undefined;
|
||||||
|
|
||||||
|
constructor(private service: MessageService) {}
|
||||||
|
|
||||||
|
showInfoViaToast() {
|
||||||
|
this.service.add({ severity: 'info', summary: 'Info Message', detail: 'PrimeNG rocks' });
|
||||||
|
}
|
||||||
|
|
||||||
|
showWarnViaToast() {
|
||||||
|
this.service.add({ severity: 'warn', summary: 'Warn Message', detail: 'There are unsaved changes' });
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorViaToast() {
|
||||||
|
this.service.add({ severity: 'error', summary: 'Error Message', detail: 'Validation failed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
showSuccessViaToast() {
|
||||||
|
this.service.add({ severity: 'success', summary: 'Success Message', detail: 'Message sent' });
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/app/pages/uikit/miscdemo.ts
Normal file
192
src/app/pages/uikit/miscdemo.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { AvatarModule } from 'primeng/avatar';
|
||||||
|
import { AvatarGroupModule } from 'primeng/avatargroup';
|
||||||
|
import { BadgeModule } from 'primeng/badge';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { ChipModule } from 'primeng/chip';
|
||||||
|
import { OverlayBadgeModule } from 'primeng/overlaybadge';
|
||||||
|
import { ProgressBarModule } from 'primeng/progressbar';
|
||||||
|
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
||||||
|
import { ScrollTopModule } from 'primeng/scrolltop';
|
||||||
|
import { SkeletonModule } from 'primeng/skeleton';
|
||||||
|
import { TagModule } from 'primeng/tag';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-misc-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, ProgressBarModule, BadgeModule, AvatarModule, ScrollPanelModule, TagModule, ChipModule, ButtonModule, SkeletonModule, AvatarGroupModule, ScrollTopModule, OverlayBadgeModule],
|
||||||
|
template: `
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">ProgressBar</div>
|
||||||
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<p-progressbar [value]="value" [showValue]="true"></p-progressbar>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<p-progressbar [value]="50" [showValue]="false"></p-progressbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Badge</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p-badge value="2"></p-badge>
|
||||||
|
<p-badge value="8" severity="success"></p-badge>
|
||||||
|
<p-badge value="4" severity="info"></p-badge>
|
||||||
|
<p-badge value="12" severity="warn"></p-badge>
|
||||||
|
<p-badge value="3" severity="danger"></p-badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Overlay</div>
|
||||||
|
<div class="flex gap-6">
|
||||||
|
<p-overlaybadge value="2">
|
||||||
|
<i class="pi pi-bell" style="font-size: 2rem"></i>
|
||||||
|
</p-overlaybadge>
|
||||||
|
<p-overlaybadge value="4" severity="danger">
|
||||||
|
<i class="pi pi-calendar" style="font-size: 2rem"></i>
|
||||||
|
</p-overlaybadge>
|
||||||
|
<p-overlaybadge severity="danger">
|
||||||
|
<i class="pi pi-envelope" style="font-size: 2rem"></i>
|
||||||
|
</p-overlaybadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Button</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p-button label="Emails" badge="8"></p-button>
|
||||||
|
<p-button label="Messages" icon="pi pi-users" severity="warn" badge="8" badgeSeverity="danger"></p-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Sizes</div>
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<p-badge [value]="2"></p-badge>
|
||||||
|
<p-badge [value]="4" badgeSize="large" severity="warn"></p-badge>
|
||||||
|
<p-badge [value]="6" badgeSize="xlarge" severity="success"></p-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Avatar</div>
|
||||||
|
<div class="font-semibold mb-4">Group</div>
|
||||||
|
<p-avatargroup styleClass="mb-4">
|
||||||
|
<p-avatar image="https://primefaces.org/cdn/primeng/images/demo/avatar/amyelsner.png" size="large" shape="circle"></p-avatar>
|
||||||
|
<p-avatar image="https://primefaces.org/cdn/primeng/images/demo/avatar/asiyajavayant.png" size="large" shape="circle"></p-avatar>
|
||||||
|
<p-avatar image="https://primefaces.org/cdn/primeng/images/demo/avatar/onyamalimba.png" size="large" shape="circle"></p-avatar>
|
||||||
|
<p-avatar image="https://primefaces.org/cdn/primeng/images/demo/avatar/ionibowcher.png" size="large" shape="circle"></p-avatar>
|
||||||
|
<p-avatar image="https://primefaces.org/cdn/primeng/images/demo/avatar/xuxuefeng.png" size="large" shape="circle"></p-avatar>
|
||||||
|
<p-avatar label="+2" shape="circle" size="large" [style]="{ 'background-color': '#9c27b0', color: '#ffffff' }"></p-avatar>
|
||||||
|
</p-avatargroup>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Label - Circle</div>
|
||||||
|
<p-avatar class="mr-2" label="P" size="xlarge" shape="circle"></p-avatar>
|
||||||
|
<p-avatar class="mr-2" label="V" size="large" [style]="{ 'background-color': '#2196F3', color: '#ffffff' }" shape="circle"></p-avatar>
|
||||||
|
<p-avatar class="mr-2" label="U" [style]="{ 'background-color': '#9c27b0', color: '#ffffff' }" shape="circle"></p-avatar>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Icon - Badge</div>
|
||||||
|
<p-overlaybadge value="4" severity="danger" class="inline-flex">
|
||||||
|
<p-avatar label="U" size="xlarge" />
|
||||||
|
</p-overlaybadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Skeleton</div>
|
||||||
|
<div class="rounded-border border border-surface p-6">
|
||||||
|
<div class="flex mb-4">
|
||||||
|
<p-skeleton shape="circle" size="4rem" styleClass="mr-2"></p-skeleton>
|
||||||
|
<div>
|
||||||
|
<p-skeleton width="10rem" styleClass="mb-2"></p-skeleton>
|
||||||
|
<p-skeleton width="5rem" styleClass="mb-2"></p-skeleton>
|
||||||
|
<p-skeleton height=".5rem"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p-skeleton width="100%" height="150px"></p-skeleton>
|
||||||
|
<div class="flex justify-between mt-4">
|
||||||
|
<p-skeleton width="4rem" height="2rem"></p-skeleton>
|
||||||
|
<p-skeleton width="4rem" height="2rem"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Tag</div>
|
||||||
|
<div class="font-semibold mb-4">Default</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p-tag value="Primary"></p-tag>
|
||||||
|
<p-tag severity="success" value="Success"></p-tag>
|
||||||
|
<p-tag severity="info" value="Info"></p-tag>
|
||||||
|
<p-tag severity="warn" value="Warning"></p-tag>
|
||||||
|
<p-tag severity="danger" value="Danger"></p-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Pills</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p-tag value="Primary" [rounded]="true"></p-tag>
|
||||||
|
<p-tag severity="success" value="Success" [rounded]="true"></p-tag>
|
||||||
|
<p-tag severity="info" value="Info" [rounded]="true"></p-tag>
|
||||||
|
<p-tag severity="warn" value="Warning" [rounded]="true"></p-tag>
|
||||||
|
<p-tag severity="danger" value="Danger" [rounded]="true"></p-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Icons</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p-tag icon="pi pi-user" value="Primary"></p-tag>
|
||||||
|
<p-tag icon="pi pi-check" severity="success" value="Success"></p-tag>
|
||||||
|
<p-tag icon="pi pi-info-circle" severity="info" value="Info"></p-tag>
|
||||||
|
<p-tag icon="pi pi-exclamation-triangle" severity="warn" value="Warning"></p-tag>
|
||||||
|
<p-tag icon="pi pi-times" severity="danger" value="Danger"></p-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Chip</div>
|
||||||
|
<div class="font-semibold mb-4">Basic</div>
|
||||||
|
<div class="flex items-center flex-col sm:flex-row">
|
||||||
|
<p-chip label="Action" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Comedy" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Mystery" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Thriller" styleClass="m-1" [removable]="true"></p-chip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Icon</div>
|
||||||
|
<div class="flex items-center flex-col sm:flex-row">
|
||||||
|
<p-chip label="Apple" icon="pi pi-apple" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Facebook" icon="pi pi-facebook" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Google" icon="pi pi-google" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Microsoft" icon="pi pi-microsoft" styleClass="m-1" [removable]="true"></p-chip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-semibold my-4">Image</div>
|
||||||
|
<div class="flex items-center flex-col sm:flex-row">
|
||||||
|
<p-chip label="Amy Elsner" image="https://primefaces.org/cdn/primeng/images/demo/avatar/amyelsner.png" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Asiya Javayant" image="https://primefaces.org/cdn/primeng/images/demo/avatar/asiyajavayant.png" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Onyama Limba" image="https://primefaces.org/cdn/primeng/images/demo/avatar/onyamalimba.png" styleClass="m-1"></p-chip>
|
||||||
|
<p-chip label="Xuxue Feng" image="https://primefaces.org/cdn/primeng/images/demo/avatar/xuxuefeng.png" styleClass="m-1" [removable]="true"></p-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class MiscDemo {
|
||||||
|
value = 0;
|
||||||
|
|
||||||
|
interval: any;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.value = this.value + Math.floor(Math.random() * 10) + 1;
|
||||||
|
if (this.value >= 100) {
|
||||||
|
this.value = 100;
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/app/pages/uikit/overlaydemo.ts
Normal file
230
src/app/pages/uikit/overlaydemo.ts
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DialogModule } from 'primeng/dialog';
|
||||||
|
import { ToastModule } from 'primeng/toast';
|
||||||
|
import { DrawerModule } from 'primeng/drawer';
|
||||||
|
import { Popover, PopoverModule } from 'primeng/popover';
|
||||||
|
import { ConfirmPopupModule } from 'primeng/confirmpopup';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { TooltipModule } from 'primeng/tooltip';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { Property, ProductService } from '../service/product.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-overlay-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [ToastModule, DialogModule, ButtonModule, DrawerModule, PopoverModule, ConfirmPopupModule, InputTextModule, FormsModule, TooltipModule, TableModule, ToastModule],
|
||||||
|
template: ` <div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Dialog</div>
|
||||||
|
<p-dialog header="Dialog" [(visible)]="display" [breakpoints]="{ '960px': '75vw' }" [style]="{ width: '30vw' }" [modal]="true">
|
||||||
|
<p class="leading-normal m-0">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat. 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.
|
||||||
|
</p>
|
||||||
|
<ng-template #footer>
|
||||||
|
<p-button label="Save" (click)="close()" />
|
||||||
|
</ng-template>
|
||||||
|
</p-dialog>
|
||||||
|
<p-button label="Show" [style]="{ width: 'auto' }" (click)="open()" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Popover</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<p-button type="button" label="Show" (click)="toggleDataTable(op2, $event)" />
|
||||||
|
<p-popover #op2 id="overlay_panel" [style]="{ width: '450px' }">
|
||||||
|
<p-table [value]="products" selectionMode="single" [(selection)]="selectedProduct" dataKey="id" [rows]="5" [paginator]="true" (onRowSelect)="onProductSelect(op2, $event)">
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Image</th>
|
||||||
|
<th>Price</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-product>
|
||||||
|
<tr [pSelectableRow]="product">
|
||||||
|
<td>{{ product.name }}</td>
|
||||||
|
<td><img [src]="'https://primefaces.org/cdn/primeng/images/demo/product/' + product.image" [alt]="product.name" class="w-16 shadow-sm" /></td>
|
||||||
|
<td>{{ product.price }}</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</p-popover>
|
||||||
|
<p-toast />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Tooltip</div>
|
||||||
|
<div class="inline-flex gap-4">
|
||||||
|
<input pInputText type="text" placeholder="Username" pTooltip="Your username" />
|
||||||
|
<p-button type="button" label="Save" pTooltip="Click to proceed" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Drawer</div>
|
||||||
|
<p-drawer [(visible)]="visibleLeft" header="Drawer">
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat.
|
||||||
|
</p>
|
||||||
|
</p-drawer>
|
||||||
|
|
||||||
|
<p-drawer [(visible)]="visibleRight" header="Drawer" position="right">
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat.
|
||||||
|
</p>
|
||||||
|
</p-drawer>
|
||||||
|
|
||||||
|
<p-drawer [(visible)]="visibleTop" header="Drawer" position="top">
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat.
|
||||||
|
</p>
|
||||||
|
</p-drawer>
|
||||||
|
|
||||||
|
<p-drawer [(visible)]="visibleBottom" header="Drawer" position="bottom">
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat.
|
||||||
|
</p>
|
||||||
|
</p-drawer>
|
||||||
|
|
||||||
|
<p-drawer [(visible)]="visibleFull" header="Drawer" position="full">
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||||
|
consequat.
|
||||||
|
</p>
|
||||||
|
</p-drawer>
|
||||||
|
|
||||||
|
<p-button icon="pi pi-arrow-right" (click)="visibleLeft = true" [style]="{ marginRight: '0.25em' }" />
|
||||||
|
<p-button icon="pi pi-arrow-left" (click)="visibleRight = true" [style]="{ marginRight: '0.25em' }" />
|
||||||
|
<p-button icon="pi pi-arrow-down" (click)="visibleTop = true" [style]="{ marginRight: '0.25em' }" />
|
||||||
|
<p-button icon="pi pi-arrow-up" (click)="visibleBottom = true" [style]="{ marginRight: '0.25em' }" />
|
||||||
|
<p-button icon="pi pi-external-link" (click)="visibleFull = true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">ConfirmPopup</div>
|
||||||
|
<p-confirmpopup></p-confirmpopup>
|
||||||
|
<p-button #popup (click)="confirm($event)" icon="pi pi-check" label="Confirm" class="mr-2"></p-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">ConfirmDialog</div>
|
||||||
|
<p-button label="Delete" icon="pi pi-trash" severity="danger" [style]="{ width: 'auto' }" (click)="openConfirmation()" />
|
||||||
|
<p-dialog header="Confirmation" [(visible)]="displayConfirmation" [style]="{ width: '350px' }" [modal]="true">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<i class="pi pi-exclamation-triangle mr-4" style="font-size: 2rem"> </i>
|
||||||
|
<span>Are you sure you want to proceed?</span>
|
||||||
|
</div>
|
||||||
|
<ng-template #footer>
|
||||||
|
<p-button label="No" icon="pi pi-times" (click)="closeConfirmation()" text severity="secondary" />
|
||||||
|
<p-button label="Yes" icon="pi pi-check" (click)="closeConfirmation()" severity="danger" outlined autofocus />
|
||||||
|
</ng-template>
|
||||||
|
</p-dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
providers: [ConfirmationService, MessageService, ProductService]
|
||||||
|
})
|
||||||
|
export class OverlayDemo implements OnInit {
|
||||||
|
images: any[] = [];
|
||||||
|
|
||||||
|
display: boolean = false;
|
||||||
|
|
||||||
|
products: Property[] = [];
|
||||||
|
|
||||||
|
visibleLeft: boolean = false;
|
||||||
|
|
||||||
|
visibleRight: boolean = false;
|
||||||
|
|
||||||
|
visibleTop: boolean = false;
|
||||||
|
|
||||||
|
visibleBottom: boolean = false;
|
||||||
|
|
||||||
|
visibleFull: boolean = false;
|
||||||
|
|
||||||
|
displayConfirmation: boolean = false;
|
||||||
|
|
||||||
|
selectedProduct!: Property;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private productService: ProductService,
|
||||||
|
private confirmationService: ConfirmationService,
|
||||||
|
private messageService: MessageService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.productService.getProductsSmall().then((products) => (this.products = products));
|
||||||
|
|
||||||
|
this.images = [];
|
||||||
|
this.images.push({
|
||||||
|
source: 'assets/demo/images/sopranos/sopranos1.jpg',
|
||||||
|
thumbnail: 'assets/demo/images/sopranos/sopranos1_small.jpg',
|
||||||
|
title: 'Sopranos 1'
|
||||||
|
});
|
||||||
|
this.images.push({
|
||||||
|
source: 'assets/demo/images/sopranos/sopranos2.jpg',
|
||||||
|
thumbnail: 'assets/demo/images/sopranos/sopranos2_small.jpg',
|
||||||
|
title: 'Sopranos 2'
|
||||||
|
});
|
||||||
|
this.images.push({
|
||||||
|
source: 'assets/demo/images/sopranos/sopranos3.jpg',
|
||||||
|
thumbnail: 'assets/demo/images/sopranos/sopranos3_small.jpg',
|
||||||
|
title: 'Sopranos 3'
|
||||||
|
});
|
||||||
|
this.images.push({
|
||||||
|
source: 'assets/demo/images/sopranos/sopranos4.jpg',
|
||||||
|
thumbnail: 'assets/demo/images/sopranos/sopranos4_small.jpg',
|
||||||
|
title: 'Sopranos 4'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm(event: Event) {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
key: 'confirm2',
|
||||||
|
target: event.target || new EventTarget(),
|
||||||
|
message: 'Are you sure that you want to proceed?',
|
||||||
|
icon: 'pi pi-exclamation-triangle',
|
||||||
|
accept: () => {
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'You have accepted' });
|
||||||
|
},
|
||||||
|
reject: () => {
|
||||||
|
this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
this.display = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.display = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDataTable(op: Popover, event: any) {
|
||||||
|
op.toggle(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onProductSelect(op: Popover, event: any) {
|
||||||
|
op.hide();
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Product Selected', detail: event?.data.name, life: 3000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
openConfirmation() {
|
||||||
|
this.displayConfirmation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeConfirmation() {
|
||||||
|
this.displayConfirmation = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
235
src/app/pages/uikit/panelsdemo.ts
Normal file
235
src/app/pages/uikit/panelsdemo.ts
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { AccordionModule } from 'primeng/accordion';
|
||||||
|
import { MenuItem } from 'primeng/api';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DividerModule } from 'primeng/divider';
|
||||||
|
import { FieldsetModule } from 'primeng/fieldset';
|
||||||
|
import { IconFieldModule } from 'primeng/iconfield';
|
||||||
|
import { InputIconModule } from 'primeng/inputicon';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { MenuModule } from 'primeng/menu';
|
||||||
|
import { PanelModule } from 'primeng/panel';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { SplitButtonModule } from 'primeng/splitbutton';
|
||||||
|
import { SplitterModule } from 'primeng/splitter';
|
||||||
|
import { TabsModule } from 'primeng/tabs';
|
||||||
|
import { ToolbarModule } from 'primeng/toolbar';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-panels-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ToolbarModule,
|
||||||
|
ButtonModule,
|
||||||
|
RippleModule,
|
||||||
|
SplitButtonModule,
|
||||||
|
AccordionModule,
|
||||||
|
FieldsetModule,
|
||||||
|
MenuModule,
|
||||||
|
InputTextModule,
|
||||||
|
DividerModule,
|
||||||
|
SplitterModule,
|
||||||
|
PanelModule,
|
||||||
|
TabsModule,
|
||||||
|
IconFieldModule,
|
||||||
|
InputIconModule
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Toolbar</div>
|
||||||
|
<p-toolbar>
|
||||||
|
<ng-template #start>
|
||||||
|
<p-button icon="pi pi-plus" class="mr-2" severity="secondary" text />
|
||||||
|
<p-button icon="pi pi-print" class="mr-2" severity="secondary" text />
|
||||||
|
<p-button icon="pi pi-upload" severity="secondary" text />
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #center>
|
||||||
|
<p-iconfield>
|
||||||
|
<p-inputicon>
|
||||||
|
<i class="pi pi-search"></i>
|
||||||
|
</p-inputicon>
|
||||||
|
<input pInputText placeholder="Search" />
|
||||||
|
</p-iconfield>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #end><p-splitbutton label="Save" [model]="items"></p-splitbutton></ng-template>
|
||||||
|
</p-toolbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row gap-8">
|
||||||
|
<div class="md:w-1/2">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Accordion</div>
|
||||||
|
<p-accordion value="0">
|
||||||
|
<p-accordion-panel value="0">
|
||||||
|
<p-accordion-header>Header I</p-accordion-header>
|
||||||
|
<p-accordion-content>
|
||||||
|
<p class="m-0">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||||
|
ea commodo consequat. 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.
|
||||||
|
</p>
|
||||||
|
</p-accordion-content>
|
||||||
|
</p-accordion-panel>
|
||||||
|
|
||||||
|
<p-accordion-panel value="1">
|
||||||
|
<p-accordion-header>Header II</p-accordion-header>
|
||||||
|
<p-accordion-content>
|
||||||
|
<p class="m-0">
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
|
||||||
|
explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non
|
||||||
|
numquam eius modi.
|
||||||
|
</p>
|
||||||
|
</p-accordion-content>
|
||||||
|
</p-accordion-panel>
|
||||||
|
|
||||||
|
<p-accordion-panel value="2">
|
||||||
|
<p-accordion-header>Header III</p-accordion-header>
|
||||||
|
<p-accordion-content>
|
||||||
|
<p class="m-0">
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
|
||||||
|
explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non
|
||||||
|
numquam eius modi.
|
||||||
|
</p>
|
||||||
|
</p-accordion-content>
|
||||||
|
</p-accordion-panel>
|
||||||
|
</p-accordion>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Tabs</div>
|
||||||
|
<p-tabs value="0">
|
||||||
|
<p-tablist>
|
||||||
|
<p-tab value="0">Header I</p-tab>
|
||||||
|
<p-tab value="1">Header II</p-tab>
|
||||||
|
<p-tab value="2">Header III</p-tab>
|
||||||
|
</p-tablist>
|
||||||
|
<p-tabpanels>
|
||||||
|
<p-tabpanel value="0">
|
||||||
|
<p class="m-0">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||||
|
ea commodo consequat. 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.
|
||||||
|
</p>
|
||||||
|
</p-tabpanel>
|
||||||
|
<p-tabpanel value="1">
|
||||||
|
<p class="m-0">
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt
|
||||||
|
explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Consectetur, adipisci velit, sed quia non
|
||||||
|
numquam eius modi.
|
||||||
|
</p>
|
||||||
|
</p-tabpanel>
|
||||||
|
<p-tabpanel value="2">
|
||||||
|
<p class="m-0">
|
||||||
|
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident,
|
||||||
|
similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio
|
||||||
|
cumque nihil impedit quo minus.
|
||||||
|
</p>
|
||||||
|
</p-tabpanel>
|
||||||
|
</p-tabpanels>
|
||||||
|
</p-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md:w-1/2 mt-6 md:mt-0">
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Panel</div>
|
||||||
|
<p-panel header="Header" [toggleable]="true">
|
||||||
|
<p class="m-0">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||||
|
commodo consequat. 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.
|
||||||
|
</p>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Fieldset</div>
|
||||||
|
<p-fieldset legend="Legend" [toggleable]="true">
|
||||||
|
<p class="m-0">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||||
|
commodo consequat. 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.
|
||||||
|
</p>
|
||||||
|
</p-fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-8">
|
||||||
|
<div class="font-semibold text-xl mb-4">Divider</div>
|
||||||
|
<div class="flex flex-col md:flex-row">
|
||||||
|
<div class="w-full md:w-5/12 flex flex-col items-center justify-center gap-3 py-5">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input pInputText id="username" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input pInputText id="password" type="password" />
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<p-button label="Login" icon="pi pi-user" class="w-full max-w-[17.35rem] mx-auto"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-2/12">
|
||||||
|
<p-divider layout="vertical" class="hidden! md:flex!"><b>OR</b></p-divider>
|
||||||
|
<p-divider layout="horizontal" class="flex! md:hidden!" align="center"><b>OR</b></p-divider>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-5/12 flex items-center justify-center py-5">
|
||||||
|
<p-button label="Sign Up" icon="pi pi-user-plus" severity="success" class="w-full" styleClass="w-full max-w-[17.35rem] mx-auto"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Splitter</div>
|
||||||
|
<p-splitter [style]="{ height: '300px' }" [panelSizes]="[20, 80]" [minSizes]="[10, 0]" styleClass="mb-8">
|
||||||
|
<ng-template #panel>
|
||||||
|
<div class="col flex items-center justify-center">Panel 1</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #panel>
|
||||||
|
<p-splitter layout="vertical" [panelSizes]="[50, 50]">
|
||||||
|
<ng-template #panel>
|
||||||
|
<div style="grow: 1;" class="flex items-center justify-center">Panel 2</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #panel>
|
||||||
|
<p-splitter [panelSizes]="[20, 80]">
|
||||||
|
<ng-template #panel>
|
||||||
|
<div class="col flex items-center justify-center">Panel 3</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #panel>
|
||||||
|
<div class="col flex items-center justify-center">Panel 4</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-splitter>
|
||||||
|
</ng-template>
|
||||||
|
</p-splitter>
|
||||||
|
</ng-template>
|
||||||
|
</p-splitter>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class PanelsDemo {
|
||||||
|
items: MenuItem[] = [
|
||||||
|
{
|
||||||
|
label: 'Save',
|
||||||
|
icon: 'pi pi-check'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
icon: 'pi pi-upload'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
icon: 'pi pi-trash'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Home Page',
|
||||||
|
icon: 'pi pi-home'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
568
src/app/pages/uikit/tabledemo.ts
Normal file
568
src/app/pages/uikit/tabledemo.ts
Normal file
@@ -0,0 +1,568 @@
|
|||||||
|
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { MultiSelectModule } from 'primeng/multiselect';
|
||||||
|
import { SelectModule } from 'primeng/select';
|
||||||
|
import { SliderModule } from 'primeng/slider';
|
||||||
|
import { Table, TableModule } from 'primeng/table';
|
||||||
|
import { ProgressBarModule } from 'primeng/progressbar';
|
||||||
|
import { ToggleButtonModule } from 'primeng/togglebutton';
|
||||||
|
import { ToastModule } from 'primeng/toast';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { RatingModule } from 'primeng/rating';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { InputIconModule } from 'primeng/inputicon';
|
||||||
|
import { IconFieldModule } from 'primeng/iconfield';
|
||||||
|
import { TagModule } from 'primeng/tag';
|
||||||
|
import { Customer, CustomerService, Representative } from '../service/customer.service';
|
||||||
|
import { Property, ProductService } from '../service/product.service';
|
||||||
|
import {ObjectUtils} from "primeng/utils";
|
||||||
|
|
||||||
|
interface expandedRows {
|
||||||
|
[key: string]: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-table-demo',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
TableModule,
|
||||||
|
MultiSelectModule,
|
||||||
|
SelectModule,
|
||||||
|
InputIconModule,
|
||||||
|
TagModule,
|
||||||
|
InputTextModule,
|
||||||
|
SliderModule,
|
||||||
|
ProgressBarModule,
|
||||||
|
ToggleButtonModule,
|
||||||
|
ToastModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ButtonModule,
|
||||||
|
RatingModule,
|
||||||
|
RippleModule,
|
||||||
|
IconFieldModule
|
||||||
|
],
|
||||||
|
template: ` <div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Filtering</div>
|
||||||
|
<p-table
|
||||||
|
#dt1
|
||||||
|
[value]="customers1"
|
||||||
|
dataKey="id"
|
||||||
|
[rows]="10"
|
||||||
|
[loading]="loading"
|
||||||
|
[rowHover]="true"
|
||||||
|
[showGridlines]="true"
|
||||||
|
[paginator]="true"
|
||||||
|
[globalFilterFields]="['name', 'country.name', 'representative.name', 'status']"
|
||||||
|
responsiveLayout="scroll"
|
||||||
|
>
|
||||||
|
<ng-template #caption>
|
||||||
|
<div class="flex justify-between items-center flex-column sm:flex-row">
|
||||||
|
<button pButton label="Clear" class="p-button-outlined mb-2" icon="pi pi-filter-slash" (click)="clear(dt1)"></button>
|
||||||
|
<p-iconfield iconPosition="left" class="ml-auto">
|
||||||
|
<p-inputicon>
|
||||||
|
<i class="pi pi-search"></i>
|
||||||
|
</p-inputicon>
|
||||||
|
<input pInputText type="text" (input)="onGlobalFilter(dt1, $event)" placeholder="Search keyword" />
|
||||||
|
</p-iconfield>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Name
|
||||||
|
<p-columnFilter type="text" field="name" display="menu" placeholder="Search by name"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Country
|
||||||
|
<p-columnFilter type="text" field="country.name" display="menu" placeholder="Search by country"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 14rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Agent
|
||||||
|
<p-columnFilter field="representative" matchMode="in" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false">
|
||||||
|
<ng-template #header>
|
||||||
|
<div class="px-3 pt-3 pb-0">
|
||||||
|
<span class="font-bold">Agent Picker</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #filter let-value let-filter="filterCallback">
|
||||||
|
<p-multiselect [ngModel]="value" [options]="representatives" placeholder="Any" (onChange)="filter($event.value)" optionLabel="name" styleClass="w-full">
|
||||||
|
<ng-template let-option #item>
|
||||||
|
<div class="flex items-center gap-2 w-44">
|
||||||
|
<img [alt]="option.label" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ option.image }}" width="32" />
|
||||||
|
<span>{{ option.name }}</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-multiselect>
|
||||||
|
</ng-template>
|
||||||
|
</p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 10rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Date
|
||||||
|
<p-columnFilter type="date" field="date" display="menu" placeholder="mm/dd/yyyy"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 10rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Balance
|
||||||
|
<p-columnFilter type="numeric" field="balance" display="menu" currency="USD"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Status
|
||||||
|
<p-columnFilter field="status" matchMode="equals" display="menu">
|
||||||
|
<ng-template #filter let-value let-filter="filterCallback">
|
||||||
|
<p-select [ngModel]="value" [options]="statuses" (onChange)="filter($event.value)" placeholder="Any" [style]="{ 'min-width': '12rem' }">
|
||||||
|
<ng-template let-option #item>
|
||||||
|
<span [class]="'customer-badge status-' + option.value">{{ option.label }}</span>
|
||||||
|
</ng-template>
|
||||||
|
</p-select>
|
||||||
|
</ng-template>
|
||||||
|
</p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 12rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Activity
|
||||||
|
<p-columnFilter field="activity" matchMode="between" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false">
|
||||||
|
<ng-template #filter let-filter="filterCallback">
|
||||||
|
<p-slider [ngModel]="activityValues" [range]="true" (onSlideEnd)="filter($event.values)" styleClass="m-3" [style]="{ 'min-width': '12rem' }"></p-slider>
|
||||||
|
<div class="flex items-center justify-between px-2">
|
||||||
|
<span>{{ activityValues[0] }}</span>
|
||||||
|
<span>{{ activityValues[1] }}</span>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="min-width: 8rem">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
Verified
|
||||||
|
<p-columnFilter type="boolean" field="verified" display="menu"></p-columnFilter>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-customer>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ customer.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" width="30" />
|
||||||
|
<span>{{ customer.country.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img [alt]="customer.representative.name" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ customer.representative.image }}" width="32" style="vertical-align: middle" />
|
||||||
|
<span class="image-text">{{ customer.representative.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ customer.date | date: 'MM/dd/yyyy' }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ customer.balance | currency: 'USD' : 'symbol' }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-tag [value]="customer.status.toLowerCase()" [severity]="getSeverity(customer.status.toLowerCase())" styleClass="dark:bg-surface-900!" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-progressbar [value]="customer.activity" [showValue]="false" [style]="{ height: '0.5rem' }" />
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<p-tag [value]="customer.status.toLowerCase()" [severity]="getSeverity(customer.status.toLowerCase())" styleClass="dark:bg-surface-900!" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #emptymessage>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8">No customers found.</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #loadingbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8">Loading customers data. Please wait.</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Frozen Columns</div>
|
||||||
|
<p-togglebutton [(ngModel)]="balanceFrozen" [onIcon]="'pi pi-lock'" offIcon="pi pi-lock-open" [onLabel]="'Balance'" offLabel="Balance" />
|
||||||
|
|
||||||
|
<p-table [value]="customers2" [scrollable]="true" scrollHeight="400px" styleClass="mt-4">
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th style="min-width:200px" pFrozenColumn class="font-bold">Name</th>
|
||||||
|
<th style="min-width:100px">Id</th>
|
||||||
|
<th style="min-width:200px">Country</th>
|
||||||
|
<th style="min-width:200px">Date</th>
|
||||||
|
<th style="min-width:200px">Company</th>
|
||||||
|
<th style="min-width:200px">Status</th>
|
||||||
|
<th style="min-width:200px">Activity</th>
|
||||||
|
<th style="min-width:200px">Representative</th>
|
||||||
|
<th style="min-width:200px" alignFrozen="right" pFrozenColumn [frozen]="balanceFrozen" [ngClass]="{ 'font-bold': balanceFrozen }">Balance</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-customer>
|
||||||
|
<tr>
|
||||||
|
<td pFrozenColumn class="font-bold">{{ customer.name }}</td>
|
||||||
|
<td style="min-width:100px">{{ customer.id }}</td>
|
||||||
|
<td>{{ customer.country.name }}</td>
|
||||||
|
<td>{{ customer.date }}</td>
|
||||||
|
<td>{{ customer.company }}</td>
|
||||||
|
<td>{{ customer.status }}</td>
|
||||||
|
<td>{{ customer.activity }}</td>
|
||||||
|
<td>{{ customer.representative.name }}</td>
|
||||||
|
<td alignFrozen="right" pFrozenColumn [frozen]="balanceFrozen" [ngClass]="{ 'font-bold': balanceFrozen }">
|
||||||
|
{{ formatCurrency(customer.balance) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Row Expansion</div>
|
||||||
|
<p-table [value]="products" dataKey="id" [tableStyle]="{ 'min-width': '60rem' }" [expandedRowKeys]="expandedRows">
|
||||||
|
<ng-template #caption>
|
||||||
|
<button pButton icon="pi pi-fw {{ isExpanded ? 'pi-minus' : 'pi-plus' }}" label="{{ isExpanded ? 'Collapse All' : 'Expand All' }}" (click)="expandAll()"></button>
|
||||||
|
<div class="flex table-header"></div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 5rem"></th>
|
||||||
|
<th pSortableColumn="name">Name <p-sortIcon field="name" /></th>
|
||||||
|
<th>Image</th>
|
||||||
|
<th pSortableColumn="price">Price <p-sortIcon field="price" /></th>
|
||||||
|
<th pSortableColumn="category">Category <p-sortIcon field="category" /></th>
|
||||||
|
<th pSortableColumn="rating">Reviews <p-sortIcon field="rating" /></th>
|
||||||
|
<th pSortableColumn="inventoryStatus">Status <p-sortIcon field="inventoryStatus" /></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-product let-expanded="expanded">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p-button type="button" pRipple [pRowToggler]="product" [text]="true" [rounded]="true" [plain]="true" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'" />
|
||||||
|
</td>
|
||||||
|
<td>{{ product.name }}</td>
|
||||||
|
<td>
|
||||||
|
<img [src]="'https://primefaces.org/cdn/primeng/images/demo/product/' + product.image" [alt]="product.name" width="50" class="shadow-lg" />
|
||||||
|
</td>
|
||||||
|
<td>{{ product.price | currency: 'USD' }}</td>
|
||||||
|
<td>{{ product.category }}</td>
|
||||||
|
<td>
|
||||||
|
<p-rating [ngModel]="product.rating" [readonly]="true" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-tag [value]="product.inventoryStatus" [severity]="getSeverity(product.inventoryStatus)" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #expandedrow let-product>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7">
|
||||||
|
<div class="p-4">
|
||||||
|
<h5>Orders for {{ product.name }}</h5>
|
||||||
|
<p-table [value]="product.orders" dataKey="id">
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th pSortableColumn="id">Id <p-sortIcon field="price" /></th>
|
||||||
|
<th pSortableColumn="customer">
|
||||||
|
Customer
|
||||||
|
<p-sortIcon field="customer" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="date">Date <p-sortIcon field="date" /></th>
|
||||||
|
<th pSortableColumn="amount">
|
||||||
|
Amount
|
||||||
|
<p-sortIcon field="amount" />
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="status">
|
||||||
|
Status
|
||||||
|
<p-sortIcon field="status" />
|
||||||
|
</th>
|
||||||
|
<th style="width: 4rem"></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-order>
|
||||||
|
<tr>
|
||||||
|
<td>{{ order.id }}</td>
|
||||||
|
<td>{{ order.customer }}</td>
|
||||||
|
<td>{{ order.date }}</td>
|
||||||
|
<td>
|
||||||
|
{{ order.amount | currency: 'USD' }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-tag [value]="order.status" [severity]="getSeverity(order.status)" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-button type="button" icon="pi pi-search" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #emptymessage>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">There are no order for this product yet.</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="font-semibold text-xl mb-4">Grouping</div>
|
||||||
|
<p-table [value]="customers3" sortField="representative.name" sortMode="single" [scrollable]="true" scrollHeight="400px" rowGroupMode="subheader" groupRowsBy="representative.name" [tableStyle]="{ 'min-width': '60rem' }">
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Country</th>
|
||||||
|
<th>Company</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #groupheader let-customer>
|
||||||
|
<tr pRowGroupHeader>
|
||||||
|
<td colspan="5">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img [alt]="customer.representative.name" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ customer.representative.image }}" width="32" style="vertical-align: middle" />
|
||||||
|
<span class="font-bold">{{ customer.representative.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #groupfooter let-customer>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-right font-bold pr-12">Total Customers: {{ calculateCustomerTotal(customer.representative.name) }}</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-customer let-rowIndex="rowIndex">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ customer.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" style="width: 20px" />
|
||||||
|
<span>{{ customer.country.name }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ customer.company }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-tag [value]="customer.status" [severity]="getSeverity(customer.status)" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ customer.date }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>`,
|
||||||
|
styles: `
|
||||||
|
.p-datatable-frozen-tbody {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-datatable-scrollable .p-frozen-column {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
providers: [ConfirmationService, MessageService, CustomerService, ProductService]
|
||||||
|
})
|
||||||
|
export class TableDemo implements OnInit {
|
||||||
|
customers1: Customer[] = [];
|
||||||
|
|
||||||
|
customers2: Customer[] = [];
|
||||||
|
|
||||||
|
customers3: Customer[] = [];
|
||||||
|
|
||||||
|
selectedCustomers1: Customer[] = [];
|
||||||
|
|
||||||
|
selectedCustomer: Customer = {};
|
||||||
|
|
||||||
|
representatives: Representative[] = [];
|
||||||
|
|
||||||
|
statuses: any[] = [];
|
||||||
|
|
||||||
|
products: Property[] = [];
|
||||||
|
|
||||||
|
rowGroupMetadata: any;
|
||||||
|
|
||||||
|
expandedRows: expandedRows = {};
|
||||||
|
|
||||||
|
activityValues: number[] = [0, 100];
|
||||||
|
|
||||||
|
isExpanded: boolean = false;
|
||||||
|
|
||||||
|
balanceFrozen: boolean = false;
|
||||||
|
|
||||||
|
loading: boolean = true;
|
||||||
|
|
||||||
|
@ViewChild('filter') filter!: ElementRef;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private customerService: CustomerService,
|
||||||
|
private productService: ProductService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.customerService.getCustomersLarge().then((customers) => {
|
||||||
|
this.customers1 = customers;
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.customers1.forEach((customer) => (customer.date = new Date(customer.date)));
|
||||||
|
});
|
||||||
|
this.customerService.getCustomersMedium().then((customers) => (this.customers2 = customers));
|
||||||
|
this.customerService.getCustomersLarge().then((customers) => (this.customers3 = customers));
|
||||||
|
this.productService.getProductsWithOrdersSmall().then((data) => (this.products = data));
|
||||||
|
|
||||||
|
this.representatives = [
|
||||||
|
{ name: 'Amy Elsner', image: 'amyelsner.png' },
|
||||||
|
{ name: 'Anna Fali', image: 'annafali.png' },
|
||||||
|
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
|
||||||
|
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
|
||||||
|
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
|
||||||
|
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
|
||||||
|
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
|
||||||
|
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
|
||||||
|
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
|
||||||
|
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.statuses = [
|
||||||
|
{ label: 'Unqualified', value: 'unqualified' },
|
||||||
|
{ label: 'Qualified', value: 'qualified' },
|
||||||
|
{ label: 'New', value: 'new' },
|
||||||
|
{ label: 'Negotiation', value: 'negotiation' },
|
||||||
|
{ label: 'Renewal', value: 'renewal' },
|
||||||
|
{ label: 'Proposal', value: 'proposal' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
onSort() {
|
||||||
|
this.updateRowGroupMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRowGroupMetaData() {
|
||||||
|
this.rowGroupMetadata = {};
|
||||||
|
|
||||||
|
if (this.customers3) {
|
||||||
|
for (let i = 0; i < this.customers3.length; i++) {
|
||||||
|
const rowData = this.customers3[i];
|
||||||
|
const representativeName = rowData?.representative?.name || '';
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
this.rowGroupMetadata[representativeName] = { index: 0, size: 1 };
|
||||||
|
} else {
|
||||||
|
const previousRowData = this.customers3[i - 1];
|
||||||
|
const previousRowGroup = previousRowData?.representative?.name;
|
||||||
|
if (representativeName === previousRowGroup) {
|
||||||
|
this.rowGroupMetadata[representativeName].size++;
|
||||||
|
} else {
|
||||||
|
this.rowGroupMetadata[representativeName] = { index: i, size: 1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expandAll() {
|
||||||
|
if(ObjectUtils.isEmpty(this.expandedRows)) {
|
||||||
|
this.expandedRows = this.products.reduce(
|
||||||
|
(acc, p) => {
|
||||||
|
if (p.id) {
|
||||||
|
acc[p.id] = true;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as { [key: string]: boolean }
|
||||||
|
);
|
||||||
|
this.isExpanded = true;
|
||||||
|
} else {
|
||||||
|
this.collapseAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
collapseAll() {
|
||||||
|
this.expandedRows = {};
|
||||||
|
this.isExpanded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCurrency(value: number) {
|
||||||
|
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
|
||||||
|
}
|
||||||
|
|
||||||
|
onGlobalFilter(table: Table, event: Event) {
|
||||||
|
table.filterGlobal((event.target as HTMLInputElement).value, 'contains');
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(table: Table) {
|
||||||
|
table.clear();
|
||||||
|
this.filter.nativeElement.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeverity(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case 'qualified':
|
||||||
|
case 'instock':
|
||||||
|
case 'INSTOCK':
|
||||||
|
case 'DELIVERED':
|
||||||
|
case 'delivered':
|
||||||
|
return 'success';
|
||||||
|
|
||||||
|
case 'negotiation':
|
||||||
|
case 'lowstock':
|
||||||
|
case 'LOWSTOCK':
|
||||||
|
case 'PENDING':
|
||||||
|
case 'pending':
|
||||||
|
return 'warn';
|
||||||
|
|
||||||
|
case 'unqualified':
|
||||||
|
case 'outofstock':
|
||||||
|
case 'OUTOFSTOCK':
|
||||||
|
case 'CANCELLED':
|
||||||
|
case 'cancelled':
|
||||||
|
return 'danger';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateCustomerTotal(name: string) {
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
if (this.customers2) {
|
||||||
|
for (let customer of this.customers2) {
|
||||||
|
if (customer.representative?.name === name) {
|
||||||
|
total++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user