Fix WebSocket authentication and duplicate filters
- 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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user