Make subcategory optional, source category picker from real tenant data
Matches the backend: subcategory is no longer a required field in the import modal (defaults to none/0 instead of blocking submission), and the Category dropdown now comes from GET gettenantcategories (what this tenant's own products actually use) instead of the global categories list, which was missing categoryid 2 despite it being the category tenant 1135 actually needs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { X, Save, AlertCircle } from 'lucide-react';
|
||||
import { CatalogueProduct, ImportCatalogueProductRequest } from '../services/catalogueApi';
|
||||
import { useProductSubcategories } from '../hooks/useCatalogueImport';
|
||||
import { useFiestaProductCategories } from '../services/fiestaQueries';
|
||||
import { str as fstr } from '../services/fiestaApi';
|
||||
import { useProductSubcategories, useTenantCategories } from '../hooks/useCatalogueImport';
|
||||
|
||||
interface ImportProductModalProps {
|
||||
product: CatalogueProduct;
|
||||
@@ -27,14 +25,14 @@ export default function ImportProductModal({
|
||||
const [taxPercent, setTaxPercent] = useState<string>('0');
|
||||
const [quantity, setQuantity] = useState<string>('1');
|
||||
|
||||
const { data: categoriesData = [], isLoading: isLoadingCategories } = useFiestaProductCategories();
|
||||
const categories = categoriesData.map((c: any) => ({
|
||||
categoryid: Number(c.categoryid),
|
||||
categoryname: fstr(c.categoryname),
|
||||
}));
|
||||
// Categories this tenant's own products actually use — the global
|
||||
// categories list is missing categoryids that are nonetheless in real use.
|
||||
const { data: categories = [], isLoading: isLoadingCategories } = useTenantCategories(tenantid);
|
||||
|
||||
// Subcategories are scoped to the selected category, so a product can't be
|
||||
// tagged with a subcategory that doesn't actually belong to its category.
|
||||
// Optional: many subcategories are unowned/shared and a product can be
|
||||
// imported without one (falls back to "Uncategorized" in subcategory views).
|
||||
const { data: subcategories = [], isLoading: isLoadingSubcats } = useProductSubcategories(
|
||||
tenantid,
|
||||
categoryId ? Number(categoryId) : undefined,
|
||||
@@ -42,7 +40,7 @@ export default function ImportProductModal({
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!categoryId || !subcategoryId || !retailPrice || !productCost) {
|
||||
if (!categoryId || !retailPrice || !productCost) {
|
||||
alert("Please fill in all required fields.");
|
||||
return;
|
||||
}
|
||||
@@ -53,7 +51,7 @@ export default function ImportProductModal({
|
||||
brand: product.brand,
|
||||
catalogueid: product.id,
|
||||
categoryid: Number(categoryId),
|
||||
subcategoryid: Number(subcategoryId),
|
||||
subcategoryid: subcategoryId ? Number(subcategoryId) : 0,
|
||||
quantity: Number(quantity),
|
||||
stocktype: "in",
|
||||
status: "Active",
|
||||
@@ -112,7 +110,7 @@ export default function ImportProductModal({
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-semibold text-slate-600 uppercase tracking-wider">Subcategory *</label>
|
||||
<label className="text-xs font-semibold text-slate-600 uppercase tracking-wider">Subcategory (optional)</label>
|
||||
{!categoryId ? (
|
||||
<div className="w-full px-3 py-2 border border-slate-200 rounded-lg text-slate-400">Select a category first</div>
|
||||
) : isLoadingSubcats ? (
|
||||
@@ -122,9 +120,8 @@ export default function ImportProductModal({
|
||||
value={subcategoryId}
|
||||
onChange={e => setSubcategoryId(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:border-purple-500 transition-colors bg-white"
|
||||
required
|
||||
>
|
||||
<option value="">Select subcategory...</option>
|
||||
<option value="">None</option>
|
||||
{subcategories.map((s: any) => (
|
||||
<option key={s.subcategoryid || s.id} value={s.subcategoryid || s.id}>
|
||||
{s.subcategoryname || s.name}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
importCatalogueProducts,
|
||||
getBrands,
|
||||
getProductSubcategories,
|
||||
getTenantCategories,
|
||||
removeFromStoreCatalogue,
|
||||
ImportCatalogueProductRequest
|
||||
} from '../services/catalogueApi';
|
||||
@@ -39,6 +40,13 @@ export function useProductSubcategories(tenantid: number, categoryid?: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useTenantCategories(tenantid: number) {
|
||||
return useQuery({
|
||||
queryKey: ['catalogue', 'tenantCategories', tenantid],
|
||||
queryFn: () => getTenantCategories(tenantid),
|
||||
});
|
||||
}
|
||||
|
||||
export function useImportCatalogueProduct(tenantid: number, locationid: number) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
|
||||
@@ -122,3 +122,18 @@ export async function getProductSubcategories(tenantid: number, categoryid?: num
|
||||
const { items } = await apiGet<{ subcategoryid: number; subcategoryname: string }>(url);
|
||||
return items;
|
||||
}
|
||||
|
||||
export interface TenantCategory {
|
||||
categoryid: number;
|
||||
categoryname: string;
|
||||
}
|
||||
|
||||
// Categories this tenant's own products actually use — not the global
|
||||
// productcategories list, which is missing categoryids that are nonetheless
|
||||
// in real use (e.g. categoryid 2), which would make the picker unusable.
|
||||
export async function getTenantCategories(tenantid: number) {
|
||||
const url = new URL(`${API_BASE}/products/gettenantcategories`);
|
||||
url.searchParams.set("tenantid", String(tenantid));
|
||||
const { items } = await apiGet<TenantCategory>(url);
|
||||
return items;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user