Skip to content

Commit

Permalink
Merge pull request #389 from thepolicylab-projectportals/feat-search-bar
Browse files Browse the repository at this point in the history
feat: search bar
  • Loading branch information
jashlu authored Mar 1, 2023
2 parents 50ee33a + 0575afd commit 0a41bcf
Show file tree
Hide file tree
Showing 17 changed files with 213 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pa11y.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile
run: yarn install --immutable

- name: Build site
run: yarn workspace ${{ matrix.site }} build
Expand Down
2 changes: 1 addition & 1 deletion packages/example-site/content/project/open-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
"status": "open",
"statusOfData": "example statusOfData",
"summary": "example summary",
"topics": ["example-topic"]
"topics": ["example-topic", "example-topic-2"]
}
2 changes: 1 addition & 1 deletion packages/example-site/content/project/open-project3.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
"status": "open",
"statusOfData": "example statusOfData",
"summary": "example summary",
"topics": []
"topics": ["example-topic-3", "example-topic-4"]
}
26 changes: 26 additions & 0 deletions packages/example-site/content/project/open-project4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"question": "012 345 6789 ab cd efg hij klmn 24 chars/7 spaces",
"title": "Open Project 4",
"mainContact": "first-contact",
"projectTeam": [
"first-contact",
"second-contact"
],
"agency": "example agency",
"deliverable": "example deliverable",
"description": "No topics",
"emailContent": "example emailContent",
"endDate": "2016-12-15",
"expertise": "example expertise",
"fundingInfo": "example fundingInfo",
"keyDates": "example keyDates",
"lastModified": "2022-08-31T20:33:19.394Z",
"opportunityCloses": "2022-10-28",
"purpose": "example purpose",
"requirement": "example requirement",
"startDate": "2022-06-17",
"status": "open",
"statusOfData": "example statusOfData",
"summary": "example summary",
"topics": ["example-topic-4"]
}
26 changes: 26 additions & 0 deletions packages/example-site/content/project/open-project5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"question": "0123456789 abcdefghijklmnop 26 chars/1 space",
"title": "Open Project 5",
"mainContact": "first-contact",
"projectTeam": [
"first-contact",
"second-contact"
],
"agency": "example agency",
"deliverable": "example deliverable",
"description": "No topics",
"emailContent": "example emailContent",
"endDate": "2016-12-15",
"expertise": "example expertise",
"fundingInfo": "example fundingInfo",
"keyDates": "example keyDates",
"lastModified": "2022-08-31T20:33:19.394Z",
"opportunityCloses": "2022-10-28",
"purpose": "example purpose",
"requirement": "example requirement",
"startDate": "2022-06-17",
"status": "open",
"statusOfData": "example statusOfData",
"summary": "example summary",
"topics": ["example-topic", "example-topic-3"]
}
26 changes: 26 additions & 0 deletions packages/example-site/content/project/open-project6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"question": "012 3 45 67 89 ab cd efg hij klm 23 chars/9 spaces",
"title": "Open Project 6",
"mainContact": "first-contact",
"projectTeam": [
"first-contact",
"second-contact"
],
"agency": "example agency",
"deliverable": "example deliverable",
"description": "No topics",
"emailContent": "example emailContent",
"endDate": "2016-12-15",
"expertise": "example expertise",
"fundingInfo": "example fundingInfo",
"keyDates": "example keyDates",
"lastModified": "2022-08-31T20:33:19.394Z",
"opportunityCloses": "2022-10-28",
"purpose": "example purpose",
"requirement": "example requirement",
"startDate": "2022-06-17",
"status": "open",
"statusOfData": "example statusOfData",
"summary": "example summary",
"topics": ["ccv-settings", "example-topic-4", "example-topic-2"]
}
4 changes: 4 additions & 0 deletions packages/example-site/content/topic/example-topic-2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Military",
"lastModified": "2022-11-10T13:16:39.563Z"
}
4 changes: 4 additions & 0 deletions packages/example-site/content/topic/example-topic-3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Public Health",
"lastModified": "2022-11-10T13:17:39.563Z"
}
4 changes: 4 additions & 0 deletions packages/example-site/content/topic/example-topic-4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Security",
"lastModified": "2022-11-10T13:33:39.563Z"
}
2 changes: 0 additions & 2 deletions packages/example/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,15 +438,13 @@ const Index = () => {
<ContactForm />
<ProjectPage
allProjects={allProjects}
allTopics={[]}
bgImage={headerImageSrc}
title={"sample title"}
lede={"sample lede"}
sortOptions={["endDate", "created"]}
/>
<ProjectPage
allProjects={[]}
allTopics={[]}
bgImage={headerImageSrc}
title={"sample title"}
lede={"sample lede"}
Expand Down
7 changes: 0 additions & 7 deletions packages/example/src/pages/projectpage.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import React from "react"
import { ProjectPage } from "@thepolicylab-projectportals/gatsby-theme-project-portal/src/components"

const allTopics = [
{ title: "Topic A", slug: "topic-a" },
{ title: "Topic B", slug: "topic-b" },
{ title: "Topic C", slug: "topic-c" },
]

const sample_card = {
question: "Test Question?",
slug: "test-project",
Expand Down Expand Up @@ -48,7 +42,6 @@ const ProjectPageTest = () => {
lede={"This is the lede."}
sortOptions={["created", "opportunityCloses", "startDate", "endDate"]}
allProjects={sample_cards}
allTopics={allTopics}
bgImage={""}
/>
</>
Expand Down
1 change: 1 addition & 0 deletions packages/gatsby-theme-project-portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"gatsby-plugin-sharp": "^5.6.0",
"gatsby-source-filesystem": "^5.6.0",
"gatsby-transformer-sharp": "^5.6.0",
"js-search": "^2.0.0",
"lodash": "^4.17.21",
"markdown-to-jsx": "^7.1.9",
"moment": "^2.29.4",
Expand Down
113 changes: 83 additions & 30 deletions packages/gatsby-theme-project-portal/src/components/ProjectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useState, useEffect, useRef } from "react"
import { Cards, CardProps, TopicType } from "../components"
import { Cards, CardProps } from "../components"
import { HeaderWithImage } from "./HeaderWithImage"
import { BackIcon } from "./BackIcon"
import { ForwardIcon } from "./ForwardIcon"
import Select from "react-select"
import * as JsSearch from "js-search"
import { SearchBar } from "./SearchBar"

function customSort(dateField: string, sortAscending: boolean) {
return function (a, b) {
Expand Down Expand Up @@ -38,29 +40,31 @@ export interface ProjectPageProps {
lede: string
sortOptions: [...any]
allProjects: CardProps[]
allTopics: TopicType[]
bgImage: string
}

export const ProjectPage = ({
title,
allProjects,
allTopics,
lede,
sortOptions,
bgImage,
}: ProjectPageProps) => {
const filterOptions = []

for (const project of allProjects) {
if (project.topics) {
for (const topic of project.topics) {
if (!filterOptions.some(({ value }) => value === topic.slug)) {
filterOptions.push({ value: topic.slug, label: topic.title })
const getTopics = (project: CardProps[]): CardProps[] => {
let tempFilterOptions = []
for (const tempProject of project) {
if (tempProject.topics) {
for (const topic of tempProject.topics) {
if (!tempFilterOptions.some(({ value }) => value === topic.slug)) {
tempFilterOptions.push({ value: topic.slug, label: topic.title })
}
}
}
}
return tempFilterOptions
}
const [filterOptions, setFilterOptions] = useState(getTopics(allProjects))

const ITEMS_PER_PAGE = 6
const [sortedProjects, setSortedProjects] = useState(allProjects)
const [displayProjects, setDisplayProjects] = useState(allProjects)
Expand Down Expand Up @@ -94,16 +98,6 @@ export const ProjectPage = ({

const [sortDirection, setSortDirection] = useState(sortingOptions[0])

useEffect(() => {
const sortedList = [...allProjects]
sortedList.sort(
customSort(sortDirection.field, sortDirection.sortAscending)
)
setSortedProjects(sortedList)
setPageStart(0)
setPageEnd(ITEMS_PER_PAGE)
}, [sortDirection])

const [pageStart, setPageStart] = useState(0)
const [pageEnd, setPageEnd] = useState(ITEMS_PER_PAGE)
// state for the list
Expand All @@ -122,6 +116,23 @@ export const ProjectPage = ({
scrollToRef?.current?.scrollIntoView({ behavior: "smooth" })
}

const [searchQuery, setSearchQuery] = useState([])

let search = new JsSearch.Search("slug")
search.addIndex("topicNames")
search.addIndex("question")
search.addIndex("agency")

const flattenTopics = (project: CardProps): any => {
let result = []
//creating new array of all topicNames associated
//with this project
for (let i = 0; i < project.topics.length; i++) {
result.push(project.topics[i].title)
}
return result
}

const handleLoadNext = () => {
handleScroll()
// handle load next button click
Expand Down Expand Up @@ -164,21 +175,57 @@ export const ProjectPage = ({
const [selectedOptions, setSelectedOptions] = useState([])

useEffect(() => {
if (selectedOptions.length == 0) {
setDisplayProjects(sortedProjects)
} else {
const sortedList = [...allProjects]
sortedList.sort(
customSort(sortDirection.field, sortDirection.sortAscending)
)
setSortedProjects(sortedList)
setPageStart(0)
setPageEnd(ITEMS_PER_PAGE)
}, [sortDirection])

useEffect(() => {
//consolidate what displayProjects will look like
//after 3 checks in following order:
//1. Sort by **Done in other useEffect**
//2. Filter by topic
//3. Search query

let filteredProjects = sortedProjects

//2. filter by topic. If there are any filters chosen
// apply it to filteredProjects
// or else stick with sortedProjects (which may have been updated by sortOptions) aka the first check
if (selectedOptions.length > 0) {
const filteredTopics = selectedOptions.map(({ value }) => value)
setDisplayProjects(
sortedProjects.filter((project) =>
project.topics
.map((topic) => topic.slug)
.some((topicSlug) => filteredTopics.includes(topicSlug))
)
filteredProjects = sortedProjects.filter((project) =>
project.topics
.map((topic) => topic.slug)
.some((topicSlug) => filteredTopics.includes(topicSlug))
)
}
setPageStart(0)
setPageEnd(ITEMS_PER_PAGE)
}, [selectedOptions, sortedProjects]) // triggered when list is changed

//3. search query
// if search query is used, we will now apply search results, to filteredProjects
if (searchQuery.length > 0) {
for (let i = 0; i < filteredProjects.length; i++) {
filteredProjects[i]["topicNames"] = flattenTopics(filteredProjects[i])
}
search.addDocuments(filteredProjects)
let searchResults = search.search(searchQuery)
if (searchResults.length > 0) {
filteredProjects = searchResults
}
}

setFilterOptions(getTopics(filteredProjects))
//now filteredProjects has gone through all 3 checks
//ready to update displayProjects
setDisplayProjects(filteredProjects)
//setDisplayProjects will trigger an updated display
}, [selectedOptions, sortedProjects, searchQuery]) // triggered when list is changed

const selectStyle = {
placeholder: (provided) => ({ ...provided, color: "#767676" }),
Expand Down Expand Up @@ -223,6 +270,12 @@ export const ProjectPage = ({
styles={selectStyle}
/>
</div>
<div className="flex-1 min-w-30ch auto-rows-auto flex flex-col">
<SearchBar
label={"Search"}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<div className="sr-only">
Total Results: {displayProjects.length} Projects
Expand Down
27 changes: 27 additions & 0 deletions packages/gatsby-theme-project-portal/src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, {ChangeEventHandler, Dispatch, FunctionComponent, SetStateAction} from "react"

interface SearchBarProps {
label: string
onChange: any
}

export const SearchBar: FunctionComponent<SearchBarProps> = ({
label,
onChange,
}) => {
return (
<>
<label id="search-label" className="font-bold" htmlFor="filter">
{label}
</label>
<input
className="rounded border-gray-300 hover:border-gray-400"
style={{height: "62%"}}
type="text"
aria-label="Search"
placeholder="Type to filter posts..."
onChange={onChange}
/>
</>
)
}
Loading

0 comments on commit 0a41bcf

Please sign in to comment.