Compare commits

...

9 Commits

Author SHA1 Message Date
Developer 02
acfd9b4fb8 feat(base-table.component): Attribute rowStyle und rowClass hinzugefügt 2024-11-11 16:42:22 +01:00
Developer 02
0f9ac0e637 refactor(user-representation.component): userReps.fetchData-Methode zum Refresh-Dienst hinzugefügt. 2024-11-11 15:56:30 +01:00
Developer 02
0a8a5208a0 refactor(environment.ts): if-Anweisung hinzugefügt, um einen leeren String zurückzugeben, wenn volidFrom oder validTo null ist 2024-11-11 15:36:30 +01:00
Developer 02
b1011c3ea2 refactor(style.css): Rückgängig gemacht.
- Spezifisches CSS zu rep-create-form.component.scss hinzugefügt
2024-11-11 15:29:55 +01:00
Developer 02
11913ea667 refactor(SerializerSettings.DateTimeZoneHandling): Aktualisiert, um über Appsettings konfiguriert zu werden.
- Standardmäßig auf „Lokal“ eingestellt.
2024-11-11 15:13:25 +01:00
Developer 02
ff6ebf300c refactor(user-representation.component): Aktualisiert, um als unbegrenzt zu speichern. 2024-11-11 14:09:10 +01:00
Developer 02
7e05123fbf feat(rep-create-form.component): Umschaltmöglichkeit für unbegrenzte Zeit hinzugefügt 2024-11-11 12:13:58 +01:00
Developer 02
712932a0e2 feat(rep-create-form.component): Eigenschaft zur Deaktivierung der Datumsbereichseingabe hinzugefügt 2024-11-11 10:05:09 +01:00
Developer 02
a946ba871d feat(UserRepService): Die Methode CreateAsync wurde überschrieben.
- XOR-Logik hinzugefügt, um zu prüfen, ob ValidFrom und ValidTo Null sind
 - Logik hinzugefügt, um zu kontrollieren, dass ValidFrom kleiner als ValidTo ist
2024-11-11 09:36:48 +01:00
14 changed files with 65 additions and 23 deletions

View File

@@ -2,7 +2,7 @@
<div class="dd-row input-row">
<mat-form-field class="w40p">
<mat-label>Geben Sie einen Datumsbereich ein</mat-label>
<mat-date-range-input [formGroup]="range" [rangePicker]="picker">
<mat-date-range-input [formGroup]="range" [rangePicker]="picker" [disabled]="termless">
<input matStartDate formControlName="start" placeholder="Start date">
<input matEndDate formControlName="end" placeholder="End date">
</mat-date-range-input>
@@ -17,6 +17,9 @@
<mat-error>Ungültiges Enddatum</mat-error>
}
</mat-form-field>
<mat-slide-toggle [(ngModel)]="termless" class="w20p">
Unbefristet
</mat-slide-toggle>
<button mat-fab extended (click)="create()" class="w20p">
<mat-icon>playlist_add</mat-icon>
Erstellen

View File

@@ -0,0 +1,8 @@
.dd-row mat-form-field {
margin: 1rem 0rem 0rem 0rem;
}
.dd-row mat-slide-toggle {
margin: 1rem 0rem 1rem 0rem;
padding: 0;
}

View File

