mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
style: padroniza widgets e listas do dashboard
This commit is contained in:
@@ -56,8 +56,6 @@ const VARIANT_CONFIG = {
|
|||||||
increase: "text-success",
|
increase: "text-success",
|
||||||
decrease: "text-destructive",
|
decrease: "text-destructive",
|
||||||
},
|
},
|
||||||
listItemClassName:
|
|
||||||
"flex flex-col gap-1.5 py-2 border-b border-dashed last:border-0",
|
|
||||||
includeBudgetAmount: true,
|
includeBudgetAmount: true,
|
||||||
},
|
},
|
||||||
expense: {
|
expense: {
|
||||||
@@ -70,8 +68,6 @@ const VARIANT_CONFIG = {
|
|||||||
increase: "text-destructive",
|
increase: "text-destructive",
|
||||||
decrease: "text-success",
|
decrease: "text-success",
|
||||||
},
|
},
|
||||||
listItemClassName:
|
|
||||||
"flex flex-col py-2 border-b border-dashed last:border-0",
|
|
||||||
includeBudgetAmount: false,
|
includeBudgetAmount: false,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
@@ -194,7 +190,7 @@ export function CategoryBreakdownWidgetView({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TabsContent value="list" className="mt-0">
|
<TabsContent value="list" className="mt-0">
|
||||||
<div className="flex flex-col px-0">
|
<div>
|
||||||
{data.categories.map((category, index) => {
|
{data.categories.map((category, index) => {
|
||||||
const hasIncrease =
|
const hasIncrease =
|
||||||
category.percentageChange !== null &&
|
category.percentageChange !== null &&
|
||||||
@@ -218,11 +214,8 @@ export function CategoryBreakdownWidgetView({
|
|||||||
: "text-muted-foreground";
|
: "text-muted-foreground";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={category.categoryId}>
|
||||||
key={category.categoryId}
|
<div className="flex items-center justify-between gap-3 transition-all duration-300 py-2">
|
||||||
className={config.listItemClassName}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-3">
|
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||||
<CategoryIconBadge
|
<CategoryIconBadge
|
||||||
icon={category.categoryIcon}
|
icon={category.categoryIcon}
|
||||||
@@ -245,7 +238,7 @@ export function CategoryBreakdownWidgetView({
|
|||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
|
||||||
<span>
|
<span>
|
||||||
{formatPercentage(
|
{formatPercentage(
|
||||||
category.percentageOfTotal,
|
category.percentageOfTotal,
|
||||||
@@ -253,6 +246,36 @@ export function CategoryBreakdownWidgetView({
|
|||||||
)}{" "}
|
)}{" "}
|
||||||
da {config.shareLabel}
|
da {config.shareLabel}
|
||||||
</span>
|
</span>
|
||||||
|
{hasBudget && category.budgetUsedPercentage !== null ? (
|
||||||
|
<>
|
||||||
|
<span aria-hidden>·</span>
|
||||||
|
<span
|
||||||
|
className={`flex items-center gap-1 ${budgetExceeded ? "text-destructive" : "text-info"}`}
|
||||||
|
>
|
||||||
|
<RiWallet3Line className="size-3 shrink-0" />
|
||||||
|
{budgetExceeded ? (
|
||||||
|
<>
|
||||||
|
excedeu{" "}
|
||||||
|
<span className="font-medium">
|
||||||
|
{formatCurrency(exceededAmount)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{formatPercentage(
|
||||||
|
category.budgetUsedPercentage,
|
||||||
|
config.percentageDigits,
|
||||||
|
)}{" "}
|
||||||
|
do limite
|
||||||
|
{config.includeBudgetAmount &&
|
||||||
|
category.budgetAmount !== null
|
||||||
|
? ` ${formatCurrency(category.budgetAmount)}`
|
||||||
|
: ""}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,48 +303,6 @@ export function CategoryBreakdownWidgetView({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasBudget && category.budgetUsedPercentage !== null ? (
|
|
||||||
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
|
||||||
<RiWallet3Line
|
|
||||||
className={`size-3 ${
|
|
||||||
budgetExceeded ? "text-destructive" : "text-info"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
budgetExceeded ? "text-destructive" : "text-info"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{budgetExceeded ? (
|
|
||||||
<>
|
|
||||||
{formatPercentage(
|
|
||||||
category.budgetUsedPercentage,
|
|
||||||
config.percentageDigits,
|
|
||||||
)}{" "}
|
|
||||||
do limite
|
|
||||||
{config.includeBudgetAmount &&
|
|
||||||
category.budgetAmount !== null
|
|
||||||
? ` ${formatCurrency(category.budgetAmount)}`
|
|
||||||
: ""}{" "}
|
|
||||||
- excedeu em {formatCurrency(exceededAmount)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{formatPercentage(
|
|
||||||
category.budgetUsedPercentage,
|
|
||||||
config.percentageDigits,
|
|
||||||
)}{" "}
|
|
||||||
do limite
|
|
||||||
{config.includeBudgetAmount &&
|
|
||||||
category.budgetAmount !== null
|
|
||||||
? ` ${formatCurrency(category.budgetAmount)}`
|
|
||||||
: ""}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ import {
|
|||||||
RiArrowDownSFill,
|
RiArrowDownSFill,
|
||||||
RiArrowUpLine,
|
RiArrowUpLine,
|
||||||
RiArrowUpSFill,
|
RiArrowUpSFill,
|
||||||
RiCashLine,
|
RiCalendarCheckLine,
|
||||||
RiIncreaseDecreaseLine,
|
RiScalesLine,
|
||||||
RiSubtractLine,
|
RiSubtractLine,
|
||||||
} from "@remixicon/react";
|
} from "@remixicon/react";
|
||||||
import type { DashboardCardMetrics } from "@/features/dashboard/dashboard-metrics-queries";
|
import type { DashboardCardMetrics } from "@/features/dashboard/dashboard-metrics-queries";
|
||||||
import MoneyValues from "@/shared/components/money-values";
|
import MoneyValues from "@/shared/components/money-values";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardAction,
|
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
@@ -32,20 +31,33 @@ const CARDS = [
|
|||||||
key: "receitas",
|
key: "receitas",
|
||||||
icon: RiArrowUpLine,
|
icon: RiArrowUpLine,
|
||||||
invertTrend: false,
|
invertTrend: false,
|
||||||
|
cardClass: "",
|
||||||
|
iconClass: "text-success",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Despesas",
|
label: "Despesas",
|
||||||
key: "despesas",
|
key: "despesas",
|
||||||
icon: RiArrowDownLine,
|
icon: RiArrowDownLine,
|
||||||
invertTrend: true,
|
invertTrend: true,
|
||||||
|
cardClass: "",
|
||||||
|
iconClass: "text-destructive",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Balanço",
|
label: "Balanço",
|
||||||
key: "balanco",
|
key: "balanco",
|
||||||
icon: RiIncreaseDecreaseLine,
|
icon: RiScalesLine,
|
||||||
invertTrend: false,
|
invertTrend: false,
|
||||||
|
cardClass: "",
|
||||||
|
iconClass: "text-amber-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Previsto",
|
||||||
|
key: "previsto",
|
||||||
|
icon: RiCalendarCheckLine,
|
||||||
|
invertTrend: false,
|
||||||
|
cardClass: "border border-dashed",
|
||||||
|
iconClass: "",
|
||||||
},
|
},
|
||||||
{ label: "Previsto", key: "previsto", icon: RiCashLine, invertTrend: false },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const TREND_ICONS = {
|
const TREND_ICONS = {
|
||||||
@@ -62,7 +74,7 @@ const getTrend = (current: number, previous: number): Trend => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPercentChange = (current: number, previous: number): string => {
|
const getPercentChange = (current: number, previous: number): string => {
|
||||||
const EPSILON = 0.01; // Considera valores menores que 1 centavo como zero
|
const EPSILON = 0.01;
|
||||||
|
|
||||||
if (Math.abs(previous) < EPSILON) {
|
if (Math.abs(previous) < EPSILON) {
|
||||||
if (Math.abs(current) < EPSILON) return "0%";
|
if (Math.abs(current) < EPSILON) return "0%";
|
||||||
@@ -80,48 +92,49 @@ const getPercentChange = (current: number, previous: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getTrendColor = (trend: Trend, invertTrend: boolean): string => {
|
const getTrendColor = (trend: Trend, invertTrend: boolean): string => {
|
||||||
if (trend === "flat") return "";
|
if (trend === "flat") return "text-muted-foreground";
|
||||||
const isPositive = invertTrend ? trend === "down" : trend === "up";
|
const isPositive = invertTrend ? trend === "down" : trend === "up";
|
||||||
return isPositive
|
return isPositive ? "text-success" : "text-destructive";
|
||||||
? "text-success border-success"
|
|
||||||
: "text-destructive border-destructive";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DashboardMetricsCards({ metrics }: DashboardMetricsCardsProps) {
|
export function DashboardMetricsCards({ metrics }: DashboardMetricsCardsProps) {
|
||||||
return (
|
return (
|
||||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-3 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
<div className="grid grid-cols-1 gap-3 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||||
{CARDS.map(({ label, key, icon: Icon, invertTrend }) => {
|
{CARDS.map(
|
||||||
const metric = metrics[key];
|
({ label, key, icon: Icon, invertTrend, cardClass, iconClass }) => {
|
||||||
const trend = getTrend(metric.current, metric.previous);
|
const metric = metrics[key];
|
||||||
const TrendIcon = TREND_ICONS[trend];
|
const trend = getTrend(metric.current, metric.previous);
|
||||||
const trendColor = getTrendColor(trend, invertTrend);
|
const TrendIcon = TREND_ICONS[trend];
|
||||||
|
const trendColor = getTrendColor(trend, invertTrend);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={label} className="@container/card gap-2">
|
<Card
|
||||||
<CardHeader>
|
key={label}
|
||||||
<CardTitle className="flex items-center gap-1 tracking-tighter lowercase">
|
className={`@container/card flex flex-col justify-between min-h-34 ${cardClass}`}
|
||||||
<Icon className="size-4" />
|
>
|
||||||
{label}
|
<CardHeader>
|
||||||
</CardTitle>
|
<CardTitle className="flex items-center gap-1 tracking-tight lowercase">
|
||||||
<MoneyValues className="text-2xl" amount={metric.current} />
|
<Icon className={`size-4 ${iconClass}`} />
|
||||||
<CardAction>
|
{label}
|
||||||
<div className={`flex items-center text-xs ${trendColor}`}>
|
</CardTitle>
|
||||||
<TrendIcon size={16} />
|
<div className="flex items-baseline gap-2 mt-auto pt-2">
|
||||||
{getPercentChange(metric.current, metric.previous)}
|
<MoneyValues className="text-2xl" amount={metric.current} />
|
||||||
|
<div className={`flex items-center text-xs ${trendColor}`}>
|
||||||
|
<TrendIcon size={14} />
|
||||||
|
{getPercentChange(metric.current, metric.previous)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardAction>
|
</CardHeader>
|
||||||
</CardHeader>
|
<CardFooter className="text-sm">
|
||||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
<div className="line-clamp-1 flex gap-2 text-xs">
|
<span>vs. mês anterior</span>
|
||||||
mês anterior
|
<MoneyValues amount={metric.previous} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-foreground">
|
</CardFooter>
|
||||||
<MoneyValues amount={metric.previous} />
|
</Card>
|
||||||
</div>
|
);
|
||||||
</CardFooter>
|
},
|
||||||
</Card>
|
)}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ export function DashboardWelcome({ name }: { name?: string | null }) {
|
|||||||
<section className="p-2">
|
<section className="p-2">
|
||||||
<div className="tracking-tight">
|
<div className="tracking-tight">
|
||||||
<h1 className="text-xl">
|
<h1 className="text-xl">
|
||||||
{greeting}, {displayName}
|
{greeting}, <span className="text-primary">{displayName}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm mt-1">{formattedDate}</p>
|
<p className="text-sm mt-1 text-muted-foreground">{formattedDate}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function GoalProgressItem({
|
|||||||
const percentageDelta = item.usedPercentage - 100;
|
const percentageDelta = item.usedPercentage - 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="border-b border-dashed py-2 last:border-b-0 last:pb-0">
|
<div className="transition-all duration-300 py-2">
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex min-w-0 flex-1 items-start gap-2">
|
<div className="flex min-w-0 flex-1 items-start gap-2">
|
||||||
<CategoryIconBadge
|
<CategoryIconBadge
|
||||||
@@ -52,9 +52,9 @@ export function GoalProgressItem({
|
|||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
className="size-7 text-muted-foreground hover:text-foreground"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
onClick={() => onEdit(item)}
|
onClick={() => onEdit(item)}
|
||||||
aria-label={`Editar orçamento de ${item.categoryName}`}
|
aria-label={`Editar orçamento de ${item.categoryName}`}
|
||||||
>
|
>
|
||||||
@@ -65,6 +65,6 @@ export function GoalProgressItem({
|
|||||||
<div className="ml-11 mt-1.5">
|
<div className="ml-11 mt-1.5">
|
||||||
<Progress value={progressValue} />
|
<Progress value={progressValue} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function InstallmentExpenseListItem({
|
|||||||
} = buildInstallmentExpenseDisplay(expense);
|
} = buildInstallmentExpenseDisplay(expense);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="flex items-center gap-3 border-b border-dashed pb-3 last:border-b-0 last:pb-0">
|
<div className="flex items-center gap-3 transition-all duration-300 py-2">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
@@ -71,6 +71,6 @@ export function InstallmentExpenseListItem({
|
|||||||
|
|
||||||
<Progress value={progress} className="mt-1 h-2" />
|
<Progress value={progress} className="mt-1 h-2" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function InstallmentExpensesList({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="flex flex-col gap-2">
|
<ul className="flex flex-col">
|
||||||
{expenses.map((expense) => (
|
{expenses.map((expense) => (
|
||||||
<InstallmentExpenseListItem key={expense.id} expense={expense} />
|
<InstallmentExpenseListItem key={expense.id} expense={expense} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function InstallmentExpensesWidgetView({
|
|||||||
data,
|
data,
|
||||||
}: InstallmentExpensesWidgetViewProps) {
|
}: InstallmentExpensesWidgetViewProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 px-0">
|
<div className="flex flex-col">
|
||||||
<InstallmentExpensesList expenses={data.expenses} />
|
<InstallmentExpensesList expenses={data.expenses} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ export function MyAccountsWidget({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between py-2">
|
<div className="flex justify-between py-1">
|
||||||
Saldo Total
|
Saldo Total
|
||||||
<MoneyValues className="text-2xl" amount={totalBalance} />
|
<MoneyValues className="text-2xl" amount={totalBalance} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-2 px-0">
|
<div>
|
||||||
{displayedAccounts.length === 0 ? (
|
{displayedAccounts.length === 0 ? (
|
||||||
<div className="-mt-10">
|
<div className="-mt-10">
|
||||||
<WidgetEmptyState
|
<WidgetEmptyState
|
||||||
@@ -49,12 +49,12 @@ export function MyAccountsWidget({
|
|||||||
const logoSrc = resolveLogoSrc(account.logo);
|
const logoSrc = resolveLogoSrc(account.logo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<div
|
||||||
key={account.id}
|
key={account.id}
|
||||||
className="flex items-center justify-between gap-2 border-b border-dashed py-2 last:border-0"
|
className="flex items-center justify-between transition-all duration-300 py-1.5 "
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
<div className="flex min-w-0 flex-1 items-center gap-2 py-1">
|
||||||
<div className="relative size-10 overflow-hidden">
|
<div className="relative size-9.5 overflow-hidden">
|
||||||
{logoSrc ? (
|
{logoSrc ? (
|
||||||
<Image
|
<Image
|
||||||
src={logoSrc}
|
src={logoSrc}
|
||||||
@@ -88,7 +88,7 @@ export function MyAccountsWidget({
|
|||||||
<div className="flex flex-col items-end gap-0.5 text-right">
|
<div className="flex flex-col items-end gap-0.5 text-right">
|
||||||
<MoneyValues amount={account.balance} />
|
<MoneyValues amount={account.balance} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function NoteListItem({
|
|||||||
const createdAtLabel = formatNoteCreatedAt(note.createdAt);
|
const createdAtLabel = formatNoteCreatedAt(note.createdAt);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="flex items-center justify-between gap-2 border-b border-dashed py-2 last:border-b-0 last:pb-0">
|
<div className="flex items-center justify-between gap-2 transition-all duration-300 py-2">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="truncate text-sm font-medium text-foreground">
|
<p className="truncate text-sm font-medium text-foreground">
|
||||||
{displayTitle}
|
{displayTitle}
|
||||||
@@ -40,9 +40,9 @@ export function NoteListItem({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex shrink-0 items-center">
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
className="text-muted-foreground hover:text-foreground"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
onClick={() => onOpenEdit(note)}
|
onClick={() => onOpenEdit(note)}
|
||||||
@@ -51,7 +51,7 @@ export function NoteListItem({
|
|||||||
<RiPencilLine className="size-4" />
|
<RiPencilLine className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
className="text-muted-foreground hover:text-foreground"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
onClick={() => onOpenDetails(note)}
|
onClick={() => onOpenDetails(note)}
|
||||||
@@ -60,6 +60,6 @@ export function NoteListItem({
|
|||||||
<RiFileList2Line className="size-4" />
|
<RiFileList2Line className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function PayersWidget({ payers }: PayersWidgetProps) {
|
|||||||
description="Quando houver despesas associadas a pagadores, eles aparecerão aqui."
|
description="Quando houver despesas associadas a pagadores, eles aparecerão aqui."
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ul className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{payers.map((payer) => {
|
{payers.map((payer) => {
|
||||||
const initials = buildInitials(payer.name);
|
const initials = buildInitials(payer.name);
|
||||||
const hasValidPercentageChange =
|
const hasValidPercentageChange =
|
||||||
@@ -59,12 +59,12 @@ export function PayersWidget({ payers }: PayersWidgetProps) {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<div
|
||||||
key={payer.id}
|
key={payer.id}
|
||||||
className="flex items-center justify-between border-b border-dashed last:border-b-0 last:pb-0"
|
className="flex items-center justify-between transition-all duration-300 py-1.5"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-2 py-2">
|
<div className="flex min-w-0 flex-1 items-center gap-2 py-1">
|
||||||
<Avatar className="size-10 shrink-0">
|
<Avatar className="size-9.5 shrink-0">
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
src={getAvatarSrc(payer.avatarUrl)}
|
src={getAvatarSrc(payer.avatarUrl)}
|
||||||
alt={`Avatar de ${payer.name}`}
|
alt={`Avatar de ${payer.name}`}
|
||||||
@@ -118,10 +118,10 @@ export function PayersWidget({ payers }: PayersWidgetProps) {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function PaymentBreakdownListItem({
|
|||||||
item,
|
item,
|
||||||
}: PaymentBreakdownListItemProps) {
|
}: PaymentBreakdownListItemProps) {
|
||||||
return (
|
return (
|
||||||
<li className="flex items-center gap-3 border-b border-dashed pb-3 last:border-b-0 last:pb-0">
|
<div className="flex items-center gap-3 transition-all duration-300 py-1.5">
|
||||||
<div className={ICON_WRAPPER_CLASS}>{item.icon}</div>
|
<div className={ICON_WRAPPER_CLASS}>{item.icon}</div>
|
||||||
|
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
@@ -46,6 +46,6 @@ export function PaymentBreakdownListItem({
|
|||||||
<Progress value={item.percentage} />
|
<Progress value={item.percentage} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function PaymentStatusWidgetView({
|
|||||||
pending={data.income.pending}
|
pending={data.income.pending}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="border-t border-dashed" />
|
<div className="border-t" />
|
||||||
|
|
||||||
<PaymentStatusCategorySection
|
<PaymentStatusCategorySection
|
||||||
title="A Pagar"
|
title="A Pagar"
|
||||||
|
|||||||
@@ -170,12 +170,12 @@ export function PurchasesByCategoryWidget({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ul className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{currentTransactions.map((transaction) => {
|
{currentTransactions.map((transaction) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<div
|
||||||
key={transaction.id}
|
key={transaction.id}
|
||||||
className="flex items-center justify-between gap-3 border-b border-dashed py-2 last:border-b-0 last:pb-0"
|
className="flex items-center justify-between gap-3 transition-all duration-300 py-2"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||||
<EstabelecimentoLogo name={transaction.name} size={37} />
|
<EstabelecimentoLogo name={transaction.name} size={37} />
|
||||||
@@ -193,10 +193,10 @@ export function PurchasesByCategoryWidget({
|
|||||||
<div className="shrink-0 text-foreground">
|
<div className="shrink-0 text-foreground">
|
||||||
<MoneyValues amount={transaction.amount} />
|
<MoneyValues amount={transaction.amount} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,36 +30,34 @@ export function RecurringExpensesWidget({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 px-0">
|
<div className="flex flex-col">
|
||||||
<ul className="flex flex-col gap-2">
|
{data.expenses.map((expense) => {
|
||||||
{data.expenses.map((expense) => {
|
return (
|
||||||
return (
|
<div
|
||||||
<li
|
key={expense.id}
|
||||||
key={expense.id}
|
className="flex items-center gap-2 transition-all duration-300 py-1.5"
|
||||||
className="flex items-center gap-3 border-b border-dashed pb-2 last:border-b-0 last:pb-0"
|
>
|
||||||
>
|
<EstabelecimentoLogo name={expense.name} size={37} />
|
||||||
<EstabelecimentoLogo name={expense.name} size={37} />
|
|
||||||
|
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="truncate text-foreground text-sm font-medium">
|
<p className="truncate text-foreground text-sm font-medium">
|
||||||
{expense.name}
|
{expense.name}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<MoneyValues amount={expense.amount} />
|
<MoneyValues amount={expense.amount} />
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
|
||||||
<span className="inline-flex items-center gap-1">
|
|
||||||
{expense.paymentMethod}
|
|
||||||
</span>
|
|
||||||
<span>{formatOccurrences(expense.recurrenceCount)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
|
||||||
);
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||||
})}
|
<span className="inline-flex items-center gap-1">
|
||||||
</ul>
|
{expense.paymentMethod}
|
||||||
|
</span>
|
||||||
|
<span>{formatOccurrences(expense.recurrenceCount)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ export function TopEstablishmentsWidget({
|
|||||||
description="Quando houver despesas registradas, elas aparecerão aqui."
|
description="Quando houver despesas registradas, elas aparecerão aqui."
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ul className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{data.establishments.map((establishment) => {
|
{data.establishments.map((establishment) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<div
|
||||||
key={establishment.id}
|
key={establishment.id}
|
||||||
className="flex items-center justify-between gap-3 border-b border-dashed py-2 last:border-b-0 last:pb-0"
|
className="flex items-center justify-between gap-3 transition-all duration-300 py-2"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||||
<EstabelecimentoLogo name={establishment.name} size={37} />
|
<EstabelecimentoLogo name={establishment.name} size={37} />
|
||||||
@@ -50,10 +50,10 @@ export function TopEstablishmentsWidget({
|
|||||||
<div className="shrink-0 text-foreground">
|
<div className="shrink-0 text-foreground">
|
||||||
<MoneyValues amount={establishment.amount} />
|
<MoneyValues amount={establishment.amount} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -85,9 +85,7 @@ export function TopExpensesWidget({
|
|||||||
htmlFor="card-only-toggle"
|
htmlFor="card-only-toggle"
|
||||||
className="text-sm text-muted-foreground"
|
className="text-sm text-muted-foreground"
|
||||||
>
|
>
|
||||||
{cardOnly
|
Apenas cartões
|
||||||
? "Somente cartões de crédito ou débito."
|
|
||||||
: "Todas as despesas"}
|
|
||||||
</label>
|
</label>
|
||||||
<Switch
|
<Switch
|
||||||
id="card-only-toggle"
|
id="card-only-toggle"
|
||||||
@@ -107,12 +105,12 @@ export function TopExpensesWidget({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ul className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{data.expenses.map((expense) => {
|
{data.expenses.map((expense) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<div
|
||||||
key={expense.id}
|
key={expense.id}
|
||||||
className="flex items-center justify-between gap-3 border-b border-dashed py-2 last:border-b-0 last:pb-0"
|
className="flex items-center justify-between gap-3 transition-all duration-300 py-2"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||||
<EstabelecimentoLogo name={expense.name} size={37} />
|
<EstabelecimentoLogo name={expense.name} size={37} />
|
||||||
@@ -130,10 +128,10 @@ export function TopExpensesWidget({
|
|||||||
<div className="shrink-0 text-foreground">
|
<div className="shrink-0 text-foreground">
|
||||||
<MoneyValues amount={expense.amount} />
|
<MoneyValues amount={expense.amount} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,10 +42,7 @@ export function PayerCardUsageCard({ items }: PagadorCardUsageCardProps) {
|
|||||||
const logoPath = resolveLogoSrc(item.logo);
|
const logoPath = resolveLogoSrc(item.logo);
|
||||||
const initials = buildInitials(item.name);
|
const initials = buildInitials(item.name);
|
||||||
return (
|
return (
|
||||||
<li
|
<div key={item.id} className="flex items-center justify-between">
|
||||||
key={item.id}
|
|
||||||
className="flex items-center justify-between border-b border-dashed last:border-b-0 last:pb-0"
|
|
||||||
>
|
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-2 py-2">
|
<div className="flex min-w-0 flex-1 items-center gap-2 py-2">
|
||||||
<div className="flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full">
|
<div className="flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full">
|
||||||
{logoPath ? (
|
{logoPath ? (
|
||||||
@@ -72,7 +69,7 @@ export function PayerCardUsageCard({ items }: PagadorCardUsageCardProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MoneyValues amount={item.amount} />
|
<MoneyValues amount={item.amount} />
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { EstabelecimentoLogo } from "@/features/transactions/components/shared/e
|
|||||||
import MoneyValues from "@/shared/components/money-values";
|
import MoneyValues from "@/shared/components/money-values";
|
||||||
import { CardContent } from "@/shared/components/ui/card";
|
import { CardContent } from "@/shared/components/ui/card";
|
||||||
import { Progress } from "@/shared/components/ui/progress";
|
import { Progress } from "@/shared/components/ui/progress";
|
||||||
|
import { Separator } from "@/shared/components/ui/separator";
|
||||||
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
import { WidgetEmptyState } from "@/shared/components/widget-empty-state";
|
||||||
import type {
|
import type {
|
||||||
PayerBoletoItem,
|
PayerBoletoItem,
|
||||||
@@ -41,10 +42,7 @@ export function PayerBoletoCard({ items }: PagadorBoletoCardProps) {
|
|||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
const statusLabel = buildBillStatusLabel(item);
|
const statusLabel = buildBillStatusLabel(item);
|
||||||
return (
|
return (
|
||||||
<li
|
<div key={item.id} className="flex items-center justify-between">
|
||||||
key={item.id}
|
|
||||||
className="flex items-center justify-between border-b border-dashed last:border-b-0 last:pb-0"
|
|
||||||
>
|
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-3 py-2">
|
<div className="flex min-w-0 flex-1 items-center gap-3 py-2">
|
||||||
<EstabelecimentoLogo name={item.name} size={36} />
|
<EstabelecimentoLogo name={item.name} size={36} />
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
@@ -64,7 +62,7 @@ export function PayerBoletoCard({ items }: PagadorBoletoCardProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MoneyValues amount={item.amount} />
|
<MoneyValues amount={item.amount} />
|
||||||
</li>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -105,7 +103,9 @@ export function PayerPaymentStatusCard({
|
|||||||
<span className="text-sm font-medium text-foreground">Pago</span>
|
<span className="text-sm font-medium text-foreground">Pago</span>
|
||||||
<MoneyValues amount={paidAmount} />
|
<MoneyValues amount={paidAmount} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Progress value={paidPercentage} className="h-2" />
|
<Progress value={paidPercentage} className="h-2" />
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-4 text-sm">
|
<div className="flex items-center justify-between gap-4 text-sm">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<RiCheckboxCircleLine className="size-3 text-success" />
|
<RiCheckboxCircleLine className="size-3 text-success" />
|
||||||
@@ -117,7 +117,7 @@ export function PayerPaymentStatusCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-dashed" />
|
<Separator />
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|||||||
Reference in New Issue
Block a user