All files / src/api/inventory listFetcher.ts

91.79% Statements 123/134
73.33% Branches 11/15
100% Functions 2/2
91.79% Lines 123/134

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 1351x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 1x 1x 1x 1x 1x             1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 2x 2x 3x 2x   3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x         1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x  
/**
 * @file listFetcher.ts
 * @module api/inventory/listFetcher
 *
 * @summary
 * Inventory list fetcher API call.
 * Handles HTTP request, response envelope parsing, and error recovery.
 *
 * @enterprise
 * - Single API endpoint: GET /api/inventory with pagination params
 * - Tolerant of response envelope variations (plain array, Spring Page format)
 * - Graceful error handling: returns empty page on network failure
 * - Type-safe response structure
 */
 
import http from '../httpClient';
import type { InventoryListParams, InventoryListResponse, InventoryRow } from './types';
import { toInventoryRow } from './rowNormalizers';
import { pickNumber } from './utils';
 
/**
 * Extract an array of rows from various envelope styles.
 * Supports:
 *  - plain array: [dto, dto, ...]
 *  - Spring Page: { content: [...], totalElements: 5 }
 *  - custom: { items: [...] }
 *
 * @param data - Response data from /api/inventory
 * @returns Array of raw DTO objects to normalize
 *
 * @example
 * ```typescript
 * const rows = extractRows(response.data);
 * ```
 */
const extractRows = (data: unknown): unknown[] => {
  if (Array.isArray(data)) return data;
  if (typeof data !== 'object' || data === null) return [];
 
  const r = data as Record<string, unknown>;
 
  // Try Spring Page 'content' field
  if (Array.isArray(r.content)) return r.content;

  // Try custom 'items' field
  if (Array.isArray(r.items)) return r.items;

  return [];
};
 
/**
 * Fetch a page of inventory items from the backend.
 * Handles pagination, search, filtering, and sorting.
 * Works with various response envelope formats transparently.
 *
 * @param params - Query params: page, pageSize, q (search), supplierId, sort
 * @returns Paginated response with items and total count
 *
 * @example
 * ```typescript
 * const response = await getInventoryPage({
 *   page: 0,
 *   pageSize: 20,
 *   q: 'bolt',
 *   supplierId: undefined,
 *   sort: 'name,asc'
 * });
 * ```
 */
export const getInventoryPage = async (
  params: InventoryListParams,
): Promise<InventoryListResponse> => {
  try {
    const { page, pageSize, q, supplierId, sort } = params;
 
    const resp = await http.get('/api/inventory', {
      params: {
        page,
        pageSize,
        q: q ?? '',
        supplierId,
        sort,
      },
    });
 
    // Extract response.data from Axios response
    const data: unknown = typeof resp === 'object' && resp !== null && 'data' in resp
      ? (resp as unknown as Record<string, unknown>).data
      : {};
 
    const rowsRaw = extractRows(data);
 
    // Calculate total count from response
    let total = 0;
    if (Array.isArray(data)) {
      // Plain array: total = array length
      total = data.length;
    } else if (typeof data === 'object' && data !== null) {
      const r = data as Record<string, unknown>;
      // Try Spring Page field first (totalElements), then custom (total)
      const totalElements = pickNumber(r, 'totalElements');
      const totalField = pickNumber(r, 'total');
 
      if (typeof totalElements === 'number') {
        total = totalElements;
      } else if (typeof totalField === 'number') {
        total = totalField;
      } else {
        total = rowsRaw.length;
      }
    }
 
    // Normalize all rows and filter out failed normalizations
    const items: InventoryRow[] = rowsRaw
      .map(toInventoryRow)
      .filter((r): r is InventoryRow => r !== null);
 
    return {
      items,
      total,
      page,
      pageSize,
    };
  } catch (error) {
    // Network error: return empty page
    console.error('[getInventoryPage] Error fetching inventory:', error);
    return {
      items: [],
      total: 0,
      page: params.page,
      pageSize: params.pageSize,
    };
  }
};