403Webshell
Server IP : 173.209.174.21  /  Your IP : 216.73.216.89
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux wcfs-server 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64
User : nodor ( 1000)
PHP Version : 8.3.6
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/wcfs/NAOPOS/src/pages/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/wcfs/NAOPOS/src/pages/Index.tsx
import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import {
  ClipboardList,
  Printer,
  PrinterCheck,
  Settings2,
  Store,
} from "lucide-react";
import CategoryBar from "@/components/CategoryBar";
import ProductGrid from "@/components/ProductGrid";
import Cart, { CartItem } from "@/components/Cart";
import Numpad from "@/components/Numpad";
import PaymentModal from "@/components/PaymentModal";
import SalesReport from "@/components/SalesReport";
import CatalogManager from "@/components/CatalogManager";
import {
  CatalogProduct,
  defaultCatalog,
  deriveCategories,
  loadCatalog,
  saveCatalog,
} from "@/lib/catalog";
import { Product } from "@/data/products";
import {
  PaymentMethod,
  ReportCategory,
  SaleItem,
  SaleRecord,
} from "@/types/sales";
import { formatBusinessDate, normalizeReportCategory } from "@/lib/reporting";
import {
  clearSalesOnServer,
  clearStoredSales,
  loadSalesFromServer,
  loadStoredSales,
  saveSaleToServer,
  saveStoredSales,
} from "@/lib/salesStore";
import {
  ReceiptData,
  connectPrinter,
  disconnectPrinter,
  isPrinterConnected,
  isWebSerialSupported,
  kickDrawer,
  printReceiptAndKickDrawer,
} from "@/lib/printer";

let nextManualId = 1000;

function getProductReportCategory(product: Product): ReportCategory {
  if (product.reportCategory) return product.reportCategory;
  return normalizeReportCategory(product.category, Boolean(product.taxRate));
}

