diff --git a/client/src/adapters/project.ts b/client/src/adapters/project.ts
index f20e849f4bf020c72537f36e75e984134a738e59..84beae4b2f84e3d6194e6046dd530596b0071acf 100644
--- a/client/src/adapters/project.ts
+++ b/client/src/adapters/project.ts
@@ -3,6 +3,7 @@ import { executeApiGet, executeApiPost, executeApiPut } from './util';
 import { Task } from './task';
 import { AssignedUser } from './user';
 import { Work } from './work';
+import { Activity, Completion } from './util';
 
 export interface Project {
     id: string;
@@ -50,6 +51,20 @@ export function getProjectWork(uuid: string): Promise<Work[]> {
     })), "Failed to get project work");
 }
 
+export function getProjectActivity(uuid: string, from: Date = new Date(0), to: Date = new Date()): Promise<Activity[]> {
+    return executeApiGet(
+        `project/${uuid}/activity?since=${from.getTime()}&to=${to.getTime()}`,
+        ({ activity }) => activity, "Failed to get project activity"
+    );
+}
+
+export function getProjectCompletion(uuid: string, from: Date = new Date(0), to: Date = new Date()): Promise<Completion> {
+    return executeApiGet(
+        `project/${uuid}/completion?since=${from.getTime()}&to=${to.getTime()}`,
+        ({ completion }) => completion, "Failed to get project completion"
+    );
+}
+
 interface NewTeamData {
     teams: Array<string>;
     name: string;
diff --git a/client/src/adapters/team.ts b/client/src/adapters/team.ts
index 257b2573b5e385a2ef3c86bb233e922917d25a11..d9b4c609eb1ecaea5e7c9f8ed8c59dd7c2e2ed47 100644
--- a/client/src/adapters/team.ts
+++ b/client/src/adapters/team.ts
@@ -3,6 +3,7 @@ import { executeApiDelete, executeApiGet, executeApiPost, executeApiPut } from '
 import { User } from './user';
 import { ReducedProject } from './project';
 import { Work } from './work';
+import { Activity, Completion } from './util';
 
 export interface Team {
     id: string;
@@ -50,6 +51,20 @@ export function getTeamWork(uuid: string): Promise<Work[]> {
     })), "Failed to get team work");
 }
 
+export function getTeamActivity(uuid: string, from: Date = new Date(0), to: Date = new Date()): Promise<Activity[]> {
+    return executeApiGet(
+        `team/${uuid}/activity?since=${from.getTime()}&to=${to.getTime()}`,
+        ({ activity }) => activity, "Failed to get team activity"
+    );
+}
+
+export function getTeamCompletion(uuid: string, from: Date = new Date(0), to: Date = new Date()): Promise<Completion> {
+    return executeApiGet(
+        `team/${uuid}/completion?since=${from.getTime()}&to=${to.getTime()}`,
+        ({ completion }) => completion, "Failed to get team completion"
+    );
+}
+
 export function createTeam(name: string): Promise<string> {
     return executeApiPost(`team`, { name: name }, ({ id }) => id, "Failed to create team");
 }
diff --git a/client/src/adapters/user.ts b/client/src/adapters/user.ts
index 8f02b26fef56d8a6bf1f34b69536f29b97a2f7dc..4b61bbf211e5f6da6be661ef5de0c13a2154e9d6 100644
--- a/client/src/adapters/user.ts
+++ b/client/src/adapters/user.ts
@@ -4,6 +4,7 @@ import { apiRoot } from 'config';
 import { executeApiGet, executeApiPut } from './util';
 import { Task } from './task';
 import { Work } from './work';
+import { Activity, Completion } from './util';
 
 export interface User {
     id: string;
@@ -46,6 +47,20 @@ export function getUserWork(): Promise<Work> {
     })), "Failed to get user work");
 }
 
+export function getUserActivity(from: Date = new Date(0), to: Date = new Date()): Promise<Activity[]> {
+    return executeApiGet(
+        `user/activity?since=${from.getTime()}&to=${to.getTime()}`,
+        ({ activity }) => activity, "Failed to get user activity"
+    );
+}
+
+export function getUserCompletion(from: Date = new Date(0), to: Date = new Date()): Promise<Completion> {
+    return executeApiGet(
+        `user/completion?since=${from.getTime()}&to=${to.getTime()}`,
+        ({ completion }) => completion, "Failed to get user completion"
+    );
+}
+
 export function getUser(uuid: string): Promise<User> {
     return executeApiGet(`user/${uuid}`, ({ user }) => user, "Failed to get user");
 }
diff --git a/client/src/adapters/util.ts b/client/src/adapters/util.ts
index 08a6b61442e3f8ad4da0a73079f4ef465f98b1dd..db9ea4afb1f9c3b7e39a3847782c6c7bc08cf4ea 100644
--- a/client/src/adapters/util.ts
+++ b/client/src/adapters/util.ts
@@ -3,6 +3,18 @@ import { apiRoot } from 'config';
 
 import { getAuthHeader } from './auth';
 
