parent
610ce4e707
commit
373e352576
@ -0,0 +1,117 @@
|
|||||||
|
<form (ngSubmit)="onSubmit()" #recipeForm="ngForm" class="ng-submitted">
|
||||||
|
<div class="form-group my-2 col-md-4">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Name"
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
[(ngModel)]="recipe.name"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
#nameField="ngModel"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
*ngIf="
|
||||||
|
nameField.invalid &&
|
||||||
|
(nameField.dirty || nameField.touched || recipeForm.submitted)
|
||||||
|
"
|
||||||
|
class="text-danger"
|
||||||
|
>
|
||||||
|
<div *ngIf="nameField.errors && nameField.errors['required']">
|
||||||
|
Name is required.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group my-2 col-md-4">
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<textarea
|
||||||
|
class="form-control"
|
||||||
|
id="description"
|
||||||
|
[(ngModel)]="recipe.description"
|
||||||
|
name="description"
|
||||||
|
required
|
||||||
|
rows="2"
|
||||||
|
maxlength="{{ maxDescriptionLength }}"
|
||||||
|
#descriptionField="ngModel"
|
||||||
|
></textarea>
|
||||||
|
<div class="text-muted text-end">
|
||||||
|
Characters left: {{ maxDescriptionLength - recipe.description.length }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="
|
||||||
|
descriptionField.invalid &&
|
||||||
|
(descriptionField.dirty ||
|
||||||
|
descriptionField.touched ||
|
||||||
|
recipeForm.submitted)
|
||||||
|
"
|
||||||
|
class="text-danger"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
*ngIf="descriptionField.errors && descriptionField.errors['required']"
|
||||||
|
>
|
||||||
|
Description is required.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group my-3">
|
||||||
|
<div class="custom-file">
|
||||||
|
<label for="image">Image</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
class="custom-file-input ms-1"
|
||||||
|
id="image"
|
||||||
|
(change)="onFileChange($event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-form-field class="my-3 me-3">
|
||||||
|
<mat-label>Ingredients</mat-label>
|
||||||
|
<mat-select [(ngModel)]="selectedIngredient" name="selectedIngredient">
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let ingredient of ingredients"
|
||||||
|
[value]="ingredient.id"
|
||||||
|
>
|
||||||
|
{{ ingredient.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="me-3"
|
||||||
|
mat-raised-button
|
||||||
|
type="button"
|
||||||
|
(click)="addIngredient()"
|
||||||
|
>
|
||||||
|
Add Ingredient
|
||||||
|
</button>
|
||||||
|
<div class="mb-3" *ngFor="let ingredientId of getIngredientKeys()">
|
||||||
|
<div>
|
||||||
|
{{ findIngredientName(ingredientId) }}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="ingredientQuantities[ingredientId]"
|
||||||
|
name="quantity_{{ ingredientId }}"
|
||||||
|
min="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="getIngredientKeys().length === 0 && recipeForm.submitted"
|
||||||
|
class="text-danger"
|
||||||
|
>
|
||||||
|
At least one ingredient is required.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
mat-raised-button
|
||||||
|
type="submit"
|
||||||
|
[disabled]="!recipeForm.form.valid || getIngredientKeys().length === 0"
|
||||||
|
[ngClass]="{ 'disabled-button': getIngredientKeys().length === 0 }"
|
||||||
|
>
|
||||||
|
Edit Recipe
|
||||||
|
</button>
|
||||||
|
</form>
|
@ -0,0 +1,141 @@
|
|||||||
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule, NgForm } from '@angular/forms';
|
||||||
|
import { RecipeService } from '../services/recipe.service';
|
||||||
|
import { IngredientService } from '../services/ingredient.service';
|
||||||
|
import { Recipe } from '../models/recipe';
|
||||||
|
import { IngredientRecipe } from '../models/ingredient-recipe';
|
||||||
|
import { Ingredient } from '../models/ingredient';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-recipe-edit',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './recipe-edit.component.html',
|
||||||
|
styleUrls: ['./recipe-edit.component.scss'],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class RecipeEditComponent implements OnInit {
|
||||||
|
recipe: Recipe = new Recipe(0, '', '', '', []);
|
||||||
|
ingredients: Ingredient[] = [];
|
||||||
|
ingredientQuantities: { [key: number]: number } = {};
|
||||||
|
selectedIngredient: number | null = null;
|
||||||
|
imageError: string | null = null;
|
||||||
|
maxDescriptionLength: number = 200;
|
||||||
|
isSubmitting: boolean = false;
|
||||||
|
@ViewChild('recipeForm', { static: true }) recipeForm!: NgForm;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private recipeService: RecipeService,
|
||||||
|
private ingredientService: IngredientService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.ingredients = this.ingredientService.getIngredients();
|
||||||
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
|
if (id) {
|
||||||
|
const recipeId = parseInt(id, 10);
|
||||||
|
this.recipe =
|
||||||
|
this.recipeService.getRecipeById(recipeId) ??
|
||||||
|
new Recipe(0, '', '', '', []);
|
||||||
|
this.loadIngredientQuantities(recipeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileChange(event: any): void {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const validImageTypes = [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/gif',
|
||||||
|
'image/jpg',
|
||||||
|
];
|
||||||
|
if (!validImageTypes.includes(file.type)) {
|
||||||
|
this.imageError = 'Invalid file type. Please select an image file.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.imageError = null;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
this.recipe.image = reader.result as string;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadIngredientQuantities(recipeId: number): void {
|
||||||
|
const ingredientRecipes = this.recipeService.getIngredientRecipes(recipeId);
|
||||||
|
ingredientRecipes.forEach((ingredientRecipe) => {
|
||||||
|
this.ingredientQuantities[ingredientRecipe.idIngredient] =
|
||||||
|
ingredientRecipe.quantity;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addIngredient(): void {
|
||||||
|
if (
|
||||||
|
this.selectedIngredient !== null &&
|
||||||
|
!this.ingredientQuantities[this.selectedIngredient]
|
||||||
|
) {
|
||||||
|
this.ingredientQuantities[this.selectedIngredient] = 1;
|
||||||
|
this.selectedIngredient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeIngredient(id: number): void {
|
||||||
|
delete this.ingredientQuantities[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
getIngredientKeys(): number[] {
|
||||||
|
return Object.keys(this.ingredientQuantities).map((key) =>
|
||||||
|
parseInt(key, 10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
findIngredientName(id: number): string {
|
||||||
|
const ingredient = this.ingredients.find(
|
||||||
|
(ingredient) => ingredient.id === id
|
||||||
|
);
|
||||||
|
return ingredient ? ingredient.name : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.isSubmitting) return;
|
||||||
|
|
||||||
|
this.isSubmitting = true;
|
||||||
|
|
||||||
|
this.recipe.ingredients = this.getIngredientKeys().map((id) => {
|
||||||
|
const ingredient = this.ingredientService.getIngredient(id);
|
||||||
|
return new Ingredient(id, ingredient.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.recipeService.updateRecipe(this.recipe);
|
||||||
|
|
||||||
|
this.getIngredientKeys().forEach((id) => {
|
||||||
|
const quantity = this.ingredientQuantities[id];
|
||||||
|
const ingredientRecipe = new IngredientRecipe(
|
||||||
|
id,
|
||||||
|
this.recipe.id,
|
||||||
|
quantity
|
||||||
|
);
|
||||||
|
this.recipeService.updateIngredientRecipe(ingredientRecipe);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.router.navigate(['/list']).then(() => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue