Files
familytree/UI/src/app/person/person.edit.ts
T
2025-10-27 18:10:47 +11:00

760 lines
20 KiB
TypeScript

import { ChangeDetectorRef, Component, inject, OnDestroy, OnInit, signal, ViewChild } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
import {DynamicDialogConfig} from 'primeng/dynamicdialog';
import { FormControl, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { Subject, Subscription} from 'rxjs';
import { ConfirmationService, MessageService } from 'primeng/api';
import { DatePickerModule } from 'primeng/datepicker';
import { Code, RelationShipView, Person, mState, RelationShip, PersonPhotoDto} from '../models';
import { AppSettingService, LookupService, Utils } from '../shares';
import { PersonService } from './person.service';
import { ButtonModule } from 'primeng/button';
import { SelectModule } from 'primeng/select';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import moment from 'moment';
import { HttpEvent } from '@angular/common/http';
import { TableModule } from 'primeng/table';
import { Pickperson } from '../pickperson/pickperson';
import { ImageDisplayComponent } from '../pickperson/image.display';
import { TooltipModule } from 'primeng/tooltip';
import { AutoFocusModule } from 'primeng/autofocus';
import { DomSanitizer } from '@angular/platform-browser';
import { PhotoList } from '../attachphoto/photolist';
export interface FileUploadEvent {
originalEvent: HttpEvent<any>;
files: File[];
};
export interface FileUploadHandlerEvent {
files: File[];
};
@Component({
templateUrl: 'person.edit.html',
selector: 'person-edit',
imports:[ButtonModule,TableModule,ReactiveFormsModule,TooltipModule,
AutoFocusModule,
SelectModule,CheckboxModule, InputTextModule,DatePickerModule],
styleUrls: ['person.edit.css'],
providers: [DialogService]
})
export class PersonEdit implements OnInit, OnDestroy {
editRef: DynamicDialogRef | undefined;
private sanitizer = inject(DomSanitizer);
imageDataUrl = signal<string>("");
returnUrl ='';
loginUser ='';
hostsite ='';
_error ='';
_id= -1;
_partnerId = -1;
fatherList: Code[] =[];
motherList: Code[] =[];
sexList:Code[] =[];
familyList: Person[] =[];
profileFile: File | null = null;
photoList: PersonPhotoDto[] = [];
fileName = '';
isNew = false;
validationPoints?: Code[];
partners = signal<RelationShipView[]>([]);
deletePartner: RelationShipView[] =[];
msg="[Family Edit Component] ";
private formBuilder = inject(UntypedFormBuilder);
private personService = inject(PersonService);
private messageService = inject(MessageService);
private confirmationService = inject(ConfirmationService);
//for focus input
// @ViewChild('mystaffid') mystaffNo!: MatInput;
isChange = true; // disable use false//true for not disable. make sure it true is disable button.
private subscription:Subscription = new Subscription();
subChanged$ = new Subject<boolean>();
adminuserForm = this.formBuilder.group({
id: [0], //Validators.required
email: [''], //Validators.required
firstname: ['',Validators.required], //Validators.required
lastname: ['',Validators.required], //Validators.required
phone: [''], //Validators.required
address: [''],
sex:['M',Validators.required],
image: [''],
dob: new FormControl<Date|null>(null),
alive: [false], //Validators.required
fatherId:[0],
motherId:[0]
});
constructor( private cdr: ChangeDetectorRef,public dialogService: DialogService,
public ref: DynamicDialogRef, public config: DynamicDialogConfig,
private router: Router, private route: ActivatedRoute,
private appSetting: AppSettingService,
) {
this.hostsite = this.appSetting.appSetting.attachment;
}
loadParentList( list: Person[], selfId: number): void {
this.familyList = list;
let i = 0;
let item: Code;
let family: Person;
//const list = this.familyService.parentList;
for (i = 0; i < list.length; i++)
{
family= list[i];
if (family.id != selfId)
{
item = { id: family.id,
name: family.firstName! + " " + family.lastName!,
status: family.lastName!,
active: family.alive!
};
if (family.sex == 'F')
this.motherList.push(item);
else
this.fatherList.push(item);
}
}
}
getClassForRequire(prev:string,name:string){
const notok = !this.adminuserForm.controls[name].valid &&
this.adminuserForm.controls[name].touched;
let str =prev;
if (notok)
str += " ng-invalid ng-dirty";
return str;
}
dislayImage(): any {
if (this.profileFile)
{
return URL.createObjectURL(this.profileFile);
}
else if (this.imageDataUrl() != "")
{
return this.imageDataUrl();
}
return null;
}
onFileSelected(event: any) {
const file: File = event.target.files[0];
if (file) {
this.profileFile = file;
//this.messageService.add({ severity: 'info', summary: 'Success', detail: 'File Uploaded!' });
this.adminuserForm.patchValue({ image: this.profileFile.name });
this.fileName = file.name;
this.subChanged$.next(false);
}
}
deleteFile(): void {
this.confirmationService.confirm({
message: 'Are you sure that you want to delete image?',
header: 'Confirmation',
closable: true,
closeOnEscape: true,
icon: 'pi pi-exclamation-triangle',
rejectButtonProps: {
label: 'Cancel',
severity: 'secondary',
outlined: true,
},
acceptButtonProps: {
label: 'Delete',
},
accept: () => {
this.doDeleteFile();
},
});
}
doDeleteFile(): void {
const fileName = this.adminuserForm.value.image;
if (fileName)
{
this.doDeleteImage(fileName);
}
else if (this.profileFile)
this.profileFile = null;
}
doDeleteImage(fileName:string) : void {
const familyId = this.adminuserForm.value.id;
const data = { fileName, familyId };
const delete$ = this.personService.deleteUploadFile(data);
this.subscription.add(delete$.subscribe(
{
next: x => {
if (x.statusCode == 1) {
{
this.adminuserForm.patchValue({ image: "" });
this.cdr.detectChanges();
this.messageService.add({ severity: 'success', summary: 'Delete image all ok', detail: 'file: ' + fileName });
}
}
else {
this.messageService.add({ severity: 'error', summary: 'Error delete upload file', detail: 'Fail to delete upload file: ' + x.message });
}
},
error: e => {
this.messageService.add({ severity: 'error', summary: 'Error delete upload file', detail: 'Fail to delete upload file: ' + e });
}
}
));
}
getFileToSave(file: File): FormData {
//const familyId = this.adminuserForm.value.id!;
const formData = new FormData();
formData.append("file", file);
return formData;
}
// formData.append("familyId", familyId.toString());
// console.log("upload file", familyId, file);
saveFile(formData: FormData)
{
const upload$ = this.personService.uploadFile(formData);
this.subscription.add(upload$.subscribe(
{
next: x => {
if (x.statusCode == 1) {
const filename = x.data;
//this.messageService.add({ severity: 'info', summary: 'Success', detail: 'File Uploaded to server API: ' + x.data });
this.adminuserForm.patchValue({ image: filename });
this.cdr.detectChanges();
}
else
this.messageService.add({ severity: 'error', summary: 'Error upload file', detail: 'Fail to upload file: ' + x.message });
},
error: e =>
this.messageService.add({ severity: 'error', summary: 'Error upload file', detail: 'Fail to upload file: ' })
}
));
}
populateSex(): void {
let item: Code;
item = {
id:1,
name:"Female",
status:'F'
};
this.sexList.push(item);
item = {
id:2,
name:"Male",
status:'M'
};
this.sexList.push(item);
}
ngOnInit():void {
this.populateSex();
const id = this.config.data.id;
console.log("family edit", this.config);
this.loadParentList(this.config.data.familyList, id);
//this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
// const id = Number(this.route.snapshot.paramMap.get('id'));
// now load thing up
const user = Utils.getCurrentUser();
// console.log(this.msg + "current login user ", user);
if (user.username === '')
alert("you are not login.");
else
this.loginUser = user.firstName;
this._id = id;
//console.log(this.msg + " " + id );
this.subscription.add(this.adminuserForm.valueChanges.subscribe(x => this.isChange = false));
this.subscription.add(this.subChanged$.subscribe(x => this.isChange = x));
if (id > 0)
{
this.isNew = false;
this.subscription.add(
this.personService.loadPersonById(id).subscribe({
next: x => this.assignValue(x.data),
error: e => console.log(e)
})
);
}
else
{
this.isNew = true;
}
}
// this for disable submit button
get isFieldsChange() {
const chan = this.isChange || !this.adminuserForm.valid; // this disable so need true valid = true not
console.log(this.msg + 'is fields change', chan);
return chan;
}
doViewImage(imageName:string): void {
const ref = this.dialogService.open(ImageDisplayComponent, {
data: {
imageName,
},
header: 'View Image',
draggable: true,
width: '70%',
modal:true,
maximizable: true
});
ref.onClose.subscribe((item: Person) => {
if (item) {
//console.log("after close ward edit", item);
this.messageService.add({severity:'success', summary: 'Select', detail: item.firstName!});
//update the current list
}
});
}
assignValue(item:Person): void {
this._id = item.id;
this.fileName = item.image!;
if (item.relationShips)
this.populatePartner(item.relationShips, item.id);
if (item.personPhotos)
{
this.photoList = item.personPhotos;
}
this.adminuserForm.patchValue({
id: item.id,
email: item.email,
firstname: item.firstName,
lastname: item.lastName,
alive: item.alive,
phone: item.phone,
address: item.address,
fatherId: item.fatherId,
motherId: item.motherId,
sex: item.sex,
image: item.image
});
const dob = item.dob;
if (dob)
{
this.adminuserForm.patchValue({
dob: moment(dob).toDate()
});
}
if (item.image && item.image.length > 0)
{
this.loadImage(item.image);
}
// disable use false//true for not disable.
this.subChanged$.next(true);
}
// convenience getter for easy access in form fields
get f() { return this.adminuserForm.controls;}
loadImage(fileName: string| null): void {
const download = this.personService.downloadFile(fileName!);
this.subscription.add(download.subscribe({
next: x => {
if (x.statusCode == 1)
{
this.displayIamge64(x.data);
}
else
{
console.log("error in download in api ", x.message);
}
},
error: e => console.error("error in download image", e)
}));
}
displayIamge64(baseImage: string): void
{
const changeUrl = (this.sanitizer.bypassSecurityTrustResourceUrl(baseImage) as any).changingThisBreaksApplicationSecurity;
console.log('this is bypassSecurityTrustResourceUrl', changeUrl);
const fullDataUri = `data:image/png;base64,${changeUrl}`;
this.imageDataUrl.set(fullDataUri);
}
validate(adminuser:Person): boolean {
let result = true;
console.log("validate", adminuser);
if (!adminuser.firstName)
{
this._error = 'firstname is blank or empty';
result = false;
}
else if (adminuser.lastName == '')
{
this._error = 'lastname is blank or empty';
result = false;
}
return result;
}
getPartnerForSave(): RelationShip[] {
let rlist: RelationShip[]= [];
let i =0;
let vitem: RelationShipView;
let item: RelationShip;
for (i = 0; i < this.deletePartner.length; i++)
{
vitem = this.deletePartner[i];
item = {
id: vitem.id,
personId: vitem.personId,
state: mState.Delete,
relatePersonId: vitem.relatePersonId
};
rlist.push(item);
console.log("get partner for save delete list ", vitem);
}
console.log("get partner for save before loop this.partners ", i);
for (i = 0; i < this.partners().length; i++)
{
vitem = this.partners()[i];
if (vitem.state == mState.New || vitem.state == mState.Modified)
{
item = {
id: vitem.id,
personId: vitem.personId,
state: vitem.state,
relatePersonId: vitem.relatePersonId
};
rlist.push(item);
}
console.log("get partner for save in partner list ", vitem);
}
return rlist;
}
async onSubmit(e:Event): Promise<void> {
e.preventDefault();
// if form valid then go save
//make sure
const okToSave = this.adminuserForm.valid;
if (okToSave)
{
let adminuserValue = this.adminuserForm.value;
let item:Person = {
id: adminuserValue.id,
email: adminuserValue.email,
firstName: adminuserValue.firstname,
lastName: adminuserValue.lastname,
alive: adminuserValue.alive,
phone: adminuserValue.phone,
address: adminuserValue.address,
image:adminuserValue.image,
fatherId: adminuserValue.fatherId,
motherId: adminuserValue.motherId,
sex: adminuserValue.sex
};
if (adminuserValue.dob)
{
item.dob = moment(adminuserValue.dob).format("YYYY-MM-DD");
}
this._error ='';
const allOK = this.validate(item);
if (allOK)
{
let container:any = {
person: item
};
if (this.profileFile != null)
{
container.formData = await Utils.toBase64(this.profileFile);
container.fileName = this.profileFile.name;
console.log('image as base64 ', container);
}
container.person.relationShips = this.getPartnerForSave();
this.subscription.add (
this.personService.savePerson(container).subscribe({
next: x => {
if (x.statusCode >= 1)
{
item.id = x.data;
// this.messageService.add({severity:'success', summary: 'Save user', detail: item.firstName + " " + item.lastName });
//this.router.navigate([this.returnUrl]);
console.log("the person after save", item);
const list = this.getPhotoListforSave();
if (list.length > 0)
{
this.savePhotoList(list, item);
}
else
this.ref.close(item);
}
else
{
this.messageService.add({severity:'error', summary: 'Error', detail: x.message });
}
},
error: e => {
const message = e.message;
this.messageService.add({severity:'error', summary: 'Error', detail: message });
console.error("error ", e);
}
})
);
}
else
this.messageService.add({severity:'error', summary: 'Error', detail: this._error });
}
}
getFamilyById(id: number): Person | undefined {
let result:Person |undefined;
result = this.familyList.find(x => x.id == id);
return result;
}
populatePartner(list: RelationShip[], personId: number): void {
let item: RelationShip;
let vitem: RelationShipView;
let vlist: RelationShipView[] =[];
let i = 0;
let family:Person|undefined;
for (i = 0; i < list.length; i++)
{
item = list[i];
if (item.relatePersonId != personId)
{
family = this.getFamilyById(item.relatePersonId);
}
else
{
family = this.getFamilyById(item.personId);
}
if (family)
{
vitem = {
id: item.id,
personId: item.personId,
relatePersonId: item.relatePersonId,
pfirstName: family.firstName,
plastName: family.lastName,
sex: family.sex,
state:mState.NoChange
};
vlist.push(vitem);
}
}
if (vlist.length > 0)
this.partners.set(vlist);
}
viewAttachment(): void {
this.showAttachment("Add family photo or other");
}
addPartner(): void {
const title = "Partner";
this.showPickPerson(title, (sfamily: Person) => {
let item: RelationShipView = {
id: this._partnerId--,
state: mState.New,
personId: this._id,
relatePersonId: sfamily.id,
pfirstName: sfamily.firstName,
plastName: sfamily.lastName,
sex: sfamily.sex,
};
const fm = this.partners();
fm.push(item);
this.partners.set(fm);
this.subChanged$.next(false);
console.log("after put in partners list", this.partners());
});
}
onDeletePartner(item:RelationShipView): void {
this.confirmationService.confirm({
message: 'Are you sure that you want to delete partner?',
header: 'Confirmation',
closable: true,
closeOnEscape: true,
icon: 'pi pi-exclamation-triangle',
rejectButtonProps: {
label: 'Cancel',
severity: 'secondary',
outlined: true,
},
acceptButtonProps: {
label: 'Delete',
},
accept: () => {
this.doDeletePartner(item);
},
});
}
doDeletePartner(item:RelationShipView): void {
if (item.id > 0)
{
this.deletePartner.push(item);
}
const dlist = this.partners().filter(x => x.id != item.id);
this.partners.set(dlist);
console.log("after delete partners list", this.partners());
this.subChanged$.next(false);
}
showPickPerson(title:string, callback:(id: Person) => void) :void {
const ref = this.dialogService.open(Pickperson, {
data: {
familyList: this.familyList,
},
header: title,
width: '80%',
draggable: true,
maximizable: true
});
ref.onClose.subscribe((item: Person) => {
if (item) {
//console.log("after close ward edit", item);
this.messageService.add({severity:'success', summary: 'Select', detail: item.firstName!});
//update the current list
callback(item);
}
});
}
showAttachment(title:string): void {
const ref = this.dialogService.open(PhotoList, {
data: {
id: this._id,
personPhotos: this.photoList
},
header: title,
width: '80%',
draggable: true,
maximizable: true
});
ref.onClose.subscribe((ritem: any) => {
const item = ritem.list;
const deleteIds = ritem.deleteIds;
if (item) {
console.log("after close " + title, item);
if (item.length > 0)
{
this.messageService.add({severity:'success', summary: title, detail: "photo attachment " + item.length});
}
//update the current list
// callback(item);
this.updatePhotoList(item);
}
if (deleteIds && deleteIds.length > 0)
{
let j = 0;
for (let i = 0; i < deleteIds.length; i++)
{
const id = deleteIds[i];
j = this.photoList.findIndex(x => x.id == id);
if (j > -1)
{
this.photoList.splice(j,1);
}
}
this.cdr.markForCheck();
}
});
}
updatePhotoList(list: PersonPhotoDto[]): void {
for (let i = 0; i < list.length; i++)
{
const item = list[i];
const idx = this.photoList.findIndex(x => x.id == item.id);
if (idx && idx < 0)
this.photoList.push(item);
else
{
let eitem = this.photoList[idx];
eitem.photo = item.photo;
eitem.photoType = item.photoType;
eitem.file = item.file;
}
}
console.log("the photos after close", this.photoList);
this.subChanged$.next(false);//make save button to enable.
this.cdr.markForCheck();
}
getPhotoListforSave(): PersonPhotoDto[] {
let result: PersonPhotoDto[] =[];
for (let i = 0; i < this.photoList.length; i++)
{
if (this.photoList[i].id < 0)
{
result.push(this.photoList[i]);
}
}
return result;
}
savePhotoList(list: PersonPhotoDto[], item:Person): void {
const formData = new FormData();
for (let i = 0; i < list.length; i++)
{
const file = list[i].file;
if (file)
formData.append("files", file, file.name);
}
formData.append('personId', this._id.toString());
const personPhoto$ = this.personService.savePersonPhotoList(formData);
this.subscription.add(personPhoto$.subscribe(
{
next: x => {
if (x.statusCode == 1) {
//const filename = x.data;
//this.adminuserForm.patchValue({ image: filename });
this.ref.close(item);
//this.cdr.detectChanges();
}
else
this.messageService.add({ severity: 'error', summary: 'Error save attach photos files', detail: 'Fail to photos file: ' + x.message });
},
error: e =>
this.messageService.add({ severity: 'error', summary: 'Error attach photos file', detail: 'Fail to photos file: ' })
}
));
}
cancel(e:Event):void {
e.preventDefault();
this.ref.close(null);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}