initial version
This commit is contained in:
commit
fadfa87cdb
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 @@
|
|||||||
|
# ServiceProviderApp
|
||||||
|
|
||||||
|
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.5.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
To start a local development server, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate component component-name
|
||||||
|
```
|
||||||
|
|
||||||
|
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the project run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
For end-to-end (e2e) testing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||||
120
angular.json
Normal file
120
angular.json
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"service-provider-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-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/service-provider-app",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": [],
|
||||||
|
"server": "src/main.server.ts",
|
||||||
|
"outputMode": "server",
|
||||||
|
"ssr": {
|
||||||
|
"entry": "src/server.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kB",
|
||||||
|
"maximumError": "1MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "service-provider-app:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "service-provider-app:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": "b249b897-1dd5-450b-9f05-d8dfa49b1876"
|
||||||
|
}
|
||||||
|
}
|
||||||
14944
package-lock.json
generated
Normal file
14944
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
package.json
Normal file
49
package.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "service-provider-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:service-provider-app": "node dist/service-provider-app/server/server.mjs"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/cdk": "^19.2.16",
|
||||||
|
"@angular/common": "^19.2.0",
|
||||||
|
"@angular/compiler": "^19.2.0",
|
||||||
|
"@angular/core": "^19.2.0",
|
||||||
|
"@angular/forms": "^19.2.0",
|
||||||
|
"@angular/material": "^19.2.16",
|
||||||
|
"@angular/material-moment-adapter": "^19.2.16",
|
||||||
|
"@angular/platform-browser": "^19.2.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^19.2.0",
|
||||||
|
"@angular/platform-server": "^19.2.0",
|
||||||
|
"@angular/router": "^19.2.0",
|
||||||
|
"@angular/ssr": "^19.2.5",
|
||||||
|
"chart.js": "^4.3.0",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"ng2-charts": "^8.0.0",
|
||||||
|
"ngx-cookie-service": "^19.1.2",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.15.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^19.2.5",
|
||||||
|
"@angular/cli": "^19.2.5",
|
||||||
|
"@angular/compiler-cli": "^19.2.0",
|
||||||
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@types/node": "^18.18.0",
|
||||||
|
"jasmine-core": "~5.6.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.7.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');
|
||||||
|
});
|
||||||
|
});
|
||||||
39
src/app/app.component.ts
Normal file
39
src/app/app.component.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Component } 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 {
|
||||||
|
title = 'service-provider-app';
|
||||||
|
isUserLoggedIn = false;
|
||||||
|
private userSubscription!: Subscription;
|
||||||
|
|
||||||
|
constructor(private userService: 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/app/app.config.server.ts
Normal file
14
src/app/app.config.server.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideServerRendering } from '@angular/platform-server';
|
||||||
|
import { provideServerRouting } from '@angular/ssr';
|
||||||
|
import { appConfig } from './app.config';
|
||||||
|
import { serverRoutes } from './app.routes.server';
|
||||||
|
|
||||||
|
const serverConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideServerRendering(),
|
||||||
|
provideServerRouting(serverRoutes)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||||
16
src/app/app.config.ts
Normal file
16
src/app/app.config.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { ApplicationConfig, 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 { provideHttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
|
provideRouter(routes),
|
||||||
|
provideClientHydration(withEventReplay()),
|
||||||
|
provideHttpClient(),
|
||||||
|
provideCharts(withDefaultRegisterables())]
|
||||||
|
};
|
||||||
8
src/app/app.routes.server.ts
Normal file
8
src/app/app.routes.server.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||||
|
|
||||||
|
export const serverRoutes: ServerRoute[] = [
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
renderMode: RenderMode.Prerender
|
||||||
|
}
|
||||||
|
];
|
||||||
25
src/app/app.routes.ts
Normal file
25
src/app/app.routes.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { LoginComponent } from './login/login.component';
|
||||||
|
import { AuthGuard } from './guards/auth.guard';
|
||||||
|
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { AppIdGuard } from './guards/appid.guard';
|
||||||
|
import { EditServiceProviderComponent } from './service-provider/edit/edit-service-provider.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: 'service-provider/:id', component: EditServiceProviderComponent },
|
||||||
|
{ path: '', redirectTo: 'home', pathMatch: 'full' }
|
||||||
|
],
|
||||||
|
canActivate: [AuthGuard, AppIdGuard]
|
||||||
|
},
|
||||||
|
{ path: '', redirectTo: 'login', pathMatch: 'full' },
|
||||||
|
{ path: '**', redirectTo: '/404' }
|
||||||
|
];
|
||||||
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();
|
||||||
|
}
|
||||||
54
src/app/common/secured-header/secured-header.component.html
Normal file
54
src/app/common/secured-header/secured-header.component.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<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 [matMenuTriggerFor]="users">Users</button>
|
||||||
|
<mat-menu #users="matMenu">
|
||||||
|
<button mat-menu-item (click)="navigateTo('usersettings')">User Settings</button>
|
||||||
|
</mat-menu>
|
||||||
|
<button mat-button [matMenuTriggerFor]="maintenance">Maintenance</button>
|
||||||
|
<mat-menu #maintenance="matMenu">
|
||||||
|
<button mat-menu-item (click)="navigateTo('preparer')">Preparer</button>
|
||||||
|
<button mat-menu-item (click)="navigateTo('holder')">Holder</button>
|
||||||
|
<button mat-menu-item (click)="navigateTo('carnet')">Carnet</button>
|
||||||
|
</mat-menu>
|
||||||
|
<button mat-button [matMenuTriggerFor]="admin">Admin</button>
|
||||||
|
<mat-menu #admin="matMenu">
|
||||||
|
<button mat-menu-item (click)="navigateTo('sequence')">Sequence</button>
|
||||||
|
<button mat-menu-item (click)="navigateTo('regions')">Regions</button>
|
||||||
|
<button mat-menu-item (click)="navigateTo('users')">Users</button>
|
||||||
|
</mat-menu>
|
||||||
|
<!--
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/app/common/secured-header/secured-header.component.ts
Normal file
70
src/app/common/secured-header/secured-header.component.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { Component, effect, OnInit } from '@angular/core';
|
||||||
|
import { UserService } from '../../core/services/common/user.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
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 { ThemeService } from '../../core/services/theme.service';
|
||||||
|
import { StorageService } from '../../core/services/common/storage.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 = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private userService: UserService,
|
||||||
|
private router: Router,
|
||||||
|
private navigationService: NavigationService,
|
||||||
|
private themeService: ThemeService,
|
||||||
|
private storageService: StorageService
|
||||||
|
) {
|
||||||
|
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.userService.clearUser();
|
||||||
|
this.storageService.clear();
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
this.showProfileMenu = false;
|
||||||
|
this.themeService.setTheme('default');
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateTo(route: string): void {
|
||||||
|
|
||||||
|
this.navigationService.navigate([route]);
|
||||||
|
this.showProfileMenu = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
14
src/app/core/models/preparer/basic-detail.ts
Normal file
14
src/app/core/models/preparer/basic-detail.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export interface BasicDetail {
|
||||||
|
clientid: number;
|
||||||
|
spid: number
|
||||||
|
name: string;
|
||||||
|
lookupCode: string;
|
||||||
|
address1: string;
|
||||||
|
address2?: string | null;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
country: string;
|
||||||
|
zip: string;
|
||||||
|
carnetIssuingRegion: string;
|
||||||
|
revenueLocation: string;
|
||||||
|
}
|
||||||
20
src/app/core/models/preparer/contact.ts
Normal file
20
src/app/core/models/preparer/contact.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export interface Contact {
|
||||||
|
clientContactId: number;
|
||||||
|
spid: number
|
||||||
|
clientid: number;
|
||||||
|
defaultContact: boolean;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
middleInitial?: string | null;
|
||||||
|
title: string;
|
||||||
|
phone: string;
|
||||||
|
mobile: string;
|
||||||
|
fax?: string | null;
|
||||||
|
email: string;
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
lastUpdatedBy?: string | null;
|
||||||
|
lastUpdatedDate?: Date | null;
|
||||||
|
isInactive?: boolean | null; // TODO
|
||||||
|
inactivatedDate?: Date | null; // TODO
|
||||||
|
}
|
||||||
10
src/app/core/models/preparer/location.ts
Normal file
10
src/app/core/models/preparer/location.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export interface Location {
|
||||||
|
id: number;
|
||||||
|
clientId: number;
|
||||||
|
locationName: string;
|
||||||
|
address1: string;
|
||||||
|
address2?: string | null;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
country: string;
|
||||||
|
}
|
||||||
7
src/app/core/models/preparer/preparer-filter.ts
Normal file
7
src/app/core/models/preparer/preparer-filter.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface PreparerFilter {
|
||||||
|
name?: string;
|
||||||
|
address1?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
lookupCode?: 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;
|
||||||
|
}
|
||||||
17
src/app/core/models/service-provider/basic-detail.ts
Normal file
17
src/app/core/models/service-provider/basic-detail.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export interface BasicDetail {
|
||||||
|
cargoSurety: string;
|
||||||
|
cargoPolicyNo: string;
|
||||||
|
bondSurety: string;
|
||||||
|
spid: number;
|
||||||
|
companyName: string;
|
||||||
|
lookupCode?: string;
|
||||||
|
address1: string;
|
||||||
|
address2?: string;
|
||||||
|
city: string;
|
||||||
|
country: string;
|
||||||
|
state: string;
|
||||||
|
zip: string;
|
||||||
|
issuingRegion: string;
|
||||||
|
replacementRegion: string;
|
||||||
|
appid: string;
|
||||||
|
}
|
||||||
10
src/app/core/models/service-provider/basic-fee.ts
Normal file
10
src/app/core/models/service-provider/basic-fee.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export interface BasicFee {
|
||||||
|
basicFeeId: number;
|
||||||
|
startCarnetValue: number;
|
||||||
|
endCarnetValue: number | null;
|
||||||
|
fees: number;
|
||||||
|
effectiveDate: Date;
|
||||||
|
spid?: number;
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
}
|
||||||
9
src/app/core/models/service-provider/carnet-fee.ts
Normal file
9
src/app/core/models/service-provider/carnet-fee.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface CarnetFee {
|
||||||
|
feeCommissionId: number;
|
||||||
|
feeType: string;
|
||||||
|
commissionRate: number;
|
||||||
|
effectiveDate: Date;
|
||||||
|
spid: number;
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
}
|
||||||
8
src/app/core/models/service-provider/carnet-sequence.ts
Normal file
8
src/app/core/models/service-provider/carnet-sequence.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface CarnetSequence {
|
||||||
|
spid: number;
|
||||||
|
carnetType: string;
|
||||||
|
region: number;
|
||||||
|
startNumber: number;
|
||||||
|
endNumber: number;
|
||||||
|
lastNumber: number;
|
||||||
|
}
|
||||||
19
src/app/core/models/service-provider/contact.ts
Normal file
19
src/app/core/models/service-provider/contact.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export interface Contact {
|
||||||
|
spContactId: number;
|
||||||
|
serviceProviderId: number;
|
||||||
|
defaultContact: boolean;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
middleInitial: string;
|
||||||
|
title: string;
|
||||||
|
phone: string;
|
||||||
|
mobile: string;
|
||||||
|
fax: string;
|
||||||
|
email: string;
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
lastUpdatedBy?: string | null;
|
||||||
|
lastUpdatedDate?: Date | null;
|
||||||
|
isInactive?: boolean | null;
|
||||||
|
inactivatedDate?: Date | null;
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
export interface ContinuationSheetFee {
|
||||||
|
id: number;
|
||||||
|
spid: number;
|
||||||
|
rate: number;
|
||||||
|
effectiveDate: Date;
|
||||||
|
customerType: string;
|
||||||
|
carnetType: string;
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
}
|
||||||
12
src/app/core/models/service-provider/counterfoil-fee.ts
Normal file
12
src/app/core/models/service-provider/counterfoil-fee.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface CounterfoilFee {
|
||||||
|
id: number,
|
||||||
|
spid: number,
|
||||||
|
startSets: number,
|
||||||
|
endSets: number,
|
||||||
|
customerType: string,
|
||||||
|
carnetType: string,
|
||||||
|
effectiveDate: Date,
|
||||||
|
rate: number,
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
}
|
||||||
12
src/app/core/models/service-provider/expedited-fee.ts
Normal file
12
src/app/core/models/service-provider/expedited-fee.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface ExpeditedFee {
|
||||||
|
expeditedFeeId: number;
|
||||||
|
customerType: string;
|
||||||
|
deliveryType: string;
|
||||||
|
startTime: number;
|
||||||
|
endTime: number;
|
||||||
|
timeZone: string;
|
||||||
|
fee: number;
|
||||||
|
effectiveDate: Date;
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
}
|
||||||
12
src/app/core/models/service-provider/security-deposit.ts
Normal file
12
src/app/core/models/service-provider/security-deposit.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface SecurityDeposit {
|
||||||
|
securityDepositId: number;
|
||||||
|
holderType: string;
|
||||||
|
uscibMember: boolean;
|
||||||
|
specialCommodity: string;
|
||||||
|
specialCountry: string;
|
||||||
|
rate: number;
|
||||||
|
effectiveDate: Date;
|
||||||
|
spid: number;
|
||||||
|
dateCreated?: Date | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
}
|
||||||
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
|
||||||
|
};
|
||||||
18
src/app/core/models/user.ts
Normal file
18
src/app/core/models/user.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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;
|
||||||
|
urlKey: string;
|
||||||
|
logoName: string;
|
||||||
|
themeName: string;
|
||||||
|
}
|
||||||
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;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/app/core/services/common/auth.service.ts
Normal file
17
src/app/core/services/common/auth.service.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
login(username: string, password: string): Observable<any> {
|
||||||
|
return this.http.post(`${this.apiUrl}/login`, { p_emailaddr: username, p_password: password });
|
||||||
|
}
|
||||||
|
}
|
||||||
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 { 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;
|
||||||
|
|
||||||
|
constructor(private http: 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 { Injectable } from '@angular/core';
|
||||||
|
import { CookieService as NgxCookieService } from 'ngx-cookie-service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CookieHelperService {
|
||||||
|
constructor(private ngxCookieService: 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 { 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';
|
||||||
|
|
||||||
|
constructor(private router: Router,
|
||||||
|
private location: Location,
|
||||||
|
private storageService: 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/app/core/services/common/notification.service.ts
Normal file
30
src/app/core/services/common/notification.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class NotificationService {
|
||||||
|
constructor(private snackBar: 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']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/app/core/services/common/storage.service.ts
Normal file
50
src/app/core/services/common/storage.service.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
130
src/app/core/services/common/user.service.ts
Normal file
130
src/app/core/services/common/user.service.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { 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;
|
||||||
|
|
||||||
|
userDetailsSignal = signal<User>({});
|
||||||
|
|
||||||
|
private readonly USER_EMAIL_KEY = 'CurrentUserEmail';
|
||||||
|
private readonly USER_DETAILS_KEY = 'CurrentUserData';
|
||||||
|
|
||||||
|
private userLoggedInSubject = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private storageService: StorageService) { }
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/app/core/services/home.service.ts
Normal file
42
src/app/core/services/home.service.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { 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;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: 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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/app/core/services/preparer/basic-detail.service.ts
Normal file
77
src/app/core/services/preparer/basic-detail.service.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { BasicDetail } from '../../models/preparer/basic-detail';
|
||||||
|
import { filter, map, Observable, of } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class BasicDetailService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService) { }
|
||||||
|
|
||||||
|
getBasicDetailsById(id: number): BasicDetail | any {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparerByClientid?p_spid=${this.userService.getUserSpid()}&p_clientid=${id}`).pipe(
|
||||||
|
filter(response => response.length > 0),
|
||||||
|
map(response => this.mapToBasicDetail(response?.[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToBasicDetail(basicDetails: any): BasicDetail {
|
||||||
|
return {
|
||||||
|
clientid: basicDetails.CLIENTID,
|
||||||
|
spid: basicDetails.SPID,
|
||||||
|
name: basicDetails.NAMEOF,
|
||||||
|
lookupCode: basicDetails.LOOKUPCODE,
|
||||||
|
address1: basicDetails.ADDRESS1,
|
||||||
|
address2: basicDetails.ADDRESS2,
|
||||||
|
city: basicDetails.CITY,
|
||||||
|
state: basicDetails.STATE,
|
||||||
|
country: basicDetails.COUNTRY,
|
||||||
|
carnetIssuingRegion: basicDetails.ISSUINGREGION,
|
||||||
|
revenueLocation: basicDetails.REVENUELOCATION,
|
||||||
|
zip: basicDetails.ZIP,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createBasicDetails(data: BasicDetail): Observable<any> {
|
||||||
|
const basicDetails = {
|
||||||
|
p_spid: this.userService.getUserSpid(),
|
||||||
|
p_clientname: data.name,
|
||||||
|
p_lookupcode: data.lookupCode,
|
||||||
|
p_address1: data.address1,
|
||||||
|
p_address2: data.address2,
|
||||||
|
p_city: data.city,
|
||||||
|
p_state: data.state,
|
||||||
|
p_country: data.country,
|
||||||
|
p_zip: data.zip,
|
||||||
|
p_issuingregion: data.carnetIssuingRegion,
|
||||||
|
p_revenuelocation: data.revenueLocation,
|
||||||
|
p_userid: this.userService.getUser(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateNewClients`, basicDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBasicDetails(id: number, data: BasicDetail): Observable<any> {
|
||||||
|
const basicDetails = {
|
||||||
|
p_spid: this.userService.getUserSpid(),
|
||||||
|
p_clientid: id,
|
||||||
|
p_clientname: data.name,
|
||||||
|
p_lookupcode: data.lookupCode,
|
||||||
|
p_address1: data.address1,
|
||||||
|
p_address2: data.address2,
|
||||||
|
p_city: data.city,
|
||||||
|
p_state: data.state,
|
||||||
|
p_country: data.country,
|
||||||
|
p_zip: data.zip,
|
||||||
|
p_revenuelocation: data.revenueLocation,
|
||||||
|
p_userid: this.userService.getUser(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateClient`, basicDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/app/core/services/preparer/client.service.ts
Normal file
9
src/app/core/services/preparer/client.service.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ClientService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
}
|
||||||
87
src/app/core/services/preparer/contact.service.ts
Normal file
87
src/app/core/services/preparer/contact.service.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { map, Observable, of } from 'rxjs';
|
||||||
|
import { Contact } from '../../models/preparer/contact';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ContactService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService) { }
|
||||||
|
|
||||||
|
getContactsById(id: number): Observable<Contact[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetPreparerContactsByClientid?p_spid=${this.userService.getUserSpid()}&p_clientid=${id}`).pipe(
|
||||||
|
map(response => this.mapToContacts(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToContacts(data: any[]): Contact[] {
|
||||||
|
return data.map(contact => ({
|
||||||
|
clientContactId: contact.CLIENTCONTACTID,
|
||||||
|
spid: contact.SPID,
|
||||||
|
clientid: contact.CLIENTID,
|
||||||
|
defaultContact: contact.DEFCONTACTFLAG === 'Y',
|
||||||
|
firstName: contact.FIRSTNAME,
|
||||||
|
lastName: contact.LASTNAME,
|
||||||
|
title: contact.TITLE,
|
||||||
|
phone: contact.PHONENO,
|
||||||
|
mobile: contact.MOBILENO,
|
||||||
|
fax: contact.FAXNO || null,
|
||||||
|
email: contact.EMAILADDRESS,
|
||||||
|
middleInitial: contact.MIDDLEINITIAL || null,
|
||||||
|
createdBy: contact.CREATEDBY || null,
|
||||||
|
dateCreated: contact.DATECREATED || null,
|
||||||
|
lastUpdatedBy: contact.LASTUPDATEDBY || null,
|
||||||
|
lastUpdatedDate: contact.LASTUPDATEDDATE || null,
|
||||||
|
isInactive: contact.INACTIVEFLAG === 'Y' || false,
|
||||||
|
inactivatedDate: contact.INACTIVEDATE || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createContact(clientid: number, data: Contact): Observable<any> {
|
||||||
|
const contact = {
|
||||||
|
p_spid: this.userService.getUserSpid(),
|
||||||
|
p_clientid: clientid,
|
||||||
|
p_defcontactflag: data.defaultContact ? 'Y' : 'N',
|
||||||
|
p_contactstable: [{
|
||||||
|
FirstName: data.firstName,
|
||||||
|
LastName: data.lastName,
|
||||||
|
MiddleInitial: data.middleInitial,
|
||||||
|
Title: data.title,
|
||||||
|
EmailAddress: data.email,
|
||||||
|
MobileNo: data.mobile,
|
||||||
|
PhoneNo: data.phone,
|
||||||
|
FaxNo: data.fax
|
||||||
|
}],
|
||||||
|
p_user_id: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateClientContacts`, contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContact(spContactId: number, data: Contact): Observable<any> {
|
||||||
|
const contact = {
|
||||||
|
p_spid: this.userService.getUserSpid(),
|
||||||
|
p_clientcontactid: spContactId,
|
||||||
|
p_firstname: data.firstName,
|
||||||
|
p_lastname: data.lastName,
|
||||||
|
P_middleinitial: data.middleInitial,
|
||||||
|
p_title: data.title,
|
||||||
|
p_phone: data.phone,
|
||||||
|
p_mobileno: data.mobile,
|
||||||
|
p_fax: data.fax,
|
||||||
|
p_emailaddress: data.email,
|
||||||
|
p_user_id: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateClientContacts`, contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteContact(clientContactId: string): Observable<any> {
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/InactivateSPContact?p_clientcontactid=${clientContactId}`, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/app/core/services/preparer/location.service.ts
Normal file
9
src/app/core/services/preparer/location.service.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class LocationService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BasicDetail } from '../../models/service-provider/basic-detail';
|
||||||
|
import { filter, map, Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class BasicDetailService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService) { }
|
||||||
|
|
||||||
|
getBasicDetailsById(id: number): BasicDetail | any {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetSelectedServiceprovider?p_spid=${id}`).pipe(
|
||||||
|
filter(response => response.length > 0),
|
||||||
|
map(response => this.mapToBasicDetail(response?.[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToBasicDetail(basicDetails: any): BasicDetail {
|
||||||
|
return {
|
||||||
|
spid: basicDetails.SPID,
|
||||||
|
companyName: basicDetails.NAMEOF,
|
||||||
|
lookupCode: basicDetails.LOOKUPCODE,
|
||||||
|
address1: basicDetails.ADDRESS1,
|
||||||
|
address2: basicDetails.ADDRESS2,
|
||||||
|
city: basicDetails.CITY,
|
||||||
|
state: basicDetails.STATE,
|
||||||
|
country: basicDetails.COUNTRY,
|
||||||
|
issuingRegion: basicDetails.ISSUINGREGION,
|
||||||
|
replacementRegion: basicDetails.REPLACEMENTREGION,
|
||||||
|
zip: basicDetails.ZIP,
|
||||||
|
cargoSurety: basicDetails.CARGOSURETY,
|
||||||
|
cargoPolicyNo: basicDetails.CARGOPOLICYNO,
|
||||||
|
bondSurety: basicDetails.BONDSURETY,
|
||||||
|
appid: basicDetails.ENCURLKEY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createBasicDetails(data: BasicDetail): Observable<any> {
|
||||||
|
|
||||||
|
const basicDetails = {
|
||||||
|
p_name: data.companyName,
|
||||||
|
p_lookupcode: data.lookupCode,
|
||||||
|
p_address1: data.address1,
|
||||||
|
p_address2: data.address2,
|
||||||
|
p_city: data.city,
|
||||||
|
p_state: data.state,
|
||||||
|
p_zip: data.zip,
|
||||||
|
p_country: data.country,
|
||||||
|
p_issuingregion: data.issuingRegion,
|
||||||
|
p_replacementregion: data.replacementRegion,
|
||||||
|
p_bondsurety: data.bondSurety,
|
||||||
|
p_cargopolicyno: data.cargoPolicyNo,
|
||||||
|
p_cargosurety: data.cargoSurety,
|
||||||
|
p_user_id: this.userService.getUser(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/InsertNewServiceProvider`, basicDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBasicDetails(id: number, data: BasicDetail): Observable<any> {
|
||||||
|
|
||||||
|
const basicDetails = {
|
||||||
|
p_spid: id,
|
||||||
|
p_name: data.companyName,
|
||||||
|
p_lookupcode: data.lookupCode,
|
||||||
|
p_address1: data.address1,
|
||||||
|
p_address2: data.address2,
|
||||||
|
p_city: data.city,
|
||||||
|
p_state: data.state,
|
||||||
|
p_zip: data.zip,
|
||||||
|
p_country: data.country,
|
||||||
|
p_issuingregion: data.issuingRegion,
|
||||||
|
p_replacementregion: data.replacementRegion,
|
||||||
|
p_bondsurety: data.bondSurety,
|
||||||
|
p_cargopolicyno: data.cargoPolicyNo,
|
||||||
|
p_cargosurety: data.cargoSurety,
|
||||||
|
p_user_id: this.userService.getUser(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateServiceProvider`, basicDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/app/core/services/service-provider/basic-fee.service.ts
Normal file
67
src/app/core/services/service-provider/basic-fee.service.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
import { BasicFee } from '../../models/service-provider/basic-fee';
|
||||||
|
import { CommonService } from '../common/common.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class BasicFeeService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService, private commonService: CommonService) { }
|
||||||
|
|
||||||
|
getBasicFees(spid: number): Observable<BasicFee[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetBasicFeeRates?P_SPID=${spid}&P_ACTIVE_INACTIVE=ACTIVE`).pipe(
|
||||||
|
map(response => this.mapToBasicFees(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToBasicFees(data: any[]): BasicFee[] {
|
||||||
|
return data.map(item => ({
|
||||||
|
basicFeeId: item.BASICFEESETUPID,
|
||||||
|
startCarnetValue: item.STARTCARNETVALUE,
|
||||||
|
endCarnetValue: item.ENDCARNETVALUE,
|
||||||
|
fees: item.FEES,
|
||||||
|
effectiveDate: item.EFFDATE,
|
||||||
|
createdBy: item.CREATEDBY || null,
|
||||||
|
dateCreated: item.DATECREATED || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createBasicFee(spid: number, fee: BasicFee): Observable<any> {
|
||||||
|
const payload = {
|
||||||
|
P_SPID: spid,
|
||||||
|
P_STARTCARNETVALUE: fee.startCarnetValue,
|
||||||
|
P_ENDCARNETVALUE: fee.endCarnetValue,
|
||||||
|
P_FEES: fee.fees,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(fee.effectiveDate),
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.post<any>(`${this.apiUrl}/${this.apiDb}/CreateBasicFee`, payload);
|
||||||
|
}
|
||||||
|
updateBasicFee(feeId: number, fee: BasicFee): Observable<any> {
|
||||||
|
const payload = {
|
||||||
|
P_BASICFEESETUPID: feeId,
|
||||||
|
P_STARTCARNETVALUE: fee.startCarnetValue,
|
||||||
|
P_ENDCARNETVALUE: fee.endCarnetValue,
|
||||||
|
P_FEES: fee.fees,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(fee.effectiveDate),
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}/UpdateBasicFee`, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteBasicFee(feeId: number): Observable<any> {
|
||||||
|
// return this.http.delete<any>(`${this.apiUrl}/${this.apiDb}/DeleteBasicFee/${feeId}`, {
|
||||||
|
// params: {
|
||||||
|
// p_userid: this.userService.getUser()
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
62
src/app/core/services/service-provider/carnet-fee.service.ts
Normal file
62
src/app/core/services/service-provider/carnet-fee.service.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { CarnetFee } from '../../models/service-provider/carnet-fee';
|
||||||
|
import { CommonService } from '../common/common.service';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CarnetFeeService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService, private commonService: CommonService) { }
|
||||||
|
|
||||||
|
getFeeCommissions(spid: number): Observable<CarnetFee[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetFeeComm?P_SPID=${spid}&P_ACTIVE_INACTIVE=ACTIVE`).pipe(
|
||||||
|
map(response => this.mapToFeeCommissions(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToFeeCommissions(data: any[]): CarnetFee[] {
|
||||||
|
return data.map(item => ({
|
||||||
|
feeCommissionId: item.FEECOMMID,
|
||||||
|
feeType: item.FEETYPEID,
|
||||||
|
description: item.DESCRIPTION,
|
||||||
|
commissionRate: item.COMMRATE,
|
||||||
|
effectiveDate: item.EFFDATE,
|
||||||
|
spid: item.SPID,
|
||||||
|
createdBy: item.CREATEDBY || null,
|
||||||
|
dateCreated: item.DATECREATED || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createFeeCommission(spid: number, data: CarnetFee): Observable<any> {
|
||||||
|
const feeCommission = {
|
||||||
|
P_SPID: spid,
|
||||||
|
P_PARAMID: data.feeType,
|
||||||
|
P_COMMRATE: data.commissionRate,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.post<any>(`${this.apiUrl}/${this.apiDb}/CreateFeeComm`, feeCommission);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFeeCommission(id: number, data: CarnetFee): Observable<any> {
|
||||||
|
const feeCommission = {
|
||||||
|
P_FEECOMMID: id,
|
||||||
|
P_RATE: data.commissionRate,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}/UpdateFeeComm`, feeCommission);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteFeeCommission(id: number): Observable<void> {
|
||||||
|
// return this.http.delete<void>(`${this.apiUrl}/${this.apiDb}/DeleteFeeCommission/${id}`);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { CarnetSequence } from '../../models/service-provider/carnet-sequence';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CarnetSequenceService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService) { }
|
||||||
|
|
||||||
|
getCarnetSequenceById(id: number): Observable<CarnetSequence[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCarnetSequence?p_spid=${id}`).pipe(
|
||||||
|
map(response => this.mapToCarnetSequence(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToCarnetSequence(data: any[]): CarnetSequence[] {
|
||||||
|
return data.map(item => ({
|
||||||
|
spid: item.SPID,
|
||||||
|
region: item.REGIONID,
|
||||||
|
carnetType: item.CARNETTYPE,
|
||||||
|
startNumber: item.STARTNUMBER,
|
||||||
|
endNumber: item.ENDNUMBER,
|
||||||
|
lastNumber: item.LASTNUMBER
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createCarnetSequence(data: CarnetSequence): Observable<any> {
|
||||||
|
|
||||||
|
const carnetSequence = {
|
||||||
|
p_spid: data.spid,
|
||||||
|
p_regionid: data.region,
|
||||||
|
p_startnumber: data.startNumber,
|
||||||
|
p_endnumber: data.endNumber,
|
||||||
|
p_carnettype: data.carnetType
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateCarnetSequence`, carnetSequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/app/core/services/service-provider/contact.service.ts
Normal file
85
src/app/core/services/service-provider/contact.service.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { Contact } from '../../models/service-provider/contact';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ContactService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService) { }
|
||||||
|
|
||||||
|
getContactsById(id: number): Observable<Contact[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetSPAllContacts?p_SPid=${id}`).pipe(
|
||||||
|
map(response => this.mapToContacts(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToContacts(data: any[]): Contact[] {
|
||||||
|
return data.map(contact => ({
|
||||||
|
spContactId: contact.SPCONTACTID,
|
||||||
|
serviceProviderId: contact.SPID,
|
||||||
|
defaultContact: contact.DEFCONTACTFLAG === 'Y',
|
||||||
|
firstName: contact.FIRSTNAME,
|
||||||
|
lastName: contact.LASTNAME,
|
||||||
|
title: contact.TITLE,
|
||||||
|
phone: contact.PHONENO,
|
||||||
|
mobile: contact.MOBILENO,
|
||||||
|
fax: contact.FAXNO || null,
|
||||||
|
email: contact.EMAILADDRESS,
|
||||||
|
middleInitial: contact.MIDDLEINITIAL || null,
|
||||||
|
createdBy: contact.CREATEDBY || null,
|
||||||
|
dateCreated: contact.DATECREATED || null,
|
||||||
|
lastUpdatedBy: contact.LASTUPDATEDBY || null,
|
||||||
|
lastUpdatedDate: contact.LASTUPDATEDDATE || null,
|
||||||
|
isInactive: contact.INACTIVEFLAG === 'Y' || false,
|
||||||
|
inactivatedDate: contact.INACTIVEDATE || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createContact(spid: number, data: Contact): Observable<any> {
|
||||||
|
|
||||||
|
const contact = {
|
||||||
|
p_spid: spid,
|
||||||
|
p_defcontactflag: data.defaultContact ? 'Y' : 'N',
|
||||||
|
p_firstname: data.firstName,
|
||||||
|
p_lastname: data.lastName,
|
||||||
|
P_MIDDLEINITIAL: data.middleInitial,
|
||||||
|
p_title: data.title,
|
||||||
|
p_phoneno: data.phone,
|
||||||
|
p_mobileno: data.mobile,
|
||||||
|
p_faxno: data.fax,
|
||||||
|
p_emailaddress: data.email,
|
||||||
|
p_user_id: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/InsertSPContacts`, contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContact(spContactId: number, data: Contact): Observable<any> {
|
||||||
|
|
||||||
|
const contact = {
|
||||||
|
p_spcontactid: spContactId,
|
||||||
|
p_firstname: data.firstName,
|
||||||
|
p_lastname: data.lastName,
|
||||||
|
P_MIDDLEINITIAL: data.middleInitial,
|
||||||
|
p_title: data.title,
|
||||||
|
p_phoneno: data.phone,
|
||||||
|
p_mobileno: data.mobile,
|
||||||
|
p_faxno: data.fax,
|
||||||
|
p_emailaddress: data.email,
|
||||||
|
p_user_id: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateSPContacts`, contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteContact(spContactId: string): Observable<any> {
|
||||||
|
return this.http.post(`${this.apiUrl}/${this.apiDb}/InactivateSPContact?p_spcontactid=${spContactId}`, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ContinuationSheetFee } from '../../models/service-provider/continuation-sheet-fee';
|
||||||
|
import { CommonService } from '../common/common.service';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ContinuationSheetFeeService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService, private commonService: CommonService) { }
|
||||||
|
|
||||||
|
getContinuationSheets(spid: number): Observable<ContinuationSheetFee[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCsFeeRates?P_SPID=${spid}&P_ACTIVE_INACTIVE=ACTIVE`).pipe(
|
||||||
|
map(response => this.mapToContinuationSheetFee(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToContinuationSheetFee(data: any[]): ContinuationSheetFee[] {
|
||||||
|
return data.map(item => ({
|
||||||
|
id: item.CSFEESETUPID,
|
||||||
|
spid: item.SPID,
|
||||||
|
customerType: item.CUSTOMERTYPE,
|
||||||
|
carnetType: item.CARNETTYPE,
|
||||||
|
effectiveDate: item.EFFDATE,
|
||||||
|
rate: item.RATE,
|
||||||
|
createdBy: item.CREATEDBY || null,
|
||||||
|
dateCreated: item.DATECREATED || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
addContinuationSheet(spid: number, data: ContinuationSheetFee): Observable<any> {
|
||||||
|
|
||||||
|
const continuationSheet = {
|
||||||
|
P_SPID: spid,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_CUSTOMERTYPE: data.customerType,
|
||||||
|
P_CARNETTYPE: data.carnetType,
|
||||||
|
P_RATE: data.rate,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post<any>(`${this.apiUrl}/${this.apiDb}/CreateCsFee`, continuationSheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContinuationSheet(id: number, data: ContinuationSheetFee): Observable<any> {
|
||||||
|
|
||||||
|
const continuationSheet = {
|
||||||
|
P_CSFEESETUPID: id,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_RATE: data.rate,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}/UpdateCsFee`, continuationSheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteContinuationSheet(id: string): Observable<void> {
|
||||||
|
// return this.http.delete<void>(`${this.apiUrl}/${this.apiDb}/InactivateSPContact/${id}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { CounterfoilFee } from '../../models/service-provider/counterfoil-fee';
|
||||||
|
import { CommonService } from '../common/common.service';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CounterfoilFeeService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService, private commonService: CommonService) { }
|
||||||
|
|
||||||
|
getCounterfoils(spid: number): Observable<CounterfoilFee[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetCfFeeRates?P_SPID=${spid}&P_ACTIVE_INACTIVE=ACTIVE`).pipe(
|
||||||
|
map(response => this.mapToCounterFoilFee(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToCounterFoilFee(data: any[]): CounterfoilFee[] {
|
||||||
|
return data.map(item => ({
|
||||||
|
id: item.CFFEESETUPID,
|
||||||
|
spid: item.SPID,
|
||||||
|
startSets: item.STARTSETS,
|
||||||
|
endSets: item.ENDSETS,
|
||||||
|
customerType: item.CUSTOMERTYPE,
|
||||||
|
carnetType: item.CARNETTYPE,
|
||||||
|
effectiveDate: item.EFFDATE,
|
||||||
|
rate: item.RATE,
|
||||||
|
createdBy: item.CREATEDBY || null,
|
||||||
|
dateCreated: item.DATECREATED || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
addCounterfoil(spid: number, data: CounterfoilFee): Observable<any> {
|
||||||
|
|
||||||
|
const counterfoilFee = {
|
||||||
|
P_SPID: spid,
|
||||||
|
P_STARTSETS: data.startSets,
|
||||||
|
P_ENDSETS: data.endSets,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_CUSTOMERTYPE: data.customerType,
|
||||||
|
P_CARNETTYPE: data.carnetType,
|
||||||
|
P_RATE: data.rate,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post<any>(`${this.apiUrl}/${this.apiDb}/CreateCfFee`, counterfoilFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCounterfoil(id: number, data: CounterfoilFee): Observable<any> {
|
||||||
|
|
||||||
|
const counterfoilFee = {
|
||||||
|
P_CFFEESETUPID: id,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_RATE: data.rate,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}//UpdateCfFee`, counterfoilFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteCounterfoil(id: string): Observable<void> {
|
||||||
|
// return this.http.delete<void>(`${this.apiUrl}/${this.apiDb}/InactivateSPContact/${id}`);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { ExpeditedFee } from '../../models/service-provider/expedited-fee';
|
||||||
|
import { CommonService } from '../common/common.service';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ExpeditedFeeService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService, private commonService: CommonService) { }
|
||||||
|
|
||||||
|
getExpeditedFees(spid: number): Observable<ExpeditedFee[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetEfFeeRates?P_SPID=${spid}&P_ACTIVE_INACTIVE=ACTIVE`).pipe(
|
||||||
|
map(response => this.mapToExpeditedFee(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToExpeditedFee(data: any[]): ExpeditedFee[] {
|
||||||
|
return data.map(item => ({
|
||||||
|
expeditedFeeId: item.EXPFEESETUPID,
|
||||||
|
customerType: item.CUSTOMERTYPE,
|
||||||
|
deliveryType: item.DELIVERYTYPE,
|
||||||
|
startTime: item.STARTTIME,
|
||||||
|
endTime: item.ENDTIME,
|
||||||
|
timeZone: item.TIMEZONE,
|
||||||
|
fee: item.FEES,
|
||||||
|
effectiveDate: item.EFFDATE,
|
||||||
|
spid: item.SPID,
|
||||||
|
createdBy: item.CREATEDBY || null,
|
||||||
|
dateCreated: item.DATECREATED || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createExpeditedFee(spid: number, data: ExpeditedFee): Observable<any> {
|
||||||
|
|
||||||
|
const expeditedFee = {
|
||||||
|
P_SPID: spid,
|
||||||
|
P_CUSTOMERTYPE: data.customerType,
|
||||||
|
P_DELIVERYTYPE: data.deliveryType,
|
||||||
|
P_TIMEZONE: data.timeZone,
|
||||||
|
P_STARTTIME: +data.startTime,
|
||||||
|
P_ENDTIME: +data.endTime,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_FEES: data.fee,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post<any>(`${this.apiUrl}/${this.apiDb}/CreateEfFee`, expeditedFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateExpeditedFee(id: number, data: ExpeditedFee): Observable<any> {
|
||||||
|
|
||||||
|
const expeditedFee = {
|
||||||
|
P_EFFEESETUPID: id,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_FEES: data.fee,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}//UpdateEfFee`, expeditedFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteExpeditedFee(id: string): Observable<void> {
|
||||||
|
// return this.http.delete<void>(`${this.apiUrl}/${this.apiDb}/InactivateSPContact/${id}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map, Observable } from 'rxjs';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { SecurityDeposit } from '../../models/service-provider/security-deposit';
|
||||||
|
import { CommonService } from '../common/common.service';
|
||||||
|
import { UserService } from '../common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SecurityDepositService {
|
||||||
|
private apiUrl = environment.apiUrl;
|
||||||
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private userService: UserService, private commonService: CommonService) { }
|
||||||
|
|
||||||
|
getSecurityDeposits(spid: number): Observable<SecurityDeposit[]> {
|
||||||
|
return this.http.get<any[]>(`${this.apiUrl}/${this.apiDb}/GetBondRates?P_SPID=${spid}&P_ACTIVE_INACTIVE=ACTIVE`).pipe(
|
||||||
|
map(response => this.mapToSecurityDeposit(response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToSecurityDeposit(data: any[]): SecurityDeposit[] {
|
||||||
|
return data.map(item => ({
|
||||||
|
securityDepositId: item.BONDRATESETUPID,
|
||||||
|
holderType: item.HOLDERTYPE,
|
||||||
|
uscibMember: item.USCIBMEMBERFLAG,
|
||||||
|
specialCommodity: item.SPCLCOMMODITY,
|
||||||
|
specialCountry: item.SPCLCOUNTRY,
|
||||||
|
rate: item.RATE,
|
||||||
|
effectiveDate: item.EFFDATE,
|
||||||
|
spid: item.SPID,
|
||||||
|
createdBy: item.CREATEDBY || null,
|
||||||
|
dateCreated: item.DATECREATED || null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createSecurityDeposit(spid: number, data: SecurityDeposit): Observable<any> {
|
||||||
|
|
||||||
|
const securityDeposit = {
|
||||||
|
P_SPID: spid,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_HOLDERTYPE: data.holderType,
|
||||||
|
P_USCIBMEMBERFLAG: data.uscibMember ? 'Y' : 'N',
|
||||||
|
P_SPCLCOMMODITY: data.specialCommodity,
|
||||||
|
P_SPCLCOUNTRY: data.specialCountry,
|
||||||
|
P_RATE: data.rate,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.post<any>(`${this.apiUrl}/${this.apiDb}/CreateBondRate`, securityDeposit);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSecurityDeposit(id: number, data: SecurityDeposit): Observable<any> {
|
||||||
|
|
||||||
|
const securityDeposit = {
|
||||||
|
P_BONDRATESETUPID: id,
|
||||||
|
P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate),
|
||||||
|
P_RATE: data.rate,
|
||||||
|
P_USERID: this.userService.getUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}/UpdateBondRate`, securityDeposit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSecurityDeposit(id: string): Observable<void> {
|
||||||
|
// return this.http.delete<void>(`${this.apiUrl}/${this.apiDb}/InactivateSPContact/${id}`);
|
||||||
|
// }
|
||||||
|
}
|
||||||
46
src/app/core/services/stylemanager.service.ts
Normal file
46
src/app/core/services/stylemanager.service.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class StyleManagerService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}`;
|
||||||
|
}
|
||||||
19
src/app/core/services/theme.service.ts
Normal file
19
src/app/core/services/theme.service.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { StyleManagerService } from './stylemanager.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ThemeService {
|
||||||
|
|
||||||
|
constructor(private styleManager: 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 { 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';
|
||||||
|
|
||||||
|
constructor(private cookieService: 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/app/guards/appid.guard.ts
Normal file
24
src/app/guards/appid.guard.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ActivatedRouteSnapshot, CanActivate, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { NavigationService } from '../core/services/common/navigation.service';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AppIdGuard implements CanActivate {
|
||||||
|
constructor(private navigationService: NavigationService, private router: Router) { }
|
||||||
|
|
||||||
|
canActivate(
|
||||||
|
next: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot): boolean {
|
||||||
|
|
||||||
|
const appId = next.params['appId'];
|
||||||
|
const currentAppId = this.navigationService.getCurrentAppId();
|
||||||
|
if (appId === currentAppId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.router.navigate(['/404']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/app/guards/auth.guard.ts
Normal file
21
src/app/guards/auth.guard.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
|
||||||
|
import { UserService } from '../core/services/common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
constructor(private userService: UserService, private router: Router) { }
|
||||||
|
|
||||||
|
canActivate(
|
||||||
|
next: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot): boolean {
|
||||||
|
if (this.userService.isLoggedIn()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/app/home/chart/chart.component.html
Normal file
24
src/app/home/chart/chart.component.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<div class="dashboard-chart-container">
|
||||||
|
|
||||||
|
<div *ngIf="!chartData || chartData.length === 0" class="no-data">
|
||||||
|
<mat-icon>pie_chart</mat-icon>
|
||||||
|
<p>No carnet data available</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="charts-grid">
|
||||||
|
<div *ngFor="let config of chartConfigs; let i = index" class="chart-card">
|
||||||
|
<div class="chart-header">
|
||||||
|
<h4 class="chart-title">{{ config.title }}</h4>
|
||||||
|
<button mat-icon-button class="edit-icon" (click)="navigateToManageProvider(config.spid)"
|
||||||
|
matTooltip="Edit Service Provider">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<canvas baseChart [data]="config.data" [options]="config.options" [type]="chartType"
|
||||||
|
(chartClick)="chartClicked($event, i)" [plugins]="chartPlugins">
|
||||||
|
</canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
88
src/app/home/chart/chart.component.scss
Normal file
88
src/app/home/chart/chart.component.scss
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
.dashboard-chart-container {
|
||||||
|
padding: 24px 0px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
.charts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 250px;
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 0 8px;
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--mat-sys-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-icon {
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--mat-sys-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
height: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive adjustments
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dashboard-chart-container {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.charts-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/app/home/chart/chart.component.ts
Normal file
134
src/app/home/chart/chart.component.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';
|
||||||
|
import { BaseChartDirective } from 'ng2-charts';
|
||||||
|
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||||
|
import { CarnetStatus } from '../../core/models/carnet-status';
|
||||||
|
import { NavigationService } from '../../core/services/common/navigation.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chart',
|
||||||
|
imports: [CommonModule, AngularMaterialModule, BaseChartDirective],
|
||||||
|
templateUrl: './chart.component.html',
|
||||||
|
styleUrl: './chart.component.scss'
|
||||||
|
})
|
||||||
|
export class ChartComponent {
|
||||||
|
@Input() chartData: any[] = [];
|
||||||
|
@Input() carnetStatuses: CarnetStatus[] = [];
|
||||||
|
@Output() carnetStatusData = new EventEmitter<any>();
|
||||||
|
|
||||||
|
constructor(private navigationService: NavigationService) { }
|
||||||
|
|
||||||
|
navigateToManageProvider(spid: number): void {
|
||||||
|
this.navigationService.navigate(['service-provider', spid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public chartClicked(event: any, chartIndex: number): void {
|
||||||
|
const active = event.active;
|
||||||
|
if (active?.length) {
|
||||||
|
const dataIndex = active[0].index;
|
||||||
|
const chart = this.chartConfigs[chartIndex];
|
||||||
|
|
||||||
|
const spid = chart.spid as number;
|
||||||
|
const carnetStatus = this.carnetStatuses.find(t => t.name === chart.data?.labels?.[dataIndex]);
|
||||||
|
|
||||||
|
if (carnetStatus !== undefined) {
|
||||||
|
this.carnetStatusData.emit({
|
||||||
|
spid: spid,
|
||||||
|
carnetStatus: carnetStatus.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public chartOptions: ChartConfiguration['options'] = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: true,
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
color: '#333',
|
||||||
|
font: {
|
||||||
|
size: 14
|
||||||
|
},
|
||||||
|
padding: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: (context) => {
|
||||||
|
const label = context.dataset.label || '';
|
||||||
|
const value = context.raw as number || 0;
|
||||||
|
const total = (context.dataset.data as number[]).reduce((a: number, b: number) => a + b, 0);
|
||||||
|
const percentage = Math.round((value / total) * 100);
|
||||||
|
return `${label}: ${value} (${percentage}%)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public chartType: ChartType = 'pie';
|
||||||
|
public chartPlugins = [];
|
||||||
|
|
||||||
|
public chartConfigs: {
|
||||||
|
title: string,
|
||||||
|
spid: number,
|
||||||
|
data: ChartData<'pie'>;
|
||||||
|
colors: string[];
|
||||||
|
options: ChartConfiguration['options'];
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
this.processChartData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private processChartData(): void {
|
||||||
|
this.chartConfigs = this.chartData.map(provider => {
|
||||||
|
const statusColors = provider.CARNETSTATUS.map((status: string) => {
|
||||||
|
const carnetStatus = this.carnetStatuses.find(t => t.value === status);
|
||||||
|
return carnetStatus ? carnetStatus.color : '#9E9E9E';
|
||||||
|
});
|
||||||
|
|
||||||
|
const labels = provider.CARNETSTATUS.map((status: string) => {
|
||||||
|
const carnetStatus = this.carnetStatuses.find(t => t.value === status);
|
||||||
|
return carnetStatus!.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: provider.Service_Provider_Name,
|
||||||
|
spid: provider.SPID,
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
data: provider.Carnet_Count,
|
||||||
|
backgroundColor: statusColors,
|
||||||
|
hoverBackgroundColor: statusColors.map((c: any) => this.adjustBrightness(c, -20)),
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#fff'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
colors: statusColors,
|
||||||
|
options: this.chartOptions
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private adjustBrightness(color: string, percent: number): string {
|
||||||
|
// Helper function to adjust color brightness
|
||||||
|
const num = parseInt(color.replace('#', ''), 16);
|
||||||
|
const amt = Math.round(2.55 * percent);
|
||||||
|
const R = (num >> 16) + amt;
|
||||||
|
const G = (num >> 8 & 0x00FF) + amt;
|
||||||
|
const B = (num & 0x0000FF) + amt;
|
||||||
|
|
||||||
|
return '#' + (
|
||||||
|
0x1000000 +
|
||||||
|
(R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
|
||||||
|
(G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
|
||||||
|
(B < 255 ? (B < 1 ? 0 : B) : 255)
|
||||||
|
).toString(16).slice(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/app/home/home.component.html
Normal file
95
src/app/home/home.component.html
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<div class="dashboard-container">
|
||||||
|
<div class="content">
|
||||||
|
<div class="quick-actions">
|
||||||
|
<!-- <button mat-raised-button color="accent" routerLink="/add-service-provider">
|
||||||
|
<mat-icon>add</mat-icon> Add New Service Provider
|
||||||
|
</button> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart Section -->
|
||||||
|
<div class="chart-section">
|
||||||
|
<app-chart [chartData]="carnetData" [carnetStatuses]="carnetStatuses" (carnetStatusData)="onCarnetStatusClick($event)"></app-chart>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Carnet Details Section -->
|
||||||
|
<div class="carnet-details-section">
|
||||||
|
<div class="loading-shade" *ngIf="isLoading">
|
||||||
|
<mat-spinner diameter="50"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container mat-elevation-z8" *ngIf="showTable">
|
||||||
|
<table mat-table [dataSource]="dataSource" matSort>
|
||||||
|
<ng-container matColumnDef="applicationName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Application Name</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.applicationName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="holderName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Holder Name</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.holderName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="carnetNumber">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Carnet Number</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.carnetNumber }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="usSets">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>US Sets</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.usSets }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="foreignSets">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Foregin Sets</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.foreignSets }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="transitSets">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Transit Sets</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.transitSets }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="carnetValue">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Carnet Value</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.carnetValue }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="issueDate">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Issue Date</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.issueDate |
|
||||||
|
date:'mediumDate':'UTC' }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="expiryDate">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Expiry Date</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.expiryDate |
|
||||||
|
date:'mediumDate':'UTC' }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="orderType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Order Type</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ item.orderType }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="carnetStatus">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Carnet Status</th>
|
||||||
|
<td mat-cell *matCellDef="let item">{{ getCarnetStatusLabel(item.carnetStatus) }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
|
|
||||||
|
<tr matNoDataRow *matNoDataRow>
|
||||||
|
<td [colSpan]="displayedColumns.length" class="no-data-message">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<span>No data available</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize!]"
|
||||||
|
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
63
src/app/home/home.component.scss
Normal file
63
src/app/home/home.component.scss
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
.dashboard-container {
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.9rem;
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-paginator {
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/app/home/home.component.ts
Normal file
112
src/app/home/home.component.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { Component, ViewChild } from '@angular/core';
|
||||||
|
import { HomeService } from '../core/services/home.service';
|
||||||
|
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
||||||
|
import { ChartComponent } from './chart/chart.component';
|
||||||
|
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { UserPreferences } from '../core/models/user-preference';
|
||||||
|
import { UserPreferencesService } from '../core/services/user-preference.service';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CustomPaginator } from '../shared/custom-paginator';
|
||||||
|
import { CarnetStatus } from '../core/models/carnet-status';
|
||||||
|
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
||||||
|
import { CommonService } from '../core/services/common/common.service';
|
||||||
|
import { NotificationService } from '../core/services/common/notification.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
imports: [AngularMaterialModule, ChartComponent, CommonModule],
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrl: './home.component.scss',
|
||||||
|
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
|
||||||
|
})
|
||||||
|
export class HomeComponent {
|
||||||
|
carnetData: any[] = [];
|
||||||
|
isLoading = false;
|
||||||
|
showTable = false;
|
||||||
|
userPreferences: UserPreferences;
|
||||||
|
carnetStatuses: CarnetStatus[] = [];
|
||||||
|
|
||||||
|
dataSource = new MatTableDataSource<any>();
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator, { static: false })
|
||||||
|
set paginator(value: MatPaginator) {
|
||||||
|
this.dataSource.paginator = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewChild(MatSort, { static: false })
|
||||||
|
set sort(value: MatSort) {
|
||||||
|
this.dataSource.sort = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayedColumns: string[] = ['applicationName', 'holderName', 'carnetNumber', 'usSets', 'foreignSets', 'transitSets', 'carnetValue', 'issueDate', 'expiryDate', 'orderType', 'carnetStatus'];
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private homeService: HomeService,
|
||||||
|
private errorHandler: ApiErrorHandlerService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
userPrefenceService: UserPreferencesService,
|
||||||
|
private commonService: CommonService) {
|
||||||
|
this.userPreferences = userPrefenceService.getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadCarnetStatuses();
|
||||||
|
this.loadCarnetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCarnetStatuses(): void {
|
||||||
|
this.commonService.getCarnetStatuses().subscribe({
|
||||||
|
next: (carnetStatuses) => {
|
||||||
|
this.carnetStatuses = carnetStatuses;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading carnet status:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCarnetData(): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.homeService.getCarnetSummaryData().subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.carnetData = data;
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error loading carnet data:', error);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCarnetStatusClick(event: any): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.showTable = false;
|
||||||
|
this.homeService.getCarnetDataByStatus(event.spid, event.carnetStatus).subscribe({
|
||||||
|
next: (carnetDetails) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.showTable = true;
|
||||||
|
this.dataSource.data = carnetDetails;
|
||||||
|
this.dataSource.paginator = this.paginator;
|
||||||
|
this.dataSource.sort = this.sort;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load carnet data for the selected status');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
this.isLoading = false;
|
||||||
|
console.error('Error loading carnet data for the selected status:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exportData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
getCarnetStatusLabel(value: string): string {
|
||||||
|
const carnetStatus = this.carnetStatuses.find(t => t.value === value);
|
||||||
|
return carnetStatus ? carnetStatus.name : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/app/login/login.component.html
Normal file
57
src/app/login/login.component.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<div class="login-container">
|
||||||
|
<div class="login-card">
|
||||||
|
<!-- <div class="logo-container">
|
||||||
|
<img src="images/logo.jpeg" alt="USCIB Logo" class="logo">
|
||||||
|
</div> -->
|
||||||
|
<!--
|
||||||
|
<h2 class="welcome-title">Welcome to USCIB Carnet Portal!</h2> -->
|
||||||
|
<h3 class="subtitle">ATA Carnet: Your Passport for Duty-Free Global Trade</h3>
|
||||||
|
|
||||||
|
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" class="login-form">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Username</mat-label>
|
||||||
|
<input matInput formControlName="username" required>
|
||||||
|
<mat-icon matSuffix>person</mat-icon>
|
||||||
|
<mat-error *ngIf="loginForm.get('username')?.errors?.['required']">
|
||||||
|
Username is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Password</mat-label>
|
||||||
|
<input matInput type="password" formControlName="password" required>
|
||||||
|
<mat-icon matSuffix>lock</mat-icon>
|
||||||
|
<mat-error *ngIf="loginForm.get('password')?.errors?.['required']">
|
||||||
|
Password is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="forgot-password">
|
||||||
|
<a href="#" class="forgot-link">Forgot your password?</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button mat-raised-button color="primary" type="submit" [disabled]="!loginForm.valid">
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-error class="error-message" *ngIf="errorMessage !== ''">
|
||||||
|
{{errorMessage}}
|
||||||
|
</mat-error>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h3>ATA Carnet</h3>
|
||||||
|
<p>
|
||||||
|
Also known as the "Merchandise Passport," is an international customs document that simplifies temporary
|
||||||
|
exports to over 79 countries and territories.
|
||||||
|
It allows businesses to explore new markets, showcase products at trade shows, and attend global conferences
|
||||||
|
without paying duties or taxes.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It simplifies customs procedures for the temporary movement of goods and allows goods to
|
||||||
|
enter Customs territories of the ATA Carnet system free of customs duties and taxes for up to
|
||||||
|
one year.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
109
src/app/login/login.component.scss
Normal file
109
src/app/login/login.component.scss
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
.login-container {
|
||||||
|
display: flex;
|
||||||
|
min-height: 85vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 500px;
|
||||||
|
padding: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
max-width: 120px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
text-align: center;
|
||||||
|
color: #597b7c;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: -1rem;
|
||||||
|
|
||||||
|
.forgot-link {
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
flex: 1;
|
||||||
|
padding: 4rem;
|
||||||
|
background-color: #597b7c;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.login-container {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
padding: 2rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/app/login/login.component.ts
Normal file
96
src/app/login/login.component.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
||||||
|
import { AuthService } from '../core/services/common/auth.service';
|
||||||
|
import { NavigationService } from '../core/services/common/navigation.service';
|
||||||
|
import { HomeService } from '../core/services/home.service';
|
||||||
|
import { UserService } from '../core/services/common/user.service';
|
||||||
|
import { User } from '../core/models/user';
|
||||||
|
import { ThemeService } from '../core/services/theme.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule],
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrl: './login.component.scss'
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
loginForm: FormGroup;
|
||||||
|
isLoading = false;
|
||||||
|
errorMessage = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private userService: UserService,
|
||||||
|
private homeService: HomeService,
|
||||||
|
private navigationService: NavigationService,
|
||||||
|
private router: Router,
|
||||||
|
private themeService: ThemeService
|
||||||
|
) {
|
||||||
|
this.loginForm = this.fb.group({
|
||||||
|
username: ['', Validators.required],
|
||||||
|
password: ['', Validators.required]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.userService.isLoggedIn()) {
|
||||||
|
const appId = this.navigationService.getCurrentAppId();
|
||||||
|
if (!appId) {
|
||||||
|
this.setUserData();
|
||||||
|
} else {
|
||||||
|
this.navigationService.navigate(['home']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.themeService.setTheme('default');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.loginForm.valid) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.errorMessage = '';
|
||||||
|
|
||||||
|
const { username, password } = this.loginForm.value;
|
||||||
|
|
||||||
|
this.authService.login(username, password).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
if (response?.msg) {
|
||||||
|
this.userService.setUser(username);
|
||||||
|
this.setUserData();
|
||||||
|
} else {
|
||||||
|
this.errorMessage = response?.error ?? response?.msg;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.errorMessage = 'Invalid username or password';
|
||||||
|
console.error('Login error:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserData() {
|
||||||
|
|
||||||
|
this.userService.setUserDetails().subscribe({
|
||||||
|
next: (data: User) => {
|
||||||
|
if (data?.userDetails) {
|
||||||
|
this.navigationService.setCurrentAppId(data.userDetails.urlKey);
|
||||||
|
this.navigationService.navigate(['home']);
|
||||||
|
} else {
|
||||||
|
this.errorMessage = "User doesn't have permissions.";
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error: any) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.errorMessage = "User doesn't have permissions.";
|
||||||
|
console.error('Error retrieving app data:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/app/preparer/add/add-preparer.component.html
Normal file
32
src/app/preparer/add/add-preparer.component.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<div class="preparer-container">
|
||||||
|
<mat-stepper orientation="vertical" [linear]="true" (selectionChange)="onStepChange($event)"
|
||||||
|
[selectedIndex]="currentStep">
|
||||||
|
|
||||||
|
<!-- Basic Details Step -->
|
||||||
|
<mat-step [completed]="basicDetailsCompleted">
|
||||||
|
<ng-template matStepLabel>Basic Details</ng-template>
|
||||||
|
|
||||||
|
<app-basic-details [isEditMode]="isEditMode" (clientidCreated)="onBasicDetailsSaved($event)">
|
||||||
|
</app-basic-details>
|
||||||
|
</mat-step>
|
||||||
|
|
||||||
|
<!-- Contacts Step -->
|
||||||
|
<mat-step [completed]="contactsCompleted" [editable]="!!clientid && basicDetailsCompleted">
|
||||||
|
<ng-template matStepLabel>Contacts</ng-template>
|
||||||
|
|
||||||
|
<app-contacts *ngIf="clientid" [clientid]="clientid" (hasContacts)="onContactsSaved($event)"
|
||||||
|
[userPreferences]="userPreferences">
|
||||||
|
</app-contacts>
|
||||||
|
</mat-step>
|
||||||
|
|
||||||
|
<!-- Location Step -->
|
||||||
|
<!-- <mat-step [completed]="locationCompleted" [editable]="!!clientid && contactsCompleted">
|
||||||
|
<ng-template matStepLabel>Locations</ng-template>
|
||||||
|
|
||||||
|
<app-location *ngIf="clientid" [clientid]="clientid" (hasLocation)="onLocationSaved($event)"
|
||||||
|
[userPreferences]="userPreferences">
|
||||||
|
</app-location>
|
||||||
|
</mat-step> -->
|
||||||
|
|
||||||
|
</mat-stepper>
|
||||||
|
</div>
|
||||||
23
src/app/preparer/add/add-preparer.component.scss
Normal file
23
src/app/preparer/add/add-preparer.component.scss
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.preparer-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 24px auto;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
mat-stepper {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.preparer-container {
|
||||||
|
padding: 8px;
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/app/preparer/add/add-preparer.component.ts
Normal file
48
src/app/preparer/add/add-preparer.component.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { UserPreferences } from '../../core/models/user-preference';
|
||||||
|
import { UserPreferencesService } from '../../core/services/user-preference.service';
|
||||||
|
import { StepperSelectionEvent } from '@angular/cdk/stepper';
|
||||||
|
import { BasicDetailsComponent } from '../basic-details/basic-details.component';
|
||||||
|
import { ContactsComponent } from '../contacts/contacts.component';
|
||||||
|
import { LocationComponent } from '../location/location.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-preparer',
|
||||||
|
imports: [AngularMaterialModule, CommonModule, BasicDetailsComponent, ContactsComponent, LocationComponent],
|
||||||
|
templateUrl: './add-preparer.component.html',
|
||||||
|
styleUrl: './add-preparer.component.scss'
|
||||||
|
})
|
||||||
|
export class AddPreparerComponent {
|
||||||
|
isEditMode = false;
|
||||||
|
clientid: number | null = null;
|
||||||
|
currentStep: number = 0;
|
||||||
|
isLoading: boolean = false;
|
||||||
|
userPreferences: UserPreferences;
|
||||||
|
|
||||||
|
basicDetailsCompleted: boolean = false;
|
||||||
|
contactsCompleted: boolean = false;
|
||||||
|
locationCompleted: boolean = false;
|
||||||
|
|
||||||
|
constructor(userPrefenceService: UserPreferencesService) {
|
||||||
|
this.userPreferences = userPrefenceService.getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
onBasicDetailsSaved(event: string): void {
|
||||||
|
this.clientid = +event;
|
||||||
|
this.basicDetailsCompleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onContactsSaved(event: boolean): void {
|
||||||
|
this.contactsCompleted = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLocationSaved(event: boolean): void {
|
||||||
|
this.locationCompleted = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStepChange(event: StepperSelectionEvent): void {
|
||||||
|
this.currentStep = event.selectedIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/app/preparer/basic-details/basic-details.component.html
Normal file
149
src/app/preparer/basic-details/basic-details.component.html
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<div class="basic-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]="basicDetailsForm" class="details-form" *ngIf="!isLoading"
|
||||||
|
(ngSubmit)="saveBasicDetails()">
|
||||||
|
<!-- Client Information -->
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="lookup-code">
|
||||||
|
<mat-label>Lookup Code</mat-label>
|
||||||
|
<input matInput formControlName="lookupCode" required>
|
||||||
|
<mat-error *ngIf="f['lookupCode'].errors?.['required']">
|
||||||
|
Lookup code is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="f['lookupCode'].errors?.['maxlength']">
|
||||||
|
Maximum 20 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Address Information -->
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline" class="address1">
|
||||||
|
<mat-label>Address Line 1</mat-label>
|
||||||
|
<input matInput formControlName="address1" required>
|
||||||
|
<mat-error *ngIf="f['address1'].errors?.['required']">
|
||||||
|
Address is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="f['address1'].errors?.['maxlength']">
|
||||||
|
Maximum 100 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline" class="address2">
|
||||||
|
<mat-label>Address Line 2 (Optional)</mat-label>
|
||||||
|
<input matInput formControlName="address2">
|
||||||
|
<mat-error *ngIf="f['address2'].errors?.['maxlength']">
|
||||||
|
Maximum 100 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Location Information -->
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline" class="city">
|
||||||
|
<mat-label>City</mat-label>
|
||||||
|
<input matInput formControlName="city" required>
|
||||||
|
<mat-error *ngIf="f['city'].errors?.['required']">
|
||||||
|
City is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="f['city'].errors?.['maxlength']">
|
||||||
|
Maximum 50 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="country">
|
||||||
|
<mat-label>Country</mat-label>
|
||||||
|
<mat-select formControlName="country" required
|
||||||
|
(selectionChange)="onCountryChange($event.value)">
|
||||||
|
<mat-option *ngFor="let country of countries" [value]="country.value">
|
||||||
|
{{ country.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error *ngIf="f['country'].errors?.['required']">
|
||||||
|
Country is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="state">
|
||||||
|
<mat-label>State/Province</mat-label>
|
||||||
|
<mat-select formControlName="state" required>
|
||||||
|
<mat-option *ngFor="let state of states" [value]="state.value">
|
||||||
|
{{ state.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error *ngIf="f['state'].errors?.['required']">
|
||||||
|
State is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="zip">
|
||||||
|
<mat-label>ZIP/Postal Code</mat-label>
|
||||||
|
<input matInput formControlName="zip" required>
|
||||||
|
<mat-error *ngIf="f['zip'].errors?.['required']">
|
||||||
|
ZIP/Postal code is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error
|
||||||
|
*ngIf="f['country']?.value === 'US' && f['zip']?.touched && f['zip']?.errors?.['invalidUSZip']">
|
||||||
|
Please enter a valid 5-digit US ZIP code
|
||||||
|
</mat-error>
|
||||||
|
<mat-error
|
||||||
|
*ngIf="f['country']?.value === 'CA' && f['zip']?.touched && f['zip']?.errors?.['invalidCanadaPostal']">
|
||||||
|
Please enter a valid postal code (e.g., A1B2C3)
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Carnet Issuing Region -->
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline" class="carnet-issuing-region">
|
||||||
|
<mat-label>Carnet Issuing Region</mat-label>
|
||||||
|
<mat-select formControlName="carnetIssuingRegion" required>
|
||||||
|
<mat-option *ngFor="let region of regions" [value]="region.region">
|
||||||
|
{{ region.regionname }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error *ngIf="f['carnetIssuingRegion'].errors?.['required']">
|
||||||
|
Carnet issuing region is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="revenue-location">
|
||||||
|
<mat-label>Revenue Location</mat-label>
|
||||||
|
<mat-select formControlName="revenueLocation" required>
|
||||||
|
<mat-option *ngFor="let state of states" [value]="state.value">
|
||||||
|
{{ state.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-error *ngIf="f['revenueLocation'].errors?.['required']">
|
||||||
|
Revenue location is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button mat-raised-button color="primary" type="submit" [disabled]="basicDetailsForm.invalid">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
98
src/app/preparer/basic-details/basic-details.component.scss
Normal file
98
src/app/preparer/basic-details/basic-details.component.scss
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
.basic-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) {
|
||||||
|
.basic-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
218
src/app/preparer/basic-details/basic-details.component.ts
Normal file
218
src/app/preparer/basic-details/basic-details.component.ts
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { Subject, takeUntil, zip } from 'rxjs';
|
||||||
|
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Country } from '../../core/models/country';
|
||||||
|
import { Region } from '../../core/models/region';
|
||||||
|
import { State } from '../../core/models/state';
|
||||||
|
import { CommonService } from '../../core/services/common/common.service';
|
||||||
|
import { NotificationService } from '../../core/services/common/notification.service';
|
||||||
|
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||||
|
import { BasicDetailService } from '../../core/services/preparer/basic-detail.service';
|
||||||
|
import { BasicDetail } from '../../core/models/preparer/basic-detail';
|
||||||
|
import { ZipCodeValidator } from '../../shared/validators/zipcode-validator';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-basic-details',
|
||||||
|
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
|
||||||
|
templateUrl: './basic-details.component.html',
|
||||||
|
styleUrl: './basic-details.component.scss'
|
||||||
|
})
|
||||||
|
export class BasicDetailsComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() isEditMode = false;
|
||||||
|
@Input() clientid: number = 0;
|
||||||
|
@Output() clientidCreated = new EventEmitter<string>();
|
||||||
|
@Output() clientName = new EventEmitter<string>();
|
||||||
|
|
||||||
|
basicDetailsForm: FormGroup;
|
||||||
|
countries: Country[] = [];
|
||||||
|
regions: Region[] = [];
|
||||||
|
states: State[] = [];
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
countriesHasStates = ['US', 'CA', 'MX'];
|
||||||
|
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private commonService: CommonService,
|
||||||
|
private basicDetailService: BasicDetailService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private errorHandler: ApiErrorHandlerService) {
|
||||||
|
this.basicDetailsForm = this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadLookupData();
|
||||||
|
// this.spidCreated.emit(this.spid?.toString());
|
||||||
|
// Patch edit form data
|
||||||
|
if (this.clientid > 0) {
|
||||||
|
this.basicDetailService.getBasicDetailsById(this.clientid).subscribe({
|
||||||
|
next: (basicDetail: BasicDetail) => {
|
||||||
|
if (basicDetail?.clientid > 0) {
|
||||||
|
this.patchFormData(basicDetail);
|
||||||
|
this.clientName.emit(basicDetail.name);
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error: any) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load basic details');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
this.isLoading = false;
|
||||||
|
console.error('Error loading basic details:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
createForm(): FormGroup {
|
||||||
|
return this.fb.group({
|
||||||
|
name: ['', [Validators.required, Validators.maxLength(100)]],
|
||||||
|
lookupCode: ['', Validators.required, Validators.maxLength(20)],
|
||||||
|
address1: ['', Validators.required, Validators.maxLength(100)],
|
||||||
|
address2: ['', [Validators.maxLength(100)]],
|
||||||
|
city: ['', Validators.required, Validators.maxLength(50)],
|
||||||
|
state: ['', Validators.required],
|
||||||
|
country: ['', Validators.required],
|
||||||
|
zip: ['', [Validators.required, ZipCodeValidator('country')]],
|
||||||
|
carnetIssuingRegion: ['', Validators.required],
|
||||||
|
revenueLocation: ['', Validators.required]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLookupData(): void {
|
||||||
|
this.commonService.getCountries(this.clientid)
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe({
|
||||||
|
next: (countries) => {
|
||||||
|
this.countries = countries;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Failed to load countries', error);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadRegions();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRegions(): void {
|
||||||
|
this.commonService.getRegions()
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe({
|
||||||
|
next: (regions) => {
|
||||||
|
this.regions = regions;
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Failed to load regions', error);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStates(country: string): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
country = this.countriesHasStates.includes(country) ? country : 'FN';
|
||||||
|
this.commonService.getStates(country, this.clientid)
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe({
|
||||||
|
next: (states) => {
|
||||||
|
this.states = states;
|
||||||
|
const stateControl = this.basicDetailsForm.get('state');
|
||||||
|
if (this.countriesHasStates.includes(country)) {
|
||||||
|
stateControl?.enable();
|
||||||
|
} else {
|
||||||
|
stateControl?.disable();
|
||||||
|
stateControl?.setValue('FN');
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Failed to load states', error);
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
patchFormData(data: BasicDetail): void {
|
||||||
|
this.basicDetailsForm.patchValue({
|
||||||
|
name: data.name,
|
||||||
|
lookupCode: data.lookupCode,
|
||||||
|
address1: data.address1,
|
||||||
|
address2: data.address2,
|
||||||
|
city: data.city,
|
||||||
|
country: data.country,
|
||||||
|
state: data.state,
|
||||||
|
zip: data.zip,
|
||||||
|
carnetIssuingRegion: data.carnetIssuingRegion,
|
||||||
|
revenueLocation: data.revenueLocation
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.country) {
|
||||||
|
this.loadStates(data.country);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isEditMode) {
|
||||||
|
this.basicDetailsForm.get('carnetIssuingRegion')?.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCountryChange(country: string): void {
|
||||||
|
this.basicDetailsForm.get('state')?.reset();
|
||||||
|
|
||||||
|
if (country) {
|
||||||
|
this.loadStates(country);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.basicDetailsForm.get('zip')?.updateValueAndValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveBasicDetails(): void {
|
||||||
|
if (this.basicDetailsForm.invalid) {
|
||||||
|
this.basicDetailsForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const basicDetailData: BasicDetail = this.basicDetailsForm.value;
|
||||||
|
|
||||||
|
// states
|
||||||
|
basicDetailData.state = this.basicDetailsForm.get('state')?.value;
|
||||||
|
|
||||||
|
// non editable fields values
|
||||||
|
if (this.isEditMode) {
|
||||||
|
basicDetailData.carnetIssuingRegion = this.basicDetailsForm.get('carnetIssuingRegion')?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveObservable = this.isEditMode && this.clientid > 0
|
||||||
|
? this.basicDetailService.updateBasicDetails(this.clientid, basicDetailData)
|
||||||
|
: this.basicDetailService.createBasicDetails(basicDetailData);
|
||||||
|
|
||||||
|
saveObservable.subscribe({
|
||||||
|
next: (basicData: any) => {
|
||||||
|
this.notificationService.showSuccess(`Basic details ${this.isEditMode ? 'updated' : 'added'} successfully`);
|
||||||
|
|
||||||
|
if (!this.isEditMode) {
|
||||||
|
this.clientidCreated.emit(basicData.clientId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error: any) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditMode ? 'update' : 'add'} basic details`);
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Error saving basic details:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience getter for easy access to form fields
|
||||||
|
get f() {
|
||||||
|
return this.basicDetailsForm.controls;
|
||||||
|
}
|
||||||
|
}
|
||||||
249
src/app/preparer/contacts/contacts.component.html
Normal file
249
src/app/preparer/contacts/contacts.component.html
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
<div class="contacts-container">
|
||||||
|
<div class="actions-bar">
|
||||||
|
<button mat-raised-button color="primary" (click)="addNewContact()">
|
||||||
|
<mat-icon>add</mat-icon> Add New Contact
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container mat-elevation-z8">
|
||||||
|
<div class="loading-shade" *ngIf="isLoading">
|
||||||
|
<mat-spinner diameter="50"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table mat-table [dataSource]="dataSource" matSort>
|
||||||
|
<!-- First Name Column -->
|
||||||
|
<ng-container matColumnDef="firstName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>First Name</th>
|
||||||
|
<td mat-cell *matCellDef="let contact">{{ contact.firstName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Last Name Column -->
|
||||||
|
<ng-container matColumnDef="lastName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Last Name</th>
|
||||||
|
<td mat-cell *matCellDef="let contact">{{ contact.lastName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Title Column -->
|
||||||
|
<ng-container matColumnDef="title">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Title</th>
|
||||||
|
<td mat-cell *matCellDef="let contact">{{ contact.title }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Phone Column -->
|
||||||
|
<ng-container matColumnDef="phone">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Phone</th>
|
||||||
|
<td mat-cell *matCellDef="let contact">{{ contact.phone | phone }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Email Column -->
|
||||||
|
<ng-container matColumnDef="email">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
|
||||||
|
<td mat-cell *matCellDef="let contact">{{ contact.email }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Default Contact Column -->
|
||||||
|
<ng-container matColumnDef="defaultContact">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Default</th>
|
||||||
|
<td mat-cell *matCellDef="let contact">
|
||||||
|
<mat-icon [color]="contact.defaultContact ? 'primary' : ''">
|
||||||
|
{{ contact.defaultContact ? 'star' : 'star_border' }}
|
||||||
|
</mat-icon>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Actions Column -->
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||||
|
<td mat-cell *matCellDef="let contact">
|
||||||
|
<button mat-icon-button color="primary" (click)="editContact(contact)" matTooltip="Edit">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button color="primary" (click)="createLogin()" matTooltip="Login">
|
||||||
|
<mat-icon>passkey</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button color="warn" *ngIf="!contact.defaultContact || !contact.isInactive" (click)="
|
||||||
|
deleteContact(contact.clientContactid)" [hidden]="contact.defaultContact || contact.isInactive"
|
||||||
|
matTooltip="Inactivate">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
<!-- <button mat-icon-button (click)="setDefaultContact(contact.contactId)"
|
||||||
|
[color]="contact.defaultContact ? 'primary' : ''" matTooltip="Set as default">
|
||||||
|
<mat-icon>star</mat-icon>
|
||||||
|
</button> -->
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||||
|
|
||||||
|
<tr matNoDataRow *matNoDataRow>
|
||||||
|
<td [colSpan]="displayedColumns.length" class="no-data-message">
|
||||||
|
<mat-icon>info</mat-icon>
|
||||||
|
<span>No records available</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<mat-paginator [length]="dataSource.data.length" [pageSizeOptions]="[userPreferences.pageSize!]"
|
||||||
|
[hidePageSize]="true" showFirstLastButtons></mat-paginator>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Form -->
|
||||||
|
<div class="form-container" *ngIf="showForm">
|
||||||
|
<form [formGroup]="contactForm" (ngSubmit)="saveContact()">
|
||||||
|
<div class="form-header">
|
||||||
|
<h3>{{ isEditing ? 'Edit Contact' : 'Add New Contact' }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>First Name</mat-label>
|
||||||
|
<input matInput formControlName="firstName" required>
|
||||||
|
<mat-icon matSuffix>person</mat-icon>
|
||||||
|
<mat-error *ngIf="contactForm.get('firstName')?.errors?.['required']">
|
||||||
|
First name is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="contactForm.get('firstName')?.errors?.['maxlength']">
|
||||||
|
Maximum 50 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="small-field">
|
||||||
|
<mat-label>Middle Initial</mat-label>
|
||||||
|
<input matInput formControlName="middleInitial" maxlength="1">
|
||||||
|
<mat-error *ngIf="contactForm.get('middleInitial')?.errors?.['maxlength']">
|
||||||
|
Only 1 character allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Last Name</mat-label>
|
||||||
|
<input matInput formControlName="lastName" required>
|
||||||
|
<mat-icon matSuffix>person</mat-icon>
|
||||||
|
<mat-error *ngIf="contactForm.get('lastName')?.errors?.['required']">
|
||||||
|
Last name is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="contactForm.get('lastName')?.errors?.['maxlength']">
|
||||||
|
Maximum 50 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Title</mat-label>
|
||||||
|
<input matInput formControlName="title" required>
|
||||||
|
<mat-icon matSuffix>work</mat-icon>
|
||||||
|
<mat-error *ngIf="contactForm.get('title')?.errors?.['required']">
|
||||||
|
Title is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="contactForm.get('title')?.errors?.['maxlength']">
|
||||||
|
Maximum 100 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Phone</mat-label>
|
||||||
|
<input matInput formControlName="phone" required>
|
||||||
|
<mat-icon matSuffix>phone</mat-icon>
|
||||||
|
<mat-error *ngIf="contactForm.get('phone')?.errors?.['required']">
|
||||||
|
Phone is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="contactForm.get('phone')?.errors?.['pattern']">
|
||||||
|
Please enter a valid phone number (10-15 digits)
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Mobile</mat-label>
|
||||||
|
<input matInput formControlName="mobile">
|
||||||
|
<mat-icon matSuffix>smartphone</mat-icon>
|
||||||
|
<mat-error *ngIf="contactForm.get('mobile')?.errors?.['required']">
|
||||||
|
Mobile is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="contactForm.get('mobile')?.errors?.['pattern']">
|
||||||
|
Please enter a valid mobile number (10-15 digits)
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Fax</mat-label>
|
||||||
|
<input matInput formControlName="fax">
|
||||||
|
<mat-icon matSuffix>fax</mat-icon>
|
||||||
|
<mat-error *ngIf="contactForm.get('fax')?.errors?.['pattern']">
|
||||||
|
Please enter a valid fax number (10-15 digits)
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Email</mat-label>
|
||||||
|
<input matInput formControlName="email" required>
|
||||||
|
<mat-icon matSuffix>email</mat-icon>
|
||||||
|
<mat-error *ngIf="contactForm.get('email')?.errors?.['required']">
|
||||||
|
Email is required
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="contactForm.get('email')?.errors?.['email']">
|
||||||
|
Please enter a valid email address
|
||||||
|
</mat-error>
|
||||||
|
<mat-error *ngIf="contactForm.get('email')?.errors?.['maxlength']">
|
||||||
|
Maximum 100 characters allowed
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-checkbox formControlName="defaultContact">Default Contact</mat-checkbox>
|
||||||
|
</div> -->
|
||||||
|
<div *ngIf="isEditing" class="readonly-section">
|
||||||
|
<div class="readonly-fields">
|
||||||
|
<div class="field-column">
|
||||||
|
<!-- Last Changed By -->
|
||||||
|
<div class="readonly-field">
|
||||||
|
<label>Last Changed By</label>
|
||||||
|
<div class="readonly-value">
|
||||||
|
{{contactReadOnlyFields.lastChangedBy || 'N/A'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Inactive status -->
|
||||||
|
<div class="readonly-field">
|
||||||
|
<label>Inactive Status </label>
|
||||||
|
<div class="readonly-value">
|
||||||
|
{{contactReadOnlyFields.isInactive === true ? 'Yes' : 'No' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-column">
|
||||||
|
|
||||||
|
<!-- Last Changed Date -->
|
||||||
|
<div class="readonly-field">
|
||||||
|
<label>Last Changed Date</label>
|
||||||
|
<div class="readonly-value">
|
||||||
|
{{(contactReadOnlyFields.lastChangedDate | date:'mediumDate':'UTC') || 'N/A'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inactivated Date -->
|
||||||
|
<div class="readonly-field">
|
||||||
|
<label>Inactivated Date</label>
|
||||||
|
<div class="readonly-value">
|
||||||
|
{{(contactReadOnlyFields.inactivatedDate | date:'mediumDate':'UTC') || 'N/A'}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
|
||||||
|
<button mat-raised-button color="primary" type="submit" [disabled]="contactForm.invalid">
|
||||||
|
{{ isEditing ? 'Update' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
168
src/app/preparer/contacts/contacts.component.scss
Normal file
168
src/app/preparer/contacts/contacts.component.scss
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
.contacts-container {
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
.actions-bar {
|
||||||
|
clear: both;
|
||||||
|
margin-bottom: -16px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-actions {
|
||||||
|
width: 180px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-defaultContact {
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.9rem;
|
||||||
|
color: rgba(0, 0, 0, 0.54);
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-paginator {
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
background-color: white;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--mat-sys-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-field {
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readonly-section {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
|
||||||
|
.readonly-fields {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
|
||||||
|
.field-column {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.readonly-field {
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readonly-value {
|
||||||
|
padding: 0.25rem;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive adjustments
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.contacts-container {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px !important;
|
||||||
|
|
||||||
|
.small-field {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
212
src/app/preparer/contacts/contacts.component.ts
Normal file
212
src/app/preparer/contacts/contacts.component.ts
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||||
|
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
|
||||||
|
import { CustomPaginator } from '../../shared/custom-paginator';
|
||||||
|
import { AngularMaterialModule } from '../../shared/module/angular-material.module';
|
||||||
|
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { PhonePipe } from '../../shared/pipes/phone.pipe';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { UserPreferences } from '../../core/models/user-preference';
|
||||||
|
import { ContactService } from '../../core/services/preparer/contact.service';
|
||||||
|
import { NotificationService } from '../../core/services/common/notification.service';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
|
||||||
|
import { Contact } from '../../core/models/preparer/contact';
|
||||||
|
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-contacts',
|
||||||
|
imports: [AngularMaterialModule, ReactiveFormsModule, PhonePipe, CommonModule],
|
||||||
|
templateUrl: './contacts.component.html',
|
||||||
|
styleUrl: './contacts.component.scss',
|
||||||
|
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
|
||||||
|
})
|
||||||
|
export class ContactsComponent {
|
||||||
|
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||||
|
@ViewChild(MatSort) sort!: MatSort;
|
||||||
|
|
||||||
|
displayedColumns: string[] = ['firstName', 'lastName', 'title', 'phone', 'email', 'defaultContact', 'actions'];
|
||||||
|
dataSource = new MatTableDataSource<any>();
|
||||||
|
contactForm: FormGroup;
|
||||||
|
isEditing = false;
|
||||||
|
currentContactId: number | null = null;
|
||||||
|
isLoading = false;
|
||||||
|
showForm = false;
|
||||||
|
|
||||||
|
contactReadOnlyFields: any = {
|
||||||
|
lastChangedDate: null,
|
||||||
|
lastChangedBy: null,
|
||||||
|
isInactive: null,
|
||||||
|
inactivatedDate: null
|
||||||
|
};
|
||||||
|
|
||||||
|
@Input() clientid: number = 0;
|
||||||
|
@Input() userPreferences: UserPreferences = {};
|
||||||
|
@Output() hasContacts = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private contactService: ContactService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private errorHandler: ApiErrorHandlerService
|
||||||
|
) {
|
||||||
|
this.contactForm = this.fb.group({
|
||||||
|
firstName: ['', [Validators.required, Validators.maxLength(50)]],
|
||||||
|
lastName: ['', [Validators.required, Validators.maxLength(50)]],
|
||||||
|
middleInitial: ['', [Validators.maxLength(1)]],
|
||||||
|
title: ['', [Validators.required, Validators.maxLength(100)]],
|
||||||
|
phone: ['', [Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]],
|
||||||
|
mobile: ['', [Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]],
|
||||||
|
fax: ['', [Validators.pattern(/^[0-9]{10,15}$/)]],
|
||||||
|
email: ['', [Validators.required, Validators.email, Validators.maxLength(100)]],
|
||||||
|
defaultContact: [false]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadContacts();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.dataSource.paginator = this.paginator;
|
||||||
|
this.dataSource.sort = this.sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadContacts(): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.contactService.getContactsById(this.clientid).subscribe({
|
||||||
|
next: (contacts: Contact[]) => {
|
||||||
|
this.dataSource.data = contacts;
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (error: any) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load contacts');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
this.isLoading = false;
|
||||||
|
console.error('Error loading contacts:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyFilter(event: Event): void {
|
||||||
|
// const filterValue = (event.target as HTMLInputElement).value;
|
||||||
|
// this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||||
|
|
||||||
|
// if (this.dataSource.paginator) {
|
||||||
|
// this.dataSource.paginator.firstPage();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
addNewContact(): void {
|
||||||
|
this.showForm = true;
|
||||||
|
this.isEditing = false;
|
||||||
|
this.currentContactId = null;
|
||||||
|
this.contactForm.reset();
|
||||||
|
// this.contactForm.patchValue({ defaultContact: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
editContact(contact: Contact): void {
|
||||||
|
this.showForm = true;
|
||||||
|
this.isEditing = true;
|
||||||
|
this.currentContactId = contact.clientContactId;
|
||||||
|
this.contactForm.patchValue({
|
||||||
|
firstName: contact.firstName,
|
||||||
|
lastName: contact.lastName,
|
||||||
|
middleInitial: contact.middleInitial,
|
||||||
|
title: contact.title,
|
||||||
|
phone: contact.phone,
|
||||||
|
mobile: contact.mobile,
|
||||||
|
fax: contact.fax,
|
||||||
|
email: contact.email
|
||||||
|
// defaultContact: contact.defaultContact
|
||||||
|
});
|
||||||
|
|
||||||
|
this.contactReadOnlyFields.lastChangedDate = contact.lastUpdatedDate ?? contact.dateCreated;
|
||||||
|
this.contactReadOnlyFields.lastChangedBy = contact.lastUpdatedBy ?? contact.createdBy;
|
||||||
|
this.contactReadOnlyFields.isInactive = contact.isInactive;
|
||||||
|
this.contactReadOnlyFields.inactivatedDate = contact.inactivatedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveContact(): void {
|
||||||
|
if (this.contactForm.invalid) {
|
||||||
|
this.contactForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default the first contact
|
||||||
|
const contactData: Contact = this.contactForm.value;
|
||||||
|
contactData.defaultContact = this.dataSource?.data?.length === 0;
|
||||||
|
|
||||||
|
const saveObservable = this.isEditing && (this.currentContactId! > 0)
|
||||||
|
? this.contactService.updateContact(this.currentContactId!, contactData)
|
||||||
|
: this.contactService.createContact(this.clientid, contactData);
|
||||||
|
|
||||||
|
saveObservable.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess(`Contact ${this.isEditing ? 'updated' : 'added'} successfully`);
|
||||||
|
this.loadContacts();
|
||||||
|
this.cancelEdit();
|
||||||
|
this.hasContacts.emit(true);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} contact`);
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Error saving contact:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteContact(contactId: string): void {
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||||
|
width: '350px',
|
||||||
|
data: {
|
||||||
|
title: 'Confirm Delete',
|
||||||
|
message: 'Are you sure you want to delete this contact?',
|
||||||
|
confirmText: 'Delete',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.contactService.deleteContact(contactId).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showSuccess('Contact deleted successfully');
|
||||||
|
this.loadContacts();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete contact');
|
||||||
|
this.notificationService.showError(errorMessage);
|
||||||
|
console.error('Error deleting contact:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createLogin(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEdit(): void {
|
||||||
|
this.showForm = false;
|
||||||
|
this.isEditing = false;
|
||||||
|
this.currentContactId = null;
|
||||||
|
this.contactForm.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDefaultContact(contactId: string): void {
|
||||||
|
// this.contactService.setDefaultServiceProviderContact(this.spid, contactId).subscribe({
|
||||||
|
// next: () => {
|
||||||
|
// this.notificationService.showSuccess('Default contact updated successfully');
|
||||||
|
// this.loadContacts();
|
||||||
|
// },
|
||||||
|
// error: (error) => {
|
||||||
|
// this.notificationService.showError('Failed to set default contact');
|
||||||
|
// console.error('Error setting default contact:', error);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
30
src/app/preparer/edit/edit-preparer.component.html
Normal file
30
src/app/preparer/edit/edit-preparer.component.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<h2 *ngIf="this.clientName" class="page-header">Manage {{this.clientName}}</h2>
|
||||||
|
|
||||||
|
<div class="preparer-action-buttons">
|
||||||
|
<button mat-button (click)="accordion().openAll()">Expand All</button>
|
||||||
|
<button mat-button (click)="accordion().closeAll()">Collapse All</button>
|
||||||
|
</div>
|
||||||
|
<mat-accordion class="preparer-headers-align" multi>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title> Basic Details </mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<app-basic-details [clientid]="clientid" [isEditMode]="isEditMode"
|
||||||
|
(clientName)="onClientNameUpdate($event)"></app-basic-details>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title> Contacts </mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-contacts [clientid]="clientid" [userPreferences]="userPreferences"></app-contacts>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<!--
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title> Locations </mat-panel-title>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<app-location [clientid]="clientid" [isEditMode]="isEditMode"
|
||||||
|
[userPreferences]="userPreferences"></app-location>
|
||||||
|
</mat-expansion-panel> -->
|
||||||
|
</mat-accordion>
|
||||||
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