Compare commits

..

No commits in common. 'master' and 'WORK-RHA2' have entirely different histories.

@ -1,9 +0,0 @@
export enum IngredientClass {
DairyFree = 'DAIRY_FREE',
GlutenFree = 'GLUTEN_FREE',
Porcless = 'PORCLESS',
Vegan = 'VEGAN',
Vegetarian = 'VEGETARIAN',
Pescatarian = 'PESCATARIAN',
None = 'NONE'
}

@ -0,0 +1,30 @@
export default class Profil {
private _name: string;
private _avatar: string;
private _allergy: string[];
private _diets: string[];
constructor( name: string, avatar: string, allergy: string[], diets: string[]) {
this._name = name;
this._avatar = avatar;
this._allergy = allergy;
this._diets = diets;
}
get name(): string {
return this._name;
}
get avatar(): string{
return this._avatar;
}
get allergy(): string[]{
return this._allergy;
}
get diets(): string[]{
return this._diets;
}
}

@ -4,7 +4,7 @@ import Recipes from "../../Models/Recipes";
export default class RecipesService implements IRecipesService { export default class RecipesService implements IRecipesService {
private readonly API_URL = "http://leftovers.alwaysdata.net/recipes"; private readonly API_URL = "http://leftovers.alwaysdata.net/recipes";
private readonly IA_URL = "https://codefirst.iut.uca.fr/containers/Sae_LeftOvers-leftovers_ia" private readonly IA_URL = "https://codefirst.iut.uca.fr/containers/Sae_LeftOvers-leftovers_ia/getrecipes"
async getAllRecipes(): Promise<Recipes[]> { async getAllRecipes(): Promise<Recipes[]> {
try { try {
@ -26,17 +26,9 @@ export default class RecipesService implements IRecipesService {
} }
async getRecipeWithIngredients(ids: string[]): Promise<Recipes[]>{ async getRecipeWithIngredients(ids: string[]): Promise<Recipes[]>{
const recipe: Recipes[] = [];
try { try {
const response = await axios.get(`${this.IA_URL}/getrecipes/${ids}`); const response = await axios.get(`${this.IA_URL}/${ids}`);
return response.data as Recipes[];
} catch (error) {
throw new Error('Erreur lors de la récupération des recettes dans getRecipeWithIngredients : ' + error.message);
}
}
async getRecipeWithIngredientsAndFilters(ids: string[], filters: string): Promise<Recipes[]> {
try {
const response = await axios.get(`${this.IA_URL}/getrecipeswithfilters/${ids}/${filters}`);
return response.data as Recipes[]; return response.data as Recipes[];
} catch (error) { } catch (error) {
throw new Error('Erreur lors de la récupération des recettes dans getRecipeWithIngredients : ' + error.message); throw new Error('Erreur lors de la récupération des recettes dans getRecipeWithIngredients : ' + error.message);

@ -1,38 +0,0 @@
import IngredientService from '../Services/Ingredients/IngredientsServices';
describe('IngredientService', () => {
const ingredient_service = new IngredientService();
it('should get one ingredient', async () => {
const result = await ingredient_service.getIngredientById(1)
expect(result.id).toBe(1);
});
it('should get all ingredients', async () => {
const result = await ingredient_service.getAllIngredient()
const test = result.length >= 1
expect(test).toBe(true);
});
it('should return several ingredients starting by letter a', async () => {
const result = await ingredient_service.getIngredientByLetter('a')
let test = true
for (let ingredient of result) {
if (ingredient.name[0] !== 'a') {
test = false
}
}
expect(test).toBe(true);
});
it('should return several ingredients with car in the name', async () => {
const result = await ingredient_service.getfilteredIngredient('car')
let test = true
for (let ingredient of result) {
if (!ingredient.name.includes('car')) {
test = false
}
}
expect(test).toBe(true);
});
});

@ -1,124 +0,0 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import ProfileService from '../Services/Profiles/ProfileService';
import Profile from '../Models/Profile';
type AsyncStorageMock = {
getItem: jest.Mock<Promise<string | null>, [string]>,
setItem: jest.Mock<Promise<void>, [string, string]>,
clear: jest.Mock<Promise<void>>,
};
jest.mock('@react-native-async-storage/async-storage', () => {
const asyncStorageMock: AsyncStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn(),
};
return asyncStorageMock;
});
describe('ProfileService', () => {
beforeEach(() => {
(AsyncStorage.getItem as jest.Mock).mockReset();
(AsyncStorage.setItem as jest.Mock).mockReset();
(AsyncStorage.clear as jest.Mock).mockReset();
});
describe('getProfiles', () => {
it('should return an empty array if no profiles are stored', async () => {
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(null);
const profileService = new ProfileService();
const profiles = await profileService.getProfiles();
expect(profiles).toEqual([]);
});
it('should return an array of profiles if profiles are stored', async () => {
const storedProfiles = [
{ _name: 'John', _avatar: 'avatar1', _allergy: ['none'], _diets: [] },
{ _name: 'Jane', _avatar: 'avatar2', _allergy: ['peanuts'], _diets: ['vegan'] },
];
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(JSON.stringify(storedProfiles));
const profileService = new ProfileService();
const profiles = await profileService.getProfiles();
expect(profiles.length).toBe(2);
expect(profiles[0]).toBeInstanceOf(Profile);
expect(profiles[0].name).toEqual('John');
});
});
describe('addProfile', () => {
it('should add a new profile to the stored profiles', async () => {
const existingProfiles = [
{ _name: 'John', _avatar: 'avatar1', _allergy: ['none'], _diets: [] },
];
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(JSON.stringify(existingProfiles));
(AsyncStorage.setItem as jest.Mock).mockResolvedValueOnce(null);
const newProfile = new Profile('Jane', 'avatar2', ['peanuts'], ['vegan']);
const profileService = new ProfileService();
const result = await profileService.addProfile(newProfile);
expect(result).toBe(true);
expect(AsyncStorage.setItem).toHaveBeenCalledWith('profiles', expect.any(String));
});
it('should not add a profile if it already exists', async () => {
const existingProfiles = [
{ _name: 'John', _avatar: 'avatar1', _allergy: ['none'], _diets: [] },
];
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(JSON.stringify(existingProfiles));
const existingProfile = new Profile('John', 'avatar1', ['none'], []);
const profileService = new ProfileService();
const result = await profileService.addProfile(existingProfile);
expect(result).toBe(false);
expect(AsyncStorage.setItem).not.toHaveBeenCalled();
});
});
describe('delProfile', () => {
it('should delete a profile by name', async () => {
const existingProfiles = [
{ _name: 'John', _avatar: 'avatar1', _allergy: ['none'], _diets: [] },
{ _name: 'Jane', _avatar: 'avatar2', _allergy: ['peanuts'], _diets: ['vegan'] },
];
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(JSON.stringify(existingProfiles));
(AsyncStorage.setItem as jest.Mock).mockResolvedValueOnce(null);
const profileService = new ProfileService();
const result = await profileService.delProfile('John');
expect(result).toBe(true);
expect(AsyncStorage.setItem).toHaveBeenCalledWith('profiles', expect.any(String));
});
it('should return false if the profile does not exist', async () => {
const existingProfiles = [
{ _name: 'John', _avatar: 'avatar1', _allergy: ['none'], _diets: [] },
];
(AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(JSON.stringify(existingProfiles));
const profileService = new ProfileService();
const result = await profileService.delProfile('Jane');
expect(result).toBe(false);
expect(AsyncStorage.setItem).not.toHaveBeenCalled();
});
});
});

@ -1,22 +0,0 @@
import RecipesService from '../Services/Recipes/RecipesServices';
describe('RecipesService', () => {
const recipe_service = new RecipesService();
it('should get one recipe', async () => {
const result = await recipe_service.getRecipeById(4444)
expect(result.id).toBe(4444);
});
it('should get all recipes', async () => {
const result = await recipe_service.getAllRecipes()
const test = result.length >= 1
expect(test).toBe(true);
}, 120000);
it('should get one recipe', async () => {
const result = await recipe_service.getRecipeWithIngredients(['1928:2148:2809:2853:3723:6261:6335:7076'])
const test = result.length >= 1
expect(test).toBe(true);
});
});

@ -1,5 +1,5 @@
import React, {useContext} from 'react'; import React, {useContext, useState} from 'react';
import {StyleSheet, Pressable, Text, View, Image, ScrollView} from 'react-native'; import {StyleSheet, Pressable, Text, View, Image, ScrollView, ImageSourcePropType} from 'react-native';
import brochette from '../assets/images/brochette.png'; import brochette from '../assets/images/brochette.png';
import Union_left from '../assets/images/Union_left.png'; import Union_left from '../assets/images/Union_left.png';
import Union_right from '../assets/images/Union_right.png'; import Union_right from '../assets/images/Union_right.png';

File diff suppressed because it is too large Load Diff

@ -6,8 +6,7 @@
"start": "expo start --dev-client", "start": "expo start --dev-client",
"android": "expo run:android", "android": "expo run:android",
"ios": "expo run:ios", "ios": "expo run:ios",
"web": "expo start --web", "web": "expo start --web"
"test": "jest --coverage"
}, },
"dependencies": { "dependencies": {
"@expo/webpack-config": "^19.0.0", "@expo/webpack-config": "^19.0.0",
@ -19,7 +18,6 @@
"axios": "^1.6.2", "axios": "^1.6.2",
"expo": "~49.0.15", "expo": "~49.0.15",
"expo-blur": "^12.4.1", "expo-blur": "^12.4.1",
"expo-image-picker": "~14.3.2",
"expo-linear-gradient": "~12.3.0", "expo-linear-gradient": "~12.3.0",
"expo-splash-screen": "~0.20.5", "expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.6.0", "expo-status-bar": "~1.6.0",
@ -33,12 +31,11 @@
"react-native-splash-screen": "^3.3.0", "react-native-splash-screen": "^3.3.0",
"react-native-virtualized-view": "^1.0.0", "react-native-virtualized-view": "^1.0.0",
"react-native-web": "~0.19.6", "react-native-web": "~0.19.6",
"typescript": "^5.1.3" "typescript": "^5.1.3",
"expo-image-picker": "~14.3.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0"
"@types/jest": "^29.5.11",
"jest": "^29.7.0"
}, },
"private": true "private": true
} }

@ -10,7 +10,6 @@ import ColorContext from '../theme/ColorContext';
import eventEmitter from './EventEmitter'; import eventEmitter from './EventEmitter';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import ProfileService from '../Services/Profiles/ProfileService'; import ProfileService from '../Services/Profiles/ProfileService';
import { IngredientClass } from '../Models/IngredientClass';
export default function FiltersSelection(props) { export default function FiltersSelection(props) {
const {colors} = useContext(ColorContext); const {colors} = useContext(ColorContext);
@ -25,7 +24,6 @@ export default function FiltersSelection(props) {
const [dieAdd, setDieAdd] = useState([]) const [dieAdd, setDieAdd] = useState([])
const [allAdd, setAllAdd] = useState([]) const [allAdd, setAllAdd] = useState([])
const [selectedDiets, setSelectedDiets] = useState([]) const [selectedDiets, setSelectedDiets] = useState([])
const [activeDiets, setActiveDiets] = useState([])
const fetchProfiles = async () => { const fetchProfiles = async () => {
setProfiles(await profileService.getProfiles()) setProfiles(await profileService.getProfiles())
@ -236,65 +234,7 @@ export default function FiltersSelection(props) {
}, },
}); });
const handleSaveFilters = async () => { const goBack = () => props.navigation.goBack();
let dieTemp = []
let retType = true
profiles.forEach((profile) => {
if(profile.isActive == "flex"){
profile.diets.forEach((diet) => {
retType = true
dieTemp.forEach((val) => {
if(val == diet){
retType = false
}
})
if(retType){
dieTemp.push(diet)
}
})
}
})
selectedDiets.forEach((diet) => {
retType = true
dieTemp.forEach((val) => {
if(val == diet){
retType = false
}
})
if(retType){
dieTemp.push(diet)
}
})
setActiveDiets(await handleCastFilters(dieTemp))
eventEmitter.emit("updateActiveDiets")
props.navigation.goBack()
}
const handleCastFilters = async (values) => {
let diets = []
values.forEach((val) => {
if (val == "Dairy free"){
diets.push(IngredientClass.DairyFree)
}
else if (val == "Gluten free"){
diets.push(IngredientClass.GlutenFree)
}
else if (val == "Porkless"){
diets.push(IngredientClass.Porcless)
}
else if (val == "Vegan"){
diets.push(IngredientClass.Vegan)
}
else if (val == "Vegetarian"){
diets.push(IngredientClass.Vegetarian)
}
else{
diets.push(IngredientClass.Pescatarian)
}
})
await AsyncStorage.setItem('activeDiets', JSON.stringify(diets));
return diets
}
return ( return (
<SafeAreaProvider style={{flex: 1}}> <SafeAreaProvider style={{flex: 1}}>
@ -333,7 +273,7 @@ export default function FiltersSelection(props) {
<ValidateButton title="Add Allergy" image="plus.png" colour={colors.buttonDetail} backColour={colors.buttonBackground} todo={() => props.navigation.navigate("IngredientSelection")}></ValidateButton> <ValidateButton title="Add Allergy" image="plus.png" colour={colors.buttonDetail} backColour={colors.buttonBackground} todo={() => props.navigation.navigate("IngredientSelection")}></ValidateButton>
</View> </View>
<View style={{marginTop: "6%"}}/> <View style={{marginTop: "6%"}}/>
<ValidateButton title="Save Filters" image="save.png" colour={colors.buttonMain} backColour={colors.cardBackground} todo={handleSaveFilters}></ValidateButton> <ValidateButton title="Save Filters" image="save.png" colour={colors.buttonMain} backColour={colors.cardBackground} todo={goBack}></ValidateButton>
<View style={{marginTop: "20%"}}/> <View style={{marginTop: "20%"}}/>
</LinearGradient> </LinearGradient>
</ScrollView> </ScrollView>

@ -17,8 +17,7 @@ import plus from '../assets/images/plus_small.png';
import minus from '../assets/images/minus.png'; import minus from '../assets/images/minus.png';
import RecipesServices from '../Services/Recipes/RecipesServices'; import RecipesServices from '../Services/Recipes/RecipesServices';
import Recipes from '../Models/Recipes'; import Recipes from '../Models/Recipes';
import eventEmitter from './EventEmitter';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function RecipeSuggestion({ route, navigation }) { export default function RecipeSuggestion({ route, navigation }) {
const {colors} = useContext(ColorContext) const {colors} = useContext(ColorContext)
@ -33,10 +32,10 @@ export default function RecipeSuggestion({ route, navigation }) {
const recipeService = new RecipesServices(); const recipeService = new RecipesServices();
const {ingredients} = route.params; const {ingredients} = route.params;
const limitedList = ingredients.slice(minCpt, maxCpt); const limitedList = ingredients.slice(minCpt, maxCpt);
const [activeDiets, setActiveDiets] = useState([])
let selectedIngredients: string[]; let selectedIngredients: string[];
const die = [{value: "Gluten free"}, {value: "Porkless"}, {value: "Gluten free"}, {value: "Porkless"}]
const all = [] const all = []
@ -93,33 +92,20 @@ export default function RecipeSuggestion({ route, navigation }) {
const loadRecipes = async () => { const loadRecipes = async () => {
const ids: string[] = getIngredientsIds(ingredients); const ids: string[] = getIngredientsIds(ingredients);
console.log('load recipes - active diets tab :', activeDiets)
const filters: string = activeDiets.map(diet => diet).join(':')
try { try {
if (filters) {
const recipes: Recipes[] = await recipeService.getRecipeWithIngredientsAndFilters(ids, filters)
if(recipes[0].id != -1 ){
setSelectedRecipes(recipes);
}
}
else {
const recipes: Recipes[] = await recipeService.getRecipeWithIngredients(ids); const recipes: Recipes[] = await recipeService.getRecipeWithIngredients(ids);
if(recipes[0].id != -1 ){ if(recipes[0].id != -1 ){
setSelectedRecipes(recipes); setSelectedRecipes(recipes);
} }
}
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
}; };
useEffect(() => {
fetchActiveDiets()
}, []);
useEffect(() => { useEffect(() => {
loadRecipes(); loadRecipes();
}, [activeDiets]); }, []);
const styles = StyleSheet.create({ const styles = StyleSheet.create({
linearGradient: { linearGradient: {
@ -210,23 +196,6 @@ export default function RecipeSuggestion({ route, navigation }) {
}, },
}); });
const subscriptionUpdateActiveDiets = eventEmitter.addListener('updateActiveDiets', async () => {
fetchActiveDiets()
subscriptionUpdateActiveDiets.remove();
});
const fetchActiveDiets = async () => {
try {
const results = await AsyncStorage.getItem('activeDiets');
let existingActiveDiets = JSON.parse(results);
if (existingActiveDiets.length === 0) {
existingActiveDiets = [];
}
setActiveDiets(existingActiveDiets)
} catch (error) {
console.error('Error fetching active diets:', error);
}
}
const ingredientElements = limitedList.map((source, index) => ( const ingredientElements = limitedList.map((source, index) => (
<View style={[styles.horizontalAlignment, {marginVertical: "3%"}]} key={index}> <View style={[styles.horizontalAlignment, {marginVertical: "3%"}]} key={index}>
@ -301,9 +270,9 @@ export default function RecipeSuggestion({ route, navigation }) {
<View style={styles.background}> <View style={styles.background}>
<View style={styles.filterBar}> <View style={styles.filterBar}>
<Text style={styles.filters}>Additional Filters</Text> <Text style={styles.filters}>Additional Filters</Text>
<Text style={styles.nbSelected}>{activeDiets.length} selected</Text> <Text style={styles.nbSelected}>{die.length} selected</Text>
</View> </View>
<ListWithoutSelect title="Diets" content={activeDiets}></ListWithoutSelect> <ListWithoutSelect title="Diets" content={die}></ListWithoutSelect>
<View style={{marginTop: "3%"}}/> <View style={{marginTop: "3%"}}/>
<ListWithoutSelect title="Allergies" content={all}></ListWithoutSelect> <ListWithoutSelect title="Allergies" content={all}></ListWithoutSelect>
<View style={{marginTop: "3%"}}/> <View style={{marginTop: "3%"}}/>

8723
package-lock.json generated

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save