+export interface Activity {
+    day: string;
+    time: number;
+}
+
+export interface Completion {
+    open: number,
+    closed: number,
+    suspended: number,
+    overdue: number,
+}
+
 async function executeApiRequest<T>(path: string, method: string, body: any, onSuccess: (data: any) => T, errorMessage: string): Promise<T> {
     try {
         const response = await fetch(`${apiRoot}/${path}`, {
diff --git a/server/src/v1/project.ts b/server/src/v1/project.ts
index 0e25838d6a308b250bc5e41759fe0dcd5a1729ae..d987bfb16b4d0f13e6f9831723a0ecb39710471e 100644
--- a/server/src/v1/project.ts
+++ b/server/src/v1/project.ts
@@ -223,6 +223,107 @@ project.get('/:uuid/work', async (req, res) => {
     }
 });
 
+project.get('/:uuid/activity', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const since = (req.query.since ?? 0) as number;
+            const to = (req.query.to ?? Date.now()) as number;
+            const activity = await database(
+                    database('team_members')
+                    .innerJoin('team_projects', 'team_members.team_id', 'team_projects.team_id')
+                    .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
+                    .innerJoin('workhours', 'tasks.id', 'workhours.task_id')
+                    .select({
+                        started: 'workhours.started',
+                        finished: 'workhours.finished',
+                    })
+                    .where({
+                        'team_members.user_id': req.body.token.id,
+                        'team_projects.project_id': id,
+                    })
+                    .andWhereNot({ 'workhours.finished': null })
+                    .andWhere('workhours.started', '>=', since)
+                    .andWhere('workhours.started', '<=', to)
+                    .groupBy('workhours.id')
+                )
+                .select({
+                    day: database.raw('Date(`started` / 1000, \'unixepoch\')'),
+                })
+                .sum({ time: database.raw('`finished` - `started`') })
+                .groupBy('day');
+            res.status(200).json({
+                status: 'success',
+                activity: activity,
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        console.error(e);
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get activity',
+        });
+    }
+});
+
+project.get('/:uuid/completion', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const since = (req.query.since ?? 0) as number;
+            const to = (req.query.to ?? Date.now()) as number;
+            const completion = await database(
+                    database('team_members')
+                        .innerJoin('team_projects', 'team_members.team_id', 'team_projects.team_id')
+                        .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
+                        .leftJoin('task_requirements', 'tasks.id', 'task_requirements.task_id')
+                        .leftJoin('workhours', 'tasks.id', 'workhours.task_id')
+                        .select({
+                            id: 'tasks.id',
+                            status: database.raw(
+                                'Case When `tasks`.`status` = \'open\' '
+                                + 'And Sum(`task_requirements`.`time` * 60 * 1000) < Sum(`workhours`.`finished` - `workhours`.`started`) '
+                                + 'Then \'overdue\' Else `tasks`.`status` End'),
+                        })
+                        .where({
+                            'team_members.user_id': req.body.token.id,
+                            'team_projects.project_id': id,
+                        })
+                        .andWhere('tasks.edited', '>=', since)
+                        .andWhere('tasks.created', '<=', to)
+                        .groupBy('tasks.id')
+                )
+                .select({
+                    status: 'status',
+                })
+                .count({ count: 'id' })
+                .groupBy('status') as any[];
+            res.status(200).json({
+                status: 'success',
+                completion: completion.reduce((object, { status, count }) => ({
+                    ...object,
+                    [status]: count,
+                }), { open: 0, closed: 0, suspended: 0, overdue: 0 }),
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get completion',
+        });
+    }
+});
+
 interface AddProjectBody {
     teams: Array<string>;
     name: string;
diff --git a/server/src/v1/team.ts b/server/src/v1/team.ts
index 7df69d29a5eb14e45cda33ec1df9c9fe0fb6d482..4c229de0a400ad821287cf10257aae7a10a6851b 100644
--- a/server/src/v1/team.ts
+++ b/server/src/v1/team.ts
@@ -349,6 +349,98 @@ team.get('/:uuid/work', async (req, res) => {
     }
 });
 
+team.get('/:uuid/activity', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const since = (req.query.since ?? 0) as number;
+            const to = (req.query.to ?? Date.now()) as number;
+            const activity = await database({ ut: 'team_members' })
+                .innerJoin('team_members', 'ut.team_id', 'team_members.team_id')
+                .innerJoin('workhours', 'team_members.user_id', 'workhours.user_id')
+                .select({
+                    day: database.raw('Date(`workhours`.`started` / 1000, \'unixepoch\')'),
+                })
+                .sum({ time: database.raw('`workhours`.`finished` - `workhours`.`started`') })
+                .where({
+                    'ut.user_id': req.body.token.id,
+                    'ut.team_id': id,
+                })
+                .andWhereNot({ 'workhours.finished': null })
+                .andWhere('workhours.started', '>=', since)
+                .andWhere('workhours.started', '<=', to)
+                .groupBy('day');
+            res.status(200).json({
+                status: 'success',
+                activity: activity,
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get activity',
+        });
+    }
+});
+
+team.get('/:uuid/completion', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const since = (req.query.since ?? 0) as number;
+            const to = (req.query.to ?? Date.now()) as number;
+            const completion = await database(
+                    database('team_members')
+                        .innerJoin('team_projects', 'team_members.team_id', 'team_projects.team_id')
+                        .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
+                        .leftJoin('task_requirements', 'tasks.id', 'task_requirements.task_id')
+                        .leftJoin('workhours', 'tasks.id', 'workhours.task_id')
+                        .select({
+                            id: 'tasks.id',
+                            status: database.raw(
+                                'Case When `tasks`.`status` = \'open\' '
+                                + 'And Sum(`task_requirements`.`time` * 60 * 1000) < Sum(`workhours`.`finished` - `workhours`.`started`) '
+                                + 'Then \'overdue\' Else `tasks`.`status` End'),
+                        })
+                        .where({
+                            'team_members.user_id': req.body.token.id,
+                            'team_members.team_id': id,
+                        })
+                        .andWhere('tasks.edited', '>=', since)
+                        .andWhere('tasks.created', '<=', to)
+                        .groupBy('tasks.id')
+                )
+                .select({
+                    status: 'status',
+                })
+                .count({ count: 'id' })
+                .groupBy('status') as any[];
+            res.status(200).json({
+                status: 'success',
+                completion: completion.reduce((object, { status, count }) => ({
+                    ...object,
+                    [status]: count,
+                }), { open: 0, closed: 0, suspended: 0, overdue: 0 }),
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get completion',
+        });
+    }
+});
+
 interface AddRoleBody {
     name: string;
     token: Token;
diff --git a/server/src/v1/user.ts b/server/src/v1/user.ts
index e670ca667e1562fd2f9fc5fef0fdb98bb81ed3b8..8d1db7eec2e0114ca7006d0762814a42de048fcc 100644
--- a/server/src/v1/user.ts
+++ b/server/src/v1/user.ts
@@ -185,6 +185,78 @@ user.get('/work', async (req, res) => {
     }
 });
 
+user.get('/activity', async (req, res) => {
+    try {
+        const since = (req.query.since ?? 0) as number;
+        const to = (req.query.to ?? Date.now()) as number;
+        const activity = await database('workhours')
+            .select({
+                day: database.raw('Date(`started` / 1000, \'unixepoch\')'),
+            })
+            .sum({ time: database.raw('`finished` - `started`') })
+            .where({
+                'workhours.user_id': req.body.token.id,
+            })
+            .andWhereNot({ 'workhours.finished': null })
+            .andWhere('workhours.started', '>=', since)
+            .andWhere('workhours.started', '<=', to)
+            .groupBy('day');
+        res.status(200).json({
+            status: 'success',
+            activity: activity,
+        });
+    } catch (e) {
+        console.error(e);
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get activity',
+        });
+    }
+});
+
+user.get('/completion', async (req, res) => {
+    try {
+        const since = (req.query.since ?? 0) as number;
+        const to = (req.query.to ?? Date.now()) as number;
+        const completion = await database(
+                database('task_assignees')
+                    .leftJoin('tasks', 'task_assignees.task_id', 'tasks.id')
+                    .leftJoin('task_requirements', 'tasks.id', 'task_requirements.task_id')
+                    .leftJoin('workhours', 'tasks.id', 'workhours.task_id')
+                    .select({
+                        id: 'tasks.id',
+                        status: database.raw(
+                            'Case When `tasks`.`status` = \'open\' '
+                            + 'And Sum(`task_requirements`.`time` * 60 * 1000) < Sum(`workhours`.`finished` - `workhours`.`started`) '
+                            + 'Then \'overdue\' Else `tasks`.`status` End'),
+                    })
+                    .where({
+                        'task_assignees.user_id': req.body.token.id,
+                    })
+                    .andWhere('tasks.edited', '>=', since)
+                    .andWhere('tasks.created', '<=', to)
+                    .groupBy('tasks.id')
+            )
+            .select({
+                status: 'status',
+            })
+            .count({ count: 'id' })
+            .groupBy('status') as any[];
+        res.status(200).json({
+            status: 'success',
+            completion: completion.reduce((object, { status, count }) => ({
+                ...object,
+                [status]: count,
+            }), { open: 0, closed: 0, suspended: 0, overdue: 0 }),
+        });
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get completion',
+        });
+    }
+});
+
 interface UserUpdateBody {
     token: Token;
     realname?: string;