@@ -13,6 +13,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { provideMomentDateAdapter } from '@angular/material-moment-adapter';
import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import 'moment/locale/de';
@Component({
@@ -23,7 +24,7 @@ import 'moment/locale/de';
{ provide: MAT_DATE_LOCALE, useValue: 'de-DE' },
provideMomentDateAdapter()
],
imports: [MatFormFieldModule, MatDatepickerModule, FormsModule, ReactiveFormsModule, JsonPipe, MatButtonModule, MatIconModule, MatInputModule],
imports: [MatFormFieldModule, MatDatepickerModule, FormsModule, ReactiveFormsModule, JsonPipe, MatButtonModule, MatIconModule, MatInputModule, MatSlideToggleModule],
templateUrl: './rep-create-form.component.html',
styleUrl: './rep-create-form.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -41,6 +42,7 @@ export class RepCreateFormComponent implements OnInit {
private readonly _locale = signal(inject<unknown>(MAT_DATE_LOCALE));
private readonly _adapter = inject<DateAdapter<unknown, unknown>>(DateAdapter);
private readonly _intl = inject(MatDatepickerIntl);
termless: boolean = false;
constructor() {
const dialogData: { userRep: UserRep, afterCreation: (any: any) => any } = inject(MAT_DIALOG_DATA)
@@ -68,19 +70,21 @@ export class RepCreateFormComponent implements OnInit {
create() {
const validFrom = this.range.value.start;
const validTo = this.range.value.end;
if (!validFrom || !validTo) {
if ((!validFrom || !validTo) && !this.termless) {
Swal.fire({
icon: "error",
title: "Oops...",
text: "Bitte geben Sie einen gültigen Datumsbereich ein!",
text: "Bitte geben Sie einen gültigen Datumsbereich ein oder wählen Sie unbefristet!",
});
return;
}
this.userRep.validFrom = validFrom!;
this.userRep.validTo = validTo!;
if (!this.termless) {
this.userRep.validFrom = validFrom!;
this.userRep.validTo = validTo!;
}
this.userRepService.create(this.userRep).subscribe({
next: (res) => {

View File

@@ -13,5 +13,7 @@
[infoPanel]="infoPanel"
[titlePanel]="titlePanel"
[theme]="theme"
[rowStyle] = "rowStyle"
[rowClass] = "rowClass"
(selectedRows)="onSelectedRows($event)">
</gui-grid>

View File

@@ -1,6 +1,6 @@
import { Component, Inject, Input, OnDestroy, OnInit, ViewChild, input } from '@angular/core';
import { ApiService } from '../../../services/api/user-management.api.service';
import { GuiGridModule, GuiColumn, GuiColumnMenu, GuiSorting, GuiRowDetail, GuiPaging, GuiPagingDisplay, GuiSearching, GuiCellEdit, GuiInfoPanel, GuiTitlePanel, GuiRowSelection, GuiSelectedRow, GuiGridComponent, GuiGridApi, GuiTheme } from '@generic-ui/ngx-grid';
import { GuiGridModule, GuiColumn, GuiColumnMenu, GuiSorting, GuiRowDetail, GuiPaging, GuiPagingDisplay, GuiSearching, GuiCellEdit, GuiInfoPanel, GuiTitlePanel, GuiRowSelection, GuiSelectedRow, GuiGridComponent, GuiGridApi, GuiTheme, GuiRowStyle, GuiRowClass } from '@generic-ui/ngx-grid';
import { Subscription } from 'rxjs/internal/Subscription';
import { ColorModeService, Theme } from '../../../services/button/color-mode.service';
import { CommonModule } from '@angular/common';
@@ -8,7 +8,7 @@ import { FormsModule } from '@angular/forms';
@Component({
standalone: true,
imports: [CommonModule, FormsModule, GuiGridModule ],
imports: [CommonModule, FormsModule, GuiGridModule],
selector: 'app-base-table',
templateUrl: './base-table.component.html',
styleUrl: './base-table.component.css'
@@ -111,6 +111,10 @@ export class BaseTableComponent<TModel, TApiService extends ApiService<TModel>>
@Input() columns: Array<GuiColumn> = [];
@Input() rowStyle: GuiRowStyle = {}
@Input() rowClass: GuiRowClass = {}
selected: boolean = false;
safelyUnselectAll() {
this.selected = true

View File

@@ -46,9 +46,10 @@ export class UserRepresentationComponent extends BasePageComponent implements Af
this.refreshService.removeAll();
this.refreshService.add(() => {
this.users.fetchData();
this.groups.fetchData();
this.repUsers.fetchData();
this.repGroups.fetchData();
this.groups.fetchData();
this.userReps.fetchData();
})
this.transferService.add(() => {
this.repUsers.safelyUnselectAll();
@@ -103,9 +104,7 @@ export class UserRepresentationComponent extends BasePageComponent implements Af
var newUserRep: UserRep = {
userId: this.slUserId,
groupId: this.slGroupId,
repUserId: this.slRepUserId,
validFrom: new Date(),
validTo: new Date(new Date().setDate(new Date().getDate() + 7))
repUserId: this.slRepUserId
}
this.openCreateSheet(newUserRep, res => {

View File

@@ -165,6 +165,14 @@ export const env = {
header: "Repr. Benutzer",
field: (ur: any) => ur.repUser?.username
},
{
header: "Gültig von",
field: (ur: any) => ur.validFrom ? new Date(ur.validFrom).toLocaleDateString('de-DE') : ""
},
{
header: "Gültig bis",
field: (ur: any) => ur.validTo ? new Date(ur.validTo).toLocaleDateString('de-DE') : ""
}
]
},
config_url: "/assets/config.json",

View File

@@ -167,11 +167,11 @@ export const env = {
},
{
header: "Gültig von",
field: (ur: any) => new Date(ur.validFrom).toLocaleDateString('de-DE')
field: (ur: any) => ur.validFrom ? new Date(ur.validFrom).toLocaleDateString('de-DE') : ""
},
{
header: "Gültig bis",
field: (ur: any) => new Date(ur.validTo).toLocaleDateString('de-DE')
field: (ur: any) => ur.validTo ? new Date(ur.validTo).toLocaleDateString('de-DE') : ""
}
]
},
@@ -196,4 +196,4 @@ export const env = {
{ value: "en-US", name: "en-US" }
]
}
};
};

View File

@@ -67,6 +67,7 @@ code {
.input-row {
justify-content: space-evenly;
align-items: center;
}
.button-row {
@@ -102,4 +103,4 @@ code {
.w#{$i * 5}p {
width: #{$i * 5 + "%"} !important;
}
}
}

View File

@@ -34,7 +34,7 @@ try {
.AndIf(c => !config.GetValue<bool>("UseEncryptor")));
}).AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
options.SerializerSettings.DateTimeZoneHandling = config.GetValue<DateTimeZoneHandling>("DateTimeZoneHandling");
});
if (builder.Configuration.GetValue<bool>("UseSwagger"))

View File

@@ -72,6 +72,7 @@
"IV": "gMuetIjlPvJnSzu+i7I3xg=="
},
"AllowedGroupName": "UM_ADMINS",
"DateTimeZoneHandling": "Local",
// Delete below in production
"UseEncryptor": true,
"UseSwagger": true

View File

@@ -8,5 +8,7 @@
public static readonly string UserAlreadyExists = "UserAlreadyExists";
public static readonly string UserNotFound = "UserNotFound";
public static readonly string UnauthorizedUser = "UnauthorizedUser";
public static readonly string DateRangeNotXNOR = "DateRangeNotXNOR";
public static readonly string InvalidDateRange = "InvalidDateRange";
}
}
}

