feat: migrate SummaryComponent to Django Rest and replace user with userId
This commit is contained in:
parent
518a688272
commit
876aadcd05
6 changed files with 116 additions and 113 deletions
|
|
@ -50,9 +50,9 @@
|
||||||
(mouseleave)="closeDialog()"
|
(mouseleave)="closeDialog()"
|
||||||
[style.background-color]="user.color"
|
[style.background-color]="user.color"
|
||||||
>{{ user.initials }}</span
|
>{{ user.initials }}</span
|
||||||
>
|
>} } @for (user of task.userData; track user) { @for (assigned of
|
||||||
} @for (assigned of task.assignees; track assigned.id) { @if
|
task.assignees; track assigned.id) { @if (assigned.userId === user.id)
|
||||||
(assigned.user === user.id) {
|
{
|
||||||
<span
|
<span
|
||||||
class="footer-badged"
|
class="footer-badged"
|
||||||
(mousemove)="openDialog(user.id, $event)"
|
(mousemove)="openDialog(user.id, $event)"
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
{{ "summary.headlineDescription" | translate }}
|
{{ "summary.headlineDescription" | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (!isLoading) {
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="left-container">
|
<div class="left-container">
|
||||||
<!-- TOP -->
|
<!-- Top Frame -->
|
||||||
<div class="top-frame">
|
<div class="top-frame">
|
||||||
<div class="todo-done" routerLink="/board">
|
<div class="todo-done" routerLink="/board">
|
||||||
<div class="circle">
|
<div class="circle">
|
||||||
|
|
@ -17,7 +18,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span>{{ displayNumberOfTaskStatus("todo") }}</span>
|
<span>{{ taskStatusCounts.get("todo") || 0 }}</span>
|
||||||
<p>{{ "summary.todo" | translate }}</p>
|
<p>{{ "summary.todo" | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -26,18 +27,19 @@
|
||||||
<img src="./../../../assets/img/summary/rake.svg" alt="" />
|
<img src="./../../../assets/img/summary/rake.svg" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span>{{ displayNumberOfTaskStatus("done") }}</span>
|
<span>{{ taskStatusCounts.get("done") || 0 }}</span>
|
||||||
<p>{{ "summary.done" | translate }}</p>
|
<p>{{ "summary.done" | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- MIDDLE -->
|
|
||||||
|
<!-- Middle Frame -->
|
||||||
<div class="middle-frame">
|
<div class="middle-frame">
|
||||||
<div class="urgent" routerLink="/board">
|
<div class="urgent" routerLink="/board">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<img src="./../../../assets/img/summary/urgent.svg" alt="" />
|
<img src="./../../../assets/img/summary/urgent.svg" alt="" />
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span>{{ displayNumberOfTaskStatusUrgent().length }}</span>
|
<span>{{ urgentTasks.length || 0 }}</span>
|
||||||
<p>{{ "summary.urgent" | translate }}</p>
|
<p>{{ "summary.urgent" | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -45,8 +47,8 @@
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="date">
|
<div class="date">
|
||||||
<div>
|
<div>
|
||||||
@if (displayNumberOfTaskStatusUrgent().length) {
|
@if (urgentTasks.length) {
|
||||||
<span>{{ nextUrgentTask() }}</span>
|
<span>{{ nextUrgentTask }}</span>
|
||||||
<p class="spacer">
|
<p class="spacer">
|
||||||
{{ "summary.upcomingDeadline" | translate }}
|
{{ "summary.upcomingDeadline" | translate }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -58,11 +60,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- BOTTOM -->
|
|
||||||
|
<!-- Bottom Frame -->
|
||||||
<div class="bottom-frame">
|
<div class="bottom-frame">
|
||||||
<div class="tasks" routerLink="/board">
|
<div class="tasks" routerLink="/board">
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span>{{ displayNumberOfAllTasks() }}</span>
|
<span>{{ totalTasks || 0 }}</span>
|
||||||
<p>
|
<p>
|
||||||
{{ "summary.taskBoard0" | translate }}<br />{{
|
{{ "summary.taskBoard0" | translate }}<br />{{
|
||||||
"summary.taskBoard1" | translate
|
"summary.taskBoard1" | translate
|
||||||
|
|
@ -72,7 +75,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="tasks" routerLink="/board">
|
<div class="tasks" routerLink="/board">
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span>{{ displayNumberOfTaskStatus("inprogress") }}</span>
|
<span>{{ taskStatusCounts.get("inprogress") || 0 }}</span>
|
||||||
<p>
|
<p>
|
||||||
{{ "summary.taskProgress0" | translate }}<br />{{
|
{{ "summary.taskProgress0" | translate }}<br />{{
|
||||||
"summary.taskProgress1" | translate
|
"summary.taskProgress1" | translate
|
||||||
|
|
@ -82,7 +85,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="tasks" routerLink="/board">
|
<div class="tasks" routerLink="/board">
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span>{{ displayNumberOfTaskStatus("awaitfeedback") }}</span>
|
<span>{{ taskStatusCounts.get("awaitfeedback") || 0 }}</span>
|
||||||
<p>
|
<p>
|
||||||
{{ "summary.taskAwaitingFeedback0" | translate }}<br />{{
|
{{ "summary.taskAwaitingFeedback0" | translate }}<br />{{
|
||||||
"summary.taskAwaitingFeedback1" | translate
|
"summary.taskAwaitingFeedback1" | translate
|
||||||
|
|
@ -92,22 +95,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Greeting -->
|
||||||
<div class="right-container">
|
<div class="right-container">
|
||||||
<p>{{ displayGreeting() }},</p>
|
<p>{{ greeting }},</p>
|
||||||
|
<!-- TODO -->
|
||||||
<span id="welcome-user"
|
<span id="welcome-user"
|
||||||
>{{
|
>{{ "firstName" }}<br />
|
||||||
this.firebaseService.getUserDetails(
|
{{ "lastName" }}</span
|
||||||
firebaseService.getCurrentUserId(),
|
|
||||||
"firstName"
|
|
||||||
)
|
|
||||||
}}<br />
|
|
||||||
{{
|
|
||||||
this.firebaseService.getUserDetails(
|
|
||||||
firebaseService.getCurrentUserId(),
|
|
||||||
"lastName"
|
|
||||||
)
|
|
||||||
}}</span
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="content">
|
||||||
|
<div class="left-container">
|
||||||
|
<app-loading-spinner></app-loading-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -2,124 +2,125 @@ import { Component } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { FirebaseService } from '../../services/firebase.service';
|
import { FirebaseService } from '../../services/firebase.service';
|
||||||
|
import { Task } from '../../interfaces/task.interface';
|
||||||
|
import { TaskService } from '../../services/task.service';
|
||||||
|
import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-summary',
|
selector: 'app-summary',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterModule, TranslateModule],
|
imports: [RouterModule, TranslateModule, LoadingSpinnerComponent],
|
||||||
templateUrl: './summary.component.html',
|
templateUrl: './summary.component.html',
|
||||||
styleUrl: './summary.component.scss',
|
styleUrl: './summary.component.scss',
|
||||||
})
|
})
|
||||||
export class SummaryComponent {
|
export class SummaryComponent {
|
||||||
nextUrgendTask: number[] = [];
|
nextUrgendTask: number[] = [];
|
||||||
|
|
||||||
|
allTasks: Task[] = [];
|
||||||
|
isLoading = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public firebaseService: FirebaseService,
|
public firebaseService: FirebaseService,
|
||||||
private translateService: TranslateService
|
private translateService: TranslateService,
|
||||||
|
private taskService: TaskService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the total number of tasks.
|
* This method loads all tasks from the TaskService.
|
||||||
*
|
|
||||||
* @returns The number of total tasks.
|
|
||||||
*/
|
*/
|
||||||
displayNumberOfAllTasks() {
|
ngOnInit() {
|
||||||
return this.firebaseService.getAllTasks().length;
|
this.loadTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of tasks with the given status.
|
* Loads all tasks from the TaskService.
|
||||||
*
|
|
||||||
* @param query The task status to query for.
|
|
||||||
* @returns The number of tasks with the given status.
|
|
||||||
*/
|
*/
|
||||||
displayNumberOfTaskStatus(query: string) {
|
loadTasks(): void {
|
||||||
const filteredTasks = this.firebaseService
|
this.isLoading = true;
|
||||||
.getAllTasks()
|
|
||||||
.filter((task) => task.status === query);
|
this.taskService.loadAllTasks().subscribe({
|
||||||
return filteredTasks.length;
|
next: (result) => {
|
||||||
|
this.allTasks = result.allTasks;
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error loading the tasks:', err);
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of tasks with the "urgent" priority.
|
* Gets the total number of tasks.
|
||||||
*
|
* @returns The total count of tasks.
|
||||||
* @returns The number of tasks with the "urgent" priority.
|
|
||||||
*/
|
*/
|
||||||
displayNumberOfTaskStatusUrgent() {
|
get totalTasks(): number {
|
||||||
return this.firebaseService
|
return this.allTasks.length;
|
||||||
.getAllTasks()
|
|
||||||
.filter((task) => task.priority === 'urgent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the next task with the "urgent" priority or null if none
|
* A map of task statuses to their respective counts.
|
||||||
* exist.
|
* @returns A Map where each key is a task status and each value is the count of tasks with that status.
|
||||||
*
|
|
||||||
* @returns The next task with the "urgent" priority or null.
|
|
||||||
*/
|
*/
|
||||||
nextUrgentTask() {
|
get taskStatusCounts(): Map<string, number> {
|
||||||
const urgentTasks = this.displayNumberOfTaskStatusUrgent();
|
return this.allTasks.reduce((acc, task) => {
|
||||||
if (urgentTasks.length > 0) {
|
acc.set(task.status, (acc.get(task.status) || 0) + 1);
|
||||||
const nextTask = urgentTasks.reduce((earliest, current) => {
|
return acc;
|
||||||
const currentDate = Date.parse(current.date);
|
}, new Map<string, number>());
|
||||||
const earliestDate = Date.parse(earliest.date);
|
}
|
||||||
return currentDate < earliestDate ? current : earliest;
|
|
||||||
});
|
/**
|
||||||
return this.timeConverter(nextTask.date);
|
* A list of tasks with the priority set to "urgent".
|
||||||
}
|
* @returns An array of tasks with the priority of "urgent".
|
||||||
return null;
|
*/
|
||||||
|
get urgentTasks(): any[] {
|
||||||
|
return this.allTasks.filter((task) => task.priority === 'urgent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the date of the next urgent task.
|
||||||
|
* @returns {string | null} The date of the next urgent task formatted as a string, or null if no urgent tasks exist.
|
||||||
|
*/
|
||||||
|
|
||||||
|
get nextUrgentTask(): string | null {
|
||||||
|
if (this.urgentTasks.length === 0) return null;
|
||||||
|
|
||||||
|
const nextTask = this.urgentTasks.reduce((earliest, current) => {
|
||||||
|
return Date.parse(current.date) < Date.parse(earliest.date)
|
||||||
|
? current
|
||||||
|
: earliest;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.timeConverter(nextTask.date);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a date string to a human-readable format.
|
* Converts a date string to a human-readable format.
|
||||||
*
|
* @param dateString - The date string to convert.
|
||||||
* @param dateString The date string to convert.
|
* @returns A string representing the date in the format "MMM. DD, YYYY".
|
||||||
* @returns A string in the format "Month DD, YYYY".
|
|
||||||
*/
|
*/
|
||||||
timeConverter(dateString: string) {
|
timeConverter(dateString: string): string {
|
||||||
var a = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
var months = [
|
return date.toLocaleDateString('en-US', {
|
||||||
'Jan.',
|
month: 'short',
|
||||||
'Feb.',
|
day: 'numeric',
|
||||||
'Mar.',
|
year: 'numeric',
|
||||||
'Apr.',
|
});
|
||||||
'May.',
|
|
||||||
'Jun.',
|
|
||||||
'Jul.',
|
|
||||||
'Aug.',
|
|
||||||
'Sep.',
|
|
||||||
'Oct.',
|
|
||||||
'Nov.',
|
|
||||||
'Dec.',
|
|
||||||
];
|
|
||||||
var year = a.getFullYear();
|
|
||||||
var month = months[a.getMonth()];
|
|
||||||
var date = a.getDate();
|
|
||||||
var time = month + ' ' + date + ', ' + year;
|
|
||||||
return time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a greeting message based on the current time of day.
|
* Returns a greeting based on the current time of day.
|
||||||
*
|
* @returns a localized greeting string.
|
||||||
* The time is converted to the user's local time and then compared to the
|
|
||||||
* following ranges to determine the greeting message:
|
|
||||||
* - Before 12pm: "Good morning"
|
|
||||||
* - 12pm to 6pm: "Good afternoon"
|
|
||||||
* - After 6pm: "Good evening"
|
|
||||||
*
|
|
||||||
* @returns The greeting message.
|
|
||||||
*/
|
*/
|
||||||
displayGreeting() {
|
get greeting(): string {
|
||||||
let currentTime = new Date();
|
const currentHour = new Date().getHours();
|
||||||
let localTime = new Date(currentTime.getTime() + 3600000);
|
|
||||||
let currentHour = localTime.getHours();
|
|
||||||
if (currentHour >= 5 && currentHour < 12) {
|
if (currentHour >= 5 && currentHour < 12) {
|
||||||
return this.translateService.instant('summary.morning');
|
return this.translateService.instant('summary.morning');
|
||||||
} else if (currentHour >= 12 && currentHour < 18) {
|
|
||||||
return this.translateService.instant('summary.afternoon');
|
|
||||||
} else {
|
|
||||||
return this.translateService.instant('summary.evening');
|
|
||||||
}
|
}
|
||||||
|
if (currentHour >= 12 && currentHour < 18) {
|
||||||
|
return this.translateService.instant('summary.afternoon');
|
||||||
|
}
|
||||||
|
return this.translateService.instant('summary.evening');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,5 @@ export interface Subtask {
|
||||||
|
|
||||||
export interface Assignee {
|
export interface Assignee {
|
||||||
id: string;
|
id: string;
|
||||||
user: string;
|
userId: string;
|
||||||
task: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export class TaskService {
|
||||||
const userIds = new Set(
|
const userIds = new Set(
|
||||||
tasks.flatMap((task) => [
|
tasks.flatMap((task) => [
|
||||||
task.creator,
|
task.creator,
|
||||||
...task.assignees.map((a) => a.user),
|
...task.assignees.map((a) => a.userId),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class TaskService {
|
||||||
return tasks.map((task) => ({
|
return tasks.map((task) => ({
|
||||||
...task,
|
...task,
|
||||||
userData: [
|
userData: [
|
||||||
...task.assignees.map((a) => userMap[a.user] || null),
|
...task.assignees.map((a) => userMap[a.userId] || null),
|
||||||
userMap[task.creator] || null,
|
userMap[task.creator] || null,
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
}));
|
}));
|
||||||
|
|
@ -86,7 +86,7 @@ export class TaskService {
|
||||||
return this.apiService.getTaskById(taskId).pipe(
|
return this.apiService.getTaskById(taskId).pipe(
|
||||||
switchMap((task) => {
|
switchMap((task) => {
|
||||||
if (!task) return of(null);
|
if (!task) return of(null);
|
||||||
const userIds = [task.creator, ...task.assignees.map((a) => a.user)];
|
const userIds = [task.creator, ...task.assignees.map((a) => a.userId)];
|
||||||
|
|
||||||
return this.apiService.getUsersByIds(userIds).pipe(
|
return this.apiService.getUsersByIds(userIds).pipe(
|
||||||
map((users) => ({
|
map((users) => ({
|
||||||
|
|
@ -123,7 +123,7 @@ export class TaskService {
|
||||||
private mapTaskUsers(task: Task, users: UserSummary[]): UserSummary[] {
|
private mapTaskUsers(task: Task, users: UserSummary[]): UserSummary[] {
|
||||||
const userMap = this.createUserMap(users);
|
const userMap = this.createUserMap(users);
|
||||||
return [
|
return [
|
||||||
...task.assignees.map((a) => userMap[a.user] || null),
|
...task.assignees.map((a) => userMap[a.userId] || null),
|
||||||
userMap[task.creator] || null,
|
userMap[task.creator] || null,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
<div class="assigned">
|
<div class="assigned">
|
||||||
<p>{{ "addTask.assigned" | translate }}:</p>
|
<p>{{ "addTask.assigned" | translate }}:</p>
|
||||||
@for (assigned of task.assignees; track assigned.id) { @for (user of
|
@for (assigned of task.assignees; track assigned.id) { @for (user of
|
||||||
task.userData; track user) { @if (assigned.user === user.id) {
|
task.userData; track user) { @if (assigned.userId === user.id) {
|
||||||
<div class="users">
|
<div class="users">
|
||||||
<div
|
<div
|
||||||
class="circle"
|
class="circle"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue