mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-07 22:58:14 +08:00
[dev]campaign tabs
This commit is contained in:
parent
3492e16c40
commit
45d7a7de1a
@ -114,40 +114,32 @@ export const CREATOR_LEVELS = [
|
||||
];
|
||||
|
||||
export const CREATOR_CATEGORIES = [
|
||||
{
|
||||
value: 'Phones & Electronics',
|
||||
name: 'Phones & Electronics',
|
||||
},
|
||||
{
|
||||
value: 'Womenswear & Underwear',
|
||||
name: 'Womenswear & Underwear',
|
||||
},
|
||||
{
|
||||
value: 'Sports & Outdoor',
|
||||
name: 'Sports & Outdoor',
|
||||
},
|
||||
{
|
||||
value: 'Food & Beverage',
|
||||
name: 'Food & Beverage',
|
||||
},
|
||||
{
|
||||
value: 'Health',
|
||||
name: 'Health',
|
||||
},
|
||||
{
|
||||
value: 'Kitchenware',
|
||||
name: 'Kitchenware',
|
||||
},
|
||||
{
|
||||
value: 'Household Appliances',
|
||||
name: 'Household Appliances',
|
||||
},
|
||||
{
|
||||
value: 'Womensware & Underwear',
|
||||
name: 'Womensware & Underwear',
|
||||
},
|
||||
{
|
||||
value: 'Other',
|
||||
name: 'Other',
|
||||
},
|
||||
{ value: 'Phones & Electronics', name: 'Phones & Electronics' },
|
||||
{ value: 'Homes Supplies', name: 'Homes Supplies' },
|
||||
{ value: 'Health', name: 'Health' },
|
||||
{ value: 'Textiles & Soft Furnishings', name: 'Textiles & Soft Furnishings' },
|
||||
{ value: 'Household Appliances', name: 'Household Appliances' },
|
||||
{ value: 'Womenswear & Underwear', name: 'Womenswear & Underwear' },
|
||||
{ value: 'Muslim Fashion', name: 'Muslim Fashion' },
|
||||
{ value: 'Shoes', name: 'Shoes' },
|
||||
{ value: 'Beauty & Personal Care', name: 'Beauty & Personal Care' },
|
||||
{ value: 'Pet Supplies', name: 'Pet Supplies' },
|
||||
{ value: 'Computers & Office Equipment', name: 'Computers & Office Equipment' },
|
||||
{ value: 'Baby & Maternity', name: 'Baby & Maternity' },
|
||||
{ value: 'Sports & Outdoor', name: 'Sports & Outdoor' },
|
||||
{ value: 'Toys', name: 'Toys' },
|
||||
{ value: 'Furniture', name: 'Furniture' },
|
||||
{ value: 'Kitchenware', name: 'Kitchenware' },
|
||||
{ value: 'Home Improvement', name: 'Home Improvement' },
|
||||
{ value: 'Tools & Hardware', name: 'Tools & Hardware' },
|
||||
{ value: "Kids' Fashion", name: "Kids' Fashion" },
|
||||
{ value: 'Automotive & Motorcycle', name: 'Automotive & Motorcycle' },
|
||||
{ value: 'Fashion Accessories', name: 'Fashion Accessories' },
|
||||
{ value: 'Food & Beverages', name: 'Food & Beverages' },
|
||||
{ value: 'Books, Magazines & Audio', name: 'Books, Magazines & Audio' },
|
||||
{ value: 'Menswear & Underwear', name: 'Menswear & Underwear' },
|
||||
{ value: 'Pre-Owned Collections', name: 'Pre-Owned Collections' },
|
||||
{ value: 'Luggage & Bags', name: 'Luggage & Bags' },
|
||||
{ value: 'Jewellery Accessories & Derivatives', name: 'Jewellery Accessories & Derivatives' },
|
||||
{ value: 'Other', name: 'Other' },
|
||||
];
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import SearchBar from '../components/SearchBar';
|
||||
import { Button, Form, Modal } from 'react-bootstrap';
|
||||
import { Button, Col, Form, Modal, Row, Spinner, Table } from 'react-bootstrap';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { fetchBrands, fetchBrandDetail, fetchCampaignDetail, setSelectedProduct } from '../store/slices/brandsSlice';
|
||||
import CampaignInfo from '../components/CampaignInfo';
|
||||
@ -134,7 +134,10 @@ export default function CampaignDetail() {
|
||||
<Plus size={18} />
|
||||
Add Product
|
||||
</Button>
|
||||
<ProductsList products={selectedCampaign?.link_product_details} onShowProductDetail={handleShowProductDetail} />
|
||||
<ProductsList
|
||||
products={selectedCampaign?.link_product_details}
|
||||
onShowProductDetail={handleShowProductDetail}
|
||||
/>
|
||||
<SlidePanel
|
||||
show={showProductDetail}
|
||||
onClose={() => setShowProductDetail(false)}
|
||||
@ -143,9 +146,15 @@ export default function CampaignDetail() {
|
||||
>
|
||||
<CampaignScript />
|
||||
</SlidePanel>
|
||||
<AddProductModal campaignId={campaignId} show={showAddProductModal} onHide={() => setShowAddProductModal(false)} />
|
||||
<AddProductModal
|
||||
campaignId={campaignId}
|
||||
show={showAddProductModal}
|
||||
onHide={() => setShowAddProductModal(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{activeTab === 'accepted_creators' && <AcceptedCreators />}
|
||||
{activeTab === 'matching_result' && <MatchingResult />}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
@ -184,7 +193,8 @@ function AddProductModal({ campaignId, show, onHide }) {
|
||||
onChange={(e) => setSelectedProduct(e.target.value)}
|
||||
>
|
||||
<option>Select</option>
|
||||
{products?.length > 0 && products.map((product) => (
|
||||
{products?.length > 0 &&
|
||||
products.map((product) => (
|
||||
<option key={product.id} value={product.id}>
|
||||
{product.name}
|
||||
</option>
|
||||
@ -204,3 +214,320 @@ function AddProductModal({ campaignId, show, onHide }) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function AcceptedCreators() {
|
||||
const { selectedCampaign } = useSelector((state) => state.brands);
|
||||
|
||||
const handleSort = (field) => {
|
||||
return;
|
||||
console.log(field);
|
||||
};
|
||||
|
||||
const renderSortIcon = (field) => {
|
||||
return;
|
||||
return <ChevronDown size={18} />;
|
||||
};
|
||||
|
||||
const renderStatusPill = (status) => {
|
||||
switch (status) {
|
||||
case 'unconnected':
|
||||
return <span className='status-pill'>Unconnected</span>;
|
||||
case 'connected':
|
||||
return <span className='status-pill'>Connected</span>;
|
||||
case 'pending':
|
||||
return <span className='status-pill'>Pending</span>;
|
||||
case 'rejected':
|
||||
return <span className='status-pill'>Rejected</span>;
|
||||
case 'accepted':
|
||||
return <span className='status-pill'>Accepted</span>;
|
||||
case 'provided':
|
||||
return <span className='status-pill'>Provided</span>;
|
||||
case 'completed':
|
||||
return <span className='status-pill'>Completed</span>;
|
||||
case 'draft':
|
||||
return <span className='status-pill'>Draft</span>;
|
||||
case 'published':
|
||||
return <span className='status-pill'>Published</span>;
|
||||
default:
|
||||
return <span className='status-pill'>Unknown</span>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='accepted-creators-table w-100'>
|
||||
<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={
|
||||
selectedCampaign?.creators?.length === publicCreators.length && publicCreators.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='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='status text-center'>Status</th>
|
||||
<th className='pricing text-center'>Pricing</th>
|
||||
<th className='collaboration text-center'>Collaboration</th>
|
||||
<th className='profile text-center'>Profile</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!selectedCampaign?.creators || selectedCampaign?.creators?.length <= 0 ? (
|
||||
<>
|
||||
<tr>
|
||||
<td colSpan='9' className='text-center py-4'>
|
||||
No creators found.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={9}>
|
||||
<StepProgress />
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
) : (
|
||||
selectedCampaign?.creators?.map((creator) => (
|
||||
<tr
|
||||
key={creator.creator_id}
|
||||
className={
|
||||
selectedCampaign?.creators?.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-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'>
|
||||
<div className='status-pill'>{renderStatusPill(creator.status)}</div>
|
||||
</td>
|
||||
<td className='text-center'>{creator.pricing}</td>
|
||||
<td className='text-center'>{creator.collaboration}</td>
|
||||
<td className='text-center'>{creator.profile}</td>
|
||||
<td colSpan={8}>
|
||||
<StepProgress dates={creator.dates} />
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StepProgress({ dates = [] }) {
|
||||
const labels = [
|
||||
'Brand Review',
|
||||
'Price Negotiation',
|
||||
'Creator Confirmation',
|
||||
'Draft Ready',
|
||||
'Draft Approved',
|
||||
'Published',
|
||||
];
|
||||
const steps = [
|
||||
{ label: 'Brand Review', date: '12/30/2020' },
|
||||
{ label: 'Price Negotiation', date: '12/30/2020' },
|
||||
{ label: 'Creator Confirmation', date: '12/30/2020' },
|
||||
{ label: 'Draft Ready', date: '12/30/2020' },
|
||||
{ label: 'Draft Approved', date: '12/30/2020' },
|
||||
{ label: 'Published', date: '12/30/2020' },
|
||||
];
|
||||
return (
|
||||
<div className='inline-step-progress'>
|
||||
<div className='inline-step'></div>
|
||||
{steps.map((step, index) => {
|
||||
const isCompleted = !!dates[index];
|
||||
return (
|
||||
<div className='inline-step' key={index}>
|
||||
<div className={`circle ${isCompleted ? 'completed' : 'pending'}`}>
|
||||
{isCompleted ? '✔' : ''}1
|
||||
</div>
|
||||
{index !== steps.length - 1 && <div className='line' />}
|
||||
<div className='label'>
|
||||
<span className='label-text'>{step.label}</span>
|
||||
<br />
|
||||
<span className='label-date'>{step.date || 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MatchingResult() {
|
||||
const [showMatchingResultModal, setShowMatchingResultModal] = useState(false);
|
||||
|
||||
const mockData = [
|
||||
{
|
||||
operation: 'Operation 1',
|
||||
creator_find: 100,
|
||||
creator_accepted: 100,
|
||||
avg_creator_pricing: 100,
|
||||
date: '2020-01-01',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className='matching-result-table w-100'>
|
||||
<div className='table-container'>
|
||||
<Table responsive hover className='bg-white shadow-xs rounded overflow-hidden'>
|
||||
<thead className='sticky-header'>
|
||||
<tr>
|
||||
<th># Operation</th>
|
||||
<th># Creator Find</th>
|
||||
<th># Creator Accepted</th>
|
||||
<th>Avg. Creator Pricing</th>
|
||||
<th>Date</th>
|
||||
<th>View Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mockData.length > 0 ? (
|
||||
mockData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>{item.operation}</td>
|
||||
<td>{item.creator_find}</td>
|
||||
<td>{item.creator_accepted}</td>
|
||||
<td>{item.avg_creator_pricing}</td>
|
||||
<td>{item.date}</td>
|
||||
<td>
|
||||
<Button
|
||||
variant='outline-primary'
|
||||
className='border-0'
|
||||
onClick={() => setShowMatchingResultModal(true)}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={6} className='text-center'>
|
||||
No data
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<CampaignMatchingResult
|
||||
show={showMatchingResultModal}
|
||||
onHide={() => setShowMatchingResultModal(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CampaignMatchingResult({ show, onHide }) {
|
||||
const mockData = [
|
||||
{
|
||||
creator: 'Creator 1',
|
||||
category: 'Category 1',
|
||||
followers: 100,
|
||||
gmv: 100,
|
||||
avg_video_views: 100,
|
||||
status: 'Status 1',
|
||||
pricing: 100,
|
||||
profile: 'Profile 1',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal show={show} onHide={onHide} size='lg'>
|
||||
<Modal.Header closeButton className='fw-bold'>
|
||||
Campaign Matching Result
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className='matching-result-table'>
|
||||
<div className='table-container'>
|
||||
<Table responsive hover className='bg-white shadow-xs rounded overflow-hidden'>
|
||||
<thead className='sticky-header'>
|
||||
<tr>
|
||||
<th>Creator</th>
|
||||
<th>Category</th>
|
||||
<th>Followers</th>
|
||||
<th>GMV</th>
|
||||
<th>Avg. Video Views</th>
|
||||
<th>Status</th>
|
||||
<th>Pricing</th>
|
||||
<th>Profile</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mockData.length > 0 ? (
|
||||
mockData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>{item.creator}</td>
|
||||
<td>{item.category}</td>
|
||||
<td>{item.followers}</td>
|
||||
<td>{item.gmv}</td>
|
||||
<td>{item.avg_video_views}</td>
|
||||
<td>{item.status}</td>
|
||||
<td>{item.pricing}</td>
|
||||
<td>{item.profile}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={6} className='text-center'>
|
||||
No data
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -47,3 +47,67 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inline-step-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0 65px 0;
|
||||
|
||||
.inline-step {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.completed {
|
||||
background-color: #6f6ee3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.pending {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: #ccc;
|
||||
z-index: 0;
|
||||
}
|
||||
.label {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
text-align: center;
|
||||
left: -45%;
|
||||
width: 100%;
|
||||
.label-text {
|
||||
display: inline-block;
|
||||
}
|
||||
.label-date {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.matching-result-table {
|
||||
th, td {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user