mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
style(ui): polimento visual — tema, cards, dark mode e landing page
Raio de borda global 0.625rem → 0.7rem; ajustes finos em --card e --border. DotPattern removido do layout, tela de auth e landing page. Account-card redesenhado (cores de saldo, tooltip de flags de exclusão). Budget-card, card-item, calendário (day-cell, event-modal) com layout revisado. Auth-card-shell simplificado (sem glassmorphism/blob). Landing page com mainFeatures + extraFeatures em grid único e dark mode nos botões de CTA. Imagens de preview da landing atualizadas. CSS --data-7..10 removidas. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { DayCell } from "@/features/calendar/components/day-cell";
|
||||
|
||||
import type { CalendarDay } from "@/shared/lib/types/calendar";
|
||||
import { WEEK_DAYS_SHORT } from "@/shared/utils/calendar";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
|
||||
type CalendarGridProps = {
|
||||
days: CalendarDay[];
|
||||
@@ -18,21 +16,18 @@ export function CalendarGrid({
|
||||
onCreateDay,
|
||||
}: CalendarGridProps) {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg bg-card drop-shadow-xs border-none">
|
||||
<div className="overflow-hidden rounded-lg border p-2">
|
||||
<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="text-center">
|
||||
{dayName}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-px bg-border/60 px-px pb-px pt-px">
|
||||
<div className="grid grid-cols-7 gap-px px-px pb-px pt-px">
|
||||
{days.map((day) => (
|
||||
<div
|
||||
key={day.date}
|
||||
className={cn("h-[150px] bg-card p-0.5", !day.isCurrentMonth && "")}
|
||||
>
|
||||
<div key={day.date} className="h-[150px] p-0.5">
|
||||
<DayCell day={day} onSelect={onSelectDay} onCreate={onCreateDay} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import { EVENT_TYPE_STYLES } from "@/features/calendar/components/day-cell";
|
||||
import StatusDot from "@/shared/components/status-dot";
|
||||
import { Card } from "@/shared/components/ui/card";
|
||||
import type { CalendarEvent } from "@/shared/lib/types/calendar";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
|
||||
const LEGEND_ITEMS: Array<{
|
||||
type?: CalendarEvent["type"];
|
||||
label: string;
|
||||
dotColor?: string;
|
||||
}> = [
|
||||
{ type: "transaction", label: "Lançamentos" },
|
||||
{ type: "boleto", label: "Boleto com vencimento" },
|
||||
{ type: "card", label: "Vencimento de cartão" },
|
||||
{ label: "Pagamento fatura", dotColor: "bg-success" },
|
||||
const LEGEND_ITEMS = [
|
||||
{ label: "Lançamentos", ...EVENT_TYPE_STYLES.transaction },
|
||||
{ label: "Boletos", ...EVENT_TYPE_STYLES.boleto },
|
||||
{ label: "Fatura de Cartão", ...EVENT_TYPE_STYLES.card },
|
||||
];
|
||||
|
||||
export function CalendarLegend() {
|
||||
return (
|
||||
<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">
|
||||
<StatusDot color={dotColor} />
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</Card>
|
||||
<ul className="flex items-center justify-start gap-2 px-1">
|
||||
{LEGEND_ITEMS.map((item) => (
|
||||
<li
|
||||
key={item.label}
|
||||
className={cn(
|
||||
"flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium",
|
||||
item.wrapper,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn("size-1.5 shrink-0 rounded-full", item.dot)}
|
||||
aria-hidden
|
||||
/>
|
||||
{item.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { RiAddLine } from "@remixicon/react";
|
||||
import { RiAddLine, RiCheckboxCircleFill } from "@remixicon/react";
|
||||
import type { KeyboardEvent, MouseEvent } from "react";
|
||||
import type { CalendarDay, CalendarEvent } from "@/shared/lib/types/calendar";
|
||||
import { currencyFormatter } from "@/shared/utils/currency";
|
||||
@@ -14,44 +14,33 @@ type DayCellProps = {
|
||||
|
||||
export const EVENT_TYPE_STYLES: Record<
|
||||
CalendarEvent["type"],
|
||||
{ wrapper: string; dot: string; accent?: string }
|
||||
{ wrapper: string; dot: string }
|
||||
> = {
|
||||
transaction: {
|
||||
wrapper:
|
||||
"bg-warning/10 text-warning dark:bg-warning/5 dark:text-warning border-l-4 border-warning",
|
||||
dot: "bg-warning",
|
||||
wrapper: "bg-primary/10 text-primary dark:bg-primary/5 dark:text-primary",
|
||||
dot: "bg-primary",
|
||||
},
|
||||
boleto: {
|
||||
wrapper:
|
||||
"bg-info/10 text-info dark:bg-info/5 dark:text-info border-l-4 border-info",
|
||||
wrapper: "bg-info/10 text-info dark:bg-info/5 dark:text-info",
|
||||
dot: "bg-info",
|
||||
},
|
||||
card: {
|
||||
wrapper:
|
||||
"bg-violet-100 text-violet-600 dark:bg-violet-900/10 dark:text-violet-50 border-l-4 border-violet-500",
|
||||
dot: "bg-violet-600",
|
||||
"bg-violet-100 text-violet-600 dark:bg-violet-900/10 dark:text-violet-500",
|
||||
dot: "bg-violet-600 dark:bg-violet-500",
|
||||
},
|
||||
};
|
||||
|
||||
const eventStyles = EVENT_TYPE_STYLES;
|
||||
|
||||
const formatCurrencyValue = (value: number | null | undefined) =>
|
||||
currencyFormatter.format(Math.abs(value ?? 0));
|
||||
|
||||
const formatAmount = (event: Extract<CalendarEvent, { type: "transaction" }>) =>
|
||||
formatCurrencyValue(event.transaction.amount);
|
||||
|
||||
const buildEventLabel = (event: CalendarEvent) => {
|
||||
switch (event.type) {
|
||||
case "transaction": {
|
||||
case "transaction":
|
||||
case "boleto":
|
||||
return event.transaction.name;
|
||||
}
|
||||
case "boleto": {
|
||||
return event.transaction.name;
|
||||
}
|
||||
case "card": {
|
||||
case "card":
|
||||
return event.card.name;
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -59,60 +48,48 @@ const buildEventLabel = (event: CalendarEvent) => {
|
||||
|
||||
const buildEventComplement = (event: CalendarEvent) => {
|
||||
switch (event.type) {
|
||||
case "transaction": {
|
||||
return formatAmount(event);
|
||||
}
|
||||
case "boleto": {
|
||||
case "transaction":
|
||||
case "boleto":
|
||||
return formatCurrencyValue(event.transaction.amount);
|
||||
}
|
||||
case "card": {
|
||||
if (event.card.totalDue !== null) {
|
||||
return formatCurrencyValue(event.card.totalDue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case "card":
|
||||
return event.card.totalDue !== null
|
||||
? formatCurrencyValue(event.card.totalDue)
|
||||
: null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const isPagamentoFatura = (event: CalendarEvent) => {
|
||||
return (
|
||||
event.type === "transaction" &&
|
||||
event.transaction.name.startsWith("Pagamento fatura -")
|
||||
);
|
||||
};
|
||||
|
||||
const getEventStyle = (event: CalendarEvent) => {
|
||||
if (isPagamentoFatura(event)) {
|
||||
return {
|
||||
wrapper:
|
||||
"bg-success/10 text-success dark:bg-success/5 dark:text-success border-l-4 border-success",
|
||||
dot: "bg-success",
|
||||
};
|
||||
}
|
||||
return eventStyles[event.type];
|
||||
const isPaid = (event: CalendarEvent) => {
|
||||
if (event.type === "boleto") return Boolean(event.transaction.isSettled);
|
||||
if (event.type === "card") return event.card.isPaid;
|
||||
return false;
|
||||
};
|
||||
|
||||
const DayEventPreview = ({ event }: { event: CalendarEvent }) => {
|
||||
const complement = buildEventComplement(event);
|
||||
const label = buildEventLabel(event);
|
||||
const style = getEventStyle(event);
|
||||
const style = EVENT_TYPE_STYLES[event.type];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-2 rounded p-1 text-xs",
|
||||
"flex w-full items-center justify-between gap-2 rounded-md px-2 py-1 text-xs",
|
||||
style.wrapper,
|
||||
)}
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
<span
|
||||
className={cn("size-1.5 shrink-0 rounded-full", style.dot)}
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="truncate">{label}</span>
|
||||
{isPaid(event) && (
|
||||
<RiCheckboxCircleFill className="size-3.5 shrink-0 text-success" />
|
||||
)}
|
||||
</div>
|
||||
{complement ? (
|
||||
<span className={cn("shrink-0 font-medium", style.accent ?? "text-xs")}>
|
||||
{complement}
|
||||
</span>
|
||||
<span className="shrink-0 font-medium">{complement}</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
@@ -143,8 +120,8 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) {
|
||||
onClick={() => onSelect(day)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={cn(
|
||||
"group flex h-full cursor-pointer flex-col gap-1.5 rounded-lg border border-transparent bg-card/80 p-2 text-left transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:border-primary/40 hover:bg-primary/5 dark:hover:bg-accent",
|
||||
!day.isCurrentMonth && "opacity-60",
|
||||
"group flex h-full cursor-pointer flex-col gap-1.5 rounded-lg border bg-card/80 p-2 text-left transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:border-primary/40 hover:bg-primary/5 dark:hover:bg-accent",
|
||||
!day.isCurrentMonth && "bg-muted/20 opacity-60",
|
||||
day.isToday && "border-primary/70 bg-primary/5 hover:border-primary",
|
||||
)}
|
||||
>
|
||||
@@ -159,14 +136,16 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) {
|
||||
>
|
||||
{day.label}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCreateClick}
|
||||
className="flex size-6 items-center justify-center rounded-full bg-muted text-muted-foreground opacity-0 transition-all group-hover:opacity-100 hover:bg-primary/20 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
|
||||
aria-label={`Criar lançamento em ${day.date}`}
|
||||
>
|
||||
<RiAddLine className="size-3.5" />
|
||||
</button>
|
||||
{day.isCurrentMonth && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCreateClick}
|
||||
className="flex size-6 items-center justify-center rounded-full bg-muted text-muted-foreground opacity-0 transition-all group-hover:opacity-100 hover:bg-primary/20 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
|
||||
aria-label={`Criar lançamento em ${day.date}`}
|
||||
>
|
||||
<RiAddLine className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col gap-1.5">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { RiCalendarEventLine } from "@remixicon/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { EVENT_TYPE_STYLES } from "@/features/calendar/components/day-cell";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
@@ -29,17 +30,13 @@ type EventModalProps = {
|
||||
const EventCard = ({
|
||||
children,
|
||||
type,
|
||||
isPagamentoFatura = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
type: CalendarEvent["type"];
|
||||
isPagamentoFatura?: boolean;
|
||||
}) => {
|
||||
const style = isPagamentoFatura
|
||||
? { dot: "bg-success" }
|
||||
: EVENT_TYPE_STYLES[type];
|
||||
const style = EVENT_TYPE_STYLES[type];
|
||||
return (
|
||||
<Card className="flex flex-row gap-2 p-3 mb-1">
|
||||
<Card className="flex flex-row gap-2 p-3">
|
||||
<span
|
||||
className={cn("mt-1 size-3 shrink-0 rounded-full", style.dot)}
|
||||
aria-hidden
|
||||
@@ -49,41 +46,34 @@ const EventCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
const DATE_FORMAT: Intl.DateTimeFormatOptions = {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
};
|
||||
|
||||
const renderLancamento = (
|
||||
event: Extract<CalendarEvent, { type: "transaction" }>,
|
||||
) => {
|
||||
const isReceita = event.transaction.transactionType === "Receita";
|
||||
const isPagamentoFatura =
|
||||
event.transaction.name.startsWith("Pagamento fatura -");
|
||||
|
||||
return (
|
||||
<EventCard type="transaction" isPagamentoFatura={isPagamentoFatura}>
|
||||
<EventCard type="transaction">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span
|
||||
className={`text-sm font-medium leading-tight ${
|
||||
isPagamentoFatura && "text-success"
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm font-medium leading-tight">
|
||||
{event.transaction.name}
|
||||
</span>
|
||||
|
||||
<div className="flex gap-1">
|
||||
<Badge variant={"outline"}>{event.transaction.categoriaName}</Badge>
|
||||
</div>
|
||||
<Badge variant="outline">{event.transaction.categoriaName}</Badge>
|
||||
</div>
|
||||
<span
|
||||
<MoneyValues
|
||||
showPositiveSign
|
||||
className={cn(
|
||||
"text-sm font-medium whitespace-nowrap",
|
||||
"text-base whitespace-nowrap font-medium",
|
||||
isReceita ? "text-success" : "text-foreground",
|
||||
)}
|
||||
>
|
||||
<MoneyValues
|
||||
showPositiveSign
|
||||
className="text-base"
|
||||
amount={event.transaction.amount}
|
||||
/>
|
||||
</span>
|
||||
amount={event.transaction.amount}
|
||||
/>
|
||||
</div>
|
||||
</EventCard>
|
||||
);
|
||||
@@ -91,59 +81,80 @@ const renderLancamento = (
|
||||
|
||||
const renderBoleto = (event: Extract<CalendarEvent, { type: "boleto" }>) => {
|
||||
const isPaid = Boolean(event.transaction.isSettled);
|
||||
const dueDate = event.transaction.dueDate;
|
||||
const dueDateLabel = formatFinancialDateLabel(dueDate, "Vence em", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
const dueDateLabel = formatFinancialDateLabel(
|
||||
event.transaction.dueDate,
|
||||
"Vence em",
|
||||
DATE_FORMAT,
|
||||
);
|
||||
const paymentDateLabel = isPaid
|
||||
? formatFinancialDateLabel(
|
||||
event.transaction.boletoPaymentDate,
|
||||
"Pago em",
|
||||
DATE_FORMAT,
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<EventCard type="boleto">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 items-center">
|
||||
<span className="text-sm font-medium leading-tight">
|
||||
{event.transaction.name}
|
||||
</span>
|
||||
|
||||
<span className="text-sm font-medium leading-tight">
|
||||
{event.transaction.name}
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-x-3 gap-y-0.5 text-xs">
|
||||
{dueDateLabel && (
|
||||
<span className="text-xs text-muted-foreground leading-tight">
|
||||
{dueDateLabel}
|
||||
</span>
|
||||
<span className="text-muted-foreground">{dueDateLabel}</span>
|
||||
)}
|
||||
{paymentDateLabel && (
|
||||
<span className="text-success">{paymentDateLabel}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Badge variant={"outline"}>{isPaid ? "Pago" : "Pendente"}</Badge>
|
||||
<Badge variant="outline">{isPaid ? "Pago" : "Pendente"}</Badge>
|
||||
</div>
|
||||
<span className="font-medium">
|
||||
<MoneyValues amount={event.transaction.amount} />
|
||||
</span>
|
||||
<MoneyValues
|
||||
className="font-medium whitespace-nowrap"
|
||||
amount={event.transaction.amount}
|
||||
/>
|
||||
</div>
|
||||
</EventCard>
|
||||
);
|
||||
};
|
||||
|
||||
const renderCard = (event: Extract<CalendarEvent, { type: "card" }>) => (
|
||||
<EventCard type="card">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 items-center">
|
||||
<span className="text-sm font-medium leading-tight">
|
||||
Vencimento Fatura - {event.card.name}
|
||||
</span>
|
||||
</div>
|
||||
const renderCard = (event: Extract<CalendarEvent, { type: "card" }>) => {
|
||||
const paymentDateLabel = event.card.isPaid
|
||||
? formatFinancialDateLabel(event.card.paymentDate, "Pago em", DATE_FORMAT)
|
||||
: null;
|
||||
|
||||
<Badge variant={"outline"}>{event.card.status ?? "Invoice"}</Badge>
|
||||
return (
|
||||
<EventCard type="card">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-sm font-medium leading-tight">
|
||||
Vencimento Fatura — {event.card.name}
|
||||
</span>
|
||||
{paymentDateLabel && (
|
||||
<span className="text-xs text-success">{paymentDateLabel}</span>
|
||||
)}
|
||||
<Badge variant="outline">
|
||||
{event.card.isPaid ? "Pago" : (event.card.status ?? "Fatura")}
|
||||
</Badge>
|
||||
</div>
|
||||
{event.card.totalDue !== null ? (
|
||||
<MoneyValues
|
||||
className="font-medium whitespace-nowrap"
|
||||
amount={event.card.totalDue}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{event.card.totalDue !== null ? (
|
||||
<span className="font-medium">
|
||||
<MoneyValues amount={event.card.totalDue} />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</EventCard>
|
||||
);
|
||||
</EventCard>
|
||||
);
|
||||
};
|
||||
|
||||
const SECTION_LABELS: Record<CalendarEvent["type"], string> = {
|
||||
transaction: "Lançamentos",
|
||||
boleto: "Boletos",
|
||||
card: "Faturas",
|
||||
};
|
||||
|
||||
const renderEvent = (event: CalendarEvent) => {
|
||||
switch (event.type) {
|
||||
@@ -169,28 +180,50 @@ export function EventModal({ open, day, onClose, onCreate }: EventModalProps) {
|
||||
onCreate(day.date);
|
||||
};
|
||||
|
||||
const description = day?.events.length
|
||||
? "Confira os lançamentos e vencimentos cadastrados para este dia."
|
||||
: "Nenhum lançamento encontrado para este dia. Você pode criar um novo lançamento agora.";
|
||||
const hasEvents = Boolean(day?.events.length);
|
||||
|
||||
const grouped = day
|
||||
? {
|
||||
transaction: day.events.filter((e) => e.type === "transaction"),
|
||||
boleto: day.events.filter((e) => e.type === "boleto"),
|
||||
card: day.events.filter((e) => e.type === "card"),
|
||||
}
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => (!value ? onClose() : null)}>
|
||||
<DialogContent className="max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{formattedDate}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
<DialogDescription>
|
||||
{hasEvents
|
||||
? "Lançamentos e vencimentos cadastrados para este dia."
|
||||
: "Nenhum lançamento encontrado para este dia."}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="max-h-[380px] space-y-2 overflow-y-auto pr-2">
|
||||
{day?.events.length ? (
|
||||
day.events.map((event) => (
|
||||
<div key={event.id}>{renderEvent(event)}</div>
|
||||
))
|
||||
<div className="max-h-[380px] space-y-3 overflow-y-auto pr-2">
|
||||
{hasEvents && grouped ? (
|
||||
(["transaction", "boleto", "card"] as const)
|
||||
.filter((type) => grouped[type].length > 0)
|
||||
.map((type) => (
|
||||
<div key={type} className="space-y-1.5">
|
||||
<p className="px-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{SECTION_LABELS[type]}
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
{grouped[type].map((event) => (
|
||||
<div key={event.id}>{renderEvent(event)}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="rounded-xl border border-dashed border-border/60 bg-muted/30 p-6 text-center text-sm text-muted-foreground">
|
||||
Nenhum lançamento ou vencimento registrado. Clique em{" "}
|
||||
<span className="font-medium text-primary">Novo lançamento</span>{" "}
|
||||
para começar.
|
||||
<div className="flex flex-col items-center gap-3 rounded-xl border border-dashed border-border/60 bg-muted/30 p-8 text-center">
|
||||
<RiCalendarEventLine className="size-8 text-muted-foreground/50" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhum lançamento registrado para este dia.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user