Merge branch 'main' of github.com:Poomipat-Ch/taobin_recipe_manager

This commit is contained in:
Kenta420 2024-01-22 12:50:23 +07:00
commit d6cba315ec
7 changed files with 249 additions and 84 deletions

View file

@ -274,4 +274,14 @@ export class RecipeService {
} }
); );
} }
async getRawRecipeOfProductCode(country: string, filename: string, productCode: string): Promise<Observable<{}>> {
return this._httpClient.get<{}>(
environment.api + '/recipes/' + country + '/' + filename + '/' + productCode + '/raw_full',
{
withCredentials: true,
responseType: 'json',
}
);
}
} }

View file

@ -2,7 +2,7 @@ import { CommonModule, DatePipe } from '@angular/common';
import { Component, EventEmitter, OnInit } from '@angular/core'; import { Component, EventEmitter, OnInit } from '@angular/core';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import {Observable, first, map} from 'rxjs'; import { Observable, first, map } from 'rxjs';
import { RecipeService } from 'src/app/core/services/recipe.service'; import { RecipeService } from 'src/app/core/services/recipe.service';
import { ConfirmModal } from 'src/app/shared/modal/confirm/confirm-modal.component'; import { ConfirmModal } from 'src/app/shared/modal/confirm/confirm-modal.component';
import { animate, style, transition, trigger } from '@angular/animations'; import { animate, style, transition, trigger } from '@angular/animations';
@ -20,28 +20,29 @@ import { UserPermissions } from 'src/app/core/auth/userPermissions';
import { ToppingService } from 'src/app/core/services/topping.service'; import { ToppingService } from 'src/app/core/services/topping.service';
import { copy, transformToTSV } from 'src/app/shared/helpers/copy'; import { copy, transformToTSV } from 'src/app/shared/helpers/copy';
import { isEqual } from 'lodash';
@Component({ @Component({
selector: 'app-recipe-details', selector: 'app-recipe-details',
templateUrl: './recipe-details.component.html', templateUrl: './recipe-details.component.html',
standalone: true, standalone: true,
animations: [ animations: [
trigger('inOutAnimation', [ trigger('inOutAnimation', [
transition(':enter', [ transition(':enter', [
style({ opacity: 0 }), style({ opacity: 0 }),
animate('1s ease-out', style({ opacity: 1 })), animate('1s ease-out', style({ opacity: 1 })),
]), ]),
]), ]),
], ],
imports: [ imports: [
CommonModule, CommonModule,
RouterLink, RouterLink,
ReactiveFormsModule, ReactiveFormsModule,
ConfirmModal, ConfirmModal,
DatePipe, DatePipe,
RecipeListComponent, RecipeListComponent,
FormsModule FormsModule,
] ],
}) })
export class RecipeDetailsComponent implements OnInit { export class RecipeDetailsComponent implements OnInit {
title: string = 'Recipe Detail'; title: string = 'Recipe Detail';
@ -56,7 +57,7 @@ export class RecipeDetailsComponent implements OnInit {
recipeOriginalDetail!: typeof this.recipeDetailForm.value; recipeOriginalDetail!: typeof this.recipeDetailForm.value;
commit_msg :string = ""; commit_msg: string = '';
// //
department: string = this._route.parent!.snapshot.params['department']; department: string = this._route.parent!.snapshot.params['department'];
@ -86,20 +87,21 @@ export class RecipeDetailsComponent implements OnInit {
disable: false, disable: false,
}); });
repl = [] repl = [];
tpl = [] tpl = [];
toppingSet: ToppingSet[] | null = null; toppingSet: ToppingSet[] | null = null;
submenus: Recipe01[] | null = null; submenus: Recipe01[] | null = null;
rawRecipe: {} | undefined | null = undefined;
async ngOnInit() { async ngOnInit() {
this.productCode = this._route.snapshot.params['productCode']; this.productCode = this._route.snapshot.params['productCode'];
this.recipeDetail$ = (await this._recipeService this.recipeDetail$ = (
.getRecipeDetail(this.productCode)) await this._recipeService.getRecipeDetail(this.productCode)
.pipe(first()); ).pipe(first());
this.recipeDetail$.subscribe((detail) => { this.recipeDetail$.subscribe((detail) => {
// console.log('Recipe Detail', detail); // console.log('Recipe Detail', detail);
this.recipeDetailForm.patchValue(detail); this.recipeDetailForm.patchValue(detail);
@ -107,18 +109,41 @@ export class RecipeDetailsComponent implements OnInit {
this.recipeOriginalDetail = { ...this.recipeDetailForm.getRawValue() }; this.recipeOriginalDetail = { ...this.recipeDetailForm.getRawValue() };
}); });
this._recipeService.getSubMenus(await this._recipeService.getCurrentCountry(), this._recipeService.getCurrentFile(), this.productCode).subscribe((data) => { this._recipeService
console.log('Submenus', data); .getSubMenus(
this.submenus = data; await this._recipeService.getCurrentCountry(),
}); this._recipeService.getCurrentFile(),
this.productCode
)
.subscribe((data) => {
console.log('Submenus', data);
this.submenus = data;
});
this.recipeDetailForm.valueChanges.subscribe(this.onRecipeDetailFormChange); this.recipeDetailForm.valueChanges.subscribe(this.onRecipeDetailFormChange);
(
(await this._toppingService.getToppingsOfRecipe(this.department, this._recipeService.getCurrentFile(), this.productCode)).subscribe((data) => { await this._toppingService.getToppingsOfRecipe(
this.department,
this._recipeService.getCurrentFile(),
this.productCode
)
).subscribe((data) => {
this.toppingSet = data; this.toppingSet = data;
// console.log('Toppings', data); // console.log('Toppings', data);
}) });
// get full raw value of this recipe
(
await this._recipeService.getRawRecipeOfProductCode(
this.department,
this._recipeService.getCurrentFile(),
this.productCode
)
).subscribe((data) => {
console.log('Raw Recipe', data);
this.rawRecipe = data;
});
// snap recipe detail form value // snap recipe detail form value
@ -145,34 +170,59 @@ export class RecipeDetailsComponent implements OnInit {
console.log('confirm save'); console.log('confirm save');
// get username // get username
let username:string = "" let username: string = '';
username = this._userService.getCurrentUser()!.name; username = this._userService.getCurrentUser()!.name;
// diff with rawValue, then send
let to_send = { let to_send = {
edit_by: username, edit_by: username,
commit_msg: this.commit_msg, commit_msg: this.commit_msg,
productCode: this.changedProductCode == undefined ? this.productCode : this.changedProductCode, productCode:
name: this.recipeDetailForm.getRawValue().name != this.recipeOriginalDetail.name ? this.recipeDetailForm.getRawValue().name : this.recipeOriginalDetail.name, this.changedProductCode == undefined
otherName: this.recipeDetailForm.getRawValue().otherName != this.recipeOriginalDetail.otherName ? this.recipeDetailForm.getRawValue().otherName : this.recipeOriginalDetail.otherName, ? this.productCode
description: this.recipeDetailForm.getRawValue().description != this.recipeOriginalDetail.description ? this.recipeDetailForm.getRawValue().description : this.recipeOriginalDetail.description, : this.changedProductCode,
otherDescription: this.recipeDetailForm.getRawValue().otherDescription != this.recipeOriginalDetail.otherDescription ? this.recipeDetailForm.getRawValue().otherDescription : this.recipeOriginalDetail.otherDescription, name:
LastChange: this.recipeDetailForm.getRawValue().lastModified != this.recipeOriginalDetail.lastModified ? this.recipeDetailForm.getRawValue().lastModified : this.recipeOriginalDetail.lastModified, this.recipeDetailForm.getRawValue().name !=
price: this.recipeDetailForm.getRawValue().price != this.recipeOriginalDetail.price ? this.recipeDetailForm.getRawValue().price : this.recipeOriginalDetail.price, this.recipeOriginalDetail.name
// isUse: this, ? this.recipeDetailForm.getRawValue().name
// isShow: null, : this.recipeOriginalDetail.name,
// disable: null, otherName:
recipes: [ this.recipeDetailForm.getRawValue().otherName !=
...(this.repl.length <= 0 ? [] : this.repl) this.recipeOriginalDetail.otherName
], ? this.recipeDetailForm.getRawValue().otherName
ToppingSet: [ : this.recipeOriginalDetail.otherName,
...(this.tpl.length <= 0 ? [] : this.tpl) description:
] this.recipeDetailForm.getRawValue().description !=
} this.recipeOriginalDetail.description
? this.recipeDetailForm.getRawValue().description
: this.recipeOriginalDetail.description,
otherDescription:
this.recipeDetailForm.getRawValue().otherDescription !=
this.recipeOriginalDetail.otherDescription
? this.recipeDetailForm.getRawValue().otherDescription
: this.recipeOriginalDetail.otherDescription,
LastChange:
this.recipeDetailForm.getRawValue().lastModified !=
this.recipeOriginalDetail.lastModified
? this.recipeDetailForm.getRawValue().lastModified
: this.recipeOriginalDetail.lastModified,
price:
this.recipeDetailForm.getRawValue().price !=
this.recipeOriginalDetail.price
? this.recipeDetailForm.getRawValue().price
: this.recipeOriginalDetail.price,
isUse: (this.rawRecipe as any).isUse,
isShow: (this.rawRecipe as any).isShow,
disable: (this.rawRecipe as any).disable,
recipes: [...(this.repl.length <= 0 ? [] : this.repl)],
SubMenu: [...(this.rawRecipe! as any).SubMenu!],
ToppingSet: [...(this.rawRecipe as any).ToppingSet],
};
this.concatNoEditKeyToMap(this.rawRecipe, to_send);
// TODO: update value in targeted recipe
console.log('to_send', to_send); console.log('to_send', to_send);
this._recipeService.editChanges( this._recipeService.editChanges(
await this._recipeService.getCurrentCountry(this.department), await this._recipeService.getCurrentCountry(this.department),
@ -182,14 +232,15 @@ export class RecipeDetailsComponent implements OnInit {
} }
); );
console.log('Sending changes'); console.log('Sending changes');
void this._router.navigate(['/'+this.department+'/recipes']); void this._router
.navigate(['/' + this.department + '/recipes'])
.then(() => {
window.location.reload();
});
}, },
}; };
onKeyUpCommitMsg(e: any){ onKeyUpCommitMsg(e: any) {
this.commit_msg = e.target.value; this.commit_msg = e.target.value;
} }
@ -198,7 +249,7 @@ export class RecipeDetailsComponent implements OnInit {
message: 'Do you want to close without saving?', message: 'Do you want to close without saving?',
confirmCallBack: () => { confirmCallBack: () => {
console.log('confirm close'); console.log('confirm close');
this._router.navigate(['/'+this.department+'/recipes']); this._router.navigate(['/' + this.department + '/recipes']);
}, },
}; };
@ -206,7 +257,7 @@ export class RecipeDetailsComponent implements OnInit {
if (this.isValueChanged) { if (this.isValueChanged) {
this.showConfirmSaveModal.emit(true); this.showConfirmSaveModal.emit(true);
} else { } else {
this._router.navigate(['/'+this.department+'/recipes']); this._router.navigate(['/' + this.department + '/recipes']);
} }
} }
@ -214,7 +265,7 @@ export class RecipeDetailsComponent implements OnInit {
if (this.isValueChanged) { if (this.isValueChanged) {
this.showConfirmCloseModal.emit(true); this.showConfirmCloseModal.emit(true);
} else { } else {
this._router.navigate(['/'+this.department+'/recipes']); this._router.navigate(['/' + this.department + '/recipes']);
} }
} }
@ -225,9 +276,36 @@ export class RecipeDetailsComponent implements OnInit {
} }
onRecipeListFormChange(repl: unknown[]) { onRecipeListFormChange(repl: unknown[]) {
console.log('Recipe List Form Changed', repl); // console.log('Recipe List Form Changed', repl);
this.repl = repl[1] as never[]; this.repl = repl[1] as never[];
this.tpl = repl[0] as never[]; this.tpl = repl[0] as never[];
// check length of toppinglist
console.log(
'tpl length',
this.tpl.length,
'original length',
(this.rawRecipe! as any).ToppingSet.length
);
for (let ti = 0; ti < this.tpl.length; ti++) {
// check at the same index
if (!isEqual(this.tpl[ti][0], (this.rawRecipe as any).ToppingSet[ti])) {
console.log(
'topping list changed',
ti,
this.tpl[ti][0],
(this.rawRecipe as any).ToppingSet[ti]
);
// update raw recipe
(this.rawRecipe as any).ToppingSet[ti] = this.tpl[ti][0];
console.log(
'after update topping',
(this.rawRecipe as any).ToppingSet[ti]
);
}
}
// console.log('Recipe List Form Changed', this.repl, this.tpl);
this.isValueChanged ||= repl != undefined; this.isValueChanged ||= repl != undefined;
} }
@ -235,16 +313,16 @@ export class RecipeDetailsComponent implements OnInit {
// console.log('Topping List Form Changed', tpl); // console.log('Topping List Form Changed', tpl);
this.tpl = tpl as never[]; this.tpl = tpl as never[];
this.isValueChanged ||= tpl != undefined; this.isValueChanged ||= tpl != undefined;
} }
isEditable(){ isEditable() {
return this._userService.getCurrentUser()!.permissions.includes(UserPermissions.EDITOR); return this._userService
.getCurrentUser()!
.permissions.includes(UserPermissions.EDITOR);
} }
onProductCodeChange(event: any) { onProductCodeChange(event: any) {
if(event.target.value != ""){ if (event.target.value != '') {
this.changedProductCode = event.target.value; this.changedProductCode = event.target.value;
} }
} }
@ -252,10 +330,10 @@ export class RecipeDetailsComponent implements OnInit {
// Submenus // Submenus
selectedSubProductCode: string | undefined = undefined; selectedSubProductCode: string | undefined = undefined;
hasSubmenu = () => this.submenus != null && this.submenus!.length > 0; hasSubmenu = () => this.submenus != null && this.submenus!.length > 0;
listSubMenuProductcodes = () => this.submenus!.map((recipe) => recipe.productCode); listSubMenuProductcodes = () =>
this.submenus!.map((recipe) => recipe.productCode);
selectSubmenu(productCode: string) { selectSubmenu(productCode: string) {
if (this.selectedSubProductCode == productCode) { if (this.selectedSubProductCode == productCode) {
this.selectedSubProductCode = undefined; this.selectedSubProductCode = undefined;
console.log('Unselected submenu', productCode); console.log('Unselected submenu', productCode);
@ -265,4 +343,13 @@ export class RecipeDetailsComponent implements OnInit {
this.selectedSubProductCode = productCode; this.selectedSubProductCode = productCode;
console.log('Selected submenu', productCode); console.log('Selected submenu', productCode);
} }
// concat remaining unused keys
concatNoEditKeyToMap(source: any, target: any) {
for (const key of Object.keys(source)) {
if (!Object.keys(target).includes(key)) {
target[key] = source[key];
}
}
}
} }

View file

@ -284,8 +284,6 @@ export class RecipeListComponent implements OnInit {
emitted_res.push(recipeDetailMat); emitted_res.push(recipeDetailMat);
}); });
// do another emit
this.recipeListFormChange.emit([this.toppingList, emitted_res] as unknown[]); this.recipeListFormChange.emit([this.toppingList, emitted_res] as unknown[]);
} else { } else {
this.recipeListFormChange.emit([]); this.recipeListFormChange.emit([]);
@ -425,7 +423,7 @@ export class RecipeListComponent implements OnInit {
return inRange(8111, 8130, convertFromInterProductCode(materialId)); return inRange(8111, 8130, convertFromInterProductCode(materialId));
}; };
getToppingSlotNumber = (mat: number) => convertFromInterProductCode(mat) - 8110; getToppingSlotNumber = (mat: number) => convertFromInterProductCode(mat) - 8110 - 1 ;
isStringParamExist = (i: number) => { isStringParamExist = (i: number) => {
let rawStringParam = this.recipeListData.at(i).get('StringParam')?.value; let rawStringParam = this.recipeListData.at(i).get('StringParam')?.value;
@ -561,8 +559,9 @@ export class RecipeListComponent implements OnInit {
}; };
onToppingSetChange = (event: any, index: number) => { onToppingSetChange = (event: any, index: number) => {
// console.log('onToppingSetChange at index', index, "get event", event); // where index is the actual index of slot opened for topping
this.toppingList[event[0]] = event[1]; this.toppingList[event[0]] = event[1];
// console.log('onToppingSetChange', this.toppingList); // trigger emitter
this.recipeListFormChange.emit([this.toppingList, this.recipeListData.value]);
} }
} }

