erp-construccion/docs/06-frontend-specs/components/COMP-shared.md

14 KiB

Shared Components Specification

Version: 1.0.0 Fecha: 2025-12-05


UI Components

Button

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  leftIcon?: ReactNode;
  rightIcon?: ReactNode;
  fullWidth?: boolean;
  type?: 'button' | 'submit' | 'reset';
  onClick?: () => void;
  children: ReactNode;
}

// Uso
<Button variant="primary" size="md" loading={isLoading}>
  Guardar
</Button>

Card

interface CardProps {
  className?: string;
  padding?: 'none' | 'sm' | 'md' | 'lg';
  shadow?: 'none' | 'sm' | 'md' | 'lg';
  border?: boolean;
  hoverable?: boolean;
  onClick?: () => void;
  children: ReactNode;
}

// Variantes
<Card.Header />
<Card.Body />
<Card.Footer />

Badge

interface BadgeProps {
  variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
  size?: 'sm' | 'md';
  dot?: boolean;
  removable?: boolean;
  onRemove?: () => void;
  children: ReactNode;
}

// Uso
<Badge variant="success">Activo</Badge>
<Badge variant="warning" dot>Pendiente</Badge>

Modal

interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  title?: string;
  description?: string;
  size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
  closeOnOverlayClick?: boolean;
  closeOnEscape?: boolean;
  showCloseButton?: boolean;
  children: ReactNode;
  footer?: ReactNode;
}

// Uso
<Modal isOpen={isOpen} onClose={onClose} title="Crear Proyecto" size="lg">
  <ProjectForm />
  <Modal.Footer>
    <Button variant="outline" onClick={onClose}>Cancelar</Button>
    <Button variant="primary" onClick={onSubmit}>Crear</Button>
  </Modal.Footer>
</Modal>

Tabs

interface TabsProps {
  defaultValue?: string;
  value?: string;
  onChange?: (value: string) => void;
  children: ReactNode;
}

interface TabProps {
  value: string;
  label: string;
  icon?: ReactNode;
  disabled?: boolean;
  badge?: number | string;
}

// Uso
<Tabs defaultValue="general">
  <Tabs.List>
    <Tabs.Tab value="general" label="General" />
    <Tabs.Tab value="budget" label="Presupuesto" badge={3} />
    <Tabs.Tab value="schedule" label="Cronograma" />
  </Tabs.List>
  <Tabs.Panel value="general"><GeneralTab /></Tabs.Panel>
  <Tabs.Panel value="budget"><BudgetTab /></Tabs.Panel>
  <Tabs.Panel value="schedule"><ScheduleTab /></Tabs.Panel>
</Tabs>

Dropdown

interface DropdownProps {
  trigger: ReactNode;
  items: DropdownItem[];
  align?: 'start' | 'end';
  side?: 'top' | 'bottom';
}

interface DropdownItem {
  label: string;
  value: string;
  icon?: ReactNode;
  disabled?: boolean;
  danger?: boolean;
  onClick?: () => void;
}

// Uso
<Dropdown
  trigger={<Button variant="ghost"><MoreIcon /></Button>}
  items={[
    { label: 'Editar', value: 'edit', icon: <EditIcon /> },
    { label: 'Duplicar', value: 'duplicate', icon: <CopyIcon /> },
    { label: 'Eliminar', value: 'delete', icon: <TrashIcon />, danger: true },
  ]}
/>

Progress

interface ProgressProps {
  value: number;
  max?: number;
  variant?: 'default' | 'success' | 'warning' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  showLabel?: boolean;
  labelFormat?: (value: number, max: number) => string;
  animated?: boolean;
}

// Uso
<Progress value={75} showLabel />
<Progress value={30} variant="warning" size="lg" />

Avatar

interface AvatarProps {
  src?: string;
  name?: string;
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
  shape?: 'circle' | 'square';
  status?: 'online' | 'offline' | 'busy' | 'away';
  fallback?: ReactNode;
}

