Skip to content

Commit

Permalink
feat: add currency setting override option
Browse files Browse the repository at this point in the history
close #39
  • Loading branch information
rare-magma committed May 6, 2023
1 parent 1939865 commit 81e70b9
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 77 deletions.
9 changes: 9 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,12 @@ input[type="number"] {
border: 1px solid var(--comment);
border-radius: 0.375rem;
}

.fixed-width-font {
font-family: ui-monospace, SF Mono, Menlo, Monaco, Andale Mono, monospace;
}

.dropdown-menu {
min-width: inherit;
overflow-x: hidden !important;
}
15 changes: 10 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import "./App.css";
import BudgetPage from "./components/BudgetPage";
import localforage from "localforage";

export default function App() {
localforage.config({
name: "guitos",
storeName: "budgets",
});
export const budgetsDB = localforage.createInstance({
name: "guitos",
storeName: "budgets",
});

export const optionsDB = localforage.createInstance({
name: "guitos",
storeName: "options",
});

export default function App() {
return (
<>
<Router>
Expand Down
67 changes: 60 additions & 7 deletions src/components/BudgetPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ import {
convertCsvToBudget,
createBudgetNameList,
createNewBudget,
initialCurrencyCode,
userLang,
} from "../utils";
import { Income } from "./Income";
import { Expense } from "./Expense";
import NavBar from "./NavBar";
import Papa, { ParseError } from "papaparse";
import localforage from "localforage";
import { Option } from "react-bootstrap-typeahead/types/types";
import { BsXLg } from "react-icons/bs";
import { useHotkeys } from "react-hotkeys-hook";
import { budgetsDB, optionsDB } from "../App";
import { CurrencyInputProps } from "react-currency-input-field";
// import { useWhatChanged } from "@simbathesailor/use-what-changed";

type CsvError = {
Expand All @@ -52,6 +55,14 @@ function BudgetPage() {
const [jsonError, setJsonError] = useState<JsonError[]>([]);
const [show, setShow] = useState(false);

const [currency, setCurrency] = useState<string>(initialCurrencyCode);
const [intlConfig, setIntlConfig] = useState<
CurrencyInputProps["intlConfig"]
>({
locale: userLang,
currency: currency,
});

const [budget, setBudget] = useState<Budget | null>(null);
const [budgetList, setBudgetList] = useState<Budget[]>([]);
const [budgetNameList, setBudgetNameList] = useState<
Expand Down Expand Up @@ -190,7 +201,7 @@ function BudgetPage() {
};

const handleRemove = (toBeDeleted: string) => {
localforage
budgetsDB
.removeItem(toBeDeleted)
.then(() => {
const newBudgetList = budgetList
Expand Down Expand Up @@ -240,6 +251,14 @@ function BudgetPage() {
handleGo(1, budgetList.length - 1);
};

const handleSetCurrency = (c: string) => {
optionsDB.setItem("currencyCode", c).catch((e) => {
setError(e.message);
});
setCurrency(c);
setIntlConfig({ locale: userLang, currency: c as string });
};

const handleImport = (e: React.ChangeEvent<HTMLInputElement>) => {
setLoading(true);
const importedFiles = e.target.files;
Expand Down Expand Up @@ -317,10 +336,10 @@ function BudgetPage() {

const save = (budget: Budget) => {
let list: Budget[] = [];
localforage
budgetsDB
.setItem(budget.id, budget)
.then(() => {
localforage
budgetsDB
.iterate((value) => {
list = list.concat(value as unknown as Budget);
})
Expand All @@ -342,13 +361,14 @@ function BudgetPage() {
const loadFromDb = () => {
let list: Budget[] = [];

localforage
budgetsDB
.iterate((value) => {
list = list.concat(value as unknown as Budget);
})
.then(() => {
setBudgetList(list);
setBudgetNameList(createBudgetNameList(list));

if (name.trim() !== "undefined") {
setBudget(list.filter((b: Budget) => b && b.name === name)[0]);
} else {
Expand All @@ -359,6 +379,19 @@ function BudgetPage() {
.filter((b: Budget) => b && b.id === list[0].id)[0]
);
}

optionsDB
.getItem("currencyCode")
.then((c) => {
if (c) {
setCurrency(c as string);
setIntlConfig({ locale: userLang, currency: c as string });
}
})
.catch((e) => {
setError(e.message);
});

setLoading(false);
})
.catch((e) => {
Expand All @@ -382,7 +415,7 @@ function BudgetPage() {
budgetList
.filter((b: Budget) => b && b.name === name)
.forEach((data: Budget) => {
localforage
budgetsDB
.getItem(data.id)
.then((b) => setBudget(b as unknown as Budget))
.catch((e) => {
Expand All @@ -392,7 +425,7 @@ function BudgetPage() {
});
} else {
budgetList.slice(0).forEach((data: Budget) => {
localforage
budgetsDB
.getItem(data.id)
.then((b) => {
setBudget(b as unknown as Budget);
Expand All @@ -403,6 +436,19 @@ function BudgetPage() {
});
});
}

optionsDB
.getItem("currencyCode")
.then((c) => {
if (c) {
setCurrency(c as string);
setIntlConfig({ locale: userLang, currency: c as string });
}
})
.catch((e) => {
setError(e.message);
});

setBudgetNameList(createBudgetNameList(budgetList));
setLoading(false);
} else {
Expand All @@ -421,6 +467,7 @@ function BudgetPage() {
selected={budget?.name || undefined}
id={budget?.id || undefined}
budgetNameList={budgetNameList}
currency={currency || initialCurrencyCode}
onRename={(e) => {
handleRename(e);
}}
Expand Down Expand Up @@ -448,6 +495,9 @@ function BudgetPage() {
onSelect={(e) => {
handleSelect(e);
}}
onSetCurrency={(e) => {
handleSetCurrency(e);
}}
/>

{loading && (
Expand Down Expand Up @@ -644,13 +694,15 @@ function BudgetPage() {
<StatCard
key={"stats-" + budget.expenses.total + budget.incomes.total}
stat={budget.stats}
intlConfig={intlConfig}
onChange={handleStatChange}
/>

<div className="mt-3" />

<TableCard
items={budget.incomes}
intlConfig={intlConfig}
revenueTotal={budget.incomes.total}
header="Revenue"
onChange={handleIncomeChange}
Expand All @@ -662,6 +714,7 @@ function BudgetPage() {
<Col md="6" className="mb-3">
<TableCard
items={budget.expenses}
intlConfig={intlConfig}
revenueTotal={budget.incomes.total}
header="Expenses"
onChange={handleExpenseChange}
Expand Down
4 changes: 3 additions & 1 deletion src/components/ItemFormGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { fireEvent, render, screen } from "@testing-library/react";
import ItemFormGroup from "./ItemFormGroup";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import { itemForm1 } from "../setupTests";
import { intlConfig, itemForm1 } from "../setupTests";

describe("ItemFormGroup", () => {
const onRemove = vi.fn();
Expand All @@ -12,12 +12,14 @@ describe("ItemFormGroup", () => {
render(
<ItemFormGroup
itemForm={itemForm1}
intlConfig={intlConfig}
costPercentage={1}
onRemove={onRemove}
onChange={onChange}
/>
);
});

it("renders initial state", () => {
expect(screen.getByDisplayValue("name1")).toBeInTheDocument();
expect(screen.getByDisplayValue("$10")).toBeInTheDocument();
Expand Down
8 changes: 2 additions & 6 deletions src/components/ItemFormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,25 @@ import {
Tooltip,
} from "react-bootstrap";
import { BsXLg } from "react-icons/bs";
import { currencyCode, userLang } from "../utils";
import { ItemForm } from "./ItemForm";
import CurrencyInput, { CurrencyInputProps } from "react-currency-input-field";

interface ItemFormProps {
itemForm: ItemForm;
costPercentage: number;
intlConfig: CurrencyInputProps["intlConfig"];
onRemove: (itemForm: ItemForm) => void;
onChange: (itemForm: ItemForm) => void;
}

function ItemFormGroup({
itemForm: initialItemForm,
costPercentage,
intlConfig,
onRemove,
onChange,
}: ItemFormProps) {
const [itemForm, setItemForm] = useState(initialItemForm);
const [intlConfig] = useState<CurrencyInputProps["intlConfig"]>({
locale: userLang,
currency: currencyCode,
});

const handleRemove = (item: ItemForm) => {
onRemove(item);
};
Expand Down
15 changes: 14 additions & 1 deletion src/components/NavBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";
import NavBar from "./NavBar";
import { budgetNameList, testBudget } from "../setupTests";
import { budgetNameList, intlConfig, testBudget } from "../setupTests";

describe("NavBar", () => {
const onClone = vi.fn();
Expand All @@ -14,10 +14,13 @@ describe("NavBar", () => {
const onRemove = vi.fn();
const onRename = vi.fn();
const onSelect = vi.fn();
const onSetCurrency = vi.fn();

beforeEach(() => {
render(
<NavBar
budgetNameList={budgetNameList}
currency={intlConfig.currency}
selected={"2023-04"}
id={"035c2de4-00a4-403c-8f0e-f81339be9a4e"}
onClone={onClone}
Expand All @@ -29,9 +32,11 @@ describe("NavBar", () => {
onRemove={onRemove}
onRename={onRename}
onSelect={onSelect}
onSetCurrency={onSetCurrency}
/>
);
});

it("renders initial state", async () => {
expect(screen.getByText("2023-04")).toBeInTheDocument();

Expand Down Expand Up @@ -74,4 +79,12 @@ describe("NavBar", () => {
expect(onRename).toHaveBeenCalledTimes(11);
expect(screen.getByDisplayValue("2023-04change name")).toBeInTheDocument();
});

it("triggers onSetCurrency when currency is selected from dropdown", async () => {
const newButton = screen.getAllByRole("button", { name: "new budget" });
await userEvent.click(newButton[0]);
await userEvent.type(screen.getByPlaceholderText("USD"), "CAD");
await userEvent.click(screen.getByText("CAD"));
expect(onSetCurrency).toHaveBeenCalledTimes(1);
});
});
Loading

0 comments on commit 81e70b9

Please sign in to comment.