gestion du chargement et toast des projets user

This commit is contained in:
styve Lioumba
2025-11-17 19:04:17 +01:00
parent e2aa88c434
commit c8d0f96b31
4 changed files with 119 additions and 92 deletions

View File

@@ -1,82 +1,90 @@
<div class="w-full text-center"> @if (loading().action === ActionType.UPDATE && loading().isLoading && onSubmitted()) {
<h3 class="font-ubuntu w-full text-start font-bold text-xl uppercase dark:text-white mb-4"> <app-loading message="Mise à jour de l'image de couverture..." />
Aperçu du projet } @else {
</h3> <ng-container *ngTemplateOutlet="content" />
}
<div <ng-template #content>
class="w-40 h-40 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400" <div class="w-full text-center">
> <h3 class="font-ubuntu w-full text-start font-bold text-xl uppercase dark:text-white mb-4">
@if (imagePreviewUrl != null && project != undefined) { Aperçu du projet
<img </h3>
alt="nouveau-projet"
class="object-cover object-center h-full w-full" <div
[src]="imagePreviewUrl" class="w-40 h-40 rounded-full inline-flex items-center justify-center bg-gray-200 text-gray-400"
loading="lazy" >
/> @if (imagePreviewUrl != null && project != undefined) {
} @else if (project != undefined) {
@if (project.fichier) {
<img
alt="{{ project!.nom }}"
class="object-cover object-center h-full w-full"
src="{{ environment.baseUrl }}/api/files/projets/{{ project.id }}/{{ project.fichier }}"
loading="lazy"
/>
} @else {
<img <img
alt="nouveau-projet" alt="nouveau-projet"
class="object-cover object-center h-full w-full" class="object-cover object-center h-full w-full"
src="https://api.dicebear.com/9.x/shapes/svg?seed={{ project.nom }}" [src]="imagePreviewUrl"
loading="lazy"
/>
} @else if (project != undefined) {
@if (project.fichier) {
<img
alt="{{ project!.nom }}"
class="object-cover object-center h-full w-full"
src="{{ environment.baseUrl }}/api/files/projets/{{ project.id }}/{{ project.fichier }}"
loading="lazy"
/>
} @else {
<img
alt="nouveau-projet"
class="object-cover object-center h-full w-full"
src="https://api.dicebear.com/9.x/shapes/svg?seed={{ project.nom }}"
loading="lazy"
/>
}
}
@if (project == undefined) {
<img
alt="nouveau-projet"
class="object-cover object-center h-full w-full"
src="https://api.dicebear.com/9.x/shapes/svg?seed=nouveau-projet"
loading="lazy" loading="lazy"
/> />
} }
} </div>
@if (project == undefined) {
<img
alt="nouveau-projet"
class="object-cover object-center h-full w-full"
src="https://api.dicebear.com/9.x/shapes/svg?seed=nouveau-projet"
loading="lazy"
/>
}
</div> </div>
</div>
<label <label
for="uploadFile1" for="uploadFile1"
class="flex bg-gray-800 hover:bg-gray-700 text-white text-base px-5 py-3 outline-none rounded w-max cursor-pointer mx-auto font-[sans-serif]" class="flex bg-gray-800 hover:bg-gray-700 text-white text-base px-5 py-3 outline-none rounded w-max cursor-pointer mx-auto font-[sans-serif]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 mr-2 fill-white inline"
viewBox="0 0 32 32"
> >
<path <svg
d="M23.75 11.044a7.99 7.99 0 0 0-15.5-.009A8 8 0 0 0 9 27h3a1 1 0 0 0 0-2H9a6 6 0 0 1-.035-12 1.038 1.038 0 0 0 1.1-.854 5.991 5.991 0 0 1 11.862 0A1.08 1.08 0 0 0 23 13a6 6 0 0 1 0 12h-3a1 1 0 0 0 0 2h3a8 8 0 0 0 .75-15.956z" xmlns="http://www.w3.org/2000/svg"
data-original="#000000" class="w-4 h-4 mr-2 fill-white inline"
viewBox="0 0 32 32"
>
<path
d="M23.75 11.044a7.99 7.99 0 0 0-15.5-.009A8 8 0 0 0 9 27h3a1 1 0 0 0 0-2H9a6 6 0 0 1-.035-12 1.038 1.038 0 0 0 1.1-.854 5.991 5.991 0 0 1 11.862 0A1.08 1.08 0 0 0 23 13a6 6 0 0 1 0 12h-3a1 1 0 0 0 0 2h3a8 8 0 0 0 .75-15.956z"
data-original="#000000"
/>
<path
d="M20.293 19.707a1 1 0 0 0 1.414-1.414l-5-5a1 1 0 0 0-1.414 0l-5 5a1 1 0 0 0 1.414 1.414L15 16.414V29a1 1 0 0 0 2 0V16.414z"
data-original="#000000"
/>
</svg>
<small class="text-xs">Selectionner une image</small>
<input
type="file"
id="uploadFile1"
class="hidden"
accept="image/*"
(change)="onPictureChange($event)"
/> />
<path </label>
d="M20.293 19.707a1 1 0 0 0 1.414-1.414l-5-5a1 1 0 0 0-1.414 0l-5 5a1 1 0 0 0 1.414 1.414L15 16.414V29a1 1 0 0 0 2 0V16.414z"
data-original="#000000"
/>
</svg>
<small class="text-xs">Selectionner une image</small>
<input
type="file"
id="uploadFile1"
class="hidden"
accept="image/*"
(change)="onPictureChange($event)"
/>
</label>
@if (file != null || imagePreviewUrl != null) { @if (file != null || imagePreviewUrl != null) {
<button <button
type="button" type="button"
[ngClass]="{ 'bg-purple-600': file != null || imagePreviewUrl != null }" [ngClass]="{ 'bg-purple-600': file != null || imagePreviewUrl != null }"
class="!mt-2 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block" class="!mt-2 px-6 py-2 w-full bg-[#333] hover:bg-[#444] text-sm text-white mx-auto block"
(click)="onSubmit()" (click)="onSubmit()"
> >
Mettre à jour ma photo de projet Mettre à jour ma photo de projet
</button> </button>
} }
</ng-template>

