Skip to content

Commit

Permalink
Merge pull request #1131 from City-of-Helsinki/UHF-10772
Browse files Browse the repository at this point in the history
UHF-10772: Extend linkedevents app
  • Loading branch information
jeremysteerio authored Dec 12, 2024
2 parents 08ec731 + 0de096d commit d35f85e
Show file tree
Hide file tree
Showing 21 changed files with 134 additions and 47 deletions.
2 changes: 1 addition & 1 deletion dist/css/styles.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/district-and-project-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/health-station-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/job-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/linkedevents.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/maternity-and-child-health-clinic-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/news-archive.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/ploughing-schedule.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/school-search.min.js

Large diffs are not rendered by default.

28 changes: 19 additions & 9 deletions src/js/react/apps/linkedevents/components/ResultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ function ResultCard({ end_time, id, location, name, keywords=[], start_time, ima
const { baseUrl, imagePlaceholder } = drupalSettings.helfi_events;
const url = `${baseUrl}/${currentLanguage}/events/${id}`;

// Bail if no current language
if (!name[currentLanguage]) {
return null;
}
const resolvedName = name?.[currentLanguage] || name?.fi || Object.values(name)[0] || '';

const getDate = () => {
let startDate;
Expand All @@ -43,7 +40,7 @@ function ResultCard({ end_time, id, location, name, keywords=[], start_time, ima
endDate = new Date(end_time);
isMultiDate = end_time ? overDayApart(startDate, endDate) : false;
} catch (e) {
throw new Error('DATE ERROR');
throw new Error(`DATE ERROR ${e}`);
}

if (isMultiDate) {
Expand Down Expand Up @@ -100,16 +97,29 @@ function ResultCard({ end_time, id, location, name, keywords=[], start_time, ima
return <img alt='' {...imageProps} />;
};

const image = images?.find(img => img.url);
const getImage = () => {
const image = images?.find(img => img.url);

if (image) {
return imageToElement(image);
}
if (imagePlaceholder) {
return parse(imagePlaceholder);
}

return (
<div className='image-placeholder'></div>
);
};

const isRemote = location && location.id === INTERNET_EXCEPTION;
const title = name[currentLanguage] || '';
const cardTags = getCardTags({keywords, currentLanguage});

return (
<CardItem
cardUrl={url}
cardTitle={title}
cardImage={image ? imageToElement(image) : parse(imagePlaceholder) }
cardTitle={resolvedName}
cardImage={getImage()}
cardTags={cardTags}
cardUrlExternal
location={isRemote ? 'Internet' : getLocation()}
Expand Down
3 changes: 2 additions & 1 deletion src/js/react/apps/linkedevents/components/SeeAllButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import ExternalLink from '../../../common/ExternalLink';

function SeeAllButton() {
const eventsUrl = useAtomValue(eventsPublicUrl) || '';
const { seeAllButtonOverride } = drupalSettings?.helfi_events || null;

return (
<div className="event-list__see-all-button">
<ExternalLink
data-hds-component="button"
data-hds-variant="secondary"
href={eventsUrl}
title={Drupal.t('Refine search in tapahtumat.hel.fi', {}, { context: 'Events search' })} />
title={seeAllButtonOverride || Drupal.t('Refine search in tapahtumat.hel.fi', {}, { context: 'Events search' })} />
</div>
);
}
Expand Down
54 changes: 36 additions & 18 deletions src/js/react/apps/linkedevents/containers/ResultsContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { createRef } from 'react';
import { createRef, useEffect, useState } from 'react';
import { useAtomValue } from 'jotai';

import ResultsError from '@/react/common/ResultsError';
import LoadingOverlay from '@/react/common/LoadingOverlay';
import useScrollToResults from '@/react/common/hooks/useScrollToResults';
import Pagination from '../components/Pagination';
import ResultCard from '../components/ResultCard';
import CardGhost from '@/react/common/CardGhost';
import SeeAllButton from '../components/SeeAllButton';
import { settingsAtom, urlAtom } from '../store';
import type Event from '../types/Event';
import ResultsHeader from '@/react/common/ResultsHeader';
import ResultsEmpty from '@/react/common/ResultsEmpty';
import LoadingOverlay from '@/react/common/LoadingOverlay';

type ResultsContainerProps = {
countNumber: number;
Expand All @@ -20,20 +21,20 @@ type ResultsContainerProps = {
};

function ResultsContainer({ countNumber, events, loading, error }: ResultsContainerProps) {
const { useExperimentalGhosts } = drupalSettings.helfi_events;
const settings = useAtomValue(settingsAtom);
const scrollTarget = createRef<HTMLDivElement>();
const url = useAtomValue(urlAtom);
// Checks when user makes the first search and api url is set.
const choices = Boolean(url);
useScrollToResults(scrollTarget, choices);
const [initialized, setInitialized] = useState(false);
useScrollToResults(scrollTarget, initialized && choices && !loading);

if (loading) {
return (
<div className='hdbt__loading-wrapper'>
<LoadingOverlay />
</div>
);
}
useEffect(() => {
if (!initialized && !loading) {
setInitialized(true);
}
}, [initialized, setInitialized, loading]);

if (error) {
return (
Expand All @@ -44,29 +45,46 @@ function ResultsContainer({ countNumber, events, loading, error }: ResultsContai
);
}

if (loading && !useExperimentalGhosts) {
return (
<div className='hdbt__loading-wrapper'>
<LoadingOverlay />
</div>
);
}

const size = settings.eventCount;
const pages = Math.floor(countNumber / size);
const addLastPage = countNumber > size && countNumber % size;
const count = countNumber.toString();

return (
<div className='react-search__list-container'>
{events?.length > 0 ?
const getContent = () => {
if (loading && !events.length) {
return Array.from({ length: size }, (_, i) => <CardGhost key={i} />);
}
if (events.length > 0) {
return (
<>
<ResultsHeader
resultText={
<>
{ Drupal.formatPlural(count, '1 event', '@count events',{},{context: 'Events search: result count'}) }
{Drupal.formatPlural(count, '1 event', '@count events', {}, {context: 'Events search: result count'})}
</>
}
ref={scrollTarget}
/>
{events.map(event => <ResultCard key={event.id} {...event} />)}
{events.map(event => loading ? <CardGhost key={event.id} /> : <ResultCard key={event.id} {...event} />)}
<Pagination pages={5} totalPages={addLastPage ? pages + 1 : pages} />
</>
:
<ResultsEmpty wrapperClass='event-list__no-results' ref={scrollTarget} />
}
);
}

return <ResultsEmpty wrapperClass='event-list__no-results' ref={scrollTarget} />;
};

return (
<div className={`react-search__list-container${loading ? ' loading' : ''}`}>
{getContent()}
<SeeAllButton />
</div>
);
Expand Down
7 changes: 4 additions & 3 deletions src/js/react/apps/linkedevents/containers/SearchContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import useSWR from 'swr';
import { useAtomValue, useAtom } from 'jotai';

import { useEffect, useState } from 'react';
import ResultsContainer from './ResultsContainer';
import FormContainer from './FormContainer';
import type Event from '../types/Event';
Expand All @@ -26,6 +27,7 @@ const SWR_REFRESH_OPTIONS = {
};

const SearchContainer = () => {
const { useExperimentalGhosts } = drupalSettings.helfi_events;
const initialUrl = useAtomValue(initialUrlAtom);
const initialParams = useAtomValue(initialParamsAtom);
const [params, setParams] = useAtom(paramsAtom);
Expand Down Expand Up @@ -59,13 +61,12 @@ const SearchContainer = () => {

throw new Error('Failed to get data from the API');
};
const { data, error } = useSWR(url, getEvents, SWR_REFRESH_OPTIONS);
const loading = !error && !data;
const { data, error, isLoading } = useSWR(url, getEvents, {...SWR_REFRESH_OPTIONS, keepPreviousData: useExperimentalGhosts});

return (
<>
<FormContainer />
<ResultsContainer error={error} countNumber={data?.meta.count || 0} loading={loading} events={data?.data || []} />
<ResultsContainer error={error} countNumber={data?.meta.count || 0} loading={isLoading} events={data?.data || []} />
</>
);
};
Expand Down
7 changes: 3 additions & 4 deletions src/js/react/apps/linkedevents/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import OptionType from './types/OptionType';
import FormErrors from './types/FormErrors';
import ApiKeys from './enum/ApiKeys';
import Topic from './types/Topic';
import type Event from './types/Event';

interface Options {
[key: string]: string
Expand Down Expand Up @@ -50,18 +49,18 @@ const createBaseAtom = () => {
const useFixtures = settings?.use_fixtures;
const eventsApiUrl = settings?.events_api_url;
const eventListTitle = settings?.field_event_list_title;
const eventsPublicUrl = settings?.events_public_url;
const eventsPublicUrl = settings?.events_public_url || 'https://tapahtumat.hel.fi';

const filterSettings: FilterSettings = {
showLocation: settings?.field_event_location,
showTimeFilter: settings?.field_event_time,
showFreeFilter: settings?.field_free_events,
showRemoteFilter: settings?.field_remote_events,
showTopicsFilter: settings?.field_filter_keywords.length > 0,
showTopicsFilter: settings?.field_filter_keywords?.length > 0,
eventCount: Number(settings?.field_event_count)
};
const locations = transformLocations(settings?.places);
const topics: Topic[] = settings?.field_filter_keywords.map(topic => ({
const topics: Topic[] = settings?.field_filter_keywords?.map(topic => ({
value: topic.id,
label: topic.name.charAt(0).toUpperCase() + topic.name.slice(1),
}));
Expand Down
6 changes: 5 additions & 1 deletion src/js/react/common/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ function CardItem({
languageEducation,
registrationRequired,
}: CardItemProps): JSX.Element {
const cardClass = `card${cardModifierClass ? ` ${cardModifierClass}` : ''}${cardUrlExternal ? ' card--external' : ''}`;
const cardClass = `
card
${cardModifierClass ? ` ${cardModifierClass}` : ''}
${cardUrlExternal ? ' card--external' : ''}
`;
const HeadingTag = cardTitleLevel ? `h${cardTitleLevel}` as keyof JSX.IntrinsicElements : 'h3';

return (
Expand Down
9 changes: 9 additions & 0 deletions src/js/react/common/CardGhost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function CardGhost() {
return (
<div className='card card--ghost'>
<div className='card__image'>
<div className="image-placeholder"></div>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion src/js/react/common/hooks/useScrollToResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const useScrollToResults = (ref: RefObject<HTMLElement>, shouldScrollOnRender: b

if (current && shouldScrollOnRender) {
current.setAttribute('tabindex', '-1');
current.focus();
current.focus({preventScroll: true});
current.scrollIntoView({behavior: 'smooth', block: 'center'});
}
}, [ref, shouldScrollOnRender]);
Expand Down
4 changes: 3 additions & 1 deletion src/js/types/drupalSettings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ declare namespace drupalSettings {
},
use_fixtures: boolean
}
}
},
seeAllButtonOverride: string,
useExperimentalGhosts: boolean,
};
const helfi_react_search: {
// @todo UHF-10862 Remove cookie_privacy_url once the HDBT cookie banner module is in use.
Expand Down
4 changes: 4 additions & 0 deletions src/scss/05_objects/_card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ $item-gap: $spacing-half;
}
}

.card--ghost {
background: $color-black-20;
}

.card__image {
flex-basis: 30%;
flex-shrink: 0;
Expand Down
9 changes: 9 additions & 0 deletions src/scss/06_components/paragraphs/_event-list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ $tag-vertical-padding: 5px;
@include component-side-padding;
}

.component--react-search.component--coordinates-based-event-list {
background-color: $color-white;
}

// Use experimental card border styles for this paragraph.
.component--coordinates-based-event-list .card:not(.card--ghost) {
border: 2px solid $color-black-20;
}

.events-list__empty-subtext {
margin-bottom: $spacing-and-half;
}
Expand Down
30 changes: 30 additions & 0 deletions src/scss/06_components/paragraphs/_react-search.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@
margin-bottom: $spacing-double;
}

.react-search__list-container.loading .card {
overflow: hidden;
position: relative;
}

@keyframes loading {
0% {
transform: skewX(-10deg) translateX(-150%);
}

100% {
transform: skewX(-10deg) translateX(250%);
}
}

.react-search__list-container.loading .card::after {
animation: loading 1.2s infinite;
background: linear-gradient(
90deg,
transparent,
$color-white,
transparent,
);
bottom: 0;
content: "";
position: absolute;
top: 0;
width: 50%;
};

.react-search__results-stats {
margin-bottom: $spacing;
}
Expand Down

0 comments on commit d35f85e

Please sign in to comment.