Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EdgarJr30/proyecto-de-grado-cms/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The MLM CMMS frontend uses a component-based architecture with reusable UI primitives, feature-specific components, and layout elements.

Component Organization

components/
├── ui/                    # Base UI primitives
│   ├── button.tsx
│   ├── input.tsx
│   ├── select.tsx
│   ├── Modal.tsx
│   ├── Spinner.tsx
│   └── filters/         # Filter components
├── layout/               # Layout components
│   ├── Header.tsx
│   └── Sidebar.tsx
├── navigation/           # Navigation components
├── common/               # Shared components
├── dashboard/            # Dashboard widgets
├── reports/              # Report components
├── notifications/        # Notification components
├── pwa/                  # PWA-specific components
└── app/                  # App-level components
    ├── ThemedAppRoot.tsx
    └── AppRouterContent.tsx

UI Primitives

Button Component

A flexible button component with variants:
src/components/ui/button.tsx
import * as React from "react"
import { cn } from "../../utils/cn"

export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "default" | "outline"
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant = "default", type = "button", ...props }, ref) => {
    const baseStyle =
      "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 cursor-pointer"
    const variants = {
      default: "bg-blue-600 text-white hover:bg-blue-500",
      outline: "border border-gray-300 text-gray-700 hover:bg-gray-100",
    }
    return (
      <button
        ref={ref}
        type={type}
        className={cn(baseStyle, variants[variant], className)}
        {...props}
      />
    )
  }
)
Button.displayName = "Button"

Input Component

src/components/ui/input.tsx
import * as React from "react"
import { cn } from "../../utils/cn"

export interface InputProps
  extends React.InputHTMLAttributes<HTMLInputElement> {}

export const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, type = "text", ...props }, ref) => {
    return (
      <input
        type={type}
        className={cn(
          "flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm",
          "focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
          "disabled:cursor-not-allowed disabled:opacity-50",
          className
        )}
        ref={ref}
        {...props}
      />
    )
  }
)
Input.displayName = "Input"
A reusable modal for dialogs and forms:
src/components/ui/Modal.tsx
interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  title?: string;
  children: React.ReactNode;
  size?: 'sm' | 'md' | 'lg' | 'xl';
}

export function Modal({ isOpen, onClose, title, children, size = 'md' }: ModalProps) {
  if (!isOpen) return null;
  
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center">
      <div className="fixed inset-0 bg-black/50" onClick={onClose} />
      <div className={cn("relative bg-white rounded-lg shadow-xl", sizeClasses[size])}>
        {title && (
          <div className="border-b px-6 py-4">
            <h2 className="text-xl font-semibold">{title}</h2>
          </div>
        )}
        <div className="p-6">{children}</div>
      </div>
    </div>
  );
}

Spinner Component

src/components/ui/Spinner.tsx
export function Spinner({ size = 'md' }: { size?: 'sm' | 'md' | 'lg' }) {
  const sizeClasses = {
    sm: 'w-4 h-4',
    md: 'w-8 h-8',
    lg: 'w-12 h-12',
  };
  
  return (
    <div className={cn('animate-spin rounded-full border-2 border-gray-300 border-t-blue-600', sizeClasses[size])} />
  );
}

Layout Components

App Root

The top-level app component:
src/components/app/ThemedAppRoot.tsx
import { ToastContainer } from 'react-toastify';
import { MotionConfig } from 'framer-motion';
import { useTheme } from '../../context/ThemeContext';
import AppRouterContent from './AppRouterContent';

export default function ThemedAppRoot() {
  const { isDark } = useTheme();

  return (
    <MotionConfig reducedMotion="user">
      <ToastContainer
        position="bottom-right"
        autoClose={3000}
        theme={isDark ? 'dark' : 'light'}
      />
      <AppRouterContent />
    </MotionConfig>
  );
}

Protected Route Wrapper

import { Navigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import { ScreenLoader } from '../ui/ScreenLoader';

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { loading, isAuthenticated } = useAuth();
  
  if (loading) return <ScreenLoader />;
  if (!isAuthenticated) return <Navigate to="/login" replace />;
  
  return <>{children}</>;
}

Filter Components

