mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
Custom Reports AutoComplete (#2350)
* updated saved work * merge fixes * Disable CREATE TABLE * notes * turn on db table * Fix TableGraph recall crash * table format changes * type fixes * fixing some card displays * merge fixes * revert table change * cardMenu width * Add Saved Reports Autocomplete * notes * fix invalid values crash * Title and auto-focus and esc * notes * fix filtering logic * reload saved filters * lint fix * visual graph changes * merge fixes * fix * review updates
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import React, { type ComponentProps } from 'react';
|
||||
|
||||
import { useReports } from 'loot-core/client/data-hooks/reports';
|
||||
import { type CustomReportEntity } from 'loot-core/src/types/models/reports';
|
||||
|
||||
import { Autocomplete } from './Autocomplete';
|
||||
import { ReportList } from './ReportList';
|
||||
|
||||
export function ReportAutocomplete({
|
||||
embedded,
|
||||
...props
|
||||
}: {
|
||||
embedded?: boolean;
|
||||
} & ComponentProps<typeof Autocomplete<CustomReportEntity>>) {
|
||||
const reports = useReports() || [];
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
strict={true}
|
||||
highlightFirst={true}
|
||||
embedded={embedded}
|
||||
suggestions={reports}
|
||||
renderItems={(items, getItemProps, highlightedIndex) => (
|
||||
<ReportList
|
||||
items={items}
|
||||
getItemProps={getItemProps}
|
||||
highlightedIndex={highlightedIndex}
|
||||
embedded={embedded}
|
||||
/>
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import React, { Fragment, type ComponentProps } from 'react';
|
||||
|
||||
import { theme } from '../../style/theme';
|
||||
import { View } from '../common/View';
|
||||
|
||||
import { ItemHeader } from './ItemHeader';
|
||||
|
||||
export function ReportList<T extends { id: string; name: string }>({
|
||||
items,
|
||||
getItemProps,
|
||||
highlightedIndex,
|
||||
embedded,
|
||||
}: {
|
||||
items: T[];
|
||||
getItemProps: (arg: { item: T }) => ComponentProps<typeof View>;
|
||||
highlightedIndex: number;
|
||||
embedded?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<View>
|
||||
<View
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
padding: '5px 0',
|
||||
...(!embedded && { maxHeight: 175 }),
|
||||
}}
|
||||
>
|
||||
<Fragment>{ItemHeader({ title: 'Saved Reports' })}</Fragment>
|
||||
{items.map((item, idx) => {
|
||||
return [
|
||||
<div
|
||||
{...(getItemProps ? getItemProps({ item }) : null)}
|
||||
key={item.id}
|
||||
style={{
|
||||
backgroundColor:
|
||||
highlightedIndex === idx
|
||||
? theme.menuAutoCompleteBackgroundHover
|
||||
: 'transparent',
|
||||
padding: 4,
|
||||
paddingLeft: 20,
|
||||
borderRadius: embedded ? 4 : 0,
|
||||
}}
|
||||
data-highlighted={highlightedIndex === idx || undefined}
|
||||
>
|
||||
{item.name}
|
||||
</div>,
|
||||
];
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -38,11 +38,11 @@ type MenuItem = {
|
||||
tooltip?: string;
|
||||
};
|
||||
|
||||
type MenuProps<T extends MenuItem = MenuItem> = {
|
||||
export type MenuProps<T extends MenuItem = MenuItem> = {
|
||||
header?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
items: Array<T | typeof Menu.line>;
|
||||
onMenuSelect: (itemName: T['name']) => void;
|
||||
onMenuSelect?: (itemName: T['name']) => void;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ const startDate = monthUtils.subMonths(monthUtils.currentMonth(), 5);
|
||||
const endDate = monthUtils.currentMonth();
|
||||
|
||||
export const defaultReport: CustomReportEntity = {
|
||||
id: '',
|
||||
name: '',
|
||||
startDate,
|
||||
endDate,
|
||||
isDateStatic: false,
|
||||
|
||||
@@ -30,7 +30,6 @@ export function ReportTopbar({
|
||||
onApplyFilter,
|
||||
onChangeViews,
|
||||
onReportChange,
|
||||
onResetReports,
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
@@ -179,7 +178,6 @@ export function ReportTopbar({
|
||||
report={report}
|
||||
savedStatus={savedStatus}
|
||||
onReportChange={onReportChange}
|
||||
onResetReports={onResetReports}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { createRef, useState } from 'react';
|
||||
|
||||
import { useReports } from 'loot-core/client/data-hooks/reports';
|
||||
import { send, sendCatch } from 'loot-core/src/platform/client/fetch';
|
||||
import { type CustomReportEntity } from 'loot-core/src/types/models';
|
||||
|
||||
@@ -8,6 +9,7 @@ import { Button } from '../common/Button';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
|
||||
import { SaveReportChoose } from './SaveReportChoose';
|
||||
import { SaveReportMenu } from './SaveReportMenu';
|
||||
import { SaveReportName } from './SaveReportName';
|
||||
|
||||
@@ -22,7 +24,6 @@ type SaveReportProps<T extends CustomReportEntity = CustomReportEntity> = {
|
||||
savedReport?: T;
|
||||
type: string;
|
||||
}) => void;
|
||||
onResetReports: () => void;
|
||||
};
|
||||
|
||||
export function SaveReport({
|
||||
@@ -30,15 +31,23 @@ export function SaveReport({
|
||||
report,
|
||||
savedStatus,
|
||||
onReportChange,
|
||||
onResetReports,
|
||||
}: SaveReportProps) {
|
||||
const listReports = useReports();
|
||||
const [nameMenuOpen, setNameMenuOpen] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [chooseMenuOpen, setChooseMenuOpen] = useState(false);
|
||||
const [menuItem, setMenuItem] = useState('');
|
||||
const [err, setErr] = useState('');
|
||||
const [name, setName] = useState(report.name ?? '');
|
||||
const inputRef = createRef<HTMLInputElement>();
|
||||
|
||||
async function onApply(cond: string) {
|
||||
const chooseSavedReport = listReports.find(r => cond === r.id);
|
||||
onReportChange({ savedReport: chooseSavedReport, type: 'choose' });
|
||||
setChooseMenuOpen(false);
|
||||
setName(chooseSavedReport === undefined ? '' : chooseSavedReport.name);
|
||||
}
|
||||
|
||||
const onAddUpdate = async (menuChoice: string) => {
|
||||
if (menuChoice === 'save-report') {
|
||||
const newSavedReport = {
|
||||
@@ -78,7 +87,6 @@ export function SaveReport({
|
||||
setNameMenuOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setNameMenuOpen(false);
|
||||
onReportChange({
|
||||
savedReport: updatedReport,
|
||||
@@ -98,7 +106,7 @@ export function SaveReport({
|
||||
setMenuOpen(false);
|
||||
setName('');
|
||||
await send('report/delete', report.id);
|
||||
onResetReports();
|
||||
onReportChange({ type: 'reset' });
|
||||
break;
|
||||
case 'update-report':
|
||||
setErr('');
|
||||
@@ -117,7 +125,12 @@ export function SaveReport({
|
||||
case 'reset-report':
|
||||
setMenuOpen(false);
|
||||
setName('');
|
||||
onResetReports();
|
||||
onReportChange({ type: 'reset' });
|
||||
break;
|
||||
case 'choose-report':
|
||||
setErr('');
|
||||
setMenuOpen(false);
|
||||
setChooseMenuOpen(true);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -153,9 +166,9 @@ export function SaveReport({
|
||||
{menuOpen && (
|
||||
<SaveReportMenu
|
||||
onClose={() => setMenuOpen(false)}
|
||||
report={report}
|
||||
onMenuSelect={onMenuSelect}
|
||||
savedStatus={savedStatus}
|
||||
listReports={listReports && listReports.length}
|
||||
/>
|
||||
)}
|
||||
{nameMenuOpen && (
|
||||
@@ -169,6 +182,12 @@ export function SaveReport({
|
||||
err={err}
|
||||
/>
|
||||
)}
|
||||
{chooseMenuOpen && (
|
||||
<SaveReportChoose
|
||||
onApply={onApply}
|
||||
onClose={() => setChooseMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import React, { createRef, useEffect, useState } from 'react';
|
||||
|
||||
import { theme } from '../../style/theme';
|
||||
import { Button } from '../common/Button';
|
||||
import { Stack } from '../common/Stack';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { Tooltip } from '../tooltips';
|
||||
import { GenericInput } from '../util/GenericInput';
|
||||
|
||||
type SaveReportChooseProps = {
|
||||
onApply: (cond: string) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function SaveReportChoose({ onApply, onClose }: SaveReportChooseProps) {
|
||||
const inputRef = createRef<HTMLInputElement>();
|
||||
const [err, setErr] = useState('');
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
position="bottom-right"
|
||||
style={{ padding: 15, color: theme.menuItemText }}
|
||||
width={275}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form>
|
||||
<View style={{ flexDirection: 'row', align: 'center' }}>
|
||||
<Text style={{ userSelect: 'none', flex: 1 }}>Choose Report</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
</View>
|
||||
<GenericInput
|
||||
inputRef={inputRef}
|
||||
field="report"
|
||||
subfield={null}
|
||||
type="saved"
|
||||
value={value}
|
||||
multi={false}
|
||||
style={{ marginTop: 10 }}
|
||||
onChange={(v: string) => setValue(v)}
|
||||
/>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
justify="flex-end"
|
||||
align="center"
|
||||
style={{ marginTop: 15 }}
|
||||
>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
if (!value) {
|
||||
setErr('Invalid report entered');
|
||||
return;
|
||||
}
|
||||
|
||||
onApply(value);
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
{err !== '' ? (
|
||||
<Stack direction="row" align="center" style={{ padding: 10 }}>
|
||||
<Text style={{ color: theme.errorText }}>{err}</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<View />
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -1,75 +1,83 @@
|
||||
import React from 'react';
|
||||
|
||||
import { type CustomReportEntity } from 'loot-core/src/types/models';
|
||||
|
||||
import { Menu } from '../common/Menu';
|
||||
import { Menu, type MenuProps } from '../common/Menu';
|
||||
import { MenuTooltip } from '../common/MenuTooltip';
|
||||
|
||||
export function SaveReportMenu({
|
||||
report,
|
||||
onClose,
|
||||
onMenuSelect,
|
||||
savedStatus,
|
||||
listReports,
|
||||
}: {
|
||||
report: CustomReportEntity;
|
||||
onClose: () => void;
|
||||
onMenuSelect: (item: string) => void;
|
||||
savedStatus: string;
|
||||
listReports: number;
|
||||
}) {
|
||||
const savedMenu: MenuProps =
|
||||
savedStatus === 'saved'
|
||||
? {
|
||||
items: [
|
||||
{ name: 'rename-report', text: 'Rename' },
|
||||
{ name: 'delete-report', text: 'Delete' },
|
||||
Menu.line,
|
||||
],
|
||||
}
|
||||
: {
|
||||
items: [],
|
||||
};
|
||||
|
||||
const modifiedMenu: MenuProps =
|
||||
savedStatus === 'modified'
|
||||
? {
|
||||
items: [
|
||||
{ name: 'rename-report', text: 'Rename' },
|
||||
{
|
||||
name: 'update-report',
|
||||
text: 'Update report',
|
||||
},
|
||||
{
|
||||
name: 'reload-report',
|
||||
text: 'Revert changes',
|
||||
},
|
||||
{ name: 'delete-report', text: 'Delete' },
|
||||
Menu.line,
|
||||
],
|
||||
}
|
||||
: {
|
||||
items: [],
|
||||
};
|
||||
|
||||
const unsavedMenu: MenuProps = {
|
||||
items: [
|
||||
{
|
||||
name: 'save-report',
|
||||
text: 'Save new report',
|
||||
},
|
||||
{
|
||||
name: 'reset-report',
|
||||
text: 'Reset to default',
|
||||
},
|
||||
Menu.line,
|
||||
{
|
||||
name: 'choose-report',
|
||||
text: 'Choose Report',
|
||||
disabled: listReports > 0 ? false : true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuTooltip width={150} onClose={onClose}>
|
||||
<Menu
|
||||
onMenuSelect={item => {
|
||||
onMenuSelect(item);
|
||||
}}
|
||||
items={
|
||||
report.id === undefined
|
||||
? [
|
||||
{
|
||||
name: 'save-report',
|
||||
text: 'Save new report',
|
||||
},
|
||||
{
|
||||
name: 'reset-report',
|
||||
text: 'Reset to default',
|
||||
},
|
||||
]
|
||||
: savedStatus === 'saved'
|
||||
? [
|
||||
{ name: 'rename-report', text: 'Rename' },
|
||||
{ name: 'delete-report', text: 'Delete' },
|
||||
Menu.line,
|
||||
{
|
||||
name: 'save-report',
|
||||
text: 'Save new report',
|
||||
},
|
||||
{
|
||||
name: 'reset-report',
|
||||
text: 'Reset to default',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{ name: 'rename-report', text: 'Rename' },
|
||||
{
|
||||
name: 'update-report',
|
||||
text: 'Update report',
|
||||
},
|
||||
{
|
||||
name: 'reload-report',
|
||||
text: 'Revert changes',
|
||||
},
|
||||
{ name: 'delete-report', text: 'Delete' },
|
||||
Menu.line,
|
||||
{
|
||||
name: 'save-report',
|
||||
text: 'Save new report',
|
||||
},
|
||||
{
|
||||
name: 'reset-report',
|
||||
text: 'Reset to default',
|
||||
},
|
||||
]
|
||||
}
|
||||
items={[
|
||||
...savedMenu.items,
|
||||
...modifiedMenu.items,
|
||||
...unsavedMenu.items,
|
||||
]}
|
||||
/>
|
||||
</MenuTooltip>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Input } from '../common/Input';
|
||||
import { MenuTooltip } from '../common/MenuTooltip';
|
||||
import { Stack } from '../common/Stack';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { FormField, FormLabel } from '../forms';
|
||||
|
||||
type SaveReportNameProps = {
|
||||
@@ -41,7 +42,7 @@ export function SaveReportName({
|
||||
direction="row"
|
||||
justify="flex-end"
|
||||
align="center"
|
||||
style={{ padding: 10 }}
|
||||
style={{ padding: 15 }}
|
||||
>
|
||||
<FormField style={{ flex: 1 }}>
|
||||
<FormLabel
|
||||
@@ -54,11 +55,12 @@ export function SaveReportName({
|
||||
id="name-field"
|
||||
inputRef={inputRef}
|
||||
onUpdate={setName}
|
||||
style={{ marginTop: 10 }}
|
||||
/>
|
||||
</FormField>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginTop: 18 }}
|
||||
style={{ marginTop: 30 }}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onAddUpdate(menuItem);
|
||||
@@ -74,7 +76,7 @@ export function SaveReportName({
|
||||
<Text style={{ color: theme.errorText }}>{err}</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<Text />
|
||||
<View />
|
||||
)}
|
||||
</MenuTooltip>
|
||||
);
|
||||
|
||||
@@ -248,30 +248,29 @@ export function CustomReport() {
|
||||
}
|
||||
};
|
||||
|
||||
const onResetReports = () => {
|
||||
const setReportData = input => {
|
||||
const selectAll = [];
|
||||
categories.grouped.map(categoryGroup =>
|
||||
categoryGroup.categories.map(category => selectAll.push(category)),
|
||||
);
|
||||
|
||||
setStartDate(defaultReport.startDate);
|
||||
setEndDate(defaultReport.endDate);
|
||||
setIsDateStatic(defaultReport.isDateStatic);
|
||||
setDateRange(defaultReport.dateRange);
|
||||
setMode(defaultReport.mode);
|
||||
setGroupBy(defaultReport.groupBy);
|
||||
setInterval(defaultReport.interval);
|
||||
setBalanceType(defaultReport.balanceType);
|
||||
setShowEmpty(defaultReport.showEmpty);
|
||||
setShowOffBudget(defaultReport.showOffBudget);
|
||||
setShowHiddenCategories(defaultReport.showHiddenCategories);
|
||||
setShowUncategorized(defaultReport.showUncategorized);
|
||||
setSelectedCategories(selectAll);
|
||||
setGraphType(defaultReport.graphType);
|
||||
setStartDate(input.startDate);
|
||||
setEndDate(input.endDate);
|
||||
setIsDateStatic(input.isDateStatic);
|
||||
setDateRange(input.dateRange);
|
||||
setMode(input.mode);
|
||||
setGroupBy(input.groupBy);
|
||||
setInterval(input.interval);
|
||||
setBalanceType(input.balanceType);
|
||||
setShowEmpty(input.showEmpty);
|
||||
setShowOffBudget(input.showOffBudget);
|
||||
setShowHiddenCategories(input.showHiddenCategories);
|
||||
setShowUncategorized(input.showUncategorized);
|
||||
setSelectedCategories(input.selectedCategories ?? selectAll);
|
||||
setGraphType(input.graphType);
|
||||
onApplyFilter(null);
|
||||
onCondOpChange(defaultReport.conditionsOp);
|
||||
setReport(defaultReport);
|
||||
setSavedStatus('new');
|
||||
input.conditions.forEach(condition => onApplyFilter(condition));
|
||||
onCondOpChange(input.conditionsOp);
|
||||
};
|
||||
|
||||
const onChangeAppliedFilter = (filter, changedElement) => {
|
||||
@@ -295,24 +294,17 @@ export function CustomReport() {
|
||||
break;
|
||||
case 'reload':
|
||||
setSavedStatus('saved');
|
||||
|
||||
setStartDate(report.startDate);
|
||||
setEndDate(report.endDate);
|
||||
setIsDateStatic(report.isDateStatic);
|
||||
setDateRange(report.dateRange);
|
||||
setMode(report.mode);
|
||||
setGroupBy(report.groupBy);
|
||||
setInterval(report.interval);
|
||||
setBalanceType(report.balanceType);
|
||||
setShowEmpty(report.showEmpty);
|
||||
setShowOffBudget(report.showOffBudget);
|
||||
setShowHiddenCategories(report.showHiddenCategories);
|
||||
setShowUncategorized(report.showUncategorized);
|
||||
setSelectedCategories(report.selectedCategories);
|
||||
setGraphType(report.graphType);
|
||||
onApplyFilter(null);
|
||||
report.conditions.forEach(condition => onApplyFilter(condition));
|
||||
onCondOpChange(report.conditionsOp);
|
||||
setReportData(report);
|
||||
break;
|
||||
case 'reset':
|
||||
setSavedStatus('new');
|
||||
setReport(defaultReport);
|
||||
setReportData(defaultReport);
|
||||
break;
|
||||
case 'choose':
|
||||
setSavedStatus('saved');
|
||||
setReport(savedReport);
|
||||
setReportData(savedReport);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -372,7 +364,6 @@ export function CustomReport() {
|
||||
onApplyFilter={onApplyFilter}
|
||||
onChangeViews={onChangeViews}
|
||||
onReportChange={onReportChange}
|
||||
onResetReports={onResetReports}
|
||||
/>
|
||||
{filters && filters.length > 0 && (
|
||||
<View
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useReports } from 'loot-core/client/data-hooks/reports';
|
||||
import { getMonthYearFormat } from 'loot-core/src/shared/months';
|
||||
|
||||
import { useCategories } from '../../hooks/useCategories';
|
||||
@@ -10,6 +11,7 @@ import { Autocomplete } from '../autocomplete/Autocomplete';
|
||||
import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete';
|
||||
import { FilterAutocomplete } from '../autocomplete/FilterAutocomplete';
|
||||
import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete';
|
||||
import { ReportAutocomplete } from '../autocomplete/ReportAutocomplete';
|
||||
import { Input } from '../common/Input';
|
||||
import { View } from '../common/View';
|
||||
import { Checkbox } from '../forms';
|
||||
@@ -27,6 +29,7 @@ export function GenericInput({
|
||||
onChange,
|
||||
}) {
|
||||
const { grouped: categoryGroups } = useCategories();
|
||||
const savedReports = useReports();
|
||||
const saved = useSelector(state => state.queries.saved);
|
||||
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
|
||||
|
||||
@@ -111,6 +114,21 @@ export function GenericInput({
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'report':
|
||||
content = (
|
||||
<ReportAutocomplete
|
||||
saved={savedReports}
|
||||
value={value}
|
||||
multi={multi}
|
||||
openOnFocus={true}
|
||||
onSelect={onChange}
|
||||
inputProps={{
|
||||
inputRef,
|
||||
...(showPlaceholder ? { placeholder: 'nothing' } : null),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { CategoryEntity } from './category';
|
||||
import { type RuleConditionEntity } from './rule';
|
||||
|
||||
export interface CustomReportEntity {
|
||||
id?: string;
|
||||
name?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
isDateStatic: boolean;
|
||||
@@ -73,8 +73,8 @@ export type Month = {
|
||||
};
|
||||
|
||||
export interface CustomReportData {
|
||||
id?: string;
|
||||
name?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
date_static: number;
|
||||
|
||||
6
upcoming-release-notes/2350.md
Normal file
6
upcoming-release-notes/2350.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [carkom]
|
||||
---
|
||||
|
||||
Creating an autocomplete for custom reports so they can be recalled without switching back to the dashboard.
|
||||
Reference in New Issue
Block a user