pediatric-ai-scribe-v3/src/routes/adminMilestones.js
ifedan-ed 215de4cac8
Some checks failed
Build TWA APK / build-apk (push) Has been cancelled
Build & Push Docker Image / build (push) Has been cancelled
v2.1: Add visible bulk import UI for developmental milestones
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
2026-04-01 18:06:42 +00:00

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;