diff --git a/README.md b/README.md index 75f29a5c..a873d352 100644 --- a/README.md +++ b/README.md @@ -166,8 +166,10 @@ Keyboard shortcuts can be triggered when no input field is selected. | Redo last change | R | | Clone current budget | C | | Create new budget | A | +| Open import/export panel | O | | Export data as JSON | S | | Export current budget as CSV | D | +| Open settings panel | T | | Go to older budget | PageDown | | Go to current month's budget | Home | | Go to newer budget | PageUp | diff --git a/e2e/settingsHappyPath.test.ts b/e2e/settingsHappyPath.test.ts index b96b5a10..b6c2edd8 100644 --- a/e2e/settingsHappyPath.test.ts +++ b/e2e/settingsHappyPath.test.ts @@ -43,7 +43,9 @@ test("should complete the settings happy path", async ({ page, isMobile }) => { await page.getByLabel("EUR").click(); await expect(page.getByText("€0.00")).toBeVisible(); - await expect(page.getByText("CSV")).toBeVisible(); + + await page.getByLabel("import or export budget").click(); + await expect(page.getByText("csv")).toBeVisible(); // should handle downloads const csvDownloadPromise = page.waitForEvent("download"); @@ -54,8 +56,8 @@ test("should complete the settings happy path", async ({ page, isMobile }) => { (await fs.promises.stat(await csvDownload.path())).size, ).toBeGreaterThan(0); - await page.getByLabel("budget settings").click(); - await expect(page.getByText("JSON")).toBeVisible(); + await page.getByLabel("import or export budget").click(); + await expect(page.getByText("json")).toBeVisible(); const jsonDownloadPromise = page.waitForEvent("download"); await page.getByLabel("export budget as json").click(); @@ -66,7 +68,11 @@ test("should complete the settings happy path", async ({ page, isMobile }) => { ).toBeGreaterThan(0); // should handle import - await page.locator("#import").setInputFiles("./docs/guitos-sample.json"); + await expect(page.getByLabel("import or export budget")).toBeVisible(); + await page.getByLabel("import or export budget").click(); + await page + .getByTestId("import-form-control") + .setInputFiles("./docs/guitos-sample.json"); await expect(page.getByLabel("go to older budget")).toBeVisible(); await page.getByLabel("go to older budget").click(); diff --git a/src/components/NavBar/NavBar.test.tsx b/src/components/NavBar/NavBar.test.tsx index 5252193e..90e378ec 100644 --- a/src/components/NavBar/NavBar.test.tsx +++ b/src/components/NavBar/NavBar.test.tsx @@ -54,7 +54,7 @@ describe("NavBar", () => { }); it("triggers event when import button is pressed", async () => { - await userEvent.click(screen.getByLabelText("import budget")); + await userEvent.click(screen.getByLabelText("import or export budget")); await userEvent.upload( screen.getByTestId("import-form-control"), new File([JSON.stringify(testBudget)], "budget", { @@ -64,20 +64,20 @@ describe("NavBar", () => { }); it("triggers event when export shortcuts are pressed", async () => { - await userEvent.type(screen.getByTestId("header"), "t"); + await userEvent.type(screen.getByTestId("header"), "o"); expect( screen.getByRole("button", { - name: /export budget as csv/i, + name: /import budget/i, }), ).toBeInTheDocument(); expect( screen.getByRole("button", { - name: /export budget as json/i, + name: /export budget as csv/i, }), ).toBeInTheDocument(); expect( - screen.getByRole("link", { - name: /open guitos changelog/i, + screen.getByRole("button", { + name: /export budget as json/i, }), ).toBeInTheDocument(); @@ -86,6 +86,15 @@ describe("NavBar", () => { await userEvent.type(screen.getByTestId("header"), "d"); }); + it("triggers event when settings shortcuts are pressed", async () => { + await userEvent.type(screen.getByTestId("header"), "t"); + expect( + screen.getByRole("link", { + name: /open guitos changelog/i, + }), + ).toBeInTheDocument(); + }); + it("triggers event when user changes budget name input", async () => { setBudgetMock.mockClear(); await userEvent.type(screen.getByDisplayValue("2023-03"), "change name"); diff --git a/src/components/NavBar/NavBar.tsx b/src/components/NavBar/NavBar.tsx index b8c1e1a1..3509fd93 100644 --- a/src/components/NavBar/NavBar.tsx +++ b/src/components/NavBar/NavBar.tsx @@ -18,7 +18,6 @@ import { BsArrowRight, BsPlusLg, BsQuestionLg, - BsUpload, BsXLg, } from "react-icons/bs"; import { FaRegClone } from "react-icons/fa"; @@ -28,6 +27,7 @@ import { useMove } from "../../hooks/useMove"; import { focusRef, getLabelKey } from "../../utils"; import "./NavBar.css"; import { NavBarDelete } from "./NavBarDelete"; +import { NavBarImpExp } from "./NavBarImpExp"; import { NavBarItem } from "./NavBarItem"; import { NavBarSettings } from "./NavBarSettings"; @@ -38,8 +38,6 @@ export interface SearchOption { } export function NavBar() { - const importRef = - useRef() as React.MutableRefObject; const searchRef = useRef(null); const nameRef = useRef() as React.MutableRefObject; @@ -58,7 +56,6 @@ export function NavBar() { deleteBudget, renameBudget, searchBudgets, - handleImport, } = useDB(); const { budget, undo, redo, canRedo, canUndo, budgetNameList } = useBudget(); @@ -348,44 +345,7 @@ export function NavBar() { )} - + {hasOneOrMoreBudgets && } diff --git a/src/components/NavBar/NavBarImpExp.tsx b/src/components/NavBar/NavBarImpExp.tsx new file mode 100644 index 00000000..9719c535 --- /dev/null +++ b/src/components/NavBar/NavBarImpExp.tsx @@ -0,0 +1,196 @@ +import { Dispatch, SetStateAction, useRef } from "react"; +import { + Button, + Form, + InputGroup, + Nav, + OverlayTrigger, + Popover, + Stack, + Tooltip, +} from "react-bootstrap"; +import { useHotkeys } from "react-hotkeys-hook"; +import { BsArrowDownUp, BsUpload } from "react-icons/bs"; +import { useBudget } from "../../context/BudgetContext"; +import { useDB } from "../../hooks/useDB"; +import { budgetToCsv } from "../../utils"; + +interface NavBarImpExpProps { + expanded: boolean; + setExpanded: Dispatch>; +} + +export function NavBarImpExp({ expanded, setExpanded }: NavBarImpExpProps) { + const { budget, budgetList, budgetNameList } = useBudget(); + const { handleImport } = useDB(); + const importRef = + useRef() as React.MutableRefObject; + const importButtonRef = useRef(null); + const exportCSVButtonRef = useRef(null); + const impExpButtonRef = useRef(null); + const hasOneOrMoreBudgets = budgetNameList && budgetNameList.length > 0; + + useHotkeys("o", (e) => !e.repeat && impExpButtonRef.current?.click(), { + preventDefault: true, + }); + + useHotkeys("s", (e) => !e.repeat && handleExportJSON(), { + preventDefault: true, + }); + + useHotkeys("d", (e) => !e.repeat && handleExportCSV(), { + preventDefault: true, + }); + + function handleExportJSON() { + if (budget) { + const date = new Date().toISOString(); + const filename = `guitos-${date.slice(0, -5)}.json`; + const url = window.URL.createObjectURL( + new Blob([JSON.stringify(budgetList)]), + ); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + } + } + + function handleExportCSV() { + if (budget) { + const filename = `${budget.name}.csv`; + const url = window.URL.createObjectURL(new Blob([budgetToCsv(budget)])); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + } + } + + return hasOneOrMoreBudgets ? ( + + ) : ( + + ); +} diff --git a/src/components/NavBar/NavBarSettings.tsx b/src/components/NavBar/NavBarSettings.tsx index 953efa3c..3315e638 100644 --- a/src/components/NavBar/NavBarSettings.tsx +++ b/src/components/NavBar/NavBarSettings.tsx @@ -12,10 +12,8 @@ import { Typeahead } from "react-bootstrap-typeahead"; import { Option } from "react-bootstrap-typeahead/types/types"; import { useHotkeys } from "react-hotkeys-hook"; import { BsGear } from "react-icons/bs"; -import { useBudget } from "../../context/BudgetContext"; import { useConfig } from "../../context/ConfigContext"; import { currenciesList } from "../../lists/currenciesList"; -import { budgetToCsv } from "../../utils"; interface NavBarSettingsProps { expanded: boolean; @@ -23,48 +21,13 @@ interface NavBarSettingsProps { export function NavBarSettings({ expanded }: NavBarSettingsProps) { const { currency, handleCurrency } = useConfig(); - const { budget, budgetList } = useBudget(); - const exportCSVButtonRef = useRef(null); const settingsButtonRef = useRef(null); - - useHotkeys("s", (e) => !e.repeat && handleExportJSON(), { - preventDefault: true, - }); - useHotkeys("d", (e) => !e.repeat && handleExportCSV(), { - preventDefault: true, - }); + const versionRef = useRef(null); useHotkeys("t", (e) => !e.repeat && settingsButtonRef.current?.click(), { preventDefault: true, }); - function handleExportJSON() { - if (budget) { - const date = new Date().toISOString(); - const filename = `guitos-${date.slice(0, -5)}.json`; - const url = window.URL.createObjectURL( - new Blob([JSON.stringify(budgetList)]), - ); - const link = document.createElement("a"); - link.href = url; - link.setAttribute("download", filename); - document.body.appendChild(link); - link.click(); - } - } - - function handleExportCSV() { - if (budget) { - const filename = `${budget.name}.csv`; - const url = window.URL.createObjectURL(new Blob([budgetToCsv(budget)])); - const link = document.createElement("a"); - link.href = url; - link.setAttribute("download", filename); - document.body.appendChild(link); - link.click(); - } - } - return (