add carousel & test submenu

This commit is contained in:
pakintada@gmail.com 2024-01-15 11:48:25 +07:00
parent edcccaaab9
commit 7102f644d4
7 changed files with 242 additions and 314 deletions

View file

@ -197,32 +197,47 @@ export class RecipeService {
) )
.subscribe({ .subscribe({
next(value) { next(value) {
console.log( value); console.log(value);
}, },
}); });
} }
getSavedTmp(country: string, filename:string) { getSavedTmp(country: string, filename: string) {
console.log("loading saved .tmp* file", country, filename) console.log('loading saved .tmp* file', country, filename);
// do split filename // do split filename
filename = filename.split('_')[1]; filename = filename.split('_')[1];
// this._user.getCurrentUser().subscribe((user) => { // this._user.getCurrentUser().subscribe((user) => {
// this.user = user.user; // this.user = user.user;
// }) // })
return this._httpClient return this._httpClient.get<string[]>(
.get<string[]>( environment.api + ('/recipes/saved/' + country + '/' + filename),
environment.api + ("/recipes/saved/"+ country + "/" + filename), {
{ withCredentials: true,
withCredentials: true, }
} );
); // .subscribe({
// .subscribe({ // next(value) {
// next(value) { // console.log( value);
// console.log( value); // },
// }, // });
// }); }
getSubMenus(country: string, filename: string, productCode: string) {
return this._httpClient.get<Recipe01[]>(
environment.api +
'/recipes/' +
country +
'/' +
filename +
'/' +
productCode +
'/submenus',
{
withCredentials: true,
responseType: 'json',
}
);
} }
} }

View file

@ -1,315 +1,163 @@
<div class="p-4 overflow-auto h-[90vh]"> <div class="p-4 overflow-auto h-[90vh]">
<form class="grid grid-cols-3 gap-4 mb-4" [formGroup]="recipeDetailForm"> <form [formGroup]="recipeDetailForm">
<div <!-- carousel -->
class="block col-span-1 p-6 bg-white border border-gray-200 rounded-lg shadow" <div class="carousel carousel-center space-x-8">
> <div id="name" class="carousel-item w-1/2">
<div *ngIf="isLoaded; else indicator" [@inOutAnimation]> <div
<div class="flex flex-wrap"> class="block p-6 bg-white border border-gray-200 rounded-lg shadow w-full"
<h5 class="mb-2 text-xl font-bold text-gray-900"> >
{{ recipeDetailForm.getRawValue().name }} <div *ngIf="isLoaded; else indicator" [@inOutAnimation]>
</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">
{{ recipeDetailForm.getRawValue().otherName }}
</h5>
<!-- productCode -->
<div> <!-- productCode -->
<input <div>
class="input text-lg text-gray-900 my-2" <input
type="text" class="input input-bordered input-xs text-lg text-gray-900 my-2"
name="productCode" type="text"
[value]="productCode" name="productCode"
(keyup)="onProductCodeChange($event)" [value]="productCode"
[disabled]="!isEditable()" (keyup)="onProductCodeChange($event)"
/> [disabled]="!isEditable()"
/>
</div>
<div class="flex flex-wrap">
<h5 class="mb-2 text-xl font-bold text-gray-900">
{{ recipeDetailForm.getRawValue().name }}
</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">
{{ recipeDetailForm.getRawValue().otherName }}
</h5>
</div>
<div class="flex items-center mb-2">
<div class="flex items-center">
<p class="text-sm text-gray-500">Last Modify</p>
<p class="ml-2 text-sm text-gray-900">
{{
recipeDetailForm.getRawValue().lastModified
| date : "dd/MM/yyyy HH:mm:ss"
}}
</p>
</div>
</div>
<div class="flex p-3 items-center justify-center w-full">
<img
class="rounded-lg shadow"
src="/assets/{{ 'bn_hot_america_no' }}.png"
/>
</div>
</div> </div>
</div> </div>
<div class="flex items-center mb-2">
<div class="flex items-center"> <div
<p class="text-sm text-gray-500">Last Modify</p> class="block p-6 bg-white border border-gray-200 rounded-lg shadow min-h-[300px] w-full"
<p class="ml-2 text-sm text-gray-900"> >
{{ <div
recipeDetailForm.getRawValue().lastModified *ngIf="isLoaded; else indicator"
| date : "dd/MM/yyyy HH:mm:ss" [@inOutAnimation]
}} class="grid-cols-2 w-full flex flex-col gap-2"
</p> >
<div class="flex items-center">
<label class="label text-base mr-3 w-max"
><span class="label-text text-base mr-3 w-max"
>Name:</span
></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="name"
/>
</div>
<div class="flex items-center">
<label class="label">
<span class="label-text text-base mr-3 w-max"
>Other Name:</span
></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="otherName"
/>
</div>
<div class="flex items-center">
<label class="label"
><span class="lable-text text-base mr-3 w-max"
>Description:</span
></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="description"
/>
</div>
<div class="flex items-center">
<label class="label"
><span class="label-text text-base mr-3 w-max"
>Other Description:</span
></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="otherDescription"
/>
</div>
<div *ngIf="hasSubmenu()">
<div *ngFor="let sub of listSubMenuProductcodes()">
<button class="btn btn-sm btn-primary m-2" (click)="selectSubmenu(sub)">{{sub}}</button>
</div>
</div>
</div> </div>
</div> </div>
<div class="flex p-3 items-center justify-center w-full">
<img
class="rounded-lg shadow"
src="/assets/{{ 'bn_hot_america_no' }}.png"
/>
</div>
</div> </div>
</div> <div id="recipeList" class="carousel-item w-full">
<div
<div class="overflow-auto h-[75vh] mb-4 rounded bg-white border border-gray-200 shadow w-1/2"
class="block col-span-2 p-6 bg-white border border-gray-200 rounded-lg shadow min-h-[300px]"
>
<div
*ngIf="isLoaded; else indicator"
[@inOutAnimation]
class="grid-cols-2 w-full flex flex-col gap-2"
>
<div class="flex items-center">
<label class="label text-base mr-3 w-max"
><span class="label-text text-base mr-3 w-max">Name:</span></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="name"
/>
</div>
<div class="flex items-center">
<label class="label">
<span class="label-text text-base mr-3 w-max"
>Other Name:</span
></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="otherName"
/>
</div>
<div class="flex items-center">
<label class="label"
><span class="lable-text text-base mr-3 w-max"
>Description:</span
></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="description"
/>
</div>
<div class="flex items-center">
<label class="label"
><span class="label-text text-base mr-3 w-max"
>Other Description:</span
></label
>
<input
type="text"
class="input input-sm input-bordered input-ghost w-full"
formControlName="otherDescription"
/>
</div>
</div>
</div>
<div
class="col-span-3 overflow-auto h-[75vh] mb-4 rounded bg-white border border-gray-200 shadow"
> >
<app-recipe-list <app-recipe-list
[productCode]="productCode" [productCode]="productCode"
[isSubMenu]="false"
(recipeListFormChange)="onRecipeListFormChange($event)" (recipeListFormChange)="onRecipeListFormChange($event)"
></app-recipe-list> ></app-recipe-list>
</div> </div>
</div>
<div id="toppingSet" class="carousel-item w-full">
<div class="overflow-auto h-[75vh] mb-4 rounded bg-white border border-gray-200 shadow">
<app-recipe-toppingset
[productCode]="productCode"
(toppingSetChange)="onToppingListChange($event)"
></app-recipe-toppingset>
</div>
</div>
</div>
<!-- try pop up modal -->
<!-- TODO: do topping --> <!-- TODO: do topping -->
<app-recipe-toppingset
[productCode]="productCode"
(toppingSetChange)="onToppingListChange($event)"
></app-recipe-toppingset>
<div class="grid grid-cols-2 gap-4 mb-4">
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
</div>
<div <div
class="flex items-center justify-center h-48 mb-4 rounded bg-gray-50 dark:bg-gray-800" class="sticky bottom-0 col-span-3 max-w-screen-lg flex justify-end bg-white rounded-full drop-shadow-2xl p-3"
> >
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg <div class="flex justify-center w-full start-0 py-2">
class="w-3.5 h-3.5" <a href="{{department}}/recipe/{{productCode}}#name" class="btn btn-xs">1</a>
aria-hidden="true" <a href="{{department}}/recipe/{{productCode}}#recipeList" class="btn btn-xs">2</a>
xmlns="http://www.w3.org/2000/svg" <a href="{{department}}/recipe/{{productCode}}#toppingSet" class="btn btn-xs">3</a>
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div> </div>
<div class="grid grid-cols-2 gap-4">
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
<div
class="flex items-center justify-center rounded bg-gray-50 h-28 dark:bg-gray-800"
>
<p class="text-2xl text-gray-400 dark:text-gray-500">
<svg
class="w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 1v16M1 9h16"
/>
</svg>
</p>
</div>
</div>
<div
class="sticky bottom-0 col-span-3 flex justify-end bg-white rounded-full drop-shadow-2xl p-3"
>
<!-- <div> Commit Message </div> --> <!-- <div> Commit Message </div> -->
<!-- <p class="text-2xl mr-8 text-gray-400 dark:text-gray-500">Commit Message</p> --> <!-- <p class="text-2xl mr-8 text-gray-400 dark:text-gray-500">Commit Message</p> -->
<input <input

View file

@ -92,6 +92,7 @@ export class RecipeDetailsComponent implements OnInit {
tpl = [] tpl = []
toppingSet: ToppingSet[] | null = null; toppingSet: ToppingSet[] | null = null;
submenus: Recipe01[] | null = null;
ngOnInit() { ngOnInit() {
this.productCode = this._route.snapshot.params['productCode']; this.productCode = this._route.snapshot.params['productCode'];
@ -101,13 +102,18 @@ export class RecipeDetailsComponent implements OnInit {
.pipe(first()); .pipe(first());
this.recipeDetail$.subscribe((detail) => { this.recipeDetail$.subscribe((detail) => {
console.log('Recipe Detail', detail); // console.log('Recipe Detail', detail);
this.recipeDetailForm.patchValue(detail); this.recipeDetailForm.patchValue(detail);
this.isLoaded = true; this.isLoaded = true;
this.recipeOriginalDetail = { ...this.recipeDetailForm.getRawValue() }; this.recipeOriginalDetail = { ...this.recipeDetailForm.getRawValue() };
}); });
this._recipeService.getSubMenus(this._recipeService.getCurrentCountry(), this._recipeService.getCurrentFile(), this.productCode).subscribe((data) => {
console.log('Submenus', data);
this.submenus = data;
});
this.recipeDetailForm.valueChanges.subscribe(this.onRecipeDetailFormChange); this.recipeDetailForm.valueChanges.subscribe(this.onRecipeDetailFormChange);
@ -243,4 +249,21 @@ export class RecipeDetailsComponent implements OnInit {
this.changedProductCode = event.target.value; this.changedProductCode = event.target.value;
} }
} }
// Submenus
selectedSubProductCode: string | undefined = undefined;
hasSubmenu = () => this.submenus != null && this.submenus!.length > 0;
listSubMenuProductcodes = () => this.submenus!.map((recipe) => recipe.productCode);
selectSubmenu(productCode: string) {
if (this.selectedSubProductCode == productCode) {
this.selectedSubProductCode = undefined;
console.log('Unselected submenu', productCode);
return;
}
this.selectedSubProductCode = productCode;
console.log('Selected submenu', productCode);
}
} }

View file

