Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1234/add aditional functionality to regulations #1235

Merged
merged 3 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions next/backend/meili/fetchers/homepageSearchFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ export const homepageSearchDefaultFilters: HomepageSearchFilters = {
search: '',
}

export const allSearchTypes = ['page' as const, 'blog-post' as const, 'vzn' as const]
export const allSearchTypes = [
'page' as const,
'blog-post' as const,
'vzn' as const,
'regulation' as const,
]

// https://stackoverflow.com/a/52331580
export type Unpacked<T> = T extends (infer U)[] ? U : T
Expand All @@ -37,7 +42,7 @@ export const homepageSearchFetcher = (filters: HomepageSearchFilters, locale: st
.search<MixedResults>(filters.search, {
...getMeilisearchPageOptions({ page: 1, pageSize: 5 }),
filter: [
'type = "page" OR type = "blog-post" OR type = "vzn"',
'type = "page" OR type = "blog-post" OR type = "regulation"',
`locale = ${locale} OR locale NOT EXISTS`,
],
})
Expand All @@ -49,24 +54,24 @@ export const homepageSearchFetcher = (filters: HomepageSearchFilters, locale: st
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
const dataInner = (hit as any)[type]

if (type === 'vzn') {
const { title } = dataInner
if (type === 'blog-post') {
const { title, slug } = dataInner
return {
type,
title,
// TODO: Fix link - get slug by some proper function. This one works for now for both locales, EN does not have a page for VZNs
link: `/sk/mesto-bratislava/sprava-mesta/legislativa-mesta/vseobecne-zavazne-nariadenia?keyword=${filters.search}`,
// TODO: Fix link - get slug by some proper function. This one works for now for both locales.
link: `/blog/${slug}`,
data: dataInner,
} as HomepageSearchResult
}

if (type === 'blog-post') {
const { title, slug } = dataInner
if (type === 'regulation') {
const { regNumber, titleText, fullTitle, slug } = dataInner
return {
type,
title,
title: `VZN ${regNumber} ${titleText ?? fullTitle}`,
// TODO: Fix link - get slug by some proper function. This one works for now for both locales.
link: `/blog/${slug}`,
link: `/vzn/${slug}`,
data: dataInner,
} as HomepageSearchResult
}
Expand Down
1 change: 1 addition & 0 deletions next/backend/meili/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type MixedResults =
| SearchIndexWrapped<'page', PageMeili>
| SearchIndexWrapped<'blog-post', InbaArticleMeili>
| SearchIndexWrapped<'vzn', VznMeili>
| SearchIndexWrapped<'regulation', RegulationMeili>

export type PageMeili = Omit<
Page,
Expand Down
4 changes: 2 additions & 2 deletions next/components/molecules/Sections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import InbaArticlesList from '@components/molecules/sections/general/ArticlesLis
import ContactsSection from '@components/molecules/sections/general/ContactsSection'
import FeaturedBlogPostsSection from '@components/molecules/sections/general/FeaturedBlogPostsSection'
import InbaReleasesSection from '@components/molecules/sections/general/InbaReleasesSection'
import RegulationsSection from '@components/molecules/sections/general/RegulationsSection'
import cx from 'classnames'
import * as React from 'react'

Expand All @@ -32,7 +33,6 @@ import TextWithImageSection from './sections/general/TextWithImageSection'
import TimelineSection from './sections/general/TimelineSection'
import VideosSection from './sections/general/VideosSection'
import WavesSection from './sections/general/WavesSection'
import RegulationsSection from '@components/molecules/sections/general/RegulationsSection'

const SectionContent = ({ section }: { section: SectionsFragment }) => {
switch (section.__typename) {
Expand Down Expand Up @@ -115,7 +115,7 @@ const SectionContent = ({ section }: { section: SectionsFragment }) => {
return <ContactsSection section={section} />

case 'ComponentSectionsRegulationsList':
return <RegulationsListSection section={section} />
return <RegulationsListSection />

case 'ComponentSectionsRegulations':
return <RegulationsSection section={section} />
Expand Down
114 changes: 4 additions & 110 deletions next/components/molecules/sections/general/RegulationsListSection.tsx
Original file line number Diff line number Diff line change
@@ -1,114 +1,8 @@
import { RegulationsListSectionFragment } from '@backend/graphql'
import { Typography } from '@bratislava/component-library'
import SearchBar from '@components/organisms/SearchSection/SearchBar'
import SearchResults from '@components/organisms/SearchSection/SearchResults'
import { SearchFilters } from '@components/organisms/SearchSection/useQueryBySearchOption'
import { useTranslations } from 'next-intl'
import React, { useEffect, useRef, useState } from 'react'
import { StringParam, useQueryParam, withDefault } from 'use-query-params'
import { useDebounce } from 'usehooks-ts'
import GlobalSearchSectionContent from '@components/organisms/SearchSection/GlobalSearchSectionContent'
import React from 'react'

// This component was created by reducing some functionality from the main search component GlobalSearchPageContent
// Same as in OfficialBoardSection.
// TODO there's too much code duplication here, it would be better to have one component that takes selected search options as props

export type SearchOption = {
id:
| 'allResults'
| 'pages'
| 'articles'
| 'inbaArticles'
| 'regulations'
| 'users'
| 'officialBoard'
displayName?: string
displayNamePlural: string
}

type RegulationsListSectionProps = {
section: RegulationsListSectionFragment
}

const RegulationsListSection = ({ section }: RegulationsListSectionProps) => {
// TODO return only GlobalSearchPageContent with specific search option when regulations are enabled
// return <GlobalSearchPageContent variant="specific" searchOption="regulations" />

const t = useTranslations()

const [routerQueryValue] = useQueryParam('keyword', withDefault(StringParam, ''))
const [input, setInput] = useState('')
const debouncedInput = useDebounce(input, 300)
const [searchValue, setSearchValue] = useState(debouncedInput)

useEffect(() => {
setInput(routerQueryValue)
}, [routerQueryValue])

useEffect(() => {
setSearchValue(debouncedInput)
}, [debouncedInput])

const defaultSearchOption: SearchOption = {
id: 'regulations',
displayName: t('SearchPage.regulation'),
displayNamePlural: t('SearchPage.regulations'),
}

const [currentPage, setCurrentPage] = useState(1)

useEffect(() => {
setCurrentPage(1)
}, [searchValue])

const [resultsCount, setResultsCount] = useState(0)

const setResultsCountById = (optionId: SearchOption['id'], count: number) => {
setResultsCount(count)
}

const searchFilters: SearchFilters = {
search: searchValue,
page: currentPage,
pageSize: 12,
// tagIds need to be here for now, because BlogPost and InbaArticle fetchers filter by tagIds
tagIds: [],
}

const searchRef = useRef<null | HTMLInputElement>(null)

useEffect(() => {
searchRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [searchFilters.page, searchFilters.pageSize])

return (
<div className="flex w-full flex-col gap-y-8">
<Typography type="h1">{t('searching')}</Typography>
<div className="flex flex-col gap-3 lg:gap-4">
<SearchBar
ref={searchRef}
placeholder={t('enterKeyword')}
input={input}
setInput={setInput}
setSearchQuery={setSearchValue}
/>
</div>
{resultsCount > 0 ? (
<Typography type="p">
{t('SearchPage.showingResults', {
count: resultsCount,
})}
</Typography>
) : null}
<SearchResults
variant="specificResults"
searchOption={defaultSearchOption}
filters={searchFilters}
onSetResultsCount={setResultsCountById}
onPageChange={setCurrentPage}
key={`specificResults-${defaultSearchOption.id}`}
/>
</div>
)
const RegulationsListSection = () => {
return <GlobalSearchSectionContent variant="specific" searchOption="regulations" />
}

export default RegulationsListSection
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ type Props =
}
| {
variant: 'specific'
// TODO unify with SearchOption
searchOption: 'pages' | 'articles' | 'regulations' | 'users' | 'officialBoard'
searchOption: Exclude<SearchOption['id'], 'allResults'>
}

const GlobalSearchSectionContent = ({ variant, searchOption }: Props) => {
Expand Down Expand Up @@ -80,18 +79,19 @@ const GlobalSearchSectionContent = ({ variant, searchOption }: Props) => {
displayName: t('SearchPage.contact'),
displayNamePlural: t('SearchPage.contacts'),
},
// {
// id: 'regulations',
// displayName: t('SearchPage.regulation'),
// displayNamePlural: t('SearchPage.regulations'),
// },
{
id: 'regulations',
displayName: t('SearchPage.regulation'),
displayNamePlural: t('SearchPage.regulations'),
},
{
id: 'officialBoard',
displayName: t('SearchPage.document'),
displayNamePlural: t('officialBoard'),
},
]

// TODO We mutate these values in order to use variable search sections, for example to search only in one search option. The current solution works, but it may be refactored in the future.
if (variant === 'specific') {
searchOptions = searchOptions.filter((option) => option.id === searchOption)
defaultSearchOption = searchOptions[0]
Expand Down
Loading