Clientes y facturas de cliente

This commit is contained in:
David Arranz 2025-09-22 11:24:19 +02:00
parent 5f2afd0520
commit a2b5c96cd7
8 changed files with 142 additions and 127 deletions

View File

@ -45,14 +45,14 @@ export class CustomerInvoiceReportPDFPresenter extends Presenter<
right: "10mm",
top: "10mm",
},
landscape: false,
preferCSSPageSize: true,
omitBackground: false,
printBackground: true,
displayHeaderFooter: true,
headerTemplate: "<div />",
footerTemplate:
'<div style="text-align: center;width: 297mm;font-size: 10px;">Página <span style="margin-right: 1cm"><span class="pageNumber"></span> de <span class="totalPages"></span></span></div>',
// landscape: false,
// preferCSSPageSize: true,
// omitBackground: false,
// printBackground: true,
// displayHeaderFooter: false,
// headerTemplate: "<div />",
// footerTemplate:
// '<div style="text-align: center;width: 297mm;font-size: 10px;">Página <span style="margin-right: 1cm"><span class="pageNumber"></span> de <span class="totalPages"></span></span></div>',
});
await browser.close();

View File

@ -175,7 +175,7 @@
<td>{{description}}</td>
<td class="text-right">{{#if quantity}}{{quantity}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if unit_amount}}{{unit_amount}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if total_amount}}{{total_amount}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if subtotal_amount}}{{subtotal_amount}}{{else}}&nbsp;{{/if}}</td>
</td>
</tr>
{{/each}}

View File

@ -110,15 +110,17 @@
<body>
<header>
<aside class="flex items-start mb-4 w-full">
<header id="header">
<aside class="flex items-start mb-4 w-full bg-red-600">
<!-- Bloque IZQUIERDO: imagen arriba + texto abajo, alineado a la izquierda -->
<div class="w-[70%] flex flex-col items-start text-left">
<div class="flex flex-col 70% items-start text-left bg-green-700">
<img src="https://rodax-software.com/images/logo1.jpg" alt="Logo Rodax" class="block h-14 w-auto mb-1" />
<div class="flex w-full">
<div class="p-1 ">
<p>Factura nº:<strong>&nbsp;{{invoice_number}}</strong></p>
<h3 class="text-2xl font-normal">PROFORMA</h3>
<p>Nº:<strong>&nbsp;{{invoice_number}}</strong></p>
<p><span>Fecha:<strong>&nbsp;{{invoice_date}}</strong></p>
<p>Página <span class="pageNumber"></span> de <span class="totalPages"></span></p>
</div>
<div class="p-1 ml-9">
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>
@ -145,7 +147,6 @@
<main id="main">
<section id="details" class="border-b border-black ">
<div class="relative pt-0 border-b border-black">
<!-- Badge TOTAL decorado con imagen -->
<div class="absolute -top-9 right-0">
@ -175,7 +176,7 @@
<td>{{description}}</td>
<td class="text-right">{{#if quantity}}{{quantity}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if unit_amount}}{{unit_amount}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if total_amount}}{{total_amount}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if subtotal_amount}}{{subtotal_amount}}{{else}}&nbsp;{{/if}}</td>
</td>
</tr>
{{/each}}

View File

@ -1,14 +1,12 @@
import {
AudioWaveform,
BookOpen,
Bot,
Command,
FileCheckIcon,
Frame,
GalleryVerticalEnd,
HomeIcon,
MapIcon,
PieChart,
Settings2,
SquareTerminal,
} from "lucide-react";
import {
@ -23,18 +21,22 @@ import {
HelpCircleIcon,
LayoutDashboardIcon,
ListIcon,
SearchIcon,
SettingsIcon,
UsersIcon,
} from "lucide-react";
import * as React from "react";
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader } from "@repo/shadcn-ui/components";
import { NavDocuments } from "./nav-documents.tsx";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail,
} from "@repo/shadcn-ui/components";
import { NavMain } from "./nav-main.tsx";
import { NavProjects } from "./nav-projects.tsx";
import { NavSecondary } from "./nav-secondary.tsx";
import { NavUser } from "./nav-user.tsx";
import { SearchForm } from "./search-form.tsx";
import { TeamSwitcher } from "./team-switcher.tsx";
const data = {
@ -129,11 +131,6 @@ const data = {
url: "#",
icon: HelpCircleIcon,
},
{
title: "Buscar",
url: "#",
icon: SearchIcon,
},
],
documents: [
{
@ -179,88 +176,48 @@ const data2 = {
},
],
navMain: [
{
title: "Inicio",
url: "/",
icon: HomeIcon,
isActive: true,
},
{
title: "Clientes",
url: "/customers",
icon: SquareTerminal,
icon: UsersIcon,
isActive: true,
items: [
{
title: "History",
url: "#",
title: "Listado de clientes",
url: "/customers",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
title: "Añadir un cliente",
url: "/customers/create",
},
],
},
{
title: "Proformas de cliente",
url: "/customer-proforma",
icon: Bot,
title: "Facturas proforma",
icon: FileTextIcon,
items: [
{
title: "Genesis",
url: "#",
title: "Listado de proformas",
url: "/customer-proforma",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
title: "Enviar a Veri*Factu",
url: "#",
},
],
},
{
title: "Facturas de cliente",
icon: FileCheckIcon,
items: [
{
title: "Listado de facturas",
url: "/customer-invoices",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
@ -287,18 +244,18 @@ const data2 = {
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible='icon' {...props}>
<SidebarHeader>
<SidebarHeader className='mb-3'>
<TeamSwitcher teams={data2.teams} />
<SearchForm />
</SidebarHeader>
<SidebarContent>
<NavMain items={data2.navMain} />
<NavProjects projects={data2.projects} />
<NavDocuments items={data.documents} />
<NavSecondary items={data.navSecondary} className='mt-auto' />
</SidebarContent>
<SidebarFooter>
<NavSecondary items={data.navSecondary} className='mt-auto' />
<NavUser user={data.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}

View File

@ -1,23 +1,35 @@
import { type LucideIcon, MailIcon, PlusCircleIcon } from "lucide-react";
import { ChevronRightIcon, type LucideIcon, PlusCircleIcon } from "lucide-react";
import {
Button,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from "@repo/shadcn-ui/components";
import { useNavigate } from "react-router";
type NavMainItem = {
title: string;
url?: string;
icon?: LucideIcon;
isActive?: boolean;
items?: {
title: string;
url: string;
}[];
};
export function NavMain({
items,
}: {
items: {
title: string;
url: string;
icon?: LucideIcon;
}[];
items: NavMainItem[];
}) {
const navigate = useNavigate();
@ -35,29 +47,34 @@ export function NavMain({
<PlusCircleIcon />
<span>Quick Create</span>
</SidebarMenuButton>
<Button
size='icon'
className='h-9 w-9 shrink-0 group-data-[collapsible=icon]:opacity-0'
variant='outline'
>
<MailIcon />
<span className='sr-only'>Inbox</span>
</Button>
</SidebarMenuItem>
</SidebarMenu>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
isActive={String(window.location.href).includes(item.url)}
tooltip={item.title}
onClick={() => navigate(item.url)}
className='data-[active=true]:bg-accent data-[active=true]:text-accent-foreground cursor-pointer'
>
<Collapsible key={item.title} asChild defaultOpen={true} className='group/collapsible'>
<SidebarMenuItem className='mb-6'>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <item.icon />}
<span>{item.title}</span>
<span className='font-semibold'>{item.title}</span>
<ChevronRightIcon className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild>
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroupContent>

View File

@ -0,0 +1,38 @@
import {
Button,
Label,
SidebarGroup,
SidebarGroupContent,
SidebarInput,
SidebarMenu,
SidebarMenuItem,
} from "@repo/shadcn-ui/components";
import { BinocularsIcon, SearchIcon } from "lucide-react";
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
return (
<form {...props}>
<SidebarGroup className='py-0'>
<SidebarGroupContent className='relative'>
<SidebarMenu>
<SidebarMenuItem className='flex items-center gap-2'>
<Label htmlFor='search' className='sr-only'>
Search
</Label>
<SidebarInput id='search' placeholder='Search...' className='pl-8 h-9' />
<SearchIcon className='pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none ' />
<Button
size='icon'
className='h-9 w-9 shrink-0 group-data-[collapsible=icon]:opacity-0'
variant='outline'
>
<BinocularsIcon />
<span className='sr-only'>Advanced search</span>
</Button>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</form>
);
}

View File

@ -1,13 +1,13 @@
import { TrendingDownIcon, TrendingUpIcon } from "lucide-react";
import { Badge } from "@repo/shadcn-ui/components/badge";
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@repo/shadcn-ui/components/card";
} from "@repo/shadcn-ui/components";
import { Badge } from "@repo/shadcn-ui/components/badge";
export function SectionCards() {
return (

View File

@ -1,3 +1,5 @@
"use client";
import { ChevronsUpDown, Plus } from "lucide-react";
import * as React from "react";
@ -40,31 +42,31 @@ export function TeamSwitcher({
size='lg'
className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground'
>
<div className='flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground'>
<div className='bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg'>
<activeTeam.logo className='size-4' />
</div>
<div className='grid flex-1 text-left text-sm leading-tight'>
<span className='truncate font-semibold'>{activeTeam.name}</span>
<span className='truncate font-medium'>{activeTeam.name}</span>
<span className='truncate text-xs'>{activeTeam.plan}</span>
</div>
<ChevronsUpDown className='ml-auto' />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className='w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg'
className='w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg'
align='start'
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuLabel className='text-xs text-muted-foreground'>Teams</DropdownMenuLabel>
<DropdownMenuLabel className='text-muted-foreground text-xs'>Teams</DropdownMenuLabel>
{teams.map((team, index) => (
<DropdownMenuItem
key={team.name}
onClick={() => setActiveTeam(team)}
className='gap-2 p-2'
>
<div className='flex size-6 items-center justify-center rounded-sm border'>
<team.logo className='size-4 shrink-0' />
<div className='flex size-6 items-center justify-center rounded-md border'>
<team.logo className='size-3.5 shrink-0' />
</div>
{team.name}
<DropdownMenuShortcut>{index + 1}</DropdownMenuShortcut>
@ -72,10 +74,10 @@ export function TeamSwitcher({
))}
<DropdownMenuSeparator />
<DropdownMenuItem className='gap-2 p-2'>
<div className='flex size-6 items-center justify-center rounded-md border bg-background'>
<div className='flex size-6 items-center justify-center rounded-md border bg-transparent'>
<Plus className='size-4' />
</div>
<div className='font-medium text-muted-foreground'>Add team</div>
<div className='text-muted-foreground font-medium'>Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>