docs: Add comprehensive multi-child implementation plan
Added detailed implementation plan covering: - Frontend: Dynamic UI, child selector, bulk activity logging, comparison analytics - Backend: Bulk operations, multi-child queries, family statistics - AI/Voice: Child name detection, context building, clarification flows - Database: Schema enhancements, user preferences, bulk operation tracking - State management, API enhancements, real-time sync updates - Testing strategy: Unit, integration, and E2E tests - Migration plan with feature flags for phased rollout - Performance optimizations: Caching, indexes, code splitting Also includes: - Security fixes for multi-family data leakage in analytics pages - ParentFlow branding updates - Activity tracking navigation improvements - Backend DTO and error handling fixes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ import { useLocalizedDate } from '@/hooks/useLocalizedDate';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useFormatting } from '@/hooks/useFormatting';
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
|
||||
type DateRange = '7days' | '30days' | '3months';
|
||||
|
||||
@@ -100,6 +101,7 @@ const getActivityColor = (type: ActivityType) => {
|
||||
|
||||
export const InsightsDashboard: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const { user } = useAuth();
|
||||
const { format, formatDistanceToNow } = useLocalizedDate();
|
||||
const { t } = useTranslation('insights');
|
||||
const { formatNumber } = useFormatting();
|
||||
@@ -110,30 +112,55 @@ export const InsightsDashboard: React.FC = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const familyId = user?.families?.[0]?.familyId;
|
||||
|
||||
// Fetch children on mount
|
||||
useEffect(() => {
|
||||
const fetchChildren = async () => {
|
||||
if (!familyId) {
|
||||
setError('No family found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const childrenData = await childrenApi.getChildren();
|
||||
console.log('[InsightsDashboard] Loading children for familyId:', familyId);
|
||||
const childrenData = await childrenApi.getChildren(familyId);
|
||||
console.log('[InsightsDashboard] Loaded children:', childrenData);
|
||||
setChildren(childrenData);
|
||||
|
||||
if (childrenData.length > 0) {
|
||||
setSelectedChild(childrenData[0].id);
|
||||
// Validate selected child or pick first one
|
||||
const validChild = childrenData.find(c => c.id === selectedChild);
|
||||
if (!validChild) {
|
||||
setSelectedChild(childrenData[0].id);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('[InsightsDashboard] Failed to load children:', err);
|
||||
setError(err.response?.data?.message || t('errors.loadChildren'));
|
||||
}
|
||||
};
|
||||
fetchChildren();
|
||||
}, []);
|
||||
}, [familyId]);
|
||||
|
||||
// Fetch activities when child or date range changes
|
||||
useEffect(() => {
|
||||
if (!selectedChild) return;
|
||||
if (!selectedChild || children.length === 0) return;
|
||||
|
||||
// Validate that selectedChild belongs to current user's children
|
||||
const childExists = children.some(child => child.id === selectedChild);
|
||||
if (!childExists) {
|
||||
console.warn('[InsightsDashboard] Selected child not found in user\'s children, resetting');
|
||||
setSelectedChild(children[0].id);
|
||||
setError('Selected child not found. Showing data for your first child.');
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchActivities = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
console.log('[InsightsDashboard] Fetching activities for child:', selectedChild);
|
||||
const days = dateRange === '7days' ? 7 : dateRange === '30days' ? 30 : 90;
|
||||
const endDate = endOfDay(new Date());
|
||||
const startDate = startOfDay(subDays(new Date(), days - 1));
|
||||
@@ -144,8 +171,10 @@ export const InsightsDashboard: React.FC = () => {
|
||||
startDate.toISOString(),
|
||||
endDate.toISOString()
|
||||
);
|
||||
console.log('[InsightsDashboard] Fetched activities:', activitiesData.length);
|
||||
setActivities(activitiesData);
|
||||
} catch (err: any) {
|
||||
console.error('[InsightsDashboard] Failed to load activities:', err);
|
||||
setError(err.response?.data?.message || t('errors.loadActivities'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -153,7 +182,7 @@ export const InsightsDashboard: React.FC = () => {
|
||||
};
|
||||
|
||||
fetchActivities();
|
||||
}, [selectedChild, dateRange]);
|
||||
}, [selectedChild, dateRange, children]);
|
||||
|
||||
// Calculate statistics
|
||||
const calculateStats = () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { InsightsDashboard } from './InsightsDashboard';
|
||||
import PredictionsCard from './PredictionsCard';
|
||||
import GrowthSpurtAlert from './GrowthSpurtAlert';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
@@ -45,6 +46,7 @@ function TabPanel(props: TabPanelProps) {
|
||||
}
|
||||
|
||||
export function UnifiedInsightsDashboard() {
|
||||
const { user } = useAuth();
|
||||
const [children, setChildren] = useState<Child[]>([]);
|
||||
const [selectedChildId, setSelectedChildId] = useState<string>('');
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
@@ -54,27 +56,56 @@ export function UnifiedInsightsDashboard() {
|
||||
const [insightsLoading, setInsightsLoading] = useState(false);
|
||||
const [predictionsLoading, setPredictionsLoading] = useState(false);
|
||||
const [days, setDays] = useState<number>(7);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
const familyId = user?.families?.[0]?.familyId;
|
||||
|
||||
useEffect(() => {
|
||||
loadChildren();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedChildId) {
|
||||
loadInsights();
|
||||
loadPredictions();
|
||||
if (familyId) {
|
||||
loadChildren();
|
||||
}
|
||||
}, [selectedChildId, days]);
|
||||
}, [familyId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedChildId && children.length > 0) {
|
||||
// Validate that selectedChildId belongs to current user's children
|
||||
const childExists = children.some(child => child.id === selectedChildId);
|
||||
if (childExists) {
|
||||
loadInsights();
|
||||
loadPredictions();
|
||||
} else {
|
||||
// Invalid child ID - reset to first child
|
||||
console.warn('[UnifiedInsightsDashboard] Selected child not found in user\'s children, resetting');
|
||||
setSelectedChildId(children[0].id);
|
||||
setError('Selected child not found. Showing data for your first child.');
|
||||
}
|
||||
}
|
||||
}, [selectedChildId, days, children]);
|
||||
|
||||
const loadChildren = async () => {
|
||||
if (!familyId) {
|
||||
setLoading(false);
|
||||
setError('No family found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await childrenApi.getChildren();
|
||||
console.log('[UnifiedInsightsDashboard] Loading children for familyId:', familyId);
|
||||
const data = await childrenApi.getChildren(familyId);
|
||||
console.log('[UnifiedInsightsDashboard] Loaded children:', data);
|
||||
setChildren(data);
|
||||
if (data.length > 0 && !selectedChildId) {
|
||||
setSelectedChildId(data[0].id);
|
||||
|
||||
// Only set selectedChildId if we don't have one or if it's not in the new list
|
||||
if (data.length > 0) {
|
||||
const existingChildStillValid = data.some(child => child.id === selectedChildId);
|
||||
if (!selectedChildId || !existingChildStillValid) {
|
||||
setSelectedChildId(data[0].id);
|
||||
}
|
||||
}
|
||||
setError('');
|
||||
} catch (error) {
|
||||
console.error('Failed to load children:', error);
|
||||
console.error('[UnifiedInsightsDashboard] Failed to load children:', error);
|
||||
setError('Failed to load children');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -141,6 +172,13 @@ export function UnifiedInsightsDashboard() {
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Error Alert */}
|
||||
{error && (
|
||||
<Alert severity="warning" sx={{ mb: 3 }} onClose={() => setError('')}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Child Selector */}
|
||||
{children.length > 1 && (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
|
||||
Reference in New Issue
Block a user