fix(topping): 🐛 fix missing topping after saved

Ensure topping data in slots with padding if only use in some slots.

Topping saving
This commit is contained in:
pakintada@gmail.com 2024-03-02 13:22:23 +07:00
parent 517e051f2d
commit 292c7697a4
4 changed files with 466 additions and 377 deletions

View file

@ -41,7 +41,8 @@
</div>
<!-- File Change Status -->
<button onclick="patch_merge_modal.showModal()" *ngIf="isCommitLoaded | async">
<div *ngIf="isCommitLoaded | async">
<button onclick="patch_merge_modal.showModal()" >
<h1 class="text-center font-extrabold text-2xl text-red-500 animate-pulse">Detect Changes! Click</h1>
@ -61,6 +62,7 @@
</dialog>
</div>
<div class="flex items-center">
<div class="flex items-center ml-3">

View file

@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit, OnChanges, SimpleChanges, AfterViewInit } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { CommonModule, DatePipe, NgFor, NgIf, NgOptimizedImage } from '@angular/common';
import { GoogleButtonComponent } from 'src/app/shared/googleButton/googleButton.component';
@ -32,7 +32,7 @@ interface MenuItem {
MergeComponent
]
})
export class LayoutComponent implements OnInit, OnDestroy {
export class LayoutComponent implements OnInit, AfterViewInit, OnDestroy {
current_department = this._router.snapshot.paramMap.get('department')!;
menuItems: MenuItem[] = [
@ -70,24 +70,8 @@ export class LayoutComponent implements OnInit, OnDestroy {
private _recipeService: RecipeService
) {}
async ngOnInit(): Promise<void> {
this._userService.currentUser
.pipe(takeUntil(this.exit$))
.subscribe((user) => (this.user = user));
this.clockSubscription = timer(0, 1000)
.pipe(
map(() => new Date()),
share()
)
.subscribe((time) => {
this.date = time;
});
this._httpClient.get(environment.api + "/health/redis").subscribe((status) => {
this.redisStatus = (status as any)["status"];
});
// acticvate after ngOnInit
async ngAfterViewInit(): Promise<void> {
// check if saves existed
this._recipeService.getSavedTmp(
await this._recipeService.getCurrentCountry(),
@ -120,6 +104,56 @@ export class LayoutComponent implements OnInit, OnDestroy {
});
}
async ngOnInit(): Promise<void> {
this._userService.currentUser
.pipe(takeUntil(this.exit$))
.subscribe((user) => (this.user = user));
this.clockSubscription = timer(0, 1000)
.pipe(
map(() => new Date()),
share()
)
.subscribe((time) => {
this.date = time;
});
this._httpClient.get(environment.api + "/health/redis").subscribe((status) => {
this.redisStatus = (status as any)["status"];
});
// check if saves existed
// this._recipeService.getSavedTmp(
// await this._recipeService.getCurrentCountry(),
// this._recipeService.getCurrentFile()
// ).subscribe({
// next: async (data: any) => {
// console.log("get saved tmp", data);
// if(data != null && data != undefined){
// this.isCommitLoaded = Promise.resolve(true);
// }
// // TODO: optimize
// if(data != undefined && typeof data === 'object'){
// // check if attr exists
// if(data.files != null && data.files != undefined){
// this.showDetectChanges = true;
// await AsyncStorage.setItem("detectChanges", "true");
// this.changesCommit = data.files;
// console.log("get commits", this.changesCommit);
// } else {
// this.showDetectChanges = false;
// await AsyncStorage.setItem("detectChanges", "false");
// }
// } else {
// this.showDetectChanges = false;
// await AsyncStorage.setItem("detectChanges", "false");
// }
// }
// });
}
ngOnDestroy() {
this.exit$.next();
this.exit$.complete();

View file

@ -1,5 +1,13 @@
import { CommonModule, NgFor, NgIf } from '@angular/common';
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR } from '@angular/core';
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
} from '@angular/core';
import {
FormArray,
FormBuilder,
@ -30,7 +38,7 @@ import {
stringParamsDefinition,
conditionTests,
inRange,
convertFromInterProductCode
convertFromInterProductCode,
} from 'src/app/shared/helpers/recipe';
import { RecipeToppingComponent } from '../recipe-topping/recipe-topping.component';
@ -40,18 +48,27 @@ import Lang from 'src/app/shared/helpers/lang';
selector: 'app-recipe-list',
templateUrl: './recipe-list.component.html',
standalone: true,
imports: [CommonModule,NgIf, NgFor, ReactiveFormsModule, FormsModule, RecipeToppingComponent]
imports: [
CommonModule,
NgIf,
NgFor,
ReactiveFormsModule,
FormsModule,
RecipeToppingComponent,
],
})
export class RecipeListComponent implements OnInit, OnChanges {
@Input({ required: true }) productCode!: string;
@Input() noFetch: boolean = false;
@Input() recipeList: any | undefined = undefined;
@Input() displayOnly: boolean = false;
@Input() diffChangeContext: {
@Input() diffChangeContext:
| {
changeContext: any;
skipZeroes: boolean;
toppingData: any;
} | undefined = undefined;
}
| undefined = undefined;
@Output() recipeListFormChange = new EventEmitter<unknown[]>();
materialList: MaterialCode[] = [];
@ -65,7 +82,9 @@ export class RecipeListComponent implements OnInit, OnChanges {
isMatLoaded: boolean = false;
categoriedMaterial: { [key: string]: { id: number; name: string; nameEN: string }[] } = {};
categoriedMaterial: {
[key: string]: { id: number; name: string; nameEN: string }[];
} = {};
// ------------------------------------------------------------------------
// string params
@ -112,41 +131,42 @@ export class RecipeListComponent implements OnInit, OnChanges {
private _recipeListOriginalArray!: RecipeDetailMat[];
async ngOnInit(): Promise<void> {
// fetch material
(await
this._materialService.getMaterialCodes()).subscribe((materials) => {
(await this._materialService.getMaterialCodes()).subscribe((materials) => {
this.materialList = materials;
// console.log("[MatService] get materials", materials.length);
});
(await this._materialService.getFullMaterialDetail()).subscribe((materials) => {
(await this._materialService.getFullMaterialDetail()).subscribe(
(materials) => {
this.fullMaterialList = materials;
this.categoriedMaterial = this.listCategory();
console.log(this.categoriedMaterial);
// remapping missing material attr to recipeListData
this.recipeListData.controls.forEach((recipeList: any, index: number) => {
this.recipeListData.controls.forEach(
(recipeList: any, index: number) => {
// console.log("recipeList", recipeList);
// do map name
let materialName = this.fullMaterialList!.find(
(mat) => mat.materialId.toString() == recipeList.get('materialPathId').value.toString()
(mat) =>
mat.materialId.toString() ==
recipeList.get('materialPathId').value.toString()
);
if (materialName) {
recipeList.get('name').setValue(materialName.name);
}
})
});
}
);
}
);
// this do not fetch recipe list when initialized
// use when doing `topping`
if (!this.noFetch) {
(await this._recipeService
.getRecipeDetailMat(this.productCode))
(await this._recipeService.getRecipeDetailMat(this.productCode))
.pipe(first())
.subscribe(({ result }) => {
this._recipeListOriginalArray = result;
@ -158,7 +178,9 @@ export class RecipeListComponent implements OnInit, OnChanges {
recipeDetailMat.StringParam != '' ||
recipeDetailMat.StringParam != null
) {
let currStringParam = new StringParam(recipeDetailMat.StringParam);
let currStringParam = new StringParam(
recipeDetailMat.StringParam
);
let stringParamList = currStringParam.extract().as_list();
let stringParamListTransform = [];
@ -198,7 +220,10 @@ export class RecipeListComponent implements OnInit, OnChanges {
},
],
isUse: [
{ value: recipeDetailMat.isUse, disabled: !this.isEditable() },
{
value: recipeDetailMat.isUse,
disabled: !this.isEditable(),
},
],
materialPathId: [
{
@ -259,23 +284,23 @@ export class RecipeListComponent implements OnInit, OnChanges {
{
value: recipeDetailMat.feedPattern,
disabled: !this.isEditable(),
}
},
],
feedParameter: [
{
value: recipeDetailMat.feedParameter,
disabled: !this.isEditable(),
}
]
},
],
})
);
});
this.isMatLoaded = true;
});
} else if (this.recipeList != undefined) {
console.log("test recipelist", this.recipeList);
this.recipeList.forEach( (recipeDetailMat: RecipeDetailMat, index: number) => {
console.log('test recipelist', this.recipeList);
this.recipeList.forEach(
(recipeDetailMat: RecipeDetailMat, index: number) => {
// StringParam
if (
recipeDetailMat.StringParam != '' ||
@ -284,7 +309,7 @@ export class RecipeListComponent implements OnInit, OnChanges {
let currStringParam = new StringParam(recipeDetailMat.StringParam);
let stringParamList = currStringParam.extract().as_list();
console.log("string param list", stringParamList);
console.log('string param list', stringParamList);
let stringParamListTransform: any[] = [];
for (let param of stringParamList) {
@ -298,7 +323,7 @@ export class RecipeListComponent implements OnInit, OnChanges {
this.stringParamData.push(
this._formBuilder.array(stringParamListTransform)
)
);
this.stringParams[index] = stringParamList;
}
@ -379,17 +404,18 @@ export class RecipeListComponent implements OnInit, OnChanges {
{
value: recipeDetailMat.feedPattern,
disabled: !this.isEditable(),
}
},
],
feedParameter: [
{
value: recipeDetailMat.feedParameter,
disabled: !this.isEditable(),
}
]
},
],
})
);
});
}
);
}
// event listener
@ -417,7 +443,10 @@ export class RecipeListComponent implements OnInit, OnChanges {
emitted_res.push(recipeDetailMat);
});
this.recipeListFormChange.emit([this.toppingList, emitted_res] as unknown[]);
this.recipeListFormChange.emit([
this.toppingList,
emitted_res,
] as unknown[]);
} else {
this.recipeListFormChange.emit([]);
}
@ -471,28 +500,29 @@ export class RecipeListComponent implements OnInit, OnChanges {
}
async ngOnChanges(changes: SimpleChanges): Promise<void> {
if (changes['recipeList']) {
let previousRecipeList = changes['recipeList'].previousValue;
let newUpdatedRecipeList = changes['recipeList'].currentValue;
let firstTime = changes['recipeList'].isFirstChange();
console.log("test on changes", changes);
console.log('test on changes', changes);
if (newUpdatedRecipeList != undefined && !firstTime) {
console.log("test updated input recipelist", newUpdatedRecipeList);
console.log('test updated input recipelist', newUpdatedRecipeList);
// reset
this.stringParamData.clear();
this.recipeListData.clear();
newUpdatedRecipeList.forEach( (recipeDetailMat: RecipeDetailMat, index: number) => {
newUpdatedRecipeList.forEach(
(recipeDetailMat: RecipeDetailMat, index: number) => {
// StringParam
if (
recipeDetailMat.StringParam != '' ||
recipeDetailMat.StringParam != null
) {
let currStringParam = new StringParam(recipeDetailMat.StringParam);
let currStringParam = new StringParam(
recipeDetailMat.StringParam
);
let stringParamList = currStringParam.extract().as_list();
console.log('string param list', stringParamList);
@ -509,12 +539,12 @@ export class RecipeListComponent implements OnInit, OnChanges {
this.stringParamData.push(
this._formBuilder.array(stringParamListTransform)
)
);
this.stringParams[index] = stringParamList;
}
console.log("string param debug change", this.stringParamData);
console.log('string param debug change', this.stringParamData);
// --------------- mapping missing data ---------------
@ -530,7 +560,10 @@ export class RecipeListComponent implements OnInit, OnChanges {
},
],
isUse: [
{ value: recipeDetailMat.isUse, disabled: !this.isEditable() },
{
value: recipeDetailMat.isUse,
disabled: !this.isEditable(),
},
],
materialPathId: [
{
@ -591,21 +624,22 @@ export class RecipeListComponent implements OnInit, OnChanges {
{
value: recipeDetailMat.feedPattern,
disabled: !this.isEditable(),
}
},
],
feedParameter: [
{
value: recipeDetailMat.feedParameter,
disabled: !this.isEditable(),
}
]
},
],
})
);
// map material
// fullMaterialList
this.setNameToRecipeList();
});
}
);
// resub listener
@ -671,9 +705,9 @@ export class RecipeListComponent implements OnInit, OnChanges {
}
if (initString.length > 1) {
(this.recipeListData.at(i) as any).controls.StringParam.setValue(
initString
);
(
this.recipeListData.at(i) as any
).controls.StringParam.setValue(initString);
// console.log('set', initString);
}
@ -683,21 +717,18 @@ export class RecipeListComponent implements OnInit, OnChanges {
}
}
});
}
}
}
// add new row
addRow() {
this.recipeListData.push(
this._formBuilder.group({
isUse: [{ value: false, disabled: !this.isEditable() }],
materialPathId: [{ value: 0, disabled: !this.isEditable() }],
name: [{ value: "", disabled: true }],
MixOrder: [{ value: "", disabled: !this.isEditable() }],
name: [{ value: '', disabled: true }],
MixOrder: [{ value: '', disabled: !this.isEditable() }],
stirTime: [{ value: 1, disabled: !this.isEditable() }],
powderGram: [{ value: 1, disabled: !this.isEditable() }],
powderTime: [{ value: 0, disabled: !this.isEditable() }],
@ -705,16 +736,15 @@ export class RecipeListComponent implements OnInit, OnChanges {
syrupTime: [{ value: 0, disabled: !this.isEditable() }],
waterCold: [{ value: 1, disabled: !this.isEditable() }],
waterYield: [{ value: 1, disabled: !this.isEditable() }],
feedPattern: [{ value: "", disabled: !this.isEditable() }],
feedParameter: [{ value: "", disabled: !this.isEditable() }],
StringParam: [{ value: "", disabled: !this.isEditable() }],
feedPattern: [{ value: '', disabled: !this.isEditable() }],
feedParameter: [{ value: '', disabled: !this.isEditable() }],
StringParam: [{ value: '', disabled: !this.isEditable() }],
})
);
}
// remove from selection
removeRow() {
let i = this.selectedRecipeList;
i.forEach((idx) => {
@ -725,18 +755,15 @@ export class RecipeListComponent implements OnInit, OnChanges {
}
addToSelection(i: number) {
if (this.selectedRecipeList.includes(i)) {
let index = this.selectedRecipeList.indexOf(i);
this.selectedRecipeList.splice(index, 1);
return;
}
this.selectedRecipeList.push(i);
console.log("selected recipe list", this.selectedRecipeList);
console.log('selected recipe list', this.selectedRecipeList);
}
get recipeListData(): FormArray {
return this.recipeListForm.get('recipeListData') as FormArray;
}
@ -750,7 +777,6 @@ export class RecipeListComponent implements OnInit, OnChanges {
}
isEditable() {
if (this.displayOnly) {
return !this.displayOnly;
}
@ -776,9 +802,7 @@ export class RecipeListComponent implements OnInit, OnChanges {
(mat) => mat.materialId == material
)!.name;
this.recipeListData.at(i).get('name')?.setValue(
materialName
);
this.recipeListData.at(i).get('name')?.setValue(materialName);
// console.log('set mat ', material, materialName,'to slot', i);
}
@ -831,7 +855,7 @@ export class RecipeListComponent implements OnInit, OnChanges {
// find min
// console.log(Math.floor(mat.materialId / 1000) );
let interCode = Math.floor(mat.materialId / 10000) * 10000;
let originalCode = mat.materialId - (interCode);
let originalCode = mat.materialId - interCode;
// console.log("from",mat.materialId,"interCode", interCode, "originalCode", originalCode);
category = getMaterialType(originalCode);
@ -861,7 +885,8 @@ export class RecipeListComponent implements OnInit, OnChanges {
return inRange(8111, 8130, convertFromInterProductCode(materialId));
};
getToppingSlotNumber = (mat: number) => convertFromInterProductCode(mat) - 8110 - 1 ;
getToppingSlotNumber = (mat: number) =>
convertFromInterProductCode(mat) - 8110 - 1;
// get value at index, if 'MixOrder' exist and is 1, return true
isMixOrder = (index: number) => {
@ -875,9 +900,8 @@ export class RecipeListComponent implements OnInit, OnChanges {
// }
// console.log("get::MixOrder", mixOrder, "test get::mixOrder", this.recipeListData.at(index).get('mixOrder'));
return mixOrder.toString() == "1" ? true : false;
}
return mixOrder.toString() == '1' ? true : false;
};
// ------------------ String Param ------------------
@ -946,17 +970,20 @@ export class RecipeListComponent implements OnInit, OnChanges {
return stringParamsDefinition[name] || name;
};
// ------------------ Filter ------------------
displayByCond = (index: number, key: string, condition: any, compare?: any) =>{
displayByCond = (
index: number,
key: string,
condition: any,
compare?: any
) => {
if (typeof condition == 'function') {
return condition(this.recipeListData.value[index][key], compare);
}
return conditionTests[condition](this.recipeListData.value[index][key])
return conditionTests[condition](this.recipeListData.value[index][key]);
};
// timeout
timeoutHandler: any;
timeout: number = 0;
@ -967,18 +994,17 @@ export class RecipeListComponent implements OnInit, OnChanges {
}
async openRecipeListEditor(i: number) {
await Promise.resolve();
if (this.timeoutHandler) {
if (this.timeout >= 20 && !this.displayOnly) {
// alert("Opening Recipe List Editor in detail")
if (confirm("Are you sure you want to open Recipe List Editor in detail?")) {
if (
confirm('Are you sure you want to open Recipe List Editor in detail?')
) {
this.showDetailRecipeList = true;
} else {
this.showDetailRecipeList = false;
}
}
clearInterval(this.timeoutHandler);
@ -986,7 +1012,6 @@ export class RecipeListComponent implements OnInit, OnChanges {
this.currentSelectRecipeList = i;
this.timeout = 0;
}
}
// this does update, not save
@ -998,18 +1023,13 @@ export class RecipeListComponent implements OnInit, OnChanges {
);
}
getTooltipForStirTime = (cat: {
category: string;
name: any;
id: any;
}) => {
getTooltipForStirTime = (cat: { category: string; name: any; id: any }) => {
switch (cat.category) {
case 'whipper':
return 'Mix';
case 'bean':
return 'Grinder';
case 'others':
if (inRange(8001, 8002, cat.id)) {
return 'Clean';
}
@ -1023,14 +1043,36 @@ export class RecipeListComponent implements OnInit, OnChanges {
};
onToppingSetChange = (event: any, index: number) => {
let emptyToppingTemplate = [
{
isUse: false,
groupID: '0',
defaultIDSelect: 0,
ListGroupID: [0, 0, 0, 0],
},
];
// where index is the actual index of slot opened for topping
this.toppingList[event[0]] = event[1];
// trigger emitter
this.recipeListFormChange.emit([this.toppingList, this.recipeListData.value]);
// check if topping list has empty allocation
for (
let toppingIndex = 0;
toppingIndex < this.toppingList.length;
toppingIndex++
) {
if (this.toppingList[toppingIndex] == undefined) {
// console.log("must add padding", toppingIndex);
this.toppingList[toppingIndex] = emptyToppingTemplate;
}
}
// trigger emitter
this.recipeListFormChange.emit([
this.toppingList,
this.recipeListData.value,
]);
};
// language handler
async displayLang(material: any) {
@ -1043,31 +1085,33 @@ export class RecipeListComponent implements OnInit, OnChanges {
}
}
// ----------------------------- Change/Diff -------------------------------
invokeZeroMaterialChecker = (index: number) => {
if (this.diffChangeContext?.skipZeroes == true) {
// check if current index's material is 0
let isZeroMaterial = this.recipeListData.at(index).get('materialPathId')?.value == "0";
let isZeroMaterial =
this.recipeListData.at(index).get('materialPathId')?.value == '0';
if (isZeroMaterial) {
return false;
}
}
return true;
}
};
// reinit
buildToppingContext(index: number) {
if(this.diffChangeContext != undefined && this.diffChangeContext.toppingData != undefined){
if (
this.diffChangeContext != undefined &&
this.diffChangeContext.toppingData != undefined
) {
// console.log('building context of topping',index, this.diffChangeContext.toppingData[index])
let emptyToppingTemplate = {
isUse: false,
groupID: 0,
defaultIDSelect: 0,
ListGroupID: ["0", "0", "0", "0"],
ListGroupID: ['0', '0', '0', '0'],
};
// check on data length
@ -1075,9 +1119,11 @@ export class RecipeListComponent implements OnInit, OnChanges {
// padding
}
let fetchedData = this.diffChangeContext.toppingData[index];
let raisedIndexException = "Index error! require ["+(index + 1).toString()+"] but data not found!";
let raisedIndexException =
'Index error! require [' +
(index + 1).toString() +
'] but data not found!';
if (fetchedData == undefined || fetchedData == null) {
fetchedData = emptyToppingTemplate;
@ -1085,21 +1131,26 @@ export class RecipeListComponent implements OnInit, OnChanges {
let context = {
errorInIndex: {
causedBy: "[ERROR?] has slot but topping data not found.",
fullExplanation: raisedIndexException + " Reached upper limit or not yet implemented. ",
causedBy: '[ERROR?] has slot but topping data not found.',
fullExplanation:
raisedIndexException +
' Reached upper limit or not yet implemented. ',
},
enableEditInDiffMode: false,
preFetchedData: [fetchedData]
}
preFetchedData: [fetchedData],
};
if(this.diffChangeContext.toppingData[index] != undefined || this.diffChangeContext.toppingData[index] != null || index > this.diffChangeContext.toppingData.length - 1){
context.errorInIndex.causedBy = "";
if (
this.diffChangeContext.toppingData[index] != undefined ||
this.diffChangeContext.toppingData[index] != null ||
index > this.diffChangeContext.toppingData.length - 1
) {
context.errorInIndex.causedBy = '';
}
return context;
}
return undefined;
}

View file

@ -142,15 +142,18 @@ export class RecipeToppingComponent implements OnInit, OnChanges {
})
);
}
} else {
// padding
// TODO: move padding to recipelist then insert padding between original last index and this index
if (this.index! > data.length) {
for (
let init = this.toppingList.length - 1;
this.toppingList.at(this.index!) == undefined &&
init < this.index!;
init++
) {
// for (
// let init = this.toppingList.length - 1;
// this.toppingList.at(this.index!) == undefined &&
// init < this.index!;
// init++
// ) {
// }
this.toppingList.push(
this._formBuilder.group({
isUse: false,
@ -160,7 +163,6 @@ export class RecipeToppingComponent implements OnInit, OnChanges {
})
);
}
}
console.log(
'SubscribeToppingSet',
@ -208,7 +210,7 @@ export class RecipeToppingComponent implements OnInit, OnChanges {
// apply on read mode
ngOnChanges(changes: SimpleChanges): void {
console.log('changes on topping', changes);
// console.log('changes on topping', changes);
if (changes['preFetchedData']) {
let toppingSet = changes['preFetchedData'].currentValue as Array<any>;