Skip to content

Instantly share code, notes, and snippets.

@anderskitson
Created March 14, 2021 17:02
Show Gist options
  • Save anderskitson/a2aebf75c30d23b809978ea3e5eb6098 to your computer and use it in GitHub Desktop.
Save anderskitson/a2aebf75c30d23b809978ea3e5eb6098 to your computer and use it in GitHub Desktop.
import { gql, useMutation, useQuery } from "@apollo/client";
import useSWR from "swr";
import Layout from "../../components/Layout";
import React, { useState } from "react";
import {
useTable,
usePagination,
useFilters,
useGlobalFilter,
useAsyncDebounce,
} from "react-table";
import styled from "styled-components";
import ReactDatePicker from "react-datepicker";
import { useEffect } from "react";
import "react-datepicker/dist/react-datepicker.css";
import { useForm } from "react-hook-form";
import axios from "axios";
import { matchSorter } from "match-sorter";
const DECOR_VALUES = gql`
query GetDecorValues($id: ID!) {
findUserByID(id: $id) {
decor {
data {
itemNum
purchaseDate
description
alterations
cost
pieces
category
purchaser
image
_id
}
}
}
}
`;
const UPDATE_DECOR_DOC = gql`
mutation UpdateDecorDoc(
# $ownerID: ID!
$id: ID!
$description: String
$pieces: Int
$purchaser: String
$alterations: Boolean
$cost: Int
$purchaseDate: Date
$category: String
$image: String
$itemNum: Int
) {
updateDecor(
id: $id
data: {
description: $description
pieces: $pieces
purchaser: $purchaser
alterations: $alterations
cost: $cost
purchaseDate: $purchaseDate
category: $category
image: $image
itemNum: $itemNum
# owner: { connect: $ownerID }
}
) {
description
}
}
`;
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid black;
tr {
:last-child {
td {
border-bottom: 0;
}
}
}
th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
:last-child {
border-right: 0;
}
input {
font-size: 1rem;
padding: 0;
margin: 0;
border: 0;
width: 100%;
}
&:hover {
background: lightpink;
}
}
}
.pagination {
padding: 0.5rem;
}
`;
// Define a default UI for filtering
function GlobalFilter({
preGlobalFilteredRows,
globalFilter,
setGlobalFilter,
}) {
const count = preGlobalFilteredRows.length;
const [value, setValue] = React.useState(globalFilter);
const onChange = useAsyncDebounce((value) => {
setGlobalFilter(value || undefined);
}, 200);
return (
<span>
Search:{" "}
<input
value={value || ""}
onChange={(e) => {
setValue(e.target.value);
onChange(e.target.value);
}}
placeholder={`${count} records...`}
style={{
fontSize: "1.1rem",
border: "0",
}}
/>
</span>
);
}
// Define a default UI for filtering
function DefaultColumnFilter({
column: { filterValue, preFilteredRows, setFilter },
}) {
const count = preFilteredRows.length;
return (
<input
value={filterValue || ""}
onChange={(e) => {
setFilter(e.target.value || undefined); // Set undefined to remove the filter entirely
}}
placeholder={`Search ${count} records...`}
/>
);
}
function fuzzyTextFilterFn(rows, id, filterValue) {
return matchSorter(rows, filterValue, { keys: [(row) => row.values[id]] });
}
// Let the table remove the filter if the string is empty
fuzzyTextFilterFn.autoRemove = (val) => !val;
// Create an editable cell renderer
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
updateMyData, // This is a custom function that we supplied to our table instance
// getEvent,
}) => {
// We need to keep and update the state of the cell normally
const [value, setValue] = React.useState(initialValue);
const onChange = (e) => {
setValue(e.target.value);
};
const onChangeDate = (e) => {
setValue(e);
};
const onChangeNum = (e) => {
console.log(e);
// setValue(e);
};
const onChangeCheck = (e) => {
setValue(e.target.checked);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value);
// getEvent(index, id);
};
// If the initialValue is changed external, sync it up with our state
React.useEffect(() => {
setValue(initialValue);
}, [initialValue]);
if (id === "col2") {
return (
<ReactDatePicker
onCalendarClose={onBlur}
selected={new Date(value)}
onChange={onChangeDate}
/>
);
}
if (id === "col4") {
return (
<input
type="checkbox"
defaultChecked={value}
onChange={onChangeCheck}
onBlur={onBlur}
/>
);
}
if (id === "col6") {
return (
<input type="number" value={value} onChange={onChange} onBlur={onBlur} />
);
}
if (id === "col1") {
return <span>{value}</span>;
}
if (id === "col5") {
return (
<>
<span>$$$</span>
<input
type="number"
value={value}
onChange={onChange}
onBlur={onBlur}
/>
</>
);
}
if (id === "col7") {
return (
<>
{/* <span>$$$</span> */}
<select
name="category"
defaultValue={value}
onChange={onChange}
onBlur={onBlur}
>
<option value="Curation">Curation</option>
<option value="Good"> Good</option>
</select>
</>
);
}
// if (id === "col9") {
// return <ImageComp value={value} id={id} index={index} />;
// }
return <input value={value} onChange={onChange} onBlur={onBlur} />;
};
// Set our editable cell renderer as the default Cell renderer
const defaultColumn = {
Cell: EditableCell,
};
const fetcher = (url) => fetch(url).then((r) => r.json());
export default function DecorData() {
const { data: user, error: userError } = useSWR("/api/user", fetcher);
const { data: cookieData, error: cookieError } = useSWR(
"/api/cookie",
fetcher
);
var cookieBearer = `Bearer ${cookieData}`;
if (!user || !cookieData) return <p>Loading</p>;
if (userError) return <p>{userError.message}</p>;
if (cookieError) return <p>{cookieError.message}</p>;
return (
<Layout>
<h1>View your Decor Catalog Table Here</h1>
{user && cookieBearer && (
<Table user={user} cookieBearer={cookieBearer} />
)}
</Layout>
);
}
const Table = ({ user, cookieBearer }) => {
const { loading, error, data: decorData } = useQuery(DECOR_VALUES, {
variables: { id: user.id },
context: {
headers: {
authorization: cookieBearer,
},
},
});
const massaged = decorData?.findUserByID?.decor?.data?.map((item) => {
var col = Object.values(item);
return col.map((colItem, i) => {
return { [`col${i}`]: colItem };
});
});
const result = massaged?.map((a) => Object.assign({}, ...a));
const massaged1 = decorData?.findUserByID?.decor?.data?.map((item, int) => {
var col = Object.keys(item);
return col?.map((colItem, i) => {
// console.log(colItem);
if (colItem === "image") {
return {
Header: colItem,
accessor: `col${i}`,
filter: "fuzzyText",
Cell: ({ cell: { value, row } }) => (
<ImageComp
// result={result}
row={row.id}
result={result}
header={colItem}
value={value}
cookieBearer={cookieBearer}
/>
),
};
}
if (colItem === "description") {
return {
Header: colItem,
accessor: `col${i}`,
filter: "fuzzyText",
};
}
return {
Header: colItem,
accessor: `col${i}`,
filter: "fuzzyText",
};
});
});
if (loading) return <p>Loading</p>;
if (error) return <p>{error.message}</p>;
if (!decorData) return <p>No Decord Data</p>;
return (
<>
{result && massaged1 && (
<TryThis
cookieBearer={cookieBearer}
result={result}
massaged1={massaged1}
/>
)}
</>
);
};
const ImageComp = ({ result, row, value, cookieBearer }) => {
const [selected, setSelected] = useState(false);
const [replace, setReplace] = useState(false);
const [original, setOriginal] = useState(true);
const [
updateDecorDoc,
{ data: docData, loading: savingMutate },
] = useMutation(UPDATE_DECOR_DOC, {
context: {
headers: {
authorization: cookieBearer,
},
},
});
const { register, handleSubmit, errors, control } = useForm();
const onSubmit = (data) => {
console.log(Object.keys(data)[0]);
var formData = new FormData();
formData.append(
"image",
data[Object.keys(data)][0],
data[Object.keys(data)][0].name
);
axios
.post("../api/image", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then(async function (response) {
console.log(response);
const image = response.data.result.secure_url;
console.log(image);
const res = await updateDecorDoc({
variables: {
image: image,
id: Object.keys(data)[0],
},
}).catch(console.error);
});
};
const switchButtons = () => {
setSelected(true);
setOriginal(false);
};
return (
<>
<img src={value} width="400" />
{original && (
<button onClick={() => setReplace(true)}>Replace Image</button>
)}
{replace && (
<form onSubmit={handleSubmit(onSubmit)}>
<input
ref={register}
onChange={switchButtons}
type="file"
name={result[row]["col10"]}
/>
{selected && <button type="submit">Uplaod Image</button>}
</form>
)}
</>
);
};
function TryThis({ result, massaged1, cookieBearer }) {
const [data, setData] = React.useState(result); //bad naming here
const [changed, setChanged] = React.useState({});
// const [originalData] = React.useState(data)
const [skipPageReset, setSkipPageReset] = React.useState(false);
// const getEvent = (index, id) => {
// return index;
// };
const updateMyData = (rowIndex, columnId, value) => {
// We also turn on the flag to not reset the page
setSkipPageReset(true);
var matches = columnId.match(/\d+$/);
let number;
if (matches) {
number = matches[0];
}
let header = massaged1[0][number]["Header"];
setChanged({ [header]: value, id: result[rowIndex].col10 });
setData((old) => {
// console.log(old);
return old.map((row, index) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value,
};
}
return row;
});
});
};
const [
updateDecorDoc,
{ data: docData, loading: savingMutate },
] = useMutation(UPDATE_DECOR_DOC, {
context: {
headers: {
authorization: cookieBearer,
},
},
});
const test = useEffect(async () => {
if (Object.keys(changed)[0] === "purchaseDate") {
let yourDate = changed.purchaseDate;
const offset = yourDate.getTimezoneOffset();
// yourDate = new Date(yourDate.getTime() - offset * 60 * 1000);
const date = yourDate.toISOString().split("T")[0];
const res = await updateDecorDoc({
variables: {
purchaseDate: date,
id: changed.id,
},
}).catch(console.error);
}
if (Object.keys(changed)[0] === "description") {
const res = await updateDecorDoc({
variables: {
description: changed.description,
id: changed.id,
},
}).catch(console.error);
}
if (Object.keys(changed)[0] === "alterations") {
const res = await updateDecorDoc({
variables: {
alterations: changed.alterations,
id: changed.id,
},
}).catch(console.error);
}
if (Object.keys(changed)[0] === "cost") {
const res = await updateDecorDoc({
variables: {
cost: parseInt(changed.cost),
id: changed.id,
},
}).catch(console.error);
}
if (Object.keys(changed)[0] === "category") {
const res = await updateDecorDoc({
variables: {
category: changed.category,
id: changed.id,
},
}).catch(console.error);
}
if (Object.keys(changed)[0] === "purchaser") {
const res = await updateDecorDoc({
variables: {
purchaser: changed.purchaser,
id: changed.id,
},
}).catch(console.error);
}
}, [data]);
// After data chagnes, we turn the flag back off
// so that if data actually changes when we're not
// editing it, the page is reset
React.useEffect(() => {
setSkipPageReset(false);
}, [data]);
return (
<>
{/* {result && massaged1 && (
<TableRendered result={result} massaged1={massaged1} />
)} */}
{data && result && massaged1 && (
<Styles>
{/* <button onClick={resetData}>Reset Data</button> */}
<Table1
columns={massaged1[0]}
data={data}
updateMyData={updateMyData}
skipPageReset={skipPageReset}
cookieBearer={cookieBearer}
oldState={result}
changed={changed}
// getEvent={getEvent}
/>
</Styles>
)}
</>
);
}
function Table1({
columns,
data,
updateMyData,
skipPageReset,
cookieBearer,
oldState,
changed,
}) {
// For this example, we're using pagination to illustrate how to stop
// the current page from resetting when our data changes
// Otherwise, nothing is different here.
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
toggleHideColumn,
visibleColumns,
preGlobalFilteredRows,
setGlobalFilter,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
defaultColumn,
filterTypes,
// use the skipPageReset option to disable page resetting temporarily
autoResetPage: !skipPageReset,
// updateMyData isn't part of the API, but
// anything we put into these options will
// automatically be available on the instance.
// That way we can call this function from our
// cell renderer!
updateMyData,
// getEvent,
},
useFilters, // useFilters!
useGlobalFilter, // useGlobalFilter!
usePagination
);
const hide = useEffect(() => {
toggleHideColumn("col0", true);
toggleHideColumn("col10", true);
}, []);
const filterTypes = React.useMemo(
() => ({
// Add a new fuzzyTextFilterFn filter type.
fuzzyText: fuzzyTextFilterFn,
// Or, override the default text filter to use
// "startWith"
text: (rows, id, filterValue) => {
return rows.filter((row) => {
const rowValue = row.values[id];
return rowValue !== undefined
? String(rowValue)
.toLowerCase()
.startsWith(String(filterValue).toLowerCase())
: true;
});
},
}),
[]
);
const defaultColumn = React.useMemo(
() => ({
// Let's set up our default Filter UI
Filter: DefaultColumnFilter,
}),
[]
);
// Render the UI for your table
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<div className="pagination">
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{"<<"}
</button>{" "}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{"<"}
</button>{" "}
<button onClick={() => nextPage()} disabled={!canNextPage}>
{">"}
</button>{" "}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{">>"}
</button>{" "}
<span>
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<span>
| Go to page:{" "}
<input
type="number"
defaultValue={pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>{" "}
<select
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
<pre>
<code>
{JSON.stringify(
{
data,
},
null,
2
)}
</code>
</pre>
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>
{column.render("Header")}
{/* Render the columns filter UI */}
<div>
{column.canFilter ? column.render("Filter") : null}
</div>
</th>
))}
</tr>
))}
<tr>
<th
colSpan={visibleColumns.length}
style={{
textAlign: "left",
}}
>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
</th>
</tr>
</thead>
<tbody {...getTableBodyProps()}>
{firstPageRows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<br />
<div>Showing the first 20 results of {rows.length} rows</div>
<div>
<pre>
<code>{JSON.stringify(state.filters, null, 2)}</code>
</pre>
</div>
</div>
</>
);
}
// Define a custom filter filter function!
function filterGreaterThan(rows, id, filterValue) {
return rows.filter((row) => {
const rowValue = row.values[id];
return rowValue >= filterValue;
});
}
// This is an autoRemove method on the filter function that
// when given the new filter value and returns true, the filter
// will be automatically removed. Normally this is just an undefined
// check, but here, we want to remove the filter if it's not a number
filterGreaterThan.autoRemove = (val) => typeof val !== "number";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment