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-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,
Card,
CardContent,
Select,
MenuItem,
FormControl,
InputLabel,
CircularProgress,
Alert,
Paper,
@@ -21,8 +17,6 @@ import {
ListItemText,
Avatar,
Chip,
ToggleButtonGroup,
ToggleButton,
Button,
} from '@mui/material';
import {
@@ -38,7 +32,6 @@ import {
import { useRouter } from 'next/navigation';
import { motion } from 'framer-motion';
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 { useLocalizedDate } from '@/hooks/useLocalizedDate';
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 { useAuth } from '@/lib/auth/AuthContext';
type DateRange = '7days' | '30days' | '3months';
interface DayData {
date: string;
@@ -99,74 +91,37 @@ const getActivityColor = (type: ActivityType) => {
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 { user } = useAuth();
const { format, formatDistanceToNow } = useLocalizedDate();
const { t } = useTranslation('insights');
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 [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const familyId = user?.families?.[0]?.familyId;
// Fetch children on mount
// Fetch activities when child or days changes
useEffect(() => {
const fetchChildren = async () => {
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;
}
if (!selectedChildId) 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;
console.log('[InsightsDashboard] Fetching activities for child:', selectedChildId);
const endDate = endOfDay(new Date());
const startDate = startOfDay(subDays(new Date(), days - 1));
const activitiesData = await trackingApi.getActivities(
selectedChild,
selectedChildId,
undefined,
startDate.toISOString(),
endDate.toISOString()
@@ -182,7 +137,7 @@ export const InsightsDashboard: React.FC = () => {
};
fetchActivities();
}, [selectedChild, dateRange, children]);
}, [selectedChildId, days]);
// Calculate statistics
const calculateStats = () => {
@@ -198,7 +153,6 @@ export const InsightsDashboard: React.FC = () => {
}
return acc;
}, 0);
const days = dateRange === '7days' ? 7 : dateRange === '30days' ? 30 : 90;
const avgSleepHours = days > 0 ? (totalSleepMinutes / 60 / days).toFixed(1) : '0.0';
const typeCounts: Record<string, number> = {};
@@ -217,7 +171,6 @@ export const InsightsDashboard: React.FC = () => {
// Prepare chart data
const prepareDailyData = (): DayData[] => {
const days = dateRange === '7days' ? 7 : dateRange === '30days' ? 30 : 90;
const dailyMap = new Map<string, DayData>();
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())
.slice(0, 20);
const noChildren = children.length === 0;
const noChildren = !selectedChildId;
const noActivities = activities.length === 0 && !loading;
return (
@@ -308,41 +261,6 @@ export const InsightsDashboard: React.FC = () => {
{t('subtitle')}
</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 && (
<Alert severity="error" sx={{ mb: 3 }}>

View File

@@ -187,9 +187,9 @@ export function UnifiedInsightsDashboard() {
</Alert>
)}
{/* Child Selector */}
{children.length > 1 && (
<Box sx={{ mb: 3 }}>
{/* Shared Filters */}
<Box sx={{ mb: 3, display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
{children.length > 1 && (
<FormControl sx={{ minWidth: 200 }}>
<InputLabel>Child</InputLabel>
<Select
@@ -204,8 +204,20 @@ export function UnifiedInsightsDashboard() {
))}
</Select>
</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 */}
{insights?.growthSpurt && <GrowthSpurtAlert growthSpurt={insights.growthSpurt} />}
@@ -221,7 +233,7 @@ export function UnifiedInsightsDashboard() {
{/* Tab Panels */}
<TabPanel value={tabValue} index={0}>
{/* Insights tab shows the existing InsightsDashboard */}
<InsightsDashboard />
<InsightsDashboard selectedChildId={selectedChildId} days={days} />
</TabPanel>
<TabPanel value={tabValue} index={1}>

View File

@@ -15,27 +15,10 @@ export interface AuthState {
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 = {
// Use mock user in development if no real user is stored
user: process.env.NODE_ENV === 'development' ? MOCK_USER : null,
token: process.env.NODE_ENV === 'development' ? MOCK_TOKEN : null,
isAuthenticated: process.env.NODE_ENV === 'development',
user: null,
token: null,
isAuthenticated: false,
loading: false,
};