Skip to content

Commit e56691c

Browse files
committed
Hydrate option stores; inventory UI refactor
Replace loadInitialOptions with hydrate() and improve OptionCacheStore (async API loads, query handling, cache subscription and clear). Comment initial loader calls and update AppInitializer to use hydrate. Normalize warehouse option mapping. Simplify compatibility tab imports and remove unused OnChanges. Move/clean basic-info section markup. Large refactor of inventory tab (HTML + TS): introduce local signal state, computed helpers, add/remove warehouse flow with restore-from-cache, inline editing, toggles, improved layout/styling and footer docs; remove tableHeaders dependency. Remove stray debug logs in sidebar and workspace.
1 parent dcf6c2e commit e56691c

10 files changed

Lines changed: 598 additions & 187 deletions

File tree

src/app/core/initialization/app-initializer.service.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ export class AppInitializerService {
2222
async initialize(): Promise<void> {
2323
await Promise.all([
2424
this.oemCodeStore.loadInitial(),
25-
this.statusStore.loadInitialOptions(),
26-
this.unitSaleStore.loadInitialOptions(),
27-
this.categoryStore.loadInitialOptions(),
28-
this.warehouseStore.loadInitialOptions(),
29-
this.partOriginStore.loadInitialOptions(),
30-
this.manufacturerStore.loadInitialOptions(),
25+
this.statusStore.hydrate(),
26+
this.unitSaleStore.hydrate(),
27+
this.categoryStore.hydrate(),
28+
this.warehouseStore.hydrate(),
29+
this.partOriginStore.hydrate(),
30+
this.manufacturerStore.hydrate(),
3131
]);
3232
}
3333
}

