feat: integrazione scalette comunità come playlist
- Aggiunto il parsing di lista_nome e lista_esecuzione dall'API parrocchiale in ComunitaService. - Le scalette vengono trasformate in ComunitaScaletta[] e persistite in localStorage. - PlaylistService espone comunitaPlaylists (computed) e allPlaylists (computed che unisce playlist personali e scalette comunità). - Le scalette della comunità appaiono nel dropdown Playlist SOLO quando il filtro comunità è attivo; scompaiono automaticamente alla disattivazione. - Le scalette sono read-only: i pulsanti Modifica e Elimina vengono nascosti per le playlist della comunità. - Icona people-outline e bordo sinistro secondario distinguono visivamente le scalette della comunità dalle playlist personali.
This commit is contained in:
@@ -115,11 +115,11 @@
|
||||
<ion-button
|
||||
[fill]="activeFilterType() === 'playlist' || playlistService.activeListName() !== null ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="playlistService.playlists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
||||
(click)="playlistService.allPlaylists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
||||
class="filter-chip">
|
||||
{{ playlistService.activeListName() !== null ? playlistService.activeListName() : 'Playlist' }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="playlistService.activeListName() === null && playlistService.playlists().length > 0"></ion-icon>
|
||||
<ion-icon slot="end" name="add-circle-outline" *ngIf="playlistService.activeListName() === null && playlistService.playlists().length === 0"></ion-icon>
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="playlistService.activeListName() === null && playlistService.allPlaylists().length > 0"></ion-icon>
|
||||
<ion-icon slot="end" name="add-circle-outline" *ngIf="playlistService.activeListName() === null && playlistService.allPlaylists().length === 0"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="playlistService.activeListName() !== null" (click)="clearSpecialList($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
@@ -160,23 +160,25 @@
|
||||
|
||||
<!-- Active Playlist Actions -->
|
||||
<div class="selection-pill glass" *ngIf="playlistService.activeListName() && !playlistService.selectionMode()">
|
||||
<ion-button fill="clear" color="secondary" (click)="editPlaylist()" class="mini-action-btn">
|
||||
<ion-button fill="clear" color="secondary" (click)="editPlaylist()" class="mini-action-btn" *ngIf="!isComunitaPlaylist()">
|
||||
<ion-icon slot="icon-only" name="create-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" color="secondary" (click)="shareActivePlaylist()" class="mini-action-btn">
|
||||
<ion-icon slot="icon-only" name="share-social-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" color="danger" (click)="deleteActivePlaylist()" class="mini-action-btn" *ngIf="playlistService.activePlaylistId()">
|
||||
<ion-button fill="clear" color="danger" (click)="deleteActivePlaylist()" class="mini-action-btn" *ngIf="playlistService.activePlaylistId() && !isComunitaPlaylist()">
|
||||
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<!-- Filter chips -->
|
||||
<div
|
||||
*ngFor="let item of (activeFilterType() === 'playlist' ? playlistService.playlists() : (activeFilterType() === 'liturgico' ? cantiService.indiceLiturgico() : (activeFilterType() === 'tematico' ? cantiService.indiceTematico() : [])))"
|
||||
*ngFor="let item of (activeFilterType() === 'playlist' ? playlistService.allPlaylists() : (activeFilterType() === 'liturgico' ? cantiService.indiceLiturgico() : (activeFilterType() === 'tematico' ? cantiService.indiceTematico() : [])))"
|
||||
class="momento-chip glass"
|
||||
[class.active]="activeFilterType() === 'playlist' ? playlistService.activePlaylistId() === item.id : isIndexSelected(item.id)"
|
||||
[class.comunita-chip]="activeFilterType() === 'playlist' && item.isComunita"
|
||||
(click)="activeFilterType() === 'playlist' ? selectPlaylist(item) : toggleIndex(item.id, activeFilterType()!)">
|
||||
<ion-icon *ngIf="activeFilterType() === 'playlist' && item.isComunita" name="people-outline" style="font-size: 0.85rem; margin-right: 4px; vertical-align: middle;"></ion-icon>
|
||||
{{ activeFilterType() === 'playlist' ? item.name : item.tag_name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -355,6 +355,17 @@ ion-title {
|
||||
font-weight: 600;
|
||||
border-color: var(--ion-color-secondary);
|
||||
}
|
||||
|
||||
&.comunita-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-left: 3px solid rgba(var(--ion-color-secondary-rgb), 0.6);
|
||||
background: rgba(var(--ion-color-secondary-rgb), 0.08);
|
||||
|
||||
&.active {
|
||||
border-left-color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Item Layout
|
||||
|
||||
@@ -854,6 +854,11 @@ export class HomePage implements OnDestroy {
|
||||
this.isAddingSongs.set(false);
|
||||
}
|
||||
|
||||
isComunitaPlaylist(): boolean {
|
||||
const id = this.playlistService.activePlaylistId();
|
||||
return !!id && id.startsWith('comunita_');
|
||||
}
|
||||
|
||||
clearSpecialList(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
this.playlistService.activeListIds.set([]);
|
||||
|
||||
@@ -23,6 +23,13 @@ export interface CantiSettingsItem {
|
||||
tonalita: number;
|
||||
}
|
||||
|
||||
export interface ComunitaScaletta {
|
||||
id: string;
|
||||
name: string;
|
||||
date: string;
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
export interface GetAllAppTablesResponse {
|
||||
parrocchia?: { data: ParrocchiaItem[] };
|
||||
parrocchia_canti?: { data: ParrocchiaCantiItem[] };
|
||||
@@ -43,6 +50,7 @@ export class ComunitaService {
|
||||
public comunitaCantiInfo = signal<ParrocchiaCantiItem[]>([]);
|
||||
public comunitaCantiSettings = signal<CantiSettingsItem[]>([]);
|
||||
public comunitaCantiPersonali = signal<Canto[]>([]);
|
||||
public comunitaScalette = signal<ComunitaScaletta[]>([]);
|
||||
public isFilterActive = signal<boolean>(false);
|
||||
public loading = signal<boolean>(false);
|
||||
public loadingProgress = signal<number>(0);
|
||||
@@ -55,6 +63,7 @@ export class ComunitaService {
|
||||
const savedInfo = localStorage.getItem('comunita-canti-info');
|
||||
const savedSettings = localStorage.getItem('comunita-canti-settings');
|
||||
const savedCantiPersonali = localStorage.getItem('comunita-canti-personali');
|
||||
const savedScalette = localStorage.getItem('comunita-scalette');
|
||||
const savedFilterActive = localStorage.getItem('comunita-filter-active') === 'true';
|
||||
|
||||
if (savedCode) {
|
||||
@@ -85,6 +94,9 @@ export class ComunitaService {
|
||||
if (savedCantiPersonali) {
|
||||
try { this.comunitaCantiPersonali.set(JSON.parse(savedCantiPersonali)); } catch (e) {}
|
||||
}
|
||||
if (savedScalette) {
|
||||
try { this.comunitaScalette.set(JSON.parse(savedScalette)); } catch (e) {}
|
||||
}
|
||||
this.isFilterActive.set(savedFilterActive);
|
||||
|
||||
effect(() => {
|
||||
@@ -95,6 +107,7 @@ export class ComunitaService {
|
||||
localStorage.setItem('comunita-canti-info', JSON.stringify(this.comunitaCantiInfo()));
|
||||
localStorage.setItem('comunita-canti-settings', JSON.stringify(this.comunitaCantiSettings()));
|
||||
localStorage.setItem('comunita-canti-personali', JSON.stringify(this.comunitaCantiPersonali()));
|
||||
localStorage.setItem('comunita-scalette', JSON.stringify(this.comunitaScalette()));
|
||||
localStorage.setItem('comunita-filter-active', this.isFilterActive().toString());
|
||||
});
|
||||
|
||||
@@ -143,6 +156,7 @@ export class ComunitaService {
|
||||
this.comunitaCantiInfo.set([]);
|
||||
this.comunitaCantiSettings.set([]);
|
||||
this.comunitaCantiPersonali.set([]);
|
||||
this.comunitaScalette.set([]);
|
||||
this.isFilterActive.set(false);
|
||||
return true;
|
||||
}
|
||||
@@ -182,6 +196,39 @@ export class ComunitaService {
|
||||
this.comunitaCantiIds.set(cantiList.map(c => c.id_canti));
|
||||
this.comunitaCantiSettings.set(settingsList);
|
||||
this.comunitaCantiPersonali.set(mappedCantiPersonali);
|
||||
|
||||
// Parse scalette (setlists) from lista_nome + lista_esecuzione
|
||||
const listaNomelList = (data as any).lista_nome?.data || [];
|
||||
const listaEsecuzioneList = (data as any).lista_esecuzione?.data || [];
|
||||
|
||||
// Group lista_esecuzione by id_lista and keep order by progr
|
||||
const esecuzioniByLista = new Map<string, { id_canti: string; progr: number }[]>();
|
||||
for (const item of listaEsecuzioneList) {
|
||||
if (!esecuzioniByLista.has(item.id_lista)) {
|
||||
esecuzioniByLista.set(item.id_lista, []);
|
||||
}
|
||||
esecuzioniByLista.get(item.id_lista)!.push({
|
||||
id_canti: String(item.id_canti),
|
||||
progr: Number(item.progr) || 0
|
||||
});
|
||||
}
|
||||
|
||||
// Build scalette array sorted by date (most recent first)
|
||||
const scalette: ComunitaScaletta[] = listaNomelList
|
||||
.map((ln: any) => {
|
||||
const songs = esecuzioniByLista.get(ln.id_lista) || [];
|
||||
songs.sort((a: any, b: any) => a.progr - b.progr);
|
||||
return {
|
||||
id: `comunita_${ln.id_lista}`,
|
||||
name: ln.nome || 'Scaletta',
|
||||
date: ln.progr || '',
|
||||
ids: songs.map((s: any) => s.id_canti)
|
||||
};
|
||||
})
|
||||
.filter((s: ComunitaScaletta) => s.ids.length > 0)
|
||||
.sort((a: ComunitaScaletta, b: ComunitaScaletta) => b.date.localeCompare(a.date));
|
||||
|
||||
this.comunitaScalette.set(scalette);
|
||||
this.isFilterActive.set(true);
|
||||
this.loading.set(false);
|
||||
return true;
|
||||
@@ -218,6 +265,7 @@ export class ComunitaService {
|
||||
this.comunitaCantiInfo.set(mockInfo);
|
||||
this.comunitaCantiSettings.set([]);
|
||||
this.comunitaCantiPersonali.set([]);
|
||||
this.comunitaScalette.set([]);
|
||||
this.isFilterActive.set(true);
|
||||
this.loading.set(false);
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, signal, inject } from '@angular/core';
|
||||
import { Injectable, signal, inject, computed } from '@angular/core';
|
||||
import { Storage } from '@ionic/storage-angular';
|
||||
import { Canto, CantiService } from './canti.service';
|
||||
import { ComunitaService } from './comunita.service';
|
||||
import * as QRCode from 'qrcode';
|
||||
|
||||
@Injectable({
|
||||
@@ -9,6 +10,7 @@ import * as QRCode from 'qrcode';
|
||||
export class PlaylistService {
|
||||
private storage = inject(Storage);
|
||||
private cantiService = inject(CantiService);
|
||||
private comunitaService = inject(ComunitaService);
|
||||
|
||||
public selectionMode = signal<boolean>(false);
|
||||
public selectedIds = signal<Set<string>>(new Set());
|
||||
@@ -22,6 +24,30 @@ export class PlaylistService {
|
||||
|
||||
private _storage: Storage | null = null;
|
||||
|
||||
// Community scalette exposed as playlists (only when community filter is active)
|
||||
public comunitaPlaylists = computed(() => {
|
||||
if (!this.comunitaService.comunitaCode() || !this.comunitaService.isFilterActive()) {
|
||||
return [];
|
||||
}
|
||||
return this.comunitaService.comunitaScalette().map(s => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
ids: s.ids,
|
||||
createdAt: s.date,
|
||||
isComunita: true
|
||||
}));
|
||||
});
|
||||
|
||||
// Merged list: personal playlists + community scalette
|
||||
public allPlaylists = computed(() => {
|
||||
const community = this.comunitaPlaylists();
|
||||
const personal = this.playlists();
|
||||
if (community.length > 0) {
|
||||
return [...community, ...personal.map(p => ({ ...p, isComunita: false }))];
|
||||
}
|
||||
return personal.map(p => ({ ...p, isComunita: false }));
|
||||
});
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export const VERSION = '2026.05.20.1245';
|
||||
export const VERSION = '2026.05.20.1602';
|
||||
|
||||
Reference in New Issue
Block a user