Compare commits

..

No commits in common. 'master' and 'formFront' have entirely different histories.

@ -4,13 +4,9 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="c5a06892-a370-4c6a-a9cf-5d61f93a285b" name="Changes" comment="Adding Command section, local storage exceed problem, adding error managing"> <list default="true" id="c5a06892-a370-4c6a-a9cf-5d61f93a285b" name="Changes" comment="Adding Front">
<change afterPath="$PROJECT_DIR$/daidokoro/src/app/service/command.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/daidokoro/src/app/component/command/command.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/daidokoro/src/app/component/command/command.component.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/daidokoro/src/app/component/recipe-form/recipe-form.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/daidokoro/src/app/component/recipe-form/recipe-form.component.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/daidokoro/src/app/component/command/command.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/daidokoro/src/app/component/command/command.component.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/daidokoro/src/app/component/recipe-list/recipe-list.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/daidokoro/src/app/component/recipe-list/recipe-list.component.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/daidokoro/src/app/component/recipe-list/recipe-list.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/daidokoro/src/app/component/recipe-list/recipe-list.component.ts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -27,7 +23,7 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY"> <option name="RECENT_BRANCH_BY_REPOSITORY">
<map> <map>
<entry key="$PROJECT_DIR$" value="formFront" /> <entry key="$PROJECT_DIR$" value="master" />
</map> </map>
</option> </option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@ -47,7 +43,7 @@
&quot;keyToString&quot;: { &quot;keyToString&quot;: {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;, &quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;, &quot;git-widget-placeholder&quot;: &quot;formFront&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/dorian/Documents/but3/Angular/Daidokoro&quot;, &quot;last_opened_file_path&quot;: &quot;/home/dorian/Documents/but3/Angular/Daidokoro&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
@ -87,7 +83,6 @@
<workItem from="1718625479373" duration="4985000" /> <workItem from="1718625479373" duration="4985000" />
<workItem from="1719217543390" duration="31000" /> <workItem from="1719217543390" duration="31000" />
<workItem from="1719217580949" duration="135000" /> <workItem from="1719217580949" duration="135000" />
<workItem from="1719755658119" duration="6807000" />
</task> </task>
<task id="LOCAL-00001" summary="Adding Front"> <task id="LOCAL-00001" summary="Adding Front">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -97,31 +92,7 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1718630357884</updated> <updated>1718630357884</updated>
</task> </task>
<task id="LOCAL-00002" summary="Adding Form front with the new form"> <option name="localTasksCounter" value="2" />
<option name="closed" value="true" />
<created>1719666928847</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1719666928847</updated>
</task>
<task id="LOCAL-00003" summary="Adding Ingredients from API, add and edit working good, front of the ingredients page">
<option name="closed" value="true" />
<created>1719757925681</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1719757925681</updated>
</task>
<task id="LOCAL-00004" summary="Adding recipe-details front">
<option name="closed" value="true" />
<created>1719760748129</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1719760748129</updated>
</task>
<option name="localTasksCounter" value="5" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -141,10 +112,6 @@
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" /> <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="Adding Front" /> <MESSAGE value="Adding Front" />
<MESSAGE value="Adding Form front with the new form" /> <option name="LAST_COMMIT_MESSAGE" value="Adding Front" />
<MESSAGE value="Adding Ingredients from API, add and edit working good, front of the ingredients page" />
<MESSAGE value="Adding recipe-details front" />
<MESSAGE value="Adding Command section, local storage exceed problem, adding error managing" />
<option name="LAST_COMMIT_MESSAGE" value="Adding Command section, local storage exceed problem, adding error managing" />
</component> </component>
</project> </project>

@ -12,72 +12,9 @@
![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black) ![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)
</div> </div>
# Daidokoro # :bookmark: Présentation
## Lancer l'application
Pour lancer l'application, veuillez suivre les étapes suivantes :
1. Cloner le repository du projet.
2. Se rendre dans le dossier `daidokoro`.
3. Exécuter la commande suivante : `ng serve --open`.
## Présentation rapide du projet
Daidokoro est un projet réalisé en Angular. Il s'agit d'une application web de gestion de recettes et d'ingrédients.
Les fonctionnalités incluent la gestion des utilisateurs, la consultation, l'ajout, la modification et la suppression d'ingrédients, ainsi que la création et la commande de recettes.
Le projet est conçu pour être intuitif et facile à utiliser, avec une interface utilisateur agréable et des fonctionnalités robustes.
## Prérequis demandés
Pour ce projet, les fonctionnalités suivantes étaient requises :
- Une barre de navigation commune à tout le site avec :
- Une page de connexion (/login) permettant de créer un cookie ou un local storage avec une valeur `isAdmin = true` et une redirection vers la page des ingrédients. Le bouton de connexion n'apparaît que si la valeur `isAdmin` est `false` ou inexistante.
- Une page de déconnexion (/logout) ayant le comportement inverse de la connexion.
- Un bouton vers la page des ingrédients avec une guard qui vérifie la valeur `isAdmin`. Si `true`, l'accès est permis, sinon, redirection vers la page d'accueil.
- Listing des ingrédients existants.
- Ajout d'un ingrédient.
- Modification d'un ingrédient.
- Connexion à une API pour les ingrédients, remplaçant le stub actuel.
- Sur la page d'accueil :
- Liste de recettes chargées depuis le local storage avec une image enregistrée ou un placeholder (`placehold.co/50x50`).
- Pagination de la liste de recettes.
- Un bouton pour ajouter une recette, faisant apparaître un formulaire avec :
- Nom
- Image
- Description
- Liste d'ingrédients avec possibilité de rajouter et de supprimer des ingrédients, au moins un devant être sélectionné.
- Un bouton "Commander" sur chaque recette qui ajoute la recette commandée à une liste "Commande en cours" sur la page d'accueil.
- Un bouton "Détail" redirigeant vers `/recettes/:id` et affichant la recette avec tous ses attributs.
## Réalisation des prérequis
Nous avons mis en œuvre toutes les fonctionnalités demandées :
- **Barre de navigation :** Elle est présente sur toutes les pages et comprend les boutons de connexion et de déconnexion. La gestion de `isAdmin` est effectuée via le local storage.
- **Guard d'accès aux ingrédients :** Nous avons implémenté une guard qui vérifie si l'utilisateur est connecté avant de permettre l'accès à la page des ingrédients.
- **Gestion des ingrédients :** Nous avons créé des pages pour lister, ajouter et modifier les ingrédients via l'API.
- **Page d'accueil :** La liste des recettes est chargée depuis le local storage et paginée. Un formulaire d'ajout de recette permet de créer de nouvelles recettes avec tous les champs requis. Chaque recette a des boutons "Commander" et "Détail".
## Fonctionnalités supplémentaires
En plus des fonctionnalités de base demandées, nous avons :
- Implémenté un style personnalisé pour l'application afin d'améliorer l'expérience utilisateur.
## Difficultés
Nous avons aussi rencontrer certaines difficultés :
- L'application a du mal à stocker dans le local storage à partir d'un certain quota, il est limité.
- Nous avons tenté de déployer l'application mais sans succès, le hub de codefirst était surchargé.
## Organisation du travail
Nous avons utilisé un système de branches et de merge requests pour organiser notre travail de manière efficace. Chaque fonctionnalité a été développée sur une branche dédiée et intégrée dans la branche principale via des merge requests. Cela nous a permis de travailler en parallèle tout en maintenant un code base stable et propre.
## Conclusion
Le projet Daidokoro a été une expérience enrichissante, nous permettant d'acquérir des connaissances en Angular et en gestion de projets.
このプロジェクトはレシピを閲覧・追加できるサイトを目指したプロジェクトです!あなたの作品を世界と共有しましょう!
# :construction: Développeurs # :construction: Développeurs

@ -8,6 +8,4 @@ RUN npm install -g @angular/cli
RUN npm install RUN npm install
EXPOSE 4200 ENTRYPOINT ["ng", "serve", "--host", "0.0.0.0","--port","80"]
ENTRYPOINT ["ng", "serve", "--host", "0.0.0.0"]

@ -26,10 +26,38 @@
background-color: #ddd; background-color: #ddd;
color: black; color: black;
} }
.content {
padding: 20px;
display: flex;
gap: 20px;
}
.recipe-container, .form-container {
background-color: #bab6b6;
color: black;
padding: 20px;
flex: 1;
}
.recipe-container {
display: flex;
flex-direction: column;
}
.recipe-container h2 { .recipe-container h2 {
font-size: 3em; font-size: 3em;
margin-bottom: 10px; margin-bottom: 10px;
} }
.form-container {
display: none;
flex-direction: column;
}
.show-form .form-container {
display: flex;
}
.show-form .recipe-container, .show-form .form-container {
flex: 1;
}
.button-container {
margin-top: 20px;
}
</style> </style>
</head> </head>
<body> <body>
@ -46,7 +74,6 @@
</div> </div>
</div> </div>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</body> </body>

