Form interface and Loading menu
This commit is contained in:
parent
f084cf70ae
commit
8d26d56599
@ -5,7 +5,7 @@ import {
|
||||
type DataEditorRef,
|
||||
type GridColumn,
|
||||
} from '@glideapps/glide-data-grid';
|
||||
import { ActionIcon, Stack } from '@mantine/core';
|
||||
import { ActionIcon, Group, Stack } from '@mantine/core';
|
||||
import { useElementSize, useMergedRef } from '@mantine/hooks';
|
||||
import { IconMenu2 } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
@ -19,6 +19,7 @@ import { SortUpSprite } from './components/sprites/SortUp';
|
||||
import { useGridlerStore } from './components/Store';
|
||||
import classes from './Gridler.module.css';
|
||||
import { useGridTheme } from './hooks/use-grid-theme';
|
||||
import { RightMenuIcon } from './components/RightMenuIcon';
|
||||
|
||||
export const GridlerDataGrid = () => {
|
||||
const ref = React.useRef<DataEditorRef | null>(null);
|
||||
@ -195,14 +196,11 @@ export const GridlerDataGrid = () => {
|
||||
rangeSelect="multi-rect"
|
||||
ref={refMerged as any}
|
||||
rightElement={
|
||||
<ActionIcon
|
||||
mr="xs"
|
||||
mt="2px"
|
||||
onClick={(e) => onContextClick('menu', e)}
|
||||
variant="subtle"
|
||||
>
|
||||
<IconMenu2 />
|
||||
</ActionIcon>
|
||||
<Group>
|
||||
{sections?.rightElementStart}
|
||||
<RightMenuIcon />
|
||||
{sections?.rightElementEnd}
|
||||
</Group>
|
||||
}
|
||||
rowHeight={rowHeight ?? 22}
|
||||
//rowMarkersCheckboxStyle='square'
|
||||
|
||||
@ -20,70 +20,80 @@ export const APIAdaptorGoLangv2 = React.memo((props: APIOptions) => {
|
||||
const pageSize = getState('pageSize');
|
||||
const colFilters = getState('colFilters');
|
||||
const _active_requests = getState('_active_requests');
|
||||
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
||||
if (props && props.url) {
|
||||
const head = new Headers();
|
||||
head.set('x-limit', String(pageSize ?? 50));
|
||||
head.set('x-offset', String((pageSize ?? 50) * index));
|
||||
setState('loadingData', true);
|
||||
try {
|
||||
//console.log('APIAdaptorGoLangv2', { _active_requests, index, pageSize, props });
|
||||
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('Authorization', `Token ${props.authtoken}`);
|
||||
head.set('Authorization', `Token ${props.authtoken}`);
|
||||
|
||||
if (colSort?.length && colSort.length > 0) {
|
||||
head.set(
|
||||
'x-sort',
|
||||
colSort
|
||||
?.map((sort: any) => `${sort.id} ${sort.direction}`)
|
||||
.reduce((acc: any, val: any) => `${acc},${val}`)
|
||||
if (colSort?.length && colSort.length > 0) {
|
||||
head.set(
|
||||
'x-sort',
|
||||
colSort
|
||||
?.map((sort: any) => `${sort.id} ${sort.direction}`)
|
||||
.reduce((acc: any, val: any) => `${acc},${val}`)
|
||||
);
|
||||
}
|
||||
|
||||
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)) {
|
||||
r.controller?.abort?.();
|
||||
}
|
||||
});
|
||||
if (_active_requests && currentRequestIndex >= 0 && _active_requests[currentRequestIndex]) {
|
||||
//console.log(`Already queued ${index}`, index, s._active_requests);
|
||||
setState('loadingData', false);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
await setStateFN('_active_requests', (cv) => [...(cv ?? []), { controller, page: index }]);
|
||||
|
||||
const res = await fetch(
|
||||
`${props.url}?x-limit=${String(pageSize ?? 50)}&x-offset=${String((pageSize ?? 50) * index)}`,
|
||||
{
|
||||
headers: head,
|
||||
method: 'GET',
|
||||
signal: controller?.signal,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (res.ok) {
|
||||
const cr = res.headers.get('Content-Range')?.split('/');
|
||||
if (cr?.[1] && parseInt(cr[1], 10) > 0) {
|
||||
setState('total_rows', parseInt(cr[1], 10));
|
||||
}
|
||||
|
||||
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)) {
|
||||
r.controller?.abort?.();
|
||||
const data = await res.json();
|
||||
setState('loadingData', false);
|
||||
return data ?? [];
|
||||
}
|
||||
});
|
||||
if (_active_requests && currentRequestIndex >= 0 && _active_requests[currentRequestIndex]) {
|
||||
//console.log(`Already queued ${index}`, index, s._active_requests);
|
||||
return undefined;
|
||||
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
||||
|
||||
await setStateFN('_active_requests', (cv) => [
|
||||
...(cv ?? []).filter((f) => f.page !== index),
|
||||
]);
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
await setStateFN('_active_requests', (cv) => [...(cv ?? []), { controller, page: index }]);
|
||||
|
||||
const res = await fetch(
|
||||
`${props.url}?x-limit=${String(pageSize ?? 50)}&x-offset=${String((pageSize ?? 50) * index)}`,
|
||||
{
|
||||
headers: head,
|
||||
method: 'GET',
|
||||
signal: controller?.signal,
|
||||
}
|
||||
);
|
||||
|
||||
if (res.ok) {
|
||||
const cr = res.headers.get('Content-Range')?.split('/');
|
||||
if (cr?.[1] && parseInt(cr[1], 10) > 0) {
|
||||
setState('total_rows', parseInt(cr[1], 10));
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
return data ?? [];
|
||||
}
|
||||
addError(`${res.status} ${res.statusText}`, 'api', props.url);
|
||||
|
||||
await setStateFN('_active_requests', (cv) => [...(cv ?? []).filter((f) => f.page !== index)]);
|
||||
} catch (e) {
|
||||
//console.log('APIAdaptorGoLangv2 error', e);
|
||||
addError(`Error: ${e}`, 'api', props.url);
|
||||
}
|
||||
setState('loadingData', false);
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
26
src/Gridler/components/GridlerFormInterface.tsx
Normal file
26
src/Gridler/components/GridlerFormInterface.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import type { FormRequestType } from '../utils/types';
|
||||
|
||||
import { type GridlerProps, useGridlerStore } from './Store';
|
||||
|
||||
export function GlidlerFormInterface(props: {
|
||||
getMenuItems?: GridlerProps['getMenuItems'];
|
||||
onRequestForm: (request: FormRequestType, data: Record<string, any>) => void;
|
||||
}) {
|
||||
// const [getMenuItems, getState, , mounted, setState] = useGridlerStore((s) => [
|
||||
// s.getMenuItems,
|
||||
// s.getState,
|
||||
// s.mounted,
|
||||
// s.setState,
|
||||
// ]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (mounted) {
|
||||
// setState('getMenuItems', props.getMenuItems);
|
||||
// setState('onRequestForm', props.onRequestForm);
|
||||
// }
|
||||
// }, [props.getMenuItems, props.onRequestForm, mounted, setState]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
23
src/Gridler/components/RightMenuIcon.tsx
Normal file
23
src/Gridler/components/RightMenuIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { ActionIcon } from '@mantine/core';
|
||||
import { IconMenu2 } from '@tabler/icons-react';
|
||||
|
||||
import { useGridlerStore } from './Store';
|
||||
|
||||
export function RightMenuIcon() {
|
||||
const { loadingData, onContextClick } = useGridlerStore((s) => ({
|
||||
loadingData: s.loadingData,
|
||||
onContextClick: s.onContextClick,
|
||||
}));
|
||||
|
||||
return (
|
||||
<ActionIcon
|
||||
loading={loadingData}
|
||||
mr="xs"
|
||||
mt="2px"
|
||||
onClick={(e) => onContextClick('menu', e)}
|
||||
variant="subtle"
|
||||
>
|
||||
<IconMenu2 />
|
||||
</ActionIcon>
|
||||
);
|
||||
}
|
||||
@ -23,7 +23,7 @@ import { produce } from 'immer';
|
||||
import { type PropsWithChildren, type ReactNode, useEffect } from 'react';
|
||||
|
||||
import { type MantineBetterMenuInstance, useMantineBetterMenus } from '../../MantineBetterMenu';
|
||||
import { type TRequest } from '../utils/types';
|
||||
import { type FormRequestType } from '../utils/types';
|
||||
import { ColumnFilterSet, type GridlerColumn, type GridlerColumns } from './Column';
|
||||
import { SortDownSprite } from './sprites/SortDown';
|
||||
import { SortUpSprite } from './sprites/SortUp';
|
||||
@ -80,12 +80,14 @@ export interface GridlerProps extends PropsWithChildren {
|
||||
value: any,
|
||||
store: GridlerState
|
||||
) => GridCell;
|
||||
request?: TRequest;
|
||||
|
||||
rowHeight?: number;
|
||||
sections?: {
|
||||
bottom?: React.ReactNode;
|
||||
left?: React.ReactNode;
|
||||
right?: React.ReactNode;
|
||||
rightElementEnd?: React.ReactNode;
|
||||
rightElementStart?: React.ReactNode;
|
||||
top?: React.ReactNode;
|
||||
};
|
||||
selectedRow?: number;
|
||||
@ -115,8 +117,8 @@ export interface GridlerState {
|
||||
colSize?: Record<string, number>;
|
||||
colSort?: Array<SortOption>;
|
||||
data?: Array<any>;
|
||||
|
||||
errors: Array<string>;
|
||||
|
||||
focused?: boolean;
|
||||
get: () => GridlerState;
|
||||
getCellContent: (cell: Item) => GridCell;
|
||||
@ -126,8 +128,9 @@ export interface GridlerState {
|
||||
) => CellArray | GetCellsThunk;
|
||||
getRowBuffer: (row: number) => any;
|
||||
getState: <K extends keyof GridlerStoreState>(key: K) => GridlerStoreState[K];
|
||||
|
||||
hasLocalData: boolean;
|
||||
|
||||
loadingData?: boolean;
|
||||
loadPage: (page: number, clearMode?: 'all' | 'page') => Promise<void>;
|
||||
mounted: boolean;
|
||||
onCellEdited: (cell: Item, newVal: EditableGridCell) => void;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
||||
import { Checkbox, Divider, Group, Stack, TagsInput, TextInput } from '@mantine/core';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
|
||||
@ -15,6 +15,7 @@ export const GridlerGoAPIExampleEventlog = () => {
|
||||
const [apiKey, setApiKey] = useLocalStorage({ defaultValue: '', key: 'apikey' });
|
||||
const [selectRow, setSelectRow] = useState<string | undefined>('');
|
||||
const [values, setValues] = useState<Array<Record<string, any>>>([]);
|
||||
const [sections, setSections] = useState<Record<string, unknown> | undefined>(undefined);
|
||||
const columns: GridlerColumns = [
|
||||
{
|
||||
id: 'id_process',
|
||||
@ -49,6 +50,21 @@ export const GridlerGoAPIExampleEventlog = () => {
|
||||
<TextInput label="API Url" onChange={(e) => setApiUrl(e.target.value)} value={apiUrl} />
|
||||
<TextInput label="API Key" onChange={(e) => setApiKey(e.target.value)} value={apiKey} />
|
||||
<Divider />
|
||||
<Checkbox
|
||||
label="Show Side Sections"
|
||||
checked={!!sections}
|
||||
onChange={(e) => {
|
||||
e.target.checked
|
||||
? setSections({
|
||||
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>bottom</div>,
|
||||
left: <div style={{ backgroundColor: 'orange', width: '20px' }}>L</div>,
|
||||
right: <div style={{ backgroundColor: 'green', width: '20px' }}>R</div>,
|
||||
top: <div style={{ backgroundColor: 'purple', height: '20px' }}>top</div>,
|
||||
})
|
||||
: setSections(undefined);
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
<Gridler
|
||||
height="100%"
|
||||
columns={columns}
|
||||
@ -69,12 +85,7 @@ export const GridlerGoAPIExampleEventlog = () => {
|
||||
//console.log('GridlerGoAPIExampleEventlog onChange', v);
|
||||
setValues(v);
|
||||
}}
|
||||
sections={{
|
||||
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>bottom</div>,
|
||||
left: <div style={{ backgroundColor: 'orange', width: '20px' }}>L</div>,
|
||||
right: <div style={{ backgroundColor: 'green', width: '20px' }}>R</div>,
|
||||
top: <div style={{ backgroundColor: 'purple', height: '20px' }}>top</div>,
|
||||
}}
|
||||
sections={sections}
|
||||
selectedRow={selectRow ? parseInt(selectRow, 10) : undefined}
|
||||
selectMode="row"
|
||||
uniqueid="gridtest"
|
||||
|
||||
@ -15,4 +15,4 @@ export interface APIOptions {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export type TRequest = 'change' | 'delete' | 'insert' | 'select' ;
|
||||
export type FormRequestType = 'change' | 'delete' | 'insert' | 'select' ;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user