736 lines
25 KiB
JavaScript
736 lines
25 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
|
import { json } from "@remix-run/node";
|
|
import { useLoaderData, Form, useActionData } from "@remix-run/react";
|
|
import {
|
|
Page,
|
|
Layout,
|
|
IndexTable,
|
|
Card,
|
|
Thumbnail,
|
|
TextContainer,
|
|
Spinner,
|
|
Button,
|
|
TextField,
|
|
Banner,
|
|
InlineError,
|
|
Toast,
|
|
Frame,
|
|
Select,
|
|
ProgressBar,
|
|
Checkbox,
|
|
} from "@shopify/polaris";
|
|
import { authenticate } from "../shopify.server";
|
|
import { TitleBar } from "@shopify/app-bridge-react";
|
|
|
|
|
|
async function checkShopExists(shop) {
|
|
try {
|
|
const resp = await fetch(
|
|
`https://backend.data4autos.com/checkisshopdataexists/${shop}`
|
|
);
|
|
const data = await resp.json();
|
|
return data.status === 1; // ✅ true if shop exists, false otherwise
|
|
} catch (err) {
|
|
console.error("Error checking shop:", err);
|
|
return false; // default to false if error
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export const loader = async ({ request }) => {
|
|
const { admin } = await authenticate.admin(request);
|
|
const { getTurn14AccessTokenFromMetafield } = await import("../utils/turn14Token.server");
|
|
const accessToken = await getTurn14AccessTokenFromMetafield(request);
|
|
|
|
const res = await admin.graphql(`{
|
|
shop {
|
|
metafield(namespace: "turn14", key: "selected_brands") {
|
|
value
|
|
}
|
|
}
|
|
}`);
|
|
const data = await res.json();
|
|
const rawValue = data?.data?.shop?.metafield?.value;
|
|
|
|
let brands = [];
|
|
try {
|
|
brands = JSON.parse(rawValue);
|
|
} catch (err) {
|
|
console.error("❌ Failed to parse metafield value:", err);
|
|
}
|
|
|
|
|
|
const { session } = await authenticate.admin(request);
|
|
const shop = session.shop;
|
|
|
|
return json({ brands, accessToken, shop });
|
|
};
|
|
|
|
|
|
const makes_list_raw = [
|
|
'Alfa Romeo',
|
|
'Ferrari',
|
|
'Dodge',
|
|
'Subaru',
|
|
'Toyota',
|
|
'Volkswagen',
|
|
'Volvo',
|
|
'Audi',
|
|
'BMW',
|
|
'Buick',
|
|
'Cadillac',
|
|
'Chevrolet',
|
|
'Chrysler',
|
|
'CX Automotive',
|
|
'Nissan',
|
|
'Ford',
|
|
'Hyundai',
|
|
'Infiniti',
|
|
'Lexus',
|
|
'Mercury',
|
|
'Mazda',
|
|
'Oldsmobile',
|
|
'Plymouth',
|
|
'Pontiac',
|
|
'Rolls-Royce',
|
|
'Eagle',
|
|
'Lincoln',
|
|
'Mercedes-Benz',
|
|
'GMC',
|
|
'Saab',
|
|
'Honda',
|
|
'Saturn',
|
|
'Mitsubishi',
|
|
'Isuzu',
|
|
'Jeep',
|
|
'AM General',
|
|
'Geo',
|
|
'Suzuki',
|
|
'E. P. Dutton, Inc.',
|
|
'Land Rover',
|
|
'PAS, Inc',
|
|
'Acura',
|
|
'Jaguar',
|
|
'Lotus',
|
|
'Grumman Olson',
|
|
'Porsche',
|
|
'American Motors Corporation',
|
|
'Kia',
|
|
'Lamborghini',
|
|
'Panoz Auto-Development',
|
|
'Maserati',
|
|
'Saleen',
|
|
'Aston Martin',
|
|
'Dabryan Coach Builders Inc',
|
|
'Federal Coach',
|
|
'Vector',
|
|
'Bentley',
|
|
'Daewoo',
|
|
'Qvale',
|
|
'Roush Performance',
|
|
'Autokraft Limited',
|
|
'Bertone',
|
|
'Panther Car Company Limited',
|
|
'Texas Coach Company',
|
|
'TVR Engineering Ltd',
|
|
'Morgan',
|
|
'MINI',
|
|
'Yugo',
|
|
'BMW Alpina',
|
|
'Renault',
|
|
'Bitter Gmbh and Co. Kg',
|
|
'Scion',
|
|
'Maybach',
|
|
'Lambda Control Systems',
|
|
'Merkur',
|
|
'Peugeot',
|
|
'Spyker',
|
|
'London Coach Co Inc',
|
|
'Hummer',
|
|
'Bugatti',
|
|
'Pininfarina',
|
|
'Shelby',
|
|
'Saleen Performance',
|
|
'smart',
|
|
'Tecstar, LP',
|
|
'Kenyon Corporation Of America',
|
|
'Avanti Motor Corporation',
|
|
'Bill Dovell Motor Car Company',
|
|
'Import Foreign Auto Sales Inc',
|
|
'S and S Coach Company E.p. Dutton',
|
|
'Superior Coaches Div E.p. Dutton',
|
|
'Vixen Motor Company',
|
|
'Volga Associated Automobile',
|
|
'Wallace Environmental',
|
|
'Import Trade Services',
|
|
'J.K. Motors',
|
|
'Panos',
|
|
'Quantum Technologies',
|
|
'London Taxi',
|
|
'Red Shift Ltd.',
|
|
'Ruf Automobile Gmbh',
|
|
'Excalibur Autos',
|
|
'Mahindra',
|
|
'VPG',
|
|
'Fiat',
|
|
'Sterling',
|
|
'Azure Dynamics',
|
|
'McLaren Automotive',
|
|
'Ram',
|
|
'CODA Automotive',
|
|
'Fisker',
|
|
'Tesla',
|
|
'Mcevoy Motors',
|
|
'BYD',
|
|
'ASC Incorporated',
|
|
'SRT',
|
|
'CCC Engineering',
|
|
'Mobility Ventures LLC',
|
|
'Pagani',
|
|
'Genesis',
|
|
'Karma',
|
|
'Koenigsegg',
|
|
'Aurora Cars Ltd',
|
|
'RUF Automobile',
|
|
'Dacia',
|
|
'STI',
|
|
'Daihatsu',
|
|
'Polestar',
|
|
'Kandi',
|
|
'Rivian',
|
|
'Lucid',
|
|
'JBA Motorcars, Inc.',
|
|
'Lordstown',
|
|
'Vinfast',
|
|
'INEOS Automotive',
|
|
'Bugatti Rimac',
|
|
'Grumman Allied Industries',
|
|
'Environmental Rsch and Devp Corp',
|
|
'Evans Automobiles',
|
|
'Laforza Automobile Inc',
|
|
'General Motors',
|
|
'Consulier Industries Inc',
|
|
'Goldacre',
|
|
'Isis Imports Ltd',
|
|
'PAS Inc - GMC'
|
|
];
|
|
|
|
|
|
|
|
const makes_list = makes_list_raw.sort();
|
|
|
|
export const action = async ({ request }) => {
|
|
const { admin } = await authenticate.admin(request);
|
|
const formData = await request.formData();
|
|
const brandId = formData.get("brandId");
|
|
const rawCount = formData.get("productCount");
|
|
const selectedProductIds = JSON.parse(formData.get("selectedProductIds") || "[]");
|
|
const productCount = parseInt(rawCount, 10) || 10;
|
|
|
|
const { getTurn14AccessTokenFromMetafield } = await import("../utils/turn14Token.server");
|
|
const accessToken = await getTurn14AccessTokenFromMetafield(request);
|
|
|
|
const { session } = await authenticate.admin(request);
|
|
const shop = session.shop;
|
|
|
|
const resp = await fetch("https://backend.data4autos.com/manageProducts", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"shop-domain": shop,
|
|
},
|
|
body: JSON.stringify({
|
|
shop,
|
|
brandID: brandId,
|
|
turn14accessToken: accessToken,
|
|
productCount,
|
|
selectedProductIds
|
|
}),
|
|
});
|
|
|
|
console.log("Response from manageProducts:", resp.status, resp.statusText);
|
|
if (!resp.ok) {
|
|
const err = await resp.text();
|
|
return json({ error: err }, { status: resp.status });
|
|
}
|
|
|
|
const { processId, status } = await resp.json();
|
|
console.log("Process ID:", processId, "Status:", status);
|
|
return json({ success: true, processId, status });
|
|
};
|
|
|
|
export default function ManageBrandProducts() {
|
|
const actionData = useActionData();
|
|
const { shop, brands, accessToken } = useLoaderData();
|
|
const [expandedBrand, setExpandedBrand] = useState(null);
|
|
const [itemsMap, setItemsMap] = useState({});
|
|
const [loadingMap, setLoadingMap] = useState({});
|
|
const [productCount, setProductCount] = useState("10");
|
|
const [initialLoad, setInitialLoad] = useState(true);
|
|
const [toastActive, setToastActive] = useState(false);
|
|
const [polling, setPolling] = useState(false);
|
|
const [status, setStatus] = useState(actionData?.status || "");
|
|
const [processId, setProcessId] = useState(actionData?.processId || null);
|
|
const [progress, setProgress] = useState(0);
|
|
const [totalProducts, setTotalProducts] = useState(0);
|
|
const [processedProducts, setProcessedProducts] = useState(0);
|
|
const [currentProduct, setCurrentProduct] = useState(null);
|
|
const [results, setResults] = useState([]);
|
|
const [detail, setDetail] = useState("");
|
|
|
|
const [filterregulatstock, setfilterregulatstock] = useState(false)
|
|
|
|
|
|
|
|
const [Turn14Enabled, setTurn14Enabled] = useState("12345"); // null | true | false
|
|
|
|
useEffect(() => {
|
|
if (!shop) {
|
|
console.log("⚠️ shop is undefined or empty");
|
|
return;
|
|
}
|
|
|
|
(async () => {
|
|
const result = await checkShopExists(shop);
|
|
console.log("✅ API status result:", result, "| shop:", shop);
|
|
setTurn14Enabled(result);
|
|
})();
|
|
}, [shop]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
if (actionData?.processId) {
|
|
setProcessId(actionData.processId);
|
|
setStatus(actionData.status || "processing");
|
|
setToastActive(true);
|
|
}
|
|
}, [actionData]);
|
|
|
|
const checkStatus = async () => {
|
|
setPolling(true);
|
|
try {
|
|
const response = await fetch(`https://backend.data4autos.com/manageProducts/status/${processId}`);
|
|
const data = await response.json();
|
|
|
|
setStatus(data.status);
|
|
setDetail(data.detail);
|
|
setProgress(data.progress);
|
|
setTotalProducts(data.stats.total);
|
|
setProcessedProducts(data.stats.processed);
|
|
setCurrentProduct(data.current);
|
|
|
|
if (data.results) {
|
|
setResults(data.results);
|
|
}
|
|
|
|
// Continue polling if still processing
|
|
if (data.status !== 'done' && data.status !== 'error') {
|
|
setTimeout(checkStatus, 2000);
|
|
} else {
|
|
setPolling(false);
|
|
}
|
|
} catch (error) {
|
|
setPolling(false);
|
|
setStatus('error');
|
|
setDetail('Failed to check status');
|
|
console.error('Error checking status:', error);
|
|
}
|
|
};
|
|
useEffect(() => {
|
|
let interval;
|
|
if (status?.includes("processing") && processId) {
|
|
interval = setInterval(checkStatus, 5000);
|
|
}
|
|
return () => clearInterval(interval);
|
|
}, [status, processId]);
|
|
|
|
const toggleAllBrands = async () => {
|
|
for (const brand of brands) {
|
|
await toggleBrandItems(brand.id);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (initialLoad && brands.length > 0) {
|
|
toggleAllBrands();
|
|
setInitialLoad(false);
|
|
}
|
|
}, [brands, initialLoad]);
|
|
|
|
const toggleBrandItems = async (brandId) => {
|
|
const isExpanded = expandedBrand === brandId;
|
|
if (isExpanded) {
|
|
setExpandedBrand(null);
|
|
} else {
|
|
setExpandedBrand(brandId);
|
|
if (!itemsMap[brandId]) {
|
|
setLoadingMap((prev) => ({ ...prev, [brandId]: true }));
|
|
try {
|
|
const res = await fetch(`https://turn14.data4autos.com/v1/items/brandallitemswithfitment/${brandId}`, {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
const data = await res.json();
|
|
const dataitems = data.items
|
|
const validItems = Array.isArray(dataitems)
|
|
? dataitems.filter(item => item && item.id && item.attributes)
|
|
: [];
|
|
setItemsMap((prev) => ({ ...prev, [brandId]: validItems }));
|
|
} catch (err) {
|
|
console.error("Error fetching items:", err);
|
|
setItemsMap((prev) => ({ ...prev, [brandId]: [] }));
|
|
}
|
|
setLoadingMap((prev) => ({ ...prev, [brandId]: false }));
|
|
}
|
|
}
|
|
};
|
|
|
|
const toastMarkup = toastActive ? (
|
|
<Toast
|
|
content={status.includes("completed") ?
|
|
"Products imported successfully!" :
|
|
`Status: ${status}`}
|
|
onDismiss={() => setToastActive(false)}
|
|
/>
|
|
) : null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [filters, setFilters] = useState({ make: '', model: '', year: '', drive: '', baseModel: '' });
|
|
|
|
const handleFilterChange = (field) => (value) => {
|
|
setFilters((prev) => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
|
|
|
|
const applyFitmentFilters = (items) => {
|
|
return items.filter((item) => {
|
|
const tags = item?.attributes?.fitmmentTags || {};
|
|
const productName = item?.attributes?.product_name || '';
|
|
const brand = item?.attributes?.brand || '';
|
|
const partDescription = item?.attributes?.part_description || '';
|
|
const descriptions = item?.attributes?.descriptions || [];
|
|
|
|
// // Logging the item being checked and the filters
|
|
// console.log("Checking item:", item.id); // Log the item's ID or some unique identifier
|
|
// console.log("Filters being applied:", filters);
|
|
|
|
// // Log the values for each field being checked
|
|
// console.log("Checking tags:", tags);
|
|
// console.log("Checking product name:", productName);
|
|
// console.log("Checking brand:", brand);
|
|
// console.log("Checking part description:", partDescription);
|
|
// console.log("Checking descriptions:", descriptions.map((desc) => desc.description));
|
|
|
|
// Create the result for each check
|
|
const makeMatch = !filters.make || tags.make?.includes(filters.make) || productName.includes(filters.make) || brand.includes(filters.make) || descriptions.some((desc) => desc.description.includes(filters.make));
|
|
// console.log(`Make check result: ${makeMatch}`);
|
|
|
|
const modelMatch = !filters.model || tags.model?.includes(filters.model) || productName.includes(filters.model) || brand.includes(filters.model) || descriptions.some((desc) => desc.description.includes(filters.model));
|
|
// console.log(`Model check result: ${modelMatch}`);
|
|
|
|
const yearMatch = !filters.year || tags.year?.includes(filters.year) || productName.includes(filters.year) || brand.includes(filters.year) || descriptions.some((desc) => desc.description.includes(filters.year));
|
|
/// console.log(`Year check result: ${yearMatch}`);
|
|
|
|
const driveMatch = !filters.drive || tags.drive?.includes(filters.drive) || productName.includes(filters.drive) || brand.includes(filters.drive) || descriptions.some((desc) => desc.description.includes(filters.drive));
|
|
// console.log(`Drive check result: ${driveMatch}`);
|
|
|
|
const baseModelMatch = !filters.baseModel || tags.baseModel?.includes(filters.baseModel) || productName.includes(filters.baseModel) || brand.includes(filters.baseModel) || descriptions.some((desc) => desc.description.includes(filters.baseModel));
|
|
// console.log(`Base Model check result: ${baseModelMatch}`);
|
|
|
|
// Combine all the conditions
|
|
var isMatch = makeMatch && modelMatch && yearMatch && driveMatch && baseModelMatch// && item.attributes.regular_stock
|
|
if (filterregulatstock) {
|
|
isMatch = isMatch && item?.attributes?.regular_stock
|
|
}
|
|
// Log the result of the check (whether item matches the filter or not)
|
|
// console.log(`Item ${item.id} match: ${isMatch}`);
|
|
|
|
// Return the item if it matches
|
|
return isMatch;
|
|
});
|
|
};
|
|
|
|
|
|
|
|
|
|
// const applyFitmentFilters = (items) => {
|
|
// return items.filter((item) => {
|
|
// const tags = item?.attributes?.fitmmentTags || {};
|
|
// return (
|
|
// (!filters.make || tags.make?.includes(filters.make)) &&
|
|
// (!filters.model || tags.model?.includes(filters.model)) &&
|
|
// (!filters.year || tags.year?.includes(filters.year)) &&
|
|
// (!filters.drive || tags.drive?.includes(filters.drive)) &&
|
|
// (!filters.baseModel || tags.baseModel?.includes(filters.baseModel))
|
|
// );
|
|
// });
|
|
// };
|
|
|
|
|
|
const selectedProductIds = []
|
|
|
|
|
|
return (
|
|
<Frame>
|
|
<Page title="Data4Autos Turn14 Manage Brand Products" fullWidth>
|
|
<TitleBar title="Data4Autos Turn14 Integration" />
|
|
<Layout>
|
|
<p>
|
|
<strong>Turn 14 Status:</strong> {Turn14Enabled}
|
|
</p>
|
|
{brands.length === 0 ? (
|
|
<Layout.Section>
|
|
<Card sectioned>
|
|
<p>No brands selected yet.</p>
|
|
</Card>
|
|
</Layout.Section>
|
|
) : (
|
|
<Layout.Section>
|
|
<Card>
|
|
<IndexTable
|
|
resourceName={{ singular: "brand", plural: "brands" }}
|
|
itemCount={brands.length}
|
|
headings={[
|
|
{ title: <div style={{ fontWeight: 600, fontSize: "16px", background: "#f4f6f8", padding: "15px 8px", borderRadius: "4px" }}>Brand ID</div> },
|
|
{ title: <div style={{ fontWeight: 600, fontSize: "16px", background: "#f4f6f8", padding: "15px 8px", borderRadius: "4px" }}>Brand Name</div> },
|
|
{ title: <div style={{ fontWeight: 600, fontSize: "16px", background: "#f4f6f8", padding: "15px 8px", borderRadius: "4px" }}>Brand Logo</div> },
|
|
{ title: <div style={{ fontWeight: 600, fontSize: "16px", background: "#f4f6f8", padding: "15px 8px", borderRadius: "4px" }}>Action</div> },
|
|
{ title: <div style={{ fontWeight: 600, fontSize: "16px", background: "#f4f6f8", padding: "15px 8px", borderRadius: "4px" }}>Products Count</div> },
|
|
]}
|
|
selectable={false}
|
|
>
|
|
{brands.map((brand, index) => {
|
|
|
|
return (
|
|
|
|
<IndexTable.Row id={brand.id.toString()} key={brand.id} position={index}>
|
|
<IndexTable.Cell>{brand.id}</IndexTable.Cell>
|
|
<IndexTable.Cell>{brand.name}</IndexTable.Cell>
|
|
<IndexTable.Cell>
|
|
<Thumbnail
|
|
source={
|
|
brand.logo ||
|
|
"https://cdn.shopify.com/s/files/1/0757/9955/files/no-image_280x@2x.png"
|
|
}
|
|
alt={brand.name}
|
|
size="medium"
|
|
/>
|
|
</IndexTable.Cell>
|
|
<IndexTable.Cell>
|
|
<Button onClick={() => toggleBrandItems(brand.id)} variant="primary">
|
|
{expandedBrand === brand.id ? "Hide Products" : "Show Products"}
|
|
</Button>
|
|
</IndexTable.Cell>
|
|
<IndexTable.Cell>
|
|
<span
|
|
style={{
|
|
display: "inline-block",
|
|
background: "#00d1ff29", // light teal background
|
|
color: "#00d1ff", // dark teal text
|
|
padding: "4px 8px",
|
|
borderRadius: "12px",
|
|
fontWeight: "600",
|
|
fontSize: "14px",
|
|
minWidth: "28px",
|
|
textAlign: "center"
|
|
}}
|
|
>
|
|
{itemsMap[brand.id]?.length || 0}
|
|
</span>
|
|
</IndexTable.Cell>
|
|
</IndexTable.Row>
|
|
)
|
|
})}
|
|
</IndexTable>
|
|
</Card>
|
|
</Layout.Section>
|
|
)}
|
|
|
|
{brands.map((brand) => {
|
|
const filteredItems = applyFitmentFilters(itemsMap[brand.id] || []);
|
|
// console.log("Filtered items for brand", brand.id, ":", filteredItems.map((item) => item.id));
|
|
const uniqueTags = {
|
|
make: new Set(),
|
|
model: new Set(),
|
|
year: new Set(),
|
|
drive: new Set(),
|
|
baseModel: new Set(),
|
|
};
|
|
|
|
(itemsMap[brand.id] || []).forEach(item => {
|
|
const tags = item?.attributes?.fitmmentTags || {};
|
|
Object.keys(uniqueTags).forEach(key => {
|
|
(tags[key] || []).forEach(val => uniqueTags[key].add(val));
|
|
});
|
|
});
|
|
|
|
return (
|
|
|
|
expandedBrand === brand.id &&
|
|
|
|
(
|
|
<Layout.Section fullWidth key={brand.id + "-expanded"}>
|
|
{processId && (
|
|
<Card sectioned>
|
|
<div style={{ marginBottom: "1rem", padding: "1rem", border: "1px solid #e1e1e1", borderRadius: "4px" }}>
|
|
<p>
|
|
<strong>Process ID:</strong> {processId}
|
|
</p>
|
|
|
|
<div style={{ margin: "1rem 0" }}>
|
|
<p>
|
|
<strong>Status:</strong> {status || "—"}
|
|
</p>
|
|
|
|
{progress > 0 && (
|
|
<div style={{ marginTop: "0.5rem" }}>
|
|
<ProgressBar
|
|
progress={progress}
|
|
color={
|
|
status === 'error' ? 'critical' :
|
|
status === 'done' ? 'success' : 'highlight'
|
|
}
|
|
/>
|
|
<p style={{ marginTop: "0.25rem", fontSize: "0.85rem" }}>
|
|
{processedProducts} of {totalProducts} products processed
|
|
{currentProduct && ` - Current: ${currentProduct.name} (${currentProduct.number}/${currentProduct.total})`}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{status === 'done' && results.length > 0 && (
|
|
<div style={{ marginTop: "1rem" }}>
|
|
<p>
|
|
<strong>Results:</strong> {results.length} products processed successfully
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{status === 'error' && (
|
|
<div style={{ marginTop: "1rem", color: "#ff4d4f" }}>
|
|
<strong>Error:</strong> {detail}
|
|
</div>
|
|
)}
|
|
|
|
<Button
|
|
onClick={checkStatus}
|
|
loading={polling} variant="primary" size="large"
|
|
style={{ marginTop: "1rem" }}
|
|
>
|
|
{status === 'done' ? 'View Results' : 'Check Status'}
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
)}
|
|
<Card title={`Items from ${brand.name}`} sectioned>
|
|
{loadingMap[brand.id] ? (
|
|
<Spinner accessibilityLabel="Loading items" size="small" />
|
|
) : (
|
|
<div style={{ paddingTop: "1rem" }}>
|
|
<Form method="post">
|
|
<input
|
|
type="hidden"
|
|
name="selectedProductIds"
|
|
value={JSON.stringify(filteredItems.map((item) => item.id))}
|
|
/>
|
|
<input type="hidden" name="brandId" value={brand.id} />
|
|
<div style={{ display: "flex", gap: "1rem", alignItems: "end" }}>
|
|
<TextField
|
|
label="Number of products in Selected Filter Make"
|
|
type="number"
|
|
name="productCount"
|
|
value={filteredItems.length}
|
|
onChange={(value) => setProductCount(value)}
|
|
autoComplete="off"
|
|
/>
|
|
|
|
<Checkbox
|
|
label="Filter Only the Regular Stock"
|
|
checked={filterregulatstock}
|
|
onChange={() => { setfilterregulatstock(!filterregulatstock) }}
|
|
/>
|
|
<Button
|
|
submit
|
|
primary variant="primary" size="large"
|
|
style={{ marginTop: "1rem" }}
|
|
loading={status?.includes("processing")}
|
|
>
|
|
Add First {filteredItems.length} Products from {filters.make} to Store
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
</Form>
|
|
|
|
<div style={{ padding: "20px 0px" }}>
|
|
<Card title="Filter Products by Fitment Tags" sectioned >
|
|
<Layout>
|
|
<Layout.Section oneThird>
|
|
<Select
|
|
label="Make"
|
|
options={[{ label: 'All', value: '' }, ...Array.from(makes_list).map(m => ({ label: m, value: m }))]}
|
|
onChange={handleFilterChange('make')}
|
|
value={filters.make}
|
|
/>
|
|
</Layout.Section>
|
|
|
|
</Layout>
|
|
</Card>
|
|
</div>
|
|
<div style={{
|
|
display: "grid",
|
|
gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))",
|
|
gap: 16,
|
|
}}>
|
|
{filteredItems.map((item) => (
|
|
<Card key={item.id} title={item?.attributes?.product_name || 'Untitled Product'} sectioned>
|
|
<Layout>
|
|
<Layout.Section oneThird>
|
|
<Thumbnail
|
|
source={
|
|
item?.attributes?.thumbnail ||
|
|
"https://cdn.shopify.com/s/files/1/0757/9955/files/no-image_280x@2x.png"
|
|
}
|
|
alt={item?.attributes?.product_name || 'Product image'}
|
|
size="large"
|
|
/>
|
|
</Layout.Section>
|
|
<Layout.Section>
|
|
<TextContainer spacing="tight">
|
|
<p><strong>Part Number:</strong> {item?.attributes?.part_number || 'N/A'}</p>
|
|
<p><strong>Category:</strong> {item?.attributes?.category || 'N/A'} > {item?.attributes?.subcategory || 'N/A'}</p>
|
|
<p><strong>Price:</strong> ${item?.attributes?.price || '0.00'}</p>
|
|
<p><strong>Description:</strong> {item?.attributes?.part_description || 'No description available'}</p>
|
|
</TextContainer>
|
|
</Layout.Section>
|
|
</Layout>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
</Layout.Section>
|
|
)
|
|
)
|
|
})}
|
|
</Layout>
|
|
{toastMarkup}
|
|
</Page>
|
|
</Frame>
|
|
);
|
|
} |