[dev]product basic info

This commit is contained in:
susie-laptop 2025-05-24 11:15:54 -04:00
parent 711c4652bb
commit 0fa465a2c6
10 changed files with 164 additions and 135 deletions

View File

@ -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>

View 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>
);
}

View File

@ -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>

View File

@ -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>
)
);

View File

@ -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'>

View File

@ -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' : ''}`}

View File

@ -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;

View File

@ -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%;

View File

@ -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;

View File

@ -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;
}
}
}
}
}