-
+
{{ good.name }}
@if (!good.is_default_guild_good) {
*
@@ -27,8 +27,9 @@
-
}
diff --git a/src/app/domains/pos/modules/landing/components/list-view.component.ts b/src/app/domains/pos/modules/landing/components/list-view.component.ts
index 6473798..33d45b8 100644
--- a/src/app/domains/pos/modules/landing/components/list-view.component.ts
+++ b/src/app/domains/pos/modules/landing/components/list-view.component.ts
@@ -13,26 +13,29 @@ import { ButtonDirective } from 'primeng/button';
import { Skeleton } from 'primeng/skeleton';
import images from 'src/assets/images';
import { PosLandingStore } from '../store/main.store';
+import { FavoriteCTAComponent } from './favorite-CTA.component';
@Component({
selector: 'pos-goods-list-view',
templateUrl: './list-view.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [ButtonDirective, Skeleton],
+ imports: [ButtonDirective, Skeleton, FavoriteCTAComponent],
})
export class PosGoodsListViewComponent {
private readonly store = inject(PosLandingStore);
+
@Output() onAdd = new EventEmitter
();
- goods = computed(() => this.store.filteredGoods());
+ goodPlaceholder = images.placeholders.default;
+
private readonly pageSize = 120;
readonly renderLimit = signal(this.pageSize);
+
+ goods = computed(() => this.store.filteredGoods());
visibleGoods = computed(() => this.goods()?.slice(0, this.renderLimit()));
hasMore = computed(() => (this.goods()?.length || 0) > (this.visibleGoods()?.length || 0));
loading = computed(() => this.store.getGoodsLoading());
- goodPlaceholder = images.placeholders.default;
-
addGood(good: IGoodResponse) {
this.onAdd.emit(good);
}
diff --git a/src/app/domains/pos/modules/landing/constants/apiRoutes/favorite.ts b/src/app/domains/pos/modules/landing/constants/apiRoutes/favorite.ts
new file mode 100644
index 0000000..654c7a6
--- /dev/null
+++ b/src/app/domains/pos/modules/landing/constants/apiRoutes/favorite.ts
@@ -0,0 +1,5 @@
+const baseUrl = (good_id: string) => `/api/v1/pos/goods/${good_id}/favorite`;
+
+export const POS_GOOD_FAVORITE_API_ROUTES = {
+ favorite: (good_id: string) => `${baseUrl(good_id)}`,
+};
diff --git a/src/app/domains/pos/modules/landing/constants/apiRoutes/index.ts b/src/app/domains/pos/modules/landing/constants/apiRoutes/index.ts
index 3add49b..f6ff62a 100644
--- a/src/app/domains/pos/modules/landing/constants/apiRoutes/index.ts
+++ b/src/app/domains/pos/modules/landing/constants/apiRoutes/index.ts
@@ -1,3 +1,4 @@
+export * from './favorite';
const baseUrl = '/api/v1/pos';
export const POS_API_ROUTES = {
diff --git a/src/app/domains/pos/modules/landing/services/favorite.service.ts b/src/app/domains/pos/modules/landing/services/favorite.service.ts
new file mode 100644
index 0000000..f473b67
--- /dev/null
+++ b/src/app/domains/pos/modules/landing/services/favorite.service.ts
@@ -0,0 +1,21 @@
+import { IPosInfoRawResponse, IPosInfoResponse } from '@/domains/pos/models/pos.io';
+import { IPosProfileRawResponse, IPosProfileResponse } from '@/domains/pos/models/profile.io';
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { POS_GOOD_FAVORITE_API_ROUTES } from '../constants';
+
+@Injectable({ providedIn: 'root' })
+export class PosGoodFavoriteService {
+ constructor(private http: HttpClient) {}
+
+ private apiRoutes = POS_GOOD_FAVORITE_API_ROUTES;
+
+ attach(good_id: string): Observable {
+ return this.http.post(this.apiRoutes.favorite(good_id), {});
+ }
+
+ detach(good_id: string): Observable {
+ return this.http.delete(this.apiRoutes.favorite(good_id));
+ }
+}
diff --git a/src/app/domains/pos/modules/landing/store/main.store.ts b/src/app/domains/pos/modules/landing/store/main.store.ts
index 19b9d32..058893e 100644
--- a/src/app/domains/pos/modules/landing/store/main.store.ts
+++ b/src/app/domains/pos/modules/landing/store/main.store.ts
@@ -5,12 +5,13 @@ import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils';
import { computed, inject, Injectable, signal } from '@angular/core';
-import { catchError, finalize, map, throwError } from 'rxjs';
+import { catchError, finalize, firstValueFrom, map, throwError } from 'rxjs';
import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
import { IPosInOrderGood, IPosOrderItem } from '../models/types';
import { PosService } from '../services/main.service';
export type TViewType = 'list' | 'grid';
+const FAVORITE_CATEGORY_ID = 'favorite';
interface IPosLandingState {
getGoodsLoading: boolean;
@@ -74,6 +75,9 @@ export class PosLandingStore {
if (!this.activeGoodCategory()) {
return this.state$().goods;
}
+ if (this.activeGoodCategory() === FAVORITE_CATEGORY_ID) {
+ return this.state$().goods?.filter((good) => good.is_favorite);
+ }
return this.state$().goods?.filter((s) => s.category.id === this.state$().activeGoodCategory);
});
readonly filteredGoods = computed(() =>
@@ -115,55 +119,38 @@ export class PosLandingStore {
this.state$.set({ ...INITIAL_POS_STATE });
}
- initial() {
- this.getGoods();
- this.getGoodCategories();
+ async initial() {
+ await Promise.all([this.getGoods(), this.getGoodCategories()]);
+
+ this.prepareGoodCategories();
}
fillInitial(data: Partial) {
this.state$.set({ ...INITIAL_POS_STATE, ...data });
}
- getGoods() {
+ private async getGoods() {
this.setState({ getGoodsLoading: true });
- this.service.getGoods(this.state$().searchQuery).subscribe({
- next: (res) => {
- this.setState({ goods: res.data, getGoodsLoading: false });
- },
- error: () => {
- this.setState({ getGoodsLoading: false });
- },
- });
+ try {
+ const res = await firstValueFrom(this.service.getGoods(this.state$().searchQuery));
+ this.setState({ goods: res.data, getGoodsLoading: false });
+ } catch {
+ this.setState({ getGoodsLoading: false });
+ }
}
- getGoodCategories() {
+ private async getGoodCategories() {
this.setState({ getGoodCategoriesLoading: true });
- this.service
- .getGoodCategories()
- .pipe(
- map((res) => {
- return [
- {
- id: '',
- name: 'همه',
- goods_count: res.data.reduce((acc, curr) => acc + curr.goods_count, 0),
- },
- ...res.data,
- ];
- }),
- )
- .subscribe({
- next: (res) => {
- this.setState({
- goodCategories: res,
- getGoodCategoriesLoading: false,
- activeGoodCategory: res[0]?.id,
- });
- },
- error: () => {
- this.setState({ getGoodCategoriesLoading: false });
- },
+ try {
+ const res = await firstValueFrom(this.service.getGoodCategories());
+
+ this.setState({
+ goodCategories: res.data,
+ getGoodCategoriesLoading: false,
});
+ } catch {
+ this.setState({ getGoodCategoriesLoading: false });
+ }
}
changeActiveCategory(categoryId: string) {
@@ -210,6 +197,46 @@ export class PosLandingStore {
this.setState({ searchQuery });
}
+ toggleGoodFavorite(goodId: string, newState: boolean) {
+ const updatedGoods = this.state$().goods?.map((good) => {
+ if (good.id === goodId) {
+ return { ...good, is_favorite: newState };
+ }
+ return good;
+ });
+ this.setState({ goods: updatedGoods || this.state$().goods });
+ this.prepareGoodCategories();
+ }
+
+ private prepareGoodCategories() {
+ const rawCategories =
+ this.goodCategories()?.filter(({ id }) => id !== '' && id !== FAVORITE_CATEGORY_ID) || [];
+ const favoriteGoodsCount = this.goods()?.filter((good) => good.is_favorite).length || 0;
+ const goodsCount = this.goods()?.length || 0;
+ const preparedGoodCategories = rawCategories;
+
+ if (favoriteGoodsCount > 0) {
+ preparedGoodCategories.unshift({
+ id: FAVORITE_CATEGORY_ID,
+ name: 'علاقهمندیها',
+ goods_count: favoriteGoodsCount,
+ });
+ }
+ preparedGoodCategories.unshift({
+ id: '',
+ name: 'همه',
+ goods_count: goodsCount,
+ });
+
+ this.setState({
+ goodCategories: preparedGoodCategories,
+ });
+
+ if (!this.activeGoodCategory()) {
+ this.setState({ activeGoodCategory: '' });
+ }
+ }
+
filterGoods() {}
resetInOrderGoods() {
diff --git a/src/environments/environment.tis.ts b/src/environments/environment.tis.ts
index 0ac90f5..8873f90 100644
--- a/src/environments/environment.tis.ts
+++ b/src/environments/environment.tis.ts
@@ -1,8 +1,8 @@
// TIS tenant environment configuration
export const environment = {
production: true,
- apiBaseUrl: 'https://psp-api.shift-am.ir',
- // apiBaseUrl: 'http://192.168.128.73:5002',
+ // apiBaseUrl: 'https://psp-api.shift-am.ir',
+ apiBaseUrl: 'http://192.168.128.73:5002',
host: 'localhost',
port: 5000,
enableLogging: false,