Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 133 additions & 7 deletions src/2-pages/Budgets/EnvelopeTable/Row/AvailableCell.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,81 @@
import React, { FC } from 'react'
import React, { FC, useCallback, useState } from 'react'
import { useDraggable } from '@dnd-kit/core'
import { Typography, Box } from '@mui/material'
import { Typography, Box, Popover, Stack, IconButton } from '@mui/material'
import { useTranslation } from 'react-i18next'
import { Tooltip } from '6-shared/ui/Tooltip'
import { formatMoney } from '6-shared/helpers/money'
import { WarningIcon } from '6-shared/ui/Icons'
import { WarningIcon, MoreHorizIcon } from '6-shared/ui/Icons'
import { Amount } from '6-shared/ui/Amount'
import { userSettingsModel } from '5-entities/userSettings'
import { DragTypes } from '2-pages/Budgets/DnD'
import { useIsSmall } from '../shared/shared'

type AvailableCellProps = {
id: string
hiddenOverspend?: number
available: number
budgeted: number
activity: number
isChild?: boolean
isSelf?: boolean
onBudgetClick: (e: React.MouseEvent) => void
onActivityClick: (e: React.MouseEvent) => void
}

export const AvailableCell: FC<AvailableCellProps> = props => {
const { hiddenOverspend, id, available, isChild, budgeted, isSelf } = props
const { t } = useTranslation('budgets')
const {
hiddenOverspend,
id,
available,
isChild,
budgeted,
activity,
isSelf,
onBudgetClick,
onActivityClick,
} = props
const { t } = useTranslation(['budgets', 'common'])
const isSmall = useIsSmall()
const { showExtraCellMenu } = userSettingsModel.useUserSettings()
const [popoverAnchor, setPopoverAnchor] = useState<HTMLElement | null>(null)
const showPopoverIcon = isSmall && showExtraCellMenu

const availableColor = getAvailableColor(
available,
isChild,
!!budgeted,
isSelf
)

const handleOpenPopover = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
setPopoverAnchor(e.currentTarget)
},
[]
)

const handleClosePopover = useCallback(() => {
setPopoverAnchor(null)
}, [])

