diff --git a/inventory-server/src/routes/hts-lookup.js b/inventory-server/src/routes/hts-lookup.js new file mode 100644 index 0000000..9fa8f60 --- /dev/null +++ b/inventory-server/src/routes/hts-lookup.js @@ -0,0 +1,170 @@ +const express = require('express'); +const router = express.Router(); + +// GET /api/hts-lookup?search=term +// Finds matching products and groups them by harmonized tariff code +router.get('/', async (req, res) => { + const searchTerm = typeof req.query.search === 'string' ? req.query.search.trim() : ''; + + if (!searchTerm) { + return res.status(400).json({ error: 'Search term is required' }); + } + + try { + const pool = req.app.locals.pool; + const likeTerm = `%${searchTerm}%`; + + const { rows } = await pool.query( + ` + WITH matched_products AS ( + SELECT + pid, + title, + sku, + barcode, + brand, + vendor, + harmonized_tariff_code, + NULLIF( + LOWER( + REGEXP_REPLACE( + COALESCE(NULLIF(TRIM(harmonized_tariff_code), ''), ''), + '[^0-9A-Za-z]', + '', + 'g' + ) + ), + '' + ) AS normalized_code + FROM products + WHERE visible = TRUE + AND ( + title ILIKE $1 + OR sku ILIKE $1 + OR barcode ILIKE $1 + OR vendor ILIKE $1 + OR brand ILIKE $1 + OR vendor_reference ILIKE $1 + OR harmonized_tariff_code ILIKE $1 + ) + ), + grouped AS ( + SELECT + COALESCE(NULLIF(TRIM(harmonized_tariff_code), ''), 'Unspecified') AS harmonized_tariff_code, + normalized_code, + COUNT(*)::INT AS product_count, + json_agg( + json_build_object( + 'pid', pid, + 'title', title, + 'sku', sku, + 'barcode', barcode, + 'brand', brand, + 'vendor', vendor + ) + ORDER BY title + ) AS products + FROM matched_products + GROUP BY + COALESCE(NULLIF(TRIM(harmonized_tariff_code), ''), 'Unspecified'), + normalized_code + ), + hts_lookup AS ( + SELECT + h."HTS Number" AS hts_number, + h."Indent" AS indent, + h."Description" AS description, + h."Unit of Quantity" AS unit_of_quantity, + h."General Rate of Duty" AS general_rate_of_duty, + h."Special Rate of Duty" AS special_rate_of_duty, + h."Column 2 Rate of Duty" AS column2_rate_of_duty, + h."Quota Quantity" AS quota_quantity, + h."Additional Duties" AS additional_duties, + NULLIF( + LOWER( + REGEXP_REPLACE( + COALESCE(h."HTS Number", ''), + '[^0-9A-Za-z]', + '', + 'g' + ) + ), + '' + ) AS normalized_hts_number + FROM htsdata h + ) + SELECT + g.harmonized_tariff_code, + g.product_count, + g.products, + hts.hts_details + FROM grouped g + LEFT JOIN LATERAL ( + SELECT json_agg( + json_build_object( + 'hts_number', h.hts_number, + 'indent', h.indent, + 'description', h.description, + 'unit_of_quantity', h.unit_of_quantity, + 'general_rate_of_duty', h.general_rate_of_duty, + 'special_rate_of_duty', h.special_rate_of_duty, + 'column2_rate_of_duty', h.column2_rate_of_duty, + 'quota_quantity', h.quota_quantity, + 'additional_duties', h.additional_duties + ) + ORDER BY LENGTH(COALESCE(h.normalized_hts_number, '')) ASC NULLS LAST, + NULLIF(h.indent, '')::INT NULLS LAST + ) AS hts_details + FROM hts_lookup h + WHERE COALESCE(g.normalized_code, '') <> '' + AND COALESCE(h.normalized_hts_number, '') <> '' + AND ( + g.normalized_code LIKE h.normalized_hts_number || '%' + OR h.normalized_hts_number LIKE g.normalized_code || '%' + ) + ) hts ON TRUE + ORDER BY g.product_count DESC, g.harmonized_tariff_code ASC + `, + [likeTerm] + ); + + const totalMatches = rows.reduce((sum, row) => sum + (parseInt(row.product_count, 10) || 0), 0); + + res.json({ + search: searchTerm, + total: totalMatches, + results: rows.map(row => ({ + harmonized_tariff_code: row.harmonized_tariff_code, + product_count: parseInt(row.product_count, 10) || 0, + hts_details: Array.isArray(row.hts_details) + ? row.hts_details.map(detail => ({ + hts_number: detail.hts_number, + indent: detail.indent, + description: detail.description, + unit_of_quantity: detail.unit_of_quantity, + general_rate_of_duty: detail.general_rate_of_duty, + special_rate_of_duty: detail.special_rate_of_duty, + column2_rate_of_duty: detail.column2_rate_of_duty, + quota_quantity: detail.quota_quantity, + additional_duties: detail.additional_duties + })) + : [], + products: Array.isArray(row.products) + ? row.products.map(product => ({ + pid: product.pid, + title: product.title, + sku: product.sku, + barcode: product.barcode, + brand: product.brand, + vendor: product.vendor + })) + : [] + })) + }); + } catch (error) { + console.error('Error performing HTS lookup:', error); + res.status(500).json({ error: 'Failed to lookup HTS codes' }); + } +}); + +module.exports = router; diff --git a/inventory-server/src/server.js b/inventory-server/src/server.js index c4a88b4..ac8a4d5 100644 --- a/inventory-server/src/server.js +++ b/inventory-server/src/server.js @@ -21,6 +21,7 @@ const reusableImagesRouter = require('./routes/reusable-images'); const categoriesAggregateRouter = require('./routes/categoriesAggregate'); const vendorsAggregateRouter = require('./routes/vendorsAggregate'); const brandsAggregateRouter = require('./routes/brandsAggregate'); +const htsLookupRouter = require('./routes/hts-lookup'); // Get the absolute path to the .env file const envPath = '/var/www/html/inventory/.env'; @@ -126,6 +127,7 @@ async function startServer() { app.use('/api/templates', templatesRouter); app.use('/api/ai-prompts', aiPromptsRouter); app.use('/api/reusable-images', reusableImagesRouter); + app.use('/api/hts-lookup', htsLookupRouter); // Basic health check route app.get('/health', (req, res) => { diff --git a/inventory/src/App.tsx b/inventory/src/App.tsx index c2539f2..c7cb5aa 100644 --- a/inventory/src/App.tsx +++ b/inventory/src/App.tsx @@ -22,6 +22,7 @@ const Products = lazy(() => import('./pages/Products').then(module => ({ default const Analytics = lazy(() => import('./pages/Analytics').then(module => ({ default: module.Analytics }))); const Forecasting = lazy(() => import('./pages/Forecasting')); const DiscountSimulator = lazy(() => import('./pages/DiscountSimulator')); +const HtsLookup = lazy(() => import('./pages/HtsLookup')); const Vendors = lazy(() => import('./pages/Vendors')); const Categories = lazy(() => import('./pages/Categories')); const Brands = lazy(() => import('./pages/Brands')); @@ -161,6 +162,13 @@ function App() { } /> + + }> + + + + } /> }> diff --git a/inventory/src/components/auth/FirstAccessiblePage.tsx b/inventory/src/components/auth/FirstAccessiblePage.tsx index d517b2a..dfa91c3 100644 --- a/inventory/src/components/auth/FirstAccessiblePage.tsx +++ b/inventory/src/components/auth/FirstAccessiblePage.tsx @@ -13,6 +13,7 @@ const PAGES = [ { path: "/purchase-orders", permission: "access:purchase_orders" }, { path: "/analytics", permission: "access:analytics" }, { path: "/discount-simulator", permission: "access:discount_simulator" }, + { path: "/hts-lookup", permission: "access:hts_lookup" }, { path: "/forecasting", permission: "access:forecasting" }, { path: "/import", permission: "access:import" }, { path: "/settings", permission: "access:settings" }, diff --git a/inventory/src/components/auth/PERMISSIONS.md b/inventory/src/components/auth/PERMISSIONS.md index b6f475c..1025600 100644 --- a/inventory/src/components/auth/PERMISSIONS.md +++ b/inventory/src/components/auth/PERMISSIONS.md @@ -134,6 +134,7 @@ Admin users automatically have all permissions. | `access:purchase_orders` | Access to Purchase Orders page | | `access:analytics` | Access to Analytics page | | `access:discount_simulator` | Access to Discount Simulator page | +| `access:hts_lookup` | Access to HTS Lookup page | | `access:forecasting` | Access to Forecasting page | | `access:import` | Access to Import page | | `access:settings` | Access to Settings page | diff --git a/inventory/src/components/layout/AppSidebar.tsx b/inventory/src/components/layout/AppSidebar.tsx index 9683b92..5ce772c 100644 --- a/inventory/src/components/layout/AppSidebar.tsx +++ b/inventory/src/components/layout/AppSidebar.tsx @@ -11,6 +11,7 @@ import { MessageCircle, LayoutDashboard, Percent, + FileSearch, } from "lucide-react"; import { IconCrystalBall } from "@tabler/icons-react"; import { @@ -94,6 +95,12 @@ const toolsItems = [ url: "/discount-simulator", permission: "access:discount_simulator" }, + { + title: "HTS Lookup", + icon: FileSearch, + url: "/hts-lookup", + permission: "access:hts_lookup" + }, { title: "Forecasting", icon: IconCrystalBall, diff --git a/inventory/src/pages/HtsLookup.tsx b/inventory/src/pages/HtsLookup.tsx new file mode 100644 index 0000000..e8faea7 --- /dev/null +++ b/inventory/src/pages/HtsLookup.tsx @@ -0,0 +1,340 @@ +import { useEffect, useMemo, useRef, useState, type FormEvent, type MouseEvent } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Search, Loader2, PackageOpen, Copy, Check } from "lucide-react"; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { useToast } from "@/hooks/use-toast"; +import { cn } from "@/lib/utils"; + +type HtsProduct = { + pid: number; + title: string; + sku: string; + brand?: string | null; + vendor?: string | null; + barcode?: string | null; +}; + +type HtsDetail = { + hts_number: string | null; + indent?: string | null; + description?: string | null; + unit_of_quantity?: string | null; + general_rate_of_duty?: string | null; + special_rate_of_duty?: string | null; + column2_rate_of_duty?: string | null; + quota_quantity?: string | null; + additional_duties?: string | null; +}; + +type HtsGroup = { + harmonized_tariff_code: string; + product_count: number; + products: HtsProduct[]; + hts_details?: HtsDetail[] | null; +}; + +type HtsLookupResponse = { + search: string; + total: number; + results: HtsGroup[]; +}; + +export default function HtsLookup() { + const { toast } = useToast(); + const [searchTerm, setSearchTerm] = useState(""); + const [submittedTerm, setSubmittedTerm] = useState(""); + const [copiedCode, setCopiedCode] = useState(null); + const copyTimerRef = useRef(null); + + const { + data, + error, + isFetching, + isFetched, + refetch, + } = useQuery({ + queryKey: ["hts-lookup", submittedTerm], + enabled: false, + queryFn: async () => { + const params = new URLSearchParams({ search: submittedTerm }); + const response = await fetch(`/api/hts-lookup?${params.toString()}`); + const payload = await response.json().catch(() => ({})); + + if (!response.ok) { + const message = typeof payload.error === "string" ? payload.error : "Failed to fetch HTS data"; + throw new Error(message); + } + + return payload as HtsLookupResponse; + }, + staleTime: 2 * 60 * 1000, + }); + + useEffect(() => { + if (submittedTerm) { + void refetch(); + } + }, [submittedTerm, refetch]); + + useEffect(() => { + return () => { + if (copyTimerRef.current) { + window.clearTimeout(copyTimerRef.current); + } + }; + }, []); + + useEffect(() => { + if (error instanceof Error) { + toast({ + title: "Search failed", + description: error.message, + variant: "destructive", + }); + } + }, [error, toast]); + + const totalMatches = data?.total ?? 0; + const groupedResults = useMemo(() => data?.results ?? [], [data]); + + const handleCopyClick = async (event: MouseEvent, code: string) => { + event.preventDefault(); + event.stopPropagation(); + const valueToCopy = code === "Unspecified" ? "" : code; + + if (!navigator?.clipboard) { + toast({ + title: "Clipboard unavailable", + description: "Your browser did not allow copying the code.", + variant: "destructive", + }); + return; + } + + try { + await navigator.clipboard.writeText(valueToCopy); + if (copyTimerRef.current) { + window.clearTimeout(copyTimerRef.current); + } + setCopiedCode(code); + copyTimerRef.current = window.setTimeout(() => setCopiedCode(null), 1200); + toast({ + title: "Copied HTS code", + description: valueToCopy || "Empty code copied", + }); + } catch (err) { + toast({ + title: "Copy failed", + description: err instanceof Error ? err.message : "Unable to copy code", + variant: "destructive", + }); + } + }; + + const handleSearch = (event?: FormEvent) => { + event?.preventDefault(); + const trimmed = searchTerm.trim(); + + if (!trimmed) { + toast({ + title: "Enter a search term", + description: "Search by title, SKU, vendor, barcode, or HTS code.", + }); + return; + } + + if (trimmed === submittedTerm) { + void refetch(); + } else { + setSubmittedTerm(trimmed); + } + }; + + const renderSummary = () => { + if (!isFetched || !data) return null; + + if (!groupedResults.length) { + return ( + + + +
No products found for "{data.search}".
+
+
+ ); + } + + return ( +
+ + + Search term + {data.search} + + + + + Matched products + {totalMatches} + + + + + Unique HTS codes + {groupedResults.length} + + +
+ ); + }; + + return ( +
+
+

HTS Lookup

+
+ + + + Search products + Search by product title, item number, company name, UPC, or HTS code. + + +
+ setSearchTerm(e.target.value)} + className="md:max-w-xl" + /> +
+ + {isFetched && ( + + )} +
+
+
+
+ + {renderSummary()} + + {groupedResults.length > 0 && ( + + + HTS codes by frequency + Most-used codes appear first. Expand a code to see the matching products. + + + + {groupedResults.map((group) => { + const percentage = totalMatches > 0 ? Math.round((group.product_count / totalMatches) * 100) : 0; + const codeLabel = group.harmonized_tariff_code === "Unspecified" ? "Not set" : group.harmonized_tariff_code; + const htsDetails = group.hts_details || []; + + return ( + + +
+
+
+ + + {codeLabel} + + + {group.product_count} product{group.product_count === 1 ? "" : "s"} + +
+ {htsDetails.length > 0 ? ( +
+ {htsDetails.map((detail, idx) => ( +
+ + {detail.hts_number || "—"} + + {detail.description || "No description"} +
+ ))} +
+ ) : ( +
No HTS reference found
+ )} +
+ {percentage}% of matches +
+
+ +
+ + + + Product + SKU + Brand + Vendor + Barcode + + + + {group.products.map((product) => ( + + + + {product.title} + + + {product.sku} + {product.brand || "—"} + {product.vendor || "—"} + + {product.barcode || "—"} + + + ))} + +
+
+
+
+ ); + })} +
+
+
+ )} +
+ ); +} diff --git a/inventory/tsconfig.tsbuildinfo b/inventory/tsconfig.tsbuildinfo index 68e62db..9ecdd48 100644 --- a/inventory/tsconfig.tsbuildinfo +++ b/inventory/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/config.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/auth/firstaccessiblepage.tsx","./src/components/auth/protected.tsx","./src/components/auth/requireauth.tsx","./src/components/chat/chatroom.tsx","./src/components/chat/chattest.tsx","./src/components/chat/roomlist.tsx","./src/components/chat/searchresults.tsx","./src/components/dashboard/financialoverview.tsx","./src/components/dashboard/periodselectionpopover.tsx","./src/components/discount-simulator/configpanel.tsx","./src/components/discount-simulator/resultschart.tsx","./src/components/discount-simulator/resultstable.tsx","./src/components/discount-simulator/summarycard.tsx","./src/components/forecasting/daterangepickerquick.tsx","./src/components/forecasting/quickorderbuilder.tsx","./src/components/forecasting/columns.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/layout/navuser.tsx","./src/components/overview/bestsellers.tsx","./src/components/overview/forecastmetrics.tsx","./src/components/overview/overstockmetrics.tsx","./src/components/overview/overview.tsx","./src/components/overview/purchasemetrics.tsx","./src/components/overview/replenishmentmetrics.tsx","./src/components/overview/salesmetrics.tsx","./src/components/overview/stockmetrics.tsx","./src/components/overview/topoverstockedproducts.tsx","./src/components/overview/topreplenishproducts.tsx","./src/components/overview/vendorperformance.tsx","./src/components/product-import/createproductcategorydialog.tsx","./src/components/product-import/reactspreadsheetimport.tsx","./src/components/product-import/config.ts","./src/components/product-import/index.ts","./src/components/product-import/translationsrsiprops.ts","./src/components/product-import/types.ts","./src/components/product-import/components/modalwrapper.tsx","./src/components/product-import/components/providers.tsx","./src/components/product-import/components/table.tsx","./src/components/product-import/hooks/usersi.ts","./src/components/product-import/steps/steps.tsx","./src/components/product-import/steps/uploadflow.tsx","./src/components/product-import/steps/imageuploadstep/imageuploadstep.tsx","./src/components/product-import/steps/imageuploadstep/types.ts","./src/components/product-import/steps/imageuploadstep/components/droppablecontainer.tsx","./src/components/product-import/steps/imageuploadstep/components/genericdropzone.tsx","./src/components/product-import/steps/imageuploadstep/components/unassignedimagessection.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/copybutton.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/imagedropzone.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/productcard.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/sortableimage.tsx","./src/components/product-import/steps/imageuploadstep/components/unassignedimagessection/unassignedimageitem.tsx","./src/components/product-import/steps/imageuploadstep/hooks/usebulkimageupload.ts","./src/components/product-import/steps/imageuploadstep/hooks/usedraganddrop.ts","./src/components/product-import/steps/imageuploadstep/hooks/useproductimageoperations.ts","./src/components/product-import/steps/imageuploadstep/hooks/useproductimagesinit.ts","./src/components/product-import/steps/imageuploadstep/hooks/useurlimageupload.ts","./src/components/product-import/steps/matchcolumnsstep/matchcolumnsstep.tsx","./src/components/product-import/steps/matchcolumnsstep/types.ts","./src/components/product-import/steps/matchcolumnsstep/components/matchicon.tsx","./src/components/product-import/steps/matchcolumnsstep/components/templatecolumn.tsx","./src/components/product-import/steps/matchcolumnsstep/utils/findmatch.ts","./src/components/product-import/steps/matchcolumnsstep/utils/findunmatchedrequiredfields.ts","./src/components/product-import/steps/matchcolumnsstep/utils/getfieldoptions.ts","./src/components/product-import/steps/matchcolumnsstep/utils/getmatchedcolumns.ts","./src/components/product-import/steps/matchcolumnsstep/utils/normalizecheckboxvalue.ts","./src/components/product-import/steps/matchcolumnsstep/utils/normalizetabledata.ts","./src/components/product-import/steps/matchcolumnsstep/utils/setcolumn.ts","./src/components/product-import/steps/matchcolumnsstep/utils/setignorecolumn.ts","./src/components/product-import/steps/matchcolumnsstep/utils/setsubcolumn.ts","./src/components/product-import/steps/matchcolumnsstep/utils/uniqueentries.ts","./src/components/product-import/steps/selectheaderstep/selectheaderstep.tsx","./src/components/product-import/steps/selectheaderstep/components/selectheadertable.tsx","./src/components/product-import/steps/selectheaderstep/components/columns.tsx","./src/components/product-import/steps/selectsheetstep/selectsheetstep.tsx","./src/components/product-import/steps/uploadstep/uploadstep.tsx","./src/components/product-import/steps/uploadstep/components/dropzone.tsx","./src/components/product-import/steps/uploadstep/components/columns.tsx","./src/components/product-import/steps/uploadstep/utils/readfilesasync.ts","./src/components/product-import/steps/validationstepnew/index.tsx","./src/components/product-import/steps/validationstepnew/types.ts","./src/components/product-import/steps/validationstepnew/components/aivalidationdialogs.tsx","./src/components/product-import/steps/validationstepnew/components/basecellcontent.tsx","./src/components/product-import/steps/validationstepnew/components/initializingvalidation.tsx","./src/components/product-import/steps/validationstepnew/components/searchabletemplateselect.tsx","./src/components/product-import/steps/validationstepnew/components/upcvalidationtableadapter.tsx","./src/components/product-import/steps/validationstepnew/components/validationcell.tsx","./src/components/product-import/steps/validationstepnew/components/validationcontainer.tsx","./src/components/product-import/steps/validationstepnew/components/validationtable.tsx","./src/components/product-import/steps/validationstepnew/components/cells/checkboxcell.tsx","./src/components/product-import/steps/validationstepnew/components/cells/inputcell.tsx","./src/components/product-import/steps/validationstepnew/components/cells/multiselectcell.tsx","./src/components/product-import/steps/validationstepnew/components/cells/multilineinput.tsx","./src/components/product-import/steps/validationstepnew/components/cells/selectcell.tsx","./src/components/product-import/steps/validationstepnew/hooks/useaivalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usefieldvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usefiltermanagement.tsx","./src/components/product-import/steps/validationstepnew/hooks/useinitialvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/useproductlinesfetching.tsx","./src/components/product-import/steps/validationstepnew/hooks/userowoperations.tsx","./src/components/product-import/steps/validationstepnew/hooks/usetemplatemanagement.tsx","./src/components/product-import/steps/validationstepnew/hooks/useuniqueitemnumbersvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/useuniquevalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/useupcvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usevalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usevalidationstate.tsx","./src/components/product-import/steps/validationstepnew/hooks/validationtypes.ts","./src/components/product-import/steps/validationstepnew/types/index.ts","./src/components/product-import/steps/validationstepnew/utils/aivalidationutils.ts","./src/components/product-import/steps/validationstepnew/utils/countryutils.ts","./src/components/product-import/steps/validationstepnew/utils/datamutations.ts","./src/components/product-import/steps/validationstepnew/utils/priceutils.ts","./src/components/product-import/steps/validationstepnew/utils/upcutils.ts","./src/components/product-import/utils/exceedsmaxrecords.ts","./src/components/product-import/utils/mapdata.ts","./src/components/product-import/utils/mapworkbook.ts","./src/components/product-import/utils/steps.ts","./src/components/products/productdetail.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/products/productviews.tsx","./src/components/products/products.tsx","./src/components/purchase-orders/categorymetricscard.tsx","./src/components/purchase-orders/filtercontrols.tsx","./src/components/purchase-orders/ordermetricscard.tsx","./src/components/purchase-orders/paginationcontrols.tsx","./src/components/purchase-orders/purchaseorderaccordion.tsx","./src/components/purchase-orders/purchaseorderstable.tsx","./src/components/purchase-orders/vendormetricscard.tsx","./src/components/settings/datamanagement.tsx","./src/components/settings/globalsettings.tsx","./src/components/settings/permissionselector.tsx","./src/components/settings/productsettings.tsx","./src/components/settings/promptmanagement.tsx","./src/components/settings/reusableimagemanagement.tsx","./src/components/settings/templatemanagement.tsx","./src/components/settings/userform.tsx","./src/components/settings/userlist.tsx","./src/components/settings/usermanagement.tsx","./src/components/settings/vendorsettings.tsx","./src/components/templates/searchproducttemplatedialog.tsx","./src/components/templates/templateform.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/code.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/command.tsx","./src/components/ui/date-range-picker-narrow.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/page-loading.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/config/dashboard.ts","./src/contexts/authcontext.tsx","./src/contexts/dashboardscrollcontext.tsx","./src/hooks/use-mobile.tsx","./src/hooks/use-toast.ts","./src/hooks/usedebounce.ts","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/brands.tsx","./src/pages/categories.tsx","./src/pages/chat.tsx","./src/pages/dashboard.tsx","./src/pages/discountsimulator.tsx","./src/pages/forecasting.tsx","./src/pages/import.tsx","./src/pages/login.tsx","./src/pages/overview.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx","./src/pages/smalldashboard.tsx","./src/pages/vendors.tsx","./src/services/apiv2.ts","./src/types/dashboard-shims.d.ts","./src/types/dashboard.d.ts","./src/types/discount-simulator.ts","./src/types/globals.d.ts","./src/types/products.ts","./src/types/react-data-grid.d.ts","./src/types/status-codes.ts","./src/utils/emojiutils.ts","./src/utils/naturallanguageperiod.ts","./src/utils/productutils.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/config.ts","./src/components/analytics/categoryperformance.tsx","./src/components/analytics/priceanalysis.tsx","./src/components/analytics/profitanalysis.tsx","./src/components/analytics/stockanalysis.tsx","./src/components/analytics/vendorperformance.tsx","./src/components/auth/firstaccessiblepage.tsx","./src/components/auth/protected.tsx","./src/components/auth/requireauth.tsx","./src/components/chat/chatroom.tsx","./src/components/chat/chattest.tsx","./src/components/chat/roomlist.tsx","./src/components/chat/searchresults.tsx","./src/components/dashboard/financialoverview.tsx","./src/components/dashboard/periodselectionpopover.tsx","./src/components/discount-simulator/configpanel.tsx","./src/components/discount-simulator/resultschart.tsx","./src/components/discount-simulator/resultstable.tsx","./src/components/discount-simulator/summarycard.tsx","./src/components/forecasting/daterangepickerquick.tsx","./src/components/forecasting/quickorderbuilder.tsx","./src/components/forecasting/columns.tsx","./src/components/layout/appsidebar.tsx","./src/components/layout/mainlayout.tsx","./src/components/layout/navuser.tsx","./src/components/overview/bestsellers.tsx","./src/components/overview/forecastmetrics.tsx","./src/components/overview/overstockmetrics.tsx","./src/components/overview/overview.tsx","./src/components/overview/purchasemetrics.tsx","./src/components/overview/replenishmentmetrics.tsx","./src/components/overview/salesmetrics.tsx","./src/components/overview/stockmetrics.tsx","./src/components/overview/topoverstockedproducts.tsx","./src/components/overview/topreplenishproducts.tsx","./src/components/overview/vendorperformance.tsx","./src/components/product-import/createproductcategorydialog.tsx","./src/components/product-import/reactspreadsheetimport.tsx","./src/components/product-import/config.ts","./src/components/product-import/index.ts","./src/components/product-import/translationsrsiprops.ts","./src/components/product-import/types.ts","./src/components/product-import/components/modalwrapper.tsx","./src/components/product-import/components/providers.tsx","./src/components/product-import/components/table.tsx","./src/components/product-import/hooks/usersi.ts","./src/components/product-import/steps/steps.tsx","./src/components/product-import/steps/uploadflow.tsx","./src/components/product-import/steps/imageuploadstep/imageuploadstep.tsx","./src/components/product-import/steps/imageuploadstep/types.ts","./src/components/product-import/steps/imageuploadstep/components/droppablecontainer.tsx","./src/components/product-import/steps/imageuploadstep/components/genericdropzone.tsx","./src/components/product-import/steps/imageuploadstep/components/unassignedimagessection.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/copybutton.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/imagedropzone.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/productcard.tsx","./src/components/product-import/steps/imageuploadstep/components/productcard/sortableimage.tsx","./src/components/product-import/steps/imageuploadstep/components/unassignedimagessection/unassignedimageitem.tsx","./src/components/product-import/steps/imageuploadstep/hooks/usebulkimageupload.ts","./src/components/product-import/steps/imageuploadstep/hooks/usedraganddrop.ts","./src/components/product-import/steps/imageuploadstep/hooks/useproductimageoperations.ts","./src/components/product-import/steps/imageuploadstep/hooks/useproductimagesinit.ts","./src/components/product-import/steps/imageuploadstep/hooks/useurlimageupload.ts","./src/components/product-import/steps/matchcolumnsstep/matchcolumnsstep.tsx","./src/components/product-import/steps/matchcolumnsstep/types.ts","./src/components/product-import/steps/matchcolumnsstep/components/matchicon.tsx","./src/components/product-import/steps/matchcolumnsstep/components/templatecolumn.tsx","./src/components/product-import/steps/matchcolumnsstep/utils/findmatch.ts","./src/components/product-import/steps/matchcolumnsstep/utils/findunmatchedrequiredfields.ts","./src/components/product-import/steps/matchcolumnsstep/utils/getfieldoptions.ts","./src/components/product-import/steps/matchcolumnsstep/utils/getmatchedcolumns.ts","./src/components/product-import/steps/matchcolumnsstep/utils/normalizecheckboxvalue.ts","./src/components/product-import/steps/matchcolumnsstep/utils/normalizetabledata.ts","./src/components/product-import/steps/matchcolumnsstep/utils/setcolumn.ts","./src/components/product-import/steps/matchcolumnsstep/utils/setignorecolumn.ts","./src/components/product-import/steps/matchcolumnsstep/utils/setsubcolumn.ts","./src/components/product-import/steps/matchcolumnsstep/utils/uniqueentries.ts","./src/components/product-import/steps/selectheaderstep/selectheaderstep.tsx","./src/components/product-import/steps/selectheaderstep/components/selectheadertable.tsx","./src/components/product-import/steps/selectheaderstep/components/columns.tsx","./src/components/product-import/steps/selectsheetstep/selectsheetstep.tsx","./src/components/product-import/steps/uploadstep/uploadstep.tsx","./src/components/product-import/steps/uploadstep/components/dropzone.tsx","./src/components/product-import/steps/uploadstep/components/columns.tsx","./src/components/product-import/steps/uploadstep/utils/readfilesasync.ts","./src/components/product-import/steps/validationstepnew/index.tsx","./src/components/product-import/steps/validationstepnew/types.ts","./src/components/product-import/steps/validationstepnew/components/aivalidationdialogs.tsx","./src/components/product-import/steps/validationstepnew/components/basecellcontent.tsx","./src/components/product-import/steps/validationstepnew/components/initializingvalidation.tsx","./src/components/product-import/steps/validationstepnew/components/searchabletemplateselect.tsx","./src/components/product-import/steps/validationstepnew/components/upcvalidationtableadapter.tsx","./src/components/product-import/steps/validationstepnew/components/validationcell.tsx","./src/components/product-import/steps/validationstepnew/components/validationcontainer.tsx","./src/components/product-import/steps/validationstepnew/components/validationtable.tsx","./src/components/product-import/steps/validationstepnew/components/cells/checkboxcell.tsx","./src/components/product-import/steps/validationstepnew/components/cells/inputcell.tsx","./src/components/product-import/steps/validationstepnew/components/cells/multiselectcell.tsx","./src/components/product-import/steps/validationstepnew/components/cells/multilineinput.tsx","./src/components/product-import/steps/validationstepnew/components/cells/selectcell.tsx","./src/components/product-import/steps/validationstepnew/hooks/useaivalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usefieldvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usefiltermanagement.tsx","./src/components/product-import/steps/validationstepnew/hooks/useinitialvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/useproductlinesfetching.tsx","./src/components/product-import/steps/validationstepnew/hooks/userowoperations.tsx","./src/components/product-import/steps/validationstepnew/hooks/usetemplatemanagement.tsx","./src/components/product-import/steps/validationstepnew/hooks/useuniqueitemnumbersvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/useuniquevalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/useupcvalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usevalidation.tsx","./src/components/product-import/steps/validationstepnew/hooks/usevalidationstate.tsx","./src/components/product-import/steps/validationstepnew/hooks/validationtypes.ts","./src/components/product-import/steps/validationstepnew/types/index.ts","./src/components/product-import/steps/validationstepnew/utils/aivalidationutils.ts","./src/components/product-import/steps/validationstepnew/utils/countryutils.ts","./src/components/product-import/steps/validationstepnew/utils/datamutations.ts","./src/components/product-import/steps/validationstepnew/utils/priceutils.ts","./src/components/product-import/steps/validationstepnew/utils/upcutils.ts","./src/components/product-import/utils/exceedsmaxrecords.ts","./src/components/product-import/utils/mapdata.ts","./src/components/product-import/utils/mapworkbook.ts","./src/components/product-import/utils/steps.ts","./src/components/products/productdetail.tsx","./src/components/products/productfilters.tsx","./src/components/products/producttable.tsx","./src/components/products/producttableskeleton.tsx","./src/components/products/productviews.tsx","./src/components/products/products.tsx","./src/components/purchase-orders/categorymetricscard.tsx","./src/components/purchase-orders/filtercontrols.tsx","./src/components/purchase-orders/ordermetricscard.tsx","./src/components/purchase-orders/paginationcontrols.tsx","./src/components/purchase-orders/purchaseorderaccordion.tsx","./src/components/purchase-orders/purchaseorderstable.tsx","./src/components/purchase-orders/vendormetricscard.tsx","./src/components/settings/datamanagement.tsx","./src/components/settings/globalsettings.tsx","./src/components/settings/permissionselector.tsx","./src/components/settings/productsettings.tsx","./src/components/settings/promptmanagement.tsx","./src/components/settings/reusableimagemanagement.tsx","./src/components/settings/templatemanagement.tsx","./src/components/settings/userform.tsx","./src/components/settings/userlist.tsx","./src/components/settings/usermanagement.tsx","./src/components/settings/vendorsettings.tsx","./src/components/templates/searchproducttemplatedialog.tsx","./src/components/templates/templateform.tsx","./src/components/ui/accordion.tsx","./src/components/ui/alert-dialog.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/code.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/command.tsx","./src/components/ui/date-range-picker-narrow.tsx","./src/components/ui/date-range-picker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/drawer.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/page-loading.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/progress.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/scroll-area.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/toggle-group.tsx","./src/components/ui/toggle.tsx","./src/components/ui/tooltip.tsx","./src/config/dashboard.ts","./src/contexts/authcontext.tsx","./src/contexts/dashboardscrollcontext.tsx","./src/hooks/use-mobile.tsx","./src/hooks/use-toast.ts","./src/hooks/usedebounce.ts","./src/lib/utils.ts","./src/pages/analytics.tsx","./src/pages/brands.tsx","./src/pages/categories.tsx","./src/pages/chat.tsx","./src/pages/dashboard.tsx","./src/pages/discountsimulator.tsx","./src/pages/forecasting.tsx","./src/pages/htslookup.tsx","./src/pages/import.tsx","./src/pages/login.tsx","./src/pages/overview.tsx","./src/pages/products.tsx","./src/pages/purchaseorders.tsx","./src/pages/settings.tsx","./src/pages/smalldashboard.tsx","./src/pages/vendors.tsx","./src/services/apiv2.ts","./src/types/dashboard-shims.d.ts","./src/types/dashboard.d.ts","./src/types/discount-simulator.ts","./src/types/globals.d.ts","./src/types/products.ts","./src/types/react-data-grid.d.ts","./src/types/status-codes.ts","./src/utils/emojiutils.ts","./src/utils/naturallanguageperiod.ts","./src/utils/productutils.ts"],"version":"5.6.3"} \ No newline at end of file