Skip to content
Snippets Groups Projects
Unverified Commit b347f03f authored by Matiss Janis Aboltins's avatar Matiss Janis Aboltins Committed by GitHub
Browse files

:sparkles: (dashboards) ability to rename all widgets (#3284)

parent f3660c16
No related branches found
No related tags found
No related merge requests found
......@@ -46,10 +46,9 @@ function isCustomReportWidget(widget: Widget): widget is CustomReportWidget {
return widget.type === 'custom-report';
}
function useWidgetLayout(widgets: Widget[]): (Layout & {
type: Widget['type'];
meta: Widget['meta'];
})[] {
type LayoutWidget = Layout & Pick<Widget, 'type' | 'meta'>;
function useWidgetLayout(widgets: Widget[]): LayoutWidget[] {
return widgets.map(widget => ({
i: widget.id,
type: widget.type,
......@@ -290,6 +289,16 @@ export function Overview() {
);
};
const onMetaChange = <T extends LayoutWidget>(
widget: T,
newMeta: T['meta'],
) => {
send('dashboard-update-widget', {
id: widget.i,
meta: newMeta,
});
};
const accounts = useAccounts();
if (isLoading) {
......@@ -494,16 +503,22 @@ export function Overview() {
<NetWorthCard
isEditing={isEditing}
accounts={accounts}
meta={item.meta && 'name' in item.meta ? item.meta : {}}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
/>
) : item.type === 'cash-flow-card' ? (
<CashFlowCard
isEditing={isEditing}
meta={item.meta && 'name' in item.meta ? item.meta : {}}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
/>
) : item.type === 'spending-card' ? (
<SpendingCard
isEditing={isEditing}
meta={item.meta && 'name' in item.meta ? item.meta : {}}
onMetaChange={newMeta => onMetaChange(item, newMeta)}
onRemove={() => onRemoveWidget(item.i)}
/>
) : item.type === 'custom-report' ? (
......
import React from 'react';
import { styles } from '../../style';
import { Block } from '../common/Block';
import { InitialFocus } from '../common/InitialFocus';
import { Input } from '../common/Input';
import { NON_DRAGGABLE_AREA_CLASS_NAME } from './constants';
type ReportCardNameProps = {
name: string;
isEditing: boolean;
onChange: (newName: string) => void;
onClose: () => void;
};
export const ReportCardName = ({
name,
isEditing,
onChange,
onClose,
}: ReportCardNameProps) => {
if (isEditing) {
return (
<InitialFocus>
<Input
className={NON_DRAGGABLE_AREA_CLASS_NAME}
defaultValue={name}
onEnter={e => onChange(e.currentTarget.value)}
onUpdate={onChange}
onEscape={onClose}
style={{
fontSize: 15,
fontWeight: 500,
marginTop: -6,
marginBottom: -1,
marginLeft: -6,
width: Math.max(20, name.length) + 'ch',
}}
/>
</InitialFocus>
);
}
return (
<Block
style={{
...styles.mediumText,
fontWeight: 500,
marginBottom: 5,
}}
role="heading"
>
{name}
</Block>
);
};
......@@ -5,9 +5,9 @@ import { Bar, BarChart, LabelList, ResponsiveContainer } from 'recharts';
import * as monthUtils from 'loot-core/src/shared/months';
import { integerToCurrency } from 'loot-core/src/shared/util';
import { type CashFlowWidget } from 'loot-core/src/types/models';
import { theme, styles } from '../../../style';
import { Block } from '../../common/Block';
import { theme } from '../../../style';
import { View } from '../../common/View';
import { PrivacyFilter } from '../../PrivacyFilter';
import { Change } from '../Change';
......@@ -16,6 +16,7 @@ import { Container } from '../Container';
import { DateRange } from '../DateRange';
import { LoadingIndicator } from '../LoadingIndicator';
import { ReportCard } from '../ReportCard';
import { ReportCardName } from '../ReportCardName';
import { simpleCashFlow } from '../spreadsheets/cash-flow-spreadsheet';
import { useReport } from '../useReport';
......@@ -81,14 +82,23 @@ function CustomLabel({
type CashFlowCardProps = {
isEditing?: boolean;
meta?: CashFlowWidget['meta'];
onMetaChange: (newMeta: CashFlowWidget['meta']) => void;
onRemove: () => void;
};
export function CashFlowCard({ isEditing, onRemove }: CashFlowCardProps) {
export function CashFlowCard({
isEditing,
meta,
onMetaChange,
onRemove,
}: CashFlowCardProps) {
const { t } = useTranslation();
const end = monthUtils.currentDay();
const start = monthUtils.currentMonth() + '-01';
const [nameMenuOpen, setNameMenuOpen] = useState(false);
const params = useMemo(() => simpleCashFlow(start, end), [start, end]);
const data = useReport('cash_flow_simple', params);
......@@ -105,6 +115,10 @@ export function CashFlowCard({ isEditing, onRemove }: CashFlowCardProps) {
isEditing={isEditing}
to="/reports/cash-flow"
menuItems={[
{
name: 'rename',
text: t('Rename'),
},
{
name: 'remove',
text: t('Remove'),
......@@ -112,6 +126,9 @@ export function CashFlowCard({ isEditing, onRemove }: CashFlowCardProps) {
]}
onMenuSelect={item => {
switch (item) {
case 'rename':
setNameMenuOpen(true);
break;
case 'remove':
onRemove();
break;
......@@ -127,12 +144,18 @@ export function CashFlowCard({ isEditing, onRemove }: CashFlowCardProps) {
>
<View style={{ flexDirection: 'row', padding: 20 }}>
<View style={{ flex: 1 }}>
<Block
style={{ ...styles.mediumText, fontWeight: 500, marginBottom: 5 }}
role="heading"
>
Cash Flow
</Block>
<ReportCardName
name={meta?.name || t('Cash Flow')}
isEditing={nameMenuOpen}
onChange={newName => {
onMetaChange({
...meta,
name: newName,
});
setNameMenuOpen(false);
}}
onClose={() => setNameMenuOpen(false)}
/>
<DateRange start={start} end={end} />
</View>
{data && (
......
......@@ -14,15 +14,12 @@ import { useSyncedPref } from '../../../hooks/useSyncedPref';
import { SvgExclamationSolid } from '../../../icons/v1';
import { styles } from '../../../style/index';
import { theme } from '../../../style/theme';
import { Block } from '../../common/Block';
import { InitialFocus } from '../../common/InitialFocus';
import { Input } from '../../common/Input';
import { Text } from '../../common/Text';
import { Tooltip } from '../../common/Tooltip';
import { View } from '../../common/View';
import { NON_DRAGGABLE_AREA_CLASS_NAME } from '../constants';
import { DateRange } from '../DateRange';
import { ReportCard } from '../ReportCard';
import { ReportCardName } from '../ReportCardName';
import { GetCardData } from './GetCardData';
import { MissingReportCard } from './MissingReportCard';
......@@ -146,38 +143,12 @@ function CustomReportListCardsInner({
}}
>
<View style={{ flex: 1 }}>
{nameMenuOpen ? (
<InitialFocus>
<Input
className={NON_DRAGGABLE_AREA_CLASS_NAME}
defaultValue={report.name}
onEnter={e =>
onSaveName((e.target as HTMLInputElement).value)
}
onBlur={e => onSaveName(e.target.value)}
onEscape={() => setNameMenuOpen(false)}
style={{
fontSize: 15,
fontWeight: 500,
marginTop: -6,
marginBottom: -1,
marginLeft: -6,
width: Math.max(20, report.name.length) + 'ch',
}}
/>
</InitialFocus>
) : (
<Block
style={{
...styles.mediumText,
fontWeight: 500,
marginBottom: 5,
}}
role="heading"
>
{report.name}
</Block>
)}
<ReportCardName
name={report.name}
isEditing={nameMenuOpen}
onChange={onSaveName}
onClose={() => setNameMenuOpen(false)}
/>
{report.isDateStatic ? (
<DateRange start={report.startDate} end={report.endDate} />
) : (
......
......@@ -3,7 +3,10 @@ import { useTranslation } from 'react-i18next';
import * as monthUtils from 'loot-core/src/shared/months';
import { integerToCurrency } from 'loot-core/src/shared/util';
import { type AccountEntity } from 'loot-core/src/types/models';
import {
type AccountEntity,
type NetWorthWidget,
} from 'loot-core/src/types/models';
import { useResponsive } from '../../../ResponsiveProvider';
import { styles } from '../../../style';
......@@ -15,23 +18,30 @@ import { DateRange } from '../DateRange';
import { NetWorthGraph } from '../graphs/NetWorthGraph';
import { LoadingIndicator } from '../LoadingIndicator';
import { ReportCard } from '../ReportCard';
import { ReportCardName } from '../ReportCardName';
import { createSpreadsheet as netWorthSpreadsheet } from '../spreadsheets/net-worth-spreadsheet';
import { useReport } from '../useReport';
type NetWorthCardProps = {
isEditing?: boolean;
accounts: AccountEntity[];
meta?: NetWorthWidget['meta'];
onMetaChange: (newMeta: NetWorthWidget['meta']) => void;
onRemove: () => void;
};
export function NetWorthCard({
isEditing,
accounts,
meta = {},
onMetaChange,
onRemove,
}: NetWorthCardProps) {
const { t } = useTranslation();
const { isNarrowWidth } = useResponsive();
const [nameMenuOpen, setNameMenuOpen] = useState(false);
const end = monthUtils.currentMonth();
const start = monthUtils.subMonths(end, 5);
const [isCardHovered, setIsCardHovered] = useState(false);
......@@ -49,6 +59,10 @@ export function NetWorthCard({
isEditing={isEditing}
to="/reports/net-worth"
menuItems={[
{
name: 'rename',
text: t('Rename'),
},
{
name: 'remove',
text: t('Remove'),
......@@ -56,6 +70,9 @@ export function NetWorthCard({
]}
onMenuSelect={item => {
switch (item) {
case 'rename':
setNameMenuOpen(true);
break;
case 'remove':
onRemove();
break;
......@@ -71,12 +88,18 @@ export function NetWorthCard({
>
<View style={{ flexDirection: 'row', padding: 20 }}>
<View style={{ flex: 1 }}>
<Block
style={{ ...styles.mediumText, fontWeight: 500, marginBottom: 5 }}
role="heading"
>
Net Worth
</Block>
<ReportCardName
name={meta?.name || t('Net Worth')}
isEditing={nameMenuOpen}
onChange={newName => {
onMetaChange({
...meta,
name: newName,
});
setNameMenuOpen(false);
}}
onClose={() => setNameMenuOpen(false)}
/>
<DateRange start={start} end={end} />
</View>
{data && (
......
......@@ -3,6 +3,7 @@ import { Trans, useTranslation } from 'react-i18next';
import * as monthUtils from 'loot-core/src/shared/months';
import { amountToCurrency } from 'loot-core/src/shared/util';
import { type SpendingWidget } from 'loot-core/src/types/models';
import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { useLocalPref } from '../../../hooks/useLocalPref';
......@@ -15,6 +16,7 @@ import { DateRange } from '../DateRange';
import { SpendingGraph } from '../graphs/SpendingGraph';
import { LoadingIndicator } from '../LoadingIndicator';
import { ReportCard } from '../ReportCard';
import { ReportCardName } from '../ReportCardName';
import { createSpendingSpreadsheet } from '../spreadsheets/spending-spreadsheet';
import { useReport } from '../useReport';
......@@ -22,10 +24,17 @@ import { MissingReportCard } from './MissingReportCard';
type SpendingCardProps = {
isEditing?: boolean;
meta?: SpendingWidget['meta'];
onMetaChange: (newMeta: SpendingWidget['meta']) => void;
onRemove: () => void;
};
export function SpendingCard({ isEditing, onRemove }: SpendingCardProps) {
export function SpendingCard({
isEditing,
meta,
onMetaChange,
onRemove,
}: SpendingCardProps) {
const { t } = useTranslation();
const [isCardHovered, setIsCardHovered] = useState(false);
......@@ -35,6 +44,8 @@ export function SpendingCard({ isEditing, onRemove }: SpendingCardProps) {
'spendingReportCompare',
);
const [nameMenuOpen, setNameMenuOpen] = useState(false);
const parseFilter = spendingReportFilter && JSON.parse(spendingReportFilter);
const getGraphData = useMemo(() => {
return createSpendingSpreadsheet({
......@@ -73,6 +84,10 @@ export function SpendingCard({ isEditing, onRemove }: SpendingCardProps) {
isEditing={isEditing}
to="/reports/spending"
menuItems={[
{
name: 'rename',
text: t('Rename'),
},
{
name: 'remove',
text: t('Remove'),
......@@ -80,6 +95,9 @@ export function SpendingCard({ isEditing, onRemove }: SpendingCardProps) {
]}
onMenuSelect={item => {
switch (item) {
case 'rename':
setNameMenuOpen(true);
break;
case 'remove':
onRemove();
break;
......@@ -95,12 +113,18 @@ export function SpendingCard({ isEditing, onRemove }: SpendingCardProps) {
>
<View style={{ flexDirection: 'row', padding: 20 }}>
<View style={{ flex: 1 }}>
<Block
style={{ ...styles.mediumText, fontWeight: 500, marginBottom: 5 }}
role="heading"
>
Monthly Spending
</Block>
<ReportCardName
name={meta?.name || t('Monthly Spending')}
isEditing={nameMenuOpen}
onChange={newName => {
onMetaChange({
...meta,
name: newName,
});
setNameMenuOpen(false);
}}
onClose={() => setNameMenuOpen(false)}
/>
<DateRange
start={monthUtils.addMonths(monthUtils.currentMonth(), 1)}
end={monthUtils.addMonths(monthUtils.currentMonth(), 1)}
......
......@@ -117,7 +117,7 @@ async function updateDashboard(
async function updateDashboardWidget(
widget: EverythingButIdOptional<Omit<Widget, 'tombstone'>>,
) {
await db.update('dashboard', widget);
await db.updateWithSchema('dashboard', widget);
}
async function resetDashboard() {
......
......@@ -14,9 +14,18 @@ type AbstractWidget<
tombstone: boolean;
};
type NetWorthWidget = AbstractWidget<'net-worth-card'>;
type CashFlowWidget = AbstractWidget<'cash-flow-card'>;
type SpendingWidget = AbstractWidget<'spending-card'>;
export type NetWorthWidget = AbstractWidget<
'net-worth-card',
{ name?: string } | null
>;
export type CashFlowWidget = AbstractWidget<
'cash-flow-card',
{ name?: string } | null
>;
export type SpendingWidget = AbstractWidget<
'spending-card',
{ name?: string } | null
>;
export type CustomReportWidget = AbstractWidget<
'custom-report',
{ id: string }
......
---
category: Enhancements
authors: [Matissjanis]
---
Dashboards: ability to rename all the widgets.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment