integration new apis
This commit is contained in:
@@ -116,6 +116,19 @@ export const TRANSLATIONS: Record<string, Record<string, string>> = {
|
||||
NO_TRANSLATIONS: 'No Russian translation yet',
|
||||
TRANSLATION_SAVED: 'Translation saved',
|
||||
|
||||
// --- Attributes tab ---
|
||||
ATTRIBUTES: 'Attributes',
|
||||
ATTRIBUTES_HINT: 'Key-value attributes for product specifications.',
|
||||
ATTR_KEY: 'Attribute key',
|
||||
ATTR_VALUE: 'Attribute value',
|
||||
ATTR_KEY_PLACEHOLDER: 'e.g. Material',
|
||||
ATTR_VALUE_PLACEHOLDER: 'e.g. Cotton 100%',
|
||||
NO_ATTRIBUTES: 'No attributes yet',
|
||||
|
||||
// --- New item fields ---
|
||||
COLOUR: 'Colour',
|
||||
SIZE: 'Size',
|
||||
|
||||
// --- CRUD / Toast messages ---
|
||||
CONFIRM_DELETE: 'Are you sure you want to delete',
|
||||
VALIDATION_ERROR: 'Validation error',
|
||||
@@ -261,6 +274,19 @@ export const TRANSLATIONS: Record<string, Record<string, string>> = {
|
||||
NO_TRANSLATIONS: 'Русский перевод не заполнен',
|
||||
TRANSLATION_SAVED: 'Перевод сохранён',
|
||||
|
||||
// --- Attributes tab ---
|
||||
ATTRIBUTES: 'Атрибуты',
|
||||
ATTRIBUTES_HINT: 'Ключ-значение атрибуты для характеристик товара.',
|
||||
ATTR_KEY: 'Ключ атрибута',
|
||||
ATTR_VALUE: 'Значение атрибута',
|
||||
ATTR_KEY_PLACEHOLDER: 'напр. Материал',
|
||||
ATTR_VALUE_PLACEHOLDER: 'напр. Хлопок 100%',
|
||||
NO_ATTRIBUTES: 'Атрибутов пока нет',
|
||||
|
||||
// --- New item fields ---
|
||||
COLOUR: 'Цвет',
|
||||
SIZE: 'Размер',
|
||||
|
||||
// --- CRUD / Toast messages ---
|
||||
CONFIRM_DELETE: 'Вы уверены, что хотите удалить',
|
||||
VALIDATION_ERROR: 'Ошибка валидации',
|
||||
|
||||
@@ -7,6 +7,24 @@ export interface ItemTranslation {
|
||||
description?: ItemDescriptionField[];
|
||||
}
|
||||
|
||||
/** Localized name entry */
|
||||
export interface ItemName {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Localized description entry */
|
||||
export interface ItemDescription {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Key-value attribute pair */
|
||||
export interface ItemAttribute {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -19,9 +37,14 @@ export interface Item {
|
||||
imgs: string[];
|
||||
tags: string[];
|
||||
badges?: string[];
|
||||
colour?: string;
|
||||
size?: string;
|
||||
simpleDescription: string;
|
||||
description: ItemDescriptionField[];
|
||||
subcategoryId: string;
|
||||
names?: ItemName[];
|
||||
descriptions?: ItemDescription[];
|
||||
attributes?: ItemAttribute[];
|
||||
comments?: Comment[];
|
||||
/** Optional translations keyed by language code: { ru: { name: '...', simpleDescription: '...', description: [...] } } */
|
||||
translations?: { [lang: string]: ItemTranslation };
|
||||
|
||||
@@ -135,6 +135,26 @@
|
||||
(blur)="onFieldChange('simpleDescription', item()!.simpleDescription)">
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="form-row">
|
||||
<mat-form-field appearance="outline" class="half-width">
|
||||
<mat-label>{{ 'COLOUR' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="item()!.colour"
|
||||
(blur)="onFieldChange('colour', item()!.colour)"
|
||||
placeholder="e.g. Black, Red">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="half-width">
|
||||
<mat-label>{{ 'SIZE' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="item()!.size"
|
||||
(blur)="onFieldChange('size', item()!.size)"
|
||||
placeholder="e.g. M, L, XL">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -395,7 +415,7 @@
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Translations Tab - hidden until client provides requirements
|
||||
<!-- Translations Tab -->
|
||||
<mat-tab [label]="'TRANSLATIONS' | translate">
|
||||
<div class="tab-content">
|
||||
<div class="translations-section">
|
||||
@@ -444,7 +464,78 @@
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
-->
|
||||
|
||||
<!-- Attributes Tab -->
|
||||
<mat-tab [label]="'ATTRIBUTES' | translate">
|
||||
<div class="tab-content">
|
||||
<div class="attributes-section">
|
||||
<h3>{{ 'ATTRIBUTES' | translate }}</h3>
|
||||
<p class="hint">{{ 'ATTRIBUTES_HINT' | translate }}</p>
|
||||
|
||||
<div class="add-desc-form">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'ATTR_KEY' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="newAttrKey"
|
||||
[placeholder]="'ATTR_KEY_PLACEHOLDER' | translate">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'ATTR_VALUE' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="newAttrValue"
|
||||
[placeholder]="'ATTR_VALUE_PLACEHOLDER' | translate">
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="addAttribute()">
|
||||
<mat-icon>add</mat-icon>
|
||||
{{ 'ADD_FIELD' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="desc-fields-list">
|
||||
@for (attr of item()!.attributes || []; track $index) {
|
||||
<div class="desc-field-row">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'ATTR_KEY' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="attr.key"
|
||||
(blur)="updateAttribute($index, 'key', $any($event.target).value)">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ 'ATTR_VALUE' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="attr.value"
|
||||
(blur)="updateAttribute($index, 'value', $any($event.target).value)">
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
(click)="removeAttribute($index)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!(item()!.attributes?.length)) {
|
||||
<div class="empty-state">
|
||||
<mat-icon>tune</mat-icon>
|
||||
<p>{{ 'NO_ATTRIBUTES' | translate }}</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { DragDropModule, CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-
|
||||
import { ApiService } from '../../services';
|
||||
import { ValidationService } from '../../services/validation.service';
|
||||
import { ToastService } from '../../services/toast.service';
|
||||
import { Item, ItemDescriptionField, Subcategory } from '../../models';
|
||||
import { Item, ItemDescriptionField, ItemAttribute, Subcategory } from '../../models';
|
||||
import { ConfirmDialogComponent } from '../../components/confirm-dialog/confirm-dialog.component';
|
||||
import { LoadingSkeletonComponent } from '../../components/loading-skeleton/loading-skeleton.component';
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
@@ -82,6 +82,8 @@ export class ItemEditorComponent implements OnInit {
|
||||
];
|
||||
|
||||
newBadge = '';
|
||||
newAttrKey = '';
|
||||
newAttrValue = '';
|
||||
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@@ -337,6 +339,36 @@ export class ItemEditorComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes handling
|
||||
addAttribute() {
|
||||
if (!this.newAttrKey.trim() || !this.newAttrValue.trim()) return;
|
||||
const currentItem = this.item();
|
||||
if (!currentItem) return;
|
||||
const attrs = [...(currentItem.attributes || []), { key: this.newAttrKey.trim(), value: this.newAttrValue.trim() }];
|
||||
currentItem.attributes = attrs;
|
||||
this.onFieldChange('attributes' as any, attrs);
|
||||
this.newAttrKey = '';
|
||||
this.newAttrValue = '';
|
||||
}
|
||||
|
||||
updateAttribute(index: number, field: 'key' | 'value', value: string) {
|
||||
const currentItem = this.item();
|
||||
if (!currentItem) return;
|
||||
const attrs = [...(currentItem.attributes || [])];
|
||||
attrs[index] = { ...attrs[index], [field]: value };
|
||||
currentItem.attributes = attrs;
|
||||
this.onFieldChange('attributes' as any, attrs);
|
||||
}
|
||||
|
||||
removeAttribute(index: number) {
|
||||
const currentItem = this.item();
|
||||
if (!currentItem) return;
|
||||
const attrs = [...(currentItem.attributes || [])];
|
||||
attrs.splice(index, 1);
|
||||
currentItem.attributes = attrs;
|
||||
this.onFieldChange('attributes' as any, attrs);
|
||||
}
|
||||
|
||||
goBack() {
|
||||
const item = this.item();
|
||||
if (item && item.subcategoryId) {
|
||||
|
||||
@@ -98,6 +98,8 @@ export class MockDataService {
|
||||
],
|
||||
tags: ['new', 'featured', 'bestseller'],
|
||||
badges: ['new', 'featured'],
|
||||
colour: 'Natural Titanium',
|
||||
size: '',
|
||||
simpleDescription: 'Latest iPhone with titanium design and A17 Pro chip',
|
||||
description: [
|
||||
{ key: 'Color', value: 'Natural Titanium' },
|
||||
@@ -105,6 +107,19 @@ export class MockDataService {
|
||||
{ key: 'Display', value: '6.7 inch Super Retina XDR' },
|
||||
{ key: 'Chip', value: 'A17 Pro' }
|
||||
],
|
||||
attributes: [
|
||||
{ key: 'Color', value: 'Natural Titanium' },
|
||||
{ key: 'Storage', value: '256GB' },
|
||||
{ key: 'Chip', value: 'A17 Pro' }
|
||||
],
|
||||
names: [
|
||||
{ language: 'ru', value: 'iPhone 15 Pro Max' },
|
||||
{ language: 'en', value: 'iPhone 15 Pro Max' }
|
||||
],
|
||||
descriptions: [
|
||||
{ language: 'ru', value: 'Новейший iPhone с титановым корпусом и чипом A17 Pro' },
|
||||
{ language: 'en', value: 'Latest iPhone with titanium design and A17 Pro chip' }
|
||||
],
|
||||
subcategoryId: 'sub1',
|
||||
comments: [
|
||||
{
|
||||
@@ -215,8 +230,13 @@ export class MockDataService {
|
||||
currency: 'USD',
|
||||
imgs: [`https://placehold.co/600x400?text=Product+${i}`],
|
||||
tags: ['test'],
|
||||
colour: '',
|
||||
size: i % 2 === 0 ? 'M' : 'L',
|
||||
simpleDescription: `This is test product number ${i}`,
|
||||
description: [{ key: 'Size', value: 'Medium' }],
|
||||
attributes: [{ key: 'Size', value: i % 2 === 0 ? 'M' : 'L' }],
|
||||
names: [],
|
||||
descriptions: [],
|
||||
subcategoryId
|
||||
});
|
||||
}
|
||||
@@ -434,8 +454,13 @@ export class MockDataService {
|
||||
imgs: data.imgs || [],
|
||||
tags: data.tags || [],
|
||||
badges: data.badges || [],
|
||||
colour: data.colour || '',
|
||||
size: data.size || '',
|
||||
simpleDescription: data.simpleDescription || '',
|
||||
description: data.description || [],
|
||||
attributes: data.attributes || [],
|
||||
names: data.names || [],
|
||||
descriptions: data.descriptions || [],
|
||||
subcategoryId
|
||||
};
|
||||
this.items.push(newItem);
|
||||
|
||||
Reference in New Issue
Block a user