Files
openmonetis/components/calendario/monthly-calendar.tsx
Felipe Coutinho 4237062bde feat: implementar relatórios de categorias e substituir seleção de período por picker visual
BREAKING CHANGE: Remove feature de seleção de período das preferências do usuário

  Alterações principais:

  - Adiciona sistema completo de relatórios por categoria
    - Cria página /relatorios/categorias com filtros e visualizações
    - Implementa tabela e gráfico de evolução mensal
    - Adiciona funcionalidade de exportação de dados
    - Cria skeleton otimizado para melhor UX de loading

  - Remove feature de seleção de período das preferências
    - Deleta lib/user-preferences/period.ts
    - Remove colunas periodMonthsBefore e periodMonthsAfter do schema
    - Remove todas as referências em 16+ arquivos
    - Atualiza database schema via Drizzle

  - Substitui Select de período por MonthPicker visual
    - Implementa componente PeriodPicker reutilizável
    - Integra shadcn MonthPicker customizado (português, Remix icons)
    - Substitui createMonthOptions em todos os formulários
    - Mantém formato "YYYY-MM" no banco de dados

  - Melhora design da tabela de relatórios
    - Mescla colunas Categoria e Tipo em uma única coluna
    - Substitui badge de tipo por dot colorido discreto
    - Reduz largura da tabela em ~120px
    - Atualiza skeleton para refletir nova estrutura

  - Melhorias gerais de UI
    - Reduz espaçamento entre títulos da sidebar (p-2 → px-2 py-1)
    - Adiciona MonthNavigation para navegação entre períodos
    - Otimiza loading states com skeletons detalhados
2026-01-04 03:03:09 +00:00

127 lines
3.4 KiB
TypeScript

"use client";
import { useCallback, useMemo, useState } from "react";
import { LancamentoDialog } from "@/components/lancamentos/dialogs/lancamento-dialog/lancamento-dialog";
import type {
CalendarDay,
CalendarEvent,
CalendarFormOptions,
CalendarPeriod,
} from "@/components/calendario/types";
import { buildCalendarDays } from "@/components/calendario/utils";
import { CalendarGrid } from "@/components/calendario/calendar-grid";
import { CalendarLegend } from "@/components/calendario/calendar-legend";
import { EventModal } from "@/components/calendario/event-modal";
type MonthlyCalendarProps = {
period: CalendarPeriod;
events: CalendarEvent[];
formOptions: CalendarFormOptions;
};
const parsePeriod = (period: string) => {
const [yearStr, monthStr] = period.split("-");
const year = Number.parseInt(yearStr ?? "", 10);
const month = Number.parseInt(monthStr ?? "", 10);
return { year, monthIndex: month - 1 };
};
export function MonthlyCalendar({
period,
events,
formOptions,
}: MonthlyCalendarProps) {
const { year, monthIndex } = useMemo(
() => parsePeriod(period.period),
[period.period]
);
const eventsByDay = useMemo(() => {
const map = new Map<string, CalendarEvent[]>();
events.forEach((event) => {
const list = map.get(event.date) ?? [];
list.push(event);
map.set(event.date, list);
});
return map;
}, [events]);
const days = useMemo(
() => buildCalendarDays({ year, monthIndex, events: eventsByDay }),
[eventsByDay, monthIndex, year]
);
const [selectedDay, setSelectedDay] = useState<CalendarDay | null>(null);
const [isModalOpen, setModalOpen] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
const [createDate, setCreateDate] = useState<string | null>(null);
const handleOpenCreate = useCallback((date: string) => {
setCreateDate(date);
setModalOpen(false);
setCreateOpen(true);
}, []);
const handleDaySelect = useCallback((day: CalendarDay) => {
setSelectedDay(day);
setModalOpen(true);
}, []);
const handleCreateFromCell = useCallback(
(day: CalendarDay) => {
handleOpenCreate(day.date);
},
[handleOpenCreate]
);
const handleModalClose = useCallback(() => {
setModalOpen(false);
setSelectedDay(null);
}, []);
const handleCreateDialogChange = useCallback((open: boolean) => {
setCreateOpen(open);
if (!open) {
setCreateDate(null);
}
}, []);
return (
<>
<div className="space-y-3">
<CalendarLegend />
<CalendarGrid
days={days}
onSelectDay={handleDaySelect}
onCreateDay={handleCreateFromCell}
/>
</div>
<EventModal
open={isModalOpen}
day={selectedDay}
onClose={handleModalClose}
onCreate={handleOpenCreate}
/>
<LancamentoDialog
mode="create"
open={createOpen}
onOpenChange={handleCreateDialogChange}
pagadorOptions={formOptions.pagadorOptions}
splitPagadorOptions={formOptions.splitPagadorOptions}
defaultPagadorId={formOptions.defaultPagadorId}
contaOptions={formOptions.contaOptions}
cartaoOptions={formOptions.cartaoOptions}
categoriaOptions={formOptions.categoriaOptions}
estabelecimentos={formOptions.estabelecimentos}
defaultPeriod={period.period}
defaultPurchaseDate={createDate ?? undefined}
/>
</>
);
}