From a5645a32c03f2354be12891b3115fe680c5c4225 Mon Sep 17 00:00:00 2001 From: frakarr Date: Tue, 9 Dec 2025 13:35:10 +0100 Subject: [PATCH] feat: Add reports feature Enables a new reports section in the application. This includes: - Adding a `reports` flag to `AppFeatures` and `AppSettings`. - Including a new "Reportistica" link in the main navigation for privileged users. - Adding a `getCondoPayments` endpoint to the mock DB service. - Updating the backend to support filtering payments by `condoId`. - Providing a basic `ReportsPage` component. --- .dockerignore | Bin 102 -> 136 bytes .dockerignore.txt | 2 +- App.tsx | 4 +- Dockerfile | 14 --- Dockerfile.txt | 3 +- components/Layout.tsx | 14 ++- nginx.conf | 38 ------ nginx.txt | 6 +- pages/Reports.tsx | 273 ++++++++++++++++++++++++++++++++++++++++++ pages/Settings.tsx | 13 +- server/Dockerfile | 7 -- server/Dockerfile.txt | 6 + server/db.js | 3 +- server/server.js | 27 ++++- services/mockDb.ts | 4 + types.ts | 3 +- 16 files changed, 340 insertions(+), 77 deletions(-) create mode 100644 pages/Reports.tsx diff --git a/.dockerignore b/.dockerignore index 762a7ba24895c1f7c7e89138570fbbdb5eaf7d9e..276bbec6497998dcc0f46046911191732494e1c2 100644 GIT binary patch literal 136 zcmaFAfA9PKd*gsOOBP6k196$QE)xfkW&~m&VlV*`UO=o}2_$5I7=nu6EC?gh8A8j! Y#jB35hO;3I6nmJ|<8rux;vj?K0B1~FyZ`_I literal 102 zcmYk!!3}^Q3`XI5cd;(P1`I$9{WV0Oh9ZgEOFVgbhwt#J3PMr2GmIozCqB{)>L7ML l_^u5k8c(z(YPr=XhBzph= diff --git a/.dockerignore.txt b/.dockerignore.txt index 074c6d8..91cee0e 100644 --- a/.dockerignore.txt +++ b/.dockerignore.txt @@ -6,6 +6,6 @@ dist .DS_Store docker-compose.yml Dockerfile -server/node_modules +server .idea .vscode diff --git a/App.tsx b/App.tsx index 04eb777..5e214b3 100644 --- a/App.tsx +++ b/App.tsx @@ -6,6 +6,7 @@ import { FamilyList } from './pages/FamilyList'; import { FamilyDetail } from './pages/FamilyDetail'; import { SettingsPage } from './pages/Settings'; import { TicketsPage } from './pages/Tickets'; +import { ReportsPage } from './pages/Reports'; import { LoginPage } from './pages/Login'; import { CondoService } from './services/mockDb'; @@ -34,6 +35,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> } /> @@ -43,4 +45,4 @@ const App: React.FC = () => { ); }; -export default App; \ No newline at end of file +export default App; diff --git a/Dockerfile b/Dockerfile index e57cdb8..e69de29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +0,0 @@ -# Stage 1: Build Frontend -FROM node:18-alpine as build -WORKDIR /app -COPY package*.json ./ -RUN npm install -COPY . . -RUN npm run build - -# Stage 2: Serve with Nginx -FROM nginx:alpine -COPY --from=build /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/nginx.conf -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] diff --git a/Dockerfile.txt b/Dockerfile.txt index 4a58e6f..b791813 100644 --- a/Dockerfile.txt +++ b/Dockerfile.txt @@ -10,6 +10,7 @@ RUN npm run build # Stage 2: Serve with Nginx FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/nginx.conf +# Copy the nginx configuration file (using the .txt extension as provided in source) +COPY nginx.txt /etc/nginx/nginx.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/components/Layout.tsx b/components/Layout.tsx index 719cdc7..0f26e87 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { NavLink, Outlet } from 'react-router-dom'; -import { Users, Settings, Building, LogOut, Menu, X, ChevronDown, Check, LayoutDashboard, Megaphone, Info, AlertTriangle, Hammer, Calendar, MessageSquareWarning } from 'lucide-react'; +import { Users, Settings, Building, LogOut, Menu, X, ChevronDown, Check, LayoutDashboard, Megaphone, Info, AlertTriangle, Hammer, Calendar, MessageSquareWarning, PieChart } from 'lucide-react'; import { CondoService } from '../services/mockDb'; import { Condo, Notice, AppSettings } from '../types'; @@ -240,6 +240,14 @@ export const Layout: React.FC = () => { Famiglie + {/* Privileged Links */} + {isAdmin && settings?.features.reports && ( + + + Reportistica + + )} + {/* Hide Tickets if disabled */} {settings?.features.tickets && ( @@ -264,7 +272,7 @@ export const Layout: React.FC = () => { + + + + {/* Filters Bar */} +
+
+
+ + +
+
+ + +
+
+ +
+ setSearchTerm(e.target.value)} + className="w-full border p-2 pl-9 rounded-lg text-slate-700 bg-slate-50" + /> + +
+
+
+
+ + {/* Stats Cards */} +
+
+

Incasso Totale

+

€ {stats.totalAmount.toLocaleString('it-IT', { minimumFractionDigits: 2 })}

+

+ su {stats.count} transazioni +

+
+ +
+
+

PayPal / Online

+

€ {stats.paypalAmount.toLocaleString('it-IT', { minimumFractionDigits: 2 })}

+
+
+ {stats.count > 0 ? Math.round((stats.paypalAmount / stats.totalAmount) * 100) : 0}% +
+
+ +
+
+

Manuale / Bonifico

+

