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.
This commit is contained in:
2025-12-09 13:35:10 +01:00
parent 25d84e7b51
commit a5645a32c0
16 changed files with 340 additions and 77 deletions

View File

@@ -1,7 +0,0 @@
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3001
CMD ["node", "server.js"]

View File

@@ -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"]

View File

@@ -322,7 +322,8 @@ const initDb = async () => {
multiCondo: true,
tickets: true,
payPal: true,
notices: true
notices: true,
reports: true
};
if (rows.length === 0) {

View File

@@ -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 }); }