Taobin-Recipe-Manager/client/src/app/features/recipes/recipes.component.ts
pakintada@gmail.com 09c21301d6 feat(merge_component): Add merge from website
Merge contents from patch selected by user into newer version

merge from client feature
2024-03-04 11:19:11 +07:00

659 lines
19 KiB
TypeScript

import {
AfterViewInit,
Component,
ElementRef,
OnDestroy,
OnInit,
ViewChild,
} from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import {
Recipe,
Recipe01,
RecipeOverview,
RecipesDashboard,
} 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,
Observable,
Subscription,
finalize,
map,
tap,
} from 'rxjs';
import * as lodash from 'lodash';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { NgSelectModule } from '@ng-select/ng-select';
import { FormsModule } from '@angular/forms';
import { MaterialService } from 'src/app/core/services/material.service';
import { UserService } from 'src/app/core/services/user.service';
import { UserPermissions } from 'src/app/core/auth/userPermissions';
import { ToppingService } from 'src/app/core/services/topping.service';
import { copy, transformToTSV } from 'src/app/shared/helpers/copy';
import { getCountryMapSwitcher } from 'src/app/shared/helpers/recipe';
import { AsyncStorage } from 'src/app/shared/helpers/asyncStorage';
import { NotFoundHandler } from 'src/app/shared/helpers/notFoundHandler';
@Component({
selector: 'app-recipes',
standalone: true,
imports: [
CommonModule,
RouterLink,
DatePipe,
RecipeModalComponent,
NgSelectModule,
FormsModule,
],
templateUrl: './recipes.component.html',
})
export class RecipesComponent implements OnInit, OnDestroy, AfterViewInit {
recipesDashboard$!: Observable<RecipesDashboard>;
recipeOverviewList!: RecipeOverview[];
selectMaterialFilter: number[] | null = null;
materialList: { id: number; name: string | number }[] | null = null;
materialDetail:
| {
materialId: number;
name: string;
type: string;
}[]
| null = null;
toppings: {
toppingGroup: {} | null;
toppingList: {} | null;
} | null = null;
tableHeads: string[] = [
'Product Code',
'Name',
'Other Name',
'Description',
'Other Description',
'Last Updated',
];
private offset = 0;
private take = 20;
// isLoaded: boolean = false;
isLoadMore: boolean = true;
isHasMore: boolean = true;
private searchStr = '';
private oldSearchStr = '';
savedTmpfiles: any[] = [];
saveTab: boolean = false;
showSaveNoti: boolean = false;
department: string = this.route.parent!.snapshot.params['department'];
copyList: any[] = [];
currentVersion: number | undefined = undefined;
notfoundHandler = new NotFoundHandler();
@ViewChild('table', { static: false }) set content(table: ElementRef) {
table.nativeElement.addEventListener(
'scroll',
async () => {
if (this.isHasMore === false) {
return;
}
const { scrollTop, scrollHeight, clientHeight } = table.nativeElement;
const isBottom = scrollTop + clientHeight >= scrollHeight - 10;
if (isBottom && !this.isLoadMore) {
this.isLoadMore = true;
(
await this._recipeService.getRecipeOverview({
offset: this.offset,
take: this.take,
search: this.oldSearchStr,
filename: this._recipeService.getCurrentFile(),
country: await this._recipeService.getCurrentCountry(
this.department
),
materialIds: this.selectMaterialFilter || [],
})
).subscribe(({ result, hasMore, totalCount }) => {
// console.log("result in scroll", result);
if (this.recipeOverviewList) {
this.recipeOverviewList = this.recipeOverviewList.concat(result);
} else {
this.recipeOverviewList = result;
}
this.offset += 10;
this.isHasMore = hasMore;
this.isLoadMore = false;
});
}
},
{ passive: true }
);
}
constructor(
private _recipeService: RecipeService,
private _materialService: MaterialService,
private _toppingService: ToppingService,
private route: ActivatedRoute,
private _userService: UserService,
private _router: Router
) {}
async ngOnInit(): Promise<void> {
console.log('Trigger onInit where department = ', this.department);
// check if department is legit
this.notfoundHandler.handleInvalidDepartment(
this.department!,
async () => {
await this._router.navigate(['departments']);
window.location.reload();
},
async () => {
console.log('error callback', this.department!);
// await AsyncStorage.setItem(
// 'currentRecipeCountry',
// getCountryMapSwitcher(this.department!)
// );
// // get default file that should be opened
}
);
this.recipesDashboard$ = this._recipeService
.getRecipesDashboard({
filename: this._recipeService.getCurrentFile(),
country: await this._recipeService.getCurrentCountry(this.department!),
})
.pipe(
finalize(async () => {
(
await this._recipeService.getRecipeOverview({
offset: this.offset,
take: this.take,
search: this.oldSearchStr,
filename: this._recipeService.getCurrentFile(),
country: await this._recipeService.getCurrentCountry(
this.department!
),
materialIds: this.selectMaterialFilter || [],
})
).subscribe(({ result, hasMore, totalCount }) => {
this.recipeOverviewList = result;
this.offset += 10;
this.isHasMore = hasMore;
this.isLoadMore = false;
});
})
);
// : Lag assigned
this.recipesDashboard$.subscribe(async (data) => {
this.currentVersion = data.configNumber;
console.log('current version', this.currentVersion);
console.log('data : ', data);
// set default country and filename if was "default"
let currentFile = this._recipeService.getCurrentFile();
if (currentFile == 'default') {
await AsyncStorage.setItem(
'currentRecipeCountry',
await this._recipeService.getCurrentCountry(this.department!)
);
await AsyncStorage.setItem('currentRecipeFile', data.filename);
}
});
console.log('ngAfterViewInit::department', this.department);
console.log('::CurrentFile', this._recipeService.getCurrentFile());
(
await this._materialService.getFullMaterialDetail(
this.department,
this._recipeService.getCurrentFile()
)
).subscribe((mat) => {
this.materialDetail = mat;
console.log(
this.materialDetail?.length,
'[0]=',
mat?.at(0),
'current country',
this._recipeService.getCurrentCountry(),
'currentFile',
this._recipeService.getCurrentFile()
);
// check material detail
console.log('first material', this.materialDetail![0]);
});
// end of FIXME
// this._recipeService
// .getSavedTmp(
// await this._recipeService.getCurrentCountry(this.department),
// this._recipeService.getCurrentFile()
// )
// .subscribe({
// next: (files: any) => {
// console.log('Obtain saves: ', typeof files, files);
// this.showSaveNoti = false;
// if (files != undefined && typeof files === 'object') {
// if (files.files != null) {
// console.log(
// 'Obtain saves object: ',
// files.files[0],
// typeof files
// );
// this.savedTmpfiles = files.files;
// } else {
// this.showSaveNoti = false;
// this.savedTmpfiles = [];
// console.log(this.showSaveNoti, this.savedTmpfiles);
// }
// // let svf = (document.getElementById('select_savefile_modal') as HTMLInputElement)!.checked;
// // console.log("isSavedModalOpened",svf)
// } else {
// this.showSaveNoti = false;
// this.savedTmpfiles = [];
// console.log(this.showSaveNoti, this.savedTmpfiles);
// }
// },
// });
(await this._materialService.getMaterialCodes())
.pipe(
map((mat) =>
mat.map((m) => ({
id: m.materialID,
name: m.PackageDescription || m.materialID,
}))
)
)
.subscribe((materials) => {
this.materialList = materials;
});
(
await this._toppingService.getToppings(
this.department,
this._recipeService.getCurrentFile()
)
).subscribe((tp) => {
this.toppings = {
toppingGroup: tp.ToppingGroup,
toppingList: tp.ToppingList,
};
// console.log(this.toppings);
});
this.initRecipeSelection();
}
ngAfterViewInit(): void {
console.log('After view on init trigger');
}
setSearch(event: Event) {
console.log((event.target as HTMLInputElement).value);
this.searchStr = (event.target as HTMLInputElement).value;
}
async search(event: Event) {
// country
let country = await this._recipeService
.getCurrentCountry(this.department)
.then((country) => country);
this.offset = 0;
this.isLoadMore = true;
this.oldSearchStr = this.searchStr;
(
await this._recipeService.getRecipeOverview({
offset: this.offset,
take: this.take,
search: this.oldSearchStr,
filename: this._recipeService.getCurrentFile(),
country: country,
materialIds: this.selectMaterialFilter || [],
})
).subscribe(({ result, hasMore, totalCount }) => {
this.recipeOverviewList = result;
this.offset += 10;
this.isHasMore = hasMore;
this.isLoadMore = false;
});
}
// Recipe Version selection
currentCountryFilter: BehaviorSubject<string> = new BehaviorSubject<string>(
''
);
currentFileFilter: BehaviorSubject<string> = new BehaviorSubject<string>('');
recipeCountryFiltered: string[] = [];
recipeFileCountries: string[] = [];
currentCountryFilterSubScription: Subscription | null = null;
currentFileFilterSubScription: Subscription | null = null;
selectedCountry: string | null = null;
isCountrySelected: boolean = false;
private countryInputEl?: ElementRef<HTMLInputElement>;
private fileInputEl?: ElementRef<HTMLInputElement>;
@ViewChild('countryInput', { static: false }) set countryInput(
countryInput: ElementRef<HTMLInputElement>
) {
this.countryInputEl = countryInput;
}
@ViewChild('fileInput', { static: false }) set fileInput(
fileInput: ElementRef<HTMLInputElement>
) {
this.fileInputEl = fileInput;
}
private firstTimeOpenModal = true;
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() {
if (this.firstTimeOpenModal) {
this.countryInputEl!.nativeElement.blur();
this.firstTimeOpenModal = false;
}
this.currentCountryFilterSubScription = this.currentCountryFilter.subscribe(
(c) => {
const countries = this._recipeService.getRecipeFileCountries();
// do perms
let accessibleCountries = [];
for (let country of countries) {
if (this.grantAccessCountry(country)) {
accessibleCountries.push(country);
}
}
console.log('granted accessible countries', accessibleCountries);
if (accessibleCountries.length > 0) {
this.recipeCountryFiltered = lodash.filter(
accessibleCountries,
(country) => country.toLowerCase().includes(c.toLowerCase())
);
}
}
);
}
// Grant access to view a country recipe
grantAccessCountry(countryName: string): boolean {
if (
countryName.toLowerCase().startsWith('tha') &&
this._userService
.getCurrentUser()!
.permissions.includes(UserPermissions.THAI_PERMISSION)
) {
return true;
} else if (
countryName.toLowerCase().startsWith('aus') &&
this._userService
.getCurrentUser()!
.permissions.includes(UserPermissions.AUS_PERMISSION)
) {
return true;
} else if (
countryName.toLowerCase().startsWith('malay') &&
this._userService
.getCurrentUser()!
.permissions.includes(UserPermissions.MALAY_PERMISSION)
) {
return true;
} else if (
countryName.toLowerCase().startsWith('alpha-3') &&
this._userService
.getCurrentUser()!
.permissions.includes(UserPermissions.ALPHA3_PERMISSION)
) {
return true;
}
return false;
}
getRecipeFiles() {
this.currentFileFilterSubScription = this.currentFileFilter.subscribe(
(c) => {
if (this.selectedCountry === null) {
return;
}
const fileNames = this._recipeService.getRecipeFileNames(
this.selectedCountry
);
if (fileNames && fileNames.length > 0) {
this.recipeFileCountries = lodash.filter(fileNames, (file) =>
file.toLowerCase().includes(c.toLowerCase())
);
} else {
this._recipeService
.getRecipeFiles(this.selectedCountry)
.subscribe((files) => {
this.recipeFileCountries = lodash.filter(files, (file) =>
file.toLowerCase().includes(c.toLowerCase())
);
});
}
console.log(this.recipeFileCountries);
}
);
}
async countrySelected(country: string) {
this.selectedCountry = country;
this.isCountrySelected = true;
// localStorage.setItem('currentRecipeCountry', country);
await AsyncStorage.setItem('currentRecipeCountry', country);
// force reload, will fix this later
void this._router.navigate([`/${getCountryMapSwitcher(country)}/recipes`]);
}
async loadRecipe(recipeFileName: string) {
// clear all recipes
this.offset = 0;
this.isHasMore = true;
this.isLoadMore = true;
this.oldSearchStr = '';
// localStorage.setItem('currentRecipeFile', recipeFileName);
await AsyncStorage.setItem('currentRecipeFile', recipeFileName);
console.log(
'loadRecipe',
recipeFileName,
'currentCountry',
this.department,
'selectedCountry',
this.selectedCountry
);
// clear all menus
this.recipeOverviewList = [];
// this.recipesDashboard$ = this._recipeService.getRecipesDashboard({
// filename: recipeFileName,
// country: this.selectedCountry!,
// });
// this.recipesDashboard$.subscribe((data) => {
// this.currentVersion = data.configNumber;
// console.log('current version', this.currentVersion);
// });
// this._recipeService
// .getRecipeOverview({
// offset: this.offset,
// take: this.take,
// search: this.oldSearchStr,
// filename: recipeFileName,
// country: this.selectedCountry!,
// materialIds: this.selectMaterialFilter || [],
// })
// .subscribe(({ result, hasMore, totalCount }) => {
// this.recipeOverviewList = result;
// this.offset += 10;
// this.isHasMore = hasMore;
// this.isLoadMore = false;
// });
window.location.reload();
}
// end of Recipe Version selection
openJsonTab() {
window.open(
environment.api +
`/recipes/${this._recipeService.getCurrentCountry(
this.department
)}/${this._recipeService.getCurrentFile()}/json`,
'_blank'
);
}
openLoadSaves() {
this.showSaveNoti = false;
this.saveTab = true;
}
async loadSavedFile(file_commit: any) {
this.showSaveNoti = false;
this.saveTab = false;
console.log('loadSavedFile', file_commit, this.department);
let country = this.department!;
switch (country) {
case 'tha':
country = 'Thailand';
break;
case 'aus':
country = 'Australia';
break;
case 'mys':
country = 'Malaysia';
break;
case 'alpha-3':
country = 'alpha-3';
break;
}
console.log(file_commit.Change_file.split('/')[2]);
(
await this._recipeService.getRecipeOverview({
offset: this.offset,
take: this.take,
search: this.oldSearchStr,
filename: file_commit.Change_file.split('/')[2],
country: country,
materialIds: this.selectMaterialFilter || [],
})
).subscribe(({ result, hasMore, totalCount }) => {
console.log('loadSavedFile', result);
this._recipeService.setCurrentFile(file_commit.Change_file.split('/')[2]);
window.location.reload();
});
}
ngOnDestroy(): void {
if (this.currentCountryFilterSubScription) {
this.currentCountryFilterSubScription.unsubscribe();
}
if (this.currentFileFilterSubScription) {
this.currentFileFilterSubScription.unsubscribe();
}
}
filterMaterial = (term: string, item: any) =>
item.name.toLowerCase().includes(term.toLowerCase()) ||
item.materialId.toString().includes(term);
addToCopyList(data: any) {
if (this.copyList.includes(data)) {
let index = this.copyList.indexOf(data);
this.copyList.splice(index, 1);
} else {
this.copyList = [...this.copyList, data];
}
}
async copyToTsv(data: any) {
await copy(transformToTSV(data))
.then((value) => {
console.log('copyToTsv', value);
})
.catch((err) => {
console.log('copyToTsvErr', err);
});
}
// ------------------------------------ sorting ------------------------------------
async sortByHeader(header: string) {
// productCode
// name
// otherName
// description
// otherDescription
// LastUpdate
// activate sorting
console.log('sortByHeader', header);
//
// send to server [/recipe/sort]
(
await this._recipeService.sortRecipe(
await this._recipeService.getCurrentCountry(),
this._recipeService.getCurrentFile(),
header
)
).subscribe({
next: (data: any) => {
if(data.status == 'OK'){
window.location.reload();
}
}
});
}
}