First pass through updating standalone components.

This commit is contained in:
Sercan Yemen
2023-05-04 11:37:40 +03:00
parent 715ab6a3aa
commit b2cb20634e
573 changed files with 14143 additions and 11263 deletions

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'academy',
templateUrl : './academy.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class AcademyComponent
{

View File

@@ -1,5 +1,4 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
@@ -8,22 +7,17 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FuseFindByKeyPipeModule } from '@fuse/pipes/find-by-key';
import { SharedModule } from 'app/shared/shared.module';
import { academyRoutes } from 'app/modules/admin/apps/academy/academy.routing';
import { RouterModule } from '@angular/router';
import { AcademyComponent } from 'app/modules/admin/apps/academy/academy.component';
import { academyRoutes } from 'app/modules/admin/apps/academy/academy.routing';
import { AcademyDetailsComponent } from 'app/modules/admin/apps/academy/details/details.component';
import { AcademyListComponent } from 'app/modules/admin/apps/academy/list/list.component';
import { MatTabsModule } from '@angular/material/tabs';
@NgModule({
declarations: [
AcademyComponent,
AcademyDetailsComponent,
AcademyListComponent
],
imports : [
imports: [
RouterModule.forChild(academyRoutes),
MatButtonModule,
MatFormFieldModule,
@@ -34,10 +28,11 @@ import { MatTabsModule } from '@angular/material/tabs';
MatSidenavModule,
MatSlideToggleModule,
MatTooltipModule,
FuseFindByKeyPipeModule,
SharedModule,
MatTabsModule
]
MatTabsModule,
AcademyComponent,
AcademyDetailsComponent,
AcademyListComponent,
],
})
export class AcademyModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, Observable, throwError } from 'rxjs';
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
import { AcademyService } from 'app/modules/admin/apps/academy/academy.service';
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AcademyCategoriesResolver implements Resolve<any>
{
@@ -33,7 +33,7 @@ export class AcademyCategoriesResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AcademyCoursesResolver implements Resolve<any>
{
@@ -61,7 +61,7 @@ export class AcademyCoursesResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AcademyCourseResolver implements Resolve<any>
{
@@ -70,7 +70,7 @@ export class AcademyCourseResolver implements Resolve<any>
*/
constructor(
private _router: Router,
private _academyService: AcademyService
private _academyService: AcademyService,
)
{
}
@@ -88,22 +88,23 @@ export class AcademyCourseResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Course>
{
return this._academyService.getCourseById(route.paramMap.get('id'))
.pipe(
// Error here means the requested task is not available
catchError((error) => {
.pipe(
// Error here means the requested task is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}

View File

@@ -1,15 +1,15 @@
import { Route } from '@angular/router';
import { AcademyComponent } from 'app/modules/admin/apps/academy/academy.component';
import { AcademyListComponent } from 'app/modules/admin/apps/academy/list/list.component';
import { AcademyDetailsComponent } from 'app/modules/admin/apps/academy/details/details.component';
import { AcademyCategoriesResolver, AcademyCourseResolver, AcademyCoursesResolver } from 'app/modules/admin/apps/academy/academy.resolvers';
import { AcademyDetailsComponent } from 'app/modules/admin/apps/academy/details/details.component';
import { AcademyListComponent } from 'app/modules/admin/apps/academy/list/list.component';
export const academyRoutes: Route[] = [
{
path : '',
component: AcademyComponent,
resolve : {
categories: AcademyCategoriesResolver
categories: AcademyCategoriesResolver,
},
children : [
{
@@ -17,16 +17,16 @@ export const academyRoutes: Route[] = [
pathMatch: 'full',
component: AcademyListComponent,
resolve : {
courses: AcademyCoursesResolver
}
courses: AcademyCoursesResolver,
},
},
{
path : ':id',
component: AcademyDetailsComponent,
resolve : {
course: AcademyCourseResolver
}
}
]
}
course: AcademyCourseResolver,
},
},
],
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, map, Observable, of, switchMap, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
import { BehaviorSubject, map, Observable, of, switchMap, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AcademyService
{
@@ -58,9 +58,10 @@ export class AcademyService
getCategories(): Observable<Category[]>
{
return this._httpClient.get<Category[]>('api/apps/academy/categories').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._categories.next(response);
})
}),
);
}
@@ -70,9 +71,10 @@ export class AcademyService
getCourses(): Observable<Course[]>
{
return this._httpClient.get<Course[]>('api/apps/academy/courses').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._courses.next(response);
})
}),
);
}
@@ -82,7 +84,8 @@ export class AcademyService
getCourseById(id: string): Observable<Course>
{
return this._httpClient.get<Course>('api/apps/academy/courses/course', {params: {id}}).pipe(
map((course) => {
map((course) =>
{
// Update the course
this._course.next(course);
@@ -90,7 +93,8 @@ export class AcademyService
// Return the course
return course;
}),
switchMap((course) => {
switchMap((course) =>
{
if ( !course )
{
@@ -98,7 +102,7 @@ export class AcademyService
}
return of(course);
})
}),
);
}
}

View File

@@ -1,16 +1,25 @@
import { CdkScrollable } from '@angular/cdk/scrolling';
import { DOCUMENT, NgClass, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { MatTabGroup } from '@angular/material/tabs';
import { Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTabGroup, MatTabsModule } from '@angular/material/tabs';
import { RouterLink } from '@angular/router';
import { FuseFindByKeyPipe } from '@fuse/pipes/find-by-key/find-by-key.pipe';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
import { AcademyService } from 'app/modules/admin/apps/academy/academy.service';
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'academy-details',
templateUrl : './details.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatSidenavModule, RouterLink, MatIconModule, NgIf, NgClass, NgFor, MatButtonModule, MatProgressBarModule, CdkScrollable, MatTabsModule, FuseFindByKeyPipe],
})
export class AcademyDetailsComponent implements OnInit, OnDestroy
{
@@ -30,7 +39,7 @@ export class AcademyDetailsComponent implements OnInit, OnDestroy
private _academyService: AcademyService,
private _changeDetectorRef: ChangeDetectorRef,
private _elementRef: ElementRef,
private _fuseMediaWatcherService: FuseMediaWatcherService
private _fuseMediaWatcherService: FuseMediaWatcherService,
)
{
}
@@ -47,7 +56,8 @@ export class AcademyDetailsComponent implements OnInit, OnDestroy
// Get the categories
this._academyService.categories$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((categories: Category[]) => {
.subscribe((categories: Category[]) =>
{
// Get the categories
this.categories = categories;
@@ -59,7 +69,8 @@ export class AcademyDetailsComponent implements OnInit, OnDestroy
// Get the course
this._academyService.course$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((course: Course) => {
.subscribe((course: Course) =>
{
// Get the course
this.course = course;
@@ -74,7 +85,8 @@ export class AcademyDetailsComponent implements OnInit, OnDestroy
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
.subscribe(({matchingAliases}) =>
{
// Set the drawerMode and drawerOpened
if ( matchingAliases.includes('lg') )
@@ -187,7 +199,8 @@ export class AcademyDetailsComponent implements OnInit, OnDestroy
private _scrollCurrentStepElementIntoView(): void
{
// Wrap everything into setTimeout so we can make sure that the 'current-step' class points to correct element
setTimeout(() => {
setTimeout(() =>
{
// Get the current step element and scroll it into view
const currentStepElement = this._document.getElementsByClassName('current-step')[0];
@@ -195,7 +208,7 @@ export class AcademyDetailsComponent implements OnInit, OnDestroy
{
currentStepElement.scrollIntoView({
behavior: 'smooth',
block : 'start'
block : 'start',
});
}
});

View File

@@ -1,16 +1,28 @@
import { CdkScrollable } from '@angular/cdk/scrolling';
import { I18nPluralPipe, NgClass, NgFor, NgIf, PercentPipe } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatSelectChange } from '@angular/material/select';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { BehaviorSubject, combineLatest, Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatOptionModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
import { MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/slide-toggle';
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 { AcademyService } from 'app/modules/admin/apps/academy/academy.service';
import { Category, Course } from 'app/modules/admin/apps/academy/academy.types';
import { BehaviorSubject, combineLatest, Subject, takeUntil } from 'rxjs';
@Component({
selector : 'academy-list',
templateUrl : './list.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [CdkScrollable, MatFormFieldModule, MatSelectModule, MatOptionModule, NgFor, MatIconModule, MatInputModule, MatSlideToggleModule, NgIf, NgClass, MatTooltipModule, MatProgressBarModule, MatButtonModule, RouterLink, FuseFindByKeyPipe, PercentPipe, I18nPluralPipe],
})
export class AcademyListComponent implements OnInit, OnDestroy
{
@@ -24,7 +36,7 @@ export class AcademyListComponent implements OnInit, OnDestroy
} = {
categorySlug$ : new BehaviorSubject('all'),
query$ : new BehaviorSubject(''),
hideCompleted$: new BehaviorSubject(false)
hideCompleted$: new BehaviorSubject(false),
};
private _unsubscribeAll: Subject<any> = new Subject<any>();
@@ -36,7 +48,7 @@ export class AcademyListComponent implements OnInit, OnDestroy
private _activatedRoute: ActivatedRoute,
private _changeDetectorRef: ChangeDetectorRef,
private _router: Router,
private _academyService: AcademyService
private _academyService: AcademyService,
)
{
}
@@ -53,7 +65,8 @@ export class AcademyListComponent implements OnInit, OnDestroy
// Get the categories
this._academyService.categories$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((categories: Category[]) => {
.subscribe((categories: Category[]) =>
{
this.categories = categories;
// Mark for check
@@ -63,7 +76,8 @@ export class AcademyListComponent implements OnInit, OnDestroy
// Get the courses
this._academyService.courses$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((courses: Course[]) => {
.subscribe((courses: Course[]) =>
{
this.courses = this.filteredCourses = courses;
// Mark for check
@@ -72,7 +86,8 @@ export class AcademyListComponent implements OnInit, OnDestroy
// Filter the courses
combineLatest([this.filters.categorySlug$, this.filters.query$, this.filters.hideCompleted$])
.subscribe(([categorySlug, query, hideCompleted]) => {
.subscribe(([categorySlug, query, hideCompleted]) =>
{
// Reset the filtered courses
this.filteredCourses = this.courses;

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'chat',
templateUrl : './chat.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class ChatComponent
{

View File

@@ -1,5 +1,4 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatFormFieldModule } from '@angular/material/form-field';
@@ -7,27 +6,19 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { SharedModule } from 'app/shared/shared.module';
import { chatRoutes } from 'app/modules/admin/apps/chat/chat.routing';
import { RouterModule } from '@angular/router';
import { ChatComponent } from 'app/modules/admin/apps/chat/chat.component';
import { chatRoutes } from 'app/modules/admin/apps/chat/chat.routing';
import { ChatsComponent } from 'app/modules/admin/apps/chat/chats/chats.component';
import { ContactInfoComponent } from 'app/modules/admin/apps/chat/contact-info/contact-info.component';
import { EmptyConversationComponent } from 'app/modules/admin/apps/chat/empty-conversation/empty-conversation.component';
import { ConversationComponent } from 'app/modules/admin/apps/chat/conversation/conversation.component';
import { EmptyConversationComponent } from 'app/modules/admin/apps/chat/empty-conversation/empty-conversation.component';
import { NewChatComponent } from 'app/modules/admin/apps/chat/new-chat/new-chat.component';
import { ProfileComponent } from 'app/modules/admin/apps/chat/profile/profile.component';
@NgModule({
declarations: [
ChatComponent,
ChatsComponent,
ContactInfoComponent,
ConversationComponent,
EmptyConversationComponent,
NewChatComponent,
ProfileComponent
],
imports : [
imports: [
RouterModule.forChild(chatRoutes),
MatButtonModule,
MatCheckboxModule,
@@ -36,8 +27,14 @@ import { ProfileComponent } from 'app/modules/admin/apps/chat/profile/profile.co
MatInputModule,
MatMenuModule,
MatSidenavModule,
SharedModule
]
ChatComponent,
ChatsComponent,
ContactInfoComponent,
ConversationComponent,
EmptyConversationComponent,
NewChatComponent,
ProfileComponent,
],
})
export class ChatModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, Observable, throwError } from 'rxjs';
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
import { Chat, Contact, Profile } from 'app/modules/admin/apps/chat/chat.types';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ChatChatsResolver implements Resolve<any>
{
@@ -14,7 +14,7 @@ export class ChatChatsResolver implements Resolve<any>
*/
constructor(
private _chatService: ChatService,
private _router: Router
private _router: Router,
)
{
}
@@ -36,7 +36,7 @@ export class ChatChatsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ChatChatResolver implements Resolve<any>
{
@@ -45,7 +45,7 @@ export class ChatChatResolver implements Resolve<any>
*/
constructor(
private _chatService: ChatService,
private _router: Router
private _router: Router,
)
{
}
@@ -63,28 +63,29 @@ export class ChatChatResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Chat>
{
return this._chatService.getChatById(route.paramMap.get('id'))
.pipe(
// Error here means the requested chat is not available
catchError((error) => {
.pipe(
// Error here means the requested chat is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ChatContactsResolver implements Resolve<any>
{
@@ -93,7 +94,7 @@ export class ChatContactsResolver implements Resolve<any>
*/
constructor(
private _chatService: ChatService,
private _router: Router
private _router: Router,
)
{
}
@@ -115,7 +116,7 @@ export class ChatContactsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ChatProfileResolver implements Resolve<any>
{
@@ -124,7 +125,7 @@ export class ChatProfileResolver implements Resolve<any>
*/
constructor(
private _chatService: ChatService,
private _router: Router
private _router: Router,
)
{
}

View File

@@ -1,6 +1,6 @@
import { Route } from '@angular/router';
import { ChatChatResolver, ChatChatsResolver, ChatContactsResolver, ChatProfileResolver } from 'app/modules/admin/apps/chat/chat.resolvers';
import { ChatComponent } from 'app/modules/admin/apps/chat/chat.component';
import { ChatChatResolver, ChatChatsResolver, ChatContactsResolver, ChatProfileResolver } from 'app/modules/admin/apps/chat/chat.resolvers';
import { ChatsComponent } from 'app/modules/admin/apps/chat/chats/chats.component';
import { ConversationComponent } from 'app/modules/admin/apps/chat/conversation/conversation.component';
import { EmptyConversationComponent } from 'app/modules/admin/apps/chat/empty-conversation/empty-conversation.component';
@@ -12,7 +12,7 @@ export const chatRoutes: Route[] = [
resolve : {
chats : ChatChatsResolver,
contacts: ChatContactsResolver,
profile : ChatProfileResolver
profile : ChatProfileResolver,
},
children : [
{
@@ -22,17 +22,17 @@ export const chatRoutes: Route[] = [
{
path : '',
pathMatch: 'full',
component: EmptyConversationComponent
component: EmptyConversationComponent,
},
{
path : ':id',
component: ConversationComponent,
resolve : {
conversation: ChatChatResolver
}
}
]
}
]
}
conversation: ChatChatResolver,
},
},
],
},
],
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Chat, Contact, Profile } from 'app/modules/admin/apps/chat/chat.types';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ChatService
{
@@ -75,9 +75,10 @@ export class ChatService
getChats(): Observable<any>
{
return this._httpClient.get<Chat[]>('api/apps/chat/chats').pipe(
tap((response: Chat[]) => {
tap((response: Chat[]) =>
{
this._chats.next(response);
})
}),
);
}
@@ -89,9 +90,10 @@ export class ChatService
getContact(id: string): Observable<any>
{
return this._httpClient.get<Contact>('api/apps/chat/contacts', {params: {id}}).pipe(
tap((response: Contact) => {
tap((response: Contact) =>
{
this._contact.next(response);
})
}),
);
}
@@ -101,9 +103,10 @@ export class ChatService
getContacts(): Observable<any>
{
return this._httpClient.get<Contact[]>('api/apps/chat/contacts').pipe(
tap((response: Contact[]) => {
tap((response: Contact[]) =>
{
this._contacts.next(response);
})
}),
);
}
@@ -113,9 +116,10 @@ export class ChatService
getProfile(): Observable<any>
{
return this._httpClient.get<Profile>('api/apps/chat/profile').pipe(
tap((response: Profile) => {
tap((response: Profile) =>
{
this._profile.next(response);
})
}),
);
}
@@ -127,7 +131,8 @@ export class ChatService
getChatById(id: string): Observable<any>
{
return this._httpClient.get<Chat>('api/apps/chat/chat', {params: {id}}).pipe(
map((chat) => {
map((chat) =>
{
// Update the chat
this._chat.next(chat);
@@ -135,7 +140,8 @@ export class ChatService
// Return the chat
return chat;
}),
switchMap((chat) => {
switchMap((chat) =>
{
if ( !chat )
{
@@ -143,7 +149,7 @@ export class ChatService
}
return of(chat);
})
}),
);
}
@@ -159,9 +165,10 @@ export class ChatService
take(1),
switchMap(chats => this._httpClient.patch<Chat>('api/apps/chat/chat', {
id,
chat
chat,
}).pipe(
map((updatedChat) => {
map((updatedChat) =>
{
// Find the index of the updated chat
const index = chats.findIndex(item => item.id === id);
@@ -178,16 +185,17 @@ export class ChatService
switchMap(updatedChat => this.chat$.pipe(
take(1),
filter(item => item && item.id === id),
tap(() => {
tap(() =>
{
// Update the chat if it's selected
this._chat.next(updatedChat);
// Return the updated chat
return updatedChat;
})
))
))
}),
)),
)),
);
}

View File

@@ -1,13 +1,25 @@
import { NgClass, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { Chat, Profile } from 'app/modules/admin/apps/chat/chat.types';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { RouterLink, RouterOutlet } from '@angular/router';
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
import { Chat, Profile } from 'app/modules/admin/apps/chat/chat.types';
import { NewChatComponent } from 'app/modules/admin/apps/chat/new-chat/new-chat.component';
import { ProfileComponent } from 'app/modules/admin/apps/chat/profile/profile.component';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'chat-chats',
templateUrl : './chats.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatSidenavModule, NgIf, NewChatComponent, ProfileComponent, MatButtonModule, MatIconModule, MatMenuModule, MatFormFieldModule, MatInputModule, NgFor, NgClass, RouterLink, RouterOutlet],
})
export class ChatsComponent implements OnInit, OnDestroy
{
@@ -24,7 +36,7 @@ export class ChatsComponent implements OnInit, OnDestroy
*/
constructor(
private _chatService: ChatService,
private _changeDetectorRef: ChangeDetectorRef
private _changeDetectorRef: ChangeDetectorRef,
)
{
}
@@ -41,7 +53,8 @@ export class ChatsComponent implements OnInit, OnDestroy
// Chats
this._chatService.chats$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((chats: Chat[]) => {
.subscribe((chats: Chat[]) =>
{
this.chats = this.filteredChats = chats;
// Mark for check
@@ -51,7 +64,8 @@ export class ChatsComponent implements OnInit, OnDestroy
// Profile
this._chatService.profile$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((profile: Profile) => {
.subscribe((profile: Profile) =>
{
this.profile = profile;
// Mark for check
@@ -61,7 +75,8 @@ export class ChatsComponent implements OnInit, OnDestroy
// Selected chat
this._chatService.chat$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((chat: Chat) => {
.subscribe((chat: Chat) =>
{
this.selectedChat = chat;
// Mark for check

View File

@@ -1,4 +1,7 @@
import { NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatDrawer } from '@angular/material/sidenav';
import { Chat } from 'app/modules/admin/apps/chat/chat.types';
@@ -6,7 +9,9 @@ import { Chat } from 'app/modules/admin/apps/chat/chat.types';
selector : 'chat-contact-info',
templateUrl : './contact-info.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, MatIconModule, NgIf, NgFor],
})
export class ContactInfoComponent
{

View File

@@ -1,14 +1,26 @@
import { TextFieldModule } from '@angular/cdk/text-field';
import { DatePipe, NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, NgZone, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatSidenavModule } from '@angular/material/sidenav';
import { RouterLink } from '@angular/router';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { Chat } from 'app/modules/admin/apps/chat/chat.types';
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
import { Chat } from 'app/modules/admin/apps/chat/chat.types';
import { ContactInfoComponent } from 'app/modules/admin/apps/chat/contact-info/contact-info.component';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'chat-conversation',
templateUrl : './conversation.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [NgIf, MatSidenavModule, ContactInfoComponent, MatButtonModule, RouterLink, MatIconModule, MatMenuModule, NgFor, NgClass, NgTemplateOutlet, MatFormFieldModule, MatInputModule, TextFieldModule, DatePipe],
})
export class ConversationComponent implements OnInit, OnDestroy
{
@@ -25,7 +37,7 @@ export class ConversationComponent implements OnInit, OnDestroy
private _changeDetectorRef: ChangeDetectorRef,
private _chatService: ChatService,
private _fuseMediaWatcherService: FuseMediaWatcherService,
private _ngZone: NgZone
private _ngZone: NgZone,
)
{
}
@@ -44,9 +56,11 @@ export class ConversationComponent implements OnInit, OnDestroy
private _resizeMessageInput(): void
{
// This doesn't need to trigger Angular's change detection by itself
this._ngZone.runOutsideAngular(() => {
this._ngZone.runOutsideAngular(() =>
{
setTimeout(() => {
setTimeout(() =>
{
// Set the height to 'auto' so we can correctly read the scrollHeight
this.messageInput.nativeElement.style.height = 'auto';
@@ -75,7 +89,8 @@ export class ConversationComponent implements OnInit, OnDestroy
// Chat
this._chatService.chat$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((chat: Chat) => {
.subscribe((chat: Chat) =>
{
this.chat = chat;
// Mark for check
@@ -85,7 +100,8 @@ export class ConversationComponent implements OnInit, OnDestroy
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
.subscribe(({matchingAliases}) =>
{
// Set the drawerMode if the given breakpoint is active
if ( matchingAliases.includes('lg') )

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector : 'chat-empty-conversation',
templateUrl : './empty-conversation.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatIconModule],
})
export class EmptyConversationComponent
{

View File

@@ -1,14 +1,19 @@
import { NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatDrawer } from '@angular/material/sidenav';
import { Subject, takeUntil } from 'rxjs';
import { Contact } from 'app/modules/admin/apps/chat/chat.types';
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
import { Contact } from 'app/modules/admin/apps/chat/chat.types';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'chat-new-chat',
templateUrl : './new-chat.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, MatIconModule, NgIf, NgFor],
})
export class NewChatComponent implements OnInit, OnDestroy
{
@@ -35,7 +40,8 @@ export class NewChatComponent implements OnInit, OnDestroy
// Contacts
this._chatService.contacts$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((contacts: Contact[]) => {
.subscribe((contacts: Contact[]) =>
{
this.contacts = contacts;
});
}

View File

@@ -1,14 +1,22 @@
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatDrawer } from '@angular/material/sidenav';
import { Subject, takeUntil } from 'rxjs';
import { Profile } from 'app/modules/admin/apps/chat/chat.types';
import { ChatService } from 'app/modules/admin/apps/chat/chat.service';
import { Profile } from 'app/modules/admin/apps/chat/chat.types';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'chat-profile',
templateUrl : './profile.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, MatIconModule, NgIf, MatFormFieldModule, MatInputModule, FormsModule],
})
export class ProfileComponent implements OnInit, OnDestroy
{
@@ -35,7 +43,8 @@ export class ProfileComponent implements OnInit, OnDestroy
// Profile
this._chatService.profile$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((profile: Profile) => {
.subscribe((profile: Profile) =>
{
this.profile = profile;
});
}

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'contacts',
templateUrl : './contacts.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class ContactsComponent
{

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { ContactsDetailsComponent } from 'app/modules/admin/apps/contacts/details/details.component';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class CanDeactivateContactsDetails implements CanDeactivate<ContactsDetailsComponent>
{
@@ -12,7 +12,7 @@ export class CanDeactivateContactsDetails implements CanDeactivate<ContactsDetai
component: ContactsDetailsComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
nextState: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
{
// Get the next route

View File

@@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatLuxonDateModule } from '@angular/material-luxon-adapter';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MAT_DATE_FORMATS, MatRippleModule } from '@angular/material/core';
@@ -8,7 +8,6 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatLuxonDateModule } from '@angular/material-luxon-adapter';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRadioModule } from '@angular/material/radio';
@@ -16,20 +15,15 @@ import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FuseFindByKeyPipeModule } from '@fuse/pipes/find-by-key';
import { SharedModule } from 'app/shared/shared.module';
import { contactsRoutes } from 'app/modules/admin/apps/contacts/contacts.routing';
import { RouterModule } from '@angular/router';
import { ContactsComponent } from 'app/modules/admin/apps/contacts/contacts.component';
import { contactsRoutes } from 'app/modules/admin/apps/contacts/contacts.routing';
import { ContactsDetailsComponent } from 'app/modules/admin/apps/contacts/details/details.component';
import { ContactsListComponent } from 'app/modules/admin/apps/contacts/list/list.component';
@NgModule({
declarations: [
ContactsComponent,
ContactsListComponent,
ContactsDetailsComponent
],
imports : [
imports : [
RouterModule.forChild(contactsRoutes),
MatButtonModule,
MatCheckboxModule,
@@ -47,25 +41,26 @@ import { ContactsListComponent } from 'app/modules/admin/apps/contacts/list/list
MatSidenavModule,
MatTableModule,
MatTooltipModule,
FuseFindByKeyPipeModule,
SharedModule
ContactsComponent,
ContactsListComponent,
ContactsDetailsComponent,
],
providers : [
providers: [
{
provide : MAT_DATE_FORMATS,
useValue: {
parse : {
dateInput: 'D'
dateInput: 'D',
},
display: {
dateInput : 'DDD',
monthYearLabel : 'LLL yyyy',
dateA11yLabel : 'DD',
monthYearA11yLabel: 'LLLL yyyy'
}
}
}
]
monthYearA11yLabel: 'LLLL yyyy',
},
},
},
],
})
export class ContactsModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, Observable, throwError } from 'rxjs';
import { ContactsService } from 'app/modules/admin/apps/contacts/contacts.service';
import { Contact, Country, Tag } from 'app/modules/admin/apps/contacts/contacts.types';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ContactsResolver implements Resolve<any>
{
@@ -33,7 +33,7 @@ export class ContactsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ContactsContactResolver implements Resolve<any>
{
@@ -42,7 +42,7 @@ export class ContactsContactResolver implements Resolve<any>
*/
constructor(
private _contactsService: ContactsService,
private _router: Router
private _router: Router,
)
{
}
@@ -60,28 +60,29 @@ export class ContactsContactResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Contact>
{
return this._contactsService.getContactById(route.paramMap.get('id'))
.pipe(
// Error here means the requested contact is not available
catchError((error) => {
.pipe(
// Error here means the requested contact is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ContactsCountriesResolver implements Resolve<any>
{
@@ -109,7 +110,7 @@ export class ContactsCountriesResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ContactsTagsResolver implements Resolve<any>
{

View File

@@ -1,16 +1,16 @@
import { Route } from '@angular/router';
import { ContactsComponent } from 'app/modules/admin/apps/contacts/contacts.component';
import { CanDeactivateContactsDetails } from 'app/modules/admin/apps/contacts/contacts.guards';
import { ContactsContactResolver, ContactsCountriesResolver, ContactsResolver, ContactsTagsResolver } from 'app/modules/admin/apps/contacts/contacts.resolvers';
import { ContactsComponent } from 'app/modules/admin/apps/contacts/contacts.component';
import { ContactsListComponent } from 'app/modules/admin/apps/contacts/list/list.component';
import { ContactsDetailsComponent } from 'app/modules/admin/apps/contacts/details/details.component';
import { ContactsListComponent } from 'app/modules/admin/apps/contacts/list/list.component';
export const contactsRoutes: Route[] = [
{
path : '',
component: ContactsComponent,
resolve : {
tags: ContactsTagsResolver
tags: ContactsTagsResolver,
},
children : [
{
@@ -18,7 +18,7 @@ export const contactsRoutes: Route[] = [
component: ContactsListComponent,
resolve : {
contacts : ContactsResolver,
countries: ContactsCountriesResolver
countries: ContactsCountriesResolver,
},
children : [
{
@@ -26,12 +26,12 @@ export const contactsRoutes: Route[] = [
component : ContactsDetailsComponent,
resolve : {
contact : ContactsContactResolver,
countries: ContactsCountriesResolver
countries: ContactsCountriesResolver,
},
canDeactivate: [CanDeactivateContactsDetails]
}
]
}
]
}
canDeactivate: [CanDeactivateContactsDetails],
},
],
},
],
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Contact, Country, Tag } from 'app/modules/admin/apps/contacts/contacts.types';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ContactsService
{
@@ -67,9 +67,10 @@ export class ContactsService
getContacts(): Observable<Contact[]>
{
return this._httpClient.get<Contact[]>('api/apps/contacts/all').pipe(
tap((contacts) => {
tap((contacts) =>
{
this._contacts.next(contacts);
})
}),
);
}
@@ -81,11 +82,12 @@ export class ContactsService
searchContacts(query: string): Observable<Contact[]>
{
return this._httpClient.get<Contact[]>('api/apps/contacts/search', {
params: {query}
params: {query},
}).pipe(
tap((contacts) => {
tap((contacts) =>
{
this._contacts.next(contacts);
})
}),
);
}
@@ -96,7 +98,8 @@ export class ContactsService
{
return this._contacts.pipe(
take(1),
map((contacts) => {
map((contacts) =>
{
// Find the contact
const contact = contacts.find(item => item.id === id) || null;
@@ -107,7 +110,8 @@ export class ContactsService
// Return the contact
return contact;
}),
switchMap((contact) => {
switchMap((contact) =>
{
if ( !contact )
{
@@ -115,7 +119,7 @@ export class ContactsService
}
return of(contact);
})
}),
);
}
@@ -127,15 +131,16 @@ export class ContactsService
return this.contacts$.pipe(
take(1),
switchMap(contacts => this._httpClient.post<Contact>('api/apps/contacts/contact', {}).pipe(
map((newContact) => {
map((newContact) =>
{
// Update the contacts with the new contact
this._contacts.next([newContact, ...contacts]);
// Return the new contact
return newContact;
})
))
}),
)),
);
}
@@ -151,9 +156,10 @@ export class ContactsService
take(1),
switchMap(contacts => this._httpClient.patch<Contact>('api/apps/contacts/contact', {
id,
contact
contact,
}).pipe(
map((updatedContact) => {
map((updatedContact) =>
{
// Find the index of the updated contact
const index = contacts.findIndex(item => item.id === id);
@@ -170,16 +176,17 @@ export class ContactsService
switchMap(updatedContact => this.contact$.pipe(
take(1),
filter(item => item && item.id === id),
tap(() => {
tap(() =>
{
// Update the contact if it's selected
this._contact.next(updatedContact);
// Return the updated contact
return updatedContact;
})
))
))
}),
)),
)),
);
}
@@ -193,7 +200,8 @@ export class ContactsService
return this.contacts$.pipe(
take(1),
switchMap(contacts => this._httpClient.delete('api/apps/contacts/contact', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted contact
const index = contacts.findIndex(item => item.id === id);
@@ -206,8 +214,8 @@ export class ContactsService
// Return the deleted status
return isDeleted;
})
))
}),
)),
);
}
@@ -217,9 +225,10 @@ export class ContactsService
getCountries(): Observable<Country[]>
{
return this._httpClient.get<Country[]>('api/apps/contacts/countries').pipe(
tap((countries) => {
tap((countries) =>
{
this._countries.next(countries);
})
}),
);
}
@@ -229,9 +238,10 @@ export class ContactsService
getTags(): Observable<Tag[]>
{
return this._httpClient.get<Tag[]>('api/apps/contacts/tags').pipe(
tap((tags) => {
tap((tags) =>
{
this._tags.next(tags);
})
}),
);
}
@@ -245,15 +255,16 @@ export class ContactsService
return this.tags$.pipe(
take(1),
switchMap(tags => this._httpClient.post<Tag>('api/apps/contacts/tag', {tag}).pipe(
map((newTag) => {
map((newTag) =>
{
// Update the tags with the new tag
this._tags.next([...tags, newTag]);
// Return new tag from observable
return newTag;
})
))
}),
)),
);
}
@@ -269,9 +280,10 @@ export class ContactsService
take(1),
switchMap(tags => this._httpClient.patch<Tag>('api/apps/contacts/tag', {
id,
tag
tag,
}).pipe(
map((updatedTag) => {
map((updatedTag) =>
{
// Find the index of the updated tag
const index = tags.findIndex(item => item.id === id);
@@ -284,8 +296,8 @@ export class ContactsService
// Return the updated tag
return updatedTag;
})
))
}),
)),
);
}
@@ -299,7 +311,8 @@ export class ContactsService
return this.tags$.pipe(
take(1),
switchMap(tags => this._httpClient.delete('api/apps/contacts/tag', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted tag
const index = tags.findIndex(item => item.id === id);
@@ -316,10 +329,12 @@ export class ContactsService
filter(isDeleted => isDeleted),
switchMap(isDeleted => this.contacts$.pipe(
take(1),
map((contacts) => {
map((contacts) =>
{
// Iterate through the contacts
contacts.forEach((contact) => {
contacts.forEach((contact) =>
{
const tagIndex = contact.tags.findIndex(tag => tag === id);
@@ -332,9 +347,9 @@ export class ContactsService
// Return the deleted status
return isDeleted;
})
))
))
}),
)),
)),
);
}
@@ -350,14 +365,15 @@ export class ContactsService
take(1),
switchMap(contacts => this._httpClient.post<Contact>('api/apps/contacts/avatar', {
id,
avatar
avatar,
}, {
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'Content-Type': avatar.type
}
'Content-Type': avatar.type,
},
}).pipe(
map((updatedContact) => {
map((updatedContact) =>
{
// Find the index of the updated contact
const index = contacts.findIndex(item => item.id === id);
@@ -374,16 +390,17 @@ export class ContactsService
switchMap(updatedContact => this.contact$.pipe(
take(1),
filter(item => item && item.id === id),
tap(() => {
tap(() =>
{
// Update the contact if it's selected
this._contact.next(updatedContact);
// Return the updated contact
return updatedContact;
})
))
))
}),
)),
)),
);
}
}

View File

@@ -1,20 +1,34 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, Renderer2, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { TemplatePortal } from '@angular/cdk/portal';
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 { debounceTime, Subject, takeUntil } from 'rxjs';
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 { ContactsService } from 'app/modules/admin/apps/contacts/contacts.service';
import { debounceTime, Subject, takeUntil } from 'rxjs';
@Component({
selector : 'contacts-details',
templateUrl : './details.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
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
{
@@ -46,7 +60,7 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
private _renderer2: Renderer2,
private _router: Router,
private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef
private _viewContainerRef: ViewContainerRef,
)
{
}
@@ -75,13 +89,14 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
birthday : [null],
address : [null],
notes : [null],
tags : [[]]
tags : [[]],
});
// Get the contacts
this._contactsService.contacts$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((contacts: Contact[]) => {
.subscribe((contacts: Contact[]) =>
{
this.contacts = contacts;
// Mark for check
@@ -91,7 +106,8 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
// Get the contact
this._contactsService.contact$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((contact: Contact) => {
.subscribe((contact: Contact) =>
{
// Open the drawer in case it is closed
this._contactsListComponent.matDrawer.open();
@@ -112,14 +128,15 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
if ( contact.emails.length > 0 )
{
// Iterate through them
contact.emails.forEach((email) => {
contact.emails.forEach((email) =>
{
// Create an email form group
emailFormGroups.push(
this._formBuilder.group({
email: [email.email],
label: [email.label]
})
label: [email.label],
}),
);
});
}
@@ -129,13 +146,14 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
emailFormGroups.push(
this._formBuilder.group({
email: [''],
label: ['']
})
label: [''],
}),
);
}
// Add the email form groups to the emails form array
emailFormGroups.forEach((emailFormGroup) => {
emailFormGroups.forEach((emailFormGroup) =>
{
(this.contactForm.get('emails') as UntypedFormArray).push(emailFormGroup);
});
@@ -145,15 +163,16 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
if ( contact.phoneNumbers.length > 0 )
{
// Iterate through them
contact.phoneNumbers.forEach((phoneNumber) => {
contact.phoneNumbers.forEach((phoneNumber) =>
{
// Create an email form group
phoneNumbersFormGroups.push(
this._formBuilder.group({
country : [phoneNumber.country],
phoneNumber: [phoneNumber.phoneNumber],
label : [phoneNumber.label]
})
label : [phoneNumber.label],
}),
);
});
}
@@ -164,13 +183,14 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
this._formBuilder.group({
country : ['us'],
phoneNumber: [''],
label : ['']
})
label : [''],
}),
);
}
// Add the phone numbers form groups to the phone numbers form array
phoneNumbersFormGroups.forEach((phoneNumbersFormGroup) => {
phoneNumbersFormGroups.forEach((phoneNumbersFormGroup) =>
{
(this.contactForm.get('phoneNumbers') as UntypedFormArray).push(phoneNumbersFormGroup);
});
@@ -184,7 +204,8 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
// Get the country telephone codes
this._contactsService.countries$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((codes: Country[]) => {
.subscribe((codes: Country[]) =>
{
this.countries = codes;
// Mark for check
@@ -194,7 +215,8 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
// Get the tags
this._contactsService.tags$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((tags: Tag[]) => {
.subscribe((tags: Tag[]) =>
{
this.tags = tags;
this.filteredTags = tags;
@@ -265,7 +287,8 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
contact.phoneNumbers = contact.phoneNumbers.filter(phoneNumber => phoneNumber.phoneNumber);
// Update the contact on the server
this._contactsService.updateContact(contact.id, contact).subscribe(() => {
this._contactsService.updateContact(contact.id, contact).subscribe(() =>
{
// Toggle the edit mode off
this.toggleEditMode(false);
@@ -283,13 +306,14 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
message: 'Are you sure you want to delete this contact? This action cannot be undone!',
actions: {
confirm: {
label: 'Delete'
}
}
label: 'Delete',
},
},
});
// Subscribe to the confirmation dialog closed action
confirmation.afterClosed().subscribe((result) => {
confirmation.afterClosed().subscribe((result) =>
{
// If the confirm button pressed...
if ( result === 'confirmed' )
@@ -304,7 +328,8 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
// Delete the contact
this._contactsService.deleteContact(id)
.subscribe((isDeleted) => {
.subscribe((isDeleted) =>
{
// Return if the contact wasn't deleted...
if ( !isDeleted )
@@ -389,22 +414,23 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
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'
}
])
.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(() => {
this._tagsPanelOverlayRef.attachments().subscribe(() =>
{
// Add a class to the origin
this._renderer2.addClass(this._tagsPanelOrigin.nativeElement, 'panel-opened');
@@ -420,7 +446,8 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
this._tagsPanelOverlayRef.attach(templatePortal);
// Subscribe to the backdrop click
this._tagsPanelOverlayRef.backdropClick().subscribe(() => {
this._tagsPanelOverlayRef.backdropClick().subscribe(() =>
{
// Remove the class from the origin
this._renderer2.removeClass(this._tagsPanelOrigin.nativeElement, 'panel-opened');
@@ -520,12 +547,13 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
createTag(title: string): void
{
const tag = {
title
title,
};
// Create tag on the server
this._contactsService.createTag(tag)
.subscribe((response) => {
.subscribe((response) =>
{
// Add the tag to the contact
this.addTagToContact(response);
@@ -635,7 +663,7 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
// Create an empty email form group
const emailFormGroup = this._formBuilder.group({
email: [''],
label: ['']
label: [''],
});
// Add the email form group to the emails form array
@@ -671,7 +699,7 @@ export class ContactsDetailsComponent implements OnInit, OnDestroy
const phoneNumberFormGroup = this._formBuilder.group({
country : ['us'],
phoneNumber: [''],
label : ['']
label : [''],
});
// Add the phone number form group to the phoneNumbers form array

View File

@@ -1,18 +1,24 @@
import { AsyncPipe, DOCUMENT, I18nPluralPipe, NgClass, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { UntypedFormControl } from '@angular/forms';
import { MatDrawer } from '@angular/material/sidenav';
import { filter, fromEvent, Observable, Subject, switchMap, takeUntil } from 'rxjs';
import { FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatDrawer, MatSidenavModule } from '@angular/material/sidenav';
import { ActivatedRoute, Router, RouterLink, RouterOutlet } from '@angular/router';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { Contact, Country } from 'app/modules/admin/apps/contacts/contacts.types';
import { ContactsService } from 'app/modules/admin/apps/contacts/contacts.service';
import { Contact, Country } from 'app/modules/admin/apps/contacts/contacts.types';
import { filter, fromEvent, Observable, Subject, switchMap, takeUntil } from 'rxjs';
@Component({
selector : 'contacts-list',
templateUrl : './list.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatSidenavModule, RouterOutlet, NgIf, MatFormFieldModule, MatIconModule, MatInputModule, FormsModule, ReactiveFormsModule, MatButtonModule, NgFor, NgClass, RouterLink, AsyncPipe, I18nPluralPipe],
})
export class ContactsListComponent implements OnInit, OnDestroy
{
@@ -37,7 +43,7 @@ export class ContactsListComponent implements OnInit, OnDestroy
private _contactsService: ContactsService,
@Inject(DOCUMENT) private _document: any,
private _router: Router,
private _fuseMediaWatcherService: FuseMediaWatcherService
private _fuseMediaWatcherService: FuseMediaWatcherService,
)
{
}
@@ -55,7 +61,8 @@ export class ContactsListComponent implements OnInit, OnDestroy
this.contacts$ = this._contactsService.contacts$;
this._contactsService.contacts$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((contacts: Contact[]) => {
.subscribe((contacts: Contact[]) =>
{
// Update the counts
this.contactsCount = contacts.length;
@@ -67,7 +74,8 @@ export class ContactsListComponent implements OnInit, OnDestroy
// Get the contact
this._contactsService.contact$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((contact: Contact) => {
.subscribe((contact: Contact) =>
{
// Update the selected contact
this.selectedContact = contact;
@@ -79,7 +87,8 @@ export class ContactsListComponent implements OnInit, OnDestroy
// Get the countries
this._contactsService.countries$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((countries: Country[]) => {
.subscribe((countries: Country[]) =>
{
// Update the countries
this.countries = countries;
@@ -95,13 +104,14 @@ export class ContactsListComponent implements OnInit, OnDestroy
switchMap(query =>
// Search
this._contactsService.searchContacts(query)
)
this._contactsService.searchContacts(query),
),
)
.subscribe();
// Subscribe to MatDrawer opened change
this.matDrawer.openedChange.subscribe((opened) => {
this.matDrawer.openedChange.subscribe((opened) =>
{
if ( !opened )
{
// Remove the selected contact when drawer closed
@@ -115,7 +125,8 @@ export class ContactsListComponent implements OnInit, OnDestroy
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
.subscribe(({matchingAliases}) =>
{
// Set the drawerMode if the given breakpoint is active
if ( matchingAliases.includes('lg') )
@@ -137,10 +148,11 @@ export class ContactsListComponent implements OnInit, OnDestroy
takeUntil(this._unsubscribeAll),
filter<KeyboardEvent>(event =>
(event.ctrlKey === true || event.metaKey) // Ctrl or Cmd
&& (event.key === '/') // '/'
)
&& (event.key === '/'), // '/'
),
)
.subscribe(() => {
.subscribe(() =>
{
this.createContact();
});
}
@@ -177,7 +189,8 @@ export class ContactsListComponent implements OnInit, OnDestroy
createContact(): void
{
// Create the contact
this._contactsService.createContact().subscribe((newContact) => {
this._contactsService.createContact().subscribe((newContact) =>
{
// Go to the new contact
this._router.navigate(['./', newContact.id], {relativeTo: this._activatedRoute});

View File

@@ -1,29 +1,25 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRippleModule } from '@angular/material/core';
import { MatSortModule } from '@angular/material/sort';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSortModule } from '@angular/material/sort';
import { MatTooltipModule } from '@angular/material/tooltip';
import { SharedModule } from 'app/shared/shared.module';
import { InventoryComponent } from 'app/modules/admin/apps/ecommerce/inventory/inventory.component';
import { InventoryListComponent } from 'app/modules/admin/apps/ecommerce/inventory/list/inventory.component';
import { RouterModule } from '@angular/router';
import { ecommerceRoutes } from 'app/modules/admin/apps/ecommerce/ecommerce.routing';
import { InventoryComponent } from 'app/modules/admin/apps/ecommerce/inventory/inventory.component';
import { InventoryListComponent } from 'app/modules/admin/apps/ecommerce/inventory/list/inventory.component';
@NgModule({
declarations: [
InventoryComponent,
InventoryListComponent
],
imports : [
imports: [
RouterModule.forChild(ecommerceRoutes),
MatButtonModule,
MatCheckboxModule,
@@ -38,8 +34,9 @@ import { ecommerceRoutes } from 'app/modules/admin/apps/ecommerce/ecommerce.rout
MatSelectModule,
MatSlideToggleModule,
MatTooltipModule,
SharedModule
]
InventoryComponent,
InventoryListComponent,
],
})
export class ECommerceModule
{

View File

@@ -1,13 +1,13 @@
import { Route } from '@angular/router';
import { InventoryComponent } from 'app/modules/admin/apps/ecommerce/inventory/inventory.component';
import { InventoryListComponent } from 'app/modules/admin/apps/ecommerce/inventory/list/inventory.component';
import { InventoryBrandsResolver, InventoryCategoriesResolver, InventoryProductsResolver, InventoryTagsResolver, InventoryVendorsResolver } from 'app/modules/admin/apps/ecommerce/inventory/inventory.resolvers';
import { InventoryListComponent } from 'app/modules/admin/apps/ecommerce/inventory/list/inventory.component';
export const ecommerceRoutes: Route[] = [
{
path : '',
pathMatch : 'full',
redirectTo: 'inventory'
redirectTo: 'inventory',
},
{
path : 'inventory',
@@ -21,10 +21,10 @@ export const ecommerceRoutes: Route[] = [
categories: InventoryCategoriesResolver,
products : InventoryProductsResolver,
tags : InventoryTagsResolver,
vendors : InventoryVendorsResolver
}
}
]
vendors : InventoryVendorsResolver,
},
},
],
/*children : [
{
path : '',
@@ -46,5 +46,5 @@ export const ecommerceRoutes: Route[] = [
]
}
]*/
}
},
];

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'inventory',
templateUrl : './inventory.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class InventoryComponent
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, Observable, throwError } from 'rxjs';
import { InventoryService } from 'app/modules/admin/apps/ecommerce/inventory/inventory.service';
import { InventoryBrand, InventoryCategory, InventoryPagination, InventoryProduct, InventoryTag, InventoryVendor } from 'app/modules/admin/apps/ecommerce/inventory/inventory.types';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class InventoryBrandsResolver implements Resolve<any>
{
@@ -33,7 +33,7 @@ export class InventoryBrandsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class InventoryCategoriesResolver implements Resolve<any>
{
@@ -61,7 +61,7 @@ export class InventoryCategoriesResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class InventoryProductResolver implements Resolve<any>
{
@@ -70,7 +70,7 @@ export class InventoryProductResolver implements Resolve<any>
*/
constructor(
private _inventoryService: InventoryService,
private _router: Router
private _router: Router,
)
{
}
@@ -88,28 +88,29 @@ export class InventoryProductResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<InventoryProduct>
{
return this._inventoryService.getProductById(route.paramMap.get('id'))
.pipe(
// Error here means the requested product is not available
catchError((error) => {
.pipe(
// Error here means the requested product is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class InventoryProductsResolver implements Resolve<any>
{
@@ -137,7 +138,7 @@ export class InventoryProductsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class InventoryTagsResolver implements Resolve<any>
{
@@ -165,7 +166,7 @@ export class InventoryTagsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class InventoryVendorsResolver implements Resolve<any>
{

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { InventoryBrand, InventoryCategory, InventoryPagination, InventoryProduct, InventoryTag, InventoryVendor } from 'app/modules/admin/apps/ecommerce/inventory/inventory.types';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class InventoryService
{
@@ -94,9 +94,10 @@ export class InventoryService
getBrands(): Observable<InventoryBrand[]>
{
return this._httpClient.get<InventoryBrand[]>('api/apps/ecommerce/inventory/brands').pipe(
tap((brands) => {
tap((brands) =>
{
this._brands.next(brands);
})
}),
);
}
@@ -106,9 +107,10 @@ export class InventoryService
getCategories(): Observable<InventoryCategory[]>
{
return this._httpClient.get<InventoryCategory[]>('api/apps/ecommerce/inventory/categories').pipe(
tap((categories) => {
tap((categories) =>
{
this._categories.next(categories);
})
}),
);
}
@@ -131,13 +133,14 @@ export class InventoryService
size: '' + size,
sort,
order,
search
}
search,
},
}).pipe(
tap((response) => {
tap((response) =>
{
this._pagination.next(response.pagination);
this._products.next(response.products);
})
}),
);
}
@@ -148,7 +151,8 @@ export class InventoryService
{
return this._products.pipe(
take(1),
map((products) => {
map((products) =>
{
// Find the product
const product = products.find(item => item.id === id) || null;
@@ -159,7 +163,8 @@ export class InventoryService
// Return the product
return product;
}),
switchMap((product) => {
switchMap((product) =>
{
if ( !product )
{
@@ -167,7 +172,7 @@ export class InventoryService
}
return of(product);
})
}),
);
}
@@ -179,15 +184,16 @@ export class InventoryService
return this.products$.pipe(
take(1),
switchMap(products => this._httpClient.post<InventoryProduct>('api/apps/ecommerce/inventory/product', {}).pipe(
map((newProduct) => {
map((newProduct) =>
{
// Update the products with the new product
this._products.next([newProduct, ...products]);
// Return the new product
return newProduct;
})
))
}),
)),
);
}
@@ -203,9 +209,10 @@ export class InventoryService
take(1),
switchMap(products => this._httpClient.patch<InventoryProduct>('api/apps/ecommerce/inventory/product', {
id,
product
product,
}).pipe(
map((updatedProduct) => {
map((updatedProduct) =>
{
// Find the index of the updated product
const index = products.findIndex(item => item.id === id);
@@ -222,16 +229,17 @@ export class InventoryService
switchMap(updatedProduct => this.product$.pipe(
take(1),
filter(item => item && item.id === id),
tap(() => {
tap(() =>
{
// Update the product if it's selected
this._product.next(updatedProduct);
// Return the updated product
return updatedProduct;
})
))
))
}),
)),
)),
);
}
@@ -245,7 +253,8 @@ export class InventoryService
return this.products$.pipe(
take(1),
switchMap(products => this._httpClient.delete('api/apps/ecommerce/inventory/product', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted product
const index = products.findIndex(item => item.id === id);
@@ -258,8 +267,8 @@ export class InventoryService
// Return the deleted status
return isDeleted;
})
))
}),
)),
);
}
@@ -269,9 +278,10 @@ export class InventoryService
getTags(): Observable<InventoryTag[]>
{
return this._httpClient.get<InventoryTag[]>('api/apps/ecommerce/inventory/tags').pipe(
tap((tags) => {
tap((tags) =>
{
this._tags.next(tags);
})
}),
);
}
@@ -285,15 +295,16 @@ export class InventoryService
return this.tags$.pipe(
take(1),
switchMap(tags => this._httpClient.post<InventoryTag>('api/apps/ecommerce/inventory/tag', {tag}).pipe(
map((newTag) => {
map((newTag) =>
{
// Update the tags with the new tag
this._tags.next([...tags, newTag]);
// Return new tag from observable
return newTag;
})
))
}),
)),
);
}
@@ -309,9 +320,10 @@ export class InventoryService
take(1),
switchMap(tags => this._httpClient.patch<InventoryTag>('api/apps/ecommerce/inventory/tag', {
id,
tag
tag,
}).pipe(
map((updatedTag) => {
map((updatedTag) =>
{
// Find the index of the updated tag
const index = tags.findIndex(item => item.id === id);
@@ -324,8 +336,8 @@ export class InventoryService
// Return the updated tag
return updatedTag;
})
))
}),
)),
);
}
@@ -339,7 +351,8 @@ export class InventoryService
return this.tags$.pipe(
take(1),
switchMap(tags => this._httpClient.delete('api/apps/ecommerce/inventory/tag', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted tag
const index = tags.findIndex(item => item.id === id);
@@ -356,10 +369,12 @@ export class InventoryService
filter(isDeleted => isDeleted),
switchMap(isDeleted => this.products$.pipe(
take(1),
map((products) => {
map((products) =>
{
// Iterate through the contacts
products.forEach((product) => {
products.forEach((product) =>
{
const tagIndex = product.tags.findIndex(tag => tag === id);
@@ -372,9 +387,9 @@ export class InventoryService
// Return the deleted status
return isDeleted;
})
))
))
}),
)),
)),
);
}
@@ -384,9 +399,10 @@ export class InventoryService
getVendors(): Observable<InventoryVendor[]>
{
return this._httpClient.get<InventoryVendor[]>('api/apps/ecommerce/inventory/vendors').pipe(
tap((vendors) => {
tap((vendors) =>
{
this._vendors.next(vendors);
})
}),
);
}

View File

@@ -1,13 +1,22 @@
import { AsyncPipe, CurrencyPipe, NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { debounceTime, map, merge, Observable, Subject, switchMap, takeUntil } from 'rxjs';
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
import { MatOptionModule, MatRippleModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { fuseAnimations } from '@fuse/animations';
import { FuseConfirmationService } from '@fuse/services/confirmation';
import { InventoryBrand, InventoryCategory, InventoryPagination, InventoryProduct, InventoryTag, InventoryVendor } from 'app/modules/admin/apps/ecommerce/inventory/inventory.types';
import { InventoryService } from 'app/modules/admin/apps/ecommerce/inventory/inventory.service';
import { InventoryBrand, InventoryCategory, InventoryPagination, InventoryProduct, InventoryTag, InventoryVendor } from 'app/modules/admin/apps/ecommerce/inventory/inventory.types';
import { debounceTime, map, merge, Observable, Subject, switchMap, takeUntil } from 'rxjs';
@Component({
selector : 'inventory-list',
@@ -30,11 +39,13 @@ import { InventoryService } from 'app/modules/admin/apps/ecommerce/inventory/inv
grid-template-columns: 48px 112px auto 112px 96px 96px 72px;
}
}
`
`,
],
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
animations : fuseAnimations
animations : fuseAnimations,
standalone : true,
imports : [NgIf, MatProgressBarModule, MatFormFieldModule, MatIconModule, MatInputModule, FormsModule, ReactiveFormsModule, MatButtonModule, MatSortModule, NgFor, NgTemplateOutlet, MatPaginatorModule, NgClass, MatSlideToggleModule, MatSelectModule, MatOptionModule, MatCheckboxModule, MatRippleModule, AsyncPipe, CurrencyPipe],
})
export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
{
@@ -64,7 +75,7 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
private _changeDetectorRef: ChangeDetectorRef,
private _fuseConfirmationService: FuseConfirmationService,
private _formBuilder: UntypedFormBuilder,
private _inventoryService: InventoryService
private _inventoryService: InventoryService,
)
{
}
@@ -99,13 +110,14 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
thumbnail : [''],
images : [[]],
currentImageIndex: [0], // Image index that is currently being viewed
active : [false]
active : [false],
});
// Get the brands
this._inventoryService.brands$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((brands: InventoryBrand[]) => {
.subscribe((brands: InventoryBrand[]) =>
{
// Update the brands
this.brands = brands;
@@ -117,7 +129,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get the categories
this._inventoryService.categories$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((categories: InventoryCategory[]) => {
.subscribe((categories: InventoryCategory[]) =>
{
// Update the categories
this.categories = categories;
@@ -129,7 +142,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get the pagination
this._inventoryService.pagination$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((pagination: InventoryPagination) => {
.subscribe((pagination: InventoryPagination) =>
{
// Update the pagination
this.pagination = pagination;
@@ -144,7 +158,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get the tags
this._inventoryService.tags$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((tags: InventoryTag[]) => {
.subscribe((tags: InventoryTag[]) =>
{
// Update the tags
this.tags = tags;
@@ -157,7 +172,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get the vendors
this._inventoryService.vendors$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((vendors: InventoryVendor[]) => {
.subscribe((vendors: InventoryVendor[]) =>
{
// Update the vendors
this.vendors = vendors;
@@ -171,14 +187,16 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
.pipe(
takeUntil(this._unsubscribeAll),
debounceTime(300),
switchMap((query) => {
switchMap((query) =>
{
this.closeDetails();
this.isLoading = true;
return this._inventoryService.getProducts(0, 10, 'name', 'asc', query);
}),
map(() => {
map(() =>
{
this.isLoading = false;
})
}),
)
.subscribe();
}
@@ -194,7 +212,7 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
this._sort.sort({
id : 'name',
start : 'asc',
disableClear: true
disableClear: true,
});
// Mark for check
@@ -203,7 +221,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// If the user changes the sort order...
this._sort.sortChange
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
.subscribe(() =>
{
// Reset back to the first page
this._paginator.pageIndex = 0;
@@ -213,14 +232,16 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get products if sort or page changes
merge(this._sort.sortChange, this._paginator.page).pipe(
switchMap(() => {
switchMap(() =>
{
this.closeDetails();
this.isLoading = true;
return this._inventoryService.getProducts(this._paginator.pageIndex, this._paginator.pageSize, this._sort.active, this._sort.direction);
}),
map(() => {
map(() =>
{
this.isLoading = false;
})
}),
).subscribe();
}
}
@@ -256,7 +277,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
// Get the product by id
this._inventoryService.getProductById(productId)
.subscribe((product) => {
.subscribe((product) =>
{
// Set the selected product
this.selectedProduct = product;
@@ -375,12 +397,13 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
createTag(title: string): void
{
const tag = {
title
title,
};
// Create tag on the server
this._inventoryService.createTag(tag)
.subscribe((response) => {
.subscribe((response) =>
{
// Add the tag to the product
this.addTagToProduct(response);
@@ -489,7 +512,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
createProduct(): void
{
// Create the product
this._inventoryService.createProduct().subscribe((newProduct) => {
this._inventoryService.createProduct().subscribe((newProduct) =>
{
// Go to new product
this.selectedProduct = newProduct;
@@ -514,7 +538,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
delete product.currentImageIndex;
// Update the product on the server
this._inventoryService.updateProduct(product.id, product).subscribe(() => {
this._inventoryService.updateProduct(product.id, product).subscribe(() =>
{
// Show a success message
this.showFlashMessage('success');
@@ -532,13 +557,14 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
message: 'Are you sure you want to remove this product? This action cannot be undone!',
actions: {
confirm: {
label: 'Delete'
}
}
label: 'Delete',
},
},
});
// Subscribe to the confirmation dialog closed action
confirmation.afterClosed().subscribe((result) => {
confirmation.afterClosed().subscribe((result) =>
{
// If the confirm button pressed...
if ( result === 'confirmed' )
@@ -548,7 +574,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
const product = this.selectedProductForm.getRawValue();
// Delete the product on the server
this._inventoryService.deleteProduct(product.id).subscribe(() => {
this._inventoryService.deleteProduct(product.id).subscribe(() =>
{
// Close the details
this.closeDetails();
@@ -569,7 +596,8 @@ export class InventoryListComponent implements OnInit, AfterViewInit, OnDestroy
this._changeDetectorRef.markForCheck();
// Hide it after 3 seconds
setTimeout(() => {
setTimeout(() =>
{
this.flashMessage = null;

View File

@@ -1,15 +1,21 @@
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatDrawerToggleResult } from '@angular/material/sidenav';
import { Subject, takeUntil } from 'rxjs';
import { FileManagerListComponent } from 'app/modules/admin/apps/file-manager/list/list.component';
import { RouterLink } from '@angular/router';
import { FileManagerService } from 'app/modules/admin/apps/file-manager/file-manager.service';
import { Item } from 'app/modules/admin/apps/file-manager/file-manager.types';
import { FileManagerListComponent } from 'app/modules/admin/apps/file-manager/list/list.component';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'file-manager-details',
templateUrl : './details.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, RouterLink, MatIconModule, NgIf],
})
export class FileManagerDetailsComponent implements OnInit, OnDestroy
{
@@ -22,7 +28,7 @@ export class FileManagerDetailsComponent implements OnInit, OnDestroy
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fileManagerListComponent: FileManagerListComponent,
private _fileManagerService: FileManagerService
private _fileManagerService: FileManagerService,
)
{
}
@@ -42,7 +48,8 @@ export class FileManagerDetailsComponent implements OnInit, OnDestroy
// Get the item
this._fileManagerService.item$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((item: Item) => {
.subscribe((item: Item) =>
{
// Open the drawer in case it is closed
this._fileManagerListComponent.matDrawer.open();

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'file-manager',
templateUrl : './file-manager.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class FileManagerComponent
{

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { FileManagerDetailsComponent } from 'app/modules/admin/apps/file-manager/details/details.component';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class CanDeactivateFileManagerDetails implements CanDeactivate<FileManagerDetailsComponent>
{
@@ -12,7 +12,7 @@ export class CanDeactivateFileManagerDetails implements CanDeactivate<FileManage
component: FileManagerDetailsComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
nextState: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
{
// Get the next route

View File

@@ -1,29 +1,26 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTooltipModule } from '@angular/material/tooltip';
import { SharedModule } from 'app/shared/shared.module';
import { fileManagerRoutes } from 'app/modules/admin/apps/file-manager/file-manager.routing';
import { FileManagerComponent } from 'app/modules/admin/apps/file-manager/file-manager.component';
import { RouterModule } from '@angular/router';
import { FileManagerDetailsComponent } from 'app/modules/admin/apps/file-manager/details/details.component';
import { FileManagerComponent } from 'app/modules/admin/apps/file-manager/file-manager.component';
import { fileManagerRoutes } from 'app/modules/admin/apps/file-manager/file-manager.routing';
import { FileManagerListComponent } from 'app/modules/admin/apps/file-manager/list/list.component';
@NgModule({
declarations: [
FileManagerComponent,
FileManagerDetailsComponent,
FileManagerListComponent
],
imports : [
imports: [
RouterModule.forChild(fileManagerRoutes),
MatButtonModule,
MatIconModule,
MatSidenavModule,
MatTooltipModule,
SharedModule
]
FileManagerComponent,
FileManagerDetailsComponent,
FileManagerListComponent,
],
})
export class FileManagerModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, Observable, throwError } from 'rxjs';
import { FileManagerService } from 'app/modules/admin/apps/file-manager/file-manager.service';
import { Item } from 'app/modules/admin/apps/file-manager/file-manager.types';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class FileManagerItemsResolver implements Resolve<any>
{
@@ -33,7 +33,7 @@ export class FileManagerItemsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class FileManagerFolderResolver implements Resolve<any>
{
@@ -42,7 +42,7 @@ export class FileManagerFolderResolver implements Resolve<any>
*/
constructor(
private _router: Router,
private _fileManagerService: FileManagerService
private _fileManagerService: FileManagerService,
)
{
}
@@ -60,28 +60,29 @@ export class FileManagerFolderResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Item[]>
{
return this._fileManagerService.getItems(route.paramMap.get('folderId'))
.pipe(
// Error here means the requested task is not available
catchError((error) => {
.pipe(
// Error here means the requested task is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class FileManagerItemResolver implements Resolve<any>
{
@@ -90,7 +91,7 @@ export class FileManagerItemResolver implements Resolve<any>
*/
constructor(
private _router: Router,
private _fileManagerService: FileManagerService
private _fileManagerService: FileManagerService,
)
{
}
@@ -108,22 +109,23 @@ export class FileManagerItemResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Item>
{
return this._fileManagerService.getItemById(route.paramMap.get('id'))
.pipe(
// Error here means the requested task is not available
catchError((error) => {
.pipe(
// Error here means the requested task is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}

View File

@@ -1,9 +1,9 @@
import { Route } from '@angular/router';
import { CanDeactivateFileManagerDetails } from 'app/modules/admin/apps/file-manager/file-manager.guards';
import { FileManagerComponent } from 'app/modules/admin/apps/file-manager/file-manager.component';
import { FileManagerListComponent } from 'app/modules/admin/apps/file-manager/list/list.component';
import { FileManagerDetailsComponent } from 'app/modules/admin/apps/file-manager/details/details.component';
import { FileManagerComponent } from 'app/modules/admin/apps/file-manager/file-manager.component';
import { CanDeactivateFileManagerDetails } from 'app/modules/admin/apps/file-manager/file-manager.guards';
import { FileManagerFolderResolver, FileManagerItemResolver, FileManagerItemsResolver } from 'app/modules/admin/apps/file-manager/file-manager.resolvers';
import { FileManagerListComponent } from 'app/modules/admin/apps/file-manager/list/list.component';
export const fileManagerRoutes: Route[] = [
{
@@ -14,36 +14,36 @@ export const fileManagerRoutes: Route[] = [
path : 'folders/:folderId',
component: FileManagerListComponent,
resolve : {
item: FileManagerFolderResolver
item: FileManagerFolderResolver,
},
children : [
{
path : 'details/:id',
component : FileManagerDetailsComponent,
resolve : {
item: FileManagerItemResolver
item: FileManagerItemResolver,
},
canDeactivate: [CanDeactivateFileManagerDetails]
}
]
canDeactivate: [CanDeactivateFileManagerDetails],
},
],
},
{
path : '',
component: FileManagerListComponent,
resolve : {
items: FileManagerItemsResolver
items: FileManagerItemsResolver,
},
children : [
{
path : 'details/:id',
component : FileManagerDetailsComponent,
resolve : {
item: FileManagerItemResolver
item: FileManagerItemResolver,
},
canDeactivate: [CanDeactivateFileManagerDetails]
}
]
}
]
}
canDeactivate: [CanDeactivateFileManagerDetails],
},
],
},
],
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Item, Items } from 'app/modules/admin/apps/file-manager/file-manager.types';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class FileManagerService
{
@@ -49,9 +49,10 @@ export class FileManagerService
getItems(folderId: string | null = null): Observable<Item[]>
{
return this._httpClient.get<Items>('api/apps/file-manager', {params: {folderId}}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._items.next(response);
})
}),
);
}
@@ -62,7 +63,8 @@ export class FileManagerService
{
return this._items.pipe(
take(1),
map((items) => {
map((items) =>
{
// Find within the folders and files
const item = [...items.folders, ...items.files].find(value => value.id === id) || null;
@@ -73,7 +75,8 @@ export class FileManagerService
// Return the item
return item;
}),
switchMap((item) => {
switchMap((item) =>
{
if ( !item )
{
@@ -81,7 +84,7 @@ export class FileManagerService
}
return of(item);
})
}),
);
}
}

View File

@@ -1,16 +1,22 @@
import { NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatDrawer } from '@angular/material/sidenav';
import { Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatDrawer, MatSidenavModule } from '@angular/material/sidenav';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute, Router, RouterLink, RouterOutlet } from '@angular/router';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { FileManagerService } from 'app/modules/admin/apps/file-manager/file-manager.service';
import { Item, Items } from 'app/modules/admin/apps/file-manager/file-manager.types';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'file-manager-list',
templateUrl : './list.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatSidenavModule, RouterOutlet, NgIf, RouterLink, NgFor, MatButtonModule, MatIconModule, MatTooltipModule],
})
export class FileManagerListComponent implements OnInit, OnDestroy
{
@@ -28,7 +34,7 @@ export class FileManagerListComponent implements OnInit, OnDestroy
private _changeDetectorRef: ChangeDetectorRef,
private _router: Router,
private _fileManagerService: FileManagerService,
private _fuseMediaWatcherService: FuseMediaWatcherService
private _fuseMediaWatcherService: FuseMediaWatcherService,
)
{
}
@@ -45,7 +51,8 @@ export class FileManagerListComponent implements OnInit, OnDestroy
// Get the items
this._fileManagerService.items$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((items: Items) => {
.subscribe((items: Items) =>
{
this.items = items;
// Mark for check
@@ -55,7 +62,8 @@ export class FileManagerListComponent implements OnInit, OnDestroy
// Get the item
this._fileManagerService.item$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((item: Item) => {
.subscribe((item: Item) =>
{
this.selectedItem = item;
// Mark for check
@@ -65,7 +73,8 @@ export class FileManagerListComponent implements OnInit, OnDestroy
// Subscribe to media query change
this._fuseMediaWatcherService.onMediaQueryChange$('(min-width: 1440px)')
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((state) => {
.subscribe((state) =>
{
// Calculate the drawer mode
this.drawerMode = state.matches ? 'side' : 'over';

View File

@@ -1,12 +1,19 @@
import { NgFor } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { RouterLink } from '@angular/router';
import { HelpCenterService } from 'app/modules/admin/apps/help-center/help-center.service';
import { FaqCategory } from 'app/modules/admin/apps/help-center/help-center.type';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'help-center-faqs',
templateUrl : './faqs.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatButtonModule, RouterLink, MatIconModule, NgFor, MatExpansionModule],
})
export class HelpCenterFaqsComponent implements OnInit, OnDestroy
{
@@ -32,7 +39,8 @@ export class HelpCenterFaqsComponent implements OnInit, OnDestroy
// Get the FAQs
this._helpCenterService.faqs$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((faqCategories) => {
.subscribe((faqCategories) =>
{
this.faqCategories = faqCategories;
});
}

View File

@@ -1,13 +1,18 @@
import { NgFor } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { HelpCenterService } from 'app/modules/admin/apps/help-center/help-center.service';
import { GuideCategory } from 'app/modules/admin/apps/help-center/help-center.type';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'help-center-guides-category',
templateUrl : './category.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatButtonModule, RouterLink, MatIconModule, NgFor],
})
export class HelpCenterGuidesCategoryComponent implements OnInit, OnDestroy
{
@@ -20,7 +25,7 @@ export class HelpCenterGuidesCategoryComponent implements OnInit, OnDestroy
constructor(
private _activatedRoute: ActivatedRoute,
private _helpCenterService: HelpCenterService,
private _router: Router
private _router: Router,
)
{
}
@@ -37,7 +42,8 @@ export class HelpCenterGuidesCategoryComponent implements OnInit, OnDestroy
// Get the Guides
this._helpCenterService.guides$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((guideCategories) => {
.subscribe((guideCategories) =>
{
this.guideCategory = guideCategories[0];
});
}

View File

@@ -1,12 +1,17 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { RouterLink } from '@angular/router';
import { HelpCenterService } from 'app/modules/admin/apps/help-center/help-center.service';
import { GuideCategory } from 'app/modules/admin/apps/help-center/help-center.type';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'help-center-guides-guide',
templateUrl : './guide.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatButtonModule, RouterLink, MatIconModule],
})
export class HelpCenterGuidesGuideComponent implements OnInit, OnDestroy
{
@@ -32,7 +37,8 @@ export class HelpCenterGuidesGuideComponent implements OnInit, OnDestroy
// Get the Guides
this._helpCenterService.guide$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((guideCategory) => {
.subscribe((guideCategory) =>
{
this.guideCategory = guideCategory;
});
}

View File

@@ -1,12 +1,18 @@
import { NgFor, NgIf } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { RouterLink } from '@angular/router';
import { HelpCenterService } from 'app/modules/admin/apps/help-center/help-center.service';
import { GuideCategory } from 'app/modules/admin/apps/help-center/help-center.type';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'help-center-guides',
templateUrl : './guides.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatButtonModule, RouterLink, MatIconModule, NgFor, NgIf],
})
export class HelpCenterGuidesComponent implements OnInit, OnDestroy
{
@@ -32,7 +38,8 @@ export class HelpCenterGuidesComponent implements OnInit, OnDestroy
// Get the Guide categories
this._helpCenterService.guides$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((guideCategories) => {
.subscribe((guideCategories) =>
{
this.guideCategories = guideCategories;
});
}

View File

@@ -1,12 +1,20 @@
import { NgFor } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { RouterLink } from '@angular/router';
import { HelpCenterService } from 'app/modules/admin/apps/help-center/help-center.service';
import { FaqCategory } from 'app/modules/admin/apps/help-center/help-center.type';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'help-center',
templateUrl : './help-center.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatFormFieldModule, MatInputModule, MatIconModule, RouterLink, MatExpansionModule, NgFor],
})
export class HelpCenterComponent implements OnInit, OnDestroy
{
@@ -32,7 +40,8 @@ export class HelpCenterComponent implements OnInit, OnDestroy
// Get the FAQs
this._helpCenterService.faqs$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((faqCategories) => {
.subscribe((faqCategories) =>
{
this.faqCategory = faqCategories[0];
});
}

View File

@@ -1,39 +1,34 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { FuseAlertModule } from '@fuse/components/alert';
import { SharedModule } from 'app/shared/shared.module';
import { HelpCenterComponent } from 'app/modules/admin/apps/help-center/help-center.component';
import { RouterModule } from '@angular/router';
import { HelpCenterFaqsComponent } from 'app/modules/admin/apps/help-center/faqs/faqs.component';
import { HelpCenterGuidesComponent } from 'app/modules/admin/apps/help-center/guides/guides.component';
import { HelpCenterGuidesCategoryComponent } from 'app/modules/admin/apps/help-center/guides/category/category.component';
import { HelpCenterGuidesGuideComponent } from 'app/modules/admin/apps/help-center/guides/guide/guide.component';
import { HelpCenterSupportComponent } from 'app/modules/admin/apps/help-center/support/support.component';
import { HelpCenterGuidesComponent } from 'app/modules/admin/apps/help-center/guides/guides.component';
import { HelpCenterComponent } from 'app/modules/admin/apps/help-center/help-center.component';
import { helpCenterRoutes } from 'app/modules/admin/apps/help-center/help-center.routing';
import { HelpCenterSupportComponent } from 'app/modules/admin/apps/help-center/support/support.component';
@NgModule({
declarations: [
HelpCenterComponent,
HelpCenterFaqsComponent,
HelpCenterGuidesComponent,
HelpCenterGuidesCategoryComponent,
HelpCenterGuidesGuideComponent,
HelpCenterSupportComponent
],
imports : [
imports: [
RouterModule.forChild(helpCenterRoutes),
MatButtonModule,
MatExpansionModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
FuseAlertModule,
SharedModule
]
HelpCenterComponent,
HelpCenterFaqsComponent,
HelpCenterGuidesComponent,
HelpCenterGuidesCategoryComponent,
HelpCenterGuidesGuideComponent,
HelpCenterSupportComponent,
],
})
export class HelpCenterModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { HelpCenterService } from 'app/modules/admin/apps/help-center/help-center.service';
import { FaqCategory, GuideCategory } from 'app/modules/admin/apps/help-center/help-center.type';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class HelpCenterMostAskedFaqsResolver implements Resolve<any>
{
@@ -33,7 +33,7 @@ export class HelpCenterMostAskedFaqsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class HelpCenterFaqsResolver implements Resolve<any>
{
@@ -61,7 +61,7 @@ export class HelpCenterFaqsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class HelpCenterGuidesResolver implements Resolve<any>
{
@@ -89,7 +89,7 @@ export class HelpCenterGuidesResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class HelpCenterGuidesCategoryResolver implements Resolve<any>
{
@@ -117,7 +117,7 @@ export class HelpCenterGuidesCategoryResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class HelpCenterGuidesGuideResolver implements Resolve<any>
{

View File

@@ -1,26 +1,26 @@
import { Route } from '@angular/router';
import { HelpCenterComponent } from 'app/modules/admin/apps/help-center/help-center.component';
import { HelpCenterFaqsComponent } from 'app/modules/admin/apps/help-center/faqs/faqs.component';
import { HelpCenterGuidesComponent } from 'app/modules/admin/apps/help-center/guides/guides.component';
import { HelpCenterGuidesCategoryComponent } from 'app/modules/admin/apps/help-center/guides/category/category.component';
import { HelpCenterGuidesGuideComponent } from 'app/modules/admin/apps/help-center/guides/guide/guide.component';
import { HelpCenterSupportComponent } from 'app/modules/admin/apps/help-center/support/support.component';
import { HelpCenterGuidesComponent } from 'app/modules/admin/apps/help-center/guides/guides.component';
import { HelpCenterComponent } from 'app/modules/admin/apps/help-center/help-center.component';
import { HelpCenterFaqsResolver, HelpCenterGuidesCategoryResolver, HelpCenterGuidesGuideResolver, HelpCenterGuidesResolver, HelpCenterMostAskedFaqsResolver } from 'app/modules/admin/apps/help-center/help-center.resolvers';
import { HelpCenterSupportComponent } from 'app/modules/admin/apps/help-center/support/support.component';
export const helpCenterRoutes: Route[] = [
{
path : '',
component: HelpCenterComponent,
resolve : {
faqs: HelpCenterMostAskedFaqsResolver
}
faqs: HelpCenterMostAskedFaqsResolver,
},
},
{
path : 'faqs',
component: HelpCenterFaqsComponent,
resolve : {
faqs: HelpCenterFaqsResolver
}
faqs: HelpCenterFaqsResolver,
},
},
{
path : 'guides',
@@ -29,8 +29,8 @@ export const helpCenterRoutes: Route[] = [
path : '',
component: HelpCenterGuidesComponent,
resolve : {
guides: HelpCenterGuidesResolver
}
guides: HelpCenterGuidesResolver,
},
},
{
path : ':categorySlug',
@@ -39,22 +39,22 @@ export const helpCenterRoutes: Route[] = [
path : '',
component: HelpCenterGuidesCategoryComponent,
resolve : {
guides: HelpCenterGuidesCategoryResolver
}
guides: HelpCenterGuidesCategoryResolver,
},
},
{
path : ':guideSlug',
component: HelpCenterGuidesGuideComponent,
resolve : {
guide: HelpCenterGuidesGuideResolver
}
}
]
}
]
guide: HelpCenterGuidesGuideResolver,
},
},
],
},
],
},
{
path : 'support',
component: HelpCenterSupportComponent
}
component: HelpCenterSupportComponent,
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, ReplaySubject, tap } from 'rxjs';
import { Injectable } from '@angular/core';
import { FaqCategory, Guide, GuideCategory } from 'app/modules/admin/apps/help-center/help-center.type';
import { Observable, ReplaySubject, tap } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class HelpCenterService
{
@@ -57,9 +57,10 @@ export class HelpCenterService
getAllFaqs(): Observable<FaqCategory[]>
{
return this._httpClient.get<FaqCategory[]>('api/apps/help-center/faqs').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._faqs.next(response);
})
}),
);
}
@@ -71,11 +72,12 @@ export class HelpCenterService
getFaqsByCategory(slug: string): Observable<FaqCategory[]>
{
return this._httpClient.get<FaqCategory[]>('api/apps/help-center/faqs', {
params: {slug}
params: {slug},
}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._faqs.next(response);
})
}),
);
}
@@ -87,11 +89,12 @@ export class HelpCenterService
getAllGuides(limit = '4'): Observable<GuideCategory[]>
{
return this._httpClient.get<GuideCategory[]>('api/apps/help-center/guides', {
params: {limit}
params: {limit},
}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._guides.next(response);
})
}),
);
}
@@ -103,11 +106,12 @@ export class HelpCenterService
getGuidesByCategory(slug: string): Observable<GuideCategory[]>
{
return this._httpClient.get<GuideCategory[]>('api/apps/help-center/guides', {
params: {slug}
params: {slug},
}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._guides.next(response);
})
}),
);
}
@@ -122,12 +126,13 @@ export class HelpCenterService
return this._httpClient.get<GuideCategory>('api/apps/help-center/guide', {
params: {
categorySlug,
guideSlug
}
guideSlug,
},
}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._guide.next(response);
})
}),
);
}
}

View File

@@ -1,13 +1,23 @@
import { TextFieldModule } from '@angular/cdk/text-field';
import { NgIf } from '@angular/common';
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, NgForm, Validators } from '@angular/forms';
import { FormsModule, NgForm, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { RouterLink } from '@angular/router';
import { fuseAnimations } from '@fuse/animations';
import { FuseAlertComponent } from '@fuse/components/alert';
import { HelpCenterService } from 'app/modules/admin/apps/help-center/help-center.service';
@Component({
selector : 'help-center-support',
templateUrl : './support.component.html',
encapsulation: ViewEncapsulation.None,
animations : fuseAnimations
animations : fuseAnimations,
standalone : true,
imports : [MatButtonModule, RouterLink, MatIconModule, NgIf, FuseAlertComponent, FormsModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, TextFieldModule],
})
export class HelpCenterSupportComponent implements OnInit
{
@@ -21,7 +31,7 @@ export class HelpCenterSupportComponent implements OnInit
*/
constructor(
private _formBuilder: UntypedFormBuilder,
private _helpCenterService: HelpCenterService
private _helpCenterService: HelpCenterService,
)
{
}
@@ -40,7 +50,7 @@ export class HelpCenterSupportComponent implements OnInit
name : ['', Validators.required],
email : ['', [Validators.required, Validators.email]],
subject: ['', Validators.required],
message: ['', Validators.required]
message: ['', Validators.required],
});
}
@@ -69,10 +79,11 @@ export class HelpCenterSupportComponent implements OnInit
// and remove it after 5 seconds
this.alert = {
type : 'success',
message: 'Your request has been delivered! A member of our support staff will respond as soon as possible.'
message: 'Your request has been delivered! A member of our support staff will respond as soon as possible.',
};
setTimeout(() => {
setTimeout(() =>
{
this.alert = null;
}, 7000);

View File

@@ -1,25 +1,33 @@
import { NgIf } from '@angular/common';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { QuillEditorComponent } from 'ngx-quill';
@Component({
selector : 'mailbox-compose',
templateUrl : './compose.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatButtonModule, MatIconModule, FormsModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, NgIf, QuillEditorComponent],
})
export class MailboxComposeComponent implements OnInit
{
composeForm: UntypedFormGroup;
copyFields: { cc: boolean; bcc: boolean } = {
cc : false,
bcc: false
bcc: false,
};
quillModules: any = {
toolbar: [
['bold', 'italic', 'underline'],
[{align: []}, {list: 'ordered'}, {list: 'bullet'}],
['clean']
]
['clean'],
],
};
/**
@@ -27,7 +35,7 @@ export class MailboxComposeComponent implements OnInit
*/
constructor(
public matDialogRef: MatDialogRef<MailboxComposeComponent>,
private _formBuilder: UntypedFormBuilder
private _formBuilder: UntypedFormBuilder,
)
{
}
@@ -47,7 +55,7 @@ export class MailboxComposeComponent implements OnInit
cc : ['', [Validators.email]],
bcc : ['', [Validators.email]],
subject: [''],
body : ['', [Validators.required]]
body : ['', [Validators.required]],
});
}

View File

@@ -1,17 +1,28 @@
import { Component, ElementRef, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TemplatePortal } from '@angular/cdk/portal';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { MatButton } from '@angular/material/button';
import { Subject, takeUntil } from 'rxjs';
import { TemplatePortal } from '@angular/cdk/portal';
import { DatePipe, DecimalPipe, NgClass, NgFor, NgIf, NgPlural, NgPluralCase } from '@angular/common';
import { Component, ElementRef, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { MatButton, MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { FuseScrollResetDirective } from '@fuse/directives/scroll-reset';
import { FuseFindByKeyPipe } from '@fuse/pipes/find-by-key/find-by-key.pipe';
import { labelColorDefs } from 'app/modules/admin/apps/mailbox/mailbox.constants';
import { MailboxService } from 'app/modules/admin/apps/mailbox/mailbox.service';
import { Mail, MailFolder, MailLabel } from 'app/modules/admin/apps/mailbox/mailbox.types';
import { labelColorDefs } from 'app/modules/admin/apps/mailbox/mailbox.constants';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'mailbox-details',
templateUrl : './details.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [NgIf, MatButtonModule, RouterLink, MatIconModule, MatMenuModule, NgFor, MatRippleModule, MatCheckboxModule, NgClass, FuseScrollResetDirective, NgPlural, NgPluralCase, MatFormFieldModule, MatInputModule, FuseFindByKeyPipe, DecimalPipe, DatePipe],
})
export class MailboxDetailsComponent implements OnInit, OnDestroy
{
@@ -35,7 +46,7 @@ export class MailboxDetailsComponent implements OnInit, OnDestroy
private _mailboxService: MailboxService,
private _overlay: Overlay,
private _router: Router,
private _viewContainerRef: ViewContainerRef
private _viewContainerRef: ViewContainerRef,
)
{
}
@@ -55,28 +66,32 @@ export class MailboxDetailsComponent implements OnInit, OnDestroy
// Folders
this._mailboxService.folders$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((folders: MailFolder[]) => {
.subscribe((folders: MailFolder[]) =>
{
this.folders = folders;
});
// Labels
this._mailboxService.labels$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((labels: MailLabel[]) => {
.subscribe((labels: MailLabel[]) =>
{
this.labels = labels;
});
// Mail
this._mailboxService.mail$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((mail: Mail) => {
.subscribe((mail: Mail) =>
{
this.mail = mail;
});
// Selected mail changed
this._mailboxService.selectedMailChanged
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
.subscribe(() =>
{
// De-activate the reply form
this.replyFormActive = false;
@@ -240,7 +255,8 @@ export class MailboxDetailsComponent implements OnInit, OnDestroy
this.replyFormActive = true;
// Scroll to the bottom of the details pane
setTimeout(() => {
setTimeout(() =>
{
this._elementRef.nativeElement.scrollTop = this._elementRef.nativeElement.scrollHeight;
});
}
@@ -254,7 +270,8 @@ export class MailboxDetailsComponent implements OnInit, OnDestroy
this.replyFormActive = true;
// Scroll to the bottom of the details pane
setTimeout(() => {
setTimeout(() =>
{
this._elementRef.nativeElement.scrollTop = this._elementRef.nativeElement.scrollHeight;
});
}
@@ -268,7 +285,8 @@ export class MailboxDetailsComponent implements OnInit, OnDestroy
this.replyFormActive = true;
// Scroll to the bottom of the details pane
setTimeout(() => {
setTimeout(() =>
{
this._elementRef.nativeElement.scrollTop = this._elementRef.nativeElement.scrollHeight;
});
}
@@ -302,36 +320,36 @@ export class MailboxDetailsComponent implements OnInit, OnDestroy
hasBackdrop : true,
scrollStrategy : this._overlay.scrollStrategies.block(),
positionStrategy: this._overlay.position()
.flexibleConnectedTo(this._infoDetailsPanelOrigin._elementRef.nativeElement)
.withFlexibleDimensions(true)
.withViewportMargin(16)
.withLockedPosition(true)
.withPositions([
{
originX : 'start',
originY : 'bottom',
overlayX: 'start',
overlayY: 'top'
},
{
originX : 'start',
originY : 'top',
overlayX: 'start',
overlayY: 'bottom'
},
{
originX : 'end',
originY : 'bottom',
overlayX: 'end',
overlayY: 'top'
},
{
originX : 'end',
originY : 'top',
overlayX: 'end',
overlayY: 'bottom'
}
])
.flexibleConnectedTo(this._infoDetailsPanelOrigin._elementRef.nativeElement)
.withFlexibleDimensions(true)
.withViewportMargin(16)
.withLockedPosition(true)
.withPositions([
{
originX : 'start',
originY : 'bottom',
overlayX: 'start',
overlayY: 'top',
},
{
originX : 'start',
originY : 'top',
overlayX: 'start',
overlayY: 'bottom',
},
{
originX : 'end',
originY : 'bottom',
overlayX: 'end',
overlayY: 'top',
},
{
originX : 'end',
originY : 'top',
overlayX: 'end',
overlayY: 'bottom',
},
]),
});
// Create a portal from the template
@@ -341,7 +359,8 @@ export class MailboxDetailsComponent implements OnInit, OnDestroy
this._overlayRef.attach(templatePortal);
// Subscribe to the backdrop click
this._overlayRef.backdropClick().subscribe(() => {
this._overlayRef.backdropClick().subscribe(() =>
{
// If overlay exists and attached...
if ( this._overlayRef && this._overlayRef.hasAttached() )

View File

@@ -1,9 +1,12 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector : 'mailbox-empty-details',
templateUrl : './empty-details.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatIconModule],
})
export class MailboxEmptyDetailsComponent
{

View File

@@ -1,13 +1,20 @@
import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common';
import { Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { MailboxService } from 'app/modules/admin/apps/mailbox/mailbox.service';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { RouterLink, RouterOutlet } from '@angular/router';
import { MailboxComponent } from 'app/modules/admin/apps/mailbox/mailbox.component';
import { MailboxService } from 'app/modules/admin/apps/mailbox/mailbox.service';
import { Mail, MailCategory } from 'app/modules/admin/apps/mailbox/mailbox.types';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'mailbox-list',
templateUrl : './list.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [NgIf, MatButtonModule, MatIconModule, RouterLink, MatProgressBarModule, NgFor, NgClass, RouterOutlet, DatePipe],
})
export class MailboxListComponent implements OnInit, OnDestroy
{
@@ -25,7 +32,7 @@ export class MailboxListComponent implements OnInit, OnDestroy
*/
constructor(
public mailboxComponent: MailboxComponent,
private _mailboxService: MailboxService
private _mailboxService: MailboxService,
)
{
}
@@ -42,21 +49,24 @@ export class MailboxListComponent implements OnInit, OnDestroy
// Category
this._mailboxService.category$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((category: MailCategory) => {
.subscribe((category: MailCategory) =>
{
this.category = category;
});
// Mails
this._mailboxService.mails$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((mails: Mail[]) => {
.subscribe((mails: Mail[]) =>
{
this.mails = mails;
});
// Mails loading
this._mailboxService.mailsLoading$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((mailsLoading: boolean) => {
.subscribe((mailsLoading: boolean) =>
{
this.mailsLoading = mailsLoading;
// If the mail list element is available & the mails are loaded...
@@ -70,14 +80,16 @@ export class MailboxListComponent implements OnInit, OnDestroy
// Pagination
this._mailboxService.pagination$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((pagination) => {
.subscribe((pagination) =>
{
this.pagination = pagination;
});
// Selected mail
this._mailboxService.mail$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((mail: Mail) => {
.subscribe((mail: Mail) =>
{
this.selectedMail = mail;
});
}

View File

@@ -1,12 +1,16 @@
import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatDrawer } from '@angular/material/sidenav';
import { Subject, takeUntil } from 'rxjs';
import { MatDrawer, MatSidenavModule } from '@angular/material/sidenav';
import { RouterOutlet } from '@angular/router';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { Subject, takeUntil } from 'rxjs';
import { MailboxSidebarComponent } from './sidebar/sidebar.component';
@Component({
selector : 'mailbox',
templateUrl : './mailbox.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatSidenavModule, MailboxSidebarComponent, RouterOutlet],
})
export class MailboxComponent implements OnInit, OnDestroy
{
@@ -35,7 +39,8 @@ export class MailboxComponent implements OnInit, OnDestroy
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
.subscribe(({matchingAliases}) =>
{
// Set the drawerMode and drawerOpened if the given breakpoint is active
if ( matchingAliases.includes('md') )

View File

@@ -8,58 +8,58 @@ export const labelColors = [
'blue',
'indigo',
'purple',
'pink'
'pink',
];
export const labelColorDefs = {
gray : {
text : 'text-gray-500',
bg : 'bg-gray-500',
combined: 'text-gray-800 bg-gray-100'
combined: 'text-gray-800 bg-gray-100',
},
red : {
text : 'text-red-500',
bg : 'bg-red-500',
combined: 'text-red-800 bg-red-100'
combined: 'text-red-800 bg-red-100',
},
orange: {
text : 'text-orange-500',
bg : 'bg-orange-500',
combined: 'text-orange-800 bg-orange-100'
combined: 'text-orange-800 bg-orange-100',
},
yellow: {
text : 'text-yellow-500',
bg : 'bg-yellow-500',
combined: 'text-yellow-800 bg-yellow-100'
combined: 'text-yellow-800 bg-yellow-100',
},
green : {
text : 'text-green-500',
bg : 'bg-green-500',
combined: 'text-green-800 bg-green-100'
combined: 'text-green-800 bg-green-100',
},
teal : {
text : 'text-teal-500',
bg : 'bg-teal-500',
combined: 'text-teal-800 bg-teal-100'
combined: 'text-teal-800 bg-teal-100',
},
blue : {
text : 'text-blue-500',
bg : 'bg-blue-500',
combined: 'text-blue-800 bg-blue-100'
combined: 'text-blue-800 bg-blue-100',
},
indigo: {
text : 'text-indigo-500',
bg : 'bg-indigo-500',
combined: 'text-indigo-800 bg-indigo-100'
combined: 'text-indigo-800 bg-indigo-100',
},
purple: {
text : 'text-purple-500',
bg : 'bg-purple-500',
combined: 'text-purple-800 bg-purple-100'
combined: 'text-purple-800 bg-purple-100',
},
pink : {
text : 'text-pink-500',
bg : 'bg-pink-500',
combined: 'text-pink-800 bg-pink-100'
}
combined: 'text-pink-800 bg-pink-100',
},
};

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
@@ -9,34 +9,21 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRippleModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { QuillModule } from 'ngx-quill';
import { FuseFindByKeyPipeModule } from '@fuse/pipes/find-by-key';
import { FuseNavigationModule } from '@fuse/components/navigation';
import { FuseScrollbarModule } from '@fuse/directives/scrollbar';
import { FuseScrollResetModule } from '@fuse/directives/scroll-reset';
import { SharedModule } from 'app/shared/shared.module';
import { MailboxComponent } from 'app/modules/admin/apps/mailbox/mailbox.component';
import { RouterModule } from '@angular/router';
import { MailboxComposeComponent } from 'app/modules/admin/apps/mailbox/compose/compose.component';
import { MailboxDetailsComponent } from 'app/modules/admin/apps/mailbox/details/details.component';
import { MailboxEmptyDetailsComponent } from 'app/modules/admin/apps/mailbox/empty-details/empty-details.component';
import { MailboxListComponent } from 'app/modules/admin/apps/mailbox/list/list.component';
import { MailboxComponent } from 'app/modules/admin/apps/mailbox/mailbox.component';
import { mailboxRoutes } from 'app/modules/admin/apps/mailbox/mailbox.routing';
import { MailboxSettingsComponent } from 'app/modules/admin/apps/mailbox/settings/settings.component';
import { MailboxSidebarComponent } from 'app/modules/admin/apps/mailbox/sidebar/sidebar.component';
import { mailboxRoutes } from 'app/modules/admin/apps/mailbox/mailbox.routing';
import { QuillModule } from 'ngx-quill';
@NgModule({
declarations: [
MailboxComponent,
MailboxComposeComponent,
MailboxDetailsComponent,
MailboxEmptyDetailsComponent,
MailboxListComponent,
MailboxSettingsComponent,
MailboxSidebarComponent
],
imports: [
RouterModule.forChild(mailboxRoutes),
MatButtonModule,
@@ -52,12 +39,14 @@ import { mailboxRoutes } from 'app/modules/admin/apps/mailbox/mailbox.routing';
MatSelectModule,
MatSidenavModule,
QuillModule.forRoot(),
FuseFindByKeyPipeModule,
FuseNavigationModule,
FuseScrollbarModule,
FuseScrollResetModule,
SharedModule,
]
MailboxComponent,
MailboxComposeComponent,
MailboxDetailsComponent,
MailboxEmptyDetailsComponent,
MailboxListComponent,
MailboxSettingsComponent,
MailboxSidebarComponent,
],
})
export class MailboxModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, finalize, forkJoin, Observable, throwError } from 'rxjs';
import { MailboxService } from 'app/modules/admin/apps/mailbox/mailbox.service';
import { Mail, MailFilter, MailFolder, MailLabel } from 'app/modules/admin/apps/mailbox/mailbox.types';
import { catchError, finalize, forkJoin, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class MailboxFoldersResolver implements Resolve<any>
{
@@ -33,7 +33,7 @@ export class MailboxFoldersResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class MailboxFiltersResolver implements Resolve<any>
{
@@ -61,7 +61,7 @@ export class MailboxFiltersResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class MailboxLabelsResolver implements Resolve<any>
{
@@ -89,7 +89,7 @@ export class MailboxLabelsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class MailboxMailsResolver implements Resolve<any>
{
@@ -98,7 +98,7 @@ export class MailboxMailsResolver implements Resolve<any>
*/
constructor(
private _mailboxService: MailboxService,
private _router: Router
private _router: Router,
)
{
}
@@ -152,7 +152,8 @@ export class MailboxMailsResolver implements Resolve<any>
// Fork join all the sources
return forkJoin(sources)
.pipe(
finalize(() => {
finalize(() =>
{
// If there is no selected mail, reset the mail every
// time mail list changes. This will ensure that the
@@ -176,7 +177,8 @@ export class MailboxMailsResolver implements Resolve<any>
}),
// Error here means the requested page is not available
catchError((error) => {
catchError((error) =>
{
// Log the error
console.error(error.message);
@@ -189,13 +191,13 @@ export class MailboxMailsResolver implements Resolve<any>
// Throw an error
return throwError(error);
})
}),
);
}
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class MailboxMailResolver implements Resolve<any>
{
@@ -204,7 +206,7 @@ export class MailboxMailResolver implements Resolve<any>
*/
constructor(
private _mailboxService: MailboxService,
private _router: Router
private _router: Router,
)
{
}
@@ -222,24 +224,25 @@ export class MailboxMailResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Mail>
{
return this._mailboxService.getMailById(route.paramMap.get('id'))
.pipe(
// Error here means the requested mail is either
// not available on the requested page or not
// available at all
catchError((error) => {
.pipe(
// Error here means the requested mail is either
// not available on the requested page or not
// available at all
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}

View File

@@ -1,18 +1,19 @@
import { ActivatedRouteSnapshot, Route, UrlMatchResult, UrlSegment } from '@angular/router';
import { isEqual } from 'lodash-es';
import { MailboxDetailsComponent } from 'app/modules/admin/apps/mailbox/details/details.component';
import { MailboxEmptyDetailsComponent } from 'app/modules/admin/apps/mailbox/empty-details/empty-details.component';
import { MailboxListComponent } from 'app/modules/admin/apps/mailbox/list/list.component';
import { MailboxComponent } from 'app/modules/admin/apps/mailbox/mailbox.component';
import { MailboxFiltersResolver, MailboxFoldersResolver, MailboxLabelsResolver, MailboxMailResolver, MailboxMailsResolver } from 'app/modules/admin/apps/mailbox/mailbox.resolvers';
import { MailboxListComponent } from 'app/modules/admin/apps/mailbox/list/list.component';
import { MailboxDetailsComponent } from 'app/modules/admin/apps/mailbox/details/details.component';
import { MailboxSettingsComponent } from 'app/modules/admin/apps/mailbox/settings/settings.component';
import { MailboxEmptyDetailsComponent } from 'app/modules/admin/apps/mailbox/empty-details/empty-details.component';
import { isEqual } from 'lodash-es';
/**
* Mailbox custom route matcher
*
* @param url
*/
export const mailboxRouteMatcher: (url: UrlSegment[]) => UrlMatchResult = (url: UrlSegment[]) => {
export const mailboxRouteMatcher: (url: UrlSegment[]) => UrlMatchResult = (url: UrlSegment[]) =>
{
// Prepare consumed url and positional parameters
let consumed = url;
@@ -51,11 +52,12 @@ export const mailboxRouteMatcher: (url: UrlSegment[]) => UrlMatchResult = (url:
return {
consumed,
posParams
posParams,
};
};
export const mailboxRunGuardsAndResolvers: (from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean = (from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => {
export const mailboxRunGuardsAndResolvers: (from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean = (from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) =>
{
// If we are navigating from mail to mails, meaning there is an id in
// from's deepest first child and there isn't one in the to's, we will
@@ -85,11 +87,13 @@ export const mailboxRunGuardsAndResolvers: (from: ActivatedRouteSnapshot, to: Ac
const fromParams = {};
const toParams = {};
from.paramMap.keys.forEach((key) => {
from.paramMap.keys.forEach((key) =>
{
fromParams[key] = from.paramMap.get(key);
});
to.paramMap.keys.forEach((key) => {
to.paramMap.keys.forEach((key) =>
{
toParams[key] = to.paramMap.get(key);
});
@@ -106,22 +110,22 @@ export const mailboxRoutes: Route[] = [
{
path : '',
redirectTo: 'inbox/1',
pathMatch : 'full'
pathMatch : 'full',
},
{
path : 'filter/:filter',
redirectTo: 'filter/:filter/1',
pathMatch : 'full'
pathMatch : 'full',
},
{
path : 'label/:label',
redirectTo: 'label/:label/1',
pathMatch : 'full'
pathMatch : 'full',
},
{
path : ':folder',
redirectTo: ':folder/1',
pathMatch : 'full'
pathMatch : 'full',
},
{
path : '',
@@ -129,7 +133,7 @@ export const mailboxRoutes: Route[] = [
resolve : {
filters: MailboxFiltersResolver,
folders: MailboxFoldersResolver,
labels : MailboxLabelsResolver
labels : MailboxLabelsResolver,
},
children : [
{
@@ -137,27 +141,27 @@ export const mailboxRoutes: Route[] = [
matcher : mailboxRouteMatcher,
runGuardsAndResolvers: mailboxRunGuardsAndResolvers,
resolve : {
mails: MailboxMailsResolver
mails: MailboxMailsResolver,
},
children : [
{
path : '',
pathMatch: 'full',
component: MailboxEmptyDetailsComponent
component: MailboxEmptyDetailsComponent,
},
{
path : ':id',
component: MailboxDetailsComponent,
resolve : {
mail: MailboxMailResolver
}
}
]
mail: MailboxMailResolver,
},
},
],
},
{
path : 'settings',
component: MailboxSettingsComponent
}
]
}
component: MailboxSettingsComponent,
},
],
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Mail, MailCategory, MailFilter, MailFolder, MailLabel } from 'app/modules/admin/apps/mailbox/mailbox.types';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class MailboxService
{
@@ -103,9 +103,10 @@ export class MailboxService
getFilters(): Observable<any>
{
return this._httpClient.get<MailFilter[]>('api/apps/mailbox/filters').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._filters.next(response);
})
}),
);
}
@@ -115,9 +116,10 @@ export class MailboxService
getFolders(): Observable<any>
{
return this._httpClient.get<MailFolder[]>('api/apps/mailbox/folders').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._folders.next(response);
})
}),
);
}
@@ -127,9 +129,10 @@ export class MailboxService
getLabels(): Observable<any>
{
return this._httpClient.get<MailLabel[]>('api/apps/mailbox/labels').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._labels.next(response);
})
}),
);
}
@@ -144,30 +147,32 @@ export class MailboxService
return this._httpClient.get<Mail[]>('api/apps/mailbox/mails', {
params: {
filter,
page
}
page,
},
}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._category.next({
type: 'filter',
name: filter
name: filter,
});
this._mails.next(response.mails);
this._pagination.next(response.pagination);
this._mailsLoading.next(false);
}),
switchMap((response) => {
switchMap((response) =>
{
if ( response.mails === null )
{
return throwError({
message : 'Requested page is not available!',
pagination: response.pagination
pagination: response.pagination,
});
}
return of(response);
})
}),
);
}
@@ -182,30 +187,32 @@ export class MailboxService
return this._httpClient.get<Mail[]>('api/apps/mailbox/mails', {
params: {
folder,
page
}
page,
},
}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._category.next({
type: 'folder',
name: folder
name: folder,
});
this._mails.next(response.mails);
this._pagination.next(response.pagination);
this._mailsLoading.next(false);
}),
switchMap((response) => {
switchMap((response) =>
{
if ( response.mails === null )
{
return throwError({
message : 'Requested page is not available!',
pagination: response.pagination
pagination: response.pagination,
});
}
return of(response);
})
}),
);
}
@@ -220,30 +227,32 @@ export class MailboxService
return this._httpClient.get<Mail[]>('api/apps/mailbox/mails', {
params: {
label,
page
}
page,
},
}).pipe(
tap((response: any) => {
tap((response: any) =>
{
this._category.next({
type: 'label',
name: label
name: label,
});
this._mails.next(response.mails);
this._pagination.next(response.pagination);
this._mailsLoading.next(false);
}),
switchMap((response) => {
switchMap((response) =>
{
if ( response.mails === null )
{
return throwError({
message : 'Requested page is not available!',
pagination: response.pagination
pagination: response.pagination,
});
}
return of(response);
})
}),
);
}
@@ -254,7 +263,8 @@ export class MailboxService
{
return this._mails.pipe(
take(1),
map((mails) => {
map((mails) =>
{
// Find the mail
const mail = mails.find(item => item.id === id) || null;
@@ -265,7 +275,8 @@ export class MailboxService
// Return the mail
return mail;
}),
switchMap((mail) => {
switchMap((mail) =>
{
if ( !mail )
{
@@ -273,7 +284,7 @@ export class MailboxService
}
return of(mail);
})
}),
);
}
@@ -287,14 +298,15 @@ export class MailboxService
{
return this._httpClient.patch('api/apps/mailbox/mail', {
id,
mail
mail,
}).pipe(
tap(() => {
tap(() =>
{
// Re-fetch the folders on mail update
// to get the updated counts on the sidebar
this.getFolders().subscribe();
})
}),
);
}
@@ -305,9 +317,10 @@ export class MailboxService
{
return of(true).pipe(
take(1),
tap(() => {
tap(() =>
{
this._mail.next(null);
})
}),
);
}
@@ -321,15 +334,16 @@ export class MailboxService
return this.labels$.pipe(
take(1),
switchMap(labels => this._httpClient.post<MailLabel>('api/apps/mailbox/label', {label}).pipe(
map((newLabel) => {
map((newLabel) =>
{
// Update the labels with the new label
this._labels.next([...labels, newLabel]);
// Return the new label
return newLabel;
})
))
}),
)),
);
}
@@ -345,9 +359,10 @@ export class MailboxService
take(1),
switchMap(labels => this._httpClient.patch<MailLabel>('api/apps/mailbox/label', {
id,
label
label,
}).pipe(
map((updatedLabel: any) => {
map((updatedLabel: any) =>
{
// Find the index of the updated label within the labels
const index = labels.findIndex(item => item.id === id);
@@ -360,8 +375,8 @@ export class MailboxService
// Return the updated label
return updatedLabel;
})
))
}),
)),
);
}
@@ -375,7 +390,8 @@ export class MailboxService
return this.labels$.pipe(
take(1),
switchMap(labels => this._httpClient.delete('api/apps/mailbox/label', {params: {id}}).pipe(
map((isDeleted: any) => {
map((isDeleted: any) =>
{
// Find the index of the deleted label within the labels
const index = labels.findIndex(item => item.id === id);
@@ -388,8 +404,8 @@ export class MailboxService
// Return the deleted status
return isDeleted;
})
))
}),
)),
);
}
}

View File

@@ -1,15 +1,24 @@
import { NgClass, NgFor, NgIf } from '@angular/common';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { debounceTime, take } from 'rxjs';
import { FormsModule, ReactiveFormsModule, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatOptionModule } from '@angular/material/core';
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 { MailboxComponent } from 'app/modules/admin/apps/mailbox/mailbox.component';
import { labelColorDefs, labelColors } from 'app/modules/admin/apps/mailbox/mailbox.constants';
import { MailboxService } from 'app/modules/admin/apps/mailbox/mailbox.service';
import { MailLabel } from 'app/modules/admin/apps/mailbox/mailbox.types';
import { labelColorDefs, labelColors } from 'app/modules/admin/apps/mailbox/mailbox.constants';
import { debounceTime, take } from 'rxjs';
@Component({
selector : 'mailbox-settings',
templateUrl : './settings.component.html',
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatButtonModule, MatIconModule, FormsModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatSelectModule, NgClass, NgFor, MatOptionModule, NgIf],
})
export class MailboxSettingsComponent implements OnInit
{
@@ -24,7 +33,7 @@ export class MailboxSettingsComponent implements OnInit
constructor(
public mailboxComponent: MailboxComponent,
private _formBuilder: UntypedFormBuilder,
private _mailboxService: MailboxService
private _mailboxService: MailboxService,
)
{
}
@@ -43,27 +52,29 @@ export class MailboxSettingsComponent implements OnInit
labels : this._formBuilder.array([]),
newLabel: this._formBuilder.group({
title: ['', Validators.required],
color: ['orange']
})
color: ['orange'],
}),
});
// Labels
this._mailboxService.labels$
.pipe(take(1))
.subscribe((labels: MailLabel[]) => {
.subscribe((labels: MailLabel[]) =>
{
// Get the labels
this.labels = labels;
// Iterate through the labels
labels.forEach((label) => {
labels.forEach((label) =>
{
// Create a label form group
const labelFormGroup = this._formBuilder.group({
id : [label.id],
title: [label.title, Validators.required],
slug : [label.slug],
color: [label.color]
color: [label.color],
});
// Add the label form group to the labels form array
@@ -74,7 +85,8 @@ export class MailboxSettingsComponent implements OnInit
// Update labels when there is a value change
this.labelsForm.get('labels').valueChanges
.pipe(debounceTime(500))
.subscribe(() => {
.subscribe(() =>
{
this.updateLabels();
});
}
@@ -89,14 +101,15 @@ export class MailboxSettingsComponent implements OnInit
addLabel(): void
{
// Add label to the server
this._mailboxService.addLabel(this.labelsForm.get('newLabel').value).subscribe((addedLabel) => {
this._mailboxService.addLabel(this.labelsForm.get('newLabel').value).subscribe((addedLabel) =>
{
// Push the new label to the labels form array
(this.labelsForm.get('labels') as UntypedFormArray).push(this._formBuilder.group({
id : [addedLabel.id],
title: [addedLabel.title, Validators.required],
slug : [addedLabel.slug],
color: [addedLabel.color]
color: [addedLabel.color],
}));
// Reset the new label form
@@ -129,7 +142,8 @@ export class MailboxSettingsComponent implements OnInit
updateLabels(): void
{
// Iterate through the labels form array controls
(this.labelsForm.get('labels') as UntypedFormArray).controls.forEach((labelFormGroup) => {
(this.labelsForm.get('labels') as UntypedFormArray).controls.forEach((labelFormGroup) =>
{
// If the label has been edited...
if ( labelFormGroup.dirty )

View File

@@ -1,17 +1,21 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { Subject, takeUntil } from 'rxjs';
import { MatIconModule } from '@angular/material/icon';
import { FuseNavigationItem, FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
import { MailboxService } from 'app/modules/admin/apps/mailbox/mailbox.service';
import { MailboxComposeComponent } from 'app/modules/admin/apps/mailbox/compose/compose.component';
import { labelColorDefs } from 'app/modules/admin/apps/mailbox/mailbox.constants';
import { MailboxService } from 'app/modules/admin/apps/mailbox/mailbox.service';
import { MailFilter, MailFolder, MailLabel } from 'app/modules/admin/apps/mailbox/mailbox.types';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'mailbox-sidebar',
templateUrl : './sidebar.component.html',
styleUrls : ['./sidebar.component.scss'],
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone : true,
imports : [MatButtonModule, MatIconModule, FuseVerticalNavigationComponent],
})
export class MailboxSidebarComponent implements OnInit, OnDestroy
{
@@ -31,7 +35,7 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
constructor(
private _mailboxService: MailboxService,
private _matDialog: MatDialog,
private _fuseNavigationService: FuseNavigationService
private _fuseNavigationService: FuseNavigationService,
)
{
}
@@ -48,7 +52,8 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
// Filters
this._mailboxService.filters$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((filters: MailFilter[]) => {
.subscribe((filters: MailFilter[]) =>
{
this.filters = filters;
// Generate menu links
@@ -58,7 +63,8 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
// Folders
this._mailboxService.folders$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((folders: MailFolder[]) => {
.subscribe((folders: MailFolder[]) =>
{
this.folders = folders;
// Generate menu links
@@ -71,7 +77,8 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
// Labels
this._mailboxService.labels$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((labels: MailLabel[]) => {
.subscribe((labels: MailLabel[]) =>
{
this.labels = labels;
// Generate menu links
@@ -105,9 +112,10 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
const dialogRef = this._matDialog.open(MailboxComposeComponent);
dialogRef.afterClosed()
.subscribe((result) => {
console.log('Compose dialog was closed!');
});
.subscribe((result) =>
{
console.log('Compose dialog was closed!');
});
}
// -----------------------------------------------------------------------------------------------------
@@ -125,7 +133,8 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
this._foldersMenuData = [];
// Iterate through the folders
this.folders.forEach((folder) => {
this.folders.forEach((folder) =>
{
// Generate menu item for the folder
const menuItem: FuseNavigationItem = {
@@ -133,7 +142,7 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
title: folder.title,
type : 'basic',
icon : folder.icon,
link : '/apps/mailbox/' + folder.slug
link : '/apps/mailbox/' + folder.slug,
};
// If the count is available and is bigger than zero...
@@ -141,7 +150,7 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
{
// Add the count as a badge
menuItem['badge'] = {
title: folder.count + ''
title: folder.count + '',
};
}
@@ -164,7 +173,8 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
this._filtersMenuData = [];
// Iterate through the filters
this.filters.forEach((filter) => {
this.filters.forEach((filter) =>
{
// Generate menu item for the filter
this._filtersMenuData.push({
@@ -172,7 +182,7 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
title: filter.title,
type : 'basic',
icon : filter.icon,
link : '/apps/mailbox/filter/' + filter.slug
link : '/apps/mailbox/filter/' + filter.slug,
});
});
@@ -191,7 +201,8 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
this._labelsMenuData = [];
// Iterate through the labels
this.labels.forEach((label) => {
this.labels.forEach((label) =>
{
// Generate menu item for the label
this._labelsMenuData.push({
@@ -200,9 +211,9 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
type : 'basic',
icon : 'heroicons_outline:tag',
classes: {
icon: labelColorDefs[label.color].text
icon: labelColorDefs[label.color].text,
},
link : '/apps/mailbox/label/' + label.slug
link : '/apps/mailbox/label/' + label.slug,
});
});
@@ -222,7 +233,7 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
title: 'Settings',
type : 'basic',
icon : 'heroicons_outline:cog',
link : '/apps/mailbox/settings'
link : '/apps/mailbox/settings',
});
// Update the menu data
@@ -241,27 +252,27 @@ export class MailboxSidebarComponent implements OnInit, OnDestroy
title : 'MAILBOXES',
type : 'group',
children: [
...this._foldersMenuData
]
...this._foldersMenuData,
],
},
{
title : 'FILTERS',
type : 'group',
children: [
...this._filtersMenuData
]
...this._filtersMenuData,
],
},
{
title : 'LABELS',
type : 'group',
children: [
...this._labelsMenuData
]
...this._labelsMenuData,
],
},
{
type: 'spacer'
type: 'spacer',
},
...this._otherMenuData
...this._otherMenuData,
];
}

View File

@@ -1,14 +1,24 @@
import { TextFieldModule } from '@angular/cdk/text-field';
import { AsyncPipe, NgClass, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { debounceTime, map, Observable, of, Subject, switchMap, takeUntil } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { NotesService } from 'app/modules/admin/apps/notes/notes.service';
import { Label, Note, Task } from 'app/modules/admin/apps/notes/notes.types';
import { debounceTime, map, Observable, of, Subject, switchMap, takeUntil } from 'rxjs';
@Component({
selector : 'notes-details',
templateUrl : './details.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [NgIf, MatButtonModule, MatIconModule, FormsModule, TextFieldModule, NgFor, MatCheckboxModule, NgClass, MatRippleModule, MatMenuModule, MatDialogModule, AsyncPipe],
})
export class NotesDetailsComponent implements OnInit, OnDestroy
{
@@ -25,7 +35,7 @@ export class NotesDetailsComponent implements OnInit, OnDestroy
private _changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) private _data: { note: Note },
private _notesService: NotesService,
private _matDialogRef: MatDialogRef<NotesDetailsComponent>
private _matDialogRef: MatDialogRef<NotesDetailsComponent>,
)
{
}
@@ -62,7 +72,7 @@ export class NotesDetailsComponent implements OnInit, OnDestroy
labels : [],
archived : false,
createdAt: null,
updatedAt: null
updatedAt: null,
};
this.note$ = of(note);
@@ -77,7 +87,8 @@ export class NotesDetailsComponent implements OnInit, OnDestroy
takeUntil(this._unsubscribeAll),
debounceTime(500),
switchMap(note => this._notesService.updateNote(note)))
.subscribe(() => {
.subscribe(() =>
{
// Mark for check
this._changeDetectorRef.markForCheck();
@@ -106,7 +117,8 @@ export class NotesDetailsComponent implements OnInit, OnDestroy
createNote(note: Note): void
{
this._notesService.createNote(note).pipe(
map(() => {
map(() =>
{
// Get the note
this.note$ = this._notesService.note$;
})).subscribe();
@@ -135,7 +147,8 @@ export class NotesDetailsComponent implements OnInit, OnDestroy
return;
}
this._readAsDataURL(file).then((data) => {
this._readAsDataURL(file).then((data) =>
{
// Update the image
note.image = data;
@@ -287,7 +300,8 @@ export class NotesDetailsComponent implements OnInit, OnDestroy
deleteNote(note: Note): void
{
this._notesService.deleteNote(note)
.subscribe((isDeleted) => {
.subscribe((isDeleted) =>
{
// Return if the note wasn't deleted...
if ( !isDeleted )
@@ -323,18 +337,21 @@ export class NotesDetailsComponent implements OnInit, OnDestroy
private _readAsDataURL(file: File): Promise<any>
{
// Return a new promise
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) =>
{
// Create a new reader
const reader = new FileReader();
// Resolve the promise on success
reader.onload = (): void => {
reader.onload = (): void =>
{
resolve(reader.result);
};
// Reject the promise on error
reader.onerror = (e): void => {
reader.onerror = (e): void =>
{
reject(e);
};

View File

@@ -1,13 +1,22 @@
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { debounceTime, filter, Observable, Subject, switchMap, takeUntil } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { NotesService } from 'app/modules/admin/apps/notes/notes.service';
import { Label } from 'app/modules/admin/apps/notes/notes.types';
import { debounceTime, filter, Observable, Subject, switchMap, takeUntil } from 'rxjs';
@Component({
selector : 'notes-labels',
templateUrl : './labels.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, MatDialogModule, MatIconModule, MatFormFieldModule, MatInputModule, NgIf, NgFor, FormsModule, AsyncPipe],
})
export class NotesLabelsComponent implements OnInit, OnDestroy
{
@@ -21,7 +30,7 @@ export class NotesLabelsComponent implements OnInit, OnDestroy
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _notesService: NotesService
private _notesService: NotesService,
)
{
}
@@ -45,7 +54,8 @@ export class NotesLabelsComponent implements OnInit, OnDestroy
debounceTime(500),
filter(label => label.title.trim() !== ''),
switchMap(label => this._notesService.updateLabel(label)))
.subscribe(() => {
.subscribe(() =>
{
// Mark for check
this._changeDetectorRef.markForCheck();
@@ -91,7 +101,8 @@ export class NotesLabelsComponent implements OnInit, OnDestroy
*/
deleteLabel(id: string): void
{
this._notesService.deleteLabel(id).subscribe(() => {
this._notesService.deleteLabel(id).subscribe(() =>
{
// Mark for check
this._changeDetectorRef.markForCheck();

View File

@@ -1,18 +1,28 @@
import { AsyncPipe, NgClass, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatRippleModule } from '@angular/material/core';
import { MatDialog } from '@angular/material/dialog';
import { BehaviorSubject, combineLatest, distinctUntilChanged, map, Observable, Subject, takeUntil } from 'rxjs';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSidenavModule } from '@angular/material/sidenav';
import { FuseMasonryComponent } from '@fuse/components/masonry';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { NotesDetailsComponent } from 'app/modules/admin/apps/notes/details/details.component';
import { NotesLabelsComponent } from 'app/modules/admin/apps/notes/labels/labels.component';
import { NotesService } from 'app/modules/admin/apps/notes/notes.service';
import { Label, Note } from 'app/modules/admin/apps/notes/notes.types';
import { cloneDeep } from 'lodash-es';
import { BehaviorSubject, combineLatest, distinctUntilChanged, map, Observable, Subject, takeUntil } from 'rxjs';
@Component({
selector : 'notes-list',
templateUrl : './list.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatSidenavModule, MatRippleModule, NgClass, MatIconModule, NgIf, NgFor, MatButtonModule, MatFormFieldModule, MatInputModule, FuseMasonryComponent, AsyncPipe],
})
export class NotesListComponent implements OnInit, OnDestroy
{
@@ -34,7 +44,7 @@ export class NotesListComponent implements OnInit, OnDestroy
private _changeDetectorRef: ChangeDetectorRef,
private _fuseMediaWatcherService: FuseMediaWatcherService,
private _matDialog: MatDialog,
private _notesService: NotesService
private _notesService: NotesService,
)
{
}
@@ -70,7 +80,8 @@ export class NotesListComponent implements OnInit, OnDestroy
// Get notes
this.notes$ = combineLatest([this._notesService.notes$, this.filter$, this.searchQuery$]).pipe(
distinctUntilChanged(),
map(([notes, filter, searchQuery]) => {
map(([notes, filter, searchQuery]) =>
{
if ( !notes || !notes.length )
{
@@ -105,13 +116,14 @@ export class NotesListComponent implements OnInit, OnDestroy
}
return filteredNotes;
})
}),
);
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
.subscribe(({matchingAliases}) =>
{
// Set the drawerMode and drawerOpened if the given breakpoint is active
if ( matchingAliases.includes('lg') )
@@ -178,8 +190,8 @@ export class NotesListComponent implements OnInit, OnDestroy
this._matDialog.open(NotesDetailsComponent, {
autoFocus: false,
data : {
note: {}
}
note: {},
},
});
}
@@ -199,8 +211,8 @@ export class NotesListComponent implements OnInit, OnDestroy
this._matDialog.open(NotesDetailsComponent, {
autoFocus: false,
data : {
note: cloneDeep(note)
}
note: cloneDeep(note),
},
});
}

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'notes',
templateUrl : './notes.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class NotesComponent
{

View File

@@ -1,30 +1,23 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatRippleModule } from '@angular/material/core';
import { MatSidenavModule } from '@angular/material/sidenav';
import { FuseMasonryModule } from '@fuse/components/masonry';
import { SharedModule } from 'app/shared/shared.module';
import { NotesComponent } from 'app/modules/admin/apps/notes/notes.component';
import { RouterModule } from '@angular/router';
import { NotesDetailsComponent } from 'app/modules/admin/apps/notes/details/details.component';
import { NotesListComponent } from 'app/modules/admin/apps/notes/list/list.component';
import { NotesLabelsComponent } from 'app/modules/admin/apps/notes/labels/labels.component';
import { NotesListComponent } from 'app/modules/admin/apps/notes/list/list.component';
import { NotesComponent } from 'app/modules/admin/apps/notes/notes.component';
import { notesRoutes } from 'app/modules/admin/apps/notes/notes.routing';
@NgModule({
declarations: [
NotesComponent,
NotesDetailsComponent,
NotesListComponent,
NotesLabelsComponent
],
imports : [
imports: [
RouterModule.forChild(notesRoutes),
MatButtonModule,
MatCheckboxModule,
@@ -35,9 +28,11 @@ import { notesRoutes } from 'app/modules/admin/apps/notes/notes.routing';
MatMenuModule,
MatRippleModule,
MatSidenavModule,
FuseMasonryModule,
SharedModule
]
NotesComponent,
NotesDetailsComponent,
NotesListComponent,
NotesLabelsComponent,
],
})
export class NotesModule
{

View File

@@ -1,6 +1,6 @@
import { Route } from '@angular/router';
import { NotesComponent } from 'app/modules/admin/apps/notes/notes.component';
import { NotesListComponent } from 'app/modules/admin/apps/notes/list/list.component';
import { NotesComponent } from 'app/modules/admin/apps/notes/notes.component';
export const notesRoutes: Route[] = [
{
@@ -9,8 +9,8 @@ export const notesRoutes: Route[] = [
children : [
{
path : '',
component: NotesListComponent
}
]
}
component: NotesListComponent,
},
],
},
];

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Label, Note } from 'app/modules/admin/apps/notes/notes.types';
import { cloneDeep } from 'lodash-es';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class NotesService
{
@@ -59,9 +59,10 @@ export class NotesService
getLabels(): Observable<Label[]>
{
return this._httpClient.get<Label[]>('api/apps/notes/labels').pipe(
tap((response: Label[]) => {
tap((response: Label[]) =>
{
this._labels.next(response);
})
}),
);
}
@@ -73,11 +74,12 @@ export class NotesService
addLabel(title: string): Observable<Label[]>
{
return this._httpClient.post<Label[]>('api/apps/notes/labels', {title}).pipe(
tap((labels) => {
tap((labels) =>
{
// Update the labels
this._labels.next(labels);
})
}),
);
}
@@ -89,14 +91,15 @@ export class NotesService
updateLabel(label: Label): Observable<Label[]>
{
return this._httpClient.patch<Label[]>('api/apps/notes/labels', {label}).pipe(
tap((labels) => {
tap((labels) =>
{
// Update the notes
this.getNotes().subscribe();
// Update the labels
this._labels.next(labels);
})
}),
);
}
@@ -108,14 +111,15 @@ export class NotesService
deleteLabel(id: string): Observable<Label[]>
{
return this._httpClient.delete<Label[]>('api/apps/notes/labels', {params: {id}}).pipe(
tap((labels) => {
tap((labels) =>
{
// Update the notes
this.getNotes().subscribe();
// Update the labels
this._labels.next(labels);
})
}),
);
}
@@ -125,9 +129,10 @@ export class NotesService
getNotes(): Observable<Note[]>
{
return this._httpClient.get<Note[]>('api/apps/notes/all').pipe(
tap((response: Note[]) => {
tap((response: Note[]) =>
{
this._notes.next(response);
})
}),
);
}
@@ -138,7 +143,8 @@ export class NotesService
{
return this._notes.pipe(
take(1),
map((notes) => {
map((notes) =>
{
// Find within the folders and files
const note = notes.find(value => value.id === id) || null;
@@ -149,7 +155,8 @@ export class NotesService
// Return the note
return note;
}),
switchMap((note) => {
switchMap((note) =>
{
if ( !note )
{
@@ -157,7 +164,7 @@ export class NotesService
}
return of(note);
})
}),
);
}
@@ -171,9 +178,9 @@ export class NotesService
{
return this._httpClient.post<Note>('api/apps/notes/tasks', {
note,
task
task,
}).pipe(switchMap(() => this.getNotes().pipe(
switchMap(() => this.getNoteById(note.id))
switchMap(() => this.getNoteById(note.id)),
)));
}
@@ -187,8 +194,8 @@ export class NotesService
return this._httpClient.post<Note>('api/apps/notes', {note}).pipe(
switchMap(response => this.getNotes().pipe(
switchMap(() => this.getNoteById(response.id).pipe(
map(() => response)
))
map(() => response),
)),
)));
}
@@ -209,11 +216,12 @@ export class NotesService
}
return this._httpClient.patch<Note>('api/apps/notes', {updatedNote}).pipe(
tap((response) => {
tap((response) =>
{
// Update the notes
this.getNotes().subscribe();
})
}),
);
}
@@ -225,14 +233,15 @@ export class NotesService
deleteNote(note: Note): Observable<boolean>
{
return this._httpClient.delete<boolean>('api/apps/notes', {params: {id: note.id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Update the notes
this.getNotes().subscribe();
// Return the deleted status
return isDeleted;
})
}),
);
}
}

View File

@@ -1,12 +1,17 @@
import { CdkTextareaAutosize, TextFieldModule } from '@angular/cdk/text-field';
import { NgClass } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector : 'scrumboard-board-add-card',
templateUrl : './add-card.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, NgClass, MatIconModule, FormsModule, ReactiveFormsModule, TextFieldModule],
})
export class ScrumboardBoardAddCardComponent implements OnInit
{
@@ -23,7 +28,7 @@ export class ScrumboardBoardAddCardComponent implements OnInit
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _formBuilder: UntypedFormBuilder
private _formBuilder: UntypedFormBuilder,
)
{
}
@@ -39,7 +44,7 @@ export class ScrumboardBoardAddCardComponent implements OnInit
{
// Initialize the new list form
this.form = this._formBuilder.group({
title: ['']
title: [''],
});
}
@@ -69,7 +74,8 @@ export class ScrumboardBoardAddCardComponent implements OnInit
this.form.get('title').setValue('');
// Reset the size of the textarea
setTimeout(() => {
setTimeout(() =>
{
this.titleInput.nativeElement.value = '';
this.titleAutosize.reset();
});

View File

@@ -1,11 +1,16 @@
import { NgClass } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector : 'scrumboard-board-add-list',
templateUrl : './add-list.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, NgClass, MatIconModule, FormsModule, ReactiveFormsModule],
})
export class ScrumboardBoardAddListComponent implements OnInit
{
@@ -21,7 +26,7 @@ export class ScrumboardBoardAddListComponent implements OnInit
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _formBuilder: UntypedFormBuilder
private _formBuilder: UntypedFormBuilder,
)
{
}
@@ -37,7 +42,7 @@ export class ScrumboardBoardAddListComponent implements OnInit
{
// Initialize the new list form
this.form = this._formBuilder.group({
title: ['']
title: [''],
});
}

View File

@@ -1,18 +1,28 @@
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, CdkDropListGroup, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { CdkScrollable } from '@angular/cdk/scrolling';
import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { Subject, takeUntil } from 'rxjs';
import { DateTime } from 'luxon';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { RouterLink, RouterOutlet } from '@angular/router';
import { FuseConfirmationService } from '@fuse/services/confirmation';
import { ScrumboardService } from 'app/modules/admin/apps/scrumboard/scrumboard.service';
import { Board, Card, List } from 'app/modules/admin/apps/scrumboard/scrumboard.models';
import { ScrumboardService } from 'app/modules/admin/apps/scrumboard/scrumboard.service';
import { DateTime } from 'luxon';
import { Subject, takeUntil } from 'rxjs';
import { ScrumboardBoardAddCardComponent } from './add-card/add-card.component';
import { ScrumboardBoardAddListComponent } from './add-list/add-list.component';
@Component({
selector : 'scrumboard-board',
templateUrl : './board.component.html',
styleUrls : ['./board.component.scss'],
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, RouterLink, MatIconModule, CdkScrollable, CdkDropList, CdkDropListGroup, NgFor, CdkDrag, CdkDragHandle, MatMenuModule, NgIf, NgClass, ScrumboardBoardAddCardComponent, ScrumboardBoardAddListComponent, RouterOutlet, DatePipe],
})
export class ScrumboardBoardComponent implements OnInit, OnDestroy
{
@@ -32,7 +42,7 @@ export class ScrumboardBoardComponent implements OnInit, OnDestroy
private _changeDetectorRef: ChangeDetectorRef,
private _formBuilder: UntypedFormBuilder,
private _fuseConfirmationService: FuseConfirmationService,
private _scrumboardService: ScrumboardService
private _scrumboardService: ScrumboardService,
)
{
}
@@ -48,13 +58,14 @@ export class ScrumboardBoardComponent implements OnInit, OnDestroy
{
// Initialize the list title form
this.listTitleForm = this._formBuilder.group({
title: ['']
title: [''],
});
// Get the board
this._scrumboardService.board$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((board: Board) => {
.subscribe((board: Board) =>
{
this.board = {...board};
// Mark for check
@@ -84,7 +95,8 @@ export class ScrumboardBoardComponent implements OnInit, OnDestroy
renameList(listTitleInput: HTMLElement): void
{
// Use timeout so it can wait for menu to close
setTimeout(() => {
setTimeout(() =>
{
listTitleInput.focus();
});
}
@@ -106,7 +118,7 @@ export class ScrumboardBoardComponent implements OnInit, OnDestroy
const list = new List({
boardId : this.board.id,
position: this.board.lists.length ? this.board.lists[this.board.lists.length - 1].position + this._positionStep : this._positionStep,
title : title
title : title,
});
// Save the list
@@ -155,13 +167,14 @@ export class ScrumboardBoardComponent implements OnInit, OnDestroy
message: 'Are you sure you want to delete this list and its cards? This action cannot be undone!',
actions: {
confirm: {
label: 'Delete'
}
}
label: 'Delete',
},
},
});
// Subscribe to the confirmation dialog closed action
confirmation.afterClosed().subscribe((result) => {
confirmation.afterClosed().subscribe((result) =>
{
// If the confirm button pressed...
if ( result === 'confirmed' )
@@ -183,7 +196,7 @@ export class ScrumboardBoardComponent implements OnInit, OnDestroy
boardId : this.board.id,
listId : list.id,
position: list.cards.length ? list.cards[list.cards.length - 1].position + this._positionStep : this._positionStep,
title : title
title : title,
});
// Save the card
@@ -304,7 +317,8 @@ export class ScrumboardBoardComponent implements OnInit, OnDestroy
if ( !Number.isInteger(currentItem.position) || currentItem.position >= this._maxPosition )
{
// Re-calculate all orders
items = items.map((value, index) => {
items = items.map((value, index) =>
{
value.position = (index + 1) * this._positionStep;
return value;
});

View File

@@ -1,14 +1,20 @@
import { CdkScrollable } from '@angular/cdk/scrolling';
import { NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { DateTime } from 'luxon';
import { MatIconModule } from '@angular/material/icon';
import { RouterLink } from '@angular/router';
import { Board } from 'app/modules/admin/apps/scrumboard/scrumboard.models';
import { ScrumboardService } from 'app/modules/admin/apps/scrumboard/scrumboard.service';
import { DateTime } from 'luxon';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'scrumboard-boards',
templateUrl : './boards.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [CdkScrollable, NgFor, RouterLink, MatIconModule, NgIf],
})
export class ScrumboardBoardsComponent implements OnInit, OnDestroy
{
@@ -22,7 +28,7 @@ export class ScrumboardBoardsComponent implements OnInit, OnDestroy
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _scrumboardService: ScrumboardService
private _scrumboardService: ScrumboardService,
)
{
}
@@ -39,7 +45,8 @@ export class ScrumboardBoardsComponent implements OnInit, OnDestroy
// Get the boards
this._scrumboardService.boards$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((boards: Board[]) => {
.subscribe((boards: Board[]) =>
{
this.boards = boards;
// Mark for check

View File

@@ -1,13 +1,14 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ScrumboardCardDetailsComponent } from 'app/modules/admin/apps/scrumboard/card/details/details.component';
import { ActivatedRoute, Router } from '@angular/router';
import { ScrumboardCardDetailsComponent } from 'app/modules/admin/apps/scrumboard/card/details/details.component';
@Component({
selector : 'scrumboard-card',
templateUrl : './card.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
})
export class ScrumboardCardComponent implements OnInit
{
@@ -17,7 +18,7 @@ export class ScrumboardCardComponent implements OnInit
constructor(
private _activatedRoute: ActivatedRoute,
private _matDialog: MatDialog,
private _router: Router
private _router: Router,
)
{
}
@@ -34,9 +35,10 @@ export class ScrumboardCardComponent implements OnInit
// Launch the modal
this._matDialog.open(ScrumboardCardDetailsComponent, {autoFocus: false})
.afterClosed()
.subscribe(() => {
.subscribe(() =>
{
// Go up twice because card routes are setup like this; "card/CARD_ID"
// Go up twice because card routes are set up like this; "card/CARD_ID"
this._router.navigate(['./../..'], {relativeTo: this._activatedRoute});
});
}

View File

@@ -1,18 +1,27 @@
import { TextFieldModule } from '@angular/cdk/text-field';
import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxChange, MatCheckboxModule } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogRef } from '@angular/material/dialog';
import { debounceTime, Subject, takeUntil, tap } from 'rxjs';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { Board, Card, Label } from 'app/modules/admin/apps/scrumboard/scrumboard.models';
import { ScrumboardService } from 'app/modules/admin/apps/scrumboard/scrumboard.service';
import { assign } from 'lodash-es';
import { DateTime } from 'luxon';
import { ScrumboardService } from 'app/modules/admin/apps/scrumboard/scrumboard.service';
import { Board, Card, Label } from 'app/modules/admin/apps/scrumboard/scrumboard.models';
import { debounceTime, Subject, takeUntil, tap } from 'rxjs';
@Component({
selector : 'scrumboard-card-details',
templateUrl : './details.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, MatIconModule, FormsModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule, TextFieldModule, NgClass, NgIf, MatDatepickerModule, NgFor, MatCheckboxModule, DatePipe],
})
export class ScrumboardCardDetailsComponent implements OnInit, OnDestroy
{
@@ -33,7 +42,7 @@ export class ScrumboardCardDetailsComponent implements OnInit, OnDestroy
public matDialogRef: MatDialogRef<ScrumboardCardDetailsComponent>,
private _changeDetectorRef: ChangeDetectorRef,
private _formBuilder: UntypedFormBuilder,
private _scrumboardService: ScrumboardService
private _scrumboardService: ScrumboardService,
)
{
}
@@ -50,7 +59,8 @@ export class ScrumboardCardDetailsComponent implements OnInit, OnDestroy
// Get the board
this._scrumboardService.board$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((board) => {
.subscribe((board) =>
{
// Board data
this.board = board;
@@ -62,7 +72,8 @@ export class ScrumboardCardDetailsComponent implements OnInit, OnDestroy
// Get the card details
this._scrumboardService.card$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((card) => {
.subscribe((card) =>
{
this.card = card;
});
@@ -72,7 +83,7 @@ export class ScrumboardCardDetailsComponent implements OnInit, OnDestroy
title : ['', Validators.required],
description: [''],
labels : [[]],
dueDate : [null]
dueDate : [null],
});
// Fill the form
@@ -81,21 +92,23 @@ export class ScrumboardCardDetailsComponent implements OnInit, OnDestroy
title : this.card.title,
description: this.card.description,
labels : this.card.labels,
dueDate : this.card.dueDate
dueDate : this.card.dueDate,
});
// Update card when there is a value change on the card form
this.cardForm.valueChanges
.pipe(
tap((value) => {
tap((value) =>
{
// Update the card object
this.card = assign(this.card, value);
}),
debounceTime(300),
takeUntil(this._unsubscribeAll)
takeUntil(this._unsubscribeAll),
)
.subscribe((value) => {
.subscribe((value) =>
{
// Update the card on the server
this._scrumboardService.updateCard(value.id, value).subscribe();
@@ -263,18 +276,21 @@ export class ScrumboardCardDetailsComponent implements OnInit, OnDestroy
private _readAsDataURL(file: File): Promise<any>
{
// Return a new promise
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) =>
{
// Create a new reader
const reader = new FileReader();
// Resolve the promise on success
reader.onload = (): void => {
reader.onload = (): void =>
{
resolve(reader.result);
};
// Reject the promise on error
reader.onerror = (e): void => {
reader.onerror = (e): void =>
{
reject(e);
};

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'scrumboard',
templateUrl : './scrumboard.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class ScrumboardComponent
{

View File

@@ -31,7 +31,8 @@ export class Board implements Required<IBoard>
// Lists
if ( board.lists )
{
this.lists = board.lists.map((list) => {
this.lists = board.lists.map((list) =>
{
if ( !(list instanceof List) )
{
return new List(list);
@@ -44,7 +45,8 @@ export class Board implements Required<IBoard>
// Labels
if ( board.labels )
{
this.labels = board.labels.map((label) => {
this.labels = board.labels.map((label) =>
{
if ( !(label instanceof Label) )
{
return new Label(label);
@@ -57,7 +59,8 @@ export class Board implements Required<IBoard>
// Members
if ( board.members )
{
this.members = board.members.map((member) => {
this.members = board.members.map((member) =>
{
if ( !(member instanceof Member) )
{
return new Member(member);
@@ -94,7 +97,8 @@ export class List implements Required<IList>
// Cards
if ( list.cards )
{
this.cards = list.cards.map((card) => {
this.cards = list.cards.map((card) =>
{
if ( !(card instanceof Card) )
{
return new Card(card);
@@ -137,7 +141,8 @@ export class Card implements Required<ICard>
// Labels
if ( card.labels )
{
this.labels = card.labels.map((label) => {
this.labels = card.labels.map((label) =>
{
if ( !(label instanceof Label) )
{
return new Label(label);

View File

@@ -1,38 +1,29 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from '@angular/core';
import { MAT_LUXON_DATE_FORMATS, MatLuxonDateModule } from '@angular/material-luxon-adapter';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MAT_DATE_FORMATS } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatLuxonDateModule, MAT_LUXON_DATE_FORMATS } from '@angular/material-luxon-adapter';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { SharedModule } from 'app/shared/shared.module';
import { ScrumboardComponent } from 'app/modules/admin/apps/scrumboard/scrumboard.component';
import { ScrumboardBoardsComponent } from 'app/modules/admin/apps/scrumboard/boards/boards.component';
import { ScrumboardBoardComponent } from 'app/modules/admin/apps/scrumboard/board/board.component';
import { RouterModule } from '@angular/router';
import { ScrumboardBoardAddCardComponent } from 'app/modules/admin/apps/scrumboard/board/add-card/add-card.component';
import { ScrumboardBoardAddListComponent } from 'app/modules/admin/apps/scrumboard/board/add-list/add-list.component';
import { ScrumboardBoardComponent } from 'app/modules/admin/apps/scrumboard/board/board.component';
import { ScrumboardBoardsComponent } from 'app/modules/admin/apps/scrumboard/boards/boards.component';
import { ScrumboardCardComponent } from 'app/modules/admin/apps/scrumboard/card/card.component';
import { ScrumboardCardDetailsComponent } from 'app/modules/admin/apps/scrumboard/card/details/details.component';
import { ScrumboardComponent } from 'app/modules/admin/apps/scrumboard/scrumboard.component';
import { scrumboardRoutes } from 'app/modules/admin/apps/scrumboard/scrumboard.routing';
@NgModule({
declarations: [
ScrumboardComponent,
ScrumboardBoardsComponent,
ScrumboardBoardComponent,
ScrumboardBoardAddCardComponent,
ScrumboardBoardAddListComponent,
ScrumboardCardComponent,
ScrumboardCardDetailsComponent
],
imports : [
imports : [
RouterModule.forChild(scrumboardRoutes),
DragDropModule,
MatButtonModule,
@@ -45,14 +36,20 @@ import { scrumboardRoutes } from 'app/modules/admin/apps/scrumboard/scrumboard.r
MatLuxonDateModule,
MatMenuModule,
MatProgressBarModule,
SharedModule
ScrumboardComponent,
ScrumboardBoardsComponent,
ScrumboardBoardComponent,
ScrumboardBoardAddCardComponent,
ScrumboardBoardAddListComponent,
ScrumboardCardComponent,
ScrumboardCardDetailsComponent,
],
providers : [
providers: [
{
provide : MAT_DATE_FORMATS,
useValue: MAT_LUXON_DATE_FORMATS
}
]
useValue: MAT_LUXON_DATE_FORMATS,
},
],
})
export class ScrumboardModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, Observable, throwError } from 'rxjs';
import { Board, Card } from 'app/modules/admin/apps/scrumboard/scrumboard.models';
import { ScrumboardService } from 'app/modules/admin/apps/scrumboard/scrumboard.service';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ScrumboardBoardsResolver implements Resolve<any>
{
@@ -13,7 +13,7 @@ export class ScrumboardBoardsResolver implements Resolve<any>
* Constructor
*/
constructor(
private _scrumboardService: ScrumboardService
private _scrumboardService: ScrumboardService,
)
{
}
@@ -35,7 +35,7 @@ export class ScrumboardBoardsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ScrumboardBoardResolver implements Resolve<any>
{
@@ -44,7 +44,7 @@ export class ScrumboardBoardResolver implements Resolve<any>
*/
constructor(
private _router: Router,
private _scrumboardService: ScrumboardService
private _scrumboardService: ScrumboardService,
)
{
}
@@ -62,28 +62,29 @@ export class ScrumboardBoardResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Board>
{
return this._scrumboardService.getBoard(route.paramMap.get('boardId'))
.pipe(
// Error here means the requested task is not available
catchError((error) => {
.pipe(
// Error here means the requested task is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ScrumboardCardResolver implements Resolve<any>
{
@@ -92,7 +93,7 @@ export class ScrumboardCardResolver implements Resolve<any>
*/
constructor(
private _router: Router,
private _scrumboardService: ScrumboardService
private _scrumboardService: ScrumboardService,
)
{
}
@@ -110,22 +111,23 @@ export class ScrumboardCardResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Card>
{
return this._scrumboardService.getCard(route.paramMap.get('cardId'))
.pipe(
// Error here means the requested task is not available
catchError((error) => {
.pipe(
// Error here means the requested task is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}

View File

@@ -1,31 +1,31 @@
import { Route } from '@angular/router';
import { ScrumboardBoardsComponent } from 'app/modules/admin/apps/scrumboard/boards/boards.component';
import { ScrumboardBoardResolver, ScrumboardBoardsResolver, ScrumboardCardResolver } from 'app/modules/admin/apps/scrumboard/scrumboard.resolvers';
import { ScrumboardBoardComponent } from 'app/modules/admin/apps/scrumboard/board/board.component';
import { ScrumboardBoardsComponent } from 'app/modules/admin/apps/scrumboard/boards/boards.component';
import { ScrumboardCardComponent } from 'app/modules/admin/apps/scrumboard/card/card.component';
import { ScrumboardBoardResolver, ScrumboardBoardsResolver, ScrumboardCardResolver } from 'app/modules/admin/apps/scrumboard/scrumboard.resolvers';
export const scrumboardRoutes: Route[] = [
{
path : '',
component: ScrumboardBoardsComponent,
resolve : {
boards: ScrumboardBoardsResolver
}
boards: ScrumboardBoardsResolver,
},
},
{
path : ':boardId',
component: ScrumboardBoardComponent,
resolve : {
board: ScrumboardBoardResolver
board: ScrumboardBoardResolver,
},
children : [
{
path : 'card/:cardId',
component: ScrumboardCardComponent,
resolve : {
card: ScrumboardCardResolver
}
}
]
}
card: ScrumboardCardResolver,
},
},
],
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Board, Card, Label, List } from 'app/modules/admin/apps/scrumboard/scrumboard.models';
import { BehaviorSubject, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class ScrumboardService
{
@@ -17,7 +17,7 @@ export class ScrumboardService
* Constructor
*/
constructor(
private _httpClient: HttpClient
private _httpClient: HttpClient,
)
{
// Set the private defaults
@@ -65,7 +65,7 @@ export class ScrumboardService
{
return this._httpClient.get<Board[]>('api/apps/scrumboard/boards').pipe(
map(response => response.map(item => new Board(item))),
tap(boards => this._boards.next(boards))
tap(boards => this._boards.next(boards)),
);
}
@@ -78,7 +78,7 @@ export class ScrumboardService
{
return this._httpClient.get<Board>('api/apps/scrumboard/board', {params: {id}}).pipe(
map(response => new Board(response)),
tap(board => this._board.next(board))
tap(board => this._board.next(board)),
);
}
@@ -92,15 +92,16 @@ export class ScrumboardService
return this.boards$.pipe(
take(1),
switchMap(boards => this._httpClient.put<Board>('api/apps/scrumboard/board', {board}).pipe(
map((newBoard) => {
map((newBoard) =>
{
// Update the boards with the new board
this._boards.next([...boards, newBoard]);
// Return new board from observable
return newBoard;
})
))
}),
)),
);
}
@@ -116,9 +117,10 @@ export class ScrumboardService
take(1),
switchMap(boards => this._httpClient.patch<Board>('api/apps/scrumboard/board', {
id,
board
board,
}).pipe(
map((updatedBoard) => {
map((updatedBoard) =>
{
// Find the index of the updated board
const index = boards.findIndex(item => item.id === id);
@@ -131,8 +133,8 @@ export class ScrumboardService
// Return the updated board
return updatedBoard;
})
))
}),
)),
);
}
@@ -146,7 +148,8 @@ export class ScrumboardService
return this.boards$.pipe(
take(1),
switchMap(boards => this._httpClient.delete('api/apps/scrumboard/board', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted board
const index = boards.findIndex(item => item.id === id);
@@ -165,8 +168,8 @@ export class ScrumboardService
// Return the deleted status
return isDeleted;
})
))
}),
)),
);
}
@@ -179,7 +182,8 @@ export class ScrumboardService
{
return this._httpClient.post<List>('api/apps/scrumboard/board/list', {list}).pipe(
map(response => new List(response)),
tap((newList) => {
tap((newList) =>
{
// Get the board value
const board = this._board.value;
@@ -192,7 +196,7 @@ export class ScrumboardService
// Update the board
this._board.next(board);
})
}),
);
}
@@ -205,7 +209,8 @@ export class ScrumboardService
{
return this._httpClient.patch<List>('api/apps/scrumboard/board/list', {list}).pipe(
map(response => new List(response)),
tap((updatedList) => {
tap((updatedList) =>
{
// Get the board value
const board = this._board.value;
@@ -221,7 +226,7 @@ export class ScrumboardService
// Update the board
this._board.next(board);
})
}),
);
}
@@ -234,13 +239,15 @@ export class ScrumboardService
{
return this._httpClient.patch<List[]>('api/apps/scrumboard/board/lists', {lists}).pipe(
map(response => response.map(item => new List(item))),
tap((updatedLists) => {
tap((updatedLists) =>
{
// Get the board value
const board = this._board.value;
// Go through the updated lists
updatedLists.forEach((updatedList) => {
updatedLists.forEach((updatedList) =>
{
// Find the index of the updated list
const index = board.lists.findIndex(item => item.id === updatedList.id);
@@ -254,7 +261,7 @@ export class ScrumboardService
// Update the board
this._board.next(board);
})
}),
);
}
@@ -266,7 +273,8 @@ export class ScrumboardService
deleteList(id: string): Observable<boolean>
{
return this._httpClient.delete<boolean>('api/apps/scrumboard/board/list', {params: {id}}).pipe(
tap((isDeleted) => {
tap((isDeleted) =>
{
// Get the board value
const board = this._board.value;
@@ -282,7 +290,7 @@ export class ScrumboardService
// Update the board
this._board.next(board);
})
}),
);
}
@@ -293,11 +301,12 @@ export class ScrumboardService
{
return this._board.pipe(
take(1),
map((board) => {
map((board) =>
{
// Find the card
const card = board.lists.find(list => list.cards.some(item => item.id === id))
.cards.find(item => item.id === id);
.cards.find(item => item.id === id);
// Update the card
this._card.next(card);
@@ -305,7 +314,8 @@ export class ScrumboardService
// Return the card
return card;
}),
switchMap((card) => {
switchMap((card) =>
{
if ( !card )
{
@@ -313,7 +323,7 @@ export class ScrumboardService
}
return of(card);
})
}),
);
}
@@ -326,13 +336,15 @@ export class ScrumboardService
{
return this._httpClient.put<Card>('api/apps/scrumboard/board/card', {card}).pipe(
map(response => new Card(response)),
tap((newCard) => {
tap((newCard) =>
{
// Get the board value
const board = this._board.value;
// Find the list and push the new card in it
board.lists.forEach((listItem, index, list) => {
board.lists.forEach((listItem, index, list) =>
{
if ( listItem.id === newCard.listId )
{
list[index].cards.push(newCard);
@@ -344,7 +356,7 @@ export class ScrumboardService
// Return the new card
return newCard;
})
}),
);
}
@@ -360,13 +372,16 @@ export class ScrumboardService
take(1),
switchMap(board => this._httpClient.patch<Card>('api/apps/scrumboard/board/card', {
id,
card
card,
}).pipe(
map((updatedCard) => {
map((updatedCard) =>
{
// Find the card and update it
board.lists.forEach((listItem) => {
listItem.cards.forEach((cardItem, index, array) => {
board.lists.forEach((listItem) =>
{
listItem.cards.forEach((cardItem, index, array) =>
{
if ( cardItem.id === id )
{
array[index] = updatedCard;
@@ -382,8 +397,8 @@ export class ScrumboardService
// Return the updated card
return updatedCard;
})
))
}),
)),
);
}
@@ -396,13 +411,15 @@ export class ScrumboardService
{
return this._httpClient.patch<Card[]>('api/apps/scrumboard/board/cards', {cards}).pipe(
map(response => response.map(item => new Card(item))),
tap((updatedCards) => {
tap((updatedCards) =>
{
// Get the board value
const board = this._board.value;
// Go through the updated cards
updatedCards.forEach((updatedCard) => {
updatedCards.forEach((updatedCard) =>
{
// Find the index of the updated card's list
const listIndex = board.lists.findIndex(list => list.id === updatedCard.listId);
@@ -419,7 +436,7 @@ export class ScrumboardService
// Update the board
this._board.next(board);
})
}),
);
}
@@ -433,11 +450,14 @@ export class ScrumboardService
return this.board$.pipe(
take(1),
switchMap(board => this._httpClient.delete('api/apps/scrumboard/board/card', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the card and delete it
board.lists.forEach((listItem) => {
listItem.cards.forEach((cardItem, index, array) => {
board.lists.forEach((listItem) =>
{
listItem.cards.forEach((cardItem, index, array) =>
{
if ( cardItem.id === id )
{
array.splice(index, 1);
@@ -453,8 +473,8 @@ export class ScrumboardService
// Return the deleted status
return isDeleted;
})
))
}),
)),
);
}
@@ -504,7 +524,8 @@ export class ScrumboardService
return this.board$.pipe(
take(1),
switchMap(board => this._httpClient.post<Label>('api/apps/scrumboard/board/label', {label}).pipe(
map((newLabel) => {
map((newLabel) =>
{
// Update the board labels with the new label
board.labels = [...board.labels, newLabel];
@@ -514,8 +535,8 @@ export class ScrumboardService
// Return new label from observable
return newLabel;
})
))
}),
)),
);
}
@@ -531,9 +552,10 @@ export class ScrumboardService
take(1),
switchMap(board => this._httpClient.patch<Label>('api/apps/scrumboard/board/label', {
id,
label
label,
}).pipe(
map((updatedLabel) => {
map((updatedLabel) =>
{
// Find the index of the updated label
const index = board.labels.findIndex(item => item.id === id);
@@ -546,8 +568,8 @@ export class ScrumboardService
// Return the updated label
return updatedLabel;
})
))
}),
)),
);
}
@@ -561,7 +583,8 @@ export class ScrumboardService
return this.board$.pipe(
take(1),
switchMap(board => this._httpClient.delete('api/apps/scrumboard/board/label', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted label
const index = board.labels.findIndex(item => item.id === id);
@@ -573,8 +596,10 @@ export class ScrumboardService
if ( isDeleted )
{
// Remove the label from any card that uses it
board.lists.forEach((list) => {
list.cards.forEach((card) => {
board.lists.forEach((list) =>
{
list.cards.forEach((card) =>
{
const labelIndex = card.labels.findIndex(label => label.id === id);
if ( labelIndex > -1 )
{
@@ -589,8 +614,8 @@ export class ScrumboardService
// Return the deleted status
return isDeleted;
})
))
}),
)),
);
}

View File

@@ -1,22 +1,36 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, Renderer2, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { TemplatePortal } from '@angular/cdk/portal';
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 { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, Renderer2, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatDrawerToggleResult } from '@angular/material/sidenav';
import { ActivatedRoute, NavigationEnd, Router, RouterLink } from '@angular/router';
import { FuseFindByKeyPipe } from '@fuse/pipes/find-by-key/find-by-key.pipe';
import { FuseConfirmationService } from '@fuse/services/confirmation';
import { debounceTime, filter, Subject, takeUntil, tap } from 'rxjs';
import { assign } from 'lodash-es';
import { DateTime } from 'luxon';
import { Tag, Task } from 'app/modules/admin/apps/tasks/tasks.types';
import { TasksListComponent } from 'app/modules/admin/apps/tasks/list/list.component';
import { TasksService } from 'app/modules/admin/apps/tasks/tasks.service';
import { Tag, Task } from 'app/modules/admin/apps/tasks/tasks.types';
import { assign } from 'lodash-es';
import { DateTime } from 'luxon';
import { debounceTime, filter, Subject, takeUntil, tap } from 'rxjs';
@Component({
selector : 'tasks-details',
templateUrl : './details.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [FormsModule, ReactiveFormsModule, MatButtonModule, NgIf, MatIconModule, MatMenuModule, RouterLink, MatDividerModule, MatFormFieldModule, MatInputModule, TextFieldModule, NgFor, MatRippleModule, MatCheckboxModule, NgClass, MatDatepickerModule, FuseFindByKeyPipe, DatePipe],
})
export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
{
@@ -46,7 +60,7 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
private _tasksListComponent: TasksListComponent,
private _tasksService: TasksService,
private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef
private _viewContainerRef: ViewContainerRef,
)
{
}
@@ -73,13 +87,14 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
dueDate : [null],
priority : [0],
tags : [[]],
order : [0]
order : [0],
});
// Get the tags
this._tasksService.tags$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((tags: Tag[]) => {
.subscribe((tags: Tag[]) =>
{
this.tags = tags;
this.filteredTags = tags;
@@ -90,7 +105,8 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
// Get the tasks
this._tasksService.tasks$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((tasks: Task[]) => {
.subscribe((tasks: Task[]) =>
{
this.tasks = tasks;
// Mark for check
@@ -100,7 +116,8 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
// Get the task
this._tasksService.task$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((task: Task) => {
.subscribe((task: Task) =>
{
// Open the drawer in case it is closed
this._tasksListComponent.matDrawer.open();
@@ -118,15 +135,17 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
// Update task when there is a value change on the task form
this.taskForm.valueChanges
.pipe(
tap((value) => {
tap((value) =>
{
// Update the task object
this.task = assign(this.task, value);
}),
debounceTime(300),
takeUntil(this._unsubscribeAll)
takeUntil(this._unsubscribeAll),
)
.subscribe((value) => {
.subscribe((value) =>
{
// Update the task on the server
this._tasksService.updateTask(value.id, value).subscribe();
@@ -139,9 +158,10 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
this._router.events
.pipe(
takeUntil(this._unsubscribeAll),
filter(event => event instanceof NavigationEnd)
filter(event => event instanceof NavigationEnd),
)
.subscribe(() => {
.subscribe(() =>
{
// Focus on the title field
this._titleField.nativeElement.focus();
@@ -157,9 +177,10 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
this._tasksListComponent.matDrawer.openedChange
.pipe(
takeUntil(this._unsubscribeAll),
filter(opened => opened)
filter(opened => opened),
)
.subscribe(() => {
.subscribe(() =>
{
// Focus on the title element
this._titleField.nativeElement.focus();
@@ -217,22 +238,23 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
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'
}
])
.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(() => {
this._tagsPanelOverlayRef.attachments().subscribe(() =>
{
// Focus to the search input once the overlay has been attached
this._tagsPanelOverlayRef.overlayElement.querySelector('input').focus();
@@ -245,7 +267,8 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
this._tagsPanelOverlayRef.attach(templatePortal);
// Subscribe to the backdrop click
this._tagsPanelOverlayRef.backdropClick().subscribe(() => {
this._tagsPanelOverlayRef.backdropClick().subscribe(() =>
{
// If overlay exists and attached...
if ( this._tagsPanelOverlayRef && this._tagsPanelOverlayRef.hasAttached() )
@@ -342,12 +365,13 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
createTag(title: string): void
{
const tag = {
title
title,
};
// Create tag on the server
this._tasksService.createTag(tag)
.subscribe((response) => {
.subscribe((response) =>
{
// Add the tag to the task
this.addTagToTask(response);
@@ -479,13 +503,14 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
message: 'Are you sure you want to delete this task? This action cannot be undone!',
actions: {
confirm: {
label: 'Delete'
}
}
label: 'Delete',
},
},
});
// Subscribe to the confirmation dialog closed action
confirmation.afterClosed().subscribe((result) => {
confirmation.afterClosed().subscribe((result) =>
{
// If the confirm button pressed...
if ( result === 'confirmed' )
@@ -501,7 +526,8 @@ export class TasksDetailsComponent implements OnInit, AfterViewInit, OnDestroy
// Delete the task
this._tasksService.deleteTask(id)
.subscribe((isDeleted) => {
.subscribe((isDeleted) =>
{
// Return if the task wasn't deleted...
if ( !isDeleted )

View File

@@ -1,19 +1,24 @@
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDragPreview, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { DatePipe, DOCUMENT, NgClass, NgFor, NgIf, TitleCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { MatDrawer } from '@angular/material/sidenav';
import { filter, fromEvent, Subject, takeUntil } from 'rxjs';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatDrawer, MatSidenavModule } from '@angular/material/sidenav';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute, Router, RouterLink, RouterOutlet } from '@angular/router';
import { FuseNavigationService, FuseVerticalNavigationComponent } from '@fuse/components/navigation';
import { Tag, Task } from 'app/modules/admin/apps/tasks/tasks.types';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { TasksService } from 'app/modules/admin/apps/tasks/tasks.service';
import { Tag, Task } from 'app/modules/admin/apps/tasks/tasks.types';
import { filter, fromEvent, Subject, takeUntil } from 'rxjs';
@Component({
selector : 'tasks-list',
templateUrl : './list.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatSidenavModule, RouterOutlet, NgIf, MatButtonModule, MatTooltipModule, MatIconModule, CdkDropList, NgFor, CdkDrag, NgClass, CdkDragPreview, CdkDragHandle, RouterLink, TitleCasePipe, DatePipe],
})
export class TasksListComponent implements OnInit, OnDestroy
{
@@ -26,7 +31,7 @@ export class TasksListComponent implements OnInit, OnDestroy
tasksCount: any = {
completed : 0,
incomplete: 0,
total : 0
total : 0,
};
private _unsubscribeAll: Subject<any> = new Subject<any>();
@@ -40,7 +45,7 @@ export class TasksListComponent implements OnInit, OnDestroy
private _router: Router,
private _tasksService: TasksService,
private _fuseMediaWatcherService: FuseMediaWatcherService,
private _fuseNavigationService: FuseNavigationService
private _fuseNavigationService: FuseNavigationService,
)
{
}
@@ -57,7 +62,8 @@ export class TasksListComponent implements OnInit, OnDestroy
// Get the tags
this._tasksService.tags$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((tags: Tag[]) => {
.subscribe((tags: Tag[]) =>
{
this.tags = tags;
// Mark for check
@@ -67,7 +73,8 @@ export class TasksListComponent implements OnInit, OnDestroy
// Get the tasks
this._tasksService.tasks$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((tasks: Task[]) => {
.subscribe((tasks: Task[]) =>
{
this.tasks = tasks;
// Update the counts
@@ -79,7 +86,8 @@ export class TasksListComponent implements OnInit, OnDestroy
this._changeDetectorRef.markForCheck();
// Update the count on the navigation
setTimeout(() => {
setTimeout(() =>
{
// Get the component -> navigation data -> item
const mainNavigationComponent = this._fuseNavigationService.getComponent<FuseVerticalNavigationComponent>('mainNavigation');
@@ -102,7 +110,8 @@ export class TasksListComponent implements OnInit, OnDestroy
// Get the task
this._tasksService.task$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((task: Task) => {
.subscribe((task: Task) =>
{
this.selectedTask = task;
// Mark for check
@@ -112,7 +121,8 @@ export class TasksListComponent implements OnInit, OnDestroy
// Subscribe to media query change
this._fuseMediaWatcherService.onMediaQueryChange$('(min-width: 1440px)')
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((state) => {
.subscribe((state) =>
{
// Calculate the drawer mode
this.drawerMode = state.matches ? 'side' : 'over';
@@ -127,10 +137,11 @@ export class TasksListComponent implements OnInit, OnDestroy
takeUntil(this._unsubscribeAll),
filter<KeyboardEvent>(event =>
(event.ctrlKey === true || event.metaKey) // Ctrl or Cmd
&& (event.key === '/' || event.key === '.') // '/' or '.' key
)
&& (event.key === '/' || event.key === '.'), // '/' or '.' key
),
)
.subscribe((event: KeyboardEvent) => {
.subscribe((event: KeyboardEvent) =>
{
// If the '/' pressed
if ( event.key === '/' )
@@ -180,7 +191,8 @@ export class TasksListComponent implements OnInit, OnDestroy
createTask(type: 'task' | 'section'): void
{
// Create the task
this._tasksService.createTask(type).subscribe((newTask) => {
this._tasksService.createTask(type).subscribe((newTask) =>
{
// Go to the new task
this._router.navigate(['./', newTask.id], {relativeTo: this._activatedRoute});

View File

@@ -1,10 +1,13 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector : 'tasks',
templateUrl : './tasks.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [RouterOutlet],
})
export class TasksComponent
{

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { TasksDetailsComponent } from 'app/modules/admin/apps/tasks/details/details.component';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class CanDeactivateTasksDetails implements CanDeactivate<TasksDetailsComponent>
{
@@ -12,7 +12,7 @@ export class CanDeactivateTasksDetails implements CanDeactivate<TasksDetailsComp
component: TasksDetailsComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
nextState: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
{
// Get the next route

View File

@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from '@angular/core';
import { MAT_LUXON_DATE_FORMATS, MatLuxonDateModule } from '@angular/material-luxon-adapter';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
@@ -10,27 +10,21 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatLuxonDateModule, MAT_LUXON_DATE_FORMATS } from '@angular/material-luxon-adapter';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FuseFindByKeyPipeModule } from '@fuse/pipes/find-by-key';
import { SharedModule } from 'app/shared/shared.module';
import { tasksRoutes } from 'app/modules/admin/apps/tasks/tasks.routing';
import { TasksComponent } from 'app/modules/admin/apps/tasks/tasks.component';
import { RouterModule } from '@angular/router';
import { TasksDetailsComponent } from 'app/modules/admin/apps/tasks/details/details.component';
import { TasksListComponent } from 'app/modules/admin/apps/tasks/list/list.component';
import { TasksComponent } from 'app/modules/admin/apps/tasks/tasks.component';
import { tasksRoutes } from 'app/modules/admin/apps/tasks/tasks.routing';
@NgModule({
declarations: [
TasksComponent,
TasksDetailsComponent,
TasksListComponent
],
imports : [
imports : [
RouterModule.forChild(tasksRoutes),
DragDropModule,
MatAutocompleteModule,
@@ -49,15 +43,16 @@ import { TasksListComponent } from 'app/modules/admin/apps/tasks/list/list.compo
MatSelectModule,
MatSidenavModule,
MatTooltipModule,
FuseFindByKeyPipeModule,
SharedModule
TasksComponent,
TasksDetailsComponent,
TasksListComponent,
],
providers : [
providers: [
{
provide : MAT_DATE_FORMATS,
useValue: MAT_LUXON_DATE_FORMATS
}
]
useValue: MAT_LUXON_DATE_FORMATS,
},
],
})
export class TasksModule
{

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { catchError, Observable, throwError } from 'rxjs';
import { TasksService } from 'app/modules/admin/apps/tasks/tasks.service';
import { Tag, Task } from 'app/modules/admin/apps/tasks/tasks.types';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class TasksTagsResolver implements Resolve<any>
{
@@ -33,7 +33,7 @@ export class TasksTagsResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class TasksResolver implements Resolve<any>
{
@@ -61,7 +61,7 @@ export class TasksResolver implements Resolve<any>
}
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class TasksTaskResolver implements Resolve<any>
{
@@ -70,7 +70,7 @@ export class TasksTaskResolver implements Resolve<any>
*/
constructor(
private _router: Router,
private _tasksService: TasksService
private _tasksService: TasksService,
)
{
}
@@ -88,22 +88,23 @@ export class TasksTaskResolver implements Resolve<any>
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Task>
{
return this._tasksService.getTaskById(route.paramMap.get('id'))
.pipe(
// Error here means the requested task is not available
catchError((error) => {
.pipe(
// Error here means the requested task is not available
catchError((error) =>
{
// Log the error
console.error(error);
// Log the error
console.error(error);
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Get the parent url
const parentUrl = state.url.split('/').slice(0, -1).join('/');
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Navigate to there
this._router.navigateByUrl(parentUrl);
// Throw an error
return throwError(error);
})
);
// Throw an error
return throwError(error);
}),
);
}
}

View File

@@ -1,35 +1,35 @@
import { Route } from '@angular/router';
import { TasksDetailsComponent } from 'app/modules/admin/apps/tasks/details/details.component';
import { TasksListComponent } from 'app/modules/admin/apps/tasks/list/list.component';
import { TasksComponent } from 'app/modules/admin/apps/tasks/tasks.component';
import { CanDeactivateTasksDetails } from 'app/modules/admin/apps/tasks/tasks.guards';
import { TasksResolver, TasksTagsResolver, TasksTaskResolver } from 'app/modules/admin/apps/tasks/tasks.resolvers';
import { TasksComponent } from 'app/modules/admin/apps/tasks/tasks.component';
import { TasksListComponent } from 'app/modules/admin/apps/tasks/list/list.component';
import { TasksDetailsComponent } from 'app/modules/admin/apps/tasks/details/details.component';
export const tasksRoutes: Route[] = [
{
path : '',
component: TasksComponent,
resolve : {
tags: TasksTagsResolver
tags: TasksTagsResolver,
},
children : [
{
path : '',
component: TasksListComponent,
resolve : {
tasks: TasksResolver
tasks: TasksResolver,
},
children : [
{
path : ':id',
component : TasksDetailsComponent,
resolve : {
task: TasksTaskResolver
task: TasksTaskResolver,
},
canDeactivate: [CanDeactivateTasksDetails]
}
]
}
]
}
canDeactivate: [CanDeactivateTasksDetails],
},
],
},
],
},
];

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
import { Injectable } from '@angular/core';
import { Tag, Task } from 'app/modules/admin/apps/tasks/tasks.types';
import { BehaviorSubject, filter, map, Observable, of, switchMap, take, tap, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class TasksService
{
@@ -58,9 +58,10 @@ export class TasksService
getTags(): Observable<Tag[]>
{
return this._httpClient.get<Tag[]>('api/apps/tasks/tags').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._tags.next(response);
})
}),
);
}
@@ -74,15 +75,16 @@ export class TasksService
return this.tags$.pipe(
take(1),
switchMap(tags => this._httpClient.post<Tag>('api/apps/tasks/tag', {tag}).pipe(
map((newTag) => {
map((newTag) =>
{
// Update the tags with the new tag
this._tags.next([...tags, newTag]);
// Return new tag from observable
return newTag;
})
))
}),
)),
);
}
@@ -98,9 +100,10 @@ export class TasksService
take(1),
switchMap(tags => this._httpClient.patch<Tag>('api/apps/tasks/tag', {
id,
tag
tag,
}).pipe(
map((updatedTag) => {
map((updatedTag) =>
{
// Find the index of the updated tag
const index = tags.findIndex(item => item.id === id);
@@ -113,8 +116,8 @@ export class TasksService
// Return the updated tag
return updatedTag;
})
))
}),
)),
);
}
@@ -128,7 +131,8 @@ export class TasksService
return this.tags$.pipe(
take(1),
switchMap(tags => this._httpClient.delete('api/apps/tasks/tag', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted tag
const index = tags.findIndex(item => item.id === id);
@@ -145,10 +149,12 @@ export class TasksService
filter(isDeleted => isDeleted),
switchMap(isDeleted => this.tasks$.pipe(
take(1),
map((tasks) => {
map((tasks) =>
{
// Iterate through the tasks
tasks.forEach((task) => {
tasks.forEach((task) =>
{
const tagIndex = task.tags.findIndex(tag => tag === id);
@@ -161,9 +167,9 @@ export class TasksService
// Return the deleted status
return isDeleted;
})
))
))
}),
)),
)),
);
}
@@ -173,9 +179,10 @@ export class TasksService
getTasks(): Observable<Task[]>
{
return this._httpClient.get<Task[]>('api/apps/tasks/all').pipe(
tap((response) => {
tap((response) =>
{
this._tasks.next(response);
})
}),
);
}
@@ -206,7 +213,8 @@ export class TasksService
{
return this._tasks.pipe(
take(1),
map((tasks) => {
map((tasks) =>
{
// Find the task
const task = tasks.find(item => item.id === id) || null;
@@ -217,7 +225,8 @@ export class TasksService
// Return the task
return task;
}),
switchMap((task) => {
switchMap((task) =>
{
if ( !task )
{
@@ -225,7 +234,7 @@ export class TasksService
}
return of(task);
})
}),
);
}
@@ -239,15 +248,16 @@ export class TasksService
return this.tasks$.pipe(
take(1),
switchMap(tasks => this._httpClient.post<Task>('api/apps/tasks/task', {type}).pipe(
map((newTask) => {
map((newTask) =>
{
// Update the tasks with the new task
this._tasks.next([newTask, ...tasks]);
// Return the new task
return newTask;
})
))
}),
)),
);
}
@@ -260,40 +270,42 @@ export class TasksService
updateTask(id: string, task: Task): Observable<Task>
{
return this.tasks$
.pipe(
take(1),
switchMap(tasks => this._httpClient.patch<Task>('api/apps/tasks/task', {
id,
task
}).pipe(
map((updatedTask) => {
.pipe(
take(1),
switchMap(tasks => this._httpClient.patch<Task>('api/apps/tasks/task', {
id,
task,
}).pipe(
map((updatedTask) =>
{
// Find the index of the updated task
const index = tasks.findIndex(item => item.id === id);
// Find the index of the updated task
const index = tasks.findIndex(item => item.id === id);
// Update the task
tasks[index] = updatedTask;
// Update the task
tasks[index] = updatedTask;
// Update the tasks
this._tasks.next(tasks);
// Update the tasks
this._tasks.next(tasks);
// Return the updated task
return updatedTask;
}),
switchMap(updatedTask => this.task$.pipe(
take(1),
filter(item => item && item.id === id),
tap(() => {
// Return the updated task
return updatedTask;
}),
switchMap(updatedTask => this.task$.pipe(
take(1),
filter(item => item && item.id === id),
tap(() =>
{
// Update the task if it's selected
this._task.next(updatedTask);
// Update the task if it's selected
this._task.next(updatedTask);
// Return the updated task
return updatedTask;
})
))
))
);
// Return the updated task
return updatedTask;
}),
)),
)),
);
}
/**
@@ -306,7 +318,8 @@ export class TasksService
return this.tasks$.pipe(
take(1),
switchMap(tasks => this._httpClient.delete('api/apps/tasks/task', {params: {id}}).pipe(
map((isDeleted: boolean) => {
map((isDeleted: boolean) =>
{
// Find the index of the deleted task
const index = tasks.findIndex(item => item.id === id);
@@ -319,8 +332,8 @@ export class TasksService
// Return the deleted status
return isDeleted;
})
))
}),
)),
);
}
}

View File

@@ -1,14 +1,22 @@
import { DecimalPipe, NgFor } from '@angular/common';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { Router } from '@angular/router';
import { Subject, takeUntil } from 'rxjs';
import { ApexOptions } from 'ng-apexcharts';
import { AnalyticsService } from 'app/modules/admin/dashboards/analytics/analytics.service';
import { ApexOptions, NgApexchartsModule } from 'ng-apexcharts';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'analytics',
templateUrl : './analytics.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, MatIconModule, MatMenuModule, MatButtonToggleModule, NgApexchartsModule, MatTooltipModule, NgFor, DecimalPipe],
})
export class AnalyticsComponent implements OnInit, OnDestroy
{
@@ -30,7 +38,7 @@ export class AnalyticsComponent implements OnInit, OnDestroy
*/
constructor(
private _analyticsService: AnalyticsService,
private _router: Router
private _router: Router,
)
{
}
@@ -47,7 +55,8 @@ export class AnalyticsComponent implements OnInit, OnDestroy
// Get the data
this._analyticsService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data) => {
.subscribe((data) =>
{
// Store the data
this.data = data;
@@ -60,14 +69,16 @@ export class AnalyticsComponent implements OnInit, OnDestroy
window['Apex'] = {
chart: {
events: {
mounted: (chart: any, options?: any): void => {
mounted: (chart: any, options?: any): void =>
{
this._fixSvgFill(chart.el);
},
updated: (chart: any, options?: any): void => {
updated: (chart: any, options?: any): void =>
{
this._fixSvgFill(chart.el);
}
}
}
},
},
},
};
}
@@ -119,11 +130,12 @@ export class AnalyticsComponent implements OnInit, OnDestroy
// 2. Filter out the ones that doesn't have cross reference so we only left with the ones that use the 'url(#id)' syntax
// 3. Insert the 'currentURL' at the front of the 'fill' attribute value
Array.from(element.querySelectorAll('*[fill]'))
.filter(el => el.getAttribute('fill').indexOf('url(') !== -1)
.forEach((el) => {
const attrVal = el.getAttribute('fill');
el.setAttribute('fill', `url(${currentURL}${attrVal.slice(attrVal.indexOf('#'))}`);
});
.filter(el => el.getAttribute('fill').indexOf('url(') !== -1)
.forEach((el) =>
{
const attrVal = el.getAttribute('fill');
el.setAttribute('fill', `url(${currentURL}${attrVal.slice(attrVal.indexOf('#'))}`);
});
}
/**
@@ -139,8 +151,8 @@ export class AnalyticsComponent implements OnInit, OnDestroy
animations: {
speed : 400,
animateGradually: {
enabled: false
}
enabled: false,
},
},
fontFamily: 'inherit',
foreColor : 'inherit',
@@ -148,18 +160,18 @@ export class AnalyticsComponent implements OnInit, OnDestroy
height : '100%',
type : 'area',
toolbar : {
show: false
show: false,
},
zoom : {
enabled: false
}
enabled: false,
},
},
colors : ['#818CF8'],
dataLabels: {
enabled: false
enabled: false,
},
fill : {
colors: ['#312E81']
colors: ['#312E81'],
},
grid : {
show : true,
@@ -168,259 +180,259 @@ export class AnalyticsComponent implements OnInit, OnDestroy
top : 10,
bottom: -40,
left : 0,
right : 0
right : 0,
},
position : 'back',
xaxis : {
lines: {
show: true
}
}
show: true,
},
},
},
series : this.data.visitors.series,
stroke : {
width: 2
width: 2,
},
tooltip : {
followCursor: true,
theme : 'dark',
x : {
format: 'MMM dd, yyyy'
format: 'MMM dd, yyyy',
},
y : {
formatter: (value: number): string => `${value}`
}
formatter: (value: number): string => `${value}`,
},
},
xaxis : {
axisBorder: {
show: false
show: false,
},
axisTicks : {
show: false
show: false,
},
crosshairs: {
stroke: {
color : '#475569',
dashArray: 0,
width : 2
}
width : 2,
},
},
labels : {
offsetY: -20,
style : {
colors: '#CBD5E1'
}
colors: '#CBD5E1',
},
},
tickAmount: 20,
tooltip : {
enabled: false
enabled: false,
},
type : 'datetime'
type : 'datetime',
},
yaxis : {
axisTicks : {
show: false
show: false,
},
axisBorder: {
show: false
show: false,
},
min : (min): number => min - 750,
max : (max): number => max + 250,
tickAmount: 5,
show : false
}
show : false,
},
};
// Conversions
this.chartConversions = {
chart : {
animations: {
enabled: false
enabled: false,
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'area',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#38BDF8'],
fill : {
colors : ['#38BDF8'],
opacity: 0.5
opacity: 0.5,
},
series : this.data.conversions.series,
stroke : {
curve: 'smooth'
curve: 'smooth',
},
tooltip: {
followCursor: true,
theme : 'dark'
theme : 'dark',
},
xaxis : {
type : 'category',
categories: this.data.conversions.labels
categories: this.data.conversions.labels,
},
yaxis : {
labels: {
formatter: (val): string => val.toString()
}
}
formatter: (val): string => val.toString(),
},
},
};
// Impressions
this.chartImpressions = {
chart : {
animations: {
enabled: false
enabled: false,
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'area',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#34D399'],
fill : {
colors : ['#34D399'],
opacity: 0.5
opacity: 0.5,
},
series : this.data.impressions.series,
stroke : {
curve: 'smooth'
curve: 'smooth',
},
tooltip: {
followCursor: true,
theme : 'dark'
theme : 'dark',
},
xaxis : {
type : 'category',
categories: this.data.impressions.labels
categories: this.data.impressions.labels,
},
yaxis : {
labels: {
formatter: (val): string => val.toString()
}
}
formatter: (val): string => val.toString(),
},
},
};
// Visits
this.chartVisits = {
chart : {
animations: {
enabled: false
enabled: false,
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'area',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#FB7185'],
fill : {
colors : ['#FB7185'],
opacity: 0.5
opacity: 0.5,
},
series : this.data.visits.series,
stroke : {
curve: 'smooth'
curve: 'smooth',
},
tooltip: {
followCursor: true,
theme : 'dark'
theme : 'dark',
},
xaxis : {
type : 'category',
categories: this.data.visits.labels
categories: this.data.visits.labels,
},
yaxis : {
labels: {
formatter: (val): string => val.toString()
}
}
formatter: (val): string => val.toString(),
},
},
};
// Visitors vs Page Views
this.chartVisitorsVsPageViews = {
chart : {
animations: {
enabled: false
enabled: false,
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'area',
toolbar : {
show: false
show: false,
},
zoom : {
enabled: false
}
enabled: false,
},
},
colors : ['#64748B', '#94A3B8'],
dataLabels: {
enabled: false
enabled: false,
},
fill : {
colors : ['#64748B', '#94A3B8'],
opacity: 0.5
opacity: 0.5,
},
grid : {
show : false,
padding: {
bottom: -40,
left : 0,
right : 0
}
right : 0,
},
},
legend : {
show: false
show: false,
},
series : this.data.visitorsVsPageViews.series,
stroke : {
curve: 'smooth',
width: 2
width: 2,
},
tooltip : {
followCursor: true,
theme : 'dark',
x : {
format: 'MMM dd, yyyy'
}
format: 'MMM dd, yyyy',
},
},
xaxis : {
axisBorder: {
show: false
show: false,
},
labels : {
offsetY: -20,
rotate : 0,
style : {
colors: 'var(--fuse-text-secondary)'
}
colors: 'var(--fuse-text-secondary)',
},
},
tickAmount: 3,
tooltip : {
enabled: false
enabled: false,
},
type : 'datetime'
type : 'datetime',
},
yaxis : {
labels : {
style: {
colors: 'var(--fuse-text-secondary)'
}
colors: 'var(--fuse-text-secondary)',
},
},
max : (max): number => max + 250,
min : (min): number => min - 250,
show : false,
tickAmount: 5
}
tickAmount: 5,
},
};
// New vs. returning
@@ -429,16 +441,16 @@ export class AnalyticsComponent implements OnInit, OnDestroy
animations: {
speed : 400,
animateGradually: {
enabled: false
}
enabled: false,
},
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'donut',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#3182CE', '#63B3ED'],
labels : this.data.newVsReturning.labels,
@@ -447,36 +459,36 @@ export class AnalyticsComponent implements OnInit, OnDestroy
customScale : 0.9,
expandOnClick: false,
donut : {
size: '70%'
}
}
size: '70%',
},
},
},
series : this.data.newVsReturning.series,
states : {
hover : {
filter: {
type: 'none'
}
type: 'none',
},
},
active: {
filter: {
type: 'none'
}
}
type: 'none',
},
},
},
tooltip : {
enabled : true,
fillSeriesColor: false,
theme : 'dark',
custom : ({
seriesIndex,
w
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
seriesIndex,
w,
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
<div class="w-3 h-3 rounded-full" style="background-color: ${w.config.colors[seriesIndex]};"></div>
<div class="ml-2 text-md leading-none">${w.config.labels[seriesIndex]}:</div>
<div class="ml-2 text-md font-bold leading-none">${w.config.series[seriesIndex]}%</div>
</div>`
}
</div>`,
},
};
// Gender
@@ -485,16 +497,16 @@ export class AnalyticsComponent implements OnInit, OnDestroy
animations: {
speed : 400,
animateGradually: {
enabled: false
}
enabled: false,
},
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'donut',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#319795', '#4FD1C5'],
labels : this.data.gender.labels,
@@ -503,36 +515,36 @@ export class AnalyticsComponent implements OnInit, OnDestroy
customScale : 0.9,
expandOnClick: false,
donut : {
size: '70%'
}
}
size: '70%',
},
},
},
series : this.data.gender.series,
states : {
hover : {
filter: {
type: 'none'
}
type: 'none',
},
},
active: {
filter: {
type: 'none'
}
}
type: 'none',
},
},
},
tooltip : {
enabled : true,
fillSeriesColor: false,
theme : 'dark',
custom : ({
seriesIndex,
w
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
seriesIndex,
w,
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
<div class="w-3 h-3 rounded-full" style="background-color: ${w.config.colors[seriesIndex]};"></div>
<div class="ml-2 text-md leading-none">${w.config.labels[seriesIndex]}:</div>
<div class="ml-2 text-md font-bold leading-none">${w.config.series[seriesIndex]}%</div>
</div>`
}
</div>`,
},
};
// Age
@@ -541,16 +553,16 @@ export class AnalyticsComponent implements OnInit, OnDestroy
animations: {
speed : 400,
animateGradually: {
enabled: false
}
enabled: false,
},
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'donut',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#DD6B20', '#F6AD55'],
labels : this.data.age.labels,
@@ -559,36 +571,36 @@ export class AnalyticsComponent implements OnInit, OnDestroy
customScale : 0.9,
expandOnClick: false,
donut : {
size: '70%'
}
}
size: '70%',
},
},
},
series : this.data.age.series,
states : {
hover : {
filter: {
type: 'none'
}
type: 'none',
},
},
active: {
filter: {
type: 'none'
}
}
type: 'none',
},
},
},
tooltip : {
enabled : true,
fillSeriesColor: false,
theme : 'dark',
custom : ({
seriesIndex,
w
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
seriesIndex,
w,
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
<div class="w-3 h-3 rounded-full" style="background-color: ${w.config.colors[seriesIndex]};"></div>
<div class="ml-2 text-md leading-none">${w.config.labels[seriesIndex]}:</div>
<div class="ml-2 text-md font-bold leading-none">${w.config.series[seriesIndex]}%</div>
</div>`
}
</div>`,
},
};
// Language
@@ -597,16 +609,16 @@ export class AnalyticsComponent implements OnInit, OnDestroy
animations: {
speed : 400,
animateGradually: {
enabled: false
}
enabled: false,
},
},
fontFamily: 'inherit',
foreColor : 'inherit',
height : '100%',
type : 'donut',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#805AD5', '#B794F4'],
labels : this.data.language.labels,
@@ -615,36 +627,36 @@ export class AnalyticsComponent implements OnInit, OnDestroy
customScale : 0.9,
expandOnClick: false,
donut : {
size: '70%'
}
}
size: '70%',
},
},
},
series : this.data.language.series,
states : {
hover : {
filter: {
type: 'none'
}
type: 'none',
},
},
active: {
filter: {
type: 'none'
}
}
type: 'none',
},
},
},
tooltip : {
enabled : true,
fillSeriesColor: false,
theme : 'dark',
custom : ({
seriesIndex,
w
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
seriesIndex,
w,
}): string => `<div class="flex items-center h-8 min-h-8 max-h-8 px-3">
<div class="w-3 h-3 rounded-full" style="background-color: ${w.config.colors[seriesIndex]};"></div>
<div class="ml-2 text-md leading-none">${w.config.labels[seriesIndex]}:</div>
<div class="ml-2 text-md font-bold leading-none">${w.config.series[seriesIndex]}%</div>
</div>`
}
</div>`,
},
};
}
}

View File

@@ -1,5 +1,4 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatDividerModule } from '@angular/material/divider';
@@ -9,16 +8,14 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { NgApexchartsModule } from 'ng-apexcharts';
import { SharedModule } from 'app/shared/shared.module';
import { RouterModule } from '@angular/router';
import { AnalyticsComponent } from 'app/modules/admin/dashboards/analytics/analytics.component';
import { analyticsRoutes } from 'app/modules/admin/dashboards/analytics/analytics.routing';
import { NgApexchartsModule } from 'ng-apexcharts';
@NgModule({
declarations: [
AnalyticsComponent
],
imports : [
imports: [
RouterModule.forChild(analyticsRoutes),
MatButtonModule,
MatButtonToggleModule,
@@ -30,8 +27,8 @@ import { analyticsRoutes } from 'app/modules/admin/dashboards/analytics/analytic
MatTableModule,
MatTooltipModule,
NgApexchartsModule,
SharedModule
]
AnalyticsComponent,
],
})
export class AnalyticsModule
{

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AnalyticsService } from 'app/modules/admin/dashboards/analytics/analytics.service';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AnalyticsResolver implements Resolve<any>
{

View File

@@ -7,7 +7,7 @@ export const analyticsRoutes: Route[] = [
path : '',
component: AnalyticsComponent,
resolve : {
data: AnalyticsResolver
}
}
data: AnalyticsResolver,
},
},
];

View File

@@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, tap } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class AnalyticsService
{
@@ -38,9 +38,10 @@ export class AnalyticsService
getData(): Observable<any>
{
return this._httpClient.get('api/dashboards/analytics').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._data.next(response);
})
}),
);
}
}

View File

@@ -1,15 +1,26 @@
import { CurrencyPipe, DecimalPipe, NgClass, NgFor, NgIf, UpperCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { DateTime } from 'luxon';
import { ApexOptions, ChartComponent } from 'ng-apexcharts';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatOptionModule } from '@angular/material/core';
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 { MatSidenavModule } from '@angular/material/sidenav';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { CryptoService } from 'app/modules/admin/dashboards/crypto/crypto.service';
import { DateTime } from 'luxon';
import { ApexOptions, ChartComponent, NgApexchartsModule } from 'ng-apexcharts';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'crypto',
templateUrl : './crypto.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatSidenavModule, NgFor, MatIconModule, NgClass, NgApexchartsModule, MatFormFieldModule, MatSelectModule, MatOptionModule, NgIf, FormsModule, MatInputModule, MatButtonModule, UpperCasePipe, DecimalPipe, CurrencyPipe],
})
export class CryptoComponent implements OnInit, OnDestroy
{
@@ -28,7 +39,7 @@ export class CryptoComponent implements OnInit, OnDestroy
constructor(
private _cryptoService: CryptoService,
private _changeDetectorRef: ChangeDetectorRef,
private _fuseMediaWatcherService: FuseMediaWatcherService
private _fuseMediaWatcherService: FuseMediaWatcherService,
)
{
}
@@ -45,7 +56,8 @@ export class CryptoComponent implements OnInit, OnDestroy
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
.subscribe(({matchingAliases}) =>
{
// Set the drawerMode and drawerOpened if 'lg' breakpoint is active
if ( matchingAliases.includes('lg') )
@@ -66,7 +78,8 @@ export class CryptoComponent implements OnInit, OnDestroy
// Get the data
this._cryptoService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data) => {
.subscribe((data) =>
{
// Store the data
this.data = data;
@@ -101,7 +114,7 @@ export class CryptoComponent implements OnInit, OnDestroy
this.btcOptions = {
chart : {
animations: {
enabled: false
enabled: false,
},
fontFamily: 'inherit',
foreColor : 'inherit',
@@ -109,15 +122,15 @@ export class CryptoComponent implements OnInit, OnDestroy
height : '100%',
type : 'line',
toolbar : {
show: false
show: false,
},
zoom : {
enabled: false
}
enabled: false,
},
},
colors : ['#5A67D8'],
dataLabels: {
enabled: false
enabled: false,
},
grid : {
borderColor : 'var(--fuse-border)',
@@ -126,29 +139,29 @@ export class CryptoComponent implements OnInit, OnDestroy
strokeDashArray: 6,
xaxis : {
lines: {
show: true
}
show: true,
},
},
yaxis : {
lines: {
show: true
}
}
show: true,
},
},
},
legend : {
show: false
show: false,
},
series : this.data.btc.price.series,
stroke : {
width: 2,
curve: 'straight'
curve: 'straight',
},
tooltip : {
shared: true,
theme : 'dark',
y : {
formatter: (value: number): string => '$' + value.toFixed(2)
}
formatter: (value: number): string => '$' + value.toFixed(2),
},
},
xaxis : {
type : 'numeric',
@@ -157,25 +170,25 @@ export class CryptoComponent implements OnInit, OnDestroy
position: 'back',
fill : {
type : 'color',
color: 'var(--fuse-border)'
color: 'var(--fuse-border)',
},
width : 3,
stroke : {
dashArray: 0,
width : 0
width : 0,
},
opacity : 0.9
opacity : 0.9,
},
tickAmount: 8,
axisTicks : {
show : true,
color: 'var(--fuse-border)'
color: 'var(--fuse-border)',
},
axisBorder: {
show: false
show: false,
},
tooltip : {
enabled: false
enabled: false,
},
labels : {
show : true,
@@ -185,53 +198,53 @@ export class CryptoComponent implements OnInit, OnDestroy
hideOverlappingLabels: true,
formatter : (value): string => DateTime.now().minus({minutes: Math.abs(parseInt(value, 10))}).toFormat('HH:mm'),
style : {
colors: 'currentColor'
}
}
colors: 'currentColor',
},
},
},
yaxis : {
axisTicks : {
show : true,
color: 'var(--fuse-border)'
color: 'var(--fuse-border)',
},
axisBorder : {
show: false
show: false,
},
forceNiceScale: true,
labels : {
minWidth : 40,
formatter: (value: number): string => '$' + value.toFixed(0),
style : {
colors: 'currentColor'
}
}
}
colors: 'currentColor',
},
},
},
};
// Watchlist options
this.watchlistChartOptions = {
chart : {
animations: {
enabled: false
enabled: false,
},
width : '100%',
height : '100%',
type : 'line',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#A0AEC0'],
stroke : {
width: 2,
curve: 'smooth'
curve: 'smooth',
},
tooltip: {
enabled: false
enabled: false,
},
xaxis : {
type: 'category'
}
type: 'category',
},
};
}
}

View File

@@ -1,5 +1,4 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatFormFieldModule } from '@angular/material/form-field';
@@ -11,16 +10,14 @@ import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { NgApexchartsModule } from 'ng-apexcharts';
import { SharedModule } from 'app/shared/shared.module';
import { RouterModule } from '@angular/router';
import { CryptoComponent } from 'app/modules/admin/dashboards/crypto/crypto.component';
import { cryptoRoutes } from 'app/modules/admin/dashboards/crypto/crypto.routing';
import { NgApexchartsModule } from 'ng-apexcharts';
@NgModule({
declarations: [
CryptoComponent
],
imports : [
imports: [
RouterModule.forChild(cryptoRoutes),
MatButtonModule,
MatButtonToggleModule,
@@ -34,8 +31,8 @@ import { cryptoRoutes } from 'app/modules/admin/dashboards/crypto/crypto.routing
MatTableModule,
MatTabsModule,
NgApexchartsModule,
SharedModule
]
CryptoComponent,
],
})
export class CryptoModule
{

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { CryptoService } from 'app/modules/admin/dashboards/crypto/crypto.service';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class CryptoResolver implements Resolve<any>
{

View File

@@ -7,7 +7,7 @@ export const cryptoRoutes: Route[] = [
path : '',
component: CryptoComponent,
resolve : {
data: CryptoResolver
}
}
data: CryptoResolver,
},
},
];

View File

@@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, tap } from 'rxjs';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class CryptoService
{
@@ -38,9 +38,10 @@ export class CryptoService
getData(): Observable<any>
{
return this._httpClient.get('api/dashboards/crypto').pipe(
tap((response: any) => {
tap((response: any) =>
{
this._data.next(response);
})
}),
);
}
}

View File

@@ -1,15 +1,23 @@
import { CurrencyPipe, DatePipe, NgClass } from '@angular/common';
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Subject, takeUntil } from 'rxjs';
import { ApexOptions } from 'ng-apexcharts';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { FinanceService } from 'app/modules/admin/dashboards/finance/finance.service';
import { ApexOptions, NgApexchartsModule } from 'ng-apexcharts';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector : 'finance',
templateUrl : './finance.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
standalone : true,
imports : [MatButtonModule, MatIconModule, MatMenuModule, MatDividerModule, NgApexchartsModule, MatTableModule, MatSortModule, NgClass, MatProgressBarModule, CurrencyPipe, DatePipe],
})
export class FinanceComponent implements OnInit, AfterViewInit, OnDestroy
{
@@ -40,7 +48,8 @@ export class FinanceComponent implements OnInit, AfterViewInit, OnDestroy
// Get the data
this._financeService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data) => {
.subscribe((data) =>
{
// Store the data
this.data = data;
@@ -104,8 +113,8 @@ export class FinanceComponent implements OnInit, AfterViewInit, OnDestroy
animations: {
speed : 400,
animateGradually: {
enabled: false
}
enabled: false,
},
},
fontFamily: 'inherit',
foreColor : 'inherit',
@@ -113,33 +122,33 @@ export class FinanceComponent implements OnInit, AfterViewInit, OnDestroy
height : '100%',
type : 'area',
sparkline : {
enabled: true
}
enabled: true,
},
},
colors : ['#A3BFFA', '#667EEA'],
fill : {
colors : ['#CED9FB', '#AECDFD'],
opacity: 0.5,
type : 'solid'
type : 'solid',
},
series : this.data.accountBalance.series,
stroke : {
curve: 'straight',
width: 2
width: 2,
},
tooltip: {
followCursor: true,
theme : 'dark',
x : {
format: 'MMM dd, yyyy'
format: 'MMM dd, yyyy',
},
y : {
formatter: (value): string => value + '%'
}
formatter: (value): string => value + '%',
},
},
xaxis : {
type: 'datetime'
}
type: 'datetime',
},
};
}
}

Some files were not shown because too many files have changed in this diff Show More