Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f07a317
feat/rating-redesign: adding names to ratings and ratings to comments
SultanTheHL May 16, 2026
4bd731c
feat/rating-redesign: change button name
SultanTheHL May 24, 2026
9204bf2
feat/rating-redesign: add rating count
SultanTheHL May 24, 2026
62ae8ec
Revert "feat/rating-redesign: add rating count"
SultanTheHL May 25, 2026
222e394
Merge branch 'main' into feat/rating-redesign
SultanTheHL May 25, 2026
d03e40b
feat/rating-redesign: updating tests
SultanTheHL May 25, 2026
d8e1d89
feat/rating-redesign: fix lint issues
SultanTheHL May 25, 2026
658bf53
feat/rating-redesign: security fix
SultanTheHL May 25, 2026
7ce408f
Merge branch 'main' into feat/rating-redesign
SultanTheHL May 25, 2026
f14e469
feat/rating-redesign: security fix
SultanTheHL May 25, 2026
a889cb6
feat/rating-redesign: color borders, wrap components and hover effect
SultanTheHL May 27, 2026
265f101
feat/rating-redesign: pr comments
SultanTheHL May 27, 2026
822afe9
feat/rating-redesign: fix
SultanTheHL May 27, 2026
55bc511
feat/rating-redesign: prettier
SultanTheHL May 27, 2026
1cb9e09
feat/rating-redesign: renaming tests
SultanTheHL May 27, 2026
370a9e5
Merge branch 'main' into feat/rating-redesign
SultanTheHL May 28, 2026
11f2b34
feat/rating-redesign: pr comments
SultanTheHL May 29, 2026
ad7142b
feat/rating-redesign: security fix
SultanTheHL May 29, 2026
b82bf9b
feat/rating-redesign: fix
SultanTheHL May 29, 2026
5dc9ebc
feat/rating-redesign: fix
SultanTheHL May 29, 2026
0fea177
feat/rating-redesign: changing translations
SultanTheHL Jun 1, 2026
5d3f028
feat/rating-redesign: translation change
SultanTheHL Jun 2, 2026
e0610af
feat/rating-redesign: PR fix
SultanTheHL Jun 4, 2026
c767b62
feat/rating-redesign: test fix and text
SultanTheHL Jun 4, 2026
eec9893
feat/rating-redesign:design adjustments
SultanTheHL Jun 4, 2026
d9e5405
feat/rating-redesign: fixing text and tests
SultanTheHL Jun 4, 2026
26f053f
Merge branch 'main' into feat/rating-redesign
SultanTheHL Jun 4, 2026
02cd411
feat/rating-redesign: border change
SultanTheHL Jun 7, 2026
69e76f4
Merge remote-tracking branch 'origin/feat/rating-redesign' into feat/…
SultanTheHL Jun 7, 2026
6e21813
feat/rating-redesign: changing preset colors in dark mode
SultanTheHL Jun 7, 2026
cfe4391
feat/rating-redesign: wrap rating pills on mobile and drop dead toolt…
az108 Jun 13, 2026
80154df
Merge remote-tracking branch 'origin/main' into feat/rating-redesign
Cathy0123456789 Jul 17, 2026
b10cab1
Merge branch 'main' into feat/rating-redesign
Cathy0123456789 Jul 24, 2026
4cea5d2
`Bugfix`: Make selected rating chips readable in both colour schemes
az108 Jul 26, 2026
95c9100
`Bugfix`: Give the rating chips one label colour on a lighter scale
az108 Jul 26, 2026
86960d4
`Bugfix`: Lighten the poor rating chip
az108 Jul 26, 2026
213b591
`Bugfix`: Respace the green rating chips so excellent can be lighter
az108 Jul 26, 2026
a5b7440
`Development`: Replace the rating pills with a star scale
az108 Jul 26, 2026
4711455
`Development`: Colour the rating stars by step and preview on hover
az108 Jul 26, 2026
5cd17e8
`Development`: Use one star scale for the rating input and the card a…
az108 Jul 26, 2026
b94f046
`Test`: Cover the star rating display and register its half-star icon
az108 Jul 26, 2026
57bb491
`Development`: Load the application ratings once on the review page
az108 Jul 26, 2026
d9331db
`Development`: Match comment ratings to their author by id
az108 Jul 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
<div class="flex gap-[2%] w-full max-w-[21rem] h-4 rounded-pill overflow-hidden">
@for (item of likertValues; track $index) {
<div
class="flex-1 transition-colors"
[class]="selectable() ? 'cursor-pointer hover:brightness-75' : ''"
[style.background-color]="getSectionColor($index)"
[pTooltip]="tooltipTexts()[$index]"
tooltipPosition="top"
tabindex="0"
role="button"
(click)="onSectionClick($index)"
(keydown.enter)="onSectionClick($index)"
></div>
}
</div>
@if (selectable()) {
<div class="flex flex-wrap gap-2">
@for (item of likertValues; track $index) {
<button
type="button"
class="px-3 py-1.5 rounded-full text-sm font-medium transition-colors duration-150 border border-border-default focus:outline-none"
[style.background-color]="getButtonBg($index)"
[style.color]="getButtonTextColor($index)"
Comment thread
SultanTheHL marked this conversation as resolved.
Outdated
(click)="onSectionClick($index)"
(keydown.enter)="onSectionClick($index)"
>
{{ tooltipTexts()[$index] }}
</button>
}
</div>
} @else if (rating() !== undefined) {
<span
class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium text-white"
[style.background-color]="getColorForValue(rating()!)"
Comment thread
SultanTheHL marked this conversation as resolved.
Outdated
>{{ getSelectedLabel() }}</span
>
}
Original file line number Diff line number Diff line change
@@ -1,71 +1,85 @@
import { Component, computed, inject, input, model } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { TranslateService } from '@ngx-translate/core';
import { TooltipModule } from 'primeng/tooltip';

