# AkiFilter

It handles form state, persistence, visibility management, and applied filter chips automatically.

### <mark style="color:red;">Installation</mark>

```bash
pnpm add @akinon/akifilter
```

### <mark style="color:red;">Basic Usage</mark>

```tsx
import { Akifilter, filterField, type AkifilterSchema } from '@akinon/akifilter';

interface FilterValues {
  name: string;
  status: string;
  createdAt: Date | null;
}

const filterSchema: AkifilterSchema<FilterValues> = [
  filterField<FilterValues>()
    .key('name')
    .type('text')
    .label('Name')
    .placeholder('Search by name...')
    .config({visible: true)
    .build(),
  filterField<FilterValues>()
    .key('status')
    .type('select')
    .label('Status')
    .placeholder('Select status')
    .options([
      { label: 'Active', value: 'active' },
      { label: 'Inactive', value: 'inactive' },
      { label: 'Pending', value: 'pending' }
    ])
    .build(),
  filterField<FilterValues>()
    .key('createdAt')
    .type('date')
    .label('Created Date')
    .placeholder('Select date')
    .build()
];

export const ProductFilters = () => {
  const handleValuesChange = (values: Partial<FilterValues>) => {
    console.log('Filter values changed:', values);
    // Fetch data with new filter values
  };

  return (
    <Akifilter
      filterSchema={filterSchema}
      onValuesChange={handleValuesChange}
      storageNamespace="products"
    />
  );
};
```

### <mark style="color:red;">Props</mark>

| Prop                    | Type                           | Description                                            |
| ----------------------- | ------------------------------ | ------------------------------------------------------ |
| `filterSchema`          | `AkifilterSchema<T>`           | Declarative description of the filter fields           |
| `storageNamespace`      | `string`                       | Optional namespace for local storage persistence       |
| `defaultValues`         | `Partial<T>`                   | Default values supplied by the host application        |
| `onValuesChange`        | `(values: Partial<T>) => void` | Callback on every value change with normalised payload |
| `onVisibleFieldsChange` | `(keys: string[]) => void`     | Callback when visible field keys change                |
| `onClearAll`            | `() => void`                   | Callback when user clears all filters                  |
| `onImportCsv`           | `() => void`                   | Callback for CSV import action                         |
| `onImportXls`           | `() => void`                   | Callback for XLS/XLSX import action                    |
| `enableImportCsv`       | `boolean`                      | Shows the CSV import button in toolbar                 |
| `enableImportXls`       | `boolean`                      | Shows the XLS/XLSX import button in toolbar            |

### <mark style="color:red;">Filter Field Builder</mark>

Use `filterField()` builder to create filter field definitions with a fluent API:

```tsx
import { filterField } from '@akinon/akifilter';

const field = filterField<FilterValues>()
  .key('fieldName')           // Field identifier (required)
  .type('text')               // Field type (required)
  .label('Field Label')       // Display label
  .placeholder('Placeholder') // Input placeholder
  .config({visible:true})     // Field config (visible etc.)
  .defaultValue('initial')    // Default value
  .build();
```

### <mark style="color:red;">Field Types</mark>

#### Text Field

```tsx
filterField<FilterValues>()
  .key('search')
  .type('text')
  .label('Search')
  .placeholder('Search...')
  .build()
```

#### Number Field

```tsx
filterField<FilterValues>()
  .key('minPrice')
  .type('number')
  .label('Minimum Price')
  .placeholder('Enter amount')
  .build()
```

#### Select Field

```tsx
filterField<FilterValues>()
  .key('category')
  .type('select')
  .label('Category')
  .placeholder('Select category')
  .options([
    { label: 'Electronics', value: 'electronics' },
    { label: 'Clothing', value: 'clothing' },
    { label: 'Books', value: 'books' }
  ])
  .build()
```

#### Date Field

```tsx
filterField<FilterValues>()
  .key('createdAt')
  .type('date')
  .label('Created Date')
  .placeholder('Select date')
  .build()
```

#### Date with Time

```tsx
filterField<FilterValues>()
  .key('scheduledAt')
  .type('date')
  .label('Scheduled At')
  .placeholder('Select date and time')
  .showTime(true)
  .build()
```