// Uso
<Avatar src={user.avatar} name={user.name} size="md" status="online" />
<Avatar.Group max={3}>
  <Avatar name="User 1" />
  <Avatar name="User 2" />
  <Avatar name="User 3" />
  <Avatar name="User 4" />
</Avatar.Group>

Tooltip

interface TooltipProps {
  content: ReactNode;
  position?: 'top' | 'bottom' | 'left' | 'right';
  delay?: number;
  disabled?: boolean;
  children: ReactNode;
}

// Uso
<Tooltip content="Mas informacion sobre este campo">
  <InfoIcon className="text-gray-400" />
</Tooltip>

Alert

interface AlertProps {
  type: 'info' | 'success' | 'warning' | 'error';
  title?: string;
  message: string;
  dismissible?: boolean;
  onDismiss?: () => void;
  action?: {
    label: string;
    onClick: () => void;
  };
}

// Uso
<Alert
  type="warning"
  title="Atencion"
  message="El presupuesto excede el limite aprobado"
  action={{ label: 'Ver detalles', onClick: () => {} }}
/>

Form Components

Input

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  helper?: string;
  error?: string;
  leftAddon?: ReactNode;
  rightAddon?: ReactNode;
  leftIcon?: ReactNode;
  rightIcon?: ReactNode;
}

// Uso
<Input
  label="Nombre del proyecto"
  placeholder="Ingrese nombre"
  error={errors.name?.message}
  helper="Maximo 200 caracteres"
/>

Select

interface SelectProps {
  label?: string;
  placeholder?: string;
  options: SelectOption[];
  value?: string | string[];
  onChange?: (value: string | string[]) => void;
  multiple?: boolean;
  searchable?: boolean;
  clearable?: boolean;
  disabled?: boolean;
  error?: string;
  loading?: boolean;
}

interface SelectOption {
  value: string;
  label: string;
  disabled?: boolean;
  group?: string;
}

// Uso
<Select
  label="Estado"
  options={statusOptions}
  value={status}
  onChange={setStatus}
  searchable
/>

DatePicker

interface DatePickerProps {
  label?: string;
  value?: Date | null;
  onChange?: (date: Date | null) => void;
  format?: string;
  placeholder?: string;
  minDate?: Date;
  maxDate?: Date;
  disabled?: boolean;
  error?: string;
}

interface DateRangePickerProps {
  label?: string;
  value?: { start: Date | null; end: Date | null };
  onChange?: (range: { start: Date | null; end: Date | null }) => void;
  // ... same as DatePicker
}

// Uso
<DatePicker
  label="Fecha de inicio"
  value={startDate}
  onChange={setStartDate}
  minDate={new Date()}
/>

<DateRangePicker
  label="Periodo"
  value={dateRange}
  onChange={setDateRange}
/>

FileUpload

interface FileUploadProps {
  label?: string;
  accept?: string;
  maxSize?: number; // bytes
  maxFiles?: number;
  multiple?: boolean;
  onUpload?: (files: File[]) => void;
  onRemove?: (file: File) => void;
  value?: File[];
  error?: string;
  helper?: string;
  preview?: boolean;
  dragAndDrop?: boolean;
}

// Uso
<FileUpload
  label="Documentos"
  accept=".pdf,.doc,.docx"
  maxSize={10 * 1024 * 1024}
  maxFiles={5}
  multiple
  dragAndDrop
  onUpload={handleUpload}
/>

CurrencyInput

interface CurrencyInputProps {
  label?: string;
  value?: number;
  onChange?: (value: number) => void;
  currency?: string; // MXN, USD
  locale?: string;
  precision?: number;
  min?: number;
  max?: number;
  error?: string;
}

// Uso
<CurrencyInput
  label="Monto"
  value={amount}
  onChange={setAmount}
  currency="MXN"
/>

Form (React Hook Form Integration)