@ -54,8 +54,9 @@
<td <td
class="m-2 px-4 py-4 font-medium text-gray-900 whitespace-nowrap" class="m-2 px-4 py-4 font-medium text-gray-900 whitespace-nowrap"
*ngIf=" *ngIf="
displayByCond(i, 'syrupGram', 'not-zero', { compare: undefined }) displayByCond(i, 'syrupGram', 'not-zero', { compare: undefined }) &&
" getTypeForRecipeListAtIndex(i)['category'] == 'syrup'
"
> >
<div class="flex items-center space-x-2 bg-purple-300 rounded-md"> <div class="flex items-center space-x-2 bg-purple-300 rounded-md">
<p>Volume</p> <p>Volume</p>

View file

@ -40,6 +40,7 @@ import {
}) })
export class RecipeListComponent implements OnInit { export class RecipeListComponent implements OnInit {
@Input({ required: true }) productCode!: string; @Input({ required: true }) productCode!: string;
@Input() isSubMenu: boolean = false;
@Output() recipeListFormChange = new EventEmitter<unknown[]>(); @Output() recipeListFormChange = new EventEmitter<unknown[]>();
materialList: MaterialCode[] = []; materialList: MaterialCode[] = [];

View file

@ -426,6 +426,24 @@ func (d *Data) GetToppingsOfRecipe(countryID, filename string, productCode strin
return recipe.ToppingSet, nil return recipe.ToppingSet, nil
} }
func (d *Data) GetSubmenusOfRecipe(countryID, filename, productCode string) ([]models.Recipe01, error) {
recipe, err := d.GetRecipe01ByProductCode(filename, countryID, productCode)
if err != nil {
d.taoLogger.Log.Error("Error when read recipe file, Return default recipe", zap.Error(err))
return []models.Recipe01{}, err
}
submenu := recipe.SubMenu
if submenu == nil {
return []models.Recipe01{}, fmt.Errorf("no submenu")
}
return submenu, nil
}
func (d *Data) GetCountryNameByID(countryID string) (string, error) { func (d *Data) GetCountryNameByID(countryID string) (string, error) {
for _, country := range d.Countries { for _, country := range d.Countries {
if country.CountryID == countryID { if country.CountryID == countryID {

View file

@ -59,6 +59,8 @@ func (rr *RecipeRouter) Route(r chi.Router) {
r.Get("/{country}/{filename}/{product_code}/toppings", rr.getToppingsOfRecipe) r.Get("/{country}/{filename}/{product_code}/toppings", rr.getToppingsOfRecipe)
r.Get("/{country}/{filename}/{product_code}/submenus", rr.getSubmenusOfRecipe)
r.Get("/{country}/{filename}/json", rr.getRecipeJson) r.Get("/{country}/{filename}/json", rr.getRecipeJson)
r.Post("/edit/{country}/{filename}", rr.updateRecipe) r.Post("/edit/{country}/{filename}", rr.updateRecipe)
@ -520,6 +522,26 @@ func (rr *RecipeRouter) getToppingsOfRecipe(w http.ResponseWriter, r *http.Reque
json.NewEncoder(w).Encode(topps) json.NewEncoder(w).Encode(topps)
} }
func (rr *RecipeRouter) getSubmenusOfRecipe(w http.ResponseWriter, r *http.Request) {
countryID := chi.URLParam(r, "country")
filename := chi.URLParam(r, "filename")
productCode := chi.URLParam(r, "product_code")
w.Header().Add("Content-Type", "application/json")
submenus, err := rr.data.GetSubmenusOfRecipe(countryID, filename, productCode)
rr.taoLogger.Log.Debug("RecipeRouter.getSubmenusOfRecipe", zap.Any("submenus", len(submenus)))
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(submenus)
}
func (rr *RecipeRouter) doMergeJson(w http.ResponseWriter, r *http.Request) { func (rr *RecipeRouter) doMergeJson(w http.ResponseWriter, r *http.Request) {
// TODO: v2, change to binary instead // TODO: v2, change to binary instead
if !APIhandler(w, r) { if !APIhandler(w, r) {