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