Skip to content

Commit b2285b4

Browse files
committed
Enhance product workspace UI and update product models
Introduce model and API payload improvements and revamp the product workspace UI. Update UpdateProductPayload (nullable dimensions, barcode, part_origin_id) and add nested UpdateProductPricePayload and UpdateProductInventoryPayload. Change ProductService.getById to expect wrapped getAllResponse. Enhance design-system input to expose step/min/max attrs. Large refactor of product form/sidebar: new responsive, scrollable tab strip, left/right scroll controls, accessible tab roles, new tab keys (classification, logistics, visibility), status/SKU display, improved badges and layout. product-workspace now handles wrapped responses, sets current status, maps nested pricing/inventory, and comments out unused media/admin components. Add .no-scrollbar CSS. Misc whitespace/formatting tweaks.
1 parent ae8e05c commit b2285b4

16 files changed

Lines changed: 494 additions & 444 deletions

File tree

src/app/core/config/compatibility-modules.config.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ export interface CompatibilityModuleConfig {
1111
headerIconClass: string;
1212
description: string;
1313
countKey:
14-
| 'oemCodesCount'
15-
| 'productCodesCount'
16-
| 'equivalentProductsCount'
17-
| 'vehicleApplicationsCount';
14+
'oemCodesCount' | 'productCodesCount' | 'equivalentProductsCount' | 'vehicleApplicationsCount';
1815
}
1916

2017
/** Módulos de compatibilidade — fonte única alinhada ao menu do shell-state */

