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
|
<ion-button
|
||||||
[fill]="activeFilterType() === 'playlist' || playlistService.activeListName() !== null ? 'solid' : 'outline'"
|
[fill]="activeFilterType() === 'playlist' || playlistService.activeListName() !== null ? 'solid' : 'outline'"
|
||||||
size="small"
|
size="small"
|
||||||
(click)="playlistService.playlists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
(click)="playlistService.allPlaylists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
||||||
class="filter-chip">
|
class="filter-chip">
|
||||||
{{ playlistService.activeListName() !== null ? playlistService.activeListName() : 'Playlist' }}
|
{{ 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="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.playlists().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)">
|
<span class="close-icon-wrapper" *ngIf="playlistService.activeListName() !== null" (click)="clearSpecialList($event)">
|
||||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||||
</span>
|
</span>
|
||||||
@@ -160,23 +160,25 @@
|
|||||||
|
|
||||||
<!-- Active Playlist Actions -->
|
<!-- Active Playlist Actions -->
|
||||||
<div class="selection-pill glass" *ngIf="playlistService.activeListName() && !playlistService.selectionMode()">
|
<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-icon slot="icon-only" name="create-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button fill="clear" color="secondary" (click)="shareActivePlaylist()" class="mini-action-btn">
|
<ion-button fill="clear" color="secondary" (click)="shareActivePlaylist()" class="mini-action-btn">
|
||||||
<ion-icon slot="icon-only" name="share-social-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="share-social-outline"></ion-icon>
|
||||||
</ion-button>
|
</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-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter chips -->
|
<!-- Filter chips -->
|
||||||
<div
|
<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="momento-chip glass"
|
||||||
[class.active]="activeFilterType() === 'playlist' ? playlistService.activePlaylistId() === item.id : isIndexSelected(item.id)"
|
[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()!)">
|
(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 }}
|
{{ activeFilterType() === 'playlist' ? item.name : item.tag_name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -355,6 +355,17 @@ ion-title {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-color: var(--ion-color-secondary);
|
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
|
// Custom Item Layout
|
||||||
|
|||||||
@@ -854,6 +854,11 @@ export class HomePage implements OnDestroy {
|
|||||||
this.isAddingSongs.set(false);
|
this.isAddingSongs.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isComunitaPlaylist(): boolean {
|
||||||
|
const id = this.playlistService.activePlaylistId();
|
||||||
|
return !!id && id.startsWith('comunita_');
|
||||||
|
}
|
||||||
|
|
||||||
clearSpecialList(event?: Event) {
|
clearSpecialList(event?: Event) {
|
||||||
if (event) event.stopPropagation();
|
if (event) event.stopPropagation();
|
||||||
this.playlistService.activeListIds.set([]);
|
this.playlistService.activeListIds.set([]);
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ export interface CantiSettingsItem {
|
|||||||
tonalita: number;
|
tonalita: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ComunitaScaletta {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
date: string;
|
||||||
|
ids: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetAllAppTablesResponse {
|
export interface GetAllAppTablesResponse {
|
||||||
parrocchia?: { data: ParrocchiaItem[] };
|
parrocchia?: { data: ParrocchiaItem[] };
|
||||||
parrocchia_canti?: { data: ParrocchiaCantiItem[] };
|
parrocchia_canti?: { data: ParrocchiaCantiItem[] };
|
||||||
@@ -43,6 +50,7 @@ export class ComunitaService {
|
|||||||
public comunitaCantiInfo = signal<ParrocchiaCantiItem[]>([]);
|
public comunitaCantiInfo = signal<ParrocchiaCantiItem[]>([]);
|
||||||
public comunitaCantiSettings = signal<CantiSettingsItem[]>([]);
|
public comunitaCantiSettings = signal<CantiSettingsItem[]>([]);
|
||||||
public comunitaCantiPersonali = signal<Canto[]>([]);
|
public comunitaCantiPersonali = signal<Canto[]>([]);
|
||||||
|
public comunitaScalette = signal<ComunitaScaletta[]>([]);
|
||||||
public isFilterActive = signal<boolean>(false);
|
public isFilterActive = signal<boolean>(false);
|
||||||
public loading = signal<boolean>(false);
|
public loading = signal<boolean>(false);
|
||||||
public loadingProgress = signal<number>(0);
|
public loadingProgress = signal<number>(0);
|
||||||
@@ -55,6 +63,7 @@ export class ComunitaService {
|
|||||||
const savedInfo = localStorage.getItem('comunita-canti-info');
|
const savedInfo = localStorage.getItem('comunita-canti-info');
|
||||||
const savedSettings = localStorage.getItem('comunita-canti-settings');
|
const savedSettings = localStorage.getItem('comunita-canti-settings');
|
||||||
const savedCantiPersonali = localStorage.getItem('comunita-canti-personali');
|
const savedCantiPersonali = localStorage.getItem('comunita-canti-personali');
|
||||||
|
const savedScalette = localStorage.getItem('comunita-scalette');
|
||||||
const savedFilterActive = localStorage.getItem('comunita-filter-active') === 'true';
|
const savedFilterActive = localStorage.getItem('comunita-filter-active') === 'true';
|
||||||
|
|
||||||
if (savedCode) {
|
if (savedCode) {
|
||||||
@@ -85,6 +94,9 @@ export class ComunitaService {
|
|||||||
if (savedCantiPersonali) {
|
if (savedCantiPersonali) {
|
||||||
try { this.comunitaCantiPersonali.set(JSON.parse(savedCantiPersonali)); } catch (e) {}
|
try { this.comunitaCantiPersonali.set(JSON.parse(savedCantiPersonali)); } catch (e) {}
|
||||||
}
|
}
|
||||||
|
if (savedScalette) {
|
||||||
|
try { this.comunitaScalette.set(JSON.parse(savedScalette)); } catch (e) {}
|
||||||
|
}
|
||||||
this.isFilterActive.set(savedFilterActive);
|
this.isFilterActive.set(savedFilterActive);
|
||||||
|
|
||||||
effect(() => {
|
effect(() => {
|
||||||
@@ -95,6 +107,7 @@ export class ComunitaService {
|
|||||||
localStorage.setItem('comunita-canti-info', JSON.stringify(this.comunitaCantiInfo()));
|
localStorage.setItem('comunita-canti-info', JSON.stringify(this.comunitaCantiInfo()));
|
||||||
localStorage.setItem('comunita-canti-settings', JSON.stringify(this.comunitaCantiSettings()));
|
localStorage.setItem('comunita-canti-settings', JSON.stringify(this.comunitaCantiSettings()));
|
||||||
localStorage.setItem('comunita-canti-personali', JSON.stringify(this.comunitaCantiPersonali()));
|
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());
|
localStorage.setItem('comunita-filter-active', this.isFilterActive().toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,6 +156,7 @@ export class ComunitaService {
|
|||||||
this.comunitaCantiInfo.set([]);
|
this.comunitaCantiInfo.set([]);
|
||||||
this.comunitaCantiSettings.set([]);
|
this.comunitaCantiSettings.set([]);
|
||||||
this.comunitaCantiPersonali.set([]);
|
this.comunitaCantiPersonali.set([]);
|
||||||
|
this.comunitaScalette.set([]);
|
||||||
this.isFilterActive.set(false);
|
this.isFilterActive.set(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -182,6 +196,39 @@ export class ComunitaService {
|
|||||||
this.comunitaCantiIds.set(cantiList.map(c => c.id_canti));
|
this.comunitaCantiIds.set(cantiList.map(c => c.id_canti));
|
||||||
this.comunitaCantiSettings.set(settingsList);
|
this.comunitaCantiSettings.set(settingsList);
|
||||||
this.comunitaCantiPersonali.set(mappedCantiPersonali);
|
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.isFilterActive.set(true);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
return true;
|
return true;
|
||||||
@@ -218,6 +265,7 @@ export class ComunitaService {
|
|||||||
this.comunitaCantiInfo.set(mockInfo);
|
this.comunitaCantiInfo.set(mockInfo);
|
||||||
this.comunitaCantiSettings.set([]);
|
this.comunitaCantiSettings.set([]);
|
||||||
this.comunitaCantiPersonali.set([]);
|
this.comunitaCantiPersonali.set([]);
|
||||||
|
this.comunitaScalette.set([]);
|
||||||
this.isFilterActive.set(true);
|
this.isFilterActive.set(true);
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
return true;
|
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 { Storage } from '@ionic/storage-angular';
|
||||||
import { Canto, CantiService } from './canti.service';
|
import { Canto, CantiService } from './canti.service';
|
||||||
|
import { ComunitaService } from './comunita.service';
|
||||||
import * as QRCode from 'qrcode';
|
import * as QRCode from 'qrcode';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -9,6 +10,7 @@ import * as QRCode from 'qrcode';
|
|||||||
export class PlaylistService {
|
export class PlaylistService {
|
||||||
private storage = inject(Storage);
|
private storage = inject(Storage);
|
||||||
private cantiService = inject(CantiService);
|
private cantiService = inject(CantiService);
|
||||||
|
private comunitaService = inject(ComunitaService);
|
||||||
|
|
||||||
public selectionMode = signal<boolean>(false);
|
public selectionMode = signal<boolean>(false);
|
||||||
public selectedIds = signal<Set<string>>(new Set());
|
public selectedIds = signal<Set<string>>(new Set());
|
||||||
@@ -22,6 +24,30 @@ export class PlaylistService {
|
|||||||
|
|
||||||
private _storage: Storage | null = null;
|
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() {
|
constructor() {
|
||||||
this.init();
|
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