Skip to content

Commit

Permalink
Merge branch 'update/vendor-dashboard-structure' into update/vendor-d…
Browse files Browse the repository at this point in the history
…ashboard-structure-convert-withdraw

# Conflicts:
#	src/components/index.tsx
  • Loading branch information
Aunshon committed Jan 24, 2025
2 parents 8dc05a2 + 95f4b14 commit ffbfedc
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 1 deletion.
208 changes: 208 additions & 0 deletions docs/frontend/filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Filter Component

The Filter component provides a standardized way to implement filtering functionality across your application. It creates a consistent user experience by managing filter fields, filter triggers, and reset functionality.

## Features

- Unified interface for multiple filter fields
- Configurable filter and reset buttons
- Namespace support for unique identification
- Flexible field composition

## Installation

#### Dokan lite

```jsx
import Filter from '../components';
```

#### Dokan pro or external plugins

```jsx
import { Filter } from '@dokan/components';
```

## Usage

```jsx
<Filter
fields={[
<CustomerFilter key="customer_filter" {...props} />,
<DateFilter key="date_filter" {...props} />
]}
onFilter={handleFilter}
onReset={clearFilter}
showFilter={true}
showReset={true}
namespace="vendor_subscription"
/>
```

## Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `namespace` | `string` | Yes | Unique identifier for the filter group |
| `fields` | `ReactNode[]` | No | Array of filter field components to be rendered |
| `onFilter` | `() => void` | No | Handler function called when the filter button is clicked |
| `onReset` | `() => void` | No | Handler function called when the reset button is clicked |
| `showFilter` | `boolean` | No | Controls visibility of the filter button (default: true) |
| `showReset` | `boolean` | No | Controls visibility of the reset button (default: true) |

## Example Implementation

```jsx
import { Filter, CustomerFilter, DateFilter } from '@your-package/components';

const MyTableComponent = () => {
const [filterArgs, setFilterArgs] = useState({});
const [searchedCustomer, setSearchedCustomer] = useState(null);

const handleFilter = () => {
// Process request payload through applyFilters
const requestPayload = applyFilters('dokan_subscription_filter_request_param', {
...filterArgs,
per_page: perPage,
page: currentPage,
} );

// Make server request with processed payload
fetchFilteredData(requestPayload);
};

const clearFilter = () => {
setFilterArgs({});
setSearchedCustomer(null);
};

const handleCustomerSearch = (customer) => {
setSearchedCustomer(customer);
};

return (
<div>
<Filter
fields={[
<CustomerFilter
key="customer_filter"
filterArgs={filterArgs}
setFilterArgs={setFilterArgs}
searchedCustomer={searchedCustomer}
handleCustomerSearch={handleCustomerSearch}
/>,
<DateFilter
key="date_filter"
filterArgs={filterArgs}
setFilterArgs={setFilterArgs}
/>
]}
onFilter={handleFilter}
onReset={clearFilter}
showFilter={true}
showReset={true}
namespace="my_table_filters"
/>
{/* Table component */}
</div>
);
};
```

## Creating Custom Filter Fields

When creating custom filter field components to use with the Filter component:

1. Each field component should manage its own state
2. Field components should update the parent's filterArgs through props
3. Include a unique key prop for each field
4. Handle reset functionality through props

Example custom filter field:

```jsx
const CustomFilter = ({ filterArgs, setFilterArgs }) => {
const handleChange = (value) => {
setFilterArgs({
...filterArgs,
customField: value
});
};

return (
<input
type="text"
value={filterArgs.customField || ''}
onChange={(e) => handleChange(e.target.value)}
/>
);
};
```

## Important Implementation Notes

### Server Request Handling
Before making requests to the server in your `onFilter` handler, you must process the request payload through an `applyFilters` function. This is a critical step for maintaining consistent filter behavior across the application.

#### Filter Hook Naming Convention
The filter hook name should follow this pattern:
- Start with "dokan"
- Follow with the feature or task name
- End with "_filter_request_param"
- Must be in snake_case format

You can use the `snakeCase` utility to ensure proper formatting:

