feat: migrate SummaryComponent to Django Rest and replace user with userId

This commit is contained in:
Chneemann 2025-03-23 15:10:39 +01:00
parent 518a688272
commit 876aadcd05
6 changed files with 116 additions and 113 deletions

View file

@ -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)"

View file

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

View file

@ -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);
return filteredTasks.length;
}
/** this.taskService.loadAllTasks().subscribe({
* Returns the number of tasks with the "urgent" priority. next: (result) => {
* this.allTasks = result.allTasks;
* @returns The number of tasks with the "urgent" priority. this.isLoading = false;
*/ },
displayNumberOfTaskStatusUrgent() { error: (err) => {
return this.firebaseService console.error('Error loading the tasks:', err);
.getAllTasks() this.isLoading = false;
.filter((task) => task.priority === 'urgent'); },
}
/**
* Return the next task with the "urgent" priority or null if none
* exist.
*
* @returns The next task with the "urgent" priority or null.
*/
nextUrgentTask() {
const urgentTasks = this.displayNumberOfTaskStatusUrgent();
if (urgentTasks.length > 0) {
const nextTask = urgentTasks.reduce((earliest, current) => {
const currentDate = Date.parse(current.date);
const earliestDate = Date.parse(earliest.date);
return currentDate < earliestDate ? current : earliest;
}); });
return this.timeConverter(nextTask.date);
} }
return null;
/**
* Gets the total number of tasks.
* @returns The total count of tasks.
*/
get totalTasks(): number {
return this.allTasks.length;
}
/**
* A map of task statuses to their respective counts.
* @returns A Map where each key is a task status and each value is the count of tasks with that status.
*/
get taskStatusCounts(): Map<string, number> {
return this.allTasks.reduce((acc, task) => {
acc.set(task.status, (acc.get(task.status) || 0) + 1);
return acc;
}, new Map<string, number>());
}
/**
* A list of tasks with the priority set to "urgent".
* @returns An array of tasks with the priority of "urgent".
*/
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) { }
if (currentHour >= 12 && currentHour < 18) {
return this.translateService.instant('summary.afternoon'); return this.translateService.instant('summary.afternoon');
} else { }
return this.translateService.instant('summary.evening'); return this.translateService.instant('summary.evening');
} }
}
} }

View file

@ -25,6 +25,5 @@ export interface Subtask {
export interface Assignee { export interface Assignee {
id: string; id: string;
user: string; userId: string;
task: string;
} }

View file

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

View file

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