Form interface and Loading menu

This commit is contained in:
Hein 2025-10-15 16:24:39 +02:00
parent f084cf70ae
commit 8d26d56599
7 changed files with 148 additions and 77 deletions

View File

@ -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'

View File

@ -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 [];
};

View 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 <></>;
}

View 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>
);
}

View File

@ -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;

View File

@ -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"

View File

@ -15,4 +15,4 @@ export interface APIOptions {
url?: string;
}
export type TRequest = 'change' | 'delete' | 'insert' | 'select' ;
export type FormRequestType = 'change' | 'delete' | 'insert' | 'select' ;