Today's Schedule
+ +Recent Scores
+ +Kids
Planner
-β‘ Auto-schedule a course
-${greet}! π
${dateStr}
`; + + try { + const [students, scheduleData, recentScores] = await Promise.all([ + api('/api/students'), + api(`/api/admin/schedule?date=${new Date().toISOString().slice(0,10)}`).catch(() => ({ rows: [] })), + api('/api/admin/recent-scores').catch(() => ({ scores: [] })), + ]); + + const THEME_HEX = { blue:'#4f86f7', green:'#2e9e5b', purple:'#7c5cbf', orange:'#e8802a', pink:'#d45d8a', red:'#e84040', teal:'#20a8a0', yellow:'#c8960c', indigo:'#5b6abf' }; + const rows = scheduleData.rows || scheduleData || []; + + kidGrid.innerHTML = ''; + for (const s of students) { + const kidRows = rows.filter(r => r.student_id === s.id); + const total = kidRows.length; + const done = kidRows.filter(r => r.done).length; + const pct = total ? Math.round(done / total * 100) : 0; + const accent = THEME_HEX[s.theme?.replace('theme-','')] || '#4f7ef8'; + kidGrid.innerHTML += ` +No schedule for today.
'; + + const scores = recentScores.scores || recentScores || []; + scoresEl.innerHTML = scores.length ? scores.slice(0, 8).map(sc => { + const pct = sc.total > 0 ? Math.round(sc.score / sc.total * 100) : null; + const cls = pct === null ? '' : pct >= 80 ? '' : pct >= 60 ? ' mid' : ' low'; + const kidName = students.find(s => s.id === sc.student_id)?.name || ''; + return `No scores yet.
'; + + } catch (e) { + kidGrid.innerHTML = 'Could not load dashboard data.
'; + } +} + // ============================================================ // Kids // ============================================================ @@ -1310,8 +1415,8 @@ $('#planner-student').addEventListener('change', () => { }); $('#planner-view-week').addEventListener('click', () => { plannerMode = 'week'; - $('#planner-view-week').classList.add('active-view'); - $('#planner-view-month').classList.remove('active-view'); + $('#planner-view-week').classList.add('active'); + $('#planner-view-month').classList.remove('active'); $('#planner-copy').hidden = false; renderPlanner(); }); @@ -1321,8 +1426,8 @@ $('#planner-view-month').addEventListener('click', () => { const ref = new Date(plannerWeekStart + 'T00:00:00'); plannerMonthRef = `${ref.getFullYear()}-${String(ref.getMonth() + 1).padStart(2, '0')}-01`; plannerWeekStart = firstWeekStartOfMonth(plannerMonthRef); - $('#planner-view-month').classList.add('active-view'); - $('#planner-view-week').classList.remove('active-view'); + $('#planner-view-month').classList.add('active'); + $('#planner-view-week').classList.remove('active'); $('#planner-copy').hidden = true; renderPlanner(); }); @@ -1455,26 +1560,53 @@ function renderDayCell(day, tasks, compact = false, spellingTests = [], today = const dayTasks = tasks.filter((t) => t.date === day.date); const dayTests = spellingTests.filter((t) => t.date === day.date); const isToday = day.date === today; + const isPast = day.date < today; + const doneCount = dayTasks.filter(t => t.done).length; + const total = dayTasks.length + dayTests.length; + const testBadges = dayTests.map((t) => { const pct = Math.round((t.score / t.total) * 100); - return `${compact ? day.name.slice(0,3) : day.name}${fmtDate(day.date)}
-β
` : '')} + return `What are Parts of Speech?
+Every word in English belongs to a part of speech β a category that describes how the word works in a sentence.
+The 8 Parts of Speech
+-
+
- Noun β names a person, place, thing, or idea. Example: dog, school, happiness +
- Pronoun β replaces a noun. Example: he, she, they, it +
- Verb β shows action or state of being. Example: run, is, think +
- Adjective β describes a noun. Example: blue, tall, three +
- Adverb β describes a verb, adjective, or another adverb. Example: quickly, very, well +
- Preposition β shows relationship. Example: in, on, under, beside +
- Conjunction β joins words or phrases. Example: and, but, or, because +
- Interjection β expresses emotion. Example: Wow! Oh! Ouch! +
Learning parts of speech helps you understand how sentences are built and makes you a stronger reader and writer.
+`, 0, null, 1, daysAgo(14), 0, 'none'); +const lessonPos = itemId++; + +// Quiz: Parts of Speech +insertItem.run(itemId, laUnit1, 'quiz', 'Parts of Speech Quiz', null, 20, null, 2, daysAgo(10), 1, 'none'); +const quizPos = itemId++; +insertQ.run(qId++, quizPos, 'mc', 'Which word is a NOUN?', JSON.stringify(['quickly','beautiful','mountain','runs']), 'mountain', 5, 1); +insertQ.run(qId++, quizPos, 'mc', 'Which word is a VERB?', JSON.stringify(['slowly','purple','jump','the']), 'jump', 5, 2); +insertQ.run(qId++, quizPos, 'mc', 'Which word is an ADJECTIVE?', JSON.stringify(['gracefully','enormous','laugh','because']), 'enormous', 5, 3); +insertQ.run(qId++, quizPos, 'sa', 'Write a sentence using a preposition.', null, null, 5, 4); + +// Lesson: Sentence Structure +insertItem.run(itemId, laUnit1, 'lesson', 'Sentence Structure', ` +Building Strong Sentences
+Every complete sentence needs two things: a subject (who or what the sentence is about) and a predicate (what the subject does or is).
+Simple Sentences
+A simple sentence has one independent clause: The dog barked.
+Compound Sentences
+Two independent clauses joined by a conjunction: The dog barked, and the cat hid.
+Complex Sentences
+One independent clause + one dependent clause: Because it was raining, we stayed inside.
+Using a variety of sentence types makes your writing more interesting to read!
+`, 0, null, 3, daysAgo(7), 0, 'none'); +itemId++; + +insertUnit.run(unitId, 1, 'Creative Writing', 2); +const laUnit2 = unitId++; + +insertItem.run(itemId, laUnit2, 'lesson', 'The Writing Process', ` +Five Steps to Great Writing
+-
+
- Prewrite β Brainstorm ideas. Use a web, list, or outline. +
- Draft β Write your ideas without worrying about perfection. +
- Revise β Improve your ideas. Add details, rearrange, clarify. +
- Edit β Fix spelling, grammar, and punctuation. +
- Publish β Share your finished work! +
Good writers don't write perfectly the first time β they revise and improve!
+`, 0, null, 1, daysAgo(4), 0, 'none'); +itemId++; + +insertItem.run(itemId, laUnit2, 'assignment', 'Descriptive Paragraph', ` +Write a descriptive paragraph (at least 5 sentences) about your favorite place. +Use vivid adjectives and at least two different sentence types (simple, compound, or complex). + +Remember to: +- Start with a topic sentence +- Include sensory details (what you see, hear, smell, feel) +- End with a concluding sentence +`, 25, null, 2, daysAhead(3), 0, 'none'); +itemId++; + +/* βββ Mathematics ββββββββββββββββββββββββββββββββββββββββββ + Unit 1: Fractions + Unit 2: Geometry */ + +insertUnit.run(unitId, 2, 'Fractions & Decimals', 1); +const mathUnit1 = unitId++; + +insertItem.run(itemId, mathUnit1, 'lesson', 'Understanding Fractions', ` +What Is a Fraction?
+A fraction represents a part of a whole. It has two parts:
+-
+
- Numerator β the top number (how many parts you have) +
- Denominator β the bottom number (how many equal parts the whole is divided into) +
Equivalent Fractions
+Fractions that name the same amount are called equivalent fractions.
+Example: 1/2 = 2/4 = 4/8
+Simplifying Fractions
+Divide the numerator and denominator by their greatest common factor (GCF).
+Example: 8/12 β GCF is 4 β 2/3
+`, 0, null, 1, daysAgo(21), 0, 'none'); +itemId++; + +insertItem.run(itemId, mathUnit1, 'quiz', 'Fractions Quiz', null, 30, null, 2, daysAgo(14), 1, 'none'); +const quizFrac = itemId++; +insertQ.run(qId++, quizFrac, 'mc', 'What is the simplified form of 6/9?', JSON.stringify(['1/3','2/3','3/4','6/9']), '2/3', 10, 1); +insertQ.run(qId++, quizFrac, 'mc', 'Which fraction is equivalent to 1/2?', JSON.stringify(['2/3','3/4','4/8','5/9']), '4/8', 10, 2); +insertQ.run(qId++, quizFrac, 'sa', 'Write two fractions that are equivalent to 3/4.', null, null, 10, 3); + +insertItem.run(itemId, mathUnit1, 'lesson', 'Adding & Subtracting Fractions', ` +Same Denominator
+When denominators are the same, just add or subtract the numerators:
+3/8 + 2/8 = 5/8
+Different Denominators
+Find the Least Common Denominator (LCD), convert, then add:
+1/3 + 1/4 β LCD = 12 β 4/12 + 3/12 = 7/12
+`, 0, null, 3, daysAgo(7), 0, 'none'); +itemId++; + +insertItem.run(itemId, mathUnit1, 'assignment', 'Fractions Worksheet', ` +Solve these fraction problems. Show your work! + +1. 2/5 + 1/5 = +2. 3/4 - 1/4 = +3. 1/3 + 1/2 = +4. 5/6 - 1/3 = +5. Simplify 10/15 +6. Simplify 9/12 +7. Are 2/3 and 8/12 equivalent? Explain why. +`, 20, null, 4, daysAhead(1), 1, 'none'); +itemId++; + +insertUnit.run(unitId, 2, 'Geometry', 2); +const mathUnit2 = unitId++; + +insertItem.run(itemId, mathUnit2, 'lesson', 'Polygons & Angles', ` +What Is a Polygon?
+A polygon is a closed shape made of straight line segments.
+Common Polygons
+-
+
- Triangle β 3 sides, angles sum to 180Β° +
- Quadrilateral β 4 sides, angles sum to 360Β° +
- Pentagon β 5 sides +
- Hexagon β 6 sides +
Types of Angles
+-
+
- Acute β less than 90Β° +
- Right β exactly 90Β° +
- Obtuse β between 90Β° and 180Β° +
- Straight β exactly 180Β° +
Earth's Water Is Always Moving
+The water cycle (also called the hydrological cycle) describes the continuous movement of water through Earth's systems.
+The Four Main Stages
+-
+
- Evaporation β Heat from the sun turns liquid water into water vapor, which rises into the atmosphere. +
- Condensation β Water vapor cools and forms tiny droplets that make clouds and fog. +
- Precipitation β Water falls from clouds as rain, snow, sleet, or hail. +
- Collection β Water gathers in oceans, lakes, rivers, and groundwater, and the cycle begins again. +
The sun is the energy source that drives the water cycle!
+`, 0, null, 1, daysAgo(18), 0, 'none'); +itemId++; + +insertItem.run(itemId, sciUnit1, 'quiz', 'Water Cycle Quiz', null, 25, null, 2, daysAgo(12), 1, 'none'); +const quizWater = itemId++; +insertQ.run(qId++, quizWater, 'mc', 'What is the process where liquid water becomes water vapor?', JSON.stringify(['Condensation','Precipitation','Evaporation','Collection']), 'Evaporation', 5, 1); +insertQ.run(qId++, quizWater, 'mc', 'What energy source drives the water cycle?', JSON.stringify(['The moon','Wind','The sun','Earth\'s core']), 'The sun', 5, 2); +insertQ.run(qId++, quizWater, 'mc', 'What is it called when water falls from clouds?', JSON.stringify(['Evaporation','Condensation','Collection','Precipitation']), 'Precipitation', 5, 3); +insertQ.run(qId++, quizWater, 'mc', 'Which of these is NOT a form of precipitation?', JSON.stringify(['Rain','Snow','Fog','Hail']), 'Fog', 5, 4); +insertQ.run(qId++, quizWater, 'sa', 'Explain in your own words what condensation is.', null, null, 5, 5); + +insertItem.run(itemId, sciUnit1, 'assignment', 'Water Cycle Diagram', ` +Draw and label a diagram of the water cycle. Your diagram must include: +- At least 4 labeled stages (evaporation, condensation, precipitation, collection) +- Arrows showing the direction of movement +- The sun as the energy source +- At least one body of water (ocean, lake, or river) + +Take a photo of your completed diagram and submit it, or describe it in writing. +`, 30, null, 3, daysAhead(2), 0, 'photo'); +itemId++; + +insertUnit.run(unitId, 3, 'Ecosystems', 2); +const sciUnit2 = unitId++; + +insertItem.run(itemId, sciUnit2, 'lesson', 'Food Chains & Food Webs', ` +Energy in Ecosystems
+All living things need energy. In an ecosystem, energy flows from one organism to another through food chains.
+Producers
+Plants are producers β they make their own food using sunlight (photosynthesis).
+Consumers
+-
+
- Primary consumers β eat plants (herbivores). Example: rabbits, deer, caterpillars +
- Secondary consumers β eat primary consumers. Example: foxes, frogs, small birds +
- Tertiary consumers β eat secondary consumers. Example: eagles, sharks, wolves +
Decomposers
+Fungi and bacteria break down dead organisms and return nutrients to the soil.
+`, 0, null, 1, daysAhead(7), 0, 'none'); +itemId++; + +/* βββ American History βββββββββββββββββββββββββββββββββββββ + Unit 1: Colonial America + Unit 2: American Revolution */ + +insertUnit.run(unitId, 4, 'Colonial America', 1); +const histUnit1 = unitId++; + +insertItem.run(itemId, histUnit1, 'lesson', 'The 13 Original Colonies', ` +Building a New World
+Between 1607 and 1733, thirteen English colonies were established along the eastern coast of North America.
+Three Regions
+New England Colonies
+Massachusetts, Connecticut, Rhode Island, New Hampshire β Known for fishing, shipbuilding, and trade.
+Middle Colonies
+New York, New Jersey, Pennsylvania, Delaware β Known as the "breadbasket" for their grain farms.
+Southern Colonies
+Virginia, Maryland, North Carolina, South Carolina, Georgia β Known for tobacco, rice, and indigo plantations.
+Life in the colonies was shaped by religion, the land, and relationships with Native Americans.
+`, 0, null, 1, daysAgo(25), 0, 'none'); +itemId++; + +insertItem.run(itemId, histUnit1, 'quiz', 'Colonial America Quiz', null, 20, null, 2, daysAgo(20), 1, 'none'); +const quizColonial = itemId++; +insertQ.run(qId++, quizColonial, 'mc', 'Which region was known as the "breadbasket" colonies?', JSON.stringify(['New England','Middle Colonies','Southern Colonies','Western Territories']), 'Middle Colonies', 5, 1); +insertQ.run(qId++, quizColonial, 'mc', 'What was the first permanent English settlement in America?', JSON.stringify(['Plymouth','Boston','Jamestown','Philadelphia']), 'Jamestown', 5, 2); +insertQ.run(qId++, quizColonial, 'mc', 'How many original colonies were there?', JSON.stringify(['10','12','13','15']), '13', 5, 3); +insertQ.run(qId++, quizColonial, 'sa', 'Name two crops grown in the Southern Colonies.', null, null, 5, 4); + +insertItem.run(itemId, histUnit1, 'lesson', 'Daily Life in the Colonies', ` +Life in Colonial Times
+Colonial children had very different lives from kids today. Most families farmed and made things by hand.
+Education
+Children in New England attended dame schools taught by women in their homes. They learned to read using the New England Primer. Boys might go on to a Latin grammar school; girls usually stayed home to learn household skills.
+Work
+Children helped with farm chores, cooking, spinning, and candle-making from a young age.
+Religion
+Religion was central to colonial life. Many colonists came to America seeking religious freedom.
+`, 0, null, 3, daysAgo(15), 0, 'none'); +itemId++; + +insertUnit.run(unitId, 4, 'American Revolution', 2); +const histUnit2 = unitId++; + +insertItem.run(itemId, histUnit2, 'lesson', 'Road to Revolution', ` +Growing Tensions
+By the 1760s, colonists were frustrated with British rule. Several key events pushed them toward revolution.
+Taxation Without Representation
+Britain taxed the colonies β the Stamp Act (1765) and Townshend Acts (1767) β without giving them a voice in Parliament. Colonists protested "No taxation without representation!"
+Boston Massacre (1770)
+British soldiers fired into a crowd of colonists, killing five people. This event inflamed anti-British feelings.
+Boston Tea Party (1773)
+Colonists dumped 342 chests of British tea into Boston Harbor to protest the Tea Act. Britain responded with the Intolerable Acts.
+These events pushed the colonies toward declaring independence.
+`, 0, null, 1, daysAhead(4), 0, 'none'); +itemId++; + +/* βββ Bible ββββββββββββββββββββββββββββββββββββββββββββββββ + Unit 1: Old Testament Heroes */ + +insertUnit.run(unitId, 5, 'Old Testament Heroes', 1); +const bibleUnit1 = unitId++; + +insertItem.run(itemId, bibleUnit1, 'lesson', 'Noah and the Ark', ` +A Story of Faith and Obedience
+The story of Noah is found in Genesis 6β9.
+God's Instruction
+God saw that the world had become full of wickedness. He chose Noah β a righteous man β and instructed him to build an ark (a large wooden boat) to save his family and two of every kind of animal.
+Noah's Obedience
+Though it had never rained before (some scholars believe), Noah trusted God and obeyed. He built the ark exactly as God instructed.
+The Flood
+It rained for 40 days and 40 nights. The floodwaters covered the earth for 150 days before receding.
+God's Promise
+After the flood, God placed a rainbow in the sky as a sign of His covenant β a promise never to flood the entire earth again.
+"I have set my rainbow in the clouds, and it will be the sign of the covenant between me and the earth." β Genesis 9:13+`, 0, null, 1, daysAgo(10), 0, 'none'); +itemId++; + +insertItem.run(itemId, bibleUnit1, 'quiz', 'Noah and the Ark Quiz', null, 15, null, 2, daysAgo(7), 1, 'none'); +const quizNoah = itemId++; +insertQ.run(qId++, quizNoah, 'mc', 'Where is the story of Noah found in the Bible?', JSON.stringify(['Exodus','Matthew','Genesis','Psalms']), 'Genesis', 5, 1); +insertQ.run(qId++, quizNoah, 'mc', 'How many days and nights did it rain?', JSON.stringify(['7','20','40','100']), '40', 5, 2); +insertQ.run(qId++, quizNoah, 'sa', 'What did the rainbow represent after the flood?', null, null, 5, 3); + +insertItem.run(itemId, bibleUnit1, 'lesson', 'David and Goliath', ` +
Courage and Faith
+Found in 1 Samuel 17, the story of David and Goliath shows that with God, the smallest person can overcome great challenges.
+The Challenge
+Goliath was a Philistine warrior over 9 feet tall who taunted the Israelite army. No soldier was brave enough to fight him β except a young shepherd boy named David.
+David's Confidence
+David told King Saul that God had helped him protect his sheep from lions and bears, so God would help him defeat Goliath too.
+The Victory
+Armed with only his sling and five smooth stones, David ran toward Goliath, slung a stone, and struck the giant in the forehead. Goliath fell, and the Philistines fled.
+`, 0, null, 3, daysAhead(3), 0, 'none'); +itemId++; + +console.log('Courses, units, and items created.'); + +/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + SUBMISSIONS (completed work with scores) +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ +const insertSub = db.prepare(` + INSERT INTO submissions (id, student_id, item_id, status, score, points_possible, answers, completed_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) +`); +const insertSubHist = db.prepare(` + INSERT INTO submission_history (student_id, item_id, score, points_possible, answers, completed_at) + VALUES (?, ?, ?, ?, ?, ?) +`); + +let subId = 1; + +// Emma's completed work +// Parts of Speech lesson β done +insertSub.run(subId++, 1, lessonPos, 'done', null, 0, null, tsAgo(13)); +// Parts of Speech Quiz β 15/20 +const posAnswers = JSON.stringify({ 1: 'mountain', 2: 'jump', 3: 'enormous', 4: 'We went to the park in the morning.' }); +insertSub.run(subId++, 1, quizPos, 'done', 15, 20, posAnswers, tsAgo(9)); +insertSubHist.run(1, quizPos, 15, 20, posAnswers, tsAgo(9)); + +// Fractions Quiz β 25/30 (Emma) +const fracAnswers = JSON.stringify({ 1: '2/3', 2: '4/8', 3: '6/8 and 9/12' }); +insertSub.run(subId++, 1, quizFrac, 'done', 25, 30, fracAnswers, tsAgo(13)); +insertSubHist.run(1, quizFrac, 25, 30, fracAnswers, tsAgo(13)); + +// Water Cycle Quiz β 20/25 (Emma) +const waterAnswers = JSON.stringify({ 1: 'Evaporation', 2: 'The sun', 3: 'Precipitation', 4: 'Fog', 5: 'Condensation is when water vapor cools and turns back into liquid droplets.' }); +insertSub.run(subId++, 1, quizWater, 'done', 20, 25, waterAnswers, tsAgo(11)); +insertSubHist.run(1, quizWater, 20, 25, waterAnswers, tsAgo(11)); + +// Colonial America Quiz β 18/20 (Emma) +const colonialAnswers = JSON.stringify({ 1: 'Middle Colonies', 2: 'Jamestown', 3: '13', 4: 'Tobacco and rice' }); +insertSub.run(subId++, 1, quizColonial, 'done', 18, 20, colonialAnswers, tsAgo(19)); +insertSubHist.run(1, quizColonial, 18, 20, colonialAnswers, tsAgo(19)); + +// Noah quiz β 12/15 (Emma) +const noahAnswers = JSON.stringify({ 1: 'Genesis', 2: '40', 3: "The rainbow was God's promise never to flood the whole earth again." }); +insertSub.run(subId++, 1, quizNoah, 'done', 12, 15, noahAnswers, tsAgo(6)); +insertSubHist.run(1, quizNoah, 12, 15, noahAnswers, tsAgo(6)); + +// Liam's completed work +// Fractions Quiz β 20/30 (Liam, first attempt) +const fracAnswersLiam1 = JSON.stringify({ 1: '2/3', 2: '3/4', 3: '4/6 and 8/12' }); +insertSub.run(subId++, 2, quizFrac, 'done', 20, 30, fracAnswersLiam1, tsAgo(15)); +insertSubHist.run(2, quizFrac, 20, 30, fracAnswersLiam1, tsAgo(15)); +// Liam retook β 27/30 +const fracAnswersLiam2 = JSON.stringify({ 1: '2/3', 2: '4/8', 3: '6/8 and 9/12' }); +insertSub.run(subId++, 2, quizFrac, 'done', 27, 30, fracAnswersLiam2, tsAgo(12)); +insertSubHist.run(2, quizFrac, 27, 30, fracAnswersLiam2, tsAgo(12)); + +// Water Cycle β 22/25 (Liam) +const waterLiam = JSON.stringify({ 1: 'Evaporation', 2: 'The sun', 3: 'Precipitation', 4: 'Fog', 5: 'When warm air cools it turns into water droplets.' }); +insertSub.run(subId++, 2, quizWater, 'done', 22, 25, waterLiam, tsAgo(10)); +insertSubHist.run(2, quizWater, 22, 25, waterLiam, tsAgo(10)); + +// Colonial β 15/20 (Liam) +const colonialLiam = JSON.stringify({ 1: 'Middle Colonies', 2: 'Jamestown', 3: '13', 4: 'Tobacco and corn' }); +insertSub.run(subId++, 2, quizColonial, 'done', 15, 20, colonialLiam, tsAgo(18)); +insertSubHist.run(2, quizColonial, 15, 20, colonialLiam, tsAgo(18)); + +// Parts of Speech (Liam) β 16/20 +const posLiam = JSON.stringify({ 1: 'mountain', 2: 'jump', 3: 'enormous', 4: 'I put the book on the table.' }); +insertSub.run(subId++, 2, quizPos, 'done', 16, 20, posLiam, tsAgo(8)); +insertSubHist.run(2, quizPos, 16, 20, posLiam, tsAgo(8)); + +console.log('Submissions created.'); + +/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + SPELLING LISTS +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ +const insertList = db.prepare('INSERT INTO lists (id, name, builtin, group_name) VALUES (?, ?, 0, ?)'); +const insertWord = db.prepare('INSERT INTO words (id, list_id, word, sentence, definition) VALUES (?, ?, ?, ?, ?)'); + +let listId = 1, wordId = 1; + +// List 1: 4th Grade Week 1 +insertList.run(listId, '4th Grade β Week 1', 'Grade 4'); +const list1 = listId++; +const week1words = [ + ['beautiful', 'The sunset was absolutely beautiful.', 'pleasing to the senses or mind'], + ['necessary', 'It is necessary to drink water every day.', 'needed; essential'], + ['separate', 'Please separate the red and blue blocks.', 'to set apart; to divide'], + ['because', 'I stayed inside because it was raining.', 'for the reason that'], + ['different', 'Each snowflake is different from every other.','not the same as another'], + ['important', 'Exercise is important for staying healthy.', 'having great significance'], + ['knowledge', 'Reading books builds your knowledge.', 'facts or skills acquired through learning'], + ['surprise', 'The party was a complete surprise.', 'an unexpected event or feeling'], + ['through', 'We walked through the park.', 'moving from one side to the other'], + ['whether', 'I wasn\'t sure whether to go or stay.', 'expressing a doubt or choice'], +]; +week1words.forEach(([w, s, d]) => insertWord.run(wordId++, list1, w, s, d)); + +// List 2: 4th Grade Week 2 +insertList.run(listId, '4th Grade β Week 2', 'Grade 4'); +const list2 = listId++; +const week2words = [ + ['character', 'The main character in the story was brave.', 'a person in a story; a person\'s qualities'], + ['complete', 'Did you complete your homework?', 'having all necessary parts; to finish'], + ['continue', 'We will continue reading tomorrow.', 'to keep going'], + ['difficult', 'The math problem was very difficult.', 'not easy; hard to do'], + ['environment', 'We should protect the natural environment.', 'the surroundings in which one lives'], + ['frequently', 'She frequently visits her grandparents.', 'often; many times'], + ['government', 'The government makes laws for the country.', 'the group that rules a nation'], + ['language', 'English is a fascinating language.', 'a system of words used to communicate'], + ['position', 'What position does he play on the team?', 'a place where someone or something is located'], + ['remember', 'I will always remember that day.', 'to keep in mind; to recall'], +]; +week2words.forEach(([w, s, d]) => insertWord.run(wordId++, list2, w, s, d)); + +// List 3: Science Vocabulary +insertList.run(listId, 'Science Vocabulary', 'Science'); +const list3 = listId++; +const sciWords = [ + ['evaporation', 'Evaporation turns liquid water into vapor.', 'process of liquid changing to gas'], + ['condensation', 'Condensation forms on a cold glass on a hot day.', 'process of gas changing to liquid'], + ['precipitation','Precipitation includes rain, snow, and hail.', 'water falling from clouds'], + ['ecosystem', 'A pond is a small ecosystem.', 'a community of living and nonliving things'], + ['photosynthesis','Plants use photosynthesis to make food.', 'how plants make food using sunlight'], + ['organism', 'Every living thing is an organism.', 'any living thing'], + ['hypothesis', 'We formed a hypothesis before the experiment.', 'an educated guess to be tested'], + ['atmosphere', 'The atmosphere protects Earth from the sun\'s rays.', 'the layer of gases around a planet'], +]; +sciWords.forEach(([w, s, d]) => insertWord.run(wordId++, list3, w, s, d)); + +// List 4: Bible Memory Words +insertList.run(listId, 'Bible Memory Words', 'Bible'); +const list4 = listId++; +const bibleWords = [ + ['covenant', 'God made a covenant with Noah.', 'a serious agreement or promise'], + ['righteous', 'Noah was a righteous man.', 'morally right; following God\'s ways'], + ['obedient', 'David was obedient to God\'s calling.', 'doing what you are told; compliant'], + ['sacrifice', 'Abraham trusted God with the sacrifice.', 'giving something valuable for a purpose'], + ['steadfast', 'Be steadfast in your faith.', 'firm and unwavering'], + ['gracious', 'God is gracious and full of compassion.', 'showing grace; kind and merciful'], +]; +bibleWords.forEach(([w, s, d]) => insertWord.run(wordId++, list4, w, s, d)); + +console.log('Spelling lists and words created.'); + +/* ββ assignments (connect students to lists) ββββββββββββββββ */ +const insertAssignment = db.prepare('INSERT INTO assignments (student_id, list_id, assigned_at) VALUES (?, ?, ?)'); +insertAssignment.run(1, list1, tsAgo(14)); // Emma β Week 1 +insertAssignment.run(1, list2, tsAgo(7)); // Emma β Week 2 +insertAssignment.run(1, list3, tsAgo(18)); // Emma β Science +insertAssignment.run(1, list4, tsAgo(10)); // Emma β Bible +insertAssignment.run(2, list1, tsAgo(14)); // Liam β Week 1 +insertAssignment.run(2, list2, tsAgo(7)); // Liam β Week 2 +insertAssignment.run(2, list3, tsAgo(18)); // Liam β Science + +/* ββ spelling progress (Leitner boxes) ββββββββββββββββββββββ */ +const insertProgress = db.prepare(` + INSERT INTO progress (student_id, word_id, box, due, attempts, correct) + VALUES (?, ?, ?, ?, ?, ?) +`); + +// Emma has practiced all of Week 1 β most words are in boxes 3-5 +const emmaWordIds = Array.from({length: 10}, (_, i) => i + 1); // words 1-10 are list1 +const boxDist = [5,5,4,4,3,3,3,2,4,5]; +emmaWordIds.forEach((wid, i) => { + const box = boxDist[i]; + const due = box >= 4 ? daysAhead(7) : box === 3 ? daysAhead(2) : today(); + insertProgress.run(1, wid, box, due, 4 + box, 3 + box); +}); + +// Emma β Week 2 words partially practiced (boxes 1-3) +const week2ids = Array.from({length: 10}, (_, i) => i + 11); +week2ids.forEach((wid, i) => { + const box = Math.min(3, 1 + Math.floor(i / 4)); + insertProgress.run(1, wid, box, i < 5 ? today() : daysAhead(1), 1 + box, box); +}); + +// Liam β Week 1 partially (boxes 1-3) +emmaWordIds.forEach((wid, i) => { + const box = Math.min(3, 1 + Math.floor(i / 4)); + insertProgress.run(2, wid, box, i < 4 ? today() : daysAhead(1), 1 + box, box); +}); + +/* ββ spelling tests βββββββββββββββββββββββββββββββββββββββββ */ +const insertTest = db.prepare('INSERT INTO tests (id, student_id, list_id, score, total, at) VALUES (?, ?, ?, ?, ?, ?)'); +const insertTA = db.prepare('INSERT INTO test_answers (test_id, word_id, typed, correct) VALUES (?, ?, ?, ?)'); + +// Emma β 3 spelling tests +insertTest.run(1, 1, list1, 8, 10, tsAgo(12)); +// test answers for test 1 +emmaWordIds.slice(0, 10).forEach((wid, i) => { + const word = week1words[i][0]; + const right = i < 8; + const typed = right ? word : (i === 8 ? 'thrugh' : 'wether'); + insertTA.run(1, wid, typed, right ? 1 : 0); +}); + +insertTest.run(2, 1, list1, 10, 10, tsAgo(5)); +emmaWordIds.slice(0, 10).forEach((wid, i) => { + insertTA.run(2, wid, week1words[i][0], 1); +}); + +insertTest.run(3, 1, list3, 7, 8, tsAgo(2)); +Array.from({length: 8}, (_, i) => i + 21).forEach((wid, i) => { + const right = i < 7; + insertTA.run(3, wid, right ? sciWords[i][0] : 'atmosfere', right ? 1 : 0); +}); + +// Liam β 2 spelling tests +insertTest.run(4, 2, list1, 7, 10, tsAgo(11)); +emmaWordIds.slice(0, 10).forEach((wid, i) => { + const right = i < 7; + const word = week1words[i][0]; + insertTA.run(4, wid, right ? word : word.replace(/[aeiou]/, ''), right ? 1 : 0); +}); + +insertTest.run(5, 2, list1, 9, 10, tsAgo(4)); +emmaWordIds.slice(0, 10).forEach((wid, i) => { + const right = i !== 4; + insertTA.run(5, wid, right ? week1words[i][0] : 'diffrent', right ? 1 : 0); +}); + +console.log('Spelling tests created.'); + +/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + FLASHCARD DECKS +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ +const insertDeck = db.prepare('INSERT INTO decks (id, name, builtin, group_name) VALUES (?, ?, 0, ?)'); +const insertCard = db.prepare('INSERT INTO cards (id, deck_id, front, back) VALUES (?, ?, ?, ?)'); + +let deckId = 1, cardId = 1; + +insertDeck.run(deckId, 'Math Facts β Multiplication', 'Math'); +const deck1 = deckId++; +const multFacts = [ + ['6 Γ 7', '42'], ['6 Γ 8', '48'], ['7 Γ 7', '49'], ['7 Γ 8', '56'], + ['8 Γ 8', '64'], ['8 Γ 9', '72'], ['9 Γ 7', '63'], ['9 Γ 9', '81'], + ['6 Γ 9', '54'], ['7 Γ 9', '63'], ['4 Γ 8', '32'], ['5 Γ 7', '35'], +]; +multFacts.forEach(([f, b]) => insertCard.run(cardId++, deck1, f, b)); + +insertDeck.run(deckId, 'US States & Capitals', 'Geography'); +const deck2 = deckId++; +const states = [ + ['California', 'Sacramento'], ['Texas', 'Austin'], ['Florida', 'Tallahassee'], + ['New York', 'Albany'], ['Pennsylvania', 'Harrisburg'], ['Ohio', 'Columbus'], + ['Virginia', 'Richmond'], ['Georgia', 'Atlanta'], ['Michigan', 'Lansing'], + ['Massachusetts', 'Boston'], ['Illinois', 'Springfield'], ['Arizona', 'Phoenix'], +]; +states.forEach(([f, b]) => insertCard.run(cardId++, deck2, `Capital of ${f}`, b)); + +insertDeck.run(deckId, 'Science Vocabulary', 'Science'); +const deck3 = deckId++; +sciWords.forEach(([w, , d]) => insertCard.run(cardId++, deck3, w, d)); + +insertDeck.run(deckId, 'Grammar Terms', 'English'); +const deck4 = deckId++; +const grammarCards = [ + ['Noun', 'A word that names a person, place, thing, or idea'], + ['Verb', 'A word that shows action or state of being'], + ['Adjective', 'A word that describes a noun'], + ['Adverb', 'A word that describes a verb, adjective, or another adverb'], + ['Pronoun', 'A word that takes the place of a noun'], + ['Preposition', 'A word that shows relationship (in, on, under, beside)'], + ['Conjunction', 'A word that joins words or clauses (and, but, or, because)'], + ['Subject', 'Who or what the sentence is about'], + ['Predicate', 'What the subject does or is'], + ['Independent clause', 'A group of words that can stand alone as a sentence'], +]; +grammarCards.forEach(([f, b]) => insertCard.run(cardId++, deck4, f, b)); + +console.log('Flashcard decks created.'); + +/* ββ card progress (Emma has practiced Math deck) βββββββββββ */ +const insertCardProg = db.prepare(` + INSERT INTO card_progress (student_id, card_id, box, due, attempts, correct) + VALUES (?, ?, ?, ?, ?, ?) +`); +const mathCardIds = Array.from({length: 12}, (_, i) => i + 1); +const mathBoxes = [5,5,4,4,3,3,5,2,4,3,4,5]; +mathCardIds.forEach((cid, i) => { + const box = mathBoxes[i]; + const due = box >= 4 ? daysAhead(5) : box === 3 ? daysAhead(1) : today(); + insertCardProg.run(1, cid, box, due, 3 + box, 2 + box); +}); + +/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + SCHEDULE (planner entries for this week) +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ +const insertSchedule = db.prepare(` + INSERT INTO schedule (student_id, date, item_id, title, done, sort, status) + VALUES (?, ?, ?, ?, ?, ?, 'pending') +`); + +// Get day-of-week offsets so we land MonβFri +const getWeekday = (offset) => { + const d = new Date(); + const day = d.getDay(); // 0=Sun + const monday = new Date(d); + monday.setDate(d.getDate() - (day === 0 ? 6 : day - 1)); + monday.setDate(monday.getDate() + offset); + return monday.toISOString().slice(0, 10); +}; + +const mon = getWeekday(0), tue = getWeekday(1), wed = getWeekday(2), + thu = getWeekday(3), fri = getWeekday(4); + +// Emma's schedule this week +insertSchedule.run(1, mon, null, 'Morning Bible reading β Genesis 7', 1, 1); +insertSchedule.run(1, mon, null, 'Spelling practice β Week 2 words', 0, 2); +insertSchedule.run(1, tue, null, 'Math: Adding Fractions worksheet', 0, 1); +insertSchedule.run(1, tue, null, 'History reading: Road to Revolution', 0, 2); +insertSchedule.run(1, wed, null, 'Science: Water Cycle review', 0, 1); +insertSchedule.run(1, wed, null, 'Language Arts: Descriptive paragraph draft', 0, 2); +insertSchedule.run(1, thu, null, 'Spelling test β Week 2', 0, 1); +insertSchedule.run(1, thu, null, 'Math: Geometry lesson', 0, 2); +insertSchedule.run(1, fri, null, 'Free reading (30 min)', 0, 1); +insertSchedule.run(1, fri, null, 'Review week β all subjects', 0, 2); + +// Liam's schedule +insertSchedule.run(2, mon, null, 'Spelling practice β Week 2 words', 0, 1); +insertSchedule.run(2, mon, null, 'Math: Fractions review', 0, 2); +insertSchedule.run(2, tue, null, 'Science: Water Cycle notes', 0, 1); +insertSchedule.run(2, tue, null, 'Language Arts: Parts of Speech', 0, 2); +insertSchedule.run(2, wed, null, 'History: Colonial Life lesson', 0, 1); +insertSchedule.run(2, wed, null, 'Math: Geometry intro', 0, 2); +insertSchedule.run(2, thu, null, 'Spelling test β Week 2', 0, 1); +insertSchedule.run(2, fri, null, 'Free reading (30 min)', 0, 1); + +console.log('Schedule created.'); + +/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + MESSAGES +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ +const insertMsg = db.prepare(` + INSERT INTO messages (student_id, sender, body, sent_at, read_at) + VALUES (?, ?, ?, ?, ?) +`); +insertMsg.run(1, 'parent', 'Great job on your spelling test, Emma! 10/10! π', tsAgo(5), tsAgo(5)); +insertMsg.run(1, 'student', 'Thanks! I practiced a lot. Can we do science experiments today?', tsAgo(5), tsAgo(5)); +insertMsg.run(1, 'parent', 'I love that idea! Let\'s do the water cycle experiment after lunch.', tsAgo(5), tsAgo(5)); +insertMsg.run(2, 'parent', 'Liam, remember to finish your fractions worksheet before dinner.', tsAgo(2), tsAgo(2)); +insertMsg.run(2, 'student', 'Ok! I already did problems 1-4, just have 5-7 left.', tsAgo(2), null); + +console.log('Messages created.'); + +/* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + SETTINGS +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ +const upsertSetting = db.prepare('INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)'); +upsertSetting.run('school_name', 'Emmert Academy'); +upsertSetting.run('timezone', 'America/Chicago'); + +console.log('\nβ Demo data seeded successfully!'); +console.log(' Students: Emma (π¦) and Liam (π»)'); +console.log(' Courses: Language Arts, Math, Earth Science, American History, Bible'); +console.log(' Spelling: 4 lists with 36 words total'); +console.log(' Flashcards: 4 decks with', 12 + 12 + 8 + 10, 'cards'); +console.log(' Quiz submissions with scores for both students'); +console.log(' Weekly planner schedules'); diff --git a/server.js b/server.js index eaaf931..4272805 100644 --- a/server.js +++ b/server.js @@ -829,6 +829,24 @@ app.post('/api/parent/pin', requirePin, (req, res) => { res.json({ ok: true }); }); +app.get('/api/admin/schedule', requirePin, (req, res) => { + const { date } = req.query; + const d = date || new Date().toISOString().slice(0, 10); + const rows = db.prepare(`SELECT * FROM schedule WHERE date = ? ORDER BY student_id, sort`).all(d); + res.json({ rows }); +}); + +app.get('/api/admin/recent-scores', requirePin, (req, res) => { + const scores = db.prepare(` + SELECT s.id, s.student_id, i.title, s.score, s.total, s.submitted_at + FROM submissions s + JOIN items i ON i.id = s.item_id + ORDER BY s.submitted_at DESC + LIMIT 20 + `).all(); + res.json({ scores }); +}); + app.get('/api/admin/app-settings', requirePin, (req, res) => { res.json({ school_name: getSetting('school_name') || '',