changes are done
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable, Subject, timer } from 'rxjs';
|
||||
import { debounce, retry, catchError, tap, map } from 'rxjs/operators';
|
||||
import { Observable, Subject, throwError } from 'rxjs';
|
||||
import { debounceTime, retry, catchError, map, groupBy, mergeMap } from 'rxjs/operators';
|
||||
import { Project, Category, Subcategory, Item, ItemsListResponse } from '../models';
|
||||
import { MockDataService } from './mock-data.service';
|
||||
import { ToastService } from './toast.service';
|
||||
import { environment } from '../../environments/environment';
|
||||
|
||||
@Injectable({
|
||||
@@ -12,15 +13,22 @@ import { environment } from '../../environments/environment';
|
||||
export class ApiService {
|
||||
private http = inject(HttpClient);
|
||||
private mockService = inject(MockDataService);
|
||||
private toast = inject(ToastService);
|
||||
private readonly API_BASE = environment.apiUrl;
|
||||
|
||||
|
||||
/** Whether a debounced save is in-flight */
|
||||
saving = signal(false);
|
||||
|
||||
// Debounced save queue
|
||||
private saveQueue$ = new Subject<SaveOperation>();
|
||||
|
||||
constructor() {
|
||||
// Set up auto-save with 500ms debounce
|
||||
// Debounce per unique type+id+field so independent fields don't clobber each other
|
||||
this.saveQueue$
|
||||
.pipe(debounce(() => timer(500)))
|
||||
.pipe(
|
||||
groupBy(op => `${op.type}:${op.id}:${op.field}`),
|
||||
mergeMap(group$ => group$.pipe(debounceTime(500)))
|
||||
)
|
||||
.subscribe(operation => {
|
||||
this.executeSave(operation);
|
||||
});
|
||||
@@ -198,7 +206,8 @@ export class ApiService {
|
||||
}
|
||||
|
||||
// Debounced auto-save
|
||||
queueSave(type: 'category' | 'subcategory' | 'item', id: string, field: string, value: any) {
|
||||
queueSave(type: 'category' | 'subcategory' | 'item', id: string, field: string, value: unknown) {
|
||||
this.saving.set(true);
|
||||
this.saveQueue$.next({ type, id, field, value });
|
||||
}
|
||||
|
||||
@@ -219,12 +228,18 @@ export class ApiService {
|
||||
}
|
||||
|
||||
request.subscribe({
|
||||
next: () => console.log(`Saved ${operation.type} ${operation.id} - ${operation.field}`),
|
||||
error: (err) => console.error(`Failed to save ${operation.type}`, err)
|
||||
next: () => {
|
||||
this.saving.set(false);
|
||||
this.toast.success('Saved');
|
||||
},
|
||||
error: (err) => {
|
||||
this.saving.set(false);
|
||||
this.toast.error(err.message || 'Failed to save');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleError(error: any): Observable<never> {
|
||||
private handleError = (error: any): Observable<never> => {
|
||||
let errorMessage = 'An unexpected error occurred';
|
||||
|
||||
if (error.error instanceof ErrorEvent) {
|
||||
@@ -269,15 +284,15 @@ export class ApiService {
|
||||
url: error.url
|
||||
});
|
||||
|
||||
throw { message: errorMessage, status: error.status, originalError: error };
|
||||
}
|
||||
return throwError(() => ({ message: errorMessage, status: error.status, originalError: error }));
|
||||
};
|
||||
}
|
||||
|
||||
interface SaveOperation {
|
||||
type: 'category' | 'subcategory' | 'item';
|
||||
id: string;
|
||||
field: string;
|
||||
value: any;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
interface ItemFilters {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './api.service';
|
||||
export * from './validation.service';
|
||||
export * from './toast.service';
|
||||
export * from './language.service';
|
||||
|
||||
@@ -194,18 +194,24 @@ export class MockDataService {
|
||||
}
|
||||
];
|
||||
|
||||
// Generate more items for testing infinite scroll
|
||||
private generateMoreItems(subcategoryId: string, count: number): Item[] {
|
||||
// Cache for generated test items so pagination is stable
|
||||
private generatedItems = new Map<string, Item[]>();
|
||||
|
||||
// Generate more items for testing infinite scroll (cached per subcategory)
|
||||
private getGeneratedItems(subcategoryId: string, count: number): Item[] {
|
||||
if (this.generatedItems.has(subcategoryId)) {
|
||||
return this.generatedItems.get(subcategoryId)!;
|
||||
}
|
||||
const items: Item[] = [];
|
||||
for (let i = 6; i <= count + 5; i++) {
|
||||
items.push({
|
||||
id: `item${i}`,
|
||||
id: `${subcategoryId}-item${i}`,
|
||||
name: `Test Product ${i}`,
|
||||
visible: Math.random() > 0.3,
|
||||
visible: i % 4 !== 0,
|
||||
priority: i,
|
||||
quantity: Math.floor(Math.random() * 100),
|
||||
price: Math.floor(Math.random() * 1000) + 100,
|
||||
discount: Math.random() > 0.7 ? Math.floor(Math.random() * 30) + 5 : 0,
|
||||
quantity: (i * 7) % 100,
|
||||
price: ((i * 13) % 1000) + 100,
|
||||
discount: i % 3 === 0 ? (i * 5) % 30 + 5 : 0,
|
||||
currency: 'USD',
|
||||
imgs: [`https://via.placeholder.com/600x400?text=Product+${i}`],
|
||||
tags: ['test'],
|
||||
@@ -214,6 +220,7 @@ export class MockDataService {
|
||||
subcategoryId
|
||||
});
|
||||
}
|
||||
this.generatedItems.set(subcategoryId, items);
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -360,7 +367,7 @@ export class MockDataService {
|
||||
}
|
||||
|
||||
getItems(subcategoryId: string, page = 1, limit = 20, search?: string, filters?: any): Observable<ItemsListResponse> {
|
||||
let allItems = [...this.items, ...this.generateMoreItems(subcategoryId, 50)];
|
||||
let allItems = [...this.items, ...this.getGeneratedItems(subcategoryId, 50)];
|
||||
|
||||
// Filter by subcategory
|
||||
allItems = allItems.filter(item => item.subcategoryId === subcategoryId);
|
||||
|
||||
Reference in New Issue
Block a user