Ultimos cambios

parent 451d56f2
import RecipeDetail from '@/views/RecipeDetail.vue';
import api from './api'; import api from './api';
export const recipeService = { export const recipeService = {
...@@ -8,19 +7,43 @@ export const recipeService = { ...@@ -8,19 +7,43 @@ export const recipeService = {
if (size != null) params.append('size', size); if (size != null) params.append('size', size);
if (sortDirection != null) params.append('sortDirection', sortDirection); if (sortDirection != null) params.append('sortDirection', sortDirection);
const response = await api.get(`/recipe?${params.toString()}`); const response = await api.get(`/recipe?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
madeFavorite: async (id) => { madeFavorite: async (id) => {
const response = await api.patch(`/recipe/${id}/favorite`); const response = await api.patch(`/recipe/${id}/favorite`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
removeFavorite: async (id) => { removeFavorite: async (id) => {
const response = await api.delete(`/recipe/${id}/favorite`); const response = await api.delete(`/recipe/${id}/favorite`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
readDetail: async (id) => { readDetail: async (id) => {
const response = await api.get(`/recipe/${id}`); const response = await api.get(`/recipe/${id}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
create: async (recipeData) => { create: async (recipeData) => {
...@@ -28,7 +51,13 @@ export const recipeService = { ...@@ -28,7 +51,13 @@ export const recipeService = {
return response.data; return response.data;
}, },
delete: async (id) => { delete: async (id) => {
const response = await api.delete(`/recipe/${id}`); const response = await api.delete(`/recipe/${id}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
update: async (recipeId, newData) => { update: async (recipeId, newData) => {
...@@ -41,7 +70,13 @@ export const recipeService = { ...@@ -41,7 +70,13 @@ export const recipeService = {
if (page) params.append('page', page); if (page) params.append('page', page);
if (size) params.append('size', size); if (size) params.append('size', size);
const response = await api.get(`/recipe/search?${params.toString()}`) const response = await api.get(`/recipe/search?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
readFavorites: async (page, size, sortDirection) => { readFavorites: async (page, size, sortDirection) => {
...@@ -50,7 +85,49 @@ export const recipeService = { ...@@ -50,7 +85,49 @@ export const recipeService = {
if (size != null) params.append('size', size); if (size != null) params.append('size', size);
if (sortDirection != null) params.append('sortDirection', sortDirection); if (sortDirection != null) params.append('sortDirection', sortDirection);
const response = await api.get(`/recipe/favorites?${params.toString()}`); const response = await api.get(`/recipe/favorites?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
searchAI: async (query) => {
const response = await api.post(`/recipe/search?ingredients=${query}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data;
},
searchMoreAI: async (sessionId) => {
const response = await api.post(
'/recipe/search/more',
{},
{
headers: {
"Content-Type": "application/json",
"X-Session-ID": sessionId
}
}
)
return response.data;
},
getDetailAI: async (sessionId, index) => {
const response = await api.post(
`/recipe/detail/${index}`,
{},
{
headers: {
"Content-Type": "application/json",
"X-Session-ID": sessionId
}
}
)
return response.data; return response.data;
} }
} }
\ No newline at end of file
...@@ -2,7 +2,13 @@ import api from './api'; ...@@ -2,7 +2,13 @@ import api from './api';
export const userService = { export const userService = {
readUser: async () => { readUser: async () => {
const response = await api.get('/user/me'); const response = await api.get('/user/me', {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
readAll: async (page, size, sortBy, sortDirection) => { readAll: async (page, size, sortBy, sortDirection) => {
...@@ -16,31 +22,73 @@ export const userService = { ...@@ -16,31 +22,73 @@ export const userService = {
} }
// Petición al endpoint con los parámetros // Petición al endpoint con los parámetros
const response = await api.get(`/user?${params.toString()}`); const response = await api.get(`/user?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
deactivate: async (id) => { deactivate: async (id) => {
const response = await api.patch(`/user/${id}/deactivate`); const response = await api.patch(`/user/${id}/deactivate`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
activate: async (id) => { activate: async (id) => {
const response = await api.patch(`/user/${id}/activate`); const response = await api.patch(`/user/${id}/activate`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
deactivateMe: async () => { deactivateMe: async () => {
const response = await api.patch(`/user/deactivate`); const response = await api.patch(`/user/deactivate`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
update: async (updatedUser) => { update: async (updatedUser) => {
const response = await api.put('/user', updatedUser); const response = await api.put('/user', updatedUser,
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
changePassword: async (passwords) => { changePassword: async (passwords) => {
const response = await api.patch(`user/password`, passwords); const response = await api.patch(`user/password`, passwords,
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
changeRole: async (id, role) => { changeRole: async (id, role) => {
const response = await api.patch(`/user/${id}/role`, role); const response = await api.patch(`/user/${id}/role`, role,
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
}, },
search: async (query, page, size) => { search: async (query, page, size) => {
...@@ -49,7 +97,13 @@ export const userService = { ...@@ -49,7 +97,13 @@ export const userService = {
if (page) params.append('page', page); if (page) params.append('page', page);
if (size) params.append('size', size); if (size) params.append('size', size);
const response = await api.get(`/user/search?${params.toString()}`) const response = await api.get(`/user/search?${params.toString()}`, {},
{
headers: {
"Content-Type": "application/json"
}
}
);
return response.data; return response.data;
} }
} }
\ No newline at end of file
...@@ -9,7 +9,13 @@ export const useRecipeStore = defineStore('recipe', { ...@@ -9,7 +9,13 @@ export const useRecipeStore = defineStore('recipe', {
pageSize: 10, pageSize: 10,
totalElements: 0, totalElements: 0,
totalPages: 0, totalPages: 0,
sortDirection: 'desc' sortDirection: 'desc',
// Estados búsqueda por IA
aiRecipes: [],
aiSessionId: null,
loading: false,
aiRecipe: null,
loadingDetail: false
}), }),
actions: { actions: {
async readAll(page, size, sortDirection) { async readAll(page, size, sortDirection) {
...@@ -111,6 +117,63 @@ export const useRecipeStore = defineStore('recipe', { ...@@ -111,6 +117,63 @@ export const useRecipeStore = defineStore('recipe', {
this.totalPages = 0; this.totalPages = 0;
throw error; throw error;
} }
},
async searchAI(query) {
this.loading = true;
// Limpiamos los resultados de la búsqueda anterior
this.aiRecipes = [];
this.aiSessionId = null;
try {
const { recipes, sessionId } = await recipeService.searchAI(query);
if (recipes) {
this.aiRecipes = recipes;
this.aiSessionId = sessionId;
} else {
this.aiRecipes = [];
this.aiSessionId = null;
}
} catch (error) {
console.error('Error al hacer la búsqueda por IA:', error);
this.aiRecipes = [];
this.aiSessionId = null;
throw error;
} finally {
this.loading = false;
}
},
async searchMoreAI(sessionId) {
this.loading = true;
try {
const response = await recipeService.searchMoreAI(sessionId);
if (!response || response.length === 0) {
return;
}
this.aiRecipes.push(...response);
} catch (error) {
console.error('Error al cargar más recetas', error);
throw error;
} finally {
this.loading = false;
}
},
async getDetailAI(sessionId, index) {
this.loadingDetail = true;
this.aiRecipe = null;
try {
const response = await recipeService.getDetailAI(sessionId, index);
this.aiRecipe = response;
} catch (error) {
console.error('Error al obtener el detalle de la receta:', error);
this.aiRecipe = null;
throw error;
} finally {
this.loadingDetail = false;
}
} }
} }
}); });
\ No newline at end of file
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
</div> </div>
<div v-for="ingredient in ingredients" :key="ingredient.id" class="row align-items-center mb-2"> <div v-for="ingredient in ingredients" :key="ingredient.id" class="row align-items-center mb-2">
<div class="col-3"> <div class="col-3">
<input type="number" class="form-control" v-model="ingredient.quantity" placeholder="Ej: 2"> <input type="text" class="form-control" v-model="ingredient.quantity" placeholder="Ej: 2">
</div> </div>
<div class="col-3"> <div class="col-3">
<input type="text" class="form-control" v-model="ingredient.unitOfMeasure" placeholder="Ej: tazas"> <input type="text" class="form-control" v-model="ingredient.unitOfMeasure" placeholder="Ej: tazas">
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
<div v-else class="mb-4 mt-3 text-center"> <div v-else class="mb-4 mt-3 text-center">
<label for="recipePicture" class="form-label fw-bold">Cambiar imagen</label> <label for="recipePicture" class="form-label fw-bold">Cambiar imagen</label>
<input id="recipePicture" type="file" class="form-control mx-auto" style="max-width: 300px" @change="handlePictureChange" /> <input id="recipePicture" type="file" class="form-control mx-auto" @change="handlePictureChange" />
</div> </div>
<!-- Descripción --> <!-- Descripción -->
...@@ -68,25 +68,37 @@ ...@@ -68,25 +68,37 @@
<hr class="my-4" /> <hr class="my-4" />
<!-- Ingredientes -->
<div class="row"> <div class="row">
<!-- Ingredientes -->
<div class="col-lg-6"> <div class="col-lg-6">
<div class="mb-4"> <div class="mb-4">
<h5 class="fw-bold">Ingredientes</h5> <h5 class="section-title">
<ul v-if="mode === 'view'" class="list-unstyled"> <i class="bi bi-basket me-2"></i> Ingredientes
<li v-for="(ing, index) in processedIngredients" :key="index" class="mb-1"> </h5>
<i class="bi bi-check-circle-fill me-2 text-success"></i>
{{ ing.quantity }} {{ ing.unit }} {{ ing.connective }} {{ ing.name }} <!-- Vista -->
<ul v-if="mode === 'view'" class="ingredients-list">
<li v-for="(ing, index) in processedIngredients" :key="index" class="ingredient-item">
<span class="ingredient-name">{{ ing.name }}</span>
<span class="ingredient-amount">
{{ ing.quantity }} {{ ing.unit }} {{ ing.connective }}
</span>
</li> </li>
</ul> </ul>
<!-- Edición -->
<div v-else class="ingredient-form-section"> <div v-else class="ingredient-form-section">
<div v-for="(ing, index) in editableRecipe.ingredients" :key="index" class="input-group mb-2"> <div v-for="(ing, index) in editableRecipe.ingredients" :key="index" class="ingredient-form mb-2">
<input type="number" v-model="ing.quantity" class="form-control" placeholder="Cantidad"> <input type="text" v-model="ing.name" class="form-control mb-2" placeholder="Ingrediente">
<input type="text" v-model="ing.unitOfMeasure" class="form-control" placeholder="Unidad"> <div class="d-flex gap-2 mt-2">
<input type="text" v-model="ing.name" class="form-control" placeholder="Ingrediente"> <input type="text" v-model="ing.quantity" class="form-control" placeholder="Cantidad">
<button @click="editableRecipe.ingredients.splice(index, 1)" class="btn btn-outline-danger" type="button"><i class="bi bi-trash"></i></button> <input type="text" v-model="ing.unitOfMeasure" class="form-control" placeholder="Unidad">
<button @click="editableRecipe.ingredients.splice(index, 1)" class="btn btn-outline-danger" type="button">
<i class="bi bi-trash"></i>
</button>
</div>
</div> </div>
<button @click="editableRecipe.ingredients.push({ quantity: '', unit: '', ingredient: '' })" class="btn btn-outline-primary-custom mt-2 w-100"> <button @click="editableRecipe.ingredients.push({ quantity: '', unit: '', name: '' })" class="btn btn-outline-primary-custom mt-2 w-100">
<i class="bi bi-plus-circle"></i> Añadir Ingrediente <i class="bi bi-plus-circle"></i> Añadir Ingrediente
</button> </button>
</div> </div>
...@@ -96,21 +108,28 @@ ...@@ -96,21 +108,28 @@
<!-- Pasos --> <!-- Pasos -->
<div class="col-lg-6"> <div class="col-lg-6">
<div class="mb-4"> <div class="mb-4">
<h5 class="fw-bold">Pasos de Preparación</h5> <h5 class="section-title">
<ol v-if="mode === 'view'"> <i class="bi bi-list-ol me-2"></i> Pasos de preparación
<li v-for="(step, index) in recipe.steps" :key="index" class="mb-2"> </h5>
{{ step.description }}
<!-- Vista -->
<ol v-if="mode === 'view'" class="steps-list">
<li v-for="(step, index) in recipe.steps" :key="index" class="step-item">
<span class="step-number">{{ index + 1 }}</span>
<span class="step-text">{{ step.description }}</span>
</li> </li>
</ol> </ol>
<!-- Edición -->
<div v-else class="steps-form-section"> <div v-else class="steps-form-section">
<div v-for="(step, index) in editableRecipe.steps" :key="index" class="input-group mb-2"> <div v-for="(step, index) in editableRecipe.steps" :key="index" class="step-form">
<span class="input-group-text">{{ index + 1 }}</span> <div class="d-flex gap-2 mb-2">
<textarea <span class="input-group-text">{{ index + 1 }}</span>
v-model="step.description" <textarea v-model="step.description" class="form-control" rows="2"></textarea>
class="form-control" <button @click="editableRecipe.steps.splice(index, 1)" class="btn btn-outline-danger" type="button">
rows="3"> <i class="bi bi-trash"></i>
</textarea> </button>
<button @click="editableRecipe.steps.splice(index, 1)" class="btn btn-outline-danger" type="button"><i class="bi bi-trash"></i></button> </div>
</div> </div>
<button @click="editableRecipe.steps.push({ description: '' })" class="btn btn-outline-primary-custom mt-2 w-100"> <button @click="editableRecipe.steps.push({ description: '' })" class="btn btn-outline-primary-custom mt-2 w-100">
<i class="bi bi-plus-circle"></i> Añadir Paso <i class="bi bi-plus-circle"></i> Añadir Paso
...@@ -120,6 +139,7 @@ ...@@ -120,6 +139,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
...@@ -175,12 +195,12 @@ const processedIngredients = computed(() => { ...@@ -175,12 +195,12 @@ const processedIngredients = computed(() => {
return recipe.value.ingredients.map(ing => { return recipe.value.ingredients.map(ing => {
const unit = ing.unitOfMeasure ? ing.unitOfMeasure.toLowerCase() : ''; const unit = ing.unitOfMeasure ? ing.unitOfMeasure.toLowerCase() : '';
const name = ing.name ? ing.name.toLowerCase() : ''; const name = ing.name ? ing.name.toLowerCase() : '';
const connective = ing.unitOfMeasure ? 'de' : ''; // const connective = ing.unitOfMeasure ? 'de' : '';
return { return {
...ing, ...ing,
unit, unit,
name, name
connective // connective
}; };
}); });
}); });
...@@ -333,4 +353,73 @@ h5 { ...@@ -333,4 +353,73 @@ h5 {
color: white; color: white;
font-size: 14px; font-size: 14px;
} }
/* Títulos de sección */
.section-title {
font-weight: 700;
color: #793E6C;
border-bottom: 2px solid #eee;
padding-bottom: 4px;
margin-bottom: 16px;
display: flex;
align-items: center;
}
/* INGREDIENTES vista */
.ingredients-list {
list-style: none;
padding: 0;
margin: 0;
}
.ingredient-item {
display: flex;
justify-content: space-between;
padding: 6px 10px;
border-bottom: 1px solid #eee;
}
.ingredient-name {
font-weight: 500;
}
.ingredient-amount {
color: #555;
font-size: 0.9rem;
}
/* PASOS vista */
.steps-list {
list-style: none;
padding: 0;
margin: 0;
}
.step-item {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
background: #f5f5f5;
padding: 10px 14px;
border-radius: 8px;
}
.step-number {
flex-shrink: 0;
width: 28px;
height: 28px;
border-radius: 50%;
background: #793e6c;
color: white;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.step-text {
flex: 1;
}
</style> </style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment