import { Dialog, DialogFooter, PrimaryButton, DefaultButton, CommandBar, ShimmeredDetailsList, SelectionMode, DetailsListLayoutMode, ConstrainMode, IColumn, Label, Selection, ICommandBarItemProps, Icon, Stack, Spinner } from "@fluentui/react"; import { PropsWithChildren, ReactElement, useState, useEffect, useRef, useCallback, useMemo } from "react"; import { FluentDialog } from "./FluentDialog"; import React from "react"; import { IFormItem, IFormListRenderProps, FormRef, useStateRef, formListHelper, IFormListColumnInfo, ValidationResult, lang, id, IFormBuilderListMenuItem } from "@wiberg/formbuilder"; import { Searcher } from "../.."; const FluentList = (props: PropsWithChildren>) : ReactElement | null => { const editorFormRef = useRef>(); const [items, setItems] = useState(props.items); const [filteredItems, setFilteredItems] = useState(props.filteredItems); const [showEditor, setShowEditor] = useState(false); const [columns, setColumns, columnRef] = useStateRef>(); const [showValidationOverrideConfirm, setShowValidationOverrideConfirm] = useState(false); const [enableShimmer, setEnableShimmer] = useState(false); const [newItemMode, setNewItemMode] = useState(false); const [menuItems, setMenuItems] = useState | undefined>(undefined); const [validationFailed, setValidationFailed] = useState(false); const [formLoading, setFormLoading] = useState(false); const [selection] = useState(new Selection({ selectionMode: props.listProps.config.multiSelect ? SelectionMode.multiple : SelectionMode.single, getKey: (item: any, index?: number) => props.listProps.config.itemIdentifier(item), onSelectionChanged: () => { props.updateSelectedItems(selection.getSelection() as Array ?? []); } })); useEffect(() => { let cols = buildColumns(); setColumns(cols); }, [props.columns]) useEffect(() => { setEnableShimmer(props.shimmer); }, [props.shimmer]) useEffect(() => { if (props.items?.length) setEnableShimmer(false); setItems(props.items); }, [props.items]) useEffect(() => { setFilteredItems(props.filteredItems); }, [props.filteredItems]) const buildMenuItem = useCallback( (item: IFormBuilderListMenuItem) : ICommandBarItemProps => { return { key: item.title, text: item.title, onClick: () => item.action(props.selectedItems), disabled: props.readOnly || formListHelper.isMenuItemDisabled(item.selectionMode, props.selectedItems?.length), iconProps: { iconName: item.iconName }, subMenuProps: item.subMenuItems == null ? undefined : { items: item.subMenuItems.map(buildMenuItem) } } as ICommandBarItemProps }, [items, props.selectedItems, props.readOnly] ) useEffect(() => { if (props.listProps.menuConfig?.actions == null) return; let actions = props.listProps.menuConfig?.actions(createItem, editItem, deleteItems); if (!actions?.length) return; // is it needed to set to empty array as well? (ewi) // parse to commandbarProps let commandBarItems = actions.map(buildMenuItem); setMenuItems(commandBarItems); }, [props.listProps.menuConfig, props.selectedItems]) const buildColumns = useCallback(() : Array => { return props.columns.map((_: IColumn) => { let col = _ as IColumn; col.onColumnClick = () => props.sortColumn(_ as IFormListColumnInfo); const resizable = props.listProps.columnConfig?.resizableColumns; if (resizable === true || (resizable?.findIndex(_ => _ == col.fieldName) ?? -1) >= 0) col.isResizable = true; const widthConfig = props.listProps.columnConfig?.columnWidths; if (widthConfig != null) { // do not override width from item schema if (typeof widthConfig === 'number' && col.minWidth == null) { col.minWidth = widthConfig; } else if (Array.isArray(widthConfig)) { const config = widthConfig?.find(_ => _.column == col.fieldName); if (config?.width != null) col.minWidth = config.width; } } return col; }); }, [props.columns, props.sortColumn, props.listProps.columnConfig?.resizableColumns]) // TODO: move crud operations out to FormList (ewi) // with alot of other stuff as well const createItem = useCallback(async (pre?: (item: T) => boolean | void | Promise) => { let item = {} as T; if (pre) { let result = await pre(item); if (result === false) return false; } setShowEditor(true); setNewItemMode(true); selection.setAllSelected(false); props.updateSelectedItems([item]); }, [props.updateSelectedItems, setNewItemMode, setShowEditor]) const editItem = useCallback(async (pre?: (item: T) => boolean | void | Promise) => { let sItem = {...props.selectedItems[0]}; if (pre) { let result = await pre(sItem); if (result === false) return false; props.updateSelectedItems([sItem]); } setShowEditor(true); setNewItemMode(false); }, [props.updateSelectedItems, setNewItemMode, setShowEditor, props.selectedItems]) const deleteItems = useCallback(async (pre?: (items: Array) => boolean | void | Promise) => { const success = await props.deleteItems(pre); if (!success) return; selection.setAllSelected(false); }, [props.deleteItems]) const validateForm = useCallback(async () : Promise => { let validated = await editorFormRef.current?.validateItem(); return validated; }, [editorFormRef.current?.validateItem]) const saveForm = useCallback(async () : Promise => { setFormLoading(true); let validated = await validateForm(); if (validated == ValidationResult.FailedDontBlock) { setShowValidationOverrideConfirm(true); } else if (validated == ValidationResult.Failed) { setValidationFailed(true); } else if (validated == ValidationResult.Success) { if (props.listProps?.editorConfig?.dismissImmediately) { new Promise((resolve, reject) => { props.onItemChange(editorFormRef.current?.getItem() as T); resolve(); }); } else await props.onItemChange(editorFormRef.current?.getItem() as T); setValidationFailed(false); setShowEditor(false); } setFormLoading(false); }, [validateForm, setShowValidationOverrideConfirm, setValidationFailed, setFormLoading, props.listProps?.editorConfig?.dismissImmediately, props.onItemChange, editorFormRef.current, setShowEditor]) const dismissForm = useCallback(async () : Promise => { if (newItemMode) props.updateSelectedItems([]); setNewItemMode(false); setShowEditor(false) }, [setNewItemMode, setShowEditor, props.updateSelectedItems]) const getItem = useCallback(() : T | any => { return editorFormRef.current?.getItem() as T | any; }, [editorFormRef.current]) const onRenderFooterContent = useCallback( () => { if (props.listProps?.editorConfig?.customFooter != undefined) return props.listProps.editorConfig.customFooter(saveForm, dismissForm, validateForm, getItem); else return ( { formLoading ? : } { validationFailed &&
}
) }, [validationFailed, formLoading, props.listProps?.editorConfig?.customFooter, saveForm, dismissForm, validateForm, getItem] ); const handleColumnReorder = useCallback((draggedIndex: number, targetIndex: number) => { if (columns == null) return; const draggedItems = columns[draggedIndex]; const newColumns: IColumn[] = [...columns]; // insert before the dropped item newColumns.splice(draggedIndex, 1); newColumns.splice(targetIndex, 0, draggedItems); setColumns(newColumns); }, [columns, setColumns]) return <> { (showEditor && props.selectedItems?.length) && { setValidationFailed(false); setShowEditor(show); } } ref={ editorFormRef } /> }
{ props.listProps.searchConfig?.searchEnabled && , searchText: string) => { props.updateFilteredItems(filteredItems); selection.setAllSelected(false); props.updateSelectedItems([]); }} className="formbuilder-searcher" delay={600} searchPlaceHolder={props.listProps.searchConfig.searchPlaceHolder} enableTextHighlight={true} searchFields={ props.listProps.searchConfig.searchableFields } highLightElements={() => Array.from(document.querySelectorAll('.ms-List .ms-DetailsRow-cell:not(.ms-DetailsRow-cellCheck)')) .filter(_ => { if (props.listProps.searchConfig?.searchableFields == null) return []; return props.listProps.searchConfig.searchableFields.findIndex((sf: string) => sf == (_.attributes as any)["data-automation-key"].value) >= 0 }) } filterOnItemsChange /> } { menuItems !== undefined && }
{/* */} `item-${props.listProps.config.itemIdentifier(item) ?? id.make()}`} onItemInvoked={ props.listProps.config.disableItemInvoke ? undefined : (item?: T, index?: number | undefined, ev?: Event | undefined) => { if (item == null) { props.updateSelectedItems([]); } else { props.updateSelectedItems([item]); } editItem(); } } columnReorderOptions={ columns?.length ? { handleColumnReorder } : undefined } // only enable if columns are specified and not auto generated by Fluent useReducedRowRenderer /> {/* */}
{ (!enableShimmer && !!!items?.length) && (props.listProps?.shimmerConfig?.noItemsElement ?? null) } } export default FluentList;