interface FormProps<T extends FieldValues> {
  onSubmit: (data: T) => void | Promise<void>;
  defaultValues?: DefaultValues<T>;
  schema?: ZodSchema<T>;
  children: ReactNode;
}

// Uso
<Form onSubmit={handleSubmit} schema={projectSchema}>
  <Form.Field name="name" label="Nombre">
    <Input placeholder="Nombre del proyecto" />
  </Form.Field>
  <Form.Field name="status" label="Estado">
    <Select options={statusOptions} />
  </Form.Field>
  <Form.Submit>Guardar</Form.Submit>
</Form>

Table Components

DataTable

interface DataTableProps<T> {
  columns: ColumnDef<T>[];
  data: T[];
  loading?: boolean;
  emptyMessage?: string;
  sortable?: boolean;
  selectable?: boolean;
  selectedRows?: string[];
  onSelectRows?: (ids: string[]) => void;
  onRowClick?: (row: T) => void;
  rowActions?: (row: T) => DropdownItem[];
  pagination?: PaginationProps;
  stickyHeader?: boolean;
}

interface ColumnDef<T> {
  id: string;
  header: string | ReactNode;
  accessor: keyof T | ((row: T) => any);
  cell?: (value: any, row: T) => ReactNode;
  sortable?: boolean;
  width?: number | string;
  align?: 'left' | 'center' | 'right';
}

// Uso
const columns: ColumnDef<Project>[] = [
  { id: 'code', header: 'Codigo', accessor: 'code', sortable: true },
  { id: 'name', header: 'Nombre', accessor: 'name', sortable: true },
  {
    id: 'status',
    header: 'Estado',
    accessor: 'status',
    cell: (value) => <Badge variant={getVariant(value)}>{value}</Badge>
  },
  {
    id: 'progress',
    header: 'Avance',
    accessor: 'progressPercentage',
    cell: (value) => <Progress value={value} size="sm" />
  }
];

<DataTable
  columns={columns}
  data={projects}
  loading={isLoading}
  selectable
  pagination={{ total, page, limit, onChange }}
  rowActions={(row) => [
    { label: 'Editar', onClick: () => handleEdit(row) },
    { label: 'Eliminar', onClick: () => handleDelete(row), danger: true }
  ]}
/>

TableFilters

interface TableFiltersProps {
  filters: FilterConfig[];
  values: Record<string, any>;
  onChange: (values: Record<string, any>) => void;
  onClear?: () => void;
}

interface FilterConfig {
  key: string;
  label: string;
  type: 'text' | 'select' | 'date' | 'dateRange' | 'number';
  options?: SelectOption[]; // for select
  placeholder?: string;
}

// Uso
<TableFilters
  filters={[
    { key: 'search', label: 'Buscar', type: 'text' },
    { key: 'status', label: 'Estado', type: 'select', options: statusOptions },
    { key: 'dateRange', label: 'Fecha', type: 'dateRange' }
  ]}
  values={filters}
  onChange={setFilters}
  onClear={resetFilters}
/>

Chart Components

LineChart

interface LineChartProps {
  data: ChartData[];
  xKey: string;
  yKey: string | string[];
  height?: number;
  showGrid?: boolean;
  showLegend?: boolean;
  showTooltip?: boolean;
  colors?: string[];
  curve?: 'linear' | 'monotone' | 'step';
}

// Uso
<LineChart
  data={progressData}
  xKey="date"
  yKey={['planned', 'actual']}
  height={300}
  showLegend
/>

BarChart

interface BarChartProps {
  data: ChartData[];
  xKey: string;
  yKey: string | string[];
  height?: number;
  orientation?: 'vertical' | 'horizontal';
  stacked?: boolean;
  showGrid?: boolean;
  showLegend?: boolean;
  colors?: string[];
}

// Uso
<BarChart
  data={budgetData}
  xKey="category"
  yKey={['budgeted', 'actual']}
  height={400}
  showLegend
/>

PieChart

