Skip to content

Commit

Permalink
Merge pull request #1266 from openedx/ammar/advance-analytics-plotly-…
Browse files Browse the repository at this point in the history
…chart-components

advance analytics plotly chart components
  • Loading branch information
muhammad-ammar authored Jul 31, 2024
2 parents 0639733 + cabb37c commit 2dad691
Show file tree
Hide file tree
Showing 32 changed files with 3,324 additions and 5 deletions.
1,899 changes: 1,894 additions & 5 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@
"jest-environment-jsdom": "29.7.0",
"lodash": "4.17.21",
"lodash.debounce": "4.0.8",
"plotly.js": "^2.33.0",
"prop-types": "15.7.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-helmet": "6.1.0",
"react-instantsearch-dom": "6.8.3",
"react-markdown": "6.0.0",
"react-plotly.js": "^2.6.0",
"react-redux": "7.2.9",
"react-router": "6.21.1",
"react-router-dom": "6.21.1",
Expand Down
239 changes: 239 additions & 0 deletions src/components/AdvanceAnalyticsV2/AnalyticsV2Page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import React, { useState } from 'react';
import {
Form, Tabs, Tab,
} from '@openedx/paragon';
import { Helmet } from 'react-helmet';

import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import Hero from '../Hero';
import Stats from './Stats';
import Enrollments from './tabs/Enrollments';
import Engagements from './tabs/Engagements';
import Completions from './tabs/Completions';
import Leaderboard from './tabs/Leaderboard';
import Skills from './tabs/Skills';

const PAGE_TITLE = 'AnalyticsV2';

const AnalyticsV2Page = () => {
const [activeTab, setActiveTab] = useState('enrollments');
const [granularity, setGranularity] = useState('daily');
const [calculation, setCalculation] = useState('total');
const dataRefreshDate = '';
const intl = useIntl();

return (
<>
<Helmet title={PAGE_TITLE} />
<Hero title={PAGE_TITLE} />
<div className="container-fluid w-100">
<div className="row data-refresh-msg-container mb-4">
<div className="col">
<span>
<FormattedMessage
id="advance.analytics.data.refresh.msg"
defaultMessage="Data updated on {date}"
description="Data refresh message"
values={{ date: dataRefreshDate }}
/>
</span>
</div>
</div>

<div className="row filter-container mb-4">
<div className="col">
<Form.Group>
<Form.Label>
<FormattedMessage
id="advance.analytics.filter.start.date"
defaultMessage="Start Date"
description="Advance analytics Start date filter label"
/>
</Form.Label>
<Form.Control
type="date"
/>
</Form.Group>
</div>
<div className="col">
<Form.Group>
<Form.Label>
<FormattedMessage
id="advance.analytics.filter.end.date"
defaultMessage="End Date"
description="Advance analytics End date filter label"
/>
</Form.Label>
<Form.Control
type="date"
/>
</Form.Group>
</div>
<div className="col">
<Form.Group>
<Form.Label>
<FormattedMessage
id="advance.analytics.filter.date.granularity"
defaultMessage="Date granularity"
description="Advance analytics Date granularity filter label"
/>
</Form.Label>
<Form.Control
as="select"
value={granularity}
onChange={(e) => setGranularity(e.target.value)}
>
<option value="daily">
{intl.formatMessage({
id: 'advance.analytics.filter.granularity.option.daily',
defaultMessage: 'Daily',
description: 'Advance analytics granularity filter daily option',
})}
</option>
<option value="weekly">
{intl.formatMessage({
id: 'advance.analytics.filter.granularity.option.weekly',
defaultMessage: 'Weekly',
description: 'Advance analytics granularity filter weekly option',
})}
</option>
<option value="monthly">
{intl.formatMessage({
id: 'advance.analytics.filter.granularity.option.monthly',
defaultMessage: 'Monthly',
description: 'Advance analytics granularity filter monthly option',
})}
</option>
<option value="quarterly">
{intl.formatMessage({
id: 'advance.analytics.filter.granularity.option.quarterly',
defaultMessage: 'Quarterly',
description: 'Advance analytics granularity filter quarterly option',
})}
</option>
</Form.Control>
</Form.Group>
</div>
<div className="col">
<Form.Group>
<Form.Label>
<FormattedMessage
id="advance.analytics.filter.calculation"
defaultMessage="Calculation"
description="Advance analytics Calculation filter label"
/>
</Form.Label>
<Form.Control
as="select"
value={calculation}
onChange={(e) => setCalculation(e.target.value)}
>
<option value="total">
{intl.formatMessage({
id: 'advance.analytics.filter.calculation.option.total',
defaultMessage: 'Total',
description: 'Advance analytics calculation filter total option',
})}
</option>
<option value="running_total">
{intl.formatMessage({
id: 'advance.analytics.filter.calculation.option.running.total',
defaultMessage: 'Running Total',
description: 'Advance analytics calculation filter running total option',
})}
</option>
<option value="average_3">
{intl.formatMessage({
id: 'advance.analytics.filter.calculation.option.average.3',
defaultMessage: 'Moving Average (3 Period)',
description: 'Advance analytics calculation filter moving average 3 period option',
})}
</option>
<option value="average_7">
{intl.formatMessage({
id: 'advance.analytics.filter.calculation.option.average.7',
defaultMessage: 'Moving Average (7 Period)',
description: 'Advance analytics calculation filter moving average 7 period option',
})}
</option>
</Form.Control>
</Form.Group>
</div>
</div>

<div className="row stats-container mb-4">
<Stats
enrollments={0}
distinctCourses={0}
dailySessions={0}
learningHours={0}
completions={0}
/>
</div>

<div className="tabs-container">
<Tabs
variant="tabs"
activeKey={activeTab}
onSelect={(tab) => {
setActiveTab(tab);
}}
>
<Tab
eventKey="enrollments"
title={intl.formatMessage({
id: 'advance.analytics.enrollment.tab.title',
defaultMessage: 'Enrollments',
description: 'Title for the enrollments tab in advance analytics.',
})}
>
<Enrollments />
</Tab>
<Tab
eventKey="engagements"
title={intl.formatMessage({
id: 'advance.analytics.engagement.tab.title',
defaultMessage: 'Engagements',
description: 'Title for the engagements tab in advance analytics.',
})}
>
<Engagements />
</Tab>
<Tab
eventKey="completions"
title={intl.formatMessage({
id: 'advance.analytics.completions.tab.title',
defaultMessage: 'Completions',
description: 'Title for the completions tab in advance analytics.',
})}
>
<Completions />
</Tab>
<Tab
eventKey="leaderboard"
title={intl.formatMessage({
id: 'advance.analytics.leaderboard.tab.title',
defaultMessage: 'Leaderboard',
description: 'Title for the leaderboard tab in advance analytics.',
})}
>
<Leaderboard />
</Tab>
<Tab
eventKey="skills"
title={intl.formatMessage({
id: 'advance.analytics.skills.tab.title',
defaultMessage: 'Skills',
description: 'Title for the skills tab in advance analytics.',
})}
>
<Skills />
</Tab>
</Tabs>
</div>
</div>
</>
);
};