€ {stats.manualAmount.toLocaleString('it-IT', { minimumFractionDigits: 2 })}

+
+
+ {stats.count > 0 ? Math.round((stats.manualAmount / stats.totalAmount) * 100) : 0}% +
+
+
+ + {/* Transactions Table */} +
+
+

Dettaglio Transazioni

+ {filteredData.length} risultati +
+
+ + + + + + + + + + + + + {filteredData.length === 0 ? ( + + ) : ( + filteredData.map((t) => ( + + + + + + + + + )) + )} + +
DataFamigliaRiferimentoMetodoNote / ID TransazioneImporto
Nessuna transazione trovata con i filtri attuali.
+
+ + {new Date(t.datePaid).toLocaleDateString()} +
+
+
{t.familyName}
+
Int. {t.familyUnit}
+
+ + {MONTH_NAMES[t.forMonth - 1].substring(0, 3)} {t.forYear} + + + + {t.method === 'PayPal' ? : } + {t.method} + + + {t.notes || '-'} + + € {t.amount.toFixed(2)} +
+
+
+ + ); +}; diff --git a/pages/Settings.tsx b/pages/Settings.tsx index c6d84fb..b670449 100644 --- a/pages/Settings.tsx +++ b/pages/Settings.tsx @@ -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, CreditCard, ToggleLeft, ToggleRight, LayoutGrid } 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, PieChart } from 'lucide-react'; export const SettingsPage: React.FC = () => { const currentUser = CondoService.getCurrentUser(); @@ -623,6 +623,17 @@ export const SettingsPage: React.FC = () => { + + {/* Reports */} +
+
+

Reportistica Avanzata

+

Abilita grafici e tabelle dettagliate sui pagamenti per amministratori.

+
+ +
diff --git a/server/Dockerfile b/server/Dockerfile index 6762e55..e69de29 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,7 +0,0 @@ -FROM node:18-alpine -WORKDIR /app -COPY package*.json ./ -RUN npm install --production -COPY . . -EXPOSE 3001 -CMD ["node", "server.js"] diff --git a/server/Dockerfile.txt b/server/Dockerfile.txt index 21b924d..ca4eef1 100644 --- a/server/Dockerfile.txt +++ b/server/Dockerfile.txt @@ -1,8 +1,14 @@ FROM node:18-alpine WORKDIR /app + +# Set production environment +ENV NODE_ENV=production + COPY package*.json ./ RUN npm install --production + COPY . . + EXPOSE 3001 CMD ["node", "server.js"] diff --git a/server/db.js b/server/db.js index 24790ed..72656af 100644 --- a/server/db.js +++ b/server/db.js @@ -322,7 +322,8 @@ const initDb = async () => { multiCondo: true, tickets: true, payPal: true, - notices: true + notices: true, + reports: true }; if (rows.length === 0) { diff --git a/server/server.js b/server/server.js index 39c0ecd..bb8d243 100644 --- a/server/server.js +++ b/server/server.js @@ -148,7 +148,7 @@ app.get('/api/settings', authenticateToken, async (req, res) => { res.json({ currentYear: rows[0].current_year, smtpConfig: rows[0].smtp_config || {}, - features: rows[0].features || { multiCondo: true, tickets: true, payPal: true, notices: true } + features: rows[0].features || { multiCondo: true, tickets: true, payPal: true, notices: true, reports: true } }); } else { res.status(404).json({ message: 'Settings not found' }); } } catch (e) { res.status(500).json({ error: e.message }); } @@ -349,7 +349,7 @@ app.get('/api/notices/unread', authenticateToken, async (req, res) => { // --- PAYMENTS --- app.get('/api/payments', authenticateToken, async (req, res) => { - const { familyId } = req.query; + const { familyId, condoId } = req.query; try { const isPrivileged = req.user.role === 'admin' || req.user.role === 'poweruser'; if (!isPrivileged) { @@ -359,12 +359,27 @@ app.get('/api/payments', authenticateToken, async (req, res) => { return res.json(rows.map(mapPaymentRow)); } } - let query = 'SELECT * FROM payments'; + + let query = 'SELECT p.* FROM payments p'; let params = []; - if (familyId) { - query += ' WHERE family_id = ?'; - params.push(familyId); + + // If condoId provided, we need to JOIN with families to filter + if (condoId) { + query += ' JOIN families f ON p.family_id = f.id WHERE f.condo_id = ?'; + params.push(condoId); + + if (familyId) { + query += ' AND p.family_id = ?'; + params.push(familyId); + } + } else if (familyId) { + query += ' WHERE p.family_id = ?'; + params.push(familyId); } + + // Sort by date (newest first) + query += ' ORDER BY p.date_paid DESC'; + const [rows] = await pool.query(query, params); res.json(rows.map(mapPaymentRow)); } catch (e) { res.status(500).json({ error: e.message }); } diff --git a/services/mockDb.ts b/services/mockDb.ts index c64af97..f8213a0 100644 --- a/services/mockDb.ts +++ b/services/mockDb.ts @@ -226,6 +226,10 @@ export const CondoService = { return request(`/payments?familyId=${familyId}`); }, + getCondoPayments: async (condoId: string): Promise => { + return request(`/payments?condoId=${condoId}`); + }, + addPayment: async (payment: Omit): Promise => { return request('/payments', { method: 'POST', diff --git a/types.ts b/types.ts index 7796c8d..3a5f9e4 100644 --- a/types.ts +++ b/types.ts @@ -51,6 +51,7 @@ export interface AppFeatures { tickets: boolean; payPal: boolean; notices: boolean; + reports: boolean; } export interface AlertDefinition { @@ -164,4 +165,4 @@ export interface Ticket { attachments?: TicketAttachment[]; userName?: string; // Joined field userEmail?: string; // Joined field -} \ No newline at end of file +}