feat(dashboard/boletos): nome do boleto como link para lançamentos do período

- nome do boleto virou link para /transactions?q=<nome>
- quando o período selecionado não é o atual, inclui ?periodo=<mes-ano> na URL
- ícone RiExternalLinkLine ao lado do nome, mesmo padrão do widget de faturas

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-05-02 22:08:23 +00:00
parent 0f5c735be0
commit 2fc6d11d78
5 changed files with 84 additions and 13 deletions

View File

@@ -1,4 +1,5 @@
import { RiCheckboxCircleFill } from "@remixicon/react"; import { RiCheckboxCircleFill, RiExternalLinkLine } from "@remixicon/react";
import Link from "next/link";
import { import {
buildBillStatusLabel, buildBillStatusLabel,
buildBillWidgetStatusLabel, buildBillWidgetStatusLabel,
@@ -13,14 +14,25 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/shared/components/ui/tooltip"; } from "@/shared/components/ui/tooltip";
import { getCurrentPeriod, formatPeriodForUrl } from "@/shared/utils/period";
import { cn } from "@/shared/utils/ui"; import { cn } from "@/shared/utils/ui";
type BillListItemProps = { type BillListItemProps = {
bill: DashboardBill; bill: DashboardBill;
period?: string;
onPay: (billId: string) => void; onPay: (billId: string) => void;
}; };
export function BillListItem({ bill, onPay }: BillListItemProps) { function buildTransactionsHref(name: string, period?: string): string {
const params = new URLSearchParams({ q: name });
const current = getCurrentPeriod();
if (period && period !== current) {
params.set("periodo", formatPeriodForUrl(period));
}
return `/transactions?${params.toString()}`;
}
export function BillListItem({ bill, period, onPay }: BillListItemProps) {
const statusLabel = buildBillWidgetStatusLabel(bill); const statusLabel = buildBillWidgetStatusLabel(bill);
const absoluteStatusLabel = buildBillStatusLabel(bill); const absoluteStatusLabel = buildBillStatusLabel(bill);
const overdue = isBillOverdue(bill); const overdue = isBillOverdue(bill);
@@ -28,6 +40,7 @@ export function BillListItem({ bill, onPay }: BillListItemProps) {
statusLabel && statusLabel !== absoluteStatusLabel statusLabel && statusLabel !== absoluteStatusLabel
? absoluteStatusLabel ? absoluteStatusLabel
: null; : null;
const href = buildTransactionsHref(bill.name, period);
return ( return (
<li className="flex items-center justify-between transition-all duration-300 py-1.5"> <li className="flex items-center justify-between transition-all duration-300 py-1.5">
@@ -35,9 +48,16 @@ export function BillListItem({ bill, onPay }: BillListItemProps) {
<EstablishmentLogo name={bill.name} size={37} /> <EstablishmentLogo name={bill.name} size={37} />
<div className="min-w-0"> <div className="min-w-0">
<span className="block truncate text-sm font-medium text-foreground"> <Link
{bill.name} href={href}
</span> className="inline-flex max-w-full items-center gap-1 text-sm font-medium text-foreground underline-offset-2 hover:text-primary hover:underline"
>
<span className="truncate">{bill.name}</span>
<RiExternalLinkLine
className="size-3 shrink-0 text-muted-foreground"
aria-hidden
/>
</Link>
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground"> <div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
{statusLabel ? ( {statusLabel ? (
statusTooltipLabel ? ( statusTooltipLabel ? (

View File

@@ -5,10 +5,11 @@ import { BillListItem } from "./bill-list-item";
type BillsListProps = { type BillsListProps = {
bills: DashboardBill[]; bills: DashboardBill[];
period?: string;
onPay: (billId: string) => void; onPay: (billId: string) => void;
}; };
export function BillsList({ bills, onPay }: BillsListProps) { export function BillsList({ bills, period, onPay }: BillsListProps) {
if (bills.length === 0) { if (bills.length === 0) {
return ( return (
<WidgetEmptyState <WidgetEmptyState
@@ -22,7 +23,7 @@ export function BillsList({ bills, onPay }: BillsListProps) {
return ( return (
<ul className="flex flex-col"> <ul className="flex flex-col">
{bills.map((bill) => ( {bills.map((bill) => (
<BillListItem key={bill.id} bill={bill} onPay={onPay} /> <BillListItem key={bill.id} bill={bill} period={period} onPay={onPay} />
))} ))}
</ul> </ul>
); );

View File

@@ -1,14 +1,23 @@
import type { BillDialogState } from "@/features/dashboard/bills/bills-helpers"; import type { BillDialogState } from "@/features/dashboard/bills/bills-helpers";
import type { DashboardBill } from "@/features/dashboard/bills/bills-queries"; import type {
BillPaymentAccountOption,
DashboardBill,
} from "@/features/dashboard/bills/bills-queries";
import { BillPaymentDialog } from "./bill-payment-dialog"; import { BillPaymentDialog } from "./bill-payment-dialog";
import { BillsList } from "./bills-list"; import { BillsList } from "./bills-list";
type BillsWidgetViewProps = { type BillsWidgetViewProps = {
bills: DashboardBill[]; bills: DashboardBill[];
period?: string;
selectedBill: DashboardBill | null; selectedBill: DashboardBill | null;
isModalOpen: boolean; isModalOpen: boolean;
modalState: BillDialogState; modalState: BillDialogState;
isPending: boolean; isPending: boolean;
paymentAccountId: string;
onPaymentAccountChange: (accountId: string) => void;
paymentDate: Date;
onPaymentDateChange: (date: Date) => void;
paymentAccountOptions: BillPaymentAccountOption[];
onOpenPaymentDialog: (billId: string) => void; onOpenPaymentDialog: (billId: string) => void;
onClosePaymentDialog: () => void; onClosePaymentDialog: () => void;
onConfirmPayment: () => void; onConfirmPayment: () => void;
@@ -16,10 +25,16 @@ type BillsWidgetViewProps = {
export function BillsWidgetView({ export function BillsWidgetView({
bills, bills,
period,
selectedBill, selectedBill,
isModalOpen, isModalOpen,
modalState, modalState,
isPending, isPending,
paymentAccountId,
onPaymentAccountChange,
paymentDate,
onPaymentDateChange,
paymentAccountOptions,
onOpenPaymentDialog, onOpenPaymentDialog,
onClosePaymentDialog, onClosePaymentDialog,
onConfirmPayment, onConfirmPayment,
@@ -27,7 +42,7 @@ export function BillsWidgetView({
return ( return (
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<BillsList bills={bills} onPay={onOpenPaymentDialog} /> <BillsList bills={bills} period={period} onPay={onOpenPaymentDialog} />
</div> </div>
<BillPaymentDialog <BillPaymentDialog
@@ -35,6 +50,11 @@ export function BillsWidgetView({
open={isModalOpen} open={isModalOpen}
modalState={modalState} modalState={modalState}
isPending={isPending} isPending={isPending}
paymentAccountId={paymentAccountId}
onPaymentAccountChange={onPaymentAccountChange}
paymentDate={paymentDate}
onPaymentDateChange={onPaymentDateChange}
paymentAccountOptions={paymentAccountOptions}
onClose={onClosePaymentDialog} onClose={onClosePaymentDialog}
onConfirm={onConfirmPayment} onConfirm={onConfirmPayment}
/> />

View File

@@ -1,20 +1,35 @@
"use client"; "use client";
import type { DashboardBill } from "@/features/dashboard/bills/bills-queries"; import type {
BillPaymentAccountOption,
DashboardBill,
} from "@/features/dashboard/bills/bills-queries";
import { useBillWidgetController } from "@/features/dashboard/bills/use-bill-widget-controller"; import { useBillWidgetController } from "@/features/dashboard/bills/use-bill-widget-controller";
import { BillsWidgetView } from "../bills/bills-widget-view"; import { BillsWidgetView } from "../bills/bills-widget-view";
type BillWidgetProps = { type BillWidgetProps = {
bills?: DashboardBill[]; bills?: DashboardBill[];
paymentAccountOptions?: BillPaymentAccountOption[];
period?: string;
}; };
export function BillWidget({ bills }: BillWidgetProps) { const EMPTY_OPTIONS: BillPaymentAccountOption[] = [];
export function BillWidget({
bills,
paymentAccountOptions = EMPTY_OPTIONS,
period,
}: BillWidgetProps) {
const { const {
items, items,
selectedBill, selectedBill,
isModalOpen, isModalOpen,
modalState, modalState,
isPending, isPending,
paymentAccountId,
setPaymentAccountId,
paymentDate,
setPaymentDate,
openPaymentDialog, openPaymentDialog,
closePaymentDialog, closePaymentDialog,
confirmPayment, confirmPayment,
@@ -23,10 +38,16 @@ export function BillWidget({ bills }: BillWidgetProps) {
return ( return (
<BillsWidgetView <BillsWidgetView
bills={items} bills={items}
period={period}
selectedBill={selectedBill} selectedBill={selectedBill}
isModalOpen={isModalOpen} isModalOpen={isModalOpen}
modalState={modalState} modalState={modalState}
isPending={isPending} isPending={isPending}
paymentAccountId={paymentAccountId}
onPaymentAccountChange={setPaymentAccountId}
paymentDate={paymentDate}
onPaymentDateChange={setPaymentDate}
paymentAccountOptions={paymentAccountOptions}
onOpenPaymentDialog={openPaymentDialog} onOpenPaymentDialog={openPaymentDialog}
onClosePaymentDialog={closePaymentDialog} onClosePaymentDialog={closePaymentDialog}
onConfirmPayment={confirmPayment} onConfirmPayment={confirmPayment}

View File

@@ -93,7 +93,10 @@ export const widgetsConfig: WidgetConfig[] = [
subtitle: "Resumo das faturas do período", subtitle: "Resumo das faturas do período",
icon: <RiBillLine className="size-4" />, icon: <RiBillLine className="size-4" />,
component: ({ data }) => ( component: ({ data }) => (
<InvoicesWidget invoices={data.invoicesSnapshot.invoices} /> <InvoicesWidget
invoices={data.invoicesSnapshot.invoices}
paymentAccountOptions={data.invoicesSnapshot.paymentAccountOptions}
/>
), ),
}, },
{ {
@@ -101,7 +104,13 @@ export const widgetsConfig: WidgetConfig[] = [
title: "Boletos", title: "Boletos",
subtitle: "Controle de boletos do período", subtitle: "Controle de boletos do período",
icon: <RiBarcodeLine className="size-4" />, icon: <RiBarcodeLine className="size-4" />,
component: ({ data }) => <BillWidget bills={data.billsSnapshot.bills} />, component: ({ data, period }) => (
<BillWidget
bills={data.billsSnapshot.bills}
paymentAccountOptions={data.invoicesSnapshot.paymentAccountOptions}
period={period}
/>
),
}, },
{ {
id: "payment-status", id: "payment-status",