feat: Introduce app feature flags

This commit refactors the application settings to include a new `AppFeatures` interface. This allows for granular control over which features are enabled for the application.

The `AppFeatures` object includes boolean flags for:
- `multiCondo`: Enables or disables the multi-condominium management feature.
- `tickets`: Placeholder for future ticket system integration.
- `payPal`: Enables or disables PayPal payment gateway integration.
- `notices`: Enables or disables the display and management of notices.

These flags are now fetched and stored in the application state, influencing UI elements and logic across various pages to conditionally render features based on their enabled status. For example, the multi-condo selection in `Layout.tsx` and the notice display in `FamilyList.tsx` are now gated by these feature flags. The `FamilyDetail.tsx` page also uses the `payPal` flag to conditionally enable the PayPal payment option.

The `SettingsPage.tsx` has been updated to include a new 'features' tab for managing these flags.
This commit is contained in:
2025-12-07 20:21:01 +01:00
parent 5311400615
commit 919be985c9
8 changed files with 243 additions and 67 deletions

View File

@@ -2,14 +2,14 @@
import React, { useEffect, useState } from 'react';
import { CondoService } from '../services/mockDb';
import { AppSettings, Family, User, AlertDefinition, Condo, Notice, NoticeIconType, NoticeRead } from '../types';
import { Save, Building, Coins, Plus, Pencil, Trash2, X, CalendarCheck, AlertTriangle, User as UserIcon, Server, Bell, Clock, FileText, Lock, Megaphone, CheckCircle2, Info, Hammer, Link as LinkIcon, Eye, Calendar, List, UserCog, Mail, Power, MapPin, CreditCard } from 'lucide-react';
import { Save, Building, Coins, Plus, Pencil, Trash2, X, CalendarCheck, AlertTriangle, User as UserIcon, Server, Bell, Clock, FileText, Lock, Megaphone, CheckCircle2, Info, Hammer, Link as LinkIcon, Eye, Calendar, List, UserCog, Mail, Power, MapPin, CreditCard, ToggleLeft, ToggleRight, LayoutGrid } from 'lucide-react';
export const SettingsPage: React.FC = () => {
const currentUser = CondoService.getCurrentUser();
const isAdmin = currentUser?.role === 'admin';
// Tab configuration
type TabType = 'profile' | 'general' | 'condos' | 'families' | 'users' | 'notices' | 'alerts' | 'smtp';
type TabType = 'profile' | 'features' | 'general' | 'condos' | 'families' | 'users' | 'notices' | 'alerts' | 'smtp';
const [activeTab, setActiveTab] = useState<TabType>(isAdmin ? 'general' : 'profile');
const [loading, setLoading] = useState(true);
@@ -204,6 +204,18 @@ export const SettingsPage: React.FC = () => {
}
};
const handleFeaturesSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!globalSettings) return;
setSaving(true);
try {
await CondoService.updateSettings(globalSettings);
setSuccessMsg('Funzionalità salvate!');
setTimeout(() => setSuccessMsg(''), 3000);
window.location.reload(); // Refresh to apply changes to layout
} catch(e) { console.error(e); } finally { setSaving(false); }
};
const handleSmtpSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!globalSettings) return;
@@ -463,6 +475,18 @@ export const SettingsPage: React.FC = () => {
const getCondoName = (id: string) => condos.find(c => c.id === id)?.name || 'Sconosciuto';
// Helpers for Toggle Feature
const toggleFeature = (key: keyof AppSettings['features']) => {
if (!globalSettings) return;
setGlobalSettings({
...globalSettings,
features: {
...globalSettings.features,
[key]: !globalSettings.features[key]
}
});
};
// --- TABS CONFIG ---
const tabs: {id: TabType, label: string, icon: React.ReactNode}[] = [
@@ -470,11 +494,24 @@ export const SettingsPage: React.FC = () => {
];
if (isAdmin) {
tabs.push(
{ id: 'general', label: 'Condominio', icon: <Building className="w-4 h-4"/> },
{ id: 'condos', label: 'Lista Condomini', icon: <List className="w-4 h-4"/> },
{ id: 'features', label: 'Funzionalità', icon: <LayoutGrid className="w-4 h-4"/> },
{ id: 'general', label: 'Condominio', icon: <Building className="w-4 h-4"/> }
);
if (globalSettings?.features.multiCondo) {
tabs.push({ id: 'condos', label: 'Lista Condomini', icon: <List className="w-4 h-4"/> });
}
tabs.push(
{ id: 'families', label: 'Famiglie', icon: <Coins className="w-4 h-4"/> },
{ id: 'users', label: 'Utenti', icon: <UserCog className="w-4 h-4"/> },
{ id: 'notices', label: 'Bacheca', icon: <Megaphone className="w-4 h-4"/> },
{ id: 'users', label: 'Utenti', icon: <UserCog className="w-4 h-4"/> }
);
if (globalSettings?.features.notices) {
tabs.push({ id: 'notices', label: 'Bacheca', icon: <Megaphone className="w-4 h-4"/> });
}
tabs.push(
{ id: 'alerts', label: 'Avvisi Email', icon: <Bell className="w-4 h-4"/> },
{ id: 'smtp', label: 'SMTP', icon: <Mail className="w-4 h-4"/> }
);
@@ -523,11 +560,77 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* Features Tab */}
{isAdmin && activeTab === 'features' && globalSettings && (
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6 max-w-3xl">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2">
<LayoutGrid className="w-5 h-5 text-blue-600" /> Funzionalità Piattaforma
</h3>
</div>
<form onSubmit={handleFeaturesSubmit} className="space-y-6">
<div className="space-y-4">
{/* Multi Condo */}
<div className="flex items-center justify-between p-4 bg-slate-50 rounded-xl border border-slate-100">
<div>
<p className="font-bold text-slate-800">Gestione Multicondominio</p>
<p className="text-sm text-slate-500">Abilita la gestione di più stabili. Se disattivo, il sistema gestirà un solo condominio.</p>
</div>
<button type="button" onClick={() => toggleFeature('multiCondo')} className={`${globalSettings.features.multiCondo ? 'bg-green-500' : 'bg-slate-300'} relative inline-flex h-6 w-11 items-center rounded-full transition-colors`}>
<span className={`${globalSettings.features.multiCondo ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}/>
</button>
</div>
{/* Tickets */}
<div className="flex items-center justify-between p-4 bg-slate-50 rounded-xl border border-slate-100">
<div>
<p className="font-bold text-slate-800">Gestione Tickets</p>
<p className="text-sm text-slate-500">Abilita il sistema di segnalazione guasti e richieste (Segnalazioni).</p>
</div>
<button type="button" onClick={() => toggleFeature('tickets')} className={`${globalSettings.features.tickets ? 'bg-green-500' : 'bg-slate-300'} relative inline-flex h-6 w-11 items-center rounded-full transition-colors`}>
<span className={`${globalSettings.features.tickets ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}/>
</button>
</div>
{/* PayPal */}
<div className="flex items-center justify-between p-4 bg-slate-50 rounded-xl border border-slate-100">
<div>
<p className="font-bold text-slate-800">Pagamenti PayPal</p>
<p className="text-sm text-slate-500">Permetti ai condomini di pagare le rate tramite PayPal.</p>
</div>
<button type="button" onClick={() => toggleFeature('payPal')} className={`${globalSettings.features.payPal ? 'bg-green-500' : 'bg-slate-300'} relative inline-flex h-6 w-11 items-center rounded-full transition-colors`}>
<span className={`${globalSettings.features.payPal ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}/>
</button>
</div>
{/* Notices */}
<div className="flex items-center justify-between p-4 bg-slate-50 rounded-xl border border-slate-100">
<div>
<p className="font-bold text-slate-800">Bacheca Avvisi</p>
<p className="text-sm text-slate-500">Mostra la bacheca digitale per comunicazioni ai condomini.</p>
</div>
<button type="button" onClick={() => toggleFeature('notices')} className={`${globalSettings.features.notices ? 'bg-green-500' : 'bg-slate-300'} relative inline-flex h-6 w-11 items-center rounded-full transition-colors`}>
<span className={`${globalSettings.features.notices ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}/>
</button>
</div>
</div>
<div className="pt-2 flex justify-between items-center">
<span className="text-green-600 font-medium">{successMsg}</span>
<button type="submit" className="bg-blue-600 text-white px-6 py-2.5 rounded-lg font-medium hover:bg-blue-700 flex gap-2">
<Save className="w-4 h-4" /> Salva Configurazione
</button>
</div>
</form>
</div>
)}
{/* General Tab */}
{isAdmin && activeTab === 'general' && (
<div className="space-y-6 animate-fade-in">
{!activeCondo ? (
<div className="bg-amber-50 border border-amber-200 text-amber-800 p-4 rounded-lg">Nessun condominio selezionato. Crea un condominio nella sezione "Lista Condomini".</div>
<div className="bg-amber-50 border border-amber-200 text-amber-800 p-4 rounded-lg">Nessun condominio selezionato.</div>
) : (
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8 max-w-2xl">
<h3 className="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"><Building className="w-5 h-5 text-blue-600" /> Dati Condominio Corrente</h3>
@@ -610,11 +713,6 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* Rest of the file (Families, Users, Notices, Alerts, SMTP Tabs) remains mostly same, just update modal */}
{/* ... (Existing Tabs Code for Families, Users, Notices, Alerts, SMTP) ... */}
{/* Only change is inside CONDO MODAL */}
{/* Families Tab */}
{isAdmin && activeTab === 'families' && (
<div className="space-y-4 animate-fade-in">
@@ -994,22 +1092,24 @@ export const SettingsPage: React.FC = () => {
</div>
{/* PayPal Integration Section */}
<div className="bg-blue-50 p-3 rounded-lg border border-blue-100">
<div className="flex items-center gap-2 mb-2 text-blue-800">
<CreditCard className="w-4 h-4"/>
<span className="text-xs font-bold uppercase">Configurazione Pagamenti</span>
{globalSettings?.features.payPal && (
<div className="bg-blue-50 p-3 rounded-lg border border-blue-100">
<div className="flex items-center gap-2 mb-2 text-blue-800">
<CreditCard className="w-4 h-4"/>
<span className="text-xs font-bold uppercase">Configurazione Pagamenti</span>
</div>
<div>
<label className="text-xs font-semibold text-slate-600 block mb-1">PayPal Client ID (REST API App)</label>
<input
className="w-full border p-2 rounded text-slate-700 text-sm"
placeholder="Es: Afg... (Ottienilo da developer.paypal.com)"
value={condoForm.paypalClientId}
onChange={e => setCondoForm({...condoForm, paypalClientId: e.target.value})}
/>
<p className="text-[10px] text-slate-500 mt-1">Necessario per abilitare i pagamenti online delle rate.</p>
</div>
</div>
<div>
<label className="text-xs font-semibold text-slate-600 block mb-1">PayPal Client ID (REST API App)</label>
<input
className="w-full border p-2 rounded text-slate-700 text-sm"
placeholder="Es: Afg... (Ottienilo da developer.paypal.com)"
value={condoForm.paypalClientId}
onChange={e => setCondoForm({...condoForm, paypalClientId: e.target.value})}
/>
<p className="text-[10px] text-slate-500 mt-1">Necessario per abilitare i pagamenti online delle rate.</p>
</div>
</div>
)}
{/* Notes */}
<div>