Sostituito frontend angular new con frontend angular. Sostituito frontend angular con frontend angular old

This commit is contained in:
Lorenzo Sanesi
2026-08-10 13:10:18 +02:00
parent 3d4eac4ea5
commit 0ed70eaa18
54 changed files with 7685 additions and 12730 deletions
@@ -0,0 +1,126 @@
<div class="p-4 flex flex-col h-full text-white animate-fade-in">
@if (group(); as currentGroup) {
<!-- Header -->
<header class="mb-4 flex items-center">
<button (click)="goBack.emit()" class="p-2 rounded-full hover:bg-slate-700 mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
</svg>
</button>
<div>
<h1 class="text-3xl font-bold">{{ currentGroup.name }}</h1>
<p class="text-slate-400 mt-1">{{ currentGroup.members.length }} Membri</p>
</div>
</header>
<main class="flex-grow overflow-y-auto pr-2 -mr-2 space-y-6">
<!-- Members Section -->
<section>
<h2 class="text-xl font-semibold text-cyan-400 mb-3">Membri</h2>
<div class="space-y-3">
@for (member of sortedMembers(); track member.id) {
<div class="flex items-center bg-slate-800 p-2 rounded-lg">
<img class="w-10 h-10 rounded-full mr-4" [src]="member.avatarUrl" [alt]="member.name">
<div class="flex-grow">
<p class="font-medium">{{ member.name }}</p>
<div class="flex items-center text-xs text-amber-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
<span>{{ member.score }} Punti Gloria</span>
</div>
</div>
<button (click)="removeMember(member.id)" class="text-slate-500 hover:text-red-500 p-2 rounded-full transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
}
</div>
@if (!isAddingMembers()) {
<button (click)="toggleAddMembers()" class="mt-4 w-full flex items-center justify-center py-2 px-4 border-2 border-dashed border-slate-600 rounded-lg text-slate-400 hover:bg-slate-700 hover:border-slate-500 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
Aggiungi Partecipanti
</button>
} @else {
<div class="mt-4 bg-slate-800 p-4 rounded-lg border border-slate-700">
<h3 class="text-lg font-semibold text-cyan-400 mb-3">Seleziona Amici</h3>
<div class="max-h-40 overflow-y-auto space-y-2 bg-slate-700 p-3 rounded-md border border-slate-600">
@for (friend of availableFriends(); track friend.id) {
<label class="flex items-center space-x-3 cursor-pointer p-2 rounded-md hover:bg-slate-600 transition-colors">
<input type="checkbox"
(change)="onFriendSelect(friend.id, $event)"
[checked]="selectedFriendIds()[friend.id] || false"
class="h-5 w-5 rounded bg-slate-800 border-slate-500 text-cyan-500 focus:ring-cyan-600 cursor-pointer">
<img class="w-8 h-8 rounded-full" [src]="friend.avatarUrl" [alt]="friend.name">
<span class="font-medium text-slate-200">{{ friend.name }}</span>
</label>
}
@empty {
<p class="text-slate-400 text-center py-4">Tutti i tuoi amici sono già in questo gruppo!</p>
}
</div>
<div class="flex justify-end space-x-3 mt-4">
<button type="button" (click)="toggleAddMembers()" class="px-4 py-2 rounded-md bg-slate-600 hover:bg-slate-500 text-white font-semibold transition-colors">Annulla</button>
<button type="button"
(click)="addSelectedFriends()"
[disabled]="!isAddFriendsFormValid()"
[class]="'px-4 py-2 rounded-md font-semibold transition-colors ' + (isAddFriendsFormValid() ? 'bg-cyan-500 hover:bg-cyan-600 text-white' : 'bg-slate-700 text-slate-500 cursor-not-allowed')">
Aggiungi
</button>
</div>
</div>
}
</section>
<!-- Accordions -->
<section class="space-y-4">
<!-- Active Bets -->
<div>
<button (click)="toggleAccordion('active')" class="w-full flex justify-between items-center bg-slate-800 p-3 rounded-lg hover:bg-slate-700 transition-colors">
<h2 class="text-xl font-semibold text-cyan-400">Scommesse Attive ({{ activeBets().length }})</h2>
<svg xmlns="http://www.w3.org/2000/svg" [class]="'h-6 w-6 transition-transform ' + (activeAccordion() === 'active' ? 'rotate-180' : '')" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</button>
@if (activeAccordion() === 'active') {
<div class="pt-4 space-y-4 animate-fade-in">
@for (bet of activeBets(); track bet.id) {
<app-bet-card [bet]="bet"></app-bet-card>
}
@empty {
<p class="text-center text-slate-500 pt-4">Nessuna scommessa attiva in questo gruppo.</p>
}
</div>
}
</div>
<!-- Past Bets -->
<div>
<button (click)="toggleAccordion('past')" class="w-full flex justify-between items-center bg-slate-800 p-3 rounded-lg hover:bg-slate-700 transition-colors">
<h2 class="text-xl font-semibold text-cyan-400">Scommesse Concluse ({{ pastBets().length }})</h2>
<svg xmlns="http://www.w3.org/2000/svg" [class]="'h-6 w-6 transition-transform ' + (activeAccordion() === 'past' ? 'rotate-180' : '')" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
</svg>
</button>
@if (activeAccordion() === 'past') {
<div class="pt-4 space-y-4 animate-fade-in">
@for (bet of pastBets(); track bet.id) {
<app-bet-card [bet]="bet"></app-bet-card>
}
@empty {
<p class="text-center text-slate-500 pt-4">Nessuna scommessa conclusa in questo gruppo.</p>
}
</div>
}
</div>
</section>
</main>
} @else {
<p class="text-center text-slate-500 mt-8">Gruppo non trovato.</p>
}
</div>
@@ -0,0 +1,89 @@
import { Component, ChangeDetectionStrategy, input, output, inject, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DataService } from '@/src/core/services/data.service';
import { Bet, BetStatus } from '@/src/core/models/bet.model';
import { BetCardComponent } from '@/src/shared/components/bet-card/bet-card.component';
@Component({
selector: 'app-group-detail',
standalone: true,
imports: [CommonModule, BetCardComponent],
templateUrl: './group-detail.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GroupDetailComponent {
groupId = input.required<string>();
goBack = output<void>();
private dataService = inject(DataService);
private allGroups = this.dataService.userGroups;
group = computed(() => this.allGroups().find(g => g.id === this.groupId()) ?? null);
readonly groupBets = computed(() => this.dataService.getBetsByGroupId(this.groupId()));
isAddingMembers = signal(false);
selectedFriendIds = signal<{ [key: string]: boolean }>({});
sortedMembers = computed(() => {
return this.group()?.members.slice().sort((a, b) => b.score - a.score) ?? [];
});
activeBets = computed(() => {
return this.groupBets().filter((b: Bet) => b.status === BetStatus.Active || b.status === BetStatus.AwaitingValidation);
});
pastBets = computed(() => {
return this.groupBets().filter((b: Bet) => b.status === BetStatus.Completed || b.status === BetStatus.Expired);
});
availableFriends = computed(() => {
const friends = this.dataService.friends();
const memberIds = new Set(this.group()?.members.map(m => m.id) ?? []);
return friends.filter(f => !memberIds.has(f.id));
});
isAddFriendsFormValid = computed(() => {
return Object.keys(this.selectedFriendIds()).length > 0;
});
activeAccordion = signal<'active' | 'past' | 'none'>('active');
toggleAccordion(section: 'active' | 'past') {
this.activeAccordion.update(current => current === section ? 'none' : section);
}
async removeMember(userId: string) {
if (confirm('Sei sicuro di voler rimuovere questo membro?')) {
await this.dataService.removeUserFromGroup(this.groupId(), userId);
}
}
toggleAddMembers() {
this.isAddingMembers.update(val => !val);
this.selectedFriendIds.set({});
}
onFriendSelect(friendId: string, event: Event) {
const isChecked = (event.target as HTMLInputElement).checked;
this.selectedFriendIds.update(currentIds => {
const newIds = { ...currentIds };
if (isChecked) {
newIds[friendId] = true;
} else {
delete newIds[friendId];
}
return newIds;
});
}
async addSelectedFriends() {
if (!this.isAddFriendsFormValid()) return;
const selectedIds = Object.keys(this.selectedFriendIds());
const friendsToAdd = this.availableFriends().filter(f => selectedIds.includes(f.id));
await this.dataService.addUsersToGroup(this.groupId(), friendsToAdd);
this.toggleAddMembers();
}
}