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:
Suriya
2026-07-16 18:19:41 +05:30
parent 32291cddbe
commit 186546f1fe
3 changed files with 33 additions and 13 deletions

View File

@@ -1,9 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { X, Save, AlertCircle } from 'lucide-react'; import { X, Save, AlertCircle } from 'lucide-react';
import { CatalogueProduct, ImportCatalogueProductRequest } from '../services/catalogueApi'; import { CatalogueProduct, ImportCatalogueProductRequest } from '../services/catalogueApi';
import { useProductSubcategories } from '../hooks/useCatalogueImport'; import { useProductSubcategories, useTenantCategories } from '../hooks/useCatalogueImport';
import { useFiestaProductCategories } from '../services/fiestaQueries';
import { str as fstr } from '../services/fiestaApi';
interface ImportProductModalProps { interface ImportProductModalProps {
product: CatalogueProduct; product: CatalogueProduct;
@@ -27,14 +25,14 @@ export default function ImportProductModal({
const [taxPercent, setTaxPercent] = useState<string>('0'); const [taxPercent, setTaxPercent] = useState<string>('0');
const [quantity, setQuantity] = useState<string>('1'); const [quantity, setQuantity] = useState<string>('1');
const { data: categoriesData = [], isLoading: isLoadingCategories } = useFiestaProductCategories(); // Categories this tenant's own products actually use — the global
const categories = categoriesData.map((c: any) => ({ // categories list is missing categoryids that are nonetheless in real use.
categoryid: Number(c.categoryid), const { data: categories = [], isLoading: isLoadingCategories } = useTenantCategories(tenantid);
categoryname: fstr(c.categoryname),
}));
// Subcategories are scoped to the selected category, so a product can't be // 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. // 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( const { data: subcategories = [], isLoading: isLoadingSubcats } = useProductSubcategories(
tenantid, tenantid,
categoryId ? Number(categoryId) : undefined, categoryId ? Number(categoryId) : undefined,
@@ -42,7 +40,7 @@ export default function ImportProductModal({
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!categoryId || !subcategoryId || !retailPrice || !productCost) { if (!categoryId || !retailPrice || !productCost) {
alert("Please fill in all required fields."); alert("Please fill in all required fields.");
return; return;
} }
@@ -53,7 +51,7 @@ export default function ImportProductModal({
brand: product.brand, brand: product.brand,
catalogueid: product.id, catalogueid: product.id,
categoryid: Number(categoryId), categoryid: Number(categoryId),
subcategoryid: Number(subcategoryId), subcategoryid: subcategoryId ? Number(subcategoryId) : 0,
quantity: Number(quantity), quantity: Number(quantity),
stocktype: "in", stocktype: "in",
status: "Active", status: "Active",
@@ -112,7 +110,7 @@ export default function ImportProductModal({
)} )}
</div> </div>
<div className="space-y-1.5"> <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 ? ( {!categoryId ? (
<div className="w-full px-3 py-2 border border-slate-200 rounded-lg text-slate-400">Select a category first</div> <div className="w-full px-3 py-2 border border-slate-200 rounded-lg text-slate-400">Select a category first</div>
) : isLoadingSubcats ? ( ) : isLoadingSubcats ? (
@@ -122,9 +120,8 @@ export default function ImportProductModal({
value={subcategoryId} value={subcategoryId}
onChange={e => setSubcategoryId(e.target.value)} 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" 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) => ( {subcategories.map((s: any) => (
<option key={s.subcategoryid || s.id} value={s.subcategoryid || s.id}> <option key={s.subcategoryid || s.id} value={s.subcategoryid || s.id}>
{s.subcategoryname || s.name} {s.subcategoryname || s.name}

View File

@@ -5,6 +5,7 @@ import {
importCatalogueProducts, importCatalogueProducts,
getBrands, getBrands,
getProductSubcategories, getProductSubcategories,
getTenantCategories,
removeFromStoreCatalogue, removeFromStoreCatalogue,
ImportCatalogueProductRequest ImportCatalogueProductRequest
} from '../services/catalogueApi'; } 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) { export function useImportCatalogueProduct(tenantid: number, locationid: number) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({

View File

@@ -122,3 +122,18 @@ export async function getProductSubcategories(tenantid: number, categoryid?: num
const { items } = await apiGet<{ subcategoryid: number; subcategoryname: string }>(url); const { items } = await apiGet<{ subcategoryid: number; subcategoryname: string }>(url);
return items; 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;
}