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 (
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer width="full">
|
||||
<RdxPageStack spacing="default">
|
||||
<RdxPageHeader
|
||||
description={t("web.settings.description")}
|
||||
title={t("web.settings.title")}
|
||||
/>
|
||||
<RdxPageContainer className="space-y-6" width="full">
|
||||
<RdxPageHeader
|
||||
description={t("web.settings.description")}
|
||||
title={t("web.settings.title")}
|
||||
/>
|
||||
|
||||
<RdxPageStack spacing="default">
|
||||
<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">
|
||||
<SettingsNavigation sections={sections} />
|
||||
|
||||
@ -4,6 +4,7 @@ export type PaymentMethodSummary = {
|
||||
id: UniqueID;
|
||||
companyId: UniqueID;
|
||||
name: Name;
|
||||
description: string | null;
|
||||
isActive: boolean;
|
||||
isSystem: boolean;
|
||||
isDefault: boolean;
|
||||
|
||||
@ -13,6 +13,7 @@ export class PaymentMethodSummarySnapshotBuilder implements IPaymentMethodSummar
|
||||
company_id: paymentMethod.companyId.toString(),
|
||||
|
||||
name: paymentMethod.name.toString(),
|
||||
description: paymentMethod.description,
|
||||
|
||||
is_system: paymentMethod.isSystem,
|
||||
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!,
|
||||
companyId: companyId!,
|
||||
name: name!,
|
||||
description: raw.description ?? null,
|
||||
isActive: isActive,
|
||||
isSystem: isSystem,
|
||||
isDefault: isDefault,
|
||||
|
||||
@ -5,6 +5,7 @@ export const PaymentMethodSummarySchema = z.object({
|
||||
company_id: z.uuid(),
|
||||
|
||||
name: z.string(),
|
||||
description: z.string().nullable(),
|
||||
is_active: z.boolean(),
|
||||
is_system: z.boolean(),
|
||||
is_default: z.boolean(),
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
"paymentMethods": {
|
||||
"title": "Payment methods",
|
||||
"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": {
|
||||
"placeholder": "Search method..."
|
||||
},
|
||||
@ -46,6 +50,7 @@
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"system": "System",
|
||||
"manual": "Manual",
|
||||
"default": "Default"
|
||||
},
|
||||
"create": {
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
"paymentMethods": {
|
||||
"title": "Métodos de pago",
|
||||
"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": {
|
||||
"placeholder": "Buscar método..."
|
||||
},
|
||||
@ -46,6 +50,7 @@
|
||||
"active": "Activo",
|
||||
"inactive": "Inactivo",
|
||||
"system": "Sistema",
|
||||
"manual": "Manual",
|
||||
"default": "Predeterminado"
|
||||
},
|
||||
"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,
|
||||
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
|
||||
isSystem: dto.is_system,
|
||||
isActive: dto.is_active,
|
||||
|
||||
@ -10,6 +10,7 @@ export interface PaymentMethodListRow {
|
||||
companyId: string;
|
||||
|
||||
name: string;
|
||||
description: string | null;
|
||||
|
||||
isSystem: boolean;
|
||||
isActive: boolean;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { CirclePlusIcon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
|
||||
@ -6,17 +7,18 @@ export const PaymentMethodsEmptyState = ({ onCreate }: { onCreate: () => void })
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-dashed border-border bg-muted/20 p-8 text-center">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">{t("settings.paymentMethods.empty.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("settings.paymentMethods.empty.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button className="mt-6" onClick={onCreate}>
|
||||
{t("settings.paymentMethods.actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
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"
|
||||
onClick={onCreate}
|
||||
variant="outline"
|
||||
>
|
||||
<span className="flex items-center justify-center gap-2">
|
||||
<CirclePlusIcon aria-hidden className="size-5 text-muted-foreground" />
|
||||
{t("settings.paymentMethods.add.title")}
|
||||
</span>
|
||||
<span className="text-sm font-normal text-muted-foreground">
|
||||
{t("settings.paymentMethods.add.description")}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
import { PageErrorState } from "@erp/core/components";
|
||||
import { RdxBadge, RdxPageContainer } from "@repo/rdx-ui/components";
|
||||
import { Button, Card, CardContent, RadioGroup, RadioGroupItem } from "@repo/shadcn-ui/components";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import {
|
||||
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 { usePaymentMethodsSettingsPageController } from "../../controllers";
|
||||
@ -71,46 +79,55 @@ export const PaymentMethodsSettingsPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
{t("settings.paymentMethods.title")}
|
||||
</h2>
|
||||
<p className="max-w-3xl text-sm text-muted-foreground">
|
||||
{t("settings.paymentMethods.description")}
|
||||
</p>
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<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">
|
||||
{t("settings.paymentMethods.title")}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("settings.paymentMethods.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{ctrl.list.length > 0 && (
|
||||
<Button className="shrink-0 self-start" onClick={featureCtrl.openCreateDialog}>
|
||||
<PlusIcon aria-hidden className="size-4" />
|
||||
{t("settings.paymentMethods.actions.create")}
|
||||
</Button>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent className="@container space-y-3">
|
||||
{ctrl.list.length > 0 && (
|
||||
<RadioGroup
|
||||
aria-label={t("settings.paymentMethods.default.label")}
|
||||
className="min-w-0 grid-cols-1 gap-3"
|
||||
disabled={featureCtrl.isChangingDefault}
|
||||
onValueChange={(paymentMethodId) => {
|
||||
const paymentMethod = ctrl.list.find((item) => item.id === paymentMethodId);
|
||||
|
||||
<Button onClick={featureCtrl.openCreateDialog}>
|
||||
<PlusIcon aria-hidden className="mr-2 size-4" />
|
||||
{t("settings.paymentMethods.actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
if (!paymentMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
{ctrl.list.length === 0 ? (
|
||||
<PaymentMethodsEmptyState onCreate={featureCtrl.openCreateDialog} />
|
||||
) : (
|
||||
<RadioGroup
|
||||
aria-label={t("settings.paymentMethods.default.label")}
|
||||
className="space-y-3"
|
||||
disabled={featureCtrl.isChangingDefault}
|
||||
onValueChange={(paymentMethodId) => {
|
||||
const paymentMethod = ctrl.list.find((item) => item.id === paymentMethodId);
|
||||
|
||||
if (!paymentMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
featureCtrl.setDefault(paymentMethod);
|
||||
}}
|
||||
value={ctrl.list.find((item) => item.isDefault)?.id}
|
||||
>
|
||||
{ctrl.list.map((paymentMethod) => (
|
||||
<Card key={paymentMethod.id}>
|
||||
<CardContent className="flex flex-col gap-4 p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex min-w-0 items-start gap-4">
|
||||
featureCtrl.setDefault(paymentMethod);
|
||||
}}
|
||||
value={ctrl.list.find((item) => item.isDefault)?.id}
|
||||
>
|
||||
{ctrl.list.map((paymentMethod) => (
|
||||
<div
|
||||
className={cn(
|
||||
"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
|
||||
aria-label={t("settings.paymentMethods.default.rowLabel", {
|
||||
name: paymentMethod.name,
|
||||
@ -118,116 +135,131 @@ export const PaymentMethodsSettingsPage = () => {
|
||||
id={`payment-method-default-${paymentMethod.id}`}
|
||||
value={paymentMethod.id}
|
||||
/>
|
||||
|
||||
<div className="min-w-0 space-y-1">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<label
|
||||
className="cursor-pointer text-sm font-medium"
|
||||
htmlFor={`payment-method-default-${paymentMethod.id}`}
|
||||
<div className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
||||
<CreditCardIcon aria-hidden className="size-5" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<label
|
||||
className="cursor-pointer break-words text-sm font-semibold text-foreground"
|
||||
htmlFor={`payment-method-default-${paymentMethod.id}`}
|
||||
>
|
||||
{paymentMethod.name}
|
||||
</label>
|
||||
{paymentMethod.description && (
|
||||
<p
|
||||
className="truncate text-sm text-muted-foreground"
|
||||
title={paymentMethod.description}
|
||||
>
|
||||
{paymentMethod.name}
|
||||
</label>
|
||||
|
||||
{paymentMethod.isSystem ? (
|
||||
<RdxBadge variant="secondary">
|
||||
{t("settings.paymentMethods.status.system")}
|
||||
</RdxBadge>
|
||||
) : null}
|
||||
|
||||
{paymentMethod.isDefault ? (
|
||||
<RdxBadge variant="outline">
|
||||
{t("settings.paymentMethods.status.default")}
|
||||
</RdxBadge>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>
|
||||
{paymentMethod.isActive
|
||||
? t("settings.paymentMethods.status.active")
|
||||
: t("settings.paymentMethods.status.inactive")}
|
||||
</span>
|
||||
</div>
|
||||
{paymentMethod.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PaymentMethodRowActions
|
||||
activeDisabledReason={activeDisabledReason(
|
||||
paymentMethod.isDefault,
|
||||
paymentMethod.isSystem
|
||||
<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")}
|
||||
</RdxBadge>
|
||||
)}
|
||||
deleteDisabledReason={deleteDisabledReason(
|
||||
paymentMethod.isDefault,
|
||||
paymentMethod.isSystem
|
||||
)}
|
||||
isTogglingActive={featureCtrl.isTogglingActive}
|
||||
onDelete={featureCtrl.openDeleteDialog}
|
||||
onEdit={featureCtrl.openEditDialog}
|
||||
onToggleActive={(item) => {
|
||||
if (item.isActive) {
|
||||
featureCtrl.disable(item);
|
||||
return;
|
||||
}
|
||||
<RdxBadge
|
||||
className={cn(
|
||||
"gap-1.5 rounded-full px-3",
|
||||
paymentMethod.isActive && "bg-primary/10 text-primary"
|
||||
)}
|
||||
variant={paymentMethod.isActive ? "secondary" : "destructive"}
|
||||
>
|
||||
<CircleIcon aria-hidden className="fill-current" />
|
||||
{paymentMethod.isActive
|
||||
? t("settings.paymentMethods.status.active")
|
||||
: t("settings.paymentMethods.status.inactive")}
|
||||
</RdxBadge>
|
||||
<RdxBadge className="rounded-full px-3" variant="secondary">
|
||||
{paymentMethod.isSystem
|
||||
? t("settings.paymentMethods.status.system")
|
||||
: t("settings.paymentMethods.status.manual")}
|
||||
</RdxBadge>
|
||||
</div>
|
||||
<div className="ml-auto shrink-0">
|
||||
<PaymentMethodRowActions
|
||||
activeDisabledReason={activeDisabledReason(
|
||||
paymentMethod.isDefault,
|
||||
paymentMethod.isSystem
|
||||
)}
|
||||
deleteDisabledReason={deleteDisabledReason(
|
||||
paymentMethod.isDefault,
|
||||
paymentMethod.isSystem
|
||||
)}
|
||||
isTogglingActive={featureCtrl.isTogglingActive}
|
||||
onDelete={featureCtrl.openDeleteDialog}
|
||||
onEdit={featureCtrl.openEditDialog}
|
||||
onToggleActive={(item) => {
|
||||
if (item.isActive) {
|
||||
featureCtrl.disable(item);
|
||||
return;
|
||||
}
|
||||
|
||||
featureCtrl.enable(item);
|
||||
}}
|
||||
paymentMethod={paymentMethod}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</RadioGroup>
|
||||
)}
|
||||
featureCtrl.enable(item);
|
||||
}}
|
||||
paymentMethod={paymentMethod}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
)}
|
||||
<PaymentMethodsEmptyState onCreate={featureCtrl.openCreateDialog} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<PaymentMethodFormDialog
|
||||
description={t("settings.paymentMethods.create.description")}
|
||||
form={featureCtrl.form}
|
||||
formId={featureCtrl.formId}
|
||||
isEditMode={false}
|
||||
isSubmitting={featureCtrl.isSaving}
|
||||
onSubmit={featureCtrl.onSubmit}
|
||||
open={featureCtrl.isCreateOpen}
|
||||
openChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeCreateDialog();
|
||||
}
|
||||
}}
|
||||
submitLabel={t("settings.paymentMethods.actions.create")}
|
||||
title={t("settings.paymentMethods.create.title")}
|
||||
/>
|
||||
|
||||
<PaymentMethodFormDialog
|
||||
description={t("settings.paymentMethods.edit.description")}
|
||||
disableActiveField={featureCtrl.editingPaymentMethod?.isDefault === true}
|
||||
form={featureCtrl.form}
|
||||
formId={`${featureCtrl.formId}-edit`}
|
||||
isEditMode
|
||||
isSubmitting={featureCtrl.isSaving || featureCtrl.isEditingPaymentMethodLoading}
|
||||
onSubmit={featureCtrl.onSubmit}
|
||||
open={
|
||||
Boolean(featureCtrl.editingPaymentMethod) || featureCtrl.isEditingPaymentMethodLoading
|
||||
<PaymentMethodFormDialog
|
||||
description={t("settings.paymentMethods.create.description")}
|
||||
form={featureCtrl.form}
|
||||
formId={featureCtrl.formId}
|
||||
isEditMode={false}
|
||||
isSubmitting={featureCtrl.isSaving}
|
||||
onSubmit={featureCtrl.onSubmit}
|
||||
open={featureCtrl.isCreateOpen}
|
||||
openChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeCreateDialog();
|
||||
}
|
||||
openChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeEditDialog();
|
||||
}
|
||||
}}
|
||||
submitLabel={t("settings.paymentMethods.actions.save")}
|
||||
title={t("settings.paymentMethods.edit.title")}
|
||||
/>
|
||||
}}
|
||||
submitLabel={t("settings.paymentMethods.actions.create")}
|
||||
title={t("settings.paymentMethods.create.title")}
|
||||
/>
|
||||
|
||||
<DeletePaymentMethodDialog
|
||||
isSubmitting={featureCtrl.isDeleting}
|
||||
onConfirm={featureCtrl.confirmDelete}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeDeleteDialog();
|
||||
}
|
||||
}}
|
||||
open={Boolean(featureCtrl.deletingPaymentMethod)}
|
||||
target={featureCtrl.deletingPaymentMethod}
|
||||
/>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
<PaymentMethodFormDialog
|
||||
description={t("settings.paymentMethods.edit.description")}
|
||||
disableActiveField={featureCtrl.editingPaymentMethod?.isDefault === true}
|
||||
form={featureCtrl.form}
|
||||
formId={`${featureCtrl.formId}-edit`}
|
||||
isEditMode
|
||||
isSubmitting={featureCtrl.isSaving || featureCtrl.isEditingPaymentMethodLoading}
|
||||
onSubmit={featureCtrl.onSubmit}
|
||||
open={
|
||||
Boolean(featureCtrl.editingPaymentMethod) || featureCtrl.isEditingPaymentMethodLoading
|
||||
}
|
||||
openChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeEditDialog();
|
||||
}
|
||||
}}
|
||||
submitLabel={t("settings.paymentMethods.actions.save")}
|
||||
title={t("settings.paymentMethods.edit.title")}
|
||||
/>
|
||||
|
||||
<DeletePaymentMethodDialog
|
||||
isSubmitting={featureCtrl.isDeleting}
|
||||
onConfirm={featureCtrl.confirmDelete}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeDeleteDialog();
|
||||
}
|
||||
}}
|
||||
open={Boolean(featureCtrl.deletingPaymentMethod)}
|
||||
target={featureCtrl.deletingPaymentMethod}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,5 +2,4 @@ export * from "./empty-cell-value";
|
||||
export * from "./error-alert";
|
||||
export * from "./error-state";
|
||||
export * from "./form";
|
||||
export * from "./page-header";
|
||||
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 { 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 { PlusIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
@ -96,9 +101,8 @@ export const ListIssuedInvoicesPage = () => {
|
||||
return (
|
||||
<RdxPageFrame>
|
||||
<RdxPageContainer className="space-y-6" width="full">
|
||||
<PageHeader
|
||||
description={t("pages.issued_invoices.list.description")}
|
||||
rightSlot={
|
||||
<RdxPageHeader
|
||||
actions={
|
||||
<Button
|
||||
aria-label={t("pages.issued_invoices.create.title")}
|
||||
className="hidden"
|
||||
@ -108,6 +112,7 @@ export const ListIssuedInvoicesPage = () => {
|
||||
{t("pages.issued_invoices.create.title")}
|
||||
</Button>
|
||||
}
|
||||
description={t("pages.issued_invoices.list.description")}
|
||||
title={t("pages.issued_invoices.list.title")}
|
||||
/>
|
||||
|
||||
|
||||
@ -39,14 +39,13 @@ export const RdxPageHeader = ({
|
||||
{leading ? <div className="shrink-0">{leading}</div> : null}
|
||||
|
||||
<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}
|
||||
</h1>
|
||||
|
||||
{description ? (
|
||||
<p className="text-sm font-medium text-muted-foreground">{description}</p>
|
||||
) : null}
|
||||
|
||||
{metadata ? (
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
{metadata}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user