Reusable filter bar for data tables:
src/components/ui/filters/FilterBar.tsx
interface FilterBarProps {
  filters: FilterConfig[];
  values: Record<string, any>;
  onChange: (key: string, value: any) => void;
  onReset: () => void;
}

export function FilterBar({ filters, values, onChange, onReset }: FilterBarProps) {
  return (
    <div className="flex gap-4 items-center">
      {filters.map((filter) => (
        <FilterInput
          key={filter.key}
          config={filter}
          value={values[filter.key]}
          onChange={(value) => onChange(filter.key, value)}
        />
      ))}
      <Button variant="outline" onClick={onReset}>
        Limpiar filtros
      </Button>
    </div>
  );
}

Context Providers

Components that provide global state:
src/context/AuthContext.tsx
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [loading, setLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const refresh = useCallback(async () => {
    const { data: { session } } = await supabase.auth.getSession();
    setIsAuthenticated(!!session);
    setLoading(false);
  }, []);

  useEffect(() => {
    refresh();
    const { data: { subscription } } = supabase.auth.onAuthStateChange(() => {
      refresh();
    });
    return () => subscription.unsubscribe();
  }, [refresh]);

  return (
    <AuthContext.Provider value={{ loading, isAuthenticated, refresh }}>
      {children}
    </AuthContext.Provider>
  );
};

Animation Components

Motion primitives using Framer Motion:
src/components/ui/motionPrimitives.tsx
import { motion } from 'framer-motion';

export const FadeIn = motion.div;

export const SlideIn = ({ children, direction = 'up' }: SlideInProps) => (
  <motion.div
    initial={{ opacity: 0, y: direction === 'up' ? 20 : -20 }}
    animate={{ opacity: 1, y: 0 }}
    exit={{ opacity: 0, y: direction === 'up' ? 20 : -20 }}
  >
    {children}
  </motion.div>
);
The app respects prefers-reduced-motion through the MotionConfig wrapper.

Permission-Aware Components

Components that render based on permissions:
import { Can } from '../rbac/PermissionsContext';

function TicketActions({ ticket }: { ticket: Ticket }) {
  return (
    <div>
      <Can perm="work_orders:update">
        <Button onClick={handleEdit}>Edit</Button>
      </Can>
      
      <Can perm="work_orders:delete">
        <Button variant="outline" onClick={handleDelete}>Delete</Button>
      </Can>
    </div>
  );
}

Form Components

Common form patterns:
import { useState } from 'react';
import { Input } from './ui/input';
import { Button } from './ui/button';

function TicketForm({ onSubmit }: { onSubmit: (data: TicketData) => void }) {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    onSubmit({ title, description });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <div className="space-y-4">
        <div>
          <label className="block text-sm font-medium mb-1">Título</label>
          <Input
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            required
          />
        </div>
        
        <div>
          <label className="block text-sm font-medium mb-1">Descripción</label>
          <textarea
            value={description}
            onChange={(e) => setDescription(e.target.value)}
            className="w-full rounded-md border border-gray-300 p-2"
          />
        </div>
        
        <Button type="submit">Crear Ticket</Button>
      </div>
    </form>
  );
}

Utility Functions

Class Name Merging

src/utils/cn.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
Use cn() to merge Tailwind classes safely, avoiding conflicts.

Best Practices

export const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, ...props }, ref) => {
    return <input ref={ref} className={cn(baseClasses, className)} {...props} />;
  }
);
If a pattern is used more than twice, extract it into a component in components/ui/ or components/common/.
Each component should do one thing well. Compose smaller components into larger features.
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'default' | 'outline';
  size?: 'sm' | 'md' | 'lg';
}

Styling

The application uses Tailwind CSS v4 for styling:
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow-md">
  <h2 className="text-xl font-semibold text-gray-900">Title</h2>
  <Button variant="default">Action</Button>
</div>

Theme Support

Components respond to theme context:
import { useTheme } from '../context/ThemeContext';

function ThemedComponent() {
  const { isDark } = useTheme();
  
  return (
    <div className={isDark ? 'bg-gray-900 text-white' : 'bg-white text-gray-900'}>
      Content
    </div>
  );
}

Next Steps

Structure

Review the overall architecture

Routing

Learn about routing patterns

Services

Understand data access

Build docs developers (and LLMs) love