src/app/core/models/products/product-request.model.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,25 +78,46 @@ export interface ProductInventoryPayload {
7878
export interface UpdateProductPayload {
7979
category_id?: string | null;
8080
manufacturer_id?: string | null;
81+
part_origin_id?: string | null;
8182
status_id?: string | null;
8283

8384
internal_code?: string | null;
85+
barcode?: string | null;
8486
name?: string | null;
85-
8687
slug?: string | null;
88+
8789
icon?: string | null;
8890
image?: string | null;
8991

9092
short_description?: string | null;
9193
description?: string | null;
9294

93-
weight?: number;
94-
height?: number;
95-
width?: number;
96-
length?: number;
95+
weight?: number | null;
96+
height?: number | null;
97+
width?: number | null;
98+
length?: number | null;
99+
100+
unit?: string | null;
97101

98102
active?: boolean;
99103
featured?: boolean;
100104

101-
unit?: string | null;
105+
price?: UpdateProductPricePayload;
106+
107+
inventory?: UpdateProductInventoryPayload;
108+
}
109+
110+
export interface UpdateProductPricePayload {
111+
price?: number | null;
112+
promotional_price?: number | null;
113+
promotion_start?: string | null;
114+
promotion_end?: string | null;
115+
}
116+
117+
export interface UpdateProductInventoryPayload {
118+
warehouse_id?: string | null;
119+
quantity?: number | null;
120+
minimum_quantity?: number | null;
121+
maximum_quantity?: number | null;
122+
allow_backorder?: boolean;
102123
}

src/app/core/services/product-service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { CreateProductPayload } from '../models/products/product-create.model';
55
import { UpdateProductPayload } from '../models/products/product-request.model';
66
import { ProductResponse } from '../models/products/product-response.model';
77
import { environment } from '../../../environments/environment';
8+
import { getAllResponse } from '../models/generals/general-responses-list.model';
89

910
@Injectable({
1011
providedIn: 'root',
@@ -19,7 +20,7 @@ export class ProductService {
1920
}
2021

2122
getById(id: string) {
22-
return this.http.get<ProductResponse>(`${this.api}/${this.flag}/${id}`);
23+
return this.http.get<getAllResponse<ProductResponse>>(`${this.api}/${this.flag}/${id}`);
2324
}
2425

2526
create(payload: CreateProductPayload) {

src/app/design-system/input/input.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
[placeholder]="placeholder()"
2424
[disabled]="disabled()"
2525
[readOnly]="readonly()"
26+
[attr.step]="type() === 'number' ? step() : null"
27+
[attr.min]="type() === 'number' ? min() : null"
28+
[attr.max]="type() === 'number' ? max() : null"
2629
(input)="onInput($event)"
2730
[class]="inputClasses()"
2831
/>

src/app/design-system/input/input.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export class InputComponent {
2020
readonly error = input<string | undefined>(undefined);
2121
readonly helperText = input<string | undefined>(undefined);
2222
readonly iconPrefix = input<string | undefined>(undefined);
23+
readonly step = input<number | undefined>(undefined);
24+
readonly min = input<number | undefined>(undefined);
25+
readonly max = input<number | undefined>(undefined);
2326
readonly inputId = input<string>('input-' + Math.random().toString(36).substring(2, 7));
2427

2528
readonly valueChange = output<string>();

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

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
<div class="space-y-6">
2-
@switch (activeTab()) {
3-
@case ('oem') {
2+
@switch (activeTab()) { @case ('oem') {
43
<!-- Card OEM Codes -->
54
<div
65
class="bg-white dark:bg-slate-800 rounded-[12px] border border-gray-200/80 dark:border-slate-700/80 p-6 shadow-2xs"
76
>
87
<div class="flex items-center justify-between mb-4">
98
<div>
109
<h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-center gap-2">
11-
<app-icon [name]="getModule('oem').headerIcon" size="w-5 h-5" [class]="getModule('oem').headerIconClass" />
10+
<app-icon
11+
[name]="getModule('oem').headerIcon"
12+
size="w-5 h-5"
13+
[class]="getModule('oem').headerIconClass"
14+
/>
1215
{{ getModule('oem').label }}
1316
</h3>
14-
<p class="text-xs text-gray-500 dark:text-slate-400">
15-
{{ getModule('oem').description }}
16-
</p>
17+
<p class="text-xs text-gray-500 dark:text-slate-400">{{ getModule('oem').description }}</p>
1718
</div>
1819

1920
@if (!isReadOnly()) {
@@ -111,16 +112,19 @@ <h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-cent
111112
</div>
112113
}
113114
</div>
114-
}
115-
@case ('codigos') {
115+
} @case ('codigos') {
116116
<!-- Card Product Codes -->
117117
<div
118118
class="bg-white dark:bg-slate-800 rounded-[12px] border border-gray-200/80 dark:border-slate-700/80 p-6 shadow-2xs"
119119
>
120120
<div class="flex items-center justify-between mb-4">
121121
<div>
122122
<h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-center gap-2">
123-
<app-icon [name]="getModule('codigos').headerIcon" size="w-5 h-5" [class]="getModule('codigos').headerIconClass" />
123+
<app-icon
124+
[name]="getModule('codigos').headerIcon"
125+
size="w-5 h-5"
126+
[class]="getModule('codigos').headerIconClass"
127+
/>
124128
{{ getModule('codigos').label }}
125129
</h3>
126130
<p class="text-xs text-gray-500 dark:text-slate-400">
@@ -204,16 +208,19 @@ <h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-cent
204208
</div>
205209
}
206210
</div>
207-
}
208-
@case ('equivalentes') {
211+
} @case ('equivalentes') {
209212
<!-- Card Produtos Equivalentes -->
210213
<div
211214
class="bg-white dark:bg-slate-800 rounded-[12px] border border-gray-200/80 dark:border-slate-700/80 p-6 shadow-2xs"
212215
>
213216
<div class="flex items-center justify-between mb-4">
214217
<div>
215218
<h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-center gap-2">
216-
<app-icon [name]="getModule('equivalentes').headerIcon" size="w-5 h-5" [class]="getModule('equivalentes').headerIconClass" />
219+
<app-icon
220+
[name]="getModule('equivalentes').headerIcon"
221+
size="w-5 h-5"
222+
[class]="getModule('equivalentes').headerIconClass"
223+
/>
217224
{{ getModule('equivalentes').label }}
218225
</h3>
219226
<p class="text-xs text-gray-500 dark:text-slate-400">
@@ -297,16 +304,19 @@ <h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-cent
297304
</div>
298305
}
299306
</div>
300-
}
301-
@case ('compatibilidade') {
307+
} @case ('compatibilidade') {
302308
<!-- Card Aplicações Veiculares -->
303309
<div
304310
class="bg-white dark:bg-slate-800 rounded-[12px] border border-gray-200/80 dark:border-slate-700/80 p-6 shadow-2xs"
305311
>
306312
<div class="flex items-center justify-between mb-4">
307313
<div>
308314
<h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-center gap-2">
309-
<app-icon [name]="getModule('compatibilidade').headerIcon" size="w-5 h-5" [class]="getModule('compatibilidade').headerIconClass" />
315+
<app-icon
316+
[name]="getModule('compatibilidade').headerIcon"
317+
size="w-5 h-5"
318+
[class]="getModule('compatibilidade').headerIconClass"
319+
/>
310320
{{ getModule('compatibilidade').label }}
311321
</h3>
312322
<p class="text-xs text-gray-500 dark:text-slate-400">
@@ -400,6 +410,5 @@ <h3 class="text-base font-bold text-gray-900 dark:text-slate-100 flex items-cent
400410
</div>
401411
}
402412
</div>
403-
}
404-
}
413+
} }
405414
</div>
Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,63 @@
11
<div class="space-y-6">
22
<!-- Section 1: Informações Básicas (Etapa 1) -->
3-
@if (currentStep() === 0 || currentStep() === 1) {
4-
<app-product-basic-info-section
5-
[formName]="form().name"
6-
[formSlug]="form().slug"
7-
[formShortDescription]="form().shortDescription"
8-
[formDescription]="form().description"
9-
[isReadOnly]="isReadOnly()"
10-
[isCreateMode]="isCreateMode()"
11-
[errors]="errors()"
12-
(fieldChange)="onFieldChange($event.field, $event.value)"
13-
(descriptionChange)="descriptionChange.emit($event)"
14-
/>
3+
@if ((currentStep() === 0 && activeTab() === 'geral') || currentStep() === 1) {
4+
<app-product-basic-info-section
5+
[formName]="form().name"
6+
[formSlug]="form().slug"
7+
[formShortDescription]="form().shortDescription"
8+
[formDescription]="form().description"
9+
[isReadOnly]="isReadOnly()"
10+
[isCreateMode]="isCreateMode()"
11+
[errors]="errors()"
12+
(fieldChange)="onFieldChange($event.field, $event.value)"
13+
(descriptionChange)="descriptionChange.emit($event)"
14+
/>
1515
}
1616

1717
<!-- Section 2: Identificação & Classificação (Etapa 2) -->
18-
@if (currentStep() === 0 || currentStep() === 2) {
19-
<app-product-identification-section
20-
[formCategoryId]="form().categoryId"
21-
[formManufacturerId]="form().manufacturerId"
22-
[formPartOriginId]="form().partOriginId"
23-
[formStatusId]="form().statusId"
24-
[formInternalCode]="form().internalCode"
25-
[formBarcode]="form().barcode"
26-
[isReadOnly]="isReadOnly()"
27-
[isCreateMode]="isCreateMode()"
28-
[errors]="errors()"
29-
[categoryOptions]="options().categoryOptions"
30-
[categoryLoading]="options().categoryLoading"
31-
[manufacturerOptions]="options().manufacturerOptions"
32-
[manufacturerLoading]="options().manufacturerLoading"
33-
[partOriginOptions]="options().partOriginOptions"
34-
[statusOptions]="options().statusOptions"
35-
(fieldChange)="onFieldChange($event.field, $event.value)"
36-
(categorySearch)="categorySearch.emit($event)"
37-
(manufacturerSearch)="manufacturerSearch.emit($event)"
38-
/>
18+
@if ((currentStep() === 0 && activeTab() === 'classification') || currentStep() === 2) {
19+
<app-product-identification-section
20+
[formCategoryId]="form().categoryId"
21+
[formManufacturerId]="form().manufacturerId"
22+
[formPartOriginId]="form().partOriginId"
23+
[formStatusId]="form().statusId"
24+
[formInternalCode]="form().internalCode"
25+
[formBarcode]="form().barcode"
26+
[isReadOnly]="isReadOnly()"
27+
[isCreateMode]="isCreateMode()"
28+
[errors]="errors()"
29+
[categoryOptions]="options().categoryOptions"
30+
[categoryLoading]="options().categoryLoading"
31+
[manufacturerOptions]="options().manufacturerOptions"
32+
[manufacturerLoading]="options().manufacturerLoading"
33+
[partOriginOptions]="options().partOriginOptions"
34+
[statusOptions]="options().statusOptions"
35+
(fieldChange)="onFieldChange($event.field, $event.value)"
36+
(categorySearch)="categorySearch.emit($event)"
37+
(manufacturerSearch)="manufacturerSearch.emit($event)"
38+
/>
3939
}
4040

4141
<!-- Section 3 & 4: Especificações & Visibilidade (Etapa 3) -->
42-
@if (currentStep() === 0 || currentStep() === 3) {
43-
<app-product-dimensions-section
44-
[formUnitId]="form().unitId"
45-
[formWeight]="form().weight"
46-
[formHeight]="form().height"
47-
[formWidth]="form().width"
48-
[formLength]="form().length"
49-
[isReadOnly]="isReadOnly()"
50-
[unitOptions]="options().unitOptions"
51-
(fieldChange)="onFieldChange($event.field, $event.value)"
52-
(numberFieldChange)="onNumberFieldChange($event.field, $event.value)"
53-
/>
42+
@if ((currentStep() === 0 && activeTab() === 'logistics') || currentStep() === 3) {
43+
<app-product-dimensions-section
44+
[formUnitId]="form().unitId"
45+
[formWeight]="form().weight"
46+
[formHeight]="form().height"
47+
[formWidth]="form().width"
48+
[formLength]="form().length"
49+
[isReadOnly]="isReadOnly()"
50+
[unitOptions]="options().unitOptions"
51+
(fieldChange)="onFieldChange($event.field, $event.value)"
52+
(numberFieldChange)="onNumberFieldChange($event.field, $event.value)"
53+
/>
5454

55-
<app-product-visibility-section
56-
[formActive]="form().active"
57-
[formFeatured]="form().featured"
58-
[isReadOnly]="isReadOnly()"
59-
[isCreateMode]="isCreateMode()"
60-
(booleanFieldToggle)="onBooleanFieldToggle($event.field, $event.currentValue)"
61-
/>
55+
<app-product-visibility-section
56+
[formActive]="form().active"
57+
[formFeatured]="form().featured"
58+
[isReadOnly]="isReadOnly()"
59+
[isCreateMode]="isCreateMode()"
60+
(booleanFieldToggle)="onBooleanFieldToggle($event.field, $event.currentValue)"
61+
/>
6262
}
6363
</div>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export class ProductGeneralTabComponent {
3030
readonly isCreateMode = input<boolean>(false);
3131
readonly currentStep = input<number>(0);
3232
readonly errors = input<Record<string, string>>({});
33+
readonly activeTab = input<string>('geral');
3334

3435
// Outputs
3536
readonly fieldChange = output<{ field: string; value: string }>();

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

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ import { InputComponent } from '../../../../../../design-system/input/input';
77
imports: [InputComponent],
88
changeDetection: ChangeDetectionStrategy.OnPush,
99
template: `
10-
<div class="bg-white dark:bg-slate-800 rounded-[12px] border border-gray-200/80 dark:border-slate-700/80 p-6 shadow-2xs space-y-4">
10+
<div
11+
class="bg-white dark:bg-slate-800 rounded-[12px] border border-gray-200/80 dark:border-slate-700/80 p-6 shadow-2xs space-y-4"
12+
>
1113
<div class="border-b border-gray-100 dark:border-slate-700/60 pb-3">
1214
<h3 class="text-xs font-bold text-gray-900 dark:text-slate-200 uppercase tracking-wider">
1315
Informações Básicas & Descrições
1416
</h3>
15-
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">Título, identificador URL e descrições técnicas e comerciais</p>
17+
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">
18+
Título, identificador URL e descrições técnicas e comerciais
19+
</p>
1620
</div>
1721
1822
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@@ -29,14 +33,14 @@ import { InputComponent } from '../../../../../../design-system/input/input';
2933
3034
<!-- Slug -->
3135
@if (!isCreateMode()) {
32-
<app-input
33-
label="Slug (URL amigável)"
34-
placeholder="Ex.: jogo-de-pastilhas-de-freio-dianteira"
35-
[value]="formSlug()"
36-
[disabled]="isReadOnly()"
37-
helperText="Gerado automaticamente ou personalizável"
38-
(valueChange)="fieldChange.emit({ field: 'slug', value: $event })"
39-
/>
36+
<app-input
37+
label="Slug (URL amigável)"
38+
placeholder="Ex.: jogo-de-pastilhas-de-freio-dianteira"
39+
[value]="formSlug()"
40+
[disabled]="isReadOnly()"
41+
helperText="Gerado automaticamente ou personalizável"
42+
(valueChange)="fieldChange.emit({ field: 'slug', value: $event })"
43+
/>
4044
}
4145
</div>
4246
@@ -51,7 +55,10 @@ import { InputComponent } from '../../../../../../design-system/input/input';
5155
5256
<!-- Descrição Detalhada -->
5357
<div>
54-
<label for="description-textarea" class="block text-xs font-medium text-gray-700 dark:text-slate-300 mb-1">
58+
<label
59+
for="description-textarea"
60+
class="block text-xs font-medium text-gray-700 dark:text-slate-300 mb-1"
61+
>
5562
Descrição Detalhada
5663
</label>
5764
<textarea
@@ -65,7 +72,7 @@ import { InputComponent } from '../../../../../../design-system/input/input';
6572
></textarea>
6673
</div>
6774
</div>
68-
`
75+
`,
6976
})
7077
export class ProductBasicInfoSectionComponent {
7178
readonly formName = input<string>('');

0 commit comments

Comments
 (0)