-
เลือก Recipe ไฟล์
+
Select Recipe File
+
+
diff --git a/client/src/app/features/recipes/recipes.component.ts b/client/src/app/features/recipes/recipes.component.ts
index 0fdb273..cbc723a 100644
--- a/client/src/app/features/recipes/recipes.component.ts
+++ b/client/src/app/features/recipes/recipes.component.ts
@@ -1,4 +1,10 @@
-import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
+import {
+ Component,
+ ElementRef,
+ OnDestroy,
+ OnInit,
+ ViewChild,
+} from '@angular/core';
import { UserService } from 'src/app/core/services/user.service';
import { User } from 'src/app/core/models/user.model';
import { DatePipe, NgFor, NgIf } from '@angular/common';
@@ -6,27 +12,20 @@ import { Recipe, Recipe01 } from 'src/app/core/models/recipe.model';
import { RecipeService } from 'src/app/core/services/recipe.service';
import { environment } from 'src/environments/environment';
import { RecipeModalComponent } from 'src/app/shared/modal/recipe-details/recipe-modal.component';
-import { BehaviorSubject } from 'rxjs';
+import { BehaviorSubject, Subject, Subscriber, Subscription } from 'rxjs';
import * as lodash from 'lodash';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
-interface RecipeFileFilter {
- country: string | null;
- fileName: string | null;
-}
-
@Component({
selector: 'app-recipes',
standalone: true,
imports: [RouterLink, NgIf, NgFor, DatePipe, RecipeModalComponent],
templateUrl: './recipes.component.html',
})
-export class DashboardComponent implements OnInit {
+export class DashboardComponent implements OnInit, OnDestroy {
recipes: Recipe | null = null;
recipes01: Recipe01[] | null = null;
- recipeFileCountries: string[] = [];
- recipeFileNames: string[] = [];
- fileName: string = '';
+ currentFile: string = '';
tableHeads: string[] = [
'Product Code',
@@ -38,13 +37,6 @@ export class DashboardComponent implements OnInit {
private offset = 0;
private take = 20;
- recipeFileFilter: BehaviorSubject
=
- new BehaviorSubject({
- country: null,
- fileName: null,
- });
- recipeFileFilter$ = this.recipeFileFilter.asObservable();
-
isLoaded: boolean = false;
isLoadMore: boolean = false;
isHasMore: boolean = true;
@@ -52,7 +44,12 @@ export class DashboardComponent implements OnInit {
private searchStr = '';
private oldSearchStr = '';
+ tableCtx?: ElementRef;
+
@ViewChild('table', { static: false }) set content(table: ElementRef) {
+ // expose element ref for other fn
+ this.tableCtx = table;
+
table.nativeElement.addEventListener(
'scroll',
() => {
@@ -82,7 +79,7 @@ export class DashboardComponent implements OnInit {
...recipesWithoutRecipe01,
Recipe01: [],
};
- this.fileName = fileName;
+ this.currentFile = fileName;
this.offset += 10;
this.isLoadMore = false;
this.isHasMore = hasMore;
@@ -114,23 +111,13 @@ export class DashboardComponent implements OnInit {
...recipesWithoutRecipe01,
Recipe01: [],
};
- this.fileName = fileName;
+ this.currentFile = fileName;
this.offset += 10;
this.isLoaded = true;
this.isHasMore = hasMore;
});
- this.recipeFileFilter$.subscribe((version) => {
- this.recipeFileCountries = lodash.filter(
- this._recipeService.getRecipeFileCountries(),
- (v) => {
- if (version.country) {
- return v.includes(version.country);
- }
- return true;
- }
- );
- });
+ this.initRecipeSelection();
}
setSearch(event: Event) {
@@ -155,14 +142,75 @@ export class DashboardComponent implements OnInit {
...recipesWithoutRecipe01,
Recipe01: [],
};
- this.fileName = fileName;
+ this.currentFile = fileName;
this.offset += 10;
this.isLoaded = true;
this.isHasMore = hasMore;
});
}
- loadRecipe(recipeVersion: string) {
+ // Recipe Version selection
+ currentCountryFilter: BehaviorSubject = new BehaviorSubject(
+ ''
+ );
+ currentFileFilter: BehaviorSubject = new BehaviorSubject('');
+ recipeCountryFiltered: string[] = [];
+ recipeFileCountries: string[] = [];
+
+ currentCountryFilterSubScription: Subscription | null = null;
+ currentFileFilterSubScription: Subscription | null = null;
+
+ selectedCountry: string | null = null;
+ isCountrySelected: boolean = false;
+
+ initRecipeSelection() {
+ if (this._recipeService.getRecipeFileCountries().length == 0) {
+ this._recipeService.getRecipeCountries().subscribe((countries) => {
+ this.recipeCountryFiltered = countries;
+ });
+ }
+ }
+
+ setCountryFilter(event: Event) {
+ this.currentCountryFilter.next((event.target as HTMLInputElement).value);
+ }
+
+ setFileFilter(event: Event) {
+ this.currentFileFilter.next((event.target as HTMLInputElement).value);
+ }
+
+ getRecipeCountries() {
+ this.currentCountryFilterSubScription = this.currentCountryFilter.subscribe(
+ (c) => {
+ const countries = this._recipeService.getRecipeFileCountries();
+ if (countries.length > 0) {
+ this.recipeFileCountries = lodash.filter(countries, (country) =>
+ country.includes(c)
+ );
+ }
+ }
+ );
+ }
+
+ getRecipeFileCountries() {
+ this.currentFileFilterSubScription = this.currentFileFilter.subscribe(
+ (c) => {
+ const countries = this._recipeService.getRecipeFileCountries();
+ if (countries.length > 0) {
+ this.recipeCountryFiltered = lodash.filter(countries, (country) =>
+ country.includes(c)
+ );
+ }
+ }
+ );
+ }
+
+ countrySelected(country: string) {
+ this.selectedCountry = country;
+ this.isCountrySelected = true;
+ }
+
+ loadRecipe(recipeFileName: string) {
// clear all recipes
this.recipes = null;
this.recipes01 = null;
@@ -177,7 +225,7 @@ export class DashboardComponent implements OnInit {
offset: this.offset,
take: this.take,
search: this.oldSearchStr,
- version: recipeVersion,
+ version: recipeFileName,
})
.subscribe(({ recipes, hasMore, fileName }) => {
const { Recipe01, ...recipesWithoutRecipe01 } = recipes;
@@ -190,24 +238,14 @@ export class DashboardComponent implements OnInit {
...recipesWithoutRecipe01,
Recipe01: [],
};
- this.fileName = fileName;
+ this.currentFile = fileName;
this.offset += 10;
this.isLoaded = true;
this.isHasMore = hasMore;
});
}
- setRecipeVersion(event: Event) {
- this.recipeVersion.next((event.target as HTMLInputElement).value);
- }
-
- getRecipeVersions() {
- if (this.recipeVersionData.length > 0) return;
- this._recipeService.getRecipeCountries().subscribe((versions) => {
- this.recipeVersionData = versions;
- this.recipeFileCountries = versions;
- });
- }
+ // end of Recipe Version selection
openJsonTab() {
window.open(
@@ -216,4 +254,17 @@ export class DashboardComponent implements OnInit {
'_blank'
);
}
+
+ scrollToTop() {
+ this.tableCtx!.nativeElement.scroll;
+ }
+
+ ngOnDestroy(): void {
+ if (this.currentCountryFilterSubScription) {
+ this.currentCountryFilterSubScription.unsubscribe();
+ }
+ if (this.currentFileFilterSubScription) {
+ this.currentFileFilterSubScription.unsubscribe();
+ }
+ }
}
diff --git a/server/data/data.go b/server/data/data.go
index ed663dd..8b2cb37 100644
--- a/server/data/data.go
+++ b/server/data/data.go
@@ -8,7 +8,14 @@ import (
"path/filepath"
"recipe-manager/helpers"
"recipe-manager/models"
+ "recipe-manager/services/logger"
"time"
+
+ "go.uber.org/zap"
+)
+
+var (
+ Log = logger.GetInstance()
)
func readFile(version string) *models.Recipe {
@@ -123,6 +130,27 @@ func (d *Data) GetRecipe01() []models.Recipe01 {
return d.currentRecipe.Recipe01
}
+func (d *Data) GetRecipe01ByProductCode(code string) models.Recipe01 {
+ result := make([]models.Recipe01, 0)
+
+ for _, v := range d.currentRecipe.Recipe01 {
+ if v.ProductCode == code {
+ result = append(result, v)
+ }
+ }
+
+ return result[0]
+}
+
+func (d *Data) SetValuesToRecipe(recipe models.Recipe01) {
+ for _, v := range d.currentRecipe.Recipe01 {
+ if v.ProductCode == recipe.ProductCode {
+ v = recipe
+ break
+ }
+ }
+}
+
func (d *Data) GetMaterialSetting(version string) []models.MaterialSetting {
result := make([]models.MaterialSetting, 0)
@@ -235,3 +263,13 @@ func (d *Data) GetCountryIDByName(countryName string) (string, error) {
}
return "", fmt.Errorf("country name: %s not found", countryName)
}
+
+func (d *Data) ExportToJSON() []byte {
+ b_recipe, err := json.Marshal(d.currentRecipe)
+ if err != nil {
+ Log.Error("Error when marshal recipe", zap.Error(err))
+ return nil
+ }
+
+ return b_recipe
+}
diff --git a/server/models/recipe.go b/server/models/recipe.go
index a9c17db..df7a8ea 100644
--- a/server/models/recipe.go
+++ b/server/models/recipe.go
@@ -1,5 +1,7 @@
package models
+import "encoding/json"
+
type Recipe struct {
Timestamp string `json:"Timestamp"`
MachineSetting MachineSetting `json:"MachineSetting"`
@@ -82,6 +84,19 @@ type Recipe01 struct {
Weight_float int `json:"weight_float"`
}
+func (r *Recipe01) ToMap() map[string]interface{} {
+ var m map[string]interface{}
+ recipeRecord, _ := json.Marshal(r)
+ json.Unmarshal(recipeRecord, &m)
+ return m
+}
+
+func (r *Recipe01) FromMap(m map[string]interface{}) Recipe01 {
+ recipeRecord, _ := json.Marshal(m)
+ json.Unmarshal(recipeRecord, &r)
+ return *r
+}
+
type MatRecipe struct {
MixOrder int `json:"MixOrder"`
FeedParameter int `json:"FeedParameter"`
diff --git a/server/routers/recipe.go b/server/routers/recipe.go
index fcea72d..e618cb6 100644
--- a/server/routers/recipe.go
+++ b/server/routers/recipe.go
@@ -3,15 +3,19 @@ package routers
import (
"encoding/json"
"fmt"
+ "io/fs"
"net/http"
+ "os"
"recipe-manager/data"
"recipe-manager/models"
+ "recipe-manager/services/logger"
"recipe-manager/services/sheet"
"sort"
"strconv"
"strings"
"github.com/go-chi/chi/v5"
+ "go.uber.org/zap"
)
type RecipeRouter struct {
@@ -19,6 +23,10 @@ type RecipeRouter struct {
sheetService sheet.SheetService
}
+var (
+ Log = logger.GetInstance()
+)
+
func NewRecipeRouter(data *data.Data, sheetService sheet.SheetService) *RecipeRouter {
return &RecipeRouter{
data: data,
@@ -170,5 +178,53 @@ func (rr *RecipeRouter) Route(r chi.Router) {
}
json.NewEncoder(w).Encode(mapResult)
})
+
+ r.Post("/edit/{version}", func(w http.ResponseWriter, r *http.Request) {
+ Log.Debug("Edit: ", zap.String("path", r.RequestURI))
+ version := chi.URLParam(r, "version")
+ target_recipe := rr.data.GetRecipe(version)
+
+ Log.Debug("Target => ", zap.Any("target", target_recipe.MachineSetting.ConfigNumber))
+
+ // Body
+ var changes models.Recipe01
+ err := json.NewDecoder(r.Body).Decode(&changes)
+ if err != nil {
+ Log.Error("Decode in request failed: ", zap.Error(err))
+ }
+
+ Log.Debug("Changes: ", zap.Any("changes", changes))
+ // TODO: find the matched pd
+ target_menu := rr.data.GetRecipe01ByProductCode(changes.ProductCode)
+
+ menu_map := target_menu.ToMap()
+ change_map := changes.ToMap()
+
+ // Find changes
+ for key, val := range menu_map {
+ if val != change_map[key] {
+ menu_map[key] = change_map[key]
+ }
+ }
+
+ // Apply changes
+ tempRecipe := models.Recipe01{}
+ tempRecipe = tempRecipe.FromMap(menu_map)
+ rr.data.SetValuesToRecipe(tempRecipe)
+
+ finalData := rr.data.ExportToJSON()
+ temp_finalData, err := json.MarshalIndent(finalData, "", " ")
+ if err != nil {
+ Log.Error("Error when indent marshal recipe", zap.Error(err))
+ return
+ }
+
+ os.WriteFile("./cofffeemachineConfig/coffeethai02_"+version+".json", temp_finalData, fs.FileMode(0666))
+
+ w.Header().Add("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "status": "OK",
+ })
+ })
})
}
diff --git a/server/server.go b/server/server.go
index b02f0fa..f61729e 100644
--- a/server/server.go
+++ b/server/server.go
@@ -324,67 +324,8 @@ func (s *Server) createHandler() {
})
- // r.Get("/mergelogList", func(w http.ResponseWriter, r *http.Request) {
- // ch_dir, err := os.ReadDir("cofffeemachineConfig/changelog")
- // if err != nil {
- // Log.Error("Error while trying to read dir: ", zap.String("dir", "cofffeemachineConfig/changelog"), zap.Error(err))
- // http.Error(w, err.Error(), http.StatusInternalServerError)
- // }
- // displayable := make([]string, 0)
-
- // for _, file := range ch_dir {
- // if strings.Contains(file.Name(), ".html") {
-
- // displayable = append(displayable, file.Name()[:len(file.Name())-len(filepath.Ext(file.Name()))])
- // }
- // }
- // w.Header().Set("Content-Type", "application/json")
- // w.WriteHeader(http.StatusOK)
- // json.NewEncoder(w).Encode(map[string][]string{"dirs": displayable})
- // })
-
r.Get("/listFileInDir/*", func(w http.ResponseWriter, r *http.Request) {
- // // socket
- // socket, err := upgrader.Upgrade(w, r, nil)
-
- // if err != nil {
- // Log.Error("Error while trying to upgrade socket: ", zap.Error(err))
- // return
- // }
-
- // // TODO: Change to websocket for better performance
- // for {
- // msg_type, msg, err := socket.ReadMessage()
-
- // if err != nil {
- // Log.Warn("Idle ", zap.Any("status", err))
- // return
- // }
-
- // var msgPayload map[string]interface{}
-
- // err = json.Unmarshal(msg, &msgPayload)
- // if err != nil {
- // Log.Error("Error while trying to unmarshal message: ", zap.Error(err))
- // return
- // }
-
- // // topic := msgPayload["topic"].(string)
-
- // Log.Info("Receive message: ", zap.Any("msg_type", msg_type), zap.Any("msg", msgPayload))
-
- // response, err := json.Marshal(map[string]interface{}{
- // "message": "ok",
- // })
- // if err != nil {
- // Log.Error("Error at marshalling response: ", zap.Error(err))
- // return
- // }
- // // respons to client
- // socket.WriteMessage(msg_type, response)
- // }
-
//spl
spl_path := strings.Split(r.RequestURI, "/")
if len(spl_path) > 3 {
@@ -437,6 +378,51 @@ func (s *Server) createHandler() {
Log.Debug("Scan dir completed < ", zap.String("path", r.RequestURI))
})
+ r.Get("/get_log_relation", func(w http.ResponseWriter, r *http.Request) {
+
+ // Python looker
+ py_exec, err := exec.LookPath("python")
+ if err != nil {
+ Log.Error("Error while trying to find python: ", zap.Error(err))
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+
+ merge_timeline, api_err := os.Open("./python_api/merge_timeline.py")
+ if api_err != nil {
+ Log.Error("Error while trying to open merge_timeline.json: ", zap.Error(api_err))
+ http.Error(w, api_err.Error(), http.StatusInternalServerError)
+ }
+ defer merge_timeline.Close()
+
+ cmd := exec.Command(py_exec, merge_timeline.Name(), "get_relate")
+
+ Log.Debug("Command: ", zap.String("command", cmd.String()))
+
+ out, err := cmd.CombinedOutput()
+
+ Log.Debug("Output: ", zap.String("output", string(out)))
+
+ if err != nil {
+ Log.Error("Error while trying to run python: ", zap.Error(err))
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+
+ // clean up output
+ clean1 := strings.ReplaceAll(string(out), "\n", "")
+ clean2 := strings.ReplaceAll(clean1, "\r", "")
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "message": clean2,
+ })
+ })
+
+ r.Post("/diffpy/*", func(w http.ResponseWriter, r *http.Request) {
+ Log.Debug("Diffpy: ", zap.String("path", r.RequestURI))
+ // TODO: add command exec `python_Exec` `merge_recipe.py` `diff` `master_version` `version-version-version` `debug?` `flatten={true|false}` `out={true|false}`
+ })
+
sheetService, err := sheet.NewSheetService(context.Background(), s.cfg)
if err != nil {