diff --git a/.tsc-baseline.json b/.tsc-baseline.json index 2f0ee90de..a8e7a156e 100644 --- a/.tsc-baseline.json +++ b/.tsc-baseline.json @@ -1,6 +1,6 @@ { - "generatedAt": "2026-05-29T16:46:02.467Z", - "totalErrors": 123, + "generatedAt": "2026-05-31T13:23:04.590Z", + "totalErrors": 62, "counts": { "src/components/mockup/approval/OffscreenLayoutCapture.tsx": { "TS2345": 1 @@ -8,12 +8,6 @@ "src/components/search/AdvancedSearch.tsx": { "TS2345": 1 }, - "src/hooks/admin/useAllowedIPs.ts": { - "TS2322": 5, - "TS2345": 3, - "TS2352": 1, - "TS2769": 4 - }, "src/hooks/admin/useDeviceDetection.ts": { "TS2353": 1 }, @@ -24,17 +18,7 @@ "TS2353": 1 }, "src/hooks/auth/useAccessSecurity.ts": { - "TS2322": 2, - "TS2345": 5, - "TS2352": 3, - "TS2353": 1, - "TS2769": 9 - }, - "src/hooks/intelligence/useSalesGoals.ts": { - "TS2322": 10, - "TS2345": 5, - "TS2353": 1, - "TS2769": 6 + "TS2345": 1 }, "src/hooks/mockup/mockupGenerationService.ts": { "TS2322": 1 @@ -57,11 +41,6 @@ "TS2589": 4, "TS2769": 5 }, - "src/hooks/simulation/useSimulation.ts": { - "TS2345": 1, - "TS2589": 1, - "TS2769": 3 - }, "src/hooks/simulation/useTechniquePricing.ts": { "TS2339": 16, "TS2345": 1, @@ -89,10 +68,6 @@ }, "src/pages/products/seller-carts/CartSidebar.tsx": { "TS2322": 1 - }, - "src/pages/quotes/QuotesKanbanPage.tsx": { - "TS2345": 1, - "TS2769": 1 } } } diff --git a/src/components/admin/access-security/BlockedLogsTab.tsx b/src/components/admin/access-security/BlockedLogsTab.tsx index 369f4e7e4..818567528 100644 --- a/src/components/admin/access-security/BlockedLogsTab.tsx +++ b/src/components/admin/access-security/BlockedLogsTab.tsx @@ -15,20 +15,14 @@ import { ptBR } from 'date-fns/locale'; interface BlockedLog { id: string; created_at: string; - email: string | null; - ip_address: string; - city: string | null; - state: string | null; - country: string | null; - block_reason: string; + user_email: string | null; + ip_address: unknown; + error_message: string | null; + operation: string; + table_name: string; + user_agent: string | null; } -const BLOCK_REASON_LABELS: Record = { - ip_not_whitelisted: 'IP não autorizado', - city_not_whitelisted: 'Cidade não autorizada', - too_many_attempts: 'Muitas tentativas', -}; - interface BlockedLogsTabProps { logs: BlockedLog[]; } @@ -37,26 +31,26 @@ export function BlockedLogsTab({ logs }: BlockedLogsTabProps) { return ( - Últimos Acessos Bloqueados + Últimos Acessos Negados (RLS) - Registro das últimas 50 tentativas de acesso bloqueadas pelo sistema + Registro das últimas 50 negações de acesso pelo Row Level Security {logs.length === 0 ? (
- Nenhum acesso bloqueado registrado + Nenhum acesso negado registrado
) : ( Data/Hora - Email - IP - Localização - Motivo + Usuário + Operação + Tabela + Erro @@ -65,21 +59,16 @@ export function BlockedLogsTab({ logs }: BlockedLogsTabProps) { {format(new Date(log.created_at), 'dd/MM/yyyy HH:mm:ss', { locale: ptBR })} - {log.email || '—'} - {log.ip_address} - - {log.city - ? `${log.city}${log.state ? `, ${log.state}` : ''}${log.country ? ` (${log.country})` : ''}` - : '—'} - + {log.user_email || '—'} - - {BLOCK_REASON_LABELS[log.block_reason] || log.block_reason} + + {log.operation} + {log.table_name} + + {log.error_message || '—'} + ))} diff --git a/src/components/admin/access-security/CityWhitelistTab.tsx b/src/components/admin/access-security/CityWhitelistTab.tsx index 46c3a84a5..862de89b1 100644 --- a/src/components/admin/access-security/CityWhitelistTab.tsx +++ b/src/components/admin/access-security/CityWhitelistTab.tsx @@ -27,34 +27,33 @@ import { Globe, Loader2, Plus, Trash2 } from 'lucide-react'; import { format } from 'date-fns'; import { ptBR } from 'date-fns/locale'; -interface CityEntry { +interface CountryEntry { id: string; - city_name: string; - state: string | null; country_code: string; - is_active: boolean; - created_at: string; + country_name: string; + is_active: boolean | null; + created_at: string | null; } interface CityWhitelistTabProps { - cities: CityEntry[]; - onAdd: (city: string, state?: string) => Promise; + cities: CountryEntry[]; + onAdd: (country_code: string, state?: string) => Promise; onRemove: (id: string) => void; onToggle: (id: string, active: boolean) => void; } export function CityWhitelistTab({ cities, onAdd, onRemove, onToggle }: CityWhitelistTabProps) { - const [newCity, setNewCity] = useState(''); - const [newState, setNewState] = useState(''); + const [newCode, setNewCode] = useState(''); + const [newName, setNewName] = useState(''); const [adding, setAdding] = useState(false); const handleAdd = async () => { - if (!newCity.trim()) return; + if (!newCode.trim() || !newName.trim()) return; setAdding(true); - const ok = await onAdd(newCity.trim(), newState.trim() || undefined); + const ok = await onAdd(newCode.trim().toUpperCase(), newName.trim()); if (ok) { - setNewCity(''); - setNewState(''); + setNewCode(''); + setNewName(''); } setAdding(false); }; @@ -62,34 +61,38 @@ export function CityWhitelistTab({ cities, onAdd, onRemove, onToggle }: CityWhit return ( - Cidades na Whitelist + Países na Whitelist - Adicione cidades de onde é permitido acessar o sistema. A localização é detectada pelo IP + Adicione países de onde é permitido acessar o sistema. A localização é detectada pelo IP do usuário.
-
- +
+ setNewCity(e.target.value)} + placeholder="BR" + value={newCode} + onChange={(e) => setNewCode(e.target.value)} + maxLength={2} onKeyDown={(e) => e.key === 'Enter' && handleAdd()} />
-
- +
+ setNewState(e.target.value)} - maxLength={2} + placeholder="Ex: Brasil" + value={newName} + onChange={(e) => setNewName(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAdd()} />
- @@ -97,14 +100,13 @@ export function CityWhitelistTab({ cities, onAdd, onRemove, onToggle }: CityWhit {cities.length === 0 ? (
- Nenhuma cidade cadastrada + Nenhum país cadastrado
) : (
- Cidade - Estado + Código País Status Adicionado em @@ -112,19 +114,20 @@ export function CityWhitelistTab({ cities, onAdd, onRemove, onToggle }: CityWhit - {cities.map((city) => ( - - {city.city_name} - {city.state || '—'} - {city.country_code} + {cities.map((country) => ( + + {country.country_code} + {country.country_name} onToggle(city.id, checked)} + checked={country.is_active ?? true} + onCheckedChange={(checked) => onToggle(country.id, checked)} /> - {format(new Date(city.created_at), 'dd/MM/yyyy HH:mm', { locale: ptBR })} + {country.created_at + ? format(new Date(country.created_at), 'dd/MM/yyyy HH:mm', { locale: ptBR }) + : '—'} @@ -140,16 +143,16 @@ export function CityWhitelistTab({ cities, onAdd, onRemove, onToggle }: CityWhit - Remover cidade? + Remover país? - {city.city_name} será removida da - whitelist. + {country.country_name} será removido + da whitelist. Cancelar onRemove(city.id)} + onClick={() => onRemove(country.id)} className="bg-destructive text-destructive-foreground" > Remover diff --git a/src/components/admin/access-security/IpWhitelistTab.tsx b/src/components/admin/access-security/IpWhitelistTab.tsx index a1786a36d..e15dd2976 100644 --- a/src/components/admin/access-security/IpWhitelistTab.tsx +++ b/src/components/admin/access-security/IpWhitelistTab.tsx @@ -30,7 +30,7 @@ import { ptBR } from 'date-fns/locale'; interface IpEntry { id: string; ip_address: string; - label: string | null; + reason: string | null; is_active: boolean; created_at: string; } @@ -112,7 +112,7 @@ export function IpWhitelistTab({ ips, onAdd, onRemove, onToggle }: IpWhitelistTa {ips.map((ip) => ( {ip.ip_address} - {ip.label || '—'} + {ip.reason || '—'} ({ role: m.role, content: m.content })), - systemPrompt, - contextId, - }), - signal: controller.signal, + const response = await fetch(`${SUPABASE_URL}/functions/v1/expert-chat`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + apikey: SUPABASE_PUBLISHABLE_KEY, + Authorization: `Bearer ${token}`, }, - ); + body: JSON.stringify({ + messages: nextMessages.map((m) => ({ role: m.role, content: m.content })), + systemPrompt, + contextId, + }), + signal: controller.signal, + }); if (!response.ok) { if (response.status === 429) diff --git a/src/components/expert/chat/useExpertChat.ts b/src/components/expert/chat/useExpertChat.ts index 832979137..1371db272 100644 --- a/src/components/expert/chat/useExpertChat.ts +++ b/src/components/expert/chat/useExpertChat.ts @@ -470,60 +470,53 @@ export function useExpertChat({ if (convId) await saveMessage(convId, 'user', userMessage); try { - const response = await fetch( - `${SUPABASE_URL}/functions/v1/expert-chat`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}`, - }, - body: JSON.stringify({ - messages: [...messages, { role: 'user', content: userMessage }] - .map((m) => ({ role: m.role, content: m.content })) - .filter((m) => m.content?.length > 0), - clientId: clientId || undefined, - categoryFilter: - flowFilters.selectedCategories.length > 0 - ? flowFilters.selectedCategories - : undefined, - priceMin: flowFilters.priceMin ? Number(flowFilters.priceMin) : undefined, - priceMax: flowFilters.priceMax ? Number(flowFilters.priceMax) : undefined, - materialFilter: - flowFilters.selectedMaterials.length > 0 ? flowFilters.selectedMaterials : undefined, - colorFilter: - flowFilters.selectedColors.length > 0 ? flowFilters.selectedColors : undefined, - genderFilter: - flowFilters.selectedGenders.length > 0 ? flowFilters.selectedGenders : undefined, - supplierFilter: - flowFilters.selectedSuppliers.length > 0 ? flowFilters.selectedSuppliers : undefined, - techniqueFilter: - flowFilters.selectedTechniques.length > 0 - ? flowFilters.selectedTechniques - : undefined, - publicoFilter: - flowFilters.selectedPublicos.length > 0 ? flowFilters.selectedPublicos : undefined, - dataComemorativaFilter: - flowFilters.selectedDatasComemorativas.length > 0 - ? flowFilters.selectedDatasComemorativas - : undefined, - endomarketingFilter: - flowFilters.selectedEndomarketing.length > 0 - ? flowFilters.selectedEndomarketing - : undefined, - nichoFilter: - flowFilters.selectedNichos.length > 0 ? flowFilters.selectedNichos : undefined, - tagFilter: flowFilters.selectedTags.length > 0 ? flowFilters.selectedTags : undefined, - onlyInStock: flowFilters.onlyInStock || undefined, - onlyNew: flowFilters.onlyNew || undefined, - onlyKit: flowFilters.onlyKit || undefined, - onlyBestseller: flowFilters.onlyBestseller || undefined, - onlyFeatured: flowFilters.onlyFeatured || undefined, - hasPersonalization: flowFilters.hasPersonalization || undefined, - }), - signal: abortController.signal, + const response = await fetch(`${SUPABASE_URL}/functions/v1/expert-chat`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}`, }, - ); + body: JSON.stringify({ + messages: [...messages, { role: 'user', content: userMessage }] + .map((m) => ({ role: m.role, content: m.content })) + .filter((m) => m.content?.length > 0), + clientId: clientId || undefined, + categoryFilter: + flowFilters.selectedCategories.length > 0 ? flowFilters.selectedCategories : undefined, + priceMin: flowFilters.priceMin ? Number(flowFilters.priceMin) : undefined, + priceMax: flowFilters.priceMax ? Number(flowFilters.priceMax) : undefined, + materialFilter: + flowFilters.selectedMaterials.length > 0 ? flowFilters.selectedMaterials : undefined, + colorFilter: + flowFilters.selectedColors.length > 0 ? flowFilters.selectedColors : undefined, + genderFilter: + flowFilters.selectedGenders.length > 0 ? flowFilters.selectedGenders : undefined, + supplierFilter: + flowFilters.selectedSuppliers.length > 0 ? flowFilters.selectedSuppliers : undefined, + techniqueFilter: + flowFilters.selectedTechniques.length > 0 ? flowFilters.selectedTechniques : undefined, + publicoFilter: + flowFilters.selectedPublicos.length > 0 ? flowFilters.selectedPublicos : undefined, + dataComemorativaFilter: + flowFilters.selectedDatasComemorativas.length > 0 + ? flowFilters.selectedDatasComemorativas + : undefined, + endomarketingFilter: + flowFilters.selectedEndomarketing.length > 0 + ? flowFilters.selectedEndomarketing + : undefined, + nichoFilter: + flowFilters.selectedNichos.length > 0 ? flowFilters.selectedNichos : undefined, + tagFilter: flowFilters.selectedTags.length > 0 ? flowFilters.selectedTags : undefined, + onlyInStock: flowFilters.onlyInStock || undefined, + onlyNew: flowFilters.onlyNew || undefined, + onlyKit: flowFilters.onlyKit || undefined, + onlyBestseller: flowFilters.onlyBestseller || undefined, + onlyFeatured: flowFilters.onlyFeatured || undefined, + hasPersonalization: flowFilters.hasPersonalization || undefined, + }), + signal: abortController.signal, + }); if (!response.ok) { const errorData = await response.json(); diff --git a/src/components/intelligence/MarketIntelligenceChart.tsx b/src/components/intelligence/MarketIntelligenceChart.tsx index a7cc3671a..b424dda10 100644 --- a/src/components/intelligence/MarketIntelligenceChart.tsx +++ b/src/components/intelligence/MarketIntelligenceChart.tsx @@ -32,10 +32,7 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; import { KpiCard } from '@/components/ui/kpi-card'; -import { - formatTooltipNumber, - formatTooltipPercent, -} from '@/lib/format-utils'; +import { formatTooltipNumber, formatTooltipPercent } from '@/lib/format-utils'; import { useMarketIntelligenceMacro, type MacroMarketPoint, @@ -307,14 +304,16 @@ export function MarketIntelligenceChart({ ) : ( <> - Velocidade média de saída: {formatTooltipNumber(avgDepletion, 1)} un/dia. -

- Dica de Argumentação: - {avgDepletion > 30 - ? ` "Este produto está voando com ${formatTooltipNumber(avgDepletion, 1)} saídas/dia! Recomendo garantir o lote agora para não perder o timing de venda."` + Velocidade média de saída:{' '} + {formatTooltipNumber(avgDepletion, 1)} un/dia. +
+
+ Dica de Argumentação: + {avgDepletion > 30 + ? ` "Este produto está voando com ${formatTooltipNumber(avgDepletion, 1)} saídas/dia! Recomendo garantir o lote agora para não perder o timing de venda."` : avgDepletion > 0 ? ` "Temos um giro saudável de ${formatTooltipNumber(avgDepletion, 1)} unidades/dia. É um item de segurança para o seu estoque base."` - : " Sem registros de saída recente no mercado. Pode ser uma oportunidade de nicho ou aguardando reposição."} + : ' Sem registros de saída recente no mercado. Pode ser uma oportunidade de nicho ou aguardando reposição.'} ) } @@ -334,14 +333,16 @@ export function MarketIntelligenceChart({ ) : ( <> - Nível de interesse: {demandLevel || "Sem dados"}. -

- Cenário Prático: + Nível de interesse:{' '} + {demandLevel || 'Sem dados'}. +
+
+ Cenário Prático: {demandLevel === 'Muito Alta' || demandLevel === 'Alta' ? ` "Interesse ${demandLevel} detectado! Ótimo momento para combos, aproveitando que a busca orgânica está no pico."` : demandLevel === 'Moderada' - ? " \"Demanda equilibrada. Momento ideal para manter o estoque de segurança e focar em vendas consultivas.\"" - : " \"Interesse em fase inicial ou baixa. Foco em ações de marketing para despertar o desejo no seu cliente.\""} + ? ' "Demanda equilibrada. Momento ideal para manter o estoque de segurança e focar em vendas consultivas."' + : ' "Interesse em fase inicial ou baixa. Foco em ações de marketing para despertar o desejo no seu cliente."'} ) } @@ -367,16 +368,17 @@ export function MarketIntelligenceChart({ ) : ( <> - Variação da procura: {formatTooltipPercent(trendPercent)}. -

- Como agir: - {trendPercent > 15 - ? ` "A procura subiu ${formatTooltipPercent(trendPercent)} esta semana! Se esperarmos, o preço pode subir ou o lote esgotar rápido."` + Variação da procura:{' '} + {formatTooltipPercent(trendPercent)}. +
+
+ Como agir: + {trendPercent > 15 + ? ` "A procura subiu ${formatTooltipPercent(trendPercent)} esta semana! Se esperarmos, o preço pode subir ou o lote esgotar rápido."` : trendPercent < -15 ? ` "Notamos um recuo de ${formatTooltipPercent(trendPercent)}. É a janela perfeita para negociarmos uma condição agressiva."` - : " O mercado segue estável. Temos previsibilidade total de custos e prazos para o seu pedido hoje."} + : ' O mercado segue estável. Temos previsibilidade total de custos e prazos para o seu pedido hoje.'} - ) } /> @@ -394,14 +396,26 @@ export function MarketIntelligenceChart({ ) : ( <> - Estoque global: {formatTooltipNumber(kpis?.totalCurrentStock, 0)} un. + Estoque global:{' '} + + {formatTooltipNumber(kpis?.totalCurrentStock, 0)} un + + . +
+ Duração estimada:{' '} + + {avgDepletion > 0 + ? Math.round((kpis?.totalCurrentStock ?? 0) / avgDepletion) + : '---'}{' '} + dias + + . +

- Duração estimada: {avgDepletion > 0 ? Math.round((kpis?.totalCurrentStock ?? 0) / avgDepletion) : "---"} dias. -

- Gatilho de Venda: + Gatilho de Venda: {avgDepletion > 0 && (kpis?.totalCurrentStock ?? 0) / avgDepletion < 15 ? ` "Urgente: Restam só ${formatTooltipNumber(kpis?.totalCurrentStock, 0)} unidades no mercado (menos de 15 dias). Garanta sua cota!"` - : ` "Estoque de ${formatTooltipNumber(kpis?.totalCurrentStock, 0)} un disponível. A tendência sugere que este é o momento seguro para reposição."` } + : ` "Estoque de ${formatTooltipNumber(kpis?.totalCurrentStock, 0)} un disponível. A tendência sugere que este é o momento seguro para reposição."`} ) } diff --git a/src/components/mockup/MockupCompareDialog.tsx b/src/components/mockup/MockupCompareDialog.tsx index d54bf71da..f3c7f1539 100644 --- a/src/components/mockup/MockupCompareDialog.tsx +++ b/src/components/mockup/MockupCompareDialog.tsx @@ -16,7 +16,7 @@ interface CompareMockup { mockup_url: string; layout_url?: string | null; created_at: string; - bitrix_clients?: { name: string } | null; + client_name?: string | null; } interface MockupCompareDialogProps { @@ -81,8 +81,8 @@ export function MockupCompareDialog({ {mockup.technique_name} - {mockup.bitrix_clients?.name && ( -

👤 {mockup.bitrix_clients.name}

+ {mockup.client_name && ( +

👤 {mockup.client_name}

)}

{formatDistanceToNow(new Date(mockup.created_at), { diff --git a/src/components/pdf/proposal/ProposalProductTable.tsx b/src/components/pdf/proposal/ProposalProductTable.tsx index a5007db0b..2c1b97c0a 100644 --- a/src/components/pdf/proposal/ProposalProductTable.tsx +++ b/src/components/pdf/proposal/ProposalProductTable.tsx @@ -188,8 +188,8 @@ export function ProposalProductTable({ items, showHeader = true, startIndex = 0 .join(' · '); return ( -

- {allMatchingVariants.map((v) => ( + {allMatchingVariants.map((v, i) => (