All files / src/components/Recipe/RecipeSearch consts.ts

0% Statements 0/30
0% Branches 0/37
0% Functions 0/4
0% Lines 0/27

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                                                                                                                                                                           
import { z } from 'zod';
import type { RecipeSearchFilters } from './types';
 
export const DEFAULT_FILTERS: RecipeSearchFilters = {
  title: '',
  categoryKey: null,
  difficultyLevelKey: null,
  labelKeys: [],
  maxCookingTime: '',
};
 
/* ─── Zod validation schema ───────────────────── */
 
export const recipeSearchSchema = z.object({
  title: z.string().max(100),
  categoryKey: z.string().nullable(),
  difficultyLevelKey: z.string().nullable(),
  labelKeys: z.array(z.string()),
  maxCookingTime: z.union([z.number().int().positive(), z.literal('')]),
});
 
const PARAM_TITLE = 'q';
const PARAM_CATEGORY = 'category';
const PARAM_DIFFICULTY = 'difficulty';
const PARAM_LABELS = 'labels';
const PARAM_MAX_TIME = 'maxTime';
 
/** Serialize filters into URLSearchParams (omitting defaults). */
export const filtersToSearchParams = (
  filters: RecipeSearchFilters,
): URLSearchParams => {
  const params = new URLSearchParams();
  if (filters.title.trim()) params.set(PARAM_TITLE, filters.title.trim());
  if (filters.categoryKey) params.set(PARAM_CATEGORY, filters.categoryKey);
  if (filters.difficultyLevelKey)
    params.set(PARAM_DIFFICULTY, filters.difficultyLevelKey);
  if (filters.labelKeys.length > 0)
    params.set(PARAM_LABELS, filters.labelKeys.join(','));
  if (filters.maxCookingTime)
    params.set(PARAM_MAX_TIME, String(filters.maxCookingTime));
  return params;
};
 
/** Parse URLSearchParams back to RecipeSearchFilters. */
export const searchParamsToFilters = (
  params: URLSearchParams,
): RecipeSearchFilters => {
  const labelsRaw = params.get(PARAM_LABELS);
  const maxTimeRaw = params.get(PARAM_MAX_TIME);
  return {
    title: params.get(PARAM_TITLE) ?? '',
    categoryKey: params.get(PARAM_CATEGORY) ?? null,
    difficultyLevelKey: params.get(PARAM_DIFFICULTY) ?? null,
    labelKeys: labelsRaw ? labelsRaw.split(',').filter(Boolean) : [],
    maxCookingTime: maxTimeRaw ? Number(maxTimeRaw) : '',
  };
};
 
/** Returns true when at least one filter differs from defaults. */
export const isSearchActive = (filters: RecipeSearchFilters): boolean => {
  return (
    filters.title.trim() !== '' ||
    filters.categoryKey !== null ||
    filters.difficultyLevelKey !== null ||
    filters.labelKeys.length > 0 ||
    filters.maxCookingTime !== ''
  );
};
 
/** Convert client-side filters to the GraphQL RecipeFilterInput shape. */
export const buildQueryFilter = (filters: RecipeSearchFilters) => {
  if (!isSearchActive(filters)) return undefined;
 
  return {
    ...(filters.title.trim() && { title: filters.title.trim() }),
    ...(filters.categoryKey && { categoryKey: filters.categoryKey }),
    ...(filters.difficultyLevelKey && {
      difficultyLevelKey: filters.difficultyLevelKey,
    }),
    ...(filters.labelKeys.length > 0 && { labelKeys: filters.labelKeys }),
    ...(filters.maxCookingTime && {
      maxCookingTime: Number(filters.maxCookingTime),
    }),
  };
};