Skip to content

Commit

Permalink
feat: Integrate goals, micro-goals, and tasks with Firebase (#12)
Browse files Browse the repository at this point in the history
- Linked goal, micro-goal, and task addition directly to Firebase,
replacing hardcoded placeholders.
- Refined UI with `AddItem` for consistent input UX across goals,
micro-goals, and tasks.
- Added `Collapse` animations for smoother goal/micro-goal
expand/collapse interactions.
- Enhanced `useGoalsUpdater` hook to handle database updates and status
toggling.
- Extract `calculateProgress.js` into the utils folder.

---------

Co-authored-by: ZL Asica <[email protected]>
  • Loading branch information
marycaserio and ZL-Asica authored Oct 29, 2024
1 parent 086bbe0 commit a137f80
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 332 deletions.
38 changes: 38 additions & 0 deletions src/components/Home/AddItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import AddIcon from '@mui/icons-material/Add';
import { IconButton, InputAdornment, TextField } from '@mui/material';
import { useState } from 'react';

const AddItem = ({ label, onAdd }) => {
const [inputValue, setInputValue] = useState('');

const handleAdd = async () => {
if (inputValue.trim()) {
await onAdd(inputValue);
setInputValue('');
}
};

return (
<TextField
variant="outlined"
label={label}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
fullWidth
onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">
<IconButton color="primary" onClick={handleAdd} disabled={!inputValue.trim()}>
<AddIcon />
</IconButton>
</InputAdornment>
),
},
}}
/>
);
};

export default AddItem;
181 changes: 12 additions & 169 deletions src/components/Home/GoalTracker.jsx
Original file line number Diff line number Diff line change
@@ -1,177 +1,20 @@
import AddItem from '@components/Home/AddItem';
import MacroGoal from '@components/Home/MacroGoal';
import { useUser } from '@contexts/UserContext';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import {
Box,
Checkbox,
CircularProgress,
Divider,
IconButton,
List,
ListItem,
ListItemText,
Paper,
Typography,
} from '@mui/material';
import { useState } from 'react';

const ProgressIndicator = ({ value, size = 40, thickness = 4 }) => (
<Box sx={{ position: 'relative', display: 'inline-flex', mr: 2 }}>
<CircularProgress variant="determinate" value={value} size={size} thickness={thickness} />
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" component="div" color="text.secondary">
{`${Math.round(value)}%`}
</Typography>
</Box>
</Box>
);

const Task = ({ task, onToggle }) => (
<ListItem dense>
<Checkbox edge="start" checked={task.completed} onChange={onToggle} />
<ListItemText primary={task.name} />
</ListItem>
);

