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/components/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/wcfs/NAOPOS/src/components/CatalogManager.tsx
import { useEffect, useState } from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import {
  CatalogProduct,
  defaultCatalog,
  saveCatalogDetailed,
} from "@/lib/catalog";
import { reportBuckets } from "@/lib/posSettings";

interface CatalogManagerProps {
  open: boolean;
  onClose: () => void;
  products: CatalogProduct[];
  onSaved: (products: CatalogProduct[]) => void;
}

function slugify(value: string): string {
  return value
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 40);
}

const CatalogManager = ({
  open,
  onClose,
  products,
  onSaved,
}: CatalogManagerProps) => {
  const [draft, setDraft] = useState<CatalogProduct[]>(products);
  const [status, setStatus] = useState("");

  useEffect(() => {
    setDraft(products);
  }, [products, open]);

  const updateProduct = (
    id: string,
    field: keyof CatalogProduct,
    value: string | number | boolean | undefined,
  ) => {
    setDraft((prev) =>
      prev.map((product) => {
        if (product.id !== id) return product;

        const next = { ...product, [field]: value };
        if (field === "taxable" && value === false) {
          next.taxRate = undefined;
        }
        if (field === "taxable" && value === true && next.taxRate == null) {
          next.taxRate = 0;
        }
        return next;
      }),
    );
  };

  const addProduct = () => {
    const index = draft.length + 1;
    setDraft((prev) => [
      ...prev,
      {
        id: `custom-${index}`,
        name: `New Item ${index}`,
        price: 0,
        category: "General",
        emoji: "ITEM",
        imageUrl: "",
        taxable: false,
        reportCategory: "Non-Taxable",
      },
    ]);
  };

  const removeProduct = (id: string) => {
    setDraft((prev) => prev.filter((product) => product.id !== id));
  };

  const handleSave = async () => {
    const cleaned = draft.map((product, index) => ({
      ...product,
      id: slugify(product.id || product.name || `product-${index + 1}`),
      name: product.name.trim() || `Item ${index + 1}`,
      category: product.category.trim() || "General",
      emoji: product.emoji.trim() || "ITEM",
      imageUrl: product.imageUrl?.trim() || undefined,
      price: Number(product.price) || 0,
      taxRate: product.taxable ? Number(product.taxRate || 0) : undefined,
    }));

    const result = await saveCatalogDetailed(cleaned);
    setStatus(result.ok ? "Saved for all devices" : result.error || "Save failed");
    if (result.ok) {
      onSaved(cleaned);
    }
  };

  const handleResetDefaults = async () => {
    const defaults = defaultCatalog();
    const result = await saveCatalogDetailed(defaults);
    setStatus(result.ok ? "Reset to default catalog" : result.error || "Reset failed");
    if (result.ok) {
      setDraft(defaults);
      onSaved(defaults);
    }
  };

  return (
    <Dialog open={open} onOpenChange={onClose}>
      <DialogContent className="max-h-[90vh] overflow-y-auto border-border bg-card sm:max-w-5xl">
        <DialogHeader>
          <DialogTitle className="text-lg font-extrabold text-foreground">
            Catalog Manager
          </DialogTitle>
          <p className="text-sm text-muted-foreground">
            Regular product buttons are saved on the server here.
          </p>
        </DialogHeader>

        <div className="space-y-3">
          {draft.map((product) => (
            <div
              key={product.id}
              className="grid grid-cols-1 gap-2 rounded-xl border border-border bg-background p-3 md:grid-cols-7"
            >
              <input
                value={product.name}
                onChange={(event) =>
                  updateProduct(product.id, "name", event.target.value)
                }
                className="rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground"
                placeholder="Name"
              />
              <input
                value={product.category}
                onChange={(event) =>
                  updateProduct(product.id, "category", event.target.value)
                }
                className="rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground"
                placeholder="Category"
              />
              <input
                type="number"
                min="0"
                step="0.01"
                value={product.price}
                onChange={(event) =>
                  updateProduct(
                    product.id,
                    "price",
                    parseFloat(event.target.value || "0"),
                  )
                }
                className="rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground"
                placeholder="Price"
              />
              <input
                value={product.emoji}
                onChange={(event) =>
                  updateProduct(product.id, "emoji", event.target.value)
                }
                className="rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground"
                placeholder="Button tag"
              />
              <input
                value={product.imageUrl ?? ""}
                onChange={(event) =>
                  updateProduct(product.id, "imageUrl", event.target.value)
                }
                className="rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground"
                placeholder="Image URL"
              />
              <select
                value={product.reportCategory}
                onChange={(event) =>
                  updateProduct(
                    product.id,
                    "reportCategory",
                    event.target.value,
                  )
                }
                className="rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground"
              >
                {reportBuckets.map((bucket) => (
                  <option key={bucket} value={bucket}>
                    {bucket}
                  </option>
                ))}
              </select>
              <div className="flex gap-2">
                <select
                  value={product.taxable ? "taxable" : "non-taxable"}
                  onChange={(event) =>
                    updateProduct(
                      product.id,
                      "taxable",
                      event.target.value === "taxable",
                    )
                  }
                  className="flex-1 rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground"
                >
                  <option value="taxable">Taxable</option>
                  <option value="non-taxable">Non-Taxable</option>
                </select>
                <input
                  type="number"
                  min="0"
                  step="0.01"
                  disabled={!product.taxable}
                  value={product.taxRate ?? ""}
                  onChange={(event) =>
                    updateProduct(
                      product.id,
                      "taxRate",
                      event.target.value === ""
                        ? undefined
                        : parseFloat(event.target.value),
                    )
                  }
                  className="w-28 rounded-lg border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground disabled:opacity-50"
                  placeholder="Tax %"
                />
                <button
                  onClick={() => removeProduct(product.id)}
                  className="rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-xs font-bold text-destructive"
                >
                  Remove
                </button>
              </div>
            </div>
          ))}
        </div>

        <div className="flex items-center justify-between gap-2 pt-2">
          <div className="text-xs font-semibold text-muted-foreground">
            {status}
          </div>
          <div className="flex gap-2">
            <button
              onClick={addProduct}
              className="rounded-lg bg-secondary px-4 py-2 text-sm font-bold text-secondary-foreground"
            >
              Add Product
            </button>
            <button
              onClick={handleResetDefaults}
              className="rounded-lg border border-border bg-background px-4 py-2 text-sm font-bold text-foreground"
            >
              Reset Defaults
            </button>
            <button
              onClick={handleSave}
              className="rounded-lg bg-primary px-4 py-2 text-sm font-bold text-primary-foreground"
            >
              Save Catalog
            </button>
          </div>
        </div>
      </DialogContent>
    </Dialog>
  );
};

export default CatalogManager;

Youez - 2016 - github.com/yon3zu
LinuXploit