src/app/core/services/inital-loader-service.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ export class InitialDataLoader {
1212
// etc.
1313

1414
load(): void {
15-
this.manufacturerStore.loadInitialOptions();
16-
15+
// this.manufacturerStore.loadInitialOptions();
1716
// this.categoryStore.loadInitialOptions();
1817
// this.countryStore.loadInitialOptions();
1918
// ...

src/app/core/store/base/option-cache-store.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,21 @@ export abstract class OptionCacheStore<TOption, TQueryCache> {
1515
protected abstract readonly INITIAL_KEY: string;
1616
protected abstract readonly QUERY_KEY: string;
1717

18+
/**
19+
* Responsável por buscar as opções na API.
20+
*
21+
* O Store concreto pode sobrescrever o retorno do serviço,
22+
* realizando aqui a adaptação necessária para TOption.
23+
*/
1824
protected abstract fetchOptions(query: GeneralOptionQuery): Observable<getAllResponse<TOption[]>>;
1925

2026
protected readonly _options = signal<getAllResponse<TOption[]> | null>(null);
2127

2228
readonly options = this._options.asReadonly();
2329

30+
/**
31+
* Lista pronta para ser consumida pela aplicação.
32+
*/
2433
readonly optionList = computed(() => this._options()?.data ?? []);
2534

2635
protected readonly defaultQuery = createDefaultQuery();
@@ -41,39 +50,50 @@ export abstract class OptionCacheStore<TOption, TQueryCache> {
4150
}
4251

4352
/**
44-
* Fluxo utilizado pelo carregamento inicial da aplicação.
53+
* Hidrata o Store ao iniciar a aplicação.
54+
*
55+
* 1. Procura os dados iniciais no IndexedDB.
56+
* 2. Se encontrar, popula o Signal.
57+
* 3. Se não encontrar, busca na API e grava no cache.
4558
*/
46-
async loadInitialOptions(): Promise<void> {
59+
async hydrate(): Promise<void> {
4760
const cached = await this.cache.get<getAllResponse<TOption[]>>(this.INITIAL_KEY);
4861

4962
if (cached) {
5063
this._options.set(cached);
5164
return;
5265
}
5366

54-
this.loadFromApi(this.defaultQuery, this.INITIAL_KEY);
67+
await this.loadFromApi(this.defaultQuery, this.INITIAL_KEY);
5568
}
5669

5770
/**
58-
* Fluxo público utilizado pelos componentes.
71+
* Fluxo utilizado para buscas filtradas.
72+
*
73+
* Quando a consulta é a consulta padrão, volta a utilizar
74+
* os dados iniciais.
5975
*/
6076
async loadOptions(query: GeneralOptionQuery): Promise<void> {
6177
this.currentQuery = query;
6278

6379
if (isDefaultQuery(query)) {
6480
await this.cache.remove(this.QUERY_KEY);
65-
await this.loadInitialOptions();
81+
await this.hydrate();
6682
return;
6783
}
6884

6985
await this.loadQuery(query);
7086
}
7187

88+
/**
89+
* Carrega uma consulta específica.
90+
*/
7291
private async loadQuery(query: GeneralOptionQuery): Promise<void> {
7392
const cached = await this.cache.get<TQueryCache>(this.QUERY_KEY);
7493

7594
if (cached && this.isSameCachedQuery(cached, query)) {
7695
this._options.set(this.getCachedResponse(cached));
96+
7797
return;
7898
}
7999

@@ -92,7 +112,10 @@ export abstract class OptionCacheStore<TOption, TQueryCache> {
92112
});
93113
}
94114

95-
private loadFromApi(query: GeneralOptionQuery, cacheKey: string): void {
115+
/**
116+
* Busca dados na API e persiste no cache.
117+
*/
118+
private async loadFromApi(query: GeneralOptionQuery, cacheKey: string): Promise<void> {
96119
this.fetchOptions(query).subscribe({
97120
next: async (response) => {
98121
this._options.set(response);
@@ -106,6 +129,10 @@ export abstract class OptionCacheStore<TOption, TQueryCache> {
106129
});
107130
}
108131

132+
/**
133+
* Mantém o Signal sincronizado quando outra instância da
134+
* aplicação altera o cache inicial.
135+
*/
109136
private subscribeToCacheChanges(): void {
110137
this.cache.subscribe(this.INITIAL_KEY, async () => {
111138
if (!isDefaultQuery(this.currentQuery)) {
@@ -120,6 +147,9 @@ export abstract class OptionCacheStore<TOption, TQueryCache> {
120147
});
121148
}
122149

150+
/**
151+
* Limpa o estado do Store e os respectivos caches.
152+
*/
123153
async clear(): Promise<void> {
124154
this._options.set(null);
125155

src/app/core/store/warehouse/warehouse-store.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ export class WarehouseStore extends OptionCacheStore<SelectOption, WarehouseQuer
2525
map((response: getAllResponse<GeneralOption[]>) => {
2626
return {
2727
...response,
28-
data: response.data.map((option: GeneralOption) => ({
29-
...option,
28+
data: response.data.map((option: any) => ({
29+
value: option.id ?? option.value,
30+
label: option.label,
31+
sublabel: option.description ?? option.sublabel ?? '',
3032
disabled: false,
3133
})),
3234
};

src/app/features/catalog/components/product-form/compatibility-tab/compatibility-tab.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
import {
2-
Component,
3-
ChangeDetectionStrategy,
4-
computed,
5-
input,
6-
output,
7-
signal,
8-
OnChanges,
9-
SimpleChanges,
10-
} from '@angular/core';
1+
import { Component, ChangeDetectionStrategy, computed, input, output, signal } from '@angular/core';
112
import { ButtonComponent } from '../../../../../design-system/button/button';
123
import { AppIconComponent } from '../../../../../design-system/icon/app-icon';
134
import { PaginationComponent } from '../../../../../design-system/pagination/pagination';
@@ -28,10 +19,7 @@ import { ManufacturerOption } from '../../../../../core/models/manufactureres/ma
2819
changeDetection: ChangeDetectionStrategy.OnPush,
2920
templateUrl: './compatibility-tab.html',
3021
})
31-
export class ProductCompatibilityTabComponent implements OnChanges {
32-
ngOnChanges(changes: SimpleChanges): void {
33-
console.log('[ProductCompatibilityTabComponent] ngOnChanges', changes);
34-
}
22+
export class ProductCompatibilityTabComponent {
3523
readonly activeTab = input.required<FormTab>();
3624
readonly oemCodes = input<OemCode[]>([]);
3725
readonly selectedOemCodeIds = input<string[]>([]);

src/app/features/catalog/components/product-form/general-tab/sections/basic-info-section.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,17 @@ import { InputComponent } from '../../../../../../design-system/input/input';
3030
[error]="errors()['name']"
3131
(valueChange)="fieldChange.emit({ field: 'name', value: $event })"
3232
/>
33+
<!-- Descrição Curta -->
34+
<app-input
35+
label="Descrição Curta"
36+
placeholder="Resumo comercial do produto em poucas palavras..."
37+
[value]="formShortDescription()"
38+
[disabled]="isReadOnly()"
39+
(valueChange)="fieldChange.emit({ field: 'short_description', value: $event })"
40+
/>
3341
3442
<!-- Slug -->
35-
@if (!isCreateMode()) {
43+
<!-- @if (!isCreateMode()) {
3644
<app-input
3745
label="Slug (URL amigável)"
3846
placeholder="Ex.: jogo-de-pastilhas-de-freio-dianteira"
@@ -41,18 +49,9 @@ import { InputComponent } from '../../../../../../design-system/input/input';
4149
helperText="Gerado automaticamente ou personalizável"
4250
(valueChange)="fieldChange.emit({ field: 'slug', value: $event })"
4351
/>
44-
}
52+
} -->
4553
</div>
4654
47-
<!-- Descrição Curta -->
48-
<app-input
49-
label="Descrição Curta"
50-
placeholder="Resumo comercial do produto em poucas palavras..."
51-
[value]="formShortDescription()"
52-
[disabled]="isReadOnly()"
53-
(valueChange)="fieldChange.emit({ field: 'short_description', value: $event })"
54-
/>
55-
5655
<!-- Descrição Detalhada -->
5756
<div>
5857
<label

0 commit comments

Comments
 (0)