const MicroGoal = ({ microGoal, macroGoalIndex, microGoalIndex, onToggleTask, onToggleExpand }) => {
const progress =
(microGoal.tasks.filter((t) => t.completed).length / microGoal.tasks.length) * 100;

return (
<Paper elevation={2} sx={{ p: 2, mb: 2, bgcolor: 'background.default' }}>
<Box display="flex" alignItems="center">
<ProgressIndicator value={progress} size={32} thickness={3} />
<Typography variant="subtitle1">{microGoal.name}</Typography>
<IconButton
onClick={() => onToggleExpand(macroGoalIndex, microGoalIndex)}
size="small"
sx={{ ml: 'auto' }}
>
{microGoal.expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Box>
{microGoal.expanded && (
<List sx={{ mt: 1, pl: 4 }}>
{microGoal.tasks.map((task, taskIndex) => (
<Task
key={`${macroGoalIndex}-${microGoalIndex}-${taskIndex}`} // Unique key for tasks
task={task}
onToggle={() => onToggleTask(macroGoalIndex, microGoalIndex, taskIndex)}
/>
))}
</List>
)}
</Paper>
);
};

const MacroGoal = ({ macroGoal, macroGoalIndex, onToggleTask, onToggleExpand }) => {
const progress =
(macroGoal.microgoals.reduce((acc, mg) => acc + mg.tasks.filter((t) => t.completed).length, 0) /
macroGoal.microgoals.reduce((acc, mg) => acc + mg.tasks.length, 0)) *
100;

return (
<Paper elevation={3} sx={{ p: 3, mb: 4, bgcolor: 'background.paper' }}>
<Box display="flex" alignItems="center" mb={2}>
<ProgressIndicator value={progress} size={48} thickness={4} />
<Typography variant="h5" sx={{ fontWeight: 'bold' }}>
{macroGoal.name}
</Typography>
<IconButton onClick={() => onToggleExpand(macroGoalIndex)} size="small" sx={{ ml: 'auto' }}>
{macroGoal.expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Box>
<Divider sx={{ mb: 2 }} />
{macroGoal.expanded && (
<List sx={{ pl: 2 }}>
{macroGoal.microgoals.map((microGoal, microGoalIndex) => (
<MicroGoal
key={`${macroGoalIndex}-${microGoalIndex}`} // Unique key for microgoals
microGoal={microGoal}
macroGoalIndex={macroGoalIndex}
microGoalIndex={microGoalIndex} // Pass the micro goal index
onToggleTask={onToggleTask}
onToggleExpand={onToggleExpand}
/>
))}
</List>
)}
</Paper>
);
};
import useGoalsUpdater from '@hooks/useGoalsUpdater';
import { Box } from '@mui/material';

export default function GoalTracker() {
const { user, loading } = useUser();
const [goals, setGoals] = useState(user.goals || []);

const handleToggleTask = (macroGoalIndex, microGoalIndex, taskIndex) => {
setGoals((prevGoals) =>
prevGoals.map((macroGoal, mgi) =>
mgi === macroGoalIndex
? {
...macroGoal,
microgoals: macroGoal.microgoals.map((microGoal, mgi) =>
mgi === microGoalIndex
? {
...microGoal,
tasks: microGoal.tasks.map((task, ti) =>
ti === taskIndex ? { ...task, completed: !task.completed } : task,
),
}
: microGoal,
),
}
: macroGoal,
),
);
};

const handleToggleExpand = (macroGoalIndex, microGoalIndex) => {
setGoals((prevGoals) =>
prevGoals.map((macroGoal, mgi) =>
mgi === macroGoalIndex
? microGoalIndex !== undefined
? {
...macroGoal,
microgoals: macroGoal.microgoals.map((microGoal, mgi) =>
mgi === microGoalIndex
? { ...microGoal, expanded: !microGoal.expanded }
: microGoal,
),
}
: { ...macroGoal, expanded: !macroGoal.expanded }
: macroGoal,
),
);
};
const { user } = useUser();
const { addGoal } = useGoalsUpdater();

return (
<Box sx={{ maxWidth: 800, margin: 'auto', pt: 4 }}>
{loading ? (
<CircularProgress />
) : (
goals.map((macroGoal, macroGoalIndex) => (
<MacroGoal
key={macroGoalIndex} // Use the index as the key
macroGoal={macroGoal}
macroGoalIndex={macroGoalIndex} // Pass the index
onToggleTask={handleToggleTask}
onToggleExpand={handleToggleExpand}
/>
))
)}
<Box sx={{ maxWidth: 800, margin: 'auto', padding: 2 }}>
<AddItem label="New Goal" onAdd={addGoal} />
<Box sx={{ height: 16 }} />
{user.goals.map((macroGoal, index) => (
<MacroGoal key={index} macroGoal={macroGoal} macroGoalIndex={index} />
))}
</Box>
);
}
45 changes: 45 additions & 0 deletions src/components/Home/MacroGoal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import AddItem from '@components/Home/AddItem';
import MicroGoal from '@components/Home/MicroGoal';
import ProgressIndicator from '@components/Home/ProgressIndicator';
import useGoalsUpdater from '@hooks/useGoalsUpdater';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
import { Box, Collapse, Divider, IconButton, List, Paper, Typography } from '@mui/material';
import { calculateProgress } from '@utils/calculateProgress';

const MacroGoal = ({ macroGoal, macroGoalIndex }) => {
const { addMicrogoal, toggleGoalExpansion } = useGoalsUpdater();
const progress = calculateProgress(macroGoal.microgoals);

return (
<Paper elevation={2} sx={{ p: 2, mb: 3, borderRadius: 2 }}>
<Box display="flex" alignItems="center">
<ProgressIndicator value={progress} size={40} thickness={3} />
<Typography variant="h6" sx={{ flexGrow: 1, ml: 2 }}>
{macroGoal.name}
</Typography>
<IconButton onClick={() => toggleGoalExpansion(macroGoalIndex)} size="small">
{macroGoal.expanded ? <ExpandLess /> : <ExpandMore />}
</IconButton>
</Box>
<Collapse in={macroGoal.expanded} timeout="auto" unmountOnExit>
<Divider sx={{ my: 1 }} />
<AddItem
label="New Microgoal"
onAdd={(microGoalName) => addMicrogoal(macroGoalIndex, microGoalName)}
/>
<List>
{macroGoal.microgoals.map((microGoal, microGoalIndex) => (
<MicroGoal
key={microGoalIndex}
microGoal={microGoal}
macroGoalIndex={macroGoalIndex}
microGoalIndex={microGoalIndex}
/>
))}
</List>
</Collapse>
</Paper>
);
};

export default MacroGoal;
46 changes: 46 additions & 0 deletions src/components/Home/MicroGoal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import AddItem from '@components/Home/AddItem';
import ProgressIndicator from '@components/Home/ProgressIndicator';
import Task from '@components/Home/Task';
import useGoalsUpdater from '@hooks/useGoalsUpdater';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
import { Box, Collapse, IconButton, List, Paper, Typography } from '@mui/material';
import { calculateProgress } from '@utils/calculateProgress';

const MicroGoal = ({ microGoal, macroGoalIndex, microGoalIndex }) => {
const { addTask, toggleMicroGoalExpansion, toggleTaskCompletion } = useGoalsUpdater();
const progress = calculateProgress([microGoal]);

return (
<Paper elevation={1} sx={{ p: 1, mb: 2, bgcolor: 'background.default', borderRadius: 1 }}>
<Box display="flex" alignItems="center">
<ProgressIndicator value={progress} size={30} thickness={3} />
<Typography variant="subtitle1" sx={{ flexGrow: 1, ml: 1 }}>
{microGoal.name}
</Typography>
<IconButton
onClick={() => toggleMicroGoalExpansion(macroGoalIndex, microGoalIndex)}
size="small"
>
{microGoal.expanded ? <ExpandLess /> : <ExpandMore />}
</IconButton>
</Box>
<Collapse in={microGoal.expanded} timeout="auto" unmountOnExit>
<List sx={{ pl: 2 }}>
{microGoal.tasks.map((task, taskIndex) => (
<Task
key={taskIndex}
task={task}
onToggle={() => toggleTaskCompletion(macroGoalIndex, microGoalIndex, taskIndex)}
/>
))}
</List>
<AddItem
label="New Task"
onAdd={(taskName) => addTask(macroGoalIndex, microGoalIndex, taskName)}
/>
</Collapse>
</Paper>
);
};

export default MicroGoal;
45 changes: 45 additions & 0 deletions src/components/Home/ProgressIndicator.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Box, CircularProgress, Typography } from '@mui/material';

const ProgressIndicator = ({ value, size = 40, thickness = 4 }) => (
<Box sx={{ position: 'relative', display: 'inline-flex', mr: 2 }}>
<CircularProgress
variant="determinate"
value={value}
size={size}
thickness={thickness}
sx={{
color: (theme) =>
value >= 100
? theme.palette.success.main
: `linear-gradient(to right, ${theme.palette.info.main}, ${theme.palette.warning.main})`,
transition: 'all 0.4s ease-in-out', // Smooth transition effect
}}
/>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography
variant="caption"
component="div"
color={value >= 100 ? 'success.main' : 'text.secondary'}
sx={{
fontWeight: value >= 100 ? 'bold' : 'normal',
fontSize: value >= 100 ? '0.75rem' : 'inherit',
}}
>
{`${Math.round(value)}%`}
</Typography>
</Box>
</Box>
);

export default ProgressIndicator;
Loading

0 comments on commit a137f80

Please sign in to comment.