mirror of
https://github.com/richard-loafle/fuse-angular.git
synced 2026-03-26 02:39:23 +00:00
First pass through updating standalone components.
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
))
|
||||
))
|
||||
}),
|
||||
)),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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') )
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
))
|
||||
))
|
||||
}),
|
||||
)),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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[] = [
|
||||
]
|
||||
}
|
||||
]*/
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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]],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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() )
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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') )
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
))
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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: [''],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
))
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
))
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ export const analyticsRoutes: Route[] = [
|
||||
path : '',
|
||||
component: AnalyticsComponent,
|
||||
resolve : {
|
||||
data: AnalyticsResolver
|
||||
}
|
||||
}
|
||||
data: AnalyticsResolver,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ export const cryptoRoutes: Route[] = [
|
||||
path : '',
|
||||
component: CryptoComponent,
|
||||
resolve : {
|
||||
data: CryptoResolver
|
||||
}
|
||||
}
|
||||
data: CryptoResolver,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user