From 5369494925433f90d4ee35ef5e9a568db7d0de2e Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Mon, 7 Oct 2024 21:07:22 +0100 Subject: [PATCH] :sparkles: (dashboards) ability to quick-edit widget names from inner report pages (#3587) --- .../components/EditablePageHeaderTitle.tsx | 89 +++++++ .../src/components/reports/Header.tsx | 234 +++++++++--------- .../components/reports/reports/CashFlow.tsx | 31 ++- .../components/reports/reports/NetWorth.tsx | 30 ++- .../components/reports/reports/Spending.tsx | 30 ++- packages/loot-core/src/server/reports/app.ts | 4 +- upcoming-release-notes/3587.md | 6 + 7 files changed, 297 insertions(+), 127 deletions(-) create mode 100644 packages/desktop-client/src/components/EditablePageHeaderTitle.tsx create mode 100644 upcoming-release-notes/3587.md diff --git a/packages/desktop-client/src/components/EditablePageHeaderTitle.tsx b/packages/desktop-client/src/components/EditablePageHeaderTitle.tsx new file mode 100644 index 00000000000..0552bb4bb33 --- /dev/null +++ b/packages/desktop-client/src/components/EditablePageHeaderTitle.tsx @@ -0,0 +1,89 @@ +import React, { useCallback, useEffect, useState } from 'react'; + +import { SvgPencil1 } from '../icons/v2'; +import { theme } from '../style'; + +import { Button } from './common/Button2'; +import { InitialFocus } from './common/InitialFocus'; +import { Input } from './common/Input'; +import { View } from './common/View'; + +type EditablePageHeaderTitleProps = { + title: string; + onSave: (newValue: string) => void; +}; + +export function EditablePageHeaderTitle({ + title: initialTitle, + onSave, +}: EditablePageHeaderTitleProps) { + const [isEditing, setIsEditing] = useState(false); + const [title, setTitle] = useState(initialTitle); + + useEffect(() => setTitle(initialTitle), [initialTitle]); + + const onSaveValue = useCallback( + (newValue: string) => { + onSave(newValue); + setTitle(newValue); + setIsEditing(false); + }, + [onSave], + ); + + if (isEditing) { + return ( + + onSaveValue(e.currentTarget.value)} + onBlur={e => onSaveValue(e.target.value)} + onEscape={() => setIsEditing(false)} + style={{ + fontSize: 25, + fontWeight: 500, + marginTop: -3, + marginBottom: -3, + marginLeft: -6, + paddingTop: 2, + paddingBottom: 2, + width: Math.max(20, title.length) + 'ch', + }} + /> + + ); + } + + return ( + + {title} + + + + ); +} diff --git a/packages/desktop-client/src/components/reports/Header.tsx b/packages/desktop-client/src/components/reports/Header.tsx index d9322c22735..7c4705976a1 100644 --- a/packages/desktop-client/src/components/reports/Header.tsx +++ b/packages/desktop-client/src/components/reports/Header.tsx @@ -1,5 +1,4 @@ import { type ComponentProps, type ReactNode } from 'react'; -import { useLocation } from 'react-router-dom'; import * as monthUtils from 'loot-core/src/shared/months'; import { @@ -61,8 +60,6 @@ export function Header({ children, }: HeaderProps) { const isDashboardsFeatureEnabled = useFeatureFlag('dashboards'); - const location = useLocation(); - const path = location.pathname; const { isNarrowWidth } = useResponsive(); return ( @@ -73,141 +70,140 @@ export function Header({ flexShrink: 0, }} > - {!['/reports/custom'].includes(path) && ( + + {isDashboardsFeatureEnabled && mode && ( + + )} + - {isDashboardsFeatureEnabled && mode && ( - )} - - onChangeDates(...getLatestRange(2))} > - - onChangeDates( - ...validateEnd( - allMonths[allMonths.length - 1].name, - start, - newValue, - ), - ) - } - value={end} - options={allMonths.map(({ name, pretty }) => [name, pretty])} - style={{ marginRight: 10 }} + 3 months + + + + + + {filters && ( + - + )} + + {children ? ( - {show1Month && ( - - )} - - - - - - {filters && ( - - )} + {children} + ) : ( + + )} + - {children ? ( - - {children} - - ) : ( - - )} - - )} {filters && filters.length > 0 && ( { + if (!widget) { + throw new Error('No widget that could be saved.'); + } + + const name = newName || t('Cash Flow'); + await send('dashboard-update-widget', { + id: widget.id, + meta: { + ...(widget.meta ?? {}), + name, + }, + }); + }; + if (!allMonths || !data) { return null; } const { graphData, totalExpenses, totalIncome, totalTransfers } = data; - const title = widget?.meta?.name ?? t('Cash Flow'); return ( ) : ( - + + ) : ( + title + ) + } + /> ) } padding={0} diff --git a/packages/desktop-client/src/components/reports/reports/NetWorth.tsx b/packages/desktop-client/src/components/reports/reports/NetWorth.tsx index 5a75a58b954..88e18302144 100644 --- a/packages/desktop-client/src/components/reports/reports/NetWorth.tsx +++ b/packages/desktop-client/src/components/reports/reports/NetWorth.tsx @@ -20,6 +20,7 @@ import { theme, styles } from '../../../style'; import { Button } from '../../common/Button2'; import { Paragraph } from '../../common/Paragraph'; import { View } from '../../common/View'; +import { EditablePageHeaderTitle } from '../../EditablePageHeaderTitle'; import { MobileBackButton } from '../../mobile/MobileBackButton'; import { MobilePageHeader, Page, PageHeader } from '../../Page'; import { PrivacyFilter } from '../../PrivacyFilter'; @@ -145,7 +146,21 @@ function NetWorthInner({ widget }: NetWorthInnerProps) { const navigate = useNavigate(); const { isNarrowWidth } = useResponsive(); - const title = widget?.meta?.name ?? t('Net Worth'); + const title = widget?.meta?.name || t('Net Worth'); + const onSaveWidgetName = async (newName: string) => { + if (!widget) { + throw new Error('No widget that could be saved.'); + } + + const name = newName || t('Net Worth'); + await send('dashboard-update-widget', { + id: widget.id, + meta: { + ...(widget.meta ?? {}), + name, + }, + }); + }; if (!allMonths || !data) { return null; @@ -162,7 +177,18 @@ function NetWorthInner({ widget }: NetWorthInnerProps) { } /> ) : ( - + + ) : ( + title + ) + } + /> ) } padding={0} diff --git a/packages/desktop-client/src/components/reports/reports/Spending.tsx b/packages/desktop-client/src/components/reports/reports/Spending.tsx index a04753b917b..8ccd573a197 100644 --- a/packages/desktop-client/src/components/reports/reports/Spending.tsx +++ b/packages/desktop-client/src/components/reports/reports/Spending.tsx @@ -26,6 +26,7 @@ import { Select } from '../../common/Select'; import { Text } from '../../common/Text'; import { Tooltip } from '../../common/Tooltip'; import { View } from '../../common/View'; +import { EditablePageHeaderTitle } from '../../EditablePageHeaderTitle'; import { AppliedFilters } from '../../filters/AppliedFilters'; import { FilterButton } from '../../filters/FiltersMenu'; import { MobileBackButton } from '../../mobile/MobileBackButton'; @@ -181,7 +182,21 @@ function SpendingInternal({ widget }: SpendingInternalProps) { compare === monthUtils.currentMonth() || Math.abs(data.intervalData[27].compare) > 0; - const title = widget?.meta?.name ?? t('Monthly Spending'); + const title = widget?.meta?.name || t('Monthly Spending'); + const onSaveWidgetName = async (newName: string) => { + if (!widget) { + throw new Error('No widget that could be saved.'); + } + + const name = newName || t('Monthly Spending'); + await send('dashboard-update-widget', { + id: widget.id, + meta: { + ...(widget.meta ?? {}), + name, + }, + }); + }; return ( ) : ( - + + ) : ( + title + ) + } + /> ) } padding={0} diff --git a/packages/loot-core/src/server/reports/app.ts b/packages/loot-core/src/server/reports/app.ts index faa86b1bae0..889432c1451 100644 --- a/packages/loot-core/src/server/reports/app.ts +++ b/packages/loot-core/src/server/reports/app.ts @@ -120,7 +120,7 @@ async function createReport(report: CustomReportEntity) { const nameExists = await reportNameExists(item.name, item.id ?? '', true); if (nameExists) { - throw new Error('There is already a filter named ' + item.name); + throw new Error('There is already a report named ' + item.name); } // Create the report here based on the info @@ -140,7 +140,7 @@ async function updateReport(item: CustomReportEntity) { const nameExists = await reportNameExists(item.name, item.id, false); if (nameExists) { - throw new Error('There is already a filter named ' + item.name); + throw new Error('There is already a report named ' + item.name); } await db.updateWithSchema('custom_reports', reportModel.fromJS(item)); diff --git a/upcoming-release-notes/3587.md b/upcoming-release-notes/3587.md new file mode 100644 index 00000000000..c2854a83a80 --- /dev/null +++ b/upcoming-release-notes/3587.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [MatissJanis] +--- + +Dashboards: ability to quick-edit widget names from inner report pages.