Refatoracao geral: limpeza de logs, correcoes de fim de linha, animacoes framer-motion e automacao de deploy no package.json

This commit is contained in:
2026-03-23 11:36:35 +00:00
parent a4450ad7e5
commit 75c75f6547
52 changed files with 9525 additions and 9544 deletions

View File

@@ -9,7 +9,8 @@
"build:analyze": "vite build --analyze",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"type-check": "tsc --noEmit"
"type-check": "tsc --noEmit",
"deploy": "npm run build && git add . && git commit -m 'build and deploy auto' && git push"
},
"dependencies": {
"@supabase/supabase-js": "^2.38.4",

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { FolderOpen, FileText, Clock, CheckCircle, Edit, Copy, Trash2 } from 'lucide-react'
import { useState } from 'react'
@@ -9,6 +8,7 @@ import { useNavigate } from 'react-router-dom'
import { supabase } from '@/lib/supabase'
import { deleteDatabook } from '@/lib/mutations'
import { toast } from '@/lib/toast'
import { motion, AnimatePresence } from 'framer-motion'
// Função para obter cores do status
const getStatusColor = (status: string) => {
@@ -58,37 +58,24 @@ export default function Dashboard() {
if (error) throw error
// Calcular progresso real baseado em documentos
const projetosComProgresso = await Promise.all(
((data as any) || []).map(async (projeto: any) => {
// Buscar total de tópicos do template
const { count: totalTopicos } = await supabase
.from('templates_topicos')
.select('*', { count: 'exact' })
// Buscar documentos do projeto
const { data: documentos, count: totalDocumentos } = await supabase
const { data: documentos } = await supabase
.from('documentos_auto_indexados')
.select('secao_numero', { count: 'exact' })
.eq('databook_id', (projeto as any).id)
.select('secao_numero')
.eq('databook_id',projeto.id)
// Contar seções únicas com documentos
const secoesUnicas = new Set(documentos?.map(d => d.secao_numero).filter(Boolean))
const secoesUnicas = new Set((documentos as any)?.map((d: any) => d.secao_numero).filter(Boolean))
const secoesComDocs = secoesUnicas.size
// Calcular percentual
const progresso = (totalTopicos && totalTopicos > 0)
? Math.round((secoesComDocs / totalTopicos) * 100)
: 0
console.log(`Projeto ${projeto.numero_projeto}:`, {
totalTopicos,
totalDocumentos,
secoesComDocs,
progresso
})
// Atualizar progresso no banco se mudou
if (progresso !== projeto.progresso_percentual) {
await supabase
.from('projetos')
@@ -145,13 +132,11 @@ export default function Dashboard() {
}
const handleEdit = (projeto: any) => {
// Navegar para página de edição (a ser implementada)
navigate(`/databook/${projeto.id}/editar`)
}
const handleClone = async (projeto: any) => {
try {
// Buscar dados completos do projeto
const { data: projetoCompleto } = await supabase
.from('projetos')
.select('*, databooks_mestres(*)')
@@ -163,7 +148,6 @@ export default function Dashboard() {
return
}
// Criar cópia do projeto
const { data: novoProjeto } = await supabase
.from('projetos')
.insert([{
@@ -177,9 +161,8 @@ export default function Dashboard() {
.select()
.single()
if (novoProjeto && projetoCompleto.databooks_mestres) {
// Copiar databook mestre
const databookMestre = projetoCompleto.databooks_mestres
if (novoProjeto && (projetoCompleto as any).databooks_mestres) {
const databookMestre = (projetoCompleto as any).databooks_mestres
await supabase
.from('databooks_mestres')
.insert([{
@@ -206,7 +189,6 @@ export default function Dashboard() {
queryClient.invalidateQueries({ queryKey: ['projetos'] })
toast.success('Projeto clonado com sucesso!')
} catch (error) {
console.error('Erro ao clonar projeto:', error)
toast.error('Erro ao clonar projeto')
}
}
@@ -216,25 +198,25 @@ export default function Dashboard() {
name: 'Total Projetos',
value: projetos?.length || 0,
icon: FolderOpen,
color: 'bg-blue-500',
color: 'bg-blue-600',
},
{
name: 'Em Andamento',
value: projetos?.filter(p => p.status === 'em_andamento').length || 0,
icon: Clock,
color: 'bg-yellow-500',
color: 'bg-amber-500',
},
{
name: 'Finalizados',
value: projetos?.filter(p => p.status === 'finalizado').length || 0,
icon: CheckCircle,
color: 'bg-green-500',
color: 'bg-emerald-500',
},
{
name: 'Templates',
value: templates?.length || 0,
value: (templates as any)?.length || 0,
icon: FileText,
color: 'bg-purple-500',
color: 'bg-violet-600',
},
]
@@ -250,126 +232,123 @@ export default function Dashboard() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Dashboard</h1>
<Button onClick={() => navigate('/databook/novo')}>
<Button onClick={() => navigate('/databook/novo')} className="shadow-lg shadow-primary/20">
Novo Databook
</Button>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{stats.map((stat) => (
<div key={stat.name} className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
{stats.map((stat, i) => (
<motion.div
key={stat.name}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.1 }}
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-6 hover:shadow-md transition-shadow"
>
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600 dark:text-gray-400">{stat.name}</p>
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">{stat.name}</p>
<p className="text-3xl font-bold text-gray-900 dark:text-gray-100 mt-2">{stat.value}</p>
</div>
<div className={`${stat.color} p-3 rounded-lg`}>
<div className={`${stat.color} p-3 rounded-xl shadow-inner`}>
<stat.icon className="text-white" size={24} />
</div>
</div>
</div>
</motion.div>
))}
</div>
{/* Recent Projects */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden"
>
<div className="p-6 border-b border-gray-100 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Projetos Recentes</h2>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 dark:bg-gray-900">
<thead className="bg-gray-50/50 dark:bg-gray-900/50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Projeto
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Cliente
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Status
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Progresso
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Ações
</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Projeto</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Cliente</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Progresso</th>
<th className="px-6 py-4 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Ações</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
<tbody className="divide-y divide-gray-100 dark:divide-gray-700">
<AnimatePresence>
{projetos && projetos.length > 0 ? (
projetos.map((projeto) => (
<tr key={projeto.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
projetos.map((projeto: any, i) => (
<motion.tr
key={projeto.id}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.5 + (i * 0.05) }}
className="hover:bg-gray-50/50 dark:hover:bg-gray-700/50 transition-colors"
>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">{projeto.nome_projeto}</div>
<div className="text-sm text-gray-500 dark:text-gray-400">{projeto.numero_projeto}</div>
<div className="text-sm font-semibold text-gray-900 dark:text-gray-100">{projeto.nome_projeto}</div>
<div className="text-xs text-gray-500 dark:text-gray-400 font-mono mt-0.5">{projeto.numero_projeto}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
{projeto.clientes?.nome || '-'}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-2 py-1 text-xs font-medium rounded-full ${getStatusColor(projeto.status)}`}>
<span className={`px-2.5 py-1 text-xs font-bold rounded-full uppercase tracking-wider ${getStatusColor(projeto.status)}`}>
{getStatusLabel(projeto.status)}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="w-full bg-gray-200 rounded-full h-2 mr-2">
<div
className="bg-primary h-2 rounded-full"
style={{ width: `${projeto.progresso_percentual}%` }}
></div>
<div className="flex items-center gap-3">
<div className="flex-1 bg-gray-100 dark:bg-gray-700 rounded-full h-2 min-w-[100px]">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${projeto.progresso_percentual || 0}%` }}
className="bg-primary-600 h-full rounded-full shadow-sm"
></motion.div>
</div>
<span className="text-sm text-gray-600 dark:text-gray-300">{projeto.progresso_percentual}%</span>
<span className="text-xs font-bold text-gray-600 dark:text-gray-400">{projeto.progresso_percentual || 0}%</span>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
{[
{ icon: FileText, color: 'text-gray-600 hover:text-blue-600', onClick: () => navigate(`/databook/${projeto.id}`), title: 'Ver detalhes' },
{ icon: Edit, color: 'text-gray-600 hover:text-blue-600', onClick: () => handleEdit(projeto), title: 'Editar' },
{ icon: Copy, color: 'text-gray-600 hover:text-emerald-600', onClick: () => handleClone(projeto), title: 'Clonar' },
{ icon: Trash2, color: 'text-gray-600 hover:text-red-600', onClick: () => handleDelete(projeto), title: 'Deletar' },
].map((action, actionIdx) => (
<button
onClick={() => navigate(`/databook/${projeto.id}`)}
className="text-blue-600 hover:text-blue-800 p-1 hover:bg-blue-50 rounded transition-colors"
title="Ver detalhes"
key={actionIdx}
onClick={action.onClick}
className={`${action.color} p-2 hover:bg-white dark:hover:bg-gray-600 rounded-lg transition-all border border-transparent hover:border-gray-200 dark:hover:border-gray-500 shadow-sm hover:shadow`}
title={action.title}
>
<FileText size={18} />
</button>
<button
onClick={() => handleEdit(projeto)}
className="text-blue-600 hover:text-blue-800 p-1 hover:bg-blue-50 rounded transition-colors"
title="Editar"
>
<Edit size={18} />
</button>
<button
onClick={() => handleClone(projeto)}
className="text-green-600 hover:text-green-800 p-1 hover:bg-green-50 rounded transition-colors"
title="Clonar"
>
<Copy size={18} />
</button>
<button
onClick={() => handleDelete(projeto)}
className="text-red-600 hover:text-red-800 p-1 hover:bg-red-50 rounded transition-colors"
title="Deletar"
>
<Trash2 size={18} />
<action.icon size={16} />
</button>
))}
</div>
</td>
</tr>
</motion.tr>
))
) : (
<tr>
<td colSpan={5} className="px-6 py-12 text-center text-gray-500">
Nenhum projeto encontrado. Crie seu primeiro databook!
<td colSpan={5} className="px-6 py-12 text-center text-gray-500 dark:text-gray-400 italic">
Nenhum projeto encontrado. Comece criando um novo databook.
</td>
</tr>
)}
</AnimatePresence>
</tbody>
</table>
</div>
</div>
</motion.div>
{/* Modal de Confirmação de Exclusão */}
<Modal
@@ -378,13 +357,15 @@ export default function Dashboard() {
title="Deletar Projeto"
>
<div className="space-y-4">
<p className="text-gray-600">
<p className="text-gray-600 dark:text-gray-300">
Tem certeza que deseja deletar o projeto <strong>{projetoToDelete?.nome_projeto}</strong>?
</p>
<p className="text-sm text-gray-500">
Esta ação não pode ser desfeita e todos os dados relacionados serão perdidos.
<div className="bg-red-50 dark:bg-red-900/20 p-3 rounded-lg border border-red-100 dark:border-red-900/30">
<p className="text-sm text-red-700 dark:text-red-400 font-medium">
Esta ação não pode ser desfeita e todos os dados relacionados serão perdidos permanentemente.
</p>
<div className="flex justify-end gap-3">
</div>
<div className="flex justify-end gap-3 pt-4">
<Button
variant="outline"
onClick={() => setDeleteModalOpen(false)}
@@ -392,12 +373,12 @@ export default function Dashboard() {
Cancelar
</Button>
<Button
variant="primary"
variant="danger"
onClick={confirmDelete}
isLoading={deleteMutation.isPending}
className="bg-red-600 hover:bg-red-700"
className="px-8 shadow-lg shadow-red-500/20"
>
Deletar
Sim, Deletar
</Button>
</div>
</div>
@@ -405,4 +386,3 @@ export default function Dashboard() {
</div>
)
}