integration new apis

This commit is contained in:
sdarbinyan
2026-03-24 00:07:45 +04:00
parent b71e806bca
commit 50508b281c
5 changed files with 200 additions and 3 deletions

View File

@@ -116,6 +116,19 @@ export const TRANSLATIONS: Record<string, Record<string, string>> = {
NO_TRANSLATIONS: 'No Russian translation yet', NO_TRANSLATIONS: 'No Russian translation yet',
TRANSLATION_SAVED: 'Translation saved', 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 --- // --- CRUD / Toast messages ---
CONFIRM_DELETE: 'Are you sure you want to delete', CONFIRM_DELETE: 'Are you sure you want to delete',
VALIDATION_ERROR: 'Validation error', VALIDATION_ERROR: 'Validation error',
@@ -261,6 +274,19 @@ export const TRANSLATIONS: Record<string, Record<string, string>> = {
NO_TRANSLATIONS: 'Русский перевод не заполнен', NO_TRANSLATIONS: 'Русский перевод не заполнен',
TRANSLATION_SAVED: 'Перевод сохранён', 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 --- // --- CRUD / Toast messages ---
CONFIRM_DELETE: 'Вы уверены, что хотите удалить', CONFIRM_DELETE: 'Вы уверены, что хотите удалить',
VALIDATION_ERROR: 'Ошибка валидации', VALIDATION_ERROR: 'Ошибка валидации',

View File

@@ -7,6 +7,24 @@ export interface ItemTranslation {
description?: ItemDescriptionField[]; 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 { export interface Item {
id: string; id: string;
name: string; name: string;
@@ -19,9 +37,14 @@ export interface Item {
imgs: string[]; imgs: string[];
tags: string[]; tags: string[];
badges?: string[]; badges?: string[];
colour?: string;
size?: string;
simpleDescription: string; simpleDescription: string;
description: ItemDescriptionField[]; description: ItemDescriptionField[];
subcategoryId: string; subcategoryId: string;
names?: ItemName[];
descriptions?: ItemDescription[];
attributes?: ItemAttribute[];
comments?: Comment[]; comments?: Comment[];
/** Optional translations keyed by language code: { ru: { name: '...', simpleDescription: '...', description: [...] } } */ /** Optional translations keyed by language code: { ru: { name: '...', simpleDescription: '...', description: [...] } } */
translations?: { [lang: string]: ItemTranslation }; translations?: { [lang: string]: ItemTranslation };

View File

@@ -135,6 +135,26 @@
(blur)="onFieldChange('simpleDescription', item()!.simpleDescription)"> (blur)="onFieldChange('simpleDescription', item()!.simpleDescription)">
</textarea> </textarea>
</mat-form-field> </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> </div>
</mat-tab> </mat-tab>
@@ -395,7 +415,7 @@
</div> </div>
</mat-tab> </mat-tab>
<!-- Translations Tab - hidden until client provides requirements <!-- Translations Tab -->
<mat-tab [label]="'TRANSLATIONS' | translate"> <mat-tab [label]="'TRANSLATIONS' | translate">
<div class="tab-content"> <div class="tab-content">
<div class="translations-section"> <div class="translations-section">
@@ -444,7 +464,78 @@
</div> </div>
</div> </div>
</mat-tab> </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> </mat-tab-group>
} }
</div> </div>

View File

@@ -18,7 +18,7 @@ import { DragDropModule, CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-
import { ApiService } from '../../services'; import { ApiService } from '../../services';
import { ValidationService } from '../../services/validation.service'; import { ValidationService } from '../../services/validation.service';
import { ToastService } from '../../services/toast.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 { ConfirmDialogComponent } from '../../components/confirm-dialog/confirm-dialog.component';
import { LoadingSkeletonComponent } from '../../components/loading-skeleton/loading-skeleton.component'; import { LoadingSkeletonComponent } from '../../components/loading-skeleton/loading-skeleton.component';
import { LanguageService } from '../../services/language.service'; import { LanguageService } from '../../services/language.service';
@@ -82,6 +82,8 @@ export class ItemEditorComponent implements OnInit {
]; ];
newBadge = ''; newBadge = '';
newAttrKey = '';
newAttrValue = '';
private destroyRef = inject(DestroyRef); 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() { goBack() {
const item = this.item(); const item = this.item();
if (item && item.subcategoryId) { if (item && item.subcategoryId) {

View File

@@ -98,6 +98,8 @@ export class MockDataService {
], ],
tags: ['new', 'featured', 'bestseller'], tags: ['new', 'featured', 'bestseller'],
badges: ['new', 'featured'], badges: ['new', 'featured'],
colour: 'Natural Titanium',
size: '',
simpleDescription: 'Latest iPhone with titanium design and A17 Pro chip', simpleDescription: 'Latest iPhone with titanium design and A17 Pro chip',
description: [ description: [
{ key: 'Color', value: 'Natural Titanium' }, { key: 'Color', value: 'Natural Titanium' },
@@ -105,6 +107,19 @@ export class MockDataService {
{ key: 'Display', value: '6.7 inch Super Retina XDR' }, { key: 'Display', value: '6.7 inch Super Retina XDR' },
{ key: 'Chip', value: 'A17 Pro' } { 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', subcategoryId: 'sub1',
comments: [ comments: [
{ {
@@ -215,8 +230,13 @@ export class MockDataService {
currency: 'USD', currency: 'USD',
imgs: [`https://placehold.co/600x400?text=Product+${i}`], imgs: [`https://placehold.co/600x400?text=Product+${i}`],
tags: ['test'], tags: ['test'],
colour: '',
size: i % 2 === 0 ? 'M' : 'L',
simpleDescription: `This is test product number ${i}`, simpleDescription: `This is test product number ${i}`,
description: [{ key: 'Size', value: 'Medium' }], description: [{ key: 'Size', value: 'Medium' }],
attributes: [{ key: 'Size', value: i % 2 === 0 ? 'M' : 'L' }],
names: [],
descriptions: [],
subcategoryId subcategoryId
}); });
} }
@@ -434,8 +454,13 @@ export class MockDataService {
imgs: data.imgs || [], imgs: data.imgs || [],
tags: data.tags || [], tags: data.tags || [],
badges: data.badges || [], badges: data.badges || [],
colour: data.colour || '',
size: data.size || '',
simpleDescription: data.simpleDescription || '', simpleDescription: data.simpleDescription || '',
description: data.description || [], description: data.description || [],
attributes: data.attributes || [],
names: data.names || [],
descriptions: data.descriptions || [],
subcategoryId subcategoryId
}; };
this.items.push(newItem); this.items.push(newItem);