mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 19:01:47 +00:00
feat(reports): melhora notas, calendario e analises
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
import { DayCell } from "@/components/calendario/day-cell";
|
||||
|
||||
import type { CalendarDay } from "@/components/calendario/types";
|
||||
import { WEEK_DAYS_SHORT } from "@/components/calendario/utils";
|
||||
import type { CalendarDay } from "@/lib/types/calendario";
|
||||
import { WEEK_DAYS_SHORT } from "@/lib/utils/calendario";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
|
||||
type CalendarGridProps = {
|
||||
@@ -18,10 +18,10 @@ export function CalendarGrid({
|
||||
onCreateDay,
|
||||
}: CalendarGridProps) {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg bg-card drop-shadow-xs px-2">
|
||||
<div className="overflow-hidden rounded-lg bg-card drop-shadow-xs border-none">
|
||||
<div className="grid grid-cols-7 text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{WEEK_DAYS_SHORT.map((dayName) => (
|
||||
<span key={dayName} className="px-3 py-2 text-center">
|
||||
<span key={dayName} className="px-3 py-2 text-center text-primary">
|
||||
{dayName}
|
||||
</span>
|
||||
))}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { EVENT_TYPE_STYLES } from "@/components/calendario/day-cell";
|
||||
import type { CalendarEvent } from "@/components/calendario/types";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import type { CalendarEvent } from "@/lib/types/calendario";
|
||||
import StatusDot from "../shared/status-dot";
|
||||
import { Card } from "../ui/card";
|
||||
|
||||
const LEGEND_ITEMS: Array<{
|
||||
type?: CalendarEvent["type"];
|
||||
@@ -17,17 +18,17 @@ const LEGEND_ITEMS: Array<{
|
||||
|
||||
export function CalendarLegend() {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-3 rounded-sm border border-border/60 bg-muted/20 p-2 text-xs font-medium text-muted-foreground">
|
||||
<Card className="flex flex-row gap-2 p-2 text-sm">
|
||||
{LEGEND_ITEMS.map((item, index) => {
|
||||
const dotColor =
|
||||
item.dotColor || (item.type ? EVENT_TYPE_STYLES[item.type].dot : "");
|
||||
return (
|
||||
<span key={item.type || index} className="flex items-center gap-2">
|
||||
<span className={cn("size-3 rounded-full", dotColor)} />
|
||||
<StatusDot color={dotColor} />
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { RiAddLine } from "@remixicon/react";
|
||||
import type { KeyboardEvent, MouseEvent } from "react";
|
||||
import type { CalendarDay, CalendarEvent } from "@/components/calendario/types";
|
||||
import type { CalendarDay, CalendarEvent } from "@/lib/types/calendario";
|
||||
import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { EVENT_TYPE_STYLES } from "@/components/calendario/day-cell";
|
||||
import type { CalendarDay, CalendarEvent } from "@/components/calendario/types";
|
||||
import MoneyValues from "@/components/shared/money-values";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -12,9 +12,10 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import type { CalendarDay, CalendarEvent } from "@/lib/types/calendario";
|
||||
import { friendlyDate, parseLocalDateString } from "@/lib/utils/date";
|
||||
import { formatFinancialDateLabel } from "@/lib/utils/financial-dates";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import MoneyValues from "../money-values";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { Card } from "../ui/card";
|
||||
|
||||
@@ -93,9 +94,11 @@ const renderLancamento = (
|
||||
const renderBoleto = (event: Extract<CalendarEvent, { type: "boleto" }>) => {
|
||||
const isPaid = Boolean(event.lancamento.isSettled);
|
||||
const dueDate = event.lancamento.dueDate;
|
||||
const formattedDueDate = dueDate
|
||||
? new Intl.DateTimeFormat("pt-BR").format(new Date(dueDate))
|
||||
: null;
|
||||
const dueDateLabel = formatFinancialDateLabel(dueDate, "Vence em", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
<EventCard type="boleto">
|
||||
@@ -106,9 +109,9 @@ const renderBoleto = (event: Extract<CalendarEvent, { type: "boleto" }>) => {
|
||||
{event.lancamento.name}
|
||||
</span>
|
||||
|
||||
{formattedDueDate && (
|
||||
{dueDateLabel && (
|
||||
<span className="text-xs text-muted-foreground leading-tight">
|
||||
Vence em {formattedDueDate}
|
||||
{dueDateLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { CalendarGrid } from "@/components/calendario/calendar-grid";
|
||||
import { CalendarLegend } from "@/components/calendario/calendar-legend";
|
||||
import { EventModal } from "@/components/calendario/event-modal";
|
||||
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 { LancamentoDialog } from "@/components/lancamentos/dialogs/lancamento-dialog/lancamento-dialog";
|
||||
} from "@/lib/types/calendario";
|
||||
import { buildCalendarDays } from "@/lib/utils/calendario";
|
||||
import { parsePeriod } from "@/lib/utils/period";
|
||||
|
||||
type MonthlyCalendarProps = {
|
||||
period: CalendarPeriod;
|
||||
@@ -19,23 +20,13 @@ type MonthlyCalendarProps = {
|
||||
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 { year, month } = parsePeriod(period.period);
|
||||
const monthIndex = month - 1;
|
||||
|
||||
const eventsByDay = useMemo(() => {
|
||||
const map = new Map<string, CalendarEvent[]>();
|
||||
@@ -57,35 +48,32 @@ export function MonthlyCalendar({
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [createDate, setCreateDate] = useState<string | null>(null);
|
||||
|
||||
const handleOpenCreate = useCallback((date: string) => {
|
||||
const handleOpenCreate = (date: string) => {
|
||||
setCreateDate(date);
|
||||
setModalOpen(false);
|
||||
setCreateOpen(true);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const handleDaySelect = useCallback((day: CalendarDay) => {
|
||||
const handleDaySelect = (day: CalendarDay) => {
|
||||
setSelectedDay(day);
|
||||
setModalOpen(true);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const handleCreateFromCell = useCallback(
|
||||
(day: CalendarDay) => {
|
||||
handleOpenCreate(day.date);
|
||||
},
|
||||
[handleOpenCreate],
|
||||
);
|
||||
const handleCreateFromCell = (day: CalendarDay) => {
|
||||
handleOpenCreate(day.date);
|
||||
};
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
const handleModalClose = () => {
|
||||
setModalOpen(false);
|
||||
setSelectedDay(null);
|
||||
}, []);
|
||||
};
|
||||
|
||||
const handleCreateDialogChange = useCallback((open: boolean) => {
|
||||
const handleCreateDialogChange = (open: boolean) => {
|
||||
setCreateOpen(open);
|
||||
if (!open) {
|
||||
setCreateDate(null);
|
||||
}
|
||||
}, []);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import type {
|
||||
LancamentoItem,
|
||||
SelectOption,
|
||||
} from "@/components/lancamentos/types";
|
||||
|
||||
export type CalendarEvent =
|
||||
| {
|
||||
id: string;
|
||||
type: "lancamento";
|
||||
date: string;
|
||||
lancamento: LancamentoItem;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
type: "boleto";
|
||||
date: string;
|
||||
lancamento: LancamentoItem;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
type: "cartao";
|
||||
date: string;
|
||||
card: {
|
||||
id: string;
|
||||
name: string;
|
||||
dueDay: string;
|
||||
closingDay: string;
|
||||
brand: string | null;
|
||||
status: string;
|
||||
logo: string | null;
|
||||
totalDue: number | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type CalendarPeriod = {
|
||||
period: string;
|
||||
monthName: string;
|
||||
year: number;
|
||||
};
|
||||
|
||||
export type CalendarDay = {
|
||||
date: string;
|
||||
label: string;
|
||||
isCurrentMonth: boolean;
|
||||
isToday: boolean;
|
||||
events: CalendarEvent[];
|
||||
};
|
||||
|
||||
export type CalendarFormOptions = {
|
||||
pagadorOptions: SelectOption[];
|
||||
splitPagadorOptions: SelectOption[];
|
||||
defaultPagadorId: string | null;
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
estabelecimentos: string[];
|
||||
};
|
||||
|
||||
export type CalendarData = {
|
||||
events: CalendarEvent[];
|
||||
formOptions: CalendarFormOptions;
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
import type { CalendarDay, CalendarEvent } from "@/components/calendario/types";
|
||||
|
||||
export const formatDateKey = (date: Date) => date.toISOString().slice(0, 10);
|
||||
|
||||
const getWeekdayIndex = (date: Date) => {
|
||||
const day = date.getUTCDay(); // 0 (domingo) - 6 (sábado)
|
||||
// Ajusta para segunda-feira como primeiro dia
|
||||
return day === 0 ? 6 : day - 1;
|
||||
};
|
||||
|
||||
export const buildCalendarDays = ({
|
||||
year,
|
||||
monthIndex,
|
||||
events,
|
||||
}: {
|
||||
year: number;
|
||||
monthIndex: number;
|
||||
events: Map<string, CalendarEvent[]>;
|
||||
}): CalendarDay[] => {
|
||||
const startOfMonth = new Date(Date.UTC(year, monthIndex, 1));
|
||||
const offset = getWeekdayIndex(startOfMonth);
|
||||
const startDate = new Date(Date.UTC(year, monthIndex, 1 - offset));
|
||||
const totalCells = 42; // 6 semanas
|
||||
const now = new Date();
|
||||
const todayKey = formatDateKey(
|
||||
new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate())),
|
||||
);
|
||||
|
||||
const days: CalendarDay[] = [];
|
||||
|
||||
for (let index = 0; index < totalCells; index += 1) {
|
||||
const currentDate = new Date(startDate);
|
||||
currentDate.setUTCDate(startDate.getUTCDate() + index);
|
||||
|
||||
const dateKey = formatDateKey(currentDate);
|
||||
const isCurrentMonth = currentDate.getUTCMonth() === monthIndex;
|
||||
const dateLabel = currentDate.getUTCDate().toString();
|
||||
const eventsForDay = events.get(dateKey) ?? [];
|
||||
|
||||
days.push({
|
||||
date: dateKey,
|
||||
label: dateLabel,
|
||||
isCurrentMonth,
|
||||
isToday: dateKey === todayKey,
|
||||
events: eventsForDay,
|
||||
});
|
||||
}
|
||||
|
||||
return days;
|
||||
};
|
||||
|
||||
export const WEEK_DAYS_SHORT = [
|
||||
"Seg",
|
||||
"Ter",
|
||||
"Qua",
|
||||
"Qui",
|
||||
"Sex",
|
||||
"Sáb",
|
||||
"Dom",
|
||||
];
|
||||
Reference in New Issue
Block a user