CreatorCenter_OOIN/src/components/PrivateCreatorList.jsx
2025-05-22 20:53:43 -04:00

231 lines
11 KiB
JavaScript

import React, { useEffect, useRef, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Table, Form, Spinner } from 'react-bootstrap';
import { ChevronUp, ChevronDown } from 'lucide-react';
import {
toggleCreatorSelection,
selectAllCreators,
clearCreatorSelection,
fetchPrivateCreators,
} from '../store/slices/creatorsSlice';
import { setSortBy } from '../store/slices/filtersSlice';
import '../styles/DatabaseList.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link } from 'react-router-dom';
export default function PrivateCreatorList({ path }) {
const dispatch = useDispatch();
const { privateCreators, status, selectedCreators, hasMore, isLoadingMore, pagination } = useSelector(
(state) => state.creators
);
const { sortBy, sortDirection } = useSelector((state) => state.filters);
const observer = useRef();
const loadingRef = useCallback(
(node) => {
if (isLoadingMore) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
const nextPage = pagination?.current_page + 1;
dispatch(fetchPrivateCreators({ path, page: nextPage }));
}
});
if (node) observer.current.observe(node);
},
[isLoadingMore, hasMore, pagination, path, dispatch]
);
// 组件加载时获取创作者数据
useEffect(() => {
dispatch(fetchPrivateCreators({ path, page: 1 }));
}, [path,dispatch]);
useEffect(() => {
console.log(privateCreators);
}, [privateCreators]);
// 处理全选/取消全选
const handleSelectAll = (e) => {
if (e.target.checked) {
dispatch(selectAllCreators());
} else {
dispatch(clearCreatorSelection());
}
};
// 处理单个创作者选择
const handleSelectCreator = (creatorId) => {
dispatch(toggleCreatorSelection(creatorId));
};
// 处理排序
const handleSort = (field) => {
dispatch(setSortBy(field));
};
// 渲染排序图标
const renderSortIcon = (field) => {
if (sortBy === field) {
return sortDirection === 'asc' ? <ChevronUp size={14} /> : <ChevronDown size={14} />;
}
return null;
};
// 根据类别获取样式类名
const getCategoryClassName = (category) => {
const categoryMap = {
'Phones & Electronics': 'phones',
'Womenswear & Underwear': 'women',
'Sports & Outdoor': 'sports',
'Food & Beverage': 'food',
Health: 'health',
Kitchenware: 'kitchen',
};
return categoryMap[category] || '';
};
// 如果正在加载且没有数据,显示加载中
if (status === 'loading' && (!privateCreators || privateCreators.length === 0)) {
return (
<div className='text-center p-5'>
<Spinner animation='border' role='status' variant='primary'>
<span className='visually-hidden'>Loading...</span>
</Spinner>
</div>
);
}
// 如果加载失败,显示错误信息
if (status === 'failed') {
return <div className='alert alert-danger'>Failed to load creators. Please try again later.</div>;
}
return (
<div className='creator-database-table'>
<div className='table-container'>
<Table responsive hover className='bg-white shadow-xs rounded overflow-hidden'>
<thead className='sticky-header'>
<tr>
<th className='selector' style={{ width: '40px' }}>
<Form.Check
type='checkbox'
checked={selectedCreators.length === privateCreators.length && privateCreators.length > 0}
onChange={handleSelectAll}
/>
</th>
<th className='creator' onClick={() => handleSort('name')} style={{ width: '180px' }}>
Creator {renderSortIcon('name')}
</th>
<th
className='category text-center'
onClick={() => handleSort('category')}
style={{ width: '180px' }}
>
Category {renderSortIcon('category')}
</th>
<th className='e-commerce-level text-center' onClick={() => handleSort('e_commerce_level')}>
E-commerce Level {renderSortIcon('e_commerce_level')}
</th>
<th className='exposure-level text-center' onClick={() => handleSort('exposure_level')}>
Exposure Level {renderSortIcon('exposure_level')}
</th>
<th className='followers text-center' onClick={() => handleSort('followers')}>
Followers {renderSortIcon('followers')}
</th>
<th className='gmv text-center' onClick={() => handleSort('gmv')}>
GMV {renderSortIcon('gmv')}
</th>
<th className='views text-center' onClick={() => handleSort('avg_video_views')}>
Avg. Video Views {renderSortIcon('avg_video_views')}
</th>
<th className='pricing text-center'>Pricing</th>
<th className='collab-count text-center'># Collab</th>
<th className='latest-collab text-center'>Latest Collab.</th>
<th className='e-commerce text-center'>E-commerce</th>
<th className='profile text-center'>Profile</th>
</tr>
</thead>
<tbody>
{!privateCreators || privateCreators.length <= 0 ? (
<tr>
<td colSpan='13' className='text-center py-4'>
No creators found matching your filters.
</td>
</tr>
) : (
privateCreators.map((creator) => (
<tr
key={creator.creator_id}
className={selectedCreators.includes(creator.creator_id) ? 'selected' : ''}
>
<td>
<Form.Check
type='checkbox'
checked={selectedCreators.includes(creator.creator_id)}
onChange={() => handleSelectCreator(creator.creator_id)}
/>
</td>
<td className='creator-cell'>
<div className='d-flex align-items-center'>
<div className='creator-avatar'>
<img src={creator.avatar} alt={creator.name} />
{creator.status && <span className='verified-badge'></span>}
</div>
<Link to={`/creator/${creator.creator_id}`} className='creator-name'>
{creator.name}
</Link>
</div>
</td>
<td>
<span className={`category-pill ${getCategoryClassName(creator.category)}`}>
{creator.category}
</span>
</td>
<td className='text-center'>
<span className='level-badge ecommerce-level'>{creator.e_commerce_level}</span>
</td>
<td className='text-center'>
<span
className='level-badge exposure-level'
data-level={creator.exposure_level}
>
{creator.exposure_level}
</span>
</td>
<td className='text-nowrap text-center'>{creator.followers}</td>
<td className='text-center'>
<div>{creator.gmv}</div>
<div className='small text-muted'>Items Sold: {creator.soldPercentage}</div>
</td>
<td className='text-nowrap text-center'>{creator.avg_video_views}</td>
<td className='text-center'>{creator.pricing}</td>
<td className='text-center'>{creator.collab_count}</td>
<td className='text-center'>{creator.latestCollab}</td>
<td className='text-center'>
{creator.hasEcommerce ? <div className='colored-dot blue mx-auto'></div> : null}
</td>
<td className='text-center'>
{creator.hasTiktok && (
<div className='social-icon tiktok-icon mx-auto'>
<FontAwesomeIcon icon='fa-brands fa-tiktok' />
</div>
)}
</td>
</tr>
))
)}
</tbody>
</Table>
{hasMore && (
<div ref={loadingRef} className='text-center p-3'>
<Spinner animation='border' role='status' variant='primary' size='sm'>
<span className='visually-hidden'>Loading more...</span>
</Spinner>
</div>
)}
</div>
</div>
);
}