#### Checkbox Field

```tsx
filterField<FilterValues>()
  .key('isActive')
  .type('checkbox')
  .label('Active Only')
  .build()
```

#### Textarea Field

```tsx
filterField<FilterValues>()
  .key('description')
  .type('textarea')
  .label('Description')
  .placeholder('Enter description...')
  .build()
```

#### Custom Field

For advanced use cases, use custom fields with a render function:

```tsx
filterField<FilterValues>()
  .key('customField')
  .type('custom')
  .label('Custom Filter')
  .options([{ // Used to properly render labels in active filters.
   label: "Phone",
   value: "phone"
  }])
  .render(({ field, control, formValues }) => (
    <MyCustomComponent
      control={control}
      name={field.key}
      formValues={formValues}
    />
  ))
  .build()
```

### <mark style="color:red;">Field Visibility</mark>

By default, if no fields have explicit visibility set, the first 8 fields in the schema are shown. Use .config({ visible: false }) to explicitly control which fields appear in the main filter form.

```tsx
// Explicitly visible
filterField<FilterValues>()
  .key('name')
  .type('text')
  .config({visible: true})
  .build()

// Visible callback method
filterField<FilterValues>()
  .key('name')
  .type('text')
  .config({
    visible: (formValues, currentVisibleStatus)=>{
      // formValues holds the current form values. currentVisibleStatus indicates whether the field is currently visible or not.
      return true
  }})
  .build()

// No explicit visibility (uses default behavior)
filterField<FilterValues>()
  .key('advanced')
  .type('text')
  .build()  // Shown only if within first 8 fields, otherwise accessible via modal
```

{% hint style="warning" %}
If at least one field has  visible set, only those explicitly marked fields will be shown by default. Otherwise, the first 8 fields are displayed.
{% endhint %}

### <mark style="color:red;">Conditional Field Configuration</mark>

Fields can be dynamically disabled or hidden based on other field values:

```tsx
interface FilterValues {
  type: string;
  subType: string;
}

const schema: AkifilterSchema<FilterValues> = [
  filterField<FilterValues>()
    .key('type')
    .type('select')
    .label('Type')
    .options([
      { label: 'Product', value: 'product' },
      { label: 'Service', value: 'service' }
    ])
    .build(),
  filterField<FilterValues>()
    .key('subType')
    .type('select')
    .label('Sub Type')
    .options([
      { label: 'Physical', value: 'physical' },
      { label: 'Digital', value: 'digital' }
    ])
    .config({
      // Disable when type is 'service'
      disabled: (formValues) => formValues.type === 'service',
      // Hide when type is empty
      visible: (formValues, currentVisibleStatus) => Boolean(formValues.type)
    })
    .build()
];
```

### <mark style="color:red;">Section Fields</mark>

Group related filters into collapsible sections:

```tsx
const schema: AkifilterSchema<FilterValues> = [
  // Regular fields
  filterField<FilterValues>()
    .key('name')
    .type('text')
    .label('Name')
    .build(),
  
  // Section with nested fields
  {
    key: 'advancedSection',
    type: 'section',
    label: 'Advanced Filters',
    fields: [
      filterField<FilterValues>()
        .key('minPrice')
        .type('number')
        .label('Min Price')
        .build(),
      filterField<FilterValues>()
        .key('maxPrice')
        .type('number')
        .label('Max Price')
        .build()
    ]
  }
];
```

### <mark style="color:red;">Persistence</mark>

Akifilter automatically persists filter values and visibility settings to localStorage:

```tsx
<Akifilter
  filterSchema={schema}
  storageNamespace="my-filters"  // Unique namespace for this filter instance
  onValuesChange={handleValuesChange}
/>
```

* Filter values are stored under `appliedFilters-{namespace}`
* Visibility settings are stored under `shownFilters-{namespace}`
* Values persist across page reloads

### <mark style="color:red;">Applied Filters</mark>

Active filter values are displayed as removable chips above the filter form:

* Click the `×` on a chip to remove that filter
* Use "Clear All" to reset all filters to defaults
* Values are formatted based on field type (dates use locale format, booleans show Yes/No)

