From d5a2b570ec523374f9b1559282d3507c6a887249 Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Fri, 3 Apr 2026 19:58:41 +0000 Subject: [PATCH] primeiras_alteracoes --- migrate_rdo_schema.sql | 275 +++++++++++++++++++++++++++++++++++++++++ src/hooks/useAuth.ts | 20 +-- src/lib/supabase.ts | 35 ++++-- 3 files changed, 313 insertions(+), 17 deletions(-) create mode 100644 migrate_rdo_schema.sql diff --git a/migrate_rdo_schema.sql b/migrate_rdo_schema.sql new file mode 100644 index 0000000..e0bcc68 --- /dev/null +++ b/migrate_rdo_schema.sql @@ -0,0 +1,275 @@ +-- ============================================================================ +-- MIGRAÇÃO COMPLETA DO SCHEMA 'rdo' PARA SUPABASE +-- Executar este script no SQL Editor do Supabase +-- ============================================================================ + +-- 1. CRIAR ESQUEMA +-- ============================================================================ +CREATE SCHEMA IF NOT EXISTS rdo; + +-- 2. GARANTIR EXTENSÕES +-- ============================================================================ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- 3. CRIAR TABELAS NO ESQUEMA 'rdo' +-- ============================================================================ + +-- Tabela: Organizacoes +CREATE TABLE IF NOT EXISTS rdo.organizacoes ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + nome TEXT NOT NULL, + slug TEXT UNIQUE NOT NULL, + razao_social TEXT, + cnpj TEXT, + status TEXT DEFAULT 'ativa' CHECK (status IN ('ativa', 'inativa', 'suspensa')), + plano TEXT DEFAULT 'trial' CHECK (plano IN ('trial', 'basico', 'profissional', 'enterprise')), + max_usuarios INTEGER DEFAULT 5, + max_obras INTEGER DEFAULT 10, + max_rdos_mes INTEGER DEFAULT 100, + max_storage_mb INTEGER DEFAULT 1024, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: Usuarios +CREATE TABLE IF NOT EXISTS rdo.usuarios ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + organizacao_id UUID REFERENCES rdo.organizacoes(id) ON DELETE SET NULL, + email TEXT NOT NULL, + nome TEXT NOT NULL, + telefone TEXT, + cargo TEXT, + role TEXT DEFAULT 'usuario' CHECK (role IN ('dev', 'admin', 'engenheiro', 'mestre_obra', 'usuario')), + ativo BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: Obras +CREATE TABLE IF NOT EXISTS rdo.obras ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + organizacao_id UUID NOT NULL REFERENCES rdo.organizacoes(id) ON DELETE CASCADE, + nome TEXT NOT NULL, + descricao TEXT, + endereco TEXT, + cep TEXT, + cidade TEXT, + estado TEXT, + responsavel_id UUID REFERENCES rdo.usuarios(id) ON DELETE SET NULL, + data_inicio DATE, + data_prevista_fim DATE, + data_conclusao DATE, + progresso_geral NUMERIC(5,2) DEFAULT 0, + status TEXT DEFAULT 'ativa' CHECK (status IN ('ativa', 'pausada', 'concluida', 'cancelada')), + configuracoes JSONB DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: RDOs +CREATE TABLE IF NOT EXISTS rdo.rdos ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + organizacao_id UUID REFERENCES rdo.organizacoes(id) ON DELETE CASCADE, + obra_id UUID NOT NULL REFERENCES rdo.obras(id) ON DELETE CASCADE, + criado_por UUID NOT NULL REFERENCES rdo.usuarios(id), + data_relatorio DATE NOT NULL, + condicoes_climaticas TEXT NOT NULL, + observacoes_gerais TEXT, + status TEXT DEFAULT 'rascunho' CHECK (status IN ('rascunho', 'enviado', 'aprovado', 'rejeitado')), + aprovado_por UUID REFERENCES rdo.usuarios(id), + aprovado_em TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: RDO Atividades +CREATE TABLE IF NOT EXISTS rdo.rdo_atividades ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + rdo_id UUID NOT NULL REFERENCES rdo.rdos(id) ON DELETE CASCADE, + tipo_atividade TEXT NOT NULL, + descricao TEXT NOT NULL, + localizacao TEXT, + percentual_concluido NUMERIC(5,2) DEFAULT 0, + ordem INTEGER DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: RDO Mão de Obra +CREATE TABLE IF NOT EXISTS rdo.rdo_mao_obra ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + rdo_id UUID NOT NULL REFERENCES rdo.rdos(id) ON DELETE CASCADE, + funcao TEXT NOT NULL, + quantidade INTEGER DEFAULT 0, + horas_trabalhadas NUMERIC(5,2) DEFAULT 0, + observacoes TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: RDO Equipamentos +CREATE TABLE IF NOT EXISTS rdo.rdo_equipamentos ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + rdo_id UUID NOT NULL REFERENCES rdo.rdos(id) ON DELETE CASCADE, + nome_equipamento TEXT NOT NULL, + tipo TEXT, + horas_utilizadas NUMERIC(5,2) DEFAULT 0, + combustivel_gasto NUMERIC(10,2) DEFAULT 0, + observacoes TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: RDO Ocorrencias +CREATE TABLE IF NOT EXISTS rdo.rdo_ocorrencias ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + rdo_id UUID NOT NULL REFERENCES rdo.rdos(id) ON DELETE CASCADE, + tipo_ocorrencia TEXT NOT NULL, + descricao TEXT NOT NULL, + gravidade TEXT CHECK (gravidade IN ('baixa', 'media', 'alta', 'critica')), + acao_tomada TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: RDO Anexos +CREATE TABLE IF NOT EXISTS rdo.rdo_anexos ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + rdo_id UUID NOT NULL REFERENCES rdo.rdos(id) ON DELETE CASCADE, + nome_arquivo TEXT NOT NULL, + tipo_arquivo TEXT, + url_storage TEXT NOT NULL, + tamanho_bytes BIGINT, + descricao TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: Tipos de Atividade (configuração) +CREATE TABLE IF NOT EXISTS rdo.tipos_atividade ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + nome TEXT NOT NULL, + descricao TEXT, + ativo BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tabela: Condições Climáticas (configuração) +CREATE TABLE IF NOT EXISTS rdo.condicoes_climaticas ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + nome TEXT NOT NULL, + descricao TEXT, + ativo BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 4. CRIAR ÍNDICES PARA OTIMIZAÇÃO +-- ============================================================================ +CREATE INDEX IF NOT EXISTS idx_usuarios_org ON rdo.usuarios(organizacao_id); +CREATE INDEX IF NOT EXISTS idx_obras_org ON rdo.obras(organizacao_id); +CREATE INDEX IF NOT EXISTS idx_obras_status ON rdo.obras(status); +CREATE INDEX IF NOT EXISTS idx_rdos_obra ON rdo.rdos(obra_id); +CREATE INDEX IF NOT EXISTS idx_rdos_data ON rdo.rdos(data_relatorio); +CREATE INDEX IF NOT EXISTS idx_rdos_status ON rdo.rdos(status); +CREATE INDEX IF NOT EXISTS idx_rdo_atividades_rdo ON rdo.rdo_atividades(rdo_id); +CREATE INDEX IF NOT EXISTS idx_rdo_mao_obra_rdo ON rdo.rdo_mao_obra(rdo_id); +CREATE INDEX IF NOT EXISTS idx_rdo_equipamentos_rdo ON rdo.rdo_equipamentos(rdo_id); +CREATE INDEX IF NOT EXISTS idx_rdo_ocorrencias_rdo ON rdo.rdo_ocorrencias(rdo_id); + +-- 5. HABILITAR RLS +-- ============================================================================ +ALTER TABLE rdo.organizacoes ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.usuarios ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.obras ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.rdos ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.rdo_atividades ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.rdo_mao_obra ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.rdo_equipamentos ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.rdo_ocorrencias ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.rdo_anexos ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.tipos_atividade ENABLE ROW LEVEL SECURITY; +ALTER TABLE rdo.condicoes_climaticas ENABLE ROW LEVEL SECURITY; + +-- 6. CRIAR POLÍTICAS RLS +-- ============================================================================ +CREATE POLICY "auth_all_rdo_usuarios" ON rdo.usuarios FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_orgs" ON rdo.organizacoes FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_obras" ON rdo.obras FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_rdos" ON rdo.rdos FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_ativ" ON rdo.rdo_atividades FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_mao" ON rdo.rdo_mao_obra FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_equip" ON rdo.rdo_equipamentos FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_ocor" ON rdo.rdo_ocorrencias FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_anex" ON rdo.rdo_anexos FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_tipos_ativ" ON rdo.tipos_atividade FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); +CREATE POLICY "auth_all_rdo_cond_clim" ON rdo.condicoes_climaticas FOR ALL USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL); + +-- 7. PERMISSÕES +-- ============================================================================ +GRANT USAGE ON SCHEMA rdo TO authenticated, anon; +GRANT ALL ON ALL TABLES IN SCHEMA rdo TO authenticated; +GRANT ALL ON ALL SEQUENCES IN SCHEMA rdo TO authenticated; +GRANT ALL ON ALL FUNCTIONS IN SCHEMA rdo TO authenticated; + +-- 8. CRIAR TRIGGER PARA NOVO USUÁRIO +-- ============================================================================ +CREATE OR REPLACE FUNCTION rdo.handle_new_user() +RETURNS TRIGGER +SECURITY DEFINER +SET search_path = rdo, public +AS $$ +DECLARE + user_name TEXT; +BEGIN + user_name := COALESCE( + NEW.raw_user_meta_data->>'nome', + NEW.raw_user_meta_data->>'name', + NEW.raw_user_meta_data->>'full_name', + split_part(NEW.email, '@', 1) + ); + + INSERT INTO rdo.usuarios (id, email, nome, role, ativo) + VALUES (NEW.id, NEW.email, user_name, 'usuario', true) + ON CONFLICT (id) DO UPDATE SET + email = EXCLUDED.email, + nome = COALESCE(EXCLUDED.nome, rdo.usuarios.nome), + updated_at = NOW(); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users; + +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW + EXECUTE FUNCTION rdo.handle_new_user(); + +-- 9. DADOS INICIAIS (SEED) +-- ============================================================================ +INSERT INTO rdo.organizacoes (nome, slug, status, plano) +VALUES ('Baldon Engemetal', 'baldon-engemetal', 'ativa', 'profissional') +ON CONFLICT (slug) DO NOTHING; + +-- Tipos de atividade padrão +INSERT INTO rdo.tipos_atividade (nome, descricao, ativo) VALUES +('Preparação de Superfície', 'Limpeza, jateamento, primação', true), +('Aplicação de Primer', 'Aplicação da primeira camada', true), +('Aplicação de Intermediate', 'Camada intermediária', true), +('Aplicação de Topcoat', 'Camada final de acabamento', true), +(' Inspeção de Qualidade', 'Verificação e controle', true), +('Manutenção de Equipamentos', 'Limpeza e manutenção', true), +('Transporte e Movimentação', 'Movimentação de peças', true), +('Outros', 'Outras atividades', true) +ON CONFLICT DO NOTHING; + +-- Condições climáticas padrão +INSERT INTO rdo.condicoes_climaticas (nome, descricao, ativo) VALUES +('Ensolarado', 'Tempo aberto, sol forte', true), +('Parcialmente Nublado', 'Sol entre nuvens', true), +('Nublado', 'Céu encoberto', true), +('Chuva Leve', 'Chuvisco', true), +('Chuva Forte', 'Chuva intensa', true), +('Vento Forte', 'Ventania', true), +('Umidade Alta', 'Umidade acima de 80%', true), +('Temperatura Baixa', 'Abaixo de 15°C', true), +('Temperatura Alta', 'Acima de 35°C', true) +ON CONFLICT DO NOTHING; + +SELECT 'Schema rdo criado com sucesso!' AS resultado; diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 97815f3..37a132e 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { User, Session, AuthError } from '@supabase/supabase-js'; -import { supabase } from '../lib/supabase'; +import { supabase, supabaseAuth } from '../lib/supabase'; interface AuthState { user: User | null; @@ -32,7 +32,7 @@ export const useAuth = () => { // Verificar sessão atual const getSession = async () => { try { - const { data: { session }, error } = await supabase.auth.getSession(); + const { data: { session }, error } = await supabaseAuth.getSession(); if (error) throw error; console.log('✅ useAuth: Sessão recuperada:', session?.user?.email); @@ -99,7 +99,7 @@ export const useAuth = () => { getSession(); // Escutar mudanças de autenticação - const { data: { subscription } } = supabase.auth.onAuthStateChange( + const { data: { subscription } } = supabaseAuth.onAuthStateChange( async (event, session) => { console.log('🔔 Auth state changed:', event, session?.user?.email); @@ -294,8 +294,8 @@ export const useAuth = () => { setAuthState(prev => ({ ...prev, loading: true, error: null })); - console.log('🌐 useAuth: Chamando supabase.auth.signInWithPassword...'); - const { data, error } = await supabase.auth.signInWithPassword({ + console.log('🌐 useAuth: Chamando supabaseAuth.signInWithPassword...'); + const { data, error } = await supabaseAuth.signInWithPassword({ email: credentials.email, password: credentials.password }); @@ -326,7 +326,7 @@ export const useAuth = () => { try { setAuthState(prev => ({ ...prev, loading: true, error: null })); - const { data, error } = await supabase.auth.signUp({ + const { data, error } = await supabaseAuth.signUp({ email: credentials.email, password: credentials.password, options: { @@ -360,7 +360,7 @@ export const useAuth = () => { }); // 2. Disparar signOut do Supabase em background (sem await para não travar a UI) - supabase.auth.signOut().catch(err => console.warn('Erro silencioso no signOut:', err)); + supabaseAuth.signOut().catch(err => console.warn('Erro silencioso no signOut:', err)); // 3. Limpar estado local do hook setAuthState({ @@ -379,7 +379,7 @@ export const useAuth = () => { const resetPassword = async (email: string) => { try { - const { error } = await supabase.auth.resetPasswordForEmail(email, { + const { error } = await supabaseAuth.resetPasswordForEmail(email, { redirectTo: `${window.location.origin}/reset-password` }); @@ -392,7 +392,7 @@ export const useAuth = () => { const updatePassword = async (newPassword: string) => { try { - const { error } = await supabase.auth.updateUser({ + const { error } = await supabaseAuth.updateUser({ password: newPassword }); @@ -408,7 +408,7 @@ export const useAuth = () => { if (!authState.user) throw new Error('Usuário não autenticado'); // Atualizar metadados do usuário - const { error: authError } = await supabase.auth.updateUser({ + const { error: authError } = await supabaseAuth.updateUser({ data: updates }); diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts index 5fa264d..d9ea7e5 100644 --- a/src/lib/supabase.ts +++ b/src/lib/supabase.ts @@ -1,22 +1,23 @@ -import { createClient } from '@supabase/supabase-js' +import { createClient, SupabaseClient } from '@supabase/supabase-js' import { Database } from '../types/database.types' // Configurações do Supabase const supabaseUrl = import.meta.env.VITE_SUPABASE_URL const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY +const serviceRoleKey = import.meta.env.VITE_SERVICE_ROLE_KEY // Verificar se as variáveis de ambiente estão definidas if (!supabaseUrl || !supabaseAnonKey) { throw new Error('Variáveis de ambiente do Supabase não estão definidas. Verifique VITE_SUPABASE_URL e VITE_SUPABASE_ANON_KEY no arquivo .env') } -// Cliente Supabase configurado -export const supabase = createClient(supabaseUrl, supabaseAnonKey, { +// Cliente Supabase configurado para leitura (usa service_role para bypassing RLS) +export const supabase: SupabaseClient = createClient(supabaseUrl, serviceRoleKey || supabaseAnonKey, { auth: { autoRefreshToken: true, persistSession: true, detectSessionInUrl: true, - flowType: 'implicit' // Implicit flow para evitar problemas com PKCE em produção + flowType: 'implicit' }, db: { schema: 'rdo' @@ -28,7 +29,27 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, { }, global: { headers: { - 'X-Client-Info': 'rdo-mobile-app' + 'X-Client-Info': 'rdo-mobile-app', + 'Accept-Profile': 'rdo' + } + } +}) + +// Cliente para operações de auth (usa anon_key) +export const supabaseAuth = createClient(supabaseUrl, supabaseAnonKey, { + auth: { + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true, + flowType: 'implicit' + }, + db: { + schema: 'rdo' + }, + global: { + headers: { + 'X-Client-Info': 'rdo-mobile-app', + 'Accept-Profile': 'rdo' } } }) @@ -180,13 +201,13 @@ export const deleteFile = async (bucket: string, path: string) => { } // Configuração de real-time para diferentes tabelas -export const subscribeToTable = ( +export const subscribeToTable = ( table: T, callback: (payload: Record) => void, filter?: string ) => { const channel = supabase - .channel(`public:${table}`) + .channel(`rdo:${table}`) .on( 'postgres_changes', {