# DataTable

A powerful and flexible data table component featuring sorting, row selection, inline expansion, custom cell formatting, action columns, frozen columns, and row actions. Perfect for displaying large datasets with complex data structures and user interactions.

## Installation

```bash
npm install @ticatec/uniface-element
```

## Import

```typescript
import DataTable from "@ticatec/uniface-element/DataTable";
import type { DataColumn, ActionsColumn, IndicatorColumn, RowAction } from "@ticatec/uniface-element/DataTable";
```

## Basic Usage

```svelte
<script>
  import DataTable from "@ticatec/uniface-element/DataTable";
  
  const columns = [
    { text: "Name", field: "name", width: 150 },
    { text: "Age", field: "age", width: 80, align: "center" },
    { text: "Email", field: "email", width: 200 }
  ];
  
  const list = [
    { name: "John Doe", age: 30, email: "john@example.com" },
    { name: "Jane Smith", age: 25, email: "jane@example.com" }
  ];
</script>

<DataTable {columns} {list} />
```

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `columns` | `Array<DataColumn>` | required | Column configuration array |
| `list` | `Array<any>` | required | Data array to display |
| `actionsColumn` | `ActionsColumn \| null` | `null` | Configuration for actions column |
| `indicatorColumn` | `IndicatorColumn \| null` | `null` | Configuration for indicator/selection column |
| `selectedRows` | `Array<any>` | `[]` | Array of selected row data |
| `rowHeight` | `number` | `42` | Height of regular rows in pixels |
| `inlineRowHeight` | `number` | `80` | Height of expanded inline rows |
| `rowBorder` | `boolean` | `false` | Whether to show horizontal row borders |
| `colBorder` | `boolean` | `false` | Whether to show vertical column borders |
| `emptyIndicator` | `string \| undefined` | `undefined` | Text to show when no data is available |
| `style` | `string` | `""` | Additional CSS styles |

## Types

### DataColumn

```typescript
interface DataColumn {
  text: string;                    // Column header text
  field?: string;                  // Data field name
  width: number;                   // Column width in pixels
  frozen?: boolean;                // Whether column is frozen/fixed
  align?: 'left' | 'center' | 'right';  // Text alignment
  minWidth?: number;               // Minimum column width
  warp?: boolean;                  // Whether text can wrap
  formatter?: FormatCell;          // Cell formatting function
  escapeHTML?: boolean;            // Whether to escape HTML in content
  href?: HrefBuilder;              // Link builder function
  hint?: CellHint;                 // Tooltip/hint function
  render?: any;                    // Custom Svelte component renderer
  visible?: boolean;               // Column visibility
  resizable?: boolean;             // Whether column is resizable
  compareFunction?: CompareFunction;  // Sorting comparison function
}
```

### ActionsColumn

```typescript
interface ActionsColumn {
  width: number;                   // Column width in pixels
  getActions: GetRowActions;       // Function returning actions for each row
  align?: 'left' | 'center';      // Actions alignment
}
```

### IndicatorColumn

```typescript
interface IndicatorColumn {
  width: number;                   // Column width in pixels
  selectable?: boolean;            // Whether rows are selectable
  displayNo?: boolean;             // Whether to show row numbers
  buildInlineComponent?: (data: any) => Promise<any>;  // Inline component builder
}
```

### RowAction

```typescript
interface RowAction {
  label: string;                   // Action button text
  icon?: string;                   // Optional icon name
  type?: "primary" | "secondary";  // Button type/style
  callback: ActionFunction;        // Click handler function
}
```

## Examples

### Basic Data Table

```svelte
<script>
  import DataTable from "@ticatec/uniface-element/DataTable";
  
  const columns = [
    {
      text: "Product Name",
      field: "name",
      width: 200,
      resizable: true
    },
    {
      text: "Price",
      field: "price",
      width: 100,
      align: "right",
      formatter: (value) => `$${value?.toFixed(2) ?? '0.00'}`
    },
    {
      text: "Category",
      field: "category",
      width: 150,
      align: "center"
    },
    {
      text: "Stock",
      field: "stock",
      width: 80,
      align: "center",
      compareFunction: (a, b) => (a?.stock ?? 0) - (b?.stock ?? 0)
    }
  ];
  
  const products = [
    { name: "Laptop", price: 999.99, category: "Electronics", stock: 15 },
    { name: "Phone", price: 599.99, category: "Electronics", stock: 25 },
    { name: "Book", price: 19.99, category: "Education", stock: 100 },
    { name: "Headphones", price: 149.99, category: "Electronics", stock: 8 }
  ];
</script>

<div class="table-container">
  <DataTable columns={columns} list={products} rowBorder={true} />
</div>

<style>
  .table-container {
    width: 100%;
    height: 400px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
  }
</style>
```

