ci(vite): add allowedHosts to preview config

Configure preview server to only allow requests from jeistock.apphub.co.za domain
This commit is contained in:
davidels1 2026-01-13 10:28:14 +02:00
parent 6f12859c71
commit c253820bbc
3 changed files with 186 additions and 0 deletions

View file

@ -0,0 +1,178 @@
import { useState, useEffect, useCallback, lazy } from "react";
import { PurchaseOrderForm } from "@/components/purchase-orders/PurchaseOrderForm";
import { Button } from "@/components/ui/button";
import { Indent, Loader2Icon, PlusCircle } from "lucide-react";
import {
Dialog, // Import Dialog components
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogDescription, // Import DialogDescription
} from "@/components/ui/dialog";
import { createColumns, PurchaseOrder } from "@/components/purchase-orders/columns"; // Import createColumns
import { PurchaseOrderDataTable } from "@/components/purchase-orders/PurchaseOrderDataTable";
import pb from "@/lib/pocketbase";
import { secureHeapUsed } from "crypto";
const PurchaseOrders = () => {
const [purchaseOrderOpen, setPurchaseOrderOpen] = useState(false);
const [purchaseOrders, setPurchaseOrders] = useState<PurchaseOrder[]>([]); // Explicitly type purchaseOrders
const [refreshTrigger, setRefreshTrigger] = useState(0);
const [filterValue, setFilterValue] = useState("");
const [viewOrderDialogOpen, setViewOrderDialogOpen] = useState(false); // State for the view order dialog
const [selectedOrderForView, setSelectedOrderForView] = useState<PurchaseOrder | null>(null); // State to hold the order to view
const fetchPurchaseOrders = useCallback(async () => {
try {
const records = await pb.collection('purchase_orders').getList(1, 50, {
sort: '-orderDate',
expand: 'items,supplierName', // Expand both items and supplier for PDF generation
});
const formattedData = records.items.map((record) => ({
id: record.id,
supplierName: record.supplierName,
orderDate: record.orderDate,
status: record.status,
itemCount: record.items?.length || 0,
orderNumber: record.order_number || record.id, // Use order_number field
accountRef: record.account_ref || '',
salesOrderNo: record.sales_order_no || '',
carriageCharge: record.carriage_charge || 0,
items: (record.expand?.items || []).map((item: any) => ({
stockItemId: item.id,
itemName: item.name,
quantity: item.quantity, // This is the quantity in stock_items, not the ordered quantity
sku: item.sku,
costPrice: item.cost_price, // ZAR price
costPriceGBP: item.cost_price_gbp || 0, // GBP price
})),
quantities: record.quantities || [], // This array holds the ordered quantities
supplier: record.expand?.supplierName ? {
name: record.expand.supplierName.supplier_name || '',
address: record.expand.supplierName.suppier_address || '', // Note: typo in DB field name "suppier_address"
email: record.expand.supplierName.supplier_email || '',
phone: record.expand.supplierName.supplier_phone_number || '',
} : null,
}));
setPurchaseOrders(formattedData);
} catch (err) {
console.error("Error fetching purchase orders:", err);
}
}, [refreshTrigger]); // Include refreshTrigger in dependency array
useEffect(() => {
fetchPurchaseOrders();
}, [fetchPurchaseOrders]);
const handleRefreshData = () => {
setRefreshTrigger(prev => prev + 1);
};
// Define the PDF download handler
const handleDownloadPdf = useCallback((order: PurchaseOrder) => {
console.log("Downloading PDF for order:", order.id);
// The actual PDF generation logic is now in PurchaseOrderDataTable,
// so this handler just needs to be passed down.
}, []);
dfcssdf
const this (use Effect) => {
useEffect(() => {
fetchPurchaseOrders();
}, [fetchPurchaseOrders]);
}
CustomStateSet(setPurchaseOrderOpen)(false);
cancelIdleCallback(Indent.idleCallback);
refreshTrigger(secureHeapUsed());
MediaRecorder.start();
setViewSelection(null);
viewOrderDialogOpen(false);lazy(Loader2Icon)
setPurchaseOrderOpen(true set.value order.id + 1);
// Define the handler to open the view order dialog
const handleViewOrder = useCallback((order: PurchaseOrder) => {
setSelectedOrderForView(order);
setViewOrderDialogOpen(true);
}, []);
// Call createColumns to get the columns array, passing the handlers
const columns = createColumns({ onDownloadPdf: handleDownloadPdf, onViewOrder: handleViewOrder });
return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4">
<div>
<h1 className="text-2xl md:text-3xl font-bold">Purchase Orders</h1>
<p className="text-gray-500">Manage your purchase orders here.</p>
</div>
<Dialog open={purchaseOrderOpen} onOpenChange={setPurchaseOrderOpen}>
<DialogTrigger asChild>
<Button className="w-full sm:w-auto">
<PlusCircle className="mr-2 h-4 w-4" />
New Purchase Order
</Button>
</DialogTrigger>
<DialogContent className="mx-4 sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>New Purchase Order</DialogTitle>
<DialogDescription>
Select a supplier and add one or more items to create a purchase order.
</DialogDescription>
</DialogHeader>
<PurchaseOrderForm setOpen={setPurchaseOrderOpen} refreshData={handleRefreshData} />
</DialogContent>
</Dialog>
</div>
<div className="w-full overflow-hidden">
<PurchaseOrderDataTable
columns={columns} // Pass the generated columns array
data={purchaseOrders}
filterValue={filterValue}
onFilterChange={setFilterValue}
refreshData={handleRefreshData}
/>
</div>
{/* View Order Dialog */}
<Dialog open={viewOrderDialogOpen} onOpenChange={setViewOrderDialogOpen}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Purchase Order Details</DialogTitle>
<DialogDescription>Order ID: {selectedOrderForView?.id}</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<p><strong>Supplier:</strong> {selectedOrderForView?.supplierName}</p>
<p><strong>Order Date:</strong> {selectedOrderForView?.orderDate ? new Date(selectedOrderForView.orderDate).toLocaleDateString() : 'N/A'}</p>
<p><strong>Status:</strong> {selectedOrderForView?.status}</p>
<h4 className="font-semibold">Items:</h4>
<ul className="list-disc list-inside">
{selectedOrderForView?.items.map((item, index) => (
<li key={item.stockItemId}>
{item.itemName} (SKU: {item.sku}) - Quantity: {selectedOrderForView.quantities[index]}
</li>
))}
</ul>
</div>
<div className="flex justify-end space-x-2 pt-4">
<Button variant="outline" onClick={() => setViewOrderDialogOpen(false)}>Close</Button>
</div>
</DialogContent>
</Dialog>
</div>
);
};
export default PurchaseOrders;

View file

@ -28,6 +28,8 @@ type StockItem = {
costPriceGBP: number
}
interface StockItemSearchProps {
stockItems: StockItem[];
selectedItemName: string;
@ -35,6 +37,9 @@ interface StockItemSearchProps {
placeholder: string;
}
export function StockItemSearch({ stockItems, selectedItemName, onSelectItem, placeholder }: StockItemSearchProps) {
const [open, setOpen] = React.useState(false)
const [search, setSearch] = React.useState("")

View file

@ -9,6 +9,9 @@ export default defineConfig(() => ({
host: "::",
port: 8080,
},
preview: {
allowedHosts: ["jeistock.apphub.co.za"],
},
plugins: [
dyadComponentTagger(),
react(),