initial setup
This commit is contained in:
commit
f9fe0569d3
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@ -0,0 +1,17 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
ij_typescript_use_double_quotes = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# 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
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
59
README.md
Normal file
59
README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# ClientApp
|
||||
|
||||
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.4.
|
||||
|
||||
## 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.
|
||||
149
angular.json
Normal file
149
angular.json
Normal file
@ -0,0 +1,149 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"client-app": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss",
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"skipTests": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"server": "src/main.server.ts",
|
||||
"outputMode": "server",
|
||||
"ssr": {
|
||||
"entry": "src/server.ts"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2MB",
|
||||
"maximumError": "3MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"options": {
|
||||
"allowedHosts": [
|
||||
"localhost",
|
||||
"client.alphaomegainfosys.com"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "client-app:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "client-app:build:development",
|
||||
"port": 5173
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular/build:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": "ea8b3ea5-8b3d-49fc-9322-afc880e9e04a"
|
||||
},
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"type": "component"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"type": "directive"
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"type": "service"
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:interceptor": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:module": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:resolver": {
|
||||
"typeSeparator": "."
|
||||
}
|
||||
}
|
||||
}
|
||||
9551
package-lock.json
generated
Normal file
9551
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
58
package.json
Normal file
58
package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "client-app",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"serve:ssr:client-app": "node dist/client-app/server/server.mjs"
|
||||
},
|
||||
"prettier": {
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.html",
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^20.0.4",
|
||||
"@angular/common": "^20.0.0",
|
||||
"@angular/compiler": "^20.0.0",
|
||||
"@angular/core": "^20.0.0",
|
||||
"@angular/forms": "^20.0.0",
|
||||
"@angular/material": "^20.0.4",
|
||||
"@angular/material-moment-adapter": "^20.0.4",
|
||||
"@angular/platform-browser": "^20.0.0",
|
||||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/router": "^20.0.0",
|
||||
"@angular/ssr": "^20.0.4",
|
||||
"chart.js": "^4.3.0",
|
||||
"express": "^5.1.0",
|
||||
"ng2-charts": "^8.0.0",
|
||||
"ngx-cookie-service": "^20.0.1",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "^20.0.4",
|
||||
"@angular/cli": "^20.0.4",
|
||||
"@angular/compiler-cli": "^20.0.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^20.17.19",
|
||||
"jasmine-core": "~5.7.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.8.2"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/logos/BoomerLogo.jpeg
Normal file
BIN
public/images/logos/BoomerLogo.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
BIN
public/images/logos/RoanLogo.jpeg
Normal file
BIN
public/images/logos/RoanLogo.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
BIN
public/images/logos/RockLogo.jpeg
Normal file
BIN
public/images/logos/RockLogo.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
public/images/logos/USCIBLogo.jpeg
Normal file
BIN
public/images/logos/USCIBLogo.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
236
public/themes/Theme187.css
Normal file
236
public/themes/Theme187.css
Normal file
@ -0,0 +1,236 @@
|
||||
/* Note: Color palettes are generated from primary: #607c7c, secondary: #9cb4b8 */
|
||||
html {
|
||||
/* COLOR SYSTEM VARIABLES */
|
||||
color-scheme: light;
|
||||
|
||||
/* Primary palette variables */
|
||||
--mat-sys-primary: light-dark(#486363, #afcccc);
|
||||
--mat-sys-on-primary: light-dark(#ffffff, #193535);
|
||||
--mat-sys-primary-container: light-dark(#cae8e8, #304b4c);
|
||||
--mat-sys-on-primary-container: light-dark(#022020, #cae8e8);
|
||||
--mat-sys-inverse-primary: light-dark(#afcccc, #486363);
|
||||
--mat-sys-primary-fixed: light-dark(#cae8e8, #cae8e8);
|
||||
--mat-sys-primary-fixed-dim: light-dark(#afcccc, #afcccc);
|
||||
--mat-sys-on-primary-fixed: light-dark(#022020, #022020);
|
||||
--mat-sys-on-primary-fixed-variant: light-dark(#304b4c, #304b4c);
|
||||
|
||||
/* Secondary palette variables */
|
||||
--mat-sys-secondary: light-dark(#4c6266, #b3cbcf);
|
||||
--mat-sys-on-secondary: light-dark(#ffffff, #1d3437);
|
||||
--mat-sys-secondary-container: light-dark(#cee7eb, #344a4e);
|
||||
--mat-sys-on-secondary-container: light-dark(#071f22, #cee7eb);
|
||||
--mat-sys-secondary-fixed: light-dark(#cee7eb, #cee7eb);
|
||||
--mat-sys-secondary-fixed-dim: light-dark(#b3cbcf, #b3cbcf);
|
||||
--mat-sys-on-secondary-fixed: light-dark(#071f22, #071f22);
|
||||
--mat-sys-on-secondary-fixed-variant: light-dark(#344a4e, #344a4e);
|
||||
|
||||
/* Tertiary palette variables */
|
||||
--mat-sys-tertiary: light-dark(#635b72, #cdc2dd);
|
||||
--mat-sys-on-tertiary: light-dark(#ffffff, #342d42);
|
||||
--mat-sys-tertiary-container: light-dark(#e9def9, #4b4359);
|
||||
--mat-sys-on-tertiary-container: light-dark(#1f182c, #e9def9);
|
||||
--mat-sys-tertiary-fixed: light-dark(#e9def9, #e9def9);
|
||||
--mat-sys-tertiary-fixed-dim: light-dark(#cdc2dd, #cdc2dd);
|
||||
--mat-sys-on-tertiary-fixed: light-dark(#1f182c, #1f182c);
|
||||
--mat-sys-on-tertiary-fixed-variant: light-dark(#4b4359, #4b4359);
|
||||
|
||||
/* Neutral palette variables */
|
||||
--mat-sys-background: light-dark(#faf9f8, #121414);
|
||||
--mat-sys-on-background: light-dark(#1a1c1c, #e3e2e2);
|
||||
--mat-sys-surface: light-dark(#faf9f8, #121414);
|
||||
--mat-sys-surface-dim: light-dark(#dadad9, #121414);
|
||||
--mat-sys-surface-bright: light-dark(#faf9f8, #383939);
|
||||
--mat-sys-surface-container-low: light-dark(#f4f3f3, #1a1c1c);
|
||||
--mat-sys-surface-container-lowest: light-dark(#ffffff, #0d0e0e);
|
||||
--mat-sys-surface-container: light-dark(#eeeeed, #1e2020);
|
||||
--mat-sys-surface-container-high: light-dark(#e9e8e7, #292a2a);
|
||||
--mat-sys-surface-container-highest: light-dark(#e3e2e2, #343535);
|
||||
--mat-sys-on-surface: light-dark(#1a1c1c, #e3e2e2);
|
||||
--mat-sys-shadow: light-dark(#000000, #000000);
|
||||
--mat-sys-scrim: light-dark(#000000, #000000);
|
||||
--mat-sys-surface-tint: light-dark(#486363, #afcccc);
|
||||
--mat-sys-inverse-surface: light-dark(#2f3130, #e3e2e2);
|
||||
--mat-sys-inverse-on-surface: light-dark(#f1f0f0, #2f3130);
|
||||
--mat-sys-outline: light-dark(#727878, #8b9292);
|
||||
--mat-sys-outline-variant: light-dark(#c1c8c7, #414848);
|
||||
--mat-sys-neutral10: light-dark(#1a1c1c, #1a1c1c); /* Variable used for the form field native select option text color */
|
||||
|
||||
/* Error palette variables */
|
||||
--mat-sys-error: light-dark(#ba1a1a, #ffb4ab);
|
||||
--mat-sys-on-error: light-dark(#ffffff, #690005);
|
||||
--mat-sys-error-container: light-dark(#ffdad6, #93000a);
|
||||
--mat-sys-on-error-container: light-dark(#410002, #ffdad6);
|
||||
|
||||
/* Neutral variant palette variables */
|
||||
--mat-sys-surface-variant: light-dark(#dde4e3, #414848);
|
||||
--mat-sys-on-surface-variant: light-dark(#414848, #c1c8c7);
|
||||
--mat-sys-neutral-variant20: light-dark(#2b3232, #2b3232); /* Variable used for the sidenav scrim (container background shadow when opened) */
|
||||
|
||||
/* TYPOGRAPHY SYSTEM VARIABLES */
|
||||
|
||||
/* Typography variables. Only used in the different typescale system variables. */
|
||||
--mat-sys-brand-font-family: Roboto; /* The font-family to use for brand text. */
|
||||
--mat-sys-plain-font-family: Roboto; /* The font-family to use for plain text. */
|
||||
--mat-sys-bold-font-weight: 700; /* The font-weight to use for bold text. */
|
||||
--mat-sys-medium-font-weight: 500; /* The font-weight to use for medium text. */
|
||||
--mat-sys-regular-font-weight: 400; /* The font-weight to use for regular text. */
|
||||
|
||||
/* Typescale variables. */
|
||||
/* Warning: Risk of reduced fidelity from using the composite typography tokens (ex. --mat-sys-body-large) since
|
||||
tracking cannot be represented in the "font" property shorthand. Consider using the discrete properties instead. */
|
||||
--mat-sys-body-large: var(--mat-sys-body-large-weight) var(--mat-sys-body-large-size) / var(--mat-sys-body-large-line-height) var(--mat-sys-body-large-font);
|
||||
--mat-sys-body-large-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-body-large-line-height: 1.5rem;
|
||||
--mat-sys-body-large-size: 1rem;
|
||||
--mat-sys-body-large-tracking: 0.031rem;
|
||||
--mat-sys-body-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Body medium typescale */
|
||||
--mat-sys-body-medium: var(--mat-sys-body-medium-weight) var(--mat-sys-body-medium-size) / var(--mat-sys-body-medium-line-height) var(--mat-sys-body-medium-font);
|
||||
--mat-sys-body-medium-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-body-medium-line-height: 1.25rem;
|
||||
--mat-sys-body-medium-size: 0.875rem;
|
||||
--mat-sys-body-medium-tracking: 0.016rem;
|
||||
--mat-sys-body-medium-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Body small typescale */
|
||||
--mat-sys-body-small: var(--mat-sys-body-small-weight) var(--mat-sys-body-small-size) / var(--mat-sys-body-small-line-height) var(--mat-sys-body-small-font);
|
||||
--mat-sys-body-small-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-body-small-line-height: 1rem;
|
||||
--mat-sys-body-small-size: 0.75rem;
|
||||
--mat-sys-body-small-tracking: 0.025rem;
|
||||
--mat-sys-body-small-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Display large typescale */
|
||||
--mat-sys-display-large: var(--mat-sys-display-large-weight) var(--mat-sys-display-large-size) / var(--mat-sys-display-large-line-height) var(--mat-sys-display-large-font);
|
||||
--mat-sys-display-large-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-display-large-line-height: 4rem;
|
||||
--mat-sys-display-large-size: 3.562rem;
|
||||
--mat-sys-display-large-tracking: -0.016rem;
|
||||
--mat-sys-display-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Display medium typescale */
|
||||
--mat-sys-display-medium: var(--mat-sys-display-medium-weight) var(--mat-sys-display-medium-size) / var(--mat-sys-display-medium-line-height) var(--mat-sys-display-medium-font);
|
||||
--mat-sys-display-medium-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-display-medium-line-height: 3.25rem;
|
||||
--mat-sys-display-medium-size: 2.812rem;
|
||||
--mat-sys-display-medium-tracking: 0;
|
||||
--mat-sys-display-medium-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Display small typescale */
|
||||
--mat-sys-display-small: var(--mat-sys-display-small-weight) var(--mat-sys-display-small-size) / var(--mat-sys-display-small-line-height) var(--mat-sys-display-small-font);
|
||||
--mat-sys-display-small-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-display-small-line-height: 2.75rem;
|
||||
--mat-sys-display-small-size: 2.25rem;
|
||||
--mat-sys-display-small-tracking: 0;
|
||||
--mat-sys-display-small-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Headline large typescale */
|
||||
--mat-sys-headline-large: var(--mat-sys-headline-large-weight) var(--mat-sys-headline-large-size) / var(--mat-sys-headline-large-line-height) var(--mat-sys-headline-large-font);
|
||||
--mat-sys-headline-large-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-headline-large-line-height: 2.5rem;
|
||||
--mat-sys-headline-large-size: 2rem;
|
||||
--mat-sys-headline-large-tracking: 0;
|
||||
--mat-sys-headline-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Headline medium typescale */
|
||||
--mat-sys-headline-medium: var(--mat-sys-headline-medium-weight) var(--mat-sys-headline-medium-size) / var(--mat-sys-headline-medium-line-height) var(--mat-sys-headline-medium-font);
|
||||
--mat-sys-headline-medium-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-headline-medium-line-height: 2.25rem;
|
||||
--mat-sys-headline-medium-size: 1.75rem;
|
||||
--mat-sys-headline-medium-tracking: 0;
|
||||
--mat-sys-headline-medium-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Headline small typescale */
|
||||
--mat-sys-headline-small: var(--mat-sys-headline-small-weight) var(--mat-sys-headline-small-size) / var(--mat-sys-headline-small-line-height) var(--mat-sys-headline-small-font);
|
||||
--mat-sys-headline-small-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-headline-small-line-height: 2rem;
|
||||
--mat-sys-headline-small-size: 1.5rem;
|
||||
--mat-sys-headline-small-tracking: 0;
|
||||
--mat-sys-headline-small-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Label large typescale */
|
||||
--mat-sys-label-large: var(--mat-sys-label-large-weight) var(--mat-sys-label-large-size) / var(--mat-sys-label-large-line-height) var(--mat-sys-label-large-font);
|
||||
--mat-sys-label-large-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-label-large-line-height: 1.25rem;
|
||||
--mat-sys-label-large-size: 0.875rem;
|
||||
--mat-sys-label-large-tracking: 0.006rem;
|
||||
--mat-sys-label-large-weight: var(--mat-sys-medium-font-weight);
|
||||
--mat-sys-label-large-weight-prominent: var(--mat-sys-bold-font-weight);
|
||||
|
||||
/* Label medium typescale */
|
||||
--mat-sys-label-medium: var(--mat-sys-label-medium-weight) var(--mat-sys-label-medium-size) / var(--mat-sys-label-medium-line-height) var(--mat-sys-label-medium-font);
|
||||
--mat-sys-label-medium-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-label-medium-line-height: 1rem;
|
||||
--mat-sys-label-medium-size: 0.75rem;
|
||||
--mat-sys-label-medium-tracking: 0.031rem;
|
||||
--mat-sys-label-medium-weight: var(--mat-sys-medium-font-weight);
|
||||
--mat-sys-label-medium-weight-prominent: var(--mat-sys-bold-font-weight);
|
||||
|
||||
/* Label small typescale */
|
||||
--mat-sys-label-small: var(--mat-sys-label-small-weight) var(--mat-sys-label-small-size) / var(--mat-sys-label-small-line-height) var(--mat-sys-label-small-font);
|
||||
--mat-sys-label-small-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-label-small-line-height: 1rem;
|
||||
--mat-sys-label-small-size: 0.688rem;
|
||||
--mat-sys-label-small-tracking: 0.031rem;
|
||||
--mat-sys-label-small-weight: var(--mat-sys-medium-font-weight);
|
||||
|
||||
/* Title large typescale */
|
||||
--mat-sys-title-large: var(--mat-sys-title-large-weight) var(--mat-sys-title-large-size) / var(--mat-sys-title-large-line-height) var(--mat-sys-title-large-font);
|
||||
--mat-sys-title-large-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-title-large-line-height: 1.75rem;
|
||||
--mat-sys-title-large-size: 1.375rem;
|
||||
--mat-sys-title-large-tracking: 0;
|
||||
--mat-sys-title-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Title medium typescale */
|
||||
--mat-sys-title-medium: var(--mat-sys-title-medium-weight) var(--mat-sys-title-medium-size) / var(--mat-sys-title-medium-line-height) var(--mat-sys-title-medium-font);
|
||||
--mat-sys-title-medium-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-title-medium-line-height: 1.5rem;
|
||||
--mat-sys-title-medium-size: 1rem;
|
||||
--mat-sys-title-medium-tracking: 0.009rem;
|
||||
--mat-sys-title-medium-weight: var(--mat-sys-medium-font-weight);
|
||||
|
||||
/* Title small typescale */
|
||||
--mat-sys-title-small: var(--mat-sys-title-small-weight) var(--mat-sys-title-small-size) / var(--mat-sys-title-small-line-height) var(--mat-sys-title-small-font);
|
||||
--mat-sys-title-small-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-title-small-line-height: 1.25rem;
|
||||
--mat-sys-title-small-size: 0.875rem;
|
||||
--mat-sys-title-small-tracking: 0.006rem;
|
||||
--mat-sys-title-small-weight: var(--mat-sys-medium-font-weight);
|
||||
|
||||
/* ELEVATION SYSTEM VARIABLES */
|
||||
|
||||
/* Box shadow colors. Only used in the elevation level system variables. */
|
||||
--mat-sys-umbra-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 80%);
|
||||
--mat-sys-penumbra-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 86%);
|
||||
--mat-sys-ambient-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 88%);
|
||||
|
||||
/* Elevation level system variables. These are used as the value for box-shadow CSS property. */
|
||||
--mat-sys-level0: 0px 0px 0px 0px var(--mat-sys-umbra-color), 0px 0px 0px 0px var(--mat-sys-penumbra-color), 0px 0px 0px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level1: 0px 2px 1px -1px var(--mat-sys-umbra-color), 0px 1px 1px 0px var(--mat-sys-penumbra-color), 0px 1px 3px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level2: 0px 3px 3px -2px var(--mat-sys-umbra-color), 0px 3px 4px 0px var(--mat-sys-penumbra-color), 0px 1px 8px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level3: 0px 3px 5px -1px var(--mat-sys-umbra-color), 0px 6px 10px 0px var(--mat-sys-penumbra-color), 0px 1px 18px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level4: 0px 5px 5px -3px var(--mat-sys-umbra-color), 0px 8px 10px 1px var(--mat-sys-penumbra-color), 0px 3px 14px 2px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level5: 0px 7px 8px -4px var(--mat-sys-umbra-color), 0px 12px 17px 2px var(--mat-sys-penumbra-color), 0px 5px 22px 4px var(--mat-sys-ambient-color);
|
||||
|
||||
/* SHAPE SYSTEM VARIABLES */
|
||||
--mat-sys-corner-extra-large: 28px;
|
||||
--mat-sys-corner-extra-large-top: 28px 28px 0 0;
|
||||
--mat-sys-corner-extra-small: 4px;
|
||||
--mat-sys-corner-extra-small-top: 4px 4px 0 0;
|
||||
--mat-sys-corner-full: 9999px;
|
||||
--mat-sys-corner-large: 16px;
|
||||
--mat-sys-corner-large-end: 0 16px 16px 0;
|
||||
--mat-sys-corner-large-start: 16px 0 0 16px;
|
||||
--mat-sys-corner-large-top: 16px 16px 0 0;
|
||||
--mat-sys-corner-medium: 12px;
|
||||
--mat-sys-corner-none: 0;
|
||||
--mat-sys-corner-small: 8px;
|
||||
|
||||
/* STATE SYSTEM VARIABLES */
|
||||
--mat-sys-dragged-state-layer-opacity: 0.16;
|
||||
--mat-sys-focus-state-layer-opacity: 0.12;
|
||||
--mat-sys-hover-state-layer-opacity: 0.08;
|
||||
--mat-sys-pressed-state-layer-opacity: 0.12;
|
||||
}
|
||||
236
public/themes/Theme188.css
Normal file
236
public/themes/Theme188.css
Normal file
@ -0,0 +1,236 @@
|
||||
/* Note: Color palettes are generated from primary: #103454, secondary: #18749c */
|
||||
html {
|
||||
/* COLOR SYSTEM VARIABLES */
|
||||
color-scheme: light;
|
||||
|
||||
/* Primary palette variables */
|
||||
--mat-sys-primary: light-dark(#416183, #aac9f1);
|
||||
--mat-sys-on-primary: light-dark(#ffffff, #0d3252);
|
||||
--mat-sys-primary-container: light-dark(#d1e4ff, #29496a);
|
||||
--mat-sys-on-primary-container: light-dark(#001d35, #d1e4ff);
|
||||
--mat-sys-inverse-primary: light-dark(#aac9f1, #416183);
|
||||
--mat-sys-primary-fixed: light-dark(#d1e4ff, #d1e4ff);
|
||||
--mat-sys-primary-fixed-dim: light-dark(#aac9f1, #aac9f1);
|
||||
--mat-sys-on-primary-fixed: light-dark(#001d35, #001d35);
|
||||
--mat-sys-on-primary-fixed-variant: light-dark(#29496a, #29496a);
|
||||
|
||||
/* Secondary palette variables */
|
||||
--mat-sys-secondary: light-dark(#00658b, #84cffc);
|
||||
--mat-sys-on-secondary: light-dark(#ffffff, #00344a);
|
||||
--mat-sys-secondary-container: light-dark(#c5e7ff, #004c6a);
|
||||
--mat-sys-on-secondary-container: light-dark(#001e2d, #c5e7ff);
|
||||
--mat-sys-secondary-fixed: light-dark(#c5e7ff, #c5e7ff);
|
||||
--mat-sys-secondary-fixed-dim: light-dark(#84cffc, #84cffc);
|
||||
--mat-sys-on-secondary-fixed: light-dark(#001e2d, #001e2d);
|
||||
--mat-sys-on-secondary-fixed-variant: light-dark(#004c6a, #004c6a);
|
||||
|
||||
/* Tertiary palette variables */
|
||||
--mat-sys-tertiary: light-dark(#73537b, #e1b9e8);
|
||||
--mat-sys-on-tertiary: light-dark(#ffffff, #42254a);
|
||||
--mat-sys-tertiary-container: light-dark(#fbd7ff, #5a3b62);
|
||||
--mat-sys-on-tertiary-container: light-dark(#2b1034, #fbd7ff);
|
||||
--mat-sys-tertiary-fixed: light-dark(#fbd7ff, #fbd7ff);
|
||||
--mat-sys-tertiary-fixed-dim: light-dark(#e1b9e8, #e1b9e8);
|
||||
--mat-sys-on-tertiary-fixed: light-dark(#2b1034, #2b1034);
|
||||
--mat-sys-on-tertiary-fixed-variant: light-dark(#5a3b62, #5a3b62);
|
||||
|
||||
/* Neutral palette variables */
|
||||
--mat-sys-background: light-dark(#faf9fc, #121316);
|
||||
--mat-sys-on-background: light-dark(#1a1c1e, #e3e2e5);
|
||||
--mat-sys-surface: light-dark(#faf9fc, #121316);
|
||||
--mat-sys-surface-dim: light-dark(#dad9dd, #121316);
|
||||
--mat-sys-surface-bright: light-dark(#faf9fc, #38393c);
|
||||
--mat-sys-surface-container-low: light-dark(#f4f3f6, #1a1c1e);
|
||||
--mat-sys-surface-container-lowest: light-dark(#ffffff, #0d0e11);
|
||||
--mat-sys-surface-container: light-dark(#eeedf0, #1e2022);
|
||||
--mat-sys-surface-container-high: light-dark(#e8e8eb, #292a2c);
|
||||
--mat-sys-surface-container-highest: light-dark(#e3e2e5, #333537);
|
||||
--mat-sys-on-surface: light-dark(#1a1c1e, #e3e2e5);
|
||||
--mat-sys-shadow: light-dark(#000000, #000000);
|
||||
--mat-sys-scrim: light-dark(#000000, #000000);
|
||||
--mat-sys-surface-tint: light-dark(#416183, #aac9f1);
|
||||
--mat-sys-inverse-surface: light-dark(#2f3033, #e3e2e5);
|
||||
--mat-sys-inverse-on-surface: light-dark(#f1f0f3, #2f3033);
|
||||
--mat-sys-outline: light-dark(#73777e, #8d9198);
|
||||
--mat-sys-outline-variant: light-dark(#c3c7ce, #43474e);
|
||||
--mat-sys-neutral10: light-dark(#1a1c1e, #1a1c1e); /* Variable used for the form field native select option text color */
|
||||
|
||||
/* Error palette variables */
|
||||
--mat-sys-error: light-dark(#ba1a1a, #ffb4ab);
|
||||
--mat-sys-on-error: light-dark(#ffffff, #690005);
|
||||
--mat-sys-error-container: light-dark(#ffdad6, #93000a);
|
||||
--mat-sys-on-error-container: light-dark(#410002, #ffdad6);
|
||||
|
||||
/* Neutral variant palette variables */
|
||||
--mat-sys-surface-variant: light-dark(#dfe2eb, #43474e);
|
||||
--mat-sys-on-surface-variant: light-dark(#43474e, #c3c7ce);
|
||||
--mat-sys-neutral-variant20: light-dark(#2c3137, #2c3137); /* Variable used for the sidenav scrim (container background shadow when opened) */
|
||||
|
||||
/* TYPOGRAPHY SYSTEM VARIABLES */
|
||||
|
||||
/* Typography variables. Only used in the different typescale system variables. */
|
||||
--mat-sys-brand-font-family: Roboto; /* The font-family to use for brand text. */
|
||||
--mat-sys-plain-font-family: Roboto; /* The font-family to use for plain text. */
|
||||
--mat-sys-bold-font-weight: 700; /* The font-weight to use for bold text. */
|
||||
--mat-sys-medium-font-weight: 500; /* The font-weight to use for medium text. */
|
||||
--mat-sys-regular-font-weight: 400; /* The font-weight to use for regular text. */
|
||||
|
||||
/* Typescale variables. */
|
||||
/* Warning: Risk of reduced fidelity from using the composite typography tokens (ex. --mat-sys-body-large) since
|
||||
tracking cannot be represented in the "font" property shorthand. Consider using the discrete properties instead. */
|
||||
--mat-sys-body-large: var(--mat-sys-body-large-weight) var(--mat-sys-body-large-size) / var(--mat-sys-body-large-line-height) var(--mat-sys-body-large-font);
|
||||
--mat-sys-body-large-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-body-large-line-height: 1.5rem;
|
||||
--mat-sys-body-large-size: 1rem;
|
||||
--mat-sys-body-large-tracking: 0.031rem;
|
||||
--mat-sys-body-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Body medium typescale */
|
||||
--mat-sys-body-medium: var(--mat-sys-body-medium-weight) var(--mat-sys-body-medium-size) / var(--mat-sys-body-medium-line-height) var(--mat-sys-body-medium-font);
|
||||
--mat-sys-body-medium-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-body-medium-line-height: 1.25rem;
|
||||
--mat-sys-body-medium-size: 0.875rem;
|
||||
--mat-sys-body-medium-tracking: 0.016rem;
|
||||
--mat-sys-body-medium-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Body small typescale */
|
||||
--mat-sys-body-small: var(--mat-sys-body-small-weight) var(--mat-sys-body-small-size) / var(--mat-sys-body-small-line-height) var(--mat-sys-body-small-font);
|
||||
--mat-sys-body-small-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-body-small-line-height: 1rem;
|
||||
--mat-sys-body-small-size: 0.75rem;
|
||||
--mat-sys-body-small-tracking: 0.025rem;
|
||||
--mat-sys-body-small-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Display large typescale */
|
||||
--mat-sys-display-large: var(--mat-sys-display-large-weight) var(--mat-sys-display-large-size) / var(--mat-sys-display-large-line-height) var(--mat-sys-display-large-font);
|
||||
--mat-sys-display-large-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-display-large-line-height: 4rem;
|
||||
--mat-sys-display-large-size: 3.562rem;
|
||||
--mat-sys-display-large-tracking: -0.016rem;
|
||||
--mat-sys-display-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Display medium typescale */
|
||||
--mat-sys-display-medium: var(--mat-sys-display-medium-weight) var(--mat-sys-display-medium-size) / var(--mat-sys-display-medium-line-height) var(--mat-sys-display-medium-font);
|
||||
--mat-sys-display-medium-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-display-medium-line-height: 3.25rem;
|
||||
--mat-sys-display-medium-size: 2.812rem;
|
||||
--mat-sys-display-medium-tracking: 0;
|
||||
--mat-sys-display-medium-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Display small typescale */
|
||||
--mat-sys-display-small: var(--mat-sys-display-small-weight) var(--mat-sys-display-small-size) / var(--mat-sys-display-small-line-height) var(--mat-sys-display-small-font);
|
||||
--mat-sys-display-small-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-display-small-line-height: 2.75rem;
|
||||
--mat-sys-display-small-size: 2.25rem;
|
||||
--mat-sys-display-small-tracking: 0;
|
||||
--mat-sys-display-small-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Headline large typescale */
|
||||
--mat-sys-headline-large: var(--mat-sys-headline-large-weight) var(--mat-sys-headline-large-size) / var(--mat-sys-headline-large-line-height) var(--mat-sys-headline-large-font);
|
||||
--mat-sys-headline-large-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-headline-large-line-height: 2.5rem;
|
||||
--mat-sys-headline-large-size: 2rem;
|
||||
--mat-sys-headline-large-tracking: 0;
|
||||
--mat-sys-headline-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Headline medium typescale */
|
||||
--mat-sys-headline-medium: var(--mat-sys-headline-medium-weight) var(--mat-sys-headline-medium-size) / var(--mat-sys-headline-medium-line-height) var(--mat-sys-headline-medium-font);
|
||||
--mat-sys-headline-medium-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-headline-medium-line-height: 2.25rem;
|
||||
--mat-sys-headline-medium-size: 1.75rem;
|
||||
--mat-sys-headline-medium-tracking: 0;
|
||||
--mat-sys-headline-medium-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Headline small typescale */
|
||||
--mat-sys-headline-small: var(--mat-sys-headline-small-weight) var(--mat-sys-headline-small-size) / var(--mat-sys-headline-small-line-height) var(--mat-sys-headline-small-font);
|
||||
--mat-sys-headline-small-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-headline-small-line-height: 2rem;
|
||||
--mat-sys-headline-small-size: 1.5rem;
|
||||
--mat-sys-headline-small-tracking: 0;
|
||||
--mat-sys-headline-small-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Label large typescale */
|
||||
--mat-sys-label-large: var(--mat-sys-label-large-weight) var(--mat-sys-label-large-size) / var(--mat-sys-label-large-line-height) var(--mat-sys-label-large-font);
|
||||
--mat-sys-label-large-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-label-large-line-height: 1.25rem;
|
||||
--mat-sys-label-large-size: 0.875rem;
|
||||
--mat-sys-label-large-tracking: 0.006rem;
|
||||
--mat-sys-label-large-weight: var(--mat-sys-medium-font-weight);
|
||||
--mat-sys-label-large-weight-prominent: var(--mat-sys-bold-font-weight);
|
||||
|
||||
/* Label medium typescale */
|
||||
--mat-sys-label-medium: var(--mat-sys-label-medium-weight) var(--mat-sys-label-medium-size) / var(--mat-sys-label-medium-line-height) var(--mat-sys-label-medium-font);
|
||||
--mat-sys-label-medium-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-label-medium-line-height: 1rem;
|
||||
--mat-sys-label-medium-size: 0.75rem;
|
||||
--mat-sys-label-medium-tracking: 0.031rem;
|
||||
--mat-sys-label-medium-weight: var(--mat-sys-medium-font-weight);
|
||||
--mat-sys-label-medium-weight-prominent: var(--mat-sys-bold-font-weight);
|
||||
|
||||
/* Label small typescale */
|
||||
--mat-sys-label-small: var(--mat-sys-label-small-weight) var(--mat-sys-label-small-size) / var(--mat-sys-label-small-line-height) var(--mat-sys-label-small-font);
|
||||
--mat-sys-label-small-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-label-small-line-height: 1rem;
|
||||
--mat-sys-label-small-size: 0.688rem;
|
||||
--mat-sys-label-small-tracking: 0.031rem;
|
||||
--mat-sys-label-small-weight: var(--mat-sys-medium-font-weight);
|
||||
|
||||
/* Title large typescale */
|
||||
--mat-sys-title-large: var(--mat-sys-title-large-weight) var(--mat-sys-title-large-size) / var(--mat-sys-title-large-line-height) var(--mat-sys-title-large-font);
|
||||
--mat-sys-title-large-font: var(--mat-sys-brand-font-family);
|
||||
--mat-sys-title-large-line-height: 1.75rem;
|
||||
--mat-sys-title-large-size: 1.375rem;
|
||||
--mat-sys-title-large-tracking: 0;
|
||||
--mat-sys-title-large-weight: var(--mat-sys-regular-font-weight);
|
||||
|
||||
/* Title medium typescale */
|
||||
--mat-sys-title-medium: var(--mat-sys-title-medium-weight) var(--mat-sys-title-medium-size) / var(--mat-sys-title-medium-line-height) var(--mat-sys-title-medium-font);
|
||||
--mat-sys-title-medium-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-title-medium-line-height: 1.5rem;
|
||||
--mat-sys-title-medium-size: 1rem;
|
||||
--mat-sys-title-medium-tracking: 0.009rem;
|
||||
--mat-sys-title-medium-weight: var(--mat-sys-medium-font-weight);
|
||||
|
||||
/* Title small typescale */
|
||||
--mat-sys-title-small: var(--mat-sys-title-small-weight) var(--mat-sys-title-small-size) / var(--mat-sys-title-small-line-height) var(--mat-sys-title-small-font);
|
||||
--mat-sys-title-small-font: var(--mat-sys-plain-font-family);
|
||||
--mat-sys-title-small-line-height: 1.25rem;
|
||||
--mat-sys-title-small-size: 0.875rem;
|
||||
--mat-sys-title-small-tracking: 0.006rem;
|
||||
--mat-sys-title-small-weight: var(--mat-sys-medium-font-weight);
|
||||
|
||||
/* ELEVATION SYSTEM VARIABLES */
|
||||
|
||||
/* Box shadow colors. Only used in the elevation level system variables. */
|
||||
--mat-sys-umbra-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 80%);
|
||||
--mat-sys-penumbra-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 86%);
|
||||
--mat-sys-ambient-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 88%);
|
||||
|
||||
/* Elevation level system variables. These are used as the value for box-shadow CSS property. */
|
||||
--mat-sys-level0: 0px 0px 0px 0px var(--mat-sys-umbra-color), 0px 0px 0px 0px var(--mat-sys-penumbra-color), 0px 0px 0px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level1: 0px 2px 1px -1px var(--mat-sys-umbra-color), 0px 1px 1px 0px var(--mat-sys-penumbra-color), 0px 1px 3px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level2: 0px 3px 3px -2px var(--mat-sys-umbra-color), 0px 3px 4px 0px var(--mat-sys-penumbra-color), 0px 1px 8px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level3: 0px 3px 5px -1px var(--mat-sys-umbra-color), 0px 6px 10px 0px var(--mat-sys-penumbra-color), 0px 1px 18px 0px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level4: 0px 5px 5px -3px var(--mat-sys-umbra-color), 0px 8px 10px 1px var(--mat-sys-penumbra-color), 0px 3px 14px 2px var(--mat-sys-ambient-color);
|
||||
--mat-sys-level5: 0px 7px 8px -4px var(--mat-sys-umbra-color), 0px 12px 17px 2px var(--mat-sys-penumbra-color), 0px 5px 22px 4px var(--mat-sys-ambient-color);
|
||||
|
||||
/* SHAPE SYSTEM VARIABLES */
|
||||
--mat-sys-corner-extra-large: 28px;
|
||||
--mat-sys-corner-extra-large-top: 28px 28px 0 0;
|
||||
--mat-sys-corner-extra-small: 4px;
|
||||
--mat-sys-corner-extra-small-top: 4px 4px 0 0;
|
||||
--mat-sys-corner-full: 9999px;
|
||||
--mat-sys-corner-large: 16px;
|
||||
--mat-sys-corner-large-end: 0 16px 16px 0;
|
||||
--mat-sys-corner-large-start: 16px 0 0 16px;
|
||||
--mat-sys-corner-large-top: 16px 16px 0 0;
|
||||
--mat-sys-corner-medium: 12px;
|
||||
--mat-sys-corner-none: 0;
|
||||
--mat-sys-corner-small: 8px;
|
||||
|
||||
/* STATE SYSTEM VARIABLES */
|
||||
--mat-sys-dragged-state-layer-opacity: 0.16;
|
||||
--mat-sys-focus-state-layer-opacity: 0.12;
|
||||
--mat-sys-hover-state-layer-opacity: 0.08;
|
||||
--mat-sys-pressed-state-layer-opacity: 0.12;
|
||||
}
|
||||
195
public/themes/default.css
Normal file
195
public/themes/default.css
Normal file
@ -0,0 +1,195 @@
|
||||
/* Note: Color palettes copied from node_modules\@angular\material\prebuilt-themes\azure-blue.css */
|
||||
html {
|
||||
--mat-sys-background: #faf9fd;
|
||||
--mat-sys-error: #ba1a1a;
|
||||
--mat-sys-error-container: #ffdad6;
|
||||
--mat-sys-inverse-on-surface: #f2f0f4;
|
||||
--mat-sys-inverse-primary: #abc7ff;
|
||||
--mat-sys-inverse-surface: #2f3033;
|
||||
--mat-sys-on-background: #1a1b1f;
|
||||
--mat-sys-on-error: #ffffff;
|
||||
--mat-sys-on-error-container: #93000a;
|
||||
--mat-sys-on-primary: #ffffff;
|
||||
--mat-sys-on-primary-container: #00458f;
|
||||
--mat-sys-on-primary-fixed: #001b3f;
|
||||
--mat-sys-on-primary-fixed-variant: #00458f;
|
||||
--mat-sys-on-secondary: #ffffff;
|
||||
--mat-sys-on-secondary-container: #3e4759;
|
||||
--mat-sys-on-secondary-fixed: #131c2b;
|
||||
--mat-sys-on-secondary-fixed-variant: #3e4759;
|
||||
--mat-sys-on-surface: #1a1b1f;
|
||||
--mat-sys-on-surface-variant: #44474e;
|
||||
--mat-sys-on-tertiary: #ffffff;
|
||||
--mat-sys-on-tertiary-container: #0000ef;
|
||||
--mat-sys-on-tertiary-fixed: #00006e;
|
||||
--mat-sys-on-tertiary-fixed-variant: #0000ef;
|
||||
--mat-sys-outline: #74777f;
|
||||
--mat-sys-outline-variant: #c4c6d0;
|
||||
--mat-sys-primary: #005cbb;
|
||||
--mat-sys-primary-container: #d7e3ff;
|
||||
--mat-sys-primary-fixed: #d7e3ff;
|
||||
--mat-sys-primary-fixed-dim: #abc7ff;
|
||||
--mat-sys-scrim: #000000;
|
||||
--mat-sys-secondary: #565e71;
|
||||
--mat-sys-secondary-container: #dae2f9;
|
||||
--mat-sys-secondary-fixed: #dae2f9;
|
||||
--mat-sys-secondary-fixed-dim: #bec6dc;
|
||||
--mat-sys-shadow: #000000;
|
||||
--mat-sys-surface: #faf9fd;
|
||||
--mat-sys-surface-bright: #faf9fd;
|
||||
--mat-sys-surface-container: #efedf0;
|
||||
--mat-sys-surface-container-high: #e9e7eb;
|
||||
--mat-sys-surface-container-highest: #e3e2e6;
|
||||
--mat-sys-surface-container-low: #f4f3f6;
|
||||
--mat-sys-surface-container-lowest: #ffffff;
|
||||
--mat-sys-surface-dim: #dbd9dd;
|
||||
--mat-sys-surface-tint: #005cbb;
|
||||
--mat-sys-surface-variant: #e0e2ec;
|
||||
--mat-sys-tertiary: #343dff;
|
||||
--mat-sys-tertiary-container: #e0e0ff;
|
||||
--mat-sys-tertiary-fixed: #e0e0ff;
|
||||
--mat-sys-tertiary-fixed-dim: #bec2ff;
|
||||
--mat-sys-neutral-variant20: #2d3038;
|
||||
--mat-sys-neutral10: #1a1b1f
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-level0: 0px 0px 0px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 0px rgba(0, 0, 0, 0.14), 0px 0px 0px 0px rgba(0, 0, 0, 0.12)
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-level1: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-level2: 0px 3px 3px -2px rgba(0, 0, 0, 0.2), 0px 3px 4px 0px rgba(0, 0, 0, 0.14), 0px 1px 8px 0px rgba(0, 0, 0, 0.12)
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-level3: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-level4: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12)
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-level5: 0px 7px 8px -4px rgba(0, 0, 0, 0.2), 0px 12px 17px 2px rgba(0, 0, 0, 0.14), 0px 5px 22px 4px rgba(0, 0, 0, 0.12)
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-body-large: 400 1rem / 1.5rem Roboto;
|
||||
--mat-sys-body-large-font: Roboto;
|
||||
--mat-sys-body-large-line-height: 1.5rem;
|
||||
--mat-sys-body-large-size: 1rem;
|
||||
--mat-sys-body-large-tracking: 0.031rem;
|
||||
--mat-sys-body-large-weight: 400;
|
||||
--mat-sys-body-medium: 400 0.875rem / 1.25rem Roboto;
|
||||
--mat-sys-body-medium-font: Roboto;
|
||||
--mat-sys-body-medium-line-height: 1.25rem;
|
||||
--mat-sys-body-medium-size: 0.875rem;
|
||||
--mat-sys-body-medium-tracking: 0.016rem;
|
||||
--mat-sys-body-medium-weight: 400;
|
||||
--mat-sys-body-small: 400 0.75rem / 1rem Roboto;
|
||||
--mat-sys-body-small-font: Roboto;
|
||||
--mat-sys-body-small-line-height: 1rem;
|
||||
--mat-sys-body-small-size: 0.75rem;
|
||||
--mat-sys-body-small-tracking: 0.025rem;
|
||||
--mat-sys-body-small-weight: 400;
|
||||
--mat-sys-display-large: 400 3.562rem / 4rem Roboto;
|
||||
--mat-sys-display-large-font: Roboto;
|
||||
--mat-sys-display-large-line-height: 4rem;
|
||||
--mat-sys-display-large-size: 3.562rem;
|
||||
--mat-sys-display-large-tracking: -0.016rem;
|
||||
--mat-sys-display-large-weight: 400;
|
||||
--mat-sys-display-medium: 400 2.812rem / 3.25rem Roboto;
|
||||
--mat-sys-display-medium-font: Roboto;
|
||||
--mat-sys-display-medium-line-height: 3.25rem;
|
||||
--mat-sys-display-medium-size: 2.812rem;
|
||||
--mat-sys-display-medium-tracking: 0;
|
||||
--mat-sys-display-medium-weight: 400;
|
||||
--mat-sys-display-small: 400 2.25rem / 2.75rem Roboto;
|
||||
--mat-sys-display-small-font: Roboto;
|
||||
--mat-sys-display-small-line-height: 2.75rem;
|
||||
--mat-sys-display-small-size: 2.25rem;
|
||||
--mat-sys-display-small-tracking: 0;
|
||||
--mat-sys-display-small-weight: 400;
|
||||
--mat-sys-headline-large: 400 2rem / 2.5rem Roboto;
|
||||
--mat-sys-headline-large-font: Roboto;
|
||||
--mat-sys-headline-large-line-height: 2.5rem;
|
||||
--mat-sys-headline-large-size: 2rem;
|
||||
--mat-sys-headline-large-tracking: 0;
|
||||
--mat-sys-headline-large-weight: 400;
|
||||
--mat-sys-headline-medium: 400 1.75rem / 2.25rem Roboto;
|
||||
--mat-sys-headline-medium-font: Roboto;
|
||||
--mat-sys-headline-medium-line-height: 2.25rem;
|
||||
--mat-sys-headline-medium-size: 1.75rem;
|
||||
--mat-sys-headline-medium-tracking: 0;
|
||||
--mat-sys-headline-medium-weight: 400;
|
||||
--mat-sys-headline-small: 400 1.5rem / 2rem Roboto;
|
||||
--mat-sys-headline-small-font: Roboto;
|
||||
--mat-sys-headline-small-line-height: 2rem;
|
||||
--mat-sys-headline-small-size: 1.5rem;
|
||||
--mat-sys-headline-small-tracking: 0;
|
||||
--mat-sys-headline-small-weight: 400;
|
||||
--mat-sys-label-large: 500 0.875rem / 1.25rem Roboto;
|
||||
--mat-sys-label-large-font: Roboto;
|
||||
--mat-sys-label-large-line-height: 1.25rem;
|
||||
--mat-sys-label-large-size: 0.875rem;
|
||||
--mat-sys-label-large-tracking: 0.006rem;
|
||||
--mat-sys-label-large-weight: 500;
|
||||
--mat-sys-label-large-weight-prominent: 700;
|
||||
--mat-sys-label-medium: 500 0.75rem / 1rem Roboto;
|
||||
--mat-sys-label-medium-font: Roboto;
|
||||
--mat-sys-label-medium-line-height: 1rem;
|
||||
--mat-sys-label-medium-size: 0.75rem;
|
||||
--mat-sys-label-medium-tracking: 0.031rem;
|
||||
--mat-sys-label-medium-weight: 500;
|
||||
--mat-sys-label-medium-weight-prominent: 700;
|
||||
--mat-sys-label-small: 500 0.688rem / 1rem Roboto;
|
||||
--mat-sys-label-small-font: Roboto;
|
||||
--mat-sys-label-small-line-height: 1rem;
|
||||
--mat-sys-label-small-size: 0.688rem;
|
||||
--mat-sys-label-small-tracking: 0.031rem;
|
||||
--mat-sys-label-small-weight: 500;
|
||||
--mat-sys-title-large: 400 1.375rem / 1.75rem Roboto;
|
||||
--mat-sys-title-large-font: Roboto;
|
||||
--mat-sys-title-large-line-height: 1.75rem;
|
||||
--mat-sys-title-large-size: 1.375rem;
|
||||
--mat-sys-title-large-tracking: 0;
|
||||
--mat-sys-title-large-weight: 400;
|
||||
--mat-sys-title-medium: 500 1rem / 1.5rem Roboto;
|
||||
--mat-sys-title-medium-font: Roboto;
|
||||
--mat-sys-title-medium-line-height: 1.5rem;
|
||||
--mat-sys-title-medium-size: 1rem;
|
||||
--mat-sys-title-medium-tracking: 0.009rem;
|
||||
--mat-sys-title-medium-weight: 500;
|
||||
--mat-sys-title-small: 500 0.875rem / 1.25rem Roboto;
|
||||
--mat-sys-title-small-font: Roboto;
|
||||
--mat-sys-title-small-line-height: 1.25rem;
|
||||
--mat-sys-title-small-size: 0.875rem;
|
||||
--mat-sys-title-small-tracking: 0.006rem;
|
||||
--mat-sys-title-small-weight: 500
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-corner-extra-large: 28px;
|
||||
--mat-sys-corner-extra-large-top: 28px 28px 0 0;
|
||||
--mat-sys-corner-extra-small: 4px;
|
||||
--mat-sys-corner-extra-small-top: 4px 4px 0 0;
|
||||
--mat-sys-corner-full: 9999px;
|
||||
--mat-sys-corner-large: 16px;
|
||||
--mat-sys-corner-large-end: 0 16px 16px 0;
|
||||
--mat-sys-corner-large-start: 16px 0 0 16px;
|
||||
--mat-sys-corner-large-top: 16px 16px 0 0;
|
||||
--mat-sys-corner-medium: 12px;
|
||||
--mat-sys-corner-none: 0;
|
||||
--mat-sys-corner-small: 8px
|
||||
}
|
||||
|
||||
html {
|
||||
--mat-sys-dragged-state-layer-opacity: 0.16;
|
||||
--mat-sys-focus-state-layer-opacity: 0.12;
|
||||
--mat-sys-hover-state-layer-opacity: 0.08;
|
||||
--mat-sys-pressed-state-layer-opacity: 0.12
|
||||
}
|
||||
7
src/app/app.component.html
Normal file
7
src/app/app.component.html
Normal file
@ -0,0 +1,7 @@
|
||||
<app-secured-header *ngIf="isUserLoggedIn"></app-secured-header>
|
||||
|
||||
<main class="main-content">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<app-footer></app-footer>
|
||||
22
src/app/app.component.scss
Normal file
22
src/app/app.component.scss
Normal file
@ -0,0 +1,22 @@
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
position: relative; // Needed for footer positioning
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
max-width: 90%;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.main-content {
|
||||
padding: 16px;
|
||||
padding-bottom: 80px; // Maintain bottom padding on mobile
|
||||
}
|
||||
}
|
||||
29
src/app/app.component.spec.ts
Normal file
29
src/app/app.component.spec.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'service-provider-app' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('service-provider-app');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, service-provider-app');
|
||||
});
|
||||
});
|
||||
37
src/app/app.component.ts
Normal file
37
src/app/app.component.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FooterComponent } from './common/footer/footer.component';
|
||||
import { SecuredHeaderComponent } from './common/secured-header/secured-header.component';
|
||||
import { UserService } from './core/services/common/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet, FooterComponent, CommonModule, SecuredHeaderComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
})
|
||||
export class AppComponent {
|
||||
isUserLoggedIn = false;
|
||||
private userSubscription!: Subscription;
|
||||
|
||||
private userService = inject(UserService);
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.isUserLoggedIn = this.userService.isLoggedIn();
|
||||
|
||||
this.userSubscription = this.userService
|
||||
.watchUser()
|
||||
.subscribe(userLoggedIn => {
|
||||
this.isUserLoggedIn = userLoggedIn
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.userSubscription) {
|
||||
this.userSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/app/app.config.server.ts
Normal file
12
src/app/app.config.server.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||
import { provideServerRendering, withRoutes } from '@angular/ssr';
|
||||
import { appConfig } from './app.config';
|
||||
import { serverRoutes } from './app.routes.server';
|
||||
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServerRendering(withRoutes(serverRoutes))
|
||||
]
|
||||
};
|
||||
|
||||
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||
20
src/app/app.config.ts
Normal file
20
src/app/app.config.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||
import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
|
||||
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
import { AuthInterceptor } from './core/interceptors/auth.interceptor';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideClientHydration(withEventReplay()),
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideCharts(withDefaultRegisterables()),
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
]
|
||||
};
|
||||
11
src/app/app.routes.server.ts
Normal file
11
src/app/app.routes.server.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||
|
||||
export const serverRoutes: ServerRoute[] = [
|
||||
{ path: ':appId', renderMode: RenderMode.Client },
|
||||
{ path: ':appId/home', renderMode: RenderMode.Client },
|
||||
{ path: ':appId/usersettings', renderMode: RenderMode.Client },
|
||||
{
|
||||
path: '**',
|
||||
renderMode: RenderMode.Prerender
|
||||
}
|
||||
];
|
||||
24
src/app/app.routes.ts
Normal file
24
src/app/app.routes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { AppIdGuard } from './guards/appid.guard';
|
||||
import { AuthGuard } from './guards/auth.guard';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
|
||||
import { UserSettingsComponent } from './user-settings/user-settings.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: '404', component: NotFoundComponent },
|
||||
{
|
||||
path: ':appId',
|
||||
children: [
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{ path: 'usersettings', component: UserSettingsComponent },
|
||||
{ path: 'add-carnet', loadComponent: () => import('./carnet/add/add-carnet.component').then(m => m.AddCarnetComponent) },
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' }
|
||||
],
|
||||
canActivate: [AuthGuard, AppIdGuard]
|
||||
},
|
||||
{ path: '', redirectTo: 'login', pathMatch: 'full' },
|
||||
{ path: '**', redirectTo: '/404' }
|
||||
];
|
||||
109
src/app/carnet/add/add-carnet.component.html
Normal file
109
src/app/carnet/add/add-carnet.component.html
Normal file
@ -0,0 +1,109 @@
|
||||
<div class="client-carnet-container">
|
||||
<!-- Initial Questions Section -->
|
||||
<div *ngIf="!questionsCompleted" class="questions-section">
|
||||
<h2>Add New Carnet</h2>
|
||||
|
||||
<form [formGroup]="questionsForm" (ngSubmit)="onQuestionsSubmit()">
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="newCarnet">
|
||||
<mat-label>Do you need a new carnet?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="additionalSets">
|
||||
<mat-label>Do you need additional sets?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="duplicateCarnet">
|
||||
<mat-label>Did you lose your carnet? Are you looking for duplicates?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="question-row">
|
||||
<mat-radio-group formControlName="extendCarnet">
|
||||
<mat-label>Do you need to extend the carnet?</mat-label>
|
||||
<mat-radio-button [value]="true">Yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">No</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button mat-raised-button color="primary" type="submit" [disabled]="!questionsForm.valid">
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Stepper Section (shown after questions are answered) -->
|
||||
<mat-stepper *ngIf="questionsCompleted" orientation="vertical" [linear]="isLinear"
|
||||
(selectionChange)="onStepChange($event)" [selectedIndex]="currentStep">
|
||||
|
||||
<!-- Application Name Step -->
|
||||
<mat-step [completed]="stepsCompleted.applicationDetail" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Application Name</ng-template>
|
||||
<app-application [isEditMode]="isEditMode" (applicationIdCreated)="onApplicationDetailCreated($event)">
|
||||
</app-application>
|
||||
</mat-step>
|
||||
|
||||
<!-- Holder Selection Step -->
|
||||
<mat-step [completed]="stepsCompleted.holderSelection" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Holder Selection</ng-template>
|
||||
<app-holder (completed)="onHolderSelectionSaved($event)">
|
||||
</app-holder>
|
||||
</mat-step>
|
||||
|
||||
<!-- Goods Section Step -->
|
||||
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection"
|
||||
[editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Goods Section</ng-template>
|
||||
<app-goods (completed)="onGoodsSectionSaved($event)">
|
||||
</app-goods>
|
||||
</mat-step>
|
||||
|
||||
<!-- Travel Plan Step -->
|
||||
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend'"
|
||||
[completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Travel Plan</ng-template>
|
||||
<!-- <app-travel-plan (completed)="onTravelPlanSaved($event)">
|
||||
</app-travel-plan> -->
|
||||
</mat-step>
|
||||
|
||||
<!-- Insurance Step -->
|
||||
<mat-step [completed]="stepsCompleted.insurance" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Insurance</ng-template>
|
||||
<!-- <app-insurance (completed)="onInsuranceSaved($event)">
|
||||
</app-insurance> -->
|
||||
</mat-step>
|
||||
|
||||
<!-- Shipping Step -->
|
||||
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Shipping</ng-template>
|
||||
<!-- <app-shipping (completed)="onShippingSaved($event)">
|
||||
</app-shipping> -->
|
||||
</mat-step>
|
||||
|
||||
<!-- Delivery Method Step -->
|
||||
<mat-step [completed]="stepsCompleted.deliveryMethod" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Delivery Method</ng-template>
|
||||
<!-- <app-delivery-method (completed)="onDeliveryMethodSaved($event)">
|
||||
</app-delivery-method> -->
|
||||
</mat-step>
|
||||
|
||||
<!-- Payment Step -->
|
||||
<mat-step [completed]="stepsCompleted.payment" [editable]="stepsCompleted.applicationDetail">
|
||||
<ng-template matStepLabel>Payment</ng-template>
|
||||
<!-- <app-payment (completed)="onPaymentSaved($event)">
|
||||
</app-payment> -->
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
</div>
|
||||
51
src/app/carnet/add/add-carnet.component.scss
Normal file
51
src/app/carnet/add/add-carnet.component.scss
Normal file
@ -0,0 +1,51 @@
|
||||
.client-carnet-container {
|
||||
// padding: 24px;
|
||||
// max-width: 1200px;
|
||||
// margin: 0 auto;
|
||||
|
||||
.questions-section {
|
||||
// padding: 20px;
|
||||
// border-radius: 8px;
|
||||
// margin-bottom: 24px;
|
||||
|
||||
// h2 {
|
||||
// margin-bottom: 24px;
|
||||
// color: #3f51b5;
|
||||
// }
|
||||
|
||||
.question-row {
|
||||
margin-bottom: 1rem;
|
||||
padding: 1rem;
|
||||
background: #ffff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
mat-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
// mat-label {
|
||||
// margin-bottom: 8px;
|
||||
// font-weight: 500;
|
||||
// }
|
||||
|
||||
mat-radio-button {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
102
src/app/carnet/add/add-carnet.component.ts
Normal file
102
src/app/carnet/add/add-carnet.component.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { StepperSelectionEvent } from '@angular/cdk/stepper';
|
||||
import { ApplicationComponent } from "../application/application.component";
|
||||
import { HolderComponent } from '../holder/holder.component';
|
||||
import { GoodsComponent } from '../goods/goods.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-carnet',
|
||||
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule,
|
||||
ApplicationComponent, HolderComponent, GoodsComponent],
|
||||
templateUrl: './add-carnet.component.html',
|
||||
styleUrl: './add-carnet.component.scss'
|
||||
})
|
||||
export class AddCarnetComponent {
|
||||
currentStep = 0;
|
||||
questionsCompleted = false;
|
||||
isLinear = true;
|
||||
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = null;
|
||||
isEditMode = false;
|
||||
applicationid: number = 0;
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
// Form group for initial questions
|
||||
questionsForm: FormGroup = this.fb.group({
|
||||
newCarnet: [null, Validators.required],
|
||||
additionalSets: [false],
|
||||
duplicateCarnet: [false],
|
||||
extendCarnet: [false]
|
||||
});
|
||||
|
||||
// Track completion of each step
|
||||
stepsCompleted = {
|
||||
applicationDetail: false,
|
||||
holderSelection: false,
|
||||
goodsSection: false,
|
||||
travelPlan: false,
|
||||
insurance: false,
|
||||
shipping: false,
|
||||
deliveryMethod: false,
|
||||
payment: false
|
||||
};
|
||||
|
||||
onQuestionsSubmit(): void {
|
||||
const answers = this.questionsForm.value;
|
||||
|
||||
if (answers.newCarnet) {
|
||||
this.applicationType = 'new';
|
||||
} else if (answers.additionalSets) {
|
||||
this.applicationType = 'additional';
|
||||
} else if (answers.duplicateCarnet) {
|
||||
this.applicationType = 'duplicate';
|
||||
} else if (answers.extendCarnet) {
|
||||
this.applicationType = 'extend';
|
||||
}
|
||||
|
||||
// Move to the first step after questions
|
||||
this.questionsCompleted = true;
|
||||
this.currentStep = 0;
|
||||
}
|
||||
|
||||
onStepChange(event: StepperSelectionEvent): void {
|
||||
this.currentStep = event.selectedIndex;
|
||||
}
|
||||
|
||||
onApplicationDetailCreated(applicationid: number): void {
|
||||
this.applicationid = applicationid;
|
||||
this.stepsCompleted.applicationDetail = true;
|
||||
this.isLinear = false; // Disable linear mode after application detail is created
|
||||
}
|
||||
|
||||
onHolderSelectionSaved(completed: boolean): void {
|
||||
this.stepsCompleted.holderSelection = completed;
|
||||
}
|
||||
|
||||
onGoodsSectionSaved(completed: boolean): void {
|
||||
this.stepsCompleted.goodsSection = completed;
|
||||
}
|
||||
|
||||
onTravelPlanSaved(completed: boolean): void {
|
||||
this.stepsCompleted.travelPlan = completed;
|
||||
}
|
||||
|
||||
onInsuranceSaved(completed: boolean): void {
|
||||
this.stepsCompleted.insurance = completed;
|
||||
}
|
||||
|
||||
onShippingSaved(completed: boolean): void {
|
||||
this.stepsCompleted.shipping = completed;
|
||||
}
|
||||
|
||||
onDeliveryMethodSaved(completed: boolean): void {
|
||||
this.stepsCompleted.deliveryMethod = completed;
|
||||
}
|
||||
|
||||
onPaymentSaved(completed: boolean): void {
|
||||
this.stepsCompleted.payment = completed;
|
||||
}
|
||||
}
|
||||
32
src/app/carnet/application/application.component.html
Normal file
32
src/app/carnet/application/application.component.html
Normal file
@ -0,0 +1,32 @@
|
||||
<div class="application-details-container">
|
||||
<mat-card class="details-card mat-elevation-z4">
|
||||
<mat-card-content>
|
||||
<div class="loading-shade" *ngIf="isLoading">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="applicationDetailsForm" class="details-form" *ngIf="!isLoading"
|
||||
(ngSubmit)="saveApplicationDetails()">
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="name">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput formControlName="name" required>
|
||||
<mat-error *ngIf="f['name'].errors?.['required']">
|
||||
Name is required
|
||||
</mat-error>
|
||||
<mat-error *ngIf="f['name'].errors?.['maxlength']">
|
||||
Maximum 100 characters allowed
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button mat-raised-button color="primary" type="submit"
|
||||
[disabled]="applicationDetailsForm.invalid || disableSaveButton ">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
98
src/app/carnet/application/application.component.scss
Normal file
98
src/app/carnet/application/application.component.scss
Normal file
@ -0,0 +1,98 @@
|
||||
.application-details-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
|
||||
.details-card {
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
|
||||
.loading-shade {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.details-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
|
||||
.name {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.lookup-code {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.address1,
|
||||
.address2 {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.city,
|
||||
.state,
|
||||
.country,
|
||||
.zip {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.carnet-issuing-region,
|
||||
.revenue-location {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.application-details-container {
|
||||
.details-card {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
.name,
|
||||
.lookup-code,
|
||||
.address1,
|
||||
.address2,
|
||||
.city,
|
||||
.state,
|
||||
.zip,
|
||||
.country,
|
||||
.carnet-issuing-region,
|
||||
.revenue-location {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/app/carnet/application/application.component.ts
Normal file
115
src/app/carnet/application/application.component.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ApplicationDetailService } from '../../core/services/carnet/application-detail.service';
|
||||
import { NotificationService } from '../../core/services/common/notification.service';
|
||||
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||
import { ApplicationDetail } from '../../core/models/carnet/application-detail';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-application',
|
||||
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule],
|
||||
templateUrl: './application.component.html',
|
||||
styleUrl: './application.component.scss'
|
||||
})
|
||||
export class ApplicationComponent {
|
||||
@Input() isEditMode = false;
|
||||
@Input() applicationid: number = 0;
|
||||
|
||||
@Output() applicationIdCreated = new EventEmitter<number>();
|
||||
|
||||
applicationDetailsForm: FormGroup;
|
||||
isLoading = false;
|
||||
disableSaveButton = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
private applicationDetailService = inject(ApplicationDetailService);
|
||||
private notificationService = inject(NotificationService);
|
||||
private errorHandler = inject(ApiErrorHandlerService);
|
||||
|
||||
constructor() {
|
||||
this.applicationDetailsForm = this.createForm();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// this.spidCreated.emit(this.spid?.toString());
|
||||
|
||||
// Patch edit form data
|
||||
if (this.applicationid > 0) {
|
||||
this.isLoading = true;
|
||||
this.applicationDetailService.getApplicationDetailsById(this.applicationid).subscribe({
|
||||
next: (applicationDetail: ApplicationDetail) => {
|
||||
if (applicationDetail?.applicationId > 0) {
|
||||
this.patchFormData(applicationDetail);
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error: any) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load application details');
|
||||
this.notificationService.showError(errorMessage);
|
||||
this.isLoading = false;
|
||||
console.error('Error loading application details:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
createForm(): FormGroup {
|
||||
return this.fb.group({
|
||||
name: ['', [Validators.required, Validators.maxLength(100)]],
|
||||
});
|
||||
}
|
||||
|
||||
patchFormData(data: ApplicationDetail): void {
|
||||
this.applicationDetailsForm.patchValue({
|
||||
name: data.name,
|
||||
});
|
||||
|
||||
if (this.isEditMode) {
|
||||
this.applicationDetailsForm.get('name')?.disable();
|
||||
this.disableSaveButton = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience getter for easy access to form fields
|
||||
get f() {
|
||||
return this.applicationDetailsForm.controls;
|
||||
}
|
||||
|
||||
saveApplicationDetails(): void {
|
||||
if (this.applicationDetailsForm.invalid) {
|
||||
this.applicationDetailsForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
const applicationDetailData: ApplicationDetail = this.applicationDetailsForm.value;
|
||||
|
||||
if (!this.isEditMode && this.applicationid == 0) {
|
||||
this.isLoading = true;
|
||||
this.applicationDetailService.createApplicationDetails(applicationDetailData).subscribe({
|
||||
next: (applicationData: any) => {
|
||||
this.notificationService.showSuccess(`Application details added successfully`);
|
||||
this.applicationIdCreated.emit(applicationData.P_HEADERID);
|
||||
this.applicationDetailsForm.get('name')?.disable();
|
||||
this.disableSaveButton = true;
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, `Failed to add application details`);
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Error saving application details:', error);
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/app/carnet/delivery/delivery.component.html
Normal file
1
src/app/carnet/delivery/delivery.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>delivery works!</p>
|
||||
0
src/app/carnet/delivery/delivery.component.scss
Normal file
0
src/app/carnet/delivery/delivery.component.scss
Normal file
11
src/app/carnet/delivery/delivery.component.ts
Normal file
11
src/app/carnet/delivery/delivery.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delivery',
|
||||
imports: [],
|
||||
templateUrl: './delivery.component.html',
|
||||
styleUrl: './delivery.component.scss'
|
||||
})
|
||||
export class DeliveryComponent {
|
||||
|
||||
}
|
||||
1
src/app/carnet/edit/edit-carnet.component.html
Normal file
1
src/app/carnet/edit/edit-carnet.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>edit-carnet works!</p>
|
||||
0
src/app/carnet/edit/edit-carnet.component.scss
Normal file
0
src/app/carnet/edit/edit-carnet.component.scss
Normal file
11
src/app/carnet/edit/edit-carnet.component.ts
Normal file
11
src/app/carnet/edit/edit-carnet.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-carnet',
|
||||
imports: [],
|
||||
templateUrl: './edit-carnet.component.html',
|
||||
styleUrl: './edit-carnet.component.scss'
|
||||
})
|
||||
export class EditCarnetComponent {
|
||||
|
||||
}
|
||||
1
src/app/carnet/goods/goods.component.html
Normal file
1
src/app/carnet/goods/goods.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>goods works!</p>
|
||||
0
src/app/carnet/goods/goods.component.scss
Normal file
0
src/app/carnet/goods/goods.component.scss
Normal file
14
src/app/carnet/goods/goods.component.ts
Normal file
14
src/app/carnet/goods/goods.component.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-goods',
|
||||
imports: [AngularMaterialModule, CommonModule],
|
||||
templateUrl: './goods.component.html',
|
||||
styleUrl: './goods.component.scss'
|
||||
})
|
||||
export class GoodsComponent {
|
||||
@Input() applicationid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
1
src/app/carnet/holder/holder.component.html
Normal file
1
src/app/carnet/holder/holder.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>holder works!</p>
|
||||
0
src/app/carnet/holder/holder.component.scss
Normal file
0
src/app/carnet/holder/holder.component.scss
Normal file
12
src/app/carnet/holder/holder.component.ts
Normal file
12
src/app/carnet/holder/holder.component.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-holder',
|
||||
imports: [],
|
||||
templateUrl: './holder.component.html',
|
||||
styleUrl: './holder.component.scss'
|
||||
})
|
||||
export class HolderComponent {
|
||||
@Input() applicationid: number = 0;
|
||||
@Output() completed = new EventEmitter<boolean>();
|
||||
}
|
||||
1
src/app/carnet/insurance/insurance.component.html
Normal file
1
src/app/carnet/insurance/insurance.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>insurance works!</p>
|
||||
0
src/app/carnet/insurance/insurance.component.scss
Normal file
0
src/app/carnet/insurance/insurance.component.scss
Normal file
11
src/app/carnet/insurance/insurance.component.ts
Normal file
11
src/app/carnet/insurance/insurance.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-insurance',
|
||||
imports: [],
|
||||
templateUrl: './insurance.component.html',
|
||||
styleUrl: './insurance.component.scss'
|
||||
})
|
||||
export class InsuranceComponent {
|
||||
|
||||
}
|
||||
1
src/app/carnet/payment/payment.component.html
Normal file
1
src/app/carnet/payment/payment.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>payment works!</p>
|
||||
0
src/app/carnet/payment/payment.component.scss
Normal file
0
src/app/carnet/payment/payment.component.scss
Normal file
11
src/app/carnet/payment/payment.component.ts
Normal file
11
src/app/carnet/payment/payment.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment',
|
||||
imports: [],
|
||||
templateUrl: './payment.component.html',
|
||||
styleUrl: './payment.component.scss'
|
||||
})
|
||||
export class PaymentComponent {
|
||||
|
||||
}
|
||||
1
src/app/carnet/shipping/shipping.component.html
Normal file
1
src/app/carnet/shipping/shipping.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>shipping works!</p>
|
||||
0
src/app/carnet/shipping/shipping.component.scss
Normal file
0
src/app/carnet/shipping/shipping.component.scss
Normal file
11
src/app/carnet/shipping/shipping.component.ts
Normal file
11
src/app/carnet/shipping/shipping.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shipping',
|
||||
imports: [],
|
||||
templateUrl: './shipping.component.html',
|
||||
styleUrl: './shipping.component.scss'
|
||||
})
|
||||
export class ShippingComponent {
|
||||
|
||||
}
|
||||
1
src/app/carnet/travel-plan/travel-plan.component.html
Normal file
1
src/app/carnet/travel-plan/travel-plan.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>travel-plan works!</p>
|
||||
11
src/app/carnet/travel-plan/travel-plan.component.ts
Normal file
11
src/app/carnet/travel-plan/travel-plan.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-travel-plan',
|
||||
imports: [],
|
||||
templateUrl: './travel-plan.component.html',
|
||||
styleUrl: './travel-plan.component.scss'
|
||||
})
|
||||
export class TravelPlanComponent {
|
||||
|
||||
}
|
||||
13
src/app/common/footer/footer.component.html
Normal file
13
src/app/common/footer/footer.component.html
Normal file
@ -0,0 +1,13 @@
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-links">
|
||||
<a href="#">Privacy Policy</a>
|
||||
<a href="#">Terms of Service</a>
|
||||
<a href="#">Contact Us</a>
|
||||
</div>
|
||||
|
||||
<div class="copyright">
|
||||
© {{ currentYear }} USCIB Carnet Portal. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
56
src/app/common/footer/footer.component.scss
Normal file
56
src/app/common/footer/footer.component.scss
Normal file
@ -0,0 +1,56 @@
|
||||
.footer {
|
||||
color: var(--mat-sys-primary);
|
||||
background: var(--mat-sys-primary-container);
|
||||
padding: 8px;
|
||||
border-top: 1px solid var(--mat-sys-outline-variant);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
/* Fixed footer styles */
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
/* Ensure footer stays above content */
|
||||
|
||||
.footer-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
|
||||
a {
|
||||
color: var(--mat-sys-secondary);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--mat-sys-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copyright {
|
||||
color: var(--mat-sys-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.footer {
|
||||
padding: 16px;
|
||||
|
||||
.footer-links {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/app/common/footer/footer.component.ts
Normal file
12
src/app/common/footer/footer.component.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
imports: [AngularMaterialModule],
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrl: './footer.component.scss'
|
||||
})
|
||||
export class FooterComponent {
|
||||
currentYear = new Date().getFullYear();
|
||||
}
|
||||
39
src/app/common/secured-header/secured-header.component.html
Normal file
39
src/app/common/secured-header/secured-header.component.html
Normal file
@ -0,0 +1,39 @@
|
||||
<nav class="navbar">
|
||||
<div class="navbar-brand">
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" alt="App Logo" class="logo">
|
||||
<!-- <span class="app-title">USCIB Carnet Portal</span> -->
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu">
|
||||
<button mat-button (click)="navigateTo('home')">Home</button>
|
||||
<button mat-button (click)="navigateTo('usersettings')">User Settings</button>
|
||||
<!--
|
||||
<button mat-icon-button [matMenuTriggerFor]="profile">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
</button>
|
||||
<mat-menu #profile="matMenu">
|
||||
<button mat-menu-item (click)="logout()">
|
||||
<mat-icon>logout</mat-icon>
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
</mat-menu> -->
|
||||
|
||||
<div class="profile-container">
|
||||
<button mat-icon-button (click)="toggleProfileMenu()" class="profile-button">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="profile-menu" *ngIf="showProfileMenu">
|
||||
<div class="profile-info">
|
||||
<mat-icon>email</mat-icon>
|
||||
<span>{{ userEmail }}</span>
|
||||
</div>
|
||||
|
||||
<button mat-menu-item (click)="logout()">
|
||||
<mat-icon>logout</mat-icon>
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
115
src/app/common/secured-header/secured-header.component.scss
Normal file
115
src/app/common/secured-header/secured-header.component.scss
Normal file
@ -0,0 +1,115 @@
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
height: 64px;
|
||||
color: var(--mat-sys-primary);
|
||||
background: var(--mat-sys-primary-container);
|
||||
box-shadow: var(--mat-sys-level2);
|
||||
position: relative;
|
||||
z-index: 100; // Higher than footer's z-index
|
||||
|
||||
.navbar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.logo {
|
||||
height: 40px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
|
||||
// .nav-link {
|
||||
// cursor: pointer;
|
||||
// padding: 8px 12px;
|
||||
// border-radius: 4px;
|
||||
// transition: background-color 0.3s ease;
|
||||
|
||||
// &:hover {
|
||||
// background-color: var(--mat-sys-secondary-container);
|
||||
// box-shadow: var(--mat-sys-level1);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
position: relative;
|
||||
|
||||
.profile-button {
|
||||
color: var(--mat-sys-primary);
|
||||
background: var(--mat-sys-primary-container);
|
||||
}
|
||||
|
||||
.profile-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 48px;
|
||||
background-color: var(--mat-sys-secondary-container);
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--mat-sys-level3);
|
||||
min-width: 200px;
|
||||
overflow: hidden;
|
||||
z-index: 101;
|
||||
|
||||
.profile-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
color: var(--mat-sys-secondary);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
|
||||
mat-icon {
|
||||
color: var(--mat-sys-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: var(--mat-sys-surface-container);
|
||||
color: var(--mat-sys-secondary);
|
||||
|
||||
mat-icon {
|
||||
color: var(--mat-sys-secondary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--mat-sys-secondary-container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
padding: 0 12px;
|
||||
|
||||
.app-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/app/common/secured-header/secured-header.component.ts
Normal file
65
src/app/common/secured-header/secured-header.component.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Component, effect, inject, OnInit } from '@angular/core';
|
||||
import { UserService } from '../../core/services/common/user.service';
|
||||
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NavigationService } from '../../core/services/common/navigation.service';
|
||||
import { User } from '../../core/models/user';
|
||||
import { AuthService } from '../../core/services/common/auth.service';
|
||||
import { ThemeService } from '../../core/services/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-secured-header',
|
||||
imports: [AngularMaterialModule, CommonModule],
|
||||
templateUrl: './secured-header.component.html',
|
||||
styleUrl: './secured-header.component.scss'
|
||||
})
|
||||
export class SecuredHeaderComponent implements OnInit {
|
||||
userEmail: string = '';
|
||||
logoUrl: string | undefined = '';
|
||||
showProfileMenu: boolean = false;
|
||||
userDetails: User | null = {};
|
||||
|
||||
private userService = inject(UserService);
|
||||
private authService = inject(AuthService);
|
||||
private navigationService = inject(NavigationService);
|
||||
private themeService = inject(ThemeService);
|
||||
|
||||
constructor(
|
||||
) {
|
||||
effect(() => {
|
||||
this.userDetails = this.userService.userDetailsSignal();
|
||||
if (this.userDetails?.userDetails) {
|
||||
this.setApplicationDetails();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userEmail = this.userService.getSafeUser();
|
||||
this.userDetails = this.userService.getUserDetails();
|
||||
this.setApplicationDetails();
|
||||
}
|
||||
|
||||
setApplicationDetails() {
|
||||
if (this.userDetails?.userDetails) {
|
||||
this.logoUrl = `images/logos/${this.userDetails?.userDetails?.logoName}`;
|
||||
this.themeService.setTheme(this.userDetails?.userDetails?.themeName);
|
||||
} else {
|
||||
this.themeService.setTheme('default');
|
||||
}
|
||||
}
|
||||
|
||||
toggleProfileMenu(): void {
|
||||
this.showProfileMenu = !this.showProfileMenu;
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout();
|
||||
this.showProfileMenu = false;
|
||||
}
|
||||
|
||||
navigateTo(route: string): void {
|
||||
this.navigationService.navigate([route]);
|
||||
this.showProfileMenu = false;
|
||||
}
|
||||
}
|
||||
111
src/app/core/interceptors/auth.interceptor.ts
Normal file
111
src/app/core/interceptors/auth.interceptor.ts
Normal file
@ -0,0 +1,111 @@
|
||||
// auth.interceptor.ts
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable, throwError, BehaviorSubject } from 'rxjs';
|
||||
import { catchError, filter, switchMap, take } from 'rxjs/operators';
|
||||
import { AuthService } from '../services/common/auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
private isRefreshing = false;
|
||||
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||
|
||||
private requestQueue: { request: HttpRequest<any>, next: HttpHandler }[] = [];
|
||||
|
||||
private authService = inject(AuthService);
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
|
||||
// Add withCredentials to all requests
|
||||
request = request.clone({
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
return next.handle(request).pipe(
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
if (error.status === 401) {
|
||||
// Handle 401 Unauthorized responses
|
||||
if (error.url?.includes('refresh-tokens')) {
|
||||
return throwError(() => error);
|
||||
}
|
||||
|
||||
return this.handle401Error(request, next);
|
||||
} else {
|
||||
return throwError(() => error);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private handle401Error(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (!this.isRefreshing) {
|
||||
this.isRefreshing = true;
|
||||
this.refreshTokenSubject.next(null);
|
||||
|
||||
return this.authService.refreshToken().pipe(
|
||||
switchMap((token: any) => {
|
||||
this.isRefreshing = false;
|
||||
this.refreshTokenSubject.next(token);
|
||||
|
||||
// Retry all queued requests with new token
|
||||
this.retryQueuedRequests();
|
||||
|
||||
// Retry the original request
|
||||
request = request.clone({
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
return next.handle(request);
|
||||
}),
|
||||
catchError((err) => {
|
||||
this.isRefreshing = false;
|
||||
this.authService.logout();
|
||||
return throwError(() => err);
|
||||
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// If token refresh is already in progress, add to queue
|
||||
return this.addRequestToQueue(request, next);
|
||||
}
|
||||
}
|
||||
|
||||
private addRequestToQueue(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return new Observable<HttpEvent<any>>(observer => {
|
||||
const subscription = this.refreshTokenSubject.pipe(
|
||||
filter(token => token !== null),
|
||||
take(1)
|
||||
).subscribe(token => {
|
||||
// Remove from queue
|
||||
const index = this.requestQueue.findIndex(item => item.request === request);
|
||||
if (index > -1) {
|
||||
this.requestQueue.splice(index, 1);
|
||||
}
|
||||
|
||||
// Retry request with new token
|
||||
next.handle(request).subscribe({
|
||||
next: event => observer.next(event),
|
||||
error: err => observer.error(err),
|
||||
complete: () => observer.complete()
|
||||
});
|
||||
});
|
||||
|
||||
// Add to queue
|
||||
this.requestQueue.push({ request, next });
|
||||
});
|
||||
}
|
||||
|
||||
private retryQueuedRequests(): void {
|
||||
// Process all queued requests
|
||||
while (this.requestQueue.length > 0) {
|
||||
let { request, next } = this.requestQueue.shift()!;
|
||||
|
||||
request = request.clone({
|
||||
withCredentials: true
|
||||
});
|
||||
|
||||
next.handle(request).subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/app/core/models/bond-surety.ts
Normal file
5
src/app/core/models/bond-surety.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface BondSurety {
|
||||
name: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
5
src/app/core/models/cargo-policy.ts
Normal file
5
src/app/core/models/cargo-policy.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface CargoPolicy {
|
||||
name: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
5
src/app/core/models/cargo-surety.ts
Normal file
5
src/app/core/models/cargo-surety.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface CargoSurety {
|
||||
name: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
6
src/app/core/models/carnet-status.ts
Normal file
6
src/app/core/models/carnet-status.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface CarnetStatus {
|
||||
id: string;
|
||||
name: string;
|
||||
value: string;
|
||||
color: string;
|
||||
}
|
||||
6
src/app/core/models/carnet/application-detail.ts
Normal file
6
src/app/core/models/carnet/application-detail.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface ApplicationDetail {
|
||||
clientid: number;
|
||||
spid: number;
|
||||
applicationId: number;
|
||||
name: string;
|
||||
}
|
||||
2
src/app/core/models/carnet/delivery.ts
Normal file
2
src/app/core/models/carnet/delivery.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export interface Delivery {
|
||||
}
|
||||
2
src/app/core/models/carnet/goods.ts
Normal file
2
src/app/core/models/carnet/goods.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export interface Goods {
|
||||
}
|
||||
2
src/app/core/models/carnet/holder.ts
Normal file
2
src/app/core/models/carnet/holder.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export interface Holder {
|
||||
}
|
||||
2
src/app/core/models/carnet/insurance.ts
Normal file
2
src/app/core/models/carnet/insurance.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export interface Insurance {
|
||||
}
|
||||
2
src/app/core/models/carnet/payment.ts
Normal file
2
src/app/core/models/carnet/payment.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export interface Payment {
|
||||
}
|
||||
2
src/app/core/models/carnet/shipping.ts
Normal file
2
src/app/core/models/carnet/shipping.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export interface Shipping {
|
||||
}
|
||||
2
src/app/core/models/carnet/travel-plan.ts
Normal file
2
src/app/core/models/carnet/travel-plan.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export interface TravelPlan {
|
||||
}
|
||||
5
src/app/core/models/country.ts
Normal file
5
src/app/core/models/country.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Country {
|
||||
name: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
5
src/app/core/models/delivery-type.ts
Normal file
5
src/app/core/models/delivery-type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface DeliveryType {
|
||||
name: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
5
src/app/core/models/fee-type.ts
Normal file
5
src/app/core/models/fee-type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface FeeType {
|
||||
name: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
5
src/app/core/models/region.ts
Normal file
5
src/app/core/models/region.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Region {
|
||||
id: number,
|
||||
region: string;
|
||||
regionname: string;
|
||||
}
|
||||
5
src/app/core/models/state.ts
Normal file
5
src/app/core/models/state.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface State {
|
||||
id: string;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
5
src/app/core/models/timezone.ts
Normal file
5
src/app/core/models/timezone.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface TimeZone {
|
||||
name: string;
|
||||
id: string;
|
||||
value: string;
|
||||
}
|
||||
8
src/app/core/models/user-preference.ts
Normal file
8
src/app/core/models/user-preference.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface UserPreferences {
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
// Default preferences
|
||||
export const DEFAULT_USER_PREFERENCES: UserPreferences = {
|
||||
pageSize: 5
|
||||
};
|
||||
19
src/app/core/models/user.ts
Normal file
19
src/app/core/models/user.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface User {
|
||||
roles?: string[] | null;
|
||||
menus?: string[] | null;
|
||||
menuDetails?: Menu[] | null;
|
||||
userDetails?: UserDetail | null;
|
||||
}
|
||||
|
||||
export interface Menu {
|
||||
name: string;
|
||||
pageName: string
|
||||
}
|
||||
|
||||
export interface UserDetail {
|
||||
spid: number;
|
||||
clientid: number;
|
||||
urlKey: string;
|
||||
logoName: string;
|
||||
themeName: string;
|
||||
}
|
||||
43
src/app/core/services/carnet/application-detail.service.ts
Normal file
43
src/app/core/services/carnet/application-detail.service.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { UserService } from '../common/user.service';
|
||||
import { ApplicationDetail } from '../../models/carnet/application-detail';
|
||||
import { filter, map, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApplicationDetailService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
private userService = inject(UserService);
|
||||
|
||||
getApplicationDetailsById(id: number): ApplicationDetail | any {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparerByClientid/${this.userService.getUserSpid()}/${id}`).pipe(
|
||||
filter(response => response.length > 0),
|
||||
map(response => this.mapToApplicationDetail(response?.[0])));
|
||||
}
|
||||
|
||||
private mapToApplicationDetail(applicationDetails: any): ApplicationDetail {
|
||||
return {
|
||||
clientid: applicationDetails.CLIENTID,
|
||||
spid: applicationDetails.SPID,
|
||||
name: applicationDetails.P_APPLICATIONNAME,
|
||||
applicationId: applicationDetails.P_HEADERID
|
||||
};
|
||||
}
|
||||
|
||||
createApplicationDetails(data: ApplicationDetail): Observable<any> {
|
||||
const applicationDetails = {
|
||||
P_SPID: this.userService.getUserSpid(),
|
||||
P_CLIENTID: this.userService.getUserClientid(),
|
||||
P_APPLICATIONNAME: data.name,
|
||||
P_USERID: this.userService.getUser(),
|
||||
}
|
||||
|
||||
return this.http.post(`${this.apiUrl}/${this.apiDb}/SaveCarnetApplication`, applicationDetails);
|
||||
}
|
||||
}
|
||||
9
src/app/core/services/carnet/delivery.service.ts
Normal file
9
src/app/core/services/carnet/delivery.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DeliveryService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
9
src/app/core/services/carnet/goods.service.ts
Normal file
9
src/app/core/services/carnet/goods.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GoodsService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
9
src/app/core/services/carnet/holder.service.ts
Normal file
9
src/app/core/services/carnet/holder.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HolderService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
9
src/app/core/services/carnet/insurance.service.ts
Normal file
9
src/app/core/services/carnet/insurance.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class InsuranceService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
9
src/app/core/services/carnet/payment.service.ts
Normal file
9
src/app/core/services/carnet/payment.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PaymentService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
9
src/app/core/services/carnet/shipping.service.ts
Normal file
9
src/app/core/services/carnet/shipping.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ShippingService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
9
src/app/core/services/carnet/travel-plan.service.ts
Normal file
9
src/app/core/services/carnet/travel-plan.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TravelPlanService {
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
63
src/app/core/services/common/api-error-handler.service.ts
Normal file
63
src/app/core/services/common/api-error-handler.service.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiErrorHandlerService {
|
||||
private genericErrorMessage = 'An unexpected error occurred. Please try again later.';
|
||||
|
||||
/**
|
||||
* Extracts user-friendly error messages from API error responses
|
||||
* @param error The error response object
|
||||
* @returns Array of error messages to display
|
||||
*/
|
||||
handleApiError(error: HttpErrorResponse | any, customErrorMessage: string): string {
|
||||
if (!error) {
|
||||
return (customErrorMessage) ? customErrorMessage : this.genericErrorMessage;
|
||||
}
|
||||
|
||||
// Handle 400 Bad Request with pipe-delimited messages
|
||||
if (error.status === 400 && error.error?.message) {
|
||||
return this.parsePipeDelimitedMessages(error.error.message);
|
||||
}
|
||||
|
||||
// Handle other status codes
|
||||
return this.getAppropriateGenericMessage(error.status, customErrorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits pipe-delimited messages and cleans them up
|
||||
* @param messageString The raw message string from API
|
||||
* @returns Array of cleaned error messages
|
||||
*/
|
||||
private parsePipeDelimitedMessages(messageString: string): string {
|
||||
if (!messageString || typeof messageString !== 'string') {
|
||||
return this.genericErrorMessage;
|
||||
}
|
||||
|
||||
return messageString.slice(0, -1);
|
||||
// .split('|')
|
||||
// .map(msg => msg.trim())
|
||||
// .filter(msg => msg.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns appropriate generic message based on status code
|
||||
* @param status HTTP status code
|
||||
* @returns Generic error message
|
||||
*/
|
||||
private getAppropriateGenericMessage(status: number, customErrorMessage: string): string {
|
||||
switch (status) {
|
||||
case 401:
|
||||
return 'Unauthorized access. Please login again.';
|
||||
case 403:
|
||||
return 'You do not have permission to perform this action.';
|
||||
case 404:
|
||||
return 'The requested resource was not found.';
|
||||
case 500:
|
||||
default:
|
||||
return (customErrorMessage) ? customErrorMessage : this.genericErrorMessage;;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/app/core/services/common/auth.service.ts
Normal file
51
src/app/core/services/common/auth.service.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { UserService } from './user.service';
|
||||
import { StorageService } from './storage.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ThemeService } from '../theme.service';
|
||||
import { NotificationService } from './notification.service';
|
||||
import { ApiErrorHandlerService } from './api-error-handler.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
private userService = inject(UserService);
|
||||
private storageService = inject(StorageService);
|
||||
private router = inject(Router);
|
||||
private themeService = inject(ThemeService);
|
||||
private notificationService = inject(NotificationService);
|
||||
private errorHandler = inject(ApiErrorHandlerService);
|
||||
|
||||
login(username: string, password: string): Observable<any> {
|
||||
return this.http.post(`${this.apiUrl}/login`, { p_emailaddr: username, p_password: password });
|
||||
}
|
||||
|
||||
refreshToken(): Observable<any> {
|
||||
return this.http.get(`${this.apiUrl}/refresh-tokens`, {});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
if (this.userService.isLoggedIn()) {
|
||||
this.http.post(`${this.apiUrl}/logout`, {}).subscribe({
|
||||
next: (response) => {
|
||||
this.userService.clearUser();
|
||||
this.storageService.clear();
|
||||
this.router.navigate(['/login']);
|
||||
this.themeService.setTheme('default');;
|
||||
}
|
||||
, error: (error) => {
|
||||
let errorMessage = this.errorHandler.handleApiError(error, `Logout failed`);
|
||||
this.notificationService.showError(errorMessage);
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/app/core/services/common/common.service.ts
Normal file
159
src/app/core/services/common/common.service.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { catchError, map, Observable, of } from 'rxjs';
|
||||
import { Region } from '../../models/region';
|
||||
import { State } from '../../models/state';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { DeliveryType } from '../../models/delivery-type';
|
||||
import { FeeType } from '../../models/fee-type';
|
||||
import { TimeZone } from '../../models/timezone';
|
||||
import { BondSurety } from '../../models/bond-surety';
|
||||
import { CargoPolicy } from '../../models/cargo-policy';
|
||||
import { CargoSurety } from '../../models/cargo-surety';
|
||||
import { CarnetStatus } from '../../models/carnet-status';
|
||||
import { Country } from '../../models/country';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CommonService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
|
||||
getCountries(spid: number = 0): Observable<Country[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=002&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getStates(country: string, spid: number = 0): Observable<State[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=001&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE,
|
||||
countryValue: item.ADDLPARAMVALUE1,
|
||||
})).filter((state) => state.countryValue === country) // Filter by country value
|
||||
),
|
||||
catchError((error) => {
|
||||
console.error('Error fetching states:', error);
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getRegions(): Observable<Region[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetRegions`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
id: item.REGIONID,
|
||||
region: item.REGION,
|
||||
regionname: item.REGIONNAME
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getDeliveryTypes(spid: number = 0): Observable<DeliveryType[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=006&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getTimezones(spid: number = 0): Observable<TimeZone[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=010&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getFeeTypes(spid: number = 0): Observable<FeeType[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=009&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getBondSuretys(spid: number = 0): Observable<BondSurety[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=003&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getCargoPolicies(spid: number = 0): Observable<CargoPolicy[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=004&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getCargoSuretys(spid: number = 0): Observable<CargoSurety[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=005&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getCarnetStatuses(spid: number = 0): Observable<CarnetStatus[]> {
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=011&P_SPID=0`).pipe(
|
||||
map((response) =>
|
||||
response.map((item) => ({
|
||||
name: item.PARAMDESC,
|
||||
id: item.PARAMID,
|
||||
value: item.PARAMVALUE,
|
||||
color: item.ADDLPARAMVALUE1,
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
formatUSDate(datetime: Date): string {
|
||||
const date = new Date(datetime);
|
||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getUTCDate()).padStart(2, '0');
|
||||
const year = date.getUTCFullYear();
|
||||
|
||||
return `${month}/${day}/${year}`;
|
||||
}
|
||||
}
|
||||
60
src/app/core/services/common/cookie.service.ts
Normal file
60
src/app/core/services/common/cookie.service.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { CookieService as NgxCookieService } from 'ngx-cookie-service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CookieHelperService {
|
||||
private ngxCookieService = inject(NgxCookieService);
|
||||
|
||||
/**
|
||||
* Get a value from cookie
|
||||
* @param key Cookie key
|
||||
* @returns Parsed JSON value or null if not found
|
||||
*/
|
||||
get<T>(key: string): T | null {
|
||||
try {
|
||||
const value = this.ngxCookieService.get(key);
|
||||
return value ? JSON.parse(value) : null;
|
||||
} catch (error) {
|
||||
console.error('Error parsing cookie value', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in cookie
|
||||
* @param key Cookie key
|
||||
* @param value Value to store (will be stringified)
|
||||
* @param expiresDays Number of days until cookie expires (default 365)
|
||||
*/
|
||||
set(key: string, value: any, expiresDays: number = 365): void {
|
||||
const expires = new Date();
|
||||
expires.setDate(expires.getDate() + expiresDays);
|
||||
this.ngxCookieService.set(
|
||||
key,
|
||||
JSON.stringify(value),
|
||||
expires,
|
||||
'/',
|
||||
undefined,
|
||||
false,
|
||||
'Lax'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cookie
|
||||
* @param key Cookie key to remove
|
||||
*/
|
||||
remove(key: string): void {
|
||||
this.ngxCookieService.delete(key, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cookie exists
|
||||
* @param key Cookie key to check
|
||||
*/
|
||||
has(key: string): boolean {
|
||||
return this.ngxCookieService.check(key);
|
||||
}
|
||||
}
|
||||
48
src/app/core/services/common/navigation.service.ts
Normal file
48
src/app/core/services/common/navigation.service.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
import { StorageService } from './storage.service';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NavigationService {
|
||||
|
||||
private readonly USER_APPID_KEY = 'CurrentAppId';
|
||||
|
||||
private router = inject(Router);
|
||||
private location = inject(Location);
|
||||
private storageService = inject(StorageService);
|
||||
|
||||
setCurrentAppId(appId: string): void {
|
||||
this.storageService.setItem(this.USER_APPID_KEY, appId);
|
||||
}
|
||||
|
||||
getCurrentAppId(): string {
|
||||
return this.storageService.getItem(this.USER_APPID_KEY) ?? '';
|
||||
}
|
||||
|
||||
navigate(commands: any[], extras?: any): void {
|
||||
const currentAppId = this.getCurrentAppId();
|
||||
|
||||
// Prepend appId to all navigations
|
||||
this.router.navigate([currentAppId, ...commands], extras);
|
||||
}
|
||||
|
||||
navigateByUrl(url: string, extras?: any): void {
|
||||
const currentAppId = this.getCurrentAppId();
|
||||
|
||||
// Ensure URL starts with current appId
|
||||
const fullUrl = `/${currentAppId}${url.startsWith('/') ? url : `/${url}`}`;
|
||||
this.router.navigateByUrl(fullUrl, extras);
|
||||
}
|
||||
|
||||
goBack(): void {
|
||||
this.location.back();
|
||||
}
|
||||
|
||||
goForward(): void {
|
||||
this.location.forward();
|
||||
}
|
||||
}
|
||||
32
src/app/core/services/common/notification.service.ts
Normal file
32
src/app/core/services/common/notification.service.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
|
||||
private snackBar = inject(MatSnackBar);
|
||||
|
||||
|
||||
showSuccess(message: string): void {
|
||||
this.snackBar.open(message, 'Close', {
|
||||
duration: 3000,
|
||||
panelClass: ['success-snackbar']
|
||||
});
|
||||
}
|
||||
|
||||
showError(message: string): void {
|
||||
this.snackBar.open(message, 'Close', {
|
||||
duration: 3000,
|
||||
panelClass: ['error-snackbar']
|
||||
});
|
||||
}
|
||||
|
||||
showWarning(message: string): void {
|
||||
this.snackBar.open(message, 'Close', {
|
||||
duration: 3000,
|
||||
panelClass: ['error-snackbar']
|
||||
});
|
||||
}
|
||||
}
|
||||
49
src/app/core/services/common/storage.service.ts
Normal file
49
src/app/core/services/common/storage.service.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StorageService {
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
|
||||
setItem(key: string, value: string): void {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
getItem(key: string): string | null {
|
||||
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
return sessionStorage.getItem(key);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get<T>(key: string): T | null {
|
||||
try {
|
||||
const item = sessionStorage.getItem(key);
|
||||
return item ? JSON.parse(item) as T : null;
|
||||
} catch (e) {
|
||||
console.error(`Error getting ${key} from session storage`, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
set(key: string, value: any): void {
|
||||
try {
|
||||
sessionStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (e) {
|
||||
console.error(`Error setting ${key} in session storage`, e);
|
||||
}
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
sessionStorage.clear();
|
||||
}
|
||||
|
||||
}
|
||||
44
src/app/core/services/common/timeformat.service.ts
Normal file
44
src/app/core/services/common/timeformat.service.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TimeFormatService {
|
||||
/**
|
||||
* Formats a time range with timezone
|
||||
* @param startTime 24-hour format (e.g., "13:00")
|
||||
* @param endTime 24-hour format (e.g., "17:00") - optional
|
||||
* @param timezone Timezone abbreviation (e.g., "EST")
|
||||
* @returns Formatted time range string
|
||||
*/
|
||||
formatTimeRange(startTime: string, timezone: string, endTime?: string): string {
|
||||
if (!startTime) return '';
|
||||
|
||||
const start = this.formatSingleTime(startTime);
|
||||
|
||||
if (!endTime) {
|
||||
return start.endsWith('m') ? `${start} above ${timezone}` : `${start}am above ${timezone}`;
|
||||
}
|
||||
|
||||
const end = this.formatSingleTime(endTime);
|
||||
const formattedTz = timezone ? ` ${timezone}` : '';
|
||||
|
||||
// If both times have the same meridian (am/pm), only show it at the end
|
||||
if (start.slice(-2) === end.slice(-2)) {
|
||||
return `${start.replace(/[ap]m$/, '')}-${end}${formattedTz}`;
|
||||
}
|
||||
|
||||
return `${start}-${end}${formattedTz}`;
|
||||
}
|
||||
|
||||
private formatSingleTime(time24: string): string {
|
||||
if (!time24) return time24;
|
||||
|
||||
const hours = +time24;
|
||||
const period = hours >= 12 ? 'pm' : 'am';
|
||||
let displayHours = hours % 12;
|
||||
displayHours = displayHours === 0 ? 12 : displayHours; // Convert 0 to 12
|
||||
|
||||
return `${displayHours}${period}`;
|
||||
}
|
||||
}
|
||||
158
src/app/core/services/common/user.service.ts
Normal file
158
src/app/core/services/common/user.service.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import { afterNextRender, inject, Injectable, signal } from '@angular/core';
|
||||
import { StorageService } from './storage.service';
|
||||
import { BehaviorSubject, map, Observable, of, Subject, tap } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Menu, User, UserDetail } from '../../models/user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
private spid: number = 0;
|
||||
private clientid: number = 0;
|
||||
|
||||
userDetailsSignal = signal<User>({});
|
||||
|
||||
private readonly USER_EMAIL_KEY = 'CurrentUserEmail';
|
||||
private readonly USER_DETAILS_KEY = 'CurrentUserData';
|
||||
|
||||
private userLoggedInSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private http = inject(HttpClient);
|
||||
private storageService = inject(StorageService);
|
||||
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
// Initialize user details from storage if available
|
||||
const userDetails = this.getUserDetails();
|
||||
if (userDetails) {
|
||||
this.userDetailsSignal.set(userDetails);
|
||||
}
|
||||
|
||||
// Check if user is logged in
|
||||
const userEmail = this.getUser();
|
||||
this.userLoggedInSubject.next(!!userEmail);
|
||||
});
|
||||
}
|
||||
|
||||
watchUser(): Observable<boolean> {
|
||||
return this.userLoggedInSubject.asObservable();
|
||||
}
|
||||
|
||||
setUser(email: string): void {
|
||||
if (!email) {
|
||||
console.error('Cannot set empty user email');
|
||||
return;
|
||||
}
|
||||
|
||||
this.storageService.setItem(this.USER_EMAIL_KEY, email);
|
||||
this.userLoggedInSubject.next(true);
|
||||
}
|
||||
|
||||
getUser(): string | null {
|
||||
const user = this.storageService.getItem(this.USER_EMAIL_KEY);
|
||||
if (!user) {
|
||||
this.userLoggedInSubject.next(false);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
setUserDetails(): Observable<User> {
|
||||
const userDetails = this.getUserDetails();
|
||||
|
||||
if (userDetails) {
|
||||
return of(userDetails);
|
||||
}
|
||||
|
||||
const email = this.getUser();
|
||||
|
||||
if (!email) {
|
||||
console.error('invalid user');
|
||||
}
|
||||
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetUserDetails/${email}`).pipe(
|
||||
map(response => this.mapToUser(response)),
|
||||
tap(user => this.saveUserToStorage(user)));
|
||||
}
|
||||
|
||||
getUserDetails(): User | null {
|
||||
return this.storageService.get<User>(this.USER_DETAILS_KEY);
|
||||
}
|
||||
|
||||
clearUser(): void {
|
||||
this.storageService.removeItem(this.USER_EMAIL_KEY);
|
||||
this.storageService.removeItem(this.USER_DETAILS_KEY);
|
||||
this.userLoggedInSubject.next(false);
|
||||
}
|
||||
|
||||
isLoggedIn(): boolean {
|
||||
return !!this.getUser();
|
||||
}
|
||||
|
||||
getSafeUser(): string {
|
||||
return this.getUser() || '';
|
||||
}
|
||||
|
||||
getUserSpid(): number {
|
||||
if (this.spid === 0) {
|
||||
const userDetails = this.getUserDetails();
|
||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.spid) {
|
||||
this.spid = userDetails.userDetails.spid;
|
||||
}
|
||||
}
|
||||
return this.spid;
|
||||
}
|
||||
|
||||
getUserClientid(): number {
|
||||
if (this.clientid === 0) {
|
||||
const userDetails = this.getUserDetails();
|
||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.clientid) {
|
||||
this.clientid = userDetails.userDetails.clientid;
|
||||
}
|
||||
}
|
||||
return this.clientid;
|
||||
}
|
||||
|
||||
private mapToUser(data: any): User {
|
||||
return {
|
||||
roles: data.roleDetails || null,
|
||||
menus: data.menuDetails || null,
|
||||
menuDetails: this.mapMenuDetails(data.menuPageDetails),
|
||||
userDetails: this.mapUserDetails(data.userDetails)
|
||||
};
|
||||
}
|
||||
|
||||
private mapMenuDetails(menuPageDetails: any[]): Menu[] | null {
|
||||
if (!menuPageDetails) return null;
|
||||
|
||||
return menuPageDetails.map(item => ({
|
||||
name: item.MENUNAME,
|
||||
pageName: item.PAGENAME
|
||||
}));
|
||||
}
|
||||
|
||||
private mapUserDetails(userDetails: any): UserDetail | null {
|
||||
if (!userDetails) return null;
|
||||
|
||||
return {
|
||||
spid: userDetails.SPID,
|
||||
clientid: userDetails.CLIENTID,
|
||||
urlKey: userDetails.ENCURLKEY,
|
||||
logoName: userDetails.LOGONAME,
|
||||
themeName: userDetails.THEMENAME
|
||||
};
|
||||
}
|
||||
|
||||
private saveUserToStorage(user: User): void {
|
||||
try {
|
||||
this.storageService.set(this.USER_DETAILS_KEY, user);
|
||||
this.userDetailsSignal.set(user);
|
||||
} catch (e) {
|
||||
console.error('Error saving user details to session storage', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/app/core/services/home.service.ts
Normal file
43
src/app/core/services/home.service.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { UserService } from './common/user.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HomeService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
private userService = inject(UserService);
|
||||
|
||||
getCarnetSummaryData(): Observable<any> {
|
||||
const userid = this.userService.getUser();
|
||||
return this.http.get(`${this.apiUrl}/${this.apiDb}/GetCarnetSummaryData/${userid}`);
|
||||
}
|
||||
|
||||
getCarnetDataByStatus(spid: number, carnetStatus: string): Observable<any> {
|
||||
const userid = this.userService.getUser();
|
||||
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCarnetDetailsbyCarnetStatus/${spid}/${userid}/${carnetStatus}`).pipe(
|
||||
map(response => this.mapToCarnetData(response)));
|
||||
}
|
||||
|
||||
private mapToCarnetData(data: any[]): any[] {
|
||||
return data.map(item => ({
|
||||
applicationName: item.APPLICATIONNAME,
|
||||
holderName: item.HOLDERNAME,
|
||||
carnetNumber: item.CARNETNO,
|
||||
usSets: item.USSETS,
|
||||
foreignSets: item.FOREIGNSETS,
|
||||
transitSets: item.TRANSITSETS,
|
||||
carnetValue: item.CARNETVALUE,
|
||||
issueDate: item.ISSUEDATE || null,
|
||||
expiryDate: item.EXPDATE || null,
|
||||
orderType: item.ORDERTYPE,
|
||||
carnetStatus: item.CARNETSTATUS
|
||||
}));
|
||||
}
|
||||
}
|
||||
44
src/app/core/services/stylemanager.service.ts
Normal file
44
src/app/core/services/stylemanager.service.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StyleManagerService {
|
||||
|
||||
/**
|
||||
* Set the stylesheet with the specified key.
|
||||
*/
|
||||
setStyle(key: string, href: string) {
|
||||
getLinkElementForKey(key).setAttribute('href', href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the stylesheet with the specified key.
|
||||
*/
|
||||
removeStyle(key: string) {
|
||||
const existingLinkElement = getExistingLinkElementByKey(key);
|
||||
if (existingLinkElement) {
|
||||
document.head.removeChild(existingLinkElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLinkElementForKey(key: string) {
|
||||
return getExistingLinkElementByKey(key) || createLinkElementWithKey(key);
|
||||
}
|
||||
|
||||
function getExistingLinkElementByKey(key: string) {
|
||||
return document.head.querySelector(`link[rel="stylesheet"].${getClassNameForKey(key)}`);
|
||||
}
|
||||
|
||||
function createLinkElementWithKey(key: string) {
|
||||
const linkEl = document.createElement('link');
|
||||
linkEl.setAttribute('rel', 'stylesheet');
|
||||
linkEl.classList.add(getClassNameForKey(key));
|
||||
document.head.appendChild(linkEl);
|
||||
return linkEl;
|
||||
}
|
||||
|
||||
function getClassNameForKey(key: string) {
|
||||
return `style-manager-${key}`;
|
||||
}
|
||||
18
src/app/core/services/theme.service.ts
Normal file
18
src/app/core/services/theme.service.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { StyleManagerService } from './stylemanager.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ThemeService {
|
||||
|
||||
private styleManager = inject(StyleManagerService);
|
||||
|
||||
setTheme(themeToSet: string) {
|
||||
|
||||
this.styleManager.setStyle(
|
||||
"theme",
|
||||
`themes/${themeToSet}.css`
|
||||
);
|
||||
}
|
||||
}
|
||||
30
src/app/core/services/user-preference.service.ts
Normal file
30
src/app/core/services/user-preference.service.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { DEFAULT_USER_PREFERENCES, UserPreferences } from '../models/user-preference';
|
||||
import { CookieHelperService } from './common/cookie.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserPreferencesService {
|
||||
private readonly COOKIE_KEY = 'user_preferences';
|
||||
|
||||
private cookieService = inject(CookieHelperService);
|
||||
|
||||
getPreferences(): UserPreferences {
|
||||
const preferences = this.cookieService.get<UserPreferences>(this.COOKIE_KEY);
|
||||
return preferences || { ...DEFAULT_USER_PREFERENCES };
|
||||
}
|
||||
|
||||
savePreferences(preferences: UserPreferences): void {
|
||||
this.cookieService.set(this.COOKIE_KEY, preferences);
|
||||
}
|
||||
|
||||
getUserPrefenceByKey(keyName: string): any {
|
||||
const preferences = this.getPreferences();
|
||||
return preferences[keyName as keyof typeof preferences];
|
||||
}
|
||||
|
||||
resetToDefaults(): void {
|
||||
this.cookieService.remove(this.COOKIE_KEY);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user