Ver a proveniência

init wip system

master
Daniel há 1 ano
ascendente
cometimento
e71631240b
100 ficheiros alterados com 24287 adições e 0 eliminações
  1. +16
    -0
      angular/.editorconfig
  2. +42
    -0
      angular/.gitignore
  3. +65
    -0
      angular/1deployMatsenFE.sh
  4. +88
    -0
      angular/README.md
  5. +137
    -0
      angular/angular.json
  6. +34
    -0
      angular/buildClient.sh
  7. +23
    -0
      angular/generateApi.sh
  8. +1
    -0
      angular/openapi.json
  9. +5037
    -0
      angular/openapi.yaml
  10. +7
    -0
      angular/openapitools.json
  11. +14699
    -0
      angular/package-lock.json
  12. +56
    -0
      angular/package.json
  13. +7
    -0
      angular/src/app/_components/alert.component.html
  14. +100
    -0
      angular/src/app/_components/alert.component.ts
  15. +10
    -0
      angular/src/app/_components/datetime-picker/datetime-picker.component.html
  16. +0
    -0
      angular/src/app/_components/datetime-picker/datetime-picker.component.scss
  17. +23
    -0
      angular/src/app/_components/datetime-picker/datetime-picker.component.spec.ts
  18. +70
    -0
      angular/src/app/_components/datetime-picker/datetime-picker.component.ts
  19. +141
    -0
      angular/src/app/_components/filter-bar/filter-bar.component.html
  20. +0
    -0
      angular/src/app/_components/filter-bar/filter-bar.component.scss
  21. +23
    -0
      angular/src/app/_components/filter-bar/filter-bar.component.spec.ts
  22. +301
    -0
      angular/src/app/_components/filter-bar/filter-bar.component.ts
  23. +1
    -0
      angular/src/app/_components/index.ts
  24. +83
    -0
      angular/src/app/_components/layout/two-column/two-column.component.html
  25. +0
    -0
      angular/src/app/_components/layout/two-column/two-column.component.scss
  26. +23
    -0
      angular/src/app/_components/layout/two-column/two-column.component.spec.ts
  27. +26
    -0
      angular/src/app/_components/layout/two-column/two-column.component.ts
  28. +4
    -0
      angular/src/app/_components/linked-label/linked-label.component.html
  29. +0
    -0
      angular/src/app/_components/linked-label/linked-label.component.scss
  30. +23
    -0
      angular/src/app/_components/linked-label/linked-label.component.spec.ts
  31. +21
    -0
      angular/src/app/_components/linked-label/linked-label.component.ts
  32. +30
    -0
      angular/src/app/_components/list/list-col-definition.ts
  33. +8
    -0
      angular/src/app/_components/list/list-col-type-address.ts
  34. +4
    -0
      angular/src/app/_components/list/list-get-data-function-type.ts
  35. +4
    -0
      angular/src/app/_components/list/list-update-element-function-type.ts
  36. +171
    -0
      angular/src/app/_components/list/list.component.html
  37. +0
    -0
      angular/src/app/_components/list/list.component.scss
  38. +23
    -0
      angular/src/app/_components/list/list.component.spec.ts
  39. +474
    -0
      angular/src/app/_components/list/list.component.ts
  40. +52
    -0
      angular/src/app/_components/paging/paging.component.html
  41. +0
    -0
      angular/src/app/_components/paging/paging.component.scss
  42. +23
    -0
      angular/src/app/_components/paging/paging.component.spec.ts
  43. +132
    -0
      angular/src/app/_components/paging/paging.component.ts
  44. +7
    -0
      angular/src/app/_components/search-input/search-input-col-def.ts
  45. +11
    -0
      angular/src/app/_components/search-input/search-input.component.html
  46. +0
    -0
      angular/src/app/_components/search-input/search-input.component.scss
  47. +23
    -0
      angular/src/app/_components/search-input/search-input.component.spec.ts
  48. +38
    -0
      angular/src/app/_components/search-input/search-input.component.ts
  49. +19
    -0
      angular/src/app/_components/search-select/search-select.component.html
  50. +0
    -0
      angular/src/app/_components/search-select/search-select.component.scss
  51. +23
    -0
      angular/src/app/_components/search-select/search-select.component.spec.ts
  52. +253
    -0
      angular/src/app/_components/search-select/search-select.component.ts
  53. +9
    -0
      angular/src/app/_components/toggle/toggle.component.html
  54. +0
    -0
      angular/src/app/_components/toggle/toggle.component.scss
  55. +23
    -0
      angular/src/app/_components/toggle/toggle.component.spec.ts
  56. +23
    -0
      angular/src/app/_components/toggle/toggle.component.ts
  57. +620
    -0
      angular/src/app/_forms/apiForms.ts
  58. +17
    -0
      angular/src/app/_guards/admin.guard.spec.ts
  59. +12
    -0
      angular/src/app/_guards/admin.guard.ts
  60. +17
    -0
      angular/src/app/_guards/game-account-owner.guard.spec.ts
  61. +27
    -0
      angular/src/app/_guards/game-account-owner.guard.ts
  62. +17
    -0
      angular/src/app/_guards/sales.guard.spec.ts
  63. +13
    -0
      angular/src/app/_guards/sales.guard.ts
  64. +17
    -0
      angular/src/app/_guards/user.guard.spec.ts
  65. +13
    -0
      angular/src/app/_guards/user.guard.ts
  66. +86
    -0
      angular/src/app/_helpers/app-helper.service.ts
  67. +38
    -0
      angular/src/app/_helpers/error.interceptor.ts
  68. +12
    -0
      angular/src/app/_helpers/formgroup.initializer.ts
  69. +2
    -0
      angular/src/app/_helpers/index.ts
  70. +27
    -0
      angular/src/app/_helpers/jwt.interceptor.ts
  71. +21
    -0
      angular/src/app/_helpers/loading-interceptor.service.ts
  72. +4
    -0
      angular/src/app/_helpers/modal.states.ts
  73. +116
    -0
      angular/src/app/_helpers/price-calculator.service.ts
  74. +75
    -0
      angular/src/app/_helpers/property.interceptor.ts
  75. +6
    -0
      angular/src/app/_helpers/role.ts
  76. +25
    -0
      angular/src/app/_models/alert.ts
  77. +2
    -0
      angular/src/app/_models/index.ts
  78. +6
    -0
      angular/src/app/_models/orderFilter.ts
  79. +4
    -0
      angular/src/app/_models/priceError.ts
  80. +11
    -0
      angular/src/app/_models/snipingResponse.ts
  81. +12
    -0
      angular/src/app/_models/user.ts
  82. +101
    -0
      angular/src/app/_services/account.service.ts
  83. +44
    -0
      angular/src/app/_services/alert.service.ts
  84. +65
    -0
      angular/src/app/_services/data-import.service.ts
  85. +35
    -0
      angular/src/app/_services/ea-data-connect.service.ts
  86. +2
    -0
      angular/src/app/_services/index.ts
  87. +20
    -0
      angular/src/app/_services/loading.service.ts
  88. +23
    -0
      angular/src/app/_services/log.service.ts
  89. +100
    -0
      angular/src/app/_services/sniping.service.ts
  90. +16
    -0
      angular/src/app/_validators/minMaxValidator.ts
  91. +22
    -0
      angular/src/app/_views/account/account-routing.module.ts
  92. +22
    -0
      angular/src/app/_views/account/account.module.ts
  93. +3
    -0
      angular/src/app/_views/account/layout.component.html
  94. +17
    -0
      angular/src/app/_views/account/layout.component.ts
  95. +28
    -0
      angular/src/app/_views/account/login.component.html
  96. +58
    -0
      angular/src/app/_views/account/login.component.ts
  97. +43
    -0
      angular/src/app/_views/account/register.component.html
  98. +59
    -0
      angular/src/app/_views/account/register.component.ts
  99. +13
    -0
      angular/src/app/_views/dashboard/dashboard.component.html
  100. +5
    -0
      angular/src/app/_views/dashboard/dashboard.component.scss

+ 16
- 0
angular/.editorconfig Ver ficheiro

@@ -0,0 +1,16 @@
# 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

[*.md]
max_line_length = off
trim_trailing_whitespace = false

+ 42
- 0
angular/.gitignore Ver ficheiro

@@ -0,0 +1,42 @@
# See http://help.github.com/ignore-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

+ 65
- 0
angular/1deployMatsenFE.sh Ver ficheiro

@@ -0,0 +1,65 @@
#!/bin/bash

export PATH=/opt/plesk/php/8.2/bin:$PATH;

cd /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/
sudo git pull

echo "$(tput setab 2)matsen frontend has been PULLED$(tput sgr 0)"

rm -rf /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/composer.lock
#cp -rf /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/httpdocs/composer.lock /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs
rm -rf /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/composer.json
cp -rf /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/composer.json /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs

rm -rf /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/config
cp -rf /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/config /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs

rm -rf /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/bin
cp -rf /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/bin /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs

rm -rf /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/migrations
cp -rf /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/migrations /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs

rm -rf /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/src
cp -rf /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/src /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs

rm -rf /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/public/index.php
cp -rf /var/www/vhosts/spawntree.de/git_repo_clones/futbase-fe/public/index.php /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/public

echo "$(tput setab 2)Files have been copied$(tput sgr 0)"

cd /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs
composer update --no-scripts

echo "$(tput setab 2)COMPOSER UPDATED updated$(tput sgr 0)"

php /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/bin/console doctrine:migrations:migrate

echo "$(tput setab 2)DATABASE SCHEMA updated$(tput sgr 0)"

cd /var/www/vhosts/spawntree.de/
sudo chmod 777 matsen.spawntree.de
cd /var/www/vhosts/spawntree.de/matsen.spawntree.de/
sudo chmod 777 -R *

cd /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/var/cache/
rm -R *

php /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/bin/console cache:clear
php /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/bin/console cache:warmup

echo "$(tput setab 2)CACHE HAS BEEN CLEARED$(tput sgr 0)"

cd /var/www/vhosts/spawntree.de/matsen.spawntree.de/httpdocs/var/
chmod 777 -R *
chmod 777 cache/ *
chmod 777 cache/

#service apache2 restart

#echo "$(tput setab 2)CACHE cleared$(tput sgr 0)"

echo "$(tput setab 7)$(tput setaf 1)THINK ABOUT POSSIBLE PATCHES!"

echo "You have updated matsen api!$(tput sgr 0)"

+ 88
- 0
angular/README.md Ver ficheiro

@@ -0,0 +1,88 @@
# Futmachine

This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.7.

## Development server

Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.

## Code scaffolding

Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.

## Build

Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.

## Running unit tests

Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).

## Running end-to-end tests

Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.

## Further help

To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.


############

# BEFORE Installation:
- npm -v (minimum is 8.0.0)
- node --version (minimum is 20.9.0)
- brew upgrade node
- npm install

## Installation
- npm i -g @angular/cli

### Only once
- ng new futbase --no-standalone
- Standalone is now the new default in v17 (no app.module.ts)

- cd futbase -> ng serve

## Install Bootstrap
- cd futbase
- npm i bootstrap @popperjs/core --save
- npm install bootstrap-icons

## Install Angular Material
- cd futbase
- ng add @angular/material

## Generate Dummy data
- cd futbase
- npm i @openapitools/openapi-generator-cli -D
- package.json: Scripts block:
- "generate:api": "openapi-generator-cli generate -i ./openapi.yaml -g typescript-angular -o src/app/core/api/v1 -p=removeOperationIdPrefix=true"
- Java must be installed
- cd futbase

## Generate services from openapi.yaml
- run sh generateApi.sh
- (npm run generate:api
- Wenn es nicht geht: brew install java
- sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk
- java -version)
- ACHTUNG: In Datei src/app/core/api/v1/model/partnerJsonId.ts diese zwei Zeilen löschen:
- readonly type?: string;

## Module anlegen
- cd app
- ng g m registration --route register --module app.module

## Interesting Links
- https://openapi-generator.tech/docs/installation
- https://www.kevinboosten.dev/how-i-use-an-openapi-spec-in-my-angular-projects

- https://material.angular.io/
- https://ng-bootstrap.github.io/#/home
- https://medium.com/ngconf/new-input-binding-for-ngcomponentoutlet-cb18a86a739d
- https://ng-bootstrap.github.io/#/components/typeahead/examples

## Install for autogeneration of forms:
- brew install jq


+ 137
- 0
angular/angular.json Ver ficheiro

@@ -0,0 +1,137 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"futbase": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss",
"standalone": false
},
"@schematics/angular:directive": {
"standalone": false
},
"@schematics/angular:pipe": {
"standalone": false
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "../httpdocs/public/client",
"baseHref": "/",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"@angular/material/prebuilt-themes/indigo-pink.css",
"node_modules/bootstrap/scss/bootstrap.scss",
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
"src/styles.scss"
],
"scripts": [
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
]
},
"configurations": {
"production": {
"baseHref": "/client/",
"budgets": [
{
"type": "initial",
"maximumWarning": "1mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
},
"beta": {
"baseHref": "/client/",
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.beta.ts"
}
]
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "futbase:build:production"
},
"development": {
"buildTarget": "futbase:build:development"
},
"beta": {
"buildTarget": "futbase:build:beta"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "futbase:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}

+ 34
- 0
angular/buildClient.sh Ver ficheiro

@@ -0,0 +1,34 @@
#!/bin/bash

rm -rf ../httpdocs/public/client/*

# Run ng build
ng build

# Check if ng build was successful
if [ $? -ne 0 ]; then
echo "ng build failed. Exiting script."
exit 1
fi



# Move files from ../httpdocs/public/client/browser to ../httpdocs/public/client
mv ../httpdocs/public/client/browser/* ../httpdocs/public/client/

# Check if move was successful
if [ $? -ne 0 ]; then
echo "Failed to move files. Exiting script."
exit 1
fi

# Remove the browser folder
rm -rf ../httpdocs/public/client/browser

# Check if removal was successful
if [ $? -ne 0 ]; then
echo "Failed to remove browser folder. Exiting script."
exit 1
fi

echo "Build completed and files moved successfully."

+ 23
- 0
angular/generateApi.sh Ver ficheiro

@@ -0,0 +1,23 @@
npm run generate:api

find ./src/app/core/api/v1/model -type f -exec sed -i '' -e '' -e "s/hydramember/'hydra:member'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e '' -e "s/hydratotalItems/'hydra:totalItems'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydraview/'hydra:view'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydrasearch/'hydra:search'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydratemplate/'hydra:template'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydravariableRepresentation/'hydra:variableRepresentation'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydramapping/'hydra:mapping'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydrafirst/'hydra:first'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydralast/'hydra:last'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydranext/'hydra:next'/g" {} +
find ./src/app/core/api/v1/model -type f -exec sed -i '' -e "s/hydraprevious/'hydra:previous'/g" {} +


# https://dev.to/martinmcwhorter/generate-angular-reactiveforms-from-swagger-openapi-35h9 -> alternative
# https://github.com/Humbertda/ngx-openapi-form-generator -> alternative
# https://github.com/verizonconnect/ngx-form-generator -> we use this one

cat openapi.json | jq 'walk(if type == "object" then with_entries(select(.key | test("^@") | not)) else . end)' > openapi_no_hydra.json
npx ngx-form-generator -i openapi_no_hydra.json -o src/app/_forms/ -f apiForms.ts

rm openapi_no_hydra.json

+ 1
- 0
angular/openapi.json
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 5037
- 0
angular/openapi.yaml
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 7
- 0
angular/openapitools.json Ver ficheiro

@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.3.0"
}
}

+ 14699
- 0
angular/package-lock.json
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 56
- 0
angular/package.json Ver ficheiro

@@ -0,0 +1,56 @@
{
"name": "futbase",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"generate:api": "openapi-generator-cli generate -i ./openapi.yaml -g typescript-angular -o src/app/core/api/v1 -p=removeOperationIdPrefix=true"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.0",
"@angular/cdk": "^17.0.4",
"@angular/common": "^17.0.0",
"@angular/compiler": "^17.0.0",
"@angular/core": "^17.0.0",
"@angular/forms": "^17.0.0",
"@angular/material": "^17.0.4",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"@angular/router": "^17.0.0",
"@ng-bootstrap/ng-bootstrap": "^16.0.0-rc.2",
"@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0",
"@popperjs/core": "^2.11.8",
"@types/node": "^20.11.5",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"uuid": "^9.0.1",
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.7",
"@angular/cli": "^17.0.7",
"@angular/compiler-cli": "^17.0.0",
"@openapitools/openapi-generator-cli": "^2.7.0",
"@types/jasmine": "~5.1.0",
"@types/uuid": "^9.0.8",
"@verizonconnect/ngx-form-generator": "^1.2.0",
"jasmine-core": "~5.1.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.2.2"
},
"description": "This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.7.",
"main": "index.js",
"author": "",
"license": "ISC"
}

+ 7
- 0
angular/src/app/_components/alert.component.html Ver ficheiro

@@ -0,0 +1,7 @@
<div class="alert-box" *ngIf="alerts && alerts.length > 0">
<div *ngFor="let alert of alerts" class="{{cssClass(alert)}}">
<span [innerHTML]="alert.message"></span>
<button class="btn-close" (click)="removeAlert(alert)"></button>
</div>
<div (click)="removeAllAlerts()" class="alert alerts-remove">Click here to remove all</div>
</div>

+ 100
- 0
angular/src/app/_components/alert.component.ts Ver ficheiro

@@ -0,0 +1,100 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { Router, NavigationStart } from '@angular/router';
import { Subscription } from 'rxjs';

import { Alert, AlertType } from '@app/_models';
import { AlertService } from '@app/_services';

@Component({ selector: 'alert', templateUrl: 'alert.component.html' })
export class AlertComponent implements OnInit, OnDestroy {
@Input() id = 'default-alert';
@Input() fade = true;

alerts: Alert[] = [];
alertSubscription!: Subscription;
routeSubscription!: Subscription;

constructor(private router: Router, private alertService: AlertService) { }

ngOnInit() {
// subscribe to new alert notifications
this.alertSubscription = this.alertService.onAlert(this.id)
.subscribe(alert => {
// clear alerts when an empty alert is received
if (!alert.message) {
// filter out alerts without 'keepAfterRouteChange' flag
this.alerts = this.alerts.filter(x => x.keepAfterRouteChange);

// remove 'keepAfterRouteChange' flag on the rest
this.alerts.forEach(x => delete x.keepAfterRouteChange);
return;
}

// add alert to array
this.alerts.push(alert);

// auto close alert if required
if (alert.autoClose) {
setTimeout(() => this.removeAlert(alert), 1000);
}
});

// clear alerts on location change
this.routeSubscription = this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.alertService.clear(this.id);
}
});
}

ngOnDestroy() {
// unsubscribe to avoid memory leaks
this.alertSubscription.unsubscribe();
this.routeSubscription.unsubscribe();
}

removeAlert(alert: Alert) {
// check if already removed to prevent error on auto close
if (!this.alerts.includes(alert)) return;

if (this.fade) {
// fade out alert
alert.fade = true;

// remove alert after faded out
setTimeout(() => {
this.alerts = this.alerts.filter(x => x !== alert);
}, 250);
} else {
// remove alert
this.alerts = this.alerts.filter(x => x !== alert);
}
}

removeAllAlerts() {
this.alerts.forEach((alert) => this.removeAlert(alert));
}

cssClass(alert: Alert) {
if (!alert) return;

const classes = ['alert', 'alert-dismissible', 'mb-2', 'container'];
const alertTypeClass = {
[AlertType.Success]: 'alert-success',
[AlertType.Error]: 'alert-danger',
[AlertType.Info]: 'alert-info',
[AlertType.Warning]: 'alert-warning'
}

if (alert.type !== undefined) {
classes.push(alertTypeClass[alert.type]);
}

if (alert.fade) {
classes.push('fade');
}

return classes.join(' ');
}
}

+ 10
- 0
angular/src/app/_components/datetime-picker/datetime-picker.component.html Ver ficheiro

@@ -0,0 +1,10 @@
<div [formGroup]="form" class="row">
<div class="col-6">
<label for="{{ inputId }}-date">{{ label }} ({{ 'basic.date' | translate }}):</label>
<input type="date" id="{{ inputId }}-date" class="form-control" formControlName="date" [readonly]="readonly">
</div>
<div class="col-6">
<label for="{{ inputId }}-time">{{ label }} ({{ 'basic.time' | translate }}):</label>
<input type="time" id="{{ inputId }}-time" class="form-control" formControlName="time" step="1" [readonly]="readonly">
</div>
</div>

+ 0
- 0
angular/src/app/_components/datetime-picker/datetime-picker.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/datetime-picker/datetime-picker.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DatetimePickerComponent } from './datetime-picker.component';

describe('DatetimePickerComponent', () => {
let component: DatetimePickerComponent;
let fixture: ComponentFixture<DatetimePickerComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DatetimePickerComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DatetimePickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 70
- 0
angular/src/app/_components/datetime-picker/datetime-picker.component.ts Ver ficheiro

@@ -0,0 +1,70 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {FormBuilder, FormGroup} from "@angular/forms";

@Component({
selector: 'app-datetime-picker',
templateUrl: './datetime-picker.component.html',
styleUrl: './datetime-picker.component.scss'
})
export class DatetimePickerComponent implements OnInit {
@Input() label: string = 'Date and Time';
@Input() inputId: string = 'myId';
@Input() initialValue: string | null = null;
@Input() readonly: boolean = false;
@Output() dateTimeChange = new EventEmitter<string | null>();

form: FormGroup;

constructor(private fb: FormBuilder) {
this.form = this.fb.group({
date: [''],
time: ['']
});
}

ngOnInit() {
if (this.initialValue) {
const date = new Date(this.initialValue);
this.form.patchValue({
date: this.formatDate(date),
time: this.formatTime(date)
});
}

if (this.readonly) {
this.form.disable();
}

this.form.valueChanges.subscribe(() => {
if (!this.readonly) {
this.emitDateTime();
}
});
}

private formatDate(date: Date): string {
return date.toLocaleDateString('en-CA');
}

private formatTime(date: Date): string {
return date.toLocaleTimeString('en-GB', { hour12: false });
}

private emitDateTime() {
const { date, time } = this.form.value;
if (date && time) {
const [year, month, day] = date.split('-');
const [hours, minutes, seconds] = time.split(':');
const dateTime = new Date(Number(year), Number(month) - 1, Number(day), Number(hours), Number(minutes), Number(seconds));

// Format the date to match the loaded format
const formattedDate = dateTime.toLocaleString('sv-SE', { timeZone: 'Europe/Berlin' }).replace(' ', 'T') + '+02:00';

console.log('Emitting datetime:', formattedDate);
this.dateTimeChange.emit(formattedDate);
} else {
console.log('Emitting null datetime');
this.dateTimeChange.emit(null);
}
}
}

+ 141
- 0
angular/src/app/_components/filter-bar/filter-bar.component.html Ver ficheiro

@@ -0,0 +1,141 @@
<div class="spt-form">
<div class="row">
<ng-container *ngFor="let filter of filterStates">
<ng-container [ngSwitch]="filter.type">
<!-- Boolean Filter -->
<ng-container *ngSwitchCase="FILTER_TYPE_BOOLEAN">
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget">
<div class="d-flex align-items-center mb-1">
<input type="checkbox"
class="mini-check"
[checked]="filter.active"
(change)="onFilterActiveChanged(filter.field, $event, filter.subResource)">
<p class="form-label">{{ getColumnText(filter.field) | translate }}</p>
</div>
<label class="switch">
<input type="checkbox"
[checked]="filter.value"
(change)="onBooleanFilterChanged(filter.field, $event, filter.subResource)">
<span class="slider round"></span>
</label>
</div>
</ng-container>

<!-- Text Filter -->
<ng-container *ngSwitchCase="FILTER_TYPE_TEXT">
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3">
<div class="d-flex align-items-center mb-1">
<input type="checkbox"
class="mini-check"
[checked]="filter.active"
(change)="onFilterActiveChanged(filter.field, $event, filter.subResource)">
<label class="form-label">{{ getColumnText(filter.field) | translate }}</label>
</div>
<input type="text"
class="form-control"
[value]="filter.value"
(input)="onTextFilterChanged(filter.field, $event, filter.subResource)">
</div>
</ng-container>

<!-- Date Filter -->
<ng-container *ngSwitchCase="FILTER_TYPE_DATE">
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3">
<div class="d-flex align-items-center mb-1">
<input type="checkbox"
class="mini-check"
[checked]="filter.active"
(change)="onFilterActiveChanged(filter.field, $event, filter.subResource)">
<label class="form-label">{{ getColumnText(filter.field) | translate }}</label>
</div>
<div class="row">
<div class="col-6">
<input type="date"
class="form-control"
[value]="filter.value.start"
(change)="onDateFilterChanged(filter.field, 'start', $event, filter.subResource)">
</div>
<div class="col-6">
<input type="date"
class="form-control"
[value]="filter.value.end"
(change)="onDateFilterChanged(filter.field, 'end', $event, filter.subResource)">
</div>
</div>
</div>
</ng-container>

<!-- Number Filter -->
<ng-container *ngSwitchCase="FILTER_TYPE_NUMBER">
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3">
<div class="d-flex align-items-center mb-1">
<input type="checkbox"
class="mini-check"
[checked]="filter.active"
(change)="onFilterActiveChanged(filter.field, $event, filter.subResource)">
<label class="form-label">{{ getColumnText(filter.field) | translate }}</label>
</div>
<div class="row">
<div class="col-6">
<input type="number"
class="form-control"
[value]="filter.value.min"
(input)="onNumberFilterChanged(filter.field, 'min', $event, filter.subResource)"
placeholder="Min">
</div>
<div class="col-6">
<input type="number"
class="form-control"
[value]="filter.value.max"
(input)="onNumberFilterChanged(filter.field, 'max', $event, filter.subResource)"
placeholder="Max">
</div>
</div>
</div>
</ng-container>
<ng-container *ngSwitchCase="FILTER_TYPE_CHECKBOXES">
<div class="col-12 mb-3 switch-widget">
<div class="d-flex align-items-center mb-1">
<input type="checkbox"
class="mini-check"
[checked]="filter.active"
(change)="onFilterActiveChanged(filter.field, $event, filter.subResource)">
<p class="form-label">{{ getColumnText(filter.field) | translate }}</p>
</div>
<div class="row">
<div *ngFor="let option of filter.options" class="col-12 col-sm-6 col-md-3 col-lg-2 mb-3">
<div class="d-flex align-items-center mb-1">
<p class="form-label">{{ option }}</p>
</div>
<label class="switch">
<input type="checkbox"
[id]="filter.field + '_' + option"
(change)="onCheckboxesFilterChanged(filter.field, option, $event, filter.subResource)"
[checked]="filter.value[option]"
class="form-check-input">
<span class="slider round"></span>
</label>
</div>
</div>
</div>
<!-- <div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3">-->
<!-- <input type="checkbox"-->
<!-- [id]="filter.field + '_' + option"-->
<!-- (change)="onCheckboxesFilterChanged(filter.field, option, $event, filter.subResource)"-->
<!-- [checked]="filter.value[option]"-->
<!-- class="form-check-input">-->
<!-- <label [for]="filter.field + '_' + option" class="form-check-label">-->
<!-- {{ option }}-->
<!-- </label>-->
<!-- </div>-->
</ng-container>
</ng-container>
</ng-container>
</div>
<div class="row mb-3">
<div class="col-12 d-flex justify-content-end">
<button (click)="saveFilterConfig()" class="btn btn-primary me-3">{{ 'basic.saveSettings' | translate }}</button>
<button (click)="resetAllFilters()" class="btn btn-primary">{{ 'basic.resetFilters' | translate }}</button>
</div>
</div>
</div>

+ 0
- 0
angular/src/app/_components/filter-bar/filter-bar.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/filter-bar/filter-bar.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { FilterBarComponent } from './filter-bar.component';

describe('FilterBarComponent', () => {
let component: FilterBarComponent;
let fixture: ComponentFixture<FilterBarComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [FilterBarComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FilterBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 301
- 0
angular/src/app/_components/filter-bar/filter-bar.component.ts Ver ficheiro

@@ -0,0 +1,301 @@
import { Component, EventEmitter, Input, OnInit, OnDestroy, Output } from '@angular/core';
import { ListColDefinition } from "@app/_components/list/list-col-definition";
import { Subject, debounceTime, takeUntil } from 'rxjs';
import {filter} from "rxjs/operators";

interface FilterState {
field: string;
type: string;
value: any;
active: boolean;
subResource?: string;
options?: string[];
}

interface NumberInput {
field: string;
boundType: 'min' | 'max';
value: number | null;
subResource?: string;
}

@Component({
selector: 'app-filter-bar',
templateUrl: './filter-bar.component.html',
styleUrls: ['./filter-bar.component.scss']
})
export class FilterBarComponent implements OnInit, OnDestroy {
@Input() public listColDefinitions!: ListColDefinition[];
@Input() public filterConfig!: string | null;
@Output() public filterInit = new EventEmitter<{filters: Record<string, any>, activeCount: number}>();
@Output() public filterChanged = new EventEmitter<{filters: Record<string, any>, activeCount: number}>();
@Output() public filterSave = new EventEmitter();

public static readonly FILTER_TYPE_BOOLEAN: string = 'boolean';
public static readonly FILTER_TYPE_TEXT: string = 'text';
public static readonly FILTER_TYPE_DATE: string = 'date';
public static readonly FILTER_TYPE_NUMBER: string = 'number';
public static readonly FILTER_TYPE_CHECKBOXES: string = 'checkboxes';

public filterStates: FilterState[] = [];

private numberInputSubject = new Subject<NumberInput>();
private textInputSubject = new Subject<{field: string, value: string, subResource?: string}>();
private destroy$ = new Subject<void>();

ngOnInit(): void {
this.initializeFilterStates();
this.setupNumberInputDebounce();
this.setupTextInputDebounce();
}

ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}

private countActiveFilters(): number {
return this.filterStates.filter(state => state.active).length;
}

private initializeFilterStates(): void {
let filterSettingsObj: { [key: string]: any } = {};
if (this.filterConfig !== null) {
filterSettingsObj = JSON.parse(this.filterConfig);
}
this.filterStates = this.listColDefinitions
.filter(col => col.filterType)
.map(col => {
let value = this.getInitialValueForType(col.filterType || '');
let active = false;
if (col.field !== undefined && filterSettingsObj.hasOwnProperty(col.field)) {
value = filterSettingsObj[col.field];
active = true;
}
const filterState: FilterState = {
field: col.field || '',
type: col.filterType || '',
value: value,
active: active,
subResource: col.subResource,
options: col.filterOptions
};
return filterState;
});
const activeFilters: Record<string, any> = this.getActiveFilters();
const activeCount = this.countActiveFilters();
this.filterInit.emit({ filters: activeFilters, activeCount });
}

private setupNumberInputDebounce(): void {
this.numberInputSubject.pipe(
debounceTime(300),
takeUntil(this.destroy$)
).subscribe(input => {
this.updateNumberFilter(input.field, input.boundType, input.value, input.subResource);
});
}

private setupTextInputDebounce(): void {
this.textInputSubject.pipe(
debounceTime(300),
takeUntil(this.destroy$)
).subscribe(({field, value, subResource}) => {
this.updateTextFilter(field, value, subResource);
});
}

private getInitialValueForType(type: string, options?: string[]): any {
switch (type) {
case FilterBarComponent.FILTER_TYPE_BOOLEAN:
return false;
case FilterBarComponent.FILTER_TYPE_TEXT:
return '';
case FilterBarComponent.FILTER_TYPE_DATE:
return { start: null, end: null };
case FilterBarComponent.FILTER_TYPE_NUMBER:
return { min: null, max: null };
case FilterBarComponent.FILTER_TYPE_CHECKBOXES:
if (options) {
return options.reduce((acc, option) => {
acc[option] = false;
return acc;
}, {} as {[key: string]: boolean});
}
return {};
default:
return null;
}
}

private findFilterState(field: string, subResource?: string): FilterState | undefined {
return this.filterStates.find(state =>
state.field === field && state.subResource === subResource
);
}

onBooleanFilterChanged(field: string, event: Event, subResource?: string): void {
const target = event.target as HTMLInputElement;
this.onFilterChanged(field, target.checked, subResource);
}

onTextFilterChanged(field: string, event: Event, subResource?: string): void {
const target = event.target as HTMLInputElement;
this.textInputSubject.next({ field, value: target.value, subResource });
}

private updateTextFilter(field: string, value: string, subResource?: string): void {
const filterState = this.findFilterState(field, subResource);
if (filterState && filterState.type === FilterBarComponent.FILTER_TYPE_TEXT) {
filterState.value = value;
this.emitActiveFilters();
}
}

onDateFilterChanged(field: string, dateType: 'start' | 'end', event: Event, subResource?: string): void {
const target = event.target as HTMLInputElement;
const filterState = this.findFilterState(field, subResource);
if (filterState && filterState.type === FilterBarComponent.FILTER_TYPE_DATE) {
filterState.value = { ...filterState.value, [dateType]: target.value };
this.emitActiveFilters();
}
}

onNumberFilterChanged(field: string, boundType: 'min' | 'max', event: Event, subResource?: string): void {
const target = event.target as HTMLInputElement;
const numValue = target.value === '' ? null : Number(target.value);
this.numberInputSubject.next({ field, boundType, value: numValue, subResource });
}

onCheckboxesFilterChanged(field: string, option: string, event: Event, subResource?: string): void {
const target = event.target as HTMLInputElement;
const filterState = this.findFilterState(field, subResource);
console.log(filterState);
if (filterState && filterState.type === FilterBarComponent.FILTER_TYPE_CHECKBOXES) {
filterState.value[option] = target.checked;
console.log('jojoj');
this.emitActiveFilters();
}
}

private updateNumberFilter(field: string, boundType: 'min' | 'max', value: number | null, subResource?: string): void {
const filterState = this.findFilterState(field, subResource);
if (filterState && filterState.type === FilterBarComponent.FILTER_TYPE_NUMBER) {
filterState.value = { ...filterState.value, [boundType]: value };
this.emitActiveFilters();
}
}

onFilterChanged(field: string, value: any, subResource?: string): void {
const filterState = this.findFilterState(field, subResource);
if (filterState) {
filterState.value = value;
this.emitActiveFilters();
}
}

onFilterActiveChanged(field: string, event: Event, subResource?: string): void {
const target = event.target as HTMLInputElement;
const filterState = this.findFilterState(field, subResource);
if (filterState) {
filterState.active = target.checked;
if (filterState.type === FilterBarComponent.FILTER_TYPE_CHECKBOXES) {
// Reset all checkbox values when the filter is deactivated
if (!filterState.active) {
for (const option in filterState.value) {
filterState.value[option] = false;
}
}
}
this.emitActiveFilters();
}
}

resetAllFilters(): void {
this.filterStates.forEach(state => {
state.active = false;
state.value = this.getInitialValueForType(state.type);
});
this.emitActiveFilters();
}

private getActiveFilters(): Record<string, any> {
const activeFilters: Record<string, any> = {};
this.filterStates
.filter(state => state.active)
.forEach(state => {
if (state.subResource) {
if (!activeFilters[state.subResource]) {
activeFilters[state.subResource] = {};
}
this.setFilterValue(activeFilters[state.subResource], state);
} else {
this.setFilterValue(activeFilters, state);
}
});
return activeFilters;
}

private emitActiveFilters(): void {
const activeFilters: Record<string, any> = this.getActiveFilters();
const activeCount = this.countActiveFilters();
this.filterChanged.emit({ filters: activeFilters, activeCount });
}

private setFilterValue(obj: Record<string, any>, state: FilterState): void {
if (state.type === FilterBarComponent.FILTER_TYPE_BOOLEAN) {
obj[state.field] = state.value;
} else if (state.type === FilterBarComponent.FILTER_TYPE_TEXT) {
obj[state.field] = state.value;
} else if (state.type === FilterBarComponent.FILTER_TYPE_DATE) {
if (state.value.start !== null || state.value.end !== null) {
obj[state.field] = state.value;
} else {
obj[state.field] = {};
}
} else if (state.type === FilterBarComponent.FILTER_TYPE_NUMBER) {
if (state.value.min !== null || state.value.max !== null) {
obj[state.field] = state.value;
} else {
obj[state.field] = {};
}
} else if (state.type === FilterBarComponent.FILTER_TYPE_CHECKBOXES) {
// Add this block to handle checkbox filters
const selectedOptions = Object.entries(state.value)
.filter(([_, isChecked]) => isChecked)
.map(([option, _]) => option);
if (selectedOptions.length > 0) {
obj[state.field] = selectedOptions;
}
}
}

saveFilterConfig(): void {
this.filterSave.emit();
}

getColumnText(field: string): string {
const column = this.listColDefinitions.find(col => col.field === field);
return column ? column.text : '';
}

get FILTER_TYPE_BOOLEAN(): string {
return FilterBarComponent.FILTER_TYPE_BOOLEAN;
}

get FILTER_TYPE_TEXT(): string {
return FilterBarComponent.FILTER_TYPE_TEXT;
}

get FILTER_TYPE_DATE(): string {
return FilterBarComponent.FILTER_TYPE_DATE;
}

get FILTER_TYPE_NUMBER(): string {
return FilterBarComponent.FILTER_TYPE_NUMBER;
}

get FILTER_TYPE_CHECKBOXES(): string {
return FilterBarComponent.FILTER_TYPE_CHECKBOXES;
}
}

+ 1
- 0
angular/src/app/_components/index.ts Ver ficheiro

@@ -0,0 +1 @@
export * from './alert.component';

+ 83
- 0
angular/src/app/_components/layout/two-column/two-column.component.html Ver ficheiro

@@ -0,0 +1,83 @@
<div class="row ps-2 position-relative">
<div id="btn-sidebar" (click)="toggleSidebar()" [class.nav-open]="navOpen"></div>
<div class="spt-sidebar" [class.nav-open]="navOpen">
<ul class="nav flex-column">
<li class="nav-item mb-3">
<a class="card" routerLink="/dashboard" routerLinkActive="active">
<div class="card-body position-relative" data-cat="dashboard">
<h3 class="position-absolute m-0">{{'dashboard.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/system" routerLinkActive="active">
<div class="card-body position-relative" data-cat="system">
<h3 class="position-absolute m-0">{{'system.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/logs" routerLinkActive="active">
<div class="card-body position-relative" data-cat="logs">
<h3 class="position-absolute m-0">{{'logs.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/game-accounts" routerLinkActive="active">
<div class="card-body position-relative" data-cat="account">
<h3 class="position-absolute m-0">{{'game_account.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/candidates" routerLinkActive="active">
<div class="card-body position-relative" data-cat="candidates">
<h3 class="position-absolute m-0">{{'candidate.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/tradepile-items" routerLinkActive="active">
<div class="card-body position-relative" data-cat="tradepile-items">
<h3 class="position-absolute m-0">{{'tradepile_item.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/sales" routerLinkActive="active">
<div class="card-body position-relative" data-cat="sales">
<h3 class="position-absolute m-0">{{'sales.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/sniping" routerLinkActive="active">
<div class="card-body position-relative" data-cat="sniping">
<h3 class="position-absolute m-0">{{'sniping.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/profiles" routerLinkActive="active">
<div class="card-body position-relative" data-cat="profile">
<h3 class="position-absolute m-0">{{'profile.view' | translate}}</h3>
</div>
</a>
</li>
<li class="nav-item mb-3">
<a class="card" routerLink="/users" routerLinkActive="active">
<div class="card-body position-relative" data-cat="user">
<h3 class="position-absolute m-0">{{'basic.users' | translate}}</h3>
</div>
</a>
</li>
</ul>
</div>
<div class="pb-5 spt-main">
<div class="pe-3 pt-3">
<div class="btn btn-secondary mb-1" (click)="goBack()" id="go-back">< {{'basic.back' | translate}}</div>
<router-outlet></router-outlet>
</div>
</div>
</div>

+ 0
- 0
angular/src/app/_components/layout/two-column/two-column.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/layout/two-column/two-column.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { TwoColumnComponent } from './two-column.component';

describe('TwoColumnComponent', () => {
let component: TwoColumnComponent;
let fixture: ComponentFixture<TwoColumnComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TwoColumnComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TwoColumnComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 26
- 0
angular/src/app/_components/layout/two-column/two-column.component.ts Ver ficheiro

@@ -0,0 +1,26 @@
import {Component} from '@angular/core';
import {Location} from "@angular/common";

@Component({
selector: 'app-two-column',
templateUrl: './two-column.component.html',
styleUrl: './two-column.component.scss'
})
export class TwoColumnComponent {

public navOpen: boolean;
constructor(private _location: Location) {
this.navOpen = false;
}

goBack() {
this._location.back();
}

toggleSidebar() {
this.navOpen = !this.navOpen;
}



}

+ 4
- 0
angular/src/app/_components/linked-label/linked-label.component.html Ver ficheiro

@@ -0,0 +1,4 @@
<span *ngIf="user">
<img src="./assets/images/icons/user.svg" class="icon-mini" alt=""/><a
href="/user/{{this.appHelperService.extractId(user.id)}}">{{ user.firstName }} {{ user.lastName }}</a>
</span>

+ 0
- 0
angular/src/app/_components/linked-label/linked-label.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/linked-label/linked-label.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { LinkedLabelComponent } from './linked-label.component';

describe('LinkedLabelComponent', () => {
let component: LinkedLabelComponent;
let fixture: ComponentFixture<LinkedLabelComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [LinkedLabelComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LinkedLabelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 21
- 0
angular/src/app/_components/linked-label/linked-label.component.ts Ver ficheiro

@@ -0,0 +1,21 @@
import {Component, Input} from '@angular/core';
import {UserJsonld} from "@app/core/api/v1";
import {AppHelperService} from "@app/_helpers/app-helper.service";

@Component({
selector: 'app-linked-label',
templateUrl: './linked-label.component.html',
styleUrl: './linked-label.component.scss'
})
export class LinkedLabelComponent {
@Input() public user!: UserJsonld;


constructor(
protected appHelperService: AppHelperService
) {

}


}

+ 30
- 0
angular/src/app/_components/list/list-col-definition.ts Ver ficheiro

@@ -0,0 +1,30 @@
import {ListColTypeAddress} from "@app/_components/list/list-col-type-address";
import {OrderFilter} from "@app/_models/orderFilter";

export interface ListColDefinition {
name: string,
text: string,
type: string,
field?: string,
multipleFields?: any[],
address?: ListColTypeAddress,
sortable?: boolean,
subResource?: string,
sortingFieldName?: string,
sortingSubResource?: string,
countSortSubresource?: boolean,
countSortSubresourceField?: string,
countSortSubresourceValue?: any[] | null,
countSortFilterSubResource?: string,
countSortFilterSubresourceField?: string,
countSortFilterSubresourceValue?: string | null,
length?: number,
displayedLength?: number,
groups?: string[],
onClickFunction?: Function,
updateBooleanOnClick?: boolean,
filterType?: string,
visible?: boolean,
url?: string,
filterOptions?: string[],
}

+ 8
- 0
angular/src/app/_components/list/list-col-type-address.ts Ver ficheiro

@@ -0,0 +1,8 @@
export interface ListColTypeAddress {
street: string,
streetNo: string,
zip: string,
city: string,
country: string,
_type: 'address',
}

+ 4
- 0
angular/src/app/_components/list/list-get-data-function-type.ts Ver ficheiro

@@ -0,0 +1,4 @@
// types.ts
import { Observable } from 'rxjs';

export type ListGetDataFunctionType = (index: number, pageSize: number, term?: string) => Observable<any>;

+ 4
- 0
angular/src/app/_components/list/list-update-element-function-type.ts Ver ficheiro

@@ -0,0 +1,4 @@
// types.ts
import { Observable } from 'rxjs';

export type ListUpdateElementFunctionType = (element: any) => Observable<any>;

+ 171
- 0
angular/src/app/_components/list/list.component.html Ver ficheiro

@@ -0,0 +1,171 @@
<app-paging #pagingComponent
[getDataFunction]="getData"
[dataSource]="dataSource"
[searchable]="searchable"
[hidePageSize]="hidePageSize"
[displayOptions]="displayOptions"
[defaultDisplayOption]="defaultDisplayOption"
(displayOptionChange)="onDisplayOptionChange($event)"
>
<app-toggle *ngIf="showFilterBar && filterExists"
[small]="true"
[headline]="'basic.filter'"
[activeFilterCount]="activeFilterCount"
>
<app-filter-bar
[listColDefinitions]="listColDefinitions"
[filterConfig]="filterConfig"
(filterInit)="onFilterInit($event)"
(filterChanged)="onFilterChanged($event)"
(filterSave)="saveFilterConfig()"
></app-filter-bar>
</app-toggle>
<app-toggle *ngIf="displayedColumns"
[small]="true"
[headline]="'basic.columns'"
>
<div class="row align-items-end">
<ng-container *ngFor="let column of listColDefinitions">
<div class="col-4 col-sm-3 col-md-2 col-lg-1 mb-3 switch-widget">
<p class="form-label">{{ column.text | translate }}</p>
<label class="switch">
<input type="checkbox"
[checked]="column.visible"
(change)="onToggleColumnVisibility(column.name)">
<span class="slider round"></span>
</label>
</div>
</ng-container>
</div>
<div class="row mb-3">
<div class="col-12 d-flex justify-content-end">
<button (click)="saveColumnConfig()" class="btn btn-primary me-3">{{ 'basic.saveSettings' | translate }}</button>
<button (click)="showAllColumns()" class="btn btn-primary">{{ 'basic.showAllColumns' | translate }}</button>
</div>
</div>
</app-toggle>
<div *ngIf="listColDefinitions" class="table-responsive">
<table mat-table [dataSource]="dataSource" matSort (matSortChange)="onSortChange($event)" class="mat-elevation-z8">

<!-- Iterate over listColDefinitions to define columns -->
<ng-container *ngFor="let column of listColDefinitions">
<ng-container [matColumnDef]="column.name">
<!-- Header Cell -->
<ng-container *ngIf="column.sortable">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ column.text | translate }}
</th>
</ng-container>
<ng-container *ngIf="!column.sortable">
<th mat-header-cell *matHeaderCellDef>
{{ column.text | translate }}
</th>
</ng-container>

<!-- Conditional Cells -->
<td mat-cell *matCellDef="let element; let rowIndex = index" [ngClass]="getColCssClass(column)">
<ng-container [ngSwitch]="column.type">

<ng-container *ngSwitchCase="COLUMN_TYPE_ADDRESS">
<div [innerHTML]="getElementValue(element, column)"></div>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_BOOLEAN">
<span class="traffic-light" [ngClass]="{
'green': getElementValue(element, column),
'red': !getElementValue(element, column),
'has-function': !!column.updateBooleanOnClick
}" (click)="column.updateBooleanOnClick ? updateBooleanState(element, rowIndex, column) : null"></span>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_CURRENCY">
{{ getElementValue(element, column) | currency: 'EUR' }}
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_BTN_DOWNLOAD">
<span class="btn btn-primary bi bi-download p-2-4"
data-type="user-tool" data-action="edit" (click)="onDownloadFunction(element)"></span>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_BTN_REMOVE">
<span class="spt-icon-unassign" (click)="onRemoveItemFunction(element, column)"></span>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_BTN_EDIT">
<span class="btn btn-primary bi bi-pencil p-2-4"
data-type="user-tool" data-action="edit" (click)="onEditFunction(element)"></span>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_DATE">
{{ getElementValue(element, column) | date:'dd.MM.YYYY - HH:mm':'GMT+0200' }}
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_EMAIL">
<span><a href="mailto:{{ getElementValue(element, column) }}">{{ getElementValue(element, column) }}</a></span>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_DETAIL" >
<span class="btn btn-primary spt-icon-details"
data-type="user-tool" data-action="edit"
(click)="onNavigateToDetailsFunction(element, column)">
</span>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_DETAIL_LINK">
<a class="btn btn-primary spt-icon-details" data-type="user-tool" data-a3ction="edit"
[routerLink]="[appHelperService.getLink(element, column.url)]">
</a>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_IMAGE">
<img [src]="getElementImage(element, column)" width="40" height="40"/>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_POSITION">
{{ pagingComponent.getPageSize() * (pagingComponent.getPageIndex() - 1) + dataSource.filteredData.indexOf(element) + 1 }}
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_TEXT">
{{ getElementValue(element, column) }}
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_NUMBER">
{{ getElementValue(element, column) | number:'1.0-0':'de-DE' }}
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_NUMBER_UNFORMATTED">
{{ getElementValue(element, column) }}
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_NUMBER_BOLD">
<strong>{{ getElementValue(element, column) | number:'1.0-0':'de-DE' }}</strong>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_TEXT_BOLD">
<strong>{{ getElementValue(element, column) }}</strong>
</ng-container>

<ng-container *ngSwitchCase="COLUMN_TYPE_TEXT_LINKED">
<a [routerLink]="[appHelperService.getResourceLink(element, column.subResource)]">
{{ getElementValue(element, column) }}
</a>
</ng-container>
<ng-container *ngSwitchCase="COLUMN_TYPE_WEBSITE">
<a href="{{ getElementValue(element, column) }}" target="_blank">{{ getElementValue(element, column) }}</a>
</ng-container>

</ng-container>
</td>
</ng-container>
</ng-container>

<!-- Header and Row Definitions -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns; index as i;"
(click)="onRowSelected(row, i)"
[ngClass]="{'highlighted': selectedRowIndex === i}">
</tr>
</table>
</div>

</app-paging>

+ 0
- 0
angular/src/app/_components/list/list.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/list/list.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ListComponent } from './list.component';

describe('ListComponent', () => {
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 474
- 0
angular/src/app/_components/list/list.component.ts Ver ficheiro

@@ -0,0 +1,474 @@
import {AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {MatSort, Sort} from "@angular/material/sort";
import {PagingComponent} from "@app/_components/paging/paging.component";
import {MatTableDataSource} from "@angular/material/table";
import {ListColDefinition} from "@app/_components/list/list-col-definition";
import {AppHelperService} from "@app/_helpers/app-helper.service";
import {ListGetDataFunctionType} from "@app/_components/list/list-get-data-function-type";
import {ListUpdateElementFunctionType} from "@app/_components/list/list-update-element-function-type";
import {FilterBarComponent} from "@app/_components/filter-bar/filter-bar.component";
import { Router } from '@angular/router';
import {interval, Subscription} from "rxjs";

@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrl: './list.component.scss'
})

export class ListComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() public listId!: string;
@Input() public getDataFunction!: ListGetDataFunctionType;
@Input() public onSortFunction!: Function;
@Input() public onNavigateToDetailsFunction!: Function;
@Input() public onRemoveItemFunction!: Function;
@Input() public onEditFunction!: Function;
@Input() public onDownloadFunction!: Function;
@Input() public onRowSelectedFunction!: Function;
@Input() public onUpdateBooleanStateFunction!: ListUpdateElementFunctionType;
@Input() public searchable: boolean;
@Input() public showDetailButton: boolean;
@Input() public showPosition: boolean;
@Input() public showFilterBar: boolean;
@Input() public listColDefinitions!: ListColDefinition[];
@Input() public hidePageSize: boolean;
@Input() public displayOptions!: { [key: string]: string };
@Input() public defaultDisplayOption!: string;
@Input() public refreshIntervalSeconds?: number;
@ViewChild(MatSort) sort;
@ViewChild("pagingComponent", {static: false}) protected pagingComponent!: PagingComponent;
@ViewChild("filterBarComponent", {static: false}) protected filterBarComponent!: FilterBarComponent;

public static COLUMN_TYPE_ADDRESS: string = 'address';
public static COLUMN_TYPE_BOOLEAN: string = 'boolean';
public static COLUMN_TYPE_BTN_DOWNLOAD: string = 'btn_download';
public static COLUMN_TYPE_BTN_EDIT: string = 'btn_edit';
public static COLUMN_TYPE_BTN_REMOVE: string = 'btn_remove';
public static COLUMN_TYPE_CURRENCY: string = 'euro';
public static COLUMN_TYPE_DATE: string = 'date';
public static COLUMN_TYPE_DETAIL: string = 'detail';
public static COLUMN_TYPE_DETAIL_LINK: string = 'detail_link';
public static COLUMN_TYPE_EMAIL: string = 'email';
public static COLUMN_TYPE_IMAGE: string = 'image';
public static COLUMN_TYPE_COMBINED_IMAGES: string = 'combined_images';
public static COLUMN_TYPE_NUMBER: string = 'number';
public static COLUMN_TYPE_NUMBER_UNFORMATTED: string = 'number_unformatted';
public static COLUMN_TYPE_NUMBER_BOLD: string = 'number_bold';
public static COLUMN_TYPE_POSITION: string = 'position';
public static COLUMN_TYPE_TEXT: string = 'text';
public static COLUMN_TYPE_TEXT_BOLD: string = 'text_bold';
public static COLUMN_TYPE_TEXT_LINKED: string = 'text_linked';
public static COLUMN_TYPE_WEBSITE: string = 'website';

public activeFilterCount: number = 0;

protected displayedColumns!: string[];
protected selectedRowIndex: number | null = null;
protected dataSource;
protected currentGroup!: string;
protected filterExists!: boolean;
protected sortObj!: any;
protected filterObj!: any;
protected listColDefinitionsByField!: any;
protected filterConfig: string | null;
private refreshSubscription?: Subscription;

constructor(
protected appHelperService: AppHelperService,
private router: Router,
) {
this.searchable = true;
this.showDetailButton = true;
this.showPosition = true;
this.showFilterBar = true;
this.filterExists = false;
this.filterObj = {};
this.sort = new MatSort();
this.hidePageSize = false;
this.dataSource = new MatTableDataSource<any>();
this.filterConfig = null;

}

ngOnInit(): void {
this.loadColumnConfig();
if (this.showPosition) {
this.listColDefinitions.unshift(ListComponent.getDefaultColPosition());
}
if (this.showDetailButton) {
// this.listColDefinitions.unshift(ListComponent.getDefaultColDetailBtn());
this.listColDefinitions.unshift(ListComponent.getDefaultColDetailBtnLink(this.router.routerState.snapshot.url));
}
if (this.displayOptions !== undefined) {
this.currentGroup = this.defaultDisplayOption || Object.keys(this.displayOptions)[0] || '';
}

this.listColDefinitionsByField = {};
this.listColDefinitions.forEach((value, index) => {
if (value.visible === undefined) {
value.visible = true;
}
this.listColDefinitionsByField[value['name']] = value;
if (value.filterType !== undefined) {
this.filterExists = true;
}
})
this.updateDisplayedColumns();
this.filterConfig = this.loadFilterConfig();
this.setupAutoRefresh();
}

private setupAutoRefresh(): void {
this.clearAutoRefresh();
if (this.refreshIntervalSeconds && this.refreshIntervalSeconds > 0) {
this.refreshSubscription = interval(this.refreshIntervalSeconds * 1000).subscribe(() => {
this.getData();
});
}
}

private clearAutoRefresh(): void {
if (this.refreshSubscription) {
this.refreshSubscription.unsubscribe();
}
}

saveFilterConfig(): void {
localStorage.setItem(`filterConfig_${this.listId}`, this.getFilterJsonString());
}

loadFilterConfig(): string | null {
return localStorage.getItem(`filterConfig_${this.listId}`);
}

saveColumnConfig(): void {
const config = this.listColDefinitions.map(col => ({
name: col.name,
visible: col.visible
}));
localStorage.setItem(`listConfig_${this.listId}`, JSON.stringify(config));
}

loadColumnConfig(): void {
const savedConfig = localStorage.getItem(`listConfig_${this.listId}`);
if (savedConfig) {
const config = JSON.parse(savedConfig);
this.listColDefinitions.forEach(col => {
const savedCol = config.find((c: any) => c.name === col.name);
if (savedCol) {
col.visible = savedCol.visible;
}
});
this.updateDisplayedColumns();
}
}

updateDisplayedColumns(): void {
this.displayedColumns = this.listColDefinitions
.filter(col => col.visible !== false &&
(this.displayOptions === undefined ||
col.groups?.includes(this.currentGroup) ||
col.type === ListComponent.COLUMN_TYPE_DETAIL ||
col.type === ListComponent.COLUMN_TYPE_POSITION))
.map(col => col.name);
}

onDisplayOptionChange(option: string): void {
this.currentGroup = option;
this.updateDisplayedColumns();
}

onToggleColumnVisibility(columnName: string): void {
const column = this.listColDefinitions.find(col => col.name === columnName);
if (column) {
column.visible = !column.visible;
this.updateDisplayedColumns();
}
}

showAllColumns() {
this.listColDefinitions.forEach((value, index) => {
value.visible = true;
});
this.updateDisplayedColumns();
}

getColumnVisibility(): { [key: string]: boolean } {
const visibility: { [key: string]: boolean } = {};
this.listColDefinitions.forEach(col => {
visibility[col.name] = col.visible !== false;
});
return visibility;
}

ngAfterViewInit(): void {
}

getData = (): void => {
this.getDataFunction(
this.pagingComponent.getPageIndex(),
this.pagingComponent.getPageSize(),
this.pagingComponent.getSearchValue()
).subscribe(
data => {
this.dataSource = new MatTableDataSource<any>(data['hydra:member']);
this.pagingComponent.setDataLength(data["hydra:totalItems"]);
}
)
}

onSortChange = (sortState: Sort) => {
let listColDefinition: any = this.listColDefinitionsByField[sortState.active];
this.sortObj = sortState;
this.sortObj['listColDefinition'] = listColDefinition;
this.pagingComponent.resetPageIndex();
this.onSortFunction(sortState);
this.getData();
}

onRowSelected(row: any, index: number) {
this.selectedRowIndex = index;
if (this.onRowSelectedFunction !== undefined) {
this.onRowSelectedFunction(row, index);
}
}

getElementValue(element: any, column: ListColDefinition, multipleFieldIndex?: number): any | null {
element = column.subResource !== undefined ? element[column.subResource] : element;
if (element === undefined) {
return null;
}
if (column.field !== undefined) {
if (
column.displayedLength !== undefined &&
element[column.field] !== undefined &&
element[column.field].length > column.displayedLength
) {
return element[column.field]?.slice(0, column.displayedLength) + '...';
}
return element[column.field];
}
if (column.multipleFields !== undefined) {
if (multipleFieldIndex !== undefined) {
return element[column.multipleFields[multipleFieldIndex]];
}
let res: any[] = [];
column.multipleFields.forEach((field, index) => {
res.push(element[field]);
})
return res;
}
if (column.address !== undefined) {
const field = column.address;
let addressString = '';
if (element[field.street] !== undefined && element[field.street] !== null) {
addressString += `${element[field.street].trim()} `;
}
if (element[field.streetNo] !== undefined && element[field.streetNo] !== null) {
addressString += `${element[field.streetNo].trim()} `;
}
addressString += ' <br/> ';
if (element[field.zip] !== undefined && element[field.zip] !== null) {
addressString += `${element[field.zip].trim()} `;
}
if (element[field.city] !== undefined && element[field.city] !== null) {
addressString += `${element[field.city].trim()}`;
}
addressString += ' <br/> ';

if (element[field.country] !== undefined && element[field.country] !== null) {
addressString += `${element[field.country].trim()}`;
}
return addressString;
}
return element;
}

getElementImage(element: any, column: ListColDefinition): any {
let elementValue = this.getElementValue(element, column);
if (elementValue !== undefined && elementValue !== null) {
return elementValue;
}
return "/assets/images/icons/dummy-product.png"
}


getColCssClass(column: ListColDefinition): string {
switch (column.type) {
case ListComponent.COLUMN_TYPE_DETAIL:
return "spt-button-td";
case ListComponent.COLUMN_TYPE_BTN_REMOVE:
return "spt-button-td text-end";
case ListComponent.COLUMN_TYPE_TEXT:
return "spt-version-td";
default:
return "";
}
}

public getPageIndex() {
return this.pagingComponent.getPageIndex();
}

public getPageSize() {
return this.pagingComponent.getPageSize();
}

public static getDefaultColDetailBtn(): ListColDefinition {
return {
name: 'detail',
text: '',
type: ListComponent.COLUMN_TYPE_DETAIL
} as ListColDefinition;
}

public static getDefaultColDetailBtnLink(currentUrl: string): ListColDefinition {
return {
name: 'detaillink',
text: '',
url: currentUrl,
type: ListComponent.COLUMN_TYPE_DETAIL_LINK
} as ListColDefinition;
}

public static getDefaultColPosition(): ListColDefinition {
return {
name: 'pos',
text: 'overview.number',
type: ListComponent.COLUMN_TYPE_POSITION
} as ListColDefinition;
}

public getSortingJsonString(): any
{
return JSON.stringify(this.sortObj);
}

public updateBooleanState = (element: any, index: number, column: ListColDefinition) => {
if (this.onUpdateBooleanStateFunction === undefined) {
throw new Error('no onUpdateBooleanStateFunction given');
}
if (column.field !== undefined) {
element[column.field] = !element[column.field];
} else {
throw new Error('column.field is undefined');
}
this.onUpdateBooleanStateFunction(element).subscribe(
data => {
this.updateRow(data, index);
}
)
}

public updateRow(element: any, index: number) {
const data = this.dataSource.data as any;
data[index] = element;
this.dataSource.data = data;
}

public onFilterInit(filterData: {filters: any, activeCount: number}) {
this.filterObj = filterData.filters;
this.activeFilterCount = filterData.activeCount;
}

public onFilterChanged(filterData: {filters: any, activeCount: number}) {
console.log(filterData);
const filterJson = JSON.stringify(filterData.filters);
const currentFilterJson = JSON.stringify(this.filterObj);

if (filterJson !== currentFilterJson) {
this.filterObj = filterData.filters;
this.activeFilterCount = filterData.activeCount;
this.getData();
}
}

public getFilterJsonString(): any
{
return JSON.stringify(this.filterObj);
}

get COLUMN_TYPE_ADDRESS(): string {
return ListComponent.COLUMN_TYPE_ADDRESS;
}

get COLUMN_TYPE_BOOLEAN(): string {
return ListComponent.COLUMN_TYPE_BOOLEAN;
}

get COLUMN_TYPE_BTN_DOWNLOAD(): string {
return ListComponent.COLUMN_TYPE_BTN_DOWNLOAD;
}

get COLUMN_TYPE_BTN_EDIT(): string {
return ListComponent.COLUMN_TYPE_BTN_EDIT;
}

get COLUMN_TYPE_BTN_REMOVE(): string {
return ListComponent.COLUMN_TYPE_BTN_REMOVE;
}

get COLUMN_TYPE_CURRENCY(): string {
return ListComponent.COLUMN_TYPE_CURRENCY;
}

get COLUMN_TYPE_DATE(): string {
return ListComponent.COLUMN_TYPE_DATE;
}

get COLUMN_TYPE_DETAIL(): string {
return ListComponent.COLUMN_TYPE_DETAIL;
}

get COLUMN_TYPE_DETAIL_LINK(): string {
return ListComponent.COLUMN_TYPE_DETAIL_LINK;
}

get COLUMN_TYPE_EMAIL(): string {
return ListComponent.COLUMN_TYPE_EMAIL;
}

get COLUMN_TYPE_POSITION(): string {
return ListComponent.COLUMN_TYPE_POSITION;
}

get COLUMN_TYPE_IMAGE(): string {
return ListComponent.COLUMN_TYPE_IMAGE;
}

get COLUMN_TYPE_COMBINED_IMAGES(): string {
return ListComponent.COLUMN_TYPE_COMBINED_IMAGES;
}

get COLUMN_TYPE_TEXT(): string {
return ListComponent.COLUMN_TYPE_TEXT;
}

get COLUMN_TYPE_NUMBER(): string {
return ListComponent.COLUMN_TYPE_NUMBER;
}

get COLUMN_TYPE_NUMBER_UNFORMATTED(): string {
return ListComponent.COLUMN_TYPE_NUMBER_UNFORMATTED;
}

get COLUMN_TYPE_NUMBER_BOLD(): string {
return ListComponent.COLUMN_TYPE_NUMBER_BOLD;
}

get COLUMN_TYPE_TEXT_BOLD(): string {
return ListComponent.COLUMN_TYPE_TEXT_BOLD;
}

get COLUMN_TYPE_TEXT_LINKED(): string {
return ListComponent.COLUMN_TYPE_TEXT_LINKED;
}

get COLUMN_TYPE_WEBSITE(): string {
return ListComponent.COLUMN_TYPE_WEBSITE;
}

ngOnDestroy(): void {
this.clearAutoRefresh();
}
}

+ 52
- 0
angular/src/app/_components/paging/paging.component.html Ver ficheiro

@@ -0,0 +1,52 @@
<div class="spt-tools">
<ng-container *ngIf="searchable">
<div class="spt-form">
<form [formGroup]="searchForm" class="position-relative">
<div class="row">
<div class="col-12">
<input type="text" class="form-control" formControlName="inputText"
placeholder="{{'form.search_placeholder' | translate}}">
<span class="spt-clear" *ngIf="searchForm.get('inputText')?.value" (click)="clearForm()"></span>
</div>
</div>
</form>
</div>
</ng-container>

<div *ngIf="displayOptionKeys.length > 1">
<select [value]="selectedDisplayOption" (change)="onDisplayOptionChange($any($event.target).value)">
<option *ngFor="let option of displayOptionKeys" [value]="option">
{{displayOptions[option]}}
</option>
</select>
</div>

<div class="d-flex align-items-center flex-wrap">
<mat-paginator *ngIf="dataLength > 0" class=""
[pageSizeOptions]="pageSizeOptions"
[length]="dataLength"
(page)="handlePageEvent($event)"
[pageSize]="pageSize"
[pageIndex]="pageIndex"
[hidePageSize]="hidePageSize"
showFirstLastButtons
>
</mat-paginator>
<button type="button" class="btn btn-primary ms-3"
(click)="getData()"><span class="bi bi-arrow-clockwise"> {{ 'basic.refresh' | translate }}</span>
</button>
</div>
</div>
<ng-content></ng-content>
<div *ngIf="dataInitialized && dataLength <= 0" class="spt-no-entries">{{'form.no_data' | translate}}</div>
<div class="spt-tools single">
<mat-paginator *ngIf="dataLength > 0" class=""
[pageSizeOptions]="pageSizeOptions"
[length]="dataLength"
(page)="handlePageEvent($event)"
[pageSize]="pageSize"
[pageIndex]="pageIndex"
[hidePageSize]="hidePageSize"
showFirstLastButtons>
</mat-paginator>
</div>

+ 0
- 0
angular/src/app/_components/paging/paging.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/paging/paging.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PagingComponent } from './paging.component';

describe('ListComponent', () => {
let component: PagingComponent;
let fixture: ComponentFixture<PagingComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PagingComponent]
})
.compileComponents();
fixture = TestBed.createComponent(PagingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 132
- 0
angular/src/app/_components/paging/paging.component.ts Ver ficheiro

@@ -0,0 +1,132 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild
} from '@angular/core';
import {MatPaginator, MatPaginatorIntl, PageEvent} from "@angular/material/paginator";
import {FormBuilder, FormGroup} from "@angular/forms";
import {debounceTime, distinctUntilChanged} from "rxjs";

@Component({
selector: 'app-paging',
templateUrl: './paging.component.html',
styleUrl: './paging.component.scss'
})
export class PagingComponent implements OnInit, AfterViewInit {

@Input() public dataSource!: any;
@Input() public getDataFunction!: Function;
@Input() public pageSize!: number;
@Input() public pageSizeOptions!: number[];
@Input() public searchable: boolean;
@Input() public hidePageSize: boolean;
@Input() public displayOptions!: { [key: string]: any };
@Input() public defaultDisplayOption!: string;
@Output() public displayOptionChange = new EventEmitter<string>();

@ViewChild(MatPaginator) public paginator!: MatPaginator;

protected defaultPageSize: number = 50;
protected defaultPageSizeOptions: number[] = [20, 50, 100];

protected dataLength: number;
protected pageEvent: PageEvent;
protected pageIndex: number;
protected searchForm!: FormGroup;
protected dataInitialized: boolean = false;
protected selectedDisplayOption: string = '';
protected displayOptionKeys: string[] = [];

constructor(
private fb: FormBuilder
) {
this.dataLength = 0;
this.pageEvent = new PageEvent();
this.pageIndex = 0;
this.searchable = false;
this.hidePageSize = false;
}

ngOnInit() {
this.pageSize = this.pageSize !== undefined ? this.pageSize : this.defaultPageSize;
this.pageSizeOptions = this.pageSizeOptions !== undefined ? this.pageSizeOptions : this.defaultPageSizeOptions;
this.paginator = new MatPaginator(new MatPaginatorIntl(), ChangeDetectorRef.prototype);

if (this.searchable) {
this.searchForm = this.fb.group({
inputText: ['']
});
this.searchForm.get('inputText')?.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged()
).subscribe(value => {
this.resetPageIndex();
this.getData();
});
}
if (this.displayOptions !== undefined) {
this.displayOptionKeys = Object.keys(this.displayOptions);
this.selectedDisplayOption = this.defaultDisplayOption || Object.keys(this.displayOptions)[0] || '';
}

}

ngAfterViewInit() {

}

getData() {
this.getDataFunction(
this.getPageIndex(),
this.getPageSize(),
this.searchForm ? this.searchForm.get('inputText')?.value : undefined
);
}

handlePageEvent(e: PageEvent) {
this.pageEvent = e;
this.dataLength = e.length;
this.pageIndex = e.pageIndex.valueOf();
this.pageSize = e.pageSize.valueOf();
this.getData();
}

resetPageIndex(): void {
this.pageIndex = 0;
}

getPageIndex(): number {
return this.pageIndex + 1;
}

getPageSize(): number {
return this.pageSize;
}

setDataLength(dataLength: number): void {
this.dataInitialized = true;
this.dataLength = dataLength;
}

clearForm() {
this.searchForm.get('inputText')!.setValue('');
}

getSearchValue() {
if (this.searchable) {
return this.searchForm.get('inputText')!.value;
}
return undefined;
}

onDisplayOptionChange(event: Event): void {
this.displayOptionChange.emit(String(event));
}

protected readonly Object = Object;
}

+ 7
- 0
angular/src/app/_components/search-input/search-input-col-def.ts Ver ficheiro

@@ -0,0 +1,7 @@
export interface SearchInputColDef {
column: string,
columnHeader: string,
columnType: string,
field?: string
subResource?: string
}

+ 11
- 0
angular/src/app/_components/search-input/search-input.component.html Ver ficheiro

@@ -0,0 +1,11 @@
<label for="{{ dataField }}" class="form-label">
{{ formLabelLangKey | translate }}:
</label>
<input type="text" class="form-control" id="{{dataField}}"
[ngbTypeahead]="searchItem"
[inputFormatter]="formatter"
[value]="documentForm.get(documentFormField)?.value"
[resultFormatter]="formatter"
[editable]="false"
(selectItem)="onItemSelect($event)"
/>

+ 0
- 0
angular/src/app/_components/search-input/search-input.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/search-input/search-input.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SearchInputComponent } from './search-input.component';

describe('SearchInputComponent', () => {
let component: SearchInputComponent;
let fixture: ComponentFixture<SearchInputComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SearchInputComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SearchInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 38
- 0
angular/src/app/_components/search-input/search-input.component.ts Ver ficheiro

@@ -0,0 +1,38 @@
import {Component, Input} from '@angular/core';
import {FormGroup} from "@angular/forms";
import {debounceTime, distinctUntilChanged, Observable, OperatorFunction, switchMap} from "rxjs";
import {filter, map} from "rxjs/operators";

@Component({
selector: 'app-search-input',
templateUrl: './search-input.component.html',
styleUrl: './search-input.component.scss'
})
export class SearchInputComponent {

@Input() public formId!: string;
@Input() public formLabelLangKey!: string;
@Input() public dataField!: string;
@Input() public documentForm!: FormGroup;
@Input() public documentFormField!: string;
@Input() public fetchFunction!: (term: string) => Observable<{ id: any; name: any }[]>;


protected formatter = (apiData: any) => apiData.name;

protected searchItem: OperatorFunction<string, readonly {
id: any;
name: any
}[]> = (text$: Observable<string>) =>
text$.pipe(
debounceTime(200),
distinctUntilChanged(),
filter((term) => term.length >= 2),
switchMap((term) => this.fetchFunction(term)),
map((items: {id: any, name: any}[]) => items.slice(0, 10)),
);

protected onItemSelect(selectedItem: any): void {
this.documentForm.get(this.formId)?.setValue(selectedItem.item.id);
}
}

+ 19
- 0
angular/src/app/_components/search-select/search-select.component.html Ver ficheiro

@@ -0,0 +1,19 @@
<div class="search-select" [class.hide-header]="!showHeader">
<div class="show-name" *ngIf="!displayAsButton">
<p #paragraphRef (click)="openSearchBox()" [class.search-empty]="!searchBoxFilled"></p>
<span class="spt-clear" *ngIf="searchBoxFilled" (click)="clearSearch()"></span>
</div>
<div *ngIf="displayAsButton" (click)="openSearchBox()" class="btn btn-primary mb-2">{{ 'basic.pleaseChoose' | translate }}</div>
<div class="search-toggle" [class.search-box-open]="searchBoxOpen">
<app-list #listComponent
[listId]="'searchSelect'"
[getDataFunction]="getDataFunction"
[onRowSelectedFunction]="onRowSelected"
[listColDefinitions]="listColDefinitions"
[showDetailButton]="false"
[showPosition]="false"
[onSortFunction]="onSortChange"
>
</app-list>
</div>
</div>

+ 0
- 0
angular/src/app/_components/search-select/search-select.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/search-select/search-select.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SearchSelectComponent } from './search-select.component';

describe('SearchSelectComponent', () => {
let component: SearchSelectComponent;
let fixture: ComponentFixture<SearchSelectComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SearchSelectComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SearchSelectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 253
- 0
angular/src/app/_components/search-select/search-select.component.ts Ver ficheiro

@@ -0,0 +1,253 @@
import {AfterViewInit, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
import {FormGroup} from "@angular/forms";
import {ListComponent} from "@app/_components/list/list.component";
import {ListColDefinition} from "@app/_components/list/list-col-definition";
import {Observable} from "rxjs";
import {Sort} from "@angular/material/sort";
import {FilterBarComponent} from "@app/_components/filter-bar/filter-bar.component";

@Component({
selector: 'app-search-select',
templateUrl: './search-select.component.html',
styleUrl: './search-select.component.scss'
})
export class SearchSelectComponent implements OnInit, AfterViewInit {
@Input() public formId!: string;
@Input() public resultField!: string;
@Input() public formLabelLangKey!: string;
@Input() public documentForm!: FormGroup;
@Input() public documentFormField!: string;
@Input() public getDataFunction!: (index: number, pageSize: number, term?: string) => Observable<any>;
@Input() public onRowSelectedFunction!: Function;
@Input() public dataSet!: any;
@Input() public displayedDataField!: string;
@Input() public displayedDataSubResource!: string;
@Input() public listColDefinitions!: ListColDefinition[];
@Input() public displayAsButton: boolean;
@Input() public showHeader: boolean;
@Output() rowSelected = new EventEmitter<any>();
@ViewChild('paragraphRef', {static: false}) paragraphRef!: ElementRef;
@ViewChild("listComponent", {static: false}) listComponent!: ListComponent;
protected readonly SearchSelectComponent = SearchSelectComponent;

protected selectedRowIndex: number | null = null;
protected searchBoxOpen: boolean;
protected searchBoxInitialized: boolean;
protected searchBoxFilled: boolean;

constructor() {
this.searchBoxOpen = false;
this.searchBoxInitialized = false;
this.searchBoxFilled = false;
this.displayAsButton = false;
this.showHeader = false;
}

ngOnInit(): void {
if (this.dataSet !== undefined) {
this.searchBoxFilled = true;
}
}

ngAfterViewInit(): void {
if (this.dataSet !== undefined) {
this.paragraphRef.nativeElement.textContent = this.dataSet[this.displayedDataField];
}
}

onRowSelected = (row: any, index: number) => {
if (this.onRowSelectedFunction !== undefined) {
this.onRowSelectedFunction(row, index);
} else {
this.selectedRowIndex = index;

const value = this.resultField !== undefined ? row[this.resultField] : row.id;
this.documentForm.get(this.formId)?.setValue(value);
if (this.displayedDataSubResource !== undefined) {
this.paragraphRef.nativeElement.textContent = row[this.displayedDataSubResource][this.displayedDataField];
} else {
this.paragraphRef.nativeElement.textContent = row[this.displayedDataField];
}
this.searchBoxFilled = true;
this.searchBoxOpen = false;
}
}

openSearchBox() {
this.searchBoxOpen = !this.searchBoxOpen;
if (this.searchBoxOpen && !this.searchBoxInitialized) {
this.listComponent.getData();
this.searchBoxInitialized = true;
}
}

clearSearch() {
this.paragraphRef.nativeElement.textContent = '';
this.searchBoxFilled = false;
this.documentForm.get(this.formId)?.setValue(null);
}

public getPageIndex() {
return this.listComponent.getPageIndex();
}

public getPageSize() {
return this.listComponent.getPageSize();
}

onSortChange = (sortState: Sort) => {
}

public static getDefaultColDefAccountsSniping(subResource?: string): ListColDefinition[] {
return [
ListComponent.getDefaultColPosition(),
{
name: 'profile',
text: 'game_account.profile',
type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'profile',
sortable: true,
//subResource: subResource,
} as ListColDefinition,
{
name: 'credits',
text: 'game_account.credits',
type: ListComponent.COLUMN_TYPE_NUMBER_BOLD,
field: 'credits',
//subResource: subResource,
sortable: true,
} as ListColDefinition,
{
name: 'running',
text: 'sniping.running',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'running',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'blocked',
text: 'sniping.blocked',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'blocked',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'snipingCnt1h',
text: 'sniping.snipingCnt1h',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'snipingCnt1h',
sortable: true,
} as ListColDefinition,
{
name: 'snipingCnt3h',
text: 'sniping.snipingCnt3h',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'snipingCnt3h',
sortable: true,
} as ListColDefinition,
{
name: 'snipingCnt6h',
text: 'sniping.snipingCnt6h',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'snipingCnt6h',
sortable: true,
} as ListColDefinition,
{
name: 'snipingCnt12h',
text: 'sniping.snipingCnt12h',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'snipingCnt12h',
sortable: true,
} as ListColDefinition,
{
name: 'snipingCnt24h',
text: 'sniping.snipingCnt24h',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'snipingCnt24h',
sortable: true,
} as ListColDefinition,
{
name: 'snipingCnt3d',
text: 'sniping.snipingCnt3d',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'snipingCnt3d',
sortable: true,
} as ListColDefinition,
{
name: 'snipingCnt1w',
text: 'sniping.snipingCnt1w',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'snipingCnt1w',
sortable: true,
} as ListColDefinition,
{
name: 'snipingDate',
text: 'game_account.snipingDate',
type: ListComponent.COLUMN_TYPE_DATE,
field: 'snipingDate',
sortable: true,
} as ListColDefinition,
{
name: 'relistDate',
text: 'game_account.relistDate',
type: ListComponent.COLUMN_TYPE_DATE,
field: 'relistDate',
sortable: true,
} as ListColDefinition,
];
}

public static getDefaultColDefCandidates(subResource?: string): ListColDefinition[] {
return [
ListComponent.getDefaultColPosition(),
{
name: 'image',
text: 'basic.image',
type: ListComponent.COLUMN_TYPE_COMBINED_IMAGES,
multipleFields: ['cardImageUrl', 'imageUrl', 'rating'],
} as ListColDefinition,
{
name: 'firstname',
text: 'candidate.firstname',
type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'firstname',
sortingSubResource: 'player',
sortable: true,
} as ListColDefinition,
{
name: 'lastname',
text: 'candidate.lastname',
type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'lastname',
sortingSubResource: 'player',
sortable: true,
} as ListColDefinition,
{
name: 'nickname',
text: 'candidate.nickname',
type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'nickname',
sortingSubResource: 'player',
sortable: true,
} as ListColDefinition,
{
name: 'rating',
text: 'candidate.rating',
type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'rating',
sortable: true,
} as ListColDefinition,
{
name: 'rarityName',
text: 'candidate.rarityName',
type: ListComponent.COLUMN_TYPE_TEXT,
field: 'name',
subResource: 'rarity',
sortingSubResource: 'rarity',
sortable: true,
} as ListColDefinition,
];
}
}

+ 9
- 0
angular/src/app/_components/toggle/toggle.component.html Ver ficheiro

@@ -0,0 +1,9 @@
<div class="toggle-component" (click)="openToggle()">
<div class="toggle collapsed" [ngClass]="{'small': small}" data-bs-toggle="collapse" [attr.data-bs-target]="'#collapse-' + toggleId"
aria-expanded="false">
<p>{{headline | translate}}<span *ngIf="activeFilterCount > 0" class="active-filter-count"> ( {{ activeFilterCount }} )</span></p>
</div>
<div class="collapse" id="collapse-{{ toggleId }}">
<ng-content></ng-content>
</div>
</div>

+ 0
- 0
angular/src/app/_components/toggle/toggle.component.scss Ver ficheiro


+ 23
- 0
angular/src/app/_components/toggle/toggle.component.spec.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ToggleComponent } from './toggle.component';

describe('ToggleComponent', () => {
let component: ToggleComponent;
let fixture: ComponentFixture<ToggleComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ToggleComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ToggleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 23
- 0
angular/src/app/_components/toggle/toggle.component.ts Ver ficheiro

@@ -0,0 +1,23 @@
import {Component, Input} from '@angular/core';
import { v4 as uuidv4 } from 'uuid';
@Component({
selector: 'app-toggle',
templateUrl: './toggle.component.html',
styleUrl: './toggle.component.scss'
})
export class ToggleComponent {
@Input() public headline!: string;
@Input() small: boolean = false;
@Input() activeFilterCount: number = 0;

public isOpened: boolean = false;
protected toggleId: string;

constructor() {
this.toggleId = uuidv4();
}

openToggle() {
this.isOpened = true;
}
}

+ 620
- 0
angular/src/app/_forms/apiForms.ts Ver ficheiro

@@ -0,0 +1,620 @@
import { FormGroup, FormControl, Validators } from '@angular/forms';

export const accountTradePileItemForm = new FormGroup({
dbId: new FormControl(null, []),
account: new FormControl(null, []),
candidateItem: new FormControl(null, []),
eaId: new FormControl(null, []),
eaAssetId: new FormControl(null, []),
eaResourceId: new FormControl(null, []),
rareFlag: new FormControl(null, []),
itemType: new FormControl(null, []),
rating: new FormControl(null, []),
contracts: new FormControl(null, []),
playStyle: new FormControl(null, []),
startingBid: new FormControl(null, []),
binPrice: new FormControl(null, []),
individualPrice: new FormControl(null, []),
minRange: new FormControl(null, []),
maxRange: new FormControl(null, []),
lastSalePrice: new FormControl(null, []),
tradeState: new FormControl(null, []),
eaTradeId: new FormControl(null, []),
rebuy: new FormControl(null, []),
leagueId: new FormControl(null, []),
teamId: new FormControl(null, []),
nationId: new FormControl(null, []),
listCnt: new FormControl(null, []),
openBidCnt: new FormControl(null, []),
snipedItem: new FormControl(null, []),
marketAverage: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const accountTradePileItemJsonldForm = new FormGroup({
dbId: new FormControl(null, []),
account: new FormControl(null, []),
candidateItem: new FormControl(null, []),
eaId: new FormControl(null, []),
eaAssetId: new FormControl(null, []),
eaResourceId: new FormControl(null, []),
rareFlag: new FormControl(null, []),
itemType: new FormControl(null, []),
rating: new FormControl(null, []),
contracts: new FormControl(null, []),
playStyle: new FormControl(null, []),
startingBid: new FormControl(null, []),
binPrice: new FormControl(null, []),
individualPrice: new FormControl(null, []),
minRange: new FormControl(null, []),
maxRange: new FormControl(null, []),
lastSalePrice: new FormControl(null, []),
tradeState: new FormControl(null, []),
eaTradeId: new FormControl(null, []),
rebuy: new FormControl(null, []),
leagueId: new FormControl(null, []),
teamId: new FormControl(null, []),
nationId: new FormControl(null, []),
listCnt: new FormControl(null, []),
openBidCnt: new FormControl(null, []),
snipedItem: new FormControl(null, []),
marketAverage: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const candidateForm = new FormGroup({
dbId: new FormControl(null, []),
rarity: new FormControl(null, []),
candidateStat: new FormControl(null, []),
player: new FormControl(null, []),
firstname: new FormControl(null, []),
lastname: new FormControl(null, []),
nickname: new FormControl(null, []),
fullDisplayInfo: new FormControl(null, []),
eaAssetId: new FormControl(null, [Validators.required]),
eaResourceId: new FormControl(null, [Validators.required]),
rareFlag: new FormControl(null, [Validators.required]),
rarityName: new FormControl(null, []),
image: new FormControl(null, []),
imageUrl: new FormControl(null, []),
cardImageUrl: new FormControl(null, []),
stockCountTotal: new FormControl(null, []),
stockCountReal: new FormControl(null, []),
futBinId: new FormControl(null, []),
futBinName: new FormControl(null, []),
futBinPrice: new FormControl(null, []),
futBinSellingPrice: new FormControl(null, []),
lastFutBinUpdate: new FormControl(null, []),
futWizId: new FormControl(null, []),
futWizPrice: new FormControl(null, []),
futwizName: new FormControl(null, []),
futWizSellingPrice: new FormControl(null, []),
lastFutWizUpdate: new FormControl(null, []),
rating: new FormControl(null, [Validators.required]),
highestBuyBinPrice: new FormControl(null, []),
sellStartingBid: new FormControl(null, []),
sellBinPrice: new FormControl(null, []),
lastFoundMinRange: new FormControl(null, []),
lastFoundMaxRange: new FormControl(null, []),
lastFoundLowestBin: new FormControl(null, []),
lowestBinUpdateDate: new FormControl(null, []),
buy: new FormControl(null, [Validators.required]),
maxBuyPrice: new FormControl(null, []),
buyStyle: new FormControl(null, [Validators.required]),
newBuySelective: new FormControl(null, [Validators.required]),
remove: new FormControl(null, [Validators.required]),
leagueId: new FormControl(null, []),
nationId: new FormControl(null, []),
prio: new FormControl(null, []),
listCnt: new FormControl(null, [Validators.required]),
soldCnt: new FormControl(null, [Validators.required]),
note: new FormControl(null, []),
relevant: new FormControl(null, [Validators.required]),
adjust100: new FormControl(null, [Validators.required]),
directReBuy: new FormControl(null, [Validators.required]),
marketAverage: new FormControl(null, []),
creationDate: new FormControl(null, [Validators.required])
});

export const candidateJsonldForm = new FormGroup({
dbId: new FormControl(null, []),
rarity: new FormControl(null, []),
candidateStat: new FormControl(null, []),
player: new FormControl(null, []),
firstname: new FormControl(null, []),
lastname: new FormControl(null, []),
nickname: new FormControl(null, []),
fullDisplayInfo: new FormControl(null, []),
eaAssetId: new FormControl(null, [Validators.required]),
eaResourceId: new FormControl(null, [Validators.required]),
rareFlag: new FormControl(null, [Validators.required]),
rarityName: new FormControl(null, []),
image: new FormControl(null, []),
imageUrl: new FormControl(null, []),
cardImageUrl: new FormControl(null, []),
stockCountTotal: new FormControl(null, []),
stockCountReal: new FormControl(null, []),
futBinId: new FormControl(null, []),
futBinName: new FormControl(null, []),
futBinPrice: new FormControl(null, []),
futBinSellingPrice: new FormControl(null, []),
lastFutBinUpdate: new FormControl(null, []),
futWizId: new FormControl(null, []),
futWizPrice: new FormControl(null, []),
futwizName: new FormControl(null, []),
futWizSellingPrice: new FormControl(null, []),
lastFutWizUpdate: new FormControl(null, []),
rating: new FormControl(null, [Validators.required]),
highestBuyBinPrice: new FormControl(null, []),
sellStartingBid: new FormControl(null, []),
sellBinPrice: new FormControl(null, []),
lastFoundMinRange: new FormControl(null, []),
lastFoundMaxRange: new FormControl(null, []),
lastFoundLowestBin: new FormControl(null, []),
lowestBinUpdateDate: new FormControl(null, []),
buy: new FormControl(null, [Validators.required]),
maxBuyPrice: new FormControl(null, []),
buyStyle: new FormControl(null, [Validators.required]),
newBuySelective: new FormControl(null, [Validators.required]),
remove: new FormControl(null, [Validators.required]),
leagueId: new FormControl(null, []),
nationId: new FormControl(null, []),
prio: new FormControl(null, []),
listCnt: new FormControl(null, [Validators.required]),
soldCnt: new FormControl(null, [Validators.required]),
note: new FormControl(null, []),
relevant: new FormControl(null, [Validators.required]),
adjust100: new FormControl(null, [Validators.required]),
directReBuy: new FormControl(null, [Validators.required]),
marketAverage: new FormControl(null, []),
creationDate: new FormControl(null, [Validators.required])
});

export const candidateStatJsonldForm = new FormGroup({
candidateItem: new FormControl(null, []),
revRl6: new FormControl(null, []),
rl6: new FormControl(null, []),
sold6: new FormControl(null, []),
rat6: new FormControl(null, []),
rev6: new FormControl(null, []),
revRl12: new FormControl(null, []),
rl12: new FormControl(null, []),
sold12: new FormControl(null, []),
rat12: new FormControl(null, []),
rev12: new FormControl(null, []),
revRl24: new FormControl(null, []),
rl24: new FormControl(null, []),
sold24: new FormControl(null, []),
rat24: new FormControl(null, []),
rev24: new FormControl(null, []),
revRl3d: new FormControl(null, []),
rl3d: new FormControl(null, []),
sold3d: new FormControl(null, []),
rat3d: new FormControl(null, []),
rev3d: new FormControl(null, []),
revRl1w: new FormControl(null, []),
rl1w: new FormControl(null, []),
sold1w: new FormControl(null, []),
rat1w: new FormControl(null, []),
rev1w: new FormControl(null, []),
revRl2w: new FormControl(null, []),
rl2w: new FormControl(null, []),
sold2w: new FormControl(null, []),
rat2w: new FormControl(null, []),
rev2w: new FormControl(null, []),
revRl3w: new FormControl(null, []),
rl3w: new FormControl(null, []),
sold3w: new FormControl(null, []),
rat3w: new FormControl(null, []),
rev3w: new FormControl(null, []),
revRl4w: new FormControl(null, []),
rl4w: new FormControl(null, []),
sold4w: new FormControl(null, []),
rat4w: new FormControl(null, []),
rev4w: new FormControl(null, []),
revRl: new FormControl(null, []),
rl: new FormControl(null, []),
sold: new FormControl(null, []),
rat: new FormControl(null, []),
rev: new FormControl(null, []),
snipingRev: new FormControl(null, []),
creationDate: new FormControl(null, []),
lastUpdateDate: new FormControl(null, [])
});

export const candidateStockAccountsJsonldForm = new FormGroup({
candidatesStockAccounts: new FormControl(null, []),
candidatesMissingAccounts: new FormControl(null, [])
});

export const configForm = new FormGroup({
systemActive: new FormControl(null, []),
systemRunning: new FormControl(null, []),
lastUpdateDate: new FormControl(null, []),
lastCheckDate: new FormControl(null, []),
processCnt: new FormControl(null, []),
sleepHourStart: new FormControl(null, [
Validators.min(0),
Validators.max(23)
]),
sleepHourEnd: new FormControl(null, [Validators.min(0), Validators.max(23)]),
checkMaxSales: new FormControl(null, []),
numMaxSales: new FormControl(null, [Validators.min(0), Validators.max(20)])
});

export const configJsonldForm = new FormGroup({
systemActive: new FormControl(null, []),
systemRunning: new FormControl(null, []),
lastUpdateDate: new FormControl(null, []),
lastCheckDate: new FormControl(null, []),
processCnt: new FormControl(null, []),
sleepHourStart: new FormControl(null, [
Validators.min(0),
Validators.max(23)
]),
sleepHourEnd: new FormControl(null, [Validators.min(0), Validators.max(23)]),
checkMaxSales: new FormControl(null, []),
numMaxSales: new FormControl(null, [Validators.min(0), Validators.max(20)])
});

export const gameAccountForm = new FormGroup({
dbId: new FormControl(null, []),
email: new FormControl(null, [Validators.required, Validators.email]),
profile: new FormControl(null, [Validators.required]),
password: new FormControl(null, [Validators.required]),
emailPw: new FormControl(null, []),
credits: new FormControl(null, []),
cntItems: new FormControl(null, []),
cntSoldItems: new FormControl(null, []),
cntInactiveItems: new FormControl(null, []),
active: new FormControl(null, [Validators.required]),
running: new FormControl(null, [Validators.required]),
relist: new FormControl(null, [Validators.required]),
relistDate: new FormControl(null, []),
blocked: new FormControl(null, [Validators.required]),
sniping: new FormControl(null, [Validators.required]),
snipingDate: new FormControl(null, []),
tmOpen: new FormControl(null, [Validators.required]),
tmState: new FormControl(null, []),
dead: new FormControl(null, [Validators.required]),
lockedMsg: new FormControl(null, [Validators.required]),
dynPrices: new FormControl(null, [Validators.required]),
newBuy: new FormControl(null, [Validators.required]),
newBuyDate: new FormControl(null, []),
newBuySelective: new FormControl(null, [Validators.required]),
reBuy: new FormControl(null, [Validators.required]),
rebuyDate: new FormControl(null, []),
connectionDate: new FormControl(null, []),
importWatchlist: new FormControl(null, [Validators.required]),
autoReBuy: new FormControl(null, [Validators.required]),
directReBuy: new FormControl(null, [Validators.required]),
itemMaxBuyPrice: new FormControl(null, [
Validators.min(0),
Validators.max(15000000)
]),
revenue: new FormControl(null, []),
futWizValue: new FormControl(null, []),
eaMarketAvgValue: new FormControl(null, []),
mfaCode: new FormControl(null, []),
twoFactorAuthKey: new FormControl(null, []),
login2FaViaApp: new FormControl(null, []),
eaCode1: new FormControl(null, []),
eaCode2: new FormControl(null, []),
eaCode3: new FormControl(null, []),
eaCode4: new FormControl(null, []),
eaCode5: new FormControl(null, []),
eaCode6: new FormControl(null, []),
snipingCnt1h: new FormControl(null, []),
snipingCnt3h: new FormControl(null, []),
snipingCnt6h: new FormControl(null, []),
snipingCnt12h: new FormControl(null, []),
snipingCnt24h: new FormControl(null, []),
snipingCnt3d: new FormControl(null, []),
snipingCnt1w: new FormControl(null, []),
note: new FormControl(null, []),
loopStartDate: new FormControl(null, []),
loopFinishDate: new FormControl(null, []),
creationDate: new FormControl(null, []),
owner: new FormControl(null, [])
});

export const gameAccountJsonldForm = new FormGroup({
dbId: new FormControl(null, []),
email: new FormControl(null, [Validators.required, Validators.email]),
profile: new FormControl(null, [Validators.required]),
password: new FormControl(null, [Validators.required]),
emailPw: new FormControl(null, []),
credits: new FormControl(null, []),
cntItems: new FormControl(null, []),
cntSoldItems: new FormControl(null, []),
cntInactiveItems: new FormControl(null, []),
active: new FormControl(null, [Validators.required]),
running: new FormControl(null, [Validators.required]),
relist: new FormControl(null, [Validators.required]),
relistDate: new FormControl(null, []),
blocked: new FormControl(null, [Validators.required]),
sniping: new FormControl(null, [Validators.required]),
snipingDate: new FormControl(null, []),
tmOpen: new FormControl(null, [Validators.required]),
tmState: new FormControl(null, []),
dead: new FormControl(null, [Validators.required]),
lockedMsg: new FormControl(null, [Validators.required]),
dynPrices: new FormControl(null, [Validators.required]),
newBuy: new FormControl(null, [Validators.required]),
newBuyDate: new FormControl(null, []),
newBuySelective: new FormControl(null, [Validators.required]),
reBuy: new FormControl(null, [Validators.required]),
rebuyDate: new FormControl(null, []),
connectionDate: new FormControl(null, []),
importWatchlist: new FormControl(null, [Validators.required]),
autoReBuy: new FormControl(null, [Validators.required]),
directReBuy: new FormControl(null, [Validators.required]),
itemMaxBuyPrice: new FormControl(null, [
Validators.min(0),
Validators.max(15000000)
]),
revenue: new FormControl(null, []),
futWizValue: new FormControl(null, []),
eaMarketAvgValue: new FormControl(null, []),
mfaCode: new FormControl(null, []),
twoFactorAuthKey: new FormControl(null, []),
login2FaViaApp: new FormControl(null, []),
eaCode1: new FormControl(null, []),
eaCode2: new FormControl(null, []),
eaCode3: new FormControl(null, []),
eaCode4: new FormControl(null, []),
eaCode5: new FormControl(null, []),
eaCode6: new FormControl(null, []),
snipingCnt1h: new FormControl(null, []),
snipingCnt3h: new FormControl(null, []),
snipingCnt6h: new FormControl(null, []),
snipingCnt12h: new FormControl(null, []),
snipingCnt24h: new FormControl(null, []),
snipingCnt3d: new FormControl(null, []),
snipingCnt1w: new FormControl(null, []),
note: new FormControl(null, []),
loopStartDate: new FormControl(null, []),
loopFinishDate: new FormControl(null, []),
creationDate: new FormControl(null, []),
owner: new FormControl(null, [])
});

export const logAccountCreditJsonldForm = new FormGroup({
gameAccount: new FormControl(null, []),
credits: new FormControl(null, []),
revenue: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const logAccountProfitJsonldForm = new FormGroup({
gameAccount: new FormControl(null, []),
credits: new FormControl(null, []),
revenue: new FormControl(null, []),
tpValue: new FormControl(null, []),
revToday: new FormControl(null, []),
rev3hours: new FormControl(null, []),
rev6hours: new FormControl(null, []),
rev12hours: new FormControl(null, []),
rev24hours: new FormControl(null, []),
rev3days: new FormControl(null, []),
rev1week: new FormControl(null, []),
rev2weeks: new FormControl(null, []),
rev3weeks: new FormControl(null, []),
rev4weeks: new FormControl(null, []),
rev2months: new FormControl(null, []),
rev3months: new FormControl(null, []),
revTotal: new FormControl(null, []),
numSalesToday: new FormControl(null, []),
numSales3hours: new FormControl(null, []),
numSales6hours: new FormControl(null, []),
numSales12hours: new FormControl(null, []),
numSales24hours: new FormControl(null, []),
numSales3days: new FormControl(null, []),
numSales1week: new FormControl(null, []),
numSales2weeks: new FormControl(null, []),
numSales3weeks: new FormControl(null, []),
numSales4weeks: new FormControl(null, []),
numSales2months: new FormControl(null, []),
numSales3months: new FormControl(null, []),
numSalesTotal: new FormControl(null, []),
isDailyProfit: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const logAccountSoldItemJsonldForm = new FormGroup({
dbId: new FormControl(null, []),
gameAccount: new FormControl(null, []),
candidateItem: new FormControl(null, []),
eaId: new FormControl(null, []),
contracts: new FormControl(null, []),
playStyle: new FormControl(null, []),
lastSalePrice: new FormControl(null, []),
currentBid: new FormControl(null, []),
startingBid: new FormControl(null, []),
binPrice: new FormControl(null, []),
minRange: new FormControl(null, []),
maxRange: new FormControl(null, []),
tradeState: new FormControl(null, []),
eaTradeId: new FormControl(null, []),
listCnt: new FormControl(null, []),
revenue: new FormControl(null, []),
reBought: new FormControl(null, []),
snipedItem: new FormControl(null, []),
firstListDate: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const logGeneralJsonldForm = new FormGroup({
dbId: new FormControl(null, []),
gameAccount: new FormControl(null, []),
candidateItem: new FormControl(null, []),
logType: new FormControl(null, [Validators.required]),
message: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const logTotalProfitForm = new FormGroup({
credits: new FormControl(null, []),
revenue: new FormControl(null, []),
tpValueFutwiz: new FormControl(null, []),
tpValueEaAverage: new FormControl(null, []),
revToday: new FormControl(null, []),
rev3hours: new FormControl(null, []),
rev6hours: new FormControl(null, []),
rev12hours: new FormControl(null, []),
rev24hours: new FormControl(null, []),
rev3days: new FormControl(null, []),
rev1week: new FormControl(null, []),
rev2weeks: new FormControl(null, []),
rev3weeks: new FormControl(null, []),
rev4weeks: new FormControl(null, []),
rev2months: new FormControl(null, []),
rev3months: new FormControl(null, []),
revTotal: new FormControl(null, []),
numSalesToday: new FormControl(null, []),
numSales3hours: new FormControl(null, []),
numSales6hours: new FormControl(null, []),
numSales12hours: new FormControl(null, []),
numSales24hours: new FormControl(null, []),
numSales3days: new FormControl(null, []),
numSales1week: new FormControl(null, []),
numSales2weeks: new FormControl(null, []),
numSales3weeks: new FormControl(null, []),
numSales4weeks: new FormControl(null, []),
numSales2months: new FormControl(null, []),
numSales3months: new FormControl(null, []),
numSalesTotal: new FormControl(null, []),
isDailyProfit: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const logTotalProfitJsonldForm = new FormGroup({
credits: new FormControl(null, []),
revenue: new FormControl(null, []),
tpValueFutwiz: new FormControl(null, []),
tpValueEaAverage: new FormControl(null, []),
revToday: new FormControl(null, []),
rev3hours: new FormControl(null, []),
rev6hours: new FormControl(null, []),
rev12hours: new FormControl(null, []),
rev24hours: new FormControl(null, []),
rev3days: new FormControl(null, []),
rev1week: new FormControl(null, []),
rev2weeks: new FormControl(null, []),
rev3weeks: new FormControl(null, []),
rev4weeks: new FormControl(null, []),
rev2months: new FormControl(null, []),
rev3months: new FormControl(null, []),
revTotal: new FormControl(null, []),
numSalesToday: new FormControl(null, []),
numSales3hours: new FormControl(null, []),
numSales6hours: new FormControl(null, []),
numSales12hours: new FormControl(null, []),
numSales24hours: new FormControl(null, []),
numSales3days: new FormControl(null, []),
numSales1week: new FormControl(null, []),
numSales2weeks: new FormControl(null, []),
numSales3weeks: new FormControl(null, []),
numSales4weeks: new FormControl(null, []),
numSales2months: new FormControl(null, []),
numSales3months: new FormControl(null, []),
numSalesTotal: new FormControl(null, []),
isDailyProfit: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const mediaObjectJsonldMediaObjectReadForm = new FormGroup({
contentUrl: new FormControl(null, [])
});

export const modeConfigForm = new FormGroup({
autoReBuyMinSoldItems: new FormControl(null, [
Validators.min(0),
Validators.max(20)
]),
autoReBuyMinLastHours: new FormControl(null, [
Validators.min(0),
Validators.max(20)
])
});

export const modeConfigJsonldForm = new FormGroup({
autoReBuyMinSoldItems: new FormControl(null, [
Validators.min(0),
Validators.max(20)
]),
autoReBuyMinLastHours: new FormControl(null, [
Validators.min(0),
Validators.max(20)
])
});

export const playerForm = new FormGroup({
eaAssetId: new FormControl(null, []),
firstname: new FormControl(null, []),
lastname: new FormControl(null, []),
nickname: new FormControl(null, []),
rating: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const playerJsonldForm = new FormGroup({
eaAssetId: new FormControl(null, []),
firstname: new FormControl(null, []),
lastname: new FormControl(null, []),
nickname: new FormControl(null, []),
rating: new FormControl(null, []),
creationDate: new FormControl(null, [])
});

export const rarityForm = new FormGroup({
rareFlag: new FormControl(null, []),
name: new FormControl(null, []),
untradable: new FormControl(null, []),
image: new FormControl(null, []),
imageUrl: new FormControl(null, []),
imageBronze: new FormControl(null, [])
});

export const rarityJsonldForm = new FormGroup({
rareFlag: new FormControl(null, []),
name: new FormControl(null, []),
untradable: new FormControl(null, []),
image: new FormControl(null, []),
imageUrl: new FormControl(null, []),
imageBronze: new FormControl(null, [])
});

export const systemStatJsonldForm = new FormGroup({
totalLogProfit: new FormControl(null, []),
config: new FormControl(null, []),
modeConfig: new FormControl(null, []),
numAccounts: new FormControl(null, []),
numDeadAccounts: new FormControl(null, []),
numActiveAccounts: new FormControl(null, []),
numTmOpenAccounts: new FormControl(null, []),
numTmClosedAccounts: new FormControl(null, []),
numBlockedAccounts: new FormControl(null, []),
numRunningAccounts: new FormControl(null, []),
numTradepileItems: new FormControl(null, []),
numSoldTradepileItems: new FormControl(null, []),
numActiveTradepileItems: new FormControl(null, []),
numExpiredTradepileItems: new FormControl(null, []),
numInactiveTradepileItems: new FormControl(null, []),
numCandidates: new FormControl(null, []),
numRelevantCandidates: new FormControl(null, []),
numBuyCandidates: new FormControl(null, []),
totalSnipingRev: new FormControl(null, [])
});

export const userJsonldForm = new FormGroup({
email: new FormControl(null, [Validators.required, Validators.email]),
firstName: new FormControl(null, [Validators.required]),
lastName: new FormControl(null, [Validators.required]),
image: new FormControl(null, []),
imageUrl: new FormControl(null, []),
fullName: new FormControl(null, []),
password: new FormControl(null, []),
active: new FormControl(null, []),
createdAt: new FormControl(null, [])
});

+ 17
- 0
angular/src/app/_guards/admin.guard.spec.ts Ver ficheiro

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';

import { adminGuard } from './admin.guard';

describe('adminGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => adminGuard(...guardParameters));

beforeEach(() => {
TestBed.configureTestingModule({});
});

it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});

+ 12
- 0
angular/src/app/_guards/admin.guard.ts Ver ficheiro

@@ -0,0 +1,12 @@
import {CanActivateFn, Router} from '@angular/router';
import {inject} from "@angular/core";
import {AccountService} from "@app/_services";

export const adminGuard: CanActivateFn = (route, state) => {
const accountService = inject(AccountService);
if (accountService.isLoggedIn() && accountService.isUserAdmin()) {
return true;
}
inject(Router).navigate(['/account/login'], { queryParams: { returnUrl: state.url }});
return false;
};

+ 17
- 0
angular/src/app/_guards/game-account-owner.guard.spec.ts Ver ficheiro

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';

import { gameAccountOwnerGuard } from './game-account-owner.guard';

describe('gameAccountOwnerGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => gameAccountOwnerGuard(...guardParameters));

beforeEach(() => {
TestBed.configureTestingModule({});
});

it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});

+ 27
- 0
angular/src/app/_guards/game-account-owner.guard.ts Ver ficheiro

@@ -0,0 +1,27 @@
import { CanActivateFn, Router } from '@angular/router';
import { inject } from "@angular/core";
import { map } from 'rxjs/operators';
import {GameAccountService} from "@app/core/api/v1";

export const gameAccountOwnerGuard: CanActivateFn = (route, state) => {
const gameAccountService = inject(GameAccountService);
const router = inject(Router);
const gameAccountId = route.paramMap.get('id');

if (!gameAccountId) {
router.navigate(['/error']); // Oder eine andere geeignete Route
return false;
}

return gameAccountService.gameAccountsIdGet(gameAccountId).pipe(
map(gameAccount => {
return true;
// if (isOwner) {
// return true;
// } else {
// router.navigate(['/' + ROUTE_DASHBOARD]); // Oder eine andere geeignete Route
// return false;
// }
})
);
};

+ 17
- 0
angular/src/app/_guards/sales.guard.spec.ts Ver ficheiro

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';

import { salesGuard } from './sales.guard';

describe('salesGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => salesGuard(...guardParameters));

beforeEach(() => {
TestBed.configureTestingModule({});
});

it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});

+ 13
- 0
angular/src/app/_guards/sales.guard.ts Ver ficheiro

@@ -0,0 +1,13 @@
import {CanActivateFn, Router} from '@angular/router';
import {inject} from "@angular/core";
import {AccountService} from "@app/_services";
import {Role} from "@app/_helpers/role";

export const salesGuard: CanActivateFn = (route, state) => {
const accountService = inject(AccountService);
if (accountService.isLoggedIn() && accountService.userHasRole(Role.ROLE_ADMIN)) {
return true;
}
inject(Router).navigate(['/account/login'], { queryParams: { returnUrl: state.url }});
return false;
};

+ 17
- 0
angular/src/app/_guards/user.guard.spec.ts Ver ficheiro

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';

import { userGuard } from './user.guard';

describe('userGuardGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => userGuard(...guardParameters));

beforeEach(() => {
TestBed.configureTestingModule({});
});

it('should be created', () => {
expect(executeGuard).toBeTruthy();
});
});

+ 13
- 0
angular/src/app/_guards/user.guard.ts Ver ficheiro

@@ -0,0 +1,13 @@
import {CanActivateFn, Router} from '@angular/router';
import {inject} from "@angular/core";
import {AccountService} from "@app/_services";
import {Role} from "@app/_helpers/role";

export const userGuard: CanActivateFn = (route, state) => {
const accountService = inject(AccountService);
if (accountService.isLoggedIn() && accountService.userHasRole(Role.ROLE_USER)) {
return true;
}
inject(Router).navigate(['/account/login'], { queryParams: { returnUrl: state.url }});
return false;
};

+ 86
- 0
angular/src/app/_helpers/app-helper.service.ts Ver ficheiro

@@ -0,0 +1,86 @@
import {DomSanitizer, SafeHtml} from "@angular/platform-browser";
import {Injectable} from "@angular/core";
import {NgbModal, NgbModalOptions} from "@ng-bootstrap/ng-bootstrap";
import {ModalStatus} from "@app/_helpers/modal.states";

@Injectable({providedIn: 'root'})
export class AppHelperService {

constructor(
private sanitizer: DomSanitizer,
private modalService: NgbModal,
) {
}

public extractId(iri: string | undefined | null): string {
if (iri !== undefined && iri !== null) {
const iriRegex = /\/(\d+)$/;
const match = iri.match(iriRegex);
if (match && match[1]) {
return match[1];
}
}
return "";
}

public convertDate(dateString: string | null, withTime = false) {
// number 10 for input date (2024-03-15)
// number 16 for input datetime-local (2024-04-28T03:22)
if (dateString !== null) {
const date = new Date(dateString);
return date.toISOString().slice(0, withTime ? 16 : 10);
}
return "";
}

public getSafeLongtext(longtext: any): SafeHtml {
if (longtext) {
return this.sanitizer.bypassSecurityTrustHtml(longtext.replace(/\n/g, '<br>'));
}
return false;
}

public getModalOptions(): NgbModalOptions {
return {centered: true} as NgbModalOptions;
}

public openModal(component: any, data: any, callback?: (callbackParam?: any) => void, callbackParam?: any): Promise<ModalStatus> {
const modalRef = this.modalService.open(component);
for (const key in data) {
modalRef.componentInstance[key] = data[key];
}

return modalRef.componentInstance.submit.subscribe((modalStatus: ModalStatus) => {
if (modalStatus === ModalStatus.Submitted) {
modalRef.dismiss();
if (callback) {
callback(callbackParam);
}
}
});
}

public assertType<T>(value: any, type: string): asserts value is T {
if (typeof value !== type) {
throw new Error(`Expected ${type} but received ${typeof value}`);
}
}

public getResourceLink(element: any, subResource?: any): string | null {
let resourceLink: string = '/';
element = subResource !== undefined ? element[subResource] : element;
if (element === undefined) {
return null;
}
return resourceLink + '/' + this.extractId(element['id']);
}

public getLink(element: any, url: any): string {
return url + "/" + this.extractId(element['id']);
}

public dumpObject(obj: any): string {
return JSON.stringify(obj, null, 2); // Das `null, 2` formatiert das JSON mit Einrückungen
}

}

+ 38
- 0
angular/src/app/_helpers/error.interceptor.ts Ver ficheiro

@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

import { AccountService, AlertService } from '@app/_services';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(
private accountService: AccountService,
private alertService: AlertService,
) {}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
tap(evt => {
if (evt instanceof HttpResponse) {
if ((request.method === 'POST' || request.method === 'PATCH') && evt.status === 200) {
// Erfolgsmeldung für POST und PATCH
this.alertService.success('Saved', {autoClose: true});
}
}
}),
catchError(err => {
if ([401, 403].includes(err.status) && this.accountService.userValue) {
// auto logout if 401 or 403 response returned from api
this.accountService.logout();
}
console.log(err);
this.alertService.error(err.message + ' - ' + err.error);

const error = err.error?.message || err.statusText;
return throwError(() => error);
})
);
}
}

+ 12
- 0
angular/src/app/_helpers/formgroup.initializer.ts Ver ficheiro

@@ -0,0 +1,12 @@
import {FormGroup} from "@angular/forms";

export class FormGroupInitializer {
public static initFormGroup(formGroup: FormGroup, model: any) {
for (const controlName in formGroup.controls) {
if (formGroup.controls.hasOwnProperty(controlName)) {
formGroup.patchValue({[controlName]: model[controlName] ?? null});
}
}
return formGroup;
}
}

+ 2
- 0
angular/src/app/_helpers/index.ts Ver ficheiro

@@ -0,0 +1,2 @@
export * from './error.interceptor';
export * from './jwt.interceptor';

+ 27
- 0
angular/src/app/_helpers/jwt.interceptor.ts Ver ficheiro

@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';

import { environment } from '@environments/environment';
import { AccountService } from '@app/_services';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add auth header with jwt if user is logged in and request is to the api url
const user = this.accountService.userValue;
const isLoggedIn = user && user.token;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${user.token}`
}
});
}

return next.handle(request);
}
}

+ 21
- 0
angular/src/app/_helpers/loading-interceptor.service.ts Ver ficheiro

@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import {finalize, Observable} from 'rxjs';

import {LoadingService} from "@app/_services/loading.service";

@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
constructor(
private loadingService: LoadingService
) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.loadingService.setLoading(true);
return next.handle(request).pipe(
finalize(() => {
this.loadingService.setLoading(false);
})
);
}
}

+ 4
- 0
angular/src/app/_helpers/modal.states.ts Ver ficheiro

@@ -0,0 +1,4 @@
export enum ModalStatus {
Cancelled = 'Cancelled',
Submitted = 'Submitted'
}

+ 116
- 0
angular/src/app/_helpers/price-calculator.service.ts Ver ficheiro

@@ -0,0 +1,116 @@
import { Injectable } from '@angular/core';
import {FormGroup} from "@angular/forms";
import {TranslateService} from "@ngx-translate/core";
import {PriceError} from "@app/_models/priceError";

@Injectable({
providedIn: 'root'
})
export class PriceCalculatorService {


constructor(
protected translateService: TranslateService,
) {
}

public getCorrectPrice(event: FocusEvent, form: FormGroup) {
const eventElement = event.target as HTMLInputElement;
if (form.get(eventElement.id)?.value !== null && form.get(eventElement.id)?.value !== '') {
let validPrice = this.calculateValidPrice(Number(eventElement.value));
form.get(eventElement.id)?.setValue(validPrice);
}
}


public calculateValidPrice(price: number): number {
price = Math.floor(price);

if (price < 150) return 150;
if (price < 1000) return price - (price % 50);
if (price < 10000) return price - (price % 100);
if (price < 50000) return price - (price % 250);
if (price < 100000) return price - (price % 500);
return price - (price % 1000);
}

public checkPriceConstellation(
sellStartingBid: number | string,
sellPriceBin: number | string,
lastFoundMinRange: number | string | undefined,
lastFoundMaxRange: number | string | undefined,
): PriceError {

let res = {
message: '',
error: false
} as PriceError;

if ((sellStartingBid !== null && sellStartingBid !== '') && (sellPriceBin === null || sellPriceBin === '')) {
res.error = true;
res.message = this.translateService.instant('errors.sellPriceBinSellStartingBid');
return res;
}

if ((sellPriceBin !== null && sellPriceBin !== '') && (sellStartingBid === null || sellStartingBid === '')) {
res.error = true;
res.message = this.translateService.instant('errors.sellStartingBidSellPriceBin');
return res;
}

if (sellPriceBin <= sellStartingBid && (sellStartingBid !== null && sellStartingBid !== '') && (sellPriceBin !== null && sellPriceBin !== '')) {
res.error = true;
res.message = this.translateService.instant('errors.sellPriceBinSmallerSellStartingBid');
return res;
}

if (sellStartingBid >= sellPriceBin && (sellStartingBid !== null && sellStartingBid !== '') && (sellPriceBin !== null && sellPriceBin !== '')) {
res.error = true;
res.message = this.translateService.instant('errors.sellStartingBidLargerSellPriceBin');
return res;
}

if (lastFoundMinRange) {
if (sellStartingBid < lastFoundMinRange && (sellStartingBid !== null && sellStartingBid !== '')) {
res.error = true;
res.message = this.translateService.instant('errors.sellStartingBidSmallerLastFoundMinRange');
return res;
}
}

if (lastFoundMaxRange) {
if (sellPriceBin > lastFoundMaxRange && (sellPriceBin !== null && sellPriceBin !== '')) {
res.error = true;
res.message = this.translateService.instant('errors.sellPriceBinLargerLastFoundMaxRange');
return res;
}
}

if (lastFoundMinRange && lastFoundMaxRange) {
if (lastFoundMaxRange <= lastFoundMinRange) {
res.error = true;
res.message = this.translateService.instant('errors.sellStartingBidSellPriceBin');
return res;
}

if (lastFoundMinRange >= lastFoundMaxRange) {
res.error = true;
res.message = this.translateService.instant('errors.lastFoundMinRangeLargerLastFoundMaxRange');
return res;
}
if ((lastFoundMinRange !== '') && (lastFoundMaxRange === '')) {
res.error = true;
res.message = this.translateService.instant('errors.lastFoundMaxRangeLastFoundMinRange');
return res;
}

if ((lastFoundMaxRange !== null && lastFoundMaxRange !== '') && (lastFoundMinRange === '')) {
res.error = true;
res.message = this.translateService.instant('errors.lastFoundMinRangeLastFoundMaxRange');
return res;
}
}

return res;
}
}

+ 75
- 0
angular/src/app/_helpers/property.interceptor.ts Ver ficheiro

@@ -0,0 +1,75 @@
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent, HttpResponse,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class PropertyInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
map((event) => {
if (event instanceof HttpResponse && event.body) {
let modifiedBody;

if (Array.isArray(event.body)) {
// Wenn es sich um ein Array von Ressourcen handelt
modifiedBody = this.mapDataItems(event.body);
} else if (event.body['hydra:member']) {
// Wenn es sich um eine Ressourcenkollektion handelt
modifiedBody = {
...event.body,
'hydra:member': this.mapDataItems(
event.body['hydra:member']
),
};
} else {
// Wenn es sich um eine einzelne Ressource handelt
modifiedBody = this.mapDataItem(event.body);
}

return event.clone({
body: modifiedBody,
});
}

return event;
})
);
}

private mapDataItems(items: any[]): any[] {
return items.map((item) => this.mapDataItem(item));
}

private mapDataItem(item: any): any {
if (item && typeof item === 'object') {
// Wenn es ein Objekt ist, überprüfe rekursiv
const mappedItem = { ...item };

for (const key in mappedItem) {
if (mappedItem.hasOwnProperty(key) && key === '@id') {
mappedItem['id'] = mappedItem[key];
delete mappedItem[key];
} else if (Array.isArray(mappedItem[key])) {
// Wenn es sich um ein Array handelt, überprüfe rekursiv jedes Element
mappedItem[key] = this.mapDataItems(mappedItem[key]);
} else if (mappedItem[key] && typeof mappedItem[key] === 'object') {
// Wenn es ein Objekt ist, überprüfe rekursiv
mappedItem[key] = this.mapDataItem(mappedItem[key]);
}
}

return mappedItem;
}

return item;
}
}

+ 6
- 0
angular/src/app/_helpers/role.ts Ver ficheiro

@@ -0,0 +1,6 @@

export class Role {
public static ROLE_ADMIN: string = 'ROLE_ADMIN';
public static ROLE_USER: string = 'ROLE_USER';
public static ROLE_SALES: string = 'ROLE_SALES';
}

+ 25
- 0
angular/src/app/_models/alert.ts Ver ficheiro

@@ -0,0 +1,25 @@
export class Alert {
id?: string;
type?: AlertType;
message?: string;
autoClose?: boolean;
keepAfterRouteChange?: boolean;
fade?: boolean;

constructor(init?:Partial<Alert>) {
Object.assign(this, init);
}
}

export enum AlertType {
Success,
Error,
Info,
Warning
}

export class AlertOptions {
id?: string;
autoClose?: boolean;
keepAfterRouteChange?: boolean;
}

+ 2
- 0
angular/src/app/_models/index.ts Ver ficheiro

@@ -0,0 +1,2 @@
export * from './alert';
export * from './user';

+ 6
- 0
angular/src/app/_models/orderFilter.ts Ver ficheiro

@@ -0,0 +1,6 @@
export type OrderFilter = 'asc' | 'desc' | undefined;
export const OrderFilter = {
Asc: 'asc' as OrderFilter,
Desc: 'desc' as OrderFilter,
Undefined: undefined as OrderFilter
}

+ 4
- 0
angular/src/app/_models/priceError.ts Ver ficheiro

@@ -0,0 +1,4 @@
export interface PriceError {
message: string;
error: boolean;
}

+ 11
- 0
angular/src/app/_models/snipingResponse.ts Ver ficheiro

@@ -0,0 +1,11 @@
export interface SnipingResponse {
messages: string[];
excludedAccountIds: number[],
errorAccountIds: number[],
snipingAccountIds: number[],
abortSniping: boolean,
boughtItems: number;
priceCheckFoundItems: number;
priceCheckFoundPrices: number[];
priceCheckMessages: string[];
}

+ 12
- 0
angular/src/app/_models/user.ts Ver ficheiro

@@ -0,0 +1,12 @@
import {UserJsonld} from "@app/core/api/v1";

export class User {
id?: string;
email?: string;
password?: string;
firstName?: string;
lastName?: string;
roles?: string[];
token?: string;
userResource?: UserJsonld;
}

+ 101
- 0
angular/src/app/_services/account.service.ts Ver ficheiro

@@ -0,0 +1,101 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { environment } from '@environments/environment';
import { User } from '@app/_models';

@Injectable({ providedIn: 'root' })
export class AccountService {

private userSubject: BehaviorSubject<User | null>;
public user: Observable<User | null>;

constructor(
private router: Router,
private http: HttpClient
) {
this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user')!));
this.user = this.userSubject.asObservable();
}

public get userValue() {
return this.userSubject.value;
}

login(email: string, password: string) {
return this.http.post<User>(`${environment.apiUrl}/auth`, { email, password })
.pipe(map(user => {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
this.userSubject.next(user);
return user;
}));
}

logout() {
// remove user from local storage and set current user to null
localStorage.removeItem('user');
this.userSubject.next(null);
this.router.navigate(['/account/login']);
}

isLoggedIn() {
return this.userValue !== null;
}

isUserAdmin(): boolean {
if (this.userValue && this.userValue?.roles) {
return this.userValue?.roles?.includes('ROLE_ADMIN');
}
return false;
}

userHasRole(role: string): boolean {
if (this.userValue && this.userValue?.roles) {
return this.userValue?.roles?.includes(role);
}
return false;
}

register(user: User) {
return this.http.post(`${environment.apiUrl}/users/register`, user);
}

getAll() {
return this.http.get<User[]>(`${environment.apiUrl}/users`);
}

getById(id: string) {
return this.http.get<User>(`${environment.apiUrl}/users/${id}`);
}

update(id: string, params: any) {
return this.http.put(`${environment.apiUrl}/users/${id}`, params)
.pipe(map(x => {
// update stored user if the logged in user updated their own record
if (id == this.userValue?.id) {
// update local storage
const user = { ...this.userValue, ...params };
localStorage.setItem('user', JSON.stringify(user));

// publish updated user to subscribers
this.userSubject.next(user);
}
return x;
}));
}

delete(id: string) {
return this.http.delete(`${environment.apiUrl}/users/${id}`)
.pipe(map(x => {
// auto logout if the logged in user deleted their own record
if (id == this.userValue?.id) {
this.logout();
}
return x;
}));
}
}

+ 44
- 0
angular/src/app/_services/alert.service.ts Ver ficheiro

@@ -0,0 +1,44 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { filter } from 'rxjs/operators';

import { Alert, AlertType, AlertOptions } from '@app/_models';

@Injectable({ providedIn: 'root' })
export class AlertService {
private subject = new Subject<Alert>();
private defaultId = 'default-alert';

// enable subscribing to alerts observable
onAlert(id = this.defaultId): Observable<Alert> {
return this.subject.asObservable().pipe(filter(x => x && x.id === id));
}

// convenience methods
success(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Success, message }));
}

error(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Error, message }));
}

info(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Info, message }));
}

warn(message: string, options?: AlertOptions) {
this.alert(new Alert({ ...options, type: AlertType.Warning, message }));
}

// main alert method
alert(alert: Alert) {
alert.id = alert.id || this.defaultId;
this.subject.next(alert);
}

// clear alerts
clear(id = this.defaultId) {
this.subject.next(new Alert({ id }));
}
}

+ 65
- 0
angular/src/app/_services/data-import.service.ts Ver ficheiro

@@ -0,0 +1,65 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {User} from "@app/_models";
import {environment} from "@environments/environment";

@Injectable({
providedIn: 'root'
})
export class DataImportService {

constructor(
private httpClient: HttpClient
) {

}

futwizImport(
futwizCandidateUrl: string,
futbinCandidateUrl: string,
updateCandidate: boolean = false,
buyCandidate: boolean = false,
relevantCandidate: boolean = false,
futwizCandidateHtml: string | null,
bid: number | null,
bin: number | null,
optionalEaResourceId: number | null,
) {
const formData = new FormData();
formData.append('futwizCandidateUrl', futwizCandidateUrl);
formData.append('futbinCandidateUrl', futbinCandidateUrl);
formData.append('updateCandidate', updateCandidate ? '1' : '0');
formData.append('buyCandidate', buyCandidate ? '1' : '0');
formData.append('relevantCandidate', relevantCandidate ? '1' : '0');
if (futwizCandidateHtml !== null && futwizCandidateHtml !== "") {
formData.append('futwizCandidateHtml', futwizCandidateHtml);
}
if (bid !== null) {
formData.append('bid', bid.toString());
}
if (bin !== null) {
formData.append('bin', bin.toString());
}
if (optionalEaResourceId !== null) {
formData.append('optionalEaResourceId', optionalEaResourceId.toString());
}

return this.httpClient.post(
`${environment.apiUrl}/data-import/import-futwiz-player`,
formData
);
}

eaDataImport() {
return this.httpClient.get(
`${environment.apiUrl}/data-import/import-ea-data`
);
}

futwizRaritiesImport() {
return this.httpClient.get(
`${environment.apiUrl}/data-import/import-futwiz-rarities`
);
}

}

+ 35
- 0
angular/src/app/_services/ea-data-connect.service.ts Ver ficheiro

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {User} from "@app/_models";
import {environment} from "@environments/environment";

@Injectable({
providedIn: 'root'
})
export class EaDataConnectService {

constructor(
private httpClient: HttpClient
) {

}

connectAccount(accountId: string) {
const formData = new FormData();
formData.append('accountId', accountId.toString());
return this.httpClient.post(
`${environment.apiUrl}/ea-data-connect/connect-account`,
formData
);
}

deleteCachefile(accountId: string) {
const formData = new FormData();
formData.append('accountId', accountId.toString());
return this.httpClient.post(
`${environment.apiUrl}/ea-data-connect/delete-cache-file`,
formData
);
}

}

+ 2
- 0
angular/src/app/_services/index.ts Ver ficheiro

@@ -0,0 +1,2 @@
export * from './account.service';
export * from './alert.service';

+ 20
- 0
angular/src/app/_services/loading.service.ts Ver ficheiro

@@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class LoadingService {
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading: Observable<boolean> = this.loadingSubject.asObservable();

constructor() { }

setLoading(loading: boolean): void {
this.loadingSubject.next(loading);
}

getLoading(): Observable<boolean> {
return this.loading;
}
}

+ 23
- 0
angular/src/app/_services/log.service.ts Ver ficheiro

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {User} from "@app/_models";
import {environment} from "@environments/environment";

@Injectable({
providedIn: 'root'
})
export class LogService {

constructor(
private httpClient: HttpClient
) {

}

getLogs(lines: number) {
return this.httpClient.get(
`${environment.apiUrl}/logs/logs/${lines}`,
);
}

}

+ 100
- 0
angular/src/app/_services/sniping.service.ts Ver ficheiro

@@ -0,0 +1,100 @@
import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {User} from "@app/_models";
import {environment} from "@environments/environment";

@Injectable({
providedIn: 'root'
})
export class SnipingService {

constructor(
private httpClient: HttpClient
) {

}

snipingPrepare(
accountIdsJson: number[]
) {
const formData = new FormData();
formData.append('accountIdsJson', JSON.stringify(accountIdsJson));
return this.httpClient.post(
`${environment.apiUrl}/sniping/prepare`,
formData
);
}

snipingExecute(
eaResourceId: number,
snipeTimeMin: number,
snipeTimeMax: number,
buyItemsMax: number,
buyBin: number,
sellBin: number,
accountIdsJson: number[],
round: number,
gapTimeMin: number,
gapTimeMax: number,
putOnTpOnly: boolean,
priceCheckBin: number | undefined,
doPriceCheck: boolean,
) {
const formData = new FormData();
formData.append('eaResourceId', eaResourceId.toString());
formData.append('snipeTimeMin', snipeTimeMin.toString());
formData.append('snipeTimeMax', snipeTimeMax.toString());
formData.append('buyItemsMax', buyItemsMax.toString());
formData.append('buyBin', buyBin.toString());
formData.append('sellBin', sellBin.toString());
formData.append('accountIdsJson', JSON.stringify(accountIdsJson));
formData.append('round', round.toString());
formData.append('gapTimeMin', gapTimeMin.toString());
formData.append('gapTimeMax', gapTimeMax.toString());
formData.append('putOnTpOnly', putOnTpOnly ? "1" : "0");
if (priceCheckBin) {
formData.append('priceCheckBin', priceCheckBin.toString());
}
formData.append('doPriceCheck', doPriceCheck ? "1" : "0");
return this.httpClient.post(
`${environment.apiUrl}/sniping/execute`,
formData
);

}

snipingStop(
accountIdsJson: number[]
) {
const formData = new FormData();
formData.append('accountIdsJson', JSON.stringify(accountIdsJson));
return this.httpClient.post(
`${environment.apiUrl}/sniping/stop`,
formData
);
}

checkPrice(
accountId: number,
eaResourceId: number,
priceCheckBin: number,
) {
const formData = new FormData();
formData.append('accountId', accountId.toString());
formData.append('eaResourceId', eaResourceId.toString());
formData.append('priceCheckBin', priceCheckBin.toString());
return this.httpClient.post(
`${environment.apiUrl}/sniping/check-bin-price`,
formData
);
}

updateAccountSnipingCounts() {
const formData = new FormData();
return this.httpClient.post(
`${environment.apiUrl}/sniping/update-account-sniping-counts`,
formData
);
}

}

+ 16
- 0
angular/src/app/_validators/minMaxValidator.ts Ver ficheiro

@@ -0,0 +1,16 @@
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function minMaxValidator(minKey: string, maxKey: string, errorKey: string): ValidatorFn {
return (formGroup: AbstractControl): ValidationErrors | null => {
const minControl = formGroup.get(minKey);
const maxControl = formGroup.get(maxKey);
if (minControl && maxControl && minControl.value > maxControl.value) {
return {
[errorKey]: {
message: `${minKey} (${minControl.value}) must be less than or equal to ${maxKey} (${maxControl.value})`
}
};
}
return null;
};
}

+ 22
- 0
angular/src/app/_views/account/account-routing.module.ts Ver ficheiro

@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { LayoutComponent } from './layout.component';
import { LoginComponent } from './login.component';
import { RegisterComponent } from './register.component';

const routes: Routes = [
{
path: '', component: LayoutComponent,
children: [
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent }
]
}
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AccountRoutingModule { }

+ 22
- 0
angular/src/app/_views/account/account.module.ts Ver ficheiro

@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

import { AccountRoutingModule } from './account-routing.module';
import { LayoutComponent } from './layout.component';
import { LoginComponent } from './login.component';
import { RegisterComponent } from './register.component';

@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
AccountRoutingModule
],
declarations: [
LayoutComponent,
LoginComponent,
RegisterComponent
]
})
export class AccountModule { }

+ 3
- 0
angular/src/app/_views/account/layout.component.html Ver ficheiro

@@ -0,0 +1,3 @@
<div class="container col-md-6 offset-md-3 mt-5">
<router-outlet></router-outlet>
</div>

+ 17
- 0
angular/src/app/_views/account/layout.component.ts Ver ficheiro

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';

import { AccountService } from '@app/_services';

@Component({ templateUrl: 'layout.component.html' })
export class LayoutComponent {
constructor(
private router: Router,
private accountService: AccountService
) {
// redirect to home if already logged in
if (this.accountService.userValue) {
this.router.navigate(['/']);
}
}
}

+ 28
- 0
angular/src/app/_views/account/login.component.html Ver ficheiro

@@ -0,0 +1,28 @@
<div class="card login-form">
<h1 class="card-header">Login</h1>
<div class="card-body">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="mb-3">
<label class="form-label">Username</label>
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['username'].errors }" />
<div *ngIf="submitted && f['username'].errors" class="invalid-feedback">
<div *ngIf="f['username'].hasError('required')">Username is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['password'].errors }" />
<div *ngIf="submitted && f['password'].errors" class="invalid-feedback">
<div *ngIf="f['password'].hasError('required')">Password is required</div>
</div>
</div>
<div>
<button [disabled]="loading" class="btn btn-primary">
<span *ngIf="loading" class="spinner-border spinner-border-sm me-1"></span>
Login
</button>
<!-- <a routerLink="../register" class="btn btn-link">Register</a>-->
</div>
</form>
</div>
</div>

+ 58
- 0
angular/src/app/_views/account/login.component.ts Ver ficheiro

@@ -0,0 +1,58 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';

import { AccountService, AlertService } from '@app/_services';

@Component({ templateUrl: 'login.component.html' })
export class LoginComponent implements OnInit {
form!: FormGroup;
loading = false;
submitted = false;

constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService,
private alertService: AlertService
) { }

ngOnInit() {
this.form = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
}

// convenience getter for easy access to form fields
get f() { return this.form.controls; }

onSubmit() {
this.submitted = true;

// reset alerts on submit
this.alertService.clear();

// stop here if form is invalid
if (this.form.invalid) {
return;
}

this.loading = true;
this.accountService.login(this.f['username'].value, this.f['password'].value)
.pipe(first())
.subscribe({
next: () => {
// get return url from query parameters or default to home page
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
this.router.navigateByUrl(returnUrl);
},
error: error => {
this.alertService.error(error);
this.loading = false;
}
});
}
}

+ 43
- 0
angular/src/app/_views/account/register.component.html Ver ficheiro

@@ -0,0 +1,43 @@
<div class="card">
<h4 class="card-header">Register</h4>
<div class="card-body">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="mb-3">
<label class="form-label">First Name</label>
<input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['firstName'].errors }" />
<div *ngIf="submitted && f['firstName'].errors" class="invalid-feedback">
<div *ngIf="f['firstName'].hasError('required')">First Name is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Last Name</label>
<input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['lastName'].errors }" />
<div *ngIf="submitted && f['lastName'].errors" class="invalid-feedback">
<div *ngIf="f['lastName'].hasError('required')">Last Name is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Username</label>
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['username'].errors }" />
<div *ngIf="submitted && f['username'].errors" class="invalid-feedback">
<div *ngIf="f['username'].hasError('required')">Username is required</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['password'].errors }" />
<div *ngIf="submitted && f['password'].errors" class="invalid-feedback">
<div *ngIf="f['password'].hasError('required')">Password is required</div>
<div *ngIf="f['password'].hasError('minlength')">Password must be at least 6 characters</div>
</div>
</div>
<div>
<button [disabled]="loading" class="btn btn-primary">
<span *ngIf="loading" class="spinner-border spinner-border-sm me-1"></span>
Register
</button>
<a routerLink="../login" class="btn btn-link">Cancel</a>
</div>
</form>
</div>
</div>

+ 59
- 0
angular/src/app/_views/account/register.component.ts Ver ficheiro

@@ -0,0 +1,59 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';

import { AccountService, AlertService } from '@app/_services';

@Component({ templateUrl: 'register.component.html' })
export class RegisterComponent implements OnInit {
form!: FormGroup;
loading = false;
submitted = false;

constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService,
private alertService: AlertService
) { }

ngOnInit() {
this.form = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
username: ['', Validators.required],
password: ['', [Validators.required, Validators.minLength(6)]]
});
}

// convenience getter for easy access to form fields
get f() { return this.form.controls; }

onSubmit() {
this.submitted = true;

// reset alerts on submit
this.alertService.clear();

// stop here if form is invalid
if (this.form.invalid) {
return;
}

this.loading = true;
this.accountService.register(this.form.value)
.pipe(first())
.subscribe({
next: () => {
this.alertService.success('Registration successful', { keepAfterRouteChange: true });
this.router.navigate(['../login'], { relativeTo: this.route });
},
error: error => {
this.alertService.error(error);
this.loading = false;
}
});
}
}

+ 13
- 0
angular/src/app/_views/dashboard/dashboard.component.html Ver ficheiro

@@ -0,0 +1,13 @@
<div class="spt-container">
<div class="d-flex justify-content-between align-items-start">
<h2>{{ 'dashboard.view' | translate }}</h2>
</div>
<mat-tab-group>
<mat-tab label="{{ 'dashboard.overview' | translate }}">
OVERVIEW
</mat-tab>
<mat-tab label="{{ 'dashboard.overview' | translate }}">
OVERVIEW 2
</mat-tab>
</mat-tab-group>
</div>

+ 5
- 0
angular/src/app/_views/dashboard/dashboard.component.scss Ver ficheiro

@@ -0,0 +1,5 @@
pre {
white-space: pre-wrap; /* Erlaubt Zeilenumbrüche */
word-wrap: break-word; /* Bricht lange Wörter um */
overflow-wrap: break-word; /* Sicherstellt, dass Wörter, die zu lang sind, auch umgebrochen werden */
}

Alguns ficheiros não foram mostrados porque foram alterados demasiados ficheiros neste diff

Carregando…
Cancelar
Guardar