Milestone 2: Data is showing, filters are working, search is still broken, links are clickable
parent
e5d5e6e978
commit
a1b940f932
|
|
@ -0,0 +1,17 @@
|
||||||
|
Vendor,Vendor rating,Type,Compound,Dose,Unit,Format,Quantity,Price,Ships from Country,Shipping $,MOQ,Analysis,Purity guarantee,Mass guarantee,Re-ship guarantee,Start,Close,PepChat Link,Discord Link,Telegram Link,Notes
|
||||||
|
SRY,A,promo,Tirzepatide,15,mg,3mL vial,10, $ 125 ,China,included,2,2nd party,>99%,overfill,yes,2024-09-14,While stocks last,https://peptide.chat/invite/srylab,https://discord.gg/yafWDPuC,,
|
||||||
|
QSC,AA,promo,Retatrutide,10,mg,3mL vial,10, $ 180 ,USA,included,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||||
|
QSC,AA,promo,Retatrutide,15,mg,3mL vial,10, $ 270 ,USA,included,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||||
|
QSC,AA,promo,Retatrutide,20,mg,3mL vial,10, $ 360 ,USA,included,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||||
|
QSC,AA,promo,Retatrutide,10,mg,3mL vial,10, $ 180 ,China,excluded,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||||
|
QSC,AA,promo,Retatrutide,15,mg,3mL vial,10, $ 270 ,China,excluded,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||||
|
QSC,AA,promo,Retatrutide,20,mg,3mL vial,10, $ 360 ,China,excluded,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-15,https://peptide.chat/invite/qsc,,,
|
||||||
|
ASC,A,promo,Tirzepatide,60,mg,3mL vial,10, $ 470 ,China, $ 40 ,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-21,https://peptide.chat/invite/ARMm07FG,https://discord.gg/Yzaw4yju,https://t.me/peptidechina,
|
||||||
|
ASC,A,promo,Retatrutide,12,mg,3mL vial,10, $ 240 ,China, $ 40 ,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-21,https://peptide.chat/invite/ARMm07FG,https://discord.gg/Yzaw4yju,https://t.me/peptidechina,
|
||||||
|
ASC,A,promo,GHK-Cu,50,mg,3mL vial,10, $ 45 ,China, $ 40 ,1,2nd party,>99%,overfill,yes,2024-09-14,2024-09-21,https://peptide.chat/invite/ARMm07FG,https://discord.gg/Yzaw4yju,https://t.me/peptidechina,
|
||||||
|
JL,AAA,GB,Melanotan 1,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||||
|
JL,AAA,GB,Melanotan 2,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||||
|
JL,AAA,GB,Thymulin,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||||
|
JL,AAA,GB,PT-141,10,mg,3mL vial,10, $ 75 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||||
|
JL,AAA,GB,Sermorelin,10,mg,3mL vial,10, $ 120 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||||
|
JL,AAA,GB,Survodutide,10,mg,3mL vial,10, $ 190 ,China,included,1,3rd party,>99%,overfill,yes,2024-09-14,2024-09-20,invite only,invite only,,
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
id,name,description,compound,dose,measurement,format,quantity,priceUSD,shipping,testing,guarantees,vendorRating
|
|
||||||
1,Compound Vendor A,High-quality chemical compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
|
||||||
2,Compound Vendor B,Specialized in organic compounds,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
|
||||||
3,Compound Vendor C,Leading supplier of rare compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.2
|
|
||||||
4,Compound Vendor D,Trusted by researchers globally,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
|
||||||
5,Compound Vendor E,Innovative solutions for compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.3
|
|
||||||
6,Compound Vendor F,Quality compounds at competitive prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
|
||||||
7,Compound Vendor G,Your partner in chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
|
||||||
8,Compound Vendor H,Excellence in compound distribution,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.2
|
|
||||||
9,Compound Vendor I,Global leader in compound supply,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.6
|
|
||||||
10,Compound Vendor J,Trusted by top laboratories,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
|
||||||
11,Compound Vendor K,Innovative compound solutions,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
|
||||||
12,Compound Vendor L,Quality compounds for research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
|
||||||
13,Compound Vendor M,Leading supplier of chemical compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
|
||||||
14,Compound Vendor N,Trusted by researchers worldwide,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
|
||||||
15,Compound Vendor O,Innovative solutions for chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
|
||||||
16,Compound Vendor P,Quality compounds at affordable prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
|
||||||
17,Compound Vendor Q,Your partner in compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.3
|
|
||||||
18,Compound Vendor R,Excellence in compound supply,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
|
||||||
19,Compound Vendor S,Global leader in chemical distribution,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.5
|
|
||||||
20,Compound Vendor T,Trusted by top research labs,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.2
|
|
||||||
21,Compound Vendor U,Innovative compound solutions for research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.4
|
|
||||||
22,Compound Vendor V,Quality compounds for scientific research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
|
||||||
23,Compound Vendor W,Leading supplier of research compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
|
||||||
24,Compound Vendor X,Trusted by researchers globally,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
|
||||||
25,Compound Vendor Y,Innovative solutions for compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
|
||||||
26,Compound Vendor Z,Quality compounds at competitive prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
|
||||||
27,Compound Vendor AA,Your partner in chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
|
||||||
28,Compound Vendor AB,Excellence in compound distribution,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
|
||||||
29,Compound Vendor AC,Global leader in compound supply,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.6
|
|
||||||
30,Compound Vendor AD,Trusted by top laboratories,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
|
||||||
31,Compound Vendor AE,Innovative compound solutions,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
|
||||||
32,Compound Vendor AF,Quality compounds for research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
|
||||||
33,Compound Vendor AG,Leading supplier of chemical compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
|
||||||
34,Compound Vendor AH,Trusted by researchers worldwide,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
|
||||||
35,Compound Vendor AI,Innovative solutions for chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
|
||||||
36,Compound Vendor AJ,Quality compounds at affordable prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
|
||||||
37,Compound Vendor AK,Your partner in compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.3
|
|
||||||
38,Compound Vendor AL,Excellence in compound supply,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.0
|
|
||||||
39,Compound Vendor AM,Global leader in chemical distribution,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.5
|
|
||||||
40,Compound Vendor AN,Trusted by top research labs,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.2
|
|
||||||
41,Compound Vendor AO,Innovative compound solutions for research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.4
|
|
||||||
42,Compound Vendor AP,Quality compounds for scientific research,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
|
||||||
43,Compound Vendor AQ,Leading supplier of research compounds,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.3
|
|
||||||
44,Compound Vendor AR,Trusted by researchers globally,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.0
|
|
||||||
45,Compound Vendor AS,Innovative solutions for compound research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.5
|
|
||||||
46,Compound Vendor AT,Quality compounds at competitive prices,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.2
|
|
||||||
47,Compound Vendor AU,Your partner in chemical research,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity",4.4
|
|
||||||
48,Compound Vendor AV,Excellence in compound distribution,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity, Reshipping",4.1
|
|
||||||
49,Compound Vendor AW,Global leader in compound supply,Semaglutide,10,mg,vial,10,95,Worldwide,"Purity, Stability","Purity, Quantity, Reshipping",4.6
|
|
||||||
50,Compound Vendor AX,Trusted by top laboratories,Semaglutide,10,mg,vial,10,95,Domestic,"Purity","Purity",4.1
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* CompoundBay.module.scss */
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchBarContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchBar {
|
||||||
|
width: 300px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchButton {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles for FilterSidebar
|
||||||
|
.filterSidebar {
|
||||||
|
width: 250px;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Styles for ResultsSidebar
|
||||||
|
.results {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
@ -1,200 +1,227 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Papa from "papaparse";
|
import Papa from "papaparse";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import styled from "styled-components/macro";
|
import { FaSearch } from "react-icons/fa";
|
||||||
|
|
||||||
const Container = styled.div`
|
import styles from "./CompoundBay.module.scss";
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Banner = styled.h1`
|
import { GroupBuySale } from "../../types/groupBuySale";
|
||||||
font-size: 2.5em;
|
import FilterSidebar from "./FilterSidebar";
|
||||||
margin-bottom: 20px;
|
import ResultsSidebar from "./ResultsSidebar";
|
||||||
color: #1a237e;
|
|
||||||
font-weight: 500;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchBar = styled.input`
|
|
||||||
width: 50%;
|
|
||||||
padding: 12px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border: 1px solid #bdbdbd;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
font-size: 1em;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Content = styled.div`
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1200px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Filters = styled.div`
|
|
||||||
flex: 1;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-right: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Results = styled.div`
|
|
||||||
flex: 3;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow-y: auto;
|
|
||||||
height: 600px; /* Set a fixed height for scrolling */
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FilterTitle = styled.h3`
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #1a237e;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: 500;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FilterOption = styled.div`
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: #424242;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1em;
|
|
||||||
&:hover {
|
|
||||||
color: #1a237e;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ResultCard = styled.div`
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
background-color: #ffffff;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const VendorName = styled.h3`
|
|
||||||
color: #1a237e;
|
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const VendorDescription = styled.p`
|
|
||||||
color: #616161;
|
|
||||||
font-size: 1em;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const VendorDetail = styled.p`
|
|
||||||
color: #424242;
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CompoundBay = observer(() => {
|
const CompoundBay = observer(() => {
|
||||||
|
const [groupBuySales, setGroupBuySales] = useState<GroupBuySale[]>([]);
|
||||||
|
const [visibleGroupBuySales, setVisibleGroupBuySales] = useState<
|
||||||
|
GroupBuySale[]
|
||||||
|
>([]);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [vendors, setVendors] = useState([]);
|
|
||||||
const [visibleVendors, setVisibleVendors] = useState([]);
|
|
||||||
const [loadCount, setLoadCount] = useState(10);
|
const [loadCount, setLoadCount] = useState(10);
|
||||||
|
const [expandedSales, setExpandedSales] = useState<Set<string>>(new Set());
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Filter states
|
||||||
|
const [vendorRating, setVendorRating] = useState("");
|
||||||
|
const [vendor, setVendor] = useState("");
|
||||||
|
const [shipsFromCountry, setShipsFromCountry] = useState("");
|
||||||
|
const [compound, setCompound] = useState("");
|
||||||
|
|
||||||
|
// Add new state for dropdown options
|
||||||
|
const [vendorOptions, setVendorOptions] = useState<string[]>([]);
|
||||||
|
const [countryOptions, setCountryOptions] = useState<string[]>([]);
|
||||||
|
const [compoundOptions, setCompoundOptions] = useState<string[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load and parse the CSV file
|
const fetchGroupBuySales = async () => {
|
||||||
Papa.parse("/src/data/vendors.csv", {
|
setIsLoading(true);
|
||||||
download: true,
|
try {
|
||||||
header: true,
|
const response = await fetch("/data/group_buy_sales.csv");
|
||||||
complete: (results) => {
|
const csvText = await response.text();
|
||||||
setVendors(results.data);
|
Papa.parse(csvText, {
|
||||||
setVisibleVendors(results.data.slice(0, loadCount));
|
header: true,
|
||||||
},
|
complete: (results) => {
|
||||||
});
|
const parsedSales = results.data.map((row: any) => ({
|
||||||
|
vendor: row.Vendor,
|
||||||
|
vendorRating: row["Vendor rating"],
|
||||||
|
type: row.Type,
|
||||||
|
compound: row.Compound,
|
||||||
|
dose: row.Dose,
|
||||||
|
unit: row.Unit,
|
||||||
|
format: row.Format,
|
||||||
|
quantity: row.Quantity,
|
||||||
|
price: row.Price,
|
||||||
|
shipsFromCountry: row["Ships from Country"],
|
||||||
|
shippingCost: row["Shipping $"],
|
||||||
|
moq: row.MOQ,
|
||||||
|
analysis: row.Analysis,
|
||||||
|
purityGuarantee: row["Purity guarantee"],
|
||||||
|
massGuarantee: row["Mass guarantee"],
|
||||||
|
reshipGuarantee: row["Re-ship guarantee"],
|
||||||
|
start: row.Start,
|
||||||
|
close: row.Close,
|
||||||
|
pepChatLink: row["PepChat Link"],
|
||||||
|
discordLink: row["Discord Link"],
|
||||||
|
telegramLink: row["Telegram Link"],
|
||||||
|
notes: row.Notes,
|
||||||
|
})) as GroupBuySale[];
|
||||||
|
|
||||||
|
console.log("Parsed sales:", parsedSales); // Debug log
|
||||||
|
|
||||||
|
// Check for missing properties
|
||||||
|
const validSales = parsedSales.filter((sale) => {
|
||||||
|
if (
|
||||||
|
!sale.vendor ||
|
||||||
|
!sale.compound ||
|
||||||
|
!sale.shipsFromCountry
|
||||||
|
) {
|
||||||
|
console.warn("Invalid sale object:", sale);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
setGroupBuySales(validSales);
|
||||||
|
setVisibleGroupBuySales(validSales.slice(0, loadCount));
|
||||||
|
|
||||||
|
// Extract unique options for dropdowns
|
||||||
|
const vendors = [
|
||||||
|
...new Set(validSales.map((sale) => sale.vendor)),
|
||||||
|
];
|
||||||
|
const countries = [
|
||||||
|
...new Set(
|
||||||
|
validSales.map((sale) => sale.shipsFromCountry),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const compounds = [
|
||||||
|
...new Set(validSales.map((sale) => sale.compound)),
|
||||||
|
];
|
||||||
|
|
||||||
|
setVendorOptions(vendors);
|
||||||
|
setCountryOptions(countries);
|
||||||
|
setCompoundOptions(compounds);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error("Error parsing CSV:", err);
|
||||||
|
setError("Failed to load group buy sales data.");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching CSV:", err);
|
||||||
|
setError("Failed to fetch group buy sales data.");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchGroupBuySales();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadMoreVendors = () => {
|
useEffect(() => {
|
||||||
setLoadCount((prevCount) => prevCount + 10);
|
const filteredSales = groupBuySales.filter((sale) => {
|
||||||
setVisibleVendors(vendors.slice(0, loadCount + 10));
|
return (
|
||||||
|
(vendorRating === "" || sale.vendorRating === vendorRating) &&
|
||||||
|
(vendor === "" ||
|
||||||
|
sale.vendor.toLowerCase().includes(vendor.toLowerCase())) &&
|
||||||
|
(shipsFromCountry === "" ||
|
||||||
|
sale.shipsFromCountry
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(shipsFromCountry.toLowerCase())) &&
|
||||||
|
(compound === "" ||
|
||||||
|
sale.compound
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(compound.toLowerCase())) &&
|
||||||
|
(searchTerm === "" ||
|
||||||
|
sale.vendor
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchTerm.toLowerCase()) ||
|
||||||
|
sale.compound
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchTerm.toLowerCase()))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Filtered sales:", filteredSales); // Debug log
|
||||||
|
setVisibleGroupBuySales(filteredSales.slice(0, loadCount));
|
||||||
|
}, [
|
||||||
|
groupBuySales,
|
||||||
|
vendorRating,
|
||||||
|
vendor,
|
||||||
|
shipsFromCountry,
|
||||||
|
compound,
|
||||||
|
searchTerm,
|
||||||
|
loadCount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setLoadCount(10);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScroll = (e) => {
|
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||||
const bottom =
|
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
||||||
e.target.scrollHeight - e.target.scrollTop ===
|
if (scrollHeight - scrollTop <= clientHeight * 1.5) {
|
||||||
e.target.clientHeight;
|
setLoadCount((prevCount) => prevCount + 10);
|
||||||
if (bottom) {
|
|
||||||
loadMoreVendors();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleExpand = (id: string) => {
|
||||||
|
setExpandedSales((prevExpanded) => {
|
||||||
|
const newExpanded = new Set(prevExpanded);
|
||||||
|
if (newExpanded.has(id)) {
|
||||||
|
newExpanded.delete(id);
|
||||||
|
} else {
|
||||||
|
newExpanded.add(id);
|
||||||
|
}
|
||||||
|
return newExpanded;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearFilters = () => {
|
||||||
|
setVendorRating("");
|
||||||
|
setVendor("");
|
||||||
|
setShipsFromCountry("");
|
||||||
|
setCompound("");
|
||||||
|
setSearchTerm("");
|
||||||
|
setLoadCount(10);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<div className={styles.container}>
|
||||||
<Banner>CompoundBay</Banner>
|
<h1 className={styles.banner}>CompoundBay</h1>
|
||||||
<SearchBar
|
<div className={styles.searchBarContainer}>
|
||||||
type="text"
|
<input
|
||||||
placeholder="Search vendors..."
|
type="text"
|
||||||
value={searchTerm}
|
placeholder="Search compounds..."
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
value={searchTerm}
|
||||||
/>
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
<Content>
|
className={styles.searchBar}
|
||||||
<Filters>
|
/>
|
||||||
<FilterTitle>Sort By</FilterTitle>
|
<button className={styles.searchButton} onClick={handleSearch}>
|
||||||
<FilterOption>Relevance</FilterOption>
|
<FaSearch />
|
||||||
<FilterOption>Rating</FilterOption>
|
</button>
|
||||||
<FilterTitle>Categories</FilterTitle>
|
</div>
|
||||||
<FilterOption>Organic</FilterOption>
|
<div className={styles.content}>
|
||||||
<FilterOption>Inorganic</FilterOption>
|
<FilterSidebar
|
||||||
<FilterTitle>Available On</FilterTitle>
|
vendorRating={vendorRating}
|
||||||
<FilterOption>Online</FilterOption>
|
vendor={vendor}
|
||||||
<FilterOption>In-store</FilterOption>
|
shipsFromCountry={shipsFromCountry}
|
||||||
</Filters>
|
compound={compound}
|
||||||
<Results onScroll={handleScroll}>
|
handleVendorRatingChange={setVendorRating}
|
||||||
{visibleVendors.map((vendor) => (
|
handleVendorChange={setVendor}
|
||||||
<ResultCard key={vendor.id}>
|
handleShipsFromCountryChange={setShipsFromCountry}
|
||||||
<VendorName>{vendor.name}</VendorName>
|
handleCompoundChange={setCompound}
|
||||||
<VendorDescription>
|
clearFilters={clearFilters}
|
||||||
{vendor.description}
|
vendorOptions={vendorOptions}
|
||||||
</VendorDescription>
|
countryOptions={countryOptions}
|
||||||
<VendorDetail>
|
compoundOptions={compoundOptions}
|
||||||
<strong>Compound:</strong> {vendor.compound}
|
/>
|
||||||
</VendorDetail>
|
<ResultsSidebar
|
||||||
<VendorDetail>
|
visibleGroupBuySales={visibleGroupBuySales}
|
||||||
<strong>Dose:</strong> {vendor.dose}{" "}
|
expandedSales={expandedSales}
|
||||||
{vendor.measurement}
|
toggleExpand={toggleExpand}
|
||||||
</VendorDetail>
|
handleScroll={handleScroll}
|
||||||
<VendorDetail>
|
/>
|
||||||
<strong>Format:</strong> {vendor.format}
|
</div>
|
||||||
</VendorDetail>
|
</div>
|
||||||
<VendorDetail>
|
|
||||||
<strong>Quantity:</strong> {vendor.quantity}
|
|
||||||
</VendorDetail>
|
|
||||||
<VendorDetail>
|
|
||||||
<strong>Price USD:</strong> ${vendor.priceUSD}
|
|
||||||
</VendorDetail>
|
|
||||||
<VendorDetail>
|
|
||||||
<strong>Shipping:</strong> {vendor.shipping}
|
|
||||||
</VendorDetail>
|
|
||||||
<VendorDetail>
|
|
||||||
<strong>Testing:</strong> {vendor.testing}
|
|
||||||
</VendorDetail>
|
|
||||||
<VendorDetail>
|
|
||||||
<strong>Guarantees:</strong> {vendor.guarantees}
|
|
||||||
</VendorDetail>
|
|
||||||
<VendorDetail>
|
|
||||||
<strong>Vendor Rating:</strong>{" "}
|
|
||||||
{vendor.vendorRating} / 5
|
|
||||||
</VendorDetail>
|
|
||||||
</ResultCard>
|
|
||||||
))}
|
|
||||||
</Results>
|
|
||||||
</Content>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
.filters {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterSidebar {
|
||||||
|
width: 250px;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterSection {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearFilters {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import styles from "./FilterSidebar.module.scss";
|
||||||
|
|
||||||
|
interface FilterSidebarProps {
|
||||||
|
vendorRating: string;
|
||||||
|
vendor: string;
|
||||||
|
shipsFromCountry: string;
|
||||||
|
compound: string;
|
||||||
|
handleVendorRatingChange: (rating: string) => void;
|
||||||
|
handleVendorChange: (vendor: string) => void;
|
||||||
|
handleShipsFromCountryChange: (country: string) => void;
|
||||||
|
handleCompoundChange: (compound: string) => void;
|
||||||
|
clearFilters: () => void;
|
||||||
|
vendorOptions: string[];
|
||||||
|
countryOptions: string[];
|
||||||
|
compoundOptions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilterSidebar: React.FC<FilterSidebarProps> = ({
|
||||||
|
vendorRating,
|
||||||
|
vendor,
|
||||||
|
shipsFromCountry,
|
||||||
|
compound,
|
||||||
|
handleVendorRatingChange,
|
||||||
|
handleVendorChange,
|
||||||
|
handleShipsFromCountryChange,
|
||||||
|
handleCompoundChange,
|
||||||
|
clearFilters,
|
||||||
|
vendorOptions,
|
||||||
|
countryOptions,
|
||||||
|
compoundOptions,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.filterSidebar}>
|
||||||
|
<h2>Filters</h2>
|
||||||
|
|
||||||
|
<div className={styles.filterSection}>
|
||||||
|
<h3>Vendor Rating</h3>
|
||||||
|
<select
|
||||||
|
value={vendorRating}
|
||||||
|
onChange={(e) => handleVendorRatingChange(e.target.value)}>
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="A">A</option>
|
||||||
|
<option value="AA">AA</option>
|
||||||
|
<option value="AAA">AAA</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.filterSection}>
|
||||||
|
<h3>Vendor</h3>
|
||||||
|
<select
|
||||||
|
value={vendor}
|
||||||
|
onChange={(e) => handleVendorChange(e.target.value)}>
|
||||||
|
<option value="">All</option>
|
||||||
|
{vendorOptions.map((v) => (
|
||||||
|
<option key={v} value={v}>
|
||||||
|
{v}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.filterSection}>
|
||||||
|
<h3>Ships from Country</h3>
|
||||||
|
<select
|
||||||
|
value={shipsFromCountry}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleShipsFromCountryChange(e.target.value)
|
||||||
|
}>
|
||||||
|
<option value="">All</option>
|
||||||
|
{countryOptions.map((c) => (
|
||||||
|
<option key={c} value={c}>
|
||||||
|
{c}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.filterSection}>
|
||||||
|
<h3>Compound</h3>
|
||||||
|
<select
|
||||||
|
value={compound}
|
||||||
|
onChange={(e) => handleCompoundChange(e.target.value)}>
|
||||||
|
<option value="">All</option>
|
||||||
|
{compoundOptions.map((c) => (
|
||||||
|
<option key={c} value={c}>
|
||||||
|
{c}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onClick={clearFilters} className={styles.clearFilters}>
|
||||||
|
Clear Filters
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilterSidebar;
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
.results {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: white;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.saleItem {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.saleTitle {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.salePrice {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #007bff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandedDetails {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailItem {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailLabel {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkItem {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noResults {
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue