feat(plan): adicionar análise e sugestões para OpenSheets

- Criação do documento PLAN.md com análise da aplicação
- Identificação de pontos fortes e sugestões de novas features
- Inclusão de melhorias técnicas e métricas de sucesso
This commit is contained in:
Felipe Coutinho
2025-12-09 17:24:07 +00:00
parent 0c445ee4a5
commit 95d6a45a95

806
PLAN.md Normal file
View File

@@ -0,0 +1,806 @@
---
📊 Análise e Sugestões para OpenSheets
🎯 Resumo Executivo
O OpenSheets é uma aplicação financeira bem estruturada com 184 componentes, 15 widgets de dashboard, e um design system coeso baseado em cores terracota. A análise identificou pontos fortes significativos e
oportunidades estratégicas para melhorias.
---
✅ Pontos Fortes Identificados
Arquitetura
- ✨ Server-first com Next.js 15 App Router
- 🚀 Fetching paralelo otimizado (18+ requests simultâneos)
- 🔒 Modo privacidade bem implementado
- 🎨 Design system consistente (OKLCH color space)
Componentes
- 📦 40+ componentes shadcn/ui bem organizados
- ♻️ Alta reutilização de componentes
- 🎭 Sistema de skeletons completo
- 🌙 Suporte total a tema dark/light
---
🚀 Sugestões de Novas Features
1. Análise Preditiva e Forecasting 🔮
Prioridade: Alta | Complexidade: Média
// Nova página: app/(dashboard)/previsoes/
Features:
- Previsão de gastos mensais baseada em histórico
- Alerta de contas a vencer na próxima semana
- Projeção de saldo futuro considerando despesas recorrentes
- Machine learning simples para detectar padrões de gasto
Componentes sugeridos:
- ForecastChart - Gráfico de linha com projeções
- UpcomingBillsWidget - Widget de contas a vencer
- SavingsGoalTracker - Acompanhamento de metas de economia
---
2. Metas Financeiras (Goals) 🎯
Prioridade: Alta | Complexidade: Média
// Nova tabela no schema:
export const metas = pgTable("metas", {
id: uuid("id").primaryKey().defaultRandom(),
userId: uuid("user_id").notNull().references(() => user.id),
nome: text("nome").notNull(),
valorAlvo: numeric("valor_alvo", { precision: 12, scale: 2 }).notNull(),
valorAtual: numeric("valor_atual", { precision: 12, scale: 2 }).default("0"),
prazo: timestamp("prazo"),
categoriaId: uuid("categoria_id").references(() => categorias.id),
tipo: text("tipo").notNull(), // 'economia', 'quitacao_divida', 'compra'
});
Features:
- Criar metas de economia (ex: "Viagem para Europa - R$ 10.000")
- Vincular transações às metas
- Dashboard de progresso visual
- Sugestões automáticas de quanto economizar mensalmente
---
3. Relatórios Exportáveis 📄
Prioridade: Média | Complexidade: Baixa
Formatos:
- PDF com gráficos (usando jsPDF + html2canvas)
- Excel/CSV detalhado (usando xlsx)
- JSON para backup completo
Tipos de relatório:
- Extrato mensal consolidado
- Análise de gastos por categoria
- Comparativo período a período
- Resumo anual (imposto de renda)
Código sugerido:
// lib/reports/generate-pdf-report.ts
import { jsPDF } from 'jspdf';
export async function generateMonthlyReport(userId: string, period: string) {
const data = await fetchMonthlyData(userId, period);
const doc = new jsPDF();
// Adicionar logo, gráficos, tabelas
doc.save(`relatorio-${period}.pdf`);
}
---
4. Modo Comparativo de Períodos 📊
Prioridade: Média | Complexidade: Baixa
UI sugerida:
<MonthPicker
mode="comparison"
periods={[currentPeriod, comparePeriod]}
/>
Features:
- Comparar dois meses lado a lado
- Ver variação percentual por categoria
- Identificar onde economizou/gastou mais
- Gráficos de delta de gastos
---
5. Tags/Etiquetas para Transações 🏷️
Prioridade: Baixa | Complexidade: Baixa
export const tags = pgTable("tags", {
id: uuid("id").primaryKey(),
userId: uuid("user_id").notNull(),
nome: text("nome").notNull(),
cor: text("cor").notNull(), // hex color
});
export const lancamento_tags = pgTable("lancamento_tags", {
lancamentoId: uuid("lancamento_id").references(() => lancamentos.id),
tagId: uuid("tag_id").references(() => tags.id),
});
Use cases:
- Tag "Trabalho" para despesas dedutíveis
- Tag "Emergência" para gastos não planejados
- Tag "Investimento" para rastrear aplicações
- Filtrar dashboard por tags
---
6. Anexos e Comprovantes 📎
Prioridade: Média | Complexidade: Alta
Implementação:
- Upload de imagens/PDFs de notas fiscais
- Armazenamento em storage (S3-compatible ou local)
- OCR para extrair dados automaticamente (Tesseract.js)
- Galeria de comprovantes por transação
export const anexos = pgTable("anexos", {
id: uuid("id").primaryKey(),
lancamentoId: uuid("lancamento_id").references(() => lancamentos.id),
arquivo: text("arquivo_url").notNull(),
tipo: text("tipo").notNull(), // 'imagem', 'pdf'
tamanho: integer("tamanho_bytes"),
});
---
7. Investimentos Tracking 💹
Prioridade: Baixa | Complexidade: Alta
Escopo:
- Registrar compra/venda de ações, FIIs, criptomoedas
- Importação de extratos de corretoras
- Gráfico de evolução patrimonial
- Cálculo de rentabilidade
Nova seção no sidebar:
{
title: "Investimentos",
icon: RiLineChartLine,
href: "/investimentos",
}
---
8. Gamificação e Conquistas 🏆
Prioridade: Baixa | Complexidade: Média
Conquistas sugeridas:
- "Primeiro Mês no Azul" - Receitas > Despesas
- "Economista" - Gastou menos que orçamento 3 meses seguidos
- "Organizado" - Todas transações categorizadas
- "Disciplinado" - 30 dias sem gastos em categoria específica
Implementação:
export const conquistas = pgTable("conquistas", {
id: uuid("id").primaryKey(),
codigo: text("codigo").notNull(), // 'primeiro_mes_azul'
nome: text("nome").notNull(),
descricao: text("descricao"),
icone: text("icone"),
});
export const usuario_conquistas = pgTable("usuario_conquistas", {
userId: uuid("user_id").references(() => user.id),
conquistaId: uuid("conquista_id").references(() => conquistas.id),
desbloqueadaEm: timestamp("desbloqueada_em").defaultNow(),
});
---
9. Notificações e Lembretes 🔔
Prioridade: Alta | Complexidade: Média
Tipos de notificação:
- Lembrete de fatura vencendo em 3 dias
- Orçamento atingindo 80% do limite
- Despesa incomum detectada (> 2x média da categoria)
- Cobrança recorrente não registrada este mês
Implementação:
- Cron job diário verificando condições
- Sistema de notificações in-app
- Opcional: Email notifications (Resend/Nodemailer)
- Web Push Notifications (service worker)
---
10. Importação Automática de Extratos 🔄
Prioridade: Alta | Complexidade: Alta
Métodos:
1. Upload de OFX/CSV - Parser para formatos bancários
2. API Open Banking - Integração com Pluggy/Belvo
3. Email parsing - Ler extratos enviados por email
4. OCR de PDFs - Extrair dados de PDFs bancários
Fluxo sugerido:
// app/(dashboard)/importacao/page.tsx
1. Selecionar conta bancária de destino
2. Upload de arquivo ou conectar via API
3. Pré-visualização das transações
4. Matching automático com categorias (ML)
5. Revisão e confirmação
6. Importação em lote
---
🎨 Melhorias de UI/UX
1. Redesign do Diálogo de Transação 💳
Problema: Diálogo com muitos campos condicionais pode confundir usuários
Solução:
// Wizard multi-step com progresso visual
<TransactionWizard>
<Step1 title="Informações Básicas">
{/_ Nome, valor, data _/}
</Step1>
<Step2 title="Pagamento">
{/_ Método, conta, condição _/}
</Step2>
<Step3 title="Detalhes">
{/_ Categoria, pagador, notas _/}
</Step3>
</TransactionWizard>
Indicador de progresso:
<div className="flex gap-2 mb-4">
<Step active={currentStep === 1} completed={currentStep > 1}>1</Step>
<Step active={currentStep === 2} completed={currentStep > 2}>2</Step>
<Step active={currentStep === 3}>3</Step>
</div>
---
2. Tabela Responsiva com Card View 📱
Problema: Tabelas complexas em mobile têm scroll horizontal
Solução:
// components/lancamentos/table/lancamentos-responsive-view.tsx
export function LancamentosResponsiveView() {
const isMobile = useIsMobile();
if (isMobile) {
return <LancamentosCardList items={data} />;
}
return <LancamentosTable items={data} />;
}
// Card view para mobile
function LancamentoCard({ lancamento }) {
return (
<Card className="p-4">
<div className="flex justify-between items-start">
<div>
<p className="font-semibold">{lancamento.nome}</p>
<p className="text-sm text-muted-foreground">
{lancamento.categoria}
</p>
</div>
<MoneyValue
value={lancamento.valor}
type={lancamento.tipo}
className="text-lg"
/>
</div>
<div className="mt-2 flex gap-2">
<Badge>{lancamento.condicao}</Badge>
<Badge variant="outline">{lancamento.formaPagamento}</Badge>
</div>
</Card>
);
}
---
3. Dashboard Personalizável 🔧
Problema: Todos veem os mesmos 15 widgets
Solução:
// lib/dashboard/widgets/user-widget-preferences.ts
export const widgetPreferences = pgTable("widget_preferences", {
userId: uuid("user_id").references(() => user.id),
widgetId: text("widget_id").notNull(),
ordem: integer("ordem").notNull(),
visivel: boolean("visivel").default(true),
tamanho: text("tamanho"), // 'small', 'medium', 'large'
});
// Drag-and-drop com dnd-kit
import { DndContext, closestCenter } from '@dnd-kit/core';
<DndContext onDragEnd={handleDragEnd}>
<SortableContext items={widgets}>
{widgets.map(widget => (
<SortableWidget key={widget.id} widget={widget} />
))}
</SortableContext>
</DndContext>
Features:
- Reordenar widgets via drag-and-drop
- Ocultar/mostrar widgets
- Redimensionar widgets (grid responsivo)
- Salvar preferências no banco
---
4. Busca Global (Command Palette) ⌘K
Problema: Navegar entre muitas páginas é lento
Solução:
// components/command-palette.tsx
import { RiSearchLine } from '@remixicon/react';
export function CommandPalette() {
const [open, setOpen] = useState(false);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen(true);
}
};
document.addEventListener('keydown', down);
return () => document.removeEventListener('keydown', down);
}, []);
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Buscar transações, categorias, contas..." />
<CommandList>
<CommandGroup heading="Ações Rápidas">
<CommandItem onSelect={openNewTransaction}>
<RiAddLine className="mr-2" />
Nova Transação
</CommandItem>
</CommandGroup>
<CommandGroup heading="Transações Recentes">
{recentTransactions.map(t => (
<CommandItem key={t.id}>{t.nome}</CommandItem>
))}
</CommandGroup>
<CommandGroup heading="Navegação">
<CommandItem onSelect={() => router.push('/dashboard')}>
Dashboard
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}
Ações rápidas:
- Nova transação (Ctrl+K → "nova")
- Ver conta específica
- Buscar transação por nome/valor
- Navegar para qualquer página
- Executar ações (marcar como pago, editar, excluir)
---
5. Onboarding Interativo 🎓
Problema: Novos usuários podem se sentir perdidos
Solução:
// components/onboarding/onboarding-tour.tsx
import { Joyride } from 'react-joyride';
const steps = [
{
target: '.sidebar-nav',
content: 'Aqui você navega entre as diferentes seções',
},
{
target: '[data-tour="new-transaction"]',
content: 'Clique aqui para adicionar sua primeira transação',
},
{
target: '.month-picker',
content: 'Use isto para navegar entre meses',
},
];
export function OnboardingTour() {
const { tourCompleted } = useUserPreferences();
return (
<Joyride
steps={steps}
run={!tourCompleted}
continuous
showSkipButton
/>
);
}
Checklist inicial:
- Criar primeira conta bancária
- Adicionar um cartão de crédito
- Registrar primeira transação
- Definir orçamento mensal
- Explorar dashboard
---
6. Modo Compacto / Densidade Ajustável 📏
Problema: Algumas páginas têm muito espaço em branco
Solução:
// Adicionar ao contexto de preferências
export const densitySettings = {
comfortable: { gap: 6, padding: 6, fontSize: 'text-base' },
normal: { gap: 4, padding: 4, fontSize: 'text-sm' },
compact: { gap: 2, padding: 2, fontSize: 'text-xs' },
};
// Aplicar dinamicamente
<div className={cn(
'grid',
density === 'comfortable' && 'gap-6 p-6',
density === 'normal' && 'gap-4 p-4',
density === 'compact' && 'gap-2 p-2',
)}>
Controle:
// Em ajustes/page.tsx
<Select value={density} onValueChange={setDensity}>
<SelectItem value="compact">Compacto</SelectItem>
<SelectItem value="normal">Normal</SelectItem>
<SelectItem value="comfortable">Confortável</SelectItem>
</Select>
---
7. Indicadores Visuais de Status 🚦
Problema: Difícil ver rapidamente status de contas/faturas
Solução:
// components/status-indicator.tsx
export function StatusIndicator({ status }: { status: string }) {
const config = {
'em-dia': { color: 'green', icon: RiCheckLine, label: 'Em dia' },
'vencendo': { color: 'yellow', icon: RiTimeLine, label: 'Vencendo' },
'atrasado': { color: 'red', icon: RiAlertLine, label: 'Atrasado' },
};
const { color, icon: Icon, label } = config[status];
return (
<Badge variant={color} className="gap-1">
<Icon className="h-3 w-3" />
{label}
</Badge>
);
}
Aplicar em:
- Cards de faturas (verde = paga, amarelo = próxima, vermelho = vencida)
- Boletos (status de pagamento)
- Orçamentos (verde = dentro, amarelo = 80%, vermelho = estourou)
---
8. Gráficos Interativos com Drill-Down 📊
Problema: Gráficos mostram dados mas não permitem explorar
Solução:
// components/dashboard/interactive-category-chart.tsx
<PieChart>
<Pie
data={categoryData}
onClick={(data, index) => {
// Ao clicar em fatia, abrir modal com transações daquela categoria
showCategoryDetails(data.categoryId);
}}
/>
</PieChart>
// Modal de drill-down
function CategoryDetailsModal({ categoryId, period }) {
const transactions = useCategoryTransactions(categoryId, period);
return (
<Dialog>
<DialogHeader>
<DialogTitle>Detalhes - {categoryName}</DialogTitle>
</DialogHeader>
<DialogContent>
<LancamentosTable
data={transactions}
filters={{ categoryId }}
/>
</DialogContent>
</Dialog>
);
}
---
9. Tema de Cores Personalizável 🎨
Problema: Apenas uma cor primária (terracota)
Solução:
// lib/theme/color-themes.ts
export const colorThemes = {
terracotta: { primary: 'oklch(69.18% 0.18855 38.353)' },
ocean: { primary: 'oklch(69.18% 0.18855 220)' },
forest: { primary: 'oklch(69.18% 0.18855 140)' },
sunset: { primary: 'oklch(69.18% 0.18855 25)' },
lavender: { primary: 'oklch(69.18% 0.18855 280)' },
};
// Em ajustes/page.tsx
export function ThemeColorPicker() {
const { colorTheme, setColorTheme } = useTheme();
return (
<div className="flex gap-2">
{Object.entries(colorThemes).map(([name, colors]) => (
<button
key={name}
onClick={() => setColorTheme(name)}
className="w-8 h-8 rounded-full border-2"
style={{ backgroundColor: colors.primary }}
/>
))}
</div>
);
}
---
10. Breadcrumbs e Page Headers Consistentes 🗺️
Problema: Inconsistência em headers entre páginas
Solução:
// components/page-header.tsx
interface PageHeaderProps {
title: string;
description?: string;
breadcrumbs?: { label: string; href?: string }[];
actions?: React.ReactNode;
}
export function PageHeader({
title,
description,
breadcrumbs,
actions
}: PageHeaderProps) {
return (
<div className="mb-6">
{breadcrumbs && (
<Breadcrumb className="mb-2">
{breadcrumbs.map((crumb, i) => (
<BreadcrumbItem key={i}>
{crumb.href ? (
<BreadcrumbLink href={crumb.href}>
{crumb.label}
</BreadcrumbLink>
) : (
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
)}
</BreadcrumbItem>
))}
</Breadcrumb>
)}
<div className="flex justify-between items-start">
<div>
<h1 className="text-3xl font-bold tracking-tight">{title}</h1>
{description && (
<p className="text-muted-foreground mt-2">{description}</p>
)}
</div>
{actions && <div className="flex gap-2">{actions}</div>}
</div>
</div>
);
}
// Uso:
<PageHeader
title="Transações"
description="Gerencie suas receitas e despesas"
breadcrumbs={[
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Transações' },
]}
actions={
<Button>Nova Transação</Button>
}
/>
---
🔧 Melhorias Técnicas
1. Error Boundary Global
// app/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2>Algo deu errado!</h2>
<button onClick={reset}>Tentar novamente</button>
</div>
);
}
2. Analytics e Telemetria
// lib/analytics.ts
export function trackEvent(event: string, properties?: Record<string, any>) {
// Posthog, Mixpanel, ou custom
console.log('[Analytics]', event, properties);
}
// Uso:
trackEvent('transaction_created', { type: 'despesa', amount: 100 });
3. Rate Limiting para Actions
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'),
});
export async function checkRateLimit(userId: string) {
const { success } = await ratelimit.limit(userId);
if (!success) throw new Error('Rate limit exceeded');
}
---
📊 Priorização Sugerida
🔥 Fase 1 - Quick Wins (1-2 semanas)
1. ✅ Breadcrumbs e Page Headers consistentes
2. ✅ Command Palette (⌘K)
3. ✅ Indicadores visuais de status
4. ✅ Modo card para tabelas em mobile
5. ✅ Relatórios PDF básicos
🚀 Fase 2 - Value Boost (1 mês)
1. 🎯 Metas Financeiras
2. 🔮 Análise Preditiva
3. 🔔 Sistema de Notificações
4. 📊 Gráficos interativos com drill-down
5. 🎓 Onboarding interativo
💎 Fase 3 - Diferenciais (2-3 meses)
1. 🔄 Importação automática de extratos (OFX/CSV)
2. 📎 Anexos e comprovantes com OCR
3. 🎨 Temas personalizáveis
4. 🏆 Gamificação e conquistas
5. 💹 Tracking de investimentos
---
🎯 Métricas de Sucesso
Para medir o impacto das melhorias:
1. Engajamento:
- Tempo médio na aplicação
- Frequência de uso (DAU/MAU)
- Transações criadas por usuário/mês
2. Usabilidade:
- Taxa de conclusão de onboarding
- Tempo para criar primeira transação
- Taxa de erro em formulários
3. Performance:
- LCP (Largest Contentful Paint) < 2.5s
- FID (First Input Delay) < 100ms
- CLS (Cumulative Layout Shift) < 0.1
4. Adoção de Features:
- % de usuários usando metas
- % de usuários que personalizam dashboard
- Taxa de uso do command palette
---
📝 Conclusão
O OpenSheets já possui uma base sólida com excelente arquitetura e design. As sugestões focam em:
1. Melhorar a experiência mobile (responsividade avançada)
2. Adicionar inteligência (previsões, notificações, insights)
3. Aumentar a eficiência (command palette, importação automática)
4. Personalização (temas, dashboard, densidade)
5. Gamificação (metas, conquistas) para engajamento
Próximos Passos Recomendados:
1. Validar com usuários quais features têm maior demanda
2. Implementar quick wins da Fase 1
3. A/B testing de novos designs
4. Iteração baseada em feedback