diff --git a/apps/devmx/src/app/app.config.ts b/apps/devmx/src/app/app.config.ts
index e0ac102d..0dac00a3 100644
--- a/apps/devmx/src/app/app.config.ts
+++ b/apps/devmx/src/app/app.config.ts
@@ -31,7 +31,9 @@ import {
withViewTransitions,
} from '@angular/router';
import {
+ HttpBackend,
HttpClient,
+ HttpXhrBackend,
provideHttpClient,
withInterceptors,
} from '@angular/common/http';
@@ -68,6 +70,10 @@ export const appConfig: ApplicationConfig = {
},
provideLayout(appSections),
provideHttpClient(withInterceptors([authInterceptor, loaderInterceptor])),
+ {
+ provide: HttpBackend,
+ useExisting: HttpXhrBackend,
+ },
provideHttpClientImpl(HttpClient),
...provideAccount(),
provideLayoutToolbar(AuthenticationFacade),
diff --git a/packages/account/ui-shared/src/lib/components/index.ts b/packages/account/ui-shared/src/lib/components/index.ts
index 7a426e6f..48a6c5f3 100644
--- a/packages/account/ui-shared/src/lib/components/index.ts
+++ b/packages/account/ui-shared/src/lib/components/index.ts
@@ -1,5 +1,6 @@
-export * from './user-complete/user-complete.component';
export * from './user-card-skills/user-card-skills.component';
+export * from './user-complete/user-complete.component';
+export * from './search-user/search-user.component';
export * from './code-field/code-field.component';
export * from './user-roles/user-roles.component';
export * from './user-card/user-card.component';
diff --git a/packages/account/ui-shared/src/lib/components/search-user/search-user.component.html b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.html
new file mode 100644
index 00000000..77ab0bf3
--- /dev/null
+++ b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.html
@@ -0,0 +1,18 @@
+
+ {{ label() }}
+
+ {{ hint() }}
+
+ @if (userFacade.response$ | async; as response) {
+
+ @for (option of response.data; track option.id) {
+ {{ option.displayName }}
+ }
+
+ }
+
+
diff --git a/packages/account/ui-shared/src/lib/components/search-user/search-user.component.scss b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.scss
new file mode 100644
index 00000000..bac68ec2
--- /dev/null
+++ b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.scss
@@ -0,0 +1,5 @@
+:host {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
diff --git a/packages/account/ui-shared/src/lib/components/search-user/search-user.component.ts b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.ts
new file mode 100644
index 00000000..6286da74
--- /dev/null
+++ b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.ts
@@ -0,0 +1,65 @@
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
+import { FormControl, ReactiveFormsModule } from '@angular/forms';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { MatInputModule } from '@angular/material/input';
+import { UserFacade } from '@devmx/account-data-access';
+import { User } from '@devmx/shared-api-interfaces';
+import { AsyncPipe } from '@angular/common';
+import { debounceTime, filter, startWith } from 'rxjs';
+import {
+ input,
+ inject,
+ output,
+ Component,
+ ChangeDetectionStrategy,
+} from '@angular/core';
+
+@Component({
+ selector: 'devmx-search-user',
+ templateUrl: './search-user.component.html',
+ styleUrl: './search-user.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ ReactiveFormsModule,
+ MatAutocompleteModule,
+ MatFormFieldModule,
+ MatInputModule,
+ AsyncPipe,
+ ],
+})
+export class SearchUserComponent {
+ userFacade = inject(UserFacade);
+
+ selected = output();
+
+ label = input('Usuário');
+
+ hint = input('');
+
+ control = new FormControl('');
+
+ constructor() {
+ this.control.valueChanges
+ .pipe(
+ startWith(' '),
+ filter((value) => typeof value === 'string'),
+ filter((value) => value.length > 0),
+ takeUntilDestroyed(),
+ debounceTime(400)
+ )
+ .subscribe((displayName) => {
+ this.userFacade.setFilter({ displayName });
+ this.userFacade.load();
+ });
+ }
+
+ displayFn(user: User) {
+ return user && user.displayName ? user.displayName : '';
+ }
+
+ onOptionSelected(user: User) {
+ this.selected.emit(user);
+ this.control.setValue('');
+ }
+}
diff --git a/packages/album/feature-admin/src/lib/containers/my-albums/my-albums.container.ts b/packages/album/feature-admin/src/lib/containers/my-albums/my-albums.container.ts
index 94050939..0b8aa71f 100644
--- a/packages/album/feature-admin/src/lib/containers/my-albums/my-albums.container.ts
+++ b/packages/album/feature-admin/src/lib/containers/my-albums/my-albums.container.ts
@@ -8,12 +8,13 @@ import { DialogFacade } from '@devmx/shared-ui-global/dialog';
import { MatTooltipModule } from '@angular/material/tooltip';
import { IconComponent } from '@devmx/shared-ui-global/icon';
import { AlbumCardComponent } from '@devmx/album-ui-shared';
+import { SheetFacade } from '@devmx/shared-ui-global/sheet';
import { MatButtonModule } from '@angular/material/button';
import { combineLatest, filter, map, take } from 'rxjs';
import { AlbumFacade } from '@devmx/album-data-access';
import { Album } from '@devmx/shared-api-interfaces';
+import { CreateAlbumSheet } from '../../sheets';
import { AsyncPipe } from '@angular/common';
-import { AlbumForm } from '../../forms';
@Component({
selector: 'devmx-admin-my-albums',
@@ -38,6 +39,8 @@ export class MyAlbumsContainer {
dialogFacade = inject(DialogFacade);
+ sheetFacade = inject(SheetFacade);
+
authFacade = inject(AuthenticationFacade);
albumFacade = inject(AlbumFacade);
@@ -74,15 +77,17 @@ export class MyAlbumsContainer {
}
createAlbum() {
- const form = new AlbumForm();
-
- form.patchValue({ title: new Date().toLocaleDateString() });
-
- const request$ = this.albumFacade.create(form.getRawValue());
-
- request$.pipe(take(1)).subscribe((album) => {
- this.router.navigate([album.id], { relativeTo: this.route });
- });
+ this.sheetFacade
+ .open(CreateAlbumSheet)
+ .pipe(take(1))
+ .subscribe((value) => {
+ if (value) {
+ const request$ = this.albumFacade.create(value);
+ request$.pipe(take(1)).subscribe((album) => {
+ this.router.navigate([album.id], { relativeTo: this.route });
+ });
+ }
+ });
}
deleteAlbum({ id, title }: Album) {
diff --git a/packages/album/feature-admin/src/lib/forms/album.ts b/packages/album/feature-admin/src/lib/forms/album.ts
index 1f64bfb1..c5df62dc 100644
--- a/packages/album/feature-admin/src/lib/forms/album.ts
+++ b/packages/album/feature-admin/src/lib/forms/album.ts
@@ -19,7 +19,7 @@ export class AlbumForm extends FormGroup> {
id: new FormControl('', {
nonNullable: true,
}),
- title: new FormControl(new Date().toLocaleDateString(), {
+ title: new FormControl('', {
nonNullable: true,
validators: [Validators.required],
}),
diff --git a/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.html b/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.html
new file mode 100644
index 00000000..b0fa762d
--- /dev/null
+++ b/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.html
@@ -0,0 +1,41 @@
+
diff --git a/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.scss b/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.scss
new file mode 100644
index 00000000..94b6153e
--- /dev/null
+++ b/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.scss
@@ -0,0 +1,19 @@
+:host {
+ display: flex;
+ padding: 1.5em 1em 0.5em;
+ flex-direction: column;
+
+ form {
+ flex: 1;
+ gap: 1em;
+ display: flex;
+ flex-direction: column;
+ }
+
+ footer {
+ margin-top: 1em;
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: space-between;
+ }
+}
diff --git a/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.ts b/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.ts
new file mode 100644
index 00000000..45b8679e
--- /dev/null
+++ b/packages/album/feature-admin/src/lib/sheets/create-album/create-album.sheet.ts
@@ -0,0 +1,57 @@
+import { OnInit, Component, ChangeDetectionStrategy } from '@angular/core';
+import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
+import { EditableAlbum, UserRef } from '@devmx/shared-api-interfaces';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { SheetComponent } from '@devmx/shared-ui-global/sheet';
+import { IconComponent } from '@devmx/shared-ui-global/icon';
+import { MatButtonModule } from '@angular/material/button';
+import { MatInputModule } from '@angular/material/input';
+import { MatListModule } from '@angular/material/list';
+import { ReactiveFormsModule } from '@angular/forms';
+import { AlbumForm } from '../../forms';
+import {
+ provideSelectUser,
+ SearchUserComponent,
+} from '@devmx/account-ui-shared';
+
+@Component({
+ selector: 'devmx-create-album',
+ templateUrl: './create-album.sheet.html',
+ styleUrl: './create-album.sheet.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [provideSelectUser()],
+ imports: [
+ SearchUserComponent,
+ MatBottomSheetModule,
+ ReactiveFormsModule,
+ MatFormFieldModule,
+ MatButtonModule,
+ MatInputModule,
+ MatListModule,
+ IconComponent,
+ ],
+})
+export class CreateAlbumSheet
+ extends SheetComponent
+ implements OnInit
+{
+ form = new AlbumForm();
+
+ ngOnInit() {
+ if (this.data) {
+ this.form.patch(this.data);
+ }
+ }
+
+ onContributorSelected(user: UserRef) {
+ this.form.contributors.add(user);
+ }
+
+ onSubmit() {
+ if (this.form.valid) {
+ return this.close(this.form.getRawValue());
+ }
+
+ return this.form.markAllAsTouched();
+ }
+}
diff --git a/packages/album/feature-admin/src/lib/sheets/index.ts b/packages/album/feature-admin/src/lib/sheets/index.ts
index a94565ba..d1f7bf4c 100644
--- a/packages/album/feature-admin/src/lib/sheets/index.ts
+++ b/packages/album/feature-admin/src/lib/sheets/index.ts
@@ -1 +1,2 @@
export * from './album-details/album-details.sheet';
+export * from './create-album/create-album.sheet';