interface PieChartProps {
  data: PieData[];
  nameKey: string;
  valueKey: string;
  height?: number;
  donut?: boolean;
  showLabels?: boolean;
  showLegend?: boolean;
  colors?: string[];
}

// Uso
<PieChart
  data={statusDistribution}
  nameKey="status"
  valueKey="count"
  donut
  showLegend
/>

GaugeChart

interface GaugeChartProps {
  value: number;
  max?: number;
  min?: number;
  thresholds?: { value: number; color: string }[];
  label?: string;
  showValue?: boolean;
  size?: 'sm' | 'md' | 'lg';
}

// Uso
<GaugeChart
  value={75}
  max={100}
  thresholds={[
    { value: 30, color: 'red' },
    { value: 70, color: 'yellow' },
    { value: 100, color: 'green' }
  ]}
  label="Avance Total"
  showValue
/>

Layout Components

PageHeader

interface PageHeaderProps {
  title: string;
  subtitle?: string;
  breadcrumbs?: BreadcrumbItem[];
  actions?: ReactNode;
  tabs?: TabItem[];
  activeTab?: string;
  onTabChange?: (tab: string) => void;
}

// Uso
<PageHeader
  title="Proyectos"
  subtitle="Gestiona todos los proyectos de construccion"
  breadcrumbs={[
    { label: 'Dashboard', href: '/' },
    { label: 'Proyectos' }
  ]}
  actions={<Button>+ Nuevo Proyecto</Button>}
/>

Sidebar

interface SidebarProps {
  items: SidebarItem[];
  collapsed?: boolean;
  onCollapse?: (collapsed: boolean) => void;
  logo?: ReactNode;
  footer?: ReactNode;
}

interface SidebarItem {
  label: string;
  href?: string;
  icon?: ReactNode;
  badge?: number | string;
  children?: SidebarItem[];
  active?: boolean;
}

EmptyState

interface EmptyStateProps {
  icon?: ReactNode;
  title: string;
  description?: string;
  action?: ReactNode;
}

// Uso
<EmptyState
  icon={<FolderIcon />}
  title="Sin documentos"
  description="No hay documentos en esta carpeta"
  action={<Button>Subir documento</Button>}
/>

LoadingSpinner

interface LoadingSpinnerProps {
  size?: 'sm' | 'md' | 'lg';
  fullScreen?: boolean;
  message?: string;
}

// Uso
<LoadingSpinner size="lg" message="Cargando proyectos..." />

Utility Components

ConfirmDialog

interface ConfirmDialogProps {
  isOpen: boolean;
  onClose: () => void;
  onConfirm: () => void | Promise<void>;
  title: string;
  message: string;
  confirmLabel?: string;
  cancelLabel?: string;
  variant?: 'danger' | 'warning' | 'info';
  loading?: boolean;
}

// Uso
<ConfirmDialog
  isOpen={isDeleteDialogOpen}
  onClose={() => setIsDeleteDialogOpen(false)}
  onConfirm={handleDelete}
  title="Eliminar proyecto"
  message="Esta accion no se puede deshacer. Se eliminaran todos los datos asociados."
  variant="danger"
  confirmLabel="Eliminar"
/>

Toast

// Hook
const { toast } = useToast();

toast({
  title: 'Proyecto creado',
  description: 'El proyecto se ha creado exitosamente',
  type: 'success',
  duration: 5000
});

toast.success('Guardado exitosamente');
toast.error('Error al guardar');
toast.warning('Atencion requerida');
toast.info('Informacion importante');

Skeleton

interface SkeletonProps {
  width?: number | string;
  height?: number | string;
  variant?: 'text' | 'rectangular' | 'circular';
  animation?: 'pulse' | 'wave' | 'none';
}

// Uso
<Skeleton variant="text" width="60%" />
<Skeleton variant="rectangular" height={200} />
<Skeleton variant="circular" width={40} height={40} />

Ultima actualizacion: 2025-12-05