View file

@ -2,7 +2,7 @@
<div formArrayName="toppingList" *ngFor="let topping of toppingList.controls; let i = index"> <div formArrayName="toppingList" *ngFor="let topping of toppingList.controls; let i = index">
<div formGroupName="{{ i }}"> <div formGroupName="{{ i }}">
<input type="checkbox" /> <input type="checkbox" formControlName="isUse"/>
<!-- toppingGroup --> <!-- toppingGroup -->
<ng-select <ng-select
appendTo="body" appendTo="body"

View file

@ -74,10 +74,10 @@ export class RecipeToppingComponent implements OnInit {
) )
).subscribe((data) => { ).subscribe((data) => {
this._toppingSetOriginalArray = data; this._toppingSetOriginalArray = data;
// console.log('ToppingSet', data); console.log('ToppingSet', data, this.index, data.length >= this.index!, data[0]);
// check length of toppingList if in range with given index // check length of toppingList if in range with given index
if(this.index && data.length >= this.index!){ if (data.length >= this.index!) {
this.toppingList.push( this.toppingList.push(
this._formBuilder.group({ this._formBuilder.group({
isUse: data[this.index!].isUse, isUse: data[this.index!].isUse,
@ -87,6 +87,8 @@ export class RecipeToppingComponent implements OnInit {
}) })
); );
} }
console.log('ToppingSet', this.toppingList);
}); });
// get all topping // get all topping
@ -117,11 +119,27 @@ export class RecipeToppingComponent implements OnInit {
}); });
}); });
// emit value changes // emit value changes
this.toppingForm.valueChanges.subscribe((value) => { this.toppingForm.valueChanges.subscribe((value) => {
console.log('emit value', value); console.log('emit value', value, 'for index: ', this.index! );
this.toppingSetChange.emit([this.index, this.toppingList.value]);
// transform data
this.toppingList.value.forEach((value: any) => {
value = this.transformToppingsetStructure(value);
});
// extend lifetime of data
let newMapping = this.toppingList.value.map((value: {
isUse: boolean;
groupID: string;
defaultIDSelect: string;
ListGroupID: string[];
}) => this.transformToppingsetStructure(value));
// add debug here!
console.log('newMapping', newMapping);
this.toppingSetChange.emit([this.index! , newMapping]);
}); });
} }
@ -149,13 +167,36 @@ export class RecipeToppingComponent implements OnInit {
getDefaultOfGroup(groupID: any) { getDefaultOfGroup(groupID: any) {
this.toppingList.controls.forEach((control) => { this.toppingList.controls.forEach((control) => {
if ((control.value as any).groupID == groupID) { if ((control.value as any).groupID == groupID) {
let newDefault = (this.allToppingsDefinitions as any).find( let newDefault = (this.allToppingsDefinitions as any).find(
(x: any) => x.groupId == groupID (x: any) => x.groupId == groupID
)!.default; )!.default;
control.get('defaultIDSelect')?.setValue(newDefault); control.get('defaultIDSelect')?.setValue(newDefault);
} }
}) });
} }
transformToppingsetStructure = (value: any) => {
if(value.defaultIDSelect == null){
value.defaultIDSelect = 0;
} else {
value.defaultIDSelect = parseInt(value.defaultIDSelect);
}
if(!Array.isArray(value.ListGroupID)){
value.ListGroupID = [parseInt(value.groupID), 0, 0, 0];
}
if(value.groupID == null){
value.groupID = '0';
}
return {
isUse: value.isUse,
groupID: value.groupID,
defaultIDSelect: value.defaultIDSelect,
ListGroupID: value.ListGroupID,
};
};
} }

