diff --git a/packages/desktop-client/src/components/FinancesApp.js b/packages/desktop-client/src/components/FinancesApp.js index f28507d190072a5f977e2262b5e453bfd05ebec1..988f58fa2863e1d76a1b4ddd1537c14fc513271f 100644 --- a/packages/desktop-client/src/components/FinancesApp.js +++ b/packages/desktop-client/src/components/FinancesApp.js @@ -35,6 +35,7 @@ import Settings from './Settings'; import Modals from './Modals'; import Notifications from './Notifications'; import GlobalKeys from './GlobalKeys'; +import { ManageRulesPage } from './ManageRulesPage'; // import Debugger from './Debugger'; function URLBar() { @@ -96,6 +97,7 @@ function Routes({ location }) { component={PostsOfflineNotification} /> + <Route path="/rules" exact component={ManageRulesPage} /> <Route path="/tools/fix-splits" exact component={FixSplitsTool} /> <Route diff --git a/packages/desktop-client/src/components/modals/ManageRules.js b/packages/desktop-client/src/components/ManageRules.js similarity index 85% rename from packages/desktop-client/src/components/modals/ManageRules.js rename to packages/desktop-client/src/components/ManageRules.js index 61f89bb3e41aa4314e379ea6046d997d5ce1fefe..00a6f18088d2ca5f8c498fabbe51f939cf679d69 100644 --- a/packages/desktop-client/src/components/modals/ManageRules.js +++ b/packages/desktop-client/src/components/ManageRules.js @@ -40,6 +40,7 @@ import { getRecurringDescription } from 'loot-core/src/shared/schedules'; import { getPayeesById } from 'loot-core/src/client/reducers/queries'; +import { Page } from './Page'; let SchedulesQuery = liveQueryContext(q('schedules').select('*')); @@ -446,7 +447,7 @@ function RulesHeader() { let dispatchSelected = useSelectedDispatch(); return ( - <TableHeader> + <TableHeader version="v2" style={{}}> <SelectCell exposed={true} focused={false} @@ -498,14 +499,13 @@ function RulesList({ ); } -export default function ManageRules({ history, modalProps, payeeId }) { +export default function ManageRules({ payeeId, setLoading = () => {} }) { let [allRules, setAllRules] = useState(null); let [rules, setRules] = useState(null); let dispatch = useDispatch(); let navigator = useTableNavigator(rules, ['select', 'edit']); let selectedInst = useSelected('manage-rules', allRules, []); let [hoveredRule, setHoveredRule] = useState(null); - let [loading, setLoading] = useState(true); let tableRef = useRef(null); async function loadRules() { @@ -619,88 +619,78 @@ export default function ManageRules({ history, modalProps, payeeId }) { } return ( - <Modal - title="Rules" - padding={0} - loading={loading} - {...modalProps} - style={[modalProps.style, { flex: 1, maxWidth: '90%' }]} - > - {() => ( - <SchedulesQuery.Provider> - <SelectedProvider instance={selectedInst}> - <View style={{ height: '70vh' }}> - <View style={{ flex: 1 }}> - <RulesHeader /> - <SimpleTable - ref={tableRef} - data={rules} - navigator={navigator} - loadMore={loadMore} - // Hide the last border of the item in the table - style={{ marginBottom: -1 }} - > - <RulesList - rules={rules} - selectedItems={selectedInst.items} - navigator={navigator} - hoveredRule={hoveredRule} - onHover={onHover} - onEditRule={onEditRule} - /> - </SimpleTable> - </View> - - <View - style={{ - flexDirection: 'row', - alignItems: 'center', - padding: '13px 15px', - borderTop: '1px solid ' + colors.border - }} - > - <View - style={{ - color: colors.n4, - flexDirection: 'row', - alignItems: 'center', - width: '50%' - }} - > - <Text> - Rules are always run in the order that you see them.{' '} - <ExternalLink - asAnchor={true} - href="https://actualbudget.com/docs/other/rules/" - style={{ color: colors.n4 }} - > - Learn more - </ExternalLink> - </Text> - </View> - - <View style={{ flex: 1 }} /> - - <Stack - direction="row" - align="center" - justify="flex-end" - spacing={2} + <SchedulesQuery.Provider> + <SelectedProvider instance={selectedInst}> + <View style={{ marginTop: 20, overflow: 'hidden' }}> + <View style={{ flex: 1 }}> + <RulesHeader /> + <SimpleTable + ref={tableRef} + data={rules} + navigator={navigator} + loadMore={loadMore} + // Hide the last border of the item in the table + style={{ marginBottom: -1 }} + > + <RulesList + rules={rules} + selectedItems={selectedInst.items} + navigator={navigator} + hoveredRule={hoveredRule} + onHover={onHover} + onEditRule={onEditRule} + /> + </SimpleTable> + </View> + + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + padding: '13px 15px', + borderTop: '1px solid ' + colors.border + }} + > + <View + style={{ + color: colors.n4, + flexDirection: 'row', + alignItems: 'center', + width: '50%' + }} + > + <Text> + Rules are always run in the order that you see them.{' '} + <ExternalLink + asAnchor={true} + href="https://actualbudget.com/docs/other/rules/" + style={{ color: colors.n4 }} > - {selectedInst.items.size > 0 && ( - <Button onClick={onDeleteSelected}> - Delete {selectedInst.items.size} rules - </Button> - )} - <Button primary onClick={onCreateRule}> - Create new rule - </Button> - </Stack> - </View> + Learn more + </ExternalLink> + </Text> </View> - </SelectedProvider> - </SchedulesQuery.Provider> - )} - </Modal> + + <View style={{ flex: 1 }} /> + + <Stack + direction="row" + align="center" + justify="flex-end" + spacing={2} + > + {selectedInst.items.size > 0 && ( + <Button onClick={onDeleteSelected}> + Delete {selectedInst.items.size} rules + </Button> + )} + <Button primary onClick={onCreateRule}> + Create new rule + </Button> + </Stack> + </View> + </View> + </SelectedProvider> + </SchedulesQuery.Provider> ); } diff --git a/packages/desktop-client/src/components/ManageRulesPage.js b/packages/desktop-client/src/components/ManageRulesPage.js new file mode 100644 index 0000000000000000000000000000000000000000..664468b7ea945925fece9bba3ee636483b7f66ea --- /dev/null +++ b/packages/desktop-client/src/components/ManageRulesPage.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ManageRules from './ManageRules'; +import { Page } from './Page'; + +export function ManageRulesPage() { + return ( + <Page title="Rules"> + <ManageRules /> + </Page> + ); +} diff --git a/packages/desktop-client/src/components/Modals.js b/packages/desktop-client/src/components/Modals.js index 5e756bbb3887345081ca2036a684ee89f7acb64f..ee029e6df0f1c4a7222d920bc2f16d8e2b3db4ce 100644 --- a/packages/desktop-client/src/components/Modals.js +++ b/packages/desktop-client/src/components/Modals.js @@ -17,7 +17,7 @@ import ImportTransactions from 'loot-design/src/components/modals/ImportTransact import EditField from 'loot-design/src/components/modals/EditField'; import CreateAccount from './modals/CreateAccount'; import ManagePayeesWithData from './payees/ManagePayeesWithData'; -import ManageRules from './modals/ManageRules'; +import ManageRulesModal from './modals/ManageRulesModal'; import EditRule from './modals/EditRule'; import MergeUnusedPayees from './modals/MergeUnusedPayees'; import ConfirmCategoryDelete from './modals/ConfirmCategoryDelete'; @@ -167,7 +167,7 @@ function Modals({ path="/manage-rules" render={() => { return ( - <ManageRules + <ManageRulesModal history={history} modalProps={modalProps} payeeId={options.payeeId} diff --git a/packages/desktop-client/src/components/accounts/Filters.js b/packages/desktop-client/src/components/accounts/Filters.js index e39a9cdeb474e282d8ac722af9e2f25ee09277fd..3847f75a177aa2bae35c010095677a60ed502a5f 100644 --- a/packages/desktop-client/src/components/accounts/Filters.js +++ b/packages/desktop-client/src/components/accounts/Filters.js @@ -32,7 +32,7 @@ import DeleteIcon from 'loot-design/src/svg/Delete'; import SettingsSliderAlternate from 'loot-design/src/svg/v2/SettingsSliderAlternate'; import { colors } from 'loot-design/src/style'; import GenericInput from '../util/GenericInput'; -import { Value } from '../modals/ManageRules'; +import { Value } from '../ManageRules'; let filterFields = [ 'date', diff --git a/packages/desktop-client/src/components/modals/ManageRulesModal.js b/packages/desktop-client/src/components/modals/ManageRulesModal.js new file mode 100644 index 0000000000000000000000000000000000000000..4b39870a2a8cf44f017f389853f41f3e095aa30a --- /dev/null +++ b/packages/desktop-client/src/components/modals/ManageRulesModal.js @@ -0,0 +1,25 @@ +import { Modal } from 'loot-design/src/components/common'; +import React, { useState } from 'react'; +import ManageRules from '../ManageRules'; + +export default function ManageRulesModal({ modalProps, payeeId }) { + let [loading, setLoading] = useState(true); + if (process.env.NODE_ENV === 'development') { + if (location.pathname !== '/payees') { + throw new Error( + `Possibly invalid use of ManageRulesModal, add the current url '${location.pathname}' to the allowlist if you're confident the modal can never appear on top of the '/rules' page.` + ); + } + } + return ( + <Modal + title="Rules" + padding={0} + loading={loading} + {...modalProps} + style={[modalProps.style, { flex: 1, maxWidth: '90%' }]} + > + {() => <ManageRules payeeId={payeeId} setLoading={setLoading} />} + </Modal> + ); +} diff --git a/packages/desktop-electron/index.js b/packages/desktop-electron/index.js index 91c38fd22d17fe1d17cc31a3929287626c484835..2b83d70531ace5f252978e17b9b5ebf982e69572 100644 --- a/packages/desktop-electron/index.js +++ b/packages/desktop-electron/index.js @@ -220,7 +220,6 @@ function updateMenu(isBudgetOpen) { item => item.label === 'Start Tutorial' || item.label === 'Manage Payees...' || - item.label === 'Manage Rules...' || item.label === 'Load Backup...' ) diff --git a/packages/desktop-electron/menu.js b/packages/desktop-electron/menu.js index f53096de0cfe0e99c301c0d4170eb0e30a48fae7..9761b0ec9bbb66a66e71a06fd7700eab60f8ef2e 100644 --- a/packages/desktop-electron/menu.js +++ b/packages/desktop-electron/menu.js @@ -33,20 +33,6 @@ function getMenu(isDev, createWindow) { } } }, - { - label: 'Manage Rules...', - enabled: false, - click(item, focusedWindow) { - if ( - focusedWindow && - focusedWindow.webContents.getTitle() === 'Actual' - ) { - focusedWindow.webContents.executeJavaScript( - '__actionsForMenu.pushModal("manage-rules")' - ); - } - } - }, { label: 'Load Backup...', enabled: false, diff --git a/packages/loot-design/src/components/sidebar.js b/packages/loot-design/src/components/sidebar.js index b0b8c9c304743225980ff4bb0a71ab84a9f5024d..3015ea5f5f779e3bd9c6cabf33bfdce14834eff8 100644 --- a/packages/loot-design/src/components/sidebar.js +++ b/packages/loot-design/src/components/sidebar.js @@ -31,6 +31,7 @@ import { useDraggable, useDroppable, DropHighlight } from './sort.js'; import CheveronUp from '../svg/v1/CheveronUp'; import CheveronDown from '../svg/v1/CheveronDown'; import StoreFrontIcon from '../svg/v1/StoreFront'; +import TuningIcon from '../svg/v1/Tuning'; import { useLocation } from 'react-router'; export const SIDEBAR_WIDTH = 240; @@ -376,9 +377,6 @@ const MenuButton = withRouter(function MenuButton({ history }) { case 'open-payees': dispatch(pushModal('manage-payees')); break; - case 'open-rules': - dispatch(pushModal('manage-rules')); - break; case 'find-schedules': history.push('/schedule/discover', { locationPtr: history.location }); break; @@ -397,7 +395,6 @@ const MenuButton = withRouter(function MenuButton({ history }) { let items = [ { name: 'open-payees', text: 'Manage Payees' }, - { name: 'open-rules', text: 'Manage Rules' }, { name: 'find-schedules', text: 'Find schedules' }, { name: 'repair-splits', text: 'Repair split transactions' }, Menu.line, @@ -440,7 +437,11 @@ function Tools() { let ExpandOrCollapseIcon = isOpen ? CheveronUp : CheveronDown; useEffect(() => { - if (['/schedules'].some(route => location.pathname.startsWith(route))) { + if ( + ['/schedules', '/rules'].some(route => + location.pathname.startsWith(route) + ) + ) { setOpen(true); } }, [location.pathname]); @@ -487,6 +488,15 @@ function Tools() { } to="/schedules" /> + <Item + title="Rules" + icon={ + <TuningIcon width={15} height={15} style={{ color: 'inherit' }} /> + } + to="/rules" + /> + </> + )} </> ); }