Skip to content

Commit ae2805d

Browse files
authored
feat: autocomplete cities (#6)
1 parent 636a66a commit ae2805d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2049
-568
lines changed

diagrams/account/dep-graph.svg

+383
Loading

package.json

+9-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
"@angular-devkit/build-angular": "~18.2.0",
1515
"@angular-devkit/core": "~18.2.0",
1616
"@angular-devkit/schematics": "~18.2.0",
17-
"@angular-eslint/eslint-plugin": "^18.0.1",
18-
"@angular-eslint/eslint-plugin-template": "^18.0.1",
19-
"@angular-eslint/template-parser": "^18.0.1",
17+
"@angular-eslint/eslint-plugin": "^18.3.0",
18+
"@angular-eslint/eslint-plugin-template": "^18.3.0",
19+
"@angular-eslint/template-parser": "^18.3.0",
2020
"@angular/cli": "~18.2.0",
2121
"@angular/compiler-cli": "~18.2.0",
2222
"@angular/language-service": "~18.2.0",
@@ -99,5 +99,11 @@
9999
"path": "node_modules/cz-git",
100100
"czConfig": "./config/cz.config.js"
101101
}
102+
},
103+
"pnpm": {
104+
"overrides": {
105+
"path-to-regexp@>=0.2.0 <1.9.0": ">=1.9.0",
106+
"rollup@>=4.0.0 <4.22.4": ">=4.22.4"
107+
}
102108
}
103109
}

packages/account/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
![Account Dep Graph](../../diagrams/account/dep-graph.svg)

packages/account/data-source/src/lib/services/accounts.impl.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export class AccountsServiceImpl implements AccountsService {
2222
.find(where)
2323
.skip(skip)
2424
.limit(size)
25+
.populate('city')
2526
.exec();
2627

2728
const data = accounts.map((item) => item.toJSON());
@@ -32,31 +33,29 @@ export class AccountsServiceImpl implements AccountsService {
3233
}
3334

3435
async findOne(id: string) {
35-
const account = await this.accountModel.findById(id).exec();
36-
37-
if (!account) {
38-
throw `Conta não encontrada`;
39-
}
40-
41-
return account.toJSON();
36+
const account = await this.accountModel
37+
.findById(id)
38+
.populate('city')
39+
.exec();
40+
return account ? account.toJSON() : null;
4241
}
4342

4443
async findOneBy(filter: FindFilterDto<Account>) {
45-
const account = await this.accountModel.findOne(filter).exec();
44+
const account = await this.accountModel
45+
.findOne(filter)
46+
.populate('city')
47+
.exec();
4648

4749
return account ? account.toJSON() : null;
4850
}
4951

5052
async update(id: string, data: Partial<UpdateAccountDto>) {
5153
const account = await this.accountModel
5254
.findOneAndUpdate({ _id: id }, data)
55+
.populate('city')
5356
.exec();
5457

55-
if (!account) {
56-
throw `Problema ao alterar conta`;
57-
}
58-
59-
return account.toJSON();
58+
return account ? account.toJSON() : null;
6059
}
6160

6261
async remove(id: string) {

packages/account/feature-shell/src/lib/account-feature-shell.routes.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { AccountFeatureShellComponent } from './account-feature-shell.component';
2+
import { provideAutocompleteCitiesService } from '@devmx/location-ui-forms';
23
import { providePresentation } from '@devmx/presentation-data-access';
4+
import { provideLocations } from '@devmx/location-data-access';
35
import { roleGroupsGuard, roleGroupGuard } from './guards';
46
import { Route } from '@angular/router';
57
import {
@@ -25,6 +27,8 @@ export const accountFeatureShellRoutes: Route[] = [
2527
providers: [
2628
...provideAccount(),
2729
...providePresentation(),
30+
...provideLocations(),
31+
provideAutocompleteCitiesService(),
2832
provideAccountNavFacade([
2933
{
3034
path: ['/account', 'settings'],

packages/account/feature-shell/src/lib/components/editable-account/editable-account.component.html

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@
8181
</mat-form-field>
8282
</section>
8383

84+
<devmx-autocomplete-cities formControlName="city" />
85+
8486
<footer>
8587
<div class="flex gap-1">
8688
<button type="button" mat-stroked-button (click)="toggleForm(firstName)">

packages/account/feature-shell/src/lib/components/editable-account/editable-account.component.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AutocompleteCitiesComponent } from '@devmx/location-ui-forms';
12
import { MatDatepickerModule } from '@angular/material/datepicker';
23
import { provideNativeDateAdapter } from '@angular/material/core';
34
import { MatFormFieldModule } from '@angular/material/form-field';
@@ -8,7 +9,7 @@ import { UpdateAccount } from '@devmx/account-data-access';
89
import { MatInputModule } from '@angular/material/input';
910
import { MatIconModule } from '@angular/material/icon';
1011
import { ReactiveFormsModule } from '@angular/forms';
11-
import { UpdateAccountForm } from '../../forms';
12+
import { UpdateAccountForm, UpdateAccountWithCity } from '../../forms';
1213
import {
1314
output,
1415
OnInit,
@@ -24,6 +25,7 @@ import {
2425
providers: [provideNativeDateAdapter()],
2526
imports: [
2627
ReactiveFormsModule,
28+
AutocompleteCitiesComponent,
2729
MatDatepickerModule,
2830
MatFormFieldModule,
2931
MatCheckboxModule,
@@ -37,7 +39,7 @@ import {
3739
export class EditableAccountComponent implements OnInit {
3840
form = new UpdateAccountForm();
3941

40-
submitted = output<UpdateAccount>();
42+
submitted = output<UpdateAccount | UpdateAccountWithCity>();
4143

4244
ngOnInit() {
4345
this.form.disable();

packages/account/feature-shell/src/lib/containers/settings/settings.container.html

+3
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,6 @@
6666
</div>
6767
</mat-expansion-panel>
6868
</mat-accordion>
69+
70+
71+
<!-- <devmx-autocomplete-cities /> -->

packages/account/feature-shell/src/lib/containers/settings/settings.container.ts

+33-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { ImageComponent, PhotoComponent } from '@devmx/shared-ui-global';
2+
import {
3+
AutocompleteCitiesComponent,
4+
AutocompleteCitiesService,
5+
} from '@devmx/location-ui-forms';
26
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
37
import { MatExpansionModule } from '@angular/material/expansion';
48
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
59
import { MatButtonModule } from '@angular/material/button';
610
import { AccountOut } from '@devmx/shared-api-interfaces';
11+
712
import { MatIconModule } from '@angular/material/icon';
813
import { MatCardModule } from '@angular/material/card';
914
import { CommonModule } from '@angular/common';
10-
import { take } from 'rxjs';
15+
import { AutoAssignable, UpdateAccountWithCity } from '../../forms';
16+
import { switchMap, take } from 'rxjs';
1117
import {
1218
AuthFacade,
1319
AccountFacade,
@@ -28,7 +34,7 @@ import {
2834
DestroyRef,
2935
ChangeDetectionStrategy,
3036
} from '@angular/core';
31-
import { AutoAssignable } from '../../forms';
37+
import { CityFacade } from '@devmx/location-data-access';
3238

3339
@Component({
3440
selector: 'devmx-account-settings',
@@ -47,14 +53,20 @@ import { AutoAssignable } from '../../forms';
4753
EditableAccountComponent,
4854
EditablePasswordComponent,
4955
AutoAssignableRoleComponent,
56+
AutocompleteCitiesComponent,
5057
],
58+
providers: [],
5159
standalone: true,
5260
})
5361
export class SettingsContainer implements OnInit {
5462
authFacade = inject(AuthFacade);
5563

5664
accountFacade = inject(AccountFacade);
5765

66+
autocompleteCitiesService = inject(AutocompleteCitiesService);
67+
68+
cityFacade = inject(CityFacade);
69+
5870
destroyRef = inject(DestroyRef);
5971

6072
dialog = inject(MatDialog);
@@ -94,6 +106,20 @@ export class SettingsContainer implements OnInit {
94106
});
95107

96108
this.authFacade.loadAuthUser();
109+
110+
this.autocompleteCitiesService.search$
111+
.pipe(
112+
switchMap((name) => {
113+
console.log(name);
114+
115+
return name ? this.cityFacade.search(name) : [];
116+
})
117+
)
118+
.subscribe((cities) => {
119+
console.log(cities);
120+
121+
this.autocompleteCitiesService.setCities(cities);
122+
});
97123
}
98124

99125
populate(account: AccountOut) {
@@ -114,8 +140,11 @@ export class SettingsContainer implements OnInit {
114140
});
115141
}
116142

117-
onAccountSubmitted(data: UpdateAccount) {
118-
this.accountFacade.update(data);
143+
onAccountSubmitted(data: UpdateAccount | UpdateAccountWithCity) {
144+
if (data.city && typeof data.city === 'object') {
145+
data.city = data.city.id;
146+
}
147+
this.accountFacade.update(data as UpdateAccount);
119148
}
120149

121150
onPasswordSubmitted(data: ChangePassword) {

packages/account/feature-shell/src/lib/forms/update-account.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { FormControl, FormGroup, Validators } from '@angular/forms';
22
import { UpdateAccount } from '@devmx/account-data-access';
33
import { Form, FormOption } from '@devmx/shared-ui-global';
4-
import { Gender } from '@devmx/shared-api-interfaces';
4+
import { City, Gender } from '@devmx/shared-api-interfaces';
5+
6+
export interface UpdateAccountWithCity extends Omit<UpdateAccount, 'city'> {
7+
city: City;
8+
}
59

610
export class UpdateAccountForm extends Form<UpdateAccount> {
711
genders: FormOption<Gender>[] = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"extends": ["../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
},
17+
{
18+
"files": ["*.json"],
19+
"parser": "jsonc-eslint-parser",
20+
"rules": {
21+
"@nx/dependency-checks": [
22+
"error",
23+
{
24+
"ignoredFiles": ["{projectRoot}/eslint.config.{js,cjs,mjs}"]
25+
}
26+
]
27+
}
28+
}
29+
]
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default {
2+
displayName: 'location-data-access',
3+
preset: '../../../jest.preset.js',
4+
transform: {
5+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
6+
},
7+
moduleFileExtensions: ['ts', 'js', 'html'],
8+
coverageDirectory: '../../../coverage/packages/location/data-access',
9+
passWithNoTests: true
10+
};
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "@devmx/location-data-access",
3+
"version": "0.0.1",
4+
"dependencies": {
5+
"tslib": "^2.3.0",
6+
"@devmx/location-domain": "0.0.1",
7+
"@devmx/shared-api-interfaces": "0.0.1",
8+
"@devmx/shared-data-access": "0.0.1",
9+
"@devmx/shared-util-data": "0.0.1"
10+
},
11+
"type": "commonjs",
12+
"main": "./src/index.js",
13+
"typings": "./src/index.d.ts",
14+
"private": true
15+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "location-data-access",
3+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "packages/location/data-access/src",
5+
"projectType": "library",
6+
"tags": ["type:data"],
7+
"targets": {
8+
"build": {
9+
"executor": "@nx/js:tsc",
10+
"outputs": ["{options.outputPath}"],
11+
"options": {
12+
"outputPath": "dist/packages/location/data-access",
13+
"main": "packages/location/data-access/src/index.ts",
14+
"tsConfig": "packages/location/data-access/tsconfig.lib.json",
15+
"assets": []
16+
}
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './lib/location-providers';
2+
export * from './lib/facades';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { LocationFilter } from '@devmx/location-domain';
2+
import {
3+
SearchCitiesUseCase,
4+
FindCitiesByLocationUseCase,
5+
} from '@devmx/location-domain/client';
6+
import { City, Page } from '@devmx/shared-api-interfaces';
7+
import { State } from '@devmx/shared-data-access';
8+
9+
interface CityState {
10+
cities: Page<City>;
11+
result: City[]
12+
city: City | null;
13+
}
14+
15+
export class CityFacade extends State<CityState> {
16+
constructor(
17+
private findCitiesByLocationUseCase: FindCitiesByLocationUseCase,
18+
private searchCitiesUseCase: SearchCitiesUseCase
19+
) {
20+
super({
21+
cities: { data: [], items: 0, pages: 0 },
22+
result: [],
23+
city: null,
24+
});
25+
}
26+
27+
findByLocation(filter: LocationFilter) {
28+
return this.findCitiesByLocationUseCase.execute(filter);
29+
}
30+
31+
search(name: string) {
32+
return this.searchCitiesUseCase.execute(name);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './city';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {
2+
createCityFacade,
3+
provideCityService,
4+
provideFindCitiesByLocationUseCase,
5+
provideSearchCitiesUseCase,
6+
} from './providers';
7+
8+
export function provideLocations() {
9+
return [
10+
provideCityService(),
11+
12+
provideFindCitiesByLocationUseCase(),
13+
provideSearchCitiesUseCase(),
14+
15+
createCityFacade(),
16+
];
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { CityFacade } from '../facades';
2+
import {
3+
SearchCitiesUseCase,
4+
FindCitiesByLocationUseCase,
5+
} from '@devmx/location-domain/client';
6+
7+
export function createCityFacade() {
8+
return {
9+
provide: CityFacade,
10+
useFactory(
11+
findCitiesByLocation: FindCitiesByLocationUseCase,
12+
searchCities: SearchCitiesUseCase
13+
) {
14+
return new CityFacade(findCitiesByLocation, searchCities);
15+
},
16+
deps: [FindCitiesByLocationUseCase, SearchCitiesUseCase],
17+
};
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './facades';
2+
export * from './services';
3+
export * from './use-cases';

0 commit comments

Comments
 (0)