From 90b1ac09fec42b1449d8d44af1b9bfbe13c5db4c Mon Sep 17 00:00:00 2001 From: Faran Javed Date: Sat, 27 Sep 2025 00:01:34 +0500 Subject: [PATCH] [Feat]: add new pending tags in suggestions for Table Tags Column --- .../column/columnTypeComps/columnTagsComp.tsx | 85 ++++++++++--------- .../src/comps/comps/tableComp/tableComp.tsx | 16 +++- .../src/comps/comps/tableComp/tableUtils.tsx | 34 +++++++- 3 files changed, 91 insertions(+), 44 deletions(-) diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx index a950a8fba..e1633e089 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/columnTagsComp.tsx @@ -57,8 +57,8 @@ const TagsControl = codeControl | string>( { expectedType: "string | Array", codeType: "JSON" } ); -function getTagColor(tagText : any, tagOptions: any[]) { - const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText); +function getTagColor(tagText: string, tagOptions: TagOption[]): string | undefined { + const foundOption = tagOptions.find(option => option.label === tagText); if (foundOption) { if (foundOption.colorType === "preset") { return foundOption.presetColor; @@ -73,10 +73,10 @@ function getTagColor(tagText : any, tagOptions: any[]) { return colors[index]; } -function getTagStyle(tagText: any, tagOptions: any[]) { - const foundOption = tagOptions.find((option: { label: any; }) => option.label === tagText); +function getTagStyle(tagText: string, tagOptions: TagOption[]): React.CSSProperties { + const foundOption = tagOptions.find(option => option.label === tagText); if (foundOption) { - const style: any = {}; + const style: React.CSSProperties = {}; // Handle color styling if (foundOption.colorType === "custom") { @@ -113,11 +113,23 @@ function getTagStyle(tagText: any, tagOptions: any[]) { return {}; } -function getTagIcon(tagText: any, tagOptions: any[]) { +function getTagIcon(tagText: string, tagOptions: TagOption[]): React.ReactNode | undefined { const foundOption = tagOptions.find(option => option.label === tagText); return foundOption ? foundOption.icon : undefined; } +// Utility function to process comma-separated tags into individual tags +function processTagItems(tagItems: string[]): string[] { + const result: string[] = []; + tagItems.forEach((item) => { + if (item.split(",")[1]) { + item.split(",").forEach((tag) => result.push(tag)); + } + result.push(item); + }); + return result; +} + const childrenMap = { text: TagsControl, tagColors: ColoredTagOptionControl, @@ -128,11 +140,25 @@ const getBaseValue: ColumnTypeViewFn props.text; +interface TagOption { + label: string; + colorType?: "preset" | "custom"; + presetColor?: string; + color?: string; + textColor?: string; + border?: string; + radius?: string; + margin?: string; + padding?: string; + icon?: React.ReactNode; + onEvent?: (eventType: string) => void; +} + type TagEditPropsType = { value: string | string[]; onChange: (value: string | string[]) => void; onChangeEnd: () => void; - tagOptions: any[]; + tagOptions: TagOption[]; }; export const Wrapper = styled.div` @@ -240,16 +266,7 @@ export const TagStyled = styled(Tag)` const TagEdit = React.memo((props: TagEditPropsType) => { const defaultTags = useContext(TagsContext); - const [tags, setTags] = useState(() => { - const result: string[] = []; - defaultTags.forEach((item) => { - if (item.split(",")[1]) { - item.split(",").forEach((tag) => result.push(tag)); - } - result.push(item); - }); - return result; - }); + const [tags, setTags] = useState(() => processTagItems(defaultTags)); const [open, setOpen] = useState(false); const mountedRef = useRef(true); @@ -268,24 +285,16 @@ const TagEdit = React.memo((props: TagEditPropsType) => { // Update tags when defaultTags changes useEffect(() => { if (!mountedRef.current) return; - - const result: string[] = []; - defaultTags.forEach((item) => { - if (item.split(",")[1]) { - item.split(",").forEach((tag) => result.push(tag)); - } - result.push(item); - }); - setTags(result); + setTags(processTagItems(defaultTags)); }, [defaultTags]); const handleSearch = useCallback((value: string) => { if (!mountedRef.current) return; if (defaultTags.findIndex((item) => item.includes(value)) < 0) { - setTags([...defaultTags, value]); + setTags([...processTagItems(defaultTags), value]); } else { - setTags(defaultTags); + setTags(processTagItems(defaultTags)); } props.onChange(value); }, [defaultTags, props.onChange]); @@ -426,17 +435,15 @@ export const ColumnTagsComp = (function () { const tagStyle = getTagStyle(tagText, tagOptions); return ( - - handleTagClick(e, tagText)} - > - {tagText} - - + handleTagClick(e, tagText)} + > + {tagText} + ); }); return ( diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx index 17b3b2daa..725543eff 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableComp.tsx @@ -481,7 +481,6 @@ export class TableImplComp extends TableInitComp implements IContainer { return { ...oriRow, ...changeValues }; }) .value(); - // console.info("toUpdateRowsNode. input: ", input, " res: ", res); return res; }); } @@ -517,14 +516,25 @@ export class TableImplComp extends TableInitComp implements IContainer { oriDisplayData: this.oriDisplayDataNode(), withParams: this.children.columns.withParamsNode(), dataIndexes: this.children.columns.getColumnsNode("dataIndex"), + changeSet: this.changeSetNode(), }; const resNode = withFunction(fromRecord(nodes), (input) => { const dataIndexWithParamsDict = _(input.dataIndexes) .mapValues((dataIndex, idx) => input.withParams[idx]) .mapKeys((withParams, idx) => input.dataIndexes[idx]) .value(); - const res = getColumnsAggr(input.oriDisplayData, dataIndexWithParamsDict); - // console.info("columnAggrNode: ", res); + + const columnChangeSets: Record> = {}; + _.forEach(input.changeSet, (rowData, rowId) => { + _.forEach(rowData, (value, dataIndex) => { + if (!columnChangeSets[dataIndex]) { + columnChangeSets[dataIndex] = {}; + } + columnChangeSets[dataIndex][rowId] = value; + }); + }); + + const res = getColumnsAggr(input.oriDisplayData, dataIndexWithParamsDict, columnChangeSets); return res; }); return lastValueIfEqual(this, "columnAggrNode", [resNode, nodes] as const, (a, b) => diff --git a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx index b3fe78d04..d71ae5a8d 100644 --- a/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx +++ b/client/packages/lowcoder/src/comps/comps/tableComp/tableUtils.tsx @@ -230,17 +230,47 @@ export function getColumnsAggr( oriDisplayData: JSONObject[], dataIndexWithParamsDict: NodeToValue< ReturnType["withParamsNode"]> - > + >, + columnChangeSets?: Record> ): ColumnsAggrData { return _.mapValues(dataIndexWithParamsDict, (withParams, dataIndex) => { const compType = (withParams.wrap() as any).compType; const res: Record & { compType: string } = { compType }; + if (compType === "tag") { - res.uniqueTags = _(oriDisplayData) + const originalTags = _(oriDisplayData) .map((row) => row[dataIndex]!) .filter((tag) => !!tag) + .value(); + + const pendingChanges = columnChangeSets?.[dataIndex] || {}; + const pendingTags = _(pendingChanges) + .values() + .filter((value) => !!value) + .value(); + + const extractTags = (value: any): string[] => { + if (!value) return []; + if (_.isArray(value)) return value.map(String); + if (typeof value === "string") { + // Handle comma-separated tags + if (value.includes(",")) { + return value.split(",").map(tag => tag.trim()).filter(tag => tag); + } + return [value]; + } + return [String(value)]; + }; + + const allTags = [ + ...originalTags.flatMap(extractTags), + ...pendingTags.flatMap(extractTags) + ]; + + res.uniqueTags = _(allTags) .uniq() .value(); + } else if (compType === "badgeStatus") { res.uniqueStatus = _(oriDisplayData) .map((row) => {