Work on grid
This commit is contained in:
parent
46dabed765
commit
ea6b100225
@ -10,7 +10,7 @@ import { Pager } from './components/Pager';
|
|||||||
import { SortSprite } from './components/sprites/Sort';
|
import { SortSprite } from './components/sprites/Sort';
|
||||||
import { SortDownSprite } from './components/sprites/SortDown';
|
import { SortDownSprite } from './components/sprites/SortDown';
|
||||||
import { SortUpSprite } from './components/sprites/SortUp';
|
import { SortUpSprite } from './components/sprites/SortUp';
|
||||||
import { useStore } from './components/Store';
|
import { useGridlerStore } from './components/Store';
|
||||||
import classes from './Gridler.module.css';
|
import classes from './Gridler.module.css';
|
||||||
import { useGridTheme } from './hooks/use-grid-theme';
|
import { useGridTheme } from './hooks/use-grid-theme';
|
||||||
|
|
||||||
@ -38,7 +38,8 @@ export const GridlerDataGrid = () => {
|
|||||||
rowHeight,
|
rowHeight,
|
||||||
setStateFN,
|
setStateFN,
|
||||||
total_rows,
|
total_rows,
|
||||||
} = useStore((s) => ({
|
glideProps,
|
||||||
|
} = useGridlerStore((s) => ({
|
||||||
focused: s.focused,
|
focused: s.focused,
|
||||||
getCellContent: s.getCellContent,
|
getCellContent: s.getCellContent,
|
||||||
getCellsForSelection: s.getCellsForSelection,
|
getCellsForSelection: s.getCellsForSelection,
|
||||||
@ -57,6 +58,7 @@ export const GridlerDataGrid = () => {
|
|||||||
rowHeight: s.rowHeight,
|
rowHeight: s.rowHeight,
|
||||||
setStateFN: s.setStateFN,
|
setStateFN: s.setStateFN,
|
||||||
total_rows: s.total_rows,
|
total_rows: s.total_rows,
|
||||||
|
glideProps: s.glideProps,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const refMerged = useMergedRef(ref, (r) => {
|
const refMerged = useMergedRef(ref, (r) => {
|
||||||
@ -103,6 +105,7 @@ export const GridlerDataGrid = () => {
|
|||||||
columns={(renderColumns as Array<GridColumn>) ?? []}
|
columns={(renderColumns as Array<GridColumn>) ?? []}
|
||||||
columnSelect="none"
|
columnSelect="none"
|
||||||
drawFocusRing
|
drawFocusRing
|
||||||
|
|
||||||
getCellContent={getCellContent}
|
getCellContent={getCellContent}
|
||||||
getCellsForSelection={getCellsForSelection}
|
getCellsForSelection={getCellsForSelection}
|
||||||
getRowThemeOverride={theme.getRowThemeOverride}
|
getRowThemeOverride={theme.getRowThemeOverride}
|
||||||
@ -144,7 +147,16 @@ export const GridlerDataGrid = () => {
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
}
|
}
|
||||||
rowHeight={rowHeight ?? 22}
|
rowHeight={rowHeight ?? 22}
|
||||||
|
//rowMarkersCheckboxStyle='square'
|
||||||
|
//rowMarkersKind='both'
|
||||||
|
rowMarkers={{
|
||||||
|
checkboxStyle: 'square',
|
||||||
|
kind: 'both'
|
||||||
|
}}
|
||||||
|
|
||||||
rows={total_rows}
|
rows={total_rows}
|
||||||
|
|
||||||
|
rowSelect="multi"
|
||||||
// onGridSelectionChange={(sel) => {
|
// onGridSelectionChange={(sel) => {
|
||||||
// console.log("Selection",sel);
|
// console.log("Selection",sel);
|
||||||
// }}
|
// }}
|
||||||
@ -157,10 +169,11 @@ export const GridlerDataGrid = () => {
|
|||||||
// width: 30
|
// width: 30
|
||||||
// }}
|
// }}
|
||||||
|
|
||||||
rowSelect="multi"
|
rowSelectionMode='auto'
|
||||||
spanRangeBehavior="default"
|
spanRangeBehavior="default"
|
||||||
theme={theme.gridTheme}
|
theme={theme.gridTheme}
|
||||||
width="100%"
|
width="100%"
|
||||||
|
{...glideProps}
|
||||||
/>
|
/>
|
||||||
{/* <Portal> */}
|
{/* <Portal> */}
|
||||||
<div id="portal" />
|
<div id="portal" />
|
||||||
|
|||||||
134
src/Gridler/README.md
Normal file
134
src/Gridler/README.md
Normal file
@ -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 (
|
||||||
|
<Gridler
|
||||||
|
uniqueid="my-grid"
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
@ -1,11 +1,12 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import { APIOptions } from '../../utils/types';
|
import type { APIOptions } from '../utils/types';
|
||||||
import { useStore } from './Store';
|
|
||||||
|
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.
|
//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) => {
|
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.setStateFN,
|
||||||
s.setState,
|
s.setState,
|
||||||
s.getState,
|
s.getState,
|
||||||
@ -16,13 +17,14 @@ export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => {
|
|||||||
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
const useAPIQuery: (index: number) => Promise<any> = async (index: number) => {
|
||||||
const colSort = getState('colSort');
|
const colSort = getState('colSort');
|
||||||
const pageSize = getState('pageSize');
|
const pageSize = getState('pageSize');
|
||||||
|
const colFilters = getState('colFilters');
|
||||||
const _active_requests = getState('_active_requests');
|
const _active_requests = getState('_active_requests');
|
||||||
|
|
||||||
if (props && props.url) {
|
if (props && props.url) {
|
||||||
const head = new Headers();
|
const head = new Headers();
|
||||||
head.set('x-limit', String(pageSize ?? 50));
|
head.set('x-limit', String(pageSize ?? 50));
|
||||||
head.set('x-offset', String((pageSize ?? 50) * index));
|
head.set('x-offset', String((pageSize ?? 50) * index));
|
||||||
head.set('x-fieldfilter-tablename', 'scriptcode');
|
|
||||||
head.set('Authorization', `Token ${props.authtoken}`);
|
head.set('Authorization', `Token ${props.authtoken}`);
|
||||||
|
|
||||||
if (colSort?.length && colSort.length > 0) {
|
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;
|
const currentRequestIndex = _active_requests?.findIndex((f) => f.page === index) ?? -1;
|
||||||
_active_requests?.forEach((r) => {
|
_active_requests?.forEach((r) => {
|
||||||
if ((r.page >= 0 && r.page < index - 2) || (index >= 0 && r.page > index + 2)) {
|
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(() => {
|
useEffect(() => {
|
||||||
setState('useAPIQuery', useAPIQuery);
|
setState('useAPIQuery', useAPIQuery);
|
||||||
}, [props.url, props.authtoken, mounted]);
|
}, [props.url, props.authtoken, mounted, setState]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -84,9 +84,10 @@ export const ColumnFilterInput = (props: ColumnFilterSetProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
});
|
});
|
||||||
}, [defferedFilterValue]);
|
}, [defferedFilterValue, props.column.id, props.options, props.storeState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -128,16 +129,18 @@ export const ColumnFilterInputOperator = (props: ColumnFilterSetProps) => {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
filters.push({
|
filters.push({
|
||||||
operator: 'contains',
|
|
||||||
...props.options,
|
...props.options,
|
||||||
id: props.column.id,
|
id: props.column.id,
|
||||||
value: defferedFilterValue,
|
operator: defferedFilterValue,
|
||||||
|
value: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
});
|
});
|
||||||
}, [defferedFilterValue]);
|
}, [defferedFilterValue, props.column.id, props.options, props.storeState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { useStore } from './Store';
|
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.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const Computer = React.memo(() => {
|
export const Computer = React.memo(() => {
|
||||||
const refFirstRun = useRef(0);
|
const refFirstRun = useRef(0);
|
||||||
|
const refLastFilters = useRef<any>(null);
|
||||||
const {
|
const {
|
||||||
_glideref,
|
_glideref,
|
||||||
|
colFilters,
|
||||||
colOrder,
|
colOrder,
|
||||||
colSize,
|
colSize,
|
||||||
colSort,
|
colSort,
|
||||||
@ -15,7 +16,7 @@ export const Computer = React.memo(() => {
|
|||||||
loadPage,
|
loadPage,
|
||||||
setState,
|
setState,
|
||||||
setStateFN,
|
setStateFN,
|
||||||
} = useStore((s) => ({
|
} = useGridlerStore((s) => ({
|
||||||
_glideref: s._glideref,
|
_glideref: s._glideref,
|
||||||
colFilters: s.colFilters,
|
colFilters: s.colFilters,
|
||||||
colOrder: s.colOrder,
|
colOrder: s.colOrder,
|
||||||
@ -59,6 +60,17 @@ export const Computer = React.memo(() => {
|
|||||||
});
|
});
|
||||||
}, [colSort]);
|
}, [colSort]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!colFilters) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON.stringify(refLastFilters.current) !== JSON.stringify(colFilters)) {
|
||||||
|
loadPage(0, 'all');
|
||||||
|
refLastFilters.current = colFilters;
|
||||||
|
}
|
||||||
|
}, [colFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!colSize) {
|
if (!colSize) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
import { range } from '../utils/range';
|
import { range } from '../utils/range';
|
||||||
import { useStore } from './Store';
|
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.
|
//The computer component does not need to be recalculated on every render, so we use React.memo to prevent unnecessary re-renders.
|
||||||
export const Pager = React.memo(() => {
|
export const Pager = React.memo(() => {
|
||||||
@ -15,7 +15,7 @@ export const Pager = React.memo(() => {
|
|||||||
loadPage,
|
loadPage,
|
||||||
_loadingList,
|
_loadingList,
|
||||||
hasLocalData
|
hasLocalData
|
||||||
] = useStore((s) => [
|
] = useGridlerStore((s) => [
|
||||||
s.setState,
|
s.setState,
|
||||||
s._glideref,
|
s._glideref,
|
||||||
s._visiblePages,
|
s._visiblePages,
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import {
|
import {
|
||||||
type CellArray,
|
type CellArray,
|
||||||
CompactSelection,
|
CompactSelection,
|
||||||
|
type DataEditorProps,
|
||||||
type DataEditorRef,
|
type DataEditorRef,
|
||||||
type EditableGridCell,
|
type EditableGridCell,
|
||||||
type GetCellsThunk,
|
type GetCellsThunk,
|
||||||
@ -11,7 +12,7 @@ import {
|
|||||||
type GridColumn,
|
type GridColumn,
|
||||||
type HeaderClickedEventArgs,
|
type HeaderClickedEventArgs,
|
||||||
type Item,
|
type Item,
|
||||||
type Rectangle,
|
type Rectangle
|
||||||
} from '@glideapps/glide-data-grid';
|
} from '@glideapps/glide-data-grid';
|
||||||
import { getUUID } from '@warkypublic/artemis-kit';
|
import { getUUID } from '@warkypublic/artemis-kit';
|
||||||
import { getNestedValue } from '@warkypublic/artemis-kit/object';
|
import { getNestedValue } from '@warkypublic/artemis-kit/object';
|
||||||
@ -57,8 +58,9 @@ export interface GridlerProps extends PropsWithChildren {
|
|||||||
col?: GridlerColumn,
|
col?: GridlerColumn,
|
||||||
defaultItems?: Array<any>
|
defaultItems?: Array<any>
|
||||||
) => Array<any>;
|
) => Array<any>;
|
||||||
headerHeight?: number;
|
glideProps?: Partial<DataEditorProps>
|
||||||
|
|
||||||
|
headerHeight?: number;
|
||||||
hideMenu?: (id: string) => void;
|
hideMenu?: (id: string) => void;
|
||||||
maxConcurrency?: number;
|
maxConcurrency?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
@ -72,6 +74,7 @@ export interface GridlerProps extends PropsWithChildren {
|
|||||||
) => GridCell;
|
) => GridCell;
|
||||||
request?: TRequest;
|
request?: TRequest;
|
||||||
rowHeight?: number;
|
rowHeight?: number;
|
||||||
|
selectedRow?: number;
|
||||||
showMenu?: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
showMenu?: (id: string, options?: Partial<MantineBetterMenuInstance>) => void;
|
||||||
uniqueid: string;
|
uniqueid: string;
|
||||||
}
|
}
|
||||||
@ -91,6 +94,7 @@ export interface GridlerState {
|
|||||||
colSort?: Array<SortOption>;
|
colSort?: Array<SortOption>;
|
||||||
data?: Array<any>;
|
data?: Array<any>;
|
||||||
|
|
||||||
|
|
||||||
errors: Array<string>;
|
errors: Array<string>;
|
||||||
focused?: boolean;
|
focused?: boolean;
|
||||||
get: () => GridlerState;
|
get: () => GridlerState;
|
||||||
@ -148,7 +152,7 @@ export type GridlerStoreState = GridlerProps & GridlerState;
|
|||||||
|
|
||||||
export type SortOption = { direction: 'asc' | 'desc'; id: string; order?: number };
|
export type SortOption = { direction: 'asc' | 'desc'; id: string; order?: number };
|
||||||
|
|
||||||
const { Provider, useStore } = createSyncStore<GridlerStoreState, GridlerProps>(
|
const { Provider, useStore: useGridlerStore } = createSyncStore<GridlerStoreState, GridlerProps>(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
_loadingList: CompactSelection.empty(),
|
_loadingList: CompactSelection.empty(),
|
||||||
_page_data: {},
|
_page_data: {},
|
||||||
@ -303,10 +307,11 @@ const { Provider, useStore } = createSyncStore<GridlerStoreState, GridlerProps>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.setStateFN('colOrder', (cols) => {
|
s.setStateFN('colOrder', (cols) => {
|
||||||
const renderCols = cols ?? s.renderColumns
|
const renderCols =
|
||||||
|
cols ??
|
||||||
|
s.renderColumns
|
||||||
?.map((col, i) => [col.id, i])
|
?.map((col, i) => [col.id, i])
|
||||||
.reduce((acc, [id, i]) => ({ ...acc, [id]: i }), {});;
|
.reduce((acc, [id, i]) => ({ ...acc, [id]: i }), {});
|
||||||
|
|
||||||
|
|
||||||
if (!fromItem?.id || !toItem?.id) {
|
if (!fromItem?.id || !toItem?.id) {
|
||||||
return renderCols;
|
return renderCols;
|
||||||
@ -424,7 +429,8 @@ const { Provider, useStore } = createSyncStore<GridlerStoreState, GridlerProps>(
|
|||||||
if (!coldef) {
|
if (!coldef) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const items = [
|
|
||||||
|
const sortItems = [
|
||||||
{
|
{
|
||||||
label: `Sort ${coldef?.title ?? coldef?.id}`,
|
label: `Sort ${coldef?.title ?? coldef?.id}`,
|
||||||
},
|
},
|
||||||
@ -432,19 +438,46 @@ const { Provider, useStore } = createSyncStore<GridlerStoreState, GridlerProps>(
|
|||||||
label: 'Sort Ascending',
|
label: 'Sort Ascending',
|
||||||
leftSection: <SpriteImage sprite={SortUpSprite} />,
|
leftSection: <SpriteImage sprite={SortUpSprite} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log('Sort Ascending');
|
s.setStateFN('colSort', (c) => {
|
||||||
|
const cols = [...(c ?? [])];
|
||||||
|
const idx = cols.findIndex((search) => search.id === coldef.id);
|
||||||
|
const dir = 'asc';
|
||||||
|
if (idx < 0) {
|
||||||
|
const newSort: SortOption = { direction: dir, id: coldef.id, order: cols?.length };
|
||||||
|
cols.push(newSort);
|
||||||
|
} else if (idx >= 0) {
|
||||||
|
cols[idx].direction = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Sort Descending',
|
label: 'Sort Descending',
|
||||||
leftSection: <SpriteImage sprite={SortDownSprite} />,
|
leftSection: <SpriteImage sprite={SortDownSprite} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log('Sort Descending');
|
s.setStateFN('colSort', (c) => {
|
||||||
|
const cols = [...(c ?? [])];
|
||||||
|
const idx = cols.findIndex((search) => search.id === coldef.id);
|
||||||
|
const dir = 'desc';
|
||||||
|
if (idx < 0) {
|
||||||
|
const newSort: SortOption = { direction: dir, id: coldef.id, order: cols?.length };
|
||||||
|
cols.push(newSort);
|
||||||
|
} else if (idx >= 0) {
|
||||||
|
cols[idx].direction = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isDivider: true,
|
isDivider: true,
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
const items = [
|
||||||
|
...(coldef.disableSort ? []: sortItems),
|
||||||
{
|
{
|
||||||
label: `Filter ${coldef?.title ?? coldef?.id}`,
|
label: `Filter ${coldef?.title ?? coldef?.id}`,
|
||||||
},
|
},
|
||||||
@ -638,6 +671,6 @@ const { Provider, useStore } = createSyncStore<GridlerStoreState, GridlerProps>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type useStoreReturnType = ReturnType<typeof useStore>;
|
export type useStoreReturnType = ReturnType<typeof useGridlerStore>;
|
||||||
|
|
||||||
export { Provider, useStore };
|
export { Provider, useGridlerStore };
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
import type { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
import type { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
||||||
|
|
||||||
export const SortDownSprite: Sprite = (props: Partial<SpriteProps>) => {
|
export const SortDownSprite: Sprite = (props: Partial<SpriteProps>) => {
|
||||||
const fg = props.fgColor ?? 'currentColor';
|
const fg = props.fgColor ?? 'currentColor';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
import type { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
||||||
|
|
||||||
export const SortUpSprite: Sprite = (props: Partial<SpriteProps>) => {
|
export const SortUpSprite: Sprite = (props: Partial<SpriteProps>) => {
|
||||||
const fg = props.fgColor ?? 'currentColor';
|
const fg = props.fgColor ?? 'currentColor';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
import type { Sprite, SpriteProps } from '@glideapps/glide-data-grid';
|
||||||
|
|
||||||
export const SpriteImage = (props: { alt?:string; sprite: Sprite, } & Partial<SpriteProps>) => {
|
export const SpriteImage = (props: { alt?:string; sprite: Sprite, } & Partial<SpriteProps>) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { GetRowThemeCallback } from '@glideapps/glide-data-grid';
|
|||||||
import { darken, lighten, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
import { darken, lighten, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useStore } from '../components/Store';
|
import { useGridlerStore } from '../components/Store';
|
||||||
|
|
||||||
export const offsetRows = (colorScheme: any) => (i: number) =>
|
export const offsetRows = (colorScheme: any) => (i: number) =>
|
||||||
i % 2 === 0
|
i % 2 === 0
|
||||||
@ -15,7 +15,7 @@ export const offsetRows = (colorScheme: any) => (i: number) =>
|
|||||||
export const useGridTheme = () => {
|
export const useGridTheme = () => {
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const { enableOddEvenRowColor, focused } = useStore((state) => ({
|
const { enableOddEvenRowColor, focused } = useGridlerStore((state) => ({
|
||||||
enableOddEvenRowColor: state.enableOddEvenRowColor,
|
enableOddEvenRowColor: state.enableOddEvenRowColor,
|
||||||
focused: state.focused
|
focused: state.focused
|
||||||
}));
|
}));
|
||||||
|
|||||||
3
src/Gridler/index.ts
Normal file
3
src/Gridler/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './components/Column'
|
||||||
|
export {useGridlerStore } from './components/Store'
|
||||||
|
export {Gridler} from './Gridler'
|
||||||
@ -15,37 +15,25 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
|
|
||||||
const columns: GridlerColumns = [
|
const columns: GridlerColumns = [
|
||||||
{
|
{
|
||||||
id: 'rid_atevent',
|
id: 'id_process',
|
||||||
title: 'RID',
|
title: 'RID',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Cell: (_row, _col, _colindex, value) => {
|
id: 'process',
|
||||||
return {
|
title: 'Process',
|
||||||
cursor: 'crosshair',
|
|
||||||
data: value,
|
|
||||||
displayData: `- ${value}`,
|
|
||||||
kind: 'text',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
grow: 1,
|
|
||||||
id: 'changedate',
|
|
||||||
title: 'Date',
|
|
||||||
width: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'changetime',
|
id: 'processtype',
|
||||||
title: 'Time',
|
title: 'Type',
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'changeuser',
|
|
||||||
title: 'User',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: 'actionx',
|
disableSort: true,
|
||||||
title: 'Action',
|
id: 'status',
|
||||||
|
title: 'Status',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -61,18 +49,18 @@ export const GridlerGoAPIExampleEventlog = () => {
|
|||||||
getMenuItems={(id, _state, row, col, defaultItems) => {
|
getMenuItems={(id, _state, row, col, defaultItems) => {
|
||||||
return [
|
return [
|
||||||
...(defaultItems ?? []),
|
...(defaultItems ?? []),
|
||||||
{
|
// {
|
||||||
id: 'test',
|
// id: 'test',
|
||||||
label: `Test ${id}`,
|
// label: `Test -${id}`,
|
||||||
onClick: () => {
|
// onClick: () => {
|
||||||
console.log('Test clicked', row, col);
|
// console.log('Test clicked', row, col);
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
}}
|
}}
|
||||||
uniqueid="gridtest"
|
uniqueid="gridtest"
|
||||||
>
|
>
|
||||||
<APIAdaptorGoLangv2 authtoken={apiKey} url={`${apiUrl}/core/atevent`} />
|
<APIAdaptorGoLangv2 authtoken={apiKey} url={`${apiUrl}/public/process`} />
|
||||||
</Gridler>
|
</Gridler>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
export * from './Gridler'
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type MantineBetterMenuInstance,
|
type MantineBetterMenuInstance,
|
||||||
type MantineBetterMenuInstanceItem,
|
type MantineBetterMenuInstanceItem,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user