Skip to content

Commit 59b8998

Browse files
refactor: add namy pages for page views kaliacad#195 (kaliacad#196)
--------- Co-authored-by: Thierry CH <thierrybakera12@gmail.com>
1 parent 237020d commit 59b8998

File tree

10 files changed

+189
-64
lines changed

10 files changed

+189
-64
lines changed

src/components/ArticleView/ArticleCard.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import './articleview.css';
22
import getPageURL from '../../helpers/getPageUrl';
33
import fetchImageFromArticle from '../../api/fetchImageFromArticle';
44
import fetchArticleEditor from '../../api/fetchArticleEditor';
5+
import pageNameDecoder from '../../helpers/pageNameDecoder';
56
import { useEffect, useState } from 'react';
67
import Button from '../Button';
78
const ArticleCard = ({ article, project, views_ceil, rank, country }) => {
@@ -36,7 +37,7 @@ const ArticleCard = ({ article, project, views_ceil, rank, country }) => {
3637
<div className='article-content'>
3738
<h3 className='article-title'>
3839
<a href={getPageURL(article, project)} target='_blank'>
39-
{article?.includes('_') ? article.replace(/_/g, ' ') : article}
40+
{pageNameDecoder(article)}
4041
</a>
4142
</h3>
4243
<div className='article-description flex flex-col gap-2'>

src/components/PageViews/ArticleViewsGraph.jsx

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,61 @@ import Loading from '../loading';
66

77
Chart.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
88

9-
const ArticleViewsGraph = ({ article }) => {
9+
const ArticleViewsGraph = ({ articles }) => {
1010
const [viewData, setViewData] = useState({});
1111
const [loading, setLoading] = useState(true);
12+
1213
useEffect(() => {
13-
const fetchViews = async () => {
14+
const fetchAllViews = async () => {
1415
try {
15-
const response = await fetchPageViewsCount(article);
16-
const views = response.items;
17-
const dates = views.map((view) => {
18-
const year = view.timestamp.substring(0, 4);
19-
const month = view.timestamp.substring(4, 6);
20-
const day = view.timestamp.substring(6, 8);
21-
22-
const dateString = `${year}-${month}-${day}`;
23-
const date = new Date(dateString);
24-
return date.toLocaleDateString();
16+
const allDataPromises = articles.map(async (article) => {
17+
const response = await fetchPageViewsCount(article);
18+
const views = response.items;
19+
const dates = views.map((view) => {
20+
const year = view.timestamp.substring(0, 4);
21+
const month = view.timestamp.substring(4, 6);
22+
const day = view.timestamp.substring(6, 8);
23+
24+
const dateString = `${year}-${month}-${day}`;
25+
const date = new Date(dateString);
26+
return date.toLocaleDateString();
27+
});
28+
const counts = views.map((view) => view.views);
29+
return { article: article.article, dates, counts };
2530
});
26-
const counts = views.map((view) => view.views);
31+
32+
const allData = await Promise.all(allDataPromises);
33+
34+
const combinedDates = [...new Set(allData.flatMap((data) => data.dates))].sort((a, b) => new Date(a) - new Date(b));
35+
const datasets = allData.map((data, index) => ({
36+
label: decodeURIComponent(data.article),
37+
data: combinedDates.map((date) => {
38+
const viewIndex = data.dates.indexOf(date);
39+
return viewIndex !== -1 ? data.counts[viewIndex] : 0;
40+
}),
41+
fill: false,
42+
backgroundColor: `rgba(${75 + index * 50}, 192, 192, 0.6)`,
43+
borderColor: `rgba(${75 + index * 50}, 192, 192, 1)`,
44+
}));
2745

2846
setViewData({
29-
labels: dates,
30-
datasets: [
31-
{
32-
label: 'Views',
33-
data: counts,
34-
fill: false,
35-
backgroundColor: 'rgba(75, 192, 192, 0.6)',
36-
borderColor: 'rgba(75, 192, 192, 1)',
37-
},
38-
],
47+
labels: combinedDates,
48+
datasets,
3949
});
50+
4051
setLoading(false);
4152
} catch (error) {
4253
setLoading(true);
43-
4454
return;
4555
}
4656
};
4757

48-
fetchViews();
49-
}, [article]);
58+
fetchAllViews();
59+
}, [articles]);
5060

5161
return (
52-
<div className='graph w-3/4 flex flex-col items-center'>
53-
<h2>Views for {decodeURIComponent(article.article)}</h2>
62+
<div className='graph w-full flex flex-col items-center'>
63+
<h2 className='font-bold text-xl self-start'>Views for Multiple Articles</h2>
5464
{loading ? <Loading /> : viewData.labels ? <Line data={viewData} /> : <p>No data available.</p>}
5565
</div>
5666
);
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
import { createContext } from 'react';
22

3-
const PageViewsContext = createContext(null);
3+
const PageViewsContext = createContext({
4+
dates: { start: '', end: '' },
5+
setDates: () => {},
6+
pages: [],
7+
setPages: () => {},
8+
dateType: 'Daily',
9+
setDateType: () => {},
10+
project: 'fr.wikipedia.org',
11+
setProject: () => {},
12+
platform: 'all-access',
13+
setPlatform: () => {},
14+
agent: 'user',
15+
setAgent: () => {},
16+
});
417

518
export default PageViewsContext;

src/components/PageViews/Graph.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import ArticleViewsGraph from './ArticleViewsGraph';
22
import { useContext } from 'react';
33
import PageViewsContext from './Context';
44
export default function ArticlesGraph() {
5-
const [[dateType], [project], [article], [platform], [agent]] = useContext(PageViewsContext);
6-
const articleData = {
5+
const { pages, dateType, project, platform, agent, dates } = useContext(PageViewsContext);
6+
const articlesData = pages.map((article) => ({
77
article: encodeURIComponent(article),
88
project: encodeURIComponent(project),
99
acess: encodeURIComponent(platform),
1010
agents: encodeURIComponent(agent),
1111
dateType: encodeURIComponent(dateType),
12-
// start:`${todayYear}0101`,
13-
// end:`${todayYear}${todayMonth}${todayDay}`,
14-
};
12+
start: dates.start,
13+
end: dates.end,
14+
}));
1515

16-
return <ArticleViewsGraph article={articleData} />;
16+
return <ArticleViewsGraph articles={articlesData} />;
1717
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useContext } from 'react';
2+
import PageViewsContext from './Context';
3+
import DatePicker from '../ArticleForm/DatePicker';
4+
import { todayDay, todayMonth, todayYear } from '../../helpers/dateNowSpliter';
5+
6+
// Helper function to format date strings as YYYYMMDD
7+
const stringifyDate = (date) => {
8+
const [year, month, day] = date.split('-');
9+
return `${year}${month.padStart(2, '0')}${day.padStart(2, '0')}`;
10+
};
11+
12+
export default function Interval() {
13+
const { dates, setDates } = useContext(PageViewsContext);
14+
15+
const handleChangeStart = (e) => {
16+
setDates({ ...dates, start: stringifyDate(e.target.value) });
17+
};
18+
19+
const handleChangeEnd = (e) => {
20+
setDates({ ...dates, end: stringifyDate(e.target.value) });
21+
};
22+
23+
return (
24+
<div>
25+
<DatePicker onChange={handleChangeStart} label='Start' date={new Date(`${todayYear}-01-01`).toISOString().split('T')[0]} />
26+
<DatePicker
27+
onChange={handleChangeEnd}
28+
label='End'
29+
date={new Date(`${todayYear}-${todayMonth}-${todayDay}`).toISOString().split('T')[0]}
30+
/>
31+
</div>
32+
);
33+
}

src/components/PageViews/OptionsForm.jsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
import { useContext } from 'react';
22
import PageViewsContext from './Context';
3-
3+
import Interval from './Interval';
44
const OptionsForm = () => {
5-
const [[dateType, setDateType], [project, setProject], [article, setArticle], [platform, setPlatform], [agent, setAgent]] =
6-
useContext(PageViewsContext);
5+
const { dateType, setDateType, project, setProject, platform, setPlatform, agent, setAgent } = useContext(PageViewsContext);
76
return (
87
<div className='options-form w-1/5 p-4 border border-gray-300 rounded-md bg-white'>
98
<h3>Options:</h3>
109
<div className='form-group'>
1110
<label htmlFor='project'>Project</label>
1211
<input type='text' id='project' value={project} onChange={(e) => setProject(e.target.value)} />
1312
</div>
14-
<div className='form-group'>
15-
<label htmlFor='project'>Article Name</label>
16-
<input type='text' id='project' value={article} onChange={(e) => setArticle(e.target.value)} />
17-
</div>
18-
{/* <div className='form-group'>
19-
<label htmlFor='dates'>Dates</label>
20-
<input type='text' id='date-range' value={dates} onChange={(e) => setDates(e.target.value)} />
21-
</div> */}
13+
<Interval />
2214
<div className='form-group'>
2315
<label htmlFor='dateType'>Date type</label>
2416
<select id='dateType' value={dateType} onChange={(e) => setDateType(e.target.value)}>
@@ -29,7 +21,7 @@ const OptionsForm = () => {
2921
<div className='form-group'>
3022
<label htmlFor='platform'>Platform</label>
3123
<select id='platform' value={platform} onChange={(e) => setPlatform(e.target.value)}>
32-
<option value={'all-access'}>All</option>
24+
<option value='all-access'>All</option>
3325
<option value='desktop'>Desktop</option>
3426
<option value='mobile-web'>Mobile Web</option>
3527
<option value='mobile-app'>Apps</option>
@@ -38,9 +30,10 @@ const OptionsForm = () => {
3830
<div className='form-group'>
3931
<label htmlFor='agent'>Agent</label>
4032
<select id='agent' value={agent} onChange={(e) => setAgent(e.target.value)}>
41-
<option>User</option>
42-
<option>Spider</option>
43-
<option>Bot</option>
33+
<option value='all-agent'>All</option>
34+
<option value='user'>User</option>
35+
<option value='spider'>Spider</option>
36+
<option value={'automated'}>Bot</option>
4437
</select>
4538
</div>
4639
</div>

src/components/PageViews/PageViews.jsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,39 @@ import OptionsForm from './OptionsForm';
33
import ArticlesGraph from './Graph';
44
import { useState } from 'react';
55
import PageViewsContext from './Context';
6+
import Pages from './Pages';
7+
import { todayDay, todayMonth, todayYear } from '../../helpers/dateNowSpliter';
8+
69
export default function PageViews() {
7-
// const [dates, setDates] = useState('5/17/2024 - 6/6/2024');
10+
const [dates, setDates] = useState({ start: `${todayYear}0101`, end: `${todayYear}${todayMonth}${todayDay}` });
11+
const [pages, setPages] = useState([]);
812
const [dateType, setDateType] = useState('Daily');
9-
const [project, setProject] = useState('en.wikipedia.org');
10-
const [article, setArticle] = useState('Julie_Gayet');
13+
const [project, setProject] = useState('fr.wikipedia.org');
1114
const [platform, setPlatform] = useState('all-access');
1215
const [agent, setAgent] = useState('user');
1316
return (
1417
<div className='page-views flex flex-wrap justify-center gap-4 w-full p-4 mr-10'>
1518
<PageViewsContext.Provider
16-
value={[
17-
// [dates, setDates],
18-
[dateType, setDateType],
19-
[project, setProject],
20-
[article, setArticle],
21-
[platform, setPlatform],
22-
[agent, setAgent],
23-
]}
19+
value={{
20+
dates,
21+
setDates,
22+
pages,
23+
setPages,
24+
dateType,
25+
setDateType,
26+
project,
27+
setProject,
28+
platform,
29+
setPlatform,
30+
agent,
31+
setAgent,
32+
}}
2433
>
2534
<OptionsForm />
26-
<ArticlesGraph />
35+
<div className='w-3/4'>
36+
<Pages />
37+
<ArticlesGraph />
38+
</div>
2739
</PageViewsContext.Provider>
2840
</div>
2941
);

src/components/PageViews/Pages.jsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useContext, useRef } from 'react';
2+
import PageViewsContext from './Context';
3+
import pageNameDecoder from '../../helpers/pageNameDecoder';
4+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5+
import { faAdd, faXmark } from '@fortawesome/free-solid-svg-icons';
6+
7+
export default function Pages() {
8+
const { pages, setPages } = useContext(PageViewsContext);
9+
const inputRef = useRef('');
10+
const handleSubmit = (e) => {
11+
e.preventDefault();
12+
setPages([...pages, inputRef.current.value]);
13+
inputRef.current.value = '';
14+
};
15+
16+
function deleteItemAtIndex(i) {
17+
const newPages = [...pages];
18+
if (i === 0) {
19+
newPages.pop();
20+
} else {
21+
newPages.splice(i, 1);
22+
}
23+
24+
setPages(newPages);
25+
}
26+
27+
return (
28+
<div className='flex my-3 gap-4 flex-wrap'>
29+
<form onSubmit={handleSubmit} method='POST' className='flex gap-3 flex-wrap'>
30+
<input type='text' className='page-input' name='page' ref={inputRef} />
31+
<button type='submit' className='bg-blue-500 text-white px-4 py-2 rounded'>
32+
<FontAwesomeIcon icon={faAdd} />
33+
</button>
34+
</form>
35+
<div className='list-none flex flex-wrap gap-1'>
36+
{pages.map((page, index) => (
37+
<span
38+
key={index}
39+
className='border-1 solid flex justify-center text-[12px] items-center h-6 border-[#ccc] p-1 bg-blue-300 rounded-lg'
40+
>
41+
<span>{pageNameDecoder(page)}</span>
42+
<button onClick={() => deleteItemAtIndex(index)} className='flex justify-center text-[12px] bg-transparent !p-0'>
43+
<FontAwesomeIcon icon={faXmark} className='pl-1' />
44+
</button>
45+
</span>
46+
))}
47+
</div>
48+
</div>
49+
);
50+
}

src/components/PageViews/pageviews.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
margin-bottom: 5px;
2525
font-weight: bold;
2626
}
27-
27+
.page-input[type='text'] {
28+
padding: 8px;
29+
border: 1px solid #ccc;
30+
border-radius: 4px;
31+
}
2832
.form-group input[type='text'],
2933
.form-group select {
3034
width: 100%;

src/helpers/pageNameDecoder.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const pageNameDecoder = (page) => {
2+
if (page !== undefined && page !== null) {
3+
return page?.includes('_') ? page.replace(/_/g, ' ') : page;
4+
} else {
5+
return 'N/A';
6+
}
7+
};
8+
9+
export default pageNameDecoder;

0 commit comments

Comments
 (0)