return (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
{showPopoverIcon && (
<IconButton
size="small"
onClick={handleOpenPopover}
sx={{
p: 0.25,
mr: 0.5,
opacity: 0.5,
'&:hover': { opacity: 1 },
}}
>
<MoreHorizIcon fontSize="small" />
</IconButton>
)}

<Typography variant="body1" align="right">
{!!hiddenOverspend && (
<Tooltip
Expand Down Expand Up @@ -57,7 +104,6 @@ export const AvailableCell: FC<AvailableCellProps> = props => {
mx: -2,
py: 0.5,
my: -0.5,
component: 'span',
display: 'inline-block',
color: availableColor,
}}
Expand All @@ -66,6 +112,86 @@ export const AvailableCell: FC<AvailableCellProps> = props => {
</Box>
</DraggableAmount>
</Typography>

<Popover
open={Boolean(popoverAnchor)}
anchorEl={popoverAnchor}
onClose={handleClosePopover}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
slotProps={{
paper: {
sx: {
borderRadius: 2,
p: 1.5,
minWidth: 160,
},
},
}}
>
<Stack spacing={1}>
<Box
onClick={e => {
handleClosePopover()
onBudgetClick(e)
}}
sx={{
display: 'flex',
justifyContent: 'space-between',
gap: 2,
px: 1,
py: 0.5,
borderRadius: 1,
cursor: 'pointer',
'&:hover': { bgcolor: 'action.hover' },
'&:active': { bgcolor: 'action.focus' },
}}
>
<Typography variant="body2" color="text.secondary">
{t('budget', { ns: 'common' })}
</Typography>
<Typography
variant="body2"
sx={{
color: isSelf
? 'text.disabled'
: budgeted
? 'text.primary'
: 'text.disabled',
}}
>
<Amount value={budgeted} decMode="ifOnly" />
</Typography>
</Box>
<Box
onClick={e => {
handleClosePopover()
onActivityClick(e)
}}
sx={{
display: 'flex',
justifyContent: 'space-between',
gap: 2,
px: 1,
py: 0.5,
borderRadius: 1,
cursor: 'pointer',
'&:hover': { bgcolor: 'action.hover' },
'&:active': { bgcolor: 'action.focus' },
}}
>
<Typography variant="body2" color="text.secondary">
{t('activity', { ns: 'common' })}
</Typography>
<Typography
variant="body2"
sx={{ color: activity ? 'text.primary' : 'text.disabled' }}
>
<Amount value={activity} decMode="ifOnly" />
</Typography>
</Box>
</Stack>
</Popover>
</Box>
)
}
Expand Down
3 changes: 3 additions & 0 deletions src/2-pages/Budgets/EnvelopeTable/Row/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ export const Row: FC<EnvelopeRowProps> = props => {
available={available}
isChild={isChild}
budgeted={budgeted}
activity={activity}
isSelf={isSelf}
onBudgetClick={e => openBudgetPopover(id, e.currentTarget)}
onActivityClick={() => openTransactionsPopover(id)}
/>
}
goal={
Expand Down
26 changes: 26 additions & 0 deletions src/3-widgets/Navigation/SettingsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const Settings = (props: { onClose: () => void; showLinks?: boolean }) => {
{isExpanded ? (
<>
<IconModeItem />
<ExtraCellMenuItem />
<BudgetSettingsItem />
</>
) : (
Expand Down Expand Up @@ -290,6 +291,31 @@ function IconModeItem() {
)
}

function ExtraCellMenuItem() {
const { t } = useTranslation('settings')
const dispatch = useAppDispatch()
const { showExtraCellMenu } = userSettingsModel.useUserSettings()
const handleClick = () => {
sendEvent(
`Settings: showExtraCellMenu ${showExtraCellMenu ? 'off' : 'on'}`
)
dispatch(
userSettingsModel.patch({
showExtraCellMenu: !showExtraCellMenu,
})
)
}
return (
<MenuItem onClick={handleClick}>
<ListItemIcon>
<MoreHorizIcon />
</ListItemIcon>
<ListItemText>{t('extraCellMenu')}</ListItemText>
<Switch edge="end" checked={showExtraCellMenu} />
</MenuItem>
)
}

function BudgetSettingsItem() {
const { t } = useTranslation('settings')
const dispatch = useAppDispatch()
Expand Down
4 changes: 4 additions & 0 deletions src/5-entities/userSettings/userSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export type TUserSettings = {

/** Use SVG icons instead of emoji for tags/categories */
emojiIcons: boolean

/** Shows a popover icon on mobile to access hidden budget/activity columns */
showExtraCellMenu: boolean
}
export type TUserSettingsPatch = Partial<TUserSettings>
export type TStoredUserSettings = Partial<TUserSettings>
Expand All @@ -30,6 +33,7 @@ export const getUserSettings: TSelector<TUserSettings> = createSelector(
sawMigrationAlert: raw.sawMigrationAlert ?? false,
preferZmBudgets: raw.preferZmBudgets ?? false,
emojiIcons: raw.emojiIcons ?? false,
showExtraCellMenu: raw.showExtraCellMenu ?? false,
})
)

Expand Down
1 change: 1 addition & 0 deletions src/6-shared/localization/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ export const en: typeof ru = {
regularSync: 'Auto-sync',
useIcons: 'Use icons',
useEmojis: 'Use emojis',
extraCellMenu: 'Сell menu for small screens',
useZmBudgets: 'Zenmoney budgets',
useZmBudgetsDescription: 'Use the same budgets as Zenmoney',
convertBudgetsFromZm: 'Convert budgets from Zenmoney',
Expand Down
1 change: 1 addition & 0 deletions src/6-shared/localization/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@
"regularSync": "Автосинхронизация",
"useIcons": "Использовать иконки",
"useEmojis": "Использовать эмодзи",
"extraCellMenu": "Mеню ячеек для маленьких экранов",
"useZmBudgets": "Бюджеты Дзен-мани",
"useZmBudgetsDescription": "Использовать те же бюджеты что и ДМ",
"convertBudgetsFromZm": "Конвертировать бюджеты из Дзен-мани",
Expand Down