-
{title}
+
+
{title}
)}
{children}
diff --git a/src/components/shared/form/Input.tsx b/src/components/shared/form/Input.tsx
index 4b2b1fc..c4aa4c2 100644
--- a/src/components/shared/form/Input.tsx
+++ b/src/components/shared/form/Input.tsx
@@ -57,7 +57,7 @@ export default function Input({
disabled={disabled}
min={min}
max={max}
- className={`px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-100 ${
+ className={`px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-100 focus:border-purple-100${
disabled ? 'bg-gray-100 cursor-not-allowed' : ''
} ${inputClassName}`}
{...props}
diff --git a/src/components/shared/modal/RefreshModal.tsx b/src/components/shared/modal/RefreshModal.tsx
index 2c876d1..37e23f5 100644
--- a/src/components/shared/modal/RefreshModal.tsx
+++ b/src/components/shared/modal/RefreshModal.tsx
@@ -11,6 +11,7 @@ import { RecipeGenerationRequest } from "@/types";
import { useAuthStore } from "@/store/useAuthStore";
import { useMealPrepStore } from "@/store/useMealPrepStore";
import { useMealPrepFiltersStore } from "@/store/useMealPrepFiltersStore";
+import { useFilterOptionsCache } from "@/hooks/useFilterOptionsCache";
export const RefreshModal = ({
type = "recipe",
@@ -34,7 +35,8 @@ export const RefreshModal = ({
}) => {
const [isLoading, setIsLoading] = useState(false);
const { filters } = useRecipeFiltersStore();
- const { filters: mealPrepFilters } = useMealPrepFiltersStore();
+ const { filters: mealPrepFilters } = useMealPrepFiltersStore();
+ const { mealOptions, timeOptions, difficultyOptions, dietOptions } = useFilterOptionsCache();
const { ingredients } = useIngredientsStore();
const { filteredRecipes, replaceRecipe: replaceRecipeInStore } =
@@ -48,6 +50,7 @@ export const RefreshModal = ({
setIsLoading(true);
const currentIds = filteredRecipes.map((r) => r.id);
+ const currentMealPrepIds = filteredMealPrep.map((m) => m.id);
console.log("ingredients", ingredients);
const ingredientList = ingredients
@@ -55,7 +58,7 @@ export const RefreshModal = ({
.map((ingredient) => ({
name: ingredient.name,
quantity: ingredient.quantity,
- unit_id: Number(ingredient.unit),
+ unit_id: Number(ingredient.unit.id),
}));
let refreshRequest: any;
@@ -70,7 +73,9 @@ export const RefreshModal = ({
: Number(filters.time),
servings: filters.people,
cook_level_id: Number(filters.difficulty),
- type_ids: filters.types.map((t) => Number(t)),
+ type_ids: filters.types
+ .map((typeValue) => mealOptions.find((opt) => opt.value === typeValue)?.key)
+ .filter((key): key is number => key !== undefined),
diet_id:
!filters.diet || filters.diet === ""
? null
@@ -93,14 +98,20 @@ export const RefreshModal = ({
: Number(mealPrepFilters.time),
servings: mealPrepFilters.people,
cook_level_id: Number(mealPrepFilters.difficulty),
- type_ids: mealPrepFilters.types.map((t) => Number(t)),
+ type_ids: mealPrepFilters.types
+ .map((typeValue) => mealOptions.find((opt) => opt.value === typeValue)?.key)
+ .filter((key): key is number => key !== undefined),
diet_id:
!mealPrepFilters.diet || mealPrepFilters.diet === ""
? null
: Number(mealPrepFilters.diet),
allergies_ids: mealPrepFilters.allergies_ids,
dietary_needs_ids: mealPrepFilters.dietary_needs_ids,
- freeze: mealPrepFilters.freeze, // solo si lo tenés en tu filtro, opcional
+ freeze: mealPrepFilters.freeze,
+ },
+ configuration: {
+ size: 1,
+ not_include: currentMealPrepIds,
},
};
}
@@ -114,6 +125,10 @@ export const RefreshModal = ({
}
if (newItem) {
+ console.log("RefreshModal - newItem recibido:", newItem); // Debug temporal
+ console.log("RefreshModal - newItem.title:", newItem.title); // Debug temporal
+ console.log("RefreshModal - type:", type); // Debug temporal
+
if (type === "recipe") {
replaceRecipeInStore(recipeId, newItem);
showSuccess(
@@ -121,10 +136,16 @@ export const RefreshModal = ({
`Se generó una nueva receta: "${newItem.name}"`
);
} else if (type === "meal-prep") {
+ console.log("RefreshModal - Antes de replaceMealPrepInStore:", { recipeId, newItem }); // Debug temporal
replaceMealPrepInStore(recipeId, newItem);
+ console.log("RefreshModal - Después de replaceMealPrepInStore"); // Debug temporal
+
+ const title = newItem.title || newItem.name || "Sin título";
+ console.log("RefreshModal - título final:", title); // Debug temporal
+
showSuccess(
"Meal prep refrescado exitosamente",
- `Se generó un nuevo meal prep: "${newItem.name}"`
+ `Se generó un nuevo meal prep: "${title}"`
);
}
onClose();
diff --git a/src/components/shared/modal/UnfavoriteModal.tsx b/src/components/shared/modal/UnfavoriteModal.tsx
index 84bda4c..312c1e4 100644
--- a/src/components/shared/modal/UnfavoriteModal.tsx
+++ b/src/components/shared/modal/UnfavoriteModal.tsx
@@ -52,12 +52,9 @@ const handleConfirmUnfavorite = async () => {
onConfirm={handleConfirmUnfavorite}
onCancel={onClose}
>
-
+
¿Estás seguro de que querés eliminar {recipeText} de tus {itemType} favoritos?
-
- Esta acción no se puede deshacer.
-
);
};
diff --git a/src/components/shared/skeleton/MealPrepCardSkeleton.tsx b/src/components/shared/skeleton/MealPrepCardSkeleton.tsx
new file mode 100644
index 0000000..ce956f9
--- /dev/null
+++ b/src/components/shared/skeleton/MealPrepCardSkeleton.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { BaseSkeleton } from './BaseSkeleton';
+
+export function MealPrepCardSkeleton() {
+ return (
+
+ {/* Título del meal prep */}
+
+
+
+
+ {/* 3 recetas en horizontal */}
+
+ {[1, 2, 3].map((index) => (
+
+ {/* Imagen de la receta */}
+
+
+ {/* Título de la receta */}
+
+
+ ))}
+
+
+ {/* Área de tiempo y favoritos */}
+
+
+ );
+}
+
+export default MealPrepCardSkeleton;
\ No newline at end of file
diff --git a/src/components/shared/skeleton/MealPrepDetailSkeleton.tsx b/src/components/shared/skeleton/MealPrepDetailSkeleton.tsx
new file mode 100644
index 0000000..f791285
--- /dev/null
+++ b/src/components/shared/skeleton/MealPrepDetailSkeleton.tsx
@@ -0,0 +1,92 @@
+import { BaseSkeleton } from './BaseSkeleton';
+import BackgroundLayers from '@/components/shared/BackgroundLayers';
+import ContainerShadow from '@/components/shared/containers/ContainerShadow';
+
+const RecipeTagsSkeleton = () => (
+
+ {Array(3).fill(0).map((_, i) => (
+
+ ))}
+
+);
+
+const HeaderSkeleton = () => (
+
+);
+
+const StepsSkeleton = () => (
+
+
+
+ {Array(4).fill(0).map((_, i) => (
+
+
+
+
+ ))}
+
+
+);
+
+const SidebarSkeleton = () => (
+
+ {/* PDF Download Button */}
+
+
+ {/* Ingredients Section */}
+
+
+ {Array(6).fill(0).map((_, i) => (
+
+ ))}
+
+
+ {/* Observation Section */}
+
+
+
+
+
+
+);
+
+const BackButtonSkeleton = () => (
+
+
+
+);
+
+export const MealPrepDetailSkeleton = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/src/services/generateMealPrepRecipes.service.ts b/src/services/generateMealPrepRecipes.service.ts
index 855a8c0..f08900c 100644
--- a/src/services/generateMealPrepRecipes.service.ts
+++ b/src/services/generateMealPrepRecipes.service.ts
@@ -7,7 +7,6 @@ import apiClient from "@/lib/axios.config";
export const generateMealPrepRecipes = async (requestData: MealPrepRequest): Promise
=> {
try {
- // Simulación de delay para desarrollo
await new Promise(resolve => setTimeout(resolve, 2000));
const response = await apiClient.post('/meal-preps',
@@ -26,15 +25,30 @@ export const generateMealPrepRecipes = async (requestData: MealPrepRequest): Pro
};
export const refreshMealPrep = async (
informationRecipe: MealPrepRequest
-) => {
+): Promise => {
try {
const response = await apiClient.post("/meal-preps", informationRecipe);
+
- const mappedData = response.data.map((recipe: any) => ({
- ...recipe,
- preparationTime:
- recipe.preparation_time?.description || recipe.preparationTime,
- }));
+ const mappedData = response.data.map((mealPrep: any) => {
+
+ const mapped = {
+ id: mealPrep.id,
+ title: mealPrep.title,
+ estimated_cooking_time: mealPrep.estimated_cooking_time,
+ totalPortions: mealPrep.servings || 4,
+ ingredients: mealPrep.ingredients || [],
+ observation: mealPrep.observation,
+ description: mealPrep.description,
+ recipes: mealPrep.recipes || [],
+ steps: mealPrep.steps || [],
+ isFavorite: mealPrep.favorite || false,
+ preparationTime:
+ mealPrep.preparation_time?.description || mealPrep.preparationTime,
+ };
+
+ return mapped;
+ });
return mappedData[0];
} catch (error) {
@@ -43,7 +57,6 @@ export const refreshMealPrep = async (
};
export const getMealPrepById = async (id: number) => {
try {
- // Simulación de delay para mostrar el loader (solo para desarrollo)
await new Promise(resolve => setTimeout(resolve, 1500));
const response: ApiResponse = await apiClient.get(`/meal-preps/${id}`,
@@ -55,7 +68,6 @@ export const getMealPrepById = async (id: number) => {
);
return response.data;
} catch (error) {
- // Propagamos el error para que pueda ser manejado por el componente
throw error;
}
};
\ No newline at end of file
diff --git a/src/store/useFavoritesStore.ts b/src/store/useFavoritesStore.ts
index ee5d45d..3c21c95 100644
--- a/src/store/useFavoritesStore.ts
+++ b/src/store/useFavoritesStore.ts
@@ -7,41 +7,81 @@ export const useFavoritesStore = create()(
(set, get) => ({
favoriteRecipeIds: [],
favoriteMealPrepIds: [],
+ // Nuevos arrays para rastrear cambios locales
+ addedLocallyRecipeIds: [],
+ removedLocallyRecipeIds: [],
+ addedLocallyMealPrepIds: [],
+ removedLocallyMealPrepIds: [],
addFavoriteRecipe: (recipeId: number) => {
set((state) => ({
- favoriteRecipeIds: [...new Set([...state.favoriteRecipeIds, recipeId])]
+ favoriteRecipeIds: [...new Set([...state.favoriteRecipeIds, recipeId])],
+ addedLocallyRecipeIds: [...new Set([...state.addedLocallyRecipeIds, recipeId])],
+ // Remover de eliminados si estaba ahí
+ removedLocallyRecipeIds: state.removedLocallyRecipeIds.filter(id => id !== recipeId)
}));
},
removeFavoriteRecipe: (recipeId: number) => {
set((state) => ({
- favoriteRecipeIds: state.favoriteRecipeIds.filter(id => id !== recipeId)
+ favoriteRecipeIds: state.favoriteRecipeIds.filter(id => id !== recipeId),
+ removedLocallyRecipeIds: [...new Set([...state.removedLocallyRecipeIds, recipeId])],
+ // Remover de agregados si estaba ahí
+ addedLocallyRecipeIds: state.addedLocallyRecipeIds.filter(id => id !== recipeId)
}));
},
addFavoriteMealPrep: (mealPrepId: number) => {
set((state) => ({
- favoriteMealPrepIds: [...new Set([...state.favoriteMealPrepIds, mealPrepId])]
+ favoriteMealPrepIds: [...new Set([...state.favoriteMealPrepIds, mealPrepId])],
+ addedLocallyMealPrepIds: [...new Set([...state.addedLocallyMealPrepIds, mealPrepId])],
+ removedLocallyMealPrepIds: state.removedLocallyMealPrepIds.filter(id => id !== mealPrepId)
}));
},
removeFavoriteMealPrep: (mealPrepId: number) => {
set((state) => ({
- favoriteMealPrepIds: state.favoriteMealPrepIds.filter(id => id !== mealPrepId)
+ favoriteMealPrepIds: state.favoriteMealPrepIds.filter(id => id !== mealPrepId),
+ removedLocallyMealPrepIds: [...new Set([...state.removedLocallyMealPrepIds, mealPrepId])],
+ addedLocallyMealPrepIds: state.addedLocallyMealPrepIds.filter(id => id !== mealPrepId)
}));
},
- isFavoriteRecipe: (recipeId: number) => {
- return get().favoriteRecipeIds.includes(recipeId);
+ // Nueva función que combina estado del servidor con cambios locales
+ isFavoriteRecipe: (recipeId: number, serverState?: boolean) => {
+ const state = get();
+ // Si fue eliminado localmente, NO es favorito
+ if (state.removedLocallyRecipeIds.includes(recipeId)) {
+ return false;
+ }
+ // Si fue agregado localmente, SÍ es favorito
+ if (state.addedLocallyRecipeIds.includes(recipeId)) {
+ return true;
+ }
+ // Si no hay cambios locales, usar estado del servidor o el favoriteRecipeIds
+ return serverState ?? state.favoriteRecipeIds.includes(recipeId);
},
- isFavoriteMealPrep: (mealPrepId: number) => {
- return get().favoriteMealPrepIds.includes(mealPrepId);
+ isFavoriteMealPrep: (mealPrepId: number, serverState?: boolean) => {
+ const state = get();
+ if (state.removedLocallyMealPrepIds.includes(mealPrepId)) {
+ return false;
+ }
+ if (state.addedLocallyMealPrepIds.includes(mealPrepId)) {
+ return true;
+ }
+ return serverState ?? state.favoriteMealPrepIds.includes(mealPrepId);
},
clearFavorites: () => {
- set({ favoriteRecipeIds: [], favoriteMealPrepIds: [] });
+ set({
+ favoriteRecipeIds: [],
+ favoriteMealPrepIds: [],
+ addedLocallyRecipeIds: [],
+ removedLocallyRecipeIds: [],
+ addedLocallyMealPrepIds: [],
+ removedLocallyMealPrepIds: []
+ });
},
addFavorite: (recipeId: number) => {
@@ -52,8 +92,20 @@ export const useFavoritesStore = create()(
get().removeFavoriteRecipe(recipeId);
},
- isFavorite: (recipeId: number) => {
- return get().isFavoriteRecipe(recipeId);
+ isFavorite: (recipeId: number, serverState?: boolean) => {
+ return get().isFavoriteRecipe(recipeId, serverState);
+ },
+
+ // limpieza de cambios locales después de sincronización con servidor
+ syncWithServer: (favoriteRecipeIds: number[], favoriteMealPrepIds: number[]) => {
+ set({
+ favoriteRecipeIds,
+ favoriteMealPrepIds,
+ addedLocallyRecipeIds: [],
+ removedLocallyRecipeIds: [],
+ addedLocallyMealPrepIds: [],
+ removedLocallyMealPrepIds: []
+ });
}
}),
{
diff --git a/src/store/useIngredientsStore.ts b/src/store/useIngredientsStore.ts
index 70941b3..07ae496 100644
--- a/src/store/useIngredientsStore.ts
+++ b/src/store/useIngredientsStore.ts
@@ -28,7 +28,9 @@ export const useIngredientsStore = create((set, get) => ({
'/review',
'/filters',
'/results',
- '/recipe/'
+ '/recipe/',
+ '/meal-prep/',
+ '/results-meal'
];
const isInGeneratorFlow = generatorPaths.some(path => currentPath.includes(path));
diff --git a/src/types/recipe/recipe.types.ts b/src/types/recipe/recipe.types.ts
index 5f4faef..a70f1b7 100644
--- a/src/types/recipe/recipe.types.ts
+++ b/src/types/recipe/recipe.types.ts
@@ -170,6 +170,7 @@ export interface MealPrep {
description?: string;
recipes: MealPrepRecipe[];
steps: MealPrepStep[];
+ isFavorite?: boolean; // Estado de favorito del servidor
}
export type MealPrepResponse = MealPrep[];
diff --git a/src/types/store/store.types.ts b/src/types/store/store.types.ts
index b84fabe..e693214 100644
--- a/src/types/store/store.types.ts
+++ b/src/types/store/store.types.ts
@@ -64,16 +64,23 @@ export interface RegisterStore {
export interface FavoritesState {
favoriteRecipeIds: number[];
favoriteMealPrepIds: number[];
+ // Nuevos arrays para rastrear cambios locales
+ addedLocallyRecipeIds: number[];
+ removedLocallyRecipeIds: number[];
+ addedLocallyMealPrepIds: number[];
+ removedLocallyMealPrepIds: number[];
+
addFavoriteRecipe: (recipeId: number) => void;
removeFavoriteRecipe: (recipeId: number) => void;
addFavoriteMealPrep: (mealPrepId: number) => void;
removeFavoriteMealPrep: (mealPrepId: number) => void;
- isFavoriteRecipe: (recipeId: number) => boolean;
- isFavoriteMealPrep: (mealPrepId: number) => boolean;
+ isFavoriteRecipe: (recipeId: number, serverState?: boolean) => boolean;
+ isFavoriteMealPrep: (mealPrepId: number, serverState?: boolean) => boolean;
clearFavorites: () => void;
addFavorite: (recipeId: number) => void;
removeFavorite: (recipeId: number) => void;
- isFavorite: (recipeId: number) => boolean;
+ isFavorite: (recipeId: number, serverState?: boolean) => boolean;
+ syncWithServer: (favoriteRecipeIds: number[], favoriteMealPrepIds: number[]) => void;
}
// ================================