Skip to content

Commit

Permalink
feat: implement initiatives page design, infinite scrolling and filte…
Browse files Browse the repository at this point in the history
…ring
  • Loading branch information
georgimld committed Oct 22, 2024
1 parent ecd80bf commit c0d560e
Show file tree
Hide file tree
Showing 15 changed files with 338 additions and 71 deletions.
46 changes: 23 additions & 23 deletions app/app/contexts/project-page-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import { SeverityLevel } from '@microsoft/applicationinsights-web';

import { BasicProject } from '@/common/types';
import { errorMessage } from '@/components/common/CustomToast';
import { getProjects } from '@/utils/requests/project/requests';
import { GetProjectsBySearchString } from '@/utils/requests/project/requests';

export type ProjectFilters = {
projectIds: string[];
types: string[];
searchString: string;
};

Expand All @@ -36,7 +34,7 @@ const defaultState: ProjectPageContextInterface = {
isLoading: false,
hasMore: true,
sort: SortValues.DESC,
filters: { projectIds: [], types: [], searchString: '' },
filters: { searchString: '' },
refetchProjects: () => Promise.resolve(),
toggleSort: () => {},
setFilters: () => {},
Expand Down Expand Up @@ -76,36 +74,31 @@ export const ProjectPageContextProvider = ({ children, ...props }: ProjectPageCo

const refetchProjects = useCallback(
async (options: { page: number; filters: ProjectFilters }) => {
const filters = options.filters;
const searchString = options.filters.searchString;
const pageSize = 10;

try {
if (options.page === 1) {
setIsLoading(true);
}

// const result =
// (await getProjects({
// pagination: {
// page: options.page,
// pageSize: pageSize,
// },
// filterBy: {
// projectIds: filters.projectIds.length ? filters.projectIds : undefined,
// searchString: filters.searchString.length ? filters.searchString : undefined,
// },
// sortsorBy: { updatedAt: 'DESC' },
// })) || [];
const result = (await getProjects()) || []; //todo apply filters
const result =
(await GetProjectsBySearchString({
pagination: {
page: options.page,
pageSize: pageSize,
},
sort: { by: 'title', order: 'desc' },
searchString,
})) || [];

const entries = options.page > 1 ? [...projects, ...result] : result;
setProjects(entries);
setHasMore(result.length >= pageSize);
} catch (error) {
errorMessage({ message: 'Error refetching news feed. Please check your connection and try again.' });
console.error('Error refetching news feed:', error);
errorMessage({ message: 'Error refetching projects. Please check your connection and try again.' });
console.error('Error refetching projects:', error);
appInsights.trackException({
exception: new Error('Error refetching news feed', { cause: error }),
exception: new Error('Error refetching projects', { cause: error }),
severityLevel: SeverityLevel.Error,
});
} finally {
Expand All @@ -115,6 +108,13 @@ export const ProjectPageContextProvider = ({ children, ...props }: ProjectPageCo
[appInsights, projects],
);

const updateFilters = (filters: ProjectFilters) => {
const page = 1;
setFilters(filters);
setPageNumber(page);
refetchProjects({ page, filters });
};

const contextObject: ProjectPageContextInterface = {
projects,
sort,
Expand All @@ -123,7 +123,7 @@ export const ProjectPageContextProvider = ({ children, ...props }: ProjectPageCo
hasMore,
refetchProjects: () => refetchProjects({ filters, page: pageNumber }),
toggleSort,
setFilters,
setFilters: updateFilters,
loadNextPage,
};

Expand Down
2 changes: 0 additions & 2 deletions app/app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import Box from '@mui/material/Box';
import Container from '@mui/material/Container';
import Stack from '@mui/material/Stack';

import { Project } from '@/common/types';
import BreadcrumbsNav from '@/components/common/BreadcrumbsNav';
import ErrorPage from '@/components/error/ErrorPage';
import NewsProjectCard from '@/components/newsPage/cards/NewsProjectCard';
import { Projects } from '@/components/projectsPage/Projects';
import ProjectsPageContainer from '@/components/projectsPage/ProjectsPageContainer';
import * as m from '@/src/paraglide/messages.js';
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ interface ProjectCardProps {
summary: string;
status: string;
progressBarContainersx?: SxProps;
cardSize?: { height: string; width: string };
}

export default function ProjectCard(props: ProjectCardProps) {
const { id, img, contributors, title, summary, status, progressBarContainersx } = props;
const { id, img, contributors, title, summary, status, progressBarContainersx, cardSize } = props;

const isWideScreen = useMediaQuery(theme.breakpoints.up('sm'));

return (
<Card sx={{ ...cardStyles, height: isWideScreen ? 490 : 440 }}>
<Card sx={{ ...cardStyles, height: isWideScreen ? cardSize?.height || 490 : 440, width: cardSize?.width || 466 }}>
<Box sx={cardWrapperStyles}>
<CardMedia sx={{ borderRadius: '8px', overflow: 'hidden', padding: '24px' }}>
<Image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ import Box from '@mui/material/Box';
import Skeleton from '@mui/material/Skeleton';
import Stack from '@mui/material/Stack';

export const NewsSkeleton = ({ count }: { count: number }) => {
interface SkeletonProps {
count: number;
size: {
width: string;
height: string;
};
}

export const CommonSkeleton = ({ count, size }: SkeletonProps) => {
return (
<Stack spacing={2}>
{new Array(count).fill(0).map((_, idx) => (
<Box key={idx} sx={{ backgroundColor: 'white', borderRadius: '8px' }}>
<Skeleton variant="rounded" width={'full'} height={'200px'} />
<Skeleton variant="rounded" width={size.width} height={size.height} />
</Box>
))}
</Stack>
Expand Down
2 changes: 1 addition & 1 deletion app/components/landing/projectSection/ProjectCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getImageByBreakpoint } from '@/utils/helpers';
import Carousel from '../Carousel';
import { defaultImage } from '../featuredProjectSection/FeaturedProjectSlider';

import ProjectCard from './ProjectCard';
import ProjectCard from '../../common/project/ProjectCard';
import { ProjectProps } from './ProjectSection';

import 'slick-carousel/slick/slick-theme.css';
Expand Down
2 changes: 1 addition & 1 deletion app/components/newsFeed/MobileNewsFeedFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { FilterOption } from '@/components/common/FilterSelect';
import CloseIcon from '@/components/icons/CloseIcon';
import theme from '@/styles/theme';

import ApplyFilterButton, { APPLY_BUTTON } from '../newsPage/newsFilter/ApplyFilterButton';
import ApplyFilterButton, { APPLY_BUTTON } from '../common/ApplyFilterButton';

import NewsFeedProjectsFilter from './NewsFeedProjectsFilter';
import NewsFeedSearchFilter from './NewsFeedSearchFilter';
Expand Down
4 changes: 2 additions & 2 deletions app/components/newsPage/NewsFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
ProjectUpdate,
SurveyQuestion,
} from '@/common/types';
import { NewsSkeleton } from '@/components/newsPage/skeletons/NewsSkeleton';
import { NewsCollaborationCommentThread } from '@/components/newsPage/threads/NewsCollaborationCommentThread';
import * as m from '@/src/paraglide/messages.js';

Expand All @@ -30,6 +29,7 @@ import NewsProjectCard from './cards/NewsProjectCard';
import NewsSurveyCard from './cards/NewsSurveyCard';
import { NewsPostThread } from './threads/NewsPostThread';
import { NewsUpdateThread } from './threads/NewsUpdateThread';
import { CommonSkeleton } from '../common/skeletons/CommonSkeleton';

interface NewsProps {
sx?: SxProps;
Expand All @@ -42,7 +42,7 @@ export const NewsFeed = (props: NewsProps) => {
return (
<Box sx={{ width: '100%', ...sx }} data-testid="news-container">
{isLoading ? (
<NewsSkeleton count={5} />
<CommonSkeleton count={5} size={{ width: 'full', height: '200px' }} />
) : (
<InfiniteScroll
dataLength={feed.length}
Expand Down
2 changes: 1 addition & 1 deletion app/components/newsPage/newsFilter/MobileNewsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import CloseIcon from '@/components/icons/CloseIcon';
import * as m from '@/src/paraglide/messages.js';
import theme from '@/styles/theme';

import ApplyFilterButton, { APPLY_BUTTON } from './ApplyFilterButton';
import ApplyFilterButton, { APPLY_BUTTON } from '../../common/ApplyFilterButton';
import NewsProjectsFilter from './NewsProjectsFilter';
import NewsTopicFilter from './NewsTopicFilter';

Expand Down
92 changes: 92 additions & 0 deletions app/components/projectsPage/MobileProjectFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use client';

import React, { useCallback, useState } from 'react';
import { isEqual } from 'lodash';

import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import SwipeableDrawer from '@mui/material/SwipeableDrawer';

import { closeIconButtonStyle } from '@/components/common/CustomDialog';
import CloseIcon from '@/components/icons/CloseIcon';
import theme from '@/styles/theme';

import ApplyFilterButton, { APPLY_BUTTON } from '../common/ApplyFilterButton';
import { Typography, Card } from '@mui/material';
import ProjectFilter from './ProjectFilter';

interface MobileProjectFilterProps {
open: boolean;
setOpen: (open: boolean) => void;
}

//todo combine with MobileNewsFeedFilter.tsx

export default function MobileNewsFeedFilter(props: MobileProjectFilterProps) {
const { open, setOpen } = props;

const toggleDrawer = (newOpen: boolean) => () => {
setOpen(newOpen);
};

const applyFilters = () => {
setOpen(false);
};

return (
<Box mb={0} sx={{ backgroundColor: theme.palette.background.paper }} date-testid="projects-filter">
<SwipeableDrawer
sx={{
'& .MuiPaper-root': {
backgroundColor: 'transparent',
height: 'calc(100% - 200px)',
},
}}
anchor="bottom"
open={open}
onClose={toggleDrawer(false)}
onOpen={toggleDrawer(true)}
>
<Box mr="15px" display="flex" justifyContent="flex-end" alignItems="flex-end">
<IconButton onClick={toggleDrawer(false)} sx={closeIconButtonStyle}>
<CloseIcon color={theme.palette.text.primary} />
</IconButton>
</Box>

<Box sx={drawerBoxStyle}>
<Typography variant="overline">Filtern</Typography>
<Card sx={cardStyles}>
<ProjectFilter />
</Card>
</Box>

<ApplyFilterButton onClick={applyFilters} applyButtonType={APPLY_BUTTON.ENABLED} />
</SwipeableDrawer>
</Box>
);
}

const cardStyles = {
minHeight: '300px',
borderRadius: '16px',
border: '1px solid rgba(255, 255, 255, 0.20)',
background: 'rgba(255, 255, 255, 0.10)',
boxShadow: '0px 12px 40px 0px rgba(0, 0, 0, 0.25)',
backdropFilter: 'blur(20px)',
height: 'fit-content !important',
marginBottom: 4,
};

const drawerBoxStyle = {
minHeight: '300px',
overflow: 'scroll',
p: 3,
pb: 0,
m: '15px',
mb: 0,
borderRadius: '16px',
border: '1px solid rgba(0, 90, 140, 0.20)',
backgroundColor: 'primary.light',
boxShadow:
'0px 6px 6px -3px rgba(0, 0, 0, 0.05), 0px 10px 22px 1px rgba(0, 0, 0, 0.14), 0px 4px 26px 3px rgba(0, 0, 0, 0.12)',
};
75 changes: 75 additions & 0 deletions app/components/projectsPage/ProjectFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use client';

import React, { useEffect, useState } from 'react';

import { Search } from '@mui/icons-material';
import { Box, FormControl, InputAdornment, TextField, Typography } from '@mui/material';

import { useProjects } from '@/app/contexts/project-page-context';

export default function ProjectFilter() {
const { filters, setFilters } = useProjects();
const [inputValue, setInputValue] = useState('');

const updateFilters = (searchString: string) => {
const updatedFilters = { ...filters, searchString };
setFilters(updatedFilters);
};

const handleInputChange = (event: { target: { value: string } }) => {
const newValue = event.target.value;
setInputValue(newValue);
};

useEffect(() => {
const timeoutId = setTimeout(() => {
updateFilters(inputValue);
}, 300);

return () => {
clearTimeout(timeoutId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValue]);

return (
<Box sx={{ margin: 3 }}>
<FormControl variant="standard" sx={{ width: '100%' }}>
<Typography mb={1} color="white" variant="button">
search
</Typography>
<TextField
onChange={handleInputChange}
variant="outlined"
fullWidth
sx={{
'& .MuiOutlinedInput-root': {
'& fieldset': {
borderColor: 'white',
},
'&:hover fieldset': {
borderColor: 'white',
},
'&.Mui-focused fieldset': {
borderColor: 'white',
},
'& input': {
color: 'white',
},
},
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search sx={{ color: 'white' }} />
</InputAdornment>
),
}}
inputProps={{
'aria-label': 'Search field',
}}
/>
</FormControl>
</Box>
);
}
Loading

0 comments on commit c0d560e

Please sign in to comment.