Skip to content

Commit a30cf1f

Browse files
committed
Project List updates based on applied filters
* New Shareable Card component created * ProjectList component handles the viewing logic * explore projects manages filter events * handles filtering when no cards has filters * Added images to cards cleaned up a bit
1 parent 560427a commit a30cf1f

File tree

15 files changed

+335
-51
lines changed

15 files changed

+335
-51
lines changed
Lines changed: 36 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,46 @@
1-
import React, { Component } from 'react';
2-
import sizeMe from 'react-sizeme';
1+
import React, { useState, useEffect } from 'react';
2+
import styled from 'styled-components';
33

4-
// database
5-
import { Row, Col } from 'reactstrap';
64
import { db } from '../../firebase';
75

8-
// custom components
9-
import ProjectCard from '../shared/project-card';
106
import Filter from '../shared/filter';
7+
import ProjectList from './components/project-list';
118

12-
// styles
13-
const headerStyle = { marginBottom: '20px' };
9+
const Header = styled.h2`
10+
margin-bottom: 20px;
11+
`;
1412

15-
class ExploreProjects extends Component {
16-
state = { projects: [], filterTypes: [ 'project-category', 'release-status', 'organization' ] };
13+
const filterTypes = [ 'project-category', 'release-status', 'organization' ];
1714

18-
componentDidMount = async () => {
19-
const projects = await db.getAll('projects');
20-
this.setState({ projects });
21-
};
15+
const ExploreProjects = () => {
16+
const [ appliedTags, setAppliedTags ] = useState([]);
17+
const [ projects, setProjects ] = useState([]);
18+
19+
useEffect(() => {
20+
const init = async () => {
21+
const _projects = await db.getAll('projects');
22+
setProjects(_projects);
23+
};
2224

23-
updateLikes = (likes, index) => {
24-
const { projects } = this.state;
25-
projects[index].likes = likes;
26-
this.setState({ projects });
25+
init();
26+
}, []);
27+
28+
const handleFilter = (tag) => {
29+
const index = appliedTags.findIndex(_tag => _tag === tag.id);
30+
if (index === -1) {
31+
setAppliedTags([ ...appliedTags, tag.id ]);
32+
} else {
33+
setAppliedTags(prevState => [ ...prevState.slice(0, index), ...prevState.slice(index + 1) ]);
34+
}
2735
};
2836

29-
render() {
30-
const { projects, filterTypes } = this.state;
31-
const {
32-
size: { width },
33-
} = this.props;
34-
return (
35-
<div>
36-
<h2 style={headerStyle}>All Projects</h2>
37-
<Filter types={filterTypes} projects={projects} />
38-
<Row>
39-
{projects.map((p, i) => (
40-
<Col sm key={i}>
41-
<ProjectCard
42-
width={width}
43-
key={i}
44-
shortname={p.shortname}
45-
name={p.name}
46-
id={p.id}
47-
text={p.card_des}
48-
/>
49-
</Col>
50-
))}
51-
</Row>
52-
</div>
53-
);
54-
}
55-
}
56-
export default sizeMe()(ExploreProjects);
37+
return (
38+
<div>
39+
<Header>All Projects</Header>
40+
<Filter types={filterTypes} onFilter={handleFilter} />
41+
<ProjectList projects={projects} appliedTags={appliedTags} />
42+
</div>
43+
);
44+
};
45+
46+
export default ExploreProjects;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Row, Col } from 'reactstrap';
3+
4+
import { difference } from 'lodash';
5+
import { db } from '../../../../firebase';
6+
7+
import Card from '../../../shared/card';
8+
9+
const renderProjects = allTags => projects =>
10+
projects.map((p, i) => (
11+
<Col sm key={i}>
12+
<Card
13+
name={p.name}
14+
shortname={p.shortname}
15+
description={p.description}
16+
image={p.images && p.images[0]}
17+
tags={p.tags}
18+
allTags={allTags}
19+
/>
20+
</Col>
21+
));
22+
23+
const ProjectList = ({ projects, appliedTags }) => {
24+
const [ allTags, setAllTags ] = useState([]);
25+
const [ filteredProjects, setFilteredProjects ] = useState([]);
26+
27+
useEffect(() => {
28+
const init = async () => {
29+
const _tags = await db.getAll('tags');
30+
setAllTags(_tags);
31+
};
32+
init();
33+
}, []);
34+
35+
useEffect(() => {
36+
const filtered = projects.filter(
37+
project => project.tags && difference(appliedTags, project.tags).length === 0,
38+
);
39+
setFilteredProjects(filtered);
40+
}, [ appliedTags ]);
41+
42+
return (
43+
<Row>
44+
{appliedTags.length > 0
45+
? renderProjects(allTags)(filteredProjects)
46+
: renderProjects(allTags)(projects)}
47+
</Row>
48+
);
49+
};
50+
51+
export default ProjectList;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './ProjectList';

