Guest User

Untitled

a guest
Aug 13th, 2025
298
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import { Injectable, inject, effect } from '@angular/core';
  2. import {
  3.   signalStore,
  4.   withState,
  5.   withMethods,
  6.   patchState,
  7.   withHooks,
  8. } from '@ngrx/signals';
  9. import { HttpClient } from '@angular/common/http';
  10. import { firstValueFrom } from 'rxjs';
  11. import { SelectOption } from '../components/filters';
  12.  
  13. interface ProductCategoriesState {
  14.   categories: SelectOption[];
  15.   isLoading: boolean;
  16.   error: string | null;
  17. }
  18.  
  19. const initialState: ProductCategoriesState = {
  20.   categories: [],
  21.   isLoading: false,
  22.   error: null,
  23. };
  24.  
  25. @Injectable({
  26.   providedIn: 'root',
  27. })
  28. export class ProductCategoriesStore extends signalStore(
  29.   withState(initialState),
  30.   withMethods((store, http = inject(HttpClient)) => ({
  31.     async loadCategories(): Promise<void> {
  32.       console.log('ProductCategoriesStore.loadCategories() called');
  33.  
  34.       // Set loading state
  35.       patchState(store, { isLoading: true, error: null });
  36.  
  37.       try {
  38.         console.log('Fetching categories from API...');
  39.  
  40.         // Fetch categories from DummyJSON API
  41.         const categories = await firstValueFrom(
  42.           http.get<any>('https://dummyjson.com/products/categories')
  43.         );
  44.  
  45.         console.log('Raw categories from API:', categories);
  46.         console.log('Type of categories:', typeof categories);
  47.         console.log('Is array:', Array.isArray(categories));
  48.  
  49.         if (categories && Array.isArray(categories) && categories.length > 0) {
  50.           // Log the first category to see its structure
  51.           console.log('First category structure:', categories[0]);
  52.           console.log('Type of first category:', typeof categories[0]);
  53.  
  54.           // Transform to SelectOption array - handle both string and object formats
  55.           const categoryOptions: SelectOption[] = categories.map(
  56.             (category, index) => {
  57.               console.log(
  58.                 `Processing category ${index}:`,
  59.                 category,
  60.                 'Type:',
  61.                 typeof category
  62.               );
  63.  
  64.               let categoryValue: string;
  65.               let categoryLabel: string;
  66.  
  67.               if (typeof category === 'string') {
  68.                 // If it's a string, use it directly
  69.                 categoryValue = category;
  70.                 categoryLabel = this.formatCategoryLabel(category);
  71.               } else if (category && typeof category === 'object') {
  72.                 // If it's an object, try to extract the category name
  73.                 if (category.name) {
  74.                   categoryValue = category.name;
  75.                   categoryLabel = this.formatCategoryLabel(category.name);
  76.                 } else if (category.slug) {
  77.                   categoryValue = category.slug;
  78.                   categoryLabel = this.formatCategoryLabel(category.slug);
  79.                 } else {
  80.                   // Fallback: convert object to string
  81.                   categoryValue = String(category);
  82.                   categoryLabel = this.formatCategoryLabel(categoryValue);
  83.                 }
  84.               } else {
  85.                 // Fallback: convert to string
  86.                 categoryValue = String(category);
  87.                 categoryLabel = this.formatCategoryLabel(categoryValue);
  88.               }
  89.  
  90.               return {
  91.                 value: categoryValue,
  92.                 label: categoryLabel,
  93.               };
  94.             }
  95.           );
  96.  
  97.           console.log('Transformed category options:', categoryOptions);
  98.  
  99.           // Update state with loaded categories (watchState will handle sorting)
  100.           patchState(store, {
  101.             categories: categoryOptions,
  102.             isLoading: false,
  103.             error: null,
  104.           });
  105.  
  106.           console.log(
  107.             'Categories successfully loaded into store (will be auto-sorted)'
  108.           );
  109.         } else {
  110.           console.warn('No categories received from API or not an array');
  111.           patchState(store, {
  112.             categories: [],
  113.             isLoading: false,
  114.             error: 'No categories found',
  115.           });
  116.         }
  117.       } catch (error) {
  118.         console.error('Error loading product categories:', error);
  119.  
  120.         // Update state with error
  121.         patchState(store, {
  122.           categories: [],
  123.           isLoading: false,
  124.           error:
  125.             error instanceof Error
  126.               ? error.message
  127.               : 'Failed to load categories',
  128.         });
  129.       }
  130.     },
  131.  
  132.     // Helper method to format category labels - now with safety checks
  133.     formatCategoryLabel(category: any): string {
  134.       console.log(
  135.         'formatCategoryLabel called with:',
  136.         category,
  137.         'Type:',
  138.         typeof category
  139.       );
  140.  
  141.       // Ensure we have a string to work with
  142.       const categoryString = String(category);
  143.  
  144.       // Check if it contains dashes to split
  145.       if (categoryString.includes('-')) {
  146.         return categoryString
  147.           .split('-')
  148.           .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
  149.           .join(' ');
  150.       } else {
  151.         // If no dashes, just capitalize first letter
  152.         return categoryString.charAt(0).toUpperCase() + categoryString.slice(1);
  153.       }
  154.     },
  155.  
  156.     // Method to clear error state
  157.     clearError(): void {
  158.       patchState(store, { error: null });
  159.     },
  160.  
  161.     // Method to reset store to initial state
  162.     reset(): void {
  163.       patchState(store, initialState);
  164.     },
  165.  
  166.     // CRUD Operations for Categories
  167.  
  168.     // Add a new category to the store
  169.     addCategory(newCategory: SelectOption): void {
  170.       console.log(
  171.         'ProductCategoriesStore.addCategory() called with:',
  172.         newCategory
  173.       );
  174.  
  175.       const currentCategories = store.categories();
  176.  
  177.       // Check if category already exists
  178.       const exists = currentCategories.some(
  179.         (cat) => cat.value.toLowerCase() === newCategory.value.toLowerCase()
  180.       );
  181.  
  182.       if (exists) {
  183.         console.warn('Category already exists:', newCategory.value);
  184.         patchState(store, {
  185.           error: `Category "${newCategory.label}" already exists`,
  186.         });
  187.         return;
  188.       }
  189.  
  190.       // Add the new category (watchState will handle sorting)
  191.       const updatedCategories = [...currentCategories, newCategory];
  192.  
  193.       patchState(store, {
  194.         categories: updatedCategories,
  195.         error: null,
  196.       });
  197.  
  198.       console.log(
  199.         'Category added successfully (will be auto-sorted):',
  200.         newCategory
  201.       );
  202.     },
  203.  
  204.     // Update an existing category in the store
  205.     updateCategory(categoryValue: string, updatedCategory: SelectOption): void {
  206.       console.log('ProductCategoriesStore.updateCategory() called:', {
  207.         categoryValue,
  208.         updatedCategory,
  209.       });
  210.  
  211.       const currentCategories = store.categories();
  212.       const categoryIndex = currentCategories.findIndex(
  213.         (cat) => cat.value === categoryValue
  214.       );
  215.  
  216.       if (categoryIndex === -1) {
  217.         console.warn('Category not found for update:', categoryValue);
  218.         patchState(store, {
  219.           error: `Category "${categoryValue}" not found`,
  220.         });
  221.         return;
  222.       }
  223.  
  224.       // Create updated categories array (watchState will handle sorting)
  225.       const updatedCategories = [...currentCategories];
  226.       updatedCategories[categoryIndex] = updatedCategory;
  227.  
  228.       patchState(store, {
  229.         categories: updatedCategories,
  230.         error: null,
  231.       });
  232.  
  233.       console.log(
  234.         'Category updated successfully (will be auto-sorted):',
  235.         updatedCategory
  236.       );
  237.     },
  238.  
  239.     // Delete a category from the store
  240.     deleteCategory(categoryValue: string): void {
  241.       console.log(
  242.         'ProductCategoriesStore.deleteCategory() called with:',
  243.         categoryValue
  244.       );
  245.  
  246.       const currentCategories = store.categories();
  247.       const categoryExists = currentCategories.some(
  248.         (cat) => cat.value === categoryValue
  249.       );
  250.  
  251.       if (!categoryExists) {
  252.         console.warn('Category not found for deletion:', categoryValue);
  253.         patchState(store, {
  254.           error: `Category "${categoryValue}" not found`,
  255.         });
  256.         return;
  257.       }
  258.  
  259.       // Filter out the category to delete
  260.       const updatedCategories = currentCategories.filter(
  261.         (cat) => cat.value !== categoryValue
  262.       );
  263.  
  264.       patchState(store, {
  265.         categories: updatedCategories,
  266.         error: null,
  267.       });
  268.  
  269.       console.log('Category deleted successfully:', categoryValue);
  270.     },
  271.  
  272.     // Bulk operations
  273.  
  274.     // Add multiple categories at once
  275.     addCategories(newCategories: SelectOption[]): void {
  276.       console.log(
  277.         'ProductCategoriesStore.addCategories() called with:',
  278.         newCategories
  279.       );
  280.  
  281.       const currentCategories = store.categories();
  282.       const categoriesToAdd: SelectOption[] = [];
  283.  
  284.       // Filter out duplicates
  285.       newCategories.forEach((newCat) => {
  286.         const exists = currentCategories.some(
  287.           (cat) => cat.value.toLowerCase() === newCat.value.toLowerCase()
  288.         );
  289.         if (!exists) {
  290.           categoriesToAdd.push(newCat);
  291.         }
  292.       });
  293.  
  294.       if (categoriesToAdd.length === 0) {
  295.         console.warn('No new categories to add (all duplicates)');
  296.         return;
  297.       }
  298.  
  299.       const updatedCategories = [...currentCategories, ...categoriesToAdd];
  300.  
  301.       patchState(store, {
  302.         categories: updatedCategories,
  303.         error: null,
  304.       });
  305.  
  306.       console.log(
  307.         `${categoriesToAdd.length} categories added successfully (will be auto-sorted)`
  308.       );
  309.     },
  310.  
  311.     // Delete multiple categories at once
  312.     deleteCategories(categoryValues: string[]): void {
  313.       console.log(
  314.         'ProductCategoriesStore.deleteCategories() called with:',
  315.         categoryValues
  316.       );
  317.  
  318.       const currentCategories = store.categories();
  319.       const updatedCategories = currentCategories.filter(
  320.         (cat) => !categoryValues.includes(cat.value)
  321.       );
  322.  
  323.       const deletedCount = currentCategories.length - updatedCategories.length;
  324.  
  325.       patchState(store, {
  326.         categories: updatedCategories,
  327.         error: null,
  328.       });
  329.  
  330.       console.log(`${deletedCount} categories deleted successfully`);
  331.     },
  332.  
  333.     // Find a specific category
  334.     findCategory(categoryValue: string): SelectOption | undefined {
  335.       console.log(
  336.         'ProductCategoriesStore.findCategory() called with:',
  337.         categoryValue
  338.       );
  339.       const currentCategories = store.categories();
  340.       return currentCategories.find((cat) => cat.value === categoryValue);
  341.     },
  342.  
  343.     // Check if a category exists
  344.     categoryExists(categoryValue: string): boolean {
  345.       const currentCategories = store.categories();
  346.       return currentCategories.some((cat) => cat.value === categoryValue);
  347.     },
  348.   })),
  349.  
  350.   // ✅ AUTOMATIC SORTING using withHooks and effect
  351.   withHooks({
  352.     onInit(store) {
  353.       // Effect to automatically sort categories whenever they change
  354.       effect(() => {
  355.         const categories = store.categories();
  356.  
  357.         // Only sort if categories exist and we have more than one category
  358.         if (categories && categories.length > 1) {
  359.           // Check if the array is already sorted to avoid unnecessary updates
  360.           const sorted = [...categories].sort((a, b) =>
  361.             a.label.localeCompare(b.label)
  362.           );
  363.  
  364.           // Only update if the order actually changed
  365.           const needsSorting = categories.some(
  366.             (cat, index) => cat.value !== sorted[index]?.value
  367.           );
  368.  
  369.           if (needsSorting) {
  370.             console.log('Categories not sorted, auto-sorting now...');
  371.             patchState(store, { categories: sorted });
  372.           }
  373.         }
  374.       });
  375.     },
  376.   })
  377. ) {}
  378.  
Advertisement
Add Comment
Please, Sign In to add comment