diff --git a/src/app/(application)/(generator)/meal-prep-filters/page.tsx b/src/app/(application)/(generator)/meal-prep-filters/page.tsx index 2af3bc0..1c9806c 100644 --- a/src/app/(application)/(generator)/meal-prep-filters/page.tsx +++ b/src/app/(application)/(generator)/meal-prep-filters/page.tsx @@ -203,7 +203,7 @@ export default function MealPrepFilters() { ingredients: ingredients.map((ingredient) => ({ name: ingredient.name, quantity: ingredient.quantity, - unit_id: Number(ingredient.unit), + unit_id: Number(ingredient.unit.id), })), filters: filtersToSend, configuration: { diff --git a/src/app/(application)/(generator)/meal-prep/[id]/page.tsx b/src/app/(application)/(generator)/meal-prep/[id]/page.tsx index a55e345..d9617f3 100644 --- a/src/app/(application)/(generator)/meal-prep/[id]/page.tsx +++ b/src/app/(application)/(generator)/meal-prep/[id]/page.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import apiClient from "@/lib/axios.config"; import ChefLoader from "@/components/shared/loaders/ChefLoader"; +import { MealPrepDetailSkeleton } from "@/components/shared/skeleton/MealPrepDetailSkeleton"; import BackgroundLayers from "@/components/shared/BackgroundLayers"; import ContainerShadow from "@/components/shared/containers/ContainerShadow"; import RecipeTags from "@/components/meal-prep/RecipeTags"; @@ -32,6 +33,7 @@ export default function MealPrepPage({ const { isFavoriteMealPrep, addFavoriteMealPrep, removeFavoriteMealPrep } = useFavoritesStore(); + const { show, message, @@ -48,6 +50,8 @@ export default function MealPrepPage({ const [showUnfavoriteModal, setShowUnfavoriteModal] = useState(false); const [showSubscriptionModal, setShowSubscriptionModal] = useState(false); + // Mix estado del servidor con estado local + const currentIsFavorite = mealPrep ? isFavoriteMealPrep(mealPrep.id, mealPrep.isFavorite) : false; useEffect(() => { const fetchMealPrep = async () => { try { @@ -65,6 +69,7 @@ export default function MealPrepPage({ recipes: data.recipes, ingredients: data.ingredients, observation: data.observation || undefined, + isFavorite: data.favorite || false, }; setMealPrep(mapped); @@ -77,13 +82,11 @@ export default function MealPrepPage({ fetchMealPrep(); }, [mealPrepId]); - - + const handleFavMealPrep = () => { if (!mealPrep) return; - const isCurrentlyFavorite = isFavoriteMealPrep(mealPrep.id); - if (isCurrentlyFavorite) { + if (currentIsFavorite) { setShowUnfavoriteModal(true); } else { setShowFavoriteModal(true); @@ -107,7 +110,7 @@ export default function MealPrepPage({ }; if (loading) { - return ; + return ; } if (!mealPrep) { return ( @@ -116,8 +119,6 @@ export default function MealPrepPage({ ); } - const isCurrentlyFavorite = isFavoriteMealPrep(mealPrep.id); - return ( <> @@ -130,17 +131,16 @@ export default function MealPrepPage({
-

{mealPrep.title}

- +

{mealPrep.title}

-

Paso a paso

+

Paso a paso

@@ -151,7 +151,7 @@ export default function MealPrepPage({ {/* */} - + {mealPrep.observation && ( )} diff --git a/src/app/(application)/(generator)/recipe/[id]/page.tsx b/src/app/(application)/(generator)/recipe/[id]/page.tsx index 12eee51..9b0fb3c 100644 --- a/src/app/(application)/(generator)/recipe/[id]/page.tsx +++ b/src/app/(application)/(generator)/recipe/[id]/page.tsx @@ -13,6 +13,7 @@ import RecipeSidebar from "@/components/recipe/Sidebar"; import { useRecipeGeneratorSession } from "@/hooks/useRecipeGeneratorSession"; import { useNotification } from "@/hooks/useNotification"; import { useRecipeDetail } from "@/hooks/useRecipeDetail"; +import { useFavoritesStore } from "@/store/useFavoritesStore"; import NotificationModal from "@/components/shared/modal/NotificationModal"; import RecipePDFDownload from "@/components/recipe/RecipePDFDownload"; import { useAuthStore } from "@/store/useAuthStore"; @@ -24,10 +25,14 @@ export default function RecipeDetailPage({ params }: PageProps) { const router = useRouter(); const { id: recipeId } = React.use(params); const { recipe, loading } = useRecipeDetail(recipeId); + const { isFavorite: isLocalFavorite } = useFavoritesStore(); const isPremium = useAuthStore((state) => state.user?.premium); const { message, additionalMessage, type, show, clearNotification } = useNotification(); + // Mix estado del servidor con estado local para el sidebar + const currentIsFavorite = recipe ? isLocalFavorite(recipe.id, recipe.isFavorite) : false; + if (loading) { return ; } @@ -76,7 +81,7 @@ export default function RecipeDetailPage({ params }: PageProps) { missingIngredients={recipe.missingIngredients} recipeId={recipe.id} recipeTitle={recipe.name} - isFavorite={recipe.isFavorite} + isFavorite={currentIsFavorite} mealTypes={recipe.mealTypes} />
diff --git a/src/app/(application)/(generator)/results-meal/page.tsx b/src/app/(application)/(generator)/results-meal/page.tsx index 8c433fb..522d429 100644 --- a/src/app/(application)/(generator)/results-meal/page.tsx +++ b/src/app/(application)/(generator)/results-meal/page.tsx @@ -13,7 +13,7 @@ import { faClock, faHeart } from "@fortawesome/free-regular-svg-icons"; import { faHeart as faHeartSolid } from "@fortawesome/free-solid-svg-icons"; import Container from "@/components/shared/containers/Container"; import BackgroundLayers from "@/components/shared/BackgroundLayers"; -import { RecipeCardSkeleton } from "@/components/shared/skeleton/RecipeCardSkeleton"; +import { MealPrepCardSkeleton } from "@/components/shared/skeleton/MealPrepCardSkeleton"; import { FavoriteModal } from "@/components/shared/modal/FavoriteModal"; import SubscriptionModal from "@/components/shared/modal/SubscriptionModal"; import NotificationModal from "@/components/shared/modal/NotificationModal"; @@ -89,9 +89,9 @@ export default function MealPrepResultsPage() {

{isLoading ? ( -
- {[1, 2, 3, 4].map((index) => ( - +
+ {[1].map((index) => ( + ))}
) : mealPreps.length === 0 ? ( diff --git a/src/app/(application)/(generator)/review/page.tsx b/src/app/(application)/(generator)/review/page.tsx index ec3d86c..8863e11 100644 --- a/src/app/(application)/(generator)/review/page.tsx +++ b/src/app/(application)/(generator)/review/page.tsx @@ -12,6 +12,7 @@ import RecipeIngredientInput from "@/components/recipe-generator/IngredientInput import BackgroundLayers from "@/components/shared/BackgroundLayers"; import ContainerShadow from "@/components/shared/containers/ContainerShadow"; import ChefLoader from "@/components/shared/loaders/ChefLoader"; +import Input from "@/components/shared/form/Input"; export default function ReviewPage() { useRecipeGeneratorSession(); @@ -225,46 +226,59 @@ export default function ReviewPage() { {isEditModalOpen && (
-
-

Editar ingrediente

- setNewName(e.target.value)} - className="w-full px-4 py-2 border text-color-primary rounded mb-4" - placeholder="Nombre" - autoFocus - /> - setNewQuantity(e.target.value)} - className="w-full px-4 py-2 border text-color-primary rounded mb-4" - placeholder="Cantidad" - /> - - setNewUnit((prev) => ({ - id: prev?.id ?? 0, - symbol: prev?.symbol, - description: e.target.value, - })) - } - placeholder="Unidad" - /> +
+

Editar ingrediente

+ +
+ setNewName(e.target.value)} + placeholder="Ej: Carne picada" + className="w-full" + /> -
+ setNewQuantity(e.target.value)} + placeholder="2" + min="0" + step="0.1" + className="w-full" + /> + + + setNewUnit((prev) => ({ + id: prev?.id ?? 0, + symbol: prev?.symbol, + description: e.target.value, + })) + } + placeholder="Kg" + className="w-full" + /> +
+ +
diff --git a/src/app/(application)/calendar/page.tsx b/src/app/(application)/calendar/page.tsx index 3592f12..3b0e590 100644 --- a/src/app/(application)/calendar/page.tsx +++ b/src/app/(application)/calendar/page.tsx @@ -238,7 +238,7 @@ export default function CalendarPage() { if (isLoading) { return ( -

+

Planificación Semanal

@@ -252,7 +252,7 @@ export default function CalendarPage() {
-

+

Planificación Semanal

diff --git a/src/app/(application)/favs/page.tsx b/src/app/(application)/favs/page.tsx index 3c4dd20..0fd5766 100644 --- a/src/app/(application)/favs/page.tsx +++ b/src/app/(application)/favs/page.tsx @@ -164,7 +164,7 @@ export default function Favs() { onClick={() => handleRemove(recipe.id, recipe.name, "recipe") } - className="absolute bottom-3 right-3 w-10 h-10 flex items-center justify-center text-white background-color-primary hover:bg-red-800 rounded-full shadow-md transition" + className="absolute bottom-3 right-0 w-10 h-10 flex items-center justify-center text-white background-color-primary hover:bg-red-800 rounded-full shadow-md transition" title="Eliminar de favoritos" > handleRemove(mp.id, mp.title, "meal-prep") } - className="absolute bottom-3 right-3 w-10 h-10 flex items-center justify-center text-white background-color-primary hover:bg-red-800 rounded-full shadow-md transition" + className="absolute bottom-3 right-0 w-10 h-10 flex items-center justify-center text-white background-color-primary hover:bg-red-800 rounded-full shadow-md transition" title="Eliminar de favoritos" > ); } + +export default function ChangePassword() { + return ( + + +
+ }> + + + ); +} diff --git a/src/components/calendar/RecipeCard.tsx b/src/components/calendar/RecipeCard.tsx index 2002b8f..4071761 100644 --- a/src/components/calendar/RecipeCard.tsx +++ b/src/components/calendar/RecipeCard.tsx @@ -21,7 +21,7 @@ export default function RecipeCard({ recipe, onDelete, onAdd, isEmpty = false }: return (
)} -
+
-
+
{recipe.title
-

- {recipe.title || 'Sin título'} -

+
+

+ {recipe.title || 'Sin título'} +

+
diff --git a/src/components/home/HeroHome.tsx b/src/components/home/HeroHome.tsx index 5debf17..545b1f0 100644 --- a/src/components/home/HeroHome.tsx +++ b/src/components/home/HeroHome.tsx @@ -52,7 +52,7 @@ export default function HeroHome() { {/* Contenido centrado */}
-

+

{saludo}

diff --git a/src/components/meal-prep/IngredientList.tsx b/src/components/meal-prep/IngredientList.tsx index ab8a8fd..a77215a 100644 --- a/src/components/meal-prep/IngredientList.tsx +++ b/src/components/meal-prep/IngredientList.tsx @@ -4,7 +4,7 @@ import React from "react"; import { IngredientsListProps } from "@/types"; const IngredientsList: React.FC = ({ ingredients }) => ( - +
{ingredients.map((ingredient, i) => (
diff --git a/src/components/meal-prep/MealPrepCard.tsx b/src/components/meal-prep/MealPrepCard.tsx index 9b43aec..e457679 100644 --- a/src/components/meal-prep/MealPrepCard.tsx +++ b/src/components/meal-prep/MealPrepCard.tsx @@ -42,14 +42,14 @@ export default function MealPrepCard({ mealPrep, onClick, children }: MealPrepCa />
)} -

{recipe.name}

+

{recipe.name}

))}
{children && -
e.stopPropagation()}>{children}
} +
e.stopPropagation()}>{children}
}
); } diff --git a/src/components/meal-prep/MealPrepSteps.tsx b/src/components/meal-prep/MealPrepSteps.tsx index 6d5e73d..8104514 100644 --- a/src/components/meal-prep/MealPrepSteps.tsx +++ b/src/components/meal-prep/MealPrepSteps.tsx @@ -11,10 +11,8 @@ const MealPrepSteps: React.FC = ({ steps }) => {

⏱ {step.time}' – {step.title}

-
    - -
  • {step.description}
  • - +
      +
    • {step.description}
    ))} diff --git a/src/components/meal-prep/RecipeTags.tsx b/src/components/meal-prep/RecipeTags.tsx index 3ece351..b6d9652 100644 --- a/src/components/meal-prep/RecipeTags.tsx +++ b/src/components/meal-prep/RecipeTags.tsx @@ -1,8 +1,15 @@ // components/RecipeTags.tsx import React from "react"; +import { useRouter } from "next/navigation"; import { MealPrepRecipeTagsProps } from "@/types"; const RecipeTags: React.FC = ({ recipes }) => { + const router = useRouter(); + + const handleRecipeClick = (recipeId: string | number) => { + router.push(`/recipe/${recipeId}`); + }; + return (
    {/* Botones de recetas */} @@ -10,7 +17,8 @@ const RecipeTags: React.FC = ({ recipes }) => { {recipes.map((recipe) => ( diff --git a/src/components/recipe-generator/IngredientInput.tsx b/src/components/recipe-generator/IngredientInput.tsx index fa59196..8318753 100644 --- a/src/components/recipe-generator/IngredientInput.tsx +++ b/src/components/recipe-generator/IngredientInput.tsx @@ -66,7 +66,7 @@ export default function RecipeIngredientInput() { return (

    - También podés escribir o decir qué tenés + También podés escribir lo que tenés

    @@ -77,7 +77,7 @@ export default function RecipeIngredientInput() { value={name} onChange={(e) => setName(e.target.value)} onKeyDown={handleKeyPress} - className="border rounded px-4 py-2 w-60" + className="border border-gray-300 rounded px-4 py-2 w-60 focus:outline-none focus:ring-2 focus:ring-purple-100 focus:border-purple-100" /> {/* Cantidad */} @@ -86,7 +86,7 @@ export default function RecipeIngredientInput() { placeholder="Cantidad" value={quantity} onChange={(e) => setQuantity(e.target.value)} - className="border rounded px-4 py-2 w-24" + className="border border-gray-300 rounded px-4 py-2 w-24 focus:outline-none focus:ring-2 focus:ring-purple-100 focus:border-purple-100" /> {/* Unidad */} @@ -95,7 +95,7 @@ export default function RecipeIngredientInput() { onChange={(e) => { setUnit(e.target.value); }} - className="border rounded px-2 py-2" + className="border border-gray-300 rounded px-2 py-2 focus:outline-none focus:ring-2 focus:ring-purple-100 focus:border-purple-100" disabled={!isLoaded || unitOptions.length === 0} > {!isLoaded ? ( diff --git a/src/components/recipe-generator/RecipeFilters.tsx b/src/components/recipe-generator/RecipeFilters.tsx index 6cf0dde..c57c893 100644 --- a/src/components/recipe-generator/RecipeFilters.tsx +++ b/src/components/recipe-generator/RecipeFilters.tsx @@ -137,7 +137,7 @@ export default function RecipeFilters() { const ingredientList = ingredients.map((ingredient) => ({ name: ingredient.name, quantity: ingredient.quantity, - unit_id: Number(ingredient.unit), + unit_id: Number(ingredient.unit.id), })); const informationRecipe: RecipeGenerationRequest = { diff --git a/src/components/recipe/Header.tsx b/src/components/recipe/Header.tsx index b70923c..fc53010 100644 --- a/src/components/recipe/Header.tsx +++ b/src/components/recipe/Header.tsx @@ -28,11 +28,15 @@ export default function RecipeHeader({ const { addFavorite, removeFavorite, + isFavorite: isLocalFavorite, } = useFavoritesStore(); const { showSuccess, showError } = useNotification(); + // Mix estado del servidor con estado local + const currentIsFavorite = isLocalFavorite(id, isFavorite); + const handleFavRecipe = (recipeId: number) => { - if (!isFavorite) { + if (!currentIsFavorite) { setShowFavoriteModal(true); } else { setShowUnfavoriteModal(true); @@ -51,7 +55,7 @@ export default function RecipeHeader({ <>
    -

    +

    {name}

    @@ -67,7 +71,7 @@ export default function RecipeHeader({ handleFavRecipe(id)} - isFavorite={isFavorite} + isFavorite={currentIsFavorite} />
    diff --git a/src/components/recipe/Ingredients.tsx b/src/components/recipe/Ingredients.tsx index bad92db..6aab0f3 100644 --- a/src/components/recipe/Ingredients.tsx +++ b/src/components/recipe/Ingredients.tsx @@ -12,7 +12,7 @@ export default function RecipeIngredients({ ingredients }: Props) { {ingredients.map((group, idx) => (
    {group.section && group.section.trim() && ( -
    +

    {group.section}

    )} diff --git a/src/components/recipe/StepBlock.tsx b/src/components/recipe/StepBlock.tsx index 875a223..785ca9e 100644 --- a/src/components/recipe/StepBlock.tsx +++ b/src/components/recipe/StepBlock.tsx @@ -7,22 +7,18 @@ export default function RecipeStepBlock({ section, steps }: RecipeDetailSection) return ( {steps.map((step, idx) => ( -
    - {/*step.image && ( -
    - {`Paso +
    +
    +
    + {step.number}
    - )*/} -
    -
    {step.number}
    -
    - {step.title && {step.title}: } - {step.description} + + {/* Contenido del paso */} +
    +
    + {step.title && {step.title}:} + {step.description} +
    diff --git a/src/components/shared/containers/ContainerCardDetail.tsx b/src/components/shared/containers/ContainerCardDetail.tsx index 21174ec..3f42e6e 100644 --- a/src/components/shared/containers/ContainerCardDetail.tsx +++ b/src/components/shared/containers/ContainerCardDetail.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { ContainerCardDetailProps } from '@/types/components/layout.types'; -//TODO integrarlo en mealprep export default function ContainerCardDetail({ children, customClass = '', title = ''}: ContainerCardDetailProps) { return (
    {title && ( -
    -

    {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; } // ================================