Add Medicine and Activity trackers with voice command support
Added new tracking pages: - Medicine tracker: track medication name, dosage, unit, route, and reason - Activity tracker: track play, exercise, walks, music, reading, tummy time, etc. - Both pages follow existing tracker patterns with recent activities list Voice command improvements: - Updated voice classification to support medicine and activity types - Added detailed extraction fields for medicine (medicineName, dosage, unit, route, reason) - Added detailed extraction fields for activity (activityType, duration, description) - Enhanced unknown intent dialog with manual tracker selection - Updated tracker options to match implemented pages (removed milestone) Backend changes: - Added MEDICINE and ACTIVITY to ActivityType enum - Created migration V013 to add medicine/activity to database CHECK constraint - Updated voice service prompts to include medicine and activity extraction Frontend changes: - Created /track/medicine page with full CRUD operations - Created /track/activity page with full CRUD operations - Added Medicine card to /track page with MedicalServices icon - Updated VoiceFloatingButton unknown dialog with 4 tracker options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,9 +16,14 @@ import {
|
||||
CircularProgress,
|
||||
Chip,
|
||||
IconButton,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from '@mui/material';
|
||||
import MicIcon from '@mui/icons-material/Mic';
|
||||
import MicOffIcon from '@mui/icons-material/MicOff';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useVoiceInput } from '@/hooks/useVoiceInput';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
@@ -43,6 +48,8 @@ export function VoiceFloatingButton() {
|
||||
const [classificationResult, setClassificationResult] = useState<any>(null);
|
||||
const [processedClassificationId, setProcessedClassificationId] = useState<string | null>(null);
|
||||
const [showReview, setShowReview] = useState(false);
|
||||
const [showUnknownDialog, setShowUnknownDialog] = useState(false);
|
||||
const [manualTrackingType, setManualTrackingType] = useState<string>('feeding');
|
||||
const [snackbar, setSnackbar] = useState<{
|
||||
open: boolean;
|
||||
message: string;
|
||||
@@ -91,14 +98,10 @@ export function VoiceFloatingButton() {
|
||||
setProcessingStatus(null);
|
||||
setShowReview(true);
|
||||
} else {
|
||||
// For unknown or low confidence, show error and close dialog
|
||||
// For unknown or low confidence, show unknown dialog
|
||||
setProcessingStatus(null);
|
||||
setOpen(false);
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: 'Could not understand the command. Please try again or use manual entry.',
|
||||
severity: 'warning',
|
||||
});
|
||||
setShowUnknownDialog(true);
|
||||
}
|
||||
}
|
||||
}, [classification, isListening, isProcessing, open, transcript, processedClassificationId]);
|
||||
@@ -295,6 +298,24 @@ export function VoiceFloatingButton() {
|
||||
setSnackbar(prev => ({ ...prev, open: false }));
|
||||
};
|
||||
|
||||
const handleRetry = () => {
|
||||
setShowUnknownDialog(false);
|
||||
setOpen(true);
|
||||
reset();
|
||||
setClassificationResult(null);
|
||||
setProcessingStatus(null);
|
||||
setProcessedClassificationId(null);
|
||||
// Auto-start listening
|
||||
setTimeout(() => {
|
||||
startListening();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleManualTracking = () => {
|
||||
setShowUnknownDialog(false);
|
||||
router.push(`/track/${manualTrackingType}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Floating button positioned in bottom-right */}
|
||||
@@ -439,6 +460,44 @@ export function VoiceFloatingButton() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Unknown Intent Dialog */}
|
||||
<Dialog open={showUnknownDialog} onClose={() => setShowUnknownDialog(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Could Not Understand Command</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
You said: "{transcript}"
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mt: 2 }}>
|
||||
I couldn't identify a specific activity from your command. You can either try again or manually add an activity.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<FormControl fullWidth sx={{ mt: 2 }}>
|
||||
<InputLabel>Activity Type</InputLabel>
|
||||
<Select
|
||||
value={manualTrackingType}
|
||||
onChange={(e) => setManualTrackingType(e.target.value)}
|
||||
label="Activity Type"
|
||||
>
|
||||
<MenuItem value="feeding">Feeding</MenuItem>
|
||||
<MenuItem value="sleep">Sleep</MenuItem>
|
||||
<MenuItem value="diaper">Diaper Change</MenuItem>
|
||||
<MenuItem value="medicine">Medicine</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={handleRetry} startIcon={<MicIcon />} color="primary">
|
||||
Retry Voice Command
|
||||
</Button>
|
||||
<Button onClick={handleManualTracking} startIcon={<AddIcon />} variant="contained">
|
||||
Add Manual Tracking
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Snackbar for feedback */}
|
||||
<Snackbar
|
||||
open={snackbar.open}
|
||||
|
||||
Reference in New Issue
Block a user