Frontend update
This commit is contained in:
@@ -88,7 +88,7 @@ export default function ProductsPage() {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle input changes
|
// Handle input changes
|
||||||
const handleChange = (
|
const handleChange = (
|
||||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||||
) => setProductData({ ...productData, [e.target.name]: e.target.value });
|
) => setProductData({ ...productData, [e.target.name]: e.target.value });
|
||||||
@@ -239,7 +239,6 @@ export default function ProductsPage() {
|
|||||||
onClick={() => setImportModalOpen(true)}
|
onClick={() => setImportModalOpen(true)}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
disabled={true}
|
|
||||||
>
|
>
|
||||||
<Upload className="h-4 w-4" />
|
<Upload className="h-4 w-4" />
|
||||||
Import Products
|
Import Products
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Inter } from "next/font/google"
|
import { Inter } from "next/font/google"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
import { ThemeProvider } from "@/components/layout/theme-provider"
|
import { ThemeProvider } from "@/components/layout/theme-provider"
|
||||||
|
import { Toaster } from "sonner"
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
import KeepOnline from "@/components/KeepOnline"
|
import KeepOnline from "@/components/KeepOnline"
|
||||||
|
|
||||||
@@ -20,7 +21,12 @@ export default function RootLayout({
|
|||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
|
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
|
||||||
<KeepOnline />
|
<Toaster
|
||||||
|
theme="dark"
|
||||||
|
richColors
|
||||||
|
position="top-right"
|
||||||
|
/>
|
||||||
|
<KeepOnline />
|
||||||
{children}
|
{children}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -13,6 +13,26 @@ interface ImportProductsModalProps {
|
|||||||
onImportComplete: () => void;
|
onImportComplete: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Price {
|
||||||
|
quantity: number;
|
||||||
|
unit: string;
|
||||||
|
pricePerUnit: number;
|
||||||
|
totalPrice: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add unit standardization mapping
|
||||||
|
const unitMatch: Record<string, string> = {
|
||||||
|
"cart": "pcs",
|
||||||
|
"gr": "gr",
|
||||||
|
"pcs": "pcs",
|
||||||
|
"unit": "pcs",
|
||||||
|
"tip": "pcs",
|
||||||
|
"bar": "pcs",
|
||||||
|
"vape": "pcs",
|
||||||
|
"ml": "pcs",
|
||||||
|
"pill": "pcs",
|
||||||
|
};
|
||||||
|
|
||||||
export default function ImportProductsModal({ open, setOpen, onImportComplete }: ImportProductsModalProps) {
|
export default function ImportProductsModal({ open, setOpen, onImportComplete }: ImportProductsModalProps) {
|
||||||
const [file, setFile] = useState<File | null>(null);
|
const [file, setFile] = useState<File | null>(null);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
@@ -35,34 +55,71 @@ export default function ImportProductsModal({ open, setOpen, onImportComplete }:
|
|||||||
try {
|
try {
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
const fileContent = await file.text();
|
const fileContent = await file.text();
|
||||||
|
const sections = fileContent.split("----------------------------------------------------------");
|
||||||
const products = []
|
const processedProducts = [];
|
||||||
const sections = fileContent.split("----------------------------------------------------------")
|
|
||||||
|
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
if(!section.trim()) continue
|
const productInfo = section.split("\n\n")[1];
|
||||||
|
if (!productInfo) continue;
|
||||||
|
|
||||||
const lines = section.trim().split("\n")
|
const [name] = productInfo.split("\n");
|
||||||
const name = lines[0].trim()
|
if (!name) continue;
|
||||||
const category = lines[1].trim().split("->")[0].trim()
|
|
||||||
const subcategory = lines[1].trim().split("->")[1].split("•")[0].trim()
|
|
||||||
|
|
||||||
console.log(`${name} - ${category} - ${subcategory}`)
|
const [category, subcategory] = productInfo.split("\n")[1].split("•")[0].split("->")
|
||||||
|
.map(item => item.trim());
|
||||||
|
|
||||||
const pricing = lines.slice(3).filter(line => line.includes('@')).map(line => {
|
if (!category || !subcategory) continue;
|
||||||
const price = line.split('@')[0].trim()
|
|
||||||
const unit = line.split('@')[1].trim()
|
|
||||||
return { price, unit }
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(pricing)
|
// Process pricing
|
||||||
|
const priceText = section.split("\n\n")[2];
|
||||||
|
if (!priceText) continue;
|
||||||
|
|
||||||
|
const priceLines = priceText.trim().split("\n");
|
||||||
|
const prices = priceLines
|
||||||
|
.map(line => {
|
||||||
|
const matches = line.match(/from (\d+\.?\d*) (cart[s]?|gr|grams?|g|pcs|unit[s]?|tip[s]?|bar[s]?|vape[s]?|ml|pill[s]?) @ £(\d+\.?\d*)/);
|
||||||
|
if (!matches) return null;
|
||||||
|
|
||||||
|
const quantity = parseFloat(matches[1]);
|
||||||
|
const totalPrice = parseFloat(matches[3]);
|
||||||
|
const pricePerUnit = totalPrice / quantity;
|
||||||
|
|
||||||
|
const rawUnit = matches[2].replace(/s$/, '');
|
||||||
|
return {
|
||||||
|
quantity,
|
||||||
|
unit: unitMatch[rawUnit] || rawUnit,
|
||||||
|
pricePerUnit,
|
||||||
|
totalPrice
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((price): price is Price => price !== null)
|
||||||
|
.sort((a, b) => a.quantity - b.quantity);
|
||||||
|
|
||||||
|
if (prices.length > 0) {
|
||||||
|
processedProducts.push({ name, category, subcategory, prices });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Processed products:', processedProducts);
|
||||||
|
|
||||||
|
// Get auth token from cookie
|
||||||
|
const authToken = document.cookie.split("Authorization=")[1];
|
||||||
|
|
||||||
//toast.success(`Successfully imported ${result.count} products`);
|
// Send to API
|
||||||
|
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/products/batch`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ products: processedProducts })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to upload products');
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`Successfully imported ${processedProducts.length} products`);
|
||||||
onImportComplete();
|
onImportComplete();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user