src/components/shared/card/Card.jsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react';
2+
import { withRouter } from 'react-router-dom';
3+
import styled from 'styled-components';
4+
5+
import CardImage from './components/card-image';
6+
import CardTitle from './components/card-title';
7+
import CardBody from './components/card-body';
8+
import CardTags from './components/card-tags';
9+
10+
const Container = styled.div`
11+
height: 300px;
12+
display: flex;
13+
flex-direction: column;
14+
border: 1px solid rgba(33, 33, 33, 0.2);
15+
border-radius: 0.5rem;
16+
margin-bottom: 10px;
17+
min-width: 318px;
18+
max-width: 320px;
19+
20+
:hover {
21+
cursor: pointer;
22+
}
23+
24+
:hover #project-title {
25+
text-decoration: underline;
26+
color: #0056b3;
27+
}
28+
`;
29+
30+
const CardHeader = styled.div`
31+
display: flex;
32+
flex: 1;
33+
justify-content: space-between;
34+
35+
padding: 30px 20px 0px;
36+
max-height: min-content;
37+
`;
38+
39+
const Card = ({ history, name, shortname, description, image, tags, allTags }) => {
40+
const openProject = () => {
41+
if (shortname !== undefined) {
42+
history.push(`/projects/${ shortname }`);
43+
}
44+
};
45+
46+
return (
47+
<Container onClick={openProject}>
48+
<CardHeader>
49+
<CardTitle title={name} />
50+
<CardImage image={image} />
51+
</CardHeader>
52+
<CardTags tags={tags} allTags={allTags} />
53+
<CardBody>{description}</CardBody>
54+
</Container>
55+
);
56+
};
57+
58+
Card.defaultProps = {
59+
description: '',
60+
};
61+
62+
export default withRouter(Card);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
4+
const Container = styled.div`
5+
margin: 0px 20px 30px;
6+
// padding-top: 10px;
7+
overflow: hidden;
8+
display: -webkit-box;
9+
-webkit-line-clamp: 3;
10+
-webkit-box-orient: vertical;
11+
height: 78px;
12+
`;
13+
14+
const Text = styled.div`
15+
line-height: 1.3;
16+
font-size: 1.25rem;
17+
color: lightslategrey;
18+
`;
19+
20+
const CardBody = ({ children }) => (
21+
<Container>
22+
<Text>{children}</Text>
23+
</Container>
24+
);
25+
26+
export default CardBody;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './CardBody';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
4+
const Container = styled.div`
5+
img {
6+
max-width: 120px;
7+
max-height: 84px;
8+
width: auto;
9+
height: auto;
10+
}
11+
`;
12+
13+
const CardImage = ({ image }) => (
14+
<Container>
15+
<img src={image || 'https://via.placeholder.com/300'} alt="project cover" />
16+
</Container>
17+
);
18+
19+
export default CardImage;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './CardImage';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React, { useMemo } from 'react';
2+
import styled from 'styled-components';
3+
4+
const Container = styled.div`
5+
max-height: min-content;
6+
flex: 1;
7+
display: flex;
8+
flex-wrap: wrap;
9+
color: #007bff;
10+
padding: 10px 20px;
11+
`;
12+
13+
const CardTags = ({ tags, allTags }) => {
14+
const _tags = useMemo(() => allTags.filter(tag => tags.includes(tag.id)), [ tags, allTags ]);
15+
return (
16+
<Container>
17+
{_tags.map(tag => (
18+
<span>#{tag.name}</span>
19+
))}
20+
</Container>
21+
);
22+
};
23+
24+
CardTags.defaultProps = {
25+
tags: [],
26+
};
27+
28+
export default CardTags;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './CardTags';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
4+
const Container = styled.div`
5+
// height: min-content;
6+
// align-self: flex-end;
7+
`;
8+
9+
const CardTitle = ({ title }) => (
10+
<Container id="project-title">
11+
<h4>{title}</h4>
12+
</Container>
13+
);
14+
15+
export default CardTitle;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './CardTitle';

src/components/shared/card/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './Card';

src/components/shared/filter/Filter.jsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import React, { useState, useEffect } from 'react';
2+
import styled from 'styled-components';
23

34
import { Row } from 'reactstrap';
45
import { db } from '../../../firebase';
56

67
import FilterDropdown from './components/filter-dropdown';
78
import SearchBar from './components/search-bar';
89

9-
const Filter = ({ types, projects }) => {
10+
const Container = styled.div`
11+
margin-bottom: 10px;
12+
`;
13+
14+
const Filter = ({ types, onFilter }) => {
1015
const [ categories, setCategories ] = useState([]);
1116
const [ toggles, setToggles ] = useState([]);
1217

@@ -38,8 +43,6 @@ const Filter = ({ types, projects }) => {
3843
};
3944

4045
const onSelectTag = (i, j) => {
41-
console.log('i', i);
42-
console.log('j', j);
4346
setCategories(prevState => [
4447
...prevState.slice(0, i),
4548
[
@@ -49,10 +52,12 @@ const Filter = ({ types, projects }) => {
4952
],
5053
...prevState.slice(i + 1),
5154
]);
55+
56+
onFilter(categories[i][j]);
5257
};
5358

5459
return (
55-
<div>
60+
<Container>
5661
<SearchBar categories={categories} handleCancel={onSelectTag} />
5762
<Row>
5863
{categories.map((category, i) => (
@@ -65,7 +70,7 @@ const Filter = ({ types, projects }) => {
6570
/>
6671
))}
6772
</Row>
68-
</div>
73+
</Container>
6974
);
7075
};
7176

0 commit comments

Comments
 (0)