View File

@@ -1,21 +1,23 @@
import { Component, effect, inject, Input, output } from '@angular/core'; import { Component, effect, inject, Input, output, signal } from '@angular/core';
import { AuthService } from '@app/core/services/authentication/auth.service'; import { NgClass, NgTemplateOutlet } from '@angular/common';
import { NgClass } from '@angular/common';
import { environment } from '@env/environment'; import { environment } from '@env/environment';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { ProjectViewModel } from '@app/ui/projects/project.presenter.model'; import { ProjectViewModel } from '@app/ui/projects/project.presenter.model';
import { ProjectFacade } from '@app/ui/projects/project.facade'; import { ProjectFacade } from '@app/ui/projects/project.facade';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
import { LoadingComponent } from '@app/shared/components/loading/loading.component';
@Component({ @Component({
selector: 'app-project-picture-form', selector: 'app-project-picture-form',
standalone: true, standalone: true,
imports: [NgClass], imports: [NgClass, LoadingComponent, NgTemplateOutlet],
templateUrl: './project-picture-form.component.html', templateUrl: './project-picture-form.component.html',
styleUrl: './project-picture-form.component.scss', styleUrl: './project-picture-form.component.scss',
}) })
export class ProjectPictureFormComponent { export class ProjectPictureFormComponent {
@Input({ required: true }) project: ProjectViewModel | undefined = undefined; @Input({ required: true }) project: ProjectViewModel | undefined = undefined;
protected readonly environment = environment;
protected readonly ActionType = ActionType;
onFormSubmitted = output<any>(); onFormSubmitted = output<any>();
private readonly toastrService = inject(ToastrService); private readonly toastrService = inject(ToastrService);
@@ -24,28 +26,19 @@ export class ProjectPictureFormComponent {
protected readonly loading = this.projectFacade.loading; protected readonly loading = this.projectFacade.loading;
protected readonly error = this.projectFacade.error; protected readonly error = this.projectFacade.error;
private readonly authService = inject(AuthService);
file: File | null = null; // Variable to store file file: File | null = null; // Variable to store file
imagePreviewUrl: string | null = null; // URL for image preview imagePreviewUrl: string | null = null; // URL for image preview
protected onSubmitted = signal<boolean>(false);
constructor() { constructor() {
let message = '';
effect(() => { effect(() => {
if (!this.loading().isLoading) { if (!this.loading().isLoading && this.onSubmitted()) {
switch (this.loading().action) { switch (this.loading().action) {
case ActionType.UPDATE: case ActionType.UPDATE:
this.authService.updateUser(); message = `L'aperçu du projet ${this.project!.nom} ont bien été modifier !`;
this.customToast(ActionType.UPDATE, message);
this.toastrService.success( this.onSubmitted.set(false);
`L'aperçu du projet ${this.project!.nom} ont bien été modifier !`,
`Mise à jour`,
{
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
}
);
this.onFormSubmitted.emit(''); this.onFormSubmitted.emit('');
break; break;
} }
@@ -59,6 +52,7 @@ export class ProjectPictureFormComponent {
formData.append('fichier', this.file); // "fichier" est le nom du champ dans PocketBase formData.append('fichier', this.file); // "fichier" est le nom du champ dans PocketBase
this.projectFacade.update(this.project?.id!, formData); this.projectFacade.update(this.project?.id!, formData);
this.onSubmitted.set(true);
} }
} }
@@ -78,5 +72,28 @@ export class ProjectPictureFormComponent {
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
protected readonly environment = environment; private customToast(action: ActionType, message: string): void {
if (this.error().hasError) {
this.toastrService.error(
`Une erreur s'est produite, veuillez réessayer ulterieurement`,
`Erreur`,
{
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
}
);
return;
}
this.toastrService.success(
`${message}`,
`${action === ActionType.UPDATE ? 'Mise à jour' : ''}`,
{
closeButton: true,
progressAnimation: 'decreasing',
progressBar: true,
}
);
}
} }

View File

@@ -40,6 +40,7 @@ export class UserAvatarFormComponent {
message = `Votre photo de profile a bien été modifier !`; message = `Votre photo de profile a bien été modifier !`;
this.customToast(ActionType.UPDATE, message); this.customToast(ActionType.UPDATE, message);
this.onSubmitted.set(false); this.onSubmitted.set(false);
this.onFormSubmitted.emit('');
break; break;
} }
} }
@@ -52,8 +53,6 @@ export class UserAvatarFormComponent {
formData.append('avatar', this.file); // "avatar" est le nom du champ dans PocketBase formData.append('avatar', this.file); // "avatar" est le nom du champ dans PocketBase
this.facade.update(this.user?.id!, formData as Partial<User>); this.facade.update(this.user?.id!, formData as Partial<User>);
this.onFormSubmitted.emit('');
this.onSubmitted.set(true); this.onSubmitted.set(true);
} }
} }

View File

@@ -11,12 +11,14 @@ import { CreateProjectDto } from '@app/domain/projects/dto/create-project.dto';
import { ErrorResponse } from '@app/domain/error-response.util'; import { ErrorResponse } from '@app/domain/error-response.util';
import { ActionType } from '@app/domain/action-type.util'; import { ActionType } from '@app/domain/action-type.util';
import { LoaderAction } from '@app/domain/loader-action.util'; import { LoaderAction } from '@app/domain/loader-action.util';
import { AuthService } from '@app/core/services/authentication/auth.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ProjectFacade { export class ProjectFacade {
private readonly projectRepo = inject(PROJECT_REPOSITORY_TOKEN); private readonly projectRepo = inject(PROJECT_REPOSITORY_TOKEN);
private readonly authService = inject(AuthService);
private readonly createUseCase = new CreateProjectUseCase(this.projectRepo); private readonly createUseCase = new CreateProjectUseCase(this.projectRepo);
private readonly listUseCase = new ListProjectUseCase(this.projectRepo); private readonly listUseCase = new ListProjectUseCase(this.projectRepo);
@@ -84,6 +86,7 @@ export class ProjectFacade {
this.UpdateUseCase.execute(userId, data).subscribe({ this.UpdateUseCase.execute(userId, data).subscribe({
next: (project: Project) => { next: (project: Project) => {
this.project.set(this.projectPresenter.toViewModel(project)); this.project.set(this.projectPresenter.toViewModel(project));
this.authService.updateUser();
this.handleError(ActionType.UPDATE, false, null, false); this.handleError(ActionType.UPDATE, false, null, false);
}, },
error: (err) => { error: (err) => {