From 2fe7fd5b2f3f5b34c1dadc497def089eac15a20e Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 23 Sep 2025 11:45:01 -0400 Subject: [PATCH] Layout tweaks for financial overview, add cogs % line --- .../dashboard/FinancialOverview.tsx | 422 +++++++++++++----- .../dashboard/PeriodSelectionPopover.tsx | 80 +--- inventory/tsconfig.tsbuildinfo | 2 +- 3 files changed, 313 insertions(+), 191 deletions(-) diff --git a/inventory/src/components/dashboard/FinancialOverview.tsx b/inventory/src/components/dashboard/FinancialOverview.tsx index d1fa5af..de6139f 100644 --- a/inventory/src/components/dashboard/FinancialOverview.tsx +++ b/inventory/src/components/dashboard/FinancialOverview.tsx @@ -44,7 +44,13 @@ import { import type { TooltipProps } from "recharts"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Skeleton } from "@/components/ui/skeleton"; -import { ArrowUpRight, ArrowDownRight, Minus, TrendingUp, AlertCircle } from "lucide-react"; +import { + Tooltip as UITooltip, + TooltipContent, + TooltipTrigger, + TooltipProvider, +} from "@/components/ui/tooltip"; +import { ArrowUp, ArrowDown, Minus, TrendingUp, AlertCircle, Info } from "lucide-react"; import PeriodSelectionPopover, { type QuickPreset, } from "@/components/dashboard/PeriodSelectionPopover"; @@ -112,7 +118,7 @@ type FinancialResponse = { trend: FinancialTrendPoint[]; }; -type ChartSeriesKey = "income" | "cogs" | "profit" | "margin"; +type ChartSeriesKey = "income" | "cogs" | "cogsPercentage" | "profit" | "margin"; type GroupByOption = "day" | "month" | "quarter" | "year"; @@ -121,6 +127,7 @@ type ChartPoint = { timestamp: string | null; income: number | null; cogs: number | null; + cogsPercentage: number | null; profit: number | null; margin: number | null; tooltipLabel: string; @@ -128,15 +135,17 @@ type ChartPoint = { }; const chartColors: Record = { - income: "#2563eb", + income: "#3b82f6", cogs: "#f97316", + cogsPercentage: "#fb923c", profit: "#10b981", - margin: "#0ea5e9", + margin: "#8b5cf6", }; const SERIES_LABELS: Record = { income: "Total Income", cogs: "COGS", + cogsPercentage: "COGS % of Income", profit: "Gross Profit", margin: "Profit Margin", }; @@ -148,15 +157,16 @@ const SERIES_DEFINITIONS: Array<{ }> = [ { key: "income", label: SERIES_LABELS.income, type: "currency" }, { key: "cogs", label: SERIES_LABELS.cogs, type: "currency" }, + { key: "cogsPercentage", label: SERIES_LABELS.cogsPercentage, type: "percentage" }, { key: "profit", label: SERIES_LABELS.profit, type: "currency" }, { key: "margin", label: SERIES_LABELS.margin, type: "percentage" }, ]; const GROUP_BY_CHOICES: Array<{ value: GroupByOption; label: string }> = [ - { value: "day", label: "Daily" }, - { value: "month", label: "Monthly" }, - { value: "quarter", label: "Quarterly" }, - { value: "year", label: "Yearly" }, + { value: "day", label: "Days" }, + { value: "month", label: "Months" }, + { value: "quarter", label: "Quarters" }, + { value: "year", label: "Years" }, ]; const MONTHS = [ @@ -176,9 +186,9 @@ const MONTHS = [ const QUARTERS = ["Q1", "Q2", "Q3", "Q4"]; -const MONTH_COUNT_LIMIT = 12; -const QUARTER_COUNT_LIMIT = 8; -const YEAR_COUNT_LIMIT = 5; +const MONTH_COUNT_LIMIT = 999; +const QUARTER_COUNT_LIMIT = 999; +const YEAR_COUNT_LIMIT = 999; const formatMonthLabel = (year: number, monthIndex: number) => `${MONTHS[monthIndex]} ${year}`; @@ -252,6 +262,7 @@ type AggregatedTrendPoint = { timestamp: string; income: number; cogs: number; + cogsPercentage: number; profit: number; margin: number; isFuture: boolean; @@ -364,6 +375,7 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption): const income = bucket.income; const profit = income - bucket.cogs; const margin = income !== 0 ? (profit / income) * 100 : 0; + const cogsPercentage = income !== 0 ? (bucket.cogs / income) * 100 : 0; return { id: bucket.key, @@ -373,6 +385,7 @@ const aggregateTrendPoints = (points: RawTrendPoint[], groupBy: GroupByOption): timestamp: bucket.start.toISOString(), income, cogs: bucket.cogs, + cogsPercentage, profit, margin, isFuture: false, @@ -472,6 +485,7 @@ const extendAggregatedTrendPoints = ( timestamp: bucketStart.toISOString(), income: 0, cogs: 0, + cogsPercentage: 0, profit: 0, margin: 0, isFuture: isFutureBucket, @@ -509,14 +523,14 @@ const buildTrendLabel = ( if (options?.isPercentage) { return { direction, - label: `${absolute > 0 ? "+" : absolute < 0 ? "-" : ""}${formatPercentage(absoluteValue, 1, "pp")} vs previous`, + label: `${absolute > 0 ? "+" : absolute < 0 ? "-" : ""}${formatPercentage(absoluteValue, 1, "%")}`, }; } if (typeof percentage === "number" && Number.isFinite(percentage)) { return { direction, - label: `${absolute > 0 ? "+" : absolute < 0 ? "-" : ""}${formatPercentage(Math.abs(percentage), 1)} vs previous`, + label: `${absolute > 0 ? "+" : absolute < 0 ? "-" : ""}${formatPercentage(Math.abs(percentage), 1)}`, }; } @@ -630,6 +644,7 @@ const FinancialOverview = () => { const [metrics, setMetrics] = useState>({ income: true, cogs: true, + cogsPercentage: true, profit: true, margin: true, }); @@ -836,14 +851,7 @@ const FinancialOverview = () => { const cards = useMemo( () => { if (!data?.totals) { - return [] as Array<{ - key: string; - title: string; - value: string; - description?: string; - trend: TrendSummary | null; - accentClass: string; - }>; + return [] as FinancialStatCardConfig[]; } const totals = data.totals; @@ -880,43 +888,57 @@ const FinancialOverview = () => { : computeMarginFrom(previousProfitValue ?? 0, previousIncome) : null; + const incomeDescription = previousIncome != null ? `Previous: ${safeCurrency(previousIncome, 0)}` : undefined; + const cogsDescription = previousCogs != null ? `Previous: ${safeCurrency(previousCogs, 0)}` : undefined; + const profitDescription = previousProfitValue != null ? `Previous: ${safeCurrency(previousProfitValue, 0)}` : undefined; + const marginDescription = previousMarginValue != null ? `Previous: ${safePercentage(previousMarginValue, 1)}` : undefined; + return [ { key: "income", title: "Total Income", value: safeCurrency(totalIncome, 0), - description: previousIncome != null ? `Previous: ${safeCurrency(previousIncome, 0)}` : undefined, + description: incomeDescription, trend: buildTrendLabel(comparison?.income ?? buildComparisonFromValues(totalIncome, previousIncome ?? null)), - accentClass: "text-indigo-600 dark:text-indigo-400", + accentClass: "text-blue-500 dark:text-blue-400", + tooltip: + "Gross sales minus refunds and discounts, plus shipping fees collected (shipping, small-order, and rush fees). Taxes are excluded.", + showDescription: incomeDescription != null, }, { key: "cogs", title: "COGS", value: safeCurrency(cogsValue, 0), - description: previousCogs != null ? `Previous: ${safeCurrency(previousCogs, 0)}` : undefined, + description: cogsDescription, trend: buildTrendLabel(comparison?.cogs ?? buildComparisonFromValues(cogsValue, previousCogs ?? null), { invertDirection: true, }), - accentClass: "text-amber-600 dark:text-amber-400", + accentClass: "text-orange-500 dark:text-orange-400", + tooltip: "Sum of reported product cost of goods sold (cogs_amount) for completed sales actions in the period.", + showDescription: cogsDescription != null, }, { key: "profit", title: "Gross Profit", value: safeCurrency(profitValue, 0), - description: previousProfitValue != null ? `Previous: ${safeCurrency(previousProfitValue, 0)}` : undefined, + description: profitDescription, trend: buildTrendLabel(comparison?.profit ?? buildComparisonFromValues(profitValue, previousProfitValue ?? null)), - accentClass: "text-emerald-600 dark:text-emerald-400", + accentClass: "text-emerald-500 dark:text-emerald-400", + tooltip: "Total Income minus COGS.", + showDescription: profitDescription != null, }, { key: "margin", title: "Profit Margin", value: safePercentage(marginValue, 1), - description: previousMarginValue != null ? `Previous: ${safePercentage(previousMarginValue, 1)}` : undefined, + description: marginDescription, trend: buildTrendLabel( comparison?.margin ?? buildComparisonFromValues(marginValue, previousMarginValue ?? null), { isPercentage: true } ), - accentClass: "text-sky-600 dark:text-sky-400", + accentClass: "text-purple-500 dark:text-purple-400", + tooltip: "Gross Profit divided by Total Income, expressed as a percentage.", + showDescription: marginDescription != null, }, ]; }, @@ -938,6 +960,7 @@ const FinancialOverview = () => { timestamp: point.timestamp, income: point.isFuture ? null : point.income, cogs: point.isFuture ? null : point.cogs, + cogsPercentage: point.isFuture ? null : point.cogsPercentage, profit: point.isFuture ? null : point.profit, margin: point.isFuture ? null : point.margin, tooltipLabel: point.tooltipLabel, @@ -966,20 +989,34 @@ const FinancialOverview = () => { const partialLabel = effectiveRangeEnd.toLocaleDateString("en-US", { month: "short", day: "numeric", - year: "numeric", + }); return `${label} (through ${partialLabel})`; }, [isLast30DaysMode, customPeriod, selectedRange, effectiveRangeEnd]); - const marginDomain = useMemo<[number, number]>(() => { - if (!metrics.margin || !chartData.length) { + const percentageDomain = useMemo<[number, number]>(() => { + if ((!metrics.margin && !metrics.cogsPercentage) || !chartData.length) { return [0, 100]; } - const values = chartData - .map((point) => point.margin) - .filter((value): value is number => typeof value === "number" && Number.isFinite(value)); + const values: number[] = []; + + if (metrics.margin) { + values.push( + ...chartData + .map((point) => point.margin) + .filter((value): value is number => typeof value === "number" && Number.isFinite(value)) + ); + } + + if (metrics.cogsPercentage) { + values.push( + ...chartData + .map((point) => point.cogsPercentage) + .filter((value): value is number => typeof value === "number" && Number.isFinite(value)) + ); + } if (!values.length) { return [0, 100]; @@ -995,9 +1032,10 @@ const FinancialOverview = () => { const padding = (max - min) * 0.1; return [min - padding, max + padding]; - }, [chartData, metrics.margin]); + }, [chartData, metrics.margin, metrics.cogsPercentage]); const hasActiveMetrics = useMemo(() => Object.values(metrics).some(Boolean), [metrics]); + const showPercentageAxis = metrics.margin || metrics.cogsPercentage; const detailRows = useMemo( () => @@ -1007,6 +1045,7 @@ const FinancialOverview = () => { timestamp: point.timestamp, income: point.income, cogs: point.cogs, + cogsPercentage: point.cogsPercentage, profit: point.profit, margin: point.margin, isFuture: point.isFuture, @@ -1124,16 +1163,7 @@ const FinancialOverview = () => {
{!error && ( <> - - +
+ + + )} @@ -1276,8 +1327,11 @@ const FinancialOverview = () => { className="sm:hidden w-20 my-2" /> +
+
Group By:
+ + + +
)} @@ -1327,19 +1382,15 @@ const FinancialOverview = () => { ) : ( - + - - - - - - + + - - + + { className="text-xs text-muted-foreground" tick={{ fill: "currentColor" }} /> - {metrics.margin && ( + {showPercentageAxis && ( formatPercentage(value, 0)} - domain={marginDomain} + domain={percentageDomain} className="text-xs text-muted-foreground" tick={{ fill: "currentColor" }} /> )} } /> SERIES_LABELS[value as ChartSeriesKey] ?? value} /> - {metrics.income ? ( - - ) : null} + {/* Stacked areas showing revenue breakdown */} {metrics.cogs ? ( ) : null} {metrics.profit ? ( @@ -1396,10 +1438,39 @@ const FinancialOverview = () => { yAxisId="left" type="monotone" dataKey="profit" + stackId="revenue" name={SERIES_LABELS.profit} stroke={chartColors.profit} fill="url(#financialProfit)" + strokeWidth={1} + /> + ) : null} + {/* Show total income as a line for reference if selected */} + {metrics.income ? ( + + ) : null} + {metrics.cogsPercentage ? ( + ) : null} {metrics.margin ? ( @@ -1426,24 +1497,41 @@ const FinancialOverview = () => { ); }; +type FinancialStatCardConfig = { + key: string; + title: string; + value: string; + description?: string; + trend: TrendSummary | null; + accentClass: string; + tooltip?: string; + isLoading?: boolean; + showDescription?: boolean; +}; + function FinancialStatGrid({ cards, }: { - cards: Array<{ - key: string; - title: string; - value: string; - description?: string; - trend: TrendSummary | null; - accentClass: string; - }>; + cards: FinancialStatCardConfig[]; }) { return ( -
- {cards.map((card) => ( - - ))} -
+ +
+ {cards.map((card) => ( + + ))} +
+
); } @@ -1453,18 +1541,69 @@ function FinancialStatCard({ description, trend, accentClass, + tooltip, + isLoading, + showDescription, }: { title: string; value: string; description?: string; trend: TrendSummary | null; accentClass: string; + tooltip?: string; + isLoading?: boolean; + showDescription?: boolean; }) { + const shouldShowDescription = isLoading ? showDescription !== false : Boolean(description); + return ( - {title} - {trend?.label && ( +
+ {isLoading ? ( + <> + + Placeholder + + + + + + + + + + ) : ( + <> + {title} + {tooltip ? ( + + + + + + {tooltip} + + + ) : null} + + )} +
+ {isLoading ? ( + + + + Placeholder + + + + ) : trend?.label ? ( {trend.direction === "up" ? ( - + ) : trend.direction === "down" ? ( - + ) : ( )} {trend.label} - )} + ) : null}
- {value} + {isLoading ? ( + + 0 + + + ) : ( + value + )}
- {description && ( + {isLoading ? ( + shouldShowDescription ? ( +
+ + Placeholder text + + +
+ ) : null + ) : description ? (
{description}
- )} + ) : null}
); @@ -1499,18 +1654,18 @@ function FinancialStatCard({ function SkeletonStats() { return ( -
+
{Array.from({ length: 4 }).map((_, index) => ( - - - - - - - - - - + ))}
); @@ -1518,36 +1673,46 @@ function SkeletonStats() { function SkeletonChart() { return ( -
+
{/* Grid lines */} {[...Array(6)].map((_, i) => (
))} {/* Y-axis labels */}
{[...Array(6)].map((_, i) => ( - + ))}
{/* X-axis labels */}
{[...Array(7)].map((_, i) => ( - + ))}
- {/* Chart line */} + {/* Chart area */}
+ {/* Simulated line chart */} +
@@ -1575,19 +1740,41 @@ const FinancialTooltip = ({ active, payload, label }: TooltipProps [entry.dataKey as ChartSeriesKey, entry])); + + // Sort payload according to desired order + const orderedPayload = desiredOrder + .map(key => payloadMap.get(key)) + .filter((entry): entry is typeof payload[0] => entry !== undefined); + return (

{resolvedLabel}

- {payload.map((entry, index) => { + {orderedPayload.map((entry, index) => { const key = (entry.dataKey ?? "") as ChartSeriesKey; const rawValue = entry.value; let formattedValue: string; + let percentageOfRevenue = ""; if (isFuturePoint || rawValue == null) { formattedValue = "—"; } else if (typeof rawValue === "number") { - formattedValue = key === "margin" ? formatPercentage(rawValue, 1) : formatCurrency(rawValue, 0); + const isPercentageSeries = key === "margin" || key === "cogsPercentage"; + formattedValue = isPercentageSeries ? formatPercentage(rawValue, 1) : formatCurrency(rawValue, 0); + + // Add percentage of revenue for COGS only + if (key === "cogs" && income > 0 && !isFuturePoint) { + const percentage = (rawValue / income) * 100; + percentageOfRevenue = ` (${formatPercentage(percentage, 1)})`; + } } else if (typeof rawValue === "string") { formattedValue = rawValue; } else { @@ -1596,10 +1783,15 @@ const FinancialTooltip = ({ active, payload, label }: TooltipProps - + {SERIES_LABELS[key] ?? entry.name ?? key} - {formattedValue} + + {formattedValue}{percentageOfRevenue} +
); })} diff --git a/inventory/src/components/dashboard/PeriodSelectionPopover.tsx b/inventory/src/components/dashboard/PeriodSelectionPopover.tsx index a77c30d..20167fb 100644 --- a/inventory/src/components/dashboard/PeriodSelectionPopover.tsx +++ b/inventory/src/components/dashboard/PeriodSelectionPopover.tsx @@ -7,7 +7,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { Calendar } from "lucide-react"; +import { ChevronDown } from "lucide-react"; import { generateNaturalLanguagePreview, parseNaturalLanguagePeriod, @@ -22,29 +22,6 @@ export type QuickPreset = | "lastQuarter" | "thisYear"; -const SUGGESTIONS = [ - "last 30 days", - "this month", - "last month", - "this quarter", - "last quarter", - "this year", - "last year", - "last 3 months", - "last 6 months", - "last 2 quarters", - "Q1 2024", - "q1-q3 24", - "q1 24 - q2 25", - "January 2024", - "jan-24", - "jan-may 24", - "2023", - "2021-2023", - "21-23", - "January to March 2024", - "jan 2023 - may 2024", -]; interface PeriodSelectionPopoverProps { open: boolean; @@ -66,17 +43,7 @@ const PeriodSelectionPopover = ({ onApplyResult, }: PeriodSelectionPopoverProps) => { const [inputValue, setInputValue] = useState(""); - const [showSuggestions, setShowSuggestions] = useState(false); - const filteredSuggestions = useMemo(() => { - if (!inputValue) { - return SUGGESTIONS; - } - return SUGGESTIONS.filter((suggestion) => - suggestion.toLowerCase().includes(inputValue.toLowerCase()) && - suggestion.toLowerCase() !== inputValue.toLowerCase() - ); - }, [inputValue]); const preview = useMemo(() => { if (!inputValue) { @@ -95,7 +62,6 @@ const PeriodSelectionPopover = ({ const resetInput = () => { setInputValue(""); - setShowSuggestions(false); }; const applyResult = (value: string) => { @@ -110,7 +76,6 @@ const PeriodSelectionPopover = ({ const handleInputChange = (value: string) => { setInputValue(value); - setShowSuggestions(value.length > 0); }; const handleKeyDown: KeyboardEventHandler = (event) => { @@ -123,10 +88,6 @@ const PeriodSelectionPopover = ({ } }; - const handleSuggestionClick = (suggestion: string) => { - setInputValue(suggestion); - applyResult(suggestion); - }; const handleQuickSelect = (preset: QuickPreset) => { onQuickSelect(preset); @@ -138,8 +99,8 @@ const PeriodSelectionPopover = ({ @@ -200,11 +161,10 @@ const PeriodSelectionPopover = ({
-
Or type a custom period
+
Or enter a custom period:
handleInputChange(event.target.value)} onKeyDown={handleKeyDown} @@ -212,52 +172,22 @@ const PeriodSelectionPopover = ({ /> {inputValue && ( -
+
{preview.label ? (
- Recognized as: {preview.label}
) : (
- Not recognized - try a different format + Not recognized
)}
)} - {showSuggestions && filteredSuggestions.length > 0 && ( -
- {filteredSuggestions.slice(0, 6).map((suggestion) => ( - - ))} -
- )} - {inputValue === "" && ( -
-
Examples:
-
- {SUGGESTIONS.slice(0, 6).map((suggestion) => ( - - ))} -
-
- )}
diff --git a/inventory/tsconfig.tsbuildinfo b/inventory/tsconfig.tsbuildinfo index 608e8c4..18ab64e 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/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/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/reactspreadsheetimport.tsx","./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/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/validationstepnew.tsx","./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/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/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/datamutations.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/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/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/types/dashboard.d.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/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/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/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/reactspreadsheetimport.tsx","./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/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/validationstepnew.tsx","./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/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/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/datamutations.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/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/types/dashboard-shims.d.ts","./src/types/dashboard.d.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