From ea6b1002254326c10e5ed29808b289f8a21a37b3 Mon Sep 17 00:00:00 2001 From: Hein Date: Mon, 22 Sep 2025 14:58:25 +0200 Subject: [PATCH] Work on grid --- src/Gridler/GridlerDataGrid.tsx | 19 ++- src/Gridler/README.md | 134 ++++++++++++++++++ src/Gridler/components/APIAdaptorGoLangv2.tsx | 20 ++- src/Gridler/components/Column.tsx | 11 +- src/Gridler/components/Computer.tsx | 18 ++- src/Gridler/components/Pager.tsx | 4 +- src/Gridler/components/Store.tsx | 57 ++++++-- src/Gridler/components/sprites/Sort.ts | 2 +- src/Gridler/components/sprites/SortDown.tsx | 2 +- src/Gridler/components/sprites/SortUp.tsx | 2 +- .../components/sprites/SpriteImage.tsx | 2 +- src/Gridler/hooks/use-grid-theme.ts | 4 +- src/Gridler/index.ts | 3 + src/Gridler/stories/Examples.goapi.tsx | 46 +++--- src/lib.ts | 5 +- 15 files changed, 264 insertions(+), 65 deletions(-) create mode 100644 src/Gridler/README.md create mode 100644 src/Gridler/index.ts diff --git a/src/Gridler/GridlerDataGrid.tsx b/src/Gridler/GridlerDataGrid.tsx index 72a8c1e..68236e9 100644 --- a/src/Gridler/GridlerDataGrid.tsx +++ b/src/Gridler/GridlerDataGrid.tsx @@ -10,7 +10,7 @@ import { Pager } from './components/Pager'; import { SortSprite } from './components/sprites/Sort'; import { SortDownSprite } from './components/sprites/SortDown'; import { SortUpSprite } from './components/sprites/SortUp'; -import { useStore } from './components/Store'; +import { useGridlerStore } from './components/Store'; import classes from './Gridler.module.css'; import { useGridTheme } from './hooks/use-grid-theme'; @@ -38,7 +38,8 @@ export const GridlerDataGrid = () => { rowHeight, setStateFN, total_rows, - } = useStore((s) => ({ + glideProps, + } = useGridlerStore((s) => ({ focused: s.focused, getCellContent: s.getCellContent, getCellsForSelection: s.getCellsForSelection, @@ -57,6 +58,7 @@ export const GridlerDataGrid = () => { rowHeight: s.rowHeight, setStateFN: s.setStateFN, total_rows: s.total_rows, + glideProps: s.glideProps, })); const refMerged = useMergedRef(ref, (r) => { @@ -103,6 +105,7 @@ export const GridlerDataGrid = () => { columns={(renderColumns as Array) ?? []} columnSelect="none" drawFocusRing + getCellContent={getCellContent} getCellsForSelection={getCellsForSelection} getRowThemeOverride={theme.getRowThemeOverride} @@ -144,7 +147,16 @@ export const GridlerDataGrid = () => { } rowHeight={rowHeight ?? 22} + //rowMarkersCheckboxStyle='square' + //rowMarkersKind='both' + rowMarkers={{ + checkboxStyle: 'square', + kind: 'both' + }} + rows={total_rows} + + rowSelect="multi" // onGridSelectionChange={(sel) => { // console.log("Selection",sel); // }} @@ -157,10 +169,11 @@ export const GridlerDataGrid = () => { // width: 30 // }} - rowSelect="multi" + rowSelectionMode='auto' spanRangeBehavior="default" theme={theme.gridTheme} width="100%" + {...glideProps} /> {/* */}
diff --git a/src/Gridler/README.md b/src/Gridler/README.md new file mode 100644 index 0000000..6e19c9a --- /dev/null +++ b/src/Gridler/README.md @@ -0,0 +1,134 @@ +# Gridler + +A powerful React data grid component built on top of @glideapps/glide-data-grid with enhanced features for data manipulation and visualization. + +## Features + +### ✅ Completed Features + +#### Core Grid Functionality +- **Data Display**: Support for both local data and remote API data sources +- **Column Management**: Dynamic column configuration with customizable properties +- **Cell Rendering**: Custom cell renderers with flexible display options +- **Row Selection**: Multi-row selection capabilities +- **Context Menus**: Right-click context menus for cells, headers, and general grid areas + +#### Sorting & Filtering +- **Local File Sorting**: Built-in sorting for local data sources +- **Column Sorting**: Click-to-sort headers with ascending/descending indicators +- **Advanced Filtering**: Column-based filtering with multiple operators: + - Contains, Equal, Not Equal + - Greater Than, Greater Than or Equal + - Less Than, Less Than or Equal + - Between, Starts With, Ends With +- **Filter UI**: Dedicated filter input components with debounced search +- **Searching**: Built-in search functionality across data + +#### Column Features +- **Show/Hide Columns**: Dynamic column visibility control +- **Column Resizing**: Adjustable column widths with min/max constraints +- **Column Reordering**: Drag-and-drop column repositioning +- **Column Persistence**: Automatic saving of column order and sizes +- **Filtering Interface/Menu**: Comprehensive filtering controls per column + +#### Data Management +- **Pagination**: Built-in pager component for large datasets +- **Progressive Scrolling**: Efficient rendering for large data sets +- **State Persistence**: Column preferences saved across sessions +- **Error Handling**: Comprehensive error management and display + +#### UI/UX Features +- **Theme Support**: Customizable grid themes with dark/light mode support +- **Responsive Design**: Adaptive layout for different screen sizes +- **Loading States**: Visual feedback during data loading +- **Keyboard Navigation**: Full keyboard accessibility +- **Sprites/Icons**: Custom sort indicators and UI elements + +#### Integration Features +- **API Adaptor**: Go Lang v2 REST API integration +- **Zustand Store**: Centralized state management +- **Mantine Integration**: Seamless integration with Mantine UI components +- **TypeScript Support**: Full TypeScript definitions and type safety + +### 🚧 Planned Features + +#### Enhanced Filtering +- **Date Range Filters**: Specialized date/time filtering controls +- **Multi-Select Filters**: Dropdown filters with multiple selection options +- **Custom Filter Types**: Support for custom filter implementations +- **Filter Presets**: Save and load common filter combinations +- **Advanced Search**: Global search with highlighting and regex support + +#### Export/Import +- **CSV Export**: Export filtered/sorted data to CSV format +- **Excel Export**: Export to Excel with formatting preservation +- **JSON Export**: Export data in JSON format +- **Print Support**: Print-friendly grid layouts +- **Data Import**: Import data from various file formats + +#### Advanced Grid Features +- **Cell Editing**: In-place cell editing with validation +- **Bulk Operations**: Multi-row operations (delete, update, etc.) +- **Grouping**: Row grouping with expand/collapse functionality +- **Aggregation**: Column aggregation (sum, average, count, etc.) +- **Virtual Scrolling**: Enhanced performance for very large datasets +- **Frozen Columns**: Pin important columns to left/right +- **Row Grouping**: Hierarchical data display + +#### UI Enhancements +- **Column Templates**: Pre-defined column configurations +- **Grid Templates**: Save and load entire grid configurations +- **Toolbar Customization**: Configurable toolbar with custom actions +- **Status Bar**: Display grid statistics and information +- **Full Screen Mode**: Maximize grid to full viewport +- **Responsive Breakpoints**: Better mobile/tablet support + +#### Performance & Optimization +- **Data Virtualization**: Improved virtual scrolling implementation +- **Lazy Loading**: On-demand data loading for large datasets +- **Caching Strategy**: Intelligent data caching for better performance +- **Memory Optimization**: Reduced memory footprint for large grids + +#### Developer Experience +- **Plugin System**: Extensible plugin architecture +- **Custom Hooks**: Reusable hooks for common grid operations +- **Documentation**: Comprehensive API documentation and examples +- **Testing Suite**: Complete test coverage for all features +- **Storybook Stories**: Interactive component examples + +## Usage + +```tsx +import { Gridler } from './Gridler'; + +const MyGrid = () => { + const columns = [ + { id: 'name', title: 'Name', width: 200 }, + { id: 'email', title: 'Email', width: 250 }, + { id: 'role', title: 'Role', width: 150 } + ]; + + const data = [ + { name: 'John Doe', email: 'john@example.com', role: 'Admin' }, + { name: 'Jane Smith', email: 'jane@example.com', role: 'User' } + ]; + + return ( + + ); +}; +``` + +## Architecture + +- **Gridler.tsx**: Main component wrapper with provider setup +- **GridlerDataGrid.tsx**: Core data grid implementation +- **Store.tsx**: Zustand-based state management +- **Column.tsx**: Column definitions and filtering components +- **components/**: Reusable grid components (Pager, Computer, etc.) +- **hooks/**: Custom React hooks for grid functionality +- **utils/**: Utility functions and type definitions \ No newline at end of file diff --git a/src/Gridler/components/APIAdaptorGoLangv2.tsx b/src/Gridler/components/APIAdaptorGoLangv2.tsx index 6b5c5e5..6bafc77 100644 --- a/src/Gridler/components/APIAdaptorGoLangv2.tsx +++ b/src/Gridler/components/APIAdaptorGoLangv2.tsx @@ -1,11 +1,12 @@ import React, { useEffect } from 'react'; -import { APIOptions } from '../../utils/types'; -import { useStore } from './Store'; +import type { APIOptions } from '../utils/types'; + +import { useGridlerStore } from './Store'; //The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders. export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => { - const [setStateFN, setState, getState, addError, mounted] = useStore((s) => [ + const [setStateFN, setState, getState, addError, mounted] = useGridlerStore((s) => [ s.setStateFN, s.setState, s.getState, @@ -16,13 +17,14 @@ export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => { const useAPIQuery: (index: number) => Promise = async (index: number) => { const colSort = getState('colSort'); const pageSize = getState('pageSize'); + const colFilters = getState('colFilters'); const _active_requests = getState('_active_requests'); if (props && props.url) { const head = new Headers(); head.set('x-limit', String(pageSize ?? 50)); head.set('x-offset', String((pageSize ?? 50) * index)); - head.set('x-fieldfilter-tablename', 'scriptcode'); + head.set('Authorization', `Token ${props.authtoken}`); if (colSort?.length && colSort.length > 0) { @@ -34,6 +36,14 @@ export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => { ); } + if (colFilters?.length && colFilters.length > 0) { + colFilters?.filter((f)=>f.value?.length > 0)?.forEach((filter: any) => { + if (filter.value && filter.value !== '') { + head.set(`x-searchop-${filter.operator}-${filter.id}`, `${filter.value}`); + } + }); + } + const currentRequestIndex = _active_requests?.findIndex((f) => f.page === index) ?? -1; _active_requests?.forEach((r) => { if ((r.page >= 0 && r.page < index - 2) || (index >= 0 && r.page > index + 2)) { @@ -80,7 +90,7 @@ export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => { useEffect(() => { setState('useAPIQuery', useAPIQuery); - }, [props.url, props.authtoken, mounted]); + }, [props.url, props.authtoken, mounted, setState]); return <>; }); diff --git a/src/Gridler/components/Column.tsx b/src/Gridler/components/Column.tsx index 998fcd4..0c05bbe 100644 --- a/src/Gridler/components/Column.tsx +++ b/src/Gridler/components/Column.tsx @@ -84,9 +84,10 @@ export const ColumnFilterInput = (props: ColumnFilterSetProps) => { }); } + return filters; }); - }, [defferedFilterValue]); + }, [defferedFilterValue, props.column.id, props.options, props.storeState]); return ( { }; } else { filters.push({ - operator: 'contains', ...props.options, id: props.column.id, - value: defferedFilterValue, + operator: defferedFilterValue, + value: '', }); } + + return filters; }); - }, [defferedFilterValue]); + }, [defferedFilterValue, props.column.id, props.options, props.storeState]); return (