For Dokan Lite:
```jsx
import { snakeCase } from "@/utilities";
```

For Dokan Pro or other plugins:
```jsx
import { snakeCase } from "@dokan/utilities";
```

Example usage:
```jsx
const handleFilter = () => {
// Convert hook name to snake_case
const hookName = snakeCase('dokan_subscription_filter_request_param');

// Apply filters to your request payload before sending to server
const requestPayload = applyFilters(hookName, {
...filterArgs,
per_page: perPage,
page: currentPage,
} );

// Now make your server request with the processed payload
fetchData(requestPayload);
};
```

Parameters for `applyFilters`:
- First parameter: Hook name (string, must follow naming convention)
- Second parameter: Request payload (object)
- Third parameter: Namespace from Filter component props (string)

This step ensures:
- Consistent filter processing across the application
- Proper parameter formatting
- Extensibility for future filter modifications
- Standardized hook naming across the application

## Best Practices

1. Always provide unique keys for field components
2. Implement proper type checking for filter arguments
3. Handle edge cases in reset functionality
4. Use consistent naming conventions for filter arguments
5. Include error handling in filter and reset handlers

## Notes

- The Filter component is designed to work with table components but can be used in other contexts
- All filter fields should be controlled components
- The namespace prop is used internally for generating unique identifiers
- Filter and reset functionality should be implemented in the parent component
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"vue-wp-list-table": "^1.3.0",
"vue2-daterange-picker": "^0.6.8",
"vuedraggable": "^2.24.3",
"wp-readme-to-markdown": "^1.0.1"
"wp-readme-to-markdown": "^1.0.1",
"tailwind-merge": "^2.6.0"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
Expand Down
78 changes: 78 additions & 0 deletions src/components/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Button } from '@getdokan/dokan-ui';
import { __ } from '@wordpress/i18n';
import { twMerge } from 'tailwind-merge';
// @ts-ignore
// eslint-disable-next-line import/no-unresolved
import { snakeCase, kebabCase } from '../utilities';

interface FilterProps {
/** Namespace for the filter, used to generate unique IDs */
namespace: string;
/** Array of React nodes representing the filter fields */
fields?: React.ReactNode[];
/** Whether to show the reset button */
showReset?: boolean;
/** Whether to show the filter button */
showFilter?: boolean;
/** Callback function to handle filter action */
onFilter?: () => void;
/** Callback function to handle reset action */
onReset?: () => void;
/** Additional class names for the filter container */
className?: string;
}

const Filter = ( {
namespace = '',
fields = [],
showReset = true,
showFilter = true,
onFilter = () => {},
onReset = () => {},
className = '',
}: FilterProps ) => {
const snakeCaseNamespace = snakeCase( namespace );
const filterId = `dokan_${ snakeCaseNamespace }_filters`;

// @ts-ignore
const filteredFields = wp.hooks.applyFilters( filterId, fields );

return (
<div
className={ twMerge(
'flex gap-4 flex-row flex-wrap pb-5 items-end dokan-dashboard-filters',
className
) }
id={ kebabCase( filterId ) }
data-filter-id={ filterId }
>
{ filteredFields.map( ( fieldNode: React.ReactNode, index ) => {
return (
<div className="dokan-dashboard-filter-item" key={ index }>
{ fieldNode }
</div>
);
} ) }

{ showFilter && (
<Button
color="primary"
className="bg-dokan-btn hover:dokan-btn-hover h-10"
label={ __( 'Filter', 'dokan' ) }
onClick={ onFilter }
/>
) }

{ showReset && (
<Button
color="primary"
className="bg-dokan-btn hover:dokan-btn-hover h-10"
label={ __( 'Reset', 'dokan' ) }
onClick={ onReset }
/>
) }
</div>
);
};

export default Filter;
1 change: 1 addition & 0 deletions src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export {
} from '@wordpress/dataviews/wp';
export { default as PriceHtml } from './PriceHtml';
export { default as DateTimeHtml } from './DateTimeHtml';
export { default as Filter } from './Filter';

0 comments on commit ffbfedc

Please sign in to comment.