Skip to content

Commit e9d494b

Browse files
authored
Add a CFP view (#1282)
* feat: add a CFP view * fix: ordering by closing date * fix: display opened CFPs sorting in closing date * feat: improve a little the CFP view UI * fix: cfp view thank to gouuuuzzz * fix: the number of events is now the number of opened cfp in cfp view * feat: in cfp view don't display certains filters * feat: improve cfp view UI * feat: improve CFP view UI * feat: add title and aria-label in views icons
1 parent e3a26ed commit e9d494b

File tree

9 files changed

+325
-8
lines changed

9 files changed

+325
-8
lines changed

page/src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Year } from 'routes/year';
77
import { DatePage } from 'routes/datepage';
88
import { MapPage } from 'routes/mappage';
99
import { ListPage } from 'routes/listpage';
10+
import { CfpPage } from 'routes/cfppage';
1011
import { FilterContext } from 'contexts/FilterContext';
1112
import { ScrollToTopButton } from './components/ScrollToTopButton/ScrollToTopButton';
1213

@@ -27,6 +28,7 @@ const App = () => {
2728
<Route path=":year/calendar/:month?/:date?" Component={DatePage} />
2829
<Route path="/:year/map" Component={MapPage} />
2930
<Route path="/:year/list" Component={ListPage} />
31+
<Route path="/:year/cfp" Component={CfpPage} />
3032
</Routes>
3133
<ScrollToTopButton />
3234
</Router>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from 'react';
2+
import 'styles/CfpView.css';
3+
import { Clock, CalendarClock } from 'lucide-react';
4+
5+
import {useYearEvents} from 'app.hooks';
6+
import {getMonthName, getMonthNames} from 'utils';
7+
import {formatEventDates} from 'components/EventDisplay/EventDisplay.utils';
8+
9+
const CfpView = () => {
10+
let events = useYearEvents();
11+
12+
// Display only opened callForPapers
13+
events = events.filter(e => e.cfp && new Date(e.cfp.untilDate + 24 * 60 * 60 * 1000) > new Date());
14+
15+
// Sort CFPs based on the closing date
16+
events = events.sort((a, b) => {
17+
if (!a.cfp?.untilDate || !b.cfp?.untilDate) return 0;
18+
return new Date(a.cfp.untilDate) - new Date(b.cfp.untilDate);
19+
});
20+
21+
const eventsByMonth = events.reduce((acc, cur) => {
22+
let monthKey;
23+
monthKey = getMonthName(new Date(cur.cfp.untilDate).getMonth());
24+
if (!acc[monthKey]) {
25+
acc[monthKey] = [];
26+
}
27+
acc[monthKey].push(cur);
28+
29+
return acc;
30+
}, {});
31+
32+
// Get the month names in the correct order based on sort type
33+
const monthOrder = Object.keys(eventsByMonth).sort((a, b) => {
34+
const monthA = getMonthNames().indexOf(a);
35+
const monthB = getMonthNames().indexOf(b);
36+
return monthA - monthB;
37+
});
38+
39+
40+
return (
41+
<div className="cfpView">
42+
{monthOrder.map(month => (
43+
<React.Fragment key={month}>
44+
<h1>{month} Opened CFPs:</h1>
45+
<div className="eventsGridDisplay">
46+
{eventsByMonth[month].map((e, i) => (
47+
48+
<div key={`${month}_ev_${i}`} className='eventCell'>
49+
50+
<div className="content">
51+
<div>
52+
<b>{e.hyperlink ? <a class="title" href={e.hyperlink} target="_blank">{e.name}</a> : ''} ({formatEventDates(e.date)})</b>
53+
54+
<span class="until"><Clock color="green" />Until {e.cfp.until} </span>
55+
<span>{e.location}</span>
56+
</div>
57+
<a href={e.cfp.link} target="_blank" title="Submit to the CFP" className="submitButton">
58+
<CalendarClock />
59+
Submit to the CFP
60+
</a>
61+
</div>
62+
</div>
63+
))}
64+
</div>
65+
</React.Fragment>
66+
))}
67+
</div>
68+
);
69+
70+
};
71+
72+
export default CfpView;

page/src/components/Filters/Filters.jsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useCountries, useRegions, useRegionsMap } from 'app.hooks';
88
import 'styles/Filters.css';
99
import { FilterContext } from 'contexts/FilterContext';
1010

