feat: Add tickets module and PayPal integration

Introduces a new 'Tickets' module for users to submit and manage issues within their condominium. This includes defining ticket types, statuses, priorities, and categories.

Additionally, this commit integrates PayPal as a payment option for family fee payments, enabling users to pay directly via PayPal using their client ID.

Key changes:
- Added `Ticket` related types and enums.
- Implemented `TicketService` functions for CRUD operations.
- Integrated `@paypal/react-paypal-js` library.
- Added `paypalClientId` to `AppSettings` and `Condo` types.
- Updated `FamilyDetail` page to include PayPal payment option.
- Added 'Segnalazioni' navigation link to `Layout`.
This commit is contained in:
2025-12-07 19:49:59 +01:00
parent 2566b406e1
commit 5311400615
14 changed files with 977 additions and 146 deletions

View File

@@ -2,7 +2,7 @@
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 } 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 } from 'lucide-react';
export const SettingsPage: React.FC = () => {
const currentUser = CondoService.getCurrentUser();
@@ -40,6 +40,7 @@ export const SettingsPage: React.FC = () => {
province: '',
zipCode: '',
notes: '',
paypalClientId: '',
defaultMonthlyQuota: 100
});
@@ -235,7 +236,7 @@ export const SettingsPage: React.FC = () => {
// --- Condo Management Handlers ---
const openAddCondoModal = () => {
setEditingCondo(null);
setCondoForm({ name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', defaultMonthlyQuota: 100 });
setCondoForm({ name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', paypalClientId: '', defaultMonthlyQuota: 100 });
setShowCondoModal(true);
};
@@ -249,6 +250,7 @@ export const SettingsPage: React.FC = () => {
province: c.province || '',
zipCode: c.zipCode || '',
notes: c.notes || '',
paypalClientId: c.paypalClientId || '',
defaultMonthlyQuota: c.defaultMonthlyQuota
});
setShowCondoModal(true);
@@ -266,6 +268,7 @@ export const SettingsPage: React.FC = () => {
province: condoForm.province,
zipCode: condoForm.zipCode,
notes: condoForm.notes,
paypalClientId: condoForm.paypalClientId,
defaultMonthlyQuota: condoForm.defaultMonthlyQuota
};
@@ -607,6 +610,11 @@ 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">
@@ -789,7 +797,7 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* ALERT MODAL */}
{/* ALERT MODAL (Existing) */}
{showAlertModal && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-xl shadow-xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
@@ -826,7 +834,7 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* NOTICE MODAL */}
{/* NOTICE MODAL (Existing) */}
{showNoticeModal && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200">
@@ -868,7 +876,7 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* READ DETAILS MODAL */}
{/* READ DETAILS MODAL (Existing) */}
{showReadDetailsModal && selectedNoticeId && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-xl shadow-xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
@@ -907,7 +915,7 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* USER MODAL */}
{/* USER MODAL (Existing) */}
{showUserModal && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-xl shadow-xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
@@ -950,10 +958,10 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* CONDO MODAL */}
{/* CONDO MODAL (UPDATED) */}
{showCondoModal && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200">
<div className="bg-white rounded-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200 overflow-y-auto max-h-[90vh]">
<h3 className="font-bold text-lg mb-4 text-slate-800">{editingCondo ? 'Modifica Condominio' : 'Nuovo Condominio'}</h3>
<form onSubmit={handleCondoSubmit} className="space-y-4">
@@ -985,9 +993,27 @@ export const SettingsPage: React.FC = () => {
</div>
</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>
</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>
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-20" placeholder="Note (opzionali)" value={condoForm.notes} onChange={e => setCondoForm({...condoForm, notes: e.target.value})} />
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-16" placeholder="Note (opzionali)" value={condoForm.notes} onChange={e => setCondoForm({...condoForm, notes: e.target.value})} />
</div>
{/* Quota */}
@@ -1005,7 +1031,7 @@ export const SettingsPage: React.FC = () => {
</div>
)}
{/* FAMILY MODAL */}
{/* FAMILY MODAL (Existing) */}
{showFamilyModal && (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
<div className="bg-white rounded-2xl shadow-xl w-full max-w-lg p-6">
@@ -1068,4 +1094,4 @@ export const SettingsPage: React.FC = () => {
)}
</div>
);
};
};