mirror of
https://github.com/richard-loafle/fuse-angular.git
synced 2025-01-09 12:05:08 +00:00
(pages/activities) Added Activities page (timeline)
This commit is contained in:
parent
4694bb401d
commit
3b727fe859
|
@ -97,6 +97,9 @@ export const appRoutes: Route[] = [
|
|||
// Pages
|
||||
{path: 'pages', children: [
|
||||
|
||||
// Activities
|
||||
{path: 'activities', loadChildren: () => import('app/modules/admin/pages/activities/activities.module').then(m => m.ActivitiesModule)},
|
||||
|
||||
// Authentication
|
||||
{path: 'authentication', loadChildren: () => import('app/modules/admin/pages/authentication/authentication.module').then(m => m.AuthenticationModule)},
|
||||
|
||||
|
|
|
@ -151,6 +151,13 @@ export const defaultNavigation: FuseNavigationItem[] = [
|
|||
type : 'group',
|
||||
icon : 'heroicons_outline:document',
|
||||
children: [
|
||||
{
|
||||
id : 'pages.activities',
|
||||
title: 'Activities',
|
||||
type : 'basic',
|
||||
icon : 'heroicons_outline:menu-alt-2',
|
||||
link : '/pages/activities'
|
||||
},
|
||||
{
|
||||
id : 'pages.authentication',
|
||||
title : 'Authentication',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { AcademyMockApi } from 'app/mock-api/apps/academy/api';
|
||||
import { ActivitiesMockApi } from 'app/mock-api/pages/activities/api';
|
||||
import { AnalyticsMockApi } from 'app/mock-api/dashboards/analytics/api';
|
||||
import { AuthMockApi } from 'app/mock-api/common/auth/api';
|
||||
import { CalendarMockApi } from 'app/mock-api/apps/calendar/api';
|
||||
|
@ -21,6 +22,7 @@ import { UserMockApi } from 'app/mock-api/common/user/api';
|
|||
|
||||
export const mockApiServices = [
|
||||
AcademyMockApi,
|
||||
ActivitiesMockApi,
|
||||
AnalyticsMockApi,
|
||||
AuthMockApi,
|
||||
CalendarMockApi,
|
||||
|
|
38
src/app/mock-api/pages/activities/api.ts
Normal file
38
src/app/mock-api/pages/activities/api.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FuseMockApiService } from '@fuse/lib/mock-api';
|
||||
import { activities as activitiesData } from 'app/mock-api/pages/activities/data';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ActivitiesMockApi
|
||||
{
|
||||
private _activities: any = activitiesData;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _fuseMockApiService: FuseMockApiService)
|
||||
{
|
||||
// Register Mock API handlers
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Register Mock API handlers
|
||||
*/
|
||||
registerHandlers(): void
|
||||
{
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Activities - GET
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
this._fuseMockApiService
|
||||
.onGet('api/pages/activities')
|
||||
.reply(() => [200, cloneDeep(this._activities)]);
|
||||
}
|
||||
}
|
87
src/app/mock-api/pages/activities/data.ts
Normal file
87
src/app/mock-api/pages/activities/data.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
/* eslint-disable */
|
||||
import * as moment from 'moment';
|
||||
import { Activity } from 'app/modules/admin/pages/activities/activities.types';
|
||||
|
||||
export const activities: Activity[] = [
|
||||
{
|
||||
id : '493190c9-5b61-4912-afe5-78c21f1044d7',
|
||||
icon : 'heroicons_solid:star',
|
||||
description : 'Your submission has been accepted',
|
||||
date : moment().subtract(25, 'minutes').toISOString(), // 25 minutes ago
|
||||
extraContent: `<div class="font-bold">Congratulations for your acceptance!</div><br>
|
||||
<div>Hi Brian,<br>Your submission has been accepted and you are ready to move into the next phase. Once you are ready, reach out to me and we will ...</div>`
|
||||
},
|
||||
{
|
||||
id : '6e3e97e5-effc-4fb7-b730-52a151f0b641',
|
||||
image : 'assets/images/avatars/male-04.jpg',
|
||||
description : '<strong>Leo Gill</strong> added you to <strong>Top Secret Project</strong> group and assigned you as a <strong>Project Manager</strong>',
|
||||
date : moment().subtract(50, 'minutes').toISOString(), // 50 minutes ago
|
||||
linkedContent: 'Top Secret Project',
|
||||
link : '/dashboards/project',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : 'b91ccb58-b06c-413b-b389-87010e03a120',
|
||||
icon : 'heroicons_solid:mail',
|
||||
description : 'You have 15 unread mails across 3 mailboxes',
|
||||
date : moment().subtract(3, 'hours').toISOString(), // 3 hours ago
|
||||
linkedContent: 'Mailbox',
|
||||
link : '/apps/mailbox',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : '541416c9-84a7-408a-8d74-27a43c38d797',
|
||||
icon : 'heroicons_solid:refresh',
|
||||
description : 'Your <strong>Docker container</strong> is ready to publish',
|
||||
date : moment().subtract(5, 'hours').toISOString(), // 5 hours ago
|
||||
linkedContent: 'Download the Container',
|
||||
link : '.',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : 'ef7b95a7-8e8b-4616-9619-130d9533add9',
|
||||
image : 'assets/images/avatars/male-06.jpg',
|
||||
description: '<strong>Roger Murray</strong> accepted your friend request',
|
||||
date : moment().subtract(7, 'hours').toISOString() // 7 hours ago
|
||||
},
|
||||
{
|
||||
id : 'eb8aa470-635e-461d-88e1-23d9ea2a5665',
|
||||
image : 'assets/images/avatars/female-04.jpg',
|
||||
description: '<strong>Sophie Stone</strong> sent you a direct message',
|
||||
date : moment().subtract(9, 'hours').toISOString() // 9 hours ago
|
||||
},
|
||||
{
|
||||
id : 'b85c2338-cc98-4140-bbf8-c226ce4e395e',
|
||||
icon : 'heroicons_solid:mail',
|
||||
description : 'You have 3 new mails',
|
||||
date : moment().subtract(1, 'day').toISOString(), // 1 day ago
|
||||
extraContent: `<div class="space-y-2">
|
||||
<div class="font-medium">Please review and sign the attached agreement</div>
|
||||
<div class="font-medium">Delivery address confirmation</div>
|
||||
<div class="font-medium">Previous clients and their invoices</div>
|
||||
</div>`,
|
||||
linkedContent: 'Mailbox',
|
||||
link : '/apps/mailbox',
|
||||
useRouter : true
|
||||
},
|
||||
{
|
||||
id : 'fd0f01b4-f3de-4333-add5-cd86850279f8',
|
||||
image : 'assets/images/avatars/female-02.jpg',
|
||||
description : '<strong>Tina Harris</strong> started a chat with you',
|
||||
date : moment().subtract(1, 'day').toISOString(), // 1 day ago,
|
||||
linkedContent: 'Go to Chat (Tina Harris)',
|
||||
link : '/apps/chat/5636c0ba-fa47-42ca-9160-27340583041e'
|
||||
},
|
||||
{
|
||||
id : '8f8e1bf9-4661-4939-9e43-390957b60f42',
|
||||
icon : 'heroicons_solid:star',
|
||||
description: 'Your submission has been accepted and you are ready to sign-up for the final assigment which will be ready in 2 days',
|
||||
date : moment().subtract(3, 'days').toISOString() // 3 days ago
|
||||
},
|
||||
{
|
||||
id : '30af917b-7a6a-45d1-822f-9e7ad7f8bf69',
|
||||
icon : 'heroicons_solid:refresh',
|
||||
description: 'Your Vagrant container is ready to download',
|
||||
date : moment().subtract(4, 'day').toISOString() // 4 days ago
|
||||
}
|
||||
];
|
117
src/app/modules/admin/pages/activities/activities.component.html
Normal file
117
src/app/modules/admin/pages/activities/activities.component.html
Normal file
|
@ -0,0 +1,117 @@
|
|||
<div class="relative flex flex-col flex-auto min-w-0 overflow-hidden">
|
||||
|
||||
<!-- Main -->
|
||||
<div class="flex flex-col flex-auto px-6 py-10 sm:px-16 sm:pt-18 sm:pb-20">
|
||||
|
||||
<!-- Activity feed -->
|
||||
<div class="w-full max-w-3xl">
|
||||
|
||||
<!-- Title -->
|
||||
<div class="text-4xl font-extrabold tracking-tight leading-none">All Activities</div>
|
||||
<div class="mt-1.5 text-lg text-secondary">Application wide activities are listed here as individual items, starting with the most recent.</div>
|
||||
|
||||
<ng-container *ngIf="(activities$ | async) as activities; else loading">
|
||||
<ng-container *ngIf="activities.length; else noActivity">
|
||||
<div class="mt-8">
|
||||
<ol>
|
||||
<!-- Activities -->
|
||||
<ng-container *ngFor="let activity of activities; let i = index; let first = first; let last = last; trackBy: trackByFn">
|
||||
|
||||
<!-- Date separator -->
|
||||
<ng-container *ngIf="first || !isSameDay(activity.date, activities[i - 1].date)">
|
||||
<li class="relative flex py-7">
|
||||
<div class="relative py-2 px-8 text-md font-medium leading-5 rounded-full bg-primary text-on-primary">
|
||||
{{getRelativeFormat(activity.date)}}
|
||||
</div>
|
||||
</li>
|
||||
</ng-container>
|
||||
|
||||
<!-- Activity -->
|
||||
<li class="relative flex py-7">
|
||||
<!-- Line -->
|
||||
<ng-container *ngIf="!last && isSameDay(activity.date, activities[i + 1].date)">
|
||||
<div class="absolute top-7 left-5 w-0.5 h-full -ml-px bg-gray-300 dark:bg-gray-600"></div>
|
||||
</ng-container>
|
||||
|
||||
<div class="relative flex flex-auto">
|
||||
<!-- Icon -->
|
||||
<ng-container *ngIf="activity.icon && !activity.image">
|
||||
<div class="flex flex-shrink-0 items-center justify-center w-10 h-10 mr-4 rounded-full bg-gray-500">
|
||||
<mat-icon
|
||||
class="icon-size-5 text-white"
|
||||
[svgIcon]="activity.icon">
|
||||
</mat-icon>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Image -->
|
||||
<ng-container *ngIf="activity.image">
|
||||
<img
|
||||
class="flex-shrink-0 w-10 h-10 mr-4 rounded-full overflow-hidden object-cover object-center"
|
||||
[src]="activity.image"
|
||||
[alt]="'Activity image'">
|
||||
</ng-container>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex flex-col flex-auto items-start">
|
||||
<!-- Description -->
|
||||
<ng-container *ngIf="activity.description">
|
||||
<div
|
||||
[innerHTML]="activity.description"></div>
|
||||
</ng-container>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center mt-2 sm:mt-1 sm:space-x-2 text-md leading-5">
|
||||
<!-- Date -->
|
||||
<div class="text-secondary">
|
||||
{{activity.date | date:'MMM dd, h:mm a'}}
|
||||
</div>
|
||||
<!-- Linked content -->
|
||||
<ng-container *ngIf="activity.linkedContent">
|
||||
<div class="hidden sm:block">•</div>
|
||||
<!-- Internal link -->
|
||||
<ng-container *ngIf="activity.useRouter">
|
||||
<a
|
||||
class="cursor-pointer text-primary"
|
||||
[routerLink]="activity.link">
|
||||
{{activity.linkedContent}}
|
||||
</a>
|
||||
</ng-container>
|
||||
<!-- External link -->
|
||||
<ng-container *ngIf="!activity.useRouter">
|
||||
<a
|
||||
class="cursor-pointer text-primary"
|
||||
[href]="activity.link"
|
||||
target="_blank">
|
||||
{{activity.linkedContent}}
|
||||
</a>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<!-- Extra content -->
|
||||
<ng-container *ngIf="activity.extraContent">
|
||||
<div
|
||||
class="mt-4 py-4 px-5 rounded bg-gray-200 dark:bg-gray-800"
|
||||
[innerHTML]="activity.extraContent"></div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ol>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<!-- Loading template -->
|
||||
<ng-template #loading>
|
||||
Loading...
|
||||
</ng-template>
|
||||
|
||||
<!-- No Activity template -->
|
||||
<ng-template #noActivity>
|
||||
There are is activity at the moment...
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,87 @@
|
|||
import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as moment from 'moment';
|
||||
import { Activity } from 'app/modules/admin/pages/activities/activities.types';
|
||||
import { ActivitiesService } from 'app/modules/admin/pages/activities/activities.service';
|
||||
|
||||
@Component({
|
||||
selector : 'activity',
|
||||
templateUrl : './activities.component.html',
|
||||
encapsulation : ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ActivitiesComponent implements OnInit
|
||||
{
|
||||
activities$: Observable<Activity[]>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(public _activityService: ActivitiesService)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void
|
||||
{
|
||||
// Get the activities
|
||||
this.activities$ = this._activityService.activities;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns whether the given dates are different days
|
||||
*
|
||||
* @param current
|
||||
* @param compare
|
||||
*/
|
||||
isSameDay(current: string, compare: string): boolean
|
||||
{
|
||||
return moment(current, moment.ISO_8601).isSame(moment(compare, moment.ISO_8601), 'day');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relative format of the given date
|
||||
*
|
||||
* @param date
|
||||
*/
|
||||
getRelativeFormat(date: string): string
|
||||
{
|
||||
const today = moment().startOf('day');
|
||||
const yesterday = moment().subtract(1, 'day').startOf('day');
|
||||
|
||||
// Is today?
|
||||
if ( moment(date, moment.ISO_8601).isSame(today, 'day') )
|
||||
{
|
||||
return 'Today';
|
||||
}
|
||||
|
||||
// Is yesterday?
|
||||
if ( moment(date, moment.ISO_8601).isSame(yesterday, 'day') )
|
||||
{
|
||||
return 'Yesterday';
|
||||
}
|
||||
|
||||
return moment(date, moment.ISO_8601).fromNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any
|
||||
{
|
||||
return item.id || index;
|
||||
}
|
||||
}
|
20
src/app/modules/admin/pages/activities/activities.module.ts
Normal file
20
src/app/modules/admin/pages/activities/activities.module.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
import { ActivitiesComponent } from 'app/modules/admin/pages/activities/activities.component';
|
||||
import { activitiesRoutes } from 'app/modules/admin/pages/activities/activities.routing';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ActivitiesComponent
|
||||
],
|
||||
imports : [
|
||||
RouterModule.forChild(activitiesRoutes),
|
||||
MatIconModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class ActivitiesModule
|
||||
{
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ActivitiesService } from 'app/modules/admin/pages/activities/activities.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ActivitiesResolver implements Resolve<any>
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _activityService: ActivitiesService)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Resolve
|
||||
*
|
||||
* @param route
|
||||
* @param state
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
|
||||
{
|
||||
return this._activityService.getActivities();
|
||||
}
|
||||
}
|
13
src/app/modules/admin/pages/activities/activities.routing.ts
Normal file
13
src/app/modules/admin/pages/activities/activities.routing.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Route } from '@angular/router';
|
||||
import { ActivitiesComponent } from 'app/modules/admin/pages/activities/activities.component';
|
||||
import { ActivitiesResolver } from 'app/modules/admin/pages/activities/activities.resolvers';
|
||||
|
||||
export const activitiesRoutes: Route[] = [
|
||||
{
|
||||
path : '',
|
||||
component: ActivitiesComponent,
|
||||
resolve : {
|
||||
activities: ActivitiesResolver
|
||||
}
|
||||
}
|
||||
];
|
49
src/app/modules/admin/pages/activities/activities.service.ts
Normal file
49
src/app/modules/admin/pages/activities/activities.service.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { Activity } from 'app/modules/admin/pages/activities/activities.types';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ActivitiesService
|
||||
{
|
||||
// Private
|
||||
private _activities: BehaviorSubject<any> = new BehaviorSubject(null);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _httpClient: HttpClient)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Getter for activities
|
||||
*/
|
||||
get activities(): Observable<any>
|
||||
{
|
||||
return this._activities.asObservable();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get activities
|
||||
*/
|
||||
getActivities(): Observable<any>
|
||||
{
|
||||
return this._httpClient.get<Activity[]>('api/pages/activities').pipe(
|
||||
tap((response: Activity[]) => {
|
||||
this._activities.next(response);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
12
src/app/modules/admin/pages/activities/activities.types.ts
Normal file
12
src/app/modules/admin/pages/activities/activities.types.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export interface Activity
|
||||
{
|
||||
id: string;
|
||||
icon?: string;
|
||||
image?: string;
|
||||
description?: string;
|
||||
date: string;
|
||||
extraContent?: string;
|
||||
linkedContent?: string;
|
||||
link?: string;
|
||||
useRouter?: boolean;
|
||||
}
|
Loading…
Reference in New Issue
Block a user