Created
March 14, 2021 17:02
-
-
Save anderskitson/a2aebf75c30d23b809978ea3e5eb6098 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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