### <mark style="color:red;">Visibility Modal</mark>

Users can customize which filters appear in the main form:

1. Click the "Select Filters" button in the toolbar
2. Search and paginate through available filters
3. Toggle checkboxes to show/hide filters
4. Selections are persisted to localStorage

### <mark style="color:red;">Import Actions</mark>

Enable CSV/XLS import buttons for bulk filter operations:

```tsx
<Akifilter
  filterSchema={schema}
  enableImportCsv
  enableImportXls
  onImportCsv={() => {
    // Handle CSV import
    console.log('Import CSV clicked');
  }}
  onImportXls={() => {
    // Handle XLS import
    console.log('Import XLS clicked');
  }}
  onValuesChange={handleValuesChange}
/>
```

### <mark style="color:red;">Complete Example</mark>

```tsx
import { Akifilter, filterField, type AkifilterSchema } from '@akinon/akifilter';

interface OrderFilters {
  orderNumber: string;
  status: string;
  customerName: string;
  minTotal: number | null;
  maxTotal: number | null;
  createdFrom: Date | null;
  createdTo: Date | null;
  isPaid: boolean;
}

const orderFilterSchema: AkifilterSchema<OrderFilters> = [
  filterField<OrderFilters>()
    .key('orderNumber')
    .type('text')
    .label('Order Number')
    .placeholder('Enter order number')
    .build(),
  filterField<OrderFilters>()
    .key('status')
    .type('select')
    .label('Status')
    .placeholder('Select status')
    .options([
      { label: 'Pending', value: 'pending' },
      { label: 'Processing', value: 'processing' },
      { label: 'Shipped', value: 'shipped' },
      { label: 'Delivered', value: 'delivered' },
      { label: 'Cancelled', value: 'cancelled' }
    ])
    .build(),
  filterField<OrderFilters>()
    .key('customerName')
    .type('text')
    .label('Customer Name')
    .placeholder('Search customer...')
    .build(),
  filterField<OrderFilters>()
    .key('isPaid')
    .type('checkbox')
    .label('Paid Orders Only')
    .build(),
  {
    key: 'priceRange',
    type: 'section',
    label: 'Price Range',
    fields: [
      filterField<OrderFilters>()
        .key('minTotal')
        .type('number')
        .label('Min Total')
        .placeholder('0')
        .build(),
      filterField<OrderFilters>()
        .key('maxTotal')
        .type('number')
        .label('Max Total')
        .placeholder('10000')
        .build()
    ]
  },
  {
    key: 'dateRange',
    type: 'section',
    label: 'Date Range',
    fields: [
      filterField<OrderFilters>()
        .key('createdFrom')
        .type('date')
        .label('From')
        .placeholder('Start date')
        .build(),
      filterField<OrderFilters>()
        .key('createdTo')
        .type('date')
        .label('To')
        .placeholder('End date')
        .build()
    ]
  }
];

export const OrderListFilters = () => {
  const handleValuesChange = (values: Partial<OrderFilters>) => {
    // Build query params and fetch orders
    const params = new URLSearchParams();
    
    Object.entries(values).forEach(([key, value]) => {
      if (value !== null && value !== undefined) {
        params.set(key, String(value));
      }
    });

    // fetchOrders(params);
  };

  const handleClearAll = () => {
    // Reset to initial state
  };

  return (
    <Akifilter
      filterSchema={orderFilterSchema}
      storageNamespace="order-list"
      onValuesChange={handleValuesChange}
      onClearAll={handleClearAll}
    />
  );
};
```

### <mark style="color:red;">Internationalization</mark>

Akifilter uses `@akinon/akilocale` for built-in translations. Default labels and messages are provided in English and Turkish.

### <mark style="color:red;">Best Practices</mark>

1. **Define TypeScript interfaces** - Always type your filter values for better type safety
2. **Use meaningful storage namespaces** - Prevents conflicts between different filter instances
3. **Mark primary filters as visible** - Show the most commonly used filters by default
4. **Group related filters** - Use section fields to organize complex filter sets
5. **Handle empty states** - Provide appropriate feedback when no filters are applied


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.akinon.com/akinon-ui/ui-kit/components/akifilter.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