View file

@ -365,7 +365,7 @@ func (d *Data) SetValuesToRecipe(base_recipe []models.Recipe01, recipe models.Re
for k, v := range recipe01_Map { for k, v := range recipe01_Map {
if !reflect.DeepEqual(base_recipe01_Map[k], v) { if !reflect.DeepEqual(base_recipe01_Map[k], v) {
d.taoLogger.Log.Debug("SetValuesToRecipe", zap.Any("key", k)) d.taoLogger.Log.Debug("SetValuesToRecipe", zap.Any("key", k), zap.Any("old", base_recipe01_Map[k]), zap.Any("new", v))
base_recipe01_Map[k] = v base_recipe01_Map[k] = v
} }
} }

View file

@ -61,6 +61,9 @@ func (rr *RecipeRouter) Route(r chi.Router) {
r.Get("/{country}/{filename}/{product_code}/submenus", rr.getSubmenusOfRecipe) r.Get("/{country}/{filename}/{product_code}/submenus", rr.getSubmenusOfRecipe)
// fetch raw recipe json
r.Get("/{country}/{filename}/{product_code}/raw_full", rr.getRawRecipeOfProductCode)
r.Get("/{country}/{filename}/json", rr.getRecipeJson) r.Get("/{country}/{filename}/json", rr.getRecipeJson)
r.Post("/edit/{country}/{filename}", rr.updateRecipe) r.Post("/edit/{country}/{filename}", rr.updateRecipe)
@ -546,6 +549,31 @@ func (rr *RecipeRouter) getSubmenusOfRecipe(w http.ResponseWriter, r *http.Reque
json.NewEncoder(w).Encode(submenus) json.NewEncoder(w).Encode(submenus)
} }
// get raw recipe
func (rr *RecipeRouter) getRawRecipeOfProductCode(w http.ResponseWriter, r *http.Request) {
countryID := chi.URLParam(r, "country")
filename := chi.URLParam(r, "filename")
productCode := chi.URLParam(r, "product_code")
// debug
rr.taoLogger.Log.Debug("RecipeRouter.getRawRecipeOfProductCode", zap.Any("countryID", countryID), zap.Any("filename", filename), zap.Any("productCode", productCode))
w.Header().Add("Content-Type", "application/json")
recipe, err := rr.data.GetRecipe01ByProductCode(filename, countryID, productCode)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
// return recipe
rr.taoLogger.Log.Debug("RecipeRouter.getRawRecipeOfProductCode", zap.Any("recipe", recipe))
json.NewEncoder(w).Encode(recipe)
}
func (rr *RecipeRouter) doMergeJson(w http.ResponseWriter, r *http.Request) { func (rr *RecipeRouter) doMergeJson(w http.ResponseWriter, r *http.Request) {
// TODO: v2, change to binary instead // TODO: v2, change to binary instead
if !APIhandler(w, r) { if !APIhandler(w, r) {