NEW FEATURES: - Bulk Import button in Admin Panel → Developmental Milestones section - "Import Default Milestones Data" button appears when database is empty - "Re-import All" button to clear and re-import all static data - Visible notice when no milestones exist with one-click import IMPROVEMENTS: - Auto-shows empty state notice when database has no milestones - Backend bulk-import endpoint now supports clearExisting parameter - Imports ALL age groups from static data (birth to 11 years) - Better UX - admin doesn't need CLI to populate milestone data FIXES: - Makes milestone admin editing feature discoverable and usable - No need to manually run import script anymore
149 lines
4.3 KiB
JavaScript
149 lines
4.3 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { adminMiddleware } = require('../middleware/auth');
|
|
const db = require('../db/database');
|
|
|
|
// Get all milestones (optionally filtered by age group)
|
|
router.get('/milestones', adminMiddleware, async (req, res) => {
|
|
try {
|
|
const { age_group } = req.query;
|
|
let query = 'SELECT * FROM developmental_milestones';
|
|
let params = [];
|
|
|
|
if (age_group) {
|
|
query += ' WHERE age_group = $1';
|
|
params.push(age_group);
|
|
}
|
|
|
|
query += ' ORDER BY age_group, domain, sort_order, id';
|
|
|
|
const result = await db.query(query, params);
|
|
res.json({ success: true, milestones: result.rows });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Get unique age groups and domains
|
|
router.get('/milestones/meta', adminMiddleware, async (req, res) => {
|
|
try {
|
|
const ageGroups = await db.query(
|
|
'SELECT DISTINCT age_group FROM developmental_milestones ORDER BY age_group'
|
|
);
|
|
const domains = await db.query(
|
|
'SELECT DISTINCT domain FROM developmental_milestones ORDER BY domain'
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
age_groups: ageGroups.rows.map(r => r.age_group),
|
|
domains: domains.rows.map(r => r.domain)
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Create new milestone
|
|
router.post('/milestones', adminMiddleware, async (req, res) => {
|
|
try {
|
|
const { age_group, domain, milestone_text, sort_order } = req.body;
|
|
|
|
if (!age_group || !domain || !milestone_text) {
|
|
return res.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
const result = await db.query(
|
|
`INSERT INTO developmental_milestones (age_group, domain, milestone_text, sort_order)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING *`,
|
|
[age_group, domain, milestone_text, sort_order || 0]
|
|
);
|
|
|
|
res.json({ success: true, milestone: result.rows[0] });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Update milestone
|
|
router.put('/milestones/:id', adminMiddleware, async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { age_group, domain, milestone_text, sort_order } = req.body;
|
|
|
|
if (!age_group || !domain || !milestone_text) {
|
|
return res.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
const result = await db.query(
|
|
`UPDATE developmental_milestones
|
|
SET age_group = $1, domain = $2, milestone_text = $3, sort_order = $4, updated_at = NOW()
|
|
WHERE id = $5
|
|
RETURNING *`,
|
|
[age_group, domain, milestone_text, sort_order || 0, id]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Milestone not found' });
|
|
}
|
|
|
|
res.json({ success: true, milestone: result.rows[0] });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Delete milestone
|
|
router.delete('/milestones/:id', adminMiddleware, async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const result = await db.query(
|
|
'DELETE FROM developmental_milestones WHERE id = $1 RETURNING *',
|
|
[id]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Milestone not found' });
|
|
}
|
|
|
|
res.json({ success: true, message: 'Milestone deleted' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// Bulk import milestones (from static data)
|
|
router.post('/milestones/bulk-import', adminMiddleware, async (req, res) => {
|
|
try {
|
|
const { milestones, clearExisting } = req.body;
|
|
|
|
if (!Array.isArray(milestones)) {
|
|
return res.status(400).json({ error: 'Expected array of milestones' });
|
|
}
|
|
|
|
// Clear existing if requested
|
|
if (clearExisting) {
|
|
await db.query('DELETE FROM developmental_milestones');
|
|
}
|
|
|
|
let imported = 0;
|
|
for (const m of milestones) {
|
|
if (!m.age_group || !m.domain || !m.milestone_text) continue;
|
|
|
|
await db.query(
|
|
`INSERT INTO developmental_milestones (age_group, domain, milestone_text, sort_order)
|
|
VALUES ($1, $2, $3, $4)`,
|
|
[m.age_group, m.domain, m.milestone_text, m.sort_order || 0]
|
|
);
|
|
imported++;
|
|
}
|
|
|
|
res.json({ success: true, imported });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|