### Table with Row Selection and Actions

```svelte
<script>
  import DataTable from "@ticatec/uniface-element/DataTable";
  
  const columns = [
    { text: "Name", field: "name", width: 150 },
    { text: "Email", field: "email", width: 200 },
    { text: "Role", field: "role", width: 120 },
    { text: "Status", field: "status", width: 100, align: "center",
      formatter: (value) => {
        const colors = { active: 'green', inactive: 'red', pending: 'orange' };
        return `<span style="color: ${colors[value] || 'black'}">${value}</span>`;
      },
      escapeHTML: true
    }
  ];
  
  const indicatorColumn = {
    width: 60,
    selectable: true,
    displayNo: true
  };
  
  const actionsColumn = {
    width: 120,
    align: 'center',
    getActions: (user) => [
      {
        label: 'Edit',
        icon: 'icon_google_create',
        callback: () => handleEdit(user)
      },
      {
        label: 'Delete',
        icon: 'icon_google_delete',
        type: 'secondary',
        callback: () => handleDelete(user)
      }
    ]
  };
  
  const users = [
    { name: "Alice Johnson", email: "alice@company.com", role: "Admin", status: "active" },
    { name: "Bob Smith", email: "bob@company.com", role: "User", status: "active" },
    { name: "Carol Brown", email: "carol@company.com", role: "Manager", status: "pending" }
  ];
  
  let selectedRows = [];
  
  function handleEdit(user) {
    console.log('Editing user:', user);
  }
  
  function handleDelete(user) {
    console.log('Deleting user:', user);
  }
  
  $: console.log('Selected users:', selectedRows);
</script>

<DataTable 
  {columns} 
  list={users} 
  {indicatorColumn} 
  {actionsColumn}
  bind:selectedRows
  rowBorder={true}
  colBorder={true}
/>
```

### Table with Custom Cell Rendering and Sorting

```svelte
<script>
  import DataTable from "@ticatec/uniface-element/DataTable";
  import UserAvatar from "./UserAvatar.svelte";
  
  const columns = [
    {
      text: "User",
      field: "user",
      width: 200,
      render: UserAvatar
    },
    {
      text: "Score",
      field: "score",
      width: 100,
      align: "center",
      formatter: (value) => `${value}/100`,
      compareFunction: (a, b) => (a?.score ?? 0) - (b?.score ?? 0)
    },
    {
      text: "Last Login",
      field: "lastLogin",
      width: 150,
      compareFunction: (a, b) => {
        const dateA = new Date(a?.lastLogin ?? 0);
        const dateB = new Date(b?.lastLogin ?? 0);
        return dateA.getTime() - dateB.getTime();
      },
      formatter: (value) => {
        return value ? new Date(value).toLocaleDateString() : 'Never';
      }
    },
    {
      text: "Actions",
      field: "website",
      width: 120,
      href: (item) => [{
        text: "Visit Profile",
        action: () => window.open(`/profile/${item.id}`, '_blank')
      }, {
        text: "Send Message",
        action: () => handleSendMessage(item)
      }]
    }
  ];
  
  const userData = [
    { 
      id: 1, 
      user: { name: "John Doe", avatar: "/avatars/john.jpg" },
      score: 85,
      lastLogin: "2024-01-15",
      website: "john-portfolio.com"
    },
    { 
      id: 2, 
      user: { name: "Jane Smith", avatar: "/avatars/jane.jpg" },
      score: 92,
      lastLogin: "2024-01-10",
      website: "jane-designs.com"
    }
  ];
  
  function handleSendMessage(user) {
    console.log('Sending message to:', user.user.name);
  }
</script>

<DataTable {columns} list={userData} rowHeight={50} />
```

### Table with Inline Row Expansion

