feat: agregar soporte para descripciones en métodos de pago y mejorar la interfaz de usuario
This commit is contained in:
parent
685472b3ef
commit
021c19fc40
@ -24,13 +24,13 @@ export const SettingsLayout = ({ sections }: SettingsLayoutProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RdxPageFrame>
|
<RdxPageFrame>
|
||||||
<RdxPageContainer width="full">
|
<RdxPageContainer className="space-y-6" width="full">
|
||||||
<RdxPageStack spacing="default">
|
|
||||||
<RdxPageHeader
|
<RdxPageHeader
|
||||||
description={t("web.settings.description")}
|
description={t("web.settings.description")}
|
||||||
title={t("web.settings.title")}
|
title={t("web.settings.title")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<RdxPageStack spacing="default">
|
||||||
<div className="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
<div className="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
||||||
<aside className="min-w-0 lg:sticky lg:top-6 lg:self-start">
|
<aside className="min-w-0 lg:sticky lg:top-6 lg:self-start">
|
||||||
<SettingsNavigation sections={sections} />
|
<SettingsNavigation sections={sections} />
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export type PaymentMethodSummary = {
|
|||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
name: Name;
|
name: Name;
|
||||||
|
description: string | null;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
isSystem: boolean;
|
isSystem: boolean;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export class PaymentMethodSummarySnapshotBuilder implements IPaymentMethodSummar
|
|||||||
company_id: paymentMethod.companyId.toString(),
|
company_id: paymentMethod.companyId.toString(),
|
||||||
|
|
||||||
name: paymentMethod.name.toString(),
|
name: paymentMethod.name.toString(),
|
||||||
|
description: paymentMethod.description,
|
||||||
|
|
||||||
is_system: paymentMethod.isSystem,
|
is_system: paymentMethod.isSystem,
|
||||||
is_active: paymentMethod.isActive,
|
is_active: paymentMethod.isActive,
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import { Sequelize } from "sequelize";
|
||||||
|
|
||||||
|
import { PaymentMethodSummarySchema } from "../../../../../../common/dto/payment-methods/shared/payment-method-summary.dto";
|
||||||
|
import { PaymentMethodSummarySnapshotBuilder } from "../../../../../application/payment-methods/snapshot-builders/summary/payment-method-summary-snapshot-builder";
|
||||||
|
import initializePaymentMethodModel, {
|
||||||
|
PaymentMethodModel,
|
||||||
|
} from "../models/sequelize-payment-method.model";
|
||||||
|
|
||||||
|
import { SequelizePaymentMethodSummaryMapper } from "./sequelize-payment-method-summary.mapper";
|
||||||
|
|
||||||
|
const database = new Sequelize({ dialect: "mysql", logging: false });
|
||||||
|
initializePaymentMethodModel(database);
|
||||||
|
|
||||||
|
for (const description of [
|
||||||
|
"Cargo automático en cuenta bancaria.",
|
||||||
|
"Descripción extensa ".repeat(10),
|
||||||
|
null,
|
||||||
|
]) {
|
||||||
|
test(`conserva la descripción ${description === null ? "ausente" : "textual"} desde persistencia hasta el DTO (${description?.length ?? 0})`, () => {
|
||||||
|
const source = {
|
||||||
|
id: "11111111-1111-4111-8111-111111111111",
|
||||||
|
company_id: "22222222-2222-4222-8222-222222222222",
|
||||||
|
name: "Método de prueba",
|
||||||
|
description,
|
||||||
|
is_active: true,
|
||||||
|
is_system: false,
|
||||||
|
is_default: true,
|
||||||
|
};
|
||||||
|
const result = new SequelizePaymentMethodSummaryMapper().mapToReadModel(
|
||||||
|
PaymentMethodModel.build(source)
|
||||||
|
);
|
||||||
|
assert.equal(result.isFailure, false);
|
||||||
|
const dto = new PaymentMethodSummarySnapshotBuilder().toOutput(result.data);
|
||||||
|
assert.deepEqual(PaymentMethodSummarySchema.parse(dto), source);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -40,6 +40,7 @@ export class SequelizePaymentMethodSummaryMapper extends SequelizeQueryMapper<
|
|||||||
id: id!,
|
id: id!,
|
||||||
companyId: companyId!,
|
companyId: companyId!,
|
||||||
name: name!,
|
name: name!,
|
||||||
|
description: raw.description ?? null,
|
||||||
isActive: isActive,
|
isActive: isActive,
|
||||||
isSystem: isSystem,
|
isSystem: isSystem,
|
||||||
isDefault: isDefault,
|
isDefault: isDefault,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export const PaymentMethodSummarySchema = z.object({
|
|||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
|
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
description: z.string().nullable(),
|
||||||
is_active: z.boolean(),
|
is_active: z.boolean(),
|
||||||
is_system: z.boolean(),
|
is_system: z.boolean(),
|
||||||
is_default: z.boolean(),
|
is_default: z.boolean(),
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
"paymentMethods": {
|
"paymentMethods": {
|
||||||
"title": "Payment methods",
|
"title": "Payment methods",
|
||||||
"description": "Manage the payment methods available for the company.",
|
"description": "Manage the payment methods available for the company.",
|
||||||
|
"add": {
|
||||||
|
"title": "Add a new payment method",
|
||||||
|
"description": "Set up a new payment method for your company."
|
||||||
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Search method..."
|
"placeholder": "Search method..."
|
||||||
},
|
},
|
||||||
@ -46,6 +50,7 @@
|
|||||||
"active": "Active",
|
"active": "Active",
|
||||||
"inactive": "Inactive",
|
"inactive": "Inactive",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
|
"manual": "Manual",
|
||||||
"default": "Default"
|
"default": "Default"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
"paymentMethods": {
|
"paymentMethods": {
|
||||||
"title": "Métodos de pago",
|
"title": "Métodos de pago",
|
||||||
"description": "Gestiona las formas de pago disponibles para la empresa.",
|
"description": "Gestiona las formas de pago disponibles para la empresa.",
|
||||||
|
"add": {
|
||||||
|
"title": "Añadir un nuevo método de pago",
|
||||||
|
"description": "Configura una nueva forma de pago para tu empresa."
|
||||||
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Buscar método..."
|
"placeholder": "Buscar método..."
|
||||||
},
|
},
|
||||||
@ -46,6 +50,7 @@
|
|||||||
"active": "Activo",
|
"active": "Activo",
|
||||||
"inactive": "Inactivo",
|
"inactive": "Inactivo",
|
||||||
"system": "Sistema",
|
"system": "Sistema",
|
||||||
|
"manual": "Manual",
|
||||||
"default": "Predeterminado"
|
"default": "Predeterminado"
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
|
||||||
|
import type { ListPaymentMethodsResponseDTO } from "../../../../common";
|
||||||
|
|
||||||
|
import { ListPaymentMethodsAdapter } from "./list-payment-methods.adapter";
|
||||||
|
|
||||||
|
test("conserva descripciones largas y null sin alterar estados ni paginación", () => {
|
||||||
|
const descriptions = [
|
||||||
|
"Pago por transferencia bancaria.",
|
||||||
|
"Descripción extensa ".repeat(10),
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
const dto: ListPaymentMethodsResponseDTO = {
|
||||||
|
page: 0,
|
||||||
|
per_page: 10,
|
||||||
|
total_pages: 1,
|
||||||
|
total_items: 3,
|
||||||
|
metadata: {
|
||||||
|
entity: "payment_methods",
|
||||||
|
criteria: {
|
||||||
|
q: "",
|
||||||
|
filters: [],
|
||||||
|
orderBy: "name",
|
||||||
|
orderType: "ASC",
|
||||||
|
pageSize: 10,
|
||||||
|
pageNumber: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: descriptions.map((description, index) => ({
|
||||||
|
id: `11111111-1111-4111-8111-11111111111${index}`,
|
||||||
|
company_id: "22222222-2222-4222-8222-222222222222",
|
||||||
|
name: "Método de prueba",
|
||||||
|
description,
|
||||||
|
is_active: index !== 2,
|
||||||
|
is_system: index === 0,
|
||||||
|
is_default: index === 0,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
const result = ListPaymentMethodsAdapter.fromDto(dto);
|
||||||
|
assert.deepEqual(
|
||||||
|
result.items,
|
||||||
|
dto.items.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
companyId: item.company_id,
|
||||||
|
name: item.name,
|
||||||
|
description: item.description,
|
||||||
|
isActive: item.is_active,
|
||||||
|
isSystem: item.is_system,
|
||||||
|
isDefault: item.is_default,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
assert.equal(result.page, dto.page);
|
||||||
|
assert.equal(result.perPage, dto.per_page);
|
||||||
|
assert.equal(result.totalPages, dto.total_pages);
|
||||||
|
assert.equal(result.totalItems, dto.total_items);
|
||||||
|
});
|
||||||
@ -45,6 +45,7 @@ const PaymentMehodListRowAdapter = {
|
|||||||
companyId: dto.company_id,
|
companyId: dto.company_id,
|
||||||
|
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
|
description: dto.description,
|
||||||
|
|
||||||
isSystem: dto.is_system,
|
isSystem: dto.is_system,
|
||||||
isActive: dto.is_active,
|
isActive: dto.is_active,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export interface PaymentMethodListRow {
|
|||||||
companyId: string;
|
companyId: string;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
|
||||||
isSystem: boolean;
|
isSystem: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components";
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
|
import { CirclePlusIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
|
||||||
@ -6,17 +7,18 @@ export const PaymentMethodsEmptyState = ({ onCreate }: { onCreate: () => void })
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-dashed border-border bg-muted/20 p-8 text-center">
|
<Button
|
||||||
<div className="space-y-2">
|
className="h-auto min-h-28 w-full flex-col gap-2 whitespace-normal rounded-xl border-dashed px-4 py-6 text-center shadow-none transition-colors hover:bg-muted/50"
|
||||||
<h3 className="text-lg font-semibold">{t("settings.paymentMethods.empty.title")}</h3>
|
onClick={onCreate}
|
||||||
<p className="text-sm text-muted-foreground">
|
variant="outline"
|
||||||
{t("settings.paymentMethods.empty.description")}
|
>
|
||||||
</p>
|
<span className="flex items-center justify-center gap-2">
|
||||||
</div>
|
<CirclePlusIcon aria-hidden className="size-5 text-muted-foreground" />
|
||||||
|
{t("settings.paymentMethods.add.title")}
|
||||||
<Button className="mt-6" onClick={onCreate}>
|
</span>
|
||||||
{t("settings.paymentMethods.actions.create")}
|
<span className="text-sm font-normal text-muted-foreground">
|
||||||
|
{t("settings.paymentMethods.add.description")}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
import { PageErrorState } from "@erp/core/components";
|
import { PageErrorState } from "@erp/core/components";
|
||||||
import { RdxBadge, RdxPageContainer } from "@repo/rdx-ui/components";
|
import { RdxBadge, RdxPageContainer } from "@repo/rdx-ui/components";
|
||||||
import { Button, Card, CardContent, RadioGroup, RadioGroupItem } from "@repo/shadcn-ui/components";
|
import {
|
||||||
import { PlusIcon } from "lucide-react";
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
RadioGroup,
|
||||||
|
RadioGroupItem,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
|
import { CircleIcon, CreditCardIcon, PlusIcon, StarIcon } from "lucide-react";
|
||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import { usePaymentMethodsSettingsPageController } from "../../controllers";
|
import { usePaymentMethodsSettingsPageController } from "../../controllers";
|
||||||
@ -71,30 +79,34 @@ export const PaymentMethodsSettingsPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RdxPageContainer width="full">
|
<>
|
||||||
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6">
|
<Card>
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
<CardHeader className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div className="space-y-2">
|
<div className="flex min-w-0 items-start gap-4">
|
||||||
|
<div className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||||
|
<CreditCardIcon aria-hidden className="size-6" />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 space-y-1">
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
<h2 className="text-2xl font-semibold tracking-tight">
|
||||||
{t("settings.paymentMethods.title")}
|
{t("settings.paymentMethods.title")}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="max-w-3xl text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t("settings.paymentMethods.description")}
|
{t("settings.paymentMethods.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<Button onClick={featureCtrl.openCreateDialog}>
|
{ctrl.list.length > 0 && (
|
||||||
<PlusIcon aria-hidden className="mr-2 size-4" />
|
<Button className="shrink-0 self-start" onClick={featureCtrl.openCreateDialog}>
|
||||||
|
<PlusIcon aria-hidden className="size-4" />
|
||||||
{t("settings.paymentMethods.actions.create")}
|
{t("settings.paymentMethods.actions.create")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
|
</CardHeader>
|
||||||
{ctrl.list.length === 0 ? (
|
<CardContent className="@container space-y-3">
|
||||||
<PaymentMethodsEmptyState onCreate={featureCtrl.openCreateDialog} />
|
{ctrl.list.length > 0 && (
|
||||||
) : (
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
aria-label={t("settings.paymentMethods.default.label")}
|
aria-label={t("settings.paymentMethods.default.label")}
|
||||||
className="space-y-3"
|
className="min-w-0 grid-cols-1 gap-3"
|
||||||
disabled={featureCtrl.isChangingDefault}
|
disabled={featureCtrl.isChangingDefault}
|
||||||
onValueChange={(paymentMethodId) => {
|
onValueChange={(paymentMethodId) => {
|
||||||
const paymentMethod = ctrl.list.find((item) => item.id === paymentMethodId);
|
const paymentMethod = ctrl.list.find((item) => item.id === paymentMethodId);
|
||||||
@ -108,9 +120,14 @@ export const PaymentMethodsSettingsPage = () => {
|
|||||||
value={ctrl.list.find((item) => item.isDefault)?.id}
|
value={ctrl.list.find((item) => item.isDefault)?.id}
|
||||||
>
|
>
|
||||||
{ctrl.list.map((paymentMethod) => (
|
{ctrl.list.map((paymentMethod) => (
|
||||||
<Card key={paymentMethod.id}>
|
<div
|
||||||
<CardContent className="flex flex-col gap-4 p-4 md:flex-row md:items-center md:justify-between">
|
className={cn(
|
||||||
<div className="flex min-w-0 items-start gap-4">
|
"flex min-w-0 flex-wrap items-center gap-4 rounded-xl border border-border p-4 @min-[52rem]:flex-nowrap",
|
||||||
|
paymentMethod.isDefault ? "border-primary/20 bg-primary/5" : "bg-card"
|
||||||
|
)}
|
||||||
|
key={paymentMethod.id}
|
||||||
|
>
|
||||||
|
<div className="flex min-w-0 basis-full items-center gap-3 @min-[52rem]:basis-0 @min-[52rem]:flex-1">
|
||||||
<RadioGroupItem
|
<RadioGroupItem
|
||||||
aria-label={t("settings.paymentMethods.default.rowLabel", {
|
aria-label={t("settings.paymentMethods.default.rowLabel", {
|
||||||
name: paymentMethod.name,
|
name: paymentMethod.name,
|
||||||
@ -118,39 +135,52 @@ export const PaymentMethodsSettingsPage = () => {
|
|||||||
id={`payment-method-default-${paymentMethod.id}`}
|
id={`payment-method-default-${paymentMethod.id}`}
|
||||||
value={paymentMethod.id}
|
value={paymentMethod.id}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||||
<div className="min-w-0 space-y-1">
|
<CreditCardIcon aria-hidden className="size-5" />
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
</div>
|
||||||
|
<div className="min-w-0 flex-1 space-y-1">
|
||||||
<label
|
<label
|
||||||
className="cursor-pointer text-sm font-medium"
|
className="cursor-pointer break-words text-sm font-semibold text-foreground"
|
||||||
htmlFor={`payment-method-default-${paymentMethod.id}`}
|
htmlFor={`payment-method-default-${paymentMethod.id}`}
|
||||||
>
|
>
|
||||||
{paymentMethod.name}
|
{paymentMethod.name}
|
||||||
</label>
|
</label>
|
||||||
|
{paymentMethod.description && (
|
||||||
{paymentMethod.isSystem ? (
|
<p
|
||||||
<RdxBadge variant="secondary">
|
className="truncate text-sm text-muted-foreground"
|
||||||
{t("settings.paymentMethods.status.system")}
|
title={paymentMethod.description}
|
||||||
</RdxBadge>
|
>
|
||||||
) : null}
|
{paymentMethod.description}
|
||||||
|
</p>
|
||||||
{paymentMethod.isDefault ? (
|
)}
|
||||||
<RdxBadge variant="outline">
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{paymentMethod.isDefault && (
|
||||||
|
<RdxBadge className="gap-1.5 rounded-full bg-primary/10 px-3 text-primary">
|
||||||
|
<StarIcon aria-hidden className="fill-current" />
|
||||||
{t("settings.paymentMethods.status.default")}
|
{t("settings.paymentMethods.status.default")}
|
||||||
</RdxBadge>
|
</RdxBadge>
|
||||||
) : null}
|
)}
|
||||||
</div>
|
<RdxBadge
|
||||||
|
className={cn(
|
||||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
"gap-1.5 rounded-full px-3",
|
||||||
<span>
|
paymentMethod.isActive && "bg-primary/10 text-primary"
|
||||||
|
)}
|
||||||
|
variant={paymentMethod.isActive ? "secondary" : "destructive"}
|
||||||
|
>
|
||||||
|
<CircleIcon aria-hidden className="fill-current" />
|
||||||
{paymentMethod.isActive
|
{paymentMethod.isActive
|
||||||
? t("settings.paymentMethods.status.active")
|
? t("settings.paymentMethods.status.active")
|
||||||
: t("settings.paymentMethods.status.inactive")}
|
: t("settings.paymentMethods.status.inactive")}
|
||||||
</span>
|
</RdxBadge>
|
||||||
|
<RdxBadge className="rounded-full px-3" variant="secondary">
|
||||||
|
{paymentMethod.isSystem
|
||||||
|
? t("settings.paymentMethods.status.system")
|
||||||
|
: t("settings.paymentMethods.status.manual")}
|
||||||
|
</RdxBadge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="ml-auto shrink-0">
|
||||||
</div>
|
|
||||||
|
|
||||||
<PaymentMethodRowActions
|
<PaymentMethodRowActions
|
||||||
activeDisabledReason={activeDisabledReason(
|
activeDisabledReason={activeDisabledReason(
|
||||||
paymentMethod.isDefault,
|
paymentMethod.isDefault,
|
||||||
@ -173,11 +203,14 @@ export const PaymentMethodsSettingsPage = () => {
|
|||||||
}}
|
}}
|
||||||
paymentMethod={paymentMethod}
|
paymentMethod={paymentMethod}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
)}
|
)}
|
||||||
|
<PaymentMethodsEmptyState onCreate={featureCtrl.openCreateDialog} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<PaymentMethodFormDialog
|
<PaymentMethodFormDialog
|
||||||
description={t("settings.paymentMethods.create.description")}
|
description={t("settings.paymentMethods.create.description")}
|
||||||
@ -227,7 +260,6 @@ export const PaymentMethodsSettingsPage = () => {
|
|||||||
open={Boolean(featureCtrl.deletingPaymentMethod)}
|
open={Boolean(featureCtrl.deletingPaymentMethod)}
|
||||||
target={featureCtrl.deletingPaymentMethod}
|
target={featureCtrl.deletingPaymentMethod}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
</RdxPageContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,5 +2,4 @@ export * from "./empty-cell-value";
|
|||||||
export * from "./error-alert";
|
export * from "./error-alert";
|
||||||
export * from "./error-state";
|
export * from "./error-state";
|
||||||
export * from "./form";
|
export * from "./form";
|
||||||
export * from "./page-header";
|
|
||||||
export * from "./page-error-state";
|
export * from "./page-error-state";
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
import { Button } from "@repo/shadcn-ui/components";
|
|
||||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
|
||||||
import { ChevronLeftIcon } from "lucide-react";
|
|
||||||
import type { ReactNode } from "react";
|
|
||||||
|
|
||||||
interface PageHeaderProps {
|
|
||||||
title: ReactNode;
|
|
||||||
description?: ReactNode;
|
|
||||||
rightSlot?: ReactNode;
|
|
||||||
|
|
||||||
showBackButton?: boolean;
|
|
||||||
onBackClick?: () => void;
|
|
||||||
backButtonLabel?: string;
|
|
||||||
|
|
||||||
statusSlot?: ReactNode;
|
|
||||||
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PageHeader = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
rightSlot,
|
|
||||||
showBackButton = false,
|
|
||||||
onBackClick,
|
|
||||||
backButtonLabel = "Volver",
|
|
||||||
statusSlot,
|
|
||||||
className,
|
|
||||||
}: PageHeaderProps) => {
|
|
||||||
return (
|
|
||||||
<header
|
|
||||||
className={cn(
|
|
||||||
"mb-6 flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<div className="flex min-w-0 items-start gap-4">
|
|
||||||
{showBackButton && (
|
|
||||||
<Button
|
|
||||||
aria-label={backButtonLabel}
|
|
||||||
className="shrink-0 cursor-pointer"
|
|
||||||
onClick={onBackClick}
|
|
||||||
size="default"
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon aria-hidden="true" className="size-5" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="min-w-0 space-y-2">
|
|
||||||
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
|
||||||
<h1 className="min-w-0 truncate text-2xl font-semibold tracking-tight text-foreground xl:text-3xl font-heading">
|
|
||||||
{title}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{statusSlot}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{description && (
|
|
||||||
<p className="text-sm font-medium text-muted-foreground">{description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{rightSlot && <div className="flex shrink-0 items-center lg:pt-0">{rightSlot}</div>}
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,6 +1,11 @@
|
|||||||
import { ErrorAlert, PageErrorState, PageHeader, SimpleSearchInput } from "@erp/core/components";
|
import { ErrorAlert, PageErrorState, SimpleSearchInput } from "@erp/core/components";
|
||||||
import { useReturnToNavigation } from "@erp/core/hooks";
|
import { useReturnToNavigation } from "@erp/core/hooks";
|
||||||
import { LogoVerifactu, RdxPageContainer, RdxPageFrame } from "@repo/rdx-ui/components";
|
import {
|
||||||
|
LogoVerifactu,
|
||||||
|
RdxPageContainer,
|
||||||
|
RdxPageFrame,
|
||||||
|
RdxPageHeader,
|
||||||
|
} from "@repo/rdx-ui/components";
|
||||||
import { Alert, AlertDescription, AlertTitle, Button } from "@repo/shadcn-ui/components";
|
import { Alert, AlertDescription, AlertTitle, Button } from "@repo/shadcn-ui/components";
|
||||||
import { PlusIcon } from "lucide-react";
|
import { PlusIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -96,9 +101,8 @@ export const ListIssuedInvoicesPage = () => {
|
|||||||
return (
|
return (
|
||||||
<RdxPageFrame>
|
<RdxPageFrame>
|
||||||
<RdxPageContainer className="space-y-6" width="full">
|
<RdxPageContainer className="space-y-6" width="full">
|
||||||
<PageHeader
|
<RdxPageHeader
|
||||||
description={t("pages.issued_invoices.list.description")}
|
actions={
|
||||||
rightSlot={
|
|
||||||
<Button
|
<Button
|
||||||
aria-label={t("pages.issued_invoices.create.title")}
|
aria-label={t("pages.issued_invoices.create.title")}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
@ -108,6 +112,7 @@ export const ListIssuedInvoicesPage = () => {
|
|||||||
{t("pages.issued_invoices.create.title")}
|
{t("pages.issued_invoices.create.title")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
description={t("pages.issued_invoices.list.description")}
|
||||||
title={t("pages.issued_invoices.list.title")}
|
title={t("pages.issued_invoices.list.title")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -39,14 +39,13 @@ export const RdxPageHeader = ({
|
|||||||
{leading ? <div className="shrink-0">{leading}</div> : null}
|
{leading ? <div className="shrink-0">{leading}</div> : null}
|
||||||
|
|
||||||
<div className="min-w-0 space-y-2">
|
<div className="min-w-0 space-y-2">
|
||||||
<h1 className="min-w-0 text-2xl font-semibold tracking-tight text-foreground font-heading xl:text-3xl">
|
<h1 className="min-w-0 text-2xl font-bold tracking-tight text-foreground font-heading xl:text-3xl">
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="text-sm font-medium text-muted-foreground">{description}</p>
|
<p className="text-sm font-medium text-muted-foreground">{description}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{metadata ? (
|
{metadata ? (
|
||||||
<div className="flex min-w-0 flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex min-w-0 flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||||
{metadata}
|
{metadata}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user