diff --git a/src/hooks/crm/useRamoAtividade.ts b/src/hooks/crm/useRamoAtividade.ts index dcdc94095..27d186b82 100644 --- a/src/hooks/crm/useRamoAtividade.ts +++ b/src/hooks/crm/useRamoAtividade.ts @@ -38,6 +38,8 @@ export function useRamosAtividadeGroups() { }); } +export const useRamoAtividadeGroups = useRamosAtividadeGroups; + // Buscar ramo por ID export function useRamoAtividade(id: string | undefined) { return useQuery({ diff --git a/src/hooks/crm/useRamoAtividadeFilter.ts b/src/hooks/crm/useRamoAtividadeFilter.ts index f8c90d0c6..c511d8247 100644 --- a/src/hooks/crm/useRamoAtividadeFilter.ts +++ b/src/hooks/crm/useRamoAtividadeFilter.ts @@ -1,6 +1,10 @@ import { useState, useMemo, useCallback } from 'react'; import { useRamoAtividadeGroups, useSegmentosCompletos } from '@/hooks/crm'; -import type { RamoAtividadeGroup, SegmentoComplete, RamoAtividadeFilterState } from "@/types/ramo-atividade"; +import type { + RamoAtividadeGroup, + SegmentoComplete, + RamoAtividadeFilterState, +} from '@/types/ramo-atividade'; export type { RamoAtividadeFilterState }; @@ -24,11 +28,19 @@ export interface UseRamoAtividadeFilterReturn { } export function useRamoAtividadeFilter(): UseRamoAtividadeFilterReturn { - const { data: groups = [], isLoading: groupsLoading, error: groupsError } = useRamoAtividadeGroups(); - const { data: segmentosData, isLoading: segmentosLoading, error: segmentosError } = useSegmentosCompletos(); - - const segmentos = segmentosData?.segmentos || []; - const byRamo = segmentosData?.byRamo || new Map(); + const { + data: groups = [], + isLoading: groupsLoading, + error: groupsError, + } = useRamoAtividadeGroups(); + const { + data: segmentosData, + isLoading: segmentosLoading, + error: segmentosError, + } = useSegmentosCompletos(); + + const segmentos = useMemo(() => segmentosData?.segmentos || [], [segmentosData?.segmentos]); + const byRamo = useMemo(() => segmentosData?.byRamo || new Map(), [segmentosData?.byRamo]); const [filterState, setFilterState] = useState({ selectedRamos: [], @@ -39,31 +51,38 @@ export function useRamoAtividadeFilter(): UseRamoAtividadeFilterReturn { const error = groupsError || segmentosError; // Toggle ramo - const toggleRamo = useCallback((ramoSlug: string) => { - setFilterState(prev => { - const isSelected = prev.selectedRamos.includes(ramoSlug); - if (isSelected) { - // Remove ramo e todos os segmentos desse ramo - const segmentosNoRamo = byRamo.get(ramoSlug)?.map((s: SegmentoComplete) => s.segmento_slug) || []; + const toggleRamo = useCallback( + (ramoSlug: string) => { + setFilterState((prev) => { + const isSelected = prev.selectedRamos.includes(ramoSlug); + if (isSelected) { + // Remove ramo e todos os segmentos desse ramo + const segmentosNoRamo = + byRamo.get(ramoSlug)?.map((s: SegmentoComplete) => s.segmento_slug) || []; + return { + ...prev, + selectedRamos: prev.selectedRamos.filter((r) => r !== ramoSlug), + selectedSegmentos: prev.selectedSegmentos.filter( + (s: string) => !segmentosNoRamo.includes(s), + ), + }; + } + // Add ramo + all its segmentos + const segmentosDoRamo = + byRamo.get(ramoSlug)?.map((s: SegmentoComplete) => s.segmento_slug) || []; return { ...prev, - selectedRamos: prev.selectedRamos.filter(r => r !== ramoSlug), - selectedSegmentos: prev.selectedSegmentos.filter((s: string) => !segmentosNoRamo.includes(s)), + selectedRamos: [...prev.selectedRamos, ramoSlug], + selectedSegmentos: [...new Set([...prev.selectedSegmentos, ...segmentosDoRamo])], }; - } - // Add ramo + all its segmentos - const segmentosDoRamo = byRamo.get(ramoSlug)?.map((s: SegmentoComplete) => s.segmento_slug) || []; - return { - ...prev, - selectedRamos: [...prev.selectedRamos, ramoSlug], - selectedSegmentos: [...new Set([...prev.selectedSegmentos, ...segmentosDoRamo])], - }; - }); - }, [byRamo]); + }); + }, + [byRamo], + ); // Toggle segmento const toggleSegmento = useCallback((segmentoSlug: string) => { - setFilterState(prev => { + setFilterState((prev) => { const isSelected = prev.selectedSegmentos.includes(segmentoSlug); if (isSelected) { return { @@ -82,41 +101,61 @@ export function useRamoAtividadeFilter(): UseRamoAtividadeFilterReturn { setFilterState({ selectedRamos: [], selectedSegmentos: [] }); }, []); - const hasActiveFilters = filterState.selectedRamos.length > 0 || filterState.selectedSegmentos.length > 0; + const hasActiveFilters = + filterState.selectedRamos.length > 0 || filterState.selectedSegmentos.length > 0; const selectedCount = filterState.selectedRamos.length + filterState.selectedSegmentos.length; - const getSegmentosForRamo = useCallback((ramoSlug: string) => { - return byRamo.get(ramoSlug) || []; - }, [byRamo]); - - const getSelectedSegmentosForRamo = useCallback((ramoSlug: string) => { - const segmentosNoRamo = byRamo.get(ramoSlug) || []; - return segmentosNoRamo.filter((s: SegmentoComplete) => filterState.selectedSegmentos.includes(s.segmento_slug)); - }, [byRamo, filterState.selectedSegmentos]); + const getSegmentosForRamo = useCallback( + (ramoSlug: string) => { + return byRamo.get(ramoSlug) || []; + }, + [byRamo], + ); + + const getSelectedSegmentosForRamo = useCallback( + (ramoSlug: string) => { + const segmentosNoRamo = byRamo.get(ramoSlug) || []; + return segmentosNoRamo.filter((s: SegmentoComplete) => + filterState.selectedSegmentos.includes(s.segmento_slug), + ); + }, + [byRamo, filterState.selectedSegmentos], + ); // Segmentos filtrados const filteredSegmentos = useMemo(() => { let result = segmentos; if (filterState.selectedRamos.length > 0) { - result = result.filter(s => filterState.selectedRamos.includes(s.ramo_slug)); + result = result.filter((s) => filterState.selectedRamos.includes(s.ramo_slug)); } return result; }, [segmentos, filterState.selectedRamos]); - const isRamoSelected = useCallback((ramoSlug: string) => { - return filterState.selectedRamos.includes(ramoSlug); - }, [filterState.selectedRamos]); - - const isSegmentoSelected = useCallback((segmentoSlug: string) => { - return filterState.selectedSegmentos.includes(segmentoSlug); - }, [filterState.selectedSegmentos]); - - const isRamoPartiallySelected = useCallback((ramoSlug: string) => { - const segmentosDoRamo = getSegmentosForRamo(ramoSlug); - if (segmentosDoRamo.length === 0) return false; - const selectedCount = segmentosDoRamo.filter(s => filterState.selectedSegmentos.includes(s.segmento_slug)).length; - return selectedCount > 0 && selectedCount < segmentosDoRamo.length; - }, [getSegmentosForRamo, filterState.selectedSegmentos]); + const isRamoSelected = useCallback( + (ramoSlug: string) => { + return filterState.selectedRamos.includes(ramoSlug); + }, + [filterState.selectedRamos], + ); + + const isSegmentoSelected = useCallback( + (segmentoSlug: string) => { + return filterState.selectedSegmentos.includes(segmentoSlug); + }, + [filterState.selectedSegmentos], + ); + + const isRamoPartiallySelected = useCallback( + (ramoSlug: string) => { + const segmentosDoRamo = getSegmentosForRamo(ramoSlug); + if (segmentosDoRamo.length === 0) return false; + const selectedCount = segmentosDoRamo.filter((s) => + filterState.selectedSegmentos.includes(s.segmento_slug), + ).length; + return selectedCount > 0 && selectedCount < segmentosDoRamo.length; + }, + [getSegmentosForRamo, filterState.selectedSegmentos], + ); return { groups, diff --git a/src/lib/personalization/repositories/technique.repository.ts b/src/lib/personalization/repositories/technique.repository.ts index d346bbc4b..da6f5dc7a 100644 --- a/src/lib/personalization/repositories/technique.repository.ts +++ b/src/lib/personalization/repositories/technique.repository.ts @@ -1,5 +1,4 @@ import type { TecnicaUnificada } from '@/types/tecnica-unificada'; -import { fetchExternalData } from '@/lib/external-db'; export interface TechniqueQueryOptions { search?: string; @@ -39,6 +38,23 @@ interface PaginatedResponse { status: number; } +interface FetchExternalDataOptions { + url: string; + headers?: HeadersInit; +} + +async function fetchExternalData({ url, headers }: FetchExternalDataOptions): Promise { + const response = await fetch(url, { headers }); + + if (!response.ok) { + throw new Error( + `External technique API request failed: ${response.status} ${response.statusText}`, + ); + } + + return response.json() as Promise; +} + function externalToTecnicaUnificada(row: TecnicaGravacaoExterno): TecnicaUnificada { return { id: row.id, @@ -77,12 +93,13 @@ export async function findAll(options: TechniqueQueryOptions = {}): Promise - t.nome.toLowerCase().includes(search) || - t.codigo.toLowerCase().includes(search) || - t.descricao?.toLowerCase().includes(search) + if (search) { + const normalizedSearch = search.toLowerCase(); + tecnicas = tecnicas.filter( + (t: TecnicaUnificada) => + t.nome.toLowerCase().includes(normalizedSearch) || + t.codigo.toLowerCase().includes(normalizedSearch) || + t.descricao?.toLowerCase().includes(normalizedSearch), ); } @@ -121,7 +138,10 @@ export async function create(tecnica: Omit): Promise): Promise { +export async function update( + id: string, + updates: Partial, +): Promise { const response = await fetch(`${GRAVACAO_API}/tecnicas/${id}`, { method: 'PATCH', headers: { diff --git a/src/pages/kit-builder/useKitBuilderQuote.ts b/src/pages/kit-builder/useKitBuilderQuote.ts index 20bf12d55..01a2da831 100644 --- a/src/pages/kit-builder/useKitBuilderQuote.ts +++ b/src/pages/kit-builder/useKitBuilderQuote.ts @@ -37,6 +37,7 @@ export function useKitBuilderQuote() { // Create quote const { data: quote, error: quoteError } = await supabase + // rls-allow: insert cria orçamento do usuário atual; RLS valida user_id .from('quotes') .insert({ user_id: user.id,