```svelte
<script>
  import DataTable from "@ticatec/uniface-element/DataTable";
  import OrderDetails from "./OrderDetails.svelte";
  
  const columns = [
    { text: "Order ID", field: "orderId", width: 120 },
    { text: "Customer", field: "customer", width: 150 },
    { text: "Total", field: "total", width: 100, align: "right",
      formatter: (value) => `$${value?.toFixed(2) ?? '0.00'}`
    },
    { text: "Status", field: "status", width: 120, align: "center" },
    { text: "Date", field: "orderDate", width: 120 }
  ];
  
  const indicatorColumn = {
    width: 50,
    selectable: false,
    displayNo: false,
    buildInlineComponent: async (orderData) => {
      // Simulate API call to fetch order details
      await new Promise(resolve => setTimeout(resolve, 1000));
      return OrderDetails;
    }
  };
  
  const orders = [
    {
      orderId: "ORD-001",
      customer: "John Doe",
      total: 299.99,
      status: "Shipped",
      orderDate: "2024-01-15",
      items: [
        { product: "Laptop", quantity: 1, price: 299.99 }
      ]
    },
    {
      orderId: "ORD-002", 
      customer: "Jane Smith",
      total: 149.98,
      status: "Processing",
      orderDate: "2024-01-14",
      items: [
        { product: "Mouse", quantity: 2, price: 24.99 },
        { product: "Keyboard", quantity: 1, price: 99.99 }
      ]
    }
  ];
</script>

<DataTable 
  {columns} 
  list={orders} 
  {indicatorColumn}
  inlineRowHeight={200}
  emptyIndicator="No orders found"
/>
```

### Table with Frozen Columns

```svelte
<script>
  import DataTable from "@ticatec/uniface-element/DataTable";
  
  const columns = [
    {
      text: "Name",
      field: "name",
      width: 150,
      frozen: true,
      resizable: false
    },
    {
      text: "ID",
      field: "employeeId",
      width: 100,
      frozen: true,
      align: "center"
    },
    { text: "Department", field: "department", width: 150 },
    { text: "Position", field: "position", width: 180 },
    { text: "Salary", field: "salary", width: 120, align: "right",
      formatter: (value) => `$${value?.toLocaleString() ?? '0'}`
    },
    { text: "Start Date", field: "startDate", width: 120 },
    { text: "Manager", field: "manager", width: 150 },
    { text: "Location", field: "location", width: 200 },
    { text: "Phone", field: "phone", width: 140 },
    { text: "Email", field: "email", width: 200 }
  ];
  
  const employees = [
    {
      name: "John Doe",
      employeeId: "EMP001",
      department: "Engineering",
      position: "Senior Developer",
      salary: 95000,
      startDate: "2022-03-15",
      manager: "Alice Johnson",
      location: "New York",
      phone: "555-0123",
      email: "john.doe@company.com"
    },
    // ... more employee records
  ];
</script>

<div style="width: 800px; height: 400px;">
  <DataTable {columns} list={employees} />
</div>
```

### Advanced Table with All Features