type LikertValue = -2 | -1 | 0 | 1 | 2;

interface LikertEntry {
value: LikertValue;
key: string;
}

@Component({
selector: 'jhi-rating',
imports: [TooltipModule],
templateUrl: './rating.component.html',
})
export class RatingComponent {
rating = model<number | undefined>(undefined);
selectable = input<boolean>(false);

// Likert scale values from -2 to +2
readonly likertValues = [-2, -1, 0, 1, 2];
readonly likertScale: LikertEntry[] = [
{ value: -2, key: 'very_bad' },
{ value: -1, key: 'bad' },
{ value: 0, key: 'neutral' },
{ value: 1, key: 'good' },
{ value: 2, key: 'very_good' },
];

readonly likertValues: LikertValue[] = this.likertScale.map(s => s.value);

readonly tooltipTexts = computed(() => {
this.langChange();
return this.tooltipKeys.map(suffix => this.translateService.instant(`evaluation.ratings.${suffix}`));
return this.likertScale.map(s => this.translateService.instant(`evaluation.ratings.${s.key}`) as string);
Comment thread
SultanTheHL marked this conversation as resolved.
Outdated
});

protected readonly Array = Array;

private readonly tooltipKeys = ['very_bad', 'bad', 'neutral', 'good', 'very_good'];
private translateService = inject(TranslateService);
private langChange = toSignal(this.translateService.onLangChange, { initialValue: undefined });

onSectionClick(index: number): void {
if (!this.selectable()) {
return;
}
if (!this.selectable()) return;
const entry = this.entryAt(index);
if (entry === undefined) return;
this.rating.set(this.rating() === entry.value ? undefined : entry.value);
}

const newRating = this.likertValues[index];
// If clicking the same rating, unselect it
if (this.rating() === newRating) {
this.rating.set(undefined);
} else {
this.rating.set(newRating);
getColorForValue(value: number): string {
switch (value) {
case -2:
return 'var(--color-negative-active)';
case -1:
return 'var(--color-negative-hover)';
case 0:
return 'var(--color-warning-default)';
case 1:
return 'var(--color-positive-hover)';
case 2:
return 'var(--color-positive-active)';
default:
return 'var(--p-background-surface-alt)';
}
}

getSectionColor(index: number): string {
const currentRating = this.rating();
const sectionValue = this.likertValues[index];
getButtonBg(index: number): string {
const entry = this.entryAt(index);
if (entry === undefined) return 'var(--p-background-surface-alt)';
return this.rating() === entry.value ? this.getColorForValue(entry.value) : 'var(--p-background-surface-alt)';
}

if (currentRating === undefined) {
return 'var(--p-background-surface-alt)';
}
getButtonTextColor(index: number): string {
const entry = this.entryAt(index);
if (entry === undefined) return 'var(--p-text-color)';
return this.rating() === entry.value ? 'white' : 'var(--p-text-color)';
}

if (sectionValue === currentRating) {
switch (sectionValue) {
case -2:
return 'var(--color-negative-active)';
case -1:
return 'var(--color-negative-hover)';
case 0:
return 'var(--color-warning-default)';
case 1:
return 'var(--color-positive-hover)';
case 2:
return 'var(--color-positive-active)';
}
}
return 'var(--p-background-surface-alt)';
getSelectedLabel(): string {
const r = this.rating();
if (r === undefined) return '';
const entry = this.likertScale.find(s => s.value === r);
if (entry === undefined) return '';
return this.translateService.instant(`evaluation.ratings.${entry.key}`) as string;
}

getCursor(): string {
return this.selectable() ? 'pointer' : 'default';
private entryAt(index: number): LikertEntry | undefined {
return this.likertScale.find((_, i) => i === index);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
[canEdit]="c.canEdit ?? false"
[isCreate]="false"
[text]="c.message ?? ''"
[rating]="ratingForAuthor(c.author ?? '')"
[editingId]="editingId()"
(enterEdit)="editingId.set(c.commentId)"
(exitEdit)="editingId.set(undefined)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ToastService } from 'app/service/toast-service';
import { Comment } from 'app/shared/components/molecules/comment/comment';
import { InternalCommentResourceApi } from 'app/generated/api/internal-comment-resource-api';
import { InternalCommentDTO } from 'app/generated/model/internal-comment-dto';
import { RatingResourceApi } from 'app/generated/api/rating-resource-api';
import { RatingDTO } from 'app/generated/model/rating-dto';

import TranslateDirective from '../../../language/translate.directive';

Expand All @@ -15,12 +17,15 @@ import TranslateDirective from '../../../language/translate.directive';
})
export class CommentSection {
commentApi = inject(InternalCommentResourceApi);
ratingApi = inject(RatingResourceApi);
accountService = inject(AccountService);
toast = inject(ToastService);

applicationId = input.required<string | undefined>();

protected comments = signal<InternalCommentDTO[]>([]);
protected otherRatings = signal<RatingDTO[]>([]);
protected currentUserRating = signal<number | undefined>(undefined);
protected createDraft = signal<string>('');
protected currentUser = this.accountService.loadedUser()?.name ?? '';
protected editingId = signal<string | undefined>(undefined);
Expand All @@ -30,11 +35,31 @@ export class CommentSection {
this.createDraft.set('');
if (id !== undefined) {
void this.loadComments();
void this.loadOtherRatings(id);
} else {
this.comments.set([]);
this.otherRatings.set([]);
this.currentUserRating.set(undefined);
}
});

ratingForAuthor(author: string): number | undefined {
Comment thread
SultanTheHL marked this conversation as resolved.
Outdated
if (author === this.currentUser) {
return this.currentUserRating();
}
return this.otherRatings().find(r => r.from === author)?.rating;
}

async loadOtherRatings(applicationId: string): Promise<void> {
try {
const data = await firstValueFrom(this.ratingApi.getRatings(applicationId));
this.otherRatings.set(data.otherRatings ?? []);
this.currentUserRating.set(data.currentUserRating ?? undefined);
} catch {
this.toast.showError({ summary: 'Error', detail: 'Failed to load comment ratings' });
Comment thread
SultanTheHL marked this conversation as resolved.
Outdated
}
}

async loadComments(): Promise<void> {
const id = this.applicationId();
if (id === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<div class="flex justify-between items-center w-full h-[2.1rem] px-4 py-1 rounded-t-lg border border-border-default">
<div class="flex flex-row items-center gap-4">
<span class="font-semibold">{{ author() }}</span>
@if (!isCreate() && rating() !== undefined) {
<jhi-rating [rating]="rating()" />
}
@if (!isCreate()) {
<span class="text-[0.8rem] text-text-secondary">
{{ createdAt() | timeAgo }}
Expand Down Expand Up @@ -52,7 +55,7 @@
</div>
} @else if (isCreate()) {
<div class="flex flex-row justify-end gap-4 mt-4">
<jhi-button label="button.send" icon="paper-plane" severity="primary" (click)="onSave()" [disabled]="!canSave()" />
Comment thread
SultanTheHL marked this conversation as resolved.
<jhi-button label="button.post" icon="paper-plane" severity="primary" (click)="onSave()" [disabled]="!canSave()" />
</div>
}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { TimeAgoPipe } from 'app/shared/pipes';

import { ButtonComponent } from '../../atoms/button/button.component';
import { ConfirmDialog } from '../../atoms/confirm-dialog/confirm-dialog';
import { RatingComponent } from '../../atoms/rating/rating.component';

@Component({
selector: 'jhi-comment',
imports: [ButtonComponent, TextareaModule, TimeAgoPipe, ConfirmDialog],
imports: [ButtonComponent, TextareaModule, TimeAgoPipe, ConfirmDialog, RatingComponent],
templateUrl: './comment.html',
encapsulation: ViewEncapsulation.None,
})
Expand All @@ -22,6 +23,7 @@ export class Comment {
createdAt = input<string>('');
canEdit = input<boolean>(false);
isCreate = input<boolean>(false);
rating = input<number | undefined>(undefined);

saved = output<string>();
deleted = output();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
<p class="text-sm" jhiTranslate="evaluation.details.ratingDescription"></p>

<jhi-sub-section class="w-full flex gap-8" titleKey="evaluation.details.ratingYourRating">
<p class="text-xs" jhiTranslate="evaluation.details.ratingYourRatingLabel"></p>
Comment thread
SultanTheHL marked this conversation as resolved.
<div class="mt-2">
<jhi-rating [(rating)]="myRating" [selectable]="true" />
</div>
<jhi-rating [(rating)]="myRating" [selectable]="true" />
</jhi-sub-section>

@if (otherRatings().length > 0) {
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/de/button.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"edit": "Bearbeiten",
"save": "Speichern",
"send": "Senden",
"post": "Posten",
Comment thread
SultanTheHL marked this conversation as resolved.
Outdated
"view": "Anzeigen",
"withdraw": "Zurückziehen",
"back": "Zurück",
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/en/button.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"edit": "Edit",
"save": "Save",
"send": "Send",
"post": "Post",
"view": "View",
"withdraw": "Withdraw",
"back": "Back",
Expand Down
Comment thread
SultanTheHL marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('RatingComponent', () => {
expect(component.rating()).toBe(2);
});

// ---------------- BUTTON BACKGROUND ----------------
it.each<[number | undefined, number, string]>([
[undefined, 0, 'var(--p-background-surface-alt)'],
[-2, 0, 'var(--color-negative-active)'],
Expand All @@ -58,10 +59,22 @@ describe('RatingComponent', () => {
[1, 3, 'var(--color-positive-hover)'],
[2, 4, 'var(--color-positive-active)'],
[2, 0, 'var(--p-background-surface-alt)'],
])('should return %s for rating=%s section=%s', (rating, section, expected) => {
])('getButtonBg: rating=%s index=%s %s', (rating, index, expected) => {
fixture.componentRef.setInput('rating', rating);
fixture.detectChanges();
expect(component.getSectionColor(section)).toBe(expected);
expect(component.getButtonBg(index)).toBe(expected);
});

// ---------------- BUTTON TEXT COLOR ----------------
it.each<[number | undefined, number, string]>([
[-2, 0, 'white'],
[1, 3, 'white'],
[undefined, 0, 'var(--p-text-color)'],
[-2, 1, 'var(--p-text-color)'],
])('getButtonTextColor: rating=%s index=%s → %s', (rating, index, expected) => {
fixture.componentRef.setInput('rating', rating);
fixture.detectChanges();
expect(component.getButtonTextColor(index)).toBe(expected);
});

// ---------------- TOOLTIP TEXTS ----------------
Expand All @@ -74,12 +87,17 @@ describe('RatingComponent', () => {
expect(tooltips[4]).toBe('evaluation.ratings.very_good');
});

it.each<[boolean, string]>([
[true, 'pointer'],
[false, 'default'],
])('should return cursor=%s for selectable=%s', (selectable, expected) => {
fixture.componentRef.setInput('selectable', selectable);
// ---------------- SELECTED LABEL ----------------
it.each<[number | undefined, string]>([
[undefined, ''],
[-2, 'evaluation.ratings.very_bad'],
[-1, 'evaluation.ratings.bad'],
[0, 'evaluation.ratings.neutral'],
[1, 'evaluation.ratings.good'],
[2, 'evaluation.ratings.very_good'],
])('getSelectedLabel: rating=%s → %s', (rating, expected) => {
fixture.componentRef.setInput('rating', rating);
fixture.detectChanges();
expect(component.getCursor()).toBe(expected);
expect(component.getSelectedLabel()).toBe(expected);
});
});
Loading