Frontend update
This commit is contained in:
@@ -88,7 +88,7 @@ export default function ProductsPage() {
|
||||
}));
|
||||
};
|
||||
|
||||
// Handle input changes
|
||||
// Handle input changes
|
||||
const handleChange = (
|
||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => setProductData({ ...productData, [e.target.name]: e.target.value });
|
||||
@@ -239,7 +239,6 @@ export default function ProductsPage() {
|
||||
onClick={() => setImportModalOpen(true)}
|
||||
variant="outline"
|
||||
className="gap-2"
|
||||
disabled={true}
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
Import Products
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Inter } from "next/font/google"
|
||||
import "./globals.css"
|
||||
import { ThemeProvider } from "@/components/layout/theme-provider"
|
||||
import { Toaster } from "sonner"
|
||||
import type React from "react"
|
||||
import KeepOnline from "@/components/KeepOnline"
|
||||
|
||||
@@ -20,7 +21,12 @@ export default function RootLayout({
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
|
||||
<KeepOnline />
|
||||
<Toaster
|
||||
theme="dark"
|
||||
richColors
|
||||
position="top-right"
|
||||
/>
|
||||
<KeepOnline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
|
||||
@@ -8,11 +8,31 @@ import { toast } from "sonner";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
||||
interface ImportProductsModalProps {
|
||||
open: boolean;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => 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) {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
@@ -35,34 +55,71 @@ export default function ImportProductsModal({ open, setOpen, onImportComplete }:
|
||||
try {
|
||||
setIsUploading(true);
|
||||
const fileContent = await file.text();
|
||||
|
||||
const products = []
|
||||
const sections = fileContent.split("----------------------------------------------------------")
|
||||
const sections = fileContent.split("----------------------------------------------------------");
|
||||
const processedProducts = [];
|
||||
|
||||
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 = lines[0].trim()
|
||||
const category = lines[1].trim().split("->")[0].trim()
|
||||
const subcategory = lines[1].trim().split("->")[1].split("•")[0].trim()
|
||||
const [name] = productInfo.split("\n");
|
||||
if (!name) continue;
|
||||
|
||||
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 => {
|
||||
const price = line.split('@')[0].trim()
|
||||
const unit = line.split('@')[1].trim()
|
||||
return { price, unit }
|
||||
})
|
||||
if (!category || !subcategory) continue;
|
||||
|
||||
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];
|
||||
|
||||
// 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 ${result.count} products`);
|
||||
toast.success(`Successfully imported ${processedProducts.length} products`);
|
||||
onImportComplete();
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
|
||||
@@ -144,7 +144,7 @@ export default function OrderTable() {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ orderIds: Array.from(selectedOrders) })
|
||||
});
|
||||
|
||||
|
||||
setOrders(prev =>
|
||||
prev.map(order =>
|
||||
selectedOrders.has(order._id)
|
||||
|
||||
Reference in New Issue
Block a user