@ -1,4 +1,4 @@
import {Component, NgModule} from '@angular/core'; import { Component } from '@angular/core';
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router'; import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
import {RecipeFormComponent} from "./component/recipe-form/recipe-form.component"; import {RecipeFormComponent} from "./component/recipe-form/recipe-form.component";
import {AccueilComponent} from "./component/accueil/accueil.component"; import {AccueilComponent} from "./component/accueil/accueil.component";
@ -13,6 +13,7 @@ import {LinkService} from "./service/link.service";
standalone: true, standalone: true,
imports: [RouterOutlet, AccueilComponent, RecipeFormComponent, NgForOf, NgIf, RecipeListComponent, RouterLink, RouterLinkActive], imports: [RouterOutlet, AccueilComponent, RecipeFormComponent, NgForOf, NgIf, RecipeListComponent, RouterLink, RouterLinkActive],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.css'
}) })
export class AppComponent { export class AppComponent {
links : Link[] = this.linkService.getLinks() links : Link[] = this.linkService.getLinks()
@ -21,12 +22,12 @@ export class AppComponent {
} }
onClickLogin(event: Event): void { onClickLogin(event: Event): void {
event.preventDefault(); event.preventDefault(); // Prevent the default anchor behavior
this.loginService.login(); this.loginService.login();
} }
onClickLogout(event: Event): void { onClickLogout(event: Event): void {
event.preventDefault(); event.preventDefault(); // Prevent the default anchor behavior
this.loginService.logout(); this.loginService.logout();
} }

@ -2,12 +2,7 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import {provideRouter, withComponentInputBinding} from '@angular/router'; import {provideRouter, withComponentInputBinding} from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { provideHttpClient } from "@angular/common/http";
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes,withComponentInputBinding())]
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes,withComponentInputBinding()),
provideHttpClient()
]
}; };

@ -3,11 +3,9 @@ import {AuthGuard} from "./guard.guard";
import {IngredientsComponent} from "./component/ingredients/ingredients.component"; import {IngredientsComponent} from "./component/ingredients/ingredients.component";
import {AccueilComponent} from "./component/accueil/accueil.component"; import {AccueilComponent} from "./component/accueil/accueil.component";
import {RecipeDetailComponent} from "./component/recipe-detail/recipe-detail.component"; import {RecipeDetailComponent} from "./component/recipe-detail/recipe-detail.component";
import {CommandComponent} from "./component/command/command.component";
export const routes: Routes = [ export const routes: Routes = [
{path: 'ingredients', component:IngredientsComponent,canActivate: [AuthGuard]}, {path: 'ingredients', component:IngredientsComponent,canActivate: [AuthGuard]},
{path: 'recipe/:id', component: RecipeDetailComponent}, {path: 'recipe/:id', component: RecipeDetailComponent},
{path: '', component:AccueilComponent}, {path: '', component:AccueilComponent}
{path: 'command', component: CommandComponent}
]; ];

@ -1,51 +0,0 @@
<style>
.command-list {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.recipe-item {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
margin: 10px 0;
padding: 10px 20px;
width: 80%;
display: flex;
justify-content: space-between;
align-items: center;
}
.recipe-item h3 {
margin: 0;
}
.recipe-item button {
background-color: #ff5252;
color: #fff;
border: none;
border-radius: 5px;
padding: 5px 10px;
cursor: pointer;
}
.recipe-item button:hover {
background-color: #ff0000;
}
</style>
<div class="command-list">
<h2>Ma Commande</h2>
<div *ngFor="let recipe of recipes" class="recipe-item">
<img src="{{recipe.$image}}" width="10%" alt="ImageRecipe"
onerror="this.onerror=null;this.src='https://placehold.co/100x100/black/white?text=Not+Found';">
<h3>{{ recipe.$name }}</h3>
<button (click)="removeRecipe(recipe)">Retirer</button>
</div>
<p *ngIf="recipes.length === 0">Aucune recette dans la commande.</p>
</div>

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CommandComponent } from './command.component';
describe('CommandComponent', () => {
let component: CommandComponent;
let fixture: ComponentFixture<CommandComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CommandComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CommandComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -1,25 +0,0 @@
import { Component } from '@angular/core';
import { CommandService } from '../../service/command.service';
import { Recipe } from '../../model/recipe.model';
import {NgFor, NgIf} from '@angular/common';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-command',
standalone: true,
imports: [NgFor, RouterLink, NgIf],
templateUrl: './command.component.html'
})
export class CommandComponent {
recipes: Recipe[] = [];
errorMessage: string | null = null;
constructor(private commandService: CommandService) {
this.recipes = this.commandService.getRecipes();
}
removeRecipe(recipe: Recipe) {
this.commandService.removeRecipe(recipe);
this.recipes = this.commandService.getRecipes();
}
}

