mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-08 05:28:14 +08:00
[dev]creator detail
This commit is contained in:
parent
7549e0f47b
commit
711c4652bb
@ -4,10 +4,10 @@ import { fas } from '@fortawesome/free-solid-svg-icons';
|
|||||||
import Router from './router';
|
import Router from './router';
|
||||||
import './styles/Campaign.scss';
|
import './styles/Campaign.scss';
|
||||||
import '@/styles/custom-theme.scss';
|
import '@/styles/custom-theme.scss';
|
||||||
import { Chart as ChartJS, ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js';
|
import { Chart as ChartJS, ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale, LineElement, PointElement } from 'chart.js';
|
||||||
import NotificationBar from './components/NotificationBar';
|
import NotificationBar from './components/NotificationBar';
|
||||||
|
|
||||||
ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale);
|
ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale, LineElement, PointElement);
|
||||||
|
|
||||||
// Add Font Awesome icons to library
|
// Add Font Awesome icons to library
|
||||||
library.add(faTiktok, fas, faYoutube, faInstagram);
|
library.add(faTiktok, fas, faYoutube, faInstagram);
|
||||||
|
@ -2,22 +2,18 @@ import { ArrowLeft, Crown, Eye, Heart, Instagram, Link, Mail, MapPin } from 'luc
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Card, Table } from 'react-bootstrap';
|
import { Card, Table } from 'react-bootstrap';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { Link as RouterLink, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { clearCreator, fetchCreatorDetail } from '../store/slices/creatorsSlice';
|
import {
|
||||||
import { Bar, Doughnut } from 'react-chartjs-2';
|
clearCreator,
|
||||||
|
fetchCreatorDetail,
|
||||||
|
fetchCreatorFollowers,
|
||||||
|
fetchCreatorMetrics,
|
||||||
|
fetchCreatorTrends,
|
||||||
|
fetchCreatorVideos,
|
||||||
|
} from '../store/slices/creatorsSlice';
|
||||||
|
import { Bar, Doughnut, Line } from 'react-chartjs-2';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
const data = {
|
|
||||||
labels: ['Red', 'Blue', 'Green', 'Purple'],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'GMV',
|
|
||||||
data: [12, 19, 5, 2],
|
|
||||||
backgroundColor: ['rgba(217, 107, 139)', 'rgba(101, 105, 225)', 'rgba(93, 200, 179)', 'rgba(122, 87, 218)'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
@ -66,11 +62,18 @@ export default function CreatorDetail({}) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchCreatorDetail({ creatorId: id }));
|
dispatch(fetchCreatorDetail({ creatorId: id }));
|
||||||
|
fetchCreatorBaisInfo();
|
||||||
return () => {
|
return () => {
|
||||||
dispatch(clearCreator());
|
dispatch(clearCreator());
|
||||||
};
|
};
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
const fetchCreatorBaisInfo = () => {
|
||||||
|
dispatch(fetchCreatorMetrics({ creatorId: id }));
|
||||||
|
dispatch(fetchCreatorFollowers({ creatorId: id }));
|
||||||
|
dispatch(fetchCreatorTrends({ creatorId: id }));
|
||||||
|
dispatch(fetchCreatorVideos({ creatorId: id }));
|
||||||
|
};
|
||||||
const processChartData = (data, chart) => {
|
const processChartData = (data, chart) => {
|
||||||
switch (chart) {
|
switch (chart) {
|
||||||
case 'channel':
|
case 'channel':
|
||||||
@ -284,7 +287,150 @@ export default function CreatorDetail({}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CreatorBasicInfo({ selectedCreator }) {
|
function CreatorBasicInfo({ selectedCreator }) {
|
||||||
const [activeTab, setActiveTab] = useState('gmv');
|
useEffect(() => {}, [selectedCreator]);
|
||||||
|
return (
|
||||||
|
<div className='creator-basic-info card'>
|
||||||
|
<div className='basic-info-list'>
|
||||||
|
<div className='basic-info-title'>Collaboration Metrics</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>
|
||||||
|
{selectedCreator?.metricsData?.collaboration_metrics?.avg_commission_rate || '--'}
|
||||||
|
</div>
|
||||||
|
<div className='name'>Avg. Commission Rate</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>
|
||||||
|
{selectedCreator?.metricsData?.collaboration_metrics?.products_count || '--'}
|
||||||
|
</div>
|
||||||
|
<div className='name'>Products</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>
|
||||||
|
{selectedCreator?.metricsData?.collaboration_metrics?.brand_collaborations || '--'}
|
||||||
|
</div>
|
||||||
|
<div className='name'>Brand Collaborations</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>
|
||||||
|
{selectedCreator?.metricsData?.collaboration_metrics?.product_price || '--'}
|
||||||
|
</div>
|
||||||
|
<div className='name'>Product Price</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-list'>
|
||||||
|
<div className='basic-info-title'>
|
||||||
|
Video<span className='time-range'>{selectedCreator?.metricsData?.video?.date_range || '--'}</span>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.video?.gpm || '--'}</div>
|
||||||
|
<div className='name'>Video GPM</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.video?.videos_count || '--'}</div>
|
||||||
|
<div className='name'>Videos</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.video?.avg_views || '--'}</div>
|
||||||
|
<div className='name'>Avg. Video Views</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.video?.avg_engagement || '--'}</div>
|
||||||
|
<div className='name'>Avg. Video Engagement</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.video?.avg_likes || '--'}</div>
|
||||||
|
<div className='name'>Avg. Video Likes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-list'>
|
||||||
|
<div className='basic-info-title'>
|
||||||
|
Shoppable Video
|
||||||
|
<span className='time-range'>
|
||||||
|
{selectedCreator?.metricsData?.shoppable_video?.date_range || '--'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.gpm || '--'}</div>
|
||||||
|
<div className='name'>Video GPM</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.videos_count || '--'}</div>
|
||||||
|
<div className='name'>Videos</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.avg_views || '--'}</div>
|
||||||
|
<div className='name'>Avg. Video Views</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.avg_engagement || '--'}</div>
|
||||||
|
<div className='name'>Avg. Video Engagement</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.avg_likes || '--'}</div>
|
||||||
|
<div className='name'>Avg. Video Likes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-list'>
|
||||||
|
<div className='basic-info-title'>
|
||||||
|
LIVE<span className='time-range'>{selectedCreator?.metricsData?.live?.date_range || '--'}</span>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.live?.gpm || '--'}</div>
|
||||||
|
<div className='name'>LIVE GPM</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.live?.lives_count || '--'}</div>
|
||||||
|
<div className='name'>LIVE Videos</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.live?.avg_views || '--'}</div>
|
||||||
|
<div className='name'>Avg. LIVE Views</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.live?.avg_engagement || '--'}</div>
|
||||||
|
<div className='name'>Avg. LIVE Engagement</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.live?.avg_likes || '--'}</div>
|
||||||
|
<div className='name'>Avg. LIVE Likes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-list'>
|
||||||
|
<div className='basic-info-title'>
|
||||||
|
Shoppable LIVE
|
||||||
|
<span className='time-range'>
|
||||||
|
{selectedCreator?.metricsData?.shoppable_live?.date_range || '--'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.gpm || '--'}</div>
|
||||||
|
<div className='name'>LIVE GPM</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.lives_count || '--'}</div>
|
||||||
|
<div className='name'>LIVE Videos</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.avg_views || '--'}</div>
|
||||||
|
<div className='name'>Avg. LIVE Views</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.avg_engagement || '--'}</div>
|
||||||
|
<div className='name'>Avg. LIVE Engagement</div>
|
||||||
|
</div>
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.avg_likes || '--'}</div>
|
||||||
|
<div className='name'>Avg. LIVE Likes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{selectedCreator?.followerData && <CreatorFollowerInfo selectedCreator={selectedCreator} />}
|
||||||
|
{selectedCreator?.trendsData && <CreatorTrends selectedCreator={selectedCreator} />}
|
||||||
|
{selectedCreator?.videosData && <CreatorVideos selectedCreator={selectedCreator} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreatorFollowerInfo({ selectedCreator }) {
|
||||||
const barOptions = {
|
const barOptions = {
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false },
|
legend: { display: false },
|
||||||
@ -316,238 +462,216 @@ function CreatorBasicInfo({ selectedCreator }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const processChartData = (data, chart) => {
|
||||||
const barData = {
|
switch (chart) {
|
||||||
labels: ['TX', 'FL', 'NY', 'GE', 'CA'],
|
case 'gender':
|
||||||
datasets: [
|
return {
|
||||||
{
|
labels: Object.keys(data),
|
||||||
label: 'Top 5 Locations',
|
datasets: [
|
||||||
data: [11, 8, 6, 5.5, 5],
|
{
|
||||||
backgroundColor: '#6C63FF',
|
label: 'Gender',
|
||||||
borderRadius: 10,
|
data: Object.values(data),
|
||||||
barPercentage: 0.6,
|
backgroundColor: [
|
||||||
},
|
'rgba(217, 107, 139)',
|
||||||
],
|
'rgba(101, 105, 225)',
|
||||||
|
'rgba(93, 200, 179)',
|
||||||
|
'rgba(122, 87, 218)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'age':
|
||||||
|
return {
|
||||||
|
labels: Object.keys(data),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Age',
|
||||||
|
data: Object.values(data),
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(217, 107, 139)',
|
||||||
|
'rgba(101, 105, 225)',
|
||||||
|
'rgba(93, 200, 179)',
|
||||||
|
'rgba(122, 87, 218)',
|
||||||
|
'rgba(234, 145, 110)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'location':
|
||||||
|
return {
|
||||||
|
labels: Object.keys(data),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Location',
|
||||||
|
data: Object.values(data),
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(217, 107, 139)',
|
||||||
|
'rgba(101, 105, 225)',
|
||||||
|
'rgba(93, 200, 179)',
|
||||||
|
'rgba(122, 87, 218)',
|
||||||
|
'rgba(234, 145, 110)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='creator-basic-info card'>
|
<div className='basic-info-list'>
|
||||||
<div className='basic-info-list'>
|
<div className='basic-info-title'>
|
||||||
<div className='basic-info-title'>Collaboration Metrics</div>
|
Follwers<span className='time-range'>{selectedCreator?.date_range || '--'}</span>
|
||||||
<div className='basic-info-item'>
|
</div>
|
||||||
<div className='value'>{selectedCreator?.avg_commission_rate || '--'}</div>
|
<div className='followers-data-charts'>
|
||||||
<div className='name'>Avg. Commission Rate</div>
|
<div className='data-chart'>
|
||||||
|
<div className='chart-title'>Follower Gender</div>
|
||||||
|
<Doughnut
|
||||||
|
data={processChartData(selectedCreator?.followerData?.gender, 'gender')}
|
||||||
|
options={{ ...options, cutout: 60 }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='basic-info-item'>
|
<div className='data-chart'>
|
||||||
<div className='value'>{selectedCreator?.products || '--'}</div>
|
<div className='chart-title'>Follower Age</div>
|
||||||
<div className='name'>Products</div>
|
<Doughnut
|
||||||
|
data={processChartData(selectedCreator?.followerData?.age, 'age')}
|
||||||
|
options={{ ...options, cutout: 60 }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='basic-info-item'>
|
<div className='data-chart'>
|
||||||
<div className='value'>{selectedCreator?.brand_collaborations || '--'}</div>
|
<div className='chart-title'>Top 5 Locations</div>
|
||||||
<div className='name'>Brand Collaborations</div>
|
<Bar
|
||||||
</div>
|
data={processChartData(selectedCreator?.followerData?.locations, 'location')}
|
||||||
<div className='basic-info-item'>
|
options={barOptions}
|
||||||
<div className='value'>{selectedCreator?.product_price || '--'}</div>
|
/>
|
||||||
<div className='name'>Product Price</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='basic-info-list'>
|
</div>
|
||||||
<div className='basic-info-title'>
|
);
|
||||||
Video<span className='time-range'>{selectedCreator?.videoTimeRange || '--'}</span>
|
}
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoGpm || '--'}</div>
|
|
||||||
<div className='name'>Video GPM</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.videos?.length || '--'}</div>
|
|
||||||
<div className='name'>Videos</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoViews || '--'}</div>
|
|
||||||
<div className='name'>Avg. Video Views</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoEngagements || '--'}</div>
|
|
||||||
<div className='name'>Avg. Video Engagement</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoLikes || '--'}</div>
|
|
||||||
<div className='name'>Avg. Video Likes</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-list'>
|
|
||||||
<div className='basic-info-title'>
|
|
||||||
Shoppable Video<span className='time-range'>{selectedCreator?.videoTimeRange || '--'}</span>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoGpm || '--'}</div>
|
|
||||||
<div className='name'>Video GPM</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.videos?.length || '--'}</div>
|
|
||||||
<div className='name'>Videos</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoViews || '--'}</div>
|
|
||||||
<div className='name'>Avg. Video Views</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoEngagements || '--'}</div>
|
|
||||||
<div className='name'>Avg. Video Engagement</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgVideoLikes || '--'}</div>
|
|
||||||
<div className='name'>Avg. Video Likes</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-list'>
|
|
||||||
<div className='basic-info-title'>
|
|
||||||
LIVE<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveGpm || '--'}</div>
|
|
||||||
<div className='name'>LIVE GPM</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.liveVideos || '--'}</div>
|
|
||||||
<div className='name'>LIVE Videos</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveViews || '--'}</div>
|
|
||||||
<div className='name'>Avg. LIVE Views</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveEngagements || '--'}</div>
|
|
||||||
<div className='name'>Avg. LIVE Engagement</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveLikes || '--'}</div>
|
|
||||||
<div className='name'>Avg. LIVE Likes</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-list'>
|
|
||||||
<div className='basic-info-title'>
|
|
||||||
Shoppable LIVE<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveGpm || '--'}</div>
|
|
||||||
<div className='name'>LIVE GPM</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.liveVideos || '--'}</div>
|
|
||||||
<div className='name'>LIVE Videos</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveViews || '--'}</div>
|
|
||||||
<div className='name'>Avg. LIVE Views</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveEngagements || '--'}</div>
|
|
||||||
<div className='name'>Avg. LIVE Engagement</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='value'>{selectedCreator?.avgLiveLikes || '--'}</div>
|
|
||||||
<div className='name'>Avg. LIVE Likes</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-list'>
|
|
||||||
<div className='basic-info-title'>
|
|
||||||
Follwers<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
|
||||||
</div>
|
|
||||||
<div className='followers-data-charts'>
|
|
||||||
<div className='data-chart'>
|
|
||||||
<div className='chart-title'>Follower Gender</div>
|
|
||||||
<Doughnut data={data} options={{ ...options, cutout: 60 }} />
|
|
||||||
</div>
|
|
||||||
<div className='data-chart'>
|
|
||||||
<div className='chart-title'>Follower Age</div>
|
|
||||||
<Doughnut data={data} options={{ ...options, cutout: 60 }} />
|
|
||||||
</div>
|
|
||||||
<div className='data-chart'>
|
|
||||||
<div className='chart-title'>Top 5 Locations</div>
|
|
||||||
<Bar data={barData} options={barOptions} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-list'>
|
|
||||||
<div className='basic-info-title'>
|
|
||||||
Trends<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
|
||||||
</div>
|
|
||||||
<div className='tab-switches'>
|
|
||||||
<div
|
|
||||||
className={`tab-switch-item ${activeTab === 'gmv' ? 'active' : ''}`}
|
|
||||||
onClick={() => setActiveTab('gmv')}
|
|
||||||
>
|
|
||||||
GMV
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tab-switch-item ${activeTab === 'sold' ? 'active' : ''}`}
|
|
||||||
onClick={() => setActiveTab('sold')}
|
|
||||||
>
|
|
||||||
Items Sold
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tab-switch-item ${activeTab === 'followers' ? 'active' : ''}`}
|
|
||||||
onClick={() => setActiveTab('followers')}
|
|
||||||
>
|
|
||||||
Followers
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`tab-switch-item ${activeTab === 'views' ? 'active' : ''}`}
|
|
||||||
onClick={() => setActiveTab('views')}
|
|
||||||
>
|
|
||||||
Video Views
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='basic-info-list video-list'>
|
|
||||||
<div className='basic-info-title'>Videos</div>
|
|
||||||
{selectedCreator?.videos?.length > 0 &&
|
|
||||||
selectedCreator?.videos.map((video) => (
|
|
||||||
<div className='basic-info-item'>
|
|
||||||
<div className='picture' style={{ backgroundImage: `url(${video.picture})` }}></div>
|
|
||||||
<div className='right-side-info'>
|
|
||||||
<Crown size={16} />
|
|
||||||
<div className='video-title'>{video.title}</div>
|
|
||||||
<div className='release-time item-info'>
|
|
||||||
Release Time<span className='time'>{video.releaseTime}</span>
|
|
||||||
</div>
|
|
||||||
<div className='views item-info'>
|
|
||||||
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
|
||||||
{video.views}
|
|
||||||
</div>
|
|
||||||
<div className='like item-info'>
|
|
||||||
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
|
||||||
{video.likes}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className='basic-info-title'>Videos with Product</div>
|
function CreatorTrends({ selectedCreator }) {
|
||||||
{selectedCreator?.videosWithProduct?.length > 0 &&
|
const [activeTab, setActiveTab] = useState('gmv');
|
||||||
selectedCreator?.videosWithProduct.map((video) => (
|
|
||||||
<div className='basic-info-item'>
|
const lineOptions = {
|
||||||
<div className='picture' style={{ backgroundImage: `url(${video.picture})` }}></div>
|
responsive: true,
|
||||||
<div className='right-side-info'>
|
maintainAspectRatio: false,
|
||||||
<Crown size={16} className='crown-icon' />
|
plugins: {
|
||||||
<div className='video-title'>{video.title}</div>
|
legend: {
|
||||||
<div className='release-time item-info'>
|
position: 'bottom',
|
||||||
Release Time<span className='time'>{video.releaseTime}</span>
|
},
|
||||||
</div>
|
},
|
||||||
<div className='views item-info'>
|
};
|
||||||
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
const processChartData = (tab) => {
|
||||||
{video.views}
|
return {
|
||||||
</div>
|
labels: selectedCreator?.trendsData?.dates,
|
||||||
<div className='like item-info'>
|
datasets: [
|
||||||
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
{
|
||||||
{video.likes}
|
label: tab,
|
||||||
</div>
|
data: selectedCreator?.trendsData[tab],
|
||||||
|
fill: false,
|
||||||
|
borderColor: '#6366f1',
|
||||||
|
backgroundColor: '#6366f1',
|
||||||
|
tension: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='basic-info-list'>
|
||||||
|
<div className='basic-info-title'>
|
||||||
|
Trends<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
||||||
|
</div>
|
||||||
|
<div className='tab-switches'>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'gmv' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('gmv')}
|
||||||
|
>
|
||||||
|
GMV
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'items_sold' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('items_sold')}
|
||||||
|
>
|
||||||
|
Items Sold
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'followers' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('followers')}
|
||||||
|
>
|
||||||
|
Followers
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'video_views' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('video_views')}
|
||||||
|
>
|
||||||
|
Video Views
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'engagement_rate' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('engagement_rate')}
|
||||||
|
>
|
||||||
|
Engagement Rate
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='line-chart'>
|
||||||
|
<Line data={processChartData(activeTab)} options={lineOptions} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function CreatorVideos({ selectedCreator }) {
|
||||||
|
return (
|
||||||
|
<div className='basic-info-list video-list'>
|
||||||
|
<div className='basic-info-title'>Videos</div>
|
||||||
|
{selectedCreator?.videosData?.regular_videos.total > 0 &&
|
||||||
|
selectedCreator?.videosData.regular_videos?.videos.map((video) => (
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='picture' style={{ backgroundImage: `url(${video.thumbnail_url})` }}></div>
|
||||||
|
<div className='right-side-info'>
|
||||||
|
<Crown size={16} className='crown-icon' />
|
||||||
|
<RouterLink to={video.video_url} className='video-title text-dark'>
|
||||||
|
{video.title}
|
||||||
|
</RouterLink>
|
||||||
|
<div className='release-time item-info'>
|
||||||
|
Release Time<span className='time'>{video.release_date}</span>
|
||||||
|
</div>
|
||||||
|
<div className='views item-info'>
|
||||||
|
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
||||||
|
{video.view_count}
|
||||||
|
</div>
|
||||||
|
<div className='like item-info'>
|
||||||
|
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
||||||
|
{video.like_count}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
))}
|
||||||
|
|
||||||
|
<div className='basic-info-title'>Videos with Product</div>
|
||||||
|
{selectedCreator?.videosData?.product_videos?.total > 0 &&
|
||||||
|
selectedCreator?.videosData?.product_videos?.videos.map((video) => (
|
||||||
|
<div className='basic-info-item'>
|
||||||
|
<div className='picture' style={{ backgroundImage: `url(${video.thumbnail_url})` }}></div>
|
||||||
|
<div className='right-side-info'>
|
||||||
|
<Crown size={16} className='crown-icon' />
|
||||||
|
<RouterLink to={video.video_url} className='video-title text-dark'>
|
||||||
|
{video.title}
|
||||||
|
</RouterLink>
|
||||||
|
<div className='release-time item-info'>
|
||||||
|
Release Time<span className='time'>{video.release_date}</span>
|
||||||
|
</div>
|
||||||
|
<div className='views item-info'>
|
||||||
|
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
||||||
|
{video.view_count}
|
||||||
|
</div>
|
||||||
|
<div className='like item-info'>
|
||||||
|
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
||||||
|
{video.like_count}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ export const fetchPrivateCreators = createAsyncThunk(
|
|||||||
|
|
||||||
export const fetchCreatorDetail = createAsyncThunk(
|
export const fetchCreatorDetail = createAsyncThunk(
|
||||||
'creators/fetchCreatorDetail',
|
'creators/fetchCreatorDetail',
|
||||||
async ({ creatorId }, { getState, rejectWithValue }) => {
|
async ({ creatorId }, { dispatch, rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/daren_detail/creators/${creatorId}`);
|
const response = await api.get(`/daren_detail/creators/${creatorId}`);
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
@ -270,6 +270,71 @@ export const fetchCreatorDetail = createAsyncThunk(
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(response.message);
|
throw new Error(response.message);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('fetchCreatorDetail.rejected', error);
|
||||||
|
return rejectWithValue(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchCreatorMetrics = createAsyncThunk(
|
||||||
|
'creators/fetchCreatorMetrics',
|
||||||
|
async ({ creatorId }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/daren_detail/creators/${creatorId}/metrics`);
|
||||||
|
if (response.code === 200 || response.code === 201) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return rejectWithValue(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchCreatorFollowers = createAsyncThunk(
|
||||||
|
'creators/fetchCreatorFollowers',
|
||||||
|
async ({ creatorId }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/daren_detail/creator/${creatorId}/followers`);
|
||||||
|
if (response.code === 200) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return rejectWithValue(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchCreatorTrends = createAsyncThunk(
|
||||||
|
'creators/fetchCreatorTrends',
|
||||||
|
async ({ creatorId }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/daren_detail/creator/${creatorId}/trends`);
|
||||||
|
if (response.code === 200) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return rejectWithValue(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchCreatorVideos = createAsyncThunk(
|
||||||
|
'creators/fetchCreatorVideos',
|
||||||
|
async ({ creatorId }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/daren_detail/creator/${creatorId}/videos`);
|
||||||
|
if (response.code === 200) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return rejectWithValue(error.message);
|
return rejectWithValue(error.message);
|
||||||
}
|
}
|
||||||
@ -285,7 +350,9 @@ export const addCreatorsToCampaign = createAsyncThunk(
|
|||||||
campaign_id: campaignId,
|
campaign_id: campaignId,
|
||||||
});
|
});
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
dispatch(setNotificationBarMessage({ message: 'Creators added to campaign successfully', type: 'success' }));
|
dispatch(
|
||||||
|
setNotificationBarMessage({ message: 'Creators added to campaign successfully', type: 'success' })
|
||||||
|
);
|
||||||
return response;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
dispatch(setNotificationBarMessage({ message: response.message, type: 'error' }));
|
dispatch(setNotificationBarMessage({ message: response.message, type: 'error' }));
|
||||||
@ -395,6 +462,18 @@ const creatorsSlice = createSlice({
|
|||||||
.addCase(fetchCreatorDetail.rejected, (state, action) => {
|
.addCase(fetchCreatorDetail.rejected, (state, action) => {
|
||||||
state.status = 'failed';
|
state.status = 'failed';
|
||||||
state.error = action.error.message;
|
state.error = action.error.message;
|
||||||
|
})
|
||||||
|
.addCase(fetchCreatorMetrics.fulfilled, (state, action) => {
|
||||||
|
state.selectedCreator.metricsData = action.payload.data;
|
||||||
|
})
|
||||||
|
.addCase(fetchCreatorFollowers.fulfilled, (state, action) => {
|
||||||
|
state.selectedCreator.followerData = action.payload.data;
|
||||||
|
})
|
||||||
|
.addCase(fetchCreatorTrends.fulfilled, (state, action) => {
|
||||||
|
state.selectedCreator.trendsData = action.payload.data;
|
||||||
|
})
|
||||||
|
.addCase(fetchCreatorVideos.fulfilled, (state, action) => {
|
||||||
|
state.selectedCreator.videosData = action.payload.data;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -56,12 +56,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
height: 100%;
|
||||||
.creator-info-detail-container {
|
.creator-info-detail-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
height: calc(100% - 1.5rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.creator-info-container {
|
.creator-info-container {
|
||||||
flex: 4;
|
flex: 4;
|
||||||
@ -320,6 +323,13 @@
|
|||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
height: 178px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.crown-icon {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: $danger-500;
|
||||||
|
}
|
||||||
.video-title {
|
.video-title {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -343,6 +353,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.line-chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.collab-info {
|
.collab-info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
Loading…
Reference in New Issue
Block a user