11-
const Filters = () => {
11+
const Filters = ({ view }) => {
1212
const context = useContext(FilterContext);
1313
const [searchParams, setSearchParams] = useSearchParams(context.searchParams);
1414
const [open, setOpen] = useState(context.open);
@@ -64,18 +64,26 @@ const Filters = () => {
6464
</div>
6565

6666
<div className='filtersList'>
67+
68+
69+
{view != "cfp" ?
6770
<div className='filtersItem'>
6871
<input checked={search.callForPapers == 'true'} type='checkbox' id='filter-call-for-papers' onChange={(e) => onChange('callForPapers', e.target.checked)} />
6972
<label htmlFor='filter-call-for-papers'>Call For Papers Open</label>
70-
</div>
73+
</div> : ''}
74+
75+
{view != "cfp" ?
7176
<div className='filtersItem'>
7277
<input checked={search.closedCaptions == 'true'} type='checkbox' id='filter-closed-captions' onChange={(e) => onChange('closedCaptions', e.target.checked)} />
7378
<label htmlFor='filter-closed-captions'>Closed Captions</label>
74-
</div>
79+
</div> : ''}
80+
81+
{view != "cfp" ?
7582
<div className='filtersItem'>
7683
<input checked={search.scholarship == 'true'} type='checkbox' id='filter-scholarship' onChange={(e) => onChange('scholarship', e.target.checked)} />
7784
<label htmlFor='filter-scholarship'>Scholarship</label>
78-
</div>
85+
</div> : ''}
86+
7987
<div className='filtersItem'>
8088
<input checked={search.online == 'true'} type='checkbox' id='filter-online' onChange={(e) => onChange('online', e.target.checked)} />
8189
<label htmlFor='filter-online'>Online</label>

page/src/components/ViewSelector/ViewSelector.jsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import {useNavigate, useSearchParams, createSearchParams, useParams} from "react-router-dom";
33
import 'styles/ViewSelector.css';
4-
import {Calendar, List, Map} from 'lucide-react';
4+
import {Calendar, List, Map, Megaphone} from 'lucide-react';
55

66
const ViewSelector = ({selected}) => {
77
const navigate = useNavigate();
@@ -10,20 +10,33 @@ const ViewSelector = ({selected}) => {
1010

1111
return (
1212
<div className="view-type-selector">
13+
<Megaphone
14+
className={selected === 'cfp' ? 'view-selector cfp-view selected' : 'view-selector cfp-view'}
15+
onClick={() => navigate(`/${year}/cfp?${createSearchParams(searchParams)}`)}
16+
size={'32px'}
17+
aria-label="Opened CFPs"
18+
title="Opened CFPs"
19+
/>
1320
<Calendar
1421
className={selected === 'calendar' ? 'view-selector calendar-view selected' : 'view-selector calendar-view'}
1522
onClick={() => navigate(`/${year}/calendar?${createSearchParams(searchParams)}`)}
1623
size={'32px'}
24+
aria-label="Calendar"
25+
title="Calendar"
1726
/>
1827
<List
1928
className={selected === 'list' ? 'view-selector list-view selected' : 'view-selector list-view'}
2029
onClick={() => navigate(`/${year}/list?${createSearchParams(searchParams)}`)}
2130
size={'32px'}
31+
aria-label="List"
32+
title="List"
2233
/>
2334
<Map
2435
className={selected === 'map' ? 'view-selector map-view selected' : 'view-selector map-view'}
2536
onClick={() => navigate(`/${year}/map?${createSearchParams(searchParams)}`)}
2637
size={'32px'}
38+
aria-label="Map"
39+
title="Map"
2740
/>
2841
</div>
2942
);

page/src/components/YearSelector/YearSelector.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import EventCount from 'components/EventCount/EventCount';
99
import ViewSelector from 'components/ViewSelector/ViewSelector';
1010

1111
const YearSelector = ({ isMap, year, onChange, view }) => {
12-
const yearEvents = useYearEvents()
12+
let yearEvents = useYearEvents()
13+
if (view == "cfp") {
14+
// Display only opened callForPapers
15+
yearEvents = yearEvents.filter(e => e.cfp && new Date(e.cfp.untilDate + 24 * 60 * 60 * 1000) > new Date());
16+
}
1317
return (
1418
<div>
1519
<div className="yearNavigatorWrapper">

page/src/routes/cfppage.jsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Filters from "components/Filters/Filters";
2+
import CfpView from "components/CfpView/CfpView";
3+
import YearSelector from "components/YearSelector/YearSelector";
4+
import { createSearchParams, useNavigate, useParams, useSearchParams } from "react-router-dom";
5+
6+
export function CfpPage() {
7+
const {year} = useParams();
8+
const navigate = useNavigate();
9+
const [searchParams] = useSearchParams();
10+
11+
return (
12+
<div className="dcaGrid">
13+
<Filters
14+
view="cfp"
15+
/>
16+
<div className="dcaContent">
17+
<YearSelector
18+
isMap={false}
19+
year={parseInt(year, 10)}
20+
onChange={year => {
21+
navigate(`/${year}/cfp?${createSearchParams(searchParams)}`);
22+
}}
23+
view="cfp"
24+
/>
25+
26+
<CfpView year={year} />
27+
</div>
28+
</div>
29+
);
30+
}

page/src/routes/datepage.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useHasYearEvents } from "../app.hooks";
33
import { createSearchParams, useNavigate, useParams, useSearchParams } from "react-router-dom";
44
import YearSelector from "components/YearSelector/YearSelector";
55
import { CalendarClock, CalendarDays } from "lucide-react";
6-
import ViewSelector from "components/ViewSelector/ViewSelector";
76
import CalendarGrid from "components/CalendarGrid/CalendarGrid";
87
import SelectedEvents from "components/SelectedEvents/SelectedEvents";
98

page/src/routes/listpage.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Filters from "components/Filters/Filters";
22
import ListView from "components/ListView/ListView";
3-
import ViewSelector from "components/ViewSelector/ViewSelector";
43
import YearSelector from "components/YearSelector/YearSelector";
54
import { createSearchParams, useNavigate, useParams, useSearchParams } from "react-router-dom";
65

0 commit comments

Comments
 (0)