forked from git.gladyson/openmonetis
chore(plan): remover arquivo PLAN.md
This commit is contained in:
806
PLAN.md
806
PLAN.md
@@ -1,806 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
📊 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
|
|
||||||
Reference in New Issue
Block a user