Compare commits
22 Commits
b07b73d3b4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5673eb5255 | |||
| 556a2ace41 | |||
| d5a2b570ec | |||
|
|
bc70aaaa5c | ||
|
|
ab71f9abc8 | ||
|
|
2261da27ad | ||
|
|
aaeab42050 | ||
|
|
25c75df1fd | ||
|
|
45c0de3605 | ||
|
|
0ad29893d0 | ||
|
|
c9c7f40a0b | ||
|
|
e09a4f8389 | ||
|
|
e198eb3b09 | ||
| c9e75fbe2c | |||
| 8bda3377b9 | |||
| 0897943b5d | |||
| b6e4f14bf9 | |||
| 958ca0fb92 | |||
| 98ab91c21a | |||
| 569747f166 | |||
| da6b1c8847 | |||
| d028e51615 |
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
|
||||||
110
.github/SECRETS_SETUP.md
vendored
110
.github/SECRETS_SETUP.md
vendored
@@ -1,110 +0,0 @@
|
|||||||
# Configuração de Secrets para GitHub Actions
|
|
||||||
|
|
||||||
Para que a automação funcione corretamente, você precisa configurar os seguintes secrets no seu repositório GitHub:
|
|
||||||
|
|
||||||
## Como Configurar Secrets
|
|
||||||
|
|
||||||
1. Vá para o seu repositório no GitHub
|
|
||||||
2. Clique em **Settings** (Configurações)
|
|
||||||
3. No menu lateral, clique em **Secrets and variables** > **Actions**
|
|
||||||
4. Clique em **New repository secret**
|
|
||||||
5. Adicione cada secret listado abaixo
|
|
||||||
|
|
||||||
## Secrets Necessários
|
|
||||||
|
|
||||||
### 🔑 REMOTE_REPO_TOKEN
|
|
||||||
**Descrição:** Token de acesso pessoal para o repositório remoto `https://github.com/Reifonas/TS_RDO.git`
|
|
||||||
|
|
||||||
**Como obter:**
|
|
||||||
1. Vá para GitHub Settings > Developer settings > Personal access tokens > Tokens (classic)
|
|
||||||
2. Clique em "Generate new token (classic)"
|
|
||||||
3. Selecione as permissões:
|
|
||||||
- `repo` (acesso completo a repositórios)
|
|
||||||
- `workflow` (atualizar workflows)
|
|
||||||
- `write:packages` (se usar packages)
|
|
||||||
4. Copie o token gerado
|
|
||||||
5. Cole no secret `REMOTE_REPO_TOKEN`
|
|
||||||
|
|
||||||
### 🌐 NETLIFY_AUTH_TOKEN (Opcional)
|
|
||||||
**Descrição:** Token de autenticação do Netlify para deploy automático
|
|
||||||
|
|
||||||
**Como obter:**
|
|
||||||
1. Faça login no Netlify
|
|
||||||
2. Vá para User settings > Applications > Personal access tokens
|
|
||||||
3. Clique em "New access token"
|
|
||||||
4. Dê um nome e clique em "Generate token"
|
|
||||||
5. Copie o token e cole no secret `NETLIFY_AUTH_TOKEN`
|
|
||||||
|
|
||||||
### 🆔 NETLIFY_SITE_ID (Opcional)
|
|
||||||
**Descrição:** ID do site no Netlify
|
|
||||||
|
|
||||||
**Como obter:**
|
|
||||||
1. No dashboard do Netlify, clique no seu site
|
|
||||||
2. Vá para Site settings > General > Site details
|
|
||||||
3. Copie o "Site ID"
|
|
||||||
4. Cole no secret `NETLIFY_SITE_ID`
|
|
||||||
|
|
||||||
## Verificação da Configuração
|
|
||||||
|
|
||||||
Após configurar os secrets, você pode testar a automação:
|
|
||||||
|
|
||||||
1. **Teste Manual:**
|
|
||||||
- Vá para Actions no seu repositório
|
|
||||||
- Clique em "Auto Sync and Deploy"
|
|
||||||
- Clique em "Run workflow"
|
|
||||||
- Marque "Force deploy" se quiser forçar
|
|
||||||
|
|
||||||
2. **Teste Automático:**
|
|
||||||
- Faça qualquer alteração no código
|
|
||||||
- Commit e push para a branch main
|
|
||||||
- A action será executada automaticamente
|
|
||||||
|
|
||||||
## Estrutura dos Secrets
|
|
||||||
|
|
||||||
```
|
|
||||||
Repository Secrets:
|
|
||||||
├── REMOTE_REPO_TOKEN # Token para repositório remoto (OBRIGATÓRIO)
|
|
||||||
├── NETLIFY_AUTH_TOKEN # Token Netlify (opcional)
|
|
||||||
├── NETLIFY_SITE_ID # ID do site Netlify (opcional)
|
|
||||||
└── GITHUB_TOKEN # Automático (não precisa configurar)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### ❌ Erro: "Authentication failed"
|
|
||||||
- Verifique se o `REMOTE_REPO_TOKEN` está correto
|
|
||||||
- Confirme se o token tem as permissões necessárias
|
|
||||||
- Verifique se o token não expirou
|
|
||||||
|
|
||||||
### ❌ Erro: "Repository not found"
|
|
||||||
- Confirme se o repositório `Reifonas/TS_RDO` existe
|
|
||||||
- Verifique se o token tem acesso ao repositório
|
|
||||||
|
|
||||||
### ❌ Erro de Deploy Netlify
|
|
||||||
- Verifique se `NETLIFY_AUTH_TOKEN` e `NETLIFY_SITE_ID` estão corretos
|
|
||||||
- Confirme se o site existe no Netlify
|
|
||||||
|
|
||||||
## Logs e Monitoramento
|
|
||||||
|
|
||||||
Para acompanhar a execução:
|
|
||||||
1. Vá para **Actions** no seu repositório
|
|
||||||
2. Clique na execução desejada
|
|
||||||
3. Expanda os jobs para ver os logs detalhados
|
|
||||||
|
|
||||||
## Segurança
|
|
||||||
|
|
||||||
⚠️ **IMPORTANTE:**
|
|
||||||
- Nunca compartilhe seus tokens
|
|
||||||
- Use tokens com permissões mínimas necessárias
|
|
||||||
- Revogue tokens antigos quando não precisar mais
|
|
||||||
- Monitore o uso dos tokens regularmente
|
|
||||||
|
|
||||||
## Frequência de Execução
|
|
||||||
|
|
||||||
A action executa:
|
|
||||||
- ✅ A cada push na branch main ou develop
|
|
||||||
- ✅ A cada pull request para main
|
|
||||||
- ✅ A cada 30 minutos (agendado)
|
|
||||||
- ✅ Manualmente quando solicitado
|
|
||||||
|
|
||||||
Para alterar a frequência, edite o arquivo `.github/workflows/auto-sync-deploy.yml`
|
|
||||||
263
.github/workflows/auto-sync-deploy.yml
vendored
263
.github/workflows/auto-sync-deploy.yml
vendored
@@ -1,263 +0,0 @@
|
|||||||
name: Auto Sync and Deploy
|
|
||||||
|
|
||||||
# Triggers para execução da action
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main, develop ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
schedule:
|
|
||||||
# Executa a cada 30 minutos
|
|
||||||
- cron: '*/30 * * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
force_deploy:
|
|
||||||
description: 'Force deploy even without changes'
|
|
||||||
required: false
|
|
||||||
default: 'false'
|
|
||||||
type: boolean
|
|
||||||
target_branch:
|
|
||||||
description: 'Target branch for deployment'
|
|
||||||
required: false
|
|
||||||
default: 'main'
|
|
||||||
type: string
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: '18'
|
|
||||||
PNPM_VERSION: '8'
|
|
||||||
TARGET_REPO: 'Reifonas/TS_RDO'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Job para verificar mudanças
|
|
||||||
check-changes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
has-changes: ${{ steps.changes.outputs.has-changes }}
|
|
||||||
changed-files: ${{ steps.changes.outputs.changed-files }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout código
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Verificar mudanças
|
|
||||||
id: changes
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ inputs.force_deploy }}" = "true" ]; then
|
|
||||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "changed-files=scheduled-run" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD || echo "")
|
|
||||||
if [ -n "$CHANGED_FILES" ]; then
|
|
||||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "changed-files<<EOF" >> $GITHUB_OUTPUT
|
|
||||||
echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
|
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "has-changes=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "changed-files=" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Job para build e testes
|
|
||||||
build-and-test:
|
|
||||||
needs: check-changes
|
|
||||||
if: needs.check-changes.outputs.has-changes == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout código
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v2
|
|
||||||
with:
|
|
||||||
version: ${{ env.PNPM_VERSION }}
|
|
||||||
|
|
||||||
- name: Instalar dependências
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Executar linting
|
|
||||||
run: pnpm run lint
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Executar testes
|
|
||||||
run: pnpm run test
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Build do projeto
|
|
||||||
run: pnpm run build
|
|
||||||
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: build-files
|
|
||||||
path: dist/
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
# Job para sincronização com repositório remoto
|
|
||||||
sync-to-remote:
|
|
||||||
needs: [check-changes, build-and-test]
|
|
||||||
if: needs.check-changes.outputs.has-changes == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout código atual
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Configurar Git
|
|
||||||
run: |
|
|
||||||
git config --global user.name "Auto Sync Bot"
|
|
||||||
git config --global user.email "auto-sync@rdo-app.com"
|
|
||||||
|
|
||||||
- name: Download build artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: build-files
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
- name: Preparar arquivos para sincronização
|
|
||||||
run: |
|
|
||||||
# Criar diretório temporário
|
|
||||||
mkdir -p /tmp/sync-repo
|
|
||||||
|
|
||||||
# Copiar arquivos essenciais (excluir node_modules, logs, etc.)
|
|
||||||
rsync -av --exclude='node_modules' \
|
|
||||||
--exclude='.git' \
|
|
||||||
--exclude='logs' \
|
|
||||||
|
|
||||||
--exclude='.env.local' \
|
|
||||||
--exclude='*.log' \
|
|
||||||
--exclude='.trae' \
|
|
||||||
--exclude='android' \
|
|
||||||
--exclude='ios' \
|
|
||||||
./ /tmp/sync-repo/
|
|
||||||
|
|
||||||
- name: Clonar repositório remoto
|
|
||||||
env:
|
|
||||||
REMOTE_TOKEN: ${{ secrets.REMOTE_REPO_TOKEN }}
|
|
||||||
run: |
|
|
||||||
cd /tmp
|
|
||||||
git clone https://${REMOTE_TOKEN}@github.com/${{ env.TARGET_REPO }}.git remote-repo
|
|
||||||
cd remote-repo
|
|
||||||
git checkout ${{ inputs.target_branch || 'main' }} || git checkout -b ${{ inputs.target_branch || 'main' }}
|
|
||||||
|
|
||||||
- name: Sincronizar arquivos
|
|
||||||
run: |
|
|
||||||
cd /tmp/remote-repo
|
|
||||||
|
|
||||||
# Remover arquivos antigos (exceto .git)
|
|
||||||
find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +
|
|
||||||
|
|
||||||
# Copiar novos arquivos
|
|
||||||
cp -r /tmp/sync-repo/* .
|
|
||||||
cp -r /tmp/sync-repo/.* . 2>/dev/null || true
|
|
||||||
|
|
||||||
# Adicionar todos os arquivos
|
|
||||||
git add .
|
|
||||||
|
|
||||||
- name: Commit e Push para repositório remoto
|
|
||||||
env:
|
|
||||||
REMOTE_TOKEN: ${{ secrets.REMOTE_REPO_TOKEN }}
|
|
||||||
run: |
|
|
||||||
cd /tmp/remote-repo
|
|
||||||
|
|
||||||
# Verificar se há mudanças
|
|
||||||
if git diff --staged --quiet; then
|
|
||||||
echo "Nenhuma mudança para sincronizar"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Criar mensagem de commit
|
|
||||||
COMMIT_MSG="Auto-sync from RDO-C: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
if [ "${{ needs.check-changes.outputs.changed-files }}" != "scheduled-run" ]; then
|
|
||||||
COMMIT_MSG="$COMMIT_MSG\n\nChanged files:\n${{ needs.check-changes.outputs.changed-files }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Commit e push
|
|
||||||
git commit -m "$COMMIT_MSG"
|
|
||||||
git push origin ${{ inputs.target_branch || 'main' }}
|
|
||||||
|
|
||||||
# Job para deploy (se necessário)
|
|
||||||
deploy:
|
|
||||||
needs: [check-changes, build-and-test, sync-to-remote]
|
|
||||||
if: needs.check-changes.outputs.has-changes == 'true' && github.ref == 'refs/heads/main'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout código
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Download build artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: build-files
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
- name: Deploy para Netlify
|
|
||||||
uses: nwtgck/actions-netlify@v3.0
|
|
||||||
with:
|
|
||||||
publish-dir: './dist'
|
|
||||||
production-branch: main
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
deploy-message: "Auto-deploy from commit ${{ github.sha }}"
|
|
||||||
enable-pull-request-comment: false
|
|
||||||
enable-commit-comment: true
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
|
||||||
timeout-minutes: 10
|
|
||||||
|
|
||||||
# Job para notificações
|
|
||||||
notify:
|
|
||||||
needs: [check-changes, build-and-test, sync-to-remote, deploy]
|
|
||||||
if: always() && needs.check-changes.outputs.has-changes == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Notificar sucesso
|
|
||||||
if: needs.sync-to-remote.result == 'success'
|
|
||||||
run: |
|
|
||||||
echo "✅ Sincronização concluída com sucesso!"
|
|
||||||
echo "Repositório: ${{ env.TARGET_REPO }}"
|
|
||||||
echo "Branch: ${{ inputs.target_branch || 'main' }}"
|
|
||||||
echo "Commit: ${{ github.sha }}"
|
|
||||||
|
|
||||||
- name: Notificar falha
|
|
||||||
if: needs.sync-to-remote.result == 'failure' || needs.build-and-test.result == 'failure'
|
|
||||||
run: |
|
|
||||||
echo "❌ Falha na sincronização!"
|
|
||||||
echo "Verifique os logs para mais detalhes."
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
# Job para limpeza
|
|
||||||
cleanup:
|
|
||||||
needs: [check-changes, build-and-test, sync-to-remote, deploy, notify]
|
|
||||||
if: always()
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Limpar artifacts
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
run_id: context.runId,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const artifact of artifacts.data.artifacts) {
|
|
||||||
if (artifact.name === 'build-files') {
|
|
||||||
await github.rest.actions.deleteArtifact({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
artifact_id: artifact.id,
|
|
||||||
});
|
|
||||||
console.log(`Artifact ${artifact.name} removido`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
64
.github/workflows/deploy.yml
vendored
64
.github/workflows/deploy.yml
vendored
@@ -1,64 +0,0 @@
|
|||||||
name: Deploy to GitHub Pages
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Executa no push para a branch main
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
# Permite execução manual
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# Define permissões necessárias para o GITHUB_TOKEN
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
# Permite apenas um deploy por vez
|
|
||||||
concurrency:
|
|
||||||
group: "pages"
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Job de build
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '18'
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm run build
|
|
||||||
|
|
||||||
- name: Setup Pages
|
|
||||||
uses: actions/configure-pages@v4
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: './dist'
|
|
||||||
|
|
||||||
# Job de deploy
|
|
||||||
deploy:
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
@@ -1,352 +0,0 @@
|
|||||||
# Análise Completa e Plano de Otimização - Aplicativo RDO
|
|
||||||
|
|
||||||
## 1. Resumo Executivo
|
|
||||||
|
|
||||||
Este documento apresenta uma análise detalhada da estrutura atual do aplicativo RDO (Relatório Diário de Obra) e propõe um plano abrangente de otimização, refatoração e modernização do código. O objetivo é melhorar a performance, legibilidade, manutenibilidade e seguir as melhores práticas atuais de desenvolvimento.
|
|
||||||
|
|
||||||
## 2. Análise da Arquitetura Atual
|
|
||||||
|
|
||||||
### 2.1 Pontos Fortes Identificados
|
|
||||||
- ✅ Uso de TypeScript para tipagem estática
|
|
||||||
- ✅ Arquitetura baseada em React 18 com hooks modernos
|
|
||||||
- ✅ Integração com Supabase para backend-as-a-service
|
|
||||||
- ✅ Implementação de React Query para gerenciamento de estado servidor
|
|
||||||
- ✅ Uso de Zustand para estado global
|
|
||||||
- ✅ Configuração de PWA com Capacitor
|
|
||||||
- ✅ Implementação de modo offline com Dexie
|
|
||||||
- ✅ Estrutura de pastas organizada
|
|
||||||
|
|
||||||
### 2.2 Problemas Críticos Identificados
|
|
||||||
|
|
||||||
#### 2.2.1 Duplicação de Lógica de Estado
|
|
||||||
- ❌ **Problema**: Existem dois sistemas paralelos para gerenciamento de obras:
|
|
||||||
- `src/hooks/useObras.ts` (useState + useEffect)
|
|
||||||
- `src/hooks/queries/useObras.ts` (React Query)
|
|
||||||
- `src/stores/useObraStore.ts` (Zustand)
|
|
||||||
- ❌ **Impacto**: Inconsistência de dados, complexidade desnecessária, bugs potenciais
|
|
||||||
|
|
||||||
#### 2.2.2 Configuração TypeScript Permissiva
|
|
||||||
- ❌ **Problema**: `tsconfig.json` com `strict: false` e outras verificações desabilitadas
|
|
||||||
- ❌ **Impacto**: Perda de benefícios da tipagem estática, bugs em runtime
|
|
||||||
|
|
||||||
#### 2.2.3 Estrutura de Rotas Repetitiva
|
|
||||||
- ❌ **Problema**: Código repetitivo no `App.tsx` com múltiplas rotas similares
|
|
||||||
- ❌ **Impacto**: Dificulta manutenção e adiciona verbosidade
|
|
||||||
|
|
||||||
#### 2.2.4 Falta de Padronização de Componentes
|
|
||||||
- ❌ **Problema**: Componentes sem padrão consistente de props e estrutura
|
|
||||||
- ❌ **Impacto**: Dificuldade de manutenção e reutilização
|
|
||||||
|
|
||||||
## 3. Plano de Otimização Detalhado
|
|
||||||
|
|
||||||
### 3.1 Fase 1: Consolidação da Arquitetura de Estado (Prioridade Alta)
|
|
||||||
|
|
||||||
#### 3.1.1 Migração para Arquitetura Unificada
|
|
||||||
**Objetivo**: Eliminar duplicações e criar uma única fonte de verdade
|
|
||||||
|
|
||||||
**Ações**:
|
|
||||||
1. **Manter apenas React Query + Zustand**:
|
|
||||||
- Remover hooks baseados em useState (`useObras.ts`, `useRdos.ts`, etc.)
|
|
||||||
- Usar React Query para estado servidor (dados do Supabase)
|
|
||||||
- Usar Zustand apenas para estado cliente (UI, configurações)
|
|
||||||
|
|
||||||
2. **Reestruturar hooks de queries**:
|
|
||||||
```typescript
|
|
||||||
// Estrutura otimizada
|
|
||||||
src/hooks/
|
|
||||||
├── queries/
|
|
||||||
│ ├── useObrasQuery.ts // React Query apenas
|
|
||||||
│ ├── useRdosQuery.ts // React Query apenas
|
|
||||||
│ └── useUsersQuery.ts // React Query apenas
|
|
||||||
├── stores/
|
|
||||||
│ ├── useUIStore.ts // Estado da UI
|
|
||||||
│ ├── useConfigStore.ts // Configurações
|
|
||||||
│ └── useOfflineStore.ts // Estado offline
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Implementar padrão de custom hooks compostos**:
|
|
||||||
```typescript
|
|
||||||
// Exemplo: useObra.ts
|
|
||||||
export const useObra = (id: string) => {
|
|
||||||
const query = useObraQuery(id);
|
|
||||||
const mutation = useUpdateObraMutation();
|
|
||||||
const uiState = useUIStore();
|
|
||||||
|
|
||||||
return {
|
|
||||||
...query,
|
|
||||||
update: mutation.mutate,
|
|
||||||
isUpdating: mutation.isPending,
|
|
||||||
// Lógica composta
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.1.2 Otimização do React Query
|
|
||||||
**Configurações aprimoradas**:
|
|
||||||
```typescript
|
|
||||||
// queryClient.ts otimizado
|
|
||||||
export const queryClient = new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
staleTime: 5 * 60 * 1000, // 5 minutos
|
|
||||||
gcTime: 10 * 60 * 1000, // 10 minutos
|
|
||||||
retry: (failureCount, error) => {
|
|
||||||
if (error?.status === 401) return false;
|
|
||||||
return failureCount < 3;
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false, // Otimização mobile
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Fase 2: Modernização do TypeScript (Prioridade Alta)
|
|
||||||
|
|
||||||
#### 3.2.1 Configuração Strict
|
|
||||||
```json
|
|
||||||
// tsconfig.json otimizado
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"exactOptionalPropertyTypes": true,
|
|
||||||
"noUncheckedIndexedAccess": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2.2 Melhoria dos Tipos
|
|
||||||
1. **Criar tipos específicos do domínio**:
|
|
||||||
```typescript
|
|
||||||
// types/domain.ts
|
|
||||||
export type ObraStatus = 'ativa' | 'pausada' | 'concluida' | 'cancelada';
|
|
||||||
export type UserRole = 'admin' | 'engenheiro' | 'mestre_obra' | 'usuario';
|
|
||||||
|
|
||||||
export interface ObraWithRelations extends Obra {
|
|
||||||
responsavel?: Usuario;
|
|
||||||
rdos?: RDO[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementar branded types para IDs**:
|
|
||||||
```typescript
|
|
||||||
export type ObraId = string & { readonly brand: unique symbol };
|
|
||||||
export type UserId = string & { readonly brand: unique symbol };
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 Fase 3: Refatoração de Componentes (Prioridade Média)
|
|
||||||
|
|
||||||
#### 3.3.1 Sistema de Design Consistente
|
|
||||||
1. **Criar componentes base reutilizáveis**:
|
|
||||||
```typescript
|
|
||||||
// components/ui/
|
|
||||||
├── Button/
|
|
||||||
│ ├── Button.tsx
|
|
||||||
│ ├── Button.types.ts
|
|
||||||
│ └── Button.stories.tsx
|
|
||||||
├── Input/
|
|
||||||
├── Modal/
|
|
||||||
└── Card/
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementar compound components**:
|
|
||||||
```typescript
|
|
||||||
// Exemplo: Modal compound component
|
|
||||||
export const Modal = {
|
|
||||||
Root: ModalRoot,
|
|
||||||
Header: ModalHeader,
|
|
||||||
Body: ModalBody,
|
|
||||||
Footer: ModalFooter,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.3.2 Otimização de Performance
|
|
||||||
1. **Implementar React.memo estratégico**:
|
|
||||||
```typescript
|
|
||||||
export const ObraCard = React.memo(({ obra }: { obra: Obra }) => {
|
|
||||||
// Componente otimizado
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Usar React.lazy para code splitting**:
|
|
||||||
```typescript
|
|
||||||
const ObraDetails = React.lazy(() => import('./pages/ObraDetails'));
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Implementar virtualization para listas grandes**:
|
|
||||||
```typescript
|
|
||||||
import { FixedSizeList as List } from 'react-window';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.4 Fase 4: Otimização de Rotas (Prioridade Média)
|
|
||||||
|
|
||||||
#### 3.4.1 Configuração Declarativa de Rotas
|
|
||||||
```typescript
|
|
||||||
// routes/config.ts
|
|
||||||
export const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
element: Dashboard,
|
|
||||||
protected: true,
|
|
||||||
layout: MainLayout,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/obra/:id',
|
|
||||||
element: ObraDetails,
|
|
||||||
protected: true,
|
|
||||||
layout: null, // Tela cheia
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
// App.tsx simplificado
|
|
||||||
export default function App() {
|
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
<QueryProvider>
|
|
||||||
<AuthProvider>
|
|
||||||
<RouteRenderer routes={routes} />
|
|
||||||
</AuthProvider>
|
|
||||||
</QueryProvider>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.5 Fase 5: Otimizações de Performance (Prioridade Média)
|
|
||||||
|
|
||||||
#### 3.5.1 Bundle Optimization
|
|
||||||
1. **Configurar Vite para otimização**:
|
|
||||||
```typescript
|
|
||||||
// vite.config.ts
|
|
||||||
export default defineConfig({
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
manualChunks: {
|
|
||||||
vendor: ['react', 'react-dom'],
|
|
||||||
supabase: ['@supabase/supabase-js'],
|
|
||||||
ui: ['@radix-ui/react-dialog', '@radix-ui/react-select'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementar preloading estratégico**:
|
|
||||||
```typescript
|
|
||||||
// Preload de rotas críticas
|
|
||||||
const prefetchObraDetails = (obraId: string) => {
|
|
||||||
queryClient.prefetchQuery({
|
|
||||||
queryKey: ['obra', obraId],
|
|
||||||
queryFn: () => getObra(obraId),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.5.2 Otimização de Imagens e Assets
|
|
||||||
1. **Implementar lazy loading de imagens**
|
|
||||||
2. **Usar WebP com fallback**
|
|
||||||
3. **Configurar service worker para cache de assets**
|
|
||||||
|
|
||||||
### 3.6 Fase 6: Melhorias de DX (Developer Experience)
|
|
||||||
|
|
||||||
#### 3.6.1 Ferramentas de Desenvolvimento
|
|
||||||
1. **Configurar ESLint mais rigoroso**:
|
|
||||||
```javascript
|
|
||||||
// eslint.config.js
|
|
||||||
export default [
|
|
||||||
...tseslint.configs.strictTypeChecked,
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unused-vars': 'error',
|
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': 'error',
|
|
||||||
'react-hooks/exhaustive-deps': 'error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementar Prettier com configuração consistente**
|
|
||||||
3. **Configurar Husky para pre-commit hooks**
|
|
||||||
|
|
||||||
#### 3.6.2 Testing Strategy
|
|
||||||
1. **Implementar testes unitários com Vitest**
|
|
||||||
2. **Testes de integração com Testing Library**
|
|
||||||
3. **E2E tests com Playwright**
|
|
||||||
|
|
||||||
## 4. Cronograma de Implementação
|
|
||||||
|
|
||||||
### Sprint 1 (2 semanas) - Fundação
|
|
||||||
- [ ] Configurar TypeScript strict mode
|
|
||||||
- [ ] Consolidar arquitetura de estado (Fase 1)
|
|
||||||
- [ ] Remover hooks duplicados
|
|
||||||
- [ ] Configurar ferramentas de desenvolvimento
|
|
||||||
|
|
||||||
### Sprint 2 (2 semanas) - Componentes
|
|
||||||
- [ ] Criar sistema de design base
|
|
||||||
- [ ] Refatorar componentes principais
|
|
||||||
- [ ] Implementar compound components
|
|
||||||
- [ ] Otimizar performance com React.memo
|
|
||||||
|
|
||||||
### Sprint 3 (1 semana) - Rotas e Performance
|
|
||||||
- [ ] Refatorar sistema de rotas
|
|
||||||
- [ ] Implementar code splitting
|
|
||||||
- [ ] Otimizar bundle com Vite
|
|
||||||
- [ ] Configurar preloading
|
|
||||||
|
|
||||||
### Sprint 4 (1 semana) - Finalização
|
|
||||||
- [ ] Testes e validação
|
|
||||||
- [ ] Documentação
|
|
||||||
- [ ] Deploy e monitoramento
|
|
||||||
|
|
||||||
## 5. Métricas de Sucesso
|
|
||||||
|
|
||||||
### 5.1 Performance
|
|
||||||
- **Bundle size**: Redução de 30%
|
|
||||||
- **First Contentful Paint**: < 1.5s
|
|
||||||
- **Largest Contentful Paint**: < 2.5s
|
|
||||||
- **Time to Interactive**: < 3s
|
|
||||||
|
|
||||||
### 5.2 Qualidade de Código
|
|
||||||
- **TypeScript coverage**: 100%
|
|
||||||
- **ESLint errors**: 0
|
|
||||||
- **Test coverage**: > 80%
|
|
||||||
- **Duplicação de código**: < 5%
|
|
||||||
|
|
||||||
### 5.3 Developer Experience
|
|
||||||
- **Build time**: Redução de 40%
|
|
||||||
- **Hot reload**: < 200ms
|
|
||||||
- **Type checking**: < 5s
|
|
||||||
|
|
||||||
## 6. Riscos e Mitigações
|
|
||||||
|
|
||||||
### 6.1 Riscos Identificados
|
|
||||||
1. **Breaking changes durante refatoração**
|
|
||||||
- *Mitigação*: Implementar testes abrangentes antes das mudanças
|
|
||||||
|
|
||||||
2. **Regressões de funcionalidade**
|
|
||||||
- *Mitigação*: Refatoração incremental com validação contínua
|
|
||||||
|
|
||||||
3. **Resistência da equipe às mudanças**
|
|
||||||
- *Mitigação*: Documentação clara e treinamento
|
|
||||||
|
|
||||||
### 6.2 Plano de Rollback
|
|
||||||
- Manter branches de feature para cada fase
|
|
||||||
- Implementar feature flags para mudanças críticas
|
|
||||||
- Monitoramento contínuo em produção
|
|
||||||
|
|
||||||
## 7. Conclusão
|
|
||||||
|
|
||||||
Este plano de otimização transformará o aplicativo RDO em uma aplicação moderna, performática e maintível. A implementação incremental garante baixo risco enquanto maximiza os benefícios de cada melhoria.
|
|
||||||
|
|
||||||
A consolidação da arquitetura de estado e a modernização do TypeScript são as prioridades mais altas, pois impactam diretamente na estabilidade e manutenibilidade do código.
|
|
||||||
|
|
||||||
Com a implementação completa deste plano, esperamos:
|
|
||||||
- **50% de redução** no tempo de desenvolvimento de novas features
|
|
||||||
- **30% de melhoria** na performance da aplicação
|
|
||||||
- **90% de redução** em bugs relacionados a tipos
|
|
||||||
- **Experiência de desenvolvimento** significativamente melhorada
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,815 +0,0 @@
|
|||||||
# Documento de Arquitetura Técnica - RDO Mobile App
|
|
||||||
|
|
||||||
## 1. Design da Arquitetura
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[Usuário Mobile] --> B[React Native/PWA Frontend]
|
|
||||||
B --> C[Supabase SDK]
|
|
||||||
C --> D[Supabase Backend]
|
|
||||||
|
|
||||||
subgraph "Frontend Layer"
|
|
||||||
B
|
|
||||||
E[Zustand State Management]
|
|
||||||
F[React Hook Form]
|
|
||||||
G[Framer Motion]
|
|
||||||
H[Tailwind CSS]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Backend as a Service (Supabase)"
|
|
||||||
D
|
|
||||||
I[PostgreSQL Database]
|
|
||||||
J[Authentication]
|
|
||||||
K[Storage (Fotos/Docs)]
|
|
||||||
L[Real-time Subscriptions]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Funcionalidades Offline"
|
|
||||||
M[IndexedDB Cache]
|
|
||||||
N[Service Worker]
|
|
||||||
O[Background Sync]
|
|
||||||
end
|
|
||||||
|
|
||||||
B --> M
|
|
||||||
N --> B
|
|
||||||
O --> C
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Descrição das Tecnologias
|
|
||||||
|
|
||||||
* **Frontend**: React\@18 + TypeScript + Vite + Tailwind CSS
|
|
||||||
|
|
||||||
* **Mobile**: PWA (Progressive Web App) com Capacitor para recursos nativos
|
|
||||||
|
|
||||||
* **Estado**: Zustand para gerenciamento de estado global
|
|
||||||
|
|
||||||
* **Formulários**: React Hook Form + Zod para validação
|
|
||||||
|
|
||||||
* **Animações**: Framer Motion para microinterações
|
|
||||||
|
|
||||||
* **UI Components**: Headless UI + Radix UI primitives
|
|
||||||
|
|
||||||
* **Ícones**: Phosphor Icons
|
|
||||||
|
|
||||||
* **Backend**: Supabase (PostgreSQL + Auth + Storage + Real-time)
|
|
||||||
|
|
||||||
* **Cache**: TanStack Query para cache de dados
|
|
||||||
|
|
||||||
* **Offline**: Workbox para service workers
|
|
||||||
|
|
||||||
## 3. Definições de Rotas
|
|
||||||
|
|
||||||
| Rota | Propósito |
|
|
||||||
| ----------------------- | --------------------------------------------- |
|
|
||||||
| / | Dashboard principal com visão geral das obras |
|
|
||||||
| /obra/:id | Detalhes específicos de uma obra |
|
|
||||||
| /obra/:id/rdo/novo | Formulário para criar novo RDO |
|
|
||||||
| /obra/:id/rdo/:rdoId | Visualizar/editar RDO específico |
|
|
||||||
| /obra/:id/tarefas | Lista de tarefas da obra |
|
|
||||||
| /cadastros | Menu principal de cadastros |
|
|
||||||
| /cadastros/obras | Formulário de cadastro de obras |
|
|
||||||
| /cadastros/usuarios | Gerenciamento de usuários |
|
|
||||||
| /cadastros/equipamentos | Cadastro de equipamentos |
|
|
||||||
| <br /> | <br /> |
|
|
||||||
| /cadastros/atividades | Tipos de atividades padrão |
|
|
||||||
| /relatorios | Dashboard de relatórios e exportações |
|
|
||||||
| /perfil | Configurações do usuário |
|
|
||||||
| /configuracoes | Configurações do aplicativo |
|
|
||||||
|
|
||||||
## 4. Definições de API
|
|
||||||
|
|
||||||
### 4.1 APIs Principais
|
|
||||||
|
|
||||||
**Autenticação de Usuário**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /auth/v1/token
|
|
||||||
```
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
| Nome do Parâmetro | Tipo | Obrigatório | Descrição |
|
|
||||||
| ----------------- | ------ | ----------- | ---------------- |
|
|
||||||
| email | string | true | Email do usuário |
|
|
||||||
| password | string | true | Senha do usuário |
|
|
||||||
|
|
||||||
Response:
|
|
||||||
|
|
||||||
| Nome do Parâmetro | Tipo | Descrição |
|
|
||||||
| ----------------- | ------ | ---------------------------- |
|
|
||||||
| access\_token | string | Token JWT para autenticação |
|
|
||||||
| user | object | Dados do usuário autenticado |
|
|
||||||
|
|
||||||
**Criar RDO**
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /rest/v1/rdos
|
|
||||||
```
|
|
||||||
|
|
||||||
Request:
|
|
||||||
|
|
||||||
| Nome do Parâmetro | Tipo | Obrigatório | Descrição |
|
|
||||||
| --------------------- | ------ | ----------- | ------------------------------ |
|
|
||||||
| obra\_id | uuid | true | ID da obra |
|
|
||||||
| data\_relatorio | date | true | Data do relatório |
|
|
||||||
| condicoes\_climaticas | string | true | Condições do tempo |
|
|
||||||
| atividades | array | true | Lista de atividades executadas |
|
|
||||||
| mao\_de\_obra | array | false | Funcionários presentes |
|
|
||||||
| equipamentos | array | false | Equipamentos utilizados |
|
|
||||||
| ocorrencias | array | false | Ocorrências registradas |
|
|
||||||
|
|
||||||
Response:
|
|
||||||
|
|
||||||
| Nome do Parâmetro | Tipo | Descrição |
|
|
||||||
| ----------------- | --------- | ------------------- |
|
|
||||||
| id | uuid | ID do RDO criado |
|
|
||||||
| status | string | Status do relatório |
|
|
||||||
| created\_at | timestamp | Data de criação |
|
|
||||||
|
|
||||||
Exemplo:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"obra_id": "123e4567-e89b-12d3-a456-426614174000",
|
|
||||||
"data_relatorio": "2024-01-15",
|
|
||||||
"condicoes_climaticas": "Ensolarado",
|
|
||||||
"atividades": [
|
|
||||||
{
|
|
||||||
"tipo": "Concretagem",
|
|
||||||
"descricao": "Concretagem da laje do 2º pavimento",
|
|
||||||
"percentual_concluido": 75
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Listar Obras**
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /rest/v1/obras
|
|
||||||
```
|
|
||||||
|
|
||||||
Response:
|
|
||||||
|
|
||||||
| Nome do Parâmetro | Tipo | Descrição |
|
|
||||||
| ----------------- | ------ | ---------------------------------------- |
|
|
||||||
| id | uuid | ID da obra |
|
|
||||||
| nome | string | Nome da obra |
|
|
||||||
| endereco | string | Endereço da obra |
|
|
||||||
| status | string | Status atual (ativa, pausada, concluída) |
|
|
||||||
| progresso | number | Percentual de conclusão |
|
|
||||||
|
|
||||||
## 5. Arquitetura do Servidor
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[Cliente Mobile] --> B[Supabase Edge Functions]
|
|
||||||
B --> C[Supabase Auth]
|
|
||||||
B --> D[PostgreSQL Database]
|
|
||||||
B --> E[Supabase Storage]
|
|
||||||
|
|
||||||
subgraph "Supabase Backend"
|
|
||||||
C
|
|
||||||
D
|
|
||||||
E
|
|
||||||
F[Real-time Engine]
|
|
||||||
G[Row Level Security]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Edge Functions"
|
|
||||||
H[Geração de Relatórios PDF]
|
|
||||||
I[Processamento de Imagens]
|
|
||||||
J[Notificações Push]
|
|
||||||
end
|
|
||||||
|
|
||||||
B --> H
|
|
||||||
B --> I
|
|
||||||
B --> J
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Modelo de Dados
|
|
||||||
|
|
||||||
### 6.1 Definição do Modelo de Dados
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
erDiagram
|
|
||||||
USUARIOS ||--o{ OBRAS : gerencia
|
|
||||||
USUARIOS ||--o{ RDOS : cria
|
|
||||||
OBRAS ||--o{ RDOS : possui
|
|
||||||
OBRAS ||--o{ TAREFAS : contem
|
|
||||||
RDOS ||--o{ RDO_ATIVIDADES : possui
|
|
||||||
RDOS ||--o{ RDO_MAO_OBRA : registra
|
|
||||||
RDOS ||--o{ RDO_EQUIPAMENTOS : utiliza
|
|
||||||
RDOS ||--o{ RDO_OCORRENCIAS : reporta
|
|
||||||
RDOS ||--o{ RDO_ANEXOS : contem
|
|
||||||
RDOS ||--o{ RDO_INSPECOES_SOLDA : possui
|
|
||||||
RDOS ||--o{ RDO_VERIFICACOES_TORQUE : possui
|
|
||||||
TAREFAS ||--o{ TASK_LOGS : possui
|
|
||||||
USUARIOS ||--o{ TASK_LOGS : executa
|
|
||||||
|
|
||||||
USUARIOS {
|
|
||||||
uuid id PK
|
|
||||||
string email UK
|
|
||||||
string nome
|
|
||||||
string telefone
|
|
||||||
string cargo
|
|
||||||
string role
|
|
||||||
boolean ativo
|
|
||||||
timestamp created_at
|
|
||||||
timestamp updated_at
|
|
||||||
}
|
|
||||||
|
|
||||||
OBRAS {
|
|
||||||
uuid id PK
|
|
||||||
string nome
|
|
||||||
string descricao
|
|
||||||
string endereco
|
|
||||||
string cep
|
|
||||||
string cidade
|
|
||||||
string estado
|
|
||||||
uuid responsavel_id FK
|
|
||||||
date data_inicio
|
|
||||||
date data_prevista_fim
|
|
||||||
decimal progresso_geral
|
|
||||||
string status
|
|
||||||
jsonb configuracoes
|
|
||||||
timestamp created_at
|
|
||||||
timestamp updated_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDOS {
|
|
||||||
uuid id PK
|
|
||||||
uuid obra_id FK
|
|
||||||
uuid criado_por FK
|
|
||||||
date data_relatorio
|
|
||||||
string condicoes_climaticas
|
|
||||||
text observacoes_gerais
|
|
||||||
string status
|
|
||||||
uuid aprovado_por FK
|
|
||||||
timestamp aprovado_em
|
|
||||||
timestamp created_at
|
|
||||||
timestamp updated_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDO_ATIVIDADES {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string tipo_atividade
|
|
||||||
text descricao
|
|
||||||
string localizacao
|
|
||||||
decimal percentual_concluido
|
|
||||||
integer ordem
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDO_MAO_OBRA {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string funcao
|
|
||||||
integer quantidade
|
|
||||||
decimal horas_trabalhadas
|
|
||||||
text observacoes
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDO_EQUIPAMENTOS {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string nome_equipamento
|
|
||||||
string tipo
|
|
||||||
decimal horas_utilizadas
|
|
||||||
decimal combustivel_gasto
|
|
||||||
text observacoes
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDO_OCORRENCIAS {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string tipo_ocorrencia
|
|
||||||
text descricao
|
|
||||||
string gravidade
|
|
||||||
text acao_tomada
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDO_ANEXOS {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string nome_arquivo
|
|
||||||
string tipo_arquivo
|
|
||||||
string url_storage
|
|
||||||
integer tamanho_bytes
|
|
||||||
text descricao
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDO_INSPECOES_SOLDA {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string identificacao_junta
|
|
||||||
string status_inspecao
|
|
||||||
string metodo_inspecao
|
|
||||||
text observacoes
|
|
||||||
uuid inspecionado_por FK
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDO_VERIFICACOES_TORQUE {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string identificacao_parafuso
|
|
||||||
decimal torque_especificado
|
|
||||||
decimal torque_aplicado
|
|
||||||
string status_verificacao
|
|
||||||
text observacoes
|
|
||||||
uuid verificado_por FK
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
TAREFAS {
|
|
||||||
uuid id PK
|
|
||||||
uuid obra_id FK
|
|
||||||
string titulo
|
|
||||||
text descricao
|
|
||||||
string status
|
|
||||||
string prioridade
|
|
||||||
uuid responsavel_id FK
|
|
||||||
date data_inicio
|
|
||||||
date data_fim
|
|
||||||
decimal progresso
|
|
||||||
jsonb metadados
|
|
||||||
timestamp created_at
|
|
||||||
timestamp updated_at
|
|
||||||
}
|
|
||||||
|
|
||||||
TASK_LOGS {
|
|
||||||
uuid id PK
|
|
||||||
uuid task_id FK
|
|
||||||
uuid usuario_id FK
|
|
||||||
string tipo_evento
|
|
||||||
text descricao
|
|
||||||
jsonb detalhes
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
RDOS ||--o{ OCORRENCIAS : possui
|
|
||||||
RDOS ||--o{ ANEXOS : contem
|
|
||||||
OBRAS ||--o{ TAREFAS : possui
|
|
||||||
EQUIPAMENTOS ||--o{ EQUIPAMENTOS_UTILIZADOS : referencia
|
|
||||||
|
|
||||||
USUARIOS {
|
|
||||||
uuid id PK
|
|
||||||
string email
|
|
||||||
string nome
|
|
||||||
string funcao
|
|
||||||
string telefone
|
|
||||||
timestamp created_at
|
|
||||||
timestamp updated_at
|
|
||||||
}
|
|
||||||
|
|
||||||
OBRAS {
|
|
||||||
uuid id PK
|
|
||||||
string nome
|
|
||||||
string endereco
|
|
||||||
string descricao
|
|
||||||
date data_inicio
|
|
||||||
date data_prevista_fim
|
|
||||||
string status
|
|
||||||
decimal progresso_percentual
|
|
||||||
uuid responsavel_id FK
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
RDOS {
|
|
||||||
uuid id PK
|
|
||||||
uuid obra_id FK
|
|
||||||
uuid criado_por FK
|
|
||||||
date data_relatorio
|
|
||||||
string condicoes_climaticas
|
|
||||||
text observacoes_gerais
|
|
||||||
string status
|
|
||||||
timestamp created_at
|
|
||||||
}
|
|
||||||
|
|
||||||
ATIVIDADES {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string tipo_atividade
|
|
||||||
text descricao
|
|
||||||
string localizacao
|
|
||||||
decimal percentual_concluido
|
|
||||||
time hora_inicio
|
|
||||||
time hora_fim
|
|
||||||
}
|
|
||||||
|
|
||||||
MAO_DE_OBRA {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string nome_funcionario
|
|
||||||
string funcao
|
|
||||||
decimal horas_trabalhadas
|
|
||||||
boolean presente
|
|
||||||
}
|
|
||||||
|
|
||||||
EQUIPAMENTOS {
|
|
||||||
uuid id PK
|
|
||||||
string nome
|
|
||||||
string tipo
|
|
||||||
string modelo
|
|
||||||
string status
|
|
||||||
text observacoes
|
|
||||||
}
|
|
||||||
|
|
||||||
EQUIPAMENTOS_UTILIZADOS {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
uuid equipamento_id FK
|
|
||||||
decimal horas_operacao
|
|
||||||
decimal combustivel_consumido
|
|
||||||
text observacoes
|
|
||||||
}
|
|
||||||
|
|
||||||
OCORRENCIAS {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string tipo
|
|
||||||
text descricao
|
|
||||||
string gravidade
|
|
||||||
boolean resolvida
|
|
||||||
}
|
|
||||||
|
|
||||||
ANEXOS {
|
|
||||||
uuid id PK
|
|
||||||
uuid rdo_id FK
|
|
||||||
string tipo
|
|
||||||
string url_arquivo
|
|
||||||
string nome_arquivo
|
|
||||||
text descricao
|
|
||||||
}
|
|
||||||
|
|
||||||
TAREFAS {
|
|
||||||
uuid id PK
|
|
||||||
uuid obra_id FK
|
|
||||||
string titulo
|
|
||||||
text descricao
|
|
||||||
date data_prevista
|
|
||||||
string status
|
|
||||||
string prioridade
|
|
||||||
uuid responsavel_id FK
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 Linguagem de Definição de Dados (DDL)
|
|
||||||
|
|
||||||
**Tabela de Usuários (usuarios)**
|
|
||||||
```sql
|
|
||||||
-- Criar tabela
|
|
||||||
CREATE TABLE usuarios (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
nome VARCHAR(100) NOT NULL,
|
|
||||||
telefone VARCHAR(20),
|
|
||||||
cargo VARCHAR(100),
|
|
||||||
role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('admin', 'supervisor', 'user')),
|
|
||||||
ativo BOOLEAN DEFAULT true,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Criar índices
|
|
||||||
CREATE INDEX idx_usuarios_email ON usuarios(email);
|
|
||||||
CREATE INDEX idx_usuarios_role ON usuarios(role);
|
|
||||||
CREATE INDEX idx_usuarios_ativo ON usuarios(ativo);
|
|
||||||
|
|
||||||
-- Dados iniciais
|
|
||||||
INSERT INTO usuarios (email, nome, cargo, role) VALUES
|
|
||||||
('admin@rdo.com', 'Administrador', 'Gerente de Projeto', 'admin'),
|
|
||||||
('supervisor@rdo.com', 'Supervisor', 'Supervisor de Obra', 'supervisor'),
|
|
||||||
('user@rdo.com', 'Usuário Padrão', 'Técnico', 'user');
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Obras (obras)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE obras (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
nome VARCHAR(255) NOT NULL,
|
|
||||||
descricao TEXT,
|
|
||||||
endereco TEXT,
|
|
||||||
cep VARCHAR(10),
|
|
||||||
cidade VARCHAR(100),
|
|
||||||
estado VARCHAR(2),
|
|
||||||
responsavel_id UUID REFERENCES usuarios(id),
|
|
||||||
data_inicio DATE,
|
|
||||||
data_prevista_fim DATE,
|
|
||||||
progresso_geral DECIMAL(5,2) DEFAULT 0.00,
|
|
||||||
status VARCHAR(20) DEFAULT 'planejamento' CHECK (status IN ('planejamento', 'em_andamento', 'pausada', 'concluida', 'cancelada')),
|
|
||||||
configuracoes JSONB DEFAULT '{}',
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_obras_responsavel ON obras(responsavel_id);
|
|
||||||
CREATE INDEX idx_obras_status ON obras(status);
|
|
||||||
CREATE INDEX idx_obras_data_inicio ON obras(data_inicio);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de RDOs (rdos)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdos (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
obra_id UUID NOT NULL REFERENCES obras(id),
|
|
||||||
criado_por UUID NOT NULL REFERENCES usuarios(id),
|
|
||||||
data_relatorio DATE NOT NULL,
|
|
||||||
condicoes_climaticas VARCHAR(100),
|
|
||||||
observacoes_gerais TEXT,
|
|
||||||
status VARCHAR(20) DEFAULT 'rascunho' CHECK (status IN ('rascunho', 'enviado', 'aprovado', 'rejeitado')),
|
|
||||||
aprovado_por UUID REFERENCES usuarios(id),
|
|
||||||
aprovado_em TIMESTAMP WITH TIME ZONE,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdos_obra ON rdos(obra_id);
|
|
||||||
CREATE INDEX idx_rdos_criado_por ON rdos(criado_por);
|
|
||||||
CREATE INDEX idx_rdos_data_relatorio ON rdos(data_relatorio);
|
|
||||||
CREATE INDEX idx_rdos_status ON rdos(status);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Atividades do RDO (rdo_atividades)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdo_atividades (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
|
|
||||||
tipo_atividade VARCHAR(100) NOT NULL,
|
|
||||||
descricao TEXT,
|
|
||||||
localizacao VARCHAR(255),
|
|
||||||
percentual_concluido DECIMAL(5,2) DEFAULT 0.00,
|
|
||||||
ordem INTEGER DEFAULT 0,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdo_atividades_rdo ON rdo_atividades(rdo_id);
|
|
||||||
CREATE INDEX idx_rdo_atividades_tipo ON rdo_atividades(tipo_atividade);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Mão de Obra (rdo_mao_obra)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdo_mao_obra (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
|
|
||||||
funcao VARCHAR(100) NOT NULL,
|
|
||||||
quantidade INTEGER NOT NULL DEFAULT 1,
|
|
||||||
horas_trabalhadas DECIMAL(5,2) NOT NULL DEFAULT 0.00,
|
|
||||||
observacoes TEXT,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdo_mao_obra_rdo ON rdo_mao_obra(rdo_id);
|
|
||||||
CREATE INDEX idx_rdo_mao_obra_funcao ON rdo_mao_obra(funcao);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Equipamentos (rdo_equipamentos)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdo_equipamentos (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
|
|
||||||
nome_equipamento VARCHAR(255) NOT NULL,
|
|
||||||
tipo VARCHAR(100),
|
|
||||||
horas_utilizadas DECIMAL(5,2) DEFAULT 0.00,
|
|
||||||
combustivel_gasto DECIMAL(8,2) DEFAULT 0.00,
|
|
||||||
observacoes TEXT,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdo_equipamentos_rdo ON rdo_equipamentos(rdo_id);
|
|
||||||
CREATE INDEX idx_rdo_equipamentos_tipo ON rdo_equipamentos(tipo);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Ocorrências (rdo_ocorrencias)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdo_ocorrencias (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
|
|
||||||
tipo_ocorrencia VARCHAR(100) NOT NULL,
|
|
||||||
descricao TEXT NOT NULL,
|
|
||||||
gravidade VARCHAR(20) DEFAULT 'baixa' CHECK (gravidade IN ('baixa', 'media', 'alta', 'critica')),
|
|
||||||
acao_tomada TEXT,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdo_ocorrencias_rdo ON rdo_ocorrencias(rdo_id);
|
|
||||||
CREATE INDEX idx_rdo_ocorrencias_tipo ON rdo_ocorrencias(tipo_ocorrencia);
|
|
||||||
CREATE INDEX idx_rdo_ocorrencias_gravidade ON rdo_ocorrencias(gravidade);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Anexos (rdo_anexos)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdo_anexos (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
|
|
||||||
nome_arquivo VARCHAR(255) NOT NULL,
|
|
||||||
tipo_arquivo VARCHAR(50),
|
|
||||||
url_storage TEXT NOT NULL,
|
|
||||||
tamanho_bytes INTEGER,
|
|
||||||
descricao TEXT,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdo_anexos_rdo ON rdo_anexos(rdo_id);
|
|
||||||
CREATE INDEX idx_rdo_anexos_tipo ON rdo_anexos(tipo_arquivo);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Inspeções de Solda (rdo_inspecoes_solda)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdo_inspecoes_solda (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
|
|
||||||
identificacao_junta VARCHAR(100) NOT NULL,
|
|
||||||
status_inspecao VARCHAR(20) DEFAULT 'pendente' CHECK (status_inspecao IN ('pendente', 'aprovada', 'rejeitada', 'retrabalho')),
|
|
||||||
metodo_inspecao VARCHAR(100),
|
|
||||||
observacoes TEXT,
|
|
||||||
inspecionado_por UUID REFERENCES usuarios(id),
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdo_inspecoes_solda_rdo ON rdo_inspecoes_solda(rdo_id);
|
|
||||||
CREATE INDEX idx_rdo_inspecoes_solda_status ON rdo_inspecoes_solda(status_inspecao);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Verificações de Torque (rdo_verificacoes_torque)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE rdo_verificacoes_torque (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
rdo_id UUID NOT NULL REFERENCES rdos(id) ON DELETE CASCADE,
|
|
||||||
identificacao_parafuso VARCHAR(100) NOT NULL,
|
|
||||||
torque_especificado DECIMAL(8,2) NOT NULL,
|
|
||||||
torque_aplicado DECIMAL(8,2) NOT NULL,
|
|
||||||
status_verificacao VARCHAR(20) DEFAULT 'conforme' CHECK (status_verificacao IN ('conforme', 'nao_conforme', 'retrabalho')),
|
|
||||||
observacoes TEXT,
|
|
||||||
verificado_por UUID REFERENCES usuarios(id),
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_rdo_verificacoes_torque_rdo ON rdo_verificacoes_torque(rdo_id);
|
|
||||||
CREATE INDEX idx_rdo_verificacoes_torque_status ON rdo_verificacoes_torque(status_verificacao);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Tarefas (tarefas)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE tarefas (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
obra_id UUID NOT NULL REFERENCES obras(id),
|
|
||||||
titulo VARCHAR(255) NOT NULL,
|
|
||||||
descricao TEXT,
|
|
||||||
status VARCHAR(20) DEFAULT 'pendente' CHECK (status IN ('pendente', 'em_andamento', 'concluida', 'cancelada')),
|
|
||||||
prioridade VARCHAR(20) DEFAULT 'media' CHECK (prioridade IN ('baixa', 'media', 'alta', 'urgente')),
|
|
||||||
responsavel_id UUID REFERENCES usuarios(id),
|
|
||||||
data_inicio DATE,
|
|
||||||
data_fim DATE,
|
|
||||||
progresso DECIMAL(5,2) DEFAULT 0.00,
|
|
||||||
metadados JSONB DEFAULT '{}',
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_tarefas_obra ON tarefas(obra_id);
|
|
||||||
CREATE INDEX idx_tarefas_responsavel ON tarefas(responsavel_id);
|
|
||||||
CREATE INDEX idx_tarefas_status ON tarefas(status);
|
|
||||||
CREATE INDEX idx_tarefas_prioridade ON tarefas(prioridade);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Logs de Tarefas (task_logs)**
|
|
||||||
```sql
|
|
||||||
CREATE TABLE task_logs (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
task_id UUID NOT NULL REFERENCES tarefas(id) ON DELETE CASCADE,
|
|
||||||
usuario_id UUID NOT NULL REFERENCES usuarios(id),
|
|
||||||
tipo_evento VARCHAR(50) NOT NULL,
|
|
||||||
descricao TEXT,
|
|
||||||
detalhes JSONB DEFAULT '{}',
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_task_logs_task ON task_logs(task_id);
|
|
||||||
CREATE INDEX idx_task_logs_usuario ON task_logs(usuario_id);
|
|
||||||
CREATE INDEX idx_task_logs_tipo ON task_logs(tipo_evento);
|
|
||||||
CREATE INDEX idx_task_logs_created_at ON task_logs(created_at DESC);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de Obras**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Criar tabela de obras
|
|
||||||
CREATE TABLE obras (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
nome VARCHAR(200) NOT NULL,
|
|
||||||
endereco TEXT NOT NULL,
|
|
||||||
descricao TEXT,
|
|
||||||
data_inicio DATE NOT NULL,
|
|
||||||
data_prevista_fim DATE,
|
|
||||||
status VARCHAR(20) DEFAULT 'ativa' CHECK (status IN ('ativa', 'pausada', 'concluida')),
|
|
||||||
progresso_percentual DECIMAL(5,2) DEFAULT 0.00,
|
|
||||||
responsavel_id UUID REFERENCES usuarios(id),
|
|
||||||
orcamento_total DECIMAL(15,2),
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Criar índices
|
|
||||||
CREATE INDEX idx_obras_status ON obras(status);
|
|
||||||
CREATE INDEX idx_obras_responsavel ON obras(responsavel_id);
|
|
||||||
CREATE INDEX idx_obras_data_inicio ON obras(data_inicio DESC);
|
|
||||||
|
|
||||||
-- Políticas RLS
|
|
||||||
ALTER TABLE obras ENABLE ROW LEVEL SECURITY;
|
|
||||||
|
|
||||||
CREATE POLICY "Usuários podem ver obras que participam" ON obras
|
|
||||||
FOR SELECT USING (
|
|
||||||
responsavel_id = auth.uid() OR
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1 FROM usuarios
|
|
||||||
WHERE id = auth.uid() AND funcao IN ('Gestor', 'Engenheiro')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Permissões
|
|
||||||
GRANT SELECT ON obras TO anon;
|
|
||||||
GRANT ALL PRIVILEGES ON obras TO authenticated;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tabela de RDOs**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Criar tabela de RDOs
|
|
||||||
CREATE TABLE rdos (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
obra_id UUID NOT NULL REFERENCES obras(id) ON DELETE CASCADE,
|
|
||||||
criado_por UUID NOT NULL REFERENCES usuarios(id),
|
|
||||||
data_relatorio DATE NOT NULL,
|
|
||||||
condicoes_climaticas VARCHAR(50) NOT NULL,
|
|
||||||
temperatura_min DECIMAL(4,1),
|
|
||||||
temperatura_max DECIMAL(4,1),
|
|
||||||
observacoes_gerais TEXT,
|
|
||||||
status VARCHAR(20) DEFAULT 'rascunho' CHECK (status IN ('rascunho', 'enviado', 'aprovado')),
|
|
||||||
aprovado_por UUID REFERENCES usuarios(id),
|
|
||||||
aprovado_em TIMESTAMP WITH TIME ZONE,
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Criar índices
|
|
||||||
CREATE INDEX idx_rdos_obra_id ON rdos(obra_id);
|
|
||||||
CREATE INDEX idx_rdos_data_relatorio ON rdos(data_relatorio DESC);
|
|
||||||
CREATE INDEX idx_rdos_status ON rdos(status);
|
|
||||||
CREATE INDEX idx_rdos_criado_por ON rdos(criado_por);
|
|
||||||
|
|
||||||
-- Constraint única para evitar múltiplos RDOs na mesma data/obra
|
|
||||||
CREATE UNIQUE INDEX idx_rdos_obra_data_unique ON rdos(obra_id, data_relatorio);
|
|
||||||
|
|
||||||
-- Políticas RLS
|
|
||||||
ALTER TABLE rdos ENABLE ROW LEVEL SECURITY;
|
|
||||||
|
|
||||||
CREATE POLICY "Usuários podem ver RDOs das suas obras" ON rdos
|
|
||||||
FOR SELECT USING (
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1 FROM obras
|
|
||||||
WHERE id = obra_id AND (
|
|
||||||
responsavel_id = auth.uid() OR
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1 FROM usuarios
|
|
||||||
WHERE usuarios.id = auth.uid() AND funcao IN ('Gestor', 'Engenheiro')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Permissões
|
|
||||||
GRANT SELECT ON rdos TO anon;
|
|
||||||
GRANT ALL PRIVILEGES ON rdos TO authenticated;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Dados Iniciais**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Inserir tipos de atividades padrão
|
|
||||||
INSERT INTO tipos_atividades (nome, categoria) VALUES
|
|
||||||
('Escavação', 'Terraplanagem'),
|
|
||||||
('Fundação', 'Estrutura'),
|
|
||||||
('Concretagem', 'Estrutura'),
|
|
||||||
('Alvenaria', 'Vedação'),
|
|
||||||
('Instalação Elétrica', 'Instalações'),
|
|
||||||
('Instalação Hidráulica', 'Instalações'),
|
|
||||||
('Revestimento', 'Acabamento'),
|
|
||||||
('Pintura', 'Acabamento');
|
|
||||||
|
|
||||||
-- Inserir condições climáticas padrão
|
|
||||||
INSERT INTO condicoes_climaticas (descricao) VALUES
|
|
||||||
('Ensolarado'),
|
|
||||||
('Parcialmente Nublado'),
|
|
||||||
('Nublado'),
|
|
||||||
('Chuvisco'),
|
|
||||||
('Chuva Leve'),
|
|
||||||
('Chuva Forte'),
|
|
||||||
('Tempestade');
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
# Documento de Requisitos do Produto - App RDO Mobile
|
|
||||||
|
|
||||||
## 1. Visão Geral do Produto
|
|
||||||
|
|
||||||
O **RDO Mobile** é um aplicativo mobile-first para registro e acompanhamento diário de atividades em obras de construção civil. O app facilita o processo de criação de Relatórios Diários de Obra (RDO), permitindo que engenheiros, mestres de obra e gestores registrem atividades, mão de obra, equipamentos, condições climáticas e ocorrências de forma prática e organizada.
|
|
||||||
|
|
||||||
O produto visa substituir os relatórios em papel ou Excel por uma solução digital moderna, melhorando a comunicação entre canteiro de obra, escritório e clientes, proporcionando acompanhamento em tempo real do progresso das obras.
|
|
||||||
|
|
||||||
## 2. Funcionalidades Principais
|
|
||||||
|
|
||||||
### 2.1 Papéis de Usuário
|
|
||||||
|
|
||||||
| Papel | Método de Registro | Permissões Principais |
|
|
||||||
|-------|-------------------|----------------------|
|
|
||||||
| Mestre de Obra | Cadastro por convite | Criar e editar RDOs, registrar atividades, gerenciar mão de obra |
|
|
||||||
| Engenheiro | Cadastro por convite | Todas as permissões + aprovar RDOs, gerar relatórios |
|
|
||||||
| Gestor | Cadastro administrativo | Visualizar todas as obras, relatórios consolidados, gerenciar usuários |
|
|
||||||
| Cliente | Acesso por convite | Visualizar progresso da obra, relatórios aprovados |
|
|
||||||
|
|
||||||
### 2.2 Módulos Funcionais
|
|
||||||
|
|
||||||
Nosso aplicativo RDO Mobile consiste nas seguintes páginas principais:
|
|
||||||
|
|
||||||
1. **Dashboard Principal**: visão geral das obras, indicadores de progresso, últimas atividades
|
|
||||||
2. **Detalhes da Obra**: informações específicas da obra, histórico de RDOs, galeria de fotos
|
|
||||||
3. **Criar RDO**: formulário completo para registro diário de atividades
|
|
||||||
4. **Lista de Tarefas**: gerenciamento de atividades planejadas e executadas
|
|
||||||
5. **Cadastros**: formulários para obras, usuários, equipamentos e tipos de atividades
|
|
||||||
6. **Relatórios**: visualização e exportação de relatórios consolidados
|
|
||||||
|
|
||||||
### 2.3 Detalhes das Páginas
|
|
||||||
|
|
||||||
| Nome da Página | Nome do Módulo | Descrição da Funcionalidade |
|
|
||||||
|----------------|----------------|-----------------------------|
|
|
||||||
| Dashboard Principal | Visão Geral | Exibir cards das obras ativas, indicadores de progresso, últimos RDOs criados, notificações importantes |
|
|
||||||
| Dashboard Principal | Navegação Rápida | Acesso rápido para criar novo RDO, visualizar obras, acessar cadastros |
|
|
||||||
| Detalhes da Obra | Informações da Obra | Mostrar dados da obra, cronograma, responsáveis, localização |
|
|
||||||
| Detalhes da Obra | Histórico RDO | Listar RDOs anteriores com filtros por data, status, responsável |
|
|
||||||
| Detalhes da Obra | Galeria de Fotos | Visualizar fotos organizadas por data, com zoom e compartilhamento |
|
|
||||||
| Criar RDO | Informações Básicas | Campos para data, obra, responsável, condições climáticas |
|
|
||||||
| Criar RDO | Atividades Executadas | Adicionar atividades com descrição, localização, percentual concluído |
|
|
||||||
| Criar RDO | Mão de Obra | Registrar funcionários presentes, horas trabalhadas, função |
|
|
||||||
| Criar RDO | Equipamentos | Listar equipamentos utilizados, horas de operação, combustível |
|
|
||||||
| Criar RDO | Ocorrências | Registrar problemas, acidentes, atrasos com descrição detalhada |
|
|
||||||
| Criar RDO | Anexos | Adicionar fotos, documentos, assinaturas digitais |
|
|
||||||
| Lista de Tarefas | Tarefas Planejadas | Visualizar atividades programadas para o dia/semana |
|
|
||||||
| Lista de Tarefas | Controle de Execução | Marcar tarefas como iniciadas, em andamento, concluídas |
|
|
||||||
| Lista de Tarefas | Progresso Visual | Mostrar percentual de conclusão com barras de progresso |
|
|
||||||
| Cadastros | Cadastro de Obras | Formulário com dados da obra, endereço, responsáveis, cronograma |
|
|
||||||
| Cadastros | Cadastro de Usuários | Registrar funcionários com foto, função, permissões |
|
|
||||||
| Cadastros | Cadastro de Equipamentos | Listar equipamentos com especificações, manutenção, disponibilidade |
|
|
||||||
| Cadastros | Tipos de Atividades | Definir categorias de atividades padrão para seleção rápida |
|
|
||||||
| Relatórios | Relatórios Consolidados | Gerar relatórios por período, obra, tipo de atividade |
|
|
||||||
| Relatórios | Exportação | Exportar relatórios em PDF, Excel, compartilhar por email/WhatsApp |
|
|
||||||
|
|
||||||
## 3. Fluxo Principal
|
|
||||||
|
|
||||||
**Fluxo do Mestre de Obra:**
|
|
||||||
1. Acessa o dashboard e visualiza as obras sob sua responsabilidade
|
|
||||||
2. Seleciona uma obra específica para trabalhar
|
|
||||||
3. Cria um novo RDO diário preenchendo todas as seções obrigatórias
|
|
||||||
4. Adiciona fotos e registra ocorrências se necessário
|
|
||||||
5. Submete o RDO para aprovação do engenheiro
|
|
||||||
6. Acompanha o status das tarefas planejadas
|
|
||||||
|
|
||||||
**Fluxo do Engenheiro:**
|
|
||||||
1. Revisa RDOs pendentes de aprovação
|
|
||||||
2. Analisa o progresso geral das obras no dashboard
|
|
||||||
3. Gera relatórios consolidados para apresentação
|
|
||||||
4. Gerencia cadastros de equipamentos e atividades
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[Dashboard Principal] --> B[Selecionar Obra]
|
|
||||||
B --> C[Detalhes da Obra]
|
|
||||||
C --> D[Criar Novo RDO]
|
|
||||||
C --> E[Lista de Tarefas]
|
|
||||||
C --> F[Histórico RDOs]
|
|
||||||
D --> G[Preencher Atividades]
|
|
||||||
G --> H[Registrar Mão de Obra]
|
|
||||||
H --> I[Adicionar Equipamentos]
|
|
||||||
I --> J[Registrar Ocorrências]
|
|
||||||
J --> K[Anexar Fotos]
|
|
||||||
K --> L[Submeter RDO]
|
|
||||||
A --> M[Cadastros]
|
|
||||||
M --> N[Obras]
|
|
||||||
M --> O[Usuários]
|
|
||||||
M --> P[Equipamentos]
|
|
||||||
A --> Q[Relatórios]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Design da Interface do Usuário
|
|
||||||
|
|
||||||
### 4.1 Estilo de Design
|
|
||||||
|
|
||||||
- **Cores Primárias**: Azul #2563EB (confiança, profissionalismo), Laranja #F97316 (energia, ação)
|
|
||||||
- **Cores Secundárias**: Cinza #64748B (neutro), Verde #10B981 (sucesso), Vermelho #EF4444 (alertas)
|
|
||||||
- **Estilo dos Botões**: Neumorphism com bordas arredondadas (12px), sombras sutis, efeitos de pressão
|
|
||||||
- **Tipografia**: Inter (títulos 18-24px), Open Sans (corpo 14-16px), peso regular e semi-bold
|
|
||||||
- **Layout**: Cards com Glassmorphism, navegação bottom tab, espaçamentos de 16px/24px
|
|
||||||
- **Ícones**: Phosphor Icons com estilo outline, tamanho 24px para ações principais
|
|
||||||
|
|
||||||
### 4.2 Visão Geral do Design das Páginas
|
|
||||||
|
|
||||||
| Nome da Página | Nome do Módulo | Elementos da UI |
|
|
||||||
|----------------|----------------|----------------|
|
|
||||||
| Dashboard Principal | Cards de Obras | Cards com glassmorphism, gradiente sutil azul-roxo, sombra 0 4px 20px rgba(0,0,0,0.1), bordas arredondadas 16px |
|
|
||||||
| Dashboard Principal | Indicadores | Gráficos circulares com animação, cores verde/amarelo/vermelho para status, fonte Inter 14px |
|
|
||||||
| Dashboard Principal | Navegação | Bottom navigation com 5 tabs, ícones Phosphor, background blur, altura 80px |
|
|
||||||
| Criar RDO | Formulário | Floating labels com animação, campos com border-radius 12px, validação em tempo real com cores |
|
|
||||||
| Criar RDO | Seções Expansíveis | Accordion com ícones de seta, transição suave 300ms, background rgba(255,255,255,0.1) |
|
|
||||||
| Lista de Tarefas | Cards de Tarefa | Swipe actions (verde para concluir, vermelho para excluir), checkbox animado, progress bar |
|
|
||||||
| Lista de Tarefas | Filtros | Chips com seleção múltipla, cores Material Design 3, espaçamento 8px |
|
|
||||||
| Cadastros | Formulários | Stepper horizontal, campos agrupados em cards, botões flutuantes para ações |
|
|
||||||
| Relatórios | Gráficos | Charts.js com tema dark/light, cores consistentes, animações de entrada |
|
|
||||||
|
|
||||||
### 4.3 Responsividade
|
|
||||||
|
|
||||||
**Mobile-first** com adaptação para tablets. Layouts flexíveis usando CSS Grid e Flexbox, tamanhos em rem (base 16px), espaçamentos escaláveis (16px/24px/32px). Safe areas para dispositivos com notch/dynamic island. Suporte a orientação portrait e landscape para tablets.
|
|
||||||
|
|
||||||
**Gestos de Navegação:**
|
|
||||||
- Swipe para direita: voltar à tela anterior
|
|
||||||
- Swipe para baixo: pull-to-refresh nas listas
|
|
||||||
- Swipe horizontal em cards: ações rápidas (editar/excluir)
|
|
||||||
- Long press: menu contextual
|
|
||||||
- Pinch to zoom: galeria de fotos
|
|
||||||
|
|
||||||
**Dark Mode Automático:**
|
|
||||||
Detecção da preferência do sistema com toggle manual. Paleta adaptada: backgrounds #1F2937, cards #374151, textos #F9FAFB, acentos mantidos com ajuste de saturação.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"projectId":"prj_yIejbjdWxs5ZZ5YWOEHm8UAqDS8k","orgId":"team_KY59uhtbyLyWDtIuz7Ew3uEk","projectName":"trae_42fqthkv","neverMindDeployCard":true}
|
|
||||||
@@ -1,7 +1,51 @@
|
|||||||
|
# Dependencies
|
||||||
node_modules
|
node_modules
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Production
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Capacitor
|
||||||
|
ios/
|
||||||
|
android/
|
||||||
|
|
||||||
|
# Scripts
|
||||||
|
scripts/
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
documentation/
|
||||||
|
.trae/
|
||||||
|
|
||||||
|
# Database scripts
|
||||||
|
database_scripts/
|
||||||
|
*.sql
|
||||||
|
|
||||||
|
# Git
|
||||||
.git
|
.git
|
||||||
.trae
|
.gitignore
|
||||||
.log
|
.gitattributes
|
||||||
.figma
|
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
# ✅ CHECKLIST NETLIFY - CONFIGURAÇÃO COMPLETA
|
|
||||||
|
|
||||||
## STATUS ATUAL
|
|
||||||
- ✅ SQL executado com sucesso (2 usuários no banco)
|
|
||||||
- ✅ Políticas RLS configuradas
|
|
||||||
- ⚠️ Login ainda não funciona no Netlify
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 CONFIGURAÇÕES NECESSÁRIAS
|
|
||||||
|
|
||||||
### 1️⃣ VARIÁVEIS DE AMBIENTE NO NETLIFY
|
|
||||||
|
|
||||||
**CRÍTICO:** O Netlify NÃO lê o arquivo `.env` local!
|
|
||||||
|
|
||||||
#### Como configurar:
|
|
||||||
|
|
||||||
1. Acesse: https://app.netlify.com
|
|
||||||
2. Clique no seu site
|
|
||||||
3. Vá em: **Site settings** → **Environment variables**
|
|
||||||
4. Clique em **Add a variable**
|
|
||||||
5. Adicione estas 2 variáveis:
|
|
||||||
|
|
||||||
```
|
|
||||||
Key: VITE_SUPABASE_URL
|
|
||||||
Value: https://xzudfhifaancyxxfdejx.supabase.co
|
|
||||||
|
|
||||||
Key: VITE_SUPABASE_ANON_KEY
|
|
||||||
Value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh6dWRmaGlmYWFuY3l4eGZkZWp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzNjE0MTAsImV4cCI6MjA4NjkzNzQxMH0.c5CHWhfXcMrm27LfxEt6OZtttXXvVJOeWu-IbnNLfWY
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **IMPORTANTE:** Após adicionar, clique em **Trigger deploy** para fazer um novo deploy
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ REDIRECT URLs NO SUPABASE
|
|
||||||
|
|
||||||
1. Acesse: https://supabase.com/dashboard/project/xzudfhifaancyxxfdejx/auth/url-configuration
|
|
||||||
|
|
||||||
2. Na seção **Redirect URLs**, adicione (substitua `SEU-SITE` pelo domínio real):
|
|
||||||
|
|
||||||
```
|
|
||||||
https://SEU-SITE.netlify.app/auth/callback
|
|
||||||
https://SEU-SITE.netlify.app/*
|
|
||||||
```
|
|
||||||
|
|
||||||
**Exemplo:**
|
|
||||||
```
|
|
||||||
https://rdo-tracksteel.netlify.app/auth/callback
|
|
||||||
https://rdo-tracksteel.netlify.app/*
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Na seção **Site URL**, configure:
|
|
||||||
```
|
|
||||||
https://SEU-SITE.netlify.app
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Clique em **Save**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3️⃣ GOOGLE CLOUD CONSOLE (OAuth)
|
|
||||||
|
|
||||||
1. Acesse: https://console.cloud.google.com/apis/credentials
|
|
||||||
|
|
||||||
2. Clique no seu **OAuth 2.0 Client ID**
|
|
||||||
|
|
||||||
3. Em **Authorized redirect URIs**, adicione:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://xzudfhifaancyxxfdejx.supabase.co/auth/v1/callback
|
|
||||||
https://SEU-SITE.netlify.app/auth/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Clique em **Save**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 COMO TESTAR
|
|
||||||
|
|
||||||
### Teste 1: Verificar Variáveis de Ambiente
|
|
||||||
|
|
||||||
Após fazer o deploy no Netlify:
|
|
||||||
|
|
||||||
1. Abra o site no Netlify
|
|
||||||
2. Pressione **F12** (Console do navegador)
|
|
||||||
3. Digite:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
console.log('URL:', import.meta.env.VITE_SUPABASE_URL)
|
|
||||||
console.log('Key:', import.meta.env.VITE_SUPABASE_ANON_KEY?.substring(0, 20) + '...')
|
|
||||||
```
|
|
||||||
|
|
||||||
**Resultado esperado:**
|
|
||||||
```
|
|
||||||
URL: https://xzudfhifaancyxxfdejx.supabase.co
|
|
||||||
Key: eyJhbGciOiJIUzI1NiIs...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Se retornar `undefined`** = Variáveis não configuradas no Netlify!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Teste 2: Verificar Login
|
|
||||||
|
|
||||||
1. Clique em "Login com Google"
|
|
||||||
2. Autorize o app
|
|
||||||
3. Deve redirecionar para `/auth/callback`
|
|
||||||
4. Deve fazer login com sucesso
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 DIAGNÓSTICO DE ERROS
|
|
||||||
|
|
||||||
### Erro: "Invalid redirect URL"
|
|
||||||
- ❌ Faltou adicionar URL no Supabase (passo 2)
|
|
||||||
- ❌ Faltou adicionar URI no Google Cloud (passo 3)
|
|
||||||
|
|
||||||
### Erro: "Environment variables not defined"
|
|
||||||
- ❌ Faltou configurar variáveis no Netlify (passo 1)
|
|
||||||
- ❌ Faltou fazer novo deploy após configurar
|
|
||||||
|
|
||||||
### Erro: 401 Unauthorized
|
|
||||||
- ✅ Já resolvido com o SQL que você executou!
|
|
||||||
|
|
||||||
### Login funciona mas volta para tela de login
|
|
||||||
- ❌ Variáveis de ambiente não configuradas
|
|
||||||
- ❌ Redirect URLs não configuradas
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 ORDEM DE EXECUÇÃO
|
|
||||||
|
|
||||||
1. ✅ **SQL executado** (você já fez!)
|
|
||||||
2. ⬜ **Configurar variáveis no Netlify** (passo 1)
|
|
||||||
3. ⬜ **Fazer novo deploy no Netlify**
|
|
||||||
4. ⬜ **Configurar Redirect URLs no Supabase** (passo 2)
|
|
||||||
5. ⬜ **Configurar OAuth no Google Cloud** (passo 3)
|
|
||||||
6. ⬜ **Testar login**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 QUAL É O SEU DOMÍNIO NETLIFY?
|
|
||||||
|
|
||||||
Para eu te dar os comandos exatos, me informe:
|
|
||||||
|
|
||||||
**Qual é a URL do seu site no Netlify?**
|
|
||||||
|
|
||||||
Exemplo: `https://rdo-tracksteel.netlify.app`
|
|
||||||
|
|
||||||
Com essa informação, posso te dar os valores exatos para copiar e colar!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 DICA RÁPIDA
|
|
||||||
|
|
||||||
Se você ainda não sabe a URL do Netlify:
|
|
||||||
|
|
||||||
1. Acesse: https://app.netlify.com
|
|
||||||
2. Clique no seu site
|
|
||||||
3. A URL está no topo da página (ex: `https://nome-do-site.netlify.app`)
|
|
||||||
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
# 🎯 CONFIGURAÇÃO EXATA - rdo.tracksteel.com.br
|
|
||||||
|
|
||||||
## ✅ VALORES PRONTOS PARA COPIAR E COLAR
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1️⃣ NETLIFY - VARIÁVEIS DE AMBIENTE
|
|
||||||
|
|
||||||
### Acesse:
|
|
||||||
https://app.netlify.com → Seu Site → Site settings → Environment variables
|
|
||||||
|
|
||||||
### Adicione estas 2 variáveis:
|
|
||||||
|
|
||||||
**Variável 1:**
|
|
||||||
```
|
|
||||||
Key: VITE_SUPABASE_URL
|
|
||||||
Value: https://xzudfhifaancyxxfdejx.supabase.co
|
|
||||||
```
|
|
||||||
|
|
||||||
**Variável 2:**
|
|
||||||
```
|
|
||||||
Key: VITE_SUPABASE_ANON_KEY
|
|
||||||
Value: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh6dWRmaGlmYWFuY3l4eGZkZWp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzNjE0MTAsImV4cCI6MjA4NjkzNzQxMH0.c5CHWhfXcMrm27LfxEt6OZtttXXvVJOeWu-IbnNLfWY
|
|
||||||
```
|
|
||||||
|
|
||||||
### ⚠️ IMPORTANTE:
|
|
||||||
Após adicionar as variáveis, clique em **"Trigger deploy"** para fazer um novo deploy!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2️⃣ SUPABASE - REDIRECT URLs
|
|
||||||
|
|
||||||
### Acesse:
|
|
||||||
https://supabase.com/dashboard/project/xzudfhifaancyxxfdejx/auth/url-configuration
|
|
||||||
|
|
||||||
### Na seção "Redirect URLs", adicione estas URLs (uma por linha):
|
|
||||||
|
|
||||||
```
|
|
||||||
https://rdo.tracksteel.com.br/auth/callback
|
|
||||||
https://rdo.tracksteel.com.br/*
|
|
||||||
http://localhost:5173/auth/callback
|
|
||||||
http://localhost:3000/auth/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### Na seção "Site URL", configure:
|
|
||||||
```
|
|
||||||
https://rdo.tracksteel.com.br
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clique em **Save**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3️⃣ GOOGLE CLOUD CONSOLE - OAUTH
|
|
||||||
|
|
||||||
### Acesse:
|
|
||||||
https://console.cloud.google.com/apis/credentials
|
|
||||||
|
|
||||||
### Clique no seu OAuth 2.0 Client ID
|
|
||||||
|
|
||||||
### Na seção "Authorized redirect URIs", adicione estas URIs:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://xzudfhifaancyxxfdejx.supabase.co/auth/v1/callback
|
|
||||||
https://rdo.tracksteel.com.br/auth/callback
|
|
||||||
http://localhost:5173/auth/callback
|
|
||||||
http://localhost:3000/auth/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### Clique em **Save**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4️⃣ TESTE APÓS CONFIGURAR
|
|
||||||
|
|
||||||
### Teste 1: Verificar Variáveis de Ambiente
|
|
||||||
|
|
||||||
1. Abra: https://rdo.tracksteel.com.br
|
|
||||||
2. Pressione **F12** (Console do navegador)
|
|
||||||
3. Cole este código:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
console.log('URL:', import.meta.env.VITE_SUPABASE_URL)
|
|
||||||
console.log('Key:', import.meta.env.VITE_SUPABASE_ANON_KEY?.substring(0, 30) + '...')
|
|
||||||
```
|
|
||||||
|
|
||||||
**Resultado esperado:**
|
|
||||||
```
|
|
||||||
URL: https://xzudfhifaancyxxfdejx.supabase.co
|
|
||||||
Key: eyJhbGciOiJIUzI1NiIsInR5cCI6Ik...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Se retornar `undefined`:**
|
|
||||||
- ❌ Variáveis não foram configuradas no Netlify
|
|
||||||
- ❌ Ou você não fez o novo deploy após configurar
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Teste 2: Login com Google
|
|
||||||
|
|
||||||
1. Acesse: https://rdo.tracksteel.com.br
|
|
||||||
2. Clique em **"Login com Google"**
|
|
||||||
3. Autorize o app
|
|
||||||
4. Deve redirecionar para: `https://rdo.tracksteel.com.br/auth/callback`
|
|
||||||
5. Deve fazer login com sucesso e ir para a página inicial
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 CHECKLIST DE EXECUÇÃO
|
|
||||||
|
|
||||||
- [ ] 1. Configurar variáveis no Netlify
|
|
||||||
- [ ] 2. Fazer novo deploy no Netlify (Trigger deploy)
|
|
||||||
- [ ] 3. Aguardar deploy finalizar (2-3 minutos)
|
|
||||||
- [ ] 4. Configurar Redirect URLs no Supabase
|
|
||||||
- [ ] 5. Configurar OAuth no Google Cloud
|
|
||||||
- [ ] 6. Testar variáveis de ambiente (Teste 1)
|
|
||||||
- [ ] 7. Testar login com Google (Teste 2)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 DIAGNÓSTICO DE PROBLEMAS
|
|
||||||
|
|
||||||
### Problema: Variáveis retornam `undefined`
|
|
||||||
**Solução:**
|
|
||||||
1. Verifique se adicionou as variáveis no Netlify
|
|
||||||
2. Verifique se fez o novo deploy
|
|
||||||
3. Aguarde o deploy finalizar completamente
|
|
||||||
4. Limpe o cache do navegador (Ctrl+Shift+Delete)
|
|
||||||
|
|
||||||
### Problema: "Invalid redirect URL"
|
|
||||||
**Solução:**
|
|
||||||
1. Verifique se adicionou as URLs no Supabase (passo 2)
|
|
||||||
2. Verifique se adicionou as URIs no Google Cloud (passo 3)
|
|
||||||
3. Aguarde 1-2 minutos para as configurações propagarem
|
|
||||||
|
|
||||||
### Problema: Erro 401 após login
|
|
||||||
**Solução:**
|
|
||||||
- ✅ Já resolvido! Você executou o SQL corretamente
|
|
||||||
|
|
||||||
### Problema: Login funciona mas volta para tela de login
|
|
||||||
**Solução:**
|
|
||||||
1. Verifique se as variáveis de ambiente estão configuradas
|
|
||||||
2. Verifique se o novo deploy foi feito
|
|
||||||
3. Limpe o cache do navegador
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 RESUMO RÁPIDO
|
|
||||||
|
|
||||||
**3 lugares para configurar:**
|
|
||||||
|
|
||||||
1. **Netlify** → Variáveis de ambiente + Novo deploy
|
|
||||||
2. **Supabase** → Redirect URLs
|
|
||||||
3. **Google Cloud** → Redirect URIs
|
|
||||||
|
|
||||||
**Tempo estimado:** 10-15 minutos
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 DICA IMPORTANTE
|
|
||||||
|
|
||||||
Após configurar tudo, aguarde 2-3 minutos para:
|
|
||||||
- Deploy do Netlify finalizar
|
|
||||||
- Configurações do Supabase propagarem
|
|
||||||
- Configurações do Google Cloud propagarem
|
|
||||||
|
|
||||||
Depois teste o login!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Qualquer dúvida, me avise em qual passo você está!**
|
|
||||||
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
# 🔧 CORREÇÃO: Autenticação no Netlify
|
|
||||||
|
|
||||||
## 🎯 PROBLEMA IDENTIFICADO
|
|
||||||
|
|
||||||
O app funciona localmente (localhost:5173 e :3000) mas falha ao autenticar no Netlify devido a:
|
|
||||||
|
|
||||||
1. ❌ Variáveis de ambiente não configuradas no Netlify
|
|
||||||
2. ❌ URLs de callback OAuth não configuradas no Supabase
|
|
||||||
3. ❌ Políticas RLS bloqueando acesso após OAuth
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ SOLUÇÃO COMPLETA
|
|
||||||
|
|
||||||
### 1️⃣ CONFIGURAR VARIÁVEIS DE AMBIENTE NO NETLIFY
|
|
||||||
|
|
||||||
Acesse: https://app.netlify.com → Seu Site → Site settings → Environment variables
|
|
||||||
|
|
||||||
Adicione estas variáveis:
|
|
||||||
|
|
||||||
```
|
|
||||||
VITE_SUPABASE_URL=https://xzudfhifaancyxxfdejx.supabase.co
|
|
||||||
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh6dWRmaGlmYWFuY3l4eGZkZWp4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzNjE0MTAsImV4cCI6MjA4NjkzNzQxMH0.c5CHWhfXcMrm27LfxEt6OZtttXXvVJOeWu-IbnNLfWY
|
|
||||||
```
|
|
||||||
|
|
||||||
**IMPORTANTE:** Após adicionar, faça um novo deploy!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2️⃣ CONFIGURAR REDIRECT URLs NO SUPABASE
|
|
||||||
|
|
||||||
Acesse: https://supabase.com/dashboard/project/xzudfhifaancyxxfdejx/auth/url-configuration
|
|
||||||
|
|
||||||
Na seção **Redirect URLs**, adicione:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://SEU-SITE.netlify.app/auth/callback
|
|
||||||
https://SEU-SITE.netlify.app/*
|
|
||||||
```
|
|
||||||
|
|
||||||
**Exemplo:**
|
|
||||||
```
|
|
||||||
https://rdo-tracksteel.netlify.app/auth/callback
|
|
||||||
https://rdo-tracksteel.netlify.app/*
|
|
||||||
```
|
|
||||||
|
|
||||||
Na seção **Site URL**, configure:
|
|
||||||
```
|
|
||||||
https://SEU-SITE.netlify.app
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3️⃣ CORRIGIR POLÍTICAS RLS PARA OAUTH
|
|
||||||
|
|
||||||
O problema principal: quando um usuário faz login via Google OAuth, o Supabase cria o registro em `auth.users`, mas a política RLS impede a criação automática do perfil em `public.usuarios`.
|
|
||||||
|
|
||||||
Execute este SQL no Supabase (SQL Editor):
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- ============================================================================
|
|
||||||
-- CORREÇÃO RLS PARA OAUTH - PERMITIR CRIAÇÃO AUTOMÁTICA DE PERFIL
|
|
||||||
-- ============================================================================
|
|
||||||
|
|
||||||
-- 1. Permitir INSERT na tabela usuarios para usuários autenticados
|
|
||||||
DROP POLICY IF EXISTS "Users can create own profile" ON public.usuarios;
|
|
||||||
|
|
||||||
CREATE POLICY "Users can create own profile" ON public.usuarios
|
|
||||||
FOR INSERT
|
|
||||||
WITH CHECK (auth.uid() = id);
|
|
||||||
|
|
||||||
-- 2. Garantir que o trigger handle_new_user funciona
|
|
||||||
-- Verificar se a função existe
|
|
||||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
BEGIN
|
|
||||||
-- Inserir novo usuário na tabela public.usuarios
|
|
||||||
INSERT INTO public.usuarios (
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
nome,
|
|
||||||
role,
|
|
||||||
ativo
|
|
||||||
) VALUES (
|
|
||||||
NEW.id,
|
|
||||||
NEW.email,
|
|
||||||
COALESCE(NEW.raw_user_meta_data->>'nome', NEW.raw_user_meta_data->>'name', split_part(NEW.email, '@', 1)),
|
|
||||||
'usuario',
|
|
||||||
true
|
|
||||||
)
|
|
||||||
ON CONFLICT (id) DO UPDATE SET
|
|
||||||
email = EXCLUDED.email,
|
|
||||||
nome = COALESCE(EXCLUDED.nome, public.usuarios.nome),
|
|
||||||
updated_at = NOW();
|
|
||||||
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
||||||
|
|
||||||
-- 3. Garantir que o trigger está ativo
|
|
||||||
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 public.handle_new_user();
|
|
||||||
|
|
||||||
-- 4. Permitir leitura de organizações para usuários sem organização (signup)
|
|
||||||
DROP POLICY IF EXISTS "Users can view orgs during signup" ON public.organizacoes;
|
|
||||||
|
|
||||||
CREATE POLICY "Users can view orgs during signup" ON public.organizacoes
|
|
||||||
FOR SELECT
|
|
||||||
USING (auth.uid() IS NOT NULL);
|
|
||||||
|
|
||||||
-- 5. Permitir criação de organização para novos usuários
|
|
||||||
DROP POLICY IF EXISTS "Users can create org" ON public.organizacoes;
|
|
||||||
|
|
||||||
CREATE POLICY "Users can create org" ON public.organizacoes
|
|
||||||
FOR INSERT
|
|
||||||
WITH CHECK (auth.uid() IS NOT NULL);
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
-- FIM DA CORREÇÃO
|
|
||||||
-- ============================================================================
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4️⃣ VERIFICAR CONFIGURAÇÃO DO GOOGLE OAUTH
|
|
||||||
|
|
||||||
No Google Cloud Console (https://console.cloud.google.com):
|
|
||||||
|
|
||||||
1. Acesse: APIs & Services → Credentials
|
|
||||||
2. Clique no seu OAuth 2.0 Client ID
|
|
||||||
3. Em **Authorized redirect URIs**, adicione:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://xzudfhifaancyxxfdejx.supabase.co/auth/v1/callback
|
|
||||||
https://SEU-SITE.netlify.app/auth/callback
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 TESTAR A CORREÇÃO
|
|
||||||
|
|
||||||
### Teste 1: Variáveis de Ambiente
|
|
||||||
```bash
|
|
||||||
# No console do navegador (F12) no site Netlify:
|
|
||||||
console.log(import.meta.env.VITE_SUPABASE_URL)
|
|
||||||
```
|
|
||||||
Deve retornar: `https://xzudfhifaancyxxfdejx.supabase.co`
|
|
||||||
|
|
||||||
### Teste 2: Login OAuth
|
|
||||||
1. Acesse seu site no Netlify
|
|
||||||
2. Clique em "Login com Google"
|
|
||||||
3. Autorize o app
|
|
||||||
4. Deve redirecionar e autenticar com sucesso
|
|
||||||
|
|
||||||
### Teste 3: Verificar Perfil Criado
|
|
||||||
No Supabase SQL Editor:
|
|
||||||
```sql
|
|
||||||
SELECT * FROM public.usuarios WHERE email = 'seu-email@gmail.com';
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 DIAGNÓSTICO DE PROBLEMAS
|
|
||||||
|
|
||||||
### Erro: "Invalid redirect URL"
|
|
||||||
- ✅ Verifique se adicionou a URL no Supabase (passo 2)
|
|
||||||
- ✅ Verifique se adicionou no Google Cloud (passo 4)
|
|
||||||
|
|
||||||
### Erro: "User not found" ou "Permission denied"
|
|
||||||
- ✅ Execute o SQL de correção RLS (passo 3)
|
|
||||||
- ✅ Verifique se o trigger `handle_new_user` está ativo
|
|
||||||
|
|
||||||
### Erro: "Environment variables not defined"
|
|
||||||
- ✅ Configure variáveis no Netlify (passo 1)
|
|
||||||
- ✅ Faça um novo deploy após adicionar
|
|
||||||
|
|
||||||
### Login funciona mas não carrega dados
|
|
||||||
- ✅ Problema de RLS - execute o SQL do passo 3
|
|
||||||
- ✅ Verifique se o usuário foi criado em `public.usuarios`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 CHECKLIST FINAL
|
|
||||||
|
|
||||||
- [ ] Variáveis de ambiente configuradas no Netlify
|
|
||||||
- [ ] Novo deploy realizado após configurar variáveis
|
|
||||||
- [ ] Redirect URLs adicionadas no Supabase
|
|
||||||
- [ ] Redirect URIs adicionadas no Google Cloud
|
|
||||||
- [ ] SQL de correção RLS executado no Supabase
|
|
||||||
- [ ] Teste de login OAuth realizado com sucesso
|
|
||||||
- [ ] Perfil do usuário criado em `public.usuarios`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 RESULTADO ESPERADO
|
|
||||||
|
|
||||||
Após seguir todos os passos:
|
|
||||||
|
|
||||||
✅ Login via Google funciona no Netlify
|
|
||||||
✅ Perfil do usuário é criado automaticamente
|
|
||||||
✅ Usuário consegue acessar o app normalmente
|
|
||||||
✅ RLS permite acesso aos dados da organização
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Data:** 24/02/2026
|
|
||||||
**Status:** Aguardando aplicação das correções
|
|
||||||
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}"]
|
|
||||||
104
TESTE_VARIAVEIS_NAVEGADOR.md
Normal file
104
TESTE_VARIAVEIS_NAVEGADOR.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# 🧪 TESTE DE VARIÁVEIS NO NAVEGADOR
|
||||||
|
|
||||||
|
## ❌ ERRO NO CONSOLE
|
||||||
|
|
||||||
|
O comando `import.meta.env` NÃO funciona no console do navegador!
|
||||||
|
|
||||||
|
Ele só funciona dentro do código da aplicação durante o build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ COMO TESTAR CORRETAMENTE
|
||||||
|
|
||||||
|
### Opção 1: Verificar no código fonte
|
||||||
|
|
||||||
|
1. Abra: https://rdo.tracksteel.com.br
|
||||||
|
2. Pressione **F12** (DevTools)
|
||||||
|
3. Vá na aba **Sources** ou **Debugger**
|
||||||
|
4. Procure por arquivos `.js` no painel esquerdo
|
||||||
|
5. Abra qualquer arquivo e procure por `xzudfhifaancyxxfdejx`
|
||||||
|
6. Se encontrar = ✅ Variáveis configuradas!
|
||||||
|
7. Se NÃO encontrar = ❌ Variáveis não foram injetadas no build
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Opção 2: Verificar no Network
|
||||||
|
|
||||||
|
1. Abra: https://rdo.tracksteel.com.br
|
||||||
|
2. Pressione **F12** (DevTools)
|
||||||
|
3. Vá na aba **Network**
|
||||||
|
4. Clique em "Login com Google"
|
||||||
|
5. Procure por requisições para `xzudfhifaancyxxfdejx.supabase.co`
|
||||||
|
6. Se aparecer = ✅ Variáveis configuradas!
|
||||||
|
7. Se NÃO aparecer = ❌ Variáveis não foram injetadas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Opção 3: Adicionar console.log temporário
|
||||||
|
|
||||||
|
Adicione este código no arquivo `src/lib/supabase.ts` (linha 6):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
||||||
|
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
|
||||||
|
|
||||||
|
// ADICIONAR ESTAS LINHAS TEMPORÁRIAS:
|
||||||
|
console.log('🔍 SUPABASE URL:', supabaseUrl)
|
||||||
|
console.log('🔍 SUPABASE KEY:', supabaseAnonKey?.substring(0, 30) + '...')
|
||||||
|
```
|
||||||
|
|
||||||
|
Depois faça commit, push e aguarde o deploy no Netlify.
|
||||||
|
|
||||||
|
Quando abrir o site, vai aparecer no console!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 PROBLEMA IDENTIFICADO
|
||||||
|
|
||||||
|
O erro **401 Unauthorized** com `grant_type=pkce` indica que:
|
||||||
|
|
||||||
|
1. ✅ As variáveis de ambiente ESTÃO configuradas (senão daria outro erro)
|
||||||
|
2. ❌ O flowType estava configurado como `pkce` que não funciona bem em produção
|
||||||
|
3. ✅ JÁ CORRIGI mudando para `implicit`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 PRÓXIMOS PASSOS
|
||||||
|
|
||||||
|
1. ✅ **Código corrigido** (mudei flowType para implicit)
|
||||||
|
2. ✅ **Push feito** para o GitHub
|
||||||
|
3. ⏳ **Aguardar deploy** no Netlify (2-3 minutos)
|
||||||
|
4. 🧪 **Testar login** novamente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⏰ AGUARDE O DEPLOY
|
||||||
|
|
||||||
|
O Netlify precisa fazer o build novamente com o código corrigido.
|
||||||
|
|
||||||
|
**Como verificar se o deploy terminou:**
|
||||||
|
|
||||||
|
1. Acesse: https://app.netlify.com
|
||||||
|
2. Clique no seu site
|
||||||
|
3. Vá em **Deploys**
|
||||||
|
4. Aguarde o deploy mais recente ficar **"Published"** (verde)
|
||||||
|
5. Depois teste o login!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 TESTE APÓS O DEPLOY
|
||||||
|
|
||||||
|
1. Abra: https://rdo.tracksteel.com.br
|
||||||
|
2. Limpe o cache (Ctrl+Shift+Delete → Limpar tudo)
|
||||||
|
3. Recarregue a página (Ctrl+F5)
|
||||||
|
4. Clique em "Login com Google"
|
||||||
|
5. Deve funcionar agora!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 SE AINDA DER ERRO
|
||||||
|
|
||||||
|
Me envie print do console (F12) mostrando:
|
||||||
|
- Aba **Console** (mensagens de erro)
|
||||||
|
- Aba **Network** (requisições falhando)
|
||||||
|
|
||||||
8
TS_RDO.code-workspace
Normal file
8
TS_RDO.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "TS_RDO"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
1
dist/assets/css/index-DXaaJsOA.css
vendored
1
dist/assets/css/index-DXaaJsOA.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/Auth-KZISTjMo.js
vendored
1
dist/assets/js/Auth-KZISTjMo.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/AuthCallback-DlZVcD4W.js
vendored
1
dist/assets/js/AuthCallback-DlZVcD4W.js
vendored
@@ -1 +0,0 @@
|
|||||||
import{j as e}from"./query-vendor-BLVqILA6.js";import{d as w,r as t}from"./router-vendor-D4by-_6Z.js";import{s as i}from"./index-doec96Hx.js";import{a0 as j,c as b,a2 as N}from"./ui-vendor-CyRvbSfR.js";import"./react-vendor-CqRd3GwO.js";import"./supabase-vendor-CnnNSQLo.js";import"./state-vendor-DHadhBU5.js";const _=()=>{const o=w(),[s,c]=t.useState("loading"),[g,n]=t.useState("Processando autenticação..."),l=t.useCallback(async()=>{try{const a=new URLSearchParams(window.location.search),d=a.get("error"),x=a.get("error_description");if(d)throw new Error(x||d);const{data:{session:r},error:m}=await i.auth.getSession();if(m)throw m;if(!r)throw new Error("Nenhuma sessão encontrada");const{data:u,error:h}=await i.from("usuarios").select("id, organizacao_id").eq("id",r.user.id).single();if(!u&&h?.code==="PGRST116"){const{error:p}=await i.from("usuarios").insert({id:r.user.id,email:r.user.email,nome:r.user.user_metadata.full_name||r.user.email?.split("@")[0]})}c("success"),n("Login realizado com sucesso!");const f=u?.organizacao_id;setTimeout(()=>{o(f?"/":"/selecionar-organizacao")},1e3)}catch(a){c("error"),n(a instanceof Error?a.message:"Erro ao processar autenticação"),setTimeout(()=>{o("/login")},3e3)}},[o]);return t.useEffect(()=>{l()},[l]),e.jsx("div",{className:"min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4",children:e.jsx("div",{className:"bg-white rounded-2xl shadow-xl p-8 max-w-md w-full",children:e.jsxs("div",{className:"flex flex-col items-center space-y-4",children:[s==="loading"&&e.jsx(j,{className:"w-16 h-16 text-blue-500 animate-spin"}),s==="success"&&e.jsx(b,{className:"w-16 h-16 text-green-500"}),s==="error"&&e.jsx(N,{className:"w-16 h-16 text-red-500"}),e.jsxs("div",{className:"text-center",children:[e.jsxs("h2",{className:"text-2xl font-bold text-gray-900 mb-2",children:[s==="loading"&&"Processando...",s==="success"&&"Sucesso!",s==="error"&&"Erro"]}),e.jsx("p",{className:"text-gray-600",children:g})]}),s==="loading"&&e.jsx("div",{className:"w-full bg-gray-200 rounded-full h-2 overflow-hidden",children:e.jsx("div",{className:"bg-blue-500 h-2 rounded-full animate-pulse w-2/3"})}),s!=="loading"&&e.jsx("p",{className:"text-sm text-gray-500",children:s==="success"?"Redirecionando para o sistema...":"Redirecionando para o login..."})]})})})};export{_ as AuthCallback};
|
|
||||||
1
dist/assets/js/Cadastros-QZZ_REjv.js
vendored
1
dist/assets/js/Cadastros-QZZ_REjv.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/Configuracoes-LYuviA0-.js
vendored
1
dist/assets/js/Configuracoes-LYuviA0-.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/CreateObra-BwwQt_hK.js
vendored
1
dist/assets/js/CreateObra-BwwQt_hK.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/CreateRDO-Bhe5_7xI.js
vendored
1
dist/assets/js/CreateRDO-Bhe5_7xI.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/CreateTask-BlEDuvPM.js
vendored
1
dist/assets/js/CreateTask-BlEDuvPM.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/Dashboard-BPPxBqO4.js
vendored
1
dist/assets/js/Dashboard-BPPxBqO4.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/DatabaseTest-BA9LjsoE.js
vendored
1
dist/assets/js/DatabaseTest-BA9LjsoE.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/ManualInstrucoes-G3xr1xLl.js
vendored
1
dist/assets/js/ManualInstrucoes-G3xr1xLl.js
vendored
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import{j as M}from"./query-vendor-BLVqILA6.js";import{r as c}from"./router-vendor-D4by-_6Z.js";const E=()=>{const l=c.useRef(null),h=c.useRef(),n=c.useRef([]);return c.useEffect(()=>{const e=l.current;if(!e)return;const r=e.getContext("2d");if(!r)return;const f=()=>{e.width=window.innerWidth,e.height=window.innerHeight},u=()=>{const t=Math.floor(e.width*e.height/15e3);n.current=[];for(let a=0;a<t;a++)n.current.push({x:Math.random()*e.width,y:Math.random()*e.height,vx:(Math.random()-.5)*.5,vy:(Math.random()-.5)*.5})},g=t=>{r.beginPath(),r.arc(t.x,t.y,2,0,Math.PI*2),r.fillStyle="rgba(147, 197, 253, 0.8)",r.fill()},y=(t,a,i,o)=>{const s=1-i/o;r.beginPath(),r.moveTo(t.x,t.y),r.lineTo(a.x,a.y),r.strokeStyle=`rgba(147, 197, 253, ${s*.3})`,r.lineWidth=1,r.stroke()},v=()=>{n.current.forEach(t=>{t.x+=t.vx,t.y+=t.vy,(t.x<0||t.x>e.width)&&(t.vx*=-1),(t.y<0||t.y>e.height)&&(t.vy*=-1),t.x=Math.max(0,Math.min(e.width,t.x)),t.y=Math.max(0,Math.min(e.height,t.y))})},x=()=>{r.clearRect(0,0,e.width,e.height);const t=r.createLinearGradient(0,0,e.width,e.height);t.addColorStop(0,"#0f172a"),t.addColorStop(.5,"#1e1b4b"),t.addColorStop(1,"#581c87"),r.fillStyle=t,r.fillRect(0,0,e.width,e.height),v();const a=120;for(let i=0;i<n.current.length;i++)for(let o=i+1;o<n.current.length;o++){const s=n.current[i],d=n.current[o],w=Math.sqrt(Math.pow(s.x-d.x,2)+Math.pow(s.y-d.y,2));w<a&&y(s,d,w,a)}n.current.forEach(g),h.current=requestAnimationFrame(x)};f(),u(),x();const m=()=>{f(),u()};return window.addEventListener("resize",m),()=>{window.removeEventListener("resize",m),h.current&&cancelAnimationFrame(h.current)}},[]),M.jsx("canvas",{ref:l,className:"fixed inset-0 w-full h-full -z-10",style:{background:"linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #581c87 100%)"}})};export{E as N};
|
|
||||||
1
dist/assets/js/ObraDetails-S1VcIvXW.js
vendored
1
dist/assets/js/ObraDetails-S1VcIvXW.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/ObraTasks-Cy-rxhIo.js
vendored
1
dist/assets/js/ObraTasks-Cy-rxhIo.js
vendored
File diff suppressed because one or more lines are too long
5
dist/assets/js/RDODetails-tkR9ftO-.js
vendored
5
dist/assets/js/RDODetails-tkR9ftO-.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/js/Reports-BmqmPZPj.js
vendored
1
dist/assets/js/Reports-BmqmPZPj.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
dist/assets/js/ThemeToggle-Cryh6OOS.js
vendored
1
dist/assets/js/ThemeToggle-Cryh6OOS.js
vendored
@@ -1 +0,0 @@
|
|||||||
import{j as a}from"./query-vendor-BLVqILA6.js";import{r as o}from"./router-vendor-D4by-_6Z.js";import{m as s,I as l,am as i}from"./ui-vendor-CyRvbSfR.js";function n(){const[t,e]=o.useState(()=>{const r=localStorage.getItem("theme");return r||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")});return o.useEffect(()=>{document.documentElement.classList.remove("light","dark"),document.documentElement.classList.add(t),localStorage.setItem("theme",t)},[t]),{theme:t,toggleTheme:()=>{e(r=>r==="light"?"dark":"light")},isDark:t==="dark"}}function h(){const{toggleTheme:t,isDark:e}=n();return a.jsx(s.button,{onClick:t,className:"relative p-2 rounded-xl bg-white/70 dark:bg-gray-800/70 backdrop-blur-md border border-gray-200/50 dark:border-gray-700/50 shadow-lg hover:shadow-xl transition-all duration-300 group",whileHover:{scale:1.05},whileTap:{scale:.95},title:e?"Alternar para modo claro":"Alternar para modo escuro",children:a.jsxs("div",{className:"relative w-6 h-6",children:[a.jsx(s.div,{initial:!1,animate:{scale:e?0:1,rotate:e?180:0,opacity:e?0:1},transition:{duration:.3,ease:"easeInOut"},className:"absolute inset-0 flex items-center justify-center",children:a.jsx(l,{className:"w-5 h-5 text-yellow-500 group-hover:text-yellow-600"})}),a.jsx(s.div,{initial:!1,animate:{scale:e?1:0,rotate:e?0:-180,opacity:e?1:0},transition:{duration:.3,ease:"easeInOut"},className:"absolute inset-0 flex items-center justify-center",children:a.jsx(i,{className:"w-5 h-5 text-blue-400 group-hover:text-blue-300"})})]})})}export{h as T};
|
|
||||||
1
dist/assets/js/configStore-DS-p50om.js
vendored
1
dist/assets/js/configStore-DS-p50om.js
vendored
File diff suppressed because one or more lines are too long
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
3
dist/assets/js/index-doec96Hx.js
vendored
3
dist/assets/js/index-doec96Hx.js
vendored
File diff suppressed because one or more lines are too long
9
dist/assets/js/query-vendor-BLVqILA6.js
vendored
9
dist/assets/js/query-vendor-BLVqILA6.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
3
dist/assets/js/state-vendor-DHadhBU5.js
vendored
3
dist/assets/js/state-vendor-DHadhBU5.js
vendored
File diff suppressed because one or more lines are too long
25
dist/assets/js/supabase-vendor-CnnNSQLo.js
vendored
25
dist/assets/js/supabase-vendor-CnnNSQLo.js
vendored
File diff suppressed because one or more lines are too long
424
dist/assets/js/ui-vendor-CyRvbSfR.js
vendored
424
dist/assets/js/ui-vendor-CyRvbSfR.js
vendored
File diff suppressed because one or more lines are too long
12
dist/assets/js/useInviteCode-BxOSdQOT.js
vendored
12
dist/assets/js/useInviteCode-BxOSdQOT.js
vendored
@@ -1,12 +0,0 @@
|
|||||||
import{r as d}from"./router-vendor-D4by-_6Z.js";import{s as n}from"./index-doec96Hx.js";const w=()=>{const[g,s]=d.useState(!1),[m,t]=d.useState(null);return{loading:g,error:m,validarConvite:async i=>{try{s(!0),t(null);const e=i.toUpperCase().trim(),{data:r,error:a}=await n.from("convites").select(`
|
|
||||||
id,
|
|
||||||
codigo,
|
|
||||||
organizacao_id,
|
|
||||||
role,
|
|
||||||
max_usos,
|
|
||||||
usos_atuais,
|
|
||||||
ativo,
|
|
||||||
expira_em,
|
|
||||||
email_convidado,
|
|
||||||
organizacoes:organizacao_id (nome)
|
|
||||||
`).eq("codigo",e).eq("ativo",!0).single();if(a||!r)return{success:!1,error:"Código de convite inválido ou expirado."};const o=r;return o.expira_em&&new Date(o.expira_em)<new Date?{success:!1,error:"Este código de convite expirou."}:o.max_usos>0&&o.usos_atuais>=o.max_usos?{success:!1,error:"Este código de convite já atingiu o limite de usos."}:{success:!0,organizacao_id:o.organizacao_id,organizacao_nome:o.organizacoes?.nome||"Organização",role:o.role}}catch(e){const r=e instanceof Error?e.message:"Erro ao validar convite";return t(r),{success:!1,error:r}}finally{s(!1)}},usarConvite:async(i,e)=>{try{s(!0),t(null);const{data:r,error:a}=await n.rpc("usar_convite",{p_codigo:i,p_usuario_id:e});if(a)throw a;const o=r;return o.success||t(o.error||"Erro ao usar convite"),o}catch(r){const a=r instanceof Error?r.message:"Erro ao usar convite";return t(a),{success:!1,error:a}}finally{s(!1)}},gerarConvite:async(i,e={})=>{try{s(!0),t(null);const{data:r,error:a}=await n.rpc("gerar_codigo_convite");if(a)throw a;const o=r;let u=null;if(e.expiraEmDias){const c=new Date;c.setDate(c.getDate()+e.expiraEmDias),u=c.toISOString()}const{data:{user:f}}=await n.auth.getUser(),{error:l}=await n.from("convites").insert({organizacao_id:i,codigo:o,criado_por:f?.id,email_convidado:e.emailConvidado||null,role:e.role||"usuario",max_usos:e.maxUsos??1,expira_em:u});if(l)throw l;return{success:!0,codigo:o}}catch(r){const a=r instanceof Error?r.message:"Erro ao gerar convite";return t(a),{success:!1,error:a}}finally{s(!1)}},listarConvites:async i=>{try{s(!0);const{data:e,error:r}=await n.from("convites").select("*").eq("organizacao_id",i).order("created_at",{ascending:!1});if(r)throw r;return e||[]}catch{return[]}finally{s(!1)}}}};export{w as u};
|
|
||||||
1
dist/assets/js/useUserStore-VOdYc7Zm.js
vendored
1
dist/assets/js/useUserStore-VOdYc7Zm.js
vendored
@@ -1 +0,0 @@
|
|||||||
import{d,p as U,s as o}from"./index-doec96Hx.js";import{c as g}from"./state-vendor-DHadhBU5.js";import"./query-vendor-BLVqILA6.js";import"./router-vendor-D4by-_6Z.js";import"./react-vendor-CqRd3GwO.js";import"./ui-vendor-CyRvbSfR.js";import"./supabase-vendor-CnnNSQLo.js";const i={currentUser:null,users:[],loading:!1,error:null},m=g()(d(U((r,u)=>({...i,setCurrentUser:e=>r({currentUser:e},!1,"setCurrentUser"),setUsers:e=>r({users:e},!1,"setUsers"),setLoading:e=>r({loading:e},!1,"setLoading"),setError:e=>r({error:e},!1,"setError"),clearError:()=>r({error:null},!1,"clearError"),reset:()=>r(i,!1,"reset"),fetchCurrentUser:async e=>{try{r({loading:!0,error:null},!1,"fetchCurrentUser:start");const{data:s,error:a}=await o.from("usuarios").select("*").eq("id",e).single();if(a)throw a;r({currentUser:s,loading:!1},!1,"fetchCurrentUser:success")}catch(s){r({error:s.message||"Erro ao buscar usuário",loading:!1},!1,"fetchCurrentUser:error")}},fetchUsers:async()=>{try{r({loading:!0,error:null},!1,"fetchUsers:start");const{data:e,error:s}=await o.from("usuarios").select("*").order("nome");if(s)throw s;r({users:e||[],loading:!1},!1,"fetchUsers:success")}catch(e){r({error:e.message||"Erro ao buscar usuários",loading:!1},!1,"fetchUsers:error")}},updateUser:async(e,s)=>{try{r({loading:!0,error:null},!1,"updateUser:start");const{data:a,error:t}=await o.from("usuarios").update({...s,updated_at:new Date().toISOString()}).eq("id",e).select().single();if(t)throw t;const{users:n,currentUser:l}=u(),f=n.map(c=>c.id===e?a:c);return r({users:f,currentUser:l?.id===e?a:l,loading:!1},!1,"updateUser:success"),!0}catch(a){return r({error:a.message||"Erro ao atualizar usuário",loading:!1},!1,"updateUser:error"),!1}},createUser:async e=>{try{r({loading:!0,error:null},!1,"createUser:start");const{data:s,error:a}=await o.from("usuarios").insert(e).select().single();if(a)throw a;const{users:t}=u();return r({users:[...t,s],loading:!1},!1,"createUser:success"),!0}catch(s){return r({error:s.message||"Erro ao criar usuário",loading:!1},!1,"createUser:error"),!1}},deleteUser:async e=>{try{r({loading:!0,error:null},!1,"deleteUser:start");const{error:s}=await o.from("usuarios").delete().eq("id",e);if(s)throw s;const{users:a,currentUser:t}=u(),n=a.filter(l=>l.id!==e);return r({users:n,currentUser:t?.id===e?null:t,loading:!1},!1,"deleteUser:success"),!0}catch(s){return r({error:s.message||"Erro ao deletar usuário",loading:!1},!1,"deleteUser:error"),!1}}}),{name:"user-store",partialize:r=>({currentUser:r.currentUser,users:r.users})}))),b=()=>m(r=>r.currentUser);export{b as useCurrentUser,m as useUserStore};
|
|
||||||
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-doec96Hx.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-BLVqILA6.js">
|
<link rel="modulepreload" crossorigin href="/assets/supabase-L170XLdN.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/ui-vendor-CyRvbSfR.js">
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BLxppFDo.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/supabase-vendor-CnnNSQLo.js">
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-DNOlzffn.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/js/state-vendor-DHadhBU5.js">
|
<link rel="modulepreload" crossorigin href="/assets/react-eH9hKoTL.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/css/index-DXaaJsOA.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;
|
||||||
21
netlify.toml
21
netlify.toml
@@ -1,21 +0,0 @@
|
|||||||
[build]
|
|
||||||
command = "npm ci && npm run build"
|
|
||||||
publish = "dist"
|
|
||||||
|
|
||||||
[build.environment]
|
|
||||||
NODE_VERSION = "22"
|
|
||||||
NODE_ENV = "production"
|
|
||||||
GENERATE_SOURCEMAP = "false"
|
|
||||||
|
|
||||||
# Redirect para SPA
|
|
||||||
[[redirects]]
|
|
||||||
from = "/*"
|
|
||||||
to = "/index.html"
|
|
||||||
status = 200
|
|
||||||
|
|
||||||
# Headers básicos de segurança
|
|
||||||
[[headers]]
|
|
||||||
for = "/*"
|
|
||||||
[headers.values]
|
|
||||||
X-Frame-Options = "DENY"
|
|
||||||
X-Content-Type-Options = "nosniff"
|
|
||||||
5044
package-lock.json
generated
5044
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
87
package.json
87
package.json
@@ -1,16 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "trae-project",
|
"name": "ts-rdo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:netlify": "vite build --mode production",
|
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"preview:netlify": "vite preview --port 4173 --host",
|
|
||||||
"start": "node scripts/start.js",
|
"start": "node scripts/start.js",
|
||||||
"check": "tsc -b --noEmit",
|
"check": "tsc -b --noEmit",
|
||||||
"analyze": "vite build --mode analyze",
|
"analyze": "vite build --mode analyze",
|
||||||
@@ -18,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: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
22
railway.json
22
railway.json
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://railway.app/railway.schema.json",
|
|
||||||
"build": {
|
|
||||||
"builder": "NIXPACKS",
|
|
||||||
"buildCommand": "pnpm install && pnpm run build"
|
|
||||||
},
|
|
||||||
"deploy": {
|
|
||||||
"startCommand": "pnpm preview --host 0.0.0.0 --port $PORT",
|
|
||||||
"healthcheckPath": "/",
|
|
||||||
"healthcheckTimeout": 100,
|
|
||||||
"restartPolicyType": "ON_FAILURE",
|
|
||||||
"restartPolicyMaxRetries": 10
|
|
||||||
},
|
|
||||||
"environments": {
|
|
||||||
"production": {
|
|
||||||
"variables": {
|
|
||||||
"NODE_ENV": "production",
|
|
||||||
"PORT": "${{RAILWAY_PUBLIC_PORT}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
render.yaml
47
render.yaml
@@ -1,47 +0,0 @@
|
|||||||
services:
|
|
||||||
- type: web
|
|
||||||
name: rdo-app
|
|
||||||
env: node
|
|
||||||
region: oregon # ou frankfurt para Europa
|
|
||||||
plan: free # plano gratuito
|
|
||||||
buildCommand: pnpm install && pnpm run build
|
|
||||||
startCommand: pnpm preview --host 0.0.0.0 --port $PORT
|
|
||||||
|
|
||||||
# Configurações do ambiente
|
|
||||||
envVars:
|
|
||||||
- key: NODE_ENV
|
|
||||||
value: production
|
|
||||||
- key: PORT
|
|
||||||
fromService:
|
|
||||||
type: web
|
|
||||||
name: rdo-app
|
|
||||||
property: port
|
|
||||||
|
|
||||||
# Configurações de build
|
|
||||||
buildFilter:
|
|
||||||
paths:
|
|
||||||
- src/**
|
|
||||||
- public/**
|
|
||||||
- index.html
|
|
||||||
- package.json
|
|
||||||
- pnpm-lock.yaml
|
|
||||||
- vite.config.ts
|
|
||||||
- tsconfig.json
|
|
||||||
|
|
||||||
# Headers personalizados
|
|
||||||
headers:
|
|
||||||
- path: /*
|
|
||||||
name: X-Frame-Options
|
|
||||||
value: DENY
|
|
||||||
- path: /*
|
|
||||||
name: X-Content-Type-Options
|
|
||||||
value: nosniff
|
|
||||||
- path: /assets/*
|
|
||||||
name: Cache-Control
|
|
||||||
value: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
# Redirects para SPA
|
|
||||||
routes:
|
|
||||||
- type: redirect
|
|
||||||
source: /*
|
|
||||||
destination: /index.html
|
|
||||||
19
scripts/push_gitea.py
Normal file
19
scripts/push_gitea.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
# @@ -> %40%40 (Codificação URL padrão para caracteres especiais)
|
||||||
|
remote_url = "https://admtracksteel:%40%40Gi05Br;;@git.reifonas.cloud/admtracksteel/RDO.git"
|
||||||
|
|
||||||
|
def run_git(args):
|
||||||
|
result = subprocess.run(["git"] + args, cwd=repo_dir, capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Erro em git {' '.join(args)}: {result.stderr}")
|
||||||
|
else:
|
||||||
|
print(f"Sucesso em git {' '.join(args)}")
|
||||||
|
return result.returncode
|
||||||
|
|
||||||
|
run_git(["remote", "set-url", "origin", remote_url])
|
||||||
|
run_git(["branch", "-M", "main"])
|
||||||
|
print("Iniciando Push de RDO para Gitea...")
|
||||||
|
run_git(["push", "-u", "origin", "main", "--force"])
|
||||||
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) {
|
||||||
setAuthState({
|
console.log('⚠️ useAuth: Nenhuma sessão ativa. Tentando bypass automático...');
|
||||||
user: null,
|
const result = await bypassLogin();
|
||||||
session: null,
|
if (!result.success) {
|
||||||
loading: false,
|
setAuthState({
|
||||||
error: null
|
user: null,
|
||||||
});
|
session: null,
|
||||||
console.log('⚠️ useAuth: Nenhuma sessão ativa');
|
loading: false,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
}
|
||||||
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: [],
|
|
||||||
};
|
|
||||||
28
vercel.json
28
vercel.json
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"rewrites": [
|
|
||||||
{
|
|
||||||
"source": "/(.*)",
|
|
||||||
"destination": "/index.html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"source": "/service-worker.js",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Cache-Control",
|
|
||||||
"value": "public, max-age=0, must-revalidate"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "/(.*\\\\.(js|css|png|jpg|jpeg|gif|ico|svg))$",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Cache-Control",
|
|
||||||
"value": "public, max-age=31536000, immutable"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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