Fix WebSocket authentication and duplicate filters
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

- Remove mock authentication from authSlice to enable real user login
- Fix WebSocket connection errors by using real JWT tokens
- Consolidate duplicate filters on insights page into single shared filter
- Update InsightsDashboard to accept props instead of managing own state
- Add MUI Grid styling for 20% min-width and centering
- Improve UX with unified filter controls for both insights and predictions
This commit is contained in:
2025-10-05 18:13:27 +00:00
parent d0b78181a3
commit 010c30637b
4 changed files with 53 additions and 120 deletions

View File

@@ -165,3 +165,23 @@ body {
outline: 2px solid #FF8B7D; outline: 2px solid #FF8B7D;
outline-offset: 2px; outline-offset: 2px;
} }
/* ============================================
MUI Grid Custom Styling
============================================ */
/* MUI Grid minimum width and centering */
.mui-17fpwt7-MuiGrid-root {
min-width: 20% !important;
}
/* Center MUI Grid containers and items */
.MuiGrid-container {
justify-content: center !important;
}
.MuiGrid-item {
display: flex !important;
justify-content: center !important;
align-items: center !important;
}

View File

@@ -7,10 +7,6 @@ import {
Grid, Grid,
Card, Card,
CardContent, CardContent,
Select,
MenuItem,
FormControl,
InputLabel,
CircularProgress, CircularProgress,
Alert, Alert,
Paper, Paper,
@@ -21,8 +17,6 @@ import {
ListItemText, ListItemText,
Avatar, Avatar,
Chip, Chip,
ToggleButtonGroup,
ToggleButton,
Button, Button,
} from '@mui/material'; } from '@mui/material';
import { import {
@@ -38,7 +32,6 @@ import {
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { trackingApi, Activity, ActivityType } from '@/lib/api/tracking'; import { trackingApi, Activity, ActivityType } from '@/lib/api/tracking';
import { childrenApi, Child } from '@/lib/api/children';
import { subDays, startOfDay, endOfDay, parseISO, differenceInMinutes } from 'date-fns'; import { subDays, startOfDay, endOfDay, parseISO, differenceInMinutes } from 'date-fns';
import { useLocalizedDate } from '@/hooks/useLocalizedDate'; import { useLocalizedDate } from '@/hooks/useLocalizedDate';
import { useTranslation } from '@/hooks/useTranslation'; import { useTranslation } from '@/hooks/useTranslation';
@@ -46,7 +39,6 @@ import { useFormatting } from '@/hooks/useFormatting';
import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { useAuth } from '@/lib/auth/AuthContext'; import { useAuth } from '@/lib/auth/AuthContext';
type DateRange = '7days' | '30days' | '3months';
interface DayData { interface DayData {
date: string; date: string;
@@ -99,74 +91,37 @@ const getActivityColor = (type: ActivityType) => {
return COLORS[type as keyof typeof COLORS] || '#CCCCCC'; return COLORS[type as keyof typeof COLORS] || '#CCCCCC';
}; };
export const InsightsDashboard: React.FC = () => { interface InsightsDashboardProps {
selectedChildId: string;
days: number;
}
export const InsightsDashboard: React.FC<InsightsDashboardProps> = ({ selectedChildId, days }) => {
const router = useRouter(); const router = useRouter();
const { user } = useAuth(); const { user } = useAuth();
const { format, formatDistanceToNow } = useLocalizedDate(); const { format, formatDistanceToNow } = useLocalizedDate();
const { t } = useTranslation('insights'); const { t } = useTranslation('insights');
const { formatNumber } = useFormatting(); const { formatNumber } = useFormatting();
const [children, setChildren] = useState<Child[]>([]);
const [selectedChild, setSelectedChild] = useState<string>('');
const [dateRange, setDateRange] = useState<DateRange>('7days');
const [activities, setActivities] = useState<Activity[]>([]); const [activities, setActivities] = useState<Activity[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const familyId = user?.families?.[0]?.familyId; const familyId = user?.families?.[0]?.familyId;
// Fetch children on mount // Fetch activities when child or days changes
useEffect(() => { useEffect(() => {
const fetchChildren = async () => { if (!selectedChildId) return;
if (!familyId) {
setError('No family found');
return;
}
try {
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) {
// 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 || 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 () => { const fetchActivities = async () => {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
console.log('[InsightsDashboard] Fetching activities for child:', selectedChild); console.log('[InsightsDashboard] Fetching activities for child:', selectedChildId);
const days = dateRange === '7days' ? 7 : dateRange === '30days' ? 30 : 90;
const endDate = endOfDay(new Date()); const endDate = endOfDay(new Date());
const startDate = startOfDay(subDays(new Date(), days - 1)); const startDate = startOfDay(subDays(new Date(), days - 1));
const activitiesData = await trackingApi.getActivities( const activitiesData = await trackingApi.getActivities(
selectedChild, selectedChildId,
undefined, undefined,
startDate.toISOString(), startDate.toISOString(),
endDate.toISOString() endDate.toISOString()
@@ -182,7 +137,7 @@ export const InsightsDashboard: React.FC = () => {
}; };
fetchActivities(); fetchActivities();
}, [selectedChild, dateRange, children]); }, [selectedChildId, days]);
// Calculate statistics // Calculate statistics
const calculateStats = () => { const calculateStats = () => {
@@ -198,7 +153,6 @@ export const InsightsDashboard: React.FC = () => {
} }
return acc; return acc;
}, 0); }, 0);
const days = dateRange === '7days' ? 7 : dateRange === '30days' ? 30 : 90;
const avgSleepHours = days > 0 ? (totalSleepMinutes / 60 / days).toFixed(1) : '0.0'; const avgSleepHours = days > 0 ? (totalSleepMinutes / 60 / days).toFixed(1) : '0.0';
const typeCounts: Record<string, number> = {}; const typeCounts: Record<string, number> = {};
@@ -217,7 +171,6 @@ export const InsightsDashboard: React.FC = () => {
// Prepare chart data // Prepare chart data
const prepareDailyData = (): DayData[] => { const prepareDailyData = (): DayData[] => {
const days = dateRange === '7days' ? 7 : dateRange === '30days' ? 30 : 90;
const dailyMap = new Map<string, DayData>(); const dailyMap = new Map<string, DayData>();
for (let i = days - 1; i >= 0; i--) { for (let i = days - 1; i >= 0; i--) {
@@ -291,7 +244,7 @@ export const InsightsDashboard: React.FC = () => {
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
.slice(0, 20); .slice(0, 20);
const noChildren = children.length === 0; const noChildren = !selectedChildId;
const noActivities = activities.length === 0 && !loading; const noActivities = activities.length === 0 && !loading;
return ( return (
@@ -308,41 +261,6 @@ export const InsightsDashboard: React.FC = () => {
{t('subtitle')} {t('subtitle')}
</Typography> </Typography>
{/* Time period selector */}
<Box sx={{ mb: 4, display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
{children.length > 1 && (
<FormControl sx={{ minWidth: 200 }}>
<InputLabel>{t('filters.child')}</InputLabel>
<Select
value={selectedChild}
onChange={(e) => setSelectedChild(e.target.value)}
label={t('filters.child')}
>
{children.map((child) => (
<MenuItem key={child.id} value={child.id}>
{child.name}
</MenuItem>
))}
</Select>
</FormControl>
)}
<ToggleButtonGroup
value={dateRange}
exclusive
onChange={(_, newValue) => newValue && setDateRange(newValue)}
sx={{
'& .MuiToggleButton-root': {
textTransform: 'none',
fontWeight: 500,
minWidth: { xs: 80, sm: 120 }
}
}}
>
<ToggleButton value="7days">{t('filters.dateRange.7days')}</ToggleButton>
<ToggleButton value="30days">{t('filters.dateRange.30days')}</ToggleButton>
<ToggleButton value="3months">{t('filters.dateRange.3months')}</ToggleButton>
</ToggleButtonGroup>
</Box>
{error && ( {error && (
<Alert severity="error" sx={{ mb: 3 }}> <Alert severity="error" sx={{ mb: 3 }}>

View File

@@ -187,9 +187,9 @@ export function UnifiedInsightsDashboard() {
</Alert> </Alert>
)} )}
{/* Child Selector */} {/* Shared Filters */}
{children.length > 1 && ( <Box sx={{ mb: 3, display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
<Box sx={{ mb: 3 }}> {children.length > 1 && (
<FormControl sx={{ minWidth: 200 }}> <FormControl sx={{ minWidth: 200 }}>
<InputLabel>Child</InputLabel> <InputLabel>Child</InputLabel>
<Select <Select
@@ -204,8 +204,20 @@ export function UnifiedInsightsDashboard() {
))} ))}
</Select> </Select>
</FormControl> </FormControl>
</Box> )}
)} <FormControl sx={{ minWidth: 150 }}>
<InputLabel>Time Period</InputLabel>
<Select
value={days}
label="Time Period"
onChange={(e) => setDays(Number(e.target.value))}
>
<MenuItem value={7}>Last 7 days</MenuItem>
<MenuItem value={30}>Last 30 days</MenuItem>
<MenuItem value={90}>Last 3 months</MenuItem>
</Select>
</FormControl>
</Box>
{/* Growth Spurt Alert */} {/* Growth Spurt Alert */}
{insights?.growthSpurt && <GrowthSpurtAlert growthSpurt={insights.growthSpurt} />} {insights?.growthSpurt && <GrowthSpurtAlert growthSpurt={insights.growthSpurt} />}
@@ -221,7 +233,7 @@ export function UnifiedInsightsDashboard() {
{/* Tab Panels */} {/* Tab Panels */}
<TabPanel value={tabValue} index={0}> <TabPanel value={tabValue} index={0}>
{/* Insights tab shows the existing InsightsDashboard */} {/* Insights tab shows the existing InsightsDashboard */}
<InsightsDashboard /> <InsightsDashboard selectedChildId={selectedChildId} days={days} />
</TabPanel> </TabPanel>
<TabPanel value={tabValue} index={1}> <TabPanel value={tabValue} index={1}>

View File

@@ -15,27 +15,10 @@ export interface AuthState {
loading: boolean; loading: boolean;
} }
// Mock user and token for development (TODO: Remove in production and implement real auth)
const MOCK_USER: User = {
id: 'user_test123',
email: 'test@maternal.app',
name: 'Test User',
familyId: 'fam_test123',
};
// Mock JWT token for development - this is just for testing, real auth needed in production
const MOCK_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c2VyX3Rlc3QxMjMiLCJmYW1pbHlJZCI6ImZhbV90ZXN0MTIzIiwiaWF0IjoxNjAwMDAwMDAwfQ.mock_signature_for_development';
// Set mock token in localStorage for development
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
localStorage.setItem('accessToken', MOCK_TOKEN);
}
const initialState: AuthState = { const initialState: AuthState = {
// Use mock user in development if no real user is stored user: null,
user: process.env.NODE_ENV === 'development' ? MOCK_USER : null, token: null,
token: process.env.NODE_ENV === 'development' ? MOCK_TOKEN : null, isAuthenticated: false,
isAuthenticated: process.env.NODE_ENV === 'development',
loading: false, loading: false,
}; };