diff --git a/src/features/calendar/components/day-cell.tsx b/src/features/calendar/components/day-cell.tsx index 634f5a8..8af8eb4 100644 --- a/src/features/calendar/components/day-cell.tsx +++ b/src/features/calendar/components/day-cell.tsx @@ -136,6 +136,8 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) { onCreate(day); }; + const overflowCount = day.events.length - previewEvents.length; + return (
onSelect(day)} onKeyDown={handleKeyDown} className={cn( - "flex h-full cursor-pointer flex-col gap-1.5 rounded-lg border border-transparent bg-card/80 p-2 text-left transition-colors 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 transition-colors duration-300", + "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", day.isToday && "border-primary/70 bg-primary/5 hover:border-primary", )} @@ -153,7 +155,7 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) { className={cn( "text-sm font-semibold leading-none", day.isToday - ? "text-orange-100 bg-primary size-5 rounded-full flex items-center justify-center" + ? "text-primary-foreground bg-primary size-5 rounded-full flex items-center justify-center" : "text-foreground/90", )} > @@ -162,7 +164,7 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) {
- {previewEvents.map((event) => ( - - ))} + {day.isCurrentMonth && + previewEvents.map((event) => ( + + ))} - {hasOverflow ? ( + {day.isCurrentMonth && hasOverflow ? ( - + ver mais + +{overflowCount} mais ) : null}
diff --git a/src/features/calendar/components/event-modal.tsx b/src/features/calendar/components/event-modal.tsx index 4898afa..d72eda4 100644 --- a/src/features/calendar/components/event-modal.tsx +++ b/src/features/calendar/components/event-modal.tsx @@ -69,8 +69,6 @@ const renderLancamento = (
- {event.transaction.condition} - {event.transaction.paymentMethod} {event.transaction.categoriaName}
@@ -198,9 +196,6 @@ export function EventModal({ open, day, onClose, onCreate }: EventModalProps) { - diff --git a/src/features/dashboard/components/category-history-widget.tsx b/src/features/dashboard/components/category-history-widget.tsx index e4c6518..4cbaaf4 100644 --- a/src/features/dashboard/components/category-history-widget.tsx +++ b/src/features/dashboard/components/category-history-widget.tsx @@ -8,7 +8,6 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import type { CategoryHistoryData } from "@/features/dashboard/categories/category-history-queries"; import { Button } from "@/shared/components/ui/button"; -import { Card, CardContent } from "@/shared/components/ui/card"; import { type ChartConfig, ChartContainer, @@ -170,274 +169,272 @@ export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) { } return ( - - -
- {selectedCategoryDetails.length > 0 && ( -
-
- {selectedCategoryDetails.map((category) => { - if (!category) return null; - const IconComponent = category.icon - ? getIconComponent(category.icon) - : null; - const colorIndex = selectedCategories.indexOf(category.id); - const color = CHART_COLORS[colorIndex % CHART_COLORS.length]; +
+
+ {selectedCategoryDetails.length > 0 && ( +
+
+ {selectedCategoryDetails.map((category) => { + if (!category) return null; + const IconComponent = category.icon + ? getIconComponent(category.icon) + : null; + const colorIndex = selectedCategories.indexOf(category.id); + const color = CHART_COLORS[colorIndex % CHART_COLORS.length]; - return ( -
- {IconComponent ? ( - - - - ) : ( -
- )} - {category.name} - -
- ); - })} -
-
- - {selectedCategories.length}/5 selecionadas - - -
-
- )} - - {selectedCategories.length < 5 && availableCategories.length > 0 && ( - - - - - - - - - Nenhuma categoria encontrada. - - {despesaCategories.length > 0 && ( - - {despesaCategories.map((category) => { - const IconComponent = category.icon - ? getIconComponent(category.icon) - : null; - return ( - handleAddCategory(category.id)} - className="gap-2" - > - {IconComponent ? ( - - ) : ( -
- )} - {category.name} - - ); - })} - - )} - - {receitaCategories.length > 0 && ( - - {receitaCategories.map((category) => { - const IconComponent = category.icon - ? getIconComponent(category.icon) - : null; - return ( - handleAddCategory(category.id)} - className="gap-2" - > - {IconComponent ? ( - - ) : ( -
- )} - {category.name} - - ); - })} - - )} - - - - - )} -
- - {isEmpty ? ( -
- - } - title="Selecione categorias para visualizar" - description="Escolha até 5 categorias para acompanhar o histórico dos últimos 8 meses, mês atual e próximo mês." - /> -
- ) : ( - - - - {filteredCategories.map((category) => ( - - - - - ))} - - - - formatCurrencyCompact(Number(value))} - /> - { - if (!active || !payload || payload.length === 0) { - return null; - } + {IconComponent ? ( + + + + ) : ( +
+ )} + {category.name} + +
+ ); + })} +
+
+ + {selectedCategories.length}/5 selecionadas + + +
+
+ )} - // Sort payload by value (descending) - const sortedPayload = [...payload].sort( - (a, b) => (b.value as number) - (a.value as number), - ); + {selectedCategories.length < 5 && availableCategories.length > 0 && ( + + + + + + + + + Nenhuma categoria encontrada. - return ( -
-
- {payload[0].payload.month} -
-
- {sortedPayload - .filter((entry) => (entry.value as number) > 0) - .map((entry) => { - const config = - chartConfig[ - entry.dataKey as keyof typeof chartConfig - ]; - const value = entry.value as number; + {despesaCategories.length > 0 && ( + + {despesaCategories.map((category) => { + const IconComponent = category.icon + ? getIconComponent(category.icon) + : null; + return ( + handleAddCategory(category.id)} + className="gap-2" + > + {IconComponent ? ( + + ) : ( +
+ )} + {category.name} + + ); + })} + + )} - return ( -
-
-
- - {config?.label} - -
- - {formatCurrency(value)} + {receitaCategories.length > 0 && ( + + {receitaCategories.map((category) => { + const IconComponent = category.icon + ? getIconComponent(category.icon) + : null; + return ( + handleAddCategory(category.id)} + className="gap-2" + > + {IconComponent ? ( + + ) : ( +
+ )} + {category.name} + + ); + })} + + )} + + + + + )} +
+ + {isEmpty ? ( +
+ + } + title="Selecione categorias para visualizar" + description="Escolha até 5 categorias para acompanhar o histórico dos últimos 8 meses, mês atual e próximo mês." + /> +
+ ) : ( + + + + {filteredCategories.map((category) => ( + + + + + ))} + + + + formatCurrencyCompact(Number(value))} + /> + { + if (!active || !payload || payload.length === 0) { + return null; + } + + // Sort payload by value (descending) + const sortedPayload = [...payload].sort( + (a, b) => (b.value as number) - (a.value as number), + ); + + return ( +
+
+ {payload[0].payload.month} +
+
+ {sortedPayload + .filter((entry) => (entry.value as number) > 0) + .map((entry) => { + const config = + chartConfig[ + entry.dataKey as keyof typeof chartConfig + ]; + const value = entry.value as number; + + return ( +
+
+
+ + {config?.label}
- ); - })} -
+ + {formatCurrency(value)} + +
+ ); + })}
- ); - }} - cursor={{ - stroke: "hsl(var(--muted-foreground))", - strokeWidth: 1, +
+ ); + }} + cursor={{ + stroke: "hsl(var(--muted-foreground))", + strokeWidth: 1, + }} + /> + {filteredCategories.map((category) => ( + - {filteredCategories.map((category) => ( - - ))} -
-
- )} - - + ))} + + + )} +
); } diff --git a/src/features/dashboard/components/goals-progress/goal-progress-item.tsx b/src/features/dashboard/components/goals-progress/goal-progress-item.tsx index 35a6640..218ef2d 100644 --- a/src/features/dashboard/components/goals-progress/goal-progress-item.tsx +++ b/src/features/dashboard/components/goals-progress/goal-progress-item.tsx @@ -25,8 +25,10 @@ export function GoalProgressItem({ const progressValue = clampGoalProgress(item.usedPercentage, 0, 100); const percentageDelta = item.usedPercentage - 100; + const isExceeded = item.status === "exceeded"; + return ( -
+
onEdit(item)} aria-label={`Editar orçamento de ${item.categoryName}`} > @@ -63,7 +65,14 @@ export function GoalProgressItem({
- +
); diff --git a/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx b/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx index 5810b09..1a8655f 100644 --- a/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx +++ b/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx @@ -8,6 +8,7 @@ import { TooltipContent, TooltipTrigger, } from "@/shared/components/ui/tooltip"; +import { getPaymentMethodIcon } from "@/shared/utils/icons"; type InstallmentExpenseListItemProps = { expense: InstallmentExpense; @@ -27,6 +28,10 @@ export function InstallmentExpenseListItem({ return (
+
+ {getPaymentMethodIcon(expense.paymentMethod)} +
+
@@ -61,7 +66,7 @@ export function InstallmentExpenseListItem({

{endDate ? `Termina em ${endDate}` : null} - {" | Restante "} + {" · Restante "} { - const parts = value.trim().split(/\s+/).filter(Boolean); - if (parts.length === 0) { - return "??"; - } - if (parts.length === 1) { - const firstPart = parts[0]; - return firstPart ? firstPart.slice(0, 2).toUpperCase() : "??"; - } - const firstChar = parts[0]?.[0] ?? ""; - const secondChar = parts[1]?.[0] ?? ""; - return `${firstChar}${secondChar}`.toUpperCase() || "??"; -}; - export function PayersWidget({ payers }: PayersWidgetProps) { return ( - +

{payers.length === 0 ? ( } @@ -123,6 +109,6 @@ export function PayersWidget({ payers }: PayersWidgetProps) { })}
)} - +
); } diff --git a/src/features/payers/components/details/payer-card-usage-card.tsx b/src/features/payers/components/details/payer-card-usage-card.tsx index 3225ca7..7872a35 100644 --- a/src/features/payers/components/details/payer-card-usage-card.tsx +++ b/src/features/payers/components/details/payer-card-usage-card.tsx @@ -5,18 +5,7 @@ import { CardContent } from "@/shared/components/ui/card"; import { WidgetEmptyState } from "@/shared/components/widget-empty-state"; import { resolveLogoSrc } from "@/shared/lib/logo"; import type { PayerCardUsageItem } from "@/shared/lib/payers/details"; - -const buildInitials = (value: string) => { - const parts = value.trim().split(/\s+/).filter(Boolean); - if (parts.length === 0) return "CC"; - if (parts.length === 1) { - const firstPart = parts[0]; - return firstPart ? firstPart.slice(0, 2).toUpperCase() : "CC"; - } - const firstChar = parts[0]?.[0] ?? ""; - const secondChar = parts[1]?.[0] ?? ""; - return `${firstChar}${secondChar}`.toUpperCase() || "CC"; -}; +import { buildInitials } from "@/shared/utils/initials"; type PagadorCardUsageCardProps = { items: PayerCardUsageItem[]; diff --git a/src/features/reports/components/establishments/establishments-list.tsx b/src/features/reports/components/establishments/establishments-list.tsx index fd8b235..68d6909 100644 --- a/src/features/reports/components/establishments/establishments-list.tsx +++ b/src/features/reports/components/establishments/establishments-list.tsx @@ -12,23 +12,12 @@ import { } from "@/shared/components/ui/card"; import { Progress } from "@/shared/components/ui/progress"; import { WidgetEmptyState } from "@/shared/components/widget-empty-state"; +import { buildInitials } from "@/shared/utils/initials"; type EstablishmentsListProps = { establishments: TopEstabelecimentosData["establishments"]; }; -const buildInitials = (value: string) => { - const parts = value.trim().split(/\s+/).filter(Boolean); - if (parts.length === 0) return "ES"; - if (parts.length === 1) { - const firstPart = parts[0]; - return firstPart ? firstPart.slice(0, 2).toUpperCase() : "ES"; - } - const firstChar = parts[0]?.[0] ?? ""; - const secondChar = parts[1]?.[0] ?? ""; - return `${firstChar}${secondChar}`.toUpperCase() || "ES"; -}; - export function EstablishmentsList({ establishments, }: EstablishmentsListProps) { diff --git a/src/shared/components/widget-card.tsx b/src/shared/components/widget-card.tsx index b477f39..4e10251 100644 --- a/src/shared/components/widget-card.tsx +++ b/src/shared/components/widget-card.tsx @@ -12,7 +12,7 @@ export type WidgetCardProps = { title: string; subtitle: string; children: React.ReactNode; - icon: React.ReactNode; + icon?: React.ReactNode; action?: React.ReactNode; }; @@ -37,11 +37,11 @@ export default function WidgetCard({
- - {icon} + + {icon && {icon}} {title} - + {subtitle}
diff --git a/src/shared/utils/initials.ts b/src/shared/utils/initials.ts new file mode 100644 index 0000000..eb280f4 --- /dev/null +++ b/src/shared/utils/initials.ts @@ -0,0 +1,15 @@ +/** + * Builds a 2-character initials string from a name. + * Falls back to the provided `fallback` (default "??") when the name is empty. + */ +export function buildInitials(value: string, fallback = "??"): string { + const parts = value.trim().split(/\s+/).filter(Boolean); + if (parts.length === 0) return fallback; + if (parts.length === 1) { + const firstPart = parts[0]; + return firstPart ? firstPart.slice(0, 2).toUpperCase() : fallback; + } + const firstChar = parts[0]?.[0] ?? ""; + const secondChar = parts[1]?.[0] ?? ""; + return `${firstChar}${secondChar}`.toUpperCase() || fallback; +}