@ -2,7 +2,7 @@
.ingredients { .ingredients {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
background-color: #bab6b6; background-color: #bab6b6;
margin: 0; margin: 0px;
padding: 20px; padding: 20px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -18,101 +18,33 @@
width: 200px; width: 200px;
margin: 20px; margin: 20px;
} }
.ingredient-card:hover { .ingredient-card:hover {
transform: scale(1.05); transform: scale(1.05);
} }
.ingredient-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.ingredient-content { .ingredient-content {
padding: 15px; padding: 15px;
} }
.ingredient-title { .ingredient-title {
font-size: 1em; font-size: 1em;
margin: 0 0 10px; margin: 0 0 10px;
} }
.ingredient-actions {
margin-top: 10px;
}
.ingredient-actions button {
margin-right: 5px;
}
.general-form-add {
margin-top: 2%;
background-color: #bab6b6;
float: left;
width: 49%;
}
.general-form-edit {
margin-top: 2%;
background-color: #bab6b6;
float: right;
width: 49%;
}
.form-group {
margin-bottom: 10px;
margin-right: 2%;
margin-left: 2%
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group input, .form-group textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
.button-form{
margin-left: 2%;
}
</style> </style>
<body> <body>
<div class="ingredients"> <div class="ingredients">
<div class="ingredient-card" *ngFor="let ingredient of ingredients">
<div class="ingredient-content">
<h2 class="ingredient-title">{{ ingredient.name }}</h2>
<p>{{ ingredient.description }}</p>
<div class="ingredient-actions">
<button (click)="edit(ingredient)">Modifier</button>
</div>
</div>
</div>
</div>
<div class="general-form-add"> <div class="ingredient-card" *ngFor="let ingredient of ingredients ">
<h2>Ajouter un ingrédient</h2> <img src="image1.jpg" alt="Recette 1" class="ingredient-image"
<div class="form-group"> onerror="this.onerror=null;this.src='https://placehold.co/100x100/black/white?text=Not+Found';">
<label for="newName">Nom:</label> <div class="ingredient-content">
<input id="newName" [(ngModel)]="newIngredient.name" required> <h2 class="ingredient-title">{{ ingredient.$name }}</h2>
</div> </div>
<div class="form-group">
<label for="newDescription">Description:</label>
<textarea id="newDescription" [(ngModel)]="newIngredient.description" required></textarea>
</div> </div>
<button class="button-form" (click)="addIngredient()">Ajouter</button>
</div>
<div *ngIf="editIngredient" class="general-form-edit">
<h2>Modifier l'ingrédient</h2>
<div class="form-group">
<label for="editName">Nom:</label>
<input id="editName" [(ngModel)]="editIngredient.name">
</div>
<div class="form-group">
<label for="editDescription">Description:</label>
<textarea id="editDescription" [(ngModel)]="editIngredient.description"></textarea>
</div> </div>
<button class="button-form" (click)="updateIngredient()">Enregistrer</button>
<button class="button-form" (click)="cancelEdit()">Annuler</button>
</div>
</body> </body>

@ -1,62 +1,45 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import { IngredientService } from "../../service/ingredient.service"; import {IngredientService} from "../../service/ingredient.service";
import { Ingredient } from "../../model/ingredient.model"; import {Ingredient} from "../../model/ingredient.model";
import { NgFor, NgIf } from "@angular/common"; import {NgFor} from "@angular/common";
import { FormsModule } from "@angular/forms"; import {LoginService} from "../../service/login.service";
import { HttpClient } from "@angular/common/http"; import {Link} from "../../model/link.model";
import {LinkService} from "../../service/link.service";
@Component({ @Component({
selector: 'app-ingredients', selector: 'app-ingredients',
standalone: true, standalone: true,
imports: [ imports: [
NgFor, NgFor,
NgIf,
FormsModule
], ],
providers: [IngredientService, HttpClient],
templateUrl: './ingredients.component.html', templateUrl: './ingredients.component.html',
styleUrl: './ingredients.component.css' styleUrl: './ingredients.component.css'
}) })
export class IngredientsComponent implements OnInit { export class IngredientsComponent implements OnInit{
ingredients: Ingredient[] = []; ingredients: Ingredient[] = [];
newIngredient: Ingredient = { id: '0', name: '', description: '' }; links: Link[] = this.linkService.getLinks()
editIngredient: Ingredient | null = null;
constructor(private ingredientService: IngredientService) {} constructor(private ingredientService : IngredientService,private loginService: LoginService,private linkService : LinkService) {}
ngOnInit() { ngOnInit() {
this.loadIngredients();
}
loadIngredients() {
this.ingredientService.getAll().subscribe(ingredients => { this.ingredientService.getAll().subscribe(ingredients => {
this.ingredients = ingredients; this.ingredients = ingredients;
}); });
} }
addIngredient() { onClickLogin(event: Event): void {
this.ingredientService.add(this.newIngredient).subscribe(ingredient => { event.preventDefault(); // Prevent the default anchor behavior
this.ingredients.push(ingredient); this.loginService.login();
this.newIngredient = { id: '0', name: '', description: '' };
});
} }
edit(ingredient: Ingredient) { onClickLogout(event: Event): void {
this.editIngredient = { ...ingredient }; event.preventDefault(); // Prevent the default anchor behavior
this.loginService.logout();
} }
updateIngredient() { isLogged(): boolean {
if (this.editIngredient) { return this.loginService.isLogged();
this.ingredientService.update(this.editIngredient).subscribe(updatedIngredient => {
const index = this.ingredients.findIndex(i => i.id === updatedIngredient.id);
this.ingredients[index] = updatedIngredient;
this.editIngredient = null;
});
}
} }
cancelEdit() {
this.editIngredient = null;
}
} }

@ -1,85 +1,7 @@
<style> <h1>{{recipe?.$name}}</h1>
.recipe-details { <p> {{recipe?.$createdAt}}</p>
font-family: Arial, sans-serif;
background-color: #bab6b6;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.recipe-card { <h3>{{recipe?.$description}}</h3>
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.2s;
width: 80%;
margin: 20px;
padding: 20px;
}
.recipe-card:hover { <img [src]="recipe?.$image" alt="Recette 1" class="recipe-image"
transform: scale(1.05); onerror="this.onerror=null;this.src='https://placehold.co/100x100/black/white?text=Not+Found';">
}
.recipe-image {
width: 100%;
max-width: 300px;
border-radius: 10px;
}
.recipe-content {
padding: 15px;
text-align: center;
}
.recipe-title {
font-size: 2em;
margin: 0 0 10px;
}
.recipe-date {
color: #777;
margin-bottom: 20px;
}
.recipe-description {
font-size: 1.2em;
margin: 20px 0;
}
.ingredient-list {
list-style-type: none;
padding: 0;
}
.ingredient-item {
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
margin: 5px 0;
}
</style>
<div class="recipe-details">
<div class="recipe-card">
<h1 class="recipe-title">{{ recipe?.$name }}</h1>
<p class="recipe-date">{{ recipe?.$createdAt }}</p>
<img [src]="recipe?.$image" alt="{{ recipe?.$name }}" class="recipe-image"
onerror="this.onerror=null;this.src='https://placehold.co/300x300/black/white?text=Not+Found';">
<div class="recipe-content">
<h3 class="recipe-description">{{ recipe?.$description }}</h3>
<h4>Ingrédients</h4>
<ul class="ingredient-list">
<li class="ingredient-item" *ngFor="let ingredient of ingredients">
{{ ingredient.ingredient.name }} : {{ ingredient.quantity }} {{ ingredient.unit }}
</li>
</ul>
</div>
</div>
</div>

@ -3,7 +3,6 @@ import {Recipe} from "../../model/recipe.model";
import {RecipeService} from "../../service/recipe.service"; import {RecipeService} from "../../service/recipe.service";
import {RouterLink, RouterLinkActive, RouterOutlet} from "@angular/router"; import {RouterLink, RouterLinkActive, RouterOutlet} from "@angular/router";
import {NgFor} from "@angular/common"; import {NgFor} from "@angular/common";
import {QuantifiedIngredientGetter} from "../../model/quantified-ingredient-getter.model";
@Component({ @Component({
selector: 'app-recipe-detail', selector: 'app-recipe-detail',
@ -15,7 +14,6 @@ import {QuantifiedIngredientGetter} from "../../model/quantified-ingredient-gett
export class RecipeDetailComponent { export class RecipeDetailComponent {
recipe : Recipe|null = null; recipe : Recipe|null = null;
ingredients : QuantifiedIngredientGetter[] = [];
constructor(private recipeService : RecipeService) { constructor(private recipeService : RecipeService) {
} }
@ -23,9 +21,6 @@ export class RecipeDetailComponent {
@Input() @Input()
set id(id: number) { set id(id: number) {
this.recipe = this.recipeService.getRecipe(id)!; this.recipe = this.recipeService.getRecipe(id)!;
this.ingredients = this.recipe.$ingredients;
} }
} }

@ -122,7 +122,7 @@
<label for="ingredient-name">Ingredient:</label> <label for="ingredient-name">Ingredient:</label>
<select id="ingredient-name" formControlName="ingredient" required> <select id="ingredient-name" formControlName="ingredient" required>
<option *ngFor="let ingredient of ingredientsList" [value]="ingredient.name">{{ ingredient.name }}</option> <option *ngFor="let ingredient of ingredientsList" [value]="ingredient.$name">{{ ingredient.$name }}</option>
</select> </select>
<button type="button" class="remove-button" (click)="removeIngredient(i)">Remove</button> <button type="button" class="remove-button" (click)="removeIngredient(i)">Remove</button>
@ -132,7 +132,7 @@
<button type="button" class="add-ingredient-button" (click)="addIngredient()">Add Ingredient</button> <button type="button" class="add-ingredient-button" (click)="addIngredient()">Add Ingredient</button>
<p>Complete the form to enable the button.</p> <p>Complete the form to enable the button.</p>
<button type="submit" class="submit-button">Submit</button> <button type="submit" class="submit-button" [disabled]="!recipeForm.valid">Submit</button>
</form> </form>
</div> </div>
</body> </body>

@ -83,17 +83,14 @@ export class RecipeFormComponent {
$ingredients: this.recipeForm.value.ingredients!.map((ingredient: any) => ({ $ingredients: this.recipeForm.value.ingredients!.map((ingredient: any) => ({
quantity: ingredient.quantity, quantity: ingredient.quantity,
unit: ingredient.unit, unit: ingredient.unit,
ingredient: this.ingredientsList.find(ing => ing.name === ingredient.ingredient) ingredient: this.ingredientsList.find(ing => ing.$name === ingredient.ingredient)
})), })),
$image: this.recipeForm.value.image $image: this.recipeForm.value.image
}; };
this.formSubmitted.emit(recipe); this.formSubmitted.emit(recipe);
this.recipeForm.reset() this.recipeForm.reset()
this.ingredients.clear() this.ingredients.clear()
this.imageBase64 = null;
this.addIngredient() this.addIngredient()
}else{
alert("Veuillez remplir tous les champs")
} }
} }
} }

@ -71,14 +71,12 @@
border: 1px solid #bbb; border: 1px solid #bbb;
border-radius: 5px; border-radius: 5px;
} }
.error-message { .page-link.active {
color: red; background-color: #333;
font-weight: bold; color: white;
margin-bottom: 4%;
} }
</style> </style>
<body> <body>
<div *ngIf="errorMessageDisplay" class="error-message">{{ errorMessage }}</div>
<div [ngClass]="{'four-column': !isFormVisible, 'two-column': isFormVisible}" class="recipe-container"> <div [ngClass]="{'four-column': !isFormVisible, 'two-column': isFormVisible}" class="recipe-container">
<div class="recipe-card" *ngFor="let recipe of paginatedRecipes"> <div class="recipe-card" *ngFor="let recipe of paginatedRecipes">
<img [src]="recipe.$image" alt="Recette 1" class="recipe-image" <img [src]="recipe.$image" alt="Recette 1" class="recipe-image"
@ -87,13 +85,12 @@
<h2 class="recipe-title">{{recipe.$name}}</h2> <h2 class="recipe-title">{{recipe.$name}}</h2>
<p class="recipe-description">{{recipe.$description}}</p> <p class="recipe-description">{{recipe.$description}}</p>
<button (click)="detailsRecipe(recipe.$id)" class="details-button">Détails</button> <button (click)="detailsRecipe(recipe.$id)" class="details-button">Détails</button>
<button (click)="addToCommand(recipe)" class="details-button" >Ajouter à ma commande</button>
</div> </div>
</div> </div>
</div> </div>
<div class="pagination"> <div class="pagination">
<button class="page-link" (click)="previousPage()" [disabled]="currentPage === 0">Précédent</button> <button class="page-link" (click)="previousPage()" [disabled]="currentPage === 0">Précédent</button>
<p>{{ currentPage +1 }}</p> <!-- <span class="page-link" *ngFor="let page of totalPages; let i = index" [class.active]="currentPage === i" (click)="changePage(i)">{{ i + 1 }}</span>-->
<button class="page-link" (click)="nextPage()" [disabled]="currentPage === totalPages - 1">Suivant</button> <button class="page-link" (click)="nextPage()" [disabled]="currentPage === totalPages - 1">Suivant</button>
</div> </div>
</body> </body>

