Frontend update

This commit is contained in:
g
2025-02-16 01:51:15 +00:00
parent da957d7c5f
commit 236fb223e4
4 changed files with 84 additions and 22 deletions

View File

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

View File

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

View File

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