Browse Source

Task/add filter functionality to access dialog component

pull/5848/head
Germán Martín 1 week ago
parent
commit
c2ce4609ec
  1. 80
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  2. 47
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html
  3. 6
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.scss

80
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts

@ -3,11 +3,15 @@ import { UpdateAccessDto } from '@ghostfolio/api/app/access/update-access.dto';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces';
import { AccountWithPlatform } from '@ghostfolio/common/types';
import { GfPortfolioFilterFormComponent } from '@ghostfolio/ui/portfolio-filter-form';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA,
Inject, Inject,
OnDestroy, OnDestroy,
OnInit OnInit
@ -28,7 +32,10 @@ import {
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { IonIcon } from '@ionic/angular/standalone';
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
import { addIcons } from 'ionicons';
import { chevronUpOutline, optionsOutline } from 'ionicons/icons';
import { EMPTY, Subject, catchError, takeUntil } from 'rxjs'; import { EMPTY, Subject, catchError, takeUntil } from 'rxjs';
import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces'; import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces';
@ -38,6 +45,8 @@ import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces';
host: { class: 'h-100' }, host: { class: 'h-100' },
imports: [ imports: [
FormsModule, FormsModule,
GfPortfolioFilterFormComponent,
IonIcon,
MatButtonModule, MatButtonModule,
MatDialogModule, MatDialogModule,
MatFormFieldModule, MatFormFieldModule,
@ -45,6 +54,7 @@ import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces';
MatSelectModule, MatSelectModule,
ReactiveFormsModule ReactiveFormsModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-create-or-update-access-dialog', selector: 'gf-create-or-update-access-dialog',
styleUrls: ['./create-or-update-access-dialog.scss'], styleUrls: ['./create-or-update-access-dialog.scss'],
templateUrl: 'create-or-update-access-dialog.html' templateUrl: 'create-or-update-access-dialog.html'
@ -54,6 +64,14 @@ export class GfCreateOrUpdateAccessDialogComponent
{ {
public accessForm: FormGroup; public accessForm: FormGroup;
public mode: 'create' | 'update'; public mode: 'create' | 'update';
public showFilterPanel = false;
public filterPanelExpanded = false;
// Datos para el filtro
public accounts: AccountWithPlatform[] = [];
public assetClasses: Filter[] = [];
public holdings: PortfolioPosition[] = [];
public tags: Filter[] = [];
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -66,6 +84,8 @@ export class GfCreateOrUpdateAccessDialogComponent
private notificationService: NotificationService private notificationService: NotificationService
) { ) {
this.mode = this.data.access?.id ? 'update' : 'create'; this.mode = this.data.access?.id ? 'update' : 'create';
addIcons({ chevronUpOutline, optionsOutline });
} }
public ngOnInit() { public ngOnInit() {
@ -73,6 +93,7 @@ export class GfCreateOrUpdateAccessDialogComponent
this.accessForm = this.formBuilder.group({ this.accessForm = this.formBuilder.group({
alias: [this.data.access.alias], alias: [this.data.access.alias],
filter: [null],
granteeUserId: [ granteeUserId: [
this.data.access.grantee, this.data.access.grantee,
isPublic ? null : Validators.required isPublic ? null : Validators.required
@ -87,19 +108,30 @@ export class GfCreateOrUpdateAccessDialogComponent
this.accessForm.get('type').valueChanges.subscribe((accessType) => { this.accessForm.get('type').valueChanges.subscribe((accessType) => {
const granteeUserIdControl = this.accessForm.get('granteeUserId'); const granteeUserIdControl = this.accessForm.get('granteeUserId');
const permissionsControl = this.accessForm.get('permissions'); const permissionsControl = this.accessForm.get('permissions');
const filterControl = this.accessForm.get('filter');
if (accessType === 'PRIVATE') { if (accessType === 'PRIVATE') {
granteeUserIdControl.setValidators(Validators.required); granteeUserIdControl.setValidators(Validators.required);
this.showFilterPanel = false;
filterControl.setValue(null);
} else { } else {
granteeUserIdControl.clearValidators(); granteeUserIdControl.clearValidators();
granteeUserIdControl.setValue(null); granteeUserIdControl.setValue(null);
permissionsControl.setValue(this.data.access.permissions[0]); permissionsControl.setValue(this.data.access.permissions[0]);
this.showFilterPanel = true;
this.loadFilterData();
} }
granteeUserIdControl.updateValueAndValidity(); granteeUserIdControl.updateValueAndValidity();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
// Si ya es público al iniciar, mostrar el panel y cargar datos
if (isPublic) {
this.showFilterPanel = true;
this.loadFilterData();
}
} }
public onCancel() { public onCancel() {
@ -119,6 +151,54 @@ export class GfCreateOrUpdateAccessDialogComponent
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private loadFilterData() {
// Cargar cuentas
this.dataService
.fetchAccounts()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.accounts = response.accounts;
this.changeDetectorRef.markForCheck();
});
// Cargar holdings y asset classes
this.dataService
.fetchPortfolioDetails({})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
if (response.holdings) {
this.holdings = Object.values(response.holdings);
// Extraer asset classes únicas
const assetClassesSet = new Set<string>();
Object.values(response.holdings).forEach((holding) => {
if (holding.assetClass) {
assetClassesSet.add(holding.assetClass);
}
});
this.assetClasses = Array.from(assetClassesSet).map((ac) => ({
id: ac,
label: ac,
type: 'ASSET_CLASS' as const
}));
}
this.changeDetectorRef.markForCheck();
});
// Cargar tags
this.dataService
.fetchTags()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.tags = response.map((tag) => ({
id: tag.id,
label: tag.name,
type: 'TAG' as const
}));
this.changeDetectorRef.markForCheck();
});
}
private async createAccess() { private async createAccess() {
const access: CreateAccessDto = { const access: CreateAccessDto = {
alias: this.accessForm.get('alias').value, alias: this.accessForm.get('alias').value,

47
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html

@ -4,13 +4,48 @@
(keyup.enter)="accessForm.valid && onSubmit()" (keyup.enter)="accessForm.valid && onSubmit()"
(ngSubmit)="onSubmit()" (ngSubmit)="onSubmit()"
> >
<h1 mat-dialog-title> <div mat-dialog-title>
@if (mode === 'create') { <div class="d-flex justify-content-between align-items-center">
<span i18n>Grant access</span> <h1 class="m-0">
} @else { @if (mode === 'create') {
<span i18n>Edit access</span> <span i18n>Grant access</span>
} @else {
<span i18n>Edit access</span>
}
</h1>
@if (showFilterPanel) {
<button
class="no-min-width"
mat-button
type="button"
(click)="filterPanelExpanded = !filterPanelExpanded"
>
<ion-icon
class="rotate-90"
size="large"
[name]="
filterPanelExpanded ? 'chevron-up-outline' : 'options-outline'
"
/>
</button>
}
</div>
@if (showFilterPanel && filterPanelExpanded) {
<div class="mt-3">
<p class="text-muted mb-3" i18n style="font-size: 0.875rem">
Configure which accounts, holdings, tags, and asset classes will be
visible in this public access. Leave empty to show all data.
</p>
<gf-portfolio-filter-form
formControlName="filter"
[accounts]="accounts"
[assetClasses]="assetClasses"
[holdings]="holdings"
[tags]="tags"
/>
</div>
} }
</h1> </div>
<div class="flex-grow-1 py-3" mat-dialog-content> <div class="flex-grow-1 py-3" mat-dialog-content>
<div> <div>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">

6
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.scss

@ -4,4 +4,10 @@
.mat-mdc-dialog-content { .mat-mdc-dialog-content {
max-height: unset; max-height: unset;
} }
ion-icon {
&.rotate-90 {
transform: rotate(90deg);
}
}
} }

Loading…
Cancel
Save