mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-07 22:58:14 +08:00
[dev]product basic info
This commit is contained in:
parent
711c4652bb
commit
0fa465a2c6
@ -25,7 +25,7 @@ export default function CampaignList() {
|
||||
<div className='campaigns-list'>
|
||||
{selectedBrand?.campaigns?.length > 0 &&
|
||||
selectedBrand.campaigns.map((campaign) => (
|
||||
<div className='campaign-info'>
|
||||
<div className='campaign-info' key={campaign.id}>
|
||||
<Link to={`/brands/${selectedBrand.id}/campaigns/${campaign.id}`} className='campaign-title'>
|
||||
{campaign.name}
|
||||
</Link>
|
||||
|
55
src/components/ProductDetail.jsx
Normal file
55
src/components/ProductDetail.jsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export default function ProductDetail() {
|
||||
const selectedProduct = useSelector((state) => state.brands.selectedProduct);
|
||||
|
||||
return (
|
||||
<div className='product-details shadow-xs'>
|
||||
<div className='product-details-header'>
|
||||
<div className='product-details-header-title'>{selectedProduct.name}</div>
|
||||
<div className='product-details-header-pid'>PID: {selectedProduct.id}</div>
|
||||
</div>
|
||||
<div className='product-details-body'>
|
||||
<div className='product-img'></div>
|
||||
<div className='product-detail-list'>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.commission_rate}</div>
|
||||
<div className='product-detail-item-label'>Commission Rate</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.available_samples}</div>
|
||||
<div className='product-detail-item-label'>Available Samples</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.sales_price_max} - {selectedProduct.sales_price_min}</div>
|
||||
<div className='product-detail-item-label'>Sales Price</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.stock}</div>
|
||||
<div className='product-detail-item-label'>Stock</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.items_sold}</div>
|
||||
<div className='product-detail-item-label'>Items Sold</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.product_rating}</div>
|
||||
<div className='product-detail-item-label'>Product Rating</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.collab_creators}</div>
|
||||
<div className='product-detail-item-label'>Collab Creators</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.gmv}</div>
|
||||
<div className='product-detail-item-label'>GMV Achieved</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.reviews_count}</div>
|
||||
<div className='product-detail-item-label'>Views Achieved</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -16,6 +16,7 @@ export default function ProductsList({ onShowProductDetail }) {
|
||||
|
||||
|
||||
const handleSort = (field) => {
|
||||
return;
|
||||
if (sortField === field) {
|
||||
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||
} else {
|
||||
@ -25,6 +26,7 @@ export default function ProductsList({ onShowProductDetail }) {
|
||||
};
|
||||
|
||||
const renderSortIcon = (field) => {
|
||||
return;
|
||||
if (sortField !== field) return null;
|
||||
return sortDirection === 'asc' ? '↑' : '↓';
|
||||
};
|
||||
@ -109,7 +111,7 @@ export default function ProductsList({ onShowProductDetail }) {
|
||||
/>
|
||||
</td>
|
||||
<td className='product-cell'>
|
||||
<div className='d-flex align-items-center' onClick={() => onShowProductDetail(product.id)} style={{cursor: 'pointer'}}>
|
||||
<div className='d-flex align-items-center' onClick={() => onShowProductDetail(product)} style={{cursor: 'pointer'}}>
|
||||
<div className='product-logo'>{product.name.slice(0, 1)}</div>
|
||||
<div className='product-name'>{product.name}</div>
|
||||
</div>
|
||||
|
@ -6,13 +6,16 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import CampaignList from '../components/CampaignList';
|
||||
import ProductsList from '../components/ProductsList';
|
||||
import { fetchBrandDetail, fetchBrandCampaigns, fetchBrandProducts } from '../store/slices/brandsSlice';
|
||||
import { fetchBrandDetail, fetchBrandCampaigns, fetchBrandProducts, setSelectedProduct } from '../store/slices/brandsSlice';
|
||||
import SlidePanel from '../components/SlidePanel';
|
||||
import ProductDetail from '../components/ProductDetail';
|
||||
|
||||
export default function BrandsDetail() {
|
||||
const { id } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const [activeTab, setActiveTab] = useState('campaigns');
|
||||
const { selectedBrand } = useSelector((state) => state.brands);
|
||||
const [showProductDetail, setShowProductDetail] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@ -22,6 +25,12 @@ export default function BrandsDetail() {
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
|
||||
const handleShowProductDetail = (product) => {
|
||||
dispatch(setSelectedProduct(product));
|
||||
setShowProductDetail(true);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
selectedBrand?.id && (
|
||||
<React.Fragment>
|
||||
@ -114,7 +123,19 @@ export default function BrandsDetail() {
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === 'campaigns' && <CampaignList />}
|
||||
{activeTab === 'products' && <ProductsList onShowProductDetail={() => {}} />}
|
||||
{activeTab === 'products' && (
|
||||
<>
|
||||
<ProductsList onShowProductDetail={handleShowProductDetail} />
|
||||
<SlidePanel
|
||||
show={showProductDetail}
|
||||
onClose={() => setShowProductDetail(false)}
|
||||
title='Product Detail'
|
||||
size='xxl'
|
||||
>
|
||||
<ProductDetail />
|
||||
</SlidePanel>
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import { Link, useParams } from 'react-router-dom';
|
||||
import SearchBar from '../components/SearchBar';
|
||||
import { Button, Form, Modal } from 'react-bootstrap';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { fetchBrands, fetchBrandDetail, findProductById, fetchCampaignDetail } from '../store/slices/brandsSlice';
|
||||
import { fetchBrands, fetchBrandDetail, fetchCampaignDetail, setSelectedProduct } from '../store/slices/brandsSlice';
|
||||
import CampaignInfo from '../components/CampaignInfo';
|
||||
import { ChevronRight, Send, Plus } from 'lucide-react';
|
||||
import ProductsList from '../components/ProductsList';
|
||||
@ -20,36 +20,25 @@ export default function CampaignDetail() {
|
||||
const [showProductDetail, setShowProductDetail] = useState(false);
|
||||
const [showAddProductModal, setShowAddProductModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchBrands());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (brandId && campaignId) {
|
||||
if (!selectedBrand) {
|
||||
if (!selectedBrand?.id) {
|
||||
dispatch(fetchBrandDetail(brandId));
|
||||
dispatch(fetchCampaignDetail(campaignId));
|
||||
} else {
|
||||
dispatch(fetchCampaignDetail(campaignId));
|
||||
}
|
||||
}
|
||||
}, [brandId, campaignId]);
|
||||
}, [dispatch, brandId, campaignId]);
|
||||
|
||||
const handleShowProductDetail = (productId) => {
|
||||
dispatch(findProductById({ brandId, campaignId, productId }));
|
||||
const handleShowProductDetail = (product) => {
|
||||
dispatch(setSelectedProduct(product));
|
||||
setShowProductDetail(true);
|
||||
};
|
||||
|
||||
return (
|
||||
selectedCampaign?.id && (
|
||||
<div className='campaign-detail'>
|
||||
<div className='function-bar'>
|
||||
<SearchBar />
|
||||
<Button>
|
||||
<Send size={18} />
|
||||
Email
|
||||
</Button>
|
||||
</div>
|
||||
<div className='breadcrumb'>
|
||||
<Link to={'/brands'} className='breadcrumb-item'>
|
||||
Brands
|
||||
@ -59,6 +48,13 @@ export default function CampaignDetail() {
|
||||
</Link>
|
||||
<div className='breadcrumb-item'>{selectedCampaign.name}</div>
|
||||
</div>
|
||||
<div className='function-bar'>
|
||||
<SearchBar />
|
||||
<Button>
|
||||
<Send size={18} />
|
||||
Email
|
||||
</Button>
|
||||
</div>
|
||||
<CampaignInfo />
|
||||
<Form className='campaign-requirements shadow-xs'>
|
||||
<Form.Group className='mb-3 additional_requirements' controlId='additional_requirements'>
|
||||
|
@ -4,6 +4,7 @@ import { Accordion, Button, Card, Col, Form, Row, Table } from 'react-bootstrap'
|
||||
import { CloudUpload, Paperclip } from 'lucide-react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { mockCreators } from '../store/slices/creatorsSlice';
|
||||
import ProductDetail from '../components/ProductDetail';
|
||||
|
||||
export default function CampaignScript() {
|
||||
const dispatch = useDispatch();
|
||||
@ -17,53 +18,7 @@ export default function CampaignScript() {
|
||||
return (
|
||||
selectedProduct?.id && (
|
||||
<div className='product-script'>
|
||||
<div className='product-details shadow-xs'>
|
||||
<div className='product-details-header'>
|
||||
<div className='product-details-header-title'>{selectedProduct.name}</div>
|
||||
<div className='product-details-header-pid'>PID: {selectedProduct.id}</div>
|
||||
</div>
|
||||
<div className='product-details-body'>
|
||||
<div className='product-img'></div>
|
||||
<div className='product-detail-list'>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.commission}</div>
|
||||
<div className='product-detail-item-label'>Commission Rate</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.availableSamples}</div>
|
||||
<div className='product-detail-item-label'>Available Samples</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.price}</div>
|
||||
<div className='product-detail-item-label'>Sales Price</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.stock}</div>
|
||||
<div className='product-detail-item-label'>Stock</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.sold}</div>
|
||||
<div className='product-detail-item-label'>Items Sold</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.rating}</div>
|
||||
<div className='product-detail-item-label'>Product Rating</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.collabCreators}</div>
|
||||
<div className='product-detail-item-label'>Collab Creators</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.gmv}</div>
|
||||
<div className='product-detail-item-label'>GMV Achieved</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.reviews}</div>
|
||||
<div className='product-detail-item-label'>Views Achieved</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ProductDetail />
|
||||
<div className='product-script-switches tab-switches'>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'collaborationInfo' ? 'active' : ''}`}
|
||||
|
@ -218,13 +218,8 @@ const brandsSlice = createSlice({
|
||||
state.selectedBrand = brand;
|
||||
state.selectedCampaign = brand?.campaigns?.find((c) => c.id.toString() === campaignId) || {};
|
||||
},
|
||||
findProductById: (state, action) => {
|
||||
const { brandId, campaignId, productId } = action.payload;
|
||||
const brand = state.brands?.find((b) => b.id.toString() === brandId);
|
||||
const campaign = brand?.campaigns?.find((c) => c.id.toString() === campaignId);
|
||||
const product = campaign?.products?.find((p) => p.id.toString() === productId.toString());
|
||||
console.log(brand, campaign, product);
|
||||
state.selectedProduct = product;
|
||||
setSelectedProduct: (state, action) => {
|
||||
state.selectedProduct = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
@ -308,6 +303,6 @@ const brandsSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { selectBrand, findBrandById, findCampaignById, findProductById } = brandsSlice.actions;
|
||||
export const { selectBrand, findBrandById, findCampaignById, setSelectedProduct } = brandsSlice.actions;
|
||||
|
||||
export default brandsSlice.reducer;
|
||||
|
@ -267,6 +267,9 @@
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.campaign-detail-info {
|
||||
width: 100%;
|
||||
|
@ -7,65 +7,6 @@
|
||||
flex-flow: column nowrap;
|
||||
gap: 1rem;
|
||||
|
||||
.product-details {
|
||||
background-color: #fff;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
.product-details-header {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: flex-start;
|
||||
.product-details-header-title {
|
||||
font-size: 1rem;
|
||||
color: $primary;
|
||||
font-weight: 800;
|
||||
}
|
||||
.product-details-header-pid {
|
||||
font-size: 0.75rem;
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
.product-details-body {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.product-img {
|
||||
flex-shrink: 0;
|
||||
width: 16rem;
|
||||
padding-right: 1rem;
|
||||
height: 15rem;
|
||||
background-color: $neutral-200;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.product-detail-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
|
||||
.product-detail-item {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
background-color: $neutral-150;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.75rem 0;
|
||||
width: 10rem;
|
||||
|
||||
.product-detail-item-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.product-detail-item-label {
|
||||
font-size: 0.875rem;
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.product-script-video-req {
|
||||
.video-req-form {
|
||||
display: flex;
|
||||
|
@ -17,3 +17,64 @@
|
||||
margin-right: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.product-details {
|
||||
background-color: #fff;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
.product-details-header {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: flex-start;
|
||||
.product-details-header-title {
|
||||
font-size: 1rem;
|
||||
color: $primary;
|
||||
font-weight: 800;
|
||||
}
|
||||
.product-details-header-pid {
|
||||
font-size: 0.75rem;
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
.product-details-body {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.product-img {
|
||||
flex-shrink: 0;
|
||||
width: 16rem;
|
||||
padding-right: 1rem;
|
||||
height: 15rem;
|
||||
background-color: $neutral-200;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.product-detail-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
|
||||
.product-detail-item {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
background-color: $neutral-150;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.75rem 0;
|
||||
width: 10rem;
|
||||
|
||||
.product-detail-item-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.product-detail-item-label {
|
||||
font-size: 0.875rem;
|
||||
color: $neutral-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user