Compare commits

..

No commits in common. "07e873bb365abe6d0ddc352f72f1025bcc85e800" and "e734cea4d867d99a7e0919db21e6e72f2078926f" have entirely different histories.

14 changed files with 106 additions and 96 deletions

View File

@ -8,7 +8,7 @@
"build": "tsc && vite build", "build": "tsc && vite build",
"build:rodax": "tsc && vite build --mode rodax", "build:rodax": "tsc && vite build --mode rodax",
"build:acana": "tsc && vite build --mode acana", "build:acana": "tsc && vite build --mode acana",
"preview": "vite preview --host --debug --mode production", "preview": "vite preview --host",
"clean": "rm -rf dist && rm -rf node_modules && rm -rf .turbo", "clean": "rm -rf dist && rm -rf node_modules && rm -rf .turbo",
"check:deps": "pnpm exec depcheck", "check:deps": "pnpm exec depcheck",
"lint": "biome lint --fix", "lint": "biome lint --fix",

View File

@ -21,32 +21,32 @@ export function PageHeader({
className, className,
}: PageHeaderProps) { }: PageHeaderProps) {
return ( return (
<div className={cn("pt-6 pb-4 lg:flex lg:items-center lg:justify-between", className)}> <div className={cn("pt-6 pb-6 lg:flex lg:items-center lg:justify-between", className)}>
{/* Lado izquierdo */} {/* Lado izquierdo */}
<div className="min-w-0 flex-1"> <div className='min-w-0 flex-1'>
<div className="flex items-start gap-4"> <div className='flex items-start gap-4'>
{backIcon && ( {backIcon && (
<Button <Button
className="cursor-pointer" variant='ghost'
size='icon'
className='cursor-pointer'
onClick={() => window.history.back()} onClick={() => window.history.back()}
size="icon"
variant="ghost"
> >
<ChevronLeftIcon className="size-5" /> <ChevronLeftIcon className='size-5' />
</Button> </Button>
)} )}
<div> <div>
<h2 className="h-8 text-xl font-semibold text-foreground sm:truncate sm:tracking-tight"> <h2 className='h-8 text-xl font-semibold text-foreground sm:truncate sm:tracking-tight'>
{title} {title}
</h2> </h2>
{description && <p className="text-sm text-primary">{description}</p>} {description && <p className='text-sm text-muted-foreground'>{description}</p>}
</div> </div>
</div> </div>
</div> </div>
{/* Lado derecho parametrizable */} {/* Lado derecho parametrizable */}
<div className="mt-4 flex lg:mt-0 lg:ml-4">{rightSlot}</div> <div className='mt-4 flex lg:mt-0 lg:ml-4'>{rightSlot}</div>
</div> </div>
); );
} }

View File

