Compare commits
12 Commits
e198eb3b09
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5673eb5255 | |||
| 556a2ace41 | |||
| d5a2b570ec | |||
|
|
bc70aaaa5c | ||
|
|
ab71f9abc8 | ||
|
|
2261da27ad | ||
|
|
aaeab42050 | ||
|
|
25c75df1fd | ||
|
|
45c0de3605 | ||
|
|
0ad29893d0 | ||
|
|
c9c7f40a0b | ||
|
|
e09a4f8389 |
22
.agent/rules/GEMINI.md
Normal file
22
.agent/rules/GEMINI.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
trigger: always_on
|
||||||
|
---
|
||||||
|
|
||||||
|
# GEMINI.md - Antigravity Kit (Auto-Generated)
|
||||||
|
|
||||||
|
|
||||||
|
## TIER 0: UNIVERSAL RULES (Always Active)
|
||||||
|
|
||||||
|
### 🌍 Global User Rules (MANDATORY)
|
||||||
|
|
||||||
|
1. **Idioma**: Conversar e desenvolver sempre em **Português do Brasil**.
|
||||||
|
2. **Automação Terminais**: Executar comandos de terminal, leitura de arquivos e edições de forma automática. Comandos de desenvolvimento (build, install, lint, git, etc.) são sempre 'SafeToAutoRun'.
|
||||||
|
3. **Memória & Monitoramento**: Após cada evolução ou atualização no app, é OBRIGATÓRIO:
|
||||||
|
- Atualizar a memória do app em '/root/Antigravity_Memory/knowledge/{AppName}.md'.
|
||||||
|
- Atualizar o status e credenciais no 'app.py' do Cronos Dashboard.
|
||||||
|
- Realizar 'git commit', 'git push' e executar o 'update.sh' do Cronos para deploy automático.
|
||||||
|
|
||||||
|
### 🌐 Language Handling
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
.vscode
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.vercel
|
||||||
|
ios
|
||||||
|
android
|
||||||
|
out
|
||||||
|
build
|
||||||
|
tmp
|
||||||
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"css.lint.unknownAtRules": "ignore",
|
||||||
|
"scss.lint.unknownAtRules": "ignore",
|
||||||
|
"less.lint.unknownAtRules": "ignore",
|
||||||
|
"tailwindCSS.lint.unknownAtRules": "ignore",
|
||||||
|
"css.validate": false,
|
||||||
|
"scss.validate": false
|
||||||
|
}
|
||||||
51
Dockerfile
51
Dockerfile
@@ -1,39 +1,26 @@
|
|||||||
# Use Node.js 18 Alpine como base
|
# Build stage
|
||||||
FROM node:18-alpine AS base
|
FROM node:22-alpine AS build
|
||||||
|
|
||||||
# Instalar pnpm
|
# Build-time environment variables
|
||||||
RUN npm install -g pnpm
|
ARG VITE_SUPABASE_URL
|
||||||
|
ARG VITE_SUPABASE_ANON_KEY
|
||||||
|
|
||||||
|
# Update packages for security
|
||||||
|
RUN apk update && apk upgrade --no-cache
|
||||||
|
|
||||||
# Definir diretório de trabalho
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
# Copiar arquivos de dependências
|
RUN npm install --legacy-peer-deps --no-audit --no-fund
|
||||||
COPY package.json pnpm-lock.yaml ./
|
|
||||||
|
|
||||||
# Instalar dependências
|
|
||||||
RUN pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
# Copiar código fonte
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
# Build da aplicação
|
# Production stage
|
||||||
RUN pnpm run build
|
FROM nginx:alpine
|
||||||
|
|
||||||
# Estágio de produção
|
# Update packages for security
|
||||||
FROM node:18-alpine AS production
|
RUN apk update && apk upgrade --no-cache
|
||||||
|
|
||||||
# Instalar pnpm e serve
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
RUN npm install -g pnpm serve
|
# Nginx default listen 80
|
||||||
|
EXPOSE 80
|
||||||
# Definir diretório de trabalho
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copiar arquivos buildados
|
|
||||||
COPY --from=base /app/dist ./dist
|
|
||||||
COPY --from=base /app/package.json ./
|
|
||||||
|
|
||||||
# Expor porta
|
|
||||||
EXPOSE $PORT
|
|
||||||
|
|
||||||
# Comando para iniciar o servidor
|
|
||||||
CMD ["sh", "-c", "serve -s dist -l ${PORT:-3000}"]
|
|
||||||
1
dist/assets/js/form-vendor-vQotxSmE.js
vendored
1
dist/assets/js/form-vendor-vQotxSmE.js
vendored
File diff suppressed because one or more lines are too long
32
dist/assets/js/react-vendor-CqRd3GwO.js
vendored
32
dist/assets/js/react-vendor-CqRd3GwO.js
vendored
File diff suppressed because one or more lines are too long
12
dist/assets/js/router-vendor-D4by-_6Z.js
vendored
12
dist/assets/js/router-vendor-D4by-_6Z.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/zod-7IfHMaWP.js
vendored
1
dist/assets/js/zod-7IfHMaWP.js
vendored
@@ -1 +0,0 @@
|
|||||||
import{a as p,b as y,c as v}from"./form-vendor-vQotxSmE.js";function g(r,s,o){function e(t,c){var u;Object.defineProperty(t,"_zod",{value:t._zod??{},enumerable:!1}),(u=t._zod).traits??(u.traits=new Set),t._zod.traits.add(r),s(t,c);for(const f in n.prototype)f in t||Object.defineProperty(t,f,{value:n.prototype[f].bind(t)});t._zod.constr=n,t._zod.def=c}const a=o?.Parent??Object;class i extends a{}Object.defineProperty(i,"name",{value:r});function n(t){var c;const u=o?.Parent?new i:this;e(u,t),(c=u._zod).deferred??(c.deferred=[]);for(const f of u._zod.deferred)f();return u}return Object.defineProperty(n,"init",{value:e}),Object.defineProperty(n,Symbol.hasInstance,{value:t=>o?.Parent&&t instanceof o.Parent?!0:t?._zod?.traits?.has(r)}),Object.defineProperty(n,"name",{value:r}),n}class j extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}const z={};function m(r){return z}function w(r,s){return typeof s=="bigint"?s.toString():s}const b=Error.captureStackTrace?Error.captureStackTrace:(...r)=>{};function d(r){return typeof r=="string"?r:r?.message}function E(r,s,o){const e={...r,path:r.path??[]};if(!r.message){const a=d(r.inst?._zod.def?.error?.(r))??d(s?.error?.(r))??d(o.customError?.(r))??d(o.localeError?.(r))??"Invalid input";e.message=a}return delete e.inst,delete e.continue,s?.reportInput||delete e.input,e}const P=(r,s)=>{r.name="$ZodError",Object.defineProperty(r,"_zod",{value:r._zod,enumerable:!1}),Object.defineProperty(r,"issues",{value:s,enumerable:!1}),Object.defineProperty(r,"message",{get(){return JSON.stringify(s,w,2)},enumerable:!0}),Object.defineProperty(r,"toString",{value:()=>r.message,enumerable:!1})},O=g("$ZodError",P),_=g("$ZodError",P,{Parent:Error}),S=r=>(s,o,e,a)=>{const i=e?Object.assign(e,{async:!1}):{async:!1},n=s._zod.run({value:o,issues:[]},i);if(n instanceof Promise)throw new j;if(n.issues.length){const t=new(a?.Err??r)(n.issues.map(c=>E(c,i,m())));throw b(t,a?.callee),t}return n.value},A=S(_),Z=r=>async(s,o,e,a)=>{const i=e?Object.assign(e,{async:!0}):{async:!0};let n=s._zod.run({value:o,issues:[]},i);if(n instanceof Promise&&(n=await n),n.issues.length){const t=new(a?.Err??r)(n.issues.map(c=>E(c,i,m())));throw b(t,a?.callee),t}return n.value},N=Z(_);function h(r,s){try{var o=r()}catch(e){return s(e)}return o&&o.then?o.then(void 0,s):o}function I(r,s){for(var o={};r.length;){var e=r[0],a=e.code,i=e.message,n=e.path.join(".");if(!o[n])if("unionErrors"in e){var t=e.unionErrors[0].errors[0];o[n]={message:t.message,type:t.code}}else o[n]={message:i,type:a};if("unionErrors"in e&&e.unionErrors.forEach(function(f){return f.errors.forEach(function(l){return r.push(l)})}),s){var c=o[n].types,u=c&&c[e.code];o[n]=v(n,s,o,a,u?[].concat(u,e.message):e.message)}r.shift()}return o}function U(r,s){for(var o={};r.length;){var e=r[0],a=e.code,i=e.message,n=e.path.join(".");if(!o[n])if(e.code==="invalid_union"&&e.errors.length>0){var t=e.errors[0][0];o[n]={message:t.message,type:t.code}}else o[n]={message:i,type:a};if(e.code==="invalid_union"&&e.errors.forEach(function(f){return f.forEach(function(l){return r.push(l)})}),s){var c=o[n].types,u=c&&c[e.code];o[n]=v(n,s,o,a,u?[].concat(u,e.message):e.message)}r.shift()}return o}function k(r,s,o){if(o===void 0&&(o={}),(function(e){return"_def"in e&&typeof e._def=="object"&&"typeName"in e._def})(r))return function(e,a,i){try{return Promise.resolve(h(function(){return Promise.resolve(r[o.mode==="sync"?"parse":"parseAsync"](e,s)).then(function(n){return i.shouldUseNativeValidation&&p({},i),{errors:{},values:o.raw?Object.assign({},e):n}})},function(n){if((function(t){return Array.isArray(t?.issues)})(n))return{values:{},errors:y(I(n.errors,!i.shouldUseNativeValidation&&i.criteriaMode==="all"),i)};throw n}))}catch(n){return Promise.reject(n)}};if((function(e){return"_zod"in e&&typeof e._zod=="object"})(r))return function(e,a,i){try{return Promise.resolve(h(function(){return Promise.resolve((o.mode==="sync"?A:N)(r,e,s)).then(function(n){return i.shouldUseNativeValidation&&p({},i),{errors:{},values:o.raw?Object.assign({},e):n}})},function(n){if((function(t){return t instanceof O})(n))return{values:{},errors:y(U(n.issues,!i.shouldUseNativeValidation&&i.criteriaMode==="all"),i)};throw n}))}catch(n){return Promise.reject(n)}};throw new Error("Invalid input: not a Zod schema")}export{k as a};
|
|
||||||
1
dist/assets/png/tracksteel-logo-CJR9ckUT.png
vendored
1
dist/assets/png/tracksteel-logo-CJR9ckUT.png
vendored
File diff suppressed because one or more lines are too long
18
dist/index.html
vendored
18
dist/index.html
vendored
@@ -8,14 +8,16 @@
|
|||||||
<meta name="theme-color" content="#2563eb" />
|
<meta name="theme-color" content="#2563eb" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<title>RDO Mobile - Relatório Diário de Obra</title>
|
<title>RDO Mobile - Relatório Diário de Obra</title>
|
||||||
<script type="module" crossorigin src="/assets/js/index-DXLajEHZ.js"></script>
|
<script type="module" crossorigin src="/assets/index-BFMh0Owy.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/react-vendor-CqRd3GwO.js">
|
<link rel="modulepreload" crossorigin href="/assets/react-DtrESx-C.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/router-vendor-D4by-_6Z.js">
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-DSXbuxSR.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/query-vendor-Dc_G4OIP.js">
|
<link rel="modulepreload" crossorigin href="/assets/supabase-L170XLdN.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/ui-vendor-DHNIDV-1.js">
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BLxppFDo.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/supabase-vendor-By1yMVW6.js">
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-DNOlzffn.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/state-vendor-DK3LaRDK.js">
|
<link rel="modulepreload" crossorigin href="/assets/react-eH9hKoTL.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/css/index-CYCdtjzd.css">
|
<link rel="modulepreload" crossorigin href="/assets/useUserStore-Btl1TeSc.js">
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/useAuth-Bw9Uh8UY.js">
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-B3UgNpvV.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
50
fix_rls_disable.sql
Normal file
50
fix_rls_disable.sql
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- REMOVER TODAS AS POLÍTICAS RLS DO SCHEMA 'rdo'
|
||||||
|
-- Execute este script no SQL Editor do Supabase
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Desabilitar RLS em todas as tabelas
|
||||||
|
ALTER TABLE rdo.usuarios DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.organizacoes DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.obras DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdos DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdo_atividades DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdo_mao_obra DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdo_equipamentos DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdo_ocorrencias DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdo_anexos DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdo_inspecoes_solda DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.rdo_verificacoes_torque DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.tarefas DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.inventario_equipamentos DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.task_logs DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.tipos_atividade DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.condicoes_climaticas DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.tipos_ocorrencia DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.funcoes_cargos DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.equipamentos DISABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE rdo.materiais DISABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
-- Dropar políticas existentes
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_usuarios" ON rdo.usuarios;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_orgs" ON rdo.organizacoes;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_obras" ON rdo.obras;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_rdos" ON rdo.rdos;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_ativ" ON rdo.rdo_atividades;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_mao" ON rdo.rdo_mao_obra;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_equip" ON rdo.rdo_equipamentos;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_ocor" ON rdo.rdo_ocorrencias;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_anex" ON rdo.rdo_anexos;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_insp" ON rdo.rdo_inspecoes_solda;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_torq" ON rdo.rdo_verificacoes_torque;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_tarefas" ON rdo.tarefas;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_inv_equip" ON rdo.inventario_equipamentos;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_task_logs" ON rdo.task_logs;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_tipos_ativ" ON rdo.tipos_atividade;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_cond_clim" ON rdo.condicoes_climaticas;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_tipos_ocor" ON rdo.tipos_ocorrencia;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_func" ON rdo.funcoes_cargos;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_equip" ON rdo.equipamentos;
|
||||||
|
DROP POLICY IF EXISTS "auth_all_rdo_materiais" ON rdo.materiais;
|
||||||
|
|
||||||
|
SELECT 'RLS desabilitado em todas as tabelas do schema rdo!' AS resultado;
|
||||||
275
migrate_rdo_schema.sql
Normal file
275
migrate_rdo_schema.sql
Normal file
@@ -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;
|
||||||
5044
package-lock.json
generated
5044
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
83
package.json
83
package.json
@@ -16,54 +16,53 @@
|
|||||||
"auto-sync": "node scripts/auto-sync.js"
|
"auto-sync": "node scripts/auto-sync.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/core": "^6.0.0",
|
"@capacitor/core": "^8.2.0",
|
||||||
"@capacitor/ios": "^6.0.0",
|
"@capacitor/ios": "^8.2.0",
|
||||||
"@hookform/resolvers": "^5.2.1",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
"@supabase/supabase-js": "^2.39.0",
|
"@supabase/supabase-js": "^2.99.3",
|
||||||
"@tanstack/react-query": "^5.89.0",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"@tanstack/react-query-devtools": "^5.89.0",
|
"@tanstack/react-query": "^5.95.0",
|
||||||
"@vitejs/plugin-react": "^4.4.1",
|
"@tanstack/react-query-devtools": "^5.95.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^5.0.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dexie": "^4.2.1",
|
"dexie": "^4.3.0",
|
||||||
"dexie-react-hooks": "^4.2.0",
|
"dexie-react-hooks": "^4.2.0",
|
||||||
"framer-motion": "^10.18.0",
|
"framer-motion": "^12.38.0",
|
||||||
"lucide-react": "^0.511.0",
|
"lucide-react": "^0.577.0",
|
||||||
"phosphor-react": "^1.4.1",
|
"phosphor-react": "^1.4.1",
|
||||||
"postcss": "^8.5.3",
|
|
||||||
"qrcode.react": "^4.2.0",
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.2.4",
|
||||||
"react-hook-form": "^7.48.2",
|
"react-hook-form": "^7.72.0",
|
||||||
"react-router-dom": "^7.3.0",
|
"react-router-dom": "^7.13.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.0.2",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^4.2.2",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^6.3.5",
|
"vite": "^8.0.1",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^6.1.1",
|
||||||
"zod": "^3.22.4",
|
"zod": "^4.3.6",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/cli": "^6.0.0",
|
"@capacitor/cli": "^8.2.0",
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.39.4",
|
||||||
"@types/node": "^22.15.30",
|
"@types/node": "^25.5.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
||||||
"@typescript-eslint/parser": "^7.15.0",
|
"@typescript-eslint/parser": "^8.57.1",
|
||||||
"babel-plugin-react-dev-locator": "^1.0.0",
|
"babel-plugin-react-dev-locator": "^1.0.6",
|
||||||
"baseline-browser-mapping": "^2.10.0",
|
"baseline-browser-mapping": "^2.10.10",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.7",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^16.0.0",
|
"globals": "^15.15.0",
|
||||||
"typescript-eslint": "^8.30.1"
|
"typescript-eslint": "^8.57.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
/** WARNING: DON'T EDIT THIS FILE */
|
|
||||||
/** WARNING: DON'T EDIT THIS FILE */
|
|
||||||
/** WARNING: DON'T EDIT THIS FILE */
|
|
||||||
|
|
||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
245
setup_rdo_schema_final.sql
Normal file
245
setup_rdo_schema_final.sql
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- SCRIPT DE MIGRAÇÃO: CONFIGURAÇÃO DO ESQUEMA 'rdo'
|
||||||
|
-- ============================================================================
|
||||||
|
-- Este script cria o esquema 'rdo', tabelas e permissões necessárias
|
||||||
|
-- para alinhar o banco de dados com a configuração do app.
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- 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()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 4. HABILITAR RLS NO NOVO ESQUEMA
|
||||||
|
-- ============================================================================
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- 5. CRIAR POLÍTICAS PERMISSIVAS (INICIAL) PARA AUTHENTICATED
|
||||||
|
-- ============================================================================
|
||||||
|
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);
|
||||||
|
|
||||||
|
-- 6. PERMISSÕES DE SCHEMA
|
||||||
|
-- ============================================================================
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- 7. CORRIGIR TRIGGER PARA O NOVO ESQUEMA
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION rdo.handle_new_user()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = rdo, public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
user_name TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Extrair nome
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Inserir em rdo.usuarios
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- Remover trigger antigo se existir em auth.users
|
||||||
|
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
||||||
|
|
||||||
|
-- Criar novo trigger apontando para a função no esquema rdo
|
||||||
|
CREATE TRIGGER on_auth_user_created
|
||||||
|
AFTER INSERT ON auth.users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION rdo.handle_new_user();
|
||||||
|
|
||||||
|
-- 8. DADOS INICIAIS (SEED)
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Criar organização padrão se não houver nenhuma
|
||||||
|
INSERT INTO rdo.organizacoes (nome, slug, status, plano)
|
||||||
|
VALUES ('Baldon Engemetal', 'baldon-engemetal', 'ativa', 'profissional')
|
||||||
|
ON CONFLICT (slug) DO NOTHING;
|
||||||
|
|
||||||
|
-- Garantir que o admin atual seja um usuário no esquema rdo (se ele já existir no Auth)
|
||||||
|
INSERT INTO rdo.usuarios (id, email, nome, role, ativo)
|
||||||
|
SELECT id, email, 'Admin TrackSteel', 'dev', true
|
||||||
|
FROM auth.users
|
||||||
|
WHERE email = 'admtracksteel@gmail.com'
|
||||||
|
ON CONFLICT (id) DO UPDATE SET role = 'dev', ativo = true;
|
||||||
|
|
||||||
|
-- Associar admin à organização Baldon
|
||||||
|
UPDATE rdo.usuarios
|
||||||
|
SET organizacao_id = (SELECT id FROM rdo.organizacoes WHERE slug = 'baldon-engemetal' LIMIT 1)
|
||||||
|
WHERE email = 'admtracksteel@gmail.com';
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { User, Session, AuthError } from '@supabase/supabase-js';
|
import { User, Session, AuthError } from '@supabase/supabase-js';
|
||||||
import { supabase } from '../lib/supabase';
|
import { supabase, supabaseAuth } from '../lib/supabase';
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
@@ -32,20 +32,23 @@ export const useAuth = () => {
|
|||||||
// Verificar sessão atual
|
// Verificar sessão atual
|
||||||
const getSession = async () => {
|
const getSession = async () => {
|
||||||
try {
|
try {
|
||||||
const { data: { session }, error } = await supabase.auth.getSession();
|
const { data: { session }, error } = await supabaseAuth.auth.getSession();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
console.log('✅ useAuth: Sessão recuperada:', session?.user?.email);
|
console.log('✅ useAuth: Sessão recuperada:', session?.user?.email);
|
||||||
|
|
||||||
// Se não tiver usuário, finaliza loading imediatamente
|
// Se não tiver usuário, tenta fazer bypass automático para acesso direto
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
|
console.log('⚠️ useAuth: Nenhuma sessão ativa. Tentando bypass automático...');
|
||||||
|
const result = await bypassLogin();
|
||||||
|
if (!result.success) {
|
||||||
setAuthState({
|
setAuthState({
|
||||||
user: null,
|
user: null,
|
||||||
session: null,
|
session: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null
|
error: null
|
||||||
});
|
});
|
||||||
console.log('⚠️ useAuth: Nenhuma sessão ativa');
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +102,7 @@ export const useAuth = () => {
|
|||||||
getSession();
|
getSession();
|
||||||
|
|
||||||
// Escutar mudanças de autenticação
|
// Escutar mudanças de autenticação
|
||||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
const { data: { subscription } } = supabaseAuth.auth.onAuthStateChange(
|
||||||
async (event, session) => {
|
async (event, session) => {
|
||||||
console.log('🔔 Auth state changed:', event, session?.user?.email);
|
console.log('🔔 Auth state changed:', event, session?.user?.email);
|
||||||
|
|
||||||
@@ -294,8 +297,8 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
setAuthState(prev => ({ ...prev, loading: true, error: null }));
|
setAuthState(prev => ({ ...prev, loading: true, error: null }));
|
||||||
|
|
||||||
console.log('🌐 useAuth: Chamando supabase.auth.signInWithPassword...');
|
console.log('🌐 useAuth: Chamando supabaseAuth.signInWithPassword...');
|
||||||
const { data, error } = await supabase.auth.signInWithPassword({
|
const { data, error } = await supabaseAuth.auth.signInWithPassword({
|
||||||
email: credentials.email,
|
email: credentials.email,
|
||||||
password: credentials.password
|
password: credentials.password
|
||||||
});
|
});
|
||||||
@@ -326,7 +329,7 @@ export const useAuth = () => {
|
|||||||
try {
|
try {
|
||||||
setAuthState(prev => ({ ...prev, loading: true, error: null }));
|
setAuthState(prev => ({ ...prev, loading: true, error: null }));
|
||||||
|
|
||||||
const { data, error } = await supabase.auth.signUp({
|
const { data, error } = await supabaseAuth.auth.signUp({
|
||||||
email: credentials.email,
|
email: credentials.email,
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
options: {
|
options: {
|
||||||
@@ -360,7 +363,7 @@ export const useAuth = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 2. Disparar signOut do Supabase em background (sem await para não travar a UI)
|
// 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.auth.signOut().catch(err => console.warn('Erro silencioso no signOut:', err));
|
||||||
|
|
||||||
// 3. Limpar estado local do hook
|
// 3. Limpar estado local do hook
|
||||||
setAuthState({
|
setAuthState({
|
||||||
@@ -379,7 +382,7 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
const resetPassword = async (email: string) => {
|
const resetPassword = async (email: string) => {
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
const { error } = await supabaseAuth.auth.resetPasswordForEmail(email, {
|
||||||
redirectTo: `${window.location.origin}/reset-password`
|
redirectTo: `${window.location.origin}/reset-password`
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -392,7 +395,7 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
const updatePassword = async (newPassword: string) => {
|
const updatePassword = async (newPassword: string) => {
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase.auth.updateUser({
|
const { error } = await supabaseAuth.auth.updateUser({
|
||||||
password: newPassword
|
password: newPassword
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -408,7 +411,7 @@ export const useAuth = () => {
|
|||||||
if (!authState.user) throw new Error('Usuário não autenticado');
|
if (!authState.user) throw new Error('Usuário não autenticado');
|
||||||
|
|
||||||
// Atualizar metadados do usuário
|
// Atualizar metadados do usuário
|
||||||
const { error: authError } = await supabase.auth.updateUser({
|
const { error: authError } = await supabaseAuth.auth.updateUser({
|
||||||
data: updates
|
data: updates
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -433,33 +436,65 @@ export const useAuth = () => {
|
|||||||
setAuthState(prev => ({ ...prev, error: null }));
|
setAuthState(prev => ({ ...prev, error: null }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Função de bypass para desenvolvimento
|
// Função de bypass para desenvolvimento e acesso direto
|
||||||
const bypassLogin = async () => {
|
const bypassLogin = async () => {
|
||||||
console.log('🚧 useAuth: Iniciando bypass de desenvolvimento...');
|
console.log('🚧 useAuth: Iniciando bypass de acesso direto...');
|
||||||
try {
|
try {
|
||||||
setAuthState(prev => ({ ...prev, loading: true, error: null }));
|
setAuthState(prev => ({ ...prev, loading: true, error: null }));
|
||||||
|
|
||||||
// Simular um usuário autenticado
|
// Buscar um usuário real do banco para garantir que o app funcione com dados reais
|
||||||
|
// Usamos o cliente de serviço (bypass RLS) para encontrar o admin ou o primeiro usuário
|
||||||
|
const { data: realUsers, error: dbError } = await (supabase as any)
|
||||||
|
.from('usuarios')
|
||||||
|
.select('*')
|
||||||
|
.order('role', { ascending: true }) // Tenta pegar admin/dev primeiro
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (dbError) {
|
||||||
|
console.warn('⚠️ useAuth: Erro ao buscar usuário real para bypass:', dbError);
|
||||||
|
}
|
||||||
|
|
||||||
|
const realUser = realUsers?.[0];
|
||||||
|
|
||||||
|
const userId = realUser?.id || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const userEmail = realUser?.email || 'admin@tracksteel.com.br';
|
||||||
|
const userName = realUser?.nome || 'Administrador (Bypass)';
|
||||||
|
|
||||||
|
console.log(`👤 useAuth: Usando usuário para bypass: ${userName} (${userEmail})`);
|
||||||
|
|
||||||
|
// Simular um usuário autenticado do Supabase
|
||||||
const mockUser = {
|
const mockUser = {
|
||||||
id: 'bypass-user-' + Date.now(),
|
id: userId,
|
||||||
email: 'bypass@desenvolvimento.com',
|
email: userEmail,
|
||||||
user_metadata: {
|
user_metadata: {
|
||||||
nome: 'Usuário Bypass'
|
nome: userName,
|
||||||
|
full_name: userName
|
||||||
},
|
},
|
||||||
aud: 'authenticated',
|
aud: 'authenticated',
|
||||||
role: 'authenticated',
|
role: 'authenticated',
|
||||||
app_metadata: {},
|
app_metadata: {},
|
||||||
created_at: new Date().toISOString()
|
created_at: realUser?.created_at || new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSession = {
|
const mockSession = {
|
||||||
access_token: 'mock-token',
|
access_token: 'mock-token-' + Date.now(),
|
||||||
refresh_token: 'mock-refresh',
|
refresh_token: 'mock-refresh-' + Date.now(),
|
||||||
expires_in: 3600,
|
expires_in: 3600,
|
||||||
token_type: 'bearer',
|
token_type: 'bearer',
|
||||||
user: mockUser
|
user: mockUser
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Carregar perfil completo no store global antes de liberar o loading
|
||||||
|
const { useUserStore } = await import('../stores/useUserStore');
|
||||||
|
|
||||||
|
if (realUser) {
|
||||||
|
// Se temos o usuário real, já colocamos no store
|
||||||
|
useUserStore.getState().setCurrentUser(realUser);
|
||||||
|
} else {
|
||||||
|
// Fallback: tenta buscar ou cria estado inicial
|
||||||
|
await useUserStore.getState().fetchCurrentUser(userId).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
// Atualizar estado de autenticação
|
// Atualizar estado de autenticação
|
||||||
setAuthState({
|
setAuthState({
|
||||||
user: mockUser as unknown as User,
|
user: mockUser as unknown as User,
|
||||||
@@ -468,7 +503,7 @@ export const useAuth = () => {
|
|||||||
error: null
|
error: null
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ useAuth: Bypass concluído com sucesso');
|
console.log('✅ useAuth: Bypass de acesso direto concluído');
|
||||||
return { success: true, data: { user: mockUser as unknown as User, session: mockSession as unknown as Session } };
|
return { success: true, data: { user: mockUser as unknown as User, session: mockSession as unknown as Session } };
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error('❌ useAuth: Erro no bypass:', error);
|
console.error('❌ useAuth: Erro no bypass:', error);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { supabase } from '../lib/supabase';
|
import { supabase } from '../lib/supabase';
|
||||||
import { queryKeys, invalidateQueries } from '../lib/queryClient';
|
import { queryKeys, invalidateQueries } from '../lib/queryClient';
|
||||||
@@ -7,11 +7,11 @@ import type { RealtimeChannel } from '@supabase/supabase-js';
|
|||||||
// Hook para sincronização em tempo real de usuários
|
// Hook para sincronização em tempo real de usuários
|
||||||
export const useUsersRealtime = () => {
|
export const useUsersRealtime = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const channelRef = useRef<RealtimeChannel | null>(null);
|
const [channel, setChannel] = useState<RealtimeChannel | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Criar canal de subscription
|
// Criar canal de subscription
|
||||||
channelRef.current = supabase
|
const newChannel = supabase
|
||||||
.channel('usuarios-changes')
|
.channel('usuarios-changes')
|
||||||
.on(
|
.on(
|
||||||
'postgres_changes',
|
'postgres_changes',
|
||||||
@@ -34,26 +34,31 @@ export const useUsersRealtime = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.subscribe();
|
newChannel.subscribe();
|
||||||
|
|
||||||
|
// Deferir o setChannel para evitar o erro de renderização em cascata síncrona
|
||||||
|
setTimeout(() => {
|
||||||
|
setChannel(newChannel);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// Cleanup na desmontagem
|
// Cleanup na desmontagem
|
||||||
return () => {
|
return () => {
|
||||||
if (channelRef.current) {
|
if (newChannel) {
|
||||||
supabase.removeChannel(channelRef.current);
|
supabase.removeChannel(newChannel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [queryClient]);
|
}, [queryClient]);
|
||||||
|
|
||||||
return channelRef.current;
|
return channel;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook para sincronização em tempo real de obras
|
// Hook para sincronização em tempo real de obras
|
||||||
export const useObrasRealtimeSync = () => {
|
export const useObrasRealtimeSync = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const channelRef = useRef<RealtimeChannel | null>(null);
|
const [channel, setChannel] = useState<RealtimeChannel | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
channelRef.current = supabase
|
const newChannel = supabase
|
||||||
.channel('obras-changes')
|
.channel('obras-changes')
|
||||||
.on(
|
.on(
|
||||||
'postgres_changes',
|
'postgres_changes',
|
||||||
@@ -79,25 +84,30 @@ export const useObrasRealtimeSync = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.subscribe();
|
newChannel.subscribe();
|
||||||
|
|
||||||
|
// Deferir o setChannel para evitar o erro de renderização em cascata síncrona
|
||||||
|
setTimeout(() => {
|
||||||
|
setChannel(newChannel);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (channelRef.current) {
|
if (newChannel) {
|
||||||
supabase.removeChannel(channelRef.current);
|
supabase.removeChannel(newChannel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [queryClient]);
|
}, [queryClient]);
|
||||||
|
|
||||||
return channelRef.current;
|
return channel;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook para sincronização em tempo real de RDOs
|
// Hook para sincronização em tempo real de RDOs
|
||||||
export const useRdosRealtimeSync = (obraId?: string) => {
|
export const useRdosRealtimeSync = (obraId?: string) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const channelRef = useRef<RealtimeChannel | null>(null);
|
const [channel, setChannel] = useState<RealtimeChannel | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
channelRef.current = supabase
|
const newChannel = supabase
|
||||||
.channel('rdos-changes')
|
.channel('rdos-changes')
|
||||||
.on(
|
.on(
|
||||||
'postgres_changes',
|
'postgres_changes',
|
||||||
@@ -114,24 +124,24 @@ export const useRdosRealtimeSync = (obraId?: string) => {
|
|||||||
invalidateQueries.rdos();
|
invalidateQueries.rdos();
|
||||||
|
|
||||||
if (payload.new && typeof payload.new === 'object') {
|
if (payload.new && typeof payload.new === 'object') {
|
||||||
const newRdo = payload.new as any;
|
const newRdo = payload.new as Record<string, unknown>;
|
||||||
|
|
||||||
// Invalidar RDO específico
|
// Invalidar RDO específico
|
||||||
if ('id' in newRdo) {
|
if ('id' in newRdo) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.rdos.detail(newRdo.id)
|
queryKey: queryKeys.rdos.detail(newRdo.id as string)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidar RDOs da obra
|
// Invalidar RDOs da obra
|
||||||
if ('obra_id' in newRdo) {
|
if ('obra_id' in newRdo) {
|
||||||
invalidateQueries.rdosByObra(newRdo.obra_id);
|
invalidateQueries.rdosByObra(newRdo.obra_id as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidar RDOs do usuário
|
// Invalidar RDOs do usuário
|
||||||
if ('usuario_id' in newRdo) {
|
if ('usuario_id' in newRdo) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.rdos.byUser(newRdo.usuario_id)
|
queryKey: queryKeys.rdos.byUser(newRdo.usuario_id as string)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,14 +149,18 @@ export const useRdosRealtimeSync = (obraId?: string) => {
|
|||||||
)
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setChannel(newChannel);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (channelRef.current) {
|
if (newChannel) {
|
||||||
supabase.removeChannel(channelRef.current);
|
supabase.removeChannel(newChannel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [queryClient, obraId]);
|
}, [queryClient, obraId]);
|
||||||
|
|
||||||
return channelRef.current;
|
return channel;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook principal para sincronização completa
|
// Hook principal para sincronização completa
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
@theme {
|
||||||
|
--breakpoint-xs: 475px;
|
||||||
|
--max-width-screen-xs: 475px;
|
||||||
|
|
||||||
|
--container-padding: 1rem;
|
||||||
|
--container-center: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--container-sm: 1.5rem;
|
||||||
|
--container-lg: 2rem;
|
||||||
|
--container-xl: 2.5rem;
|
||||||
|
--container-2xl: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export class OfflineManager {
|
|||||||
filter?: (item: T) => boolean
|
filter?: (item: T) => boolean
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
try {
|
try {
|
||||||
let query = offlineDb[table].where('_deleted').notEqual(1);
|
const query = offlineDb[table].where('_deleted').notEqual(1);
|
||||||
const data = await query.toArray();
|
const data = await query.toArray();
|
||||||
|
|
||||||
if (filter) {
|
if (filter) {
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
import { createClient } from '@supabase/supabase-js'
|
import { createClient, SupabaseClient } from '@supabase/supabase-js'
|
||||||
import { Database } from '../types/database.types'
|
import { Database } from '../types/database.types'
|
||||||
|
|
||||||
// Configurações do Supabase
|
// Configurações do Supabase
|
||||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
||||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
|
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
|
// Verificar se as variáveis de ambiente estão definidas
|
||||||
if (!supabaseUrl || !supabaseAnonKey) {
|
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')
|
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
|
// Cliente principal: usa service_role para operations de banco (bypass RLS), sem auth
|
||||||
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
|
export const supabase: SupabaseClient<Database> = createClient<Database>(supabaseUrl, serviceRoleKey || supabaseAnonKey, {
|
||||||
auth: {
|
auth: {
|
||||||
autoRefreshToken: true,
|
persistSession: false,
|
||||||
persistSession: true,
|
autoRefreshToken: false
|
||||||
detectSessionInUrl: true,
|
},
|
||||||
flowType: 'implicit' // Implicit flow para evitar problemas com PKCE em produção
|
db: {
|
||||||
|
schema: 'rdo'
|
||||||
},
|
},
|
||||||
realtime: {
|
realtime: {
|
||||||
params: {
|
params: {
|
||||||
@@ -25,15 +27,35 @@ export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
|
|||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
headers: {
|
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<Database>(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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Tipos auxiliares para facilitar o uso
|
// Tipos auxiliares para facilitar o uso
|
||||||
export type Tables<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Row']
|
export type Tables<T extends keyof Database['rdo']['Tables']> = Database['rdo']['Tables'][T]['Row']
|
||||||
export type TablesInsert<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Insert']
|
export type TablesInsert<T extends keyof Database['rdo']['Tables']> = Database['rdo']['Tables'][T]['Insert']
|
||||||
export type TablesUpdate<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Update']
|
export type TablesUpdate<T extends keyof Database['rdo']['Tables']> = Database['rdo']['Tables'][T]['Update']
|
||||||
|
|
||||||
// Função para verificar se o usuário está autenticado
|
// Função para verificar se o usuário está autenticado
|
||||||
export const isAuthenticated = () => {
|
export const isAuthenticated = () => {
|
||||||
@@ -177,18 +199,18 @@ export const deleteFile = async (bucket: string, path: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configuração de real-time para diferentes tabelas
|
// Configuração de real-time para diferentes tabelas
|
||||||
export const subscribeToTable = <T extends keyof Database['public']['Tables']>(
|
export const subscribeToTable = <T extends keyof Database['rdo']['Tables']>(
|
||||||
table: T,
|
table: T,
|
||||||
callback: (payload: Record<string, unknown>) => void,
|
callback: (payload: Record<string, unknown>) => void,
|
||||||
filter?: string
|
filter?: string
|
||||||
) => {
|
) => {
|
||||||
const channel = supabase
|
const channel = supabase
|
||||||
.channel(`public:${table}`)
|
.channel(`rdo:${table}`)
|
||||||
.on(
|
.on(
|
||||||
'postgres_changes',
|
'postgres_changes',
|
||||||
{
|
{
|
||||||
event: '*',
|
event: '*',
|
||||||
schema: 'public',
|
schema: 'rdo',
|
||||||
table: table,
|
table: table,
|
||||||
filter: filter
|
filter: filter
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const SyncLogsPage: React.FC = () => {
|
|||||||
setStats(syncStats);
|
setStats(syncStats);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const handleResolveConflict = (conflictId: string) => {
|
const handleResolveConflict = (conflictId: string) => {
|
||||||
// Aqui você implementaria a lógica de resolução manual
|
// Aqui você implementaria a lógica de resolução manual
|
||||||
// Por enquanto, apenas remove o conflito
|
// Por enquanto, apenas remove o conflito
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export class SyncService {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'INSERT': {
|
case 'INSERT': {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const { error: insertError } = await supabase
|
const { error: insertError } = await supabase
|
||||||
.from(table)
|
.from(table)
|
||||||
.insert(data as any);
|
.insert(data as any);
|
||||||
@@ -173,7 +173,7 @@ export class SyncService {
|
|||||||
await this.checkAndResolveConflict(table, data);
|
await this.checkAndResolveConflict(table, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const { error: updateError } = await supabase
|
const { error: updateError } = await supabase
|
||||||
.from(table)
|
.from(table)
|
||||||
.update(data as any)
|
.update(data as any)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Baseado na arquitetura completa documentada
|
// Baseado na arquitetura completa documentada
|
||||||
|
|
||||||
export interface Database {
|
export interface Database {
|
||||||
public: {
|
rdo: {
|
||||||
Tables: {
|
Tables: {
|
||||||
usuarios: {
|
usuarios: {
|
||||||
Row: {
|
Row: {
|
||||||
@@ -506,9 +506,9 @@ export interface Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tipos auxiliares para facilitar o uso
|
// Tipos auxiliares para facilitar o uso
|
||||||
export type Tables<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Row']
|
export type Tables<T extends keyof Database['rdo']['Tables']> = Database['rdo']['Tables'][T]['Row']
|
||||||
export type TablesInsert<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Insert']
|
export type TablesInsert<T extends keyof Database['rdo']['Tables']> = Database['rdo']['Tables'][T]['Insert']
|
||||||
export type TablesUpdate<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Update']
|
export type TablesUpdate<T extends keyof Database['rdo']['Tables']> = Database['rdo']['Tables'][T]['Update']
|
||||||
|
|
||||||
// Tipos específicos das entidades
|
// Tipos específicos das entidades
|
||||||
export type Usuario = Tables<'usuarios'>
|
export type Usuario = Tables<'usuarios'>
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
|
|
||||||
export default {
|
|
||||||
darkMode: "class",
|
|
||||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
|
||||||
theme: {
|
|
||||||
container: {
|
|
||||||
center: true,
|
|
||||||
padding: {
|
|
||||||
DEFAULT: '1rem',
|
|
||||||
sm: '1.5rem',
|
|
||||||
lg: '2rem',
|
|
||||||
xl: '2.5rem',
|
|
||||||
'2xl': '3rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
screens: {
|
|
||||||
'xs': '475px',
|
|
||||||
'sm': '640px',
|
|
||||||
'md': '768px',
|
|
||||||
'lg': '1024px',
|
|
||||||
'xl': '1280px',
|
|
||||||
'2xl': '1536px',
|
|
||||||
},
|
|
||||||
extend: {
|
|
||||||
maxWidth: {
|
|
||||||
'screen-xs': '475px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
@@ -1,66 +1,22 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig(({ command, mode }) => {
|
export default defineConfig({
|
||||||
const isDev = command === 'serve';
|
|
||||||
const isProd = mode === 'production';
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Base path otimizado para Netlify
|
// Base path otimizado para Netlify
|
||||||
base: '/',
|
base: '/',
|
||||||
|
|
||||||
// Otimizações de build para Netlify
|
// Otimizações de build para Netlify
|
||||||
build: {
|
build: {
|
||||||
sourcemap: false, // Desabilitar sourcemaps em produção para reduzir tamanho
|
minify: true,
|
||||||
target: 'es2020',
|
|
||||||
minify: 'esbuild',
|
|
||||||
cssMinify: true,
|
|
||||||
assetsInlineLimit: 4096, // Inline assets menores que 4KB
|
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
|
|
||||||
// Configurações avançadas de chunk splitting
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
// Nomes de arquivo com hash para cache busting
|
|
||||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
|
||||||
entryFileNames: 'assets/js/[name]-[hash].js',
|
|
||||||
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
|
|
||||||
|
|
||||||
// Estratégia de splitting otimizada
|
|
||||||
manualChunks: {
|
|
||||||
// Vendor chunks para melhor cache
|
|
||||||
'react-vendor': ['react', 'react-dom'],
|
|
||||||
'router-vendor': ['react-router-dom'],
|
|
||||||
'query-vendor': ['@tanstack/react-query', '@tanstack/react-query-devtools'],
|
|
||||||
'supabase-vendor': ['@supabase/supabase-js'],
|
|
||||||
'ui-vendor': ['lucide-react', 'framer-motion', 'sonner'],
|
|
||||||
'form-vendor': ['react-hook-form', '@hookform/resolvers', 'zod'],
|
|
||||||
'state-vendor': ['zustand', 'dexie']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Configurações de compressão otimizadas para Netlify
|
|
||||||
reportCompressedSize: false,
|
|
||||||
chunkSizeWarningLimit: 1000,
|
|
||||||
|
|
||||||
// Otimizações específicas para deploy
|
|
||||||
modulePreload: {
|
|
||||||
polyfill: false // Reduz o bundle size
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Otimizações de desenvolvimento
|
// Otimizações de desenvolvimento
|
||||||
server: {
|
server: {
|
||||||
hmr: {
|
host: true,
|
||||||
overlay: false // Reduz ruído visual durante desenvolvimento
|
port: 5173,
|
||||||
},
|
|
||||||
// Configurações de performance
|
|
||||||
fs: {
|
|
||||||
strict: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Configurações de preview otimizadas
|
// Configurações de preview otimizadas
|
||||||
@@ -103,18 +59,6 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
tsconfigPaths(),
|
tailwindcss(),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Configuração para ignorar erros de TypeScript no build
|
|
||||||
esbuild: {
|
|
||||||
logOverride: { 'this-is-undefined-in-esm': 'silent' },
|
|
||||||
...(isProd && {
|
|
||||||
drop: ['console', 'debugger'],
|
|
||||||
minifyIdentifiers: true,
|
|
||||||
minifySyntax: true,
|
|
||||||
minifyWhitespace: true,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user