Skip to content

Commit

Permalink
feat: add item operation history
Browse files Browse the repository at this point in the history
Signed-off-by: rare-magma <[email protected]>
  • Loading branch information
rare-magma committed Dec 24, 2023
1 parent 33fc887 commit ea21598
Showing 9 changed files with 214 additions and 19 deletions.
4 changes: 4 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -115,6 +115,10 @@ input.text-end.form-control:focus {
color: var(--textcolor);
}

.form-control:disabled {
background: var(--lightbgcolor);
}

/* Chrome, Safari, Edge, Opera */
input::-webkit-inner-spin-button,
input::-webkit-outer-spin-button {
4 changes: 3 additions & 1 deletion src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import App from "./App";
import { budgetsDB, optionsDB } from "./db";
import { budgetsDB, calcHistDB, optionsDB } from "./db";
import { budgetContextSpy, testEmptyBudgetContext } from "./setupTests";

describe("App", () => {
@@ -23,6 +23,8 @@ describe("App", () => {
expect(budgetsDB.config("storeName")).toBe("budgets");
expect(optionsDB.config("name")).toBe("guitos");
expect(optionsDB.config("storeName")).toBe("options");
expect(calcHistDB.config("name")).toBe("guitos");
expect(calcHistDB.config("storeName")).toBe("calcHistDB");
});

it("shows new budget when clicking new button", async () => {
11 changes: 11 additions & 0 deletions src/components/CalculateButton/CalculateButton.test.tsx
Original file line number Diff line number Diff line change
@@ -155,4 +155,15 @@ describe("CalculateButton", () => {

expect(onCalculate).toHaveBeenCalledWith(123, "divide");
});

it("shows history when clicking button", async () => {
const button = screen.getByRole("button", {
name: "select operation type to item value",
});
await userEvent.click(button);
const historyButton = screen.getByRole("button", {
name: "open operation history",
});
await userEvent.click(historyButton);
});
});
116 changes: 112 additions & 4 deletions src/components/CalculateButton/CalculateButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
Button,
Dropdown,
@@ -7,28 +7,56 @@ import {
Popover,
} from "react-bootstrap";
import CurrencyInput from "react-currency-input-field";
import { BsCheckLg, BsDashLg, BsPlusSlashMinus, BsXLg } from "react-icons/bs";
import {
BsCheckLg,
BsClockHistory,
BsDashLg,
BsPlusSlashMinus,
BsXLg,
} from "react-icons/bs";
import { CgMathDivide, CgMathPlus } from "react-icons/cg";
import { useBudget } from "../../context/BudgetContext";
import { useConfig } from "../../context/ConfigContext";
import { useDB } from "../../hooks/useDB";
import { ItemForm } from "../ItemForm/ItemForm";
import "./CalculateButton.css";

interface CalculateButtonProps {
itemForm: ItemForm;
label: string;
onCalculate: (changeValue: number, operation: string) => void;
onCalculate: (changeValue: number, operation: ItemOperation) => void;
}

export type ItemOperation =
| "name"
| "value"
| "add"
| "subtract"
| "multiply"
| "divide";

export interface CalculationHistoryItem {
id: string;
itemForm: ItemForm;
changeValue: number | undefined;
operation: ItemOperation;
}

export function CalculateButton({
itemForm,
label,
onCalculate,
}: CalculateButtonProps) {
const [operation, setOperation] = useState("add");
const [operation, setOperation] = useState<ItemOperation>("add");
const [changeValue, setChangeValue] = useState(0);
const [showHistory, setShowHistory] = useState(false);
const [history, setHistory] = useState<CalculationHistoryItem[]>([]);
const opButtonRef = useRef<HTMLButtonElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const { intlConfig } = useConfig();
const { getCalcHist } = useDB();
const { budget } = useBudget();
const calcHistID = `${budget?.id}-${label}-${itemForm.id}`;

function handleKeyPress(e: { key: string }) {
if (e.key === "Enter") {
@@ -43,9 +71,29 @@ export function CalculateButton({
function handleCalculate() {
if (changeValue > 0) {
onCalculate(changeValue, operation);
setShowHistory(false);
getHistory();
}
}

function handleHistory() {
getHistory();
setShowHistory(!showHistory);
}

function getHistory() {
getCalcHist(calcHistID)
.then((h) => setHistory(h))
.catch((e: unknown) => {
throw e;
});
}

useEffect(() => {
getHistory();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showHistory]);

return (
<>
<OverlayTrigger
@@ -61,6 +109,17 @@ export function CalculateButton({
className="mb-1"
key={`${itemForm.id}-${label}-operation-group`}
>
<Button
id={`item-${itemForm.id}-operation-history-button`}
key={`${itemForm.id}-${label}-operation-history-button`}
aria-label={"open operation history"}
variant="outline-secondary"
disabled={history.length > 0 ? false : true}
type="button"
onClick={handleHistory}
>
<BsClockHistory aria-hidden />
</Button>
<Dropdown>
<Dropdown.Toggle
aria-label={"select type of operation on item value"}
@@ -131,6 +190,55 @@ export function CalculateButton({
<BsCheckLg aria-hidden />
</Button>
</InputGroup>
{showHistory && (
<div style={{ maxHeight: "30vh", overflow: "auto" }}>
{history
.filter((i) => i.operation !== "value")
.map((item, index) => (
<InputGroup
size="sm"
className="mb-1"
key={`${item.id}-history-group-${index}`}
>
<CurrencyInput
id={`${label}-${itemForm.id}-${index}-history-value`}
key={`item-${itemForm.id}-${index}-${label}-history-value`}
className="text-end form-control straight-corners fixed-width-font"
aria-label={"item history value"}
name="item-history-value"
intlConfig={intlConfig}
defaultValue={item.itemForm.value}
disabled={true}
/>
<InputGroup.Text>
{item.operation === "add" && (
<CgMathPlus aria-hidden />
)}
{item.operation === "subtract" && (
<BsDashLg aria-hidden />
)}
{item.operation === "multiply" && (
<BsXLg aria-hidden />
)}
{item.operation === "divide" && (
<CgMathDivide aria-hidden />
)}
</InputGroup.Text>
<CurrencyInput
id={`${label}-${itemForm.id}-${index}-history-changeValue`}
key={`item-${itemForm.id}-${index}-${label}-history-changeValue`}
className="text-end form-control straight-corners fixed-width-font"
aria-label={"item history change value"}
name="item-history-change-value"
intlConfig={intlConfig}
defaultValue={item.changeValue}
disabled={true}
/>
</InputGroup>
))
.reverse()}
</div>
)}
</Popover.Body>
</Popover>
}
36 changes: 34 additions & 2 deletions src/components/ItemForm/ItemFormGroup.tsx
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import CurrencyInput from "react-currency-input-field";
import { BsXLg } from "react-icons/bs";
import { useBudget } from "../../context/BudgetContext";
import { useConfig } from "../../context/ConfigContext";
import { useDB } from "../../hooks/useDB";
import {
calc,
calcAvailable,
@@ -21,7 +22,10 @@ import {
parseLocaleNumber,
roundBig,
} from "../../utils";
import { CalculateButton } from "../CalculateButton/CalculateButton";
import {
CalculateButton,
ItemOperation,
} from "../CalculateButton/CalculateButton";
import { Expense } from "../TableCard/Expense";
import { Income } from "../TableCard/Income";
import { ItemForm } from "./ItemForm";
@@ -44,12 +48,33 @@ export function ItemFormGroup({
const deleteButtonRef = useRef<HTMLButtonElement>(null);
const valueRef = useRef<HTMLInputElement>(null);
const { budget, setBudget } = useBudget();
const { deleteCalcHist, saveCalcHist } = useDB();
const { intlConfig } = useConfig();
const isExpense = label === "Expenses";
const table = isExpense ? budget?.expenses : budget?.incomes;

function handleCalcHist(
operation: ItemOperation,
changeValue: number | undefined,
) {
if (!budget) return;
const newItemForm = isExpense
? budget.expenses.items.find((item) => item.id === itemForm.id)
: budget.incomes.items.find((item) => item.id === itemForm.id);
if (!newItemForm) return;
const calcHistID = `${budget.id}-${label}-${newItemForm.id}`;
saveCalcHist(calcHistID, {
id: calcHistID,
itemForm: newItemForm,
changeValue,
operation,
}).catch((e: unknown) => {
throw e;
});
}

function handleChange(
operation: string,
operation: ItemOperation,
value?: string,
event?: React.ChangeEvent<HTMLInputElement>,
changeValue?: number,
@@ -79,6 +104,7 @@ export function ItemFormGroup({
saveInHistory = true;
}
setNeedsRerender(!needsRerender);
handleCalcHist(operation, changeValue);
break;
}

@@ -89,6 +115,7 @@ export function ItemFormGroup({
draft.stats.withGoal = calcWithGoal(draft);
draft.stats.saved = calcSaved(draft);
}, budget);

setBudget(newState(), saveInHistory);
}

@@ -106,6 +133,11 @@ export function ItemFormGroup({
draft.stats.saved = calcSaved(draft);
}, budget);
setBudget(newState(), true);

const calcHistID = `${budget.id}-${label}-${toBeDeleted.id}`;
deleteCalcHist(calcHistID).catch((e: unknown) => {
throw e;
});
}

return (
5 changes: 5 additions & 0 deletions src/db.ts
Original file line number Diff line number Diff line change
@@ -9,3 +9,8 @@ export const optionsDB = localforage.createInstance({
name: "guitos",
storeName: "options",
});

export const calcHistDB = localforage.createInstance({
name: "guitos",
storeName: "calcHistDB",
});
33 changes: 32 additions & 1 deletion src/hooks/useDB.ts
Original file line number Diff line number Diff line change
@@ -4,11 +4,12 @@ import { useEffect, useState } from "react";
import { Option } from "react-bootstrap-typeahead/types/types";
import { useParams } from "react-router-dom";
import { Budget } from "../components/Budget/Budget";
import { CalculationHistoryItem } from "../components/CalculateButton/CalculateButton";
import { SearchOption } from "../components/NavBar/NavBar";
import { useBudget } from "../context/BudgetContext";
import { useConfig } from "../context/ConfigContext";
import { useGeneralContext } from "../context/GeneralContext";
import { budgetsDB, optionsDB } from "../db";
import { budgetsDB, calcHistDB, optionsDB } from "../db";
import {
convertCsvToBudget,
createBudgetNameList,
@@ -304,6 +305,33 @@ export function useDB() {
}
}

async function getCalcHist(id: string): Promise<CalculationHistoryItem[]> {
let item;
await calcHistDB
.getItem(id)
.then((i) => {
item = i;
})
.catch((e: unknown) => {
throw e;
});
return item ?? [];
}

async function saveCalcHist(id: string, item: CalculationHistoryItem) {
const calcHist = await getCalcHist(id);
const newCalcHist = [...calcHist, item];
calcHistDB.setItem(id, newCalcHist).catch((e: unknown) => {
throw e;
});
}

async function deleteCalcHist(id: string) {
await calcHistDB.removeItem(id).catch((e: unknown) => {
throw e;
});
}

function saveBudget(budget: Budget | undefined) {
if (!budget) return;
let list: Budget[] = [];
@@ -345,5 +373,8 @@ export function useDB() {
loadBudget,
loadFromDb,
options,
getCalcHist,
saveCalcHist,
deleteCalcHist,
};
}
15 changes: 8 additions & 7 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import Big from "big.js";
import Papa from "papaparse";
import { expect, test } from "vitest";
import { Budget } from "./components/Budget/Budget";
import { ItemOperation } from "./components/CalculateButton/CalculateButton";
import { chromeLocalesList } from "./lists/chromeLocalesList";
import { currenciesMap } from "./lists/currenciesMap";
import { firefoxLocalesList } from "./lists/firefoxLocalesList";
@@ -193,13 +194,13 @@ reserves,reserves,200`);

test("calc", () => {
expect(calc(123.45, 100, "add")).eq(223.45);
expect(calc(123.45, 100, "sub")).eq(23.45);
expect(calc(123.45, 100, "mul")).eq(12345);
expect(calc(123.45, 100, "div")).eq(1.23);
expect(calc(0, 100, "sub")).eq(0);
expect(calc(0, 100, "mul")).eq(0);
expect(calc(0, 100, "div")).eq(0);
expect(() => calc(0, 100, "sqrt")).toThrow();
expect(calc(123.45, 100, "subtract")).eq(23.45);
expect(calc(123.45, 100, "multiply")).eq(12345);
expect(calc(123.45, 100, "divide")).eq(1.23);
expect(calc(0, 100, "subtract")).eq(0);
expect(calc(0, 100, "multiply")).eq(0);
expect(calc(0, 100, "divide")).eq(0);
expect(() => calc(0, 100, "sqrt" as ItemOperation)).toThrow();
});

test("median", () => {
Loading

0 comments on commit ea21598

Please sign in to comment.