const Index = () => {
  const businessDate = formatBusinessDate(new Date());
  const [category, setCategory] = useState("All");
  const [cart, setCart] = useState<CartItem[]>([]);
  const [paymentOpen, setPaymentOpen] = useState(false);
  const [reportOpen, setReportOpen] = useState(false);
  const [catalogOpen, setCatalogOpen] = useState(false);
  const [sales, setSales] = useState<SaleRecord[]>([]);
  const [catalog, setCatalog] = useState<CatalogProduct[]>(defaultCatalog());
  const [printerConnected, setPrinterConnected] = useState(false);

  const categories = useMemo(() => deriveCategories(catalog), [catalog]);
  const filtered = useMemo(
    () =>
      category === "All"
        ? catalog
        : catalog.filter((product) => product.category === category),
    [catalog, category],
  );

  const addToCart = useCallback((product: Product) => {
    setCart((prev) => {
      const existing = prev.find((item) => item.id === product.id);
      if (existing) {
        return prev.map((item) =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item,
        );
      }

      return [
        ...prev,
        {
          ...product,
          quantity: 1,
          taxRate: product.taxRate,
          reportCategory: getProductReportCategory(product),
          taxable: Boolean(product.taxRate),
        },
      ];
    });
  }, []);

  const addManualItem = useCallback(
    (
      amount: number,
      label: string,
      taxable: boolean,
      taxRate?: number,
      reportCategory?: ReportCategory,
    ) => {
      const id = `manual-${nextManualId++}`;
      const displayLabel = taxable ? `${label} (T)` : label;

      setCart((prev) => [
        ...prev,
        {
          id,
          name: displayLabel,
          price: amount,
          emoji: taxable ? "TAX" : "NTX",
          quantity: 1,
          taxRate: taxable ? taxRate : undefined,
          category: label,
          reportCategory:
            reportCategory ?? normalizeReportCategory(label, taxable),
          taxable,
        },
      ]);
    },
    [],
  );

  const updateQty = useCallback((id: string, delta: number) => {
    setCart((prev) =>
      prev
        .map((item) =>
          item.id === id ? { ...item, quantity: item.quantity + delta } : item,
        )
        .filter((item) => item.quantity > 0),
    );
  }, []);

  const removeItem = useCallback((id: string) => {
    setCart((prev) => prev.filter((item) => item.id !== id));
  }, []);

  const clearCart = useCallback(() => setCart([]), []);

  const handleCheckout = useCallback(() => {
    if (cart.length === 0) return;
    setPaymentOpen(true);
  }, [cart]);

  const handleConnectPrinter = useCallback(async () => {
    if (isPrinterConnected()) {
      await disconnectPrinter();
      setPrinterConnected(false);
      toast.info("Printer disconnected");
      return;
    }

    if (!isWebSerialSupported()) {
      toast.error("WebSerial not supported. Use Chrome or Edge.");
      return;
    }

    const connected = await connectPrinter();
    setPrinterConnected(connected);
    if (connected) {
      toast.success("Printer connected");
      return;
    }

    toast.error("Could not connect to printer");
  }, []);

  const handleOpenDrawer = useCallback(async () => {
    if (!isPrinterConnected()) {
      toast.error("Connect a printer first");
      return;
    }

    try {
      await kickDrawer();
      toast.success("Drawer opened");
    } catch {
      toast.error("Failed to open drawer");
    }
  }, []);

  useEffect(() => {
    let mounted = true;

    const fetchCatalog = async () => {
      const serverCatalog = await loadCatalog();
      if (!mounted) return;

      if (serverCatalog && serverCatalog.length > 0) {
        setCatalog(serverCatalog);
        return;
      }

      setCatalog(defaultCatalog());
    };

    fetchCatalog();

    return () => {
      mounted = false;
    };
  }, []);

  useEffect(() => {
    let mounted = true;

    const loadSales = async () => {
      const serverSales = await loadSalesFromServer(businessDate);
      const nextSales = serverSales ?? loadStoredSales(businessDate);

      if (!mounted) return;
      setSales(nextSales);
    };

    loadSales();

    return () => {
      mounted = false;
    };
  }, [businessDate]);

  useEffect(() => {
    saveStoredSales(sales);
  }, [sales]);

  const completePayment = useCallback(
    async (method: PaymentMethod, amountTendered?: number) => {
      const subtotal = cart.reduce(
        (sum, item) => sum + item.price * item.quantity,
        0,
      );
      const tax = cart.reduce((sum, item) => {
        if (item.taxRate) {
          return sum + item.price * item.quantity * (item.taxRate / 100);
        }
        return sum;
      }, 0);
      const total = subtotal + tax;
      const changeDue = amountTendered ? amountTendered - total : undefined;

      const saleItems: SaleItem[] = cart.map((item) => {
        const lineTotal = item.price * item.quantity;
        const taxAmount = item.taxRate
          ? lineTotal * (item.taxRate / 100)
          : 0;

        return {
          name: item.name,
          category: item.category || "Other",
          reportCategory:
            item.reportCategory ||
            normalizeReportCategory(
              item.category || item.name,
              Boolean(item.taxRate),
            ),
          price: item.price,
          quantity: item.quantity,
          taxable: Boolean(item.taxRate || item.taxable),
          taxRate: item.taxRate,
          taxAmount,
          lineTotal,
        };
      });

      const timestamp = new Date();
      const record: SaleRecord = {
        id: `sale-${Date.now()}`,
        items: saleItems,
        subtotal,
        tax,
        total,
        paymentMethod: method,
        amountTendered,
        changeDue,
        timestamp,
        businessDate: formatBusinessDate(timestamp),
      };

      const nextSales = [...sales, record];
      setSales(nextSales);
      setPaymentOpen(false);
      setCart([]);

      const savedToServer = await saveSaleToServer(record);

      if (isPrinterConnected()) {
        try {
          const receiptData: ReceiptData = {
            storeName: "My Store",
            items: saleItems.map((item) => ({
              name: item.name,
              quantity: item.quantity,
              price: item.price,
              taxRate: item.taxRate,
              taxAmount: item.taxAmount,
            })),
            subtotal,
            tax,
            total,
            paymentMethod: method,
            amountTendered,
            changeDue:
              changeDue !== undefined && changeDue > 0 ? changeDue : undefined,
            timestamp,
          };
          await printReceiptAndKickDrawer(receiptData);
        } catch (error) {
          console.error("Print error:", error);
          toast.error("Failed to print receipt");
        }
      }

      const methodLabel = method === "cash" ? "Cash" : "Credit Card";
      const changeLabel =
        changeDue !== undefined && changeDue > 0
          ? ` | Change $${changeDue.toFixed(2)}`
          : "";
      const savedLabel = savedToServer ? "" : " | saved locally";

      toast.success(
        `Sale complete: ${methodLabel} $${total.toFixed(2)}${changeLabel}${savedLabel}`,
        { duration: 4000 },
      );
    },
    [cart, sales],
  );

  const handleClearSales = useCallback(async () => {
    const clearedOnServer = await clearSalesOnServer(businessDate);
    clearStoredSales(businessDate);
    setSales([]);

    if (clearedOnServer) {
      toast.success("Started a new business day");
      return;
    }

    toast.success("Started a new business day locally");
  }, [businessDate]);

  const handleCatalogSaved = useCallback((products: CatalogProduct[]) => {
    setCatalog(products);
    if (category !== "All" && !products.some((item) => item.category === category)) {
      setCategory("All");
    }
    toast.success("Catalog saved for all devices");
  }, [category]);

  const handleCatalogReorder = useCallback(
    async (activeId: string, overId: string) => {
      const activeIndex = catalog.findIndex((item) => item.id === activeId);
      const overIndex = catalog.findIndex((item) => item.id === overId);
      if (activeIndex < 0 || overIndex < 0 || activeIndex === overIndex) {
        return;
      }

      const nextCatalog = [...catalog];
      const [moved] = nextCatalog.splice(activeIndex, 1);
      nextCatalog.splice(overIndex, 0, moved);
      setCatalog(nextCatalog);

      const saved = await saveCatalog(nextCatalog);
      if (!saved) {
        toast.error("Could not save button order");
        return;
      }

      toast.success("Product button order saved");
    },
    [catalog],
  );

  return (
    <div className="flex h-screen flex-col bg-background">
      <header className="flex items-center gap-3 border-b border-border bg-card px-4 py-3 shadow-sm">
        <Store className="h-7 w-7 text-primary" />
        <h1 className="text-xl font-extrabold tracking-tight text-foreground">
          My Store
        </h1>
        <div className="ml-auto flex items-center gap-2">
          <button
            onClick={() => setCatalogOpen(true)}
            className="flex items-center gap-1.5 rounded-lg border border-border bg-secondary px-3 py-1.5 text-sm font-bold text-foreground transition-all hover:bg-primary hover:text-primary-foreground"
          >
            <Settings2 className="h-4 w-4" />
            <span>Catalog</span>
          </button>
          <button
            onClick={handleConnectPrinter}
            className={`flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-bold transition-all ${
              printerConnected
                ? "border-success/30 bg-success/15 text-success hover:bg-success hover:text-success-foreground"
                : "border-border bg-secondary text-foreground hover:bg-primary hover:text-primary-foreground"
            }`}
          >
            {printerConnected ? (
              <PrinterCheck className="h-4 w-4" />
            ) : (
              <Printer className="h-4 w-4" />
            )}
            <span className="hidden sm:inline">
              {printerConnected ? "Printer Connected" : "Connect Printer"}
            </span>
          </button>
          {printerConnected && (
            <button
              onClick={handleOpenDrawer}
              className="flex items-center gap-1.5 rounded-lg border border-border bg-secondary px-3 py-1.5 text-sm font-bold text-foreground transition-all hover:bg-primary hover:text-primary-foreground"
            >
              <span>Open Drawer</span>
            </button>
          )}
          <button
            onClick={() => setReportOpen(true)}
            className="flex items-center gap-1.5 rounded-lg border border-border bg-secondary px-3 py-1.5 text-sm font-bold text-foreground transition-all hover:bg-primary hover:text-primary-foreground"
          >
            <ClipboardList className="h-4 w-4" />
            <span>Report</span>
            {sales.length > 0 && (
              <span className="rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-bold text-primary-foreground">
                {sales.length}
              </span>
            )}
          </button>
          <span className="text-sm text-muted-foreground">Point of Sale</span>
        </div>
      </header>

      <div className="flex flex-1 overflow-hidden">
        <div className="hidden w-[260px] flex-shrink-0 overflow-y-auto p-3 md:block lg:w-[280px]">
          <Numpad onAddItem={addManualItem} />
        </div>

        <div className="flex flex-1 flex-col gap-3 overflow-y-auto p-4">
          <CategoryBar
            categories={categories}
            selected={category}
            onSelect={setCategory}
          />
          <ProductGrid
            products={filtered}
            onAdd={addToCart}
            onReorder={handleCatalogReorder}
          />
        </div>

        <div className="hidden w-[340px] flex-shrink-0 p-3 pl-0 md:flex lg:w-[380px]">
          <Cart
            items={cart}
            onUpdateQty={updateQty}
            onRemove={removeItem}
            onCheckout={handleCheckout}
            onClear={clearCart}
          />
        </div>
      </div>

      {cart.length > 0 && (
        <div className="border-t border-border bg-card px-4 py-3 md:hidden">
          <button
            onClick={handleCheckout}
            className="w-full rounded-xl bg-success py-3 text-base font-bold text-success-foreground shadow-md active:scale-[0.98]"
          >
            Checkout | {cart.reduce((sum, item) => sum + item.quantity, 0)} items
            {" | $"}
            {cart
              .reduce((sum, item) => sum + item.price * item.quantity, 0)
              .toFixed(2)}
          </button>
        </div>
      )}

      <PaymentModal
        open={paymentOpen}
        onClose={() => setPaymentOpen(false)}
        onComplete={completePayment}
        items={cart}
      />

      <SalesReport
        open={reportOpen}
        onClose={() => setReportOpen(false)}
        sales={sales}
        onClearSales={handleClearSales}
      />

      <CatalogManager
        open={catalogOpen}
        onClose={() => setCatalogOpen(false)}
        products={catalog}
        onSaved={handleCatalogSaved}
      />
    </div>
  );
};

export default Index;

Youez - 2016 - github.com/yon3zu
LinuXploit