-
Notifications
You must be signed in to change notification settings - Fork 422
Add emoji support to goals and optimize transaction fetching #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||||||||||||||||||||
| import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons' | ||||||||||||||||||||||||||
| import { faDollarSign, IconDefinition } from '@fortawesome/free-solid-svg-icons' | ||||||||||||||||||||||||||
| import { faDollarSign, IconDefinition, faPlus } from '@fortawesome/free-solid-svg-icons' | ||||||||||||||||||||||||||
| import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' | ||||||||||||||||||||||||||
| import { MaterialUiPickersDate } from '@material-ui/pickers/typings/date' | ||||||||||||||||||||||||||
| import 'date-fns' | ||||||||||||||||||||||||||
|
|
@@ -10,7 +10,9 @@ import { Goal } from '../../../api/types' | |||||||||||||||||||||||||
| import { selectGoalsMap, updateGoal as updateGoalRedux } from '../../../store/goalsSlice' | ||||||||||||||||||||||||||
| import { useAppDispatch, useAppSelector } from '../../../store/hooks' | ||||||||||||||||||||||||||
| import DatePicker from '../../components/DatePicker' | ||||||||||||||||||||||||||
| import EmojiPicker from '../../components/EmojiPicker' | ||||||||||||||||||||||||||
| import { Theme } from '../../components/Theme' | ||||||||||||||||||||||||||
| import { BaseEmoji } from 'emoji-mart' | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| type Props = { goal: Goal } | ||||||||||||||||||||||||||
| export function GoalManager(props: Props) { | ||||||||||||||||||||||||||
|
|
@@ -21,16 +23,20 @@ export function GoalManager(props: Props) { | |||||||||||||||||||||||||
| const [name, setName] = useState<string | null>(null) | ||||||||||||||||||||||||||
| const [targetDate, setTargetDate] = useState<Date | null>(null) | ||||||||||||||||||||||||||
| const [targetAmount, setTargetAmount] = useState<number | null>(null) | ||||||||||||||||||||||||||
| const [icon, setIcon] = useState<string | null>(null) | ||||||||||||||||||||||||||
| const [isPickerOpen, setIsPickerOpen] = useState(false) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||
| setName(props.goal.name) | ||||||||||||||||||||||||||
| setTargetDate(props.goal.targetDate) | ||||||||||||||||||||||||||
| setTargetAmount(props.goal.targetAmount) | ||||||||||||||||||||||||||
| setIcon(props.goal.icon ?? null) | ||||||||||||||||||||||||||
| }, [ | ||||||||||||||||||||||||||
| props.goal.id, | ||||||||||||||||||||||||||
| props.goal.name, | ||||||||||||||||||||||||||
| props.goal.targetDate, | ||||||||||||||||||||||||||
| props.goal.targetAmount, | ||||||||||||||||||||||||||
| props.goal.icon, | ||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||
|
|
@@ -69,15 +75,62 @@ export function GoalManager(props: Props) { | |||||||||||||||||||||||||
| name: name ?? props.goal.name, | ||||||||||||||||||||||||||
| targetDate: date ?? props.goal.targetDate, | ||||||||||||||||||||||||||
| targetAmount: targetAmount ?? props.goal.targetAmount, | ||||||||||||||||||||||||||
| icon: icon ?? props.goal.icon, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| dispatch(updateGoalRedux(updatedGoal)) | ||||||||||||||||||||||||||
| updateGoalApi(props.goal.id, updatedGoal) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const updateIcon = (nextIcon: string) => { | ||||||||||||||||||||||||||
| setIcon(nextIcon) | ||||||||||||||||||||||||||
| const updatedGoal: Goal = { | ||||||||||||||||||||||||||
| ...props.goal, | ||||||||||||||||||||||||||
| name: name ?? props.goal.name, | ||||||||||||||||||||||||||
| targetDate: targetDate ?? props.goal.targetDate, | ||||||||||||||||||||||||||
| targetAmount: targetAmount ?? props.goal.targetAmount, | ||||||||||||||||||||||||||
| icon: nextIcon, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| dispatch(updateGoalRedux(updatedGoal)) | ||||||||||||||||||||||||||
| updateGoalApi(props.goal.id, updatedGoal) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const pickEmojiOnClick = (emoji: BaseEmoji, event: React.MouseEvent) => { | ||||||||||||||||||||||||||
| updateIcon(emoji.native) | ||||||||||||||||||||||||||
| setIsPickerOpen(false) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <GoalManagerContainer> | ||||||||||||||||||||||||||
| <NameInput value={name ?? ''} onChange={updateNameOnChange} /> | ||||||||||||||||||||||||||
| <TopContainer> | ||||||||||||||||||||||||||
| <IconContainer onClick={() => setIsPickerOpen(!isPickerOpen)}> | ||||||||||||||||||||||||||
| {icon ? ( | ||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||
| <IconText>{icon}</IconText> | ||||||||||||||||||||||||||
| <RemoveIcon onClick={(e) => { | ||||||||||||||||||||||||||
| e.stopPropagation(); | ||||||||||||||||||||||||||
| updateIcon(""); | ||||||||||||||||||||||||||
| }}>×</RemoveIcon> | ||||||||||||||||||||||||||
|
Comment on lines
+111
to
+114
|
||||||||||||||||||||||||||
| <RemoveIcon onClick={(e) => { | |
| e.stopPropagation(); | |
| updateIcon(""); | |
| }}>×</RemoveIcon> | |
| <RemoveIcon | |
| onClick={e => { | |
| e.stopPropagation() | |
| updateIcon('') | |
| }} | |
| > | |
| × | |
| </RemoveIcon> |
Copilot
AI
Mar 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an existing GoalIcon component under src/ui/features/goalmanager/GoalIcon.tsx, but this change re-implements icon rendering and click handling inline. Since GoalIcon appears unused now, consider either reusing it here (to keep icon UI consistent) or removing the unused component to avoid dead code.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,17 +17,12 @@ export function TransactionItem(props: Props) { | |||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| async function fetchAll() { | ||||||||||||||||||||
| const tags: Tag[] = [] | ||||||||||||||||||||
| for (const tagId of props.transaction.tagIds) { | ||||||||||||||||||||
| const tag = await fetch(tagId) | ||||||||||||||||||||
| tags.push(tag) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| setTags(tags) | ||||||||||||||||||||
| const fetchedTags = await Promise.all(props.transaction.tagIds.map(fetch)) | ||||||||||||||||||||
| setTags(fetchedTags) | ||||||||||||||||||||
|
Comment on lines
+20
to
+21
|
||||||||||||||||||||
| const fetchedTags = await Promise.all(props.transaction.tagIds.map(fetch)) | |
| setTags(fetchedTags) | |
| try { | |
| const fetchedTags = await Promise.all(props.transaction.tagIds.map(fetch)) | |
| setTags(fetchedTags) | |
| } catch (error) { | |
| console.error('Failed to fetch tags for transaction', props.transaction.id, error) | |
| setTags([]) | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new click-target uses non-interactive elements (
div) for actions (open picker / remove icon). That breaks keyboard navigation and screen reader semantics. Prefer abutton(or the existingTransparentButton) with appropriatearia-labels and focus styles, and keep the remove control as a separate button inside the container.