update recipe detail and recipe detail list
This commit is contained in:
parent
8b45ed53ee
commit
d52cad09fd
16 changed files with 947 additions and 458 deletions
|
|
@ -1,3 +1,50 @@
|
||||||
|
export type RecipeOverview = {
|
||||||
|
id: string;
|
||||||
|
productCode: string;
|
||||||
|
name: string;
|
||||||
|
otherName: string;
|
||||||
|
description: string;
|
||||||
|
lastUpdated: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RecipesDashboard = {
|
||||||
|
configNumber: number;
|
||||||
|
LastUpdated: Date;
|
||||||
|
filename: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RecipeOverviewList = {
|
||||||
|
result: RecipeOverview[];
|
||||||
|
hasMore: boolean;
|
||||||
|
totalCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RecipeDetail = {
|
||||||
|
name: string;
|
||||||
|
otherName: string;
|
||||||
|
description: string;
|
||||||
|
otherDescription: string;
|
||||||
|
lastUpdated: Date;
|
||||||
|
picture: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RecipeDetailMat = {
|
||||||
|
materialID: number;
|
||||||
|
name: string;
|
||||||
|
mixOrder: number;
|
||||||
|
feedParameter: number;
|
||||||
|
feedPattern: number;
|
||||||
|
isUse: boolean;
|
||||||
|
materialPathId: number;
|
||||||
|
powderGram: number;
|
||||||
|
powderTime: number;
|
||||||
|
stirTime: number;
|
||||||
|
syrupGram: number;
|
||||||
|
syrupTime: number;
|
||||||
|
waterCold: number;
|
||||||
|
waterYield: number;
|
||||||
|
};
|
||||||
|
|
||||||
export interface Recipe {
|
export interface Recipe {
|
||||||
Timestamp: Date;
|
Timestamp: Date;
|
||||||
MachineSetting: MachineSetting;
|
MachineSetting: MachineSetting;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,31 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable, tap } from 'rxjs';
|
import { Observable, tap } from 'rxjs';
|
||||||
import { Recipe, Recipe01 } from '../models/recipe.model';
|
import {
|
||||||
|
Recipe,
|
||||||
|
Recipe01,
|
||||||
|
RecipeDetail,
|
||||||
|
RecipeDetailMat,
|
||||||
|
RecipeOverview,
|
||||||
|
RecipeOverviewList,
|
||||||
|
RecipesDashboard,
|
||||||
|
} from '../models/recipe.model';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { RecipeMetaData } from 'src/app/shared/types/recipe';
|
import { RecipeMetaData } from 'src/app/shared/types/recipe';
|
||||||
|
|
||||||
interface RecipeParams {
|
type RecipeOverviewParams = {
|
||||||
filename: string;
|
filename: string;
|
||||||
country: string;
|
country: string;
|
||||||
materialIds: number[];
|
materialIds: number[];
|
||||||
offset: number;
|
offset: number;
|
||||||
take: number;
|
take: number;
|
||||||
search: string;
|
search: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
type RecipeDashboardParams = {
|
||||||
|
filename: string;
|
||||||
|
country: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface RecipeFiles {
|
interface RecipeFiles {
|
||||||
[key: string]: string[];
|
[key: string]: string[];
|
||||||
|
|
@ -25,36 +38,80 @@ export class RecipeService {
|
||||||
|
|
||||||
constructor(private _httpClient: HttpClient) {}
|
constructor(private _httpClient: HttpClient) {}
|
||||||
|
|
||||||
getRecipes(
|
getRecipesDashboard(
|
||||||
params: RecipeParams = {
|
params: RecipeDashboardParams = {
|
||||||
take: 10,
|
country: this.getCurrentCountry(),
|
||||||
offset: 0,
|
filename: this.getCurrentFile(),
|
||||||
search: '',
|
}
|
||||||
|
): Observable<RecipesDashboard> {
|
||||||
|
return this._httpClient.get<RecipesDashboard>(
|
||||||
|
environment.api + '/recipes/dashboard',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
country: params.country,
|
||||||
|
filename: params.filename,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
responseType: 'json',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecipeOverview(
|
||||||
|
params: RecipeOverviewParams = {
|
||||||
country: this.getCurrentCountry(),
|
country: this.getCurrentCountry(),
|
||||||
filename: this.getCurrentFile(),
|
filename: this.getCurrentFile(),
|
||||||
materialIds: [],
|
materialIds: [],
|
||||||
|
offset: 0,
|
||||||
|
take: 20,
|
||||||
|
search: '',
|
||||||
}
|
}
|
||||||
): Observable<{
|
): Observable<RecipeOverviewList> {
|
||||||
fileName: string;
|
return this._httpClient.get<RecipeOverviewList>(
|
||||||
recipes: Recipe;
|
environment.api + '/recipes/overview',
|
||||||
hasMore: boolean;
|
{
|
||||||
}> {
|
params: {
|
||||||
return this._httpClient.get<{
|
country: params.country,
|
||||||
fileName: string;
|
filename: params.filename,
|
||||||
recipes: Recipe;
|
materialIds: params.materialIds.join(','),
|
||||||
hasMore: boolean;
|
offset: params.offset.toString(),
|
||||||
}>(environment.api + '/recipes', {
|
take: params.take.toString(),
|
||||||
params: {
|
search: params.search,
|
||||||
offset: params.offset,
|
},
|
||||||
take: params.take,
|
withCredentials: true,
|
||||||
search: params.search,
|
responseType: 'json',
|
||||||
country: params.country,
|
}
|
||||||
filename: params.filename,
|
);
|
||||||
material_ids: params.materialIds.join(','),
|
}
|
||||||
},
|
|
||||||
withCredentials: true,
|
getRecipeDetail(productCode: string): Observable<RecipeDetail> {
|
||||||
responseType: 'json',
|
return this._httpClient.get<RecipeDetail>(
|
||||||
});
|
environment.api + '/recipes/' + productCode,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
filename: this.getCurrentFile(),
|
||||||
|
country: this.getCurrentCountry(),
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
responseType: 'json',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecipeDetailMat(
|
||||||
|
productCode: string
|
||||||
|
): Observable<{ result: RecipeDetailMat[] }> {
|
||||||
|
return this._httpClient.get<{ result: RecipeDetailMat[] }>(
|
||||||
|
environment.api + '/recipes/' + productCode + '/mat',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
filename: this.getCurrentFile(),
|
||||||
|
country: this.getCurrentCountry(),
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
responseType: 'json',
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFile(): string {
|
getCurrentFile(): string {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<form class="grid grid-cols-3 gap-4 mb-4" [formGroup]="recipeDetail">
|
<form class="grid grid-cols-3 gap-4 mb-4" [formGroup]="recipeDetailForm">
|
||||||
<div
|
<div
|
||||||
class="block col-span-1 p-6 bg-white border border-gray-200 rounded-lg shadow"
|
class="block col-span-1 p-6 bg-white border border-gray-200 rounded-lg shadow"
|
||||||
>
|
>
|
||||||
<div *ngIf="isLoaded; else indicator" [@inOutAnimation]>
|
<div *ngIf="isLoaded; else indicator" [@inOutAnimation]>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<h5 class="mb-2 text-xl font-bold text-gray-900">
|
<h5 class="mb-2 text-xl font-bold text-gray-900">
|
||||||
{{ recipeDetail.value.name }}
|
{{ recipeDetailForm.getRawValue().name }}
|
||||||
</h5>
|
</h5>
|
||||||
<h5 class="mb-2 px-3 text-xl font-bold text-gray-900">|</h5>
|
<h5 class="mb-2 px-3 text-xl font-bold text-gray-900">|</h5>
|
||||||
<h5 class="mb-2 text-xl font-bold text-gray-900">
|
<h5 class="mb-2 text-xl font-bold text-gray-900">
|
||||||
{{ recipeDetail.value.otherName }}
|
{{ recipeDetailForm.getRawValue().otherName }}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mb-2">
|
<div class="flex items-center mb-2">
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
<p class="text-sm text-gray-500">Last Modify</p>
|
<p class="text-sm text-gray-500">Last Modify</p>
|
||||||
<p class="ml-2 text-sm text-gray-900">
|
<p class="ml-2 text-sm text-gray-900">
|
||||||
{{
|
{{
|
||||||
recipeDetail.value.lastModified | date : "dd/MM/yyyy HH:mm:ss"
|
recipeDetailForm.getRawValue().lastModified
|
||||||
|
| date : "dd/MM/yyyy HH:mm:ss"
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -109,11 +110,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="col-span-3 min-h-[500px] max-h-[500px] overflow-auto mb-4 rounded bg-white border border-gray-200 shadow"
|
class="col-span-3 overflow-auto mb-4 rounded bg-white border border-gray-200 shadow"
|
||||||
>
|
>
|
||||||
<app-recipe-list
|
<app-recipe-list
|
||||||
[matRecipeList]="materialListIds$"
|
[parentForm]="recipeDetailForm"
|
||||||
[parentForm]="recipeDetail"
|
[productCode]="productCode"
|
||||||
|
[actionRecord]="actionRecord"
|
||||||
|
[recipeDetailOriginal]="recipeOriginalDetail"
|
||||||
></app-recipe-list>
|
></app-recipe-list>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,25 @@
|
||||||
import { DatePipe, NgFor, NgIf } from '@angular/common';
|
import { CommonModule, DatePipe } from '@angular/common';
|
||||||
import { Component, EventEmitter, OnInit } from '@angular/core';
|
import { Component, EventEmitter, OnInit } from '@angular/core';
|
||||||
import {
|
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||||
FormArray,
|
|
||||||
FormControl,
|
|
||||||
FormGroup,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
} from '@angular/forms';
|
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
import { isEqual } from 'lodash';
|
import { Observable, first } from 'rxjs';
|
||||||
import { BehaviorSubject, Subject, finalize, 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';
|
||||||
import { MaterialService } from 'src/app/core/services/material.service';
|
import { RecipeListComponent } from './recipe-list/recipe-list.component';
|
||||||
import { RecipeMetaData, RecipeDetail } from 'src/app/shared/types/recipe';
|
|
||||||
import {
|
import {
|
||||||
RecipeListComponent,
|
RecipeDetail,
|
||||||
RecipeListDataFormGroup,
|
RecipeDetailMat,
|
||||||
} from './recipe-list/recipe-list.component';
|
} from 'src/app/core/models/recipe.model';
|
||||||
import { MatRecipe } from 'src/app/core/models/recipe.model';
|
import { Action, ActionRecord } from 'src/app/shared/actionRecord/actionRecord';
|
||||||
|
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,
|
||||||
imports: [
|
imports: [
|
||||||
NgIf,
|
CommonModule,
|
||||||
NgFor,
|
|
||||||
RouterLink,
|
RouterLink,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
|
|
@ -44,88 +37,65 @@ import { MatRecipe } from 'src/app/core/models/recipe.model';
|
||||||
})
|
})
|
||||||
export class RecipeDetailsComponent implements OnInit {
|
export class RecipeDetailsComponent implements OnInit {
|
||||||
title: string = 'Recipe Detail';
|
title: string = 'Recipe Detail';
|
||||||
recipeMetaData: RecipeMetaData | null = null;
|
|
||||||
|
|
||||||
originalRecipeDetail: BehaviorSubject<RecipeDetail | null> =
|
recipeDetail$!: Observable<RecipeDetail>;
|
||||||
new BehaviorSubject<RecipeDetail | null>(null);
|
|
||||||
|
|
||||||
matForRecipeList = this.originalRecipeDetail.pipe(
|
|
||||||
map((x) => x?.recipe.recipes)
|
|
||||||
);
|
|
||||||
|
|
||||||
isLoaded: boolean = false;
|
isLoaded: boolean = false;
|
||||||
isMatLoaded: boolean = false;
|
isMatLoaded: boolean = false;
|
||||||
|
|
||||||
|
actionRecord: ActionRecord<RecipeDetail | RecipeDetailMat> =
|
||||||
|
new ActionRecord();
|
||||||
|
|
||||||
|
recipeOriginalDetail!: typeof this.recipeDetailForm.value;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private _formBuilder: FormBuilder,
|
||||||
private _route: ActivatedRoute,
|
private _route: ActivatedRoute,
|
||||||
private _router: Router,
|
private _router: Router,
|
||||||
private _recipeService: RecipeService
|
private _recipeService: RecipeService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
recipeDetail = new FormGroup({
|
productCode!: string;
|
||||||
productCode: new FormControl<string>(''),
|
|
||||||
name: new FormControl<string>(''),
|
recipeDetailForm = this._formBuilder.group({
|
||||||
otherName: new FormControl<string>(''),
|
productCode: '',
|
||||||
description: new FormControl<string>(''),
|
name: '',
|
||||||
otherDescription: new FormControl<string>(''),
|
otherName: '',
|
||||||
lastModified: new FormControl<Date>(new Date()),
|
description: '',
|
||||||
price: new FormControl<number>(0),
|
otherDescription: '',
|
||||||
isUse: new FormControl<boolean>(false),
|
lastModified: new Date(),
|
||||||
isShow: new FormControl<boolean>(false),
|
price: 0,
|
||||||
disable: new FormControl<boolean>(false),
|
isUse: false,
|
||||||
|
isShow: false,
|
||||||
|
disable: false,
|
||||||
|
recipeListData: this._formBuilder.array([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
materialListIds$: Subject<{
|
|
||||||
ids: number[];
|
|
||||||
matRecipeList: MatRecipe[];
|
|
||||||
}> = new Subject<{
|
|
||||||
ids: number[];
|
|
||||||
matRecipeList: MatRecipe[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this._recipeService
|
this.productCode = this._route.snapshot.params['productCode'];
|
||||||
.getRecipesById(this._route.snapshot.params['productCode'])
|
|
||||||
.pipe(finalize(() => {}))
|
|
||||||
.subscribe(({ recipe, recipeMetaData }) => {
|
|
||||||
this.title = recipe.name + ' | ' + recipe.productCode;
|
|
||||||
this.recipeDetail.patchValue({
|
|
||||||
productCode: recipe.productCode,
|
|
||||||
name: recipe.name,
|
|
||||||
otherName: recipe.otherName,
|
|
||||||
description: recipe.Description,
|
|
||||||
otherDescription: recipe.otherDescription,
|
|
||||||
lastModified: recipe.LastChange,
|
|
||||||
price: recipe.cashPrice,
|
|
||||||
isUse: recipe.isUse,
|
|
||||||
isShow: recipe.isShow,
|
|
||||||
disable: recipe.disable,
|
|
||||||
});
|
|
||||||
this.originalRecipeDetail.next({
|
|
||||||
recipe: {
|
|
||||||
lastModified: recipe.LastChange,
|
|
||||||
productCode: recipe.productCode,
|
|
||||||
name: recipe.name,
|
|
||||||
otherName: recipe.otherName,
|
|
||||||
description: recipe.Description,
|
|
||||||
otherDescription: recipe.otherDescription,
|
|
||||||
price: recipe.cashPrice,
|
|
||||||
isUse: recipe.isUse,
|
|
||||||
isShow: recipe.isShow,
|
|
||||||
disable: recipe.disable,
|
|
||||||
},
|
|
||||||
recipes: recipe.recipes,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ids = recipe.recipes?.map((recipe) => recipe.materialPathId);
|
this.recipeDetail$ = this._recipeService
|
||||||
this.materialListIds$.next({
|
.getRecipeDetail(this.productCode)
|
||||||
ids: ids || [],
|
.pipe(first());
|
||||||
matRecipeList: recipe.recipes || [],
|
this.recipeDetail$.subscribe((detail) => {
|
||||||
});
|
this.recipeDetailForm.patchValue(detail);
|
||||||
|
this.isLoaded = true;
|
||||||
|
this.recipeOriginalDetail = { ...this.recipeDetailForm.getRawValue() };
|
||||||
|
});
|
||||||
|
|
||||||
this.recipeMetaData = recipeMetaData;
|
// snap recipe detail form value
|
||||||
this.isLoaded = true;
|
|
||||||
});
|
this.actionRecord.registerOnAddAction((currAction, allAction) => {
|
||||||
|
if (currAction.type === 'recipeListData') {
|
||||||
|
switch (currAction.action) {
|
||||||
|
case 'add':
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Action Record', allAction);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showConfirmSaveModal: EventEmitter<boolean> = new EventEmitter<boolean>();
|
showConfirmSaveModal: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||||
|
|
@ -137,13 +107,13 @@ export class RecipeDetailsComponent implements OnInit {
|
||||||
confirmCallBack: () => {
|
confirmCallBack: () => {
|
||||||
console.log('confirm save');
|
console.log('confirm save');
|
||||||
// TODO: update value in targeted recipe
|
// TODO: update value in targeted recipe
|
||||||
this._recipeService.editChanges(
|
// this._recipeService.editChanges(
|
||||||
this._recipeService.getCurrentCountry(),
|
// this._recipeService.getCurrentCountry(),
|
||||||
this._recipeService.getCurrentFile(),
|
// this._recipeService.getCurrentFile(),
|
||||||
{
|
// {
|
||||||
...this.recipeDetail,
|
// ...this.recipeDetail,
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
console.log('Sending changes');
|
console.log('Sending changes');
|
||||||
this._router.navigate(['/recipes']);
|
this._router.navigate(['/recipes']);
|
||||||
},
|
},
|
||||||
|
|
@ -176,8 +146,8 @@ export class RecipeDetailsComponent implements OnInit {
|
||||||
|
|
||||||
get isValueChanged() {
|
get isValueChanged() {
|
||||||
return !isEqual(
|
return !isEqual(
|
||||||
this.recipeDetail.value,
|
this.recipeOriginalDetail,
|
||||||
this.originalRecipeDetail.getValue()?.recipe
|
this.recipeDetailForm.getRawValue()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<table class="table" [formGroup]="parentForm">
|
<table class="table" [formGroup]="parentForm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-gray-200">
|
<tr class="bg-gray-200">
|
||||||
<th class="px-6 py-3">Enable</th>
|
<th class="px-6 py-3">Action</th>
|
||||||
<th class="px-6 py-3">Material ID</th>
|
<th class="px-6 py-3">Material ID</th>
|
||||||
<th class="px-6 py-3">Material Name</th>
|
<th class="px-6 py-3">Material Name</th>
|
||||||
<th class="px-6 py-3">MixOrder</th>
|
<th class="px-6 py-3">MixOrder</th>
|
||||||
|
|
@ -11,55 +11,57 @@
|
||||||
<th class="px-6 py-3">Syrup Gram</th>
|
<th class="px-6 py-3">Syrup Gram</th>
|
||||||
<th class="px-6 py-3">Syrup Time</th>
|
<th class="px-6 py-3">Syrup Time</th>
|
||||||
<th class="px-6 py-3">Water Cold</th>
|
<th class="px-6 py-3">Water Cold</th>
|
||||||
<th class="px-6 py-3">Water Hot</th>
|
<th class="px-6 py-3">Water Yield</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody formArrayName="recipes" *ngIf="isMatLoaded">
|
<tbody
|
||||||
<tr
|
formArrayName="recipeListData"
|
||||||
*ngFor="let mat of recipeListData.controls; let i = index"
|
*ngFor="let mat of recipeListData.controls; let i = index"
|
||||||
class="bg-white la border-b hover:bg-secondary"
|
>
|
||||||
>
|
<tr class="bg-white la border-b hover:bg-secondary" formGroupName="{{ i }}">
|
||||||
<div formGroupName="{{ i }}">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<button
|
||||||
<label>
|
class="btn btn-primary"
|
||||||
<input
|
(click)="deleteRecipeData(i)"
|
||||||
type="checkbox"
|
type="button"
|
||||||
class="toggle toggle-sm"
|
>
|
||||||
formControlName="enable"
|
Delete
|
||||||
/>
|
</button>
|
||||||
</label>
|
|
||||||
</td>
|
<button class="btn btn-primary" (click)="addRecipeData()" type="button">
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
Add
|
||||||
<input type="text" class="input" formControlName="id" />
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="name" />
|
<input type="text" class="input" formControlName="materialID" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="mixOrder" />
|
<input type="text" class="input" formControlName="name" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="stirTime" />
|
<input type="text" class="input" formControlName="mixOrder" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="powderGram" />
|
<input type="text" class="input" formControlName="powderGram" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="powderTime" />
|
<input type="text" class="input" formControlName="powderTime" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="SyrupGram" />
|
<input type="text" class="input" formControlName="syrupGram" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="SyrupTime" />
|
<input type="text" class="input" formControlName="syrupTime" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="waterCold" />
|
<input type="text" class="input" formControlName="waterCold" />
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
<input type="text" class="input" formControlName="waterHot" />
|
<input type="text" class="input" formControlName="waterYield" />
|
||||||
</td>
|
</td>
|
||||||
</div>
|
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
|
<input type="text" class="input" formControlName="stirTime" />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,18 @@ import { NgFor, NgIf } from '@angular/common';
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
FormArray,
|
FormArray,
|
||||||
|
FormBuilder,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { Observable } from 'rxjs';
|
import { first } from 'rxjs';
|
||||||
import { MatRecipe } from 'src/app/core/models/recipe.model';
|
import {
|
||||||
import { MaterialService } from 'src/app/core/services/material.service';
|
RecipeDetail,
|
||||||
|
RecipeDetailMat,
|
||||||
|
} from 'src/app/core/models/recipe.model';
|
||||||
|
import { RecipeService } from 'src/app/core/services/recipe.service';
|
||||||
|
import { Action, ActionRecord } from 'src/app/shared/actionRecord/actionRecord';
|
||||||
|
|
||||||
export interface RecipeListDataFormGroup {
|
export interface RecipeListDataFormGroup {
|
||||||
id: FormControl<number | null>;
|
id: FormControl<number | null>;
|
||||||
|
|
@ -31,97 +36,86 @@ export interface RecipeListDataFormGroup {
|
||||||
imports: [NgIf, NgFor, ReactiveFormsModule],
|
imports: [NgIf, NgFor, ReactiveFormsModule],
|
||||||
})
|
})
|
||||||
export class RecipeListComponent implements OnInit {
|
export class RecipeListComponent implements OnInit {
|
||||||
@Input({ required: true }) matRecipeList!: Observable<{
|
|
||||||
ids: number[];
|
|
||||||
matRecipeList: MatRecipe[];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
@Input({ required: true }) parentForm!: FormGroup;
|
@Input({ required: true }) parentForm!: FormGroup;
|
||||||
|
@Input({ required: true }) actionRecord!: ActionRecord<
|
||||||
|
RecipeDetail | RecipeDetailMat
|
||||||
|
>;
|
||||||
|
|
||||||
recipeListData!: FormArray<FormGroup<RecipeListDataFormGroup>>;
|
@Input({ required: true }) recipeDetailOriginal!: any;
|
||||||
|
|
||||||
|
@Input({ required: true }) productCode!: string;
|
||||||
|
|
||||||
isMatLoaded: boolean = false;
|
isMatLoaded: boolean = false;
|
||||||
|
|
||||||
constructor(private _materialService: MaterialService) {}
|
constructor(
|
||||||
|
private _recipeService: RecipeService,
|
||||||
|
private _formBuilder: FormBuilder
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.matRecipeList.subscribe((x) => {
|
this._recipeService
|
||||||
this._materialService.getMaterialCodes(x.ids).subscribe((data) => {
|
.getRecipeDetailMat(this.productCode)
|
||||||
const matList = x.matRecipeList
|
.pipe(first())
|
||||||
.map((item) => {
|
.subscribe(({ result }) => {
|
||||||
for (let i = 0; i < data.length; i++) {
|
if (this.recipeDetailOriginal)
|
||||||
if (item.materialPathId === 0) {
|
this.recipeDetailOriginal.recipeListData = result;
|
||||||
return {
|
else this.recipeDetailOriginal = { recipeListData: result };
|
||||||
id: 0,
|
result.forEach((recipeDetailMat: RecipeDetailMat) => {
|
||||||
name: '',
|
this.recipeListData.push(
|
||||||
enable: item.isUse,
|
this._formBuilder.group({
|
||||||
mixOrder: item.MixOrder,
|
materialID: recipeDetailMat.materialID,
|
||||||
stirTime: item.stirTime,
|
name: recipeDetailMat.name,
|
||||||
powderGram: item.powderGram,
|
enable: recipeDetailMat.isUse,
|
||||||
powderTime: item.powderTime,
|
mixOrder: recipeDetailMat.mixOrder,
|
||||||
syrupGram: item.syrupGram,
|
stirTime: recipeDetailMat.stirTime,
|
||||||
syrupTime: item.syrupTime,
|
powderGram: recipeDetailMat.powderGram,
|
||||||
waterCold: item.waterCold,
|
powderTime: recipeDetailMat.powderTime,
|
||||||
waterHot: item.waterYield,
|
syrupGram: recipeDetailMat.syrupGram,
|
||||||
};
|
syrupTime: recipeDetailMat.syrupTime,
|
||||||
}
|
waterCold: recipeDetailMat.waterCold,
|
||||||
|
waterYield: recipeDetailMat.waterYield,
|
||||||
if (item.materialPathId === data[i].materialID) {
|
})
|
||||||
return {
|
);
|
||||||
id: data[i].materialID,
|
});
|
||||||
name: data[i].PackageDescription,
|
|
||||||
enable: item.isUse,
|
|
||||||
mixOrder: item.MixOrder,
|
|
||||||
stirTime: item.stirTime,
|
|
||||||
powderGram: item.powderGram,
|
|
||||||
powderTime: item.powderTime,
|
|
||||||
syrupGram: item.syrupGram,
|
|
||||||
syrupTime: item.syrupTime,
|
|
||||||
waterCold: item.waterCold,
|
|
||||||
waterHot: item.waterYield,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: item.materialPathId,
|
|
||||||
name: '',
|
|
||||||
enable: item.isUse,
|
|
||||||
mixOrder: item.MixOrder,
|
|
||||||
stirTime: item.stirTime,
|
|
||||||
powderGram: item.powderGram,
|
|
||||||
powderTime: item.powderTime,
|
|
||||||
syrupGram: item.syrupGram,
|
|
||||||
syrupTime: item.syrupTime,
|
|
||||||
waterCold: item.waterCold,
|
|
||||||
waterHot: item.waterYield,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
return a.id === 0 ? 1 : a.id > b.id ? 1 : -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.recipeListData = new FormArray<FormGroup<RecipeListDataFormGroup>>(
|
|
||||||
matList.map((item) => {
|
|
||||||
return new FormGroup<RecipeListDataFormGroup>({
|
|
||||||
id: new FormControl<number>(item.id),
|
|
||||||
name: new FormControl<string>(item.name),
|
|
||||||
enable: new FormControl<boolean>(item.enable),
|
|
||||||
mixOrder: new FormControl<number>(item.mixOrder),
|
|
||||||
stirTime: new FormControl<number>(item.stirTime),
|
|
||||||
powderGram: new FormControl<number>(item.powderGram),
|
|
||||||
powderTime: new FormControl<number>(item.powderTime),
|
|
||||||
syrupGram: new FormControl<number>(item.syrupGram),
|
|
||||||
syrupTime: new FormControl<number>(item.syrupTime),
|
|
||||||
waterCold: new FormControl<number>(item.waterCold),
|
|
||||||
waterHot: new FormControl<number>(item.waterHot),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.parentForm.addControl('recipes', this.recipeListData);
|
|
||||||
console.log(this.parentForm);
|
|
||||||
this.isMatLoaded = true;
|
this.isMatLoaded = true;
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
get recipeListData(): FormArray {
|
||||||
|
return this.parentForm.get('recipeListData') as FormArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
addRecipeData(): void {
|
||||||
|
const newRecipeDetailMat: RecipeDetailMat = {
|
||||||
|
materialID: 0,
|
||||||
|
name: '',
|
||||||
|
mixOrder: 0,
|
||||||
|
feedParameter: 0,
|
||||||
|
feedPattern: 0,
|
||||||
|
isUse: false,
|
||||||
|
materialPathId: 0,
|
||||||
|
powderGram: 0,
|
||||||
|
powderTime: 0,
|
||||||
|
stirTime: 0,
|
||||||
|
syrupGram: 0,
|
||||||
|
syrupTime: 0,
|
||||||
|
waterCold: 0,
|
||||||
|
waterYield: 0,
|
||||||
|
};
|
||||||
|
this.recipeListData.push(this._formBuilder.group(newRecipeDetailMat));
|
||||||
|
this.actionRecord.addAction(
|
||||||
|
new Action('add', newRecipeDetailMat, 'recipeListData')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRecipeData(index: number): void {
|
||||||
|
const recipeDetailMat: RecipeDetailMat =
|
||||||
|
this.recipeListData.at(index).value;
|
||||||
|
|
||||||
|
this.recipeListData.removeAt(index);
|
||||||
|
|
||||||
|
this.actionRecord.addAction(
|
||||||
|
new Action('delete', recipeDetailMat, 'recipeListData')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,17 @@
|
||||||
class="relative overflow-auto max-h-[900px] shadow-md sm:rounded-lg"
|
class="relative overflow-auto max-h-[900px] shadow-md sm:rounded-lg"
|
||||||
#table
|
#table
|
||||||
>
|
>
|
||||||
<table *ngIf="isLoaded" class="table">
|
<table class="table">
|
||||||
<caption class="p-5 text-lg font-semibold text-left text-gray-900">
|
<caption class="p-5 text-lg font-semibold text-left text-gray-900">
|
||||||
<div class="divide-y divide-solid divide-gray-400">
|
<div
|
||||||
|
class="divide-y divide-solid divide-gray-400"
|
||||||
|
*ngIf="recipesDashboard$ | async as recipesDashboard; else loading"
|
||||||
|
>
|
||||||
<div class="flex flex-row py-3 justify-between items-center">
|
<div class="flex flex-row py-3 justify-between items-center">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span
|
<span
|
||||||
>Recipe Version {{ recipes?.MachineSetting?.configNumber }} |
|
>Recipe Version {{ recipesDashboard.configNumber }} |
|
||||||
{{ currentFile }}</span
|
{{ recipesDashboard.filename }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col ml-5">
|
<div class="flex flex-col ml-5">
|
||||||
|
|
@ -106,7 +109,9 @@
|
||||||
<div class="flex flex-col ml-auto">
|
<div class="flex flex-col ml-auto">
|
||||||
<span class=""
|
<span class=""
|
||||||
>Last Updated:
|
>Last Updated:
|
||||||
{{ recipes?.Timestamp | date : "dd-MMM-yyyy hh:mm:ss" }}</span
|
{{
|
||||||
|
recipesDashboard.configNumber | date : "dd-MMM-yyyy hh:mm:ss"
|
||||||
|
}}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -199,7 +204,7 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
*ngFor="let recipe of recipes01"
|
*ngFor="let recipe of recipeOverviewList"
|
||||||
class="bg-white la border-b hover:bg-secondary"
|
class="bg-white la border-b hover:bg-secondary"
|
||||||
>
|
>
|
||||||
<th>
|
<th>
|
||||||
|
|
@ -219,9 +224,9 @@
|
||||||
{{ recipe.name }}
|
{{ recipe.name }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4">{{ recipe.otherName }}</td>
|
<td class="px-6 py-4">{{ recipe.otherName }}</td>
|
||||||
<td class="px-6 py-4 flex-wrap max-w-xs">{{ recipe.Description }}</td>
|
<td class="px-6 py-4 flex-wrap max-w-xs">{{ recipe.description }}</td>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
{{ recipe.LastChange | date : "dd-MMM-yyyy hh:mm:ss" }}
|
{{ recipe.lastUpdated | date : "dd-MMM-yyyy hh:mm:ss" }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-4 flex">
|
<td class="px-4 py-4 flex">
|
||||||
<!-- <recipe-modal productCode="{{ recipe.productCode }}"></recipe-modal> -->
|
<!-- <recipe-modal productCode="{{ recipe.productCode }}"></recipe-modal> -->
|
||||||
|
|
@ -249,7 +254,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div *ngIf="!isLoaded">
|
<ng-template #loading>
|
||||||
<div
|
<div
|
||||||
class="flex w-full items-center justify-center h-56 border border-gray-200 rounded-lg bg-gray-50"
|
class="flex w-full items-center justify-center h-56 border border-gray-200 rounded-lg bg-gray-50"
|
||||||
>
|
>
|
||||||
|
|
@ -272,7 +277,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ng-template>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-circle fixed z-100 bottom-5 right-1"
|
class="btn btn-circle fixed z-100 bottom-5 right-1"
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,23 @@ import {
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule, DatePipe } from '@angular/common';
|
import { CommonModule, DatePipe } from '@angular/common';
|
||||||
import { Recipe, Recipe01 } from 'src/app/core/models/recipe.model';
|
import {
|
||||||
|
Recipe,
|
||||||
|
Recipe01,
|
||||||
|
RecipeOverview,
|
||||||
|
RecipesDashboard,
|
||||||
|
} from 'src/app/core/models/recipe.model';
|
||||||
import { RecipeService } from 'src/app/core/services/recipe.service';
|
import { RecipeService } from 'src/app/core/services/recipe.service';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { RecipeModalComponent } from 'src/app/shared/modal/recipe-details/recipe-modal.component';
|
import { RecipeModalComponent } from 'src/app/shared/modal/recipe-details/recipe-modal.component';
|
||||||
import { BehaviorSubject, Subscription, map } from 'rxjs';
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
Observable,
|
||||||
|
Subscription,
|
||||||
|
finalize,
|
||||||
|
map,
|
||||||
|
tap,
|
||||||
|
} from 'rxjs';
|
||||||
import * as lodash from 'lodash';
|
import * as lodash from 'lodash';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { NgSelectModule } from '@ng-select/ng-select';
|
import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
|
|
@ -31,9 +43,8 @@ import { MaterialService } from 'src/app/core/services/material.service';
|
||||||
templateUrl: './recipes.component.html',
|
templateUrl: './recipes.component.html',
|
||||||
})
|
})
|
||||||
export class RecipesComponent implements OnInit, OnDestroy {
|
export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
recipes: Recipe | null = null;
|
recipesDashboard$!: Observable<RecipesDashboard>;
|
||||||
recipes01: Recipe01[] | null = null;
|
recipeOverviewList!: RecipeOverview[];
|
||||||
currentFile: string = '';
|
|
||||||
selectMaterialFilter: number[] | null = null;
|
selectMaterialFilter: number[] | null = null;
|
||||||
materialList: { id: number; name: string | number }[] | null = null;
|
materialList: { id: number; name: string | number }[] | null = null;
|
||||||
|
|
||||||
|
|
@ -47,8 +58,8 @@ export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
private offset = 0;
|
private offset = 0;
|
||||||
private take = 20;
|
private take = 20;
|
||||||
|
|
||||||
isLoaded: boolean = false;
|
// isLoaded: boolean = false;
|
||||||
isLoadMore: boolean = false;
|
isLoadMore: boolean = true;
|
||||||
isHasMore: boolean = true;
|
isHasMore: boolean = true;
|
||||||
|
|
||||||
private searchStr = '';
|
private searchStr = '';
|
||||||
|
|
@ -72,7 +83,7 @@ export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
if (isBottom && !this.isLoadMore) {
|
if (isBottom && !this.isLoadMore) {
|
||||||
this.isLoadMore = true;
|
this.isLoadMore = true;
|
||||||
this._recipeService
|
this._recipeService
|
||||||
.getRecipes({
|
.getRecipeOverview({
|
||||||
offset: this.offset,
|
offset: this.offset,
|
||||||
take: this.take,
|
take: this.take,
|
||||||
search: this.oldSearchStr,
|
search: this.oldSearchStr,
|
||||||
|
|
@ -80,21 +91,16 @@ export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
country: this._recipeService.getCurrentCountry(),
|
country: this._recipeService.getCurrentCountry(),
|
||||||
materialIds: this.selectMaterialFilter || [],
|
materialIds: this.selectMaterialFilter || [],
|
||||||
})
|
})
|
||||||
.subscribe(({ recipes, hasMore, fileName }) => {
|
.subscribe(({ result, hasMore, totalCount }) => {
|
||||||
const { Recipe01, ...recipesWithoutRecipe01 } = recipes;
|
if (this.recipeOverviewList) {
|
||||||
if (this.recipes01 && this.isHasMore) {
|
this.recipeOverviewList =
|
||||||
this.recipes01 = [...this.recipes01, ...Recipe01];
|
this.recipeOverviewList.concat(result);
|
||||||
} else {
|
} else {
|
||||||
this.recipes01 = Recipe01;
|
this.recipeOverviewList = result;
|
||||||
}
|
}
|
||||||
this.recipes = {
|
|
||||||
...recipesWithoutRecipe01,
|
|
||||||
Recipe01: [],
|
|
||||||
};
|
|
||||||
this.currentFile = fileName;
|
|
||||||
this.offset += 10;
|
this.offset += 10;
|
||||||
this.isLoadMore = false;
|
|
||||||
this.isHasMore = hasMore;
|
this.isHasMore = hasMore;
|
||||||
|
this.isLoadMore = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -108,31 +114,30 @@ export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this._recipeService
|
this.recipesDashboard$ = this._recipeService
|
||||||
.getRecipes({
|
.getRecipesDashboard({
|
||||||
offset: this.offset,
|
|
||||||
take: this.take,
|
|
||||||
search: this.oldSearchStr,
|
|
||||||
filename: this._recipeService.getCurrentFile(),
|
filename: this._recipeService.getCurrentFile(),
|
||||||
country: this._recipeService.getCurrentCountry(),
|
country: this._recipeService.getCurrentCountry(),
|
||||||
materialIds: this.selectMaterialFilter || [],
|
|
||||||
})
|
})
|
||||||
.subscribe(({ recipes, hasMore, fileName }) => {
|
.pipe(
|
||||||
const { Recipe01, ...recipesWithoutRecipe01 } = recipes;
|
finalize(() => {
|
||||||
if (this.recipes01 && this.isHasMore) {
|
this._recipeService
|
||||||
this.recipes01 = [...this.recipes01, ...Recipe01];
|
.getRecipeOverview({
|
||||||
} else {
|
offset: this.offset,
|
||||||
this.recipes01 = Recipe01;
|
take: this.take,
|
||||||
}
|
search: this.oldSearchStr,
|
||||||
this.recipes = {
|
filename: this._recipeService.getCurrentFile(),
|
||||||
...recipesWithoutRecipe01,
|
country: this._recipeService.getCurrentCountry(),
|
||||||
Recipe01: [],
|
materialIds: this.selectMaterialFilter || [],
|
||||||
};
|
})
|
||||||
this.currentFile = fileName;
|
.subscribe(({ result, hasMore, totalCount }) => {
|
||||||
this.offset += 10;
|
this.recipeOverviewList = result;
|
||||||
this.isLoaded = true;
|
this.offset += 10;
|
||||||
this.isHasMore = hasMore;
|
this.isHasMore = hasMore;
|
||||||
});
|
this.isLoadMore = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this._materialService
|
this._materialService
|
||||||
.getMaterialCodes()
|
.getMaterialCodes()
|
||||||
|
|
@ -157,28 +162,22 @@ export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
search(event: Event) {
|
search(event: Event) {
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
|
this.isLoadMore = true;
|
||||||
this.oldSearchStr = this.searchStr;
|
this.oldSearchStr = this.searchStr;
|
||||||
this._recipeService
|
this._recipeService
|
||||||
.getRecipes({
|
.getRecipeOverview({
|
||||||
offset: this.offset,
|
offset: this.offset,
|
||||||
take: this.take,
|
take: this.take,
|
||||||
search: this.searchStr,
|
search: this.oldSearchStr,
|
||||||
filename: this._recipeService.getCurrentFile(),
|
filename: this._recipeService.getCurrentFile(),
|
||||||
country: this._recipeService.getCurrentCountry(),
|
country: this._recipeService.getCurrentCountry(),
|
||||||
materialIds: this.selectMaterialFilter || [],
|
materialIds: this.selectMaterialFilter || [],
|
||||||
})
|
})
|
||||||
.subscribe(({ recipes, hasMore, fileName }) => {
|
.subscribe(({ result, hasMore, totalCount }) => {
|
||||||
const { Recipe01, ...recipesWithoutRecipe01 } = recipes;
|
this.recipeOverviewList = result;
|
||||||
this.recipes01 = Recipe01;
|
|
||||||
|
|
||||||
this.recipes = {
|
|
||||||
...recipesWithoutRecipe01,
|
|
||||||
Recipe01: [],
|
|
||||||
};
|
|
||||||
this.currentFile = fileName;
|
|
||||||
this.offset += 10;
|
this.offset += 10;
|
||||||
this.isLoaded = true;
|
|
||||||
this.isHasMore = hasMore;
|
this.isHasMore = hasMore;
|
||||||
|
this.isLoadMore = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,17 +281,19 @@ export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
loadRecipe(recipeFileName: string) {
|
loadRecipe(recipeFileName: string) {
|
||||||
// clear all recipes
|
// clear all recipes
|
||||||
this.recipes = null;
|
|
||||||
this.recipes01 = null;
|
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
this.isLoaded = false;
|
|
||||||
this.isHasMore = true;
|
this.isHasMore = true;
|
||||||
this.isLoadMore = false;
|
this.isLoadMore = true;
|
||||||
this.oldSearchStr = '';
|
this.oldSearchStr = '';
|
||||||
localStorage.setItem('currentRecipeFile', recipeFileName);
|
localStorage.setItem('currentRecipeFile', recipeFileName);
|
||||||
|
|
||||||
|
this.recipesDashboard$ = this._recipeService.getRecipesDashboard({
|
||||||
|
filename: recipeFileName,
|
||||||
|
country: this.selectedCountry!,
|
||||||
|
});
|
||||||
|
|
||||||
this._recipeService
|
this._recipeService
|
||||||
.getRecipes({
|
.getRecipeOverview({
|
||||||
offset: this.offset,
|
offset: this.offset,
|
||||||
take: this.take,
|
take: this.take,
|
||||||
search: this.oldSearchStr,
|
search: this.oldSearchStr,
|
||||||
|
|
@ -300,21 +301,11 @@ export class RecipesComponent implements OnInit, OnDestroy {
|
||||||
country: this.selectedCountry!,
|
country: this.selectedCountry!,
|
||||||
materialIds: this.selectMaterialFilter || [],
|
materialIds: this.selectMaterialFilter || [],
|
||||||
})
|
})
|
||||||
.subscribe(({ recipes, hasMore, fileName }) => {
|
.subscribe(({ result, hasMore, totalCount }) => {
|
||||||
const { Recipe01, ...recipesWithoutRecipe01 } = recipes;
|
this.recipeOverviewList = result;
|
||||||
if (this.recipes01 && this.isHasMore) {
|
|
||||||
this.recipes01 = [...this.recipes01, ...Recipe01];
|
|
||||||
} else {
|
|
||||||
this.recipes01 = Recipe01;
|
|
||||||
}
|
|
||||||
this.recipes = {
|
|
||||||
...recipesWithoutRecipe01,
|
|
||||||
Recipe01: [],
|
|
||||||
};
|
|
||||||
this.currentFile = fileName;
|
|
||||||
this.offset += 10;
|
this.offset += 10;
|
||||||
this.isLoaded = true;
|
|
||||||
this.isHasMore = hasMore;
|
this.isHasMore = hasMore;
|
||||||
|
this.isLoadMore = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
59
client/src/app/shared/actionRecord/actionRecord.ts
Normal file
59
client/src/app/shared/actionRecord/actionRecord.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
export class Action<T> {
|
||||||
|
private _action: string;
|
||||||
|
private _data: T;
|
||||||
|
private _type: string;
|
||||||
|
|
||||||
|
constructor(action: string, data: T, type: string) {
|
||||||
|
this._action = action;
|
||||||
|
this._data = data;
|
||||||
|
this._type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
get action(): string {
|
||||||
|
return this._action;
|
||||||
|
}
|
||||||
|
|
||||||
|
get data(): T {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
get type(): string {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ActionRecord<T> {
|
||||||
|
private _actionRecord: Action<T>[];
|
||||||
|
private _onAddActionCallback: (
|
||||||
|
currentAction: Action<T>,
|
||||||
|
actionRecord: Action<T>[]
|
||||||
|
) => void = () => {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._actionRecord = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecord(): Action<T>[] {
|
||||||
|
return this._actionRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAction(action: Action<T>): void {
|
||||||
|
this._actionRecord.push(action);
|
||||||
|
this._onAddActionCallback(action, this._actionRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAction(action: Action<T>): void {
|
||||||
|
let index = this._actionRecord.indexOf(action);
|
||||||
|
this._actionRecord.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAction(): void {
|
||||||
|
this._actionRecord = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnAddAction(
|
||||||
|
fn: (currentAction: Action<T>, actionRecord: Action<T>[]) => void
|
||||||
|
): void {
|
||||||
|
this._onAddActionCallback = fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
client/src/app/shared/types/recipe.d.ts
vendored
32
client/src/app/shared/types/recipe.d.ts
vendored
|
|
@ -24,22 +24,22 @@ export interface MaterialData {
|
||||||
waterHot: number;
|
waterHot: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecipeDetail {
|
// export interface RecipeDetail {
|
||||||
recipe: {
|
// recipe: {
|
||||||
lastModified: Date;
|
// lastModified: Date;
|
||||||
productCode: string;
|
// productCode: string;
|
||||||
name: string;
|
// name: string;
|
||||||
otherName: string;
|
// otherName: string;
|
||||||
description: string;
|
// description: string;
|
||||||
otherDescription: string;
|
// otherDescription: string;
|
||||||
price: number;
|
// price: number;
|
||||||
isUse: boolean;
|
// isUse: boolean;
|
||||||
isShow: boolean;
|
// isShow: boolean;
|
||||||
disable: boolean;
|
// disable: boolean;
|
||||||
recipes?: MaterialData[];
|
// recipes?: MaterialData[];
|
||||||
};
|
// };
|
||||||
recipes?: MatRecipe[];
|
// recipes?: MatRecipe[];
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface RecipeDetailEditable {
|
export interface RecipeDetailEditable {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
||||||
15
server/.vscode/launch.json
vendored
Normal file
15
server/.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceRoot}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
82
server/contracts/recipe.go
Normal file
82
server/contracts/recipe.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package contracts
|
||||||
|
|
||||||
|
// ================================== Recipes Dashboard and Overview ==================================
|
||||||
|
|
||||||
|
type RecipeOverview struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
ProductCode string `json:"productCode"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
OtherName string `json:"otherName"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
LastUpdated string `json:"lastUpdated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeDashboardRequest struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeDashboardResponse struct {
|
||||||
|
ConfigNumber int `json:"configNumber"`
|
||||||
|
LastUpdated string `json:"lastUpdated"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeOverviewRequest struct {
|
||||||
|
Take int `json:"take"`
|
||||||
|
Skip int `json:"skip"`
|
||||||
|
Search string `json:"search"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
MatIds []int `json:"matIds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeOverviewResponse struct {
|
||||||
|
Result []RecipeOverview `json:"result"`
|
||||||
|
HasMore bool `json:"hasMore"`
|
||||||
|
TotalCount int `json:"totalCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================== Recipe Detail ==================================
|
||||||
|
|
||||||
|
type RecipeDetailRequest struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
ProductCode string `json:"productCode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeDetailResponse struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
OtherName string `json:"otherName"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
OtherDescription string `json:"otherDescription"`
|
||||||
|
LastUpdated string `json:"lastUpdated"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeDetailMat struct {
|
||||||
|
MaterialID uint64 `json:"materialID"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
MixOrder int `json:"mixOrder"`
|
||||||
|
FeedParameter int `json:"feedParameter"`
|
||||||
|
FeedPattern int `json:"feedPattern"`
|
||||||
|
IsUse bool `json:"isUse"`
|
||||||
|
MaterialPathId int `json:"materialPathId"`
|
||||||
|
PowderGram int `json:"powderGram"`
|
||||||
|
PowderTime int `json:"powderTime"`
|
||||||
|
StirTime int `json:"stirTime"`
|
||||||
|
SyrupGram int `json:"syrupGram"`
|
||||||
|
SyrupTime int `json:"syrupTime"`
|
||||||
|
WaterCold int `json:"waterCold"`
|
||||||
|
WaterYield int `json:"waterYield"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeDetailMatListRequest struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
ProductCode string `json:"productCode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecipeDetailMatListResponse struct {
|
||||||
|
Result []RecipeDetailMat `json:"result"`
|
||||||
|
}
|
||||||
|
|
@ -69,20 +69,20 @@ func NewData() *Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Data) GetRecipe(countryID, filename string) models.Recipe {
|
func (d *Data) GetRecipe(countryID, filename string) *models.Recipe {
|
||||||
|
|
||||||
if countryID == "" {
|
if countryID == "" {
|
||||||
return *d.currentRecipe
|
return d.currentRecipe
|
||||||
}
|
}
|
||||||
|
|
||||||
if filename == "" || filename == d.CurrentFile {
|
if filename == "" || filename == d.CurrentFile {
|
||||||
return *d.currentRecipe
|
return d.currentRecipe
|
||||||
}
|
}
|
||||||
|
|
||||||
if recipe, ok := d.recipeMap[filename]; ok {
|
if recipe, ok := d.recipeMap[filename]; ok {
|
||||||
d.CurrentFile = filename
|
d.CurrentFile = filename
|
||||||
d.CurrentCountryID = countryID
|
d.CurrentCountryID = countryID
|
||||||
return recipe.Recipe
|
return &recipe.Recipe
|
||||||
}
|
}
|
||||||
|
|
||||||
// change current version and read new recipe
|
// change current version and read new recipe
|
||||||
|
|
@ -92,7 +92,7 @@ func (d *Data) GetRecipe(countryID, filename string) models.Recipe {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.GetInstance().Error("Error when read recipe file", zap.Error(err))
|
logger.GetInstance().Error("Error when read recipe file", zap.Error(err))
|
||||||
return *d.currentRecipe
|
return d.currentRecipe
|
||||||
}
|
}
|
||||||
|
|
||||||
d.currentRecipe = recipe
|
d.currentRecipe = recipe
|
||||||
|
|
@ -116,23 +116,70 @@ func (d *Data) GetRecipe(countryID, filename string) models.Recipe {
|
||||||
TimeStamps: time.Now().Unix(),
|
TimeStamps: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return *d.currentRecipe
|
return d.currentRecipe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Data) GetRecipe01() []models.Recipe01 {
|
func (d *Data) GetRecipe01() []models.Recipe01 {
|
||||||
return d.currentRecipe.Recipe01
|
return d.currentRecipe.Recipe01
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Data) GetRecipe01ByProductCode(code string) models.Recipe01 {
|
func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string) (models.Recipe01, error) {
|
||||||
result := make([]models.Recipe01, 0)
|
|
||||||
|
|
||||||
for _, v := range d.currentRecipe.Recipe01 {
|
if filename == "" || filename == d.CurrentFile {
|
||||||
if v.ProductCode == code {
|
for _, v := range d.currentRecipe.Recipe01 {
|
||||||
result = append(result, v)
|
if v.ProductCode == productCode {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if recipe, ok := d.recipeMap[filename]; ok {
|
||||||
|
for _, v := range recipe.Recipe.Recipe01 {
|
||||||
|
if v.ProductCode == productCode {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result[0]
|
d.CurrentFile = filename
|
||||||
|
d.CurrentCountryID = countryID
|
||||||
|
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.GetInstance().Error("Error when read recipe file", zap.Error(err))
|
||||||
|
for _, v := range d.currentRecipe.Recipe01 {
|
||||||
|
if v.ProductCode == productCode {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.currentRecipe = recipe
|
||||||
|
|
||||||
|
// save to map
|
||||||
|
if len(d.recipeMap) > 5 { // limit keep in memory 5 version
|
||||||
|
// remove oldest version
|
||||||
|
var oldestVersion string
|
||||||
|
var oldestTime int64
|
||||||
|
for k, v := range d.recipeMap {
|
||||||
|
if oldestTime == 0 || v.TimeStamps < oldestTime {
|
||||||
|
oldestTime = v.TimeStamps
|
||||||
|
oldestVersion = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(d.recipeMap, oldestVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.recipeMap[filename] = RecipeWithTimeStamps{
|
||||||
|
Recipe: *d.currentRecipe,
|
||||||
|
TimeStamps: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range d.currentRecipe.Recipe01 {
|
||||||
|
if v.ProductCode == productCode {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.Recipe01{}, fmt.Errorf("product code: %s not found", productCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Data) SetValuesToRecipe(recipe models.Recipe01) {
|
func (d *Data) SetValuesToRecipe(recipe models.Recipe01) {
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"recipe-manager/contracts"
|
||||||
"recipe-manager/data"
|
"recipe-manager/data"
|
||||||
"recipe-manager/models"
|
"recipe-manager/models"
|
||||||
"recipe-manager/services/logger"
|
"recipe-manager/services/logger"
|
||||||
|
"recipe-manager/services/recipe"
|
||||||
"recipe-manager/services/sheet"
|
"recipe-manager/services/sheet"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -20,24 +21,45 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RecipeRouter struct {
|
type RecipeRouter struct {
|
||||||
data *data.Data
|
data *data.Data
|
||||||
sheetService sheet.SheetService
|
sheetService sheet.SheetService
|
||||||
|
recipeService recipe.RecipeService
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Log = logger.GetInstance()
|
Log = logger.GetInstance()
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRecipeRouter(data *data.Data, sheetService sheet.SheetService) *RecipeRouter {
|
func NewRecipeRouter(data *data.Data, recipeService recipe.RecipeService, sheetService sheet.SheetService) *RecipeRouter {
|
||||||
return &RecipeRouter{
|
return &RecipeRouter{
|
||||||
data: data,
|
data: data,
|
||||||
sheetService: sheetService,
|
recipeService: recipeService,
|
||||||
|
sheetService: sheetService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rr *RecipeRouter) Route(r chi.Router) {
|
func (rr *RecipeRouter) Route(r chi.Router) {
|
||||||
r.Route("/recipes", func(r chi.Router) {
|
r.Route("/recipes", func(r chi.Router) {
|
||||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
|
||||||
|
country := r.URL.Query().Get("country")
|
||||||
|
filename := r.URL.Query().Get("filename")
|
||||||
|
|
||||||
|
result, err := rr.recipeService.GetRecipeDashboard(&contracts.RecipeDashboardRequest{
|
||||||
|
Country: country,
|
||||||
|
Filename: filename,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Get("/overview", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
var take, offset uint64 = 10, 0
|
var take, offset uint64 = 10, 0
|
||||||
if newOffset, err := strconv.ParseUint(r.URL.Query().Get("offset"), 10, 64); err == nil {
|
if newOffset, err := strconv.ParseUint(r.URL.Query().Get("offset"), 10, 64); err == nil {
|
||||||
|
|
@ -50,111 +72,105 @@ func (rr *RecipeRouter) Route(r chi.Router) {
|
||||||
|
|
||||||
country := r.URL.Query().Get("country")
|
country := r.URL.Query().Get("country")
|
||||||
filename := r.URL.Query().Get("filename")
|
filename := r.URL.Query().Get("filename")
|
||||||
materialIds := r.URL.Query().Get("material_ids")
|
materialIds := r.URL.Query().Get("materialIds")
|
||||||
|
|
||||||
var materialIdsUint []uint64
|
var materialIdsUint []int
|
||||||
for _, v := range strings.Split(materialIds, ",") {
|
for _, v := range strings.Split(materialIds, ",") {
|
||||||
materialIdUint, err := strconv.ParseUint(v, 10, 64)
|
materialIdUint, err := strconv.ParseUint(v, 10, 64)
|
||||||
if err != nil || materialIdUint == 0 {
|
if err != nil || materialIdUint == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
materialIdsUint = append(materialIdsUint, materialIdUint)
|
materialIdsUint = append(materialIdsUint, int(materialIdUint))
|
||||||
}
|
}
|
||||||
|
|
||||||
countryID, err := rr.data.GetCountryIDByName(country)
|
result, err := rr.recipeService.GetRecipeOverview(&contracts.RecipeOverviewRequest{
|
||||||
|
Take: int(take),
|
||||||
|
Skip: int(offset),
|
||||||
|
Search: r.URL.Query().Get("search"),
|
||||||
|
Country: country,
|
||||||
|
Filename: filename,
|
||||||
|
MatIds: materialIdsUint,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
recipe := rr.data.GetRecipe(countryID, filename)
|
json.NewEncoder(w).Encode(result)
|
||||||
searchQuery := r.URL.Query().Get("search")
|
|
||||||
|
|
||||||
if searchQuery != "" {
|
|
||||||
recipe.Recipe01 = []models.Recipe01{}
|
|
||||||
for _, v := range rr.data.GetRecipe01() {
|
|
||||||
if strings.Contains(strings.ToLower(v.ProductCode), strings.ToLower(searchQuery)) ||
|
|
||||||
strings.Contains(strings.ToLower(v.Name), strings.ToLower(searchQuery)) ||
|
|
||||||
strings.Contains(strings.ToLower(v.OtherName), strings.ToLower(searchQuery)) {
|
|
||||||
recipe.Recipe01 = append(recipe.Recipe01, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(materialIdsUint) > 0 {
|
|
||||||
resultFilter := []models.Recipe01{}
|
|
||||||
for _, v := range recipe.Recipe01 {
|
|
||||||
for _, matID := range materialIdsUint {
|
|
||||||
for _, recipe := range v.Recipes {
|
|
||||||
if recipe.IsUse && uint64(recipe.MaterialPathId) == matID {
|
|
||||||
resultFilter = append(resultFilter, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
recipe.Recipe01 = resultFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
isHasMore := len(recipe.Recipe01) >= int(take+offset)
|
|
||||||
if isHasMore {
|
|
||||||
recipe.Recipe01 = recipe.Recipe01[offset : take+offset]
|
|
||||||
sort.Slice(recipe.Recipe01, func(i, j int) bool {
|
|
||||||
return recipe.Recipe01[i].ID < recipe.Recipe01[j].ID
|
|
||||||
})
|
|
||||||
} else if len(recipe.Recipe01) > int(offset) {
|
|
||||||
recipe.Recipe01 = recipe.Recipe01[offset:]
|
|
||||||
} else {
|
|
||||||
recipe.Recipe01 = []models.Recipe01{}
|
|
||||||
}
|
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
||||||
"fileName": rr.data.CurrentFile,
|
|
||||||
"recipes": recipe,
|
|
||||||
"hasMore": isHasMore,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Get("/{product_code}", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/{product_code}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
productCode := chi.URLParam(r, "product_code")
|
productCode := chi.URLParam(r, "product_code")
|
||||||
|
|
||||||
recipe := rr.data.GetRecipe01()
|
// recipe := rr.data.GetRecipe01()
|
||||||
recipeMetaData := rr.sheetService.GetSheet(r.Context(), "1rSUKcc5POR1KeZFGoeAZIoVoI7LPGztBhPw5Z_ConDE")
|
// recipeMetaData := rr.sheetService.GetSheet(r.Context(), "1rSUKcc5POR1KeZFGoeAZIoVoI7LPGztBhPw5Z_ConDE")
|
||||||
|
|
||||||
var recipeResult *models.Recipe01
|
// var recipeResult *models.Recipe01
|
||||||
recipeMetaDataResult := map[string]string{}
|
// recipeMetaDataResult := map[string]string{}
|
||||||
|
|
||||||
for _, v := range recipe {
|
// for _, v := range recipe {
|
||||||
if v.ProductCode == productCode {
|
// if v.ProductCode == productCode {
|
||||||
recipeResult = &v
|
// recipeResult = &v
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
for _, v := range recipeMetaData {
|
// for _, v := range recipeMetaData {
|
||||||
if v[0].(string) == productCode {
|
// if v[0].(string) == productCode {
|
||||||
recipeMetaDataResult = map[string]string{
|
// recipeMetaDataResult = map[string]string{
|
||||||
"productCode": v[0].(string),
|
// "productCode": v[0].(string),
|
||||||
"name": v[1].(string),
|
// "name": v[1].(string),
|
||||||
"otherName": v[2].(string),
|
// "otherName": v[2].(string),
|
||||||
"description": v[3].(string),
|
// "description": v[3].(string),
|
||||||
"otherDescription": v[4].(string),
|
// "otherDescription": v[4].(string),
|
||||||
"picture": v[5].(string),
|
// "picture": v[5].(string),
|
||||||
}
|
// }
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if recipeResult == nil {
|
// if recipeResult == nil {
|
||||||
http.Error(w, "Not Found", http.StatusNotFound)
|
// http.Error(w, "Not Found", http.StatusNotFound)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
// "recipe": recipeResult,
|
||||||
|
// "recipeMetaData": recipeMetaDataResult,
|
||||||
|
// })
|
||||||
|
|
||||||
|
result, err := rr.recipeService.GetRecipeDetail(&contracts.RecipeDetailRequest{
|
||||||
|
Filename: r.URL.Query().Get("filename"),
|
||||||
|
Country: r.URL.Query().Get("country"),
|
||||||
|
ProductCode: productCode,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(result)
|
||||||
"recipe": recipeResult,
|
})
|
||||||
"recipeMetaData": recipeMetaDataResult,
|
|
||||||
|
r.Get("/{product_code}/mat", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
productCode := chi.URLParam(r, "product_code")
|
||||||
|
|
||||||
|
result, err := rr.recipeService.GetRecipeDetailMat(&contracts.RecipeDetailRequest{
|
||||||
|
Filename: r.URL.Query().Get("filename"),
|
||||||
|
Country: r.URL.Query().Get("country"),
|
||||||
|
ProductCode: productCode,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Get("/{country}/{filename}/json", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/{country}/{filename}/json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -242,7 +258,12 @@ func (rr *RecipeRouter) Route(r chi.Router) {
|
||||||
|
|
||||||
Log.Debug("Changes: ", zap.Any("changes", changes))
|
Log.Debug("Changes: ", zap.Any("changes", changes))
|
||||||
// TODO: find the matched pd
|
// TODO: find the matched pd
|
||||||
target_menu := rr.data.GetRecipe01ByProductCode(changes.ProductCode)
|
target_menu, err := rr.data.GetRecipe01ByProductCode(filename, countryID, changes.ProductCode)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
Log.Error("Error when get recipe by product code", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
menu_map := target_menu.ToMap()
|
menu_map := target_menu.ToMap()
|
||||||
change_map := changes.ToMap()
|
change_map := changes.ToMap()
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"recipe-manager/routers"
|
"recipe-manager/routers"
|
||||||
"recipe-manager/services/logger"
|
"recipe-manager/services/logger"
|
||||||
"recipe-manager/services/oauth"
|
"recipe-manager/services/oauth"
|
||||||
|
"recipe-manager/services/recipe"
|
||||||
"recipe-manager/services/sheet"
|
"recipe-manager/services/sheet"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -429,8 +430,11 @@ func (s *Server) createHandler() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recipe Service
|
||||||
|
rs := recipe.NewRecipeService(database)
|
||||||
|
|
||||||
// Recipe Router
|
// Recipe Router
|
||||||
rr := routers.NewRecipeRouter(database, sheetService)
|
rr := routers.NewRecipeRouter(database, rs, sheetService)
|
||||||
rr.Route(r)
|
rr.Route(r)
|
||||||
|
|
||||||
// Material Router
|
// Material Router
|
||||||
|
|
|
||||||
192
server/services/recipe/recipe.go
Normal file
192
server/services/recipe/recipe.go
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
package recipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"recipe-manager/contracts"
|
||||||
|
"recipe-manager/data"
|
||||||
|
"recipe-manager/models"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecipeService interface {
|
||||||
|
GetRecipeDashboard(request *contracts.RecipeDashboardRequest) (contracts.RecipeDashboardResponse, error)
|
||||||
|
GetRecipeOverview(request *contracts.RecipeOverviewRequest) (contracts.RecipeOverviewResponse, error)
|
||||||
|
|
||||||
|
GetRecipeDetail(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailResponse, error)
|
||||||
|
GetRecipeDetailMat(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailMatListResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type recipeService struct {
|
||||||
|
db *data.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecipeDetail implements RecipeService.
|
||||||
|
func (rs *recipeService) GetRecipeDetail(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailResponse, error) {
|
||||||
|
|
||||||
|
recipe, err := rs.db.GetRecipe01ByProductCode(request.Filename, request.Country, request.ProductCode)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return contracts.RecipeDetailResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := contracts.RecipeDetailResponse{
|
||||||
|
Name: recipe.Name,
|
||||||
|
OtherName: recipe.OtherName,
|
||||||
|
Description: recipe.Description,
|
||||||
|
OtherDescription: recipe.OtherDescription,
|
||||||
|
LastUpdated: recipe.LastChange,
|
||||||
|
Picture: recipe.UriData[len("img="):], // remove "img=" prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecipeDetailMat implements RecipeService.
|
||||||
|
func (rs *recipeService) GetRecipeDetailMat(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailMatListResponse, error) {
|
||||||
|
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return contracts.RecipeDetailMatListResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipe, err := rs.db.GetRecipe01ByProductCode(request.Filename, request.Country, request.ProductCode)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return contracts.RecipeDetailMatListResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matIds := []uint64{}
|
||||||
|
for _, v := range recipe.Recipes {
|
||||||
|
if v.IsUse {
|
||||||
|
matIds = append(matIds, uint64(v.MaterialPathId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matsCode := rs.db.GetMaterialCode(matIds, countryID, request.Filename)
|
||||||
|
|
||||||
|
result := contracts.RecipeDetailMatListResponse{
|
||||||
|
Result: []contracts.RecipeDetailMat{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range recipe.Recipes {
|
||||||
|
for _, mat := range matsCode {
|
||||||
|
if v.MaterialPathId == int(mat.MaterialID) {
|
||||||
|
result.Result = append(result.Result, contracts.RecipeDetailMat{
|
||||||
|
MaterialID: mat.MaterialID,
|
||||||
|
Name: mat.PackageDescription,
|
||||||
|
MixOrder: v.MixOrder,
|
||||||
|
FeedParameter: v.FeedParameter,
|
||||||
|
FeedPattern: v.FeedPattern,
|
||||||
|
IsUse: v.IsUse,
|
||||||
|
MaterialPathId: v.MaterialPathId,
|
||||||
|
PowderGram: v.PowderGram,
|
||||||
|
PowderTime: v.PowderTime,
|
||||||
|
StirTime: v.StirTime,
|
||||||
|
SyrupGram: v.SyrupGram,
|
||||||
|
SyrupTime: v.SyrupTime,
|
||||||
|
WaterCold: v.WaterCold,
|
||||||
|
WaterYield: v.WaterYield,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by id
|
||||||
|
sort.Slice(result.Result, func(i, j int) bool {
|
||||||
|
return result.Result[i].MaterialID < result.Result[j].MaterialID
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *recipeService) GetRecipeDashboard(request *contracts.RecipeDashboardRequest) (contracts.RecipeDashboardResponse, error) {
|
||||||
|
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return contracts.RecipeDashboardResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipe := rs.db.GetRecipe(countryID, request.Filename)
|
||||||
|
|
||||||
|
result := contracts.RecipeDashboardResponse{
|
||||||
|
ConfigNumber: recipe.MachineSetting.ConfigNumber,
|
||||||
|
LastUpdated: recipe.Timestamp,
|
||||||
|
Filename: request.Filename,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *recipeService) GetRecipeOverview(request *contracts.RecipeOverviewRequest) (contracts.RecipeOverviewResponse, error) {
|
||||||
|
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return contracts.RecipeOverviewResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
||||||
|
}
|
||||||
|
recipe := rs.db.GetRecipe(countryID, request.Filename)
|
||||||
|
recipeFilter := recipe.Recipe01
|
||||||
|
|
||||||
|
result := contracts.RecipeOverviewResponse{}
|
||||||
|
|
||||||
|
if request.Search != "" {
|
||||||
|
searchResult := []models.Recipe01{}
|
||||||
|
for _, v := range recipeFilter {
|
||||||
|
if strings.Contains(strings.ToLower(v.ProductCode), strings.ToLower(request.Search)) ||
|
||||||
|
strings.Contains(strings.ToLower(v.Name), strings.ToLower(request.Search)) ||
|
||||||
|
strings.Contains(strings.ToLower(v.OtherName), strings.ToLower(request.Search)) {
|
||||||
|
searchResult = append(searchResult, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recipeFilter = searchResult
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(request.MatIds) > 0 {
|
||||||
|
matIdsFiltered := []models.Recipe01{}
|
||||||
|
for _, v := range recipeFilter {
|
||||||
|
for _, matID := range request.MatIds {
|
||||||
|
for _, recipe := range v.Recipes {
|
||||||
|
if recipe.IsUse && recipe.MaterialPathId == matID {
|
||||||
|
matIdsFiltered = append(matIdsFiltered, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recipeFilter = matIdsFiltered
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map to contracts.RecipeOverview
|
||||||
|
for _, v := range recipeFilter {
|
||||||
|
result.Result = append(result.Result, contracts.RecipeOverview{
|
||||||
|
ID: v.ID,
|
||||||
|
ProductCode: v.ProductCode,
|
||||||
|
Name: v.Name,
|
||||||
|
OtherName: v.OtherName,
|
||||||
|
Description: v.Description,
|
||||||
|
LastUpdated: v.LastChange,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TotalCount = len(result.Result)
|
||||||
|
|
||||||
|
result.HasMore = result.TotalCount >= request.Take+request.Skip
|
||||||
|
if result.HasMore {
|
||||||
|
result.Result = result.Result[request.Skip : request.Take+request.Skip]
|
||||||
|
sort.Slice(result.Result, func(i, j int) bool {
|
||||||
|
return result.Result[i].ID < result.Result[j].ID
|
||||||
|
})
|
||||||
|
} else if result.TotalCount > request.Skip {
|
||||||
|
result.Result = result.Result[request.Skip:]
|
||||||
|
} else {
|
||||||
|
result.Result = []contracts.RecipeOverview{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecipeService(db *data.Data) RecipeService {
|
||||||
|
return &recipeService{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue