Blog
💻 Technical··3 min read

Como Construí o TimeBudget em 2 Horas

O processo técnico por trás de um app de gestão de tempo 100% client-side. Next.js, localStorage, Recharts e zero backend.

TimeBudget é um app de gestão de tempo que roda 100% no navegador. Sem servidor, sem banco de dados, sem autenticação. Aqui está como construí.

Stack

  • Next.js 14 (App Router)
  • TypeScript
  • Tailwind CSS
  • Recharts (gráficos)
  • localStorage (persistência)
  • Lucide React (ícones)
  • date-fns (manipulação de datas)

Arquitetura

app/
├── page.tsx          # Landing
├── app/page.tsx      # App principal
├── history/page.tsx  # Histórico
├── settings/page.tsx # Configurações
hooks/
├── useLocalStorage.ts
├── useTimeEntries.ts
├── useSettings.ts
types/
└── index.ts

O modelo de dados

Três entidades principais:

interface TimeEntry {
  id: string;
  category: string;
  duration: number; // minutos
  timestamp: string; // ISO
  note?: string;
}

interface Category {
id: string;
name: string;
emoji: string;
color: string;
}

interface Settings {
dailyBudget: number; // minutos
categories: Category[];
theme: 'auto' | 'light' | 'dark';
}

Persistência com localStorage

Criei um hook genérico que sincroniza estado com localStorage:

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    if (typeof window === 'undefined') return initialValue;
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);

return [value, setValue] as const;
}

Hooks específicos (useTimeEntries, useSettings) encapsulam a lógica de negócio.

Cálculo do saldo

O saldo é calculado em tempo real:

const todayEntries = entries.filter(e => 
  isSameDay(new Date(e.timestamp), new Date())
);

const usedMinutes = todayEntries.reduce(
(sum, e) => sum + e.duration, 0
);

const remainingMinutes = settings.dailyBudget - usedMinutes;

Gráficos com Recharts

Usei Recharts pela simplicidade. Um gráfico de pizza para distribuição do dia:

<PieChart>
  <Pie
    data={categoryData}
    dataKey="value"
    nameKey="name"
    cx="50%"
    cy="50%"
    innerRadius={60}
    outerRadius={80}
  >
    {categoryData.map((entry, index) => (
      <Cell key={index} fill={entry.color} />
    ))}
  </Pie>
  <Tooltip />
</PieChart>

Design decisions

Por que não usar um backend?

  1. Privacidade — Dados sensíveis (como você gasta seu tempo) não devem ir pra servidor de terceiros
  2. Simplicidade — Menos infraestrutura = menos pontos de falha
  3. Custo — Zero servidor = zero custo de operação
  4. Offline — Funciona sem internet

Por que 7 categorias padrão?

Baseado em pesquisa sobre como pessoas dividem o tempo:
  • Trabalho (obrigação)
  • Estudo (crescimento)
  • Casa (manutenção)
  • Família (relacionamentos)
  • Lazer (diversão)
  • Saúde (exercício, médico)
  • Descanso (sono, recuperação)
Cobre 90% dos casos. O resto você cria custom.

Por que minutos e não horas?

Granularidade. 15 minutos de meditação é válido. 0.25h é estranho.

Performance

  • First Load JS: ~200KB (app page)
  • Lighthouse Performance: 90+
  • Time to Interactive: <2s
Bundle relativamente leve pra uma SPA com gráficos.

O que aprendi

  1. localStorage é suficiente — Pra muitos casos, você não precisa de banco de dados
  2. Hooks encapsulam bem — Separar lógica de storage da UI deixa o código limpo
  3. Recharts é bom o suficiente — Não precisa de D3 pra gráficos simples
  4. PWA de graça — Next.js + manifest = funciona offline

Código aberto

Tudo no GitHub: AutonomousClara/timebudget


2 horas de código, anos de produtividade.