Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions .tsc-baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"generatedAt": "2026-05-10T11:37:50.467Z",
"totalErrors": 841,
"generatedAt": "2026-05-10T17:36:00.315Z",
"totalErrors": 811,
"counts": {
"src/components/admin/DiscountApprovalQueue.tsx": {
"TS18048": 1
Expand Down Expand Up @@ -221,10 +221,6 @@
"src/components/mockup/MockupConfigPanel.tsx": {
"TS2322": 1
},
"src/components/mockup/MockupHistoryPanel.tsx": {
"TS2322": 1,
"TS2719": 1
},
"src/components/mockup/MockupProductSelector.tsx": {
"TS2345": 2
},
Expand Down Expand Up @@ -566,6 +562,7 @@
"TS18046": 9
},
"src/hooks/useMockupGenerator.ts": {
"TS2345": 1,
"TS2339": 2
},
"src/hooks/useNoveltiesSelectionMode.ts": {
Expand Down Expand Up @@ -716,13 +713,6 @@
"TS2551": 2,
"TS2322": 3
},
"src/pages/MockupGenerator.tsx": {
"TS2322": 6,
"TS2339": 19,
"TS2345": 2,
"TS2353": 1,
"TS7006": 1
},
"src/pages/ProductDetail.tsx": {
"TS2322": 9
},
Expand Down
9 changes: 8 additions & 1 deletion src/components/ai/AIMockupAssistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ interface QuickAction {
interface AIMockupAssistantProps {
productName?: string;
techniqueName?: string;
onSuggestionApply?: (type: string, value: any) => void;
/** Legacy callback (type/value pair). */
onSuggestionApply?: (type: string, value: unknown) => void;
/** Modern callback receiving structured suggestion (techniqueId, position, etc). */
onApplySuggestion?: (suggestion: {
techniqueId?: string;
position?: { x: number; y: number };
[key: string]: unknown;
}) => void;
Comment on lines 37 to +47
Comment on lines +40 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

onApplySuggestion foi tipado, mas não é executado

A prop nova (e também a legada) nunca é chamada no fluxo do componente. Em runtime, integrações como MockupGenerator não recebem sugestão aplicada.

💡 Ajuste sugerido
 export function AIMockupAssistant({
   productName,
   techniqueName,
   onSuggestionApply,
+  onApplySuggestion,
   className,
 }: AIMockupAssistantProps) {
@@
   const handleQuickAction = (action: QuickAction) => {
+    const payload =
+      action.id === "position"
+        ? { position: { x: 50, y: 50 } }
+        : { actionId: action.id };
+
+    onApplySuggestion?.(payload);
+    onSuggestionApply?.(action.id, payload);
+
     const userMessage: Message = {

Also applies to: 68-73, 115-144

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ai/AIMockupAssistant.tsx` around lines 40 - 47, The new prop
onApplySuggestion is never invoked: find the places in AIMockupAssistant where
suggestions are applied (the same spot(s) currently calling the legacy
onSuggestionApply) and add calls to onApplySuggestion as well, passing a
structured suggestion object containing techniqueId, position (x,y) and any
other metadata; ensure you still call onSuggestionApply for backward
compatibility and guard both with existence checks (e.g., if
(props.onApplySuggestion) props.onApplySuggestion({...})). Update all
suggestion-apply flows in AIMockupAssistant (including the other similar blocks
noted) so integrations like MockupGenerator receive the modern structured
suggestion.

className?: string;
}

Expand Down
25 changes: 3 additions & 22 deletions src/components/mockup/MockupHistoryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,10 @@ import { ptBR } from "date-fns/locale";
import { MockupHistorySkeleton } from "./MockupSkeleton";
import { MockupCompareDialog } from "./MockupCompareDialog";
import { MockupLightbox } from "./MockupLightbox";
import type { GeneratedMockup } from "@/hooks/mockup/mockupGenerationService";
import { ShareMenu } from "./ShareMenu";

interface GeneratedMockup {
id: string;
product_id: string | null;
product_name: string;
product_sku: string | null;
technique_id: string | null;
technique_name: string;
mockup_url: string;
layout_url: string | null;
logo_url: string;
position_x: number | null;
position_y: number | null;
logo_width_cm: number | null;
logo_height_cm: number | null;
location_name: string | null;
colors_count: number | null;
created_at: string;
client_id: string | null;
client_name: string | null;
annotations: any | null;
}
// GeneratedMockup importado de @/hooks/mockup/mockupGenerationService (SSOT)

interface Technique { id: string; name: string; code: string | null; }
interface Client { id: string; name: string; }
Expand Down Expand Up @@ -79,7 +60,7 @@ export function MockupHistoryPanel({
const [gridColumns, setGridColumns] = useState<ColumnCount>(() => getDefaultColumns());
const [lightboxMockup, setLightboxMockup] = useState<GeneratedMockup | null>(null);

const handleSetViewMode = useCallback((mode: "grid" | "list") => { setViewMode(mode); setCurrentPage(1); }, []);
const handleSetViewMode = useCallback((mode: "grid" | "list" | "table") => { setViewMode(mode === "table" ? "list" : mode); setCurrentPage(1); }, []);
const toggleCompareSelection = useCallback((id: string) => {
setSelectedForCompare(prev => {
const next = new Set(prev);
Expand Down
14 changes: 2 additions & 12 deletions src/components/mockup/MockupLightbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,9 @@ import {
import { formatDistanceToNow } from "date-fns";
import { ptBR } from "date-fns/locale";
import { ShareMenu } from "./ShareMenu";
import type { GeneratedMockup } from "@/hooks/mockup/mockupGenerationService";

interface GeneratedMockup {
id: string;
product_name: string;
product_sku: string | null;
technique_name: string;
mockup_url: string;
layout_url: string | null;
logo_url: string;
location_name: string | null;
created_at: string;
client_name: string | null;
}
// GeneratedMockup importado de @/hooks/mockup/mockupGenerationService (SSOT)

interface MockupLightboxProps {
mockup: GeneratedMockup | null;
Expand Down
2 changes: 2 additions & 0 deletions src/components/mockup/MultiAreaManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface PersonalizationArea {
logoRotation?: number;
logoScale?: number;
logoPreview: string | null;
/** File handle for the logo (used during upload, before persistence). */
logoFile?: File | null;
Comment on lines +25 to +26
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sincronize logoFile nos fluxos de copiar/remover logo

Com o novo campo em Line 25, os fluxos de cópia e remoção continuam mexendo só em logoPreview (Line 66 e Line 132). Isso pode deixar logoFile stale e gerar estado inconsistente entre áreas.

💡 Ajuste sugerido
 const applyLogoToAllAreas = () => {
   const activeArea = areas.find((a) => a.id === activeAreaId);
   if (!activeArea?.logoPreview) { toast.error("Selecione uma área com logo primeiro"); return; }
-  onAreasChange(areas.map((a) => ({ ...a, logoPreview: activeArea.logoPreview })));
+  onAreasChange(areas.map((a) => ({
+    ...a,
+    logoPreview: activeArea.logoPreview,
+    logoFile: activeArea.logoFile ?? null,
+  })));
   toast.success(`Logo aplicado em ${areas.length} áreas`);
 };
 ...
 onLogoRemove={() => {
   const updated = areas.map(a =>
     a.id === area.id
-      ? { ...a, logoData: null, logoPreview: null }
+      ? { ...a, logoData: null, logoPreview: null, logoFile: null }
       : a
   );

Also applies to: 63-67, 129-133

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/mockup/MultiAreaManager.tsx` around lines 25 - 26, Os fluxos
de copiar/remover logo estão só atualizando logoPreview e deixam logoFile
desincronizado; atualize os handlers responsáveis pelo copy logo e remove logo
(os lugares que chamam setLogoPreview — referências a logoPreview nas rotas de
cópia/remoção) para também atualizar logoFile: quando copiar/colar uma imagem,
atribua o File apropriado a logoFile (ou recrie um File/Blob consistente com
logoPreview) e quando remover, limpe logoFile para null além de limpar
logoPreview; garanta que ambos os estados (logoPreview e logoFile) são
atualizados atomically no mesmo setState/update function para evitar estados
inconsistentes.

// ─── Metadata vinda do RPC fn_get_product_customization_options ───
Comment on lines 24 to 27
/** Largura máxima de gravação na área (cm) */
maxWidthCm?: number | null;
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/useMockupTechniques.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface Technique {
id: string;
name: string;
code: string | null;
/** Permite Technique ser atribuível a MockupTechnique (que aceita campos arbitrários do bridge). */
[key: string]: unknown;
}

Comment on lines +21 to 24
export interface TechniqueWithLimits extends Technique {
Expand Down
39 changes: 21 additions & 18 deletions src/pages/MockupGenerator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ export default function MockupGenerator() {

const technique = useTechniqueHandlers({
hasLogo: mg.hasLogo,
selectedTechnique: mg.selectedTechnique,
setSelectedTechnique: mg.setSelectedTechnique,
// Cast: useMockupGenerator narrows to Technique|TechniqueWithLimits; useTechniqueHandlers
// expects MockupTechnique (less restrictive due to [key: string]: unknown). Both are compatible
// structurally, but TS can't widen a Dispatch<SetStateAction<T>> to a (t: U) => void without help.
selectedTechnique: mg.selectedTechnique as MockupTechnique | null,
setSelectedTechnique: mg.setSelectedTechnique as (t: MockupTechnique | null) => void,
setGeneratedMockup: mg.setGeneratedMockup,
setTechniqueColorConfig: mg.setTechniqueColorConfig,
});
Expand Down Expand Up @@ -129,8 +132,8 @@ export default function MockupGenerator() {
name: mg.selectedProduct.name,
sku: mg.selectedProduct.sku,
imageUrl: mg.getProductImage() || undefined,
color: mg.productSelection?.selectedColor?.name,
colorHex: mg.productSelection?.selectedColor?.hex,
color: mg.productSelection?.colorName,
colorHex: mg.productSelection?.colorHex,
material: mg.selectedProduct.materials?.[0],
heightCm: mg.selectedProduct.dimensions?.height_cm ?? null,
widthCm: mg.selectedProduct.dimensions?.width_cm ?? null,
Expand All @@ -140,23 +143,23 @@ export default function MockupGenerator() {
weightG: mg.selectedProduct.dimensions?.weight_g ?? null,
},
personalization: {
techniqueName: tech.name,
techniqueCode: tech.code || undefined,
techniqueName: tech.name ?? '',
techniqueCode: tech.code ?? '',
Comment on lines +146 to +147
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Evite "" para techniqueCode no payload de aprovação

Em Line 147, tech.code ?? '' transforma “não informado” em string vazia. Isso altera semântica do contrato e pode quebrar validação/consumo downstream.

💡 Ajuste sugerido
-        techniqueCode: tech.code ?? '',
+        techniqueCode: tech.code ?? undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
techniqueName: tech.name ?? '',
techniqueCode: tech.code ?? '',
techniqueName: tech.name ?? '',
techniqueCode: tech.code ?? undefined,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/MockupGenerator.tsx` around lines 146 - 147, O campo techniqueCode
no payload de aprovação está sendo normalizado para string vazia (tech.code ??
'') e isso altera a semântica do contrato; em vez de forçar '', preserve
undefined/omissão quando não informado: use tech.code ?? undefined ou
condicionalmente omita a chave ao construir o objeto que contém techniqueCode;
mantenha techniqueName como hoje (tech.name ?? '') e actualize a construção do
payload onde techniqueCode é usado (referência: techniqueCode, techniqueName,
tech.code, tech.name) para garantir que consumidores downstream recebam
undefined/ausência em vez de "".

locationName: tech.locationName ?? mg.activeArea?.name ?? "Frente",
widthCm: mg.activeArea?.logoWidth || 0,
heightCm: mg.activeArea?.logoHeight || 0,
colorsCount: mg.techniqueColorConfig?.colorCount,
},
pantoneColors: (mg.logoColorAnalysis.colors || []).map((c: { selectedPantone?: string; pantoneMatch?: { name?: string }; name?: string; hex: string }) => ({
name: c.selectedPantone || c.pantoneMatch?.name || c.name || "",
pantoneColors: (mg.logoColorAnalysis.colors || []).map((c) => ({
name: c.selectedPantone || c.pantoneMatch?.pantoneCode || c.name || "",
hex: c.hex,
})),
mockupImageUrl: mockupUrl,
layoutMode: mg.lastSavedLayoutMode,
};

return { data: approvalData, recordId: mg.lastSavedRecordId, userId: user.id };
}, [mg.lastSavedRecordId, mg.lastSavedMockupUrl, mg.lastSavedLayoutMode, user?.id, mg.selectedProduct, mg.selectedTechnique, mg.selectedClient, mg.activeArea?.logoWidth, mg.activeArea?.logoHeight, mg.activeArea?.name, mg.generatedMockup, profile, mg.techniqueColorConfig?.colorCount, mg.logoColorAnalysis.colors, mg.productSelection?.selectedColor?.name, mg.productSelection?.selectedColor?.hex, mg.getProductImage]);
}, [mg.lastSavedRecordId, mg.lastSavedMockupUrl, mg.lastSavedLayoutMode, user?.id, mg.selectedProduct, mg.selectedTechnique, mg.selectedClient, mg.activeArea?.logoWidth, mg.activeArea?.logoHeight, mg.activeArea?.name, mg.generatedMockup, profile, mg.techniqueColorConfig?.colorCount, mg.logoColorAnalysis.colors, mg.productSelection?.colorName, mg.productSelection?.colorHex, mg.getProductImage]);

const handleLayoutCaptured = useCallback(() => {
mg.setLastSavedRecordId(null);
Expand Down Expand Up @@ -276,7 +279,7 @@ export default function MockupGenerator() {
personalizationAreas={mg.personalizationAreas}
filteredTechniques={mg.filteredTechniques}
onProductSelect={(sel) => { mg.setProductSelection(sel); mg.setGeneratedMockup(null); }}
onTechniqueSelect={technique.handleTechniqueChange}
onTechniqueSelect={(t) => technique.handleTechniqueChange(t as MockupTechnique | null)}
onClientSelect={mg.setSelectedClient}
onReset={mg.resetForm}
activeAreaId={mg.activeAreaId}
Expand Down Expand Up @@ -310,8 +313,8 @@ export default function MockupGenerator() {
logoScale={mg.activeArea.logoScale ?? 100}
techniqueCode={mg.selectedTechnique?.code}
techniqueName={mg.selectedTechnique?.name}
maxWidth={mg.selectedTechnique?.maxWidth ?? null}
maxHeight={mg.selectedTechnique?.maxHeight ?? null}
maxWidth={(mg.selectedTechnique && 'maxWidth' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxWidth: number | null }).maxWidth : null}
maxHeight={(mg.selectedTechnique && 'maxHeight' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxHeight: number | null }).maxHeight : null}
Comment on lines +316 to +317
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Faça narrowing real antes de usar maxWidth/maxHeight/locationName

Os casts nessas linhas assumem tipo válido sem validar runtime. Como a técnica aceita campos arbitrários (unknown), pode entrar valor inválido e quebrar cálculo/renderização.

💡 Ajuste sugerido
-                      maxWidth={(mg.selectedTechnique && 'maxWidth' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxWidth: number | null }).maxWidth : null}
-                      maxHeight={(mg.selectedTechnique && 'maxHeight' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxHeight: number | null }).maxHeight : null}
+                      maxWidth={
+                        mg.selectedTechnique &&
+                        typeof (mg.selectedTechnique as Record<string, unknown>).maxWidth === "number"
+                          ? ((mg.selectedTechnique as Record<string, unknown>).maxWidth as number)
+                          : null
+                      }
+                      maxHeight={
+                        mg.selectedTechnique &&
+                        typeof (mg.selectedTechnique as Record<string, unknown>).maxHeight === "number"
+                          ? ((mg.selectedTechnique as Record<string, unknown>).maxHeight as number)
+                          : null
+                      }
@@
-                            maxWidth: ('maxWidth' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxWidth: number | null }).maxWidth : null,
-                            maxHeight: ('maxHeight' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxHeight: number | null }).maxHeight : null,
-                            locationName: ('locationName' in mg.selectedTechnique) ? (mg.selectedTechnique as { locationName: string | null }).locationName : null,
+                            maxWidth: typeof (mg.selectedTechnique as Record<string, unknown>).maxWidth === "number"
+                              ? ((mg.selectedTechnique as Record<string, unknown>).maxWidth as number)
+                              : null,
+                            maxHeight: typeof (mg.selectedTechnique as Record<string, unknown>).maxHeight === "number"
+                              ? ((mg.selectedTechnique as Record<string, unknown>).maxHeight as number)
+                              : null,
+                            locationName: typeof (mg.selectedTechnique as Record<string, unknown>).locationName === "string"
+                              ? ((mg.selectedTechnique as Record<string, unknown>).locationName as string)
+                              : null,

As per coding guidelines **/*.{ts,tsx,js,jsx}: Código TypeScript/JavaScript. Verificar: any/unknown sem narrowing posterior.

Also applies to: 345-347

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/MockupGenerator.tsx` around lines 316 - 317, mg.selectedTechnique
is typed as unknown and you're forcibly casting to access
maxWidth/maxHeight/locationName which can crash at runtime; add real narrowing
checks (e.g., implement type-guard functions like isTechniqueWithMaxWidth(t): t
is { maxWidth: number | null } and similar for maxHeight/locationName) or
validate with typeof/hasOwnProperty before reading the properties, then replace
the inline casts in the usages around mg.selectedTechnique (the expressions that
read maxWidth, maxHeight and locationName) to use those guards so only validated
values are accessed and fallback values are used when validation fails.

productHeightCm={mg.selectedProduct?.dimensions?.height_cm ?? (mg.selectedProduct?.metadata?.height_mm ? mg.selectedProduct.metadata.height_mm / 10 : null)}
productWidthCm={mg.selectedProduct?.dimensions?.width_cm ?? mg.selectedProduct?.dimensions?.diameter_cm ?? (mg.selectedProduct?.metadata?.width_mm ? mg.selectedProduct.metadata.width_mm / 10 : null)}
Comment on lines 318 to 319
onPositionChange={(x, y) => mg.updateActiveArea({ positionX: x, positionY: y })}
Expand All @@ -326,8 +329,8 @@ export default function MockupGenerator() {
product={mg.selectedProduct ? {
name: mg.selectedProduct.name, sku: mg.selectedProduct.sku,
imageUrl: mg.getProductImage() || undefined,
color: mg.productSelection?.selectedColor?.name,
colorHex: mg.productSelection?.selectedColor?.hex,
color: mg.productSelection?.colorName,
colorHex: mg.productSelection?.colorHex,
material: mg.selectedProduct.materials?.[0],
heightCm: mg.selectedProduct.dimensions?.height_cm ?? null,
widthCm: mg.selectedProduct.dimensions?.width_cm ?? null,
Expand All @@ -339,9 +342,9 @@ export default function MockupGenerator() {
technique={mg.selectedTechnique ? {
name: mg.selectedTechnique.name,
code: mg.selectedTechnique.code,
maxWidth: mg.selectedTechnique.maxWidth ?? null,
maxHeight: mg.selectedTechnique.maxHeight ?? null,
locationName: mg.selectedTechnique.locationName ?? null,
maxWidth: ('maxWidth' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxWidth: number | null }).maxWidth : null,
maxHeight: ('maxHeight' in mg.selectedTechnique) ? (mg.selectedTechnique as { maxHeight: number | null }).maxHeight : null,
locationName: ('locationName' in mg.selectedTechnique) ? (mg.selectedTechnique as { locationName: string | null }).locationName : null,
} : null}
client={mg.selectedClient}
seller={profile ? { name: profile.full_name || "—", email: profile.email || undefined } : null}
Expand Down Expand Up @@ -406,7 +409,7 @@ export default function MockupGenerator() {
<AIMockupAssistant onApplySuggestion={(suggestion) => {
if (suggestion.techniqueId) {
const tech = mg.techniques.find(t => t.id === suggestion.techniqueId);
if (tech) technique.handleTechniqueChange(tech);
if (tech) technique.handleTechniqueChange(tech as MockupTechnique);
}
if (suggestion.position) {
mg.updateActiveArea({ positionX: suggestion.position.x, positionY: suggestion.position.y });
Expand Down
3 changes: 3 additions & 0 deletions src/types/product-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export interface Product {
priceUpdatedAt?: string | null;
/** Per-product override (in days) for the "stale price" alert threshold. Default = 60. */
priceFreshnessThresholdDays?: number | null;

/** Raw metadata blob (legacy fields like height_mm, width_mm, etc — JSONB on DB). */
metadata?: { height_mm?: number | null; width_mm?: number | null; [key: string]: unknown } | null;
}

export interface KitComponent {
Expand Down
Loading