Features: - Admin can add, edit, and delete developmental milestones via dashboard - Milestones stored in PostgreSQL (developmental_milestones table) - Client-side loads milestones from API instead of static file - Import script to migrate existing static data to database - Organized by age group and domain - Supports sorting and filtering Admin UI: - New section in Admin panel for milestone management - Filter by age group - Add/Edit modal with validation - Delete with confirmation - Auto-complete for age groups and domains API Endpoints: - GET /api/milestones-data - Public endpoint for authenticated users - GET /api/admin/milestones - List all milestones (admin only) - GET /api/admin/milestones/meta - Get age groups and domains - POST /api/admin/milestones - Create milestone - PUT /api/admin/milestones/:id - Update milestone - DELETE /api/admin/milestones/:id - Delete milestone - POST /api/admin/milestones/bulk-import - Bulk import Usage: 1. Run import script: node scripts/import-milestones.js 2. Access Admin dashboard → Developmental Milestones section 3. Add/Edit/Delete milestones as needed
197 lines
5.9 KiB
JavaScript
Executable file
197 lines
5.9 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
/**
|
|
* Import developmental milestones from static data file into database
|
|
* Run: node scripts/import-milestones.js
|
|
*/
|
|
|
|
require('dotenv').config();
|
|
const db = require('../src/db/database');
|
|
|
|
// Import static data
|
|
const MILESTONES_DATA = {
|
|
"Newborn / 1 month": {
|
|
"Gross Motor": [
|
|
"Moves arms and legs equally",
|
|
"Lifts head briefly when on tummy (prone)",
|
|
"Strong flexion posture (arms and legs tucked)",
|
|
"Turns head side to side when lying on back"
|
|
],
|
|
"Fine Motor": [
|
|
"Hands mostly fisted",
|
|
"Strong grasp reflex when palm is touched",
|
|
"Brings hands near face"
|
|
],
|
|
"Language": [
|
|
"Cries to express needs",
|
|
"Startles or quiets to sounds",
|
|
"Makes brief throaty sounds"
|
|
],
|
|
"Social/Emotional": [
|
|
"Recognizes caregiver voice",
|
|
"Briefly fixates on faces at close range (8-12 inches)",
|
|
"Calms when held or hears familiar voice",
|
|
"Shows brief alert periods"
|
|
],
|
|
"Cognitive": [
|
|
"Focuses on faces briefly",
|
|
"Follows objects briefly to midline",
|
|
"Prefers black and white or high-contrast patterns",
|
|
"Responds to loud sounds (Moro reflex)"
|
|
]
|
|
},
|
|
|
|
"2 months": {
|
|
"Gross Motor": [
|
|
"Lifts head when prone (45 degrees)",
|
|
"Holds head steady when held upright briefly",
|
|
"Moves both arms and both legs",
|
|
"Pushes up on tummy"
|
|
],
|
|
"Fine Motor": [
|
|
"Opens hands briefly",
|
|
"Holds rattle if placed in hand briefly",
|
|
"Brings hands to midline"
|
|
],
|
|
"Language": [
|
|
"Coos and makes gurgling sounds",
|
|
"Makes sounds other than crying",
|
|
"Turns head toward sounds"
|
|
],
|
|
"Social/Emotional": [
|
|
"Social smile (responsive to faces)",
|
|
"Begins to self-soothe (hands to mouth)",
|
|
"Tries to look at parent",
|
|
"Calms when spoken to or picked up"
|
|
],
|
|
"Cognitive": [
|
|
"Pays attention to faces",
|
|
"Begins to follow things with eyes",
|
|
"Recognizes people at a distance"
|
|
]
|
|
},
|
|
|
|
"4 months": {
|
|
"Gross Motor": [
|
|
"Holds head steady unsupported",
|
|
"Pushes up to elbows when on tummy",
|
|
"May roll over tummy to back",
|
|
"Pushes down on legs when feet on hard surface"
|
|
],
|
|
"Fine Motor": [
|
|
"Reaches for toys with one hand",
|
|
"Uses hands and eyes together",
|
|
"Grasps and shakes hand toys"
|
|
],
|
|
"Language": [
|
|
"Babbles with expression and copies sounds",
|
|
"Cries differently for hunger pain and tiredness",
|
|
"Makes vowel sounds ah eh oh"
|
|
],
|
|
"Social/Emotional": [
|
|
"Smiles spontaneously especially at people",
|
|
"Likes to play and may cry when playing stops",
|
|
"Copies movements and facial expressions"
|
|
],
|
|
"Cognitive": [
|
|
"Lets you know if happy or sad",
|
|
"Follows moving things with eyes side to side",
|
|
"Watches faces closely",
|
|
"Recognizes familiar people at a distance"
|
|
]
|
|
},
|
|
|
|
"6 months": {
|
|
"Gross Motor": [
|
|
"Rolls over in both directions",
|
|
"Begins to sit without support",
|
|
"Supports weight on legs and might bounce",
|
|
"Rocks back and forth on hands and knees"
|
|
],
|
|
"Fine Motor": [
|
|
"Reaches for and grasps objects",
|
|
"Transfers objects hand to hand",
|
|
"Raking grasp to pick up objects",
|
|
"Brings things to mouth"
|
|
],
|
|
"Language": [
|
|
"Responds to own name",
|
|
"Responds to sounds by making sounds",
|
|
"Strings vowels together when babbling",
|
|
"Begins consonant sounds m and b",
|
|
"Makes sounds to show joy and displeasure"
|
|
],
|
|
"Social/Emotional": [
|
|
"Knows familiar faces and recognizes strangers",
|
|
"Likes to play with others especially parents",
|
|
"Responds to other peoples emotions",
|
|
"Likes to look at self in mirror"
|
|
],
|
|
"Cognitive": [
|
|
"Looks around at things nearby",
|
|
"Shows curiosity and tries to get things out of reach",
|
|
"Begins to pass things from one hand to the other"
|
|
]
|
|
}
|
|
// Add more age groups as needed...
|
|
};
|
|
|
|
async function importMilestones() {
|
|
console.log('🚀 Starting milestones import...\n');
|
|
|
|
try {
|
|
// Check if milestones already exist
|
|
const existing = await db.query('SELECT COUNT(*) as count FROM developmental_milestones');
|
|
if (parseInt(existing.rows[0].count) > 0) {
|
|
console.log(`⚠️ Database already contains ${existing.rows[0].count} milestones.`);
|
|
console.log(' Clear existing data first or skip import.');
|
|
const readline = require('readline');
|
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
|
|
await new Promise((resolve) => {
|
|
rl.question('Clear existing data and re-import? (yes/no): ', (answer) => {
|
|
rl.close();
|
|
if (answer.toLowerCase() !== 'yes') {
|
|
console.log('Import cancelled.');
|
|
process.exit(0);
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
await db.query('DELETE FROM developmental_milestones');
|
|
console.log('✅ Cleared existing milestones\n');
|
|
}
|
|
|
|
let imported = 0;
|
|
let sortOrder = 0;
|
|
|
|
for (const ageGroup in MILESTONES_DATA) {
|
|
console.log(`📋 Importing: ${ageGroup}`);
|
|
|
|
for (const domain in MILESTONES_DATA[ageGroup]) {
|
|
const milestones = MILESTONES_DATA[ageGroup][domain];
|
|
|
|
for (const milestone of milestones) {
|
|
await db.query(
|
|
`INSERT INTO developmental_milestones (age_group, domain, milestone_text, sort_order)
|
|
VALUES ($1, $2, $3, $4)`,
|
|
[ageGroup, domain, milestone, sortOrder]
|
|
);
|
|
sortOrder++;
|
|
imported++;
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`\n✅ Import complete!`);
|
|
console.log(` Imported: ${imported} milestones`);
|
|
console.log(`\nYou can now manage milestones via the Admin dashboard.\n`);
|
|
|
|
process.exit(0);
|
|
} catch (err) {
|
|
console.error('❌ Import failed:', err.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
importMilestones();
|