View File

@@ -5,13 +5,16 @@ using DigitalData.UserManager.Application.DTOs.UserRep;
using DigitalData.UserManager.Domain.Entities;
using DigitalData.UserManager.Infrastructure.Contracts;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
namespace DigitalData.UserManager.Application.Services
{
public class UserRepService : BaseService<IUserRepRepository, UserRepCreateDto, UserRepReadDto, UserRepUpdateDto, UserRep>, IUserRepService
{
private readonly IStringLocalizer<Resource> _localizer;
public UserRepService(IUserRepRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper) : base(repository, mapper)
{
_localizer = localizer;
}
public async Task<DataResult<IEnumerable<UserRepReadDto>>> ReadAllAsync(bool withUser = false, bool withRepGroup = false, bool withGroup = false, bool withRepUser = false, int? userId = null, int? groupId = null)
@@ -20,5 +23,14 @@ namespace DigitalData.UserManager.Application.Services
var urReadDTOs = _mapper.Map<IEnumerable<UserRepReadDto>>(urs);
return Result.Success(urReadDTOs);
}
public override async Task<DataResult<int>> CreateAsync(UserRepCreateDto createDto)
// XOR control
=> (createDto.ValidFrom is null && createDto.ValidTo is not null) || (createDto.ValidFrom is not null && createDto.ValidTo is null)
? Result.Fail<int>().Notice(LogLevel.None, Flag.DataIntegrityIssue, _localizer[Key.DateRangeNotXNOR])
//date range control
: (createDto.ValidFrom > createDto.ValidTo)
? Result.Fail<int>().Notice(LogLevel.None, Flag.DataIntegrityIssue, _localizer[Key.InvalidDateRange])
: await base.CreateAsync(createDto);
}
}

View File

@@ -18,13 +18,11 @@ namespace DigitalData.UserManager.Domain.Entities
[Column("REPR_USER")]
public int? RepUserId { get; set; }
[Required]
[Column("VALID_FROM")]
public required DateTime? ValidFrom { get; set; }
public DateTime? ValidFrom { get; set; }
[Required]
[Column("VALID_TO")]
public required DateTime? ValidTo { get; set; }
public DateTime? ValidTo { get; set; }
[ForeignKey("UserId")]
public virtual User? User { get; set; }