@ -1,5 +1,4 @@
import type { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
import { CustomersProvider } from "../context"; import { CustomersProvider } from "../context";
export const CustomersLayout = ({ children }: PropsWithChildren) => { export const CustomersLayout = ({ children }: PropsWithChildren) => {

View File

@ -3,12 +3,13 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false,
"scripts": { "scripts": {
"typecheck": "tsc -p tsconfig.json --noEmit", "typecheck": "tsc -p tsconfig.json --noEmit",
"clean": "rimraf .turbo node_modules dist" "clean": "rimraf .turbo node_modules dist"
}, },
"exports": { "exports": {
".": "./src/i18n.ts" ".": "./src/index.ts"
}, },
"dependencies": { "dependencies": {
"i18next": "25.6.0", "i18next": "25.6.0",

View File

@ -1,20 +1,10 @@
import i18n from "i18next"; import i18next, { type i18n } from "i18next";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
i18n let hasInit = false;
.use(LanguageDetector)
.use(initReactI18next)
.init({
detection: { order: ["navigator"] },
debug: true,
fallbackLng: "es",
interpolation: { escapeValue: false },
});
export { i18n }; export const initI18Next = () => {
/*export const initI18Next = () => {
if (hasInit) return i18next; if (hasInit) return i18next;
i18next i18next
@ -22,14 +12,14 @@ export { i18n };
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
detection: { order: ["navigator"] }, detection: { order: ["navigator"] },
debug: true, debug: false,
fallbackLng: "es", fallbackLng: "es",
interpolation: { escapeValue: false }, interpolation: { escapeValue: false },
}); });
hasInit = true; hasInit = true;
return i18next; return i18next;
};*/ };
/** /**
* Registra dinámicamente traducciones de un módulo. * Registra dinámicamente traducciones de un módulo.
@ -43,16 +33,25 @@ export const registerTranslations = (
locale: string, locale: string,
resources: Record<string, unknown> resources: Record<string, unknown>
): void => { ): void => {
/*if (!hasInit) { if (!hasInit) {
throw new Error("i18n not initialized. Call initI18Next() first."); throw new Error("i18n not initialized. Call initI18Next() first.");
}*/ }
const ns = moduleName; const ns = moduleName;
const alreadyExists = i18n.hasResourceBundle(locale, ns); const alreadyExists = i18next.hasResourceBundle(locale, ns);
if (!alreadyExists) { if (!alreadyExists) {
console.log("cargando => ", moduleName, locale); i18next.addResourceBundle(locale, ns, resources, true, true);
i18n.addResourceBundle(locale, ns, resources, true, true);
} }
}; };
/**
* Acceso al `t()` global por si se necesita en librerías de backend.
*/
export const t = (...args: Parameters<i18n["t"]>) => {
if (!hasInit) {
throw new Error("i18n not initialized. Call initI18Next() first.");
}
return i18next.t(...args);
};

View File

@ -0,0 +1 @@
export * from "./i18n";

View File

@ -23,7 +23,6 @@
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.1", "@biomejs/biome": "^2.3.1",
"@repo/i18next": "workspace:*",
"@repo/typescript-config": "workspace:*", "@repo/typescript-config": "workspace:*",
"@tailwindcss/postcss": "^4.1.5", "@tailwindcss/postcss": "^4.1.5",
"@turbo/gen": "^2.5.2", "@turbo/gen": "^2.5.2",

View File

@ -9,6 +9,7 @@ export const AppContent = ({
return ( return (
<div <div
className={cn("app-content flex flex-1 flex-col gap-4 p-4 pt-6 min-h-screen", className)} className={cn("app-content flex flex-1 flex-col gap-4 p-4 pt-6 min-h-screen", className)}
style={{ backgroundColor: "#fdfdfd" }}
{...props} {...props}
> >
{children} {children}

View File

@ -1,5 +1,5 @@
import { cn } from "@repo/shadcn-ui/lib/utils"; import { cn } from "@repo/shadcn-ui/lib/utils";
import type { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
export const AppHeader = ({ export const AppHeader = ({
className, className,
@ -7,14 +7,7 @@ export const AppHeader = ({
...props ...props
}: PropsWithChildren<{ className?: string }>) => { }: PropsWithChildren<{ className?: string }>) => {
return ( return (
<div <div className={cn("app-header gap-4 px-4 pt-0 border-b bg-background", className)} {...props}>
className={cn(
"md:rounded-tl-xl md:rounded-tr-xl",
"app-header gap-4 px-4 pt-0 border-b border-sidebar/25 bg-background",
className
)}
{...props}
>
{children} {children}
</div> </div>
); );

View File

@ -15,7 +15,7 @@ export const AppLayout = () => {
> >
<AppSidebar variant="inset" /> <AppSidebar variant="inset" />
{/* Aquí está el MAIN */} {/* Aquí está el MAIN */}
<SidebarInset className="app-main bg-white"> <SidebarInset className="app-main">
<Outlet /> <Outlet />
</SidebarInset> </SidebarInset>
</SidebarProvider> </SidebarProvider>

View File

@ -6,9 +6,11 @@ import {
SidebarRail, SidebarRail,
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { import {
AudioWaveform,
BarChartIcon, BarChartIcon,
CameraIcon, CameraIcon,
ClipboardListIcon, ClipboardListIcon,
Command,
DatabaseIcon, DatabaseIcon,
FileCheckIcon, FileCheckIcon,
FileCodeIcon, FileCodeIcon,
@ -18,6 +20,7 @@ import {
Frame, Frame,
GalleryVerticalEnd, GalleryVerticalEnd,
HelpCircleIcon, HelpCircleIcon,
HomeIcon,
LayoutDashboardIcon, LayoutDashboardIcon,
ListIcon, ListIcon,
MapIcon, MapIcon,
@ -158,14 +161,24 @@ const data2 = {
logo: GalleryVerticalEnd, logo: GalleryVerticalEnd,
plan: "Enterprise", plan: "Enterprise",
}, },
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
], ],
navMain: [ navMain: [
/*{ {
title: "Inicio", title: "Inicio",
url: "/", url: "/",
icon: HomeIcon, icon: HomeIcon,
isActive: true, isActive: true,
},*/ },
{ {
title: "Clientes", title: "Clientes",
icon: UsersIcon, icon: UsersIcon,
@ -175,10 +188,10 @@ const data2 = {
title: "Listado de clientes", title: "Listado de clientes",
url: "/customers", url: "/customers",
}, },
/*{ {
title: "Añadir un cliente", title: "Añadir un cliente",
url: "/customers/create", url: "/customers/create",
},*/ },
], ],
}, },
{ {
@ -189,10 +202,10 @@ const data2 = {
title: "Listado de proformas", title: "Listado de proformas",
url: "/proformas", url: "/proformas",
}, },
/*{ {
title: "Enviar a Veri*Factu", title: "Enviar a Veri*Factu",
url: "#", url: "#",
},*/ },
], ],
}, },
{ {

View File

@ -1,3 +1,5 @@
import { ChevronRightIcon, type LucideIcon, PlusCircleIcon } from "lucide-react";
import { import {
Collapsible, Collapsible,
CollapsibleContent, CollapsibleContent,
@ -11,7 +13,6 @@ import {
SidebarMenuSubButton, SidebarMenuSubButton,
SidebarMenuSubItem, SidebarMenuSubItem,
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { ChevronRightIcon, type LucideIcon, PlusCircleIcon } from "lucide-react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
type NavMainItem = { type NavMainItem = {
@ -25,17 +26,21 @@ type NavMainItem = {
}[]; }[];
}; };
export function NavMain({ items }: { items: NavMainItem[] }) { export function NavMain({
items,
}: {
items: NavMainItem[];
}) {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<SidebarGroup> <SidebarGroup>
<SidebarGroupContent className="flex flex-col gap-2"> <SidebarGroupContent className='flex flex-col gap-2'>
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem className="flex items-center gap-2"> <SidebarMenuItem className='flex items-center gap-2'>
<SidebarMenuButton <SidebarMenuButton
className="hidden min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground" tooltip='Quick Create'
tooltip="Quick Create" className='min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground'
> >
<PlusCircleIcon /> <PlusCircleIcon />
<span>Quick Create</span> <span>Quick Create</span>
@ -44,13 +49,13 @@ export function NavMain({ items }: { items: NavMainItem[] }) {
</SidebarMenu> </SidebarMenu>
<SidebarMenu> <SidebarMenu>
{items.map((item) => ( {items.map((item) => (
<Collapsible asChild className="group/collapsible" defaultOpen={true} key={item.title}> <Collapsible key={item.title} asChild defaultOpen={true} className='group/collapsible'>
<SidebarMenuItem className="mb-6"> <SidebarMenuItem className='mb-6'>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}> <SidebarMenuButton tooltip={item.title}>
{item.icon && <item.icon />} {item.icon && <item.icon />}
<span className="font-semibold">{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" /> <ChevronRightIcon className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
</SidebarMenuButton> </SidebarMenuButton>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent> <CollapsibleContent>

View File

@ -1,6 +1,5 @@
import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts"; import { useTranslation } from "@repo/rdx-ui/locales/i18n.ts";
import type { JSX } from "react"; import { JSX } from "react";
import { LoadingIndicator } from "./loading-indicator.tsx"; import { LoadingIndicator } from "./loading-indicator.tsx";
export type LoadingOverlayProps = { export type LoadingOverlayProps = {
@ -32,7 +31,7 @@ export const LoadingOverlay = ({ title, subtitle, ...props }: LoadingOverlayProp
} }
{...props} {...props}
> >
<LoadingIndicator look="dark" subtitle={loadingSubtitle} title={loadingTitle} /> <LoadingIndicator look='dark' title={loadingTitle} subtitle={loadingSubtitle} />
</div> </div>
); );
}; };

View File

@ -117,38 +117,38 @@
--font-serif: "Noto Serif", ui-serif, serif; --font-serif: "Noto Serif", ui-serif, serif;
--font-mono: "Noto Sans Mono", ui-monospace, monospace; --font-mono: "Noto Sans Mono", ui-monospace, monospace;
--background: #eef4ff; --background: oklch(1 0 0);
--foreground: #1e293b; --foreground: oklch(13.636% 0.02685 282.25);
--card: #ffffff; --card: oklch(1 0 0);
--card-foreground: #1e293b; --card-foreground: oklch(0.2035 0.0139 285.1021);
--popover: #ffffff; --popover: oklch(1 0 0);
--popover-foreground: #1e293b; --popover-foreground: oklch(0.2035 0.0139 285.1021);
--primary: #3478d1; --primary: oklch(0.623 0.214 259.815);
--primary-foreground: #ffffff; --primary-foreground: oklch(1 0 0);
--secondary: #e8f1fb; --secondary: oklch(0.9574 0.0011 197.1383);
--secondary-foreground: #1e293b; --secondary-foreground: oklch(0.2069 0.0098 285.5081);
--muted: #e8f1fb; --muted: oklch(0.9674 0.0013 286.3752);
--muted-foreground: #6b7d93; --muted-foreground: oklch(0.5466 0.0216 285.664);
--accent: #6b7d93; --accent: oklch(0.9299 0.0334 272.7879);
--accent-foreground: #ffffff; --accent-foreground: oklch(0.3729 0.0306 259.7328);
--destructive: #ef4444; --destructive: oklch(0.583 0.2387 28.4765);
--destructive-foreground: #ffffff; --destructive-foreground: oklch(1 0 0);
--border: #d1dce8; --border: oklch(0.9273 0.0067 286.2663);
--input: #d1dce8; --input: oklch(0.9173 0.0067 286.2663);
--ring: #3478d1; --ring: oklch(0.6187 0.2067 259.2316);
--chart-1: #3478d1; --chart-1: oklch(0.6471 0.2173 36.8511);
--chart-2: #6b7d93; --chart-2: oklch(37.973% 0.0506 187.591);
--chart-3: #5ba3e0; --chart-3: oklch(0.4247 0.0852 230.5827);
--chart-4: #4a90c9; --chart-4: oklch(0.829 0.1712 81.0381);
--chart-5: #8ba3bd; --chart-5: oklch(0.7724 0.1728 65.367);
--sidebar: #3478d1; --sidebar: oklch(0.957 0.007 272.5840410480741);
--sidebar-foreground: #ffffff; --sidebar-foreground: oklch(0.2035 0.0139 285.1021);
--sidebar-primary: #ffffff; --sidebar-primary: oklch(0.623 0.214 259.815);
--sidebar-primary-foreground: #3478d1; --sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: #4a8fe0; --sidebar-accent: oklch(0.9674 0.0013 286.3752);
--sidebar-accent-foreground: #ffffff; --sidebar-accent-foreground: oklch(0.2069 0.0098 285.5081);
--sidebar-border: #4a8fe0; --sidebar-border: oklch(0.9173 0.0067 286.2663);
--sidebar-ring: #ffffff; --sidebar-ring: oklch(0.623 0.214 259.815);
--radius: 0.4rem; --radius: 0.4rem;
--shadow-2xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05); --shadow-2xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05); --shadow-xs: 1px 1px 6px 0px hsl(0 0% 0% / 0.05);