mirror of
https://github.com/richard-loafle/fuse-angular.git
synced 2025-04-18 06:15:12 +00:00
741 lines
22 KiB
TypeScript
741 lines
22 KiB
TypeScript
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
|
|
import { TemplatePortal } from '@angular/cdk/portal';
|
|
import { TextFieldModule } from '@angular/cdk/text-field';
|
|
import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common';
|
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, Renderer2, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
|
import { FormsModule, ReactiveFormsModule, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
import { MatOptionModule, MatRippleModule } from '@angular/material/core';
|
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
import { MatInputModule } from '@angular/material/input';
|
|
import { MatSelectModule } from '@angular/material/select';
|
|
import { MatDrawerToggleResult } from '@angular/material/sidenav';
|
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|
import { FuseFindByKeyPipe } from '@fuse/pipes/find-by-key/find-by-key.pipe';
|
|
import { FuseConfirmationService } from '@fuse/services/confirmation';
|
|
import { ContactsService } from 'app/modules/admin/apps/contacts/contacts.service';
|
|
import { Contact, Country, Tag } from 'app/modules/admin/apps/contacts/contacts.types';
|
|
import { ContactsListComponent } from 'app/modules/admin/apps/contacts/list/list.component';
|
|
import { debounceTime, Subject, takeUntil } from 'rxjs';
|
|
|
|
@Component({
|
|
selector : 'contacts-details',
|
|
templateUrl : './details.component.html',
|
|
encapsulation : ViewEncapsulation.None,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
standalone : true,
|
|
imports : [NgIf, MatButtonModule, MatTooltipModule, RouterLink, MatIconModule, NgFor, FormsModule, ReactiveFormsModule, MatRippleModule, MatFormFieldModule, MatInputModule, MatCheckboxModule, NgClass, MatSelectModule, MatOptionModule, MatDatepickerModule, TextFieldModule, FuseFindByKeyPipe, DatePipe],
|
|
})
|
|
export class ContactsDetailsComponent implements OnInit, OnDestroy
|
|
{
|
|
@ViewChild('avatarFileInput') private _avatarFileInput: ElementRef;
|
|
@ViewChild('tagsPanel') private _tagsPanel: TemplateRef<any>;
|
|
@ViewChild('tagsPanelOrigin') private _tagsPanelOrigin: ElementRef;
|
|
|
|
editMode: boolean = false;
|
|
tags: Tag[];
|
|
tagsEditMode: boolean = false;
|
|
filteredTags: Tag[];
|
|
contact: Contact;
|
|
contactForm: UntypedFormGroup;
|
|
contacts: Contact[];
|
|
countries: Country[];
|
|
private _tagsPanelOverlayRef: OverlayRef;
|
|
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
constructor(
|
|
private _activatedRoute: ActivatedRoute,
|
|
private _changeDetectorRef: ChangeDetectorRef,
|
|
private _contactsListComponent: ContactsListComponent,
|
|
private _contactsService: ContactsService,
|
|
private _formBuilder: UntypedFormBuilder,
|
|
private _fuseConfirmationService: FuseConfirmationService,
|
|
private _renderer2: Renderer2,
|
|
private _router: Router,
|
|
private _overlay: Overlay,
|
|
private _viewContainerRef: ViewContainerRef,
|
|
)
|
|
{
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------------
|
|
// @ Lifecycle hooks
|
|
// -----------------------------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* On init
|
|
*/
|
|
ngOnInit(): void
|
|
{
|
|
// Open the drawer
|
|
this._contactsListComponent.matDrawer.open();
|
|
|
|
// Create the contact form
|
|
this.contactForm = this._formBuilder.group({
|
|
id : [''],
|
|
avatar : [null],
|
|
name : ['', [Validators.required]],
|
|
emails : this._formBuilder.array([]),
|
|
phoneNumbers: this._formBuilder.array([]),
|
|
title : [''],
|
|
company : [''],
|
|
birthday : [null],
|
|
address : [null],
|
|
notes : [null],
|
|
tags : [[]],
|
|
});
|
|
|
|
// Get the contacts
|
|
this._contactsService.contacts$
|
|
.pipe(takeUntil(this._unsubscribeAll))
|
|
.subscribe((contacts: Contact[]) =>
|
|
{
|
|
this.contacts = contacts;
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
});
|
|
|
|
// Get the contact
|
|
this._contactsService.contact$
|
|
.pipe(takeUntil(this._unsubscribeAll))
|
|
.subscribe((contact: Contact) =>
|
|
{
|
|
// Open the drawer in case it is closed
|
|
this._contactsListComponent.matDrawer.open();
|
|
|
|
// Get the contact
|
|
this.contact = contact;
|
|
|
|
// Clear the emails and phoneNumbers form arrays
|
|
(this.contactForm.get('emails') as UntypedFormArray).clear();
|
|
(this.contactForm.get('phoneNumbers') as UntypedFormArray).clear();
|
|
|
|
// Patch values to the form
|
|
this.contactForm.patchValue(contact);
|
|
|
|
// Setup the emails form array
|
|
const emailFormGroups = [];
|
|
|
|
if ( contact.emails.length > 0 )
|
|
{
|
|
// Iterate through them
|
|
contact.emails.forEach((email) =>
|
|
{
|
|
// Create an email form group
|
|
emailFormGroups.push(
|
|
this._formBuilder.group({
|
|
email: [email.email],
|
|
label: [email.label],
|
|
}),
|
|
);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Create an email form group
|
|
emailFormGroups.push(
|
|
this._formBuilder.group({
|
|
email: [''],
|
|
label: [''],
|
|
}),
|
|
);
|
|
}
|
|
|
|
// Add the email form groups to the emails form array
|
|
emailFormGroups.forEach((emailFormGroup) =>
|
|
{
|
|
(this.contactForm.get('emails') as UntypedFormArray).push(emailFormGroup);
|
|
});
|
|
|
|
// Setup the phone numbers form array
|
|
const phoneNumbersFormGroups = [];
|
|
|
|
if ( contact.phoneNumbers.length > 0 )
|
|
{
|
|
// Iterate through them
|
|
contact.phoneNumbers.forEach((phoneNumber) =>
|
|
{
|
|
// Create an email form group
|
|
phoneNumbersFormGroups.push(
|
|
this._formBuilder.group({
|
|
country : [phoneNumber.country],
|
|
phoneNumber: [phoneNumber.phoneNumber],
|
|
label : [phoneNumber.label],
|
|
}),
|
|
);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Create a phone number form group
|
|
phoneNumbersFormGroups.push(
|
|
this._formBuilder.group({
|
|
country : ['us'],
|
|
phoneNumber: [''],
|
|
label : [''],
|
|
}),
|
|
);
|
|
}
|
|
|
|
// Add the phone numbers form groups to the phone numbers form array
|
|
phoneNumbersFormGroups.forEach((phoneNumbersFormGroup) =>
|
|
{
|
|
(this.contactForm.get('phoneNumbers') as UntypedFormArray).push(phoneNumbersFormGroup);
|
|
});
|
|
|
|
// Toggle the edit mode off
|
|
this.toggleEditMode(false);
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
});
|
|
|
|
// Get the country telephone codes
|
|
this._contactsService.countries$
|
|
.pipe(takeUntil(this._unsubscribeAll))
|
|
.subscribe((codes: Country[]) =>
|
|
{
|
|
this.countries = codes;
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
});
|
|
|
|
// Get the tags
|
|
this._contactsService.tags$
|
|
.pipe(takeUntil(this._unsubscribeAll))
|
|
.subscribe((tags: Tag[]) =>
|
|
{
|
|
this.tags = tags;
|
|
this.filteredTags = tags;
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* On destroy
|
|
*/
|
|
ngOnDestroy(): void
|
|
{
|
|
// Unsubscribe from all subscriptions
|
|
this._unsubscribeAll.next(null);
|
|
this._unsubscribeAll.complete();
|
|
|
|
// Dispose the overlays if they are still on the DOM
|
|
if ( this._tagsPanelOverlayRef )
|
|
{
|
|
this._tagsPanelOverlayRef.dispose();
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------------
|
|
// @ Public methods
|
|
// -----------------------------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Close the drawer
|
|
*/
|
|
closeDrawer(): Promise<MatDrawerToggleResult>
|
|
{
|
|
return this._contactsListComponent.matDrawer.close();
|
|
}
|
|
|
|
/**
|
|
* Toggle edit mode
|
|
*
|
|
* @param editMode
|
|
*/
|
|
toggleEditMode(editMode: boolean | null = null): void
|
|
{
|
|
if ( editMode === null )
|
|
{
|
|
this.editMode = !this.editMode;
|
|
}
|
|
else
|
|
{
|
|
this.editMode = editMode;
|
|
}
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Update the contact
|
|
*/
|
|
updateContact(): void
|
|
{
|
|
// Get the contact object
|
|
const contact = this.contactForm.getRawValue();
|
|
|
|
// Go through the contact object and clear empty values
|
|
contact.emails = contact.emails.filter(email => email.email);
|
|
|
|
contact.phoneNumbers = contact.phoneNumbers.filter(phoneNumber => phoneNumber.phoneNumber);
|
|
|
|
// Update the contact on the server
|
|
this._contactsService.updateContact(contact.id, contact).subscribe(() =>
|
|
{
|
|
// Toggle the edit mode off
|
|
this.toggleEditMode(false);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete the contact
|
|
*/
|
|
deleteContact(): void
|
|
{
|
|
// Open the confirmation dialog
|
|
const confirmation = this._fuseConfirmationService.open({
|
|
title : 'Delete contact',
|
|
message: 'Are you sure you want to delete this contact? This action cannot be undone!',
|
|
actions: {
|
|
confirm: {
|
|
label: 'Delete',
|
|
},
|
|
},
|
|
});
|
|
|
|
// Subscribe to the confirmation dialog closed action
|
|
confirmation.afterClosed().subscribe((result) =>
|
|
{
|
|
// If the confirm button pressed...
|
|
if ( result === 'confirmed' )
|
|
{
|
|
// Get the current contact's id
|
|
const id = this.contact.id;
|
|
|
|
// Get the next/previous contact's id
|
|
const currentContactIndex = this.contacts.findIndex(item => item.id === id);
|
|
const nextContactIndex = currentContactIndex + ((currentContactIndex === (this.contacts.length - 1)) ? -1 : 1);
|
|
const nextContactId = (this.contacts.length === 1 && this.contacts[0].id === id) ? null : this.contacts[nextContactIndex].id;
|
|
|
|
// Delete the contact
|
|
this._contactsService.deleteContact(id)
|
|
.subscribe((isDeleted) =>
|
|
{
|
|
// Return if the contact wasn't deleted...
|
|
if ( !isDeleted )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Navigate to the next contact if available
|
|
if ( nextContactId )
|
|
{
|
|
this._router.navigate(['../', nextContactId], {relativeTo: this._activatedRoute});
|
|
}
|
|
// Otherwise, navigate to the parent
|
|
else
|
|
{
|
|
this._router.navigate(['../'], {relativeTo: this._activatedRoute});
|
|
}
|
|
|
|
// Toggle the edit mode off
|
|
this.toggleEditMode(false);
|
|
});
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Upload avatar
|
|
*
|
|
* @param fileList
|
|
*/
|
|
uploadAvatar(fileList: FileList): void
|
|
{
|
|
// Return if canceled
|
|
if ( !fileList.length )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const allowedTypes = ['image/jpeg', 'image/png'];
|
|
const file = fileList[0];
|
|
|
|
// Return if the file is not allowed
|
|
if ( !allowedTypes.includes(file.type) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Upload the avatar
|
|
this._contactsService.uploadAvatar(this.contact.id, file).subscribe();
|
|
}
|
|
|
|
/**
|
|
* Remove the avatar
|
|
*/
|
|
removeAvatar(): void
|
|
{
|
|
// Get the form control for 'avatar'
|
|
const avatarFormControl = this.contactForm.get('avatar');
|
|
|
|
// Set the avatar as null
|
|
avatarFormControl.setValue(null);
|
|
|
|
// Set the file input value as null
|
|
this._avatarFileInput.nativeElement.value = null;
|
|
|
|
// Update the contact
|
|
this.contact.avatar = null;
|
|
}
|
|
|
|
/**
|
|
* Open tags panel
|
|
*/
|
|
openTagsPanel(): void
|
|
{
|
|
// Create the overlay
|
|
this._tagsPanelOverlayRef = this._overlay.create({
|
|
backdropClass : '',
|
|
hasBackdrop : true,
|
|
scrollStrategy : this._overlay.scrollStrategies.block(),
|
|
positionStrategy: this._overlay.position()
|
|
.flexibleConnectedTo(this._tagsPanelOrigin.nativeElement)
|
|
.withFlexibleDimensions(true)
|
|
.withViewportMargin(64)
|
|
.withLockedPosition(true)
|
|
.withPositions([
|
|
{
|
|
originX : 'start',
|
|
originY : 'bottom',
|
|
overlayX: 'start',
|
|
overlayY: 'top',
|
|
},
|
|
]),
|
|
});
|
|
|
|
// Subscribe to the attachments observable
|
|
this._tagsPanelOverlayRef.attachments().subscribe(() =>
|
|
{
|
|
// Add a class to the origin
|
|
this._renderer2.addClass(this._tagsPanelOrigin.nativeElement, 'panel-opened');
|
|
|
|
// Focus to the search input once the overlay has been attached
|
|
this._tagsPanelOverlayRef.overlayElement.querySelector('input').focus();
|
|
});
|
|
|
|
// Create a portal from the template
|
|
const templatePortal = new TemplatePortal(this._tagsPanel, this._viewContainerRef);
|
|
|
|
// Attach the portal to the overlay
|
|
this._tagsPanelOverlayRef.attach(templatePortal);
|
|
|
|
// Subscribe to the backdrop click
|
|
this._tagsPanelOverlayRef.backdropClick().subscribe(() =>
|
|
{
|
|
// Remove the class from the origin
|
|
this._renderer2.removeClass(this._tagsPanelOrigin.nativeElement, 'panel-opened');
|
|
|
|
// If overlay exists and attached...
|
|
if ( this._tagsPanelOverlayRef && this._tagsPanelOverlayRef.hasAttached() )
|
|
{
|
|
// Detach it
|
|
this._tagsPanelOverlayRef.detach();
|
|
|
|
// Reset the tag filter
|
|
this.filteredTags = this.tags;
|
|
|
|
// Toggle the edit mode off
|
|
this.tagsEditMode = false;
|
|
}
|
|
|
|
// If template portal exists and attached...
|
|
if ( templatePortal && templatePortal.isAttached )
|
|
{
|
|
// Detach it
|
|
templatePortal.detach();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Toggle the tags edit mode
|
|
*/
|
|
toggleTagsEditMode(): void
|
|
{
|
|
this.tagsEditMode = !this.tagsEditMode;
|
|
}
|
|
|
|
/**
|
|
* Filter tags
|
|
*
|
|
* @param event
|
|
*/
|
|
filterTags(event): void
|
|
{
|
|
// Get the value
|
|
const value = event.target.value.toLowerCase();
|
|
|
|
// Filter the tags
|
|
this.filteredTags = this.tags.filter(tag => tag.title.toLowerCase().includes(value));
|
|
}
|
|
|
|
/**
|
|
* Filter tags input key down event
|
|
*
|
|
* @param event
|
|
*/
|
|
filterTagsInputKeyDown(event): void
|
|
{
|
|
// Return if the pressed key is not 'Enter'
|
|
if ( event.key !== 'Enter' )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If there is no tag available...
|
|
if ( this.filteredTags.length === 0 )
|
|
{
|
|
// Create the tag
|
|
this.createTag(event.target.value);
|
|
|
|
// Clear the input
|
|
event.target.value = '';
|
|
|
|
// Return
|
|
return;
|
|
}
|
|
|
|
// If there is a tag...
|
|
const tag = this.filteredTags[0];
|
|
const isTagApplied = this.contact.tags.find(id => id === tag.id);
|
|
|
|
// If the found tag is already applied to the contact...
|
|
if ( isTagApplied )
|
|
{
|
|
// Remove the tag from the contact
|
|
this.removeTagFromContact(tag);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise add the tag to the contact
|
|
this.addTagToContact(tag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new tag
|
|
*
|
|
* @param title
|
|
*/
|
|
createTag(title: string): void
|
|
{
|
|
const tag = {
|
|
title,
|
|
};
|
|
|
|
// Create tag on the server
|
|
this._contactsService.createTag(tag)
|
|
.subscribe((response) =>
|
|
{
|
|
// Add the tag to the contact
|
|
this.addTagToContact(response);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update the tag title
|
|
*
|
|
* @param tag
|
|
* @param event
|
|
*/
|
|
updateTagTitle(tag: Tag, event): void
|
|
{
|
|
// Update the title on the tag
|
|
tag.title = event.target.value;
|
|
|
|
// Update the tag on the server
|
|
this._contactsService.updateTag(tag.id, tag)
|
|
.pipe(debounceTime(300))
|
|
.subscribe();
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Delete the tag
|
|
*
|
|
* @param tag
|
|
*/
|
|
deleteTag(tag: Tag): void
|
|
{
|
|
// Delete the tag from the server
|
|
this._contactsService.deleteTag(tag.id).subscribe();
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Add tag to the contact
|
|
*
|
|
* @param tag
|
|
*/
|
|
addTagToContact(tag: Tag): void
|
|
{
|
|
// Add the tag
|
|
this.contact.tags.unshift(tag.id);
|
|
|
|
// Update the contact form
|
|
this.contactForm.get('tags').patchValue(this.contact.tags);
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Remove tag from the contact
|
|
*
|
|
* @param tag
|
|
*/
|
|
removeTagFromContact(tag: Tag): void
|
|
{
|
|
// Remove the tag
|
|
this.contact.tags.splice(this.contact.tags.findIndex(item => item === tag.id), 1);
|
|
|
|
// Update the contact form
|
|
this.contactForm.get('tags').patchValue(this.contact.tags);
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Toggle contact tag
|
|
*
|
|
* @param tag
|
|
*/
|
|
toggleContactTag(tag: Tag): void
|
|
{
|
|
if ( this.contact.tags.includes(tag.id) )
|
|
{
|
|
this.removeTagFromContact(tag);
|
|
}
|
|
else
|
|
{
|
|
this.addTagToContact(tag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Should the create tag button be visible
|
|
*
|
|
* @param inputValue
|
|
*/
|
|
shouldShowCreateTagButton(inputValue: string): boolean
|
|
{
|
|
return !!!(inputValue === '' || this.tags.findIndex(tag => tag.title.toLowerCase() === inputValue.toLowerCase()) > -1);
|
|
}
|
|
|
|
/**
|
|
* Add the email field
|
|
*/
|
|
addEmailField(): void
|
|
{
|
|
// Create an empty email form group
|
|
const emailFormGroup = this._formBuilder.group({
|
|
email: [''],
|
|
label: [''],
|
|
});
|
|
|
|
// Add the email form group to the emails form array
|
|
(this.contactForm.get('emails') as UntypedFormArray).push(emailFormGroup);
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Remove the email field
|
|
*
|
|
* @param index
|
|
*/
|
|
removeEmailField(index: number): void
|
|
{
|
|
// Get form array for emails
|
|
const emailsFormArray = this.contactForm.get('emails') as UntypedFormArray;
|
|
|
|
// Remove the email field
|
|
emailsFormArray.removeAt(index);
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Add an empty phone number field
|
|
*/
|
|
addPhoneNumberField(): void
|
|
{
|
|
// Create an empty phone number form group
|
|
const phoneNumberFormGroup = this._formBuilder.group({
|
|
country : ['us'],
|
|
phoneNumber: [''],
|
|
label : [''],
|
|
});
|
|
|
|
// Add the phone number form group to the phoneNumbers form array
|
|
(this.contactForm.get('phoneNumbers') as UntypedFormArray).push(phoneNumberFormGroup);
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Remove the phone number field
|
|
*
|
|
* @param index
|
|
*/
|
|
removePhoneNumberField(index: number): void
|
|
{
|
|
// Get form array for phone numbers
|
|
const phoneNumbersFormArray = this.contactForm.get('phoneNumbers') as UntypedFormArray;
|
|
|
|
// Remove the phone number field
|
|
phoneNumbersFormArray.removeAt(index);
|
|
|
|
// Mark for check
|
|
this._changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Get country info by iso code
|
|
*
|
|
* @param iso
|
|
*/
|
|
getCountryByIso(iso: string): Country
|
|
{
|
|
return this.countries.find(country => country.iso === iso);
|
|
}
|
|
|
|
/**
|
|
* Track by function for ngFor loops
|
|
*
|
|
* @param index
|
|
* @param item
|
|
*/
|
|
trackByFn(index: number, item: any): any
|
|
{
|
|
return item.id || index;
|
|
}
|
|
}
|