mirror of
https://github.com/marcogll/AnchorOS.git
synced 2026-03-15 22:24:34 +00:00
✅ COMENTARIOS AUDITABLES IMPLEMENTADOS: - 80+ archivos con JSDoc completo para auditoría manual - APIs críticas con validaciones business/security/performance - Componentes con reglas de negocio documentadas - Funciones core con edge cases y validaciones ✅ CALENDARIO MULTI-COLUMNA FUNCIONAL (95%): - Drag & drop con reprogramación automática - Filtros por sucursal/staff, tiempo real - Indicadores de conflictos y disponibilidad - APIs completas con validaciones de colisión ✅ GESTIÓN OPERATIVA COMPLETA: - CRUD staff: APIs + componente con validaciones - CRUD recursos: APIs + componente con disponibilidad - Autenticación completa con middleware seguro - Auditoría completa en todas las operaciones ✅ DOCUMENTACIÓN ACTUALIZADA: - TASKS.md: FASE 4 95% completado - README.md: Estado actual y funcionalidades - API.md: 40+ endpoints documentados ✅ SEGURIDAD Y VALIDACIONES: - RLS policies documentadas en comentarios - Business rules validadas manualmente - Performance optimizations anotadas - Error handling completo Próximos: Nómina/POS/CRM avanzado (FASE 4 final)
352 lines
10 KiB
TypeScript
352 lines
10 KiB
TypeScript
import * as React from "react"
|
|
import { cn } from "@/lib/utils"
|
|
import { ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react"
|
|
|
|
interface TableProps extends React.HTMLAttributes<HTMLTableElement> {}
|
|
|
|
/**
|
|
* Table component for displaying tabular data with sticky header.
|
|
*/
|
|
const Table = React.forwardRef<HTMLTableElement, TableProps>(
|
|
({ className, ...props }, ref) => (
|
|
<div className="relative w-full overflow-auto">
|
|
<table
|
|
ref={ref}
|
|
className={cn("w-full caption-bottom text-sm", className)}
|
|
style={{
|
|
borderCollapse: 'separate',
|
|
borderSpacing: 0
|
|
}}
|
|
{...props}
|
|
/>
|
|
</div>
|
|
)
|
|
)
|
|
Table.displayName = "Table"
|
|
|
|
interface TableHeaderProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
|
|
|
|
/**
|
|
* TableHeader component for table header with sticky positioning.
|
|
*/
|
|
const TableHeader = React.forwardRef<
|
|
HTMLTableSectionElement,
|
|
TableHeaderProps
|
|
>(({ className, ...props }, ref) => (
|
|
<thead
|
|
ref={ref}
|
|
className={cn("[&_tr]:border-b", className)}
|
|
style={{
|
|
position: 'sticky',
|
|
top: 0,
|
|
zIndex: 10,
|
|
backgroundColor: 'var(--ivory-cream)'
|
|
}}
|
|
{...props}
|
|
/>
|
|
))
|
|
TableHeader.displayName = "TableHeader"
|
|
|
|
interface TableBodyProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
|
|
|
|
/**
|
|
* TableBody component for table body with hover effects.
|
|
*/
|
|
const TableBody = React.forwardRef<
|
|
HTMLTableSectionElement,
|
|
TableBodyProps
|
|
>(({ className, ...props }, ref) => (
|
|
<tbody
|
|
ref={ref}
|
|
className={cn("[&_tr:last-child]:border-0", className)}
|
|
{...props}
|
|
/>
|
|
))
|
|
TableBody.displayName = "TableBody"
|
|
|
|
interface TableFooterProps extends React.HTMLAttributes<HTMLTableSectionElement> {}
|
|
|
|
/**
|
|
* TableFooter component for table footer.
|
|
*/
|
|
const TableFooter = React.forwardRef<
|
|
HTMLTableSectionElement,
|
|
TableFooterProps
|
|
>(({ className, ...props }, ref) => (
|
|
<tfoot
|
|
ref={ref}
|
|
className={cn(
|
|
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
className
|
|
)}
|
|
style={{
|
|
backgroundColor: 'var(--sand-beige)'
|
|
}}
|
|
{...props}
|
|
/>
|
|
))
|
|
TableFooter.displayName = "TableFooter"
|
|
|
|
interface TableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {}
|
|
|
|
/**
|
|
* TableRow component for table row with hover effect.
|
|
*/
|
|
const TableRow = React.forwardRef<HTMLTableRowElement, TableRowProps>(
|
|
({ className, ...props }, ref) => (
|
|
<tr
|
|
ref={ref}
|
|
className={cn(
|
|
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
className
|
|
)}
|
|
style={{
|
|
borderColor: 'var(--mocha-taupe)',
|
|
backgroundColor: 'var(--ivory-cream)'
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.backgroundColor = 'var(--soft-cream)'
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.backgroundColor = 'var(--ivory-cream)'
|
|
}}
|
|
{...props}
|
|
/>
|
|
)
|
|
)
|
|
TableRow.displayName = "TableRow"
|
|
|
|
interface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {}
|
|
|
|
/**
|
|
* TableHead component for table header cell with bold text.
|
|
*/
|
|
const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
|
|
({ className, ...props }, ref) => (
|
|
<th
|
|
ref={ref}
|
|
className={cn(
|
|
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
className
|
|
)}
|
|
style={{
|
|
color: 'var(--charcoal-brown)',
|
|
fontWeight: 600,
|
|
textTransform: 'uppercase',
|
|
fontSize: '11px',
|
|
letterSpacing: '0.05em'
|
|
}}
|
|
{...props}
|
|
/>
|
|
)
|
|
)
|
|
TableHead.displayName = "TableHead"
|
|
|
|
interface TableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {}
|
|
|
|
/**
|
|
* TableCell component for table data cell.
|
|
*/
|
|
const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(
|
|
({ className, ...props }, ref) => (
|
|
<td
|
|
ref={ref}
|
|
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
|
style={{
|
|
color: 'var(--charcoal-brown)'
|
|
}}
|
|
{...props}
|
|
/>
|
|
)
|
|
)
|
|
TableCell.displayName = "TableCell"
|
|
|
|
interface TableCaptionProps extends React.HTMLAttributes<HTMLTableCaptionElement> {}
|
|
|
|
/**
|
|
* TableCaption component for table caption.
|
|
*/
|
|
const TableCaption = React.forwardRef<
|
|
HTMLTableCaptionElement,
|
|
TableCaptionProps
|
|
>(({ className, ...props }, ref) => (
|
|
<caption
|
|
ref={ref}
|
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
style={{
|
|
color: 'var(--charcoal-brown)',
|
|
opacity: 0.7
|
|
}}
|
|
{...props}
|
|
/>
|
|
))
|
|
TableCaption.displayName = "TableCaption"
|
|
|
|
interface SortableTableHeadProps extends TableHeadProps {
|
|
sortable?: boolean
|
|
sortDirection?: 'asc' | 'desc' | null
|
|
onSort?: () => void
|
|
}
|
|
|
|
/**
|
|
* SortableTableHead component for sortable table headers with sort indicators.
|
|
* @param {boolean} sortable - Whether the column is sortable
|
|
* @param {string} sortDirection - Current sort direction: asc, desc, or null
|
|
* @param {Function} onSort - Callback when sort is clicked
|
|
*/
|
|
export function SortableTableHead({
|
|
sortable = false,
|
|
sortDirection = null,
|
|
onSort,
|
|
className,
|
|
children,
|
|
...props
|
|
}: SortableTableHeadProps) {
|
|
return (
|
|
<TableHead
|
|
className={cn(
|
|
sortable && "cursor-pointer hover:bg-muted/50 select-none",
|
|
className
|
|
)}
|
|
onClick={sortable ? onSort : undefined}
|
|
style={{
|
|
userSelect: sortable ? 'none' : 'auto'
|
|
}}
|
|
{...props}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
{children}
|
|
{sortable && (
|
|
<span className="flex items-center gap-0.5" style={{ opacity: sortDirection ? 1 : 0.3 }}>
|
|
<ArrowUp className="h-3 w-3" style={{ color: sortDirection === 'asc' ? 'var(--deep-earth)' : 'var(--mocha-taupe)' }} />
|
|
<ArrowDown className="h-3 w-3 -mt-2" style={{ color: sortDirection === 'desc' ? 'var(--deep-earth)' : 'var(--mocha-taupe)' }} />
|
|
</span>
|
|
)}
|
|
</div>
|
|
</TableHead>
|
|
)
|
|
}
|
|
|
|
interface PaginationProps {
|
|
currentPage: number
|
|
totalPages: number
|
|
onPageChange: (page: number) => void
|
|
pageSize?: number
|
|
totalItems?: number
|
|
showPageSizeSelector?: boolean
|
|
pageSizeOptions?: number[]
|
|
onPageSizeChange?: (size: number) => void
|
|
}
|
|
|
|
/**
|
|
* Pagination component for table pagination.
|
|
* @param {number} currentPage - Current page number (1-based)
|
|
* @param {number} totalPages - Total number of pages
|
|
* @param {Function} onPageChange - Callback when page changes
|
|
* @param {number} pageSize - Number of items per page
|
|
* @param {number} totalItems - Total number of items
|
|
* @param {boolean} showPageSizeSelector - Whether to show page size selector
|
|
* @param {number[]} pageSizeOptions - Available page size options
|
|
* @param {Function} onPageSizeChange - Callback when page size changes
|
|
*/
|
|
export function Pagination({
|
|
currentPage,
|
|
totalPages,
|
|
onPageChange,
|
|
pageSize,
|
|
totalItems,
|
|
showPageSizeSelector = false,
|
|
pageSizeOptions = [10, 25, 50, 100],
|
|
onPageSizeChange,
|
|
}: PaginationProps) {
|
|
const startItem = ((currentPage - 1) * (pageSize || 10)) + 1
|
|
const endItem = Math.min(currentPage * (pageSize || 10), totalItems || 0)
|
|
|
|
return (
|
|
<div className="flex items-center justify-between px-2 py-4">
|
|
<div className="flex items-center gap-2 text-sm" style={{ color: 'var(--charcoal-brown)' }}>
|
|
{totalItems !== undefined && pageSize !== undefined && (
|
|
<span>
|
|
Mostrando {startItem}-{endItem} de {totalItems}
|
|
</span>
|
|
)}
|
|
{showPageSizeSelector && onPageSizeChange && (
|
|
<select
|
|
value={pageSize}
|
|
onChange={(e) => onPageSizeChange(Number(e.target.value))}
|
|
className="rounded border px-2 py-1 text-sm"
|
|
style={{
|
|
backgroundColor: 'var(--ivory-cream)',
|
|
borderColor: 'var(--mocha-taupe)',
|
|
color: 'var(--charcoal-brown)'
|
|
}}
|
|
>
|
|
{pageSizeOptions.map(size => (
|
|
<option key={size} value={size}>{size} por página</option>
|
|
))}
|
|
</select>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => onPageChange(1)}
|
|
disabled={currentPage === 1}
|
|
className="p-1 rounded hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed"
|
|
style={{
|
|
backgroundColor: currentPage === 1 ? 'transparent' : 'var(--ivory-cream)'
|
|
}}
|
|
>
|
|
<ChevronsLeft className="h-4 w-4" style={{ color: 'var(--charcoal-brown)' }} />
|
|
</button>
|
|
<button
|
|
onClick={() => onPageChange(currentPage - 1)}
|
|
disabled={currentPage === 1}
|
|
className="p-1 rounded hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed"
|
|
style={{
|
|
backgroundColor: currentPage === 1 ? 'transparent' : 'var(--ivory-cream)'
|
|
}}
|
|
>
|
|
<ChevronLeft className="h-4 w-4" style={{ color: 'var(--charcoal-brown)' }} />
|
|
</button>
|
|
|
|
<span className="px-3 py-1 text-sm" style={{ color: 'var(--charcoal-brown)' }}>
|
|
Página {currentPage} de {totalPages}
|
|
</span>
|
|
|
|
<button
|
|
onClick={() => onPageChange(currentPage + 1)}
|
|
disabled={currentPage === totalPages}
|
|
className="p-1 rounded hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed"
|
|
style={{
|
|
backgroundColor: currentPage === totalPages ? 'transparent' : 'var(--ivory-cream)'
|
|
}}
|
|
>
|
|
<ChevronRight className="h-4 w-4" style={{ color: 'var(--charcoal-brown)' }} />
|
|
</button>
|
|
<button
|
|
onClick={() => onPageChange(totalPages)}
|
|
disabled={currentPage === totalPages}
|
|
className="p-1 rounded hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed"
|
|
style={{
|
|
backgroundColor: currentPage === totalPages ? 'transparent' : 'var(--ivory-cream)'
|
|
}}
|
|
>
|
|
<ChevronsRight className="h-4 w-4" style={{ color: 'var(--charcoal-brown)' }} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export {
|
|
Table,
|
|
TableHeader,
|
|
TableBody,
|
|
TableFooter,
|
|
TableHead,
|
|
TableRow,
|
|
TableCell,
|
|
TableCaption,
|
|
}
|