diff --git a/package.json b/package.json index d288a04..981b9e3 100644 --- a/package.json +++ b/package.json @@ -79,12 +79,6 @@ "plugin:prettier/recommended" ] }, - "ava": { - "require": [ - "babel-register" - ], - "babel": "inherit" - }, "browserslist": { "production": [ ">0.2%", diff --git a/src/components/OrgLink.js b/src/components/OrgLink.js new file mode 100644 index 0000000..11a8381 --- /dev/null +++ b/src/components/OrgLink.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { string } from 'prop-types'; + +import { Link } from 'react-router-dom'; + +import { slugify } from '../utils'; + +const OrgLink = ({ name, uuid }) => ( + + {name} + +); + +OrgLink.propTypes = { + name: string.isRequired, + uuid: string.isRequired, +}; + +export default OrgLink; diff --git a/src/components/PdfForm.js b/src/components/PdfForm.js new file mode 100644 index 0000000..7343d26 --- /dev/null +++ b/src/components/PdfForm.js @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; + +import { bool, func, number, shape, string } from 'prop-types'; + +import OrganizationSelector from '../containers/OrganizationSelector'; +import UserSelector from '../containers/UserSelector'; + +const onNumericChange = (currentValue, setter) => (e) => { + const parsed = parseInt(e.target.value, 10); + isNaN(parsed) ? setter(currentValue) : setter(parsed); +}; + +const PdfForm = ({ onSubmit, defaultValues = {} }) => { + const [organizationNameMatch, setOrganizationNameMatch] = useState( + defaultValues.organization && { + label: defaultValues.organization.name, + value: defaultValues.organization.uuid, + } + ); + const [organization, setOrganization] = useState( + defaultValues.organization && defaultValues.organization.uuid + ); + const [userNameMatch, setUserNameMatch] = useState( + defaultValues.user && { + label: defaultValues.user.name, + value: defaultValues.user.uuid, + } + ); + const [user, setUser] = useState( + defaultValues.user && defaultValues.user.uuid + ); + const [year, setYear] = useState(defaultValues.year); + const [url, setUrl] = useState(defaultValues.url); + const [currentPage, setCurrentPage] = useState(defaultValues.currentPage); + const [done, setDone] = useState(defaultValues.done); + + const handleSubmit = (e) => { + e.preventDefault(); + onSubmit({ + organization, + user, + url, + done: !!done, + year, + current_page: currentPage, + }); + }; + + const onYearChange = onNumericChange(year, setYear); + const onCurrentPageChange = onNumericChange(currentPage, setCurrentPage); + + return ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + setUrl(e.target.value)} + name="url" + type="url" + /> +
+ +
+ + +
+ +
+ + setDone(e.target.checked)} + /> +
+ + +
+ ); +}; + +PdfForm.propTypes = { + onSubmit: func.isRequired, + defaultValues: shape({ + organization: shape({ + uuid: string, + name: string, + }), + user: shape({ + uuid: string, + name: string, + }), + url: string, + done: bool, + year: number, + currentPage: number, + }), +}; + +PdfForm.defaultProps = { + defaultValues: { + url: '', + done: false, + year: new Date().getUTCFullYear(), + currentPage: 1, + }, +}; + +export default PdfForm; diff --git a/src/containers/AdminLinks.js b/src/containers/AdminLinks.js index 6d94260..125d0bc 100644 --- a/src/containers/AdminLinks.js +++ b/src/containers/AdminLinks.js @@ -7,6 +7,7 @@ const AdminLinks = ({ user }) => user ? ( <> + ) : ( diff --git a/src/containers/EditPdf.js b/src/containers/EditPdf.js new file mode 100644 index 0000000..049f601 --- /dev/null +++ b/src/containers/EditPdf.js @@ -0,0 +1,76 @@ +import React from 'react'; +import { string } from 'prop-types'; + +import { useParams } from 'react-router-dom'; + +import { gql, useMutation, useQuery } from '@apollo/client'; + +import PdfForm from '../components/PdfForm'; + +const GET_PDF = gql` + query getPdf($uuid: String) { + pdf(uuid: $uuid) { + organization { + name + uuid + } + user { + uuid + name + } + url + uuid + year + done + } + } +`; + +const UPDATE_PDF = gql` + mutation updatePdf($input: PdfInput!) { + upsertPdf(input: $input) { + uuid + } + } +`; + +const EditPdf = () => { + const { uuid } = useParams(); + + const { loading, error, data } = useQuery(GET_PDF, { + variables: { uuid }, + }); + + const [ + updatePdf, + { loading: updatePdfLoading, error: updatePdfError, data: updatePdfData }, + ] = useMutation(UPDATE_PDF); + + if (loading) return <>Loading...; + + if (error) throw new Error('todo catch these?'); + + const updateError = updatePdfError &&

{updatePdfError.message}

; + const updated = updatePdfData &&

Updated PDF

; + + const onSubmit = (input) => { + updatePdf({ + variables: { + input: { ...input, uuid }, + }, + }); + }; + + return ( + <> + + {updated} + {updatePdfLoading && 'Loading...'} + {updateError} + + ); +}; + +EditPdf.propTypes = { uuid: string.isRequired }; + +export default EditPdf; diff --git a/src/containers/ListPdfs.js b/src/containers/ListPdfs.js new file mode 100644 index 0000000..5d0118e --- /dev/null +++ b/src/containers/ListPdfs.js @@ -0,0 +1,68 @@ +import React from 'react'; +import { gql, useQuery } from '@apollo/client'; + +import { Link } from 'react-router-dom'; + +import OrgLink from '../components/OrgLink'; + +const LIST_PDFS_QUERY = gql` + query pdfs { + pdfs(limit: 9999, limitToCurrentUser: false) { + organization { + name + uuid + } + user { + uuid + name + } + url + uuid + year + done + } + } +`; + +const ListPdfs = () => { + const { loading, error, data } = useQuery(LIST_PDFS_QUERY); + + if (loading) return <>Loading...; + + if (error) throw new Error('todo catch these?'); + + return ( + + + + + + + + + + + + + {data.pdfs.map(({ url, uuid, year, done, organization, user }) => ( + + + + + + + + + ))} + +
UserOrganizationPDF URLYearDone?
{user ? user.name : '-'} + + + {url} + {year}{done ? '✔️' : ''} + edit +
+ ); +}; + +export default ListPdfs; diff --git a/src/containers/ManagePdfs.js b/src/containers/ManagePdfs.js new file mode 100644 index 0000000..6ac838a --- /dev/null +++ b/src/containers/ManagePdfs.js @@ -0,0 +1,77 @@ +import React from 'react'; + +import { gql, useMutation } from '@apollo/client'; + +import PdfForm from '../components/PdfForm'; + +import ListPdfs from './ListPdfs'; + +const ADD_PDF = gql` + mutation addPdf($input: PdfInput!) { + upsertPdf(input: $input) { + uuid + } + } +`; + +const ADD_PDF_OPTS = { + update(cache, { data: { upsertPdf } }) { + cache.modify({ + fields: { + pdfs(existingPdfs = []) { + const newPdfRef = cache.writeFragment({ + data: upsertPdf, + fragment: gql` + fragment NewPdf on Pdf { + id + type + } + `, + }); + return [...existingPdfs, newPdfRef]; + }, + }, + }); + }, +}; + +const ManagePdfs = () => { + const [ + addPdf, + { loading: addPdfLoading, error: addPdfError, data: addPdfData }, + ] = useMutation(ADD_PDF, ADD_PDF_OPTS); + + if (addPdfError) { + console.log( + 'xxx', + addPdfError.message, + addPdfError.graphQLErrors, + addPdfError.extraInfo, + addPdfError.networkError, + addPdfError.errors, + addPdfData + ); + } + const error = addPdfError &&

{addPdfError.message}

; + const created = addPdfData &&

Added PDF {addPdfData.upsertPdf.uuid}

; + + const onSubmit = (input) => { + addPdf({ + variables: { + input, + }, + }); + }; + + return ( +
+ + + {created} + {addPdfLoading && 'Loading...'} + {error} +
+ ); +}; + +export default ManagePdfs; diff --git a/src/containers/OrgNteeTags.js b/src/containers/OrgNteeTags.js index f496d27..94a84ed 100644 --- a/src/containers/OrgNteeTags.js +++ b/src/containers/OrgNteeTags.js @@ -1,20 +1,15 @@ import React from 'react'; -import { - Link, - Switch, - Route, - useRouteMatch, - useParams, -} from 'react-router-dom'; +import { Switch, Route, useRouteMatch, useParams } from 'react-router-dom'; import { useQuery, gql } from '@apollo/client'; import Helmet from 'react-helmet'; import Page from '../components/Page'; +import OrgLink from '../components/OrgLink'; -import { dollarsFormatter, slugify } from '../utils'; +import { dollarsFormatter } from '../utils'; const GET_ORG_NTEE_TAG = gql` query getNteeOrganizationTypes($nteeUuid: String!) { @@ -93,11 +88,9 @@ const OrgNteeTag = () => { ntee.organizations.map((org, i) => ( - - {org.name} - + + + {org.totalFunded diff --git a/src/containers/OrganizationSelector.js b/src/containers/OrganizationSelector.js index 5b3ab43..4893a92 100644 --- a/src/containers/OrganizationSelector.js +++ b/src/containers/OrganizationSelector.js @@ -33,12 +33,12 @@ const OrganizationSelector = ({ onOrgSelected, setValue, value }) => { debugger; } - const options = data.organizations.map(({ name, uuid }) => { - return { - label: name, - value: uuid, - }; - }); + const options = data.organizations + ? data.organizations.map(({ name, uuid }) => ({ + label: name, + value: uuid, + })) + : [value]; const handleInputChange = (inputValue) => { setValue(inputValue); @@ -52,6 +52,7 @@ const OrganizationSelector = ({ onOrgSelected, setValue, value }) => { return ( + ); +}; + +UserSelector.propTypes = { + onUserSelected: PropTypes.func.isRequired, + setValue: PropTypes.func.isRequired, + value: PropTypes.string, +}; + +export default UserSelector; diff --git a/src/index.js b/src/index.js index 5cad2cd..f576d88 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,8 @@ import Grants from './containers/Grants'; import Organizations from './containers/Organizations'; import Search from './containers/Search'; import Methods from './containers/Methods'; +import ManagePdfs from './containers/ManagePdfs'; +import EditPdf from './containers/EditPdf'; import OrgNteeTags from './containers/OrgNteeTags'; import AdminLinks from './containers/AdminLinks'; @@ -68,6 +70,8 @@ ReactDOM.render( + +