feat: first commit

This commit is contained in:
jeko 2025-10-20 23:25:58 +02:00
commit ac41514ba3
37 changed files with 7377 additions and 0 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

21
.prettierrc Normal file
View File

@ -0,0 +1,21 @@
{
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "ignore",
"plugins": ["@angular-eslint/template-parser"],
"overrides": [
{
"files": ["*.component.html"],
"options": {
"parser": "angular"
}
}
]
}

4
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

72
angular.json Normal file
View File

@ -0,0 +1,72 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "pnpm"
},
"newProjectRoot": "projects",
"projects": {
"planning-FE": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
},
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
}
],
"stylePreprocessorOptions": {
"includePaths": ["src/assets/style"]
},
"styles": ["src/assets/styles/_loader.scss"]
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "planning-FE:build:production"
},
"development": {
"buildTarget": "planning-FE:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
}
}
}
}
}

61
biome.json Normal file
View File

@ -0,0 +1,61 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"attributePosition": "auto",
"indentStyle": "tab",
"indentWidth": 4,
"lineWidth": 120,
"lineEnding": "lf"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noConsole": "error",
"noExplicitAny": "error",
"noDebugger": "error",
"noDoubleEquals": "error"
},
"style": {
"useImportType": "off"
}
}
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"semicolons": "always",
"trailingCommas": "all"
}
},
"json": {
"formatter": {
"indentStyle": "tab",
"indentWidth": 4,
"lineWidth": 200
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "planning-fe",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "ng serve --proxy-config proxy.conf.json"
},
"dependencies": {
"@angular/common": "^20.1.0",
"@angular/compiler": "^20.1.0",
"@angular/core": "^20.1.0",
"@angular/forms": "^20.1.0",
"@angular/platform-browser": "^20.1.0",
"@angular/router": "^20.1.0",
"@ng-bootstrap/ng-bootstrap": "^19.0.1",
"bootstrap": "^5.3.8",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular-eslint/builder": "^20.3.0",
"@angular-eslint/template-parser": "^20.3.0",
"@angular/animations": "^20.3.3",
"@angular/build": "^20.1.5",
"@angular/cli": "^20.1.5",
"@angular/compiler-cli": "^20.1.0",
"@biomejs/biome": "2.1.4",
"ngx-toastr": "^19.1.0",
"prettier": "^3.6.2",
"typescript": "~5.8.2"
}
}

6277
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

8
proxy.conf.json Normal file
View File

@ -0,0 +1,8 @@
{
"/api": {
"target": "http://localhost:8080",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,112 @@
import { Directive, OnDestroy } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { takeWhile } from "rxjs";
type CustomFormControl<TBody> = FormControl & {
_validators?: RegExp[];
_dependsOn?: {
key: keyof TBody;
value: boolean;
}[];
};
export interface IFormFieldData<TBody> {
name: keyof TBody;
value: string | number;
validators?: RegExp[];
dependsOn?: {
key: keyof TBody;
value: boolean;
}[];
}
@Directive()
export abstract class IForm<TBody> implements OnDestroy {
private _isAlive = true;
protected formBody!: TBody;
protected form!: FormGroup;
protected formValid!: boolean;
protected setForm(...fields: IFormFieldData<TBody>[]) {
const controls: Record<keyof TBody, CustomFormControl<TBody>> = {} as Record<
keyof TBody,
CustomFormControl<TBody>
>;
for (const field of fields) {
controls[field.name] = new FormControl(field.value);
controls[field.name]._validators = field.validators || [];
controls[field.name]._dependsOn = field.dependsOn || [];
}
this.form = new FormGroup(controls, { updateOn: "change" });
Object.keys(this.form.controls)?.forEach((key) => {
this.form.controls[key].valueChanges.pipe(takeWhile(() => this._isAlive)).subscribe(() => {
this.formValid = this._isControlValidAndForm();
});
});
this.formValid = false;
}
public asd(): boolean {
return this._isControlValidAndForm();
}
private _isControlValidAndForm(): boolean {
let allFieldsValid = true;
Object.keys(this.form.controls).forEach((key) => {
const dependencies = (this.form.controls[key] as CustomFormControl<TBody>)._dependsOn;
let skipCheck = false;
dependencies?.forEach((matcher) => {
const formControlValue = (this.form.controls[matcher.key as string] as CustomFormControl<TBody>).value; //[1,2,3]
if (matcher.value === true) {
if (!formControlValue) {
skipCheck = true;
}
} else {
if (formControlValue) {
skipCheck = true;
}
}
});
if (!skipCheck) {
const fieldValue = this.form.get(key)?.value;
(this.form.controls[key] as CustomFormControl<TBody>)._validators?.forEach((regexp) => {
if (!regexp.test(fieldValue)) {
allFieldsValid = false;
}
});
}
});
return this.form.valid && allFieldsValid;
}
protected prepareFormBody(): Partial<TBody> {
const rawValue = this.form.getRawValue();
this.formBody = {} as TBody;
for (const key in rawValue) {
if (Object.hasOwn(rawValue, key)) {
const value = rawValue[key];
if (value !== null && value !== undefined && value !== "") {
this.formBody[key as keyof TBody] = value;
}
}
}
return this.formBody;
}
public ngOnDestroy(): void {
this._isAlive = false;
}
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,27 @@
@forward 'sass:map';
$colors: (
dark: #313131,
light: #fefefe,
error: #b63737,
green: #2ecc71,
green-dark: #2E7D32
);
$font: (
default: 18px,
large: 24px,
ultra: 64px
);
$spacing: (
xxs: 4px,
tiny: 8px,
normal: 16px,
);
$border: (
size: 1px,
radius: 8px,
circle: 50%
);

View File

@ -0,0 +1,61 @@
@use 'sass:map';
@use "bootstrap/scss/bootstrap";
@use 'ngx-toastr/toastr';
@use 'core' as *;
@use './components/root.scss';
@use './components/modal.scss';
@use './components/layout';
@font-face {
font-family: 'regular';
src: url('./Ubuntu-Light.ttf');
}
@font-face {
font-family: 'bold';
src: url('./Ubuntu-Bold.ttf');
}
*,
*::before,
*::after {
box-sizing: border-box;
transition: all 0.2s ease-in-out;
}
html,
body {
padding: 0;
margin: 0;
font-family: 'regular', Arial, Helvetica, sans-serif;
background-color: map.get($colors, dark);
color: map.get($colors, light);
font-size: map.get($font, default);
width: 100svw;
height: 100svh;
}
.title,
.bold {
font-family: 'bold', Arial, Helvetica, sans-serif;
}
.button {
display: flex;
place-content: center;
flex-wrap: wrap;
padding: map.get($spacing, tiny) map.get($spacing, normal);
border: map.get($border, size) solid map.get($colors, light);
border-radius: map.get($border, radius);
&.__primary {
cursor: pointer;
border-color: map.get($colors, green);
&:hover {
background-color: map.get($colors, green-dark);
}
}
}

View File

@ -0,0 +1,13 @@
@use '../core' as *;
@use 'sass:map';
.row>* {
display: flex;
flex-direction: column;
gap: 4px;
.title {
font-size: 20px;
}
}

View File

@ -0,0 +1,44 @@
@use '../core' as *;
@use 'sass:map';
$-header-height: 64px;
$-header-footer: 64px;
$-content-height: calc(100% - $-header-height - $-header-footer);
.custom-modal-container {
.custom-modal-header {
height: $-header-height;
display: flex;
align-items: center;
padding: map.get($spacing, normal);
border-bottom: map.get($border, size) solid map.get($colors, dark);
.title {
text-transform: uppercase;
font-size: map.get($font, large);
flex: 1;
}
.close {
display: flex;
place-content: center;
flex-wrap: wrap;
padding: map.get($spacing, xxs);
border: map.get($border, size) solid map.get($colors, dark);
border-radius: map.get($border, circle);
width: 24px;
height: 24px;
cursor: pointer;
}
}
.custom-modal-content {
height: $-content-height;
padding: map.get($spacing, normal);
}
.custom-modal-footer {
height: $-header-footer;
}
}

View File

@ -0,0 +1,26 @@
@use '../core' as *;
@use 'sass:map';
$-header-height: 64px;
$page-container-height: calc(100% - $-header-height);
.header {
display: flex;
padding: map.get($spacing, normal);
height: $-header-height;
align-items: center;
justify-content: space-between;
}
.page-content {
height: $page-container-height;
}
.error {
display: flex;
place-content: center;
flex-wrap: wrap;
font-size: map.get($font, ultra);
color: map.get($colors, error);
height: 100%;
}

View File

@ -0,0 +1,12 @@
@if (!loadingInit) {
@if (initError) {
<div class="error bold">Il server non è online</div>
} @else {
<div class="header">
<div class="button __primary" (click)="test()">Aggiungi entry</div>
<div class="button __primary">Impostazioni</div>
</div>
<div class="page-content">CIAO</div>
}
}

View File

@ -0,0 +1,48 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from "@angular/core";
import { AddEntryModalComponent } from "@modal/add-entry/add-entry.component";
import { api } from "@static/api.class";
import { modal } from "@static/modal.class";
import { finalize } from "rxjs";
@Component({
selector: "app-root",
templateUrl: "./root.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RootComponent implements OnInit {
protected loadingInit = true;
protected isLoading = true;
protected initError = false;
private _cdr = inject(ChangeDetectorRef);
constructor() {
this._cdr.detach();
}
private _update(): void {
this._cdr.detectChanges();
}
public ngOnInit(): void {
api.checkConnection()
.pipe(
finalize(() => {
this.loadingInit = false;
this._update();
}),
)
.subscribe({
next: () => {
this.isLoading = false;
},
error: () => {
this.initError = true;
},
});
}
protected test(): void {
modal.open(AddEntryModalComponent);
}
}

7
src/config/global.ts Normal file
View File

@ -0,0 +1,7 @@
import { i18n, i18nPath } from "@static/i18n";
export const GLOBAL_CONFIG = {
defaultDateTimeLocale: "it-IT",
};
export type translateKey = i18nPath<typeof i18n.allLanguages>;

14
src/config/i18n/it_it.ts Normal file
View File

@ -0,0 +1,14 @@
export const IT_IT = {
toast: {
error: {
create_game_already_exists: "A savefile with this name already exists.",
},
},
dropdown: {
type: {
income: "Entrata",
outcome: "Uscita",
investment: "Investimento",
},
},
};

View File

@ -0,0 +1,8 @@
export const VALIDATORS = {
REQUIRED: /^.{1,}$/,
MIN_1_CHARS: /^.{1,}$/,
MAX_50_CHARS: /^.{0,50}$/,
ONLY_POSITIVE_NUMBERS: /^(?:(?!0+(?:\.0+)?$)(?:\d+|\d*\.\d+))$/,
VALID_HEX_COLOR: /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/,
DATE: /^(?:(?:31-(?:0?[13578]|1[02]))|(?:29|30-(?:0?[1,3-9]|1[0-2])))-(?:19|20)\d\d$|^29-0?2-(?:(?:19|20)(?:[02468][048]|[13579][26]))$|^(?:0?[1-9]|1\d|2[0-8])-(?:0?[1-9]|1[0-2])-(?:19|20)\d\d$/,
};

13
src/index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PlanningFE</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

28
src/main.ts Normal file
View File

@ -0,0 +1,28 @@
import { provideHttpClient } from "@angular/common/http";
import {
provideAppInitializer,
provideBrowserGlobalErrorListeners,
provideZonelessChangeDetection,
} from "@angular/core";
import { bootstrapApplication } from "@angular/platform-browser";
import { RootComponent } from "@components/root/root.component";
import { api } from "@static/api.class";
import { i18n } from "@static/i18n";
import { modal } from "@static/modal.class";
import { toast } from "@static/toast.static";
import { provideToastr } from "ngx-toastr";
bootstrapApplication(RootComponent, {
providers: [
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideHttpClient(),
provideToastr(),
provideAppInitializer(() => {
api.init();
modal.init();
toast.init();
i18n.init();
}),
],
});

View File

@ -0,0 +1,43 @@
<div class="custom-modal-container">
<div class="custom-modal-header">
<div class="title">Aggiunta entry</div>
<div class="close" (click)="close()">X</div>
</div>
<div class="custom-modal-content" [formGroup]="form">
<div class="row">
<div class="col-6">
<div class="title">Nome</div>
<input type="text" [formControlName]="'name'" />
</div>
<div class="col-6">
<div class="title">Tipo</div>
<select [formControlName]="'type'">
@for (entry of typeDropdownDatasource; track entry.id) {
<option [ngValue]="entry.id">
{{ translate(entry.value) }}
</option>
}
</select>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="title">Quantità</div>
<input type="number" [formControlName]="'amount'" />
</div>
<div class="col-6">
<div class="title">Colore</div>
<input type="text" [formControlName]="'color'" />
</div>
</div>
<div class="row">
<div class="col-12">
<!-- TODO: checkbox + changeEvent change isRecurrent to @if @else other fields -->
</div>
</div>
</div>
<div class="custom-modal-footer">
<div class="button" [class.__primary]="formValid" [class.__disabled]="!formValid">Aggiungi</div>
</div>
</div>

View File

@ -0,0 +1,116 @@
import { IForm } from "@abstract/form.abstract";
import { Component, inject } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { translateKey } from "@config/global";
import { VALIDATORS } from "@const/validators.const";
import { dataSourceList } from "@model/datasource.model";
import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
import { i18n } from "@static/i18n";
type AddEntryType = {
name: string;
type: string;
amount: number;
color: string;
recurrent: boolean;
fixedDate: string;
recurrentMonths: number[];
startDate: string;
endDate: string;
};
@Component({
selector: "new-entry-modal",
templateUrl: "./add-entry.component.html",
imports: [ReactiveFormsModule],
})
export class AddEntryModalComponent extends IForm<Partial<AddEntryType>> {
protected activeModal = inject(NgbActiveModal);
protected typeDropdownDatasource = dataSourceList.type;
constructor() {
super();
this.setForm(
{
name: "name",
value: "",
validators: [VALIDATORS.MIN_1_CHARS, VALIDATORS.MAX_50_CHARS],
},
{
name: "type",
value: "",
validators: [VALIDATORS.REQUIRED],
},
{
name: "amount",
value: "",
validators: [VALIDATORS.ONLY_POSITIVE_NUMBERS],
},
{
name: "color",
value: "",
validators: [VALIDATORS.VALID_HEX_COLOR],
},
{
name: "recurrent",
value: "",
},
{
name: "fixedDate",
value: "12-12-2020",
validators: [VALIDATORS.DATE],
dependsOn: [
{
key: "recurrent",
value: false,
},
],
},
{
name: "recurrentMonths",
value: "",
validators: [VALIDATORS.REQUIRED],
dependsOn: [
{
key: "recurrent",
value: true,
},
],
},
{
name: "startDate",
value: "",
validators: [VALIDATORS.DATE],
dependsOn: [
{
key: "recurrent",
value: true,
},
],
},
{
name: "endDate",
value: "",
validators: [VALIDATORS.DATE],
dependsOn: [
{
key: "recurrent",
value: true,
},
],
},
);
}
protected close<TResult>(res?: TResult): void {
this.asd();
this.activeModal.close(res ?? null);
}
protected translate(key: translateKey): string {
return i18n.now(key);
}
}

View File

@ -0,0 +1,16 @@
import { translateKey } from "@config/global";
export type TypeDataSource = {
id: string;
value: translateKey;
};
export const dataSourceList: {
type: TypeDataSource[];
} = {
type: [
{ id: "income", value: "dropdown.type.income" },
{ id: "outcome", value: "dropdown.type.outcome" },
{ id: "investment", value: "dropdown.type.investment" },
],
};

16
src/static/api.class.ts Normal file
View File

@ -0,0 +1,16 @@
/** biome-ignore-all lint/complexity/noStaticOnlyClass: static */
import { HttpClient } from "@angular/common/http";
import { inject } from "@angular/core";
export class api {
public static http: HttpClient;
public static init(): void {
api.http = inject(HttpClient);
}
public static checkConnection() {
return api.http.get(`/api/actuator/health`);
}
}

47
src/static/i18n.ts Normal file
View File

@ -0,0 +1,47 @@
/** biome-ignore-all lint/complexity/noStaticOnlyClass: static */
/** biome-ignore-all lint/suspicious/noExplicitAny: needed */
import { IT_IT } from "@config/i18n/it_it";
import { logger } from "./logger.static";
export type i18nPath<T> = T extends object
? {
[K in keyof T]: K extends string
? `${K}${"" | (i18nPath<T[K]> extends never ? "" : `.${i18nPath<T[K]>}`)}`
: never;
}[keyof T]
: never;
export class i18n {
public static it = IT_IT;
public static currentLang: "it";
public static allLanguages = IT_IT;
public static init(): void {
i18n.currentLang = "it";
}
public static now(key: i18nPath<typeof i18n.allLanguages>): string {
const langObject = i18n[i18n.currentLang];
const parts = key.split(".");
let result: { [key: string]: any } | string = langObject;
for (const part of parts) {
if (result && typeof result === "object" && part in result) {
result = result[part];
} else {
logger.error(`Translation key not found for current language: ${key}`);
return key;
}
}
if (typeof result === "string") {
return result;
}
logger.error(`Translation key did not resolve to a string: ${key}`);
return key;
}
}

View File

@ -0,0 +1,12 @@
/** biome-ignore-all lint/suspicious/noConsole: static logger */
/** biome-ignore-all lint/complexity/noStaticOnlyClass: static logger */
export class logger {
public static log<T>(message: T): void {
console.log(message);
}
public static error(message: string): void {
console.error(message);
}
}

46
src/static/modal.class.ts Normal file
View File

@ -0,0 +1,46 @@
/** biome-ignore-all lint/complexity/noStaticOnlyClass: static */
/** biome-ignore-all lint/suspicious/noExplicitAny: static */
import { inject, Type } from "@angular/core";
import { NgbModal, NgbModalOptions } from "@ng-bootstrap/ng-bootstrap";
type ModalOption = NgbModalOptions & {
hasCloseIcon?: boolean;
modalTitle?: string;
};
const defaultOptions: Partial<NgbModalOptions> = {
size: "lg",
centered: true,
modalDialogClass: "custom-modal-container",
};
export class modal {
public static ngbModal: NgbModal;
public static init(): void {
modal.ngbModal = inject(NgbModal);
}
public static open<TComponent, TResult>(
component: Type<TComponent>,
data?: any,
options?: ModalOption,
): Promise<TResult> {
const effectiveOptions: NgbModalOptions = {
...defaultOptions,
...(options || {}),
};
const modalRef = modal.ngbModal.open(component, effectiveOptions);
modalRef.componentInstance.modalTitle = options?.modalTitle;
modalRef.componentInstance.hasCloseIcon = options?.hasCloseIcon;
Object.keys(data || {}).forEach((item) => {
modalRef.componentInstance[item] = data[item];
});
return modalRef.result;
}
}

View File

@ -0,0 +1,21 @@
/** biome-ignore-all lint/complexity/noStaticOnlyClass: static */
import { inject } from "@angular/core";
import { ToastrService } from "ngx-toastr";
import { i18n, i18nPath } from "./i18n";
export class toast {
public static toastService: ToastrService;
public static init(): void {
toast.toastService = inject(ToastrService);
}
public static ok(message: i18nPath<typeof i18n.allLanguages.toast>): void {
toast.toastService.success(i18n.now(`toast.${message}`));
}
public static error(message: i18nPath<typeof i18n.allLanguages.toast>): void {
toast.toastService.error(i18n.now(`toast.${message}`));
}
}

8
tsconfig.app.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": ["src/**/*.ts"]
}

43
tsconfig.json Normal file
View File

@ -0,0 +1,43 @@
{
"compileOnSave": false,
"compilerOptions": {
"strict": true,
"baseUrl": "./",
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve",
"paths": {
"@pipes/*": ["src/pipes/*"],
"@components/*": ["src/components/*"],
"@services/*": ["src/services/*"],
"@config/*": ["src/config/*"],
"@abstract/*": ["src/abstract/*"],
"@ui/*": ["src/ui/*"],
"@const/*": ["src/const/*"],
"@modal/*": ["src/modal/*"],
"@model/*": ["src/model/*"],
"@directives/*": ["src/directives/*"],
"@static/*": ["src/static/*"]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"typeCheckHostBindings": true,
"strictTemplates": true
},
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}