All files / blong-browser/src/widgets SelectTableWidget.tsx

41.26% Statements 52/126
50% Branches 1/2
0% Functions 0/2
41.26% Lines 52/126

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 1271x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x             1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                                   1x 1x 1x 1x 1x 1x 1x 1x 1x        
import {Column, DataTable, type DataTableSelectionChangeParams} from '../primereact/index.js';
 
import type {IDropdownOption, IWidgetProps} from '@feasibleone/blong';
import {useEffect, useState} from 'react';
import {useBlong} from '../context/BlongContext.js';
import {dropdownRegistry} from '../model/dropdownRegistry.js';
 
type Row = IDropdownOption & Record<string, unknown>;
 
function toOptions(data: unknown): Row[] {
    if (!Array.isArray(data)) return [];
    return data.map((item: Record<string, unknown>) => ({
        ...item,
        label: String(item.label ?? item.name ?? item.value ?? item),
        value: item.value ?? item,
    }));
}
 
/**
 * SelectTableWidget — DataTable with row selection.
 * Source rows come from the dropdown registry (same as `dropdown`).
 * `value` stores the selected row's `value` key (single mode) or an array of keys (multi mode).
 */
export function SelectTableWidget({
    id,
    name: _name,
    schema,
    value,
    onChange,
    error,
    readOnly,
    disabled,
}: IWidgetProps) {
    const {
        fetch: fetchAction,
        options: staticOptions,
        dropdown: dropdownKey,
        selectionMode = 'single',
        columns,
    } = schema.widget ?? {};
    const {handler} = useBlong();
    const [rows, setRows] = useState<Row[]>(() => staticOptions ? toOptions(staticOptions) : []);
 
    useEffect(() => {
        if (staticOptions) return;
        let cancelled = false;
 
        if (dropdownKey) {
            const loader = (key: string) =>
                (
                    handler.portalDropdownList({names: [key]}, {}) as Promise<
                        Record<string, unknown>
                    >
                ).then(result => toOptions(result[key]));
            dropdownRegistry
                .get(dropdownKey, loader)
                .then(data => {
                    if (!cancelled) setRows(data as Row[]);
                })
                .catch(() => {});
            return () => {
                cancelled = true;
            };
        }

        if (!fetchAction) return;
        (handler[fetchAction]({}, {}) as Promise<unknown>)
            .then(data => {
                if (!cancelled) setRows(toOptions(data));
            })
            .catch(() => {});
        return () => {
            cancelled = true;
        };
    }, [fetchAction, dropdownKey, handler, staticOptions]);

    const isSingle = selectionMode === 'single';

    // Map stored value keys back to row objects for the DataTable
    const selectedRows = isSingle
        ? (rows.find(r => r.value === value) ?? null)
        : rows.filter(r => (value as unknown[])?.includes(r.value));

    // Derive columns from schema.widget.columns or fall back to label
    const cols: string[] = Array.isArray(columns)
        ? (columns as string[])
        : ((columns ? Object.keys(columns as object) : null) ??
          (schema.items?.properties ? Object.keys(schema.items.properties) : ['label']));

    const colHeaders: Record<string, string> = schema.items?.properties
        ? Object.fromEntries(
              Object.entries(schema.items.properties as Record<string, {title?: string}>).map(
                  ([k, v]) => [k, v.title ?? k],
              ),
          )
        : {};

    return (
        <DataTable
            value={rows}
            dataKey="value"
            data-testid={id ?? _name}
            size="small"
            selectionMode={isSingle ? 'single' : 'multiple'}
            selection={selectedRows}
            onSelectionChange={(e: DataTableSelectionChangeParams) => {
                if (disabled || readOnly) return;
                if (isSingle) {
                    onChange((e.value as Row | null)?.value ?? null);
                } else {
                    onChange((e.value as Row[]).map(r => r.value));
                }
            }}
            metaKeySelection={false}
            className={`blong-select-table w-full ${error ? 'p-invalid' : ''}`}
        >
            {cols.map(field => (
                <Column
                    key={field}
                    field={field}
                    header={colHeaders[field] ?? field}
                />
            ))}
        </DataTable>
    );
}