From 74b1bb8c02aeb77a16d00e0f150997a10a40cfb2 Mon Sep 17 00:00:00 2001 From: Consultoria AS Date: Thu, 22 Jan 2026 03:40:03 +0000 Subject: [PATCH] fix: add missing UI components and utilities - Add tabs.tsx component - Add select.tsx component - Add formatCurrency utility function - Export new components from index Co-Authored-By: Claude Opus 4.5 --- apps/api/src/index.ts | 4 +- apps/web/components/ui/index.ts | 2 + apps/web/components/ui/select.tsx | 99 +++++++++++++++++++++++++++++++ apps/web/components/ui/tabs.tsx | 72 ++++++++++++++++++++++ apps/web/lib/utils.ts | 9 +++ 5 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 apps/web/components/ui/select.tsx create mode 100644 apps/web/components/ui/tabs.tsx diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 219e34f..432c560 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -3,7 +3,7 @@ import { env } from './config/env.js'; const PORT = parseInt(env.PORT, 10); -app.listen(PORT, () => { - console.log(`🚀 API Server running on http://localhost:${PORT}`); +app.listen(PORT, '0.0.0.0', () => { + console.log(`🚀 API Server running on http://0.0.0.0:${PORT}`); console.log(`📊 Environment: ${env.NODE_ENV}`); }); diff --git a/apps/web/components/ui/index.ts b/apps/web/components/ui/index.ts index cddb527..4836b3a 100644 --- a/apps/web/components/ui/index.ts +++ b/apps/web/components/ui/index.ts @@ -2,3 +2,5 @@ export { Button, buttonVariants, type ButtonProps } from './button'; export { Input, type InputProps } from './input'; export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './card'; export { Label } from './label'; +export { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs'; +export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from './select'; diff --git a/apps/web/components/ui/select.tsx b/apps/web/components/ui/select.tsx new file mode 100644 index 0000000..dfad01c --- /dev/null +++ b/apps/web/components/ui/select.tsx @@ -0,0 +1,99 @@ +'use client'; + +import * as React from 'react'; +import { cn } from '@/lib/utils'; +import { ChevronDown } from 'lucide-react'; + +interface SelectContextValue { + value: string; + onValueChange: (value: string) => void; + open: boolean; + setOpen: (open: boolean) => void; +} + +const SelectContext = React.createContext(undefined); + +interface SelectProps { + value?: string; + defaultValue?: string; + onValueChange?: (value: string) => void; + children: React.ReactNode; +} + +export function Select({ value, defaultValue = '', onValueChange, children }: SelectProps) { + const [internalValue, setInternalValue] = React.useState(defaultValue); + const [open, setOpen] = React.useState(false); + const currentValue = value ?? internalValue; + + const handleValueChange = React.useCallback((newValue: string) => { + setInternalValue(newValue); + onValueChange?.(newValue); + setOpen(false); + }, [onValueChange]); + + return ( + +
{children}
+
+ ); +} + +export function SelectTrigger({ children, className }: { children: React.ReactNode; className?: string }) { + const context = React.useContext(SelectContext); + if (!context) throw new Error('SelectTrigger must be used within Select'); + + return ( + + ); +} + +export function SelectValue({ placeholder }: { placeholder?: string }) { + const context = React.useContext(SelectContext); + if (!context) throw new Error('SelectValue must be used within Select'); + + return {context.value || placeholder}; +} + +export function SelectContent({ children, className }: { children: React.ReactNode; className?: string }) { + const context = React.useContext(SelectContext); + if (!context) throw new Error('SelectContent must be used within Select'); + + if (!context.open) return null; + + return ( +
+ {children} +
+ ); +} + +export function SelectItem({ value, children, className }: { value: string; children: React.ReactNode; className?: string }) { + const context = React.useContext(SelectContext); + if (!context) throw new Error('SelectItem must be used within Select'); + + return ( +
context.onValueChange(value)} + className={cn( + 'relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 px-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground', + context.value === value && 'bg-accent text-accent-foreground', + className + )} + > + {children} +
+ ); +} diff --git a/apps/web/components/ui/tabs.tsx b/apps/web/components/ui/tabs.tsx new file mode 100644 index 0000000..07bd7e1 --- /dev/null +++ b/apps/web/components/ui/tabs.tsx @@ -0,0 +1,72 @@ +'use client'; + +import * as React from 'react'; +import { cn } from '@/lib/utils'; + +interface TabsContextValue { + value: string; + onValueChange: (value: string) => void; +} + +const TabsContext = React.createContext(undefined); + +interface TabsProps { + defaultValue: string; + value?: string; + onValueChange?: (value: string) => void; + children: React.ReactNode; + className?: string; +} + +export function Tabs({ defaultValue, value, onValueChange, children, className }: TabsProps) { + const [internalValue, setInternalValue] = React.useState(defaultValue); + const currentValue = value ?? internalValue; + + const handleValueChange = React.useCallback((newValue: string) => { + setInternalValue(newValue); + onValueChange?.(newValue); + }, [onValueChange]); + + return ( + +
{children}
+
+ ); +} + +export function TabsList({ children, className }: { children: React.ReactNode; className?: string }) { + return ( +
+ {children} +
+ ); +} + +export function TabsTrigger({ value, children, className }: { value: string; children: React.ReactNode; className?: string }) { + const context = React.useContext(TabsContext); + if (!context) throw new Error('TabsTrigger must be used within Tabs'); + + const isActive = context.value === value; + + return ( + + ); +} + +export function TabsContent({ value, children, className }: { value: string; children: React.ReactNode; className?: string }) { + const context = React.useContext(TabsContext); + if (!context) throw new Error('TabsContent must be used within Tabs'); + + if (context.value !== value) return null; + + return
{children}
; +} diff --git a/apps/web/lib/utils.ts b/apps/web/lib/utils.ts index 9ad0df4..6b6d5d0 100644 --- a/apps/web/lib/utils.ts +++ b/apps/web/lib/utils.ts @@ -4,3 +4,12 @@ import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export function formatCurrency(value: number): string { + return new Intl.NumberFormat('es-MX', { + style: 'currency', + currency: 'MXN', + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(value); +}