aboutsummaryrefslogtreecommitdiff
path: root/src/pages
diff options
context:
space:
mode:
authorKevin Hoerr <kjhoerr@noreply.cybr.es>2022-08-14 21:35:45 +0000
committerKevin Hoerr <kjhoerr@noreply.cybr.es>2022-08-14 21:35:45 +0000
commitc04674fa74c2e43535181431aef5d891f8839619 (patch)
tree2f7ce3b61590b39254bc22ac96272b7533ed96d2 /src/pages
parent461b1fa053bcc86d06156574ab59fa7000dbf69e (diff)
downloadpantry-c04674fa74c2e43535181431aef5d891f8839619.tar.gz
pantry-c04674fa74c2e43535181431aef5d891f8839619.tar.bz2
pantry-c04674fa74c2e43535181431aef5d891f8839619.zip
Merge planner code (#3)
Reviewed-on: https://git.submelon.dev/kjhoerr/pantry/pulls/3
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/_app.tsx16
-rw-r--r--src/pages/index.tsx162
2 files changed, 178 insertions, 0 deletions
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
new file mode 100644
index 0000000..10121ab
--- /dev/null
+++ b/src/pages/_app.tsx
@@ -0,0 +1,16 @@
+import "semantic-ui-css/semantic.min.css";
+import "../styles/globals.css";
+import type { AppProps } from "next/app";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+const queryClient = new QueryClient();
+
+function MyApp({ Component, pageProps }: AppProps) {
+ return (
+ <QueryClientProvider client={queryClient}>
+ <Component {...pageProps} />
+ </QueryClientProvider>
+ );
+}
+
+export default MyApp;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..674878a
--- /dev/null
+++ b/src/pages/index.tsx
@@ -0,0 +1,162 @@
+import type { NextPage } from "next";
+import Head from "next/head";
+import { List } from "immutable";
+import {
+ Header,
+ Table,
+ Message,
+ Container,
+ Pagination,
+} from "semantic-ui-react";
+
+import styles from "../styles/Home.module.css";
+import { useMemo, useState } from "react";
+import AddItem from "../components/add-item";
+import {
+ getGetItemsQueryKey,
+ useGetItems,
+ usePostItemsHook,
+} from "../util/pantry-item-resource";
+import { PantryItem } from "../model";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+
+const ENTRIES_PER_PAGE = Number(process.env.ENTRIES_PER_PAGE ?? "10");
+
+interface SortStateProps {
+ field: keyof PantryItem;
+ order: "ascending" | "descending";
+}
+
+const Home: NextPage = () => {
+ const { data, error } = useGetItems();
+ const postItems = usePostItemsHook();
+ const queryClient = useQueryClient();
+ const { mutate } = useMutation(postItems, {
+ onSuccess: async (item) => {
+ queryClient.setQueryData(
+ getGetItemsQueryKey(),
+ (data ?? []).concat(item)
+ );
+ },
+ });
+ const [activePage, setActivePage] = useState(1);
+ const [sortState, setSortState] = useState<SortStateProps>({
+ field: "name",
+ order: "ascending",
+ });
+
+ const hasEntries = useMemo(
+ () => error === null && (data === undefined || data.length > 0),
+ [error, data]
+ );
+ const entries = useMemo(() => {
+ const list = List<PantryItem>(data);
+ // case insensitive sort
+ const sorted = list.sortBy((item) =>
+ item[sortState.field]?.toString().toUpperCase()
+ );
+
+ return sortState.order === "ascending" ? sorted : sorted.reverse();
+ }, [data, sortState]);
+ const handleSortChange = (field: keyof PantryItem) => {
+ setSortState((state) =>
+ state.field === field
+ ? {
+ ...state,
+ order: state.order === "ascending" ? "descending" : "ascending",
+ }
+ : { field: field, order: "ascending" }
+ );
+ };
+
+ return (
+ <Container className={styles.container}>
+ <Head>
+ <title>Pantry</title>
+ <meta
+ name="description"
+ content="Meal planning with inventory management"
+ />
+ <link rel="icon" href="/favicon.ico" />
+ </Head>
+
+ <Header as="h1" className="title">
+ Pantry
+ </Header>
+
+ <AddItem addItem={(newItem) => Promise.resolve(mutate(newItem))} />
+ <Message error={error !== null} attached hidden={hasEntries}>
+ {error !== null
+ ? error.message !== undefined
+ ? `Network error occurred: ${error.message}`
+ : "Unknown network error occurred"
+ : "Nothing's in the pantry at the moment!"}
+ </Message>
+ <Table sortable attached="bottom">
+ <Table.Header>
+ <Table.Row>
+ <Table.HeaderCell
+ sorted={sortState.field === "name" ? sortState.order : undefined}
+ onClick={() => handleSortChange("name")}
+ >
+ Name
+ </Table.HeaderCell>
+ <Table.HeaderCell
+ sorted={
+ sortState.field === "description" ? sortState.order : undefined
+ }
+ onClick={() => handleSortChange("description")}
+ >
+ Description
+ </Table.HeaderCell>
+ <Table.HeaderCell
+ sorted={
+ sortState.field === "quantity" ? sortState.order : undefined
+ }
+ onClick={() => handleSortChange("quantity")}
+ >
+ Quantity
+ </Table.HeaderCell>
+ </Table.Row>
+ </Table.Header>
+ <Table.Body>
+ {entries
+ .valueSeq()
+ .slice(
+ (activePage - 1) * ENTRIES_PER_PAGE,
+ activePage * ENTRIES_PER_PAGE
+ )
+ .map((item: PantryItem) => (
+ <Table.Row key={item.id}>
+ <Table.Cell>{item.name}</Table.Cell>
+ <Table.Cell>
+ {item.description === "" ? "—" : item.description}
+ </Table.Cell>
+ <Table.Cell>
+ {item.quantity} {item.quantityUnitType}
+ </Table.Cell>
+ </Table.Row>
+ ))}
+ </Table.Body>
+ <Table.Footer>
+ <Table.Row>
+ <Table.HeaderCell colSpan="3">
+ <Pagination
+ activePage={activePage}
+ onPageChange={(_, { activePage }) =>
+ setActivePage(Number(activePage ?? 1))
+ }
+ totalPages={Math.max(
+ 1,
+ Math.ceil(entries.size / ENTRIES_PER_PAGE)
+ )}
+ />
+ </Table.HeaderCell>
+ </Table.Row>
+ </Table.Footer>
+ </Table>
+ </Container>
+ );
+};
+
+export default Home;