export default AnalyticsV2Page;
20 changes: 20 additions & 0 deletions src/components/AdvanceAnalyticsV2/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';

const Header = ({ title, subtitle }) => (
<div className="analytics-header">
<h2 className="analytics-header-title">{title}</h2>
{subtitle && <p className="analytics-header-subtitle">{subtitle}</p>}
</div>
);

Header.defaultProps = {
subtitle: undefined,
};

Header.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,
};

export default Header;
76 changes: 76 additions & 0 deletions src/components/AdvanceAnalyticsV2/Stats.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';

const Stats = ({
enrollments, distinctCourses, dailySessions, learningHours, completions,
}) => {
const formatter = Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 });

return (
<div className="container-fluid analytics-stats">
<div className="row">
<div className="col d-flex flex-column justify-content-center align-items-center">
<p className="mb-0 small title-enrollments">
<FormattedMessage
id="advance.analytics.stats.enrollments.title"
defaultMessage="Enrollments"
description="Title for the enrollments stat."
/>
</p>
<p className="font-weight-bolder analytics-stat-number value-enrollments">{formatter.format(enrollments)}</p>
</div>
<div className="col d-flex flex-column justify-content-center align-items-center">
<p className="mb-0 small title-distinct-courses">
<FormattedMessage
id="advance.analytics.stats.distinct.courses.title"
defaultMessage="Distinct Courses"
description="Title for the distinct courses stat."
/>
</p>
<p className="font-weight-bolder analytics-stat-number value-distinct-courses">{formatter.format(distinctCourses)}</p>
</div>
<div className="col d-flex flex-column justify-content-center align-items-center">
<p className="mb-0 small title-daily-sessions">
<FormattedMessage
id="advance.analytics.stats.daily.sessions.title"
defaultMessage="Daily Sessions"
description="Title for the daily sessions stat."
/>
</p>
<p className="font-weight-bolder analytics-stat-number value-daily-sessions">{formatter.format(dailySessions)}</p>
</div>
<div className="col d-flex flex-column justify-content-center align-items-center">
<p className="mb-0 small title-learning-hours">
<FormattedMessage
id="advance.analytics.stats.learning.hours.title"
defaultMessage="Learning Hours"
description="Title for the learning hours stat."
/>
</p>
<p className="font-weight-bolder analytics-stat-number value-learning-hours">{formatter.format(learningHours)}</p>
</div>
<div className="col d-flex flex-column justify-content-center align-items-center">
<p className="mb-0 small title-completions">
<FormattedMessage
id="advance.analytics.stats.completions.title"
defaultMessage="Completions"
description="Title for the completions stat."
/>
</p>
<p className="font-weight-bolder analytics-stat-number value-completions">{formatter.format(completions)}</p>
</div>
</div>
</div>
);
};

Stats.propTypes = {
enrollments: PropTypes.number.isRequired,
distinctCourses: PropTypes.number.isRequired,
dailySessions: PropTypes.number.isRequired,
learningHours: PropTypes.number.isRequired,
completions: PropTypes.number.isRequired,
};

export default Stats;
Loading

0 comments on commit 2dad691

Please sign in to comment.