```svelte
<script>
  import DataTable from "@ticatec/uniface-element/DataTable";
  import ProductDetails from "./ProductDetails.svelte";
  import ProductImage from "./ProductImage.svelte";
  
  const columns = [
    {
      text: "Image",
      field: "image",
      width: 80,
      frozen: true,
      render: ProductImage,
      align: "center"
    },
    {
      text: "Product",
      field: "name",
      width: 200,
      frozen: true,
      resizable: true,
      hint: (item) => `SKU: ${item.sku}`,
      compareFunction: (a, b) => (a?.name ?? '').localeCompare(b?.name ?? '')
    },
    {
      text: "Price",
      field: "price",
      width: 100,
      align: "right",
      formatter: (value) => `$${value?.toFixed(2) ?? '0.00'}`,
      compareFunction: (a, b) => (a?.price ?? 0) - (b?.price ?? 0)
    },
    {
      text: "Stock",
      field: "stock",
      width: 80,
      align: "center",
      formatter: (value) => {
        if (value === 0) return '<span style="color: red">Out of Stock</span>';
        if (value < 10) return `<span style="color: orange">${value}</span>`;
        return `<span style="color: green">${value}</span>`;
      },
      escapeHTML: true,
      compareFunction: (a, b) => (a?.stock ?? 0) - (b?.stock ?? 0)
    },
    {
      text: "Category",
      field: "category",
      width: 120,
      compareFunction: (a, b) => (a?.category ?? '').localeCompare(b?.category ?? '')
    },
    {
      text: "Supplier",
      field: "supplier",
      width: 150,
      href: (item) => [{
        text: "Contact Supplier",
        action: () => openSupplierContact(item.supplierId)
      }]
    },
    {
      text: "Last Updated",
      field: "updatedAt",
      width: 140,
      formatter: (value) => new Date(value).toLocaleDateString(),
      compareFunction: (a, b) => {
        const dateA = new Date(a?.updatedAt ?? 0);
        const dateB = new Date(b?.updatedAt ?? 0);
        return dateA.getTime() - dateB.getTime();
      }
    }
  ];
  
  const indicatorColumn = {
    width: 60,
    selectable: true,
    displayNo: true,
    buildInlineComponent: async (product) => {
      // Simulate loading product details
      return ProductDetails;
    }
  };
  
  const actionsColumn = {
    width: 140,
    align: 'center',
    getActions: (product) => {
      const actions = [
        {
          label: 'Edit',
          icon: 'icon_google_create',
          callback: () => editProduct(product)
        },
        {
          label: 'Clone',
          icon: 'icon_google_content_copy',
          callback: () => cloneProduct(product)
        }
      ];
      
      if (product.stock === 0) {
        actions.push({
          label: 'Restock',
          icon: 'icon_google_add_shopping_cart',
          type: 'primary',
          callback: () => restockProduct(product)
        });
      }
      
      actions.push({
        label: 'Delete',
        icon: 'icon_google_delete',
        type: 'secondary',
        callback: () => deleteProduct(product)
      });
      
      return actions;
    }
  };
  
  let products = [
    {
      id: 1,
      name: "Wireless Headphones",
      sku: "WH-001",
      price: 199.99,
      stock: 25,
      category: "Electronics",
      supplier: "TechCorp Inc.",
      supplierId: "SUP001",
      image: "/images/headphones.jpg",
      updatedAt: "2024-01-15T10:30:00Z"
    },
    // ... more products
  ];
  
  let selectedRows = [];
  
  function editProduct(product) {
    console.log('Editing product:', product);
  }
  
  function cloneProduct(product) {
    console.log('Cloning product:', product);
  }
  
  function restockProduct(product) {
    console.log('Restocking product:', product);
  }
  
  function deleteProduct(product) {
    console.log('Deleting product:', product);
  }
  
  function openSupplierContact(supplierId) {
    console.log('Opening supplier contact:', supplierId);
  }
  
  $: console.log('Selected products:', selectedRows);
</script>

<div class="advanced-table">
  <h2>Product Inventory Management</h2>
  <div class="table-wrapper">
    <DataTable 
      {columns} 
      list={products}
      {indicatorColumn}
      {actionsColumn}
      bind:selectedRows
      rowHeight={60}
      inlineRowHeight={300}
      rowBorder={true}
      colBorder={true}
      emptyIndicator="No products found. Add some products to get started."
    />
  </div>
  
  {#if selectedRows.length > 0}
    <div class="selection-info">
      <p>{selectedRows.length} product(s) selected</p>
      <button on:click={() => console.log('Bulk edit:', selectedRows)}>
        Bulk Edit
      </button>
      <button on:click={() => console.log('Bulk delete:', selectedRows)}>
        Bulk Delete
      </button>
    </div>
  {/if}
</div>

<style>
  .advanced-table {
    padding: 20px;
    height: 100vh;
    display: flex;
    flex-direction: column;
  }
  
  .table-wrapper {
    flex: 1;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    overflow: hidden;
  }
  
  .selection-info {
    margin-top: 16px;
    padding: 12px;
    background: #f8fafc;
    border-radius: 6px;
    display: flex;
    align-items: center;
    gap: 12px;
  }
  
  .selection-info button {
    padding: 6px 12px;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    background: white;
    cursor: pointer;
  }
  
  .selection-info button:hover {
    background: #f3f4f6;
  }
</style>
```

## Styling

The DataTable component supports extensive styling customization through CSS custom properties and classes:

```css
.uniface-data-table {
  --table-header-bg: #f8fafc;
  --table-header-text: #374151;
  --table-row-hover: #f9fafb;
  --table-border-color: #e5e7eb;
  --table-selected-bg: #dbeafe;
}

/* Custom row styling */
.uniface-data-table.row-border .table-row {
  border-bottom: 1px solid var(--table-border-color);
}

/* Custom column styling */
.uniface-data-table.cell-border .table-cell {
  border-right: 1px solid var(--table-border-color);
}
```

## Accessibility

The DataTable component includes several accessibility features:

- Keyboard navigation support
- ARIA labels for screen readers
- Focus management for interactive elements
- Semantic table structure
- Row selection announcements

## Best Practices

1. **Performance**: For large datasets (1000+ rows), consider implementing virtual scrolling or pagination
2. **Column Width**: Set appropriate widths based on content to prevent layout shifts
3. **Frozen Columns**: Use frozen columns sparingly - typically for key identifiers only
4. **Actions**: Limit row actions to 3-5 most important operations
5. **Loading States**: Always provide loading indicators for async operations
6. **Error Handling**: Implement proper error handling for failed inline component loading
7. **Responsive Design**: Consider hiding less important columns on smaller screens

## License

MIT