From 3831cef23452b50f8c9a415cc83e106d71950d37 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 24 Jan 2026 11:58:21 -0500 Subject: [PATCH] AI tweaks/fixes + backend api interface updates --- inventory-server/auth/server.js | 2 +- inventory-server/chat/server.js | 2 +- .../dashboard/auth-server/index.js | 2 +- inventory-server/src/middleware/cors.js | 3 +- inventory-server/src/routes/import.js | 22 +- .../src/routes/reusable-images.js | 2 +- .../services/ai/prompts/sanityCheckPrompts.js | 3 +- inventory/package.json | 2 +- .../CreateProductCategoryDialog.tsx | 331 ++++++++++-------- .../src/components/product-import/config.ts | 2 +- .../MatchColumnsStep/MatchColumnsStep.tsx | 66 +++- .../product-import/steps/UploadFlow.tsx | 1 + .../components/ValidationContainer.tsx | 8 +- .../components/cells/InputCell.tsx | 2 +- .../dialogs/AiValidationResults.tsx | 2 +- .../dialogs/SanityCheckDialog.tsx | 83 +++-- .../hooks/useAiValidation/useAiTransform.ts | 1 - .../ValidationStep/hooks/useSanityCheck.ts | 2 + .../steps/ValidationStep/store/selectors.ts | 2 +- .../ValidationStep/utils/inlineAiPayload.ts | 4 +- inventory/src/config/dashboard.ts | 18 +- inventory/src/services/apiv2.ts | 83 +++-- inventory/tsconfig.tsbuildinfo | 2 +- inventory/vite.config.ts | 65 +++- 24 files changed, 458 insertions(+), 252 deletions(-) diff --git a/inventory-server/auth/server.js b/inventory-server/auth/server.js index e9e52fc..fe5af0a 100644 --- a/inventory-server/auth/server.js +++ b/inventory-server/auth/server.js @@ -35,7 +35,7 @@ global.pool = pool; app.use(express.json()); app.use(morgan('combined')); app.use(cors({ - origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw', 'https://acot.site'], + origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw', 'https://acot.site', 'https://acob.acherryontop.com'], credentials: true })); diff --git a/inventory-server/chat/server.js b/inventory-server/chat/server.js index 5c90e56..616ae91 100644 --- a/inventory-server/chat/server.js +++ b/inventory-server/chat/server.js @@ -33,7 +33,7 @@ global.pool = pool; app.use(express.json()); app.use(morgan('combined')); app.use(cors({ - origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw', 'https://acot.site'], + origin: ['http://localhost:5175', 'http://localhost:5174', 'https://inventory.kent.pw', 'https://acot.site', 'https://acob.acherryontop.com'], credentials: true })); diff --git a/inventory-server/dashboard/auth-server/index.js b/inventory-server/dashboard/auth-server/index.js index 5e81177..9b8e214 100644 --- a/inventory-server/dashboard/auth-server/index.js +++ b/inventory-server/dashboard/auth-server/index.js @@ -33,7 +33,7 @@ const corsOptions = { origin: function(origin, callback) { const allowedOrigins = [ 'http://localhost:3000', - 'https://dashboard.kent.pw' + 'https://acob.acherryontop.com' ]; console.log('CORS check for origin:', origin); diff --git a/inventory-server/src/middleware/cors.js b/inventory-server/src/middleware/cors.js index 7c92e03..7968225 100644 --- a/inventory-server/src/middleware/cors.js +++ b/inventory-server/src/middleware/cors.js @@ -6,6 +6,7 @@ const corsMiddleware = cors({ 'https://inventory.kent.pw', 'http://localhost:5175', 'https://acot.site', + 'https://acob.acherryontop.com', /^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/, /^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/ ], @@ -27,7 +28,7 @@ const corsErrorHandler = (err, req, res, next) => { res.status(403).json({ error: 'CORS not allowed', origin: req.get('Origin'), - message: 'Origin not in allowed list: https://inventory.kent.pw, https://acot.site, localhost:5175, 192.168.x.x, or 10.x.x.x' + message: 'Origin not in allowed list: https://inventory.kent.pw, https://acot.site, https://acob.acherryontop.com, localhost:5175, 192.168.x.x, or 10.x.x.x' }); } else { next(err); diff --git a/inventory-server/src/routes/import.js b/inventory-server/src/routes/import.js index cea4027..1a7ff6c 100644 --- a/inventory-server/src/routes/import.js +++ b/inventory-server/src/routes/import.js @@ -635,7 +635,7 @@ router.post('/upload-image', upload.single('image'), async (req, res) => { // Create URL for the uploaded file - using an absolute URL with domain // This will generate a URL like: https://acot.site/uploads/products/filename.jpg - const baseUrl = 'https://acot.site'; + const baseUrl = 'https://acob.acherryontop.com'; const imageUrl = `${baseUrl}/uploads/products/${req.file.filename}`; // Schedule this image for deletion in 24 hours @@ -715,6 +715,26 @@ router.delete('/delete-image', (req, res) => { } }); +// Clear all taxonomy caches +router.post('/clear-taxonomy-cache', (req, res) => { + try { + // Clear all entries from the query cache + const cacheSize = connectionCache.queryCache.size; + connectionCache.queryCache.clear(); + + console.log(`Cleared ${cacheSize} entries from taxonomy cache`); + + res.json({ + success: true, + message: `Cache cleared (${cacheSize} entries removed)`, + clearedEntries: cacheSize + }); + } catch (error) { + console.error('Error clearing taxonomy cache:', error); + res.status(500).json({ error: 'Failed to clear cache' }); + } +}); + // Get all options for import fields router.get('/field-options', async (req, res) => { try { diff --git a/inventory-server/src/routes/reusable-images.js b/inventory-server/src/routes/reusable-images.js index 7dd3f96..d7f7c6c 100644 --- a/inventory-server/src/routes/reusable-images.js +++ b/inventory-server/src/routes/reusable-images.js @@ -194,7 +194,7 @@ router.post('/upload', upload.single('image'), async (req, res) => { } // Create URL for the uploaded file - const baseUrl = 'https://acot.site'; + const baseUrl = 'https://acob.acherryontop.com'; const imageUrl = `${baseUrl}/uploads/reusable/${req.file.filename}`; const pool = req.app.locals.pool; diff --git a/inventory-server/src/services/ai/prompts/sanityCheckPrompts.js b/inventory-server/src/services/ai/prompts/sanityCheckPrompts.js index fddd80e..f628556 100644 --- a/inventory-server/src/services/ai/prompts/sanityCheckPrompts.js +++ b/inventory-server/src/services/ai/prompts/sanityCheckPrompts.js @@ -29,6 +29,7 @@ function buildSanityCheckUserPrompt(products, prompts) { tax_cat: p.tax_cat_name || p.tax_cat, size_cat: p.size_cat_name || p.size_cat, themes: p.theme_names || p.themes, + categories: p.category_names || p.categories, weight: p.weight, length: p.length, width: p.width, @@ -59,7 +60,7 @@ function buildSanityCheckUserPrompt(products, prompts) { suggestion: 'Suggested fix or verification (optional)' } ], - summary: '1-2 sentences summarizing the batch quality' + summary: '2-3 sentences summarizing the overall product quality' }, null, 2)); parts.push(''); diff --git a/inventory/package.json b/inventory/package.json index 65a6871..8a3ee83 100644 --- a/inventory/package.json +++ b/inventory/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "build:deploy": "tsc -b && COPY_BUILD=true vite build", + "build:deploy": "tsc -b && COPY_BUILD=true DEPLOY_TARGET=netcup DEPLOY_PATH=/var/www/html/inventory/frontend vite build", "lint": "eslint .", "preview": "vite preview", "mount": "../mountremote.command" diff --git a/inventory/src/components/product-import/CreateProductCategoryDialog.tsx b/inventory/src/components/product-import/CreateProductCategoryDialog.tsx index 05a0ebb..b90331c 100644 --- a/inventory/src/components/product-import/CreateProductCategoryDialog.tsx +++ b/inventory/src/components/product-import/CreateProductCategoryDialog.tsx @@ -1,11 +1,10 @@ -import { useCallback, useEffect, useMemo, useState, useRef } from "react"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Check, ChevronsUpDown, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { Dialog, DialogContent, - DialogDescription, DialogFooter, DialogHeader, DialogTitle, @@ -14,6 +13,7 @@ import { import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; import { Command, CommandEmpty, @@ -27,9 +27,10 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { cn } from "@/lib/utils"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import config from "@/config"; import { createProductCategory, type CreateProductCategoryResponse } from "@/services/apiv2"; +import { AuthContext } from "@/contexts/AuthContext"; type Option = { label: string; @@ -84,7 +85,11 @@ export function CreateProductCategoryDialog({ environment = "prod", onCreated, }: CreateProductCategoryDialogProps) { + const { user } = useContext(AuthContext); + const hasDebugPermission = Boolean(user?.is_admin || user?.permissions?.includes("admin:debug")); + const [isOpen, setIsOpen] = useState(false); + const [activeTab, setActiveTab] = useState<"line" | "subline">(defaultLineId ? "subline" : "line"); const [companyId, setCompanyId] = useState(defaultCompanyId ?? ""); const [lineId, setLineId] = useState(defaultLineId ?? ""); const [categoryName, setCategoryName] = useState(""); @@ -92,7 +97,8 @@ export function CreateProductCategoryDialog({ const [isLoadingLines, setIsLoadingLines] = useState(false); const [lines, setLines] = useState([]); const [linesCache, setLinesCache] = useState>({}); - + const [targetEnvironment, setTargetEnvironment] = useState<"dev" | "prod">(environment); + // Popover open states const [companyOpen, setCompanyOpen] = useState(false); const [lineOpen, setLineOpen] = useState(false); @@ -116,6 +122,7 @@ export function CreateProductCategoryDialog({ setCompanyId(defaultCompanyId ?? ""); setLineId(defaultLineId ?? ""); setCategoryName(""); + setActiveTab(defaultLineId ? "subline" : "line"); } }, [isOpen, defaultCompanyId, defaultLineId]); @@ -180,8 +187,13 @@ export function CreateProductCategoryDialog({ return; } - const parentId = lineId || companyId; - const creationType: "line" | "subline" = lineId ? "subline" : "line"; + if (activeTab === "subline" && !lineId) { + toast.error("Select a parent line to create a subline"); + return; + } + + const parentId = activeTab === "subline" ? lineId : companyId; + const creationType = activeTab; setIsSubmitting(true); @@ -189,7 +201,7 @@ export function CreateProductCategoryDialog({ const result = await createProductCategory({ masterCatId: parentId, name: trimmedName, - environment, + environment: targetEnvironment, }); if (!result.success) { @@ -211,7 +223,7 @@ export function CreateProductCategoryDialog({ } } - if (!lineId) { + if (activeTab === "line") { const nextOption: Option = { label: trimmedName, value: newId ?? trimmedName }; setLinesCache((prev) => { const existing = prev[companyId] ?? []; @@ -243,13 +255,13 @@ export function CreateProductCategoryDialog({ const message = error instanceof Error ? error.message - : `Failed to create ${lineId ? "subline" : "product line"}.`; + : `Failed to create ${activeTab === "line" ? "product line" : "subline"}.`; toast.error(message); } finally { setIsSubmitting(false); } }, - [categoryName, companyId, environment, lineId, onCreated], + [activeTab, categoryName, companyId, targetEnvironment, lineId, onCreated], ); return ( @@ -257,149 +269,172 @@ export function CreateProductCategoryDialog({ {trigger} - Create Product Line or Subline - - Add a new product line beneath a company or create a subline beneath an existing line. - + Create New Line or Subline -
- {/* Company Select - Searchable */} -
- - - - - - - - - - No company found. - - {companyOptions.map((company) => ( - { - setCompanyId(company.value); - setLineId(""); - setCompanyOpen(false); - }} - > - {company.label} - {company.value === companyId && ( - - )} - - ))} - - - - - -
+ setActiveTab(value as "line" | "subline")}> + + Create Line + Create Subline + - {/* Line Select - Searchable */} -
- - - - - - - - - - No line found. - - {/* Option to clear selection */} - { - setLineId(""); - setLineOpen(false); - }} - > - None (create new line) - {lineId === "" && } - - {lines.map((line) => ( - { - setLineId(line.value); - setLineOpen(false); - }} - > - {line.label} - {line.value === lineId && ( - - )} - - ))} - - - - - - {companyId && !isLoadingLines && !lines.length && ( -

- No existing lines found for this company. A new line will be created. -

+ + {/* Company Select - shown in both tabs */} +
+ + + + + + + + + + No company found. + + {companyOptions.map((company) => ( + { + setCompanyId(company.value); + setLineId(""); + setCompanyOpen(false); + }} + > + {company.label} + {company.value === companyId && ( + + )} + + ))} + + + + + +
+ + +
+ + setCategoryName(event.target.value)} + placeholder="Enter the new line name" + /> +
+
+ + + {/* Parent Line Select - only shown in subline tab */} +
+ + + + + + + + + + No line found. + + {lines.map((line) => ( + { + setLineId(line.value); + setLineOpen(false); + }} + > + {line.label} + {line.value === lineId && ( + + )} + + ))} + + + + + + {companyId && !isLoadingLines && !lines.length && ( +

+ No existing lines found for this company. Create a line first. +

+ )} +
+ +
+ + setCategoryName(event.target.value)} + placeholder="Enter the new subline name" + /> +
+
+ + {hasDebugPermission && ( +
+
+ setTargetEnvironment(checked ? "dev" : "prod")} + /> + +
+
)} -
-
- - setCategoryName(event.target.value)} - placeholder="Enter the new line or subline name" - /> -
- - - - - - + + + + + +
); diff --git a/inventory/src/components/product-import/config.ts b/inventory/src/components/product-import/config.ts index 45a6714..322c962 100644 --- a/inventory/src/components/product-import/config.ts +++ b/inventory/src/components/product-import/config.ts @@ -143,7 +143,7 @@ export const BASE_IMPORT_FIELDS = [ label: "Cost Each", key: "cost_each", description: "Wholesale cost per unit", - alternateMatches: ["wholesale", "wholesale price", "supplier cost each", "cost each"], + alternateMatches: ["wholesale", "wholesale price", "supplier cost each", "cost each","whls"], fieldType: { type: "input", price: true diff --git a/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx b/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx index 59dbe88..a6fbd31 100644 --- a/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx +++ b/inventory/src/components/product-import/steps/MatchColumnsStep/MatchColumnsStep.tsx @@ -29,7 +29,7 @@ import { import { useQuery, useQueryClient } from "@tanstack/react-query" import config from "@/config" import { Button } from "@/components/ui/button" -import { CheckCircle2, AlertCircle, EyeIcon, EyeOffIcon, ArrowRightIcon, XIcon, FileSpreadsheetIcon, LinkIcon, CheckIcon, ChevronsUpDown, Bot } from "lucide-react" +import { CheckCircle2, AlertCircle, EyeIcon, EyeOffIcon, ArrowRightIcon, XIcon, FileSpreadsheetIcon, LinkIcon, CheckIcon, ChevronsUpDown, Bot, RefreshCw, Plus } from "lucide-react" import { Separator } from "@/components/ui/separator" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" @@ -611,6 +611,7 @@ const MatchColumnsStepComponent = ({ const { fields, autoMapHeaders, autoMapSelectValues, autoMapDistance, translations } = useRsi() const queryClient = useQueryClient() const [isLoading, setIsLoading] = useState(false) + const [isRefreshing, setIsRefreshing] = useState(false) const [columns, setColumns] = useState>(() => { // Helper function to check if a column is completely empty @@ -846,6 +847,43 @@ const MatchColumnsStepComponent = ({ [queryClient, setGlobalSelections], ); + // Handle manual cache refresh + const handleRefreshTaxonomy = useCallback(async () => { + setIsRefreshing(true); + try { + // Clear backend cache + const response = await fetch(`${config.apiUrl}/import/clear-taxonomy-cache`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error('Failed to clear backend cache'); + } + + // Clear frontend React Query cache + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ['field-options'] }), + queryClient.invalidateQueries({ queryKey: ['product-lines'] }), + queryClient.invalidateQueries({ queryKey: ['product-lines-mapped'] }), + queryClient.invalidateQueries({ queryKey: ['sublines'] }), + queryClient.invalidateQueries({ queryKey: ['sublines-mapped'] }), + ]); + + // Refetch field options immediately + await queryClient.refetchQueries({ queryKey: ['field-options'] }); + + } catch (error) { + console.error('Error refreshing taxonomy:', error); + toast.error('Failed to refresh taxonomy data', { + }); + } finally { + setIsRefreshing(false); + } + }, [queryClient]); + // Check if a field is covered by global selections const isFieldCoveredByGlobalSelections = useCallback((key: string) => { return (key === 'supplier' && !!globalSelections.supplier) || @@ -1815,11 +1853,11 @@ const MatchColumnsStepComponent = ({ -
+
- + New line or subline + } companies={fieldOptions?.companies || []} @@ -1827,6 +1865,26 @@ const MatchColumnsStepComponent = ({ defaultLineId={globalSelections.line} onCreated={handleCategoryCreated} /> + + + + + + + +

Reload all suppliers, companies, lines, categories, and other taxonomy data from the database

+
+
+
diff --git a/inventory/src/components/product-import/steps/UploadFlow.tsx b/inventory/src/components/product-import/steps/UploadFlow.tsx index ac88f56..2583e2c 100644 --- a/inventory/src/components/product-import/steps/UploadFlow.tsx +++ b/inventory/src/components/product-import/steps/UploadFlow.tsx @@ -52,6 +52,7 @@ export type StepState = | { type: StepType.validateDataNew data: any[] + file?: File globalSelections?: GlobalSelections isFromScratch?: boolean } diff --git a/inventory/src/components/product-import/steps/ValidationStep/components/ValidationContainer.tsx b/inventory/src/components/product-import/steps/ValidationStep/components/ValidationContainer.tsx index 8ed06c6..4349544 100644 --- a/inventory/src/components/product-import/steps/ValidationStep/components/ValidationContainer.tsx +++ b/inventory/src/components/product-import/steps/ValidationStep/components/ValidationContainer.tsx @@ -162,6 +162,7 @@ export const ValidationContainer = ({ size_cat: row.size_cat as string | number | undefined, size_cat_name: getFieldLabel('size_cat', row.size_cat), themes: row.themes as string | undefined, + categories: row.categories as string | undefined, weight: row.weight as string | number | undefined, length: row.length as string | number | undefined, width: row.width as string | number | undefined, @@ -220,14 +221,17 @@ export const ValidationContainer = ({ }, []); // Build product names lookup for sanity check dialog - const productNames = useMemo(() => { + // Rebuild fresh whenever dialog opens to ensure names are current after AI suggestions + const buildProductNames = useCallback(() => { const rows = useValidationStore.getState().rows; const names: Record = {}; rows.forEach((row, index) => { names[index] = (row.name as string) || `Product ${index + 1}`; }); return names; - }, [rowCount]); // Depend on rowCount to update when rows change + }, []); + + const productNames = useMemo(() => buildProductNames(), [sanityCheckDialogOpen, buildProductNames]); return ( { const field = issue.field; if (!acc[field]) { - acc[field] = []; + acc[field] = {}; } - acc[field].push(issue); + + const key = `${issue.issue}:${issue.suggestion || ''}`; + if (!acc[field][key]) { + acc[field][key] = { + field: issue.field, + issue: issue.issue, + suggestion: issue.suggestion, + productIndices: [] + }; + } + acc[field][key].productIndices.push(issue.productIndex); return acc; - }, {} as Record) || {}; + }, {} as Record>) || {}; return ( @@ -180,48 +190,59 @@ export function SanityCheckDialog({
- {/* Issues grouped by field */} - {Object.entries(issuesByField).map(([field, fieldIssues]) => ( + {/* Issues grouped by field, then by unique issue+suggestion */} + {Object.entries(issuesByField).map(([field, groups]) => (
- + {formatFieldName(field)} - {fieldIssues.length} issue{fieldIssues.length === 1 ? '' : 's'} + {Object.values(groups).reduce((sum, g) => sum + g.productIndices.length, 0)} issue{Object.values(groups).reduce((sum, g) => sum + g.productIndices.length, 0) === 1 ? '' : 's'}
- {fieldIssues.map((issue, index) => ( + {Object.entries(groups).map(([key, group]) => (
-
- - {productNames[issue.productIndex] || `Product ${issue.productIndex + 1}`} +
+ + {group.productIndices.length} product{group.productIndices.length === 1 ? '' : 's'} - {onScrollToProduct && ( - - )}
-

{issue.issue}

- {issue.suggestion && ( +
+ {group.productIndices.map((productIndex, idx) => ( + + + {productNames[productIndex] || `Product ${productIndex + 1}`} + + {onScrollToProduct && ( + + )} + {idx < group.productIndices.length - 1 && ( + , + )} + + ))} +
+

{group.issue}

+ {group.suggestion && (

- {issue.suggestion} + {group.suggestion}

)}
diff --git a/inventory/src/components/product-import/steps/ValidationStep/hooks/useAiValidation/useAiTransform.ts b/inventory/src/components/product-import/steps/ValidationStep/hooks/useAiValidation/useAiTransform.ts index 1bb8d11..ecca4c3 100644 --- a/inventory/src/components/product-import/steps/ValidationStep/hooks/useAiValidation/useAiTransform.ts +++ b/inventory/src/components/product-import/steps/ValidationStep/hooks/useAiValidation/useAiTransform.ts @@ -12,7 +12,6 @@ import { useCallback } from 'react'; import { useValidationStore } from '../../store/validationStore'; import type { AiValidationChange, AiValidationResults, AiTokenUsage } from '../../store/types'; import type { Field, SelectOption } from '../../../../types'; -import type { AiValidationResponse, AiTokenUsage as ApiTokenUsage } from './useAiApi'; /** * Helper to convert a value to number or null diff --git a/inventory/src/components/product-import/steps/ValidationStep/hooks/useSanityCheck.ts b/inventory/src/components/product-import/steps/ValidationStep/hooks/useSanityCheck.ts index 56ebe51..3eb879c 100644 --- a/inventory/src/components/product-import/steps/ValidationStep/hooks/useSanityCheck.ts +++ b/inventory/src/components/product-import/steps/ValidationStep/hooks/useSanityCheck.ts @@ -53,6 +53,8 @@ export interface ProductForSanityCheck { size_cat_name?: string; themes?: string; theme_names?: string; + categories?: string; + category_names?: string; weight?: string | number; length?: string | number; width?: string | number; diff --git a/inventory/src/components/product-import/steps/ValidationStep/store/selectors.ts b/inventory/src/components/product-import/steps/ValidationStep/store/selectors.ts index 72a9196..6123131 100644 --- a/inventory/src/components/product-import/steps/ValidationStep/store/selectors.ts +++ b/inventory/src/components/product-import/steps/ValidationStep/store/selectors.ts @@ -5,7 +5,7 @@ * only to the state they need, preventing unnecessary re-renders. */ -import { useMemo, useCallback } from 'react'; +import { useMemo } from 'react'; import { useValidationStore } from './validationStore'; import type { RowData, ValidationError } from './types'; diff --git a/inventory/src/components/product-import/steps/ValidationStep/utils/inlineAiPayload.ts b/inventory/src/components/product-import/steps/ValidationStep/utils/inlineAiPayload.ts index 27c0275..6bc39fc 100644 --- a/inventory/src/components/product-import/steps/ValidationStep/utils/inlineAiPayload.ts +++ b/inventory/src/components/product-import/steps/ValidationStep/utils/inlineAiPayload.ts @@ -107,6 +107,7 @@ export interface NameValidationPayload { line_name?: string; subline_name?: string; siblingNames?: string[]; + [key: string]: unknown; } /** @@ -118,6 +119,7 @@ export interface DescriptionValidationPayload { company_name?: string; company_id?: string; // Needed by backend to load company-specific prompts (not sent to AI) categories?: string; + [key: string]: unknown; } /** @@ -145,7 +147,7 @@ export function buildNameValidationPayload( ): NameValidationPayload { // Use override line for sibling computation if provided const effectiveRow = overrides?.line !== undefined - ? { ...row, line: overrides.line } + ? { ...row, line: String(overrides.line) } : row; const siblingNames = computeSiblingNames(effectiveRow, allRows); diff --git a/inventory/src/config/dashboard.ts b/inventory/src/config/dashboard.ts index 68a0b92..4f2604e 100644 --- a/inventory/src/config/dashboard.ts +++ b/inventory/src/config/dashboard.ts @@ -5,15 +5,15 @@ const isLocal = window.location.hostname === 'localhost' || window.location.host const useProxy = !isLocal && (window.location.hostname === 'inventory.kent.pw' || window.location.hostname === 'inventory.acot.site' || window.location.hostname === 'acot.site'); const liveDashboardConfig = { - auth: isDev || useProxy ? '/dashboard-auth' : 'https://dashboard.kent.pw/auth', - aircall: isDev || useProxy ? '/api/aircall' : 'https://dashboard.kent.pw/api/aircall', - klaviyo: isDev || useProxy ? '/api/klaviyo' : 'https://dashboard.kent.pw/api/klaviyo', - meta: isDev || useProxy ? '/api/meta' : 'https://dashboard.kent.pw/api/meta', - gorgias: isDev || useProxy ? '/api/gorgias' : 'https://dashboard.kent.pw/api/gorgias', - analytics: isDev || useProxy ? '/api/dashboard-analytics' : 'https://dashboard.kent.pw/api/analytics', - typeform: isDev || useProxy ? '/api/typeform' : 'https://dashboard.kent.pw/api/typeform', - acot: isDev || useProxy ? '/api/acot' : 'https://dashboard.kent.pw/api/acot', - clarity: isDev || useProxy ? '/api/clarity' : 'https://dashboard.kent.pw/api/clarity' + auth: isDev || useProxy ? '/dashboard-auth' : 'https://acob.acherryontop.com/auth', + aircall: isDev || useProxy ? '/api/aircall' : 'https://acob.acherryontop.com/api/aircall', + klaviyo: isDev || useProxy ? '/api/klaviyo' : 'https://acob.acherryontop.com/api/klaviyo', + meta: isDev || useProxy ? '/api/meta' : 'https://acob.acherryontop.com/api/meta', + gorgias: isDev || useProxy ? '/api/gorgias' : 'https://acob.acherryontop.com/api/gorgias', + analytics: isDev || useProxy ? '/api/dashboard-analytics' : 'https://acob.acherryontop.com/api/analytics', + typeform: isDev || useProxy ? '/api/typeform' : 'https://acob.acherryontop.com/api/typeform', + acot: isDev || useProxy ? '/api/acot' : 'https://acob.acherryontop.com/api/acot', + clarity: isDev || useProxy ? '/api/clarity' : 'https://acob.acherryontop.com/api/clarity' }; export default liveDashboardConfig; \ No newline at end of file diff --git a/inventory/src/services/apiv2.ts b/inventory/src/services/apiv2.ts index 47473c7..0af5ade 100644 --- a/inventory/src/services/apiv2.ts +++ b/inventory/src/services/apiv2.ts @@ -29,10 +29,12 @@ export interface CreateProductCategoryResponse { category?: unknown; } -const DEV_ENDPOINT = "https://work-test-backend.acherryontop.com/apiv2/product/setup_new"; -const PROD_ENDPOINT = "https://backend.acherryontop.com/apiv2/product/setup_new"; -const DEV_CREATE_CATEGORY_ENDPOINT = "https://work-test-backend.acherryontop.com/apiv2/prod_cat/new"; -const PROD_CREATE_CATEGORY_ENDPOINT = "https://backend.acherryontop.com/apiv2/prod_cat/new"; +// Always use relative URLs - proxied by Vite in dev and Caddy in production +// Frontend calls /apiv2/* -> Caddy transforms to /api/* -> proxies to www.acherryontop.com +const DEV_ENDPOINT = "/apiv2-test/product/setup_new"; +const DEV_CREATE_CATEGORY_ENDPOINT = "/apiv2-test/prod_cat/new"; +const PROD_ENDPOINT = "/apiv2/product/setup_new"; +const PROD_CREATE_CATEGORY_ENDPOINT = "/apiv2/prod_cat/new"; const isHtmlResponse = (payload: string) => { const trimmed = payload.trim(); @@ -44,31 +46,38 @@ export async function submitNewProducts({ environment, useTestDataSource, }: SubmitNewProductsArgs): Promise { - const authToken = import.meta.env.VITE_APIV2_AUTH_TOKEN; - - if (!authToken) { - throw new Error("VITE_APIV2_AUTH_TOKEN is not configured"); - } - const baseUrl = environment === "dev" ? DEV_ENDPOINT : PROD_ENDPOINT; const targetUrl = useTestDataSource ? `${baseUrl}?use_test_data_source=1` : baseUrl; const payload = new URLSearchParams(); - payload.append("auth", authToken); - const serializedProducts = JSON.stringify(products); payload.append("products", serializedProducts); let response: Response; + const fetchOptions: RequestInit = { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + }, + body: payload, + }; + + // Authentication strategy depends on endpoint + if (environment === "dev") { + // Test endpoint: Use auth token in request body + const authToken = import.meta.env.VITE_APIV2_AUTH_TOKEN; + if (authToken) { + payload.append("auth", authToken); + fetchOptions.body = payload; + } + } else { + // Prod endpoint: Use cookies (proxied in both dev and production) + fetchOptions.credentials = "include"; + } + try { - response = await fetch(targetUrl, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", - }, - body: payload, - }); + response = await fetch(targetUrl, fetchOptions); } catch (networkError) { throw new Error(networkError instanceof Error ? networkError.message : "Network request failed"); } @@ -115,16 +124,9 @@ export async function createProductCategory({ nameForCustoms, taxCodeId, }: CreateProductCategoryArgs): Promise { - const authToken = import.meta.env.VITE_APIV2_AUTH_TOKEN; - - if (!authToken) { - throw new Error("VITE_APIV2_AUTH_TOKEN is not configured"); - } - const targetUrl = environment === "dev" ? DEV_CREATE_CATEGORY_ENDPOINT : PROD_CREATE_CATEGORY_ENDPOINT; const payload = new URLSearchParams(); - payload.append("auth", authToken); payload.append("master_cat_id", masterCatId.toString()); payload.append("name", name); @@ -142,14 +144,29 @@ export async function createProductCategory({ let response: Response; + const fetchOptions: RequestInit = { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + }, + body: payload, + }; + + // Authentication strategy depends on endpoint + if (environment === "dev") { + // Test endpoint: Use auth token in request body + const authToken = import.meta.env.VITE_APIV2_AUTH_TOKEN; + if (authToken) { + payload.append("auth", authToken); + fetchOptions.body = payload; + } + } else { + // Prod endpoint: Use cookies (proxied in both dev and production) + fetchOptions.credentials = "include"; + } + try { - response = await fetch(targetUrl, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", - }, - body: payload, - }); + response = await fetch(targetUrl, fetchOptions); } catch (networkError) { throw new Error(networkError instanceof Error ? networkError.message : "Network request failed"); } diff --git a/inventory/tsconfig.tsbuildinfo b/inventory/tsconfig.tsbuildinfo index 339c2a4..77800cd 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/dashboard/shared/dashboardbadge.tsx","./src/components/dashboard/shared/dashboardcharttooltip.tsx","./src/components/dashboard/shared/dashboardsectionheader.tsx","./src/components/dashboard/shared/dashboardskeleton.tsx","./src/components/dashboard/shared/dashboardstatcard.tsx","./src/components/dashboard/shared/dashboardstatcardmini.tsx","./src/components/dashboard/shared/dashboardstates.tsx","./src/components/dashboard/shared/dashboardtable.tsx","./src/components/dashboard/shared/index.ts","./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/validationstep/index.tsx","./src/components/product-import/steps/validationstep/components/copydownbanner.tsx","./src/components/product-import/steps/validationstep/components/floatingselectionbar.tsx","./src/components/product-import/steps/validationstep/components/initializingoverlay.tsx","./src/components/product-import/steps/validationstep/components/searchabletemplateselect.tsx","./src/components/product-import/steps/validationstep/components/validationcontainer.tsx","./src/components/product-import/steps/validationstep/components/validationfooter.tsx","./src/components/product-import/steps/validationstep/components/validationtable.tsx","./src/components/product-import/steps/validationstep/components/validationtoolbar.tsx","./src/components/product-import/steps/validationstep/components/cells/checkboxcell.tsx","./src/components/product-import/steps/validationstep/components/cells/comboboxcell.tsx","./src/components/product-import/steps/validationstep/components/cells/inputcell.tsx","./src/components/product-import/steps/validationstep/components/cells/multiselectcell.tsx","./src/components/product-import/steps/validationstep/components/cells/multilineinput.tsx","./src/components/product-import/steps/validationstep/components/cells/selectcell.tsx","./src/components/product-import/steps/validationstep/dialogs/aidebugdialog.tsx","./src/components/product-import/steps/validationstep/dialogs/aivalidationprogress.tsx","./src/components/product-import/steps/validationstep/dialogs/aivalidationresults.tsx","./src/components/product-import/steps/validationstep/hooks/usefieldoptions.ts","./src/components/product-import/steps/validationstep/hooks/useproductlines.ts","./src/components/product-import/steps/validationstep/hooks/usetemplatemanagement.ts","./src/components/product-import/steps/validationstep/hooks/useupcvalidation.ts","./src/components/product-import/steps/validationstep/hooks/usevalidationactions.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/index.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/useaiapi.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/useaiprogress.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/useaitransform.ts","./src/components/product-import/steps/validationstep/store/selectors.ts","./src/components/product-import/steps/validationstep/store/types.ts","./src/components/product-import/steps/validationstep/store/validationstore.ts","./src/components/product-import/steps/validationstep/utils/aivalidationutils.ts","./src/components/product-import/steps/validationstep/utils/countryutils.ts","./src/components/product-import/steps/validationstep/utils/datamutations.ts","./src/components/product-import/steps/validationstep/utils/priceutils.ts","./src/components/product-import/steps/validationstep/utils/upcutils.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/lib/dashboard/chartconfig.ts","./src/lib/dashboard/designtokens.ts","./src/pages/analytics.tsx","./src/pages/blackfridaydashboard.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"],"errors":true,"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/dashboard/shared/dashboardbadge.tsx","./src/components/dashboard/shared/dashboardcharttooltip.tsx","./src/components/dashboard/shared/dashboardsectionheader.tsx","./src/components/dashboard/shared/dashboardskeleton.tsx","./src/components/dashboard/shared/dashboardstatcard.tsx","./src/components/dashboard/shared/dashboardstatcardmini.tsx","./src/components/dashboard/shared/dashboardstates.tsx","./src/components/dashboard/shared/dashboardtable.tsx","./src/components/dashboard/shared/index.ts","./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/validationstep/index.tsx","./src/components/product-import/steps/validationstep/components/aisuggestionbadge.tsx","./src/components/product-import/steps/validationstep/components/copydownbanner.tsx","./src/components/product-import/steps/validationstep/components/floatingselectionbar.tsx","./src/components/product-import/steps/validationstep/components/initializingoverlay.tsx","./src/components/product-import/steps/validationstep/components/searchabletemplateselect.tsx","./src/components/product-import/steps/validationstep/components/suggestionbadges.tsx","./src/components/product-import/steps/validationstep/components/validationcontainer.tsx","./src/components/product-import/steps/validationstep/components/validationfooter.tsx","./src/components/product-import/steps/validationstep/components/validationtable.tsx","./src/components/product-import/steps/validationstep/components/validationtoolbar.tsx","./src/components/product-import/steps/validationstep/components/cells/checkboxcell.tsx","./src/components/product-import/steps/validationstep/components/cells/comboboxcell.tsx","./src/components/product-import/steps/validationstep/components/cells/inputcell.tsx","./src/components/product-import/steps/validationstep/components/cells/multiselectcell.tsx","./src/components/product-import/steps/validationstep/components/cells/multilineinput.tsx","./src/components/product-import/steps/validationstep/components/cells/selectcell.tsx","./src/components/product-import/steps/validationstep/contexts/aisuggestionscontext.tsx","./src/components/product-import/steps/validationstep/dialogs/aidebugdialog.tsx","./src/components/product-import/steps/validationstep/dialogs/aivalidationprogress.tsx","./src/components/product-import/steps/validationstep/dialogs/aivalidationresults.tsx","./src/components/product-import/steps/validationstep/dialogs/sanitycheckdialog.tsx","./src/components/product-import/steps/validationstep/hooks/useautoinlineaivalidation.ts","./src/components/product-import/steps/validationstep/hooks/usecopydownvalidation.ts","./src/components/product-import/steps/validationstep/hooks/usefieldoptions.ts","./src/components/product-import/steps/validationstep/hooks/useinlineaivalidation.ts","./src/components/product-import/steps/validationstep/hooks/useproductlines.ts","./src/components/product-import/steps/validationstep/hooks/usesanitycheck.ts","./src/components/product-import/steps/validationstep/hooks/usetemplatemanagement.ts","./src/components/product-import/steps/validationstep/hooks/useupcvalidation.ts","./src/components/product-import/steps/validationstep/hooks/usevalidationactions.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/index.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/useaiapi.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/useaiprogress.ts","./src/components/product-import/steps/validationstep/hooks/useaivalidation/useaitransform.ts","./src/components/product-import/steps/validationstep/store/selectors.ts","./src/components/product-import/steps/validationstep/store/types.ts","./src/components/product-import/steps/validationstep/store/validationstore.ts","./src/components/product-import/steps/validationstep/utils/aivalidationutils.ts","./src/components/product-import/steps/validationstep/utils/countryutils.ts","./src/components/product-import/steps/validationstep/utils/datamutations.ts","./src/components/product-import/steps/validationstep/utils/inlineaipayload.ts","./src/components/product-import/steps/validationstep/utils/priceutils.ts","./src/components/product-import/steps/validationstep/utils/upcutils.ts","./src/components/product-import/steps/validationstepold/index.tsx","./src/components/product-import/steps/validationstepold/types.ts","./src/components/product-import/steps/validationstepold/components/aivalidationdialogs.tsx","./src/components/product-import/steps/validationstepold/components/basecellcontent.tsx","./src/components/product-import/steps/validationstepold/components/initializingvalidation.tsx","./src/components/product-import/steps/validationstepold/components/searchabletemplateselect.tsx","./src/components/product-import/steps/validationstepold/components/upcvalidationtableadapter.tsx","./src/components/product-import/steps/validationstepold/components/validationcell.tsx","./src/components/product-import/steps/validationstepold/components/validationcontainer.tsx","./src/components/product-import/steps/validationstepold/components/validationtable.tsx","./src/components/product-import/steps/validationstepold/components/cells/checkboxcell.tsx","./src/components/product-import/steps/validationstepold/components/cells/inputcell.tsx","./src/components/product-import/steps/validationstepold/components/cells/multiselectcell.tsx","./src/components/product-import/steps/validationstepold/components/cells/multilineinput.tsx","./src/components/product-import/steps/validationstepold/components/cells/selectcell.tsx","./src/components/product-import/steps/validationstepold/hooks/useaivalidation.tsx","./src/components/product-import/steps/validationstepold/hooks/usefieldvalidation.tsx","./src/components/product-import/steps/validationstepold/hooks/usefiltermanagement.tsx","./src/components/product-import/steps/validationstepold/hooks/useinitialvalidation.tsx","./src/components/product-import/steps/validationstepold/hooks/useproductlinesfetching.tsx","./src/components/product-import/steps/validationstepold/hooks/userowoperations.tsx","./src/components/product-import/steps/validationstepold/hooks/usetemplatemanagement.tsx","./src/components/product-import/steps/validationstepold/hooks/useuniqueitemnumbersvalidation.tsx","./src/components/product-import/steps/validationstepold/hooks/useuniquevalidation.tsx","./src/components/product-import/steps/validationstepold/hooks/useupcvalidation.tsx","./src/components/product-import/steps/validationstepold/hooks/usevalidation.tsx","./src/components/product-import/steps/validationstepold/hooks/usevalidationstate.tsx","./src/components/product-import/steps/validationstepold/hooks/validationtypes.ts","./src/components/product-import/steps/validationstepold/types/index.ts","./src/components/product-import/steps/validationstepold/utils/aivalidationutils.ts","./src/components/product-import/steps/validationstepold/utils/countryutils.ts","./src/components/product-import/steps/validationstepold/utils/datamutations.ts","./src/components/product-import/steps/validationstepold/utils/priceutils.ts","./src/components/product-import/steps/validationstepold/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/lib/dashboard/chartconfig.ts","./src/lib/dashboard/designtokens.ts","./src/pages/analytics.tsx","./src/pages/blackfridaydashboard.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 diff --git a/inventory/vite.config.ts b/inventory/vite.config.ts index 67d39cc..c31057e 100644 --- a/inventory/vite.config.ts +++ b/inventory/vite.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { loadEnv } from "vite" import fs from 'fs-extra' +import { execSync } from 'child_process' // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { @@ -16,17 +17,42 @@ export default defineConfig(({ mode }) => { name: 'copy-build', closeBundle: async () => { if (!isDev && process.env.COPY_BUILD === 'true') { - const sourcePath = path.resolve(__dirname, 'build'); - const targetPath = path.resolve(__dirname, '../inventory-server/frontend/build'); + const sourcePath = path.resolve(__dirname, 'build/'); - try { - await fs.ensureDir(path.dirname(targetPath)); - await fs.remove(targetPath); - await fs.copy(sourcePath, targetPath); - console.log('✓ Build copied to inventory-server/frontend/build'); - } catch (error) { - console.error('Error copying build files:', error); - process.exit(1); + // Check if we should use rsync (for remote deployment) + const useRsync = process.env.DEPLOY_TARGET; + + if (useRsync) { + // Use rsync over SSH - much faster than sshfs copying + const deployTarget = process.env.DEPLOY_TARGET; + const targetPath = process.env.DEPLOY_PATH || '/var/www/html/inventory/inventory-server/frontend'; + + try { + console.log(`Deploying to ${deployTarget}:${targetPath}...`); + + // Delete remote directory first, then sync + execSync(`ssh ${deployTarget} "rm -rf ${targetPath}"`, { stdio: 'inherit' }); + execSync(`ssh ${deployTarget} "mkdir -p ${targetPath}"`, { stdio: 'inherit' }); + execSync(`rsync -avz --delete ${sourcePath} ${deployTarget}:${targetPath}/`, { stdio: 'inherit' }); + + console.log('✓ Build deployed'); + } catch (error) { + console.error('Error deploying build files:', error); + process.exit(1); + } + } else { + // Local copy (original behavior) + const targetPath = path.resolve(__dirname, '../inventory-server/frontend/build'); + + try { + await fs.ensureDir(path.dirname(targetPath)); + await fs.remove(targetPath); + await fs.copy(sourcePath, targetPath); + console.log('✓ Build copied to inventory-server/frontend/build'); + } catch (error) { + console.error('Error copying build files:', error); + process.exit(1); + } } } } @@ -44,6 +70,25 @@ export default defineConfig(({ mode }) => { host: "0.0.0.0", port: 5175, proxy: { + "/api-testv2": { + target: "https://work-test-backend.acherryontop.com", + changeOrigin: true, + secure: true, + cookieDomainRewrite: "localhost", + rewrite: (path) => path.replace(/^\/api-testv2/, "/apiv2"), + }, + "/apiv2": { + target: "https://backend.acherryontop.com", + changeOrigin: true, + secure: true, + cookieDomainRewrite: "localhost", + }, + "/login": { + target: "https://backend.acherryontop.com", + changeOrigin: true, + secure: true, + cookieDomainRewrite: "localhost", + }, "/api/aircall": { target: "https://acot.site", changeOrigin: true,