(pages/activities) Added Activities page (timeline)

This commit is contained in:
sercan 2021-05-20 15:30:14 +03:00
parent 4694bb401d
commit 3b727fe859
12 changed files with 467 additions and 0 deletions

View File

@ -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)},

View File

@ -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',

View File

@ -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,

View 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)]);
}
}

View 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
}
];

View 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">&bull;</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>

View File

@ -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;
}
}

View 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
{
}

View File

@ -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();
}
}

View 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
}
}
];

View 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);
})
);
}
}

View 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;
}