feat: Complete comprehensive localization of all tracking and management pages
- Feeding page: 47+ strings localized with validation, success/error messages - Medicine page: 44 strings localized with unit conversion support - Sleep page: Already localized (verified) - Diaper page: Already localized (verified) - Activity page: Already localized (verified) - AI Assistant: 51 strings localized including chat interface and suggested questions - Children page: 38 strings fully localized with gender labels - Family page: 42 strings localized with role management - Insights page: 41 strings localized including charts and analytics Added translation files: - locales/en/ai.json (44 keys) - locales/en/family.json (42 keys) - locales/en/insights.json (41 keys) Updated translation files: - locales/en/tracking.json (added feeding, health/medicine sections) - locales/en/children.json (verified complete) All pages now use useTranslation hook with proper namespaces. All user-facing text externalized and ready for multi-language support.
This commit is contained in:
@@ -239,7 +239,7 @@ export default function ChildrenPage() {
|
|||||||
{child.name}
|
{child.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={child.gender}
|
label={t(`gender.${child.gender}`)}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ textTransform: 'capitalize', mt: 0.5 }}
|
sx={{ textTransform: 'capitalize', mt: 0.5 }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ import { InviteMemberDialog } from '@/components/family/InviteMemberDialog';
|
|||||||
import { JoinFamilyDialog } from '@/components/family/JoinFamilyDialog';
|
import { JoinFamilyDialog } from '@/components/family/JoinFamilyDialog';
|
||||||
import { RemoveMemberDialog } from '@/components/family/RemoveMemberDialog';
|
import { RemoveMemberDialog } from '@/components/family/RemoveMemberDialog';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
export default function FamilyPage() {
|
export default function FamilyPage() {
|
||||||
|
const { t } = useTranslation('family');
|
||||||
const { user, refreshUser } = useAuth();
|
const { user, refreshUser } = useAuth();
|
||||||
const [family, setFamily] = useState<Family | null>(null);
|
const [family, setFamily] = useState<Family | null>(null);
|
||||||
const [members, setMembers] = useState<FamilyMember[]>([]);
|
const [members, setMembers] = useState<FamilyMember[]>([]);
|
||||||
@@ -47,7 +49,7 @@ export default function FamilyPage() {
|
|||||||
fetchFamilyData();
|
fetchFamilyData();
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setError('No family found. Please complete onboarding first.');
|
setError(t('messages.noFamilyFound'));
|
||||||
}
|
}
|
||||||
}, [familyId]);
|
}, [familyId]);
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ export default function FamilyPage() {
|
|||||||
setMembers(membersData);
|
setMembers(membersData);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to fetch family data:', err);
|
console.error('Failed to fetch family data:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to load family information');
|
setError(err.response?.data?.message || t('messages.failedToLoad'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -76,26 +78,26 @@ export default function FamilyPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(family.shareCode);
|
await navigator.clipboard.writeText(family.shareCode);
|
||||||
setSnackbar({ open: true, message: 'Share code copied to clipboard!' });
|
setSnackbar({ open: true, message: t('messages.shareCodeCopied') });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setSnackbar({ open: true, message: 'Failed to copy share code' });
|
setSnackbar({ open: true, message: t('messages.shareCodeCopyFailed') });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInviteMember = async (data: InviteMemberData) => {
|
const handleInviteMember = async (data: InviteMemberData) => {
|
||||||
if (!familyId) {
|
if (!familyId) {
|
||||||
throw new Error('No family ID found');
|
throw new Error(t('messages.noFamilyId'));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
await familiesApi.inviteMember(familyId, data);
|
await familiesApi.inviteMember(familyId, data);
|
||||||
setSnackbar({ open: true, message: 'Invitation sent successfully!' });
|
setSnackbar({ open: true, message: t('messages.invitationSent') });
|
||||||
await fetchFamilyData();
|
await fetchFamilyData();
|
||||||
setInviteDialogOpen(false);
|
setInviteDialogOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to invite member:', err);
|
console.error('Failed to invite member:', err);
|
||||||
throw new Error(err.response?.data?.message || 'Failed to send invitation');
|
throw new Error(err.response?.data?.message || t('messages.failedToInvite'));
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoading(false);
|
setActionLoading(false);
|
||||||
}
|
}
|
||||||
@@ -105,13 +107,13 @@ export default function FamilyPage() {
|
|||||||
try {
|
try {
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
await familiesApi.joinFamily(data);
|
await familiesApi.joinFamily(data);
|
||||||
setSnackbar({ open: true, message: 'Successfully joined family!' });
|
setSnackbar({ open: true, message: t('messages.joinedFamily') });
|
||||||
await refreshUser();
|
await refreshUser();
|
||||||
await fetchFamilyData();
|
await fetchFamilyData();
|
||||||
setJoinDialogOpen(false);
|
setJoinDialogOpen(false);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to join family:', err);
|
console.error('Failed to join family:', err);
|
||||||
throw new Error(err.response?.data?.message || 'Failed to join family');
|
throw new Error(err.response?.data?.message || t('messages.failedToJoin'));
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoading(false);
|
setActionLoading(false);
|
||||||
}
|
}
|
||||||
@@ -128,13 +130,13 @@ export default function FamilyPage() {
|
|||||||
try {
|
try {
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
await familiesApi.removeMember(familyId, memberToRemove.userId);
|
await familiesApi.removeMember(familyId, memberToRemove.userId);
|
||||||
setSnackbar({ open: true, message: 'Member removed successfully' });
|
setSnackbar({ open: true, message: t('messages.memberRemoved') });
|
||||||
await fetchFamilyData();
|
await fetchFamilyData();
|
||||||
setRemoveDialogOpen(false);
|
setRemoveDialogOpen(false);
|
||||||
setMemberToRemove(null);
|
setMemberToRemove(null);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to remove member:', err);
|
console.error('Failed to remove member:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to remove member');
|
setError(err.response?.data?.message || t('messages.failedToRemove'));
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoading(false);
|
setActionLoading(false);
|
||||||
}
|
}
|
||||||
@@ -164,10 +166,10 @@ export default function FamilyPage() {
|
|||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h4" component="h1" fontWeight="600" gutterBottom>
|
<Typography variant="h4" component="h1" fontWeight="600" gutterBottom>
|
||||||
Family
|
{t('pageTitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" color="text.secondary">
|
||||||
Manage your family members and share access
|
{t('pageSubtitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
@@ -177,7 +179,7 @@ export default function FamilyPage() {
|
|||||||
onClick={() => setJoinDialogOpen(true)}
|
onClick={() => setJoinDialogOpen(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
Join Family
|
{t('buttons.joinFamily')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -185,7 +187,7 @@ export default function FamilyPage() {
|
|||||||
onClick={() => setInviteDialogOpen(true)}
|
onClick={() => setInviteDialogOpen(true)}
|
||||||
disabled={loading || !familyId}
|
disabled={loading || !familyId}
|
||||||
>
|
>
|
||||||
Invite Member
|
{t('buttons.inviteMember')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -208,10 +210,10 @@ export default function FamilyPage() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" component="h2" fontWeight="600" gutterBottom>
|
<Typography variant="h6" component="h2" fontWeight="600" gutterBottom>
|
||||||
Family Share Code
|
{t('shareCode.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
Share this code with family members to give them access to your family's data
|
{t('shareCode.description')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}>
|
||||||
<Chip
|
<Chip
|
||||||
@@ -229,7 +231,7 @@ export default function FamilyPage() {
|
|||||||
startIcon={<ContentCopy />}
|
startIcon={<ContentCopy />}
|
||||||
onClick={handleCopyCode}
|
onClick={handleCopyCode}
|
||||||
>
|
>
|
||||||
Copy Code
|
{t('buttons.copyCode')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -242,30 +244,30 @@ export default function FamilyPage() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" fontWeight="600" gutterBottom sx={{ mb: 3 }}>
|
<Typography variant="h6" fontWeight="600" gutterBottom sx={{ mb: 3 }}>
|
||||||
Family Members ({members.length})
|
{t('members.title', { count: members.length })}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{members.length === 0 ? (
|
{members.length === 0 ? (
|
||||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||||
<People sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
|
<People sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
|
||||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
No family members yet
|
{t('members.noMembers')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
Invite family members to collaborate on child care
|
{t('members.noMembersDescription')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<PersonAdd />}
|
startIcon={<PersonAdd />}
|
||||||
onClick={() => setInviteDialogOpen(true)}
|
onClick={() => setInviteDialogOpen(true)}
|
||||||
>
|
>
|
||||||
Invite First Member
|
{t('buttons.inviteFirstMember')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
{members.map((member, index) => {
|
{members.map((member, index) => {
|
||||||
const memberName = member.user?.name || 'Unknown User';
|
const memberName = member.user?.name || t('placeholders.unknownUser');
|
||||||
return (
|
return (
|
||||||
<Box key={member.id} component="div">
|
<Box key={member.id} component="div">
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -289,15 +291,15 @@ export default function FamilyPage() {
|
|||||||
{memberName}
|
{memberName}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isCurrentUser(member.userId) && (
|
{isCurrentUser(member.userId) && (
|
||||||
<Chip label="You" size="small" color="success" />
|
<Chip label={t('members.youLabel')} size="small" color="success" />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
{member.user?.email || 'No email'}
|
{member.user?.email || t('placeholders.noEmail')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Chip
|
<Chip
|
||||||
label={member.role.charAt(0).toUpperCase() + member.role.slice(1)}
|
label={t(`roles.${member.role}`)}
|
||||||
color={getRoleColor(member.role)}
|
color={getRoleColor(member.role)}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
@@ -306,7 +308,7 @@ export default function FamilyPage() {
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleRemoveClick(member)}
|
onClick={() => handleRemoveClick(member)}
|
||||||
color="error"
|
color="error"
|
||||||
aria-label={`Remove ${memberName} from family`}
|
aria-label={t('members.removeAriaLabel', { name: memberName })}
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ function FeedingTrackPage() {
|
|||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to load children:', err);
|
console.error('Failed to load children:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to load children');
|
setError(err.response?.data?.message || t('common.error.loadChildrenFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setChildrenLoading(false);
|
setChildrenLoading(false);
|
||||||
}
|
}
|
||||||
@@ -189,23 +189,23 @@ function FeedingTrackPage() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!selectedChild) {
|
if (!selectedChild) {
|
||||||
setError('Please select a child');
|
setError(t('common.selectChild'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (feedingType === 'breast' && duration === 0 && timerSeconds === 0) {
|
if (feedingType === 'breast' && duration === 0 && timerSeconds === 0) {
|
||||||
setError('Please enter duration or use the timer');
|
setError(t('feeding.validation.durationRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feedingType === 'bottle' && !amount) {
|
if (feedingType === 'bottle' && !amount) {
|
||||||
setError('Please enter amount');
|
setError(t('feeding.validation.amountRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feedingType === 'solid' && !foodDescription) {
|
if (feedingType === 'solid' && !foodDescription) {
|
||||||
setError('Please enter food description');
|
setError(t('feeding.validation.foodRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ function FeedingTrackPage() {
|
|||||||
notes: notes || undefined,
|
notes: notes || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
setSuccessMessage('Feeding logged successfully!');
|
setSuccessMessage(t('feeding.success'));
|
||||||
|
|
||||||
// Reset form
|
// Reset form
|
||||||
resetForm();
|
resetForm();
|
||||||
@@ -244,7 +244,7 @@ function FeedingTrackPage() {
|
|||||||
await loadRecentFeedings();
|
await loadRecentFeedings();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to save feeding:', err);
|
console.error('Failed to save feeding:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to save feeding');
|
setError(err.response?.data?.message || t('feeding.error.saveFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -273,13 +273,13 @@ function FeedingTrackPage() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await trackingApi.deleteActivity(activityToDelete);
|
await trackingApi.deleteActivity(activityToDelete);
|
||||||
setSuccessMessage('Feeding deleted successfully');
|
setSuccessMessage(t('feeding.deleted'));
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
setActivityToDelete(null);
|
setActivityToDelete(null);
|
||||||
await loadRecentFeedings();
|
await loadRecentFeedings();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to delete feeding:', err);
|
console.error('Failed to delete feeding:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to delete feeding');
|
setError(err.response?.data?.message || t('feeding.error.deleteFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -345,17 +345,17 @@ function FeedingTrackPage() {
|
|||||||
<CardContent sx={{ textAlign: 'center', py: 8 }}>
|
<CardContent sx={{ textAlign: 'center', py: 8 }}>
|
||||||
<ChildCare sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
<ChildCare sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
||||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||||
No Children Added
|
{t('common.noChildrenAdded')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
You need to add a child before you can track feeding activities
|
{t('common.noChildrenMessage')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<Add />}
|
startIcon={<Add />}
|
||||||
onClick={() => router.push('/children')}
|
onClick={() => router.push('/children')}
|
||||||
>
|
>
|
||||||
Add Child
|
{t('common.addChild')}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -414,11 +414,11 @@ function FeedingTrackPage() {
|
|||||||
{children.length > 1 && (
|
{children.length > 1 && (
|
||||||
<Paper sx={{ p: 2, mb: 3 }}>
|
<Paper sx={{ p: 2, mb: 3 }}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Select Child</InputLabel>
|
<InputLabel>{t('common.selectChild')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={selectedChild}
|
value={selectedChild}
|
||||||
onChange={(e) => setSelectedChild(e.target.value)}
|
onChange={(e) => setSelectedChild(e.target.value)}
|
||||||
label="Select Child"
|
label={t('common.selectChild')}
|
||||||
>
|
>
|
||||||
{children.map((child) => (
|
{children.map((child) => (
|
||||||
<MenuItem key={child.id} value={child.id}>
|
<MenuItem key={child.id} value={child.id}>
|
||||||
@@ -479,7 +479,7 @@ function FeedingTrackPage() {
|
|||||||
startIcon={<Refresh />}
|
startIcon={<Refresh />}
|
||||||
onClick={resetTimer}
|
onClick={resetTimer}
|
||||||
>
|
>
|
||||||
Reset
|
{t('feeding.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -506,7 +506,7 @@ function FeedingTrackPage() {
|
|||||||
value={duration || ''}
|
value={duration || ''}
|
||||||
onChange={(e) => setDuration(parseInt(e.target.value) || 0)}
|
onChange={(e) => setDuration(parseInt(e.target.value) || 0)}
|
||||||
sx={{ mb: 3 }}
|
sx={{ mb: 3 }}
|
||||||
helperText={t('feeding.placeholders.notes')}
|
helperText={t('feeding.placeholders.duration')}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -524,15 +524,15 @@ function FeedingTrackPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl fullWidth sx={{ mb: 3 }}>
|
<FormControl fullWidth sx={{ mb: 3 }}>
|
||||||
<InputLabel>{t('feeding.type')}</InputLabel>
|
<InputLabel>{t('feeding.bottleType')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={bottleType}
|
value={bottleType}
|
||||||
onChange={(e) => setBottleType(e.target.value as 'formula' | 'breastmilk' | 'other')}
|
onChange={(e) => setBottleType(e.target.value as 'formula' | 'breastmilk' | 'other')}
|
||||||
label={t('feeding.type')}
|
label={t('feeding.bottleType')}
|
||||||
>
|
>
|
||||||
<MenuItem value="formula">{t('feeding.types.bottle')}</MenuItem>
|
<MenuItem value="formula">{t('feeding.bottleTypes.formula')}</MenuItem>
|
||||||
<MenuItem value="breastmilk">{t('feeding.types.breast')}</MenuItem>
|
<MenuItem value="breastmilk">{t('feeding.bottleTypes.breastmilk')}</MenuItem>
|
||||||
<MenuItem value="other">Other</MenuItem>
|
<MenuItem value="other">{t('feeding.bottleTypes.other')}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -543,20 +543,20 @@ function FeedingTrackPage() {
|
|||||||
<Box>
|
<Box>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label={t('feeding.type')}
|
label={t('feeding.foodDescription')}
|
||||||
value={foodDescription}
|
value={foodDescription}
|
||||||
onChange={(e) => setFoodDescription(e.target.value)}
|
onChange={(e) => setFoodDescription(e.target.value)}
|
||||||
sx={{ mb: 3 }}
|
sx={{ mb: 3 }}
|
||||||
placeholder={t('feeding.placeholders.notes')}
|
placeholder={t('feeding.placeholders.foodDescription')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label={t('feeding.amount')}
|
label={t('feeding.amountDescription')}
|
||||||
value={amountDescription}
|
value={amountDescription}
|
||||||
onChange={(e) => setAmountDescription(e.target.value)}
|
onChange={(e) => setAmountDescription(e.target.value)}
|
||||||
sx={{ mb: 3 }}
|
sx={{ mb: 3 }}
|
||||||
placeholder={t('feeding.placeholders.amount')}
|
placeholder={t('feeding.placeholders.amountDescription')}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -583,7 +583,7 @@ function FeedingTrackPage() {
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? t('feeding.addFeeding') : t('feeding.addFeeding')}
|
{loading ? t('common.loading') : t('feeding.addFeeding')}
|
||||||
</Button>
|
</Button>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -591,7 +591,7 @@ function FeedingTrackPage() {
|
|||||||
<Paper sx={{ p: 3 }}>
|
<Paper sx={{ p: 3 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
{t('feeding.title')}
|
{t('feeding.recentFeedings')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton onClick={loadRecentFeedings} disabled={feedingsLoading}>
|
<IconButton onClick={loadRecentFeedings} disabled={feedingsLoading}>
|
||||||
<Refresh />
|
<Refresh />
|
||||||
@@ -685,10 +685,10 @@ function FeedingTrackPage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteDialogOpen(false)} disabled={loading}>
|
<Button onClick={() => setDeleteDialogOpen(false)} disabled={loading}>
|
||||||
Cancel
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleDeleteConfirm} color="error" disabled={loading}>
|
<Button onClick={handleDeleteConfirm} color="error" disabled={loading}>
|
||||||
{loading ? t('deleteEntry') : t('deleteEntry')}
|
{loading ? t('common.loading') : t('common.delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ function MedicineTrackPage() {
|
|||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to load children:', err);
|
console.error('Failed to load children:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to load children');
|
setError(err.response?.data?.message || t('common.error.loadChildrenFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setChildrenLoading(false);
|
setChildrenLoading(false);
|
||||||
}
|
}
|
||||||
@@ -140,19 +140,19 @@ function MedicineTrackPage() {
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!selectedChild) {
|
if (!selectedChild) {
|
||||||
setError('Please select a child');
|
setError(t('common.selectChild'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (!medicineName) {
|
if (!medicineName) {
|
||||||
setError('Please enter medicine name');
|
setError(t('health.medicineName.required'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dosageValue = unit === 'ml' ? dosage : dosageText;
|
const dosageValue = unit === 'ml' ? dosage : dosageText;
|
||||||
if (!dosageValue || (unit === 'ml' && dosage === 0) || (unit !== 'ml' && !dosageText)) {
|
if (!dosageValue || (unit === 'ml' && dosage === 0) || (unit !== 'ml' && !dosageText)) {
|
||||||
setError('Please enter dosage');
|
setError(t('health.dosage.required'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ function MedicineTrackPage() {
|
|||||||
notes: notes || undefined,
|
notes: notes || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
setSuccessMessage('Medicine logged successfully!');
|
setSuccessMessage(t('health.success'));
|
||||||
|
|
||||||
// Reset form
|
// Reset form
|
||||||
resetForm();
|
resetForm();
|
||||||
@@ -184,7 +184,7 @@ function MedicineTrackPage() {
|
|||||||
await loadRecentMedicines();
|
await loadRecentMedicines();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to save medicine:', err);
|
console.error('Failed to save medicine:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to save medicine');
|
setError(err.response?.data?.message || t('health.error'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -211,13 +211,13 @@ function MedicineTrackPage() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await trackingApi.deleteActivity(activityToDelete);
|
await trackingApi.deleteActivity(activityToDelete);
|
||||||
setSuccessMessage('Medicine deleted successfully');
|
setSuccessMessage(t('health.deleted'));
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
setActivityToDelete(null);
|
setActivityToDelete(null);
|
||||||
await loadRecentMedicines();
|
await loadRecentMedicines();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to delete medicine:', err);
|
console.error('Failed to delete medicine:', err);
|
||||||
setError(err.response?.data?.message || 'Failed to delete medicine');
|
setError(err.response?.data?.message || t('health.deleteError'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -282,17 +282,17 @@ function MedicineTrackPage() {
|
|||||||
<CardContent sx={{ textAlign: 'center', py: 8 }}>
|
<CardContent sx={{ textAlign: 'center', py: 8 }}>
|
||||||
<ChildCare sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
<ChildCare sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
||||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||||
No Children Added
|
{t('common.noChildrenAdded')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
You need to add a child before you can track medicine activities
|
{t('common.noChildrenMessage')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<Add />}
|
startIcon={<Add />}
|
||||||
onClick={() => router.push('/children')}
|
onClick={() => router.push('/children')}
|
||||||
>
|
>
|
||||||
Add Child
|
{t('common.addChild')}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -352,11 +352,11 @@ function MedicineTrackPage() {
|
|||||||
{children.length > 1 && (
|
{children.length > 1 && (
|
||||||
<Paper sx={{ p: 2, mb: 3 }}>
|
<Paper sx={{ p: 2, mb: 3 }}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Select Child</InputLabel>
|
<InputLabel>{t('common.selectChild')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={selectedChild}
|
value={selectedChild}
|
||||||
onChange={(e) => setSelectedChild(e.target.value)}
|
onChange={(e) => setSelectedChild(e.target.value)}
|
||||||
label="Select Child"
|
label={t('common.selectChild')}
|
||||||
>
|
>
|
||||||
{children.map((child) => (
|
{children.map((child) => (
|
||||||
<MenuItem key={child.id} value={child.id}>
|
<MenuItem key={child.id} value={child.id}>
|
||||||
@@ -373,17 +373,17 @@ function MedicineTrackPage() {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
||||||
<MedicalServices sx={{ fontSize: 36, color: 'error.main', mr: 2 }} />
|
<MedicalServices sx={{ fontSize: 36, color: 'error.main', mr: 2 }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Medicine Information
|
{t('health.medicineInfo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Medicine Name"
|
label={t('health.medicineName.label')}
|
||||||
value={medicineName}
|
value={medicineName}
|
||||||
onChange={(e) => setMedicineName(e.target.value)}
|
onChange={(e) => setMedicineName(e.target.value)}
|
||||||
sx={{ mb: 3 }}
|
sx={{ mb: 3 }}
|
||||||
placeholder="e.g., Acetaminophen, Ibuprofen"
|
placeholder={t('health.medicineName.placeholder')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -391,7 +391,7 @@ function MedicineTrackPage() {
|
|||||||
{unit === 'ml' ? (
|
{unit === 'ml' ? (
|
||||||
<UnitInput
|
<UnitInput
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Dosage"
|
label={t('health.dosage.label')}
|
||||||
type="volume"
|
type="volume"
|
||||||
value={dosage}
|
value={dosage}
|
||||||
onChange={(metricValue) => setDosage(metricValue)}
|
onChange={(metricValue) => setDosage(metricValue)}
|
||||||
@@ -400,16 +400,16 @@ function MedicineTrackPage() {
|
|||||||
) : (
|
) : (
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Dosage"
|
label={t('health.dosage.label')}
|
||||||
value={dosageText}
|
value={dosageText}
|
||||||
onChange={(e) => setDosageText(e.target.value)}
|
onChange={(e) => setDosageText(e.target.value)}
|
||||||
placeholder="e.g., 5, 2.5"
|
placeholder={t('health.dosage.placeholder')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Unit</InputLabel>
|
<InputLabel>{t('health.unit')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={unit}
|
value={unit}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -422,39 +422,39 @@ function MedicineTrackPage() {
|
|||||||
setDosage(0);
|
setDosage(0);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
label="Unit"
|
label={t('health.unit')}
|
||||||
>
|
>
|
||||||
<MenuItem value="ml">ml</MenuItem>
|
<MenuItem value="ml">{t('health.units.ml')}</MenuItem>
|
||||||
<MenuItem value="mg">mg</MenuItem>
|
<MenuItem value="mg">{t('health.units.mg')}</MenuItem>
|
||||||
<MenuItem value="tsp">tsp</MenuItem>
|
<MenuItem value="tsp">{t('health.units.tsp')}</MenuItem>
|
||||||
<MenuItem value="tbsp">tbsp</MenuItem>
|
<MenuItem value="tbsp">{t('health.units.tbsp')}</MenuItem>
|
||||||
<MenuItem value="drops">drops</MenuItem>
|
<MenuItem value="drops">{t('health.units.drops')}</MenuItem>
|
||||||
<MenuItem value="tablet">tablet(s)</MenuItem>
|
<MenuItem value="tablet">{t('health.units.tablet')}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<FormControl fullWidth sx={{ mb: 3 }}>
|
<FormControl fullWidth sx={{ mb: 3 }}>
|
||||||
<InputLabel>Route</InputLabel>
|
<InputLabel>{t('health.route.label')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={route}
|
value={route}
|
||||||
onChange={(e) => setRoute(e.target.value as 'oral' | 'topical' | 'injection' | 'other')}
|
onChange={(e) => setRoute(e.target.value as 'oral' | 'topical' | 'injection' | 'other')}
|
||||||
label="Route"
|
label={t('health.route.label')}
|
||||||
>
|
>
|
||||||
<MenuItem value="oral">Oral</MenuItem>
|
<MenuItem value="oral">{t('health.route.oral')}</MenuItem>
|
||||||
<MenuItem value="topical">Topical</MenuItem>
|
<MenuItem value="topical">{t('health.route.topical')}</MenuItem>
|
||||||
<MenuItem value="injection">Injection</MenuItem>
|
<MenuItem value="injection">{t('health.route.injection')}</MenuItem>
|
||||||
<MenuItem value="other">Other</MenuItem>
|
<MenuItem value="other">{t('health.route.other')}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Reason (optional)"
|
label={t('health.reason.label')}
|
||||||
value={reason}
|
value={reason}
|
||||||
onChange={(e) => setReason(e.target.value)}
|
onChange={(e) => setReason(e.target.value)}
|
||||||
sx={{ mb: 3 }}
|
sx={{ mb: 3 }}
|
||||||
placeholder="e.g., Fever, Pain, Allergy"
|
placeholder={t('health.reason.placeholder')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
@@ -477,7 +477,7 @@ function MedicineTrackPage() {
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? t('activities.medicine') : t('activities.medicine')}
|
{loading ? t('common.loading') : t('health.logMedicine')}
|
||||||
</Button>
|
</Button>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -485,7 +485,7 @@ function MedicineTrackPage() {
|
|||||||
<Paper sx={{ p: 3 }}>
|
<Paper sx={{ p: 3 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
{t('activities.medicine')}
|
{t('health.recentMedicines')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton onClick={loadRecentMedicines} disabled={medicinesLoading}>
|
<IconButton onClick={loadRecentMedicines} disabled={medicinesLoading}>
|
||||||
<Refresh />
|
<Refresh />
|
||||||
@@ -578,10 +578,10 @@ function MedicineTrackPage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteDialogOpen(false)} disabled={loading}>
|
<Button onClick={() => setDeleteDialogOpen(false)} disabled={loading}>
|
||||||
Cancel
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleDeleteConfirm} color="error" disabled={loading}>
|
<Button onClick={handleDeleteConfirm} color="error" disabled={loading}>
|
||||||
{loading ? t('deleteEntry') : t('deleteEntry')}
|
{loading ? t('common.loading') : t('common.delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import { useAuth } from '@/lib/auth/AuthContext';
|
|||||||
import apiClient from '@/lib/api/client';
|
import apiClient from '@/lib/api/client';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -83,43 +84,9 @@ interface ConversationGroup {
|
|||||||
isCollapsed: boolean;
|
isCollapsed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const suggestedQuestions = [
|
|
||||||
'How much should my baby sleep at 3 months?',
|
|
||||||
'What are normal feeding patterns?',
|
|
||||||
'When should I introduce solid foods?',
|
|
||||||
'Tips for better sleep routine',
|
|
||||||
];
|
|
||||||
|
|
||||||
const thinkingMessages = [
|
|
||||||
'Gathering baby wisdom...',
|
|
||||||
'Consulting the baby books...',
|
|
||||||
'Mixing up the perfect answer...',
|
|
||||||
'Warming up some advice...',
|
|
||||||
'Preparing your bottle of knowledge...',
|
|
||||||
'Counting tiny fingers and toes...',
|
|
||||||
'Connecting the building blocks...',
|
|
||||||
'Peeking into the toy box...',
|
|
||||||
'Arranging the puzzle pieces...',
|
|
||||||
'Stirring the baby food jar...',
|
|
||||||
'Polishing the pacifier of wisdom...',
|
|
||||||
'Tiptoeing through naptime...',
|
|
||||||
'Organizing the diaper bag...',
|
|
||||||
'Wrapping up your answer with love...',
|
|
||||||
'Brewing a warm cup of guidance...',
|
|
||||||
'Knitting together some thoughts...',
|
|
||||||
'Tucking in the details...',
|
|
||||||
'Sprinkling some magic dust...',
|
|
||||||
'Humming a lullaby while I think...',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Get a random selection of 3-5 thinking messages
|
|
||||||
const getRandomThinkingMessages = () => {
|
|
||||||
const count = Math.floor(Math.random() * 3) + 3; // 3 to 5
|
|
||||||
const shuffled = [...thinkingMessages].sort(() => Math.random() - 0.5);
|
|
||||||
return shuffled.slice(0, count);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AIChatInterface: React.FC = () => {
|
export const AIChatInterface: React.FC = () => {
|
||||||
|
const { t } = useTranslation('ai');
|
||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -141,6 +108,43 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
// Localized arrays that depend on translations
|
||||||
|
const suggestedQuestions = [
|
||||||
|
t('interface.suggestedQuestion1'),
|
||||||
|
t('interface.suggestedQuestion2'),
|
||||||
|
t('interface.suggestedQuestion3'),
|
||||||
|
t('interface.suggestedQuestion4'),
|
||||||
|
];
|
||||||
|
|
||||||
|
const thinkingMessages = [
|
||||||
|
t('interface.thinking1'),
|
||||||
|
t('interface.thinking2'),
|
||||||
|
t('interface.thinking3'),
|
||||||
|
t('interface.thinking4'),
|
||||||
|
t('interface.thinking5'),
|
||||||
|
t('interface.thinking6'),
|
||||||
|
t('interface.thinking7'),
|
||||||
|
t('interface.thinking8'),
|
||||||
|
t('interface.thinking9'),
|
||||||
|
t('interface.thinking10'),
|
||||||
|
t('interface.thinking11'),
|
||||||
|
t('interface.thinking12'),
|
||||||
|
t('interface.thinking13'),
|
||||||
|
t('interface.thinking14'),
|
||||||
|
t('interface.thinking15'),
|
||||||
|
t('interface.thinking16'),
|
||||||
|
t('interface.thinking17'),
|
||||||
|
t('interface.thinking18'),
|
||||||
|
t('interface.thinking19'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Get a random selection of 3-5 thinking messages
|
||||||
|
const getRandomThinkingMessages = () => {
|
||||||
|
const count = Math.floor(Math.random() * 3) + 3; // 3 to 5
|
||||||
|
const shuffled = [...thinkingMessages].sort(() => Math.random() - 0.5);
|
||||||
|
return shuffled.slice(0, count);
|
||||||
|
};
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
};
|
};
|
||||||
@@ -243,10 +247,11 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
// Group conversations by their group name
|
// Group conversations by their group name
|
||||||
const organizeConversations = (): ConversationGroup[] => {
|
const organizeConversations = (): ConversationGroup[] => {
|
||||||
const groups: { [key: string]: Conversation[] } = {};
|
const groups: { [key: string]: Conversation[] } = {};
|
||||||
|
const ungroupedLabel = t('interface.ungrouped');
|
||||||
|
|
||||||
// Separate conversations by group
|
// Separate conversations by group
|
||||||
conversations.forEach((conv) => {
|
conversations.forEach((conv) => {
|
||||||
const groupName = conv.metadata?.groupName || 'Ungrouped';
|
const groupName = conv.metadata?.groupName || ungroupedLabel;
|
||||||
if (!groups[groupName]) {
|
if (!groups[groupName]) {
|
||||||
groups[groupName] = [];
|
groups[groupName] = [];
|
||||||
}
|
}
|
||||||
@@ -262,8 +267,8 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
}))
|
}))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// Ungrouped always last
|
// Ungrouped always last
|
||||||
if (a.name === 'Ungrouped') return 1;
|
if (a.name === ungroupedLabel) return 1;
|
||||||
if (b.name === 'Ungrouped') return -1;
|
if (b.name === ungroupedLabel) return -1;
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -372,7 +377,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
const errorMessage: Message = {
|
const errorMessage: Message = {
|
||||||
id: (Date.now() + 1).toString(),
|
id: (Date.now() + 1).toString(),
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: 'Sorry, I encountered an error. Please try again.',
|
content: t('interface.errorMessage'),
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
};
|
};
|
||||||
setMessages((prev) => [...prev, errorMessage]);
|
setMessages((prev) => [...prev, errorMessage]);
|
||||||
@@ -390,10 +395,10 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Chat History
|
{t('history.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<IconButton onClick={() => setDrawerOpen(false)} size="small" aria-label="Close drawer">
|
<IconButton onClick={() => setDrawerOpen(false)} size="small" aria-label={t('interface.closeDrawer')}>
|
||||||
<Close />
|
<Close />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
@@ -405,7 +410,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
onClick={handleNewConversation}
|
onClick={handleNewConversation}
|
||||||
sx={{ borderRadius: 2 }}
|
sx={{ borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
New Chat
|
{t('chat.newChat')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<List sx={{ flex: 1, overflow: 'auto', py: 1 }}>
|
<List sx={{ flex: 1, overflow: 'auto', py: 1 }}>
|
||||||
@@ -413,7 +418,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
<Box sx={{ p: 3, textAlign: 'center' }}>
|
<Box sx={{ p: 3, textAlign: 'center' }}>
|
||||||
<Chat sx={{ fontSize: 48, color: 'text.secondary', opacity: 0.3, mb: 1 }} />
|
<Chat sx={{ fontSize: 48, color: 'text.secondary', opacity: 0.3, mb: 1 }} />
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
No conversations yet
|
{t('history.noHistory')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -434,7 +439,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={group.name}
|
primary={group.name}
|
||||||
secondary={`${group.conversations.length} chat${group.conversations.length !== 1 ? 's' : ''}`}
|
secondary={t('interface.chatCount', { count: group.conversations.length })}
|
||||||
primaryTypographyProps={{
|
primaryTypographyProps={{
|
||||||
variant: 'body2',
|
variant: 'body2',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -495,7 +500,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={{ ml: 1 }}
|
sx={{ ml: 1 }}
|
||||||
aria-label={isMobile ? 'More options' : 'Delete conversation'}
|
aria-label={isMobile ? t('interface.moreOptions') : t('interface.deleteConversation')}
|
||||||
>
|
>
|
||||||
{isMobile ? <MoreVert fontSize="small" /> : <Delete fontSize="small" />}
|
{isMobile ? <MoreVert fontSize="small" /> : <Delete fontSize="small" />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -566,10 +571,10 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
AI Parenting Assistant
|
{t('interface.assistantTitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
Ask me anything about parenting and childcare
|
{t('interface.assistantSubtitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -599,7 +604,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<AutoAwesome sx={{ fontSize: 64, color: 'primary.main', opacity: 0.5 }} />
|
<AutoAwesome sx={{ fontSize: 64, color: 'primary.main', opacity: 0.5 }} />
|
||||||
<Typography variant="h6" color="text.secondary" textAlign="center">
|
<Typography variant="h6" color="text.secondary" textAlign="center">
|
||||||
Hi {user?.name}! How can I help you today?
|
{t('interface.greeting', { name: user?.name })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -732,7 +737,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
transition: 'opacity 0.3s ease-in-out',
|
transition: 'opacity 0.3s ease-in-out',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentThinkingMessages[currentThinkingIndex] || 'Thinking...'}
|
{currentThinkingMessages[currentThinkingIndex] || t('chat.thinking')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -758,7 +763,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
maxRows={4}
|
maxRows={4}
|
||||||
placeholder="Ask me anything..."
|
placeholder={t('interface.inputPlaceholder')}
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
onKeyPress={(e) => {
|
onKeyPress={(e) => {
|
||||||
@@ -795,24 +800,23 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
||||||
This AI assistant provides general information. Always consult healthcare professionals
|
{t('interface.disclaimerFooter')}
|
||||||
for medical advice.
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Delete Confirmation Dialog */}
|
{/* Delete Confirmation Dialog */}
|
||||||
<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
|
<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
|
||||||
<DialogTitle>Delete Conversation</DialogTitle>
|
<DialogTitle>{t('interface.deleteDialogTitle')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Typography>
|
<Typography>
|
||||||
Are you sure you want to delete this conversation? This action cannot be undone.
|
{t('interface.deleteDialogMessage')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteDialogOpen(false)}>Cancel</Button>
|
<Button onClick={() => setDeleteDialogOpen(false)}>{t('interface.cancel')}</Button>
|
||||||
<Button onClick={handleDeleteConversation} color="error" variant="contained">
|
<Button onClick={handleDeleteConversation} color="error" variant="contained">
|
||||||
Delete
|
{t('interface.delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -837,7 +841,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<DriveFileMove fontSize="small" />
|
<DriveFileMove fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>Move to Group</ListItemText>
|
<ListItemText>{t('interface.moveToGroup')}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -851,7 +855,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Delete fontSize="small" />
|
<Delete fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>Delete</ListItemText>
|
<ListItemText>{t('interface.delete')}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
@@ -862,7 +866,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
maxWidth="xs"
|
maxWidth="xs"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<DialogTitle>Move to Group</DialogTitle>
|
<DialogTitle>{t('interface.moveToGroup')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<List>
|
<List>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
@@ -875,7 +879,7 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Chat />
|
<Chat />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Ungrouped" />
|
<ListItemText primary={t('interface.ungrouped')} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
<Divider />
|
<Divider />
|
||||||
{getExistingGroups().map((groupName) => (
|
{getExistingGroups().map((groupName) => (
|
||||||
@@ -898,13 +902,13 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<CreateNewFolder />
|
<CreateNewFolder />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Create New Group" />
|
<ListItemText primary={t('interface.createNewGroup')} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</List>
|
</List>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setMoveToGroupDialog({ open: false, conversationId: null })}>
|
<Button onClick={() => setMoveToGroupDialog({ open: false, conversationId: null })}>
|
||||||
Cancel
|
{t('interface.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -919,12 +923,12 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
maxWidth="xs"
|
maxWidth="xs"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<DialogTitle>Create New Group</DialogTitle>
|
<DialogTitle>{t('interface.createNewGroup')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label="Group Name"
|
label={t('interface.groupNameLabel')}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={newGroupName}
|
value={newGroupName}
|
||||||
@@ -943,10 +947,10 @@ export const AIChatInterface: React.FC = () => {
|
|||||||
setNewGroupName('');
|
setNewGroupName('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('interface.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleCreateNewGroup} variant="contained" disabled={!newGroupName.trim()}>
|
<Button onClick={handleCreateNewGroup} variant="contained" disabled={!newGroupName.trim()}>
|
||||||
Create
|
{t('interface.create')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { trackingApi, Activity, ActivityType } from '@/lib/api/tracking';
|
|||||||
import { childrenApi, Child } from '@/lib/api/children';
|
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 { 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';
|
||||||
|
|
||||||
type DateRange = '7days' | '30days' | '3months';
|
type DateRange = '7days' | '30days' | '3months';
|
||||||
@@ -99,6 +100,7 @@ const getActivityColor = (type: ActivityType) => {
|
|||||||
export const InsightsDashboard: React.FC = () => {
|
export const InsightsDashboard: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { format, formatDistanceToNow } = useLocalizedDate();
|
const { format, formatDistanceToNow } = useLocalizedDate();
|
||||||
|
const { t } = useTranslation('insights');
|
||||||
const [children, setChildren] = useState<Child[]>([]);
|
const [children, setChildren] = useState<Child[]>([]);
|
||||||
const [selectedChild, setSelectedChild] = useState<string>('');
|
const [selectedChild, setSelectedChild] = useState<string>('');
|
||||||
const [dateRange, setDateRange] = useState<DateRange>('7days');
|
const [dateRange, setDateRange] = useState<DateRange>('7days');
|
||||||
@@ -116,7 +118,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
setSelectedChild(childrenData[0].id);
|
setSelectedChild(childrenData[0].id);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.response?.data?.message || 'Failed to load children');
|
setError(err.response?.data?.message || t('errors.loadChildren'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchChildren();
|
fetchChildren();
|
||||||
@@ -142,7 +144,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
);
|
);
|
||||||
setActivities(activitiesData);
|
setActivities(activitiesData);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.response?.data?.message || 'Failed to load activities');
|
setError(err.response?.data?.message || t('errors.loadActivities'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -172,7 +174,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
activities.forEach((a) => {
|
activities.forEach((a) => {
|
||||||
typeCounts[a.type] = (typeCounts[a.type] || 0) + 1;
|
typeCounts[a.type] = (typeCounts[a.type] || 0) + 1;
|
||||||
});
|
});
|
||||||
const mostCommonType = Object.entries(typeCounts).sort((a, b) => b[1] - a[1])[0]?.[0] || 'None';
|
const mostCommonType = Object.entries(typeCounts).sort((a, b) => b[1] - a[1])[0]?.[0] || 'none';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalFeedings,
|
totalFeedings,
|
||||||
@@ -230,7 +232,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return Object.entries(typeCount).map(([name, value]) => ({
|
return Object.entries(typeCount).map(([name, value]) => ({
|
||||||
name: name.charAt(0).toUpperCase() + name.slice(1),
|
name: t(`diaperTypes.${name}`),
|
||||||
value,
|
value,
|
||||||
color: COLORS[name as keyof typeof COLORS] || '#CCCCCC',
|
color: COLORS[name as keyof typeof COLORS] || '#CCCCCC',
|
||||||
}));
|
}));
|
||||||
@@ -244,7 +246,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return Object.entries(typeCount).map(([name, count]) => ({
|
return Object.entries(typeCount).map(([name, count]) => ({
|
||||||
name: name.charAt(0).toUpperCase() + name.slice(1),
|
name: t(`activityTypes.${name}`),
|
||||||
count,
|
count,
|
||||||
color: COLORS[name as keyof typeof COLORS] || '#CCCCCC',
|
color: COLORS[name as keyof typeof COLORS] || '#CCCCCC',
|
||||||
}));
|
}));
|
||||||
@@ -269,10 +271,10 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h4" fontWeight="600" gutterBottom>
|
<Typography variant="h4" fontWeight="600" gutterBottom>
|
||||||
Insights & Analytics
|
{t('title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
||||||
Track patterns and get insights about your child's activities
|
{t('subtitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
@@ -281,11 +283,11 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
{children.length > 1 && (
|
{children.length > 1 && (
|
||||||
<Grid item xs={12} sm={6} md={4}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Child</InputLabel>
|
<InputLabel>{t('filters.child')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={selectedChild}
|
value={selectedChild}
|
||||||
onChange={(e) => setSelectedChild(e.target.value)}
|
onChange={(e) => setSelectedChild(e.target.value)}
|
||||||
label="Child"
|
label={t('filters.child')}
|
||||||
>
|
>
|
||||||
{children.map((child) => (
|
{children.map((child) => (
|
||||||
<MenuItem key={child.id} value={child.id}>
|
<MenuItem key={child.id} value={child.id}>
|
||||||
@@ -304,9 +306,9 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<ToggleButton value="7days">7 Days</ToggleButton>
|
<ToggleButton value="7days">{t('filters.dateRange.7days')}</ToggleButton>
|
||||||
<ToggleButton value="30days">30 Days</ToggleButton>
|
<ToggleButton value="30days">{t('filters.dateRange.30days')}</ToggleButton>
|
||||||
<ToggleButton value="3months">3 Months</ToggleButton>
|
<ToggleButton value="3months">{t('filters.dateRange.3months')}</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -323,17 +325,17 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<CardContent sx={{ textAlign: 'center', py: 8 }}>
|
<CardContent sx={{ textAlign: 'center', py: 8 }}>
|
||||||
<ChildCare sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
<ChildCare sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
||||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||||
No Children Added
|
{t('emptyStates.noChildren.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
Add a child to view insights and analytics
|
{t('emptyStates.noChildren.message')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<Add />}
|
startIcon={<Add />}
|
||||||
onClick={() => router.push('/children')}
|
onClick={() => router.push('/children')}
|
||||||
>
|
>
|
||||||
Add Child
|
{t('emptyStates.noChildren.action')}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -347,7 +349,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
|
|
||||||
{noActivities && !noChildren && (
|
{noActivities && !noChildren && (
|
||||||
<Alert severity="info" sx={{ mb: 3 }}>
|
<Alert severity="info" sx={{ mb: 3 }}>
|
||||||
No activities found for the selected date range. Start tracking activities to see insights!
|
{t('emptyStates.noActivities')}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -366,14 +368,14 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
<Restaurant sx={{ fontSize: 32, mr: 1 }} />
|
<Restaurant sx={{ fontSize: 32, mr: 1 }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Feedings
|
{t('stats.feedings.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h3" fontWeight="700">
|
<Typography variant="h3" fontWeight="700">
|
||||||
{stats.totalFeedings}
|
{stats.totalFeedings}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||||
Total count
|
{t('stats.feedings.subtitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -391,14 +393,14 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
<Hotel sx={{ fontSize: 32, mr: 1 }} />
|
<Hotel sx={{ fontSize: 32, mr: 1 }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Sleep
|
{t('stats.sleep.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h3" fontWeight="700">
|
<Typography variant="h3" fontWeight="700">
|
||||||
{stats.avgSleepHours}h
|
{stats.avgSleepHours}h
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||||
Average per day
|
{t('stats.sleep.subtitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -416,14 +418,14 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
<BabyChangingStation sx={{ fontSize: 32, mr: 1 }} />
|
<BabyChangingStation sx={{ fontSize: 32, mr: 1 }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Diapers
|
{t('stats.diapers.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h3" fontWeight="700">
|
<Typography variant="h3" fontWeight="700">
|
||||||
{stats.totalDiapers}
|
{stats.totalDiapers}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||||
Total changes
|
{t('stats.diapers.subtitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -441,14 +443,14 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||||
<TrendingUp sx={{ fontSize: 32, mr: 1 }} />
|
<TrendingUp sx={{ fontSize: 32, mr: 1 }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Top Activity
|
{t('stats.topActivity.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h3" fontWeight="700" sx={{ textTransform: 'capitalize' }}>
|
<Typography variant="h3" fontWeight="700" sx={{ textTransform: 'capitalize' }}>
|
||||||
{stats.mostCommonType}
|
{t(`activityTypes.${stats.mostCommonType}`)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||||
Most frequent
|
{t('stats.topActivity.subtitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -464,7 +466,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<Restaurant sx={{ mr: 1, color: COLORS.feeding }} />
|
<Restaurant sx={{ mr: 1, color: COLORS.feeding }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Feeding Frequency
|
{t('charts.feedingFrequency')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
@@ -473,7 +475,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<XAxis dataKey="date" tick={{ fontSize: 12 }} />
|
<XAxis dataKey="date" tick={{ fontSize: 12 }} />
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
<Bar dataKey="feedings" fill={COLORS.feeding} name="Feedings" />
|
<Bar dataKey="feedings" fill={COLORS.feeding} name={t('charts.chartLabels.feedings')} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -486,7 +488,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<Hotel sx={{ mr: 1, color: COLORS.sleep }} />
|
<Hotel sx={{ mr: 1, color: COLORS.sleep }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Sleep Duration (Hours)
|
{t('charts.sleepDuration')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
@@ -500,7 +502,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
dataKey="sleepHours"
|
dataKey="sleepHours"
|
||||||
stroke={COLORS.sleep}
|
stroke={COLORS.sleep}
|
||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
name="Sleep Hours"
|
name={t('charts.chartLabels.sleepHours')}
|
||||||
dot={{ fill: COLORS.sleep, r: 4 }}
|
dot={{ fill: COLORS.sleep, r: 4 }}
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
@@ -516,7 +518,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<BabyChangingStation sx={{ mr: 1, color: COLORS.diaper }} />
|
<BabyChangingStation sx={{ mr: 1, color: COLORS.diaper }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Diaper Changes by Type
|
{t('charts.diaperChangesByType')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
@@ -549,7 +551,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<Assessment sx={{ mr: 1, color: 'primary.main' }} />
|
<Assessment sx={{ mr: 1, color: 'primary.main' }} />
|
||||||
<Typography variant="h6" fontWeight="600">
|
<Typography variant="h6" fontWeight="600">
|
||||||
Activity Timeline
|
{t('charts.activityTimeline')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
@@ -559,9 +561,9 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="feedings" fill={COLORS.feeding} name="Feedings" />
|
<Bar dataKey="feedings" fill={COLORS.feeding} name={t('charts.chartLabels.feedings')} />
|
||||||
<Bar dataKey="diapers" fill={COLORS.diaper} name="Diapers" />
|
<Bar dataKey="diapers" fill={COLORS.diaper} name={t('charts.chartLabels.diapers')} />
|
||||||
<Bar dataKey="sleepHours" fill={COLORS.sleep} name="Sleep (hrs)" />
|
<Bar dataKey="sleepHours" fill={COLORS.sleep} name={t('charts.chartLabels.sleepHours')} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -573,7 +575,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Card sx={{ mb: 3 }}>
|
<Card sx={{ mb: 3 }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" fontWeight="600" gutterBottom>
|
<Typography variant="h6" fontWeight="600" gutterBottom>
|
||||||
Activity Distribution
|
{t('charts.activityDistribution')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', mt: 2 }}>
|
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', mt: 2 }}>
|
||||||
{activityTypeData.map((activity) => (
|
{activityTypeData.map((activity) => (
|
||||||
@@ -600,7 +602,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" fontWeight="600" gutterBottom>
|
<Typography variant="h6" fontWeight="600" gutterBottom>
|
||||||
Recent Activities (Last 20)
|
{t('recentActivities.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider sx={{ my: 2 }} />
|
<Divider sx={{ my: 2 }} />
|
||||||
<List sx={{ maxHeight: 400, overflow: 'auto' }}>
|
<List sx={{ maxHeight: 400, overflow: 'auto' }}>
|
||||||
@@ -627,7 +629,7 @@ export const InsightsDashboard: React.FC = () => {
|
|||||||
primary={
|
primary={
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="body1" fontWeight="600" sx={{ textTransform: 'capitalize' }}>
|
<Typography variant="body1" fontWeight="600" sx={{ textTransform: 'capitalize' }}>
|
||||||
{activity.type}
|
{t(`activityTypes.${activity.type}`)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={formatDistanceToNow(parseISO(activity.timestamp), { addSuffix: true })}
|
label={formatDistanceToNow(parseISO(activity.timestamp), { addSuffix: true })}
|
||||||
|
|||||||
@@ -58,5 +58,50 @@
|
|||||||
"safety": "Safety",
|
"safety": "Safety",
|
||||||
"nutrition": "Nutrition",
|
"nutrition": "Nutrition",
|
||||||
"general": "General"
|
"general": "General"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"assistantTitle": "AI Parenting Assistant",
|
||||||
|
"assistantSubtitle": "Ask me anything about parenting and childcare",
|
||||||
|
"greeting": "Hi {{name}}! How can I help you today?",
|
||||||
|
"inputPlaceholder": "Ask me anything...",
|
||||||
|
"closeDrawer": "Close drawer",
|
||||||
|
"moreOptions": "More options",
|
||||||
|
"deleteConversation": "Delete conversation",
|
||||||
|
"chatCount": "{{count}} chat",
|
||||||
|
"chatCount_plural": "{{count}} chats",
|
||||||
|
"ungrouped": "Ungrouped",
|
||||||
|
"errorMessage": "Sorry, I encountered an error. Please try again.",
|
||||||
|
"disclaimerFooter": "This AI assistant provides general information. Always consult healthcare professionals for medical advice.",
|
||||||
|
"deleteDialogTitle": "Delete Conversation",
|
||||||
|
"deleteDialogMessage": "Are you sure you want to delete this conversation? This action cannot be undone.",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"delete": "Delete",
|
||||||
|
"moveToGroup": "Move to Group",
|
||||||
|
"createNewGroup": "Create New Group",
|
||||||
|
"groupNameLabel": "Group Name",
|
||||||
|
"create": "Create",
|
||||||
|
"suggestedQuestion1": "How much should my baby sleep at 3 months?",
|
||||||
|
"suggestedQuestion2": "What are normal feeding patterns?",
|
||||||
|
"suggestedQuestion3": "When should I introduce solid foods?",
|
||||||
|
"suggestedQuestion4": "Tips for better sleep routine",
|
||||||
|
"thinking1": "Gathering baby wisdom...",
|
||||||
|
"thinking2": "Consulting the baby books...",
|
||||||
|
"thinking3": "Mixing up the perfect answer...",
|
||||||
|
"thinking4": "Warming up some advice...",
|
||||||
|
"thinking5": "Preparing your bottle of knowledge...",
|
||||||
|
"thinking6": "Counting tiny fingers and toes...",
|
||||||
|
"thinking7": "Connecting the building blocks...",
|
||||||
|
"thinking8": "Peeking into the toy box...",
|
||||||
|
"thinking9": "Arranging the puzzle pieces...",
|
||||||
|
"thinking10": "Stirring the baby food jar...",
|
||||||
|
"thinking11": "Polishing the pacifier of wisdom...",
|
||||||
|
"thinking12": "Tiptoeing through naptime...",
|
||||||
|
"thinking13": "Organizing the diaper bag...",
|
||||||
|
"thinking14": "Wrapping up your answer with love...",
|
||||||
|
"thinking15": "Brewing a warm cup of guidance...",
|
||||||
|
"thinking16": "Knitting together some thoughts...",
|
||||||
|
"thinking17": "Tucking in the details...",
|
||||||
|
"thinking18": "Sprinkling some magic dust...",
|
||||||
|
"thinking19": "Humming a lullaby while I think..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
maternal-web/locales/en/family.json
Normal file
43
maternal-web/locales/en/family.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"pageTitle": "Family",
|
||||||
|
"pageSubtitle": "Manage your family members and share access",
|
||||||
|
"buttons": {
|
||||||
|
"joinFamily": "Join Family",
|
||||||
|
"inviteMember": "Invite Member",
|
||||||
|
"copyCode": "Copy Code",
|
||||||
|
"inviteFirstMember": "Invite First Member"
|
||||||
|
},
|
||||||
|
"shareCode": {
|
||||||
|
"title": "Family Share Code",
|
||||||
|
"description": "Share this code with family members to give them access to your family's data"
|
||||||
|
},
|
||||||
|
"members": {
|
||||||
|
"title": "Family Members ({{count}})",
|
||||||
|
"noMembers": "No family members yet",
|
||||||
|
"noMembersDescription": "Invite family members to collaborate on child care",
|
||||||
|
"youLabel": "You",
|
||||||
|
"removeAriaLabel": "Remove {{name}} from family"
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"parent": "Parent",
|
||||||
|
"caregiver": "Caregiver",
|
||||||
|
"viewer": "Viewer"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"shareCodeCopied": "Share code copied to clipboard!",
|
||||||
|
"shareCodeCopyFailed": "Failed to copy share code",
|
||||||
|
"invitationSent": "Invitation sent successfully!",
|
||||||
|
"joinedFamily": "Successfully joined family!",
|
||||||
|
"memberRemoved": "Member removed successfully",
|
||||||
|
"noFamilyFound": "No family found. Please complete onboarding first.",
|
||||||
|
"failedToLoad": "Failed to load family information",
|
||||||
|
"noFamilyId": "No family ID found",
|
||||||
|
"failedToInvite": "Failed to send invitation",
|
||||||
|
"failedToJoin": "Failed to join family",
|
||||||
|
"failedToRemove": "Failed to remove member"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"unknownUser": "Unknown User",
|
||||||
|
"noEmail": "No email"
|
||||||
|
}
|
||||||
|
}
|
||||||
73
maternal-web/locales/en/insights.json
Normal file
73
maternal-web/locales/en/insights.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"title": "Insights & Analytics",
|
||||||
|
"subtitle": "Track patterns and get insights about your child's activities",
|
||||||
|
"filters": {
|
||||||
|
"child": "Child",
|
||||||
|
"dateRange": {
|
||||||
|
"7days": "7 Days",
|
||||||
|
"30days": "30 Days",
|
||||||
|
"3months": "3 Months"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"feedings": {
|
||||||
|
"title": "Feedings",
|
||||||
|
"subtitle": "Total count"
|
||||||
|
},
|
||||||
|
"sleep": {
|
||||||
|
"title": "Sleep",
|
||||||
|
"subtitle": "Average per day"
|
||||||
|
},
|
||||||
|
"diapers": {
|
||||||
|
"title": "Diapers",
|
||||||
|
"subtitle": "Total changes"
|
||||||
|
},
|
||||||
|
"topActivity": {
|
||||||
|
"title": "Top Activity",
|
||||||
|
"subtitle": "Most frequent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"charts": {
|
||||||
|
"feedingFrequency": "Feeding Frequency",
|
||||||
|
"sleepDuration": "Sleep Duration (Hours)",
|
||||||
|
"diaperChangesByType": "Diaper Changes by Type",
|
||||||
|
"activityTimeline": "Activity Timeline",
|
||||||
|
"activityDistribution": "Activity Distribution",
|
||||||
|
"chartLabels": {
|
||||||
|
"feedings": "Feedings",
|
||||||
|
"diapers": "Diapers",
|
||||||
|
"sleepHours": "Sleep (hrs)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"recentActivities": {
|
||||||
|
"title": "Recent Activities (Last 20)"
|
||||||
|
},
|
||||||
|
"emptyStates": {
|
||||||
|
"noChildren": {
|
||||||
|
"title": "No Children Added",
|
||||||
|
"message": "Add a child to view insights and analytics",
|
||||||
|
"action": "Add Child"
|
||||||
|
},
|
||||||
|
"noActivities": "No activities found for the selected date range. Start tracking activities to see insights!"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"loadChildren": "Failed to load children",
|
||||||
|
"loadActivities": "Failed to load activities"
|
||||||
|
},
|
||||||
|
"activityTypes": {
|
||||||
|
"feeding": "Feeding",
|
||||||
|
"sleep": "Sleep",
|
||||||
|
"diaper": "Diaper",
|
||||||
|
"medication": "Medication",
|
||||||
|
"milestone": "Milestone",
|
||||||
|
"note": "Note",
|
||||||
|
"none": "None"
|
||||||
|
},
|
||||||
|
"diaperTypes": {
|
||||||
|
"wet": "Wet",
|
||||||
|
"dirty": "Dirty",
|
||||||
|
"both": "Both",
|
||||||
|
"dry": "Dry",
|
||||||
|
"unknown": "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,17 +26,41 @@
|
|||||||
},
|
},
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"startTime": "Start Time",
|
"startTime": "Start Timer",
|
||||||
"endTime": "End Time",
|
"endTime": "Stop Timer",
|
||||||
|
"reset": "Reset",
|
||||||
"notes": "Notes",
|
"notes": "Notes",
|
||||||
|
"bottleType": "Bottle Type",
|
||||||
|
"bottleTypes": {
|
||||||
|
"formula": "Formula",
|
||||||
|
"breastmilk": "Breast Milk",
|
||||||
|
"other": "Other"
|
||||||
|
},
|
||||||
|
"foodDescription": "Food Description",
|
||||||
|
"amountDescription": "Amount Description",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"amount": "Enter amount",
|
"amount": "Enter amount",
|
||||||
"notes": "Add any notes about this feeding..."
|
"notes": "Add any notes about this feeding...",
|
||||||
|
"duration": "Or enter duration manually",
|
||||||
|
"foodDescription": "e.g., Rice cereal, Banana puree",
|
||||||
|
"amountDescription": "e.g., 1/4 cup, 2 spoonfuls"
|
||||||
},
|
},
|
||||||
"units": {
|
"units": {
|
||||||
"ml": "ml",
|
"ml": "ml",
|
||||||
"oz": "oz",
|
"oz": "oz",
|
||||||
"minutes": "minutes"
|
"minutes": "minutes"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"durationRequired": "Please enter duration or use the timer",
|
||||||
|
"amountRequired": "Please enter amount",
|
||||||
|
"foodRequired": "Please enter food description"
|
||||||
|
},
|
||||||
|
"success": "Feeding logged successfully!",
|
||||||
|
"deleted": "Feeding deleted successfully",
|
||||||
|
"recentFeedings": "Recent Feedings",
|
||||||
|
"error": {
|
||||||
|
"saveFailed": "Failed to save feeding",
|
||||||
|
"deleteFailed": "Failed to delete feeding"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sleep": {
|
"sleep": {
|
||||||
@@ -148,7 +172,35 @@
|
|||||||
},
|
},
|
||||||
"temperature": "Temperature",
|
"temperature": "Temperature",
|
||||||
"medication": "Medication",
|
"medication": "Medication",
|
||||||
"dosage": "Dosage",
|
"medicineInfo": "Medicine Information",
|
||||||
|
"medicineName": {
|
||||||
|
"label": "Medicine Name",
|
||||||
|
"placeholder": "e.g., Acetaminophen, Ibuprofen",
|
||||||
|
"required": "Please enter medicine name"
|
||||||
|
},
|
||||||
|
"dosage": {
|
||||||
|
"label": "Dosage",
|
||||||
|
"placeholder": "e.g., 5, 2.5",
|
||||||
|
"required": "Please enter dosage"
|
||||||
|
},
|
||||||
|
"unit": "Unit",
|
||||||
|
"route": {
|
||||||
|
"label": "Route",
|
||||||
|
"oral": "Oral",
|
||||||
|
"topical": "Topical",
|
||||||
|
"injection": "Injection",
|
||||||
|
"other": "Other"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"label": "Reason (optional)",
|
||||||
|
"placeholder": "e.g., Fever, Pain, Allergy"
|
||||||
|
},
|
||||||
|
"logMedicine": "Log Medicine",
|
||||||
|
"recentMedicines": "Recent Medicines",
|
||||||
|
"success": "Medicine logged successfully!",
|
||||||
|
"error": "Failed to save medicine",
|
||||||
|
"deleted": "Medicine deleted successfully",
|
||||||
|
"deleteError": "Failed to delete medicine",
|
||||||
"symptom": "Symptom",
|
"symptom": "Symptom",
|
||||||
"severity": "Severity",
|
"severity": "Severity",
|
||||||
"severities": {
|
"severities": {
|
||||||
@@ -164,6 +216,12 @@
|
|||||||
"notes": "Add any notes..."
|
"notes": "Add any notes..."
|
||||||
},
|
},
|
||||||
"units": {
|
"units": {
|
||||||
|
"ml": "ml",
|
||||||
|
"mg": "mg",
|
||||||
|
"tsp": "tsp",
|
||||||
|
"tbsp": "tbsp",
|
||||||
|
"drops": "drops",
|
||||||
|
"tablet": "tablet(s)",
|
||||||
"celsius": "°C",
|
"celsius": "°C",
|
||||||
"fahrenheit": "°F"
|
"fahrenheit": "°F"
|
||||||
}
|
}
|
||||||
@@ -202,7 +260,10 @@
|
|||||||
"noChildrenAdded": "No Children Added",
|
"noChildrenAdded": "No Children Added",
|
||||||
"noChildrenMessage": "You need to add a child before you can track activities",
|
"noChildrenMessage": "You need to add a child before you can track activities",
|
||||||
"addChild": "Add Child",
|
"addChild": "Add Child",
|
||||||
"recentActivities": "Recent Activities"
|
"recentActivities": "Recent Activities",
|
||||||
|
"error": {
|
||||||
|
"loadChildrenFailed": "Failed to load children"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"quickLog": "Quick Log",
|
"quickLog": "Quick Log",
|
||||||
"viewHistory": "View History",
|
"viewHistory": "View History",
|
||||||
|
|||||||
Reference in New Issue
Block a user