at the end of the day, it was inevitable

This commit is contained in:
Mo Elzubeir
2022-12-09 08:36:26 -06:00
commit 1218570914
1768 changed files with 887087 additions and 0 deletions
@@ -0,0 +1,56 @@
import React from 'react';
import PropTypes from 'prop-types';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import SubTabWrapper from '../../AppHeader/SubTabWrapper';
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
import ShowCharts from './CreateAnalysisSubTab/ShowCharts';
import SavedAnalysisSubTab from './SavedAnalysisSubTab/SavedAnalysisSubTab';
import CreateAnalysisSubTab from './CreateAnalysisSubTab/CreateAnalysisSubTab';
function AnalyzeTab(props) {
const { subTabs, allowAnalytics, history, activeTabName, match } = props;
if (!allowAnalytics) {
history.push('/app/search/search');
return null;
}
return (
<CSSTransitionGroup
component="div"
transitionName="TabsAnimation"
transitionAppear
transitionAppearTimeout={0}
transitionEnter={false}
transitionLeave={false}
>
<SubTabWrapper activeTabName={activeTabName} subTabs={subTabs}>
<Switch>
{/* <Route path={`${match.url}/welcome`} component={WelcomeSubTab} /> */}
<Route path={`${match.url}/saved`} component={SavedAnalysisSubTab} />
<Route
path={`${match.url}/create`}
component={CreateAnalysisSubTab}
/>
<Route
path={`${match.url}/edit/:id`}
component={CreateAnalysisSubTab}
/>
<Route path={`${match.url}/:id`} component={ShowCharts} />
<Redirect to={`${match.url}/saved`} />
</Switch>
</SubTabWrapper>
</CSSTransitionGroup>
);
}
AnalyzeTab.propTypes = {
activeTabName: PropTypes.string,
children: PropTypes.any,
history: PropTypes.object,
match: PropTypes.object,
allowAnalytics: PropTypes.bool,
subTabs: PropTypes.array
};
export default withRouter(AnalyzeTab);
@@ -0,0 +1,293 @@
import React, { Fragment, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
Button,
Form,
FormGroup,
Label,
Modal,
ModalBody,
ModalFooter,
ModalHeader
} from 'reactstrap';
import Select from 'react-select';
import { Input, Checkbox, RadioButton } from '../../../../common/FormControls';
import useForm from '../../../../common/hooks/useForm.js';
import { EXTRAS } from '../../../../../redux/modules/appState/share/forms/alertForm';
import { createAlertAPI } from '../../../../../api/analytics/createAnalytics';
import { getCurrentTimezone, timezones } from '../../../../../common/Timezones';
import { compose } from 'redux';
import reduxConnect from '../../../../../redux/utils/connect';
import translate from 'react-i18next/dist/commonjs/translate';
import { THEME_TYPES } from '../../../../../redux/modules/appState/share/forms/notificationForm';
const initialForm = {
name: '',
recipients: [],
subject: '',
automatedSubject: false,
unsubscribeNotification: false,
published: false,
allowUnsubscribe: false,
articleExtracts: EXTRAS.CONTEXTUAL,
highlight: false,
showSourceCountry: false,
showUserComments: false,
themeType: THEME_TYPES.PLAIN,
sendWhenEmpty: false,
timezone: getCurrentTimezone(),
notificationType: 'alert',
// automatic: [], // auto schedule
// sentUntil: '',
errors: {
name: null
}
};
function AlertDialog(props) {
const { toggle, isOpen, alertCharts, actions, resetAlertChart, user } = props;
const [loading, setLoading] = useState(false);
const {
form,
handleChange,
handleValidation,
errors,
validateSubmit,
resetForm
} = useForm(initialForm);
function handleSubmit() {
const obj = validateSubmit();
if (!obj) {
return actions.addAlert({ type: 'error', transKey: 'requiredInfo' });
}
setLoading(true);
if (obj.automatedSubject) {
delete obj.subject;
}
obj.sources = alertCharts.map((chart) => ({
id: chart.id,
type: 'chart'
}));
createAlertAPI(obj).then((res) => {
if (res.error) {
res.data
? actions.addAlert(res.data)
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
setLoading(false);
return;
}
actions.addAlert({ type: 'notice', transKey: 'alertSaved' });
setLoading(false);
toggle();
resetForm();
resetAlertChart();
});
}
useEffect(() => {
if (form.recipients && user.recipient && user.recipient.id) {
handleChange('recipients', [user.recipient.id]);
}
return () => resetForm();
}, []);
return (
<Modal isOpen={isOpen} toggle={toggle} backdrop="static" size="lg">
<ModalHeader toggle={toggle}>Create Alert</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label>Selected Charts</Label>
<div className="b-radius-5 bg-light p-2">
{alertCharts.map((chart, i, arr) => (
<Fragment key={chart.name}>
<span className="d-inline-block mr-1">
{chart.name}
{arr.length - 1 !== i ? ', ' : ''}
</span>
</Fragment>
))}
</div>
</FormGroup>
<Input
name="name"
title="Name"
required
value={form.name}
error={errors.name}
handleChange={handleChange}
handleValidation={handleValidation}
/>
<Checkbox
name="automatedSubject"
title="Automated Subject"
description="Use automated email subject based on the feeds"
value={form.automatedSubject}
error={errors.automatedSubject}
handleChange={handleChange}
/>
{!form.automatedSubject && (
<Input
name="subject"
title="Email Subject"
value={form.subject}
error={errors.subject}
handleChange={handleChange}
handleValidation={handleValidation}
/>
)}
<Checkbox
name="published"
title="Publish"
description="Alerts and Newsletters that are Published are available for other users to subscribe"
value={form.published}
error={errors.publish}
handleChange={handleChange}
/>
<Checkbox
name="allowUnsubscribe"
title="Unsubscribe Link"
description="Allow recipients to unsubscribe from Alert"
value={form.allowUnsubscribe}
error={errors.allowUnsubscribe}
handleChange={handleChange}
/>
<Checkbox
name="unsubscribeNotification"
title="Notifications"
description="Notify creator when recipients unsubscribe"
value={form.unsubscribeNotification}
error={errors.unsubscribeNotification}
handleChange={handleChange}
/>
<FormGroup className="radio-options">
<Label>Options</Label>
<RadioButton
name="articleExtracts"
title="Article Extracts"
formClass="mb-0"
options={[
{ label: 'Contextual extract', value: EXTRAS.CONTEXTUAL },
{ label: 'Start of text extract', value: EXTRAS.START },
{ label: 'No article extract', value: EXTRAS.NO }
]}
inline
value={form.articleExtracts}
error={errors.articleExtracts}
handleChange={handleChange}
/>
<RadioButton
name="highlight"
title="Highlight Keywords"
formClass="mb-0"
options={[
{ label: 'Yes', value: true },
{ label: 'No', value: false }
]}
inline
value={form.highlight}
error={errors.highlight}
handleChange={handleChange}
/>
<RadioButton
name="showSourceCountry"
title="Show Source Country"
formClass="mb-0"
options={[
{ label: 'Yes', value: true },
{ label: 'No', value: false }
]}
inline
value={form.showSourceCountry}
error={errors.showSourceCountry}
handleChange={handleChange}
/>
<RadioButton
name="showUserComments"
title="Show User Comments"
formClass="mb-0"
options={[
{ label: 'Yes', value: true },
{ label: 'No', value: false }
]}
inline
value={form.showUserComments}
error={errors.showUserComments}
handleChange={handleChange}
/>
<RadioButton
name="themeType"
title="Layout"
formClass="mb-0"
options={[
{ label: 'Enhanced HTML', value: THEME_TYPES.ENHANCED },
{ label: 'Plain HTML', value: THEME_TYPES.PLAIN }
]}
inline
value={form.themeType}
error={errors.themeType}
handleChange={handleChange}
/>
<RadioButton
name="sendWhenEmpty"
title="Send When Empty"
formClass="mb-0"
options={[
{ label: 'Yes', value: true },
{ label: 'No', value: false }
]}
inline
value={form.sendWhenEmpty}
error={errors.sendWhenEmpty}
handleChange={handleChange}
/>
</FormGroup>
<FormGroup>
<Label>Timezone</Label>
<Select
className="timezone-select"
value={form.timezone}
options={timezones}
clearable={false}
onChange={function (v) {
handleChange('timezone', v.value);
}}
/>
</FormGroup>
{/* <FormGroup>
<Label>Automatic</Label>
<Scheduling state={state.scheduling} actions={actions} />
</FormGroup> */}
</Form>
</ModalBody>
<ModalFooter>
<Button color="link" onClick={toggle}>
Cancel
</Button>
<Button color="primary" disabled={loading} onClick={handleSubmit}>
{loading ? 'Loading...' : 'Submit'}
</Button>
</ModalFooter>
</Modal>
);
}
AlertDialog.propTypes = {
toggle: PropTypes.func,
resetAlertChart: PropTypes.func,
isOpen: PropTypes.bool,
alertCharts: PropTypes.array,
user: PropTypes.object,
actions: PropTypes.object
};
const applyDecorators = compose(
reduxConnect('user', ['common', 'auth', 'user']),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(AlertDialog);
@@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Card,
CardBody,
CardHeader,
DropdownItem,
DropdownMenu,
DropdownToggle,
UncontrolledButtonDropdown
} from 'reactstrap';
import cx from 'classnames';
import { IoIosMenu } from 'react-icons/io';
function ChartWrapper(props) {
let { title, children, menus } = props;
const hasShowMore = menus.find((menu) => !menu.hide && menu.showInMore);
// TODO: hide alert until API is ready
menus = menus.filter((menu) => menu.title);
const isRTL = document.documentElement.dir === 'rtl';
return (
<Card className="mb-3">
<CardHeader>
{title && <div>{title}</div>}
<div className="btn-actions-pane-right actions-icon-btn">
<div className="align-content-center d-flex d-inline-flex">
{menus &&
menus.map((menu) =>
!menu.hide && !menu.showInMore && menu.icon ? (
<button
key={menu.title}
title={menu.title}
className="btn btn-icon-only mr-2 p-0"
onClick={menu.fn}
disabled={!menu.fn}
>
<menu.icon size={menu.size || 16} />
</button>
) : null
)}
</div>
{menus && hasShowMore && (
<UncontrolledButtonDropdown>
<DropdownToggle className="btn-icon btn-icon-only" color="link">
<div className="btn-icon-wrapper">
<IoIosMenu size={24} />
</div>
</DropdownToggle>
<DropdownMenu
className={`dropdown-menu-shadow dropdown-menu-hover-link${
isRTL ? ' dropdown-menu-left' : ''
}`}
>
{menus.map((menu) =>
!menu.hide && menu.showInMore ? (
<DropdownItem onClick={menu.fn} key={menu.title}>
{menu.icon && (
<i className={cx('dropdown-icon', menu.icon)}></i>
)}
<span>{menu.title}</span>
</DropdownItem>
) : null
)}
</DropdownMenu>
</UncontrolledButtonDropdown>
)}
</div>
</CardHeader>
<CardBody>{children}</CardBody>
</Card>
);
}
ChartWrapper.propTypes = {
title: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
menus: PropTypes.array
};
export default ChartWrapper;
@@ -0,0 +1,272 @@
/* eslint-disable react/jsx-no-bind */
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { DateRangePicker } from 'react-dates';
import { translate } from 'react-i18next';
import { compose } from 'redux';
import { useHistory, useParams } from 'react-router-dom';
import {
Button,
Card,
CardBody,
CardTitle,
Col,
FormGroup,
InputGroup,
Label,
Row
} from 'reactstrap';
import Loader from 'react-loader-advanced';
import { Loader as LoaderAnim } from 'react-loaders';
import { useDrop } from 'react-dnd';
import { IoIosCloseCircleOutline } from 'react-icons/io';
import {
addEditAnalyticsAPI,
getAnalyticDetailsAPI
} from '../../../../../api/analytics/createAnalytics';
import { TYPES } from '../../../../../redux/modules/appState/sidebar';
import reduxConnect from '../../../../../redux/utils/connect';
import useIsMounted from '../../../../common/hooks/useIsMounted';
import { subChartCategories } from './ShowCharts';
import { getMomentObject, setDocumentData } from '../../../../../common/helper';
const initialState = {
feeds: [],
startDate: null,
endDate: null
};
const spinner = <LoaderAnim color="#ffffff" type="ball-pulse" />;
function CreateAnalysisSubTab({ t, actions }) {
const isMounted = useIsMounted();
const history = useHistory();
const { id } = useParams();
const [form, setForm] = useState(initialState);
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
const [fetching, setFetching] = useState(!!id);
const [focusedInput, setFocusedInput] = useState();
const [{ canDrop, isOver }, drop] = useDrop({
accept: [TYPES.FEED, TYPES.CLIP_ARTICLE],
drop: droppedFeeds,
canDrop: canDroppable,
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
});
function getAnalyticData() {
setFetching(true);
getAnalyticDetailsAPI(id).then((res) => {
if (!isMounted.current) {
return;
}
if (res.error || !res.data || !res.data.context) {
setFetching(false);
res.data
? actions.addAlert(res.data)
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
history.push('/app/analyze/saved');
return;
}
const { context } = res.data;
const date = context && context.rawFilters && context.rawFilters.date;
setForm({
feeds: context.feeds.map((item) => ({
feed: { name: item.name },
id: item.id
})),
startDate: getMomentObject(date && date.start),
endDate: getMomentObject(date && date.end)
});
setFetching(false);
});
}
useEffect(() => {
setDocumentData('title', `${id ? 'Update' : 'Create'} Analysis | Analyze`);
return () => {
setDocumentData('title');
};
}, []);
useEffect(() => {
if (id) {
getAnalyticData();
}
}, [id]);
function canDroppable(item) {
if (form.feeds.find((val) => val.id === item.id)) {
return false;
}
return true;
}
function droppedFeeds(item) {
if (form.feeds.find((val) => val.id === item.id)) {
return;
}
setForm((prev) => ({ ...prev, feeds: [...prev.feeds, item] }));
}
function removeFeeds(id) {
setForm((prev) => {
const modifiedFeeds = form.feeds.filter((val) => val.id !== id);
return { ...prev, feeds: modifiedFeeds };
});
}
const isActive = canDrop && isOver;
function handleSubmit() {
const isValid = Object.values(form).every((value) =>
value ? (Array.isArray(value) ? value.length > 0 : true) : false
);
if (!isValid) {
return setError(t('common:alerts.error.requiredInfo'));
}
setError(false);
setLoading(true);
addEditAnalyticsAPI(form, id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.id) {
// on error
setLoading(false);
setError(res.errorMessage);
return;
}
actions.resetAlertChart();
setLoading(false);
history.push(`/app/analyze/${res.data.id}/${subChartCategories[0].path}`);
});
}
function handleDateChange({ startDate, endDate }) {
setForm((prev) => ({ ...prev, startDate, endDate }));
}
function onFocusChange(focus) {
setFocusedInput(focus);
}
function isOutsideRange() {
return false;
}
const isRTL = document.documentElement.dir === 'rtl';
return (
<Card className="mb-3">
<Loader message={spinner} show={fetching}>
<CardBody>
<CardTitle>
{id ? t('analyzeTab.updateDetails') : t('analyzeTab.enterDetails')}
</CardTitle>
<Row>
<Col sm="12">
<FormGroup data-tour="drop-feeds-box">
<div>
{form.feeds.length > 0 && (
<div className="mb-3">
<Label>{t('analyzeTab.selectedFeeds')}</Label>
<div>
{form.feeds.map((item) => (
<div
key={item.id}
className="bg-light d-inline d-inline-flex align-items-center mr-2 p-2 text-dark"
>
<p>{item.feed.name}</p>
<button
className="btn p-0"
onClick={function () {
removeFeeds(item.id);
}}
>
<IoIosCloseCircleOutline
size={22}
className="text-danger ml-2"
/>
</button>
</div>
))}
</div>
</div>
)}
<Label>{t('analyzeTab.selectFeeds')}</Label>
<div ref={drop} className="dropzone-wrapper">
<div>
<div className="dropzone-content">
<p>
{isActive
? t('analyzeTab.releaseDesc')
: t('analyzeTab.dropDesc')}
</p>
</div>
</div>
</div>
</div>
</FormGroup>
<FormGroup data-tour="analytics-data-range">
<Label className="mr-sm-2">{t('analyzeTab.dateRange')}</Label>
<InputGroup>
<DateRangePicker
startDateId="startDate"
endDateId="endDate"
startDate={form.startDate}
endDate={form.endDate}
onDatesChange={handleDateChange}
focusedInput={focusedInput}
onFocusChange={onFocusChange}
displayFormat="MM/DD/YYYY"
startDatePlaceholderText={t('analyzeTab.startDatePlaceholder')}
endDatePlaceholderText={t('analyzeTab.endDatePlaceholder')}
numberOfMonths={1}
isOutsideRange={isOutsideRange}
isRTL={isRTL}
/>
</InputGroup>
</FormGroup>
{error && <div className="text-danger mb-2">{error}</div>}
<Button
className="mb-2 mr-2 btn-icon"
color="primary"
disabled={loading}
data-tour="create-analytics-button"
onClick={handleSubmit}
>
{loading
? 'Loading...'
: id
? t('analyzeTab.updateBtn')
: t('analyzeTab.createBtn')}
</Button>
</Col>
</Row>
</CardBody>
</Loader>
</Card>
);
}
CreateAnalysisSubTab.propTypes = {
t: PropTypes.func.isRequired,
actions: PropTypes.object
};
const applyDecorators = compose(
reduxConnect(),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(CreateAnalysisSubTab);
@@ -0,0 +1,276 @@
import React, {
useState,
useCallback,
Fragment,
useEffect,
useMemo
} from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import {
NavLink,
Redirect,
Route,
Switch,
useHistory,
useParams
} from 'react-router-dom';
import {
Button,
DropdownItem,
DropdownMenu,
DropdownToggle,
UncontrolledDropdown
} from 'reactstrap';
import { IoIosTrash } from 'react-icons/io';
import {
Results,
Performance,
Influencers,
Sentiment,
Themes,
Demographics
// WorldMap
} from './Tabs';
import AlertDialog from './AlertDialog';
import reduxConnect from '../../../../../redux/utils/connect';
import translate from 'react-i18next/dist/commonjs/translate';
import { compose } from 'redux';
import { getAnalyticDetailsAPI } from '../../../../../api/analytics/createAnalytics';
import useIsMounted from '../../../../common/hooks/useIsMounted';
import { setDocumentData } from '../../../../../common/helper';
import { Interpolate } from 'react-i18next';
// exported for routing
export const subChartCategories = [
{
title: 'Overview',
transKey: 'overview',
path: 'overview',
component: Results
},
{
title: 'Performance',
transKey: 'performance',
path: 'performance',
component: Performance
},
{
title: 'Influencers',
transKey: 'influencers',
path: 'influencers',
component: Influencers
},
{
title: 'Sentiment',
transKey: 'sentiment',
path: 'sentiment',
component: Sentiment
},
{ title: 'Themes', transKey: 'themes', path: 'themes', component: Themes },
{
title: 'Demographics',
transKey: 'demographics',
path: 'demographics',
component: Demographics
}
// { title: 'World Map', transKey: 'worldMap', path: 'worldmap', component: WorldMap }
];
function ShowCharts({ analyze, actions, t }) {
const isMounted = useIsMounted();
const history = useHistory();
const params = useParams();
const [chartData, setChartData] = useState({});
const [alertModal, setAlertModal] = useState(false);
const [fetching, setFetching] = useState(true);
const [feedData, setFeedData] = useState(null);
const { removeAlertChart, resetAlertChart } = actions;
const { alertCharts } = analyze;
useEffect(() => {
setDocumentData('title', 'View Analysis | Analyze');
return () => {
setDocumentData('title');
};
}, []);
useEffect(() => {
if (!params.id || isNaN(params.id)) {
history.push('/app/analyze/saved');
} else {
getAnalyticData();
}
return () => resetAlertChart(); // reset store
}, [params.id]);
const updateResult = useCallback((data, chartName) => {
setChartData((prev) => ({ ...prev, [chartName]: data }));
}, []);
const subChartRoutes = useMemo(() => {
return subChartCategories.map(({ path, component: SubChart }) => (
<Route exact key={path} path={`/app/analyze/${params.id}/${path}`}>
<SubChart
id={params.id}
feedData={feedData}
chartData={chartData}
updateResult={updateResult}
/>
</Route>
));
}, [updateResult, chartData, feedData, params.id]);
function getAnalyticData() {
setFetching(true);
getAnalyticDetailsAPI(params.id).then((res) => {
if (!isMounted.current) {
return;
}
if (res.error || !res.data || !res.data.context) {
setFetching(false);
res.data
? actions.addAlert(res.data)
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
history.push('/app/analyze/saved');
return;
}
const { context } = res.data;
const date = context && context.rawFilters && context.rawFilters.date;
setFeedData({
feeds: context.feeds.map((item) => ({
feed: item.name,
id: item.id
})),
startDate: date && date.start,
endDate: date && date.end
});
setFetching(false);
});
}
function toggleModal() {
setAlertModal((prev) => !prev);
}
if (fetching) {
return 'Loading...';
}
const isRTL = document.documentElement.dir === 'rtl';
return (
<Fragment>
<div
className="d-flex"
style={{ position: 'absolute', top: 0, right: 0 }}
>
{alertCharts && alertCharts.length > 0 && (
<UncontrolledDropdown className="d-inline-block">
<DropdownToggle color="info" className="btn-shadow" caret>
<Interpolate
t={t}
i18nKey="analyzeTab.createAlert"
alertsLength={alertCharts.length}
/>
</DropdownToggle>
<DropdownMenu
className={`dropdown-menu-right rm-pointers dropdown-menu-shadow dropdown-menu-hover-link${
isRTL ? ' dropdown-menu-left' : ''
}`}
>
<DropdownItem header>
{t('analyzeTab.selectedCharts')}
</DropdownItem>
{alertCharts.map((chart, i) => (
<div className="dropdown-item" key={`${chart.name}_${i}}`}>
<span>
{chart.name}
{isNaN(chart.id) ? '' : ` (#${chart.id})`}
</span>
<Button
className="btn-icon btn-icon-only ml-auto mr-2 p-1"
color="danger"
onClick={function () {
removeAlertChart({ name: chart.name, id: chart.id });
}}
>
<IoIosTrash fontSize="1rem" className="ml-auto" />
</Button>
</div>
))}
<DropdownItem divider />
<div className="p-2 pr-3 text-right">
<Button
className="btn-shadow btn-sm"
color="primary"
onClick={toggleModal}
>
{t('analyzeTab.createAlertBtn')}
</Button>
</div>
</DropdownMenu>
</UncontrolledDropdown>
)}
{/*
<Button
className="btn-icon ml-2"
color="info"
// change style for mobile view
>
<IoIosSave className="btn-icon-wrapper" />
Save
</Button> */}
</div>
<div className="btn-actions-pane-right mask-line overflow-auto mb-3 pl-3">
{subChartCategories.map((cat, i, arr) => (
<Button
key={cat.title}
title={cat.title}
tag={NavLink}
to={`/app/analyze/${params.id}/${cat.path}`}
size="sm"
outline
color="primary"
className={cx('btn-pill btn-wide', {
'mr-1 ml-1': i !== 0 && i !== arr.length - 1
})}
activeClassName="active"
>
{t(`analyzeTab.overviewCharts.${cat.transKey}`)}
</Button>
))}
</div>
<AlertDialog
isOpen={alertModal}
toggle={toggleModal}
alertCharts={alertCharts}
resetAlertChart={resetAlertChart}
/>
<Switch>
{subChartRoutes}
<Redirect
to={`/app/analyze/${params.id}/${subChartCategories[0].path}`}
/>
</Switch>
</Fragment>
);
}
ShowCharts.propTypes = {
t: PropTypes.func.isRequired,
analyze: PropTypes.object,
actions: PropTypes.object
};
const applyDecorators = compose(
reduxConnect('analyze', ['appState', 'analyze']),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(ShowCharts);
@@ -0,0 +1,357 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Col, Row } from 'reactstrap';
import ECharts from '../../../../../common/charts/ECharts';
import ChartWrapper from '../ChartWrapper';
import {
getBarOptions,
getPieOptions
} from '../../../../../common/charts/ChartsOptions';
import { IoIosAdd, IoIosRefresh, IoIosCheckmark } from 'react-icons/io';
import reduxConnect from '../../../../../../redux/utils/connect';
import translate from 'react-i18next/dist/commonjs/translate';
import { compose } from 'redux';
import { getOverviewPieAPI } from '../../../../../../api/analytics/createAnalytics';
import useIsMounted from '../../../../../common/hooks/useIsMounted';
const initialBar = {
data: [],
error: undefined,
loading: true,
vertical: false
};
const initialPie = { data: [], error: undefined, loading: true };
function Demographics(props) {
const { actions, analyze, feedData, id, t } = props;
const isMounted = useIsMounted();
const [barCountriesData, setBarCountriesData] = useState(initialBar);
const [barLanguagesData, setBarLanguagesData] = useState(initialBar);
const [genderData, setGenderData] = useState(initialPie);
useEffect(() => {
if (!id) {
return;
}
// getCountriesData()
getLanguagesData();
getGenderData();
}, []);
useEffect(() => {
if (barCountriesData.data) {
setBarCountriesData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [barCountriesData.vertical]);
useEffect(() => {
if (barLanguagesData.data) {
setBarLanguagesData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [barLanguagesData.vertical]);
function updateResult(foo, id) {
switch (id) {
case cn.first:
// getCountriesData()
return;
case cn.second:
getLanguagesData();
return;
case cn.third:
getGenderData();
return;
default:
return;
}
}
/* Uncomment when country chart shows up
function getCountriesData() {
setBarCountriesData((prev) => ({ ...prev, loading: true }))
getOverviewPieAPI('country', id).then((res) => {
if (!isMounted.current) {
return false
}
if (res.error || !res.data.data) {
// on error
setBarCountriesData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}))
return
}
const { data } = res.data
const barOptions = {}
const errors = {}
Object.entries(data).forEach((feed) => {
const [name, value] = feed
const labels = ['Results']
const datasets = Object.keys(value).map((item) => ({
name: item,
type: 'bar',
data: [value[item]]
}))
if (!datasets || (Array.isArray(datasets) && datasets.length < 1)) {
errors[name] = t('analyzeTab.noData');
}
barOptions[name] = getBarOptions(datasets, labels)
})
setBarCountriesData({
data: barOptions,
error: errors,
loading: false,
vertical: false
})
})
} */
function getLanguagesData() {
setBarLanguagesData((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI('language', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setBarLanguagesData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const barOptions = {};
const errors = {};
Object.entries(data).forEach((feed) => {
const [name, value] = feed;
const labels = ['Results'];
const datasets = Object.keys(value).map((item) => ({
name: item,
type: 'bar',
data: [value[item]]
}));
if (!datasets || (Array.isArray(datasets) && datasets.length < 1)) {
errors[name] = t('analyzeTab.noData');
}
barOptions[name] = getBarOptions(datasets, labels);
});
setBarLanguagesData({
data: barOptions,
error: errors,
loading: false,
vertical: false
});
});
}
function getGenderData() {
setGenderData((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI('gender', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setGenderData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const pieOptions = {};
const errors = {};
Object.entries(data).forEach((feed) => {
const [name, value] = feed;
if (!value || (Array.isArray(value) && value.length < 1)) {
errors[name] = t('analyzeTab.noData');
}
pieOptions[name] = getPieOptions(
Object.entries(value).map((v) => ({
name: v[0],
value: v[1]
}))
);
});
setGenderData({
data: pieOptions,
error: errors,
loading: false
});
});
}
function changeVertical(name, id) {
name === cn.first
? setBarCountriesData((prev) => ({ ...prev, vertical: !prev.vertical }))
: setBarLanguagesData((prev) => ({ ...prev, vertical: !prev.vertical }));
}
const hideChartAlert = (name, id) =>
analyze.alertCharts.find((v) => v.name === name && v.id === id);
const hideChartPieAlert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.third && v.id === id);
const barchartMenus = (name, id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: name, id }),
showInMore: false,
hide: hideChartAlert(name, id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChartAlert(name, id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, name),
showInMore: false
},
/* {
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
}, */
{
title: t('analyzeTab.chartMenus.toggleHV'),
fn: () => changeVertical(name, id),
showInMore: true
}
];
const piechartMenus = (id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.third, id }),
showInMore: false,
hide: hideChartPieAlert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChartPieAlert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.third),
showInMore: false
}
// { title: t('analyzeTab.chartMenus.addToDashboard'), fn: () => {}, showInMore: true }
];
return (
<Row>
{/* {feedData.feeds.map((feed) => (
<Col key={feed.id} md="6">
<ChartWrapper
title={`${t('analyzeTab.charts.topLanguages')} (${feed.feed})`}
menus={barchartMenus(cn.first, feed.id)}
>
<ECharts
xLabel={barCountriesData.labels}
loading={barCountriesData.loading}
options={barCountriesData.data[feed.feed]}
message={
barCountriesData.error && barCountriesData.error[feed.feed]
}
/>
</ChartWrapper>
</Col>
))} */}
{feedData.feeds.map((feed) => (
<Col key={feed.id} md="6">
<ChartWrapper
title={`${t('analyzeTab.charts.topLanguages')} (${feed.feed})`}
menus={barchartMenus(cn.second, feed.id)}
>
<ECharts
xLabel={barLanguagesData.labels}
loading={barLanguagesData.loading}
options={barLanguagesData.data[feed.feed]}
message={
barLanguagesData.error && barLanguagesData.error[feed.feed]
}
/>
</ChartWrapper>
</Col>
))}
{feedData.feeds.map((feed) => (
<Col key={feed.id} md="6">
<ChartWrapper
title={`${t('analyzeTab.charts.gender')} (${feed.feed})`}
menus={piechartMenus(feed.id)}
>
<ECharts
loading={genderData.loading}
options={genderData.data[feed.feed]}
message={genderData.error && genderData.error[feed.feed]}
/>
</ChartWrapper>
</Col>
))}
</Row>
);
}
const cn = {
first: 'Top Countries',
second: 'Top Languages',
third: 'Gender'
};
Demographics.propTypes = {
t: PropTypes.func.isRequired,
chartData: PropTypes.object,
actions: PropTypes.object,
id: PropTypes.string,
feedData: PropTypes.object,
analyze: PropTypes.object
};
const applyDecorators = compose(
reduxConnect('analyze', ['appState', 'analyze']),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(React.memo(Demographics));
@@ -0,0 +1,290 @@
/* eslint-disable react/prop-types */
import React, {
useState,
useCallback,
Fragment,
useEffect,
useMemo
} from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { compose } from 'redux';
import { Table } from '../../../../../common/Table/Table';
import { getInfluencersAPI } from '../../../../../../api/analytics/createAnalytics';
import { reduxActions } from '../../../../../../redux/utils/connect';
import {
getQueryParams,
removeHttpsUrl,
capOnlyFirstLetter,
getValidHttpUrl
} from '../../../../../../common/helper';
import i18n from '../../../../../../i18n';
function Influencers(props) {
const [dataSource, setDataSource] = useState(null);
const [loading, setLoading] = useState(true);
const [filter] = useState(filtersNames[1].id);
const { t, actions, id, feedData } = props;
useEffect(() => {
if (!id || !dataSource) {
return;
}
getInfluencers(); //called from table
}, [filter]);
const getDetailsColumns = (id) => {
return id === filtersNames[0].id ? sourceDetails : authorDetails;
};
const authorDetails = useMemo(
() => [
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.rank'),
accessor: 'source_hashcode',
Cell: (row) => (
<div style={{ textAlign: 'center' }}>{row.index + 1}</div>
),
minWidth: 52
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.influencers'),
accessor: 'influence',
Cell: (row) =>
getValidHttpUrl(row.value) ? (
<a
target="_blank"
rel="nofollow noopener"
href={getValidHttpUrl(row.value)}
>
{row.original && row.original.author_name}
</a>
) : (
removeHttpsUrl(row.value)
),
minWidth: 130
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.sourceType'),
accessor: 'source_type',
Cell: (row) => capOnlyFirstLetter(row.value),
minWidth: 102
}
],
[i18n.language]
);
const sourceDetails = useMemo(
() => [
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.rank'),
accessor: 'source_hashcode',
Cell: (row) => (
<div style={{ textAlign: 'center' }}>{row.index + 1}</div>
),
minWidth: 52
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.influencers'),
accessor: 'influence',
Cell: (row) =>
getValidHttpUrl(row.value) ? (
<a
target="_blank"
rel="nofollow noopener"
href={getValidHttpUrl(row.value)}
>
{removeHttpsUrl(row.value)}
</a>
) : (
removeHttpsUrl(row.value)
),
minWidth: 130
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.sourceType'),
accessor: 'source_type',
Cell: (row) => capOnlyFirstLetter(row.value),
minWidth: 102
}
],
[i18n.language]
);
const sentimentColumns = useMemo(
() => [
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.total'),
// accessor: d => d.nop.total
accessor: 'totalSentiment',
minWidth: 52,
Cell: (row) => (
<div style={{ textAlign: 'center' }}>{row.value || 0}</div>
)
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.positive'),
accessor: 'POSITIVE',
minWidth: 78,
Cell: (row) => (
<div style={{ textAlign: 'center' }}>{row.value || 0}</div>
)
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.neutral'),
accessor: 'NEUTRAL',
minWidth: 78,
Cell: (row) => (
<div style={{ textAlign: 'center' }}>{row.value || 0}</div>
)
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.negative'),
accessor: 'NEGATIVE',
minWidth: 78,
Cell: (row) => (
<div style={{ textAlign: 'center' }}>{row.value || 0}</div>
)
}
],
[i18n.language]
);
const reachColumns = useMemo(
() => [
/* {
Header: i18n.t('tabsContent:analyzeTab.influencerCols.reach'),
accessor: 'reach',
minWidth: 65,
Cell: (row) => <div style={{ textAlign: 'center' }}>{row.value || 0}</div>
}, */
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.engagement'),
accessor: 'engagement',
minWidth: 105,
Cell: (row) => (
<div style={{ textAlign: 'center' }}>{row.value || 0}</div>
)
}
/* {
Header: i18n.t('tabsContent:analyzeTab.influencerCols.engagementPerMention'),
accessor: 'engagement_per_mention',
Cell: (row) => <div style={{ textAlign: 'center' }}>{row.value || 0}</div>
} */
],
[i18n.language]
);
const columnsList = useMemo(
() => [
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.details'),
headerClassName: 'text-center',
columns: getDetailsColumns(filter)
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.sentiments'),
headerClassName: 'text-center',
columns: sentimentColumns
},
{
Header: i18n.t('tabsContent:analyzeTab.influencerCols.reach'),
headerClassName: 'text-center',
columns: reachColumns
}
],
[filter, i18n.language]
);
const getInfluencers = useCallback(
(page = 0, pageSize = 10) => {
setLoading(true);
const filterParams = getQueryParams({ page, pageSize });
getInfluencersAPI(id, filter, filterParams).then((res) => {
// if (false) {
if (res.error || res.data === null || !res.data.data) {
setLoading(false);
return actions.addAlert({
type: 'error',
transKey: 'somethingWrong'
});
}
const tableData = {};
res.data.data.forEach((v) => {
tableData[v.name] = v.data;
});
setDataSource(tableData);
setLoading(false);
});
},
[id, filter]
);
return (
<Fragment>
{/* <ButtonGroup size="sm" className="mb-3 d-block text-right">
{filtersNames.map((item) => (
<Button
outline
key={item.id}
title={item.name}
color="secondary"
onClick={function () {
setFilter(item.id)
}}
active={filter === item.id}
>
{item.name}
</Button>
))}
</ButtonGroup> */}
{feedData.feeds.map((feed) => {
let tableData = dataSource;
if (!tableData || !tableData[feed.feed]) {
tableData = { [feed.feed]: [] };
// uncomment for pagination
// tableData[feed.feed] = { data: [], totalCount: 0, limit: 0, page: 0 }
}
const { totalCount = 0, limit = 0, page = 0 } = tableData[feed.feed];
return (
<Table
key={feed.id}
t={t}
cardTitle={`${t('analyzeTab.charts.topInfluencers')} (${
feed.feed
})`}
columns={columnsList}
data={tableData[feed.feed]}
totalCount={totalCount}
showTotalCount
limit={limit}
page={page}
isLoading={loading}
onFetchData={getInfluencers}
/>
);
})}
</Fragment>
);
}
const filtersNames = [
{ name: 'Source', id: 0 },
{ name: 'Author', id: 1 }
];
Influencers.propTypes = {
t: PropTypes.func.isRequired,
feedData: PropTypes.object,
id: PropTypes.string,
actions: PropTypes.object
};
const applyDecorators = compose(
translate(['tabsContent'], { wait: true }),
reduxActions()
);
export default applyDecorators(Influencers);
@@ -0,0 +1,722 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Col, Row } from 'reactstrap';
import ECharts from '../../../../../common/charts/ECharts';
import ChartWrapper from '../ChartWrapper';
import {
getBarOptions,
getPieOptions
} from '../../../../../common/charts/ChartsOptions';
import { IoIosAdd, IoIosRefresh, IoIosCheckmark } from 'react-icons/io';
import reduxConnect from '../../../../../../redux/utils/connect';
import translate from 'react-i18next/dist/commonjs/translate';
import { compose } from 'redux';
import {
getEngagementsAPI,
getEngagementsTimeAPI,
getOverviewBarAPI,
getOverviewPieAPI
} from '../../../../../../api/analytics/createAnalytics';
import useIsMounted from '../../../../../common/hooks/useIsMounted';
function Performance(props) {
const { actions, analyze, feedData, id, t } = props;
const isMounted = useIsMounted();
const [barData, setBarData] = useState({
data: [],
error: undefined,
loading: true,
vertical: false
});
const [engBarData, setEngBarData] = useState({
data: [],
error: undefined,
loading: true,
vertical: false
});
const [potentialBarData, setPotentialBarData] = useState({
data: [],
error: undefined,
loading: true,
vertical: false
});
const [sentimentBar, setSentimentBar] = useState({
data: [],
error: undefined,
loading: true
});
const [pieMentions, setpieMentions] = useState({
data: [],
error: undefined,
loading: true
});
const [pieEng, setpieEng] = useState({
data: [],
error: undefined,
loading: true
});
/* const [pieReach, setpieReach] = useState({
data: [],
error: undefined,
loading: true
}); */
useEffect(() => {
// pass filter
if (!id) {
return;
}
getBarChart();
getEngBarChart();
// getPotentialChart()
getSentimentChart();
getpieMentions();
getpieEngg();
// getpieReach()
}, []);
function updateResult(foo, id) {
switch (id) {
case cn.first:
getBarChart();
return;
case cn.second:
getEngBarChart();
return;
case cn.third:
// getPotentialChart() // Uncomment when API has data
return;
case cn.fourth:
getSentimentChart();
return;
case cn.fifth:
getpieMentions();
return;
case cn.sixth:
getpieEngg();
return;
case cn.seventh:
// getpieReach() // Uncomment when API has data
return;
default:
return;
}
}
useEffect(() => {
if (barData.data) {
setBarData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [barData.vertical]);
useEffect(() => {
if (engBarData.data) {
setEngBarData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [engBarData.vertical]);
useEffect(() => {
if (potentialBarData.data) {
setPotentialBarData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [potentialBarData.vertical]);
function getBarChart() {
setBarData((prev) => ({ ...prev, loading: true }));
getOverviewBarAPI('none', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setBarData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const labels = Object.keys(data[0].data);
const datasets = data.map((item) => ({
name: item.name,
type: barData.vertical ? 'bar' : 'line',
smooth: true,
data: Object.values(item.data)
}));
const barOptions = getBarOptions(datasets, labels);
setBarData({
data: barOptions,
error: false,
loading: false,
vertical: false
});
});
}
function getEngBarChart() {
setEngBarData((prev) => ({ ...prev, loading: true }));
getEngagementsTimeAPI(id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setEngBarData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const labels = Object.keys(data[0].data);
const datasets = data.map((item) => ({
name: item.name,
type: barData.vertical ? 'bar' : 'line',
smooth: true,
data: Object.values(item.data)
}));
const barOptions = getBarOptions(datasets, labels);
setEngBarData({
data: barOptions,
error: false,
loading: false,
vertical: false
});
});
}
/*
function getPotentialChart() {
setPotentialBarData((prev) => ({ ...prev, loading: true }));
getOverviewBarAPI('none', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setPotentialBarData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const labels = Object.keys(data);
const datasets = {
name: 'Potential reach over time',
type: potentialBarData.vertical ? 'bar' : 'line',
smooth: true,
data: Object.values(data)
};
const barOptions = getBarOptions(datasets, labels);
setPotentialBarData({
data: barOptions,
error: false,
loading: false,
vertical: false
});
});
} */
function getSentimentChart() {
setSentimentBar((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI('sentiment', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setSentimentBar((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const barOptions = {};
Object.keys(data).forEach((feed) => {
const labels = ['Results'];
const datasets = ['POSITIVE', 'NEGATIVE', 'NEUTRAL'].map((item) => ({
name: item,
type: 'bar',
data: [data[feed][item]]
}));
barOptions[feed] = getBarOptions(datasets, labels);
});
setSentimentBar({
data: barOptions,
error: false,
loading: false,
vertical: false
});
});
}
function getpieMentions() {
setpieMentions((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI('none', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setpieMentions((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const pieOptions = getPieOptions(
Object.entries(data).map((v) => ({ name: v[0], value: v[1] }))
);
setpieMentions({
data: pieOptions,
error: false,
loading: false
});
});
}
function getpieEngg() {
setpieEng((prev) => ({ ...prev, loading: true }));
getEngagementsAPI(id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setpieEng((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
// condition for other filter than 0
const { data } = res.data;
const pieOptions = getPieOptions(
Object.entries(data).map((v) => ({ name: v[0], value: v[1] }))
);
setpieEng({
data: pieOptions,
error: false,
loading: false
});
});
}
/*
function getpieReach() {
setpieReach((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI('none', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setpieReach((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const pieOptions = getPieOptions(
Object.entries(data).map((v) => ({ name: v[0], value: v[1] }))
);
setpieReach({
data: pieOptions,
error: false,
loading: false
});
});
} */
function changeVertical(chart) {
switch (chart) {
case cn.first:
setBarData((prev) => ({ ...prev, vertical: !prev.vertical }));
return;
case cn.second:
setEngBarData((prev) => ({ ...prev, vertical: !prev.vertical }));
return;
case cn.third:
setPotentialBarData((prev) => ({ ...prev, vertical: !prev.vertical }));
return;
default:
return;
}
}
const hideChart1Alert = analyze.alertCharts.find((v) => v.name === cn.first);
const hideChart2Alert = analyze.alertCharts.find((v) => v.name === cn.second);
// const hideChart3Alert = analyze.alertCharts.find((v) => v.name === cn.third);
const hideChart4Alert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.fourth && v.id === id);
const hideChart5Alert = analyze.alertCharts.find((v) => v.name === cn.fifth);
const hideChart6Alert = analyze.alertCharts.find((v) => v.name === cn.sixth);
/* const hideChart7Alert = analyze.alertCharts.find(
(v) => v.name === cn.seventh
); */
const barchart1Menus = [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.first, id: 'none' }),
showInMore: false,
hide: hideChart1Alert
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart1Alert
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.first),
showInMore: false
},
/* {
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
}, */
{
title: t('analyzeTab.chartMenus.toggleHV'),
fn: () => changeVertical(cn.first),
showInMore: true
}
];
const barchart2Menus = [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.second, id: 'none' }),
showInMore: false,
hide: hideChart2Alert
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart2Alert
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.second),
showInMore: false
},
/* {
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
}, */
{
title: t('analyzeTab.chartMenus.toggleHV'),
fn: () => changeVertical(cn.second),
showInMore: true
}
];
/*
const barchart3Menus = [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.third, id: 'none' }),
showInMore: false,
hide: hideChart3Alert
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart3Alert
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.third),
showInMore: false
},
{
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
},
{
title: t('analyzeTab.chartMenus.toggleHV'),
fn: () => changeVertical(cn.third),
showInMore: true
}
];
*/
function barchart4Menus(id) {
return [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.fourth, id }),
showInMore: false,
hide: hideChart4Alert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart4Alert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.fourth, id),
showInMore: false
}
/* {
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
} */
];
}
const pieChart1 = [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.fifth, id: 'none' }),
showInMore: false,
hide: hideChart5Alert
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart5Alert
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.fifth),
showInMore: false
}
// { title: t('analyzeTab.chartMenus.addToDashboard'), fn: () => {}, showInMore: true }
];
const pieChart2 = [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.sixth, id: 'none' }),
showInMore: false,
hide: hideChart6Alert
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart6Alert
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.sixth),
showInMore: false
}
// { title: t('analyzeTab.chartMenus.addToDashboard'), fn: () => {}, showInMore: true }
];
/*
const pieChart3 = [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.seventh, id: 'none' }),
showInMore: false,
hide: hideChart7Alert
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart7Alert
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.seventh),
showInMore: false
}
// { title: t('analyzeTab.chartMenus.addToDashboard'), fn: () => {}, showInMore: true }
];
*/
return (
<Row>
<Col md="8">
<ChartWrapper
title={t('analyzeTab.charts.mentionsOverTime')}
menus={barchart1Menus}
>
<ECharts
xLabel={barData.labels}
loading={barData.loading}
options={barData.data}
/>
</ChartWrapper>
</Col>
<Col md="4">
<ChartWrapper title={t('analyzeTab.charts.mentions')} menus={pieChart1}>
<ECharts
xLabel={pieMentions.labels}
loading={pieMentions.loading}
options={pieMentions.data}
/>
</ChartWrapper>
</Col>
<Col md="8">
<ChartWrapper
title={t('analyzeTab.charts.engagementOverTime')}
menus={barchart2Menus}
>
<ECharts
xLabel={engBarData.labels}
loading={engBarData.loading}
options={engBarData.data}
/>
</ChartWrapper>
</Col>
<Col md="4">
<ChartWrapper
title={t('analyzeTab.charts.engagement')}
menus={pieChart2}
>
<ECharts
xLabel={pieEng.labels}
loading={pieEng.loading}
options={pieEng.data}
/>
</ChartWrapper>
</Col>
{/* <Col md="8">
<ChartWrapper title={t('analyzeTab.charts.potentialReachOverTime')} menus={barchart3Menus}>
<ECharts
xLabel={potentialBarData.labels}
loading={potentialBarData.loading}
options={potentialBarData.data}
/>
</ChartWrapper>
</Col>
<Col md="4">
<ChartWrapper title={t('analyzeTab.charts.potentialReach')} menus={pieChart3}>
<ECharts
xLabel={pieReach.labels}
loading={pieReach.loading}
options={pieReach.data}
/>
</ChartWrapper>
</Col> */}
{feedData.feeds.map((feed) => (
<Col md="12" key={feed.id}>
<ChartWrapper
title={`${t('analyzeTab.charts.proportionofSentiment')} (${
feed.feed
})`}
menus={barchart4Menus(feed.id)}
>
<ECharts
xLabel={sentimentBar.labels}
loading={sentimentBar.loading}
options={sentimentBar.data[feed.feed]}
/>
</ChartWrapper>
</Col>
))}
</Row>
);
}
const cn = {
first: 'Mentions over time',
second: 'Engagement over time',
third: 'Potential reach over time',
fourth: 'Proportion of sentiment',
fifth: 'Mentions',
sixth: 'Engagement',
seventh: 'Potential Reach'
};
Performance.propTypes = {
chartData: PropTypes.object,
actions: PropTypes.object,
feedData: PropTypes.object,
id: PropTypes.string,
analyze: PropTypes.object,
t: PropTypes.func
};
const applyDecorators = compose(
reduxConnect('analyze', ['appState', 'analyze']),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(React.memo(Performance));
@@ -0,0 +1,403 @@
import React, { Fragment, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Button, ButtonGroup, Col, Row } from 'reactstrap';
import ECharts from '../../../../../common/charts/ECharts';
import ChartWrapper from '../ChartWrapper';
import {
getBarOptions,
getPieOptions
} from '../../../../../common/charts/ChartsOptions';
import { IoIosAdd, IoIosRefresh, IoIosCheckmark } from 'react-icons/io';
import reduxConnect from '../../../../../../redux/utils/connect';
import translate from 'react-i18next/dist/commonjs/translate';
import { compose } from 'redux';
import {
getOverviewBarAPI,
getOverviewPieAPI
} from '../../../../../../api/analytics/createAnalytics';
import useIsMounted from '../../../../../common/hooks/useIsMounted';
const initialBar = {
data: [],
error: undefined,
loading: true,
vertical: false
};
const initialPie = { data: [], error: undefined, loading: true };
function ResultsTab(props) {
const { actions, analyze, feedData, id, t } = props;
const isMounted = useIsMounted();
const [barData, setBarData] = useState(initialBar);
const [barTimeData, setBarTimeData] = useState(initialBar);
const [pieData, setPieData] = useState(initialPie);
const [pieTimeData, setPieTimeData] = useState(initialPie);
const [filter, setFilter] = useState(filtersNames[0].id);
useEffect(() => {
if (!id) {
return;
}
if (filter === filtersNames[0].id) {
getBarChart();
getPieChart();
} else {
getBarChartFeeds();
getPieChartFeeds();
}
}, [filter]);
useEffect(() => {
if (barData.data) {
setBarData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [barData.vertical]);
function updateResult(foo, id) {
switch (id) {
case cn.first:
filter === filtersNames[0].id ? getBarChart() : getBarChartFeeds();
return;
case cn.second:
filter === filtersNames[0].id ? getPieChart() : getPieChartFeeds();
return;
}
}
function getBarChart() {
setBarData((prev) => ({ ...prev, loading: true }));
getOverviewBarAPI(filter, id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setBarData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const labels = data[0] ? Object.keys(data[0].data) : [];
const datasets = data.map((item) => ({
name: item.name,
type: barData.vertical ? 'bar' : 'line',
smooth: true,
data: Object.values(item.data)
}));
const barOptions = getBarOptions(datasets, labels);
setBarData({
data: barOptions,
error: false,
loading: false,
vertical: false
});
});
}
function getBarChartFeeds() {
setBarTimeData((prev) => ({ ...prev, loading: true }));
getOverviewBarAPI(filter, id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setBarTimeData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const barOptions = {};
const errors = {};
data.map((feed) => {
const { name, data } = feed;
if (!data || (Array.isArray(data) && data.length < 1)) {
errors[name] = t('analyzeTab.noData');
return;
}
const labels = Object.keys(data[0].data).sort();
const datasets = data.map((item) => ({
name: item.name,
type: barTimeData.vertical ? 'bar' : 'line',
smooth: true,
data: labels.map((v) => item.data[v])
}));
barOptions[name] = getBarOptions(datasets, labels);
});
setBarTimeData({
data: barOptions,
error: errors,
loading: false,
vertical: false
});
});
}
function getPieChart() {
setPieData((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI(filter, id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setPieData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const pieOptions = getPieOptions(
Object.entries(data).map((v) => ({ name: v[0], value: v[1] }))
);
setPieData({
data: pieOptions,
error: false,
loading: false
});
});
}
function getPieChartFeeds() {
setPieTimeData((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI(filter, id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setPieTimeData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const pieOptions = {};
const errors = {};
Object.entries(data).forEach((feed) => {
const [name, value] = feed;
if (!value || (Array.isArray(value) && value.length < 1)) {
errors[name] = t('analyzeTab.noData');
}
pieOptions[name] = getPieOptions(
Object.entries(value).map((v) => ({
name: v[0],
value: v[1]
}))
);
});
setPieTimeData({
data: pieOptions,
error: errors,
loading: false
});
});
}
function changeVertical() {
setBarData((prev) => ({ ...prev, vertical: !prev.vertical }));
}
const hideChart1Alert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.first && v.id === id);
const hideChart2Alert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.second && v.id === id);
const barchartMenus = (id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.first, id }),
showInMore: false,
hide: hideChart1Alert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart1Alert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.first),
showInMore: false
},
/* {
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
}, */
{
title: 'Toggle Horizontal/Vertical',
fn: changeVertical,
showInMore: true
}
];
const piechartMenus = (id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.second, id }),
showInMore: false,
hide: hideChart2Alert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart2Alert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.second),
showInMore: false
}
// { title: t('analyzeTab.chartMenus.addToDashboard'), fn: () => {}, showInMore: true }
];
return (
<Fragment>
<div className="mask-line overflow-auto white-space-nowrap pl-3 mb-3">
<ButtonGroup size="sm">
{filtersNames.map((item) => (
<Button
outline
key={item.id}
title={item.name}
color="secondary"
onClick={function () {
setFilter(item.id);
}}
active={filter === item.id}
>
{t(`analyzeTab.overviewCharts.${item.transKey}`)}
</Button>
))}
</ButtonGroup>
</div>
{filter === filtersNames[0].id ? ( // feeds in single graph
<Row>
<Col md="8">
<ChartWrapper
title={t('analyzeTab.charts.mentionsOverTime')}
menus={barchartMenus('none')}
>
<ECharts
xLabel={barData.labels}
loading={barData.loading}
options={barData.data}
/>
</ChartWrapper>
</Col>
<Col md="4">
<ChartWrapper
title={t('analyzeTab.charts.mentions')}
menus={piechartMenus('none')}
>
<ECharts loading={pieData.loading} options={pieData.data} />
</ChartWrapper>
</Col>
</Row>
) : (
feedData.feeds.map((feed) => (
<Row key={feed.id}>
<Col md="8">
<ChartWrapper
title={`${t('analyzeTab.charts.mentionsOverTime')} (${
feed.feed
})`}
menus={barchartMenus(feed.id)}
>
<ECharts
xLabel={barTimeData.labels}
loading={barTimeData.loading}
options={barTimeData.data && barTimeData.data[feed.feed]}
message={barTimeData.error && barTimeData.error[feed.feed]}
/>
</ChartWrapper>
</Col>
<Col md="4">
<ChartWrapper
title={`${t('analyzeTab.charts.mentions')} (${feed.feed})`}
menus={piechartMenus(feed.id)}
>
<ECharts
loading={pieTimeData.loading}
options={pieTimeData.data[feed.feed]}
message={pieTimeData.error && pieTimeData.error[feed.feed]}
/>
</ChartWrapper>
</Col>
</Row>
))
)}
</Fragment>
);
}
const cn = {
first: 'Mentions Over Time',
second: 'Share of Mentions'
};
const filtersNames = [
{ name: 'None', transKey: 'none', id: 'none' },
{ name: 'Media Types', transKey: 'mediaTypes', id: 'media' },
{ name: 'Sentiments', transKey: 'sentiments', id: 'sentiment' },
// { name: 'Countries', transKey:'countries', id: 'country' },
{ name: 'Languages', transKey: 'languages', id: 'language' }
];
ResultsTab.propTypes = {
actions: PropTypes.object,
id: PropTypes.string,
t: PropTypes.func,
feedData: PropTypes.object,
analyze: PropTypes.object
};
const applyDecorators = compose(
reduxConnect('analyze', ['appState', 'analyze']),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(React.memo(ResultsTab));
@@ -0,0 +1,255 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Col, Row } from 'reactstrap';
import ECharts from '../../../../../common/charts/ECharts';
import ChartWrapper from '../ChartWrapper';
import {
getBarOptions,
getPieOptions
} from '../../../../../common/charts/ChartsOptions';
import { IoIosAdd, IoIosRefresh, IoIosCheckmark } from 'react-icons/io';
import reduxConnect from '../../../../../../redux/utils/connect';
import translate from 'react-i18next/dist/commonjs/translate';
import { compose } from 'redux';
import {
getOverviewBarAPI,
getOverviewPieAPI
} from '../../../../../../api/analytics/createAnalytics';
import useIsMounted from '../../../../../common/hooks/useIsMounted';
const initialBar = {
data: [],
error: undefined,
loading: true,
vertical: false
};
const initialPie = { data: [], error: undefined, loading: true };
function Sentiment(props) {
const { actions, analyze, feedData, id, t } = props;
const isMounted = useIsMounted();
const [barData, setBarData] = useState(initialBar);
const [pieData, setPieData] = useState(initialPie);
useEffect(() => {
if (!id) {
return;
}
getBarChart();
getPieChart();
}, []);
useEffect(() => {
if (barData.data) {
setBarData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [barData.vertical]);
function updateResult(foo, id) {
switch (id) {
case cn.first:
getBarChart();
return;
case cn.second:
getPieChart();
return;
}
}
function getBarChart() {
setBarData((prev) => ({ ...prev, loading: true }));
getOverviewBarAPI('sentiment', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setBarData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const barOptions = {};
data.forEach((feed) => {
const { name, data } = feed;
const labels = Object.keys(data[0].data).sort();
const datasets = data.map((item) => ({
name: item.name,
type: barData.vertical ? 'bar' : 'line',
smooth: true,
data: labels.map((v) => item.data[v])
}));
barOptions[name] = getBarOptions(datasets, labels);
});
setBarData({
data: barOptions,
error: false,
loading: false,
vertical: false
});
});
}
function getPieChart() {
setPieData((prev) => ({ ...prev, loading: true }));
getOverviewPieAPI('sentiment', id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setPieData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const pieOptions = {};
Object.entries(data).forEach((feed) => {
const [name, value] = feed;
pieOptions[name] = getPieOptions(
Object.entries(value).map((v) => ({
name: v[0],
value: v[1]
}))
);
});
setPieData({
data: pieOptions,
error: false,
loading: false
});
});
}
function changeVertical() {
setBarData((prev) => ({ ...prev, vertical: !prev.vertical }));
}
const hideChart1Alert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.first && v.id === id);
const hideChart2Alert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.second && v.id === id);
const barchartMenus = (id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.first, id }),
showInMore: false,
hide: hideChart1Alert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart1Alert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.first),
showInMore: false
},
/* {
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
}, */
{
title: 'Toggle Horizontal/Vertical',
fn: changeVertical,
showInMore: true
}
];
const piechartMenus = (id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.second, id }),
showInMore: false,
hide: hideChart2Alert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart2Alert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.second),
showInMore: false
}
// { title: t('analyzeTab.chartMenus.addToDashboard'), fn: () => {}, showInMore: true }
];
return feedData.feeds.map((feed) => (
<Row key={feed.id}>
<Col md="8">
<ChartWrapper
title={`${t('analyzeTab.charts.sentimentOverTime')} (${feed.feed})`}
menus={barchartMenus(feed.id)}
>
<ECharts
xLabel={barData.labels}
loading={barData.loading}
options={barData.data[feed.feed]}
/>
</ChartWrapper>
</Col>
<Col md="4">
<ChartWrapper
title={`${t('analyzeTab.charts.shareofSentiment')} (${feed.feed})`}
menus={piechartMenus(feed.id)}
>
<ECharts
loading={pieData.loading}
options={pieData.data[feed.feed]}
/>
</ChartWrapper>
</Col>
</Row>
));
}
const cn = {
first: 'Sentiment Over Time',
second: 'Share of Sentiment'
};
Sentiment.propTypes = {
actions: PropTypes.object,
feedData: PropTypes.object,
analyze: PropTypes.object,
t: PropTypes.func
};
const applyDecorators = compose(
reduxConnect('analyze', ['appState', 'analyze']),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(React.memo(Sentiment));
@@ -0,0 +1,284 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Col, Row } from 'reactstrap';
import ECharts from '../../../../../common/charts/ECharts';
import 'echarts-wordcloud';
import { capitalize } from 'lodash';
import ChartWrapper from '../ChartWrapper';
import {
getBarOptions,
PieToolbox,
WordCloudOptions
} from '../../../../../common/charts/ChartsOptions';
import { IoIosAdd, IoIosRefresh, IoIosCheckmark } from 'react-icons/io';
import reduxConnect from '../../../../../../redux/utils/connect';
import translate from 'react-i18next/dist/commonjs/translate';
import { compose } from 'redux';
import {
getThemesCloudAPI,
getThemesTimeAPI
} from '../../../../../../api/analytics/createAnalytics';
import useIsMounted from '../../../../../common/hooks/useIsMounted';
import { capFirstLetter } from '../../../../../../common/helper';
const initialBar = {
data: [],
error: undefined,
loading: true,
vertical: false
};
const initialPie = { data: [], error: undefined, loading: true };
function Themes(props) {
const { actions, analyze, feedData, id, t } = props;
const isMounted = useIsMounted();
const [barData, setBarData] = useState(initialBar);
const [wordData, setWordData] = useState(initialPie);
useEffect(() => {
if (!id) {
return;
}
getBarChart();
getWordCloud();
}, []);
useEffect(() => {
if (barData.data) {
setBarData((prev) => ({
...prev,
data: {
...prev.data,
xAxis: prev.data.yAxis,
yAxis: prev.data.xAxis
}
}));
}
}, [barData.vertical]);
function updateResult(foo, id) {
switch (id) {
case cn.first:
getBarChart();
return;
case cn.second:
getWordCloud();
return;
}
}
function getBarChart() {
setBarData((prev) => ({ ...prev, loading: true }));
getThemesTimeAPI(id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// on error
setBarData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
let labels = null;
const barOptions = {};
const errors = {};
data.forEach((feedData) => {
const { name, data } = feedData;
const datasets = data.map((item) => ({
name: capitalize(item.name),
type: barData.vertical ? 'bar' : 'line',
smooth: true,
data: Object.values(item.data)
}));
if (!labels && data && data[0] && data[0].data) {
labels = Object.keys(data[0].data);
}
barOptions[name] = getBarOptions(datasets, labels);
if (!datasets || (Array.isArray(datasets) && datasets.length < 1)) {
errors[name] = t('analyzeTab.noData');
}
});
setBarData({
data: barOptions,
error: errors,
loading: false,
vertical: false
});
});
}
function getWordCloud() {
setWordData((prev) => ({ ...prev, loading: true }));
getThemesCloudAPI(id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setWordData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const cloudOptions = {};
const errors = {};
data.forEach((feed) => {
const { name, data } = feed;
if (!data || (Array.isArray(data) && data.length < 1)) {
errors[name] = t('analyzeTab.noData');
}
cloudOptions[name] = {
tooltip: {
show: true
},
toolbox: PieToolbox,
series: [
{
...WordCloudOptions,
data: Object.entries(data).map((v) => ({
name: capFirstLetter(v[0]),
value: v[1]
}))
}
]
};
});
setWordData({
data: cloudOptions,
error: false,
loading: false
});
});
}
function changeVertical() {
setBarData((prev) => ({ ...prev, vertical: !prev.vertical }));
}
const hideChart1Alert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.first && v.id === id);
const hideChart2Alert = (id) =>
analyze.alertCharts.find((v) => v.name === cn.second && v.id === id);
const barchartMenus = (id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.first, id }),
showInMore: false,
hide: hideChart1Alert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart1Alert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.first),
showInMore: false
},
/* {
title: t('analyzeTab.chartMenus.addToDashboard'),
fn: () => {},
showInMore: true
}, */
{
title: 'Toggle Horizontal/Vertical',
fn: changeVertical,
showInMore: true
}
];
const wordCloudMenus = (id) => [
{
title: '', // t('analyzeTab.chartMenus.addToAlert'),
icon: IoIosAdd,
size: 24,
fn: () => actions.addAlertChart({ name: cn.second, id }),
showInMore: false,
hide: hideChart2Alert(id)
},
{
title: '', // t('analyzeTab.chartMenus.addedToAlerts'),
icon: IoIosCheckmark,
size: 24,
showInMore: false,
hide: !hideChart2Alert(id)
},
{
title: t('analyzeTab.chartMenus.refresh'),
icon: IoIosRefresh,
fn: () => updateResult(null, cn.second),
showInMore: false
}
// { title: t('analyzeTab.chartMenus.addToDashboard'), fn: () => {}, showInMore: true }
];
return feedData.feeds.map((feed) => (
<Row key={feed.id}>
<Col md="8">
<ChartWrapper
title={`${t('analyzeTab.charts.themesOverTime')} (${feed.feed})`}
menus={barchartMenus(feed.id)}
>
<ECharts
xLabel={barData.labels}
loading={barData.loading}
options={barData.data[feed.feed]}
message={barData.error && barData.error[feed.feed]}
/>
</ChartWrapper>
</Col>
<Col md="4">
<ChartWrapper
title={`${t('analyzeTab.charts.topThemes')} (${feed.feed})`}
menus={wordCloudMenus(feed.id)}
>
<ECharts
loading={wordData.loading}
options={wordData.data[feed.feed]}
message={barData.error && barData.error[feed.feed]}
/>
</ChartWrapper>
</Col>
</Row>
));
}
const cn = {
first: 'Themes over time',
second: 'Top Themes'
};
Themes.propTypes = {
chartData: PropTypes.object,
actions: PropTypes.object,
feedData: PropTypes.object,
t: PropTypes.func,
analyze: PropTypes.object
};
const applyDecorators = compose(
reduxConnect('analyze', ['appState', 'analyze']),
translate(['tabsContent'], { wait: true })
);
export default applyDecorators(React.memo(Themes));
@@ -0,0 +1,208 @@
import React, { useEffect, useRef, useState, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Row, Col, ButtonGroup, Button } from 'reactstrap';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import 'leaflet-dvf/dist/leaflet-dvf';
// keep above 3 in sequence
import ChartWrapper from '../ChartWrapper';
import { getWorldMapAPI } from '../../../../../../api/analytics/createAnalytics';
import useIsMounted from '../../../../../common/hooks/useIsMounted';
import { translate } from 'react-i18next';
const initialPie = {
data: [],
error: undefined,
loading: true,
selected: undefined
};
function WorldMap(props) {
const { id, t } = props;
const mapRef = useRef();
const isMounted = useIsMounted();
const [pieData, setPieData] = useState(initialPie);
const [markers, setMarkers] = useState([]);
const feedNames = (pieData.data && Object.keys(pieData.data)) || [];
useEffect(() => {
mapRef.current = L.map('leaflet-map', {
center: [0, 0],
zoom: 2,
layers: [
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
noWrap: true,
attribution:
'&copy; <a target="_blank" noreferrer noopener href="http://osm.org/copyright">OpenStreetMap</a> contributors'
})
]
});
mapRef.current.whenReady(getMapSentiments);
}, []);
useEffect(() => {
const { data, selected, error } = pieData;
const selectedData = data[feedNames[selected]];
const hasErr = error && error[feedNames[selected]];
clearMap();
if (selectedData && !hasErr) {
// loop to add marker
const markersList = [];
selectedData.forEach((data) => {
const [lat, lng] = getLatLong(data.LatLng);
if (!lat || !lng) {
return;
}
let pieChartMarker = new L.PieChartMarker(new L.LatLng(lat, lng), {
...options,
data: {
positive: data.POSITIVE,
negative: data.NEGATIVE,
neutral: data.NEUTRAL
}
});
pieChartMarker.addTo(mapRef.current);
markersList.push(pieChartMarker);
});
// eslint-disable-next-line new-cap
const group = new L.featureGroup(markersList);
mapRef.current.fitBounds(group.getBounds());
setMarkers(markersList);
}
}, [pieData.data, pieData.selected]);
function getLatLong(str) {
const [lat, lng] = str.split(', ');
return [lat && parseFloat(lat), lng && parseFloat(lng)];
}
function clearMap() {
if (mapRef.current) {
markers.forEach((v) => {
mapRef.current.removeLayer(v);
});
}
}
function getMapSentiments() {
setPieData((prev) => ({ ...prev, loading: true }));
getWorldMapAPI(id).then((res) => {
if (!isMounted.current) {
return false;
}
if (res.error || !res.data.data) {
// alert on error
setPieData((prev) => ({
...prev,
loading: false,
error: res.errorMessage
}));
return;
}
const { data } = res.data;
const dataValues = {};
const errors = {};
data.map((feed) => {
const { name, data } = feed;
if (!data || (Array.isArray(data) && data.length < 1)) {
errors[name] = t('analyzeTab.noData');
}
dataValues[name] = data;
});
setPieData({
data: dataValues,
error: errors,
loading: false,
selected: 0
});
});
}
const style = {
height: 'max(300px, calc(100vh - 200px))'
};
return (
<Row>
<Col md="12">
<ChartWrapper title="Distribution by Sentiments">
<Fragment>
<ButtonGroup size="sm" className="d-block mb-2 text-right">
{feedNames.map((name, i) => (
<Button
outline
key={name}
title={name}
color="secondary"
onClick={function () {
setPieData((prev) => ({
...prev,
selected: i
}));
}}
active={pieData.selected === i}
>
{name}
</Button>
))}
</ButtonGroup>
<div className="position-relative">
<div id="leaflet-map" style={style} />
{pieData.error && pieData.error[feedNames[pieData.selected]] ? (
<div className="no-data" style={{ zIndex: 1000 }}>
{pieData.error[feedNames[pieData.selected]]}
</div>
) : null}
</div>
</Fragment>
</ChartWrapper>
</Col>
</Row>
);
}
const options = {
stroke: false,
fillOpacity: 0.7,
radius: 20,
gradient: false,
chartOptions: {
positive: {
fillColor: '#00FF00',
displayText: function (value) {
return value.toFixed(0);
}
},
negative: {
fillColor: '#FF0000',
displayText: function (value) {
return value.toFixed(0);
}
},
neutral: {
fillColor: '#000000',
displayText: function (value) {
return value.toFixed(0);
}
}
}
// Other L.Path style options
};
WorldMap.propTypes = {
actions: PropTypes.object,
feedData: PropTypes.object,
id: PropTypes.string,
t: PropTypes.func.isRequired,
analyze: PropTypes.object
};
export default translate(['tabsContent'], { wait: true })(WorldMap);
@@ -0,0 +1,17 @@
import Results from './Results'
import Performance from './Performance'
import Influencers from './Influencers'
import Sentiment from './Sentiment'
import Themes from './Themes'
import Demographics from './Demographics'
import WorldMap from './WorldMap'
export {
Results,
Performance,
Influencers,
Sentiment,
Themes,
Demographics,
WorldMap
}
@@ -0,0 +1,58 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { deleteAnalytics } from '../../../../../api/analytics/savedAnalytics';
import { translate } from 'react-i18next';
function DeleteDialog(props) {
const [loading, setLoading] = useState(false);
const { actions, data, toggle, fetchData, t } = props;
function handleSubmit() {
setLoading(true);
deleteAnalytics(data.value).then((res) => {
if (res.error) {
res.data
? actions.addAlert(res.data)
: actions.addAlert({ type: 'error', transKey: 'somethingWrong' });
setLoading(false);
return;
}
actions.addAlert({ type: 'notice', transKey: 'analyticsDeleted' });
setLoading(false);
toggle();
fetchData();
});
}
return (
<Modal isOpen={!!data} toggle={toggle} backdrop="static">
<ModalHeader toggle={toggle}>
{t('tabsContent:analyzeTab.deleteAnalysis')}
</ModalHeader>
<ModalBody>
<div>
<p>{t('messages.deleteMessage')}</p>
</div>
</ModalBody>
<ModalFooter>
<Button color="link" onClick={toggle}>
{t('commonWords.Cancel')}
</Button>
<Button color="danger" disabled={loading} onClick={handleSubmit}>
{loading ? t('commonWords.loading') : t('commonWords.Delete')}
</Button>
</ModalFooter>
</Modal>
);
}
DeleteDialog.propTypes = {
toggle: PropTypes.func,
t: PropTypes.func.isRequired,
data: PropTypes.object.isRequired,
fetchData: PropTypes.func,
actions: PropTypes.object
};
export default React.memo(translate(['common'], { wait: true })(DeleteDialog));
@@ -0,0 +1,170 @@
/* eslint-disable react/prop-types */
import React, {
useState,
useCallback,
useMemo,
Fragment,
useEffect
} from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { compose } from 'redux';
import { Table } from '../../../../common/Table/Table';
import { savedAnalytics } from '../../../../../api/analytics/savedAnalytics';
import reduxConnect from '../../../../../redux/utils/connect';
import {
getDate,
getQueryParams,
setDocumentData
} from '../../../../../common/helper';
import { Button } from 'reactstrap';
import DeleteDialog from './DeleteDialog';
import i18n from '../../../../../i18n';
function SavedAnalysisSubTab(props) {
const [dataSource, setDataSource] = useState({ data: [] });
const [loading, setLoading] = useState(true);
const [deleteValues, setDeleteValues] = useState(false);
const { t, actions } = props;
useEffect(() => {
setDocumentData('title', 'Saved Analysis | Analyze');
return () => {
setDocumentData('title');
};
}, []);
const columns = useMemo(() => {
const columnsList = [
{
id: 'feeds',
Header: t('analyzeTab.savedAnalytics.feeds'),
accessor: (d) => d.context.feeds,
Cell: (props) =>
props.value ? props.value.map((v) => v.name).join(', ') : ''
},
{
id: 'date',
Header: t('analyzeTab.savedAnalytics.dateRange'),
accessor: (d) => d.context.rawFilters.date,
Cell: (props) =>
props.value
? `${getDate(props.value.start, 'MM/DD/YYYY')} to ${getDate(
props.value.end,
'MM/DD/YYYY'
)}`
: '-'
},
{
Header: t('analyzeTab.savedAnalytics.createdAt'),
accessor: 'createdAt',
Cell: (props) => getDate(props.value, 'MM/DD/YYYY')
},
{
Header: t('analyzeTab.savedAnalytics.actions'),
accessor: 'id',
Cell: (props) => getActions(props)
}
];
return columnsList;
}, [getActions, i18n.language]);
const getActions = useCallback((props) => {
return (
<div>
<Button
outline
className="border-0 btn-transition"
color="primary"
size="sm"
tag={Link}
to={`/app/analyze/${props.value}/overview`}
>
{t('analyzeTab.savedAnalytics.view')}
</Button>
<Button
outline
className="border-0 btn-transition"
color="secondary"
tag={Link}
to={`/app/analyze/edit/${props.value}`}
>
{t('analyzeTab.savedAnalytics.edit')}
</Button>
<Button
outline
className="border-0 btn-transition"
color="secondary"
onClick={function () {
setDeleteValues(props);
}}
>
{t('analyzeTab.savedAnalytics.delete')}
</Button>
</div>
);
}, []);
const getSavedList = useCallback(
(page, pageSize) => {
setLoading(true);
const params = getQueryParams({ page, pageSize });
savedAnalytics(params).then((res) => {
if (res.error || res.data === null || !res.data) {
setLoading(false);
return actions.addAlert({
type: 'error',
transKey: 'somethingWrong'
});
}
res.data.length > 0 && setDataSource(res.data[0]);
setLoading(false);
});
},
[savedAnalytics]
);
const { data = [], totalCount = 0, limit = 10, page = 1 } = dataSource;
return (
<Fragment>
<Table
t={t}
cardTitle={t('analyzeTab.savedAnalysis')}
columns={columns}
data={data}
totalCount={totalCount}
showTotalCount
limit={limit}
page={page}
isLoading={loading}
onFetchData={getSavedList}
/>
{deleteValues && (
<DeleteDialog
data={deleteValues}
actions={actions}
toggle={function () {
setDeleteValues(false);
}}
fetchData={function () {
getSavedList(dataSource.page - 1, dataSource.limit);
}}
/>
)}
</Fragment>
);
}
SavedAnalysisSubTab.propTypes = {
t: PropTypes.func.isRequired,
actions: PropTypes.object
};
const applyDecorators = compose(
translate(['tabsContent'], { wait: true }),
reduxConnect()
);
export default applyDecorators(SavedAnalysisSubTab);
@@ -0,0 +1,61 @@
import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'
import { Link } from 'react-router-dom'
import { compose } from 'redux'
import { Card, Col, Row } from 'reactstrap'
class WelcomeSubTab extends React.Component {
render () {
const { t } = this.props
return (
<Card className="py-md-5 mb-3">
<Row className="justify-content-center no-gutters">
<Col sm="6" md="4" xl="4" className="m-4">
<div className="border b-radius-5 text-center p-4">
<div className="icon-wrapper mb-4 rounded-circle">
<div className="icon-wrapper-bg bg-primary" />
<i className="lnr-plus-circle text-primary" />
</div>
<div>
<h5 className="mb-5">{t('analyzeTab.createNewAnalysis')}</h5>
<Link
to="/app/analyze/create"
className="btn btn-primary btn-block fsize-1 btn-lg mr-1"
>
{t('analyzeTab.go')}
</Link>
</div>
</div>
</Col>
<Col sm="6" md="4" xl="4" className="m-4">
<div className="border b-radius-5 text-center p-4">
<div className="icon-wrapper mb-4 rounded-circle">
<div className="icon-wrapper-bg bg-primary" />
<i className="lnr-list text-primary" />
</div>
<div>
<h5 className="mb-5">{t('analyzeTab.viewSavedAnalysis')}</h5>
<Link
to="/app/analyze/saved"
className="btn btn-primary btn-block fsize-1 btn-lg mr-1"
>
{t('analyzeTab.view')}
</Link>
</div>
</div>
</Col>
</Row>
</Card>
)
}
}
WelcomeSubTab.propTypes = {
t: PropTypes.func.isRequired
}
const applyDecorators = compose(translate(['tabsContent'], { wait: true }))
export default applyDecorators(WelcomeSubTab)