add some more data manipulation buttons
This commit is contained in:
parent
e3c6f6ec92
commit
0d30b03b93
|
@ -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"
|
||||||
|
|
|
@ -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" }}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</Badge>
|
||||||
|
</ListGroup.Item>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" }}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</Badge>
|
||||||
|
<SplitBetween splitBetweenAtom={item.splitBetween} />
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue