This commit is contained in:
David Arranz 2026-04-12 19:28:47 +02:00
parent 1bbaf0aaee
commit 5f9405d1a0
10 changed files with 119 additions and 129 deletions

View File

@ -1,8 +1,9 @@
import { Avatar, AvatarFallback, Badge } from "@repo/shadcn-ui/components"; import { InitialsAvatar } from "@repo/rdx-ui/components";
import { Badge } from "@repo/shadcn-ui/components";
import { Building2Icon, CopyIcon, UserIcon } from "lucide-react"; import { Building2Icon, CopyIcon, UserIcon } from "lucide-react";
import type { Proforma } from "../../../../shared"; import type { Proforma } from "../../../../shared";
import { Initials, ProformaStatusBadge } from "../../components"; import { ProformaStatusBadge } from "../../components";
export const ProformaHeader = ({ proforma }: { proforma: Proforma }) => { export const ProformaHeader = ({ proforma }: { proforma: Proforma }) => {
const handleCopyTin = async () => { const handleCopyTin = async () => {
@ -16,17 +17,7 @@ export const ProformaHeader = ({ proforma }: { proforma: Proforma }) => {
return ( return (
<div className="p-4"> <div className="p-4">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<Avatar className="size-10 border-2 border-background shadow-sm"> <InitialsAvatar name={proforma.recipient.name} />
<AvatarFallback
className={
proforma.status !== "draft"
? "bg-blue-100 text-blue-700"
: "bg-muted text-muted-foreground"
}
>
<Initials name={proforma.recipient.name} />
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@ -103,7 +103,6 @@ export function useProformasGridColumns(
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Button <Button
asChild
className="size-6 text-foreground hover:text-primary" className="size-6 text-foreground hover:text-primary"
size="icon" size="icon"
variant="ghost" variant="ghost"
@ -143,7 +142,7 @@ export function useProformasGridColumns(
return ( return (
<div> <div>
<button <button
className="text-primary hover:underline font-semibold" className="text-primary hover:underline font-semibold text-ellipsis"
onClick={() => actionHandlers.onPreviewClick?.(proforma)} onClick={() => actionHandlers.onPreviewClick?.(proforma)}
type="button" type="button"
> >
@ -158,10 +157,6 @@ export function useProformasGridColumns(
accessorKey: "series", accessorKey: "series",
header: "Serie", header: "Serie",
}, },
{
accessorKey: "reference",
header: "Reference",
},
{ {
accessorKey: "invoiceDate", accessorKey: "invoiceDate",
header: ({ column }) => { header: ({ column }) => {

View File

@ -97,7 +97,7 @@ export const ListProformasPage = () => {
} }
return ( return (
<> <section>
<AppHeader> <AppHeader>
<PageHeader <PageHeader
description={t("pages.proformas.list.description")} description={t("pages.proformas.list.description")}
@ -193,6 +193,6 @@ export const ListProformasPage = () => {
/> />
</> </>
</AppContent> </AppContent>
</> </section>
); );
}; };

View File

@ -103,6 +103,7 @@ export const ProformaUpdatePage = () => {
isSubmitting={updateCtrl.isUpdating} isSubmitting={updateCtrl.isUpdating}
onChangeCustomerClick={selectCustomerCtrl.selectCtrl.openDialog} onChangeCustomerClick={selectCustomerCtrl.selectCtrl.openDialog}
//onCreateCustomerClick={selectCustomerCtrl.createCtrl.openDialog} //onCreateCustomerClick={selectCustomerCtrl.createCtrl.openDialog}
onCreateCustomerClick={() => null}
onReset={updateCtrl.resetForm} onReset={updateCtrl.resetForm}
onSubmit={updateCtrl.onSubmit} onSubmit={updateCtrl.onSubmit}
selectedCustomer={updateCtrl.selectedCustomer} selectedCustomer={updateCtrl.selectedCustomer}

View File

@ -3,43 +3,20 @@ import {
Button, Button,
Dialog, Dialog,
DialogContent, DialogContent,
DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
Item,
ItemContent,
ItemDescription,
ItemGroup,
ItemTitle,
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils"; import { useState } from "react";
import type { CustomerSelectionOption } from "../../../../../common/features"; import type { CustomerSelectionOption } from "../../../../../common/features";
import { useTranslation } from "../../../../../web/i18n"; import { useTranslation } from "../../../../../web/i18n";
import type { useSelectCustomerDialogController } from "../../controllers"; import type { useSelectCustomerDialogController } from "../../controllers";
import { SelectCustomerEmptyCard } from "./select-customer-empty-card"; import { SelectCustomerSelectionList } from "./select-customer-selection-list";
type SelectCustomerDialogController = ReturnType<typeof useSelectCustomerDialogController>; type SelectCustomerDialogController = ReturnType<typeof useSelectCustomerDialogController>;
/*export const useSelectCustomerDialogController = () => {
isOpen: boolean;
closeDialog: () => void;
search: string;
setSearchValue: (value: string) => void;
customers: CustomerSelectionOption[];
isLoading: boolean;
isFetching: boolean;
isErrror: boolean;
error: unknown;
selectCustomer: (customer: CustomerSelectionOption) => void;
};*/
type SelectCustomerDialogProps = { type SelectCustomerDialogProps = {
ctrl: SelectCustomerDialogController; ctrl: SelectCustomerDialogController;
onCreateNewCustomerClick?: () => void; onCreateNewCustomerClick?: () => void;
@ -49,8 +26,21 @@ export const SelectCustomerDialog = ({
ctrl, ctrl,
onCreateNewCustomerClick, onCreateNewCustomerClick,
}: SelectCustomerDialogProps) => { }: SelectCustomerDialogProps) => {
const [selectedCustomer, setSelectedCustomer] = useState<CustomerSelectionOption | undefined>(
undefined
);
const { t } = useTranslation(); const { t } = useTranslation();
const handleSelect = (customer: CustomerSelectionOption) => {
setSelectedCustomer((prev) => (prev?.id === customer.id ? undefined : customer));
};
const handleConfirm = () => {
if (selectedCustomer) {
ctrl.selectCustomer(selectedCustomer);
}
};
return ( return (
<Dialog onOpenChange={(open) => !open && ctrl.closeDialog()} open={ctrl.isOpen}> <Dialog onOpenChange={(open) => !open && ctrl.closeDialog()} open={ctrl.isOpen}>
<DialogContent className="flex max-h-[85vh] flex-col sm:max-w-3xl"> <DialogContent className="flex max-h-[85vh] flex-col sm:max-w-3xl">
@ -80,60 +70,23 @@ export const SelectCustomerDialog = ({
</div> </div>
<div className="min-h-0 flex-1 overflow-hidden"> <div className="min-h-0 flex-1 overflow-hidden">
<CustomerSelectionList <SelectCustomerSelectionList
customers={ctrl.customers} customers={ctrl.customers}
isLoading={ctrl.isLoading} isLoading={ctrl.isLoading}
onSelect={ctrl.selectCustomer} onSelect={handleSelect}
selectedCustomer={selectedCustomer}
/> />
</div> </div>
<DialogFooter className="gap-2">
<Button onClick={ctrl.closeDialog} variant="outline">
Cancelar
</Button>
<Button disabled={ctrl.isLoading || !selectedCustomer} onClick={handleConfirm}>
{selectedCustomer ? "Confirmar selección" : "Seleccionar cliente"}
</Button>
</DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
}; };
type CustomerSelectionListProps = {
customers: CustomerSelectionOption[];
isLoading: boolean;
onSelect: (customer: CustomerSelectionOption) => void;
};
const CustomerSelectionList = ({ customers, isLoading, onSelect }: CustomerSelectionListProps) => {
const { t } = useTranslation();
//const columns = useCustomersGridColumns({});
if (isLoading) {
return (
<div className="space-y-2 py-2">
<div className="h-14 rounded-md border" />
<div className="h-14 rounded-md border" />
<div className="h-14 rounded-md border" />
</div>
);
}
if (customers.length === 0) {
return <SelectCustomerEmptyCard />;
}
return (
<ItemGroup>
{customers.map((customer) => (
<Item
asChild
className={cn("bg-muted/50 font-medium transition hover:text-primary hover:bg-muted")}
key={customer.id}
onClick={() => onSelect(customer)}
role="listitem"
>
<a href="#" rel="noopener noreferrer" target="_blank">
<ItemContent>
<ItemTitle className="line-clamp-1">{customer.name}</ItemTitle>
<ItemDescription>{customer.tin}</ItemDescription>
</ItemContent>
</a>
</Item>
))}
</ItemGroup>
);
};

View File

@ -0,0 +1,77 @@
import { InitialsAvatar } from "@repo/rdx-ui/components";
import {
Item,
ItemContent,
ItemDescription,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { useTranslation } from "../../../../../web/i18n";
import type { CustomerSelectionOption } from "../../entities";
import { SelectCustomerEmptyCard } from "./select-customer-empty-card";
type SelectCustomerSelectionListProps = {
customers: CustomerSelectionOption[];
isLoading: boolean;
selectedCustomer?: CustomerSelectionOption;
onSelect: (customer: CustomerSelectionOption) => void;
};
export const SelectCustomerSelectionList = ({
customers,
isLoading,
selectedCustomer,
onSelect,
}: SelectCustomerSelectionListProps) => {
const { t } = useTranslation();
//const columns = useCustomersGridColumns({});
console.log(selectedCustomer);
if (isLoading) {
return (
<div className="space-y-2 py-2">
<div className="h-14 rounded-md border" />
<div className="h-14 rounded-md border" />
<div className="h-14 rounded-md border" />
</div>
);
}
if (customers.length === 0) {
return <SelectCustomerEmptyCard />;
}
return (
<ItemGroup>
{customers.map((customer) => (
<Item
className={cn(
"cursor-pointer",
customer.id === selectedCustomer?.id && "border-primary bg-accent"
)}
key={customer.id}
onClick={() => onSelect(customer)}
role="listitem"
variant={"outline"}
>
<ItemMedia>
<InitialsAvatar
name={customer.name}
variant={customer.id === selectedCustomer?.id ? "primary" : "muted"}
/>
</ItemMedia>
<ItemContent>
<ItemTitle className="line-clamp-1">{customer.name}</ItemTitle>
<ItemDescription>{customer.tin}</ItemDescription>
</ItemContent>
</Item>
))}
</ItemGroup>
);
};

View File

@ -1,24 +1,14 @@
import { Avatar, AvatarFallback, Badge } from "@repo/shadcn-ui/components"; import { InitialsAvatar } from "@repo/rdx-ui/components";
import { Badge } from "@repo/shadcn-ui/components";
import { Building2Icon, CopyIcon, UserIcon } from "lucide-react"; import { Building2Icon, CopyIcon, UserIcon } from "lucide-react";
import type { Customer } from "../../../../shared"; import type { Customer } from "../../../../shared";
import { Initials } from "../../components";
export const CustomerHeader = ({ customer }: { customer: Customer }) => { export const CustomerHeader = ({ customer }: { customer: Customer }) => {
return ( return (
<div className="p-4"> <div className="p-4">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<Avatar className="size-10 border-2 border-background shadow-sm"> <InitialsAvatar name={customer.name} />
<AvatarFallback
className={
customer.status === "active"
? "bg-blue-100 text-blue-700"
: "bg-muted text-muted-foreground"
}
>
<Initials name={customer.name} />
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@ -1,8 +1,6 @@
import { safeHTTPUrl } from "@erp/core/client"; import { safeHTTPUrl } from "@erp/core/client";
import { DataTableColumnHeader } from "@repo/rdx-ui/components"; import { DataTableColumnHeader, InitialsAvatar } from "@repo/rdx-ui/components";
import { import {
Avatar,
AvatarFallback,
Badge, Badge,
Button, Button,
DropdownMenu, DropdownMenu,
@ -18,7 +16,7 @@ import * as React from "react";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../i18n";
import type { CustomerListRow } from "../../../../shared"; import type { CustomerListRow } from "../../../../shared";
import { AddressCell, ContactCell, Initials } from "../../components"; import { AddressCell, ContactCell } from "../../components";
type GridActionHandlers = { type GridActionHandlers = {
onEditClick?: (customer: CustomerListRow) => void; onEditClick?: (customer: CustomerListRow) => void;
@ -58,17 +56,7 @@ export function useCustomersGridColumns(
type="button" type="button"
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<Avatar className="size-10 border-2 border-background shadow-sm"> <InitialsAvatar name={customer.name} variant="primary" />
<AvatarFallback
className={
customer.status === "active"
? "bg-blue-100 text-blue-700"
: "bg-muted text-muted-foreground"
}
>
<Initials name={customer.name} />
</AvatarFallback>
</Avatar>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@ -1,5 +1,4 @@
export * from "./address-cell"; export * from "./address-cell";
export * from "./contact-cell"; export * from "./contact-cell";
export * from "./initials";
export * from "./kind-badge"; export * from "./kind-badge";
export * from "./soft"; export * from "./soft";

View File

@ -1,4 +0,0 @@
export const Initials = ({ name }: { name: string }) => {
const parts = name.trim().split(/\s+/).slice(0, 2);
return <> {parts.map((p) => p[0]?.toUpperCase() ?? "").join("") || "?"} </>;
};