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:
@@ -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",
|
||||
|
||||
@@ -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">
|
||||
{projetos && projetos.length > 0 ? (
|
||||
projetos.map((projeto) => (
|
||||
<tr key={projeto.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
<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>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
||||
{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)}`}>
|
||||
{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>
|
||||
<tbody className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<AnimatePresence>
|
||||
{projetos && projetos.length > 0 ? (
|
||||
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-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-700 dark:text-gray-300">
|
||||
{projeto.clientes?.nome || '-'}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<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 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-xs font-bold text-gray-600 dark:text-gray-400">{projeto.progresso_percentual || 0}%</span>
|
||||
</div>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-300">{projeto.progresso_percentual}%</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<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"
|
||||
>
|
||||
<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} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<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
|
||||
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}
|
||||
>
|
||||
<action.icon size={16} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</motion.tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<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>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-6 py-12 text-center text-gray-500">
|
||||
Nenhum projeto encontrado. Crie seu primeiro 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.
|
||||
</p>
|
||||
<div className="flex justify-end gap-3">
|
||||
<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>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user