From cedaba4ca60f085f496c48b19cbc3f3e659ba7da Mon Sep 17 00:00:00 2001 From: haopengzhan Date: Sun, 23 Aug 2026 13:38:00 -0700 Subject: [PATCH] fix: map exact Brightwheel video_info, state (1=start/in, 0=end, 2=out), and actor metadata --- dashboard/dashboard.js | 78 +++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/dashboard/dashboard.js b/dashboard/dashboard.js index ec37d61..838ea7c 100644 --- a/dashboard/dashboard.js +++ b/dashboard/dashboard.js @@ -642,6 +642,20 @@ function extractMediaUrls(act) { let videoUrl = null; let thumbUrl = null; + // Direct check for Brightwheel video_info object + if (act.video_info && typeof act.video_info === 'object') { + if (act.video_info.downloadable_url) videoUrl = act.video_info.downloadable_url; + else if (act.video_info.streamable_url) videoUrl = act.video_info.streamable_url; + if (act.video_info.thumbnail_url) thumbUrl = act.video_info.thumbnail_url; + } + + // Direct check for Brightwheel media object + if (act.media && typeof act.media === 'object') { + if (act.media.image_url) imageUrl = act.media.image_url; + if (act.media.video_url && !videoUrl) videoUrl = act.media.video_url; + if (act.media.thumbnail_url && !thumbUrl) thumbUrl = act.media.thumbnail_url; + } + const isVideoStr = (url) => { if (!url || typeof url !== 'string') return false; const lower = url.toLowerCase(); @@ -662,7 +676,7 @@ function extractMediaUrls(act) { if (isVideoStr(obj) && !videoUrl) videoUrl = obj; else if (isImageStr(obj) && !imageUrl) imageUrl = obj; else if (!imageUrl && !videoUrl) { - if (act.activity_type === 'ac_video' || act.type === 'video') videoUrl = obj; + if (act.activity_type === 'ac_video' || act.action_type === 'ac_video' || act.type === 'video') videoUrl = obj; else imageUrl = obj; } } @@ -677,7 +691,7 @@ function extractMediaUrls(act) { if (typeof obj === 'object') { // Check video keys const possibleVideos = [ - obj.video_url, obj.video_stream_url, obj.video_hd_url, obj.video_transcode_url, + obj.downloadable_url, obj.streamable_url, obj.video_url, obj.video_stream_url, obj.video_hd_url, obj.video_transcode_url, obj.transcoded_video_url, obj.original_video_url, obj.stream_url, obj.mp4_url, obj.video, obj.source_url, obj.download_url ]; @@ -702,7 +716,7 @@ function extractMediaUrls(act) { // Check generic url if (typeof obj.url === 'string' && obj.url.startsWith('http')) { - if (isVideoStr(obj.url) || act.activity_type === 'ac_video' || act.type === 'video') { + if (isVideoStr(obj.url) || act.activity_type === 'ac_video' || act.action_type === 'ac_video' || act.type === 'video') { if (!videoUrl) videoUrl = obj.url; } else if (!imageUrl) { imageUrl = obj.url; @@ -719,6 +733,7 @@ function extractMediaUrls(act) { } // Recurse into nested common containers + if (obj.video_info) search(obj.video_info, depth + 1); if (obj.media) search(obj.media, depth + 1); if (obj.video_data) search(obj.video_data, depth + 1); if (obj.attachments) search(obj.attachments, depth + 1); @@ -733,7 +748,7 @@ function extractMediaUrls(act) { search(act); // If it's an ac_video activity and imageUrl was found but points to video, fix assignment - if ((act.activity_type === 'ac_video' || act.type === 'video') && !videoUrl && imageUrl) { + if ((act.activity_type === 'ac_video' || act.action_type === 'ac_video' || act.type === 'video') && !videoUrl && imageUrl) { videoUrl = imageUrl; imageUrl = null; } @@ -743,11 +758,13 @@ function extractMediaUrls(act) { function normalizeActivity(act) { const { imageUrl, videoUrl, thumbUrl } = extractMediaUrls(act); - const actType = (act.activity_type || act.action_type || act.type || 'activity').toLowerCase(); - const rawSubtype = (act.action_type || act.event_type || act.subtype || act.sub_type || act.action || act.state || act.status || '').toLowerCase(); - - // Extract staff / actor name - const teacherName = act.actor_name || act.created_by?.name || act.teacher_name || act.staff_name || (act.actor ? `${act.actor.first_name || ''} ${act.actor.last_name || ''}`.trim() : '') || ''; + const actType = (act.action_type || act.activity_type || act.type || 'activity').toLowerCase(); + const rawSubtype = (act.action_type || act.event_type || act.subtype || act.sub_type || act.action || '').toLowerCase(); + const stateVal = String(act.state ?? ''); + + // Extract actor / staff name + const actor = act.actor ? `${act.actor.first_name || ''} ${act.actor.last_name || ''}`.trim() : ''; + const teacherName = actor || act.actor_name || act.created_by?.name || act.teacher_name || act.staff_name || ''; // Parse Subtypes and Human-readable Summaries for each category let actionSummary = ''; @@ -761,12 +778,12 @@ function normalizeActivity(act) { let milestoneName = act.milestone_name || act.tag_name || act.skill_name || act.milestone || null; let attendanceInfo = ''; - // 1. NAP + // 1. NAP (state 1 = start, state 0 = end/wake) if (actType.includes('nap')) { - if (rawSubtype.includes('start') || (napStart && !napEnd && !durationMinutes)) { + if (stateVal === '1' || rawSubtype.includes('start') || (napStart && !napEnd && !durationMinutes)) { actionSubtype = 'start_nap'; actionSummary = 'Nap Started'; - } else if (rawSubtype.includes('end') || rawSubtype.includes('wake') || napEnd || durationMinutes) { + } else if (stateVal === '0' || rawSubtype.includes('end') || rawSubtype.includes('wake') || napEnd || durationMinutes) { actionSubtype = 'end_nap'; const durText = durationMinutes ? `${durationMinutes} mins` : ''; actionSummary = durText ? `Nap Ended (${durText})` : 'Nap Ended'; @@ -775,20 +792,27 @@ function normalizeActivity(act) { actionSummary = durationMinutes ? `Nap (${durationMinutes} mins)` : 'Nap Log'; } } - // 2. CHECK-IN / ATTENDANCE + // 2. CHECK-IN / ATTENDANCE (state 1 = signed in, state 2 = signed out) else if (actType.includes('checkin') || actType.includes('checkout') || actType.includes('attendance')) { - const isOut = rawSubtype.includes('out') || rawSubtype.includes('away') || rawSubtype.includes('pickup') || actType.includes('checkout'); - const signedBy = act.signed_in_by || act.signed_out_by || act.guardian_name || act.actor_name || teacherName || ''; + const isOut = stateVal === '2' || rawSubtype.includes('out') || rawSubtype.includes('away') || rawSubtype.includes('pickup') || actType.includes('checkout'); if (isOut) { actionSubtype = 'signed_out'; - actionSummary = signedBy ? `Checked Out / Signed Out (by ${signedBy})` : 'Checked Out / Signed Out'; + actionSummary = actor ? `Checked Out / Signed Out (by ${actor})` : 'Checked Out / Signed Out'; } else { actionSubtype = 'signed_in'; - actionSummary = signedBy ? `Checked In / Signed In (by ${signedBy})` : 'Checked In / Signed In'; + actionSummary = actor ? `Checked In / Signed In (by ${actor})` : 'Checked In / Signed In'; } - attendanceInfo = signedBy ? `Signed by: ${signedBy}` : ''; + attendanceInfo = actor ? `Signed by: ${actor}` : ''; } - // 3. FOOD / MEALS + // 3. PHOTOS & VIDEOS + else if (actType.includes('video') || videoUrl) { + actionSubtype = 'video'; + actionSummary = act.note ? `Video: "${act.note}"` : 'Video Clip'; + } else if (actType.includes('photo') || imageUrl) { + actionSubtype = 'photo'; + actionSummary = act.note ? `Photo: "${act.note}"` : 'Photo'; + } + // 4. FOOD / MEALS else if (actType.includes('food') || actType.includes('meal')) { if (!foodType && rawSubtype) foodType = rawSubtype; const mealLabel = foodType ? capitalize(foodType) : 'Meal'; @@ -796,21 +820,13 @@ function normalizeActivity(act) { actionSubtype = foodType || 'meal'; actionSummary = eatLabel ? `${mealLabel} (${eatLabel})` : mealLabel; } - // 4. POTTY / DIAPER + // 5. POTTY / DIAPER else if (actType.includes('potty') || actType.includes('diaper')) { if (!pottyType && rawSubtype) pottyType = rawSubtype; const pLabel = pottyType ? capitalize(pottyType) : 'Check'; actionSubtype = pottyType || 'potty'; actionSummary = `Potty / Diaper (${pLabel})`; } - // 5. PHOTOS & VIDEOS - else if (actType.includes('video') || videoUrl) { - actionSubtype = 'video'; - actionSummary = act.note ? `Video: "${act.note}"` : 'Video Clip'; - } else if (actType.includes('photo') || imageUrl) { - actionSubtype = 'photo'; - actionSummary = act.note ? `Photo: "${act.note}"` : 'Photo'; - } // 6. MILESTONES & OBSERVATIONS else if (actType.includes('milestone') || actType.includes('observation') || actType.includes('learn') || actType.includes('kudos')) { actionSubtype = 'milestone'; @@ -839,9 +855,9 @@ function normalizeActivity(act) { } return { - id: act.id || act.object_id || Math.random().toString(36).substring(2), - created_at: act.created_at || act.event_date || new Date().toISOString(), - activity_type: act.activity_type || actType, + id: act.object_id || act.id || Math.random().toString(36).substring(2), + created_at: act.event_date || act.created_at || new Date().toISOString(), + activity_type: act.action_type || act.activity_type || actType, action_subtype: actionSubtype, action_summary: actionSummary, title: act.title || '',