From 65aeca57306ddcf300f4d6b944e954f767122109 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Fri, 16 Aug 2024 16:42:49 +0500 Subject: [PATCH 01/10] allow table summary row --- .../lowcoder/src/comps/comps/avatarGroup.tsx | 27 +- .../tableComp/column/tableColumnListComp.tsx | 138 ++++++ .../column/tableSummaryColumnComp.tsx | 432 ++++++++++++++++++ .../src/comps/comps/tableComp/tableComp.tsx | 15 + .../comps/comps/tableComp/tableCompView.tsx | 48 +- .../comps/tableComp/tablePropertyView.tsx | 9 + .../comps/tableComp/tableSummaryComp.tsx | 391 ++++++++++++++++ .../src/comps/comps/tableComp/tableTypes.tsx | 2 + 8 files changed, 1060 insertions(+), 2 deletions(-) create mode 100644 client/packages/lowcoder/src/comps/comps/tableComp/column/tableSummaryColumnComp.tsx create mode 100644 client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx diff --git a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx index cba007ee1..ef6b55fee 100644 --- a/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx +++ b/client/packages/lowcoder/src/comps/comps/avatarGroup.tsx @@ -12,7 +12,7 @@ import { clickEvent, eventHandlerControl, refreshEvent } from "../controls/event import styled from "styled-components"; import { useContext, ReactElement, useEffect } from "react"; import { MultiCompBuilder, stateComp, withDefault } from "../generators"; -import { EditorContext } from "comps/editorState"; +import { CompNameContext, EditorContext } from "comps/editorState"; import { IconControl } from "../controls/iconControl"; import { ColorControl } from "../controls/colorControl"; import { optionsControl } from "../controls/optionsControl"; @@ -105,6 +105,30 @@ const childrenMap = { }; const AvatarGroupView = (props: RecordConstructorToView & { dispatch: (action: CompAction) => void; }) => { + const comp = useContext(EditorContext).getUICompByName(useContext(CompNameContext)); + + const updateAvatars = () => { + debugger; + if (!comp) return; + // comp?.children.comp.children?.avatars?.children.manual.dispatchChangeValueAction({ + // initOptions: [ + // { src: "https://api.dicebear.com/7.x/miniavs/svg?seed=1", label: String.fromCharCode(65 + Math.ceil(Math.random() * 25)) }, + // // { AvatarIcon: "/icon:antd/startwotone" }, + // // { label: String.fromCharCode(65 + Math.ceil(Math.random() * 25)) }, + // // { label: String.fromCharCode(65 + Math.ceil(Math.random() * 25)) }, + // ], + // }); + // comp?.children.comp.children?.avatars.children.manual.children.manual.dispatch([ + // { src: "https://api.dicebear.com/7.x/miniavs/svg?seed=1", label: String.fromCharCode(65 + Math.ceil(Math.random() * 25)) } + // ]) + comp?.children.comp.children?.avatars.children.manual.children.manual.dispatch( + comp?.children.comp.children?.avatars.children.manual.children.manual.setChildrensAction([ + { src: "https://api.dicebear.com/7.x/miniavs/svg?seed=1", label: String.fromCharCode(65 + Math.ceil(Math.random() * 25)) } + ]) + ); + } + + return ( & { onClick={() => { props.onEvent("click") props.dispatch(changeChildAction("currentAvatar", item as JSONObject, false)); + updateAvatars(); }} > {item.label} diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnListComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnListComp.tsx index 4a3c5237b..04ea448cc 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnListComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableColumnListComp.tsx @@ -17,11 +17,13 @@ import { import { shallowEqual } from "react-redux"; import { JSONObject, JSONValue } from "util/jsonTypes"; import { lastValueIfEqual } from "util/objectUtils"; +import { SummaryColumnComp } from "./tableSummaryColumnComp"; /** * column list */ const ColumnListTmpComp = list(ColumnComp); +const SummaryColumnListTmpComp = list(SummaryColumnComp); /** * rowExample is used for code prompts @@ -190,3 +192,139 @@ export class ColumnListComp extends ColumnListTmpComp { return this.forEachAction(ColumnComp.setSelectionAction(key)); } } + +export class SummaryColumnListComp extends SummaryColumnListTmpComp { + override reduce(action: CompAction): this { + if (isMyCustomAction(action, "dataChanged")) { + const rowExample = action.value.rowExample; + const { readOnly } = getReduceContext(); + let comp = this; + if (action.value.doGeneColumn && (action.value.dynamicColumn || !readOnly)) { + const actions = this.geneColumnsAction(rowExample, action.value.data); + comp = this.reduce(this.multiAction(actions)); + } + return comp; + } + return super.reduce(action); + } + + getChangeSet() { + const changeSet: Record> = {}; + const columns = this.getView(); + columns.forEach((column) => { + const columnChangeSet = column.getChangeSet(); + Object.keys(columnChangeSet).forEach((dataIndex) => { + Object.keys(columnChangeSet[dataIndex]).forEach((key) => { + if (!_.isNil(columnChangeSet[dataIndex][key])) { + if (!changeSet[key]) changeSet[key] = {}; + changeSet[key][dataIndex] = columnChangeSet[dataIndex][key]; + } + }); + }); + }); + return changeSet; + } + + dispatchClearChangeSet() { + const columns = this.getView(); + columns.forEach((column) => column.dispatchClearChangeSet()); + } + + /** + * If the table data changes, call this method to trigger the action + */ + dataChangedAction(param: { + rowExample: JSONObject; + doGeneColumn: boolean; + dynamicColumn: boolean; + data: Array; + }) { + return customAction( + { + type: "dataChanged", + ...param, + }, + true + ); + } + + /** + * According to the data, adjust the column + */ + private geneColumnsAction(rowExample: RowExampleType, data: Array) { + // If no data, return directly + if (rowExample === undefined || rowExample === null) { + return []; + } + const dataKeys = Object.keys(rowExample); + if (dataKeys.length === 0) { + return []; + } + const columnsView = this.getView(); + const actions: Array = []; + let deleteCnt = 0; + columnsView.forEach((column, index) => { + if (column.getView().isCustom) { + return; + } + const dataIndex = column.getView().dataIndex; + if (dataIndex === COLUMN_CHILDREN_KEY || !dataKeys.find((key) => dataIndex === key)) { + // to Delete + actions.push(this.deleteAction(index - deleteCnt)); + deleteCnt += 1; + } + }); + // The order should be the same as the data + dataKeys.forEach((key) => { + if (key === COLUMN_CHILDREN_KEY && supportChildrenTree(data)) { + return; + } + if (!columnsView.find((column) => column.getView().dataIndex === key)) { + // to Add + actions.push(this.pushAction(newPrimaryColumn(key, calcColumnWidth(key, data)))); + } + }); + if (actions.length === 0) { + return []; + } + return actions; + } + + withParamsNode() { + const columns = this.getView(); + const nodes = _(columns) + .map((col) => col.children.render.getOriginalComp().node()) + .toPairs() + .fromPairs() + .value(); + const result = lastValueIfEqual( + this, + "withParamsNode", + [fromRecord(nodes), nodes] as const, + (a, b) => shallowEqual(a[1], b[1]) + )[0]; + return result; + } + + getColumnsNode( + field: T + ): RecordNode>> { + const columns = this.getView(); + const nodes = _(columns) + .map((col) => col.children[field].node() as ReturnType) + .toPairs() + .fromPairs() + .value(); + const result = lastValueIfEqual( + this, + "col_nodes_" + field, + [fromRecord(nodes), nodes] as const, + (a, b) => shallowEqual(a[1], b[1]) + )[0]; + return result; + } + + setSelectionAction(key: string) { + return this.forEachAction(ColumnComp.setSelectionAction(key)); + } +} \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/tableSummaryColumnComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableSummaryColumnComp.tsx new file mode 100644 index 000000000..c2b6087f8 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/tableSummaryColumnComp.tsx @@ -0,0 +1,432 @@ +import { BoolControl } from "comps/controls/boolControl"; +import { ColorOrBoolCodeControl, NumberControl, RadiusControl, StringControl } from "comps/controls/codeControl"; +import { dropdownControl, HorizontalAlignmentControl } from "comps/controls/dropdownControl"; +import { MultiCompBuilder, stateComp, valueComp, withContext, withDefault } from "comps/generators"; +import { withSelectedMultiContext } from "comps/generators/withSelectedMultiContext"; +import { genRandomKey } from "comps/utils/idGenerator"; +import { trans } from "i18n"; +import _ from "lodash"; +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + ConstructorToComp, + ConstructorToDataType, + ConstructorToNodeType, + ConstructorToView, + deferAction, + fromRecord, + multiChangeAction, + withFunction, + wrapChildAction, +} from "lowcoder-core"; +import { AlignClose, AlignLeft, AlignRight, IconRadius, BorderWidthIcon, TextSizeIcon, FontFamilyIcon, TextWeightIcon, ImageCompIcon, controlItem, Dropdown, OptionType } from "lowcoder-design"; +import { ColumnTypeComp, ColumnTypeCompMap } from "./columnTypeComp"; +import { ColorControl } from "comps/controls/colorControl"; +import { JSONValue } from "util/jsonTypes"; +import styled from "styled-components"; +import { TextOverflowControl } from "comps/controls/textOverflowControl"; +import { default as Divider } from "antd/es/divider"; +import { ColumnValueTooltip } from "./simpleColumnTypeComps"; +export type Render = ReturnType["getOriginalComp"]>; +export const RenderComp = withSelectedMultiContext(ColumnTypeComp); + +const columnWidthOptions = [ + { + label: trans("table.auto"), + value: "auto", + }, + { + label: trans("table.fixed"), + value: "fixed", + }, +] as const; + +const columnFixOptions = [ + { + label: , + value: "left", + }, + { + label: , + value: "close", + }, + { + label: , + value: "right", + }, +] as const; + +const cellColorLabel = trans("table.cellColor"); +const CellColorTempComp = withContext( + new MultiCompBuilder({ color: ColorOrBoolCodeControl }, (props) => props.color) + .setPropertyViewFn((children) => + children.color.propertyView({ + label: cellColorLabel, + tooltip: trans("table.cellColorDesc"), + }) + ) + .build(), + ["currentCell", "currentRow"] as const +); + +// @ts-ignore +export class CellColorComp extends CellColorTempComp { + override getPropertyView() { + return controlItem({ filterText: cellColorLabel }, super.getPropertyView()); + } +} + +// fixme, should be infer from RowColorComp, but withContext type incorrect +export type CellColorViewType = (param: { + currentRow: any; + currentCell: JSONValue | undefined; //number | string; +}) => string; + +const cellTooltipLabel = trans("table.columnTooltip"); +const CellTooltipTempComp = withContext( + new MultiCompBuilder({ tooltip: StringControl }, (props) => props.tooltip) + .setPropertyViewFn((children) => + children.tooltip.propertyView({ + label: cellTooltipLabel, + tooltip: ColumnValueTooltip, + }) + ) + .build(), + ["currentCell", "currentRow", "currentIndex"] as const +); + +// @ts-ignore +export class CellTooltipComp extends CellTooltipTempComp { + override getPropertyView() { + return controlItem({ filterText: cellTooltipLabel }, super.getPropertyView()); + } +} + +// fixme, should be infer from RowColorComp, but withContext type incorrect +export type CellTooltipViewType = (param: { + currentRow: any; + currentCell: JSONValue | undefined; //number | string; +}) => string; + + +export const columnChildrenMap = { + // column title + // title: valueComp(""), + value: StringControl, + // titleTooltip: StringControl, + // showTitle: withDefault(BoolControl, true), + cellTooltip: CellTooltipComp, + // a custom column or a data column + isCustom: valueComp(false), + // If it is a data column, it must be the name of the column and cannot be duplicated as a react key + dataIndex: valueComp(""), + hide: BoolControl, + // sortable: BoolControl, + // width: NumberControl, + // autoWidth: dropdownControl(columnWidthOptions, "auto"), + render: RenderComp, + align: HorizontalAlignmentControl, + // tempHide: stateComp(false), + fixed: dropdownControl(columnFixOptions, "close"), + // editable: BoolControl, + background: withDefault(ColorControl, ""), + margin: withDefault(RadiusControl, ""), + text: withDefault(ColorControl, ""), + border: withDefault(ColorControl, ""), + borderWidth: withDefault(RadiusControl, ""), + radius: withDefault(RadiusControl, ""), + textSize: withDefault(RadiusControl, ""), + textWeight: withDefault(StringControl, "normal"), + fontFamily: withDefault(StringControl, "sans-serif"), + fontStyle: withDefault(StringControl, 'normal'), + cellColor: CellColorComp, + textOverflow: withDefault(TextOverflowControl, "ellipsis"), + linkColor: withDefault(ColorControl, "#3377ff"), + linkHoverColor: withDefault(ColorControl, ""), + linkActiveColor: withDefault(ColorControl, ""), +}; + +const StyledBorderRadiusIcon = styled(IconRadius)` width: 24px; margin: 0 8px 0 -3px; padding: 3px;`; +const StyledBorderIcon = styled(BorderWidthIcon)` width: 24px; margin: 0 8px 0 -3px; padding: 3px;`; +const StyledTextSizeIcon = styled(TextSizeIcon)` width: 24px; margin: 0 8px 0 -3px; padding: 3px;`; +const StyledFontFamilyIcon = styled(FontFamilyIcon)` width: 24px; margin: 0 8px 0 -3px; padding: 3px;`; +const StyledTextWeightIcon = styled(TextWeightIcon)` width: 24px; margin: 0 8px 0 -3px; padding: 3px;`; +const StyledBackgroundImageIcon = styled(ImageCompIcon)` width: 24px; margin: 0 0px 0 -12px;`; + +/** + * export for test. + * Put it here temporarily to avoid circular dependencies + */ +const ColumnInitComp = new MultiCompBuilder(columnChildrenMap, (props, dispatch) => { + return { + ...props, + // onWidthResize: (width: number) => { + // dispatch( + // multiChangeAction({ + // width: changeValueAction(width, true), + // autoWidth: changeValueAction("fixed", true), + // }) + // ); + // }, + }; +}) + .setPropertyViewFn(() => <>) + .build(); + +export class SummaryColumnComp extends ColumnInitComp { + override reduce(action: CompAction) { + let comp = super.reduce(action); + if (action.type === CompActionTypes.UPDATE_NODES_V2) { + comp = comp.setChild( + "cellColor", + comp.children.cellColor.reduce( + CellColorComp.changeContextDataAction({ + currentCell: undefined, + currentRow: {}, + }) + ) + ); + comp = comp.setChild( + "cellTooltip", + comp.children.cellTooltip.reduce( + CellTooltipComp.changeContextDataAction({ + currentCell: undefined, + currentRow: {}, + currentIndex: 0, + }) + ) + ); + } + // if (action.type === CompActionTypes.CHANGE_VALUE) { + // const title = comp.children.title.unevaledValue; + // const dataIndex = comp.children.dataIndex.getView(); + // if (!Boolean(title)) { + // comp.children.title.dispatchChangeValueAction(dataIndex); + // } + // } + return comp; + } + + override getView() { + const superView = super.getView(); + const columnType = this.children.render.getSelectedComp().getComp().children.compType.getView(); + return { + ...superView, + }; + } + + exposingNode() { + const dataIndexNode = this.children.dataIndex.exposingNode(); + + const renderNode = withFunction(this.children.render.node(), (render) => ({ + wrap: render.__comp__.wrap, + map: _.mapValues(render.__map__, (value) => value.comp), + })); + return fromRecord({ + dataIndex: dataIndexNode, + render: renderNode, + }); + } + + propertyView(key: string) { + const columnType = this.children.render.getSelectedComp().getComp().children.compType.getView(); + const initialColumns = this.children.render.getSelectedComp().getParams()?.initialColumns as OptionType[] || []; + const column = this.children.render.getSelectedComp().getComp().toJsonValue(); + let columnValue = '{{currentCell}}'; + if (column.comp?.hasOwnProperty('src')) { + columnValue = (column.comp as any).src; + } else if (column.comp?.hasOwnProperty('text')) { + columnValue = (column.comp as any).text; + } + + return ( + <> + {/* {this.children.title.propertyView({ + label: trans("table.columnTitle"), + placeholder: this.children.dataIndex.getView(), + })} + {this.children.titleTooltip.propertyView({ + label: trans("table.columnTitleTooltip"), + })} */} + {/* { + // Keep the previous text value, some components do not have text, the default value is currentCell + const compType = columnType; + let comp: Record = { text: value}; + if(columnType === 'image') { + comp = { src: value }; + } + this.children.render.dispatchChangeValueAction({ + compType, + comp, + } as any); + }} + /> */} + {this.children.cellTooltip.getPropertyView()} + {this.children.value.propertyView({ + label: "Column Value" + })} + {/* FIXME: cast type currently, return type of withContext should be corrected later */} + {this.children.render.getPropertyView()} + {/* {this.children.showTitle.propertyView({ + label: trans("table.showTitle"), + tooltip: trans("table.showTitleTooltip"), + })} */} + {/* {ColumnTypeCompMap[columnType].canBeEditable() && + this.children.editable.propertyView({ label: trans("table.editable") })} + {this.children.sortable.propertyView({ + label: trans("table.sortable"), + })} */} + {this.children.hide.propertyView({ + label: trans("prop.hide"), + })} + {this.children.align.propertyView({ + label: trans("table.align"), + radioButton: true, + })} + {this.children.fixed.propertyView({ + label: trans("table.fixedColumn"), + radioButton: true, + })} + {/* {this.children.autoWidth.propertyView({ + label: trans("table.autoWidth"), + radioButton: true, + })} + {this.children.autoWidth.getView() === "fixed" && + this.children.width.propertyView({ label: trans("prop.width") })} */} + + {(columnType === 'link' || columnType === 'links') && ( + <> + + {controlItem({}, ( +
+ {"Link Style"} +
+ ))} + {this.children.linkColor.propertyView({ + label: trans('text') // trans('style.background'), + })} + {this.children.linkHoverColor.propertyView({ + label: "Hover text", // trans('style.background'), + })} + {this.children.linkActiveColor.propertyView({ + label: "Active text", // trans('style.background'), + })} + + )} + + {controlItem({}, ( +
+ {"Column Style"} +
+ ))} + {this.children.background.propertyView({ + label: trans('style.background'), + })} + {columnType !== 'link' && this.children.text.propertyView({ + label: trans('text'), + })} + {this.children.border.propertyView({ + label: trans('style.border') + })} + {this.children.borderWidth.propertyView({ + label: trans('style.borderWidth'), + preInputNode: , + placeholder: '1px', + })} + {this.children.radius.propertyView({ + label: trans('style.borderRadius'), + preInputNode: , + placeholder: '3px', + })} + {this.children.textSize.propertyView({ + label: trans('style.textSize'), + preInputNode: , + placeholder: '14px', + })} + {this.children.textWeight.propertyView({ + label: trans('style.textWeight'), + preInputNode: , + placeholder: 'normal', + })} + {this.children.fontFamily.propertyView({ + label: trans('style.fontFamily'), + preInputNode: , + placeholder: 'sans-serif', + })} + {this.children.fontStyle.propertyView({ + label: trans('style.fontStyle'), + preInputNode: , + placeholder: 'normal' + })} + {this.children.textOverflow.getPropertyView()} + {this.children.cellColor.getPropertyView()} + + ); + } + + getChangeSet() { + const dataIndex = this.children.dataIndex.getView(); + const changeSet = _.mapValues(this.children.render.getMap(), (value) => + value.getComp().children.comp.children.changeValue.getView() + ); + return { [dataIndex]: changeSet }; + } + + dispatchClearChangeSet() { + this.children.render.dispatch( + deferAction( + RenderComp.forEachAction( + wrapChildAction( + "comp", + wrapChildAction("comp", changeChildAction("changeValue", null, false)) + ) + ) + ) + ); + } + + static setSelectionAction(key: string) { + return wrapChildAction("render", RenderComp.setSelectionAction(key)); + } +} + +export type RawColumnType = ConstructorToView; +export type ColumNodeType = ConstructorToNodeType; +export type ColumnCompType = ConstructorToComp; + +/** + * Custom column initialization data + */ +// export function newCustomColumn(): ConstructorToDataType { +// return { +// // title: trans("table.customColumn"), +// dataIndex: genRandomKey(), +// isCustom: true, +// }; +// } + +/** + * Initialization data of primary column + */ +// export function newPrimaryColumn( +// key: string, +// width: number, +// title?: string, +// isTag?: boolean +// ): ConstructorToDataType { +// return { +// title: title ?? key, +// dataIndex: key, +// isCustom: false, +// autoWidth: "fixed", +// width: width + "", +// render: { compType: isTag ? "tag" : "text", comp: { text: "{{currentCell}}" } }, +// }; +// } diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx index 553a36a0d..5dedd1bba 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx @@ -227,6 +227,17 @@ export class TableImplComp extends TableInitComp implements IContainer { }) ) ); + // actions.push( + // wrapChildAction( + // "columns", + // comp.children.summary.children.columns.dataChangedAction({ + // rowExample: nextRowExample || {}, + // doGeneColumn: doGene, + // dynamicColumn: comp.children.dynamicColumn.getView(), + // data: comp.children.data.getView(), + // }) + // ) + // ); doGene && actions.push(comp.changeChildAction("dataRowExample", null)); setTimeout(() => { actions.forEach((action) => comp.dispatch(deferAction(action))); @@ -249,6 +260,10 @@ export class TableImplComp extends TableInitComp implements IContainer { "columns", comp.children.columns.reduce(comp.children.columns.setSelectionAction(newSelection)) ); + // comp.children.summary = comp.children.summary.setChild( + // "columns", + // comp.children.summary.children.columns.reduce(comp.children.summary.children.columns.setSelectionAction(newSelection)) + // ); needMoreEval = true; } diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx index 3bb8403e9..f2abd7dae 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableCompView.tsx @@ -39,10 +39,12 @@ import { SlotConfigContext } from "comps/controls/slotControl"; import { EmptyContent } from "pages/common/styledComponent"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; import { ReactRef, ResizeHandleAxis } from "layout/gridLayoutPropTypes"; -import { CellColorViewType } from "./column/tableColumnComp"; +import { CellColorViewType, ColumnComp } from "./column/tableColumnComp"; import { defaultTheme } from "@lowcoder-ee/constants/themeConstants"; import { childrenToProps } from "@lowcoder-ee/comps/generators/multi"; import { getVerticalMargin } from "@lowcoder-ee/util/cssUtil"; +import { TableSummary } from "./tableSummaryComp"; +import { SummaryColumnComp } from "./column/tableSummaryColumnComp"; function genLinerGradient(color: string) { return `linear-gradient(${color}, ${color})`; @@ -748,6 +750,7 @@ export function TableCompView(props: { const data = comp.filterData; const sort = useMemo(() => compChildren.sort.getView(), [compChildren.sort]); const toolbar = useMemo(() => compChildren.toolbar.getView(), [compChildren.toolbar]); + // const summary = useMemo(() => compChildren.summary.getView(), [compChildren.summary]); const pagination = useMemo(() => compChildren.pagination.getView(), [compChildren.pagination]); const size = useMemo(() => compChildren.size.getView(), [compChildren.size]); const onEvent = useMemo(() => compChildren.onEvent.getView(), [compChildren.onEvent]); @@ -780,11 +783,43 @@ export function TableCompView(props: { columnsAggrData, ] ); + const supportChildren = useMemo( () => supportChildrenTree(compChildren.data.getView()), [compChildren.data] ); + // const summaryColumnsMap = useMemo(() => { + // const columnsMap: Record = {}; + // summary.columns.forEach(column => { + // const columnData = column.toJsonValue(); + // columnsMap[columnData.dataIndex!] = columnData; + // }); + // return columnsMap; + // }, [summary.columns]) + + // useEffect(() => { + // // if (columns.length === comp.children.summary.children.columns.getView().length) return; + // console.log('useEffect', columns); + // const summaryColumns: any[] = []; + // columns.forEach(col => { + // const colData = col.toJsonValue(); + // const summaryColData = summaryColumnsMap[colData.dataIndex!]; + // console.log('colData Render', colData.render); + // console.log('summaryColData Render', summaryColData?.render); + // summaryColumns.push(new SummaryColumnComp({ + // value: { + // title: colData.title, + // dataIndex: colData.dataIndex, + // render: summaryColData?.render || {} + // } + // })) + // }) + // comp.children.summary.children.columns.dispatch( + // comp.children.summary.children.columns.setChildrensAction(summaryColumns as any[]) + // ); + // }, [compChildren.columns]); + const pageDataInfo = useMemo(() => { // Data pagination let pagedData = data; @@ -847,6 +882,16 @@ export function TableCompView(props: { /> ); + // const summaryView = () => { + // if (!summary.showSummary) return undefined; + // return ( + // + // ); + // } + if (antdColumns.length === 0) { return ; } @@ -925,6 +970,7 @@ export function TableCompView(props: { dataIndex: dataIndex, }); }} + // summary={summaryView} /> diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx index 636721c1f..9675c35d1 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tablePropertyView.tsx @@ -34,6 +34,7 @@ import { GreyTextColor } from "constants/style"; import { alignOptions } from "comps/controls/dropdownControl"; import { ColumnTypeCompMap } from "comps/comps/tableComp/column/columnTypeComp"; import { changeChildAction } from "lowcoder-core"; +import { summaryPropertyView } from "./tableSummaryComp"; const InsertDiv = styled.div` display: flex; @@ -418,6 +419,7 @@ function columnPropertyView>(comp: T) export function compTablePropertyView & { editorModeStatus: string }>(comp: T) { const editorModeStatus = comp.editorModeStatus; const dataLabel = trans("data"); + return ( <> {["logic", "both"].includes(editorModeStatus) && ( @@ -448,6 +450,13 @@ export function compTablePropertyView {loadingPropertyView(comp.children)} +
+ {comp.children.showSummary.propertyView({ + label: "Show Summary Row" + })} + {/* {comp.children.summary.getView().showSummary && summaryPropertyView(comp.children.summary)} */} +
+
{comp.children.toolbar.getPropertyView()}
diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx new file mode 100644 index 000000000..bbda97436 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableSummaryComp.tsx @@ -0,0 +1,391 @@ +import { default as Button } from "antd/es/button"; +import { default as Pagination, PaginationProps } from "antd/es/pagination"; +import { default as Popover } from "antd/es/popover"; +import { ThemeDetail } from "api/commonSettingApi"; +import { ColumnCompType } from "comps/comps/tableComp/column/tableColumnComp"; +import { TableChildrenType, TableOnEventView } from "comps/comps/tableComp/tableTypes"; +import { BoolControl } from "comps/controls/boolControl"; +import { StringControl } from "comps/controls/codeControl"; +import { dropdownControl } from "comps/controls/dropdownControl"; +import { TableToolbarStyleType } from "comps/controls/styleControlConstants"; +import { stateComp, withDefault } from "comps/generators"; +import { genRandomKey } from "comps/utils/idGenerator"; +import { ThemeContext } from "comps/utils/themeContext"; +import { trans } from "i18n"; +import _, { isNil } from "lodash"; +import { changeChildAction, ConstructorToView, MultiBaseComp, RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { + AlignBottom, + AlignClose, + AlignTop, + BluePlusIcon, + CheckBox, + CommonTextLabel, + CustomSelect, + DeleteIcon, + DownloadIcon, + FilterIcon, + LinkButton, + pageItemRender, + RefreshIcon, + TableColumnVisibilityIcon, + SuspensionBox, + TacoButton, + TacoInput, + ValueFromOption, + Option, + OptionItem, + controlItem, +} from "lowcoder-design"; +import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; +import styled, { css } from "styled-components"; +import { JSONValue } from "util/jsonTypes"; +import { ControlNodeCompBuilder } from "comps/generators/controlCompBuilder"; +import { defaultTheme } from "@lowcoder-ee/constants/themeConstants"; +import Table from "antd/es/table"; +import { ColumnListComp, SummaryColumnListComp } from "./column/tableColumnListComp"; +import { GreyTextColor } from "@lowcoder-ee/constants/style"; +import { getSelectedRowKeys } from "./selectionControl"; +import { CompNameContext, EditorContext } from "@lowcoder-ee/comps/editorState"; +import { SummaryColumnComp } from "./column/tableSummaryColumnComp"; + +const SaveChangeButtons = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +// const getStyle = ( +// style: TableToolbarStyleType, +// filtered: boolean, +// theme: ThemeDetail, +// position: TableSummaryType["position"], +// fixedToolbar: boolean, +// ) => { +// return css` +// background-color: ${style.background}; +// // Implement horizontal scrollbar and vertical page number selection is not blocked +// padding: 13px 12px; +// position: sticky; +// postion: -webkit-sticky; +// left: 0px !important; +// margin: ${style.margin} !important; +// z-index: 999; + +// ${fixedToolbar && position === 'below' && `bottom: 0;`}; +// ${fixedToolbar && position === 'above' && `top: 0;` }; + +// .toolbar-icons { +// .refresh, +// .download { +// cursor: pointer; + +// * { +// ${style.toolbarText !== defaultTheme.textDark ? `stroke: ${style.toolbarText}` : null}; +// } + +// &:hover * { +// stroke: ${theme?.primary}; +// } +// } + +// .filter { +// cursor: pointer; + +// * { +// ${filtered +// ? `stroke: ${defaultTheme.primary}` +// : style.toolbarText !== defaultTheme.textDark +// ? `stroke: ${style.toolbarText}` +// : null} +// } + +// &:hover * { +// stroke: ${theme?.primary}; +// } +// } + +// .column-setting { +// width: 20px; +// cursor: pointer; + +// * { +// ${style.toolbarText && style.toolbarText !== defaultTheme.textDark ? `fill: ${style.toolbarText}` : `fill: #8b8fa3`} +// } + +// &:hover * { +// fill: ${theme?.primary}; +// } +// } +// } + +// .ant-pagination-prev, +// .ant-pagination-next { +// path { +// ${style.toolbarText !== defaultTheme.textDark ? `fill: ${style.toolbarText}` : null}; +// } + +// svg:hover { +// path { +// fill: ${theme?.primary}; +// } +// } +// } + +// .ant-pagination { +// color: ${style.toolbarText}; +// } + +// .ant-pagination-item-active { +// border-color: ${style.border || theme?.primary}; + +// a { +// color: ${theme?.textDark}; +// } +// } + +// .ant-pagination-item:not(.ant-pagination-item-active) a { +// color: ${style.toolbarText}; + +// &:hover { +// color: ${theme?.primary}; +// } +// } + +// .ant-select:not(.ant-select-disabled):hover .ant-select-selector, +// .ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) +// .ant-select-selector, +// .ant-pagination-options-quick-jumper input:hover, +// .ant-pagination-options-quick-jumper input:focus { +// border-color: ${style.border || theme?.primary}; +// } +// `; +// }; + +// // overflow: auto; +// ${(props) => props.$style && getStyle( +// props.$style, +// props.$filtered, +// props.$theme, +// props.$position, +// props.$fixedToolbar, +// )} +const SummaryWrapper = styled.div<{ + // $style: TableToolbarStyleType; + // $filtered: boolean; + // $theme: ThemeDetail; + // $position: ToolbarRowType["position"]; + // $fixedToolbar: boolean; +}>` +`; + +const SummaryWrapper2 = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + min-width: max-content; + // height: 21px; + width: 100%; +`; + +const childrenMap = { + showSummary: BoolControl, + columns: SummaryColumnListComp, +}; + +type TableSummaryType = RecordConstructorToComp; +type TableSummaryView = RecordConstructorToView; + +function ColumnPropertyView>(props: { + comp: T; + columnLabel: string; +}) { + const { comp } = props; + const comp2 = useContext(EditorContext).getUICompByName(useContext(CompNameContext)); + // const selection = getSelectedRowKeys(comp.children.selection)[0] ?? "0"; + // const [columnFilterType, setColumnFilterType] = useState("all"); + const columns: SummaryColumnListComp = comp2?.children.comp.children.summary.children.columns; + const columnsView = columns.getView(); + + // const rowExample = comp.children.dataRowExample.getView(); + // const dynamicColumn = comp.children.dynamicColumn.getView(); + // const data = comp.children.data.getView(); + // const [columnBatchType, setColumnBatchType] = useState("hide"); + // const columnOptionItems = useMemo( + // () => columns.filter((c) => columnFilterType === "all" || !c.children.hide.getView()), + // [columnFilterType, columns] + // ); + + // const columnOptionToolbar = ( + // + //
+ // + // {" (" + columns.length + ")"} + //
+ // {rowExample && ( + // + // { + // // console.log("comp", comp); + // comp.dispatch( + // wrapChildAction( + // "columns", + // comp.children.columns.dataChangedAction({ + // rowExample, + // doGeneColumn: true, + // dynamicColumn: dynamicColumn, + // data: data, + // }) + // ) + // ); + // // the function below is not working + // // comp.dispatch(comp.changeChildAction("dataRowExample", null)); + // }} + // /> + // + // )} + // } + // text={trans("addItem")} + // onClick={() => { + // comp.children.columns.dispatch(comp.children.columns.pushAction(newCustomColumn())); + // }} + // /> + //
+ // ); + + return ( + <> +