add some more data manipulation buttons

This commit is contained in:
Michael Zhang 2022-10-24 03:49:22 -05:00
parent e3c6f6ec92
commit 0d30b03b93
4 changed files with 123 additions and 38 deletions

View file

@ -8,11 +8,27 @@ export interface Props {
} }
const ClickableContainer = styled.span` const ClickableContainer = styled.span`
display: inline-block;
padding: 4px 10px;
margin: 4px;
border: 1px solid #eee;
border-radius: 5px;
width: 120px;
&:hover { &:hover {
background-color: #eee; background-color: #eee;
} }
`; `;
const EditingBox = styled.input`
display: inline-block;
padding: 4px 10px;
margin: 4px;
border: 1px solid #eee;
border-radius: 5px;
width: 120px;
`;
export default function EditBox({ valueAtom }: Props) { export default function EditBox({ valueAtom }: Props) {
const [value, setValue] = useAtom(valueAtom); const [value, setValue] = useAtom(valueAtom);
const [valueInput, setValueInput] = useState(""); const [valueInput, setValueInput] = useState("");
@ -32,6 +48,7 @@ export default function EditBox({ valueAtom }: Props) {
e.preventDefault(); e.preventDefault();
try { try {
const n = parseFloat(valueInput); const n = parseFloat(valueInput);
if (isNaN(n) || !isFinite(n)) return;
setValue(n); setValue(n);
setEditing(false); setEditing(false);
} catch (e) { } catch (e) {
@ -42,7 +59,7 @@ export default function EditBox({ valueAtom }: Props) {
if (editing) { if (editing) {
return ( return (
<form onSubmit={finalize} style={{ display: "inline" }}> <form onSubmit={finalize} style={{ display: "inline" }}>
<input <EditingBox
autoFocus={true} autoFocus={true}
type="number" type="number"
step="0.01" step="0.01"

View file

@ -1,4 +1,5 @@
import { Atom, useAtom } from "jotai"; import { Atom, useAtom } from "jotai";
import { Badge, ListGroup } from "react-bootstrap";
export interface IPerson { export interface IPerson {
name: string; name: string;
@ -8,7 +9,25 @@ export interface Props {
personAtom: Atom<IPerson>; personAtom: Atom<IPerson>;
} }
export default function Person({ personAtom }: Props) { export default function Person({ personAtom, splitBetweenAtom }: Props) {
const [person, _] = useAtom(personAtom); const [person] = useAtom(personAtom);
return <span style={{ marginInline: "5px" }}>{person.name}</span>; const [splitBetween, setSplitBetween] = useAtom(splitBetweenAtom);
const removeSelf = (_) => {
setSplitBetween([...splitBetween.filter((x) => x != personAtom)]);
};
return (
<ListGroup.Item>
{person.name}
<Badge
bg="danger"
pill
onClick={removeSelf}
style={{ cursor: "pointer" }}
>
&times;
</Badge>
</ListGroup.Item>
);
} }

View file

@ -1,4 +1,7 @@
import { Atom, useAtom } from "jotai"; import { atom, Atom, useAtom, useAtomValue } from "jotai";
import { useState } from "react";
import { Badge, Card, ListGroup } from "react-bootstrap";
import { receiptAtom } from "../lib/state";
import EditBox from "./EditBox"; import EditBox from "./EditBox";
import Person, { IPerson } from "./Person"; import Person, { IPerson } from "./Person";
@ -13,16 +16,49 @@ export interface Props {
} }
function SplitBetween({ splitBetweenAtom }) { function SplitBetween({ splitBetweenAtom }) {
const [splitBetween, _] = useAtom(splitBetweenAtom); const [splitBetween, setSplitBetween] = useAtom(splitBetweenAtom);
return splitBetween.length > 0 ? ( const [input, setInput] = useState("");
const [editing, setEditing] = useState(false);
const startEditing = (_) => {
setInput("");
setEditing(true);
};
const addPerson = (e) => {
e.preventDefault();
setSplitBetween([...splitBetween, atom({ name: input })]);
setEditing(false);
};
return (
<div> <div>
Split between ({splitBetween.length}): Split between ({splitBetween.length}):
{splitBetween.map((a, i) => ( <ListGroup horizontal>
<Person personAtom={a} key={`split-${i}`} /> {splitBetween.map((a, i) => (
))} <Person
personAtom={a}
key={`split-${i}`}
splitBetweenAtom={splitBetweenAtom}
/>
))}
<ListGroup.Item onClick={startEditing}>
{editing ? (
<form onSubmit={addPerson}>
<input
autoFocus={true}
type="text"
value={input}
onBlur={(_) => setEditing(false)}
onInput={(e) => setInput(e.target.value)}
/>
</form>
) : (
"[+]"
)}
</ListGroup.Item>
</ListGroup>
</div> </div>
) : (
<></>
); );
} }
@ -31,14 +67,28 @@ function Price({ priceAtom }) {
} }
export default function ReceiptItem({ itemAtom }: Props) { export default function ReceiptItem({ itemAtom }: Props) {
const [receipt, setReceipt] = useAtom(receiptAtom);
const [item, _] = useAtom(itemAtom); const [item, _] = useAtom(itemAtom);
const removeSelf = (_) => {
setReceipt([...receipt.filter((x) => x != itemAtom)]);
};
return ( return (
<> <Card>
<span> <Card.Body>
<b>{item.name}</b> <Card.Title>{item.name}</Card.Title>
</span> Item price: <Price priceAtom={item.price} />
(<Price priceAtom={item.price} />) <Badge
<SplitBetween splitBetweenAtom={item.splitBetween} /> bg="danger"
</> pill
onClick={removeSelf}
style={{ cursor: "pointer" }}
>
&times;
</Badge>
<SplitBetween splitBetweenAtom={item.splitBetween} />
</Card.Body>
</Card>
); );
} }

View file

@ -51,26 +51,25 @@ const Home: NextPage = () => {
<EditBox valueAtom={totalAtom} /> <EditBox valueAtom={totalAtom} />
</div> </div>
<ul> {receipt.map((itemAtom, i) => {
{receipt.map((itemAtom, i) => { return <ReceiptItem itemAtom={itemAtom} key={`receiptItem-${i}`} />;
return ( })}
<li key={`receiptItem-${i}`}>
<ReceiptItem itemAtom={itemAtom} />
</li>
);
})}
</ul>
<div> {total.size > 0 && (
Total breakdown: <>
<ul> <h3>Weighted Breakdown</h3>
{[...total.entries()].map(([person, value], i) => (
<li key={`breakdown-${i}`}> <div>
<b>{person}</b>: {formatter.format(value)} <ul>
</li> {[...total.entries()].map(([person, value], i) => (
))} <li key={`breakdown-${i}`}>
</ul> <b>{person}</b>: {formatter.format(value)}
</div> </li>
))}
</ul>
</div>
</>
)}
<small> <small>
<a href="https://github.com/iptq/wisesplit/">[source]</a> <a href="https://github.com/iptq/wisesplit/">[source]</a>