@ -1,10 +1,9 @@
import {Component, Input, OnInit} from '@angular/core'; import {Component, Input, OnInit} from '@angular/core';
import { RecipeService } from '../../service/recipe.service'; import { RecipeService } from '../../service/recipe.service';
import { Recipe } from '../../model/recipe.model'; import { Recipe } from '../../model/recipe.model';
import {NgClass, NgIf, NgOptimizedImage} from "@angular/common"; import {NgClass, NgOptimizedImage} from "@angular/common";
import {NgFor} from "@angular/common"; import {NgFor} from "@angular/common";
import {Router, RouterLinkActive} from "@angular/router"; import {Router, RouterLinkActive} from "@angular/router";
import {CommandService} from "../../service/command.service";
@Component({ @Component({
selector: 'app-recipe-list', selector: 'app-recipe-list',
@ -13,23 +12,19 @@ import {CommandService} from "../../service/command.service";
NgOptimizedImage, NgOptimizedImage,
NgFor, NgFor,
NgClass, NgClass,
RouterLinkActive, RouterLinkActive
NgIf
], ],
standalone: true standalone: true
}) })
export class RecipeListComponent implements OnInit { export class RecipeListComponent implements OnInit {
recipes: Recipe[] = []; recipes: Recipe[] = [];
errorMessageDisplay: boolean = false;
errorMessage: string | null = null;
@Input() isFormVisible!: boolean; @Input() isFormVisible!: boolean;
currentPage: number = 0; currentPage: number = 0;
pageSize: number = 4; pageSize: number = 4;
totalPages: any = 0; totalPages: any = 0;
constructor(private recipeService: RecipeService,private router : Router, private commandService: CommandService) {} constructor(private recipeService: RecipeService,private router : Router) {}
ngOnInit(): void { ngOnInit(): void {
this.recipes = this.recipeService.getRecipes(); this.recipes = this.recipeService.getRecipes();
@ -60,19 +55,4 @@ export class RecipeListComponent implements OnInit {
detailsRecipe(id : number){ detailsRecipe(id : number){
this.router.navigateByUrl('/recipe/'+id); this.router.navigateByUrl('/recipe/'+id);
} }
addToCommand(recipe: Recipe) {
try {
this.commandService.addRecipe(recipe);
this.errorMessageDisplay = false;
} catch (e) {
if (e instanceof DOMException && e.name === 'QuotaExceededError') {
this.errorMessageDisplay = true;
this.errorMessage = 'Local Storage saturé, impossible d\'ajouter la recette dans le local storage des commandes.';
} else {
throw e;
}
}
}
} }

@ -2,7 +2,5 @@ import {Link} from "../model/link.model";
export var LINKS :Link[] = [ export var LINKS :Link[] = [
{$name:"Accueil",$link:""}, {$name:"Accueil",$link:""},
{$name:"Ingredients",$link:"/ingredients"}, {$name:"Ingredients",$link:"/ingredients"}
{$name:"Commande",$link:"/command"} ]
]

@ -1,6 +1,5 @@
export interface Ingredient { export interface Ingredient {
id : string, $id : number,
name : string, $name : string
description : string,
} }

@ -1,23 +0,0 @@
import { Unity } from "./unity";
import { Ingredient } from "./ingredient.model";
import { QuantifiedIngredient } from "./quantified-ingredient.model";
export class QuantifiedIngredientGetter implements QuantifiedIngredient {
constructor(
public $quantity: number,
public $unit: Unity,
public $ingredient: Ingredient
) {}
get quantity(): number {
return this.$quantity;
}
get unit(): Unity {
return this.$unit;
}
get ingredient(): Ingredient {
return this.$ingredient;
}
}

@ -1,8 +1,8 @@
import { Unity } from "./unity"; import {Unity} from "./unity";
import { Ingredient } from "./ingredient.model"; import {Ingredient} from "./ingredient.model";
export interface QuantifiedIngredient { export interface QuantifiedIngredient {
$quantity: number; $quantity : number,
$unit: Unity; $unit : Unity,
$ingredient: Ingredient; $ingredient : Ingredient
} }

@ -1,10 +1,10 @@
import {QuantifiedIngredientGetter} from "./quantified-ingredient-getter.model"; import {QuantifiedIngredient} from "./quantified-ingredient.model";
export interface Recipe { export interface Recipe {
$id : number, $id : number,
$name : string, $name : string,
$description: string, $description: string,
$createdAt : Date, $createdAt : Date,
$ingredients: QuantifiedIngredientGetter[], $ingredients: QuantifiedIngredient[],
$image?: string $image?: string
} }

@ -1,25 +0,0 @@
import { Injectable } from '@angular/core';
import { Recipe } from "../model/recipe.model";
@Injectable({
providedIn: 'root'
})
export class CommandService {
private recipes: Recipe[] = JSON.parse(localStorage.getItem('command') || '[]');
addRecipe(recipe: Recipe) {
this.recipes.push(recipe);
localStorage.setItem('command', JSON.stringify(this.recipes));
}
getRecipes(): Recipe[] {
return this.recipes;
}
removeRecipe(recipe: Recipe) {
const index = this.recipes.indexOf(recipe);
if (index > -1) {
this.recipes.splice(index, 1);
}
}
}

@ -1,26 +1,15 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Ingredient } from "../model/ingredient.model"; import {Ingredient} from "../model/ingredient.model";
import { Observable } from "rxjs"; import {$INGREDIENTS} from "../data/ingredient.stub";
import { HttpClient } from "@angular/common/http"; import {Observable, of} from "rxjs";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class IngredientService { export class IngredientService {
private apiUrl = 'https://664ba07f35bbda10987d9f99.mockapi.io/api/ingredients'; getAll() : Observable<Ingredient[]> {
return of($INGREDIENTS);
constructor(private http: HttpClient) { }
getAll(): Observable<Ingredient[]> {
return this.http.get<Ingredient[]>(this.apiUrl);
}
add(ingredient: Ingredient): Observable<Ingredient> {
return this.http.post<Ingredient>(this.apiUrl, ingredient);
}
update(ingredient: Ingredient): Observable<Ingredient> {
return this.http.put<Ingredient>(`${this.apiUrl}/${ingredient.id}`, ingredient);
} }
} }

Loading…
Cancel
Save