mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-10 03:11:46 +00:00
feat(lancamentos): filtros de status e anexo; feedback visual de fatura paga
- Novos filtros no drawer: somente pagos, somente não pagos, com anexo - Filtros de tipo/condição/pagamento agora usam slugs na URL (sem acentos) - Coluna de liquidação: lançamentos de cartão com fatura paga exibem ícone verde com tooltip — diferenciando do estado pendente - EstabelecimentoInput: popover respeita largura do input ao abrir - slugify extraído para shared/utils/string.ts - INVOICE_PAYMENT_CATEGORY_NAME adicionado em categories/constants.ts - SETTLED_FILTER_VALUES adicionado em transactions/constants.ts - establishment-logo.tsx removido (não utilizado) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,13 @@ export function EstabelecimentoInput({
|
||||
}: EstabelecimentoInputProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [searchValue, setSearchValue] = React.useState("");
|
||||
const [width, setWidth] = React.useState<number | undefined>();
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open || !containerRef.current) return;
|
||||
setWidth(containerRef.current.offsetWidth);
|
||||
}, [open]);
|
||||
|
||||
const handleSelect = (selectedValue: string) => {
|
||||
onChange(selectedValue);
|
||||
@@ -50,7 +57,6 @@ export function EstabelecimentoInput({
|
||||
onChange(newValue);
|
||||
setSearchValue(newValue);
|
||||
|
||||
// Open popover when user types and there are suggestions
|
||||
if (newValue.length > 0 && estabelecimentos.length > 0) {
|
||||
setOpen(true);
|
||||
}
|
||||
@@ -68,7 +74,7 @@ export function EstabelecimentoInput({
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen} modal>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="relative">
|
||||
<div ref={containerRef} className="relative w-full">
|
||||
<Input
|
||||
id={id}
|
||||
value={value}
|
||||
@@ -86,7 +92,8 @@ export function EstabelecimentoInput({
|
||||
</PopoverTrigger>
|
||||
{estabelecimentos.length > 0 && (
|
||||
<PopoverContent
|
||||
className="p-0 w-[--radix-popover-trigger-width]"
|
||||
className="p-0"
|
||||
style={width ? { width } : undefined}
|
||||
align="start"
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// Re-export from shared — componente movido para src/shared/components/entity-avatar/
|
||||
export { EstablishmentLogo as EstabelecimentoLogo } from "@/shared/components/entity-avatar";
|
||||
@@ -210,7 +210,7 @@ function buildColumns({
|
||||
<span className="flex items-center gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="line-clamp-2 max-w-[180px] font-medium truncate">
|
||||
<span className="line-clamp-2 max-w-[180px] font-semibold truncate">
|
||||
{name}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
@@ -570,12 +570,44 @@ function buildColumns({
|
||||
paymentMethod === "Transferência bancária" ||
|
||||
paymentMethod === "Pré-Pago | VR/VA";
|
||||
|
||||
if (!canToggleSettlement)
|
||||
if (!canToggleSettlement) {
|
||||
const invoicePaid = Boolean(row.original.isSettled);
|
||||
return (
|
||||
<span className="flex size-7 shrink-0 items-center justify-center">
|
||||
<RiBankCard2Line className="size-4 text-muted-foreground/30" />
|
||||
</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
disabled
|
||||
className={cn(
|
||||
"transition-colors",
|
||||
invoicePaid
|
||||
? "bg-success/10 text-success"
|
||||
: "text-muted-foreground/30",
|
||||
)}
|
||||
>
|
||||
{invoicePaid ? (
|
||||
<RiCheckboxCircleFill className="size-4" />
|
||||
) : (
|
||||
<RiBankCard2Line className="size-4" />
|
||||
)}
|
||||
<span className="sr-only">
|
||||
{invoicePaid
|
||||
? "Fatura paga"
|
||||
: "Lançamento de cartão de crédito"}
|
||||
</span>
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-48 text-center">
|
||||
{invoicePaid
|
||||
? "Fatura paga"
|
||||
: "Lançamentos de cartão de crédito são liquidados ao pagar a fatura"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
const readOnly = row.original.readonly;
|
||||
const loading = isSettlementLoading(row.original.id);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "react";
|
||||
import {
|
||||
PAYMENT_METHODS,
|
||||
SETTLED_FILTER_VALUES,
|
||||
TRANSACTION_CONDITIONS,
|
||||
TRANSACTION_TYPES,
|
||||
} from "@/features/transactions/constants";
|
||||
@@ -50,6 +51,8 @@ import {
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
} from "@/shared/components/ui/select";
|
||||
import { Switch } from "@/shared/components/ui/switch";
|
||||
import { slugify } from "@/shared/utils/string";
|
||||
import { cn } from "@/shared/utils/ui";
|
||||
import {
|
||||
AccountCardSelectContent,
|
||||
@@ -66,9 +69,6 @@ import type {
|
||||
|
||||
const FILTER_EMPTY_VALUE = "__all";
|
||||
|
||||
const buildStaticOptions = (values: readonly string[]) =>
|
||||
values.map((value) => ({ value, label: value }));
|
||||
|
||||
interface FilterSelectProps {
|
||||
param: string;
|
||||
placeholder: string;
|
||||
@@ -263,7 +263,9 @@ export function TransactionsFilters({
|
||||
searchParams.get("payment") ||
|
||||
searchParams.get("payer") ||
|
||||
searchParams.get("category") ||
|
||||
searchParams.get("accountCard");
|
||||
searchParams.get("accountCard") ||
|
||||
searchParams.get("settled") ||
|
||||
searchParams.get("hasAttachment");
|
||||
|
||||
const handleResetFilters = () => {
|
||||
handleReset();
|
||||
@@ -327,7 +329,10 @@ export function TransactionsFilters({
|
||||
<FilterSelect
|
||||
param="type"
|
||||
placeholder="Todos"
|
||||
options={buildStaticOptions(TRANSACTION_TYPES)}
|
||||
options={TRANSACTION_TYPES.map((v) => ({
|
||||
value: slugify(v),
|
||||
label: v,
|
||||
}))}
|
||||
widthClass="w-full border-dashed"
|
||||
disabled={isPending}
|
||||
getParamValue={getParamValue}
|
||||
@@ -345,7 +350,10 @@ export function TransactionsFilters({
|
||||
<FilterSelect
|
||||
param="condition"
|
||||
placeholder="Todas"
|
||||
options={buildStaticOptions(TRANSACTION_CONDITIONS)}
|
||||
options={TRANSACTION_CONDITIONS.map((v) => ({
|
||||
value: slugify(v),
|
||||
label: v,
|
||||
}))}
|
||||
widthClass="w-full border-dashed"
|
||||
disabled={isPending}
|
||||
getParamValue={getParamValue}
|
||||
@@ -363,7 +371,10 @@ export function TransactionsFilters({
|
||||
<FilterSelect
|
||||
param="payment"
|
||||
placeholder="Todos"
|
||||
options={buildStaticOptions(PAYMENT_METHODS)}
|
||||
options={PAYMENT_METHODS.map((v) => ({
|
||||
value: slugify(v),
|
||||
label: v,
|
||||
}))}
|
||||
widthClass="w-full border-dashed"
|
||||
disabled={isPending}
|
||||
getParamValue={getParamValue}
|
||||
@@ -547,6 +558,76 @@ export function TransactionsFilters({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm font-medium">Status</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<label
|
||||
htmlFor="filter-pago"
|
||||
className="text-sm text-muted-foreground cursor-pointer"
|
||||
>
|
||||
Somente pagos
|
||||
</label>
|
||||
<Switch
|
||||
id="filter-pago"
|
||||
checked={
|
||||
searchParams.get("settled") ===
|
||||
SETTLED_FILTER_VALUES.PAID
|
||||
}
|
||||
disabled={isPending}
|
||||
onCheckedChange={(checked) => {
|
||||
handleFilterChange(
|
||||
"settled",
|
||||
checked ? SETTLED_FILTER_VALUES.PAID : null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label
|
||||
htmlFor="filter-nao-pago"
|
||||
className="text-sm text-muted-foreground cursor-pointer"
|
||||
>
|
||||
Somente não pagos
|
||||
</label>
|
||||
<Switch
|
||||
id="filter-nao-pago"
|
||||
checked={
|
||||
searchParams.get("settled") ===
|
||||
SETTLED_FILTER_VALUES.UNPAID
|
||||
}
|
||||
disabled={isPending}
|
||||
onCheckedChange={(checked) => {
|
||||
handleFilterChange(
|
||||
"settled",
|
||||
checked ? SETTLED_FILTER_VALUES.UNPAID : null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<label
|
||||
htmlFor="filter-has-attachment"
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
Com anexo
|
||||
</label>
|
||||
<Switch
|
||||
id="filter-has-attachment"
|
||||
checked={searchParams.get("hasAttachment") === "true"}
|
||||
disabled={isPending}
|
||||
onCheckedChange={(checked) => {
|
||||
handleFilterChange(
|
||||
"hasAttachment",
|
||||
checked ? "true" : null,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DrawerFooter>
|
||||
|
||||
Reference in New Issue
Block a user