diff --git a/packages/grid/src/constants/colors.ts b/packages/grid/src/constants/colors.ts index 92d1debf..945bcfdf 100644 --- a/packages/grid/src/constants/colors.ts +++ b/packages/grid/src/constants/colors.ts @@ -12,6 +12,7 @@ export const Colors = { gray80: '#fafafa', headSelectedBgColor: '#EAEFFA', itemActiveBgColor: '#6698ff1a', + itemMatchBgColor: '#ff9f731a', waring: '#ffcd5d', success: '#73d897' }; diff --git a/packages/grid/src/core/types/ai-table.ts b/packages/grid/src/core/types/ai-table.ts index 9bc4d09f..e0bac1f9 100644 --- a/packages/grid/src/core/types/ai-table.ts +++ b/packages/grid/src/core/types/ai-table.ts @@ -9,10 +9,11 @@ export interface AITable { fields: WritableSignal; context?: RendererContext; selection: WritableSignal; + matchedCells: WritableSignal; // [`${recordId}-${fieldId}`] recordsMap: Signal<{ [kay: string]: AITableRecord }>; fieldsMap: Signal<{ [kay: string]: AITableField }>; recordsWillHidden: WritableSignal; - recordsWillMove: WritableSignal + recordsWillMove: WritableSignal; } export type AIPlugin = (aiTable: AITable) => AITable; diff --git a/packages/grid/src/core/utils/common.ts b/packages/grid/src/core/utils/common.ts index b017682b..5862daee 100644 --- a/packages/grid/src/core/utils/common.ts +++ b/packages/grid/src/core/utils/common.ts @@ -10,6 +10,7 @@ export function createAITable(records: WritableSignal, fields: W selectedFields: new Map(), selectedCells: new Map() }), + matchedCells: signal([]), recordsMap: computed(() => { return records().reduce( (object, item) => { diff --git a/packages/grid/src/grid-base.component.ts b/packages/grid/src/grid-base.component.ts index a34c4be6..251e9520 100644 --- a/packages/grid/src/grid-base.component.ts +++ b/packages/grid/src/grid-base.component.ts @@ -35,6 +35,7 @@ import { AI_TABLE_GRID_FIELD_SERVICE_MAP, AITableGridFieldService } from './serv import { AITableGridSelectionService } from './services/selection.service'; import { AIFieldConfig, AITableFieldMenuItem, AITableContextMenuItem, AITableReferences } from './types'; import { AITableFieldPropertyEditor } from './components'; +import { AITableGridMatchCellService } from './services/match-cell.service'; @Component({ selector: 'ai-table-grid-base', @@ -59,6 +60,8 @@ export class AITableGridBase implements OnInit { aiBuildRenderDataFn = input<(aiTable: AITable) => AITableValue>(); + aiKeywords = input(); + AITableFieldType = AITableFieldType; AITableSelectOptionStyle = AITableSelectOptionStyle; @@ -99,6 +102,7 @@ export class AITableGridBase implements OnInit { protected aiTableGridFieldService = inject(AITableGridFieldService); protected aiTableGridEventService = inject(AITableGridEventService); protected aiTableGridSelectionService = inject(AITableGridSelectionService); + protected aiTableGridMatchCellService = inject(AITableGridMatchCellService); ngOnInit(): void { this.initAITable(); @@ -116,6 +120,7 @@ export class AITableGridBase implements OnInit { initService() { this.aiTableGridEventService.initialize(this.aiTable, this.aiFieldConfig()?.fieldPropertyEditor); this.aiTableGridSelectionService.initialize(this.aiTable); + this.aiTableGridMatchCellService.initialize(this.aiTable); this.aiTableGridEventService.registerEvents(this.elementRef.nativeElement); this.aiTableGridFieldService.initAIFieldConfig(this.aiFieldConfig()); AI_TABLE_GRID_FIELD_SERVICE_MAP.set(this.aiTable, this.aiTableGridFieldService); diff --git a/packages/grid/src/grid.component.ts b/packages/grid/src/grid.component.ts index cd7cbb1c..e2ed7a0f 100644 --- a/packages/grid/src/grid.component.ts +++ b/packages/grid/src/grid.component.ts @@ -43,6 +43,7 @@ import { AITableGridSelectionService } from './services/selection.service'; import { AITableMouseDownType, AITableRendererConfig, ScrollActionOptions } from './types'; import { buildGridLinearRows, getColumnIndicesMap, getDetailByTargetName, handleMouseStyle, isWindows } from './utils'; import { getMousePosition } from './utils/position'; +import { AITableGridMatchCellService } from './services/match-cell.service'; @Component({ selector: 'ai-table-grid', @@ -53,7 +54,7 @@ import { getMousePosition } from './utils/position'; class: 'ai-table-grid' }, imports: [AITableRenderer], - providers: [AITableGridEventService, AITableGridFieldService, AITableGridSelectionService] + providers: [AITableGridEventService, AITableGridFieldService, AITableGridSelectionService, AITableGridMatchCellService] }) export class AITableGrid extends AITableGridBase implements OnInit, OnDestroy { private viewContainerRef = inject(ViewContainerRef); @@ -147,6 +148,14 @@ export class AITableGrid extends AITableGridBase implements OnInit, OnDestroy { this.toggleHoverCellEditor(); } }); + effect( + () => { + if (this.aiKeywords()) { + this.aiTableGridMatchCellService.findMatchedCells(this.aiKeywords()!, this.aiReferences()); + } + }, + { allowSignalWrites: true } + ); } override ngOnInit(): void { diff --git a/packages/grid/src/renderer/creations/create-cells.ts b/packages/grid/src/renderer/creations/create-cells.ts index 6499d7a9..f955d5c8 100644 --- a/packages/grid/src/renderer/creations/create-cells.ts +++ b/packages/grid/src/renderer/creations/create-cells.ts @@ -6,7 +6,7 @@ import { DEFAULT_FONT_STYLE } from '../../constants'; import { AITable, AITableQueries, RendererContext } from '../../core'; -import { AITableAreaType, AITableCellsDrawerConfig, AITableRender, AITableRowType } from '../../types'; +import { AITableCellsDrawerConfig, AITableRender, AITableRowType } from '../../types'; import { getCellHorizontalPosition, transformCellValue } from '../../utils'; import { addRowLayout } from '../drawers/add-row-layout-drawer'; import { cellDrawer } from '../drawers/cell-drawer'; @@ -85,7 +85,15 @@ export const createCells = (config: AITableCellsDrawerConfig) => { const isSiblingActiveCell = recordId === activeCell?.recordId && fieldId !== activeCell?.fieldId; const isActiveCell = recordId === activeCell?.recordId; - if (isCheckedRow || isSelected || isSiblingActiveCell) { + let matchedCellsMap: { [key: string]: boolean } = {}; + aiTable.matchedCells().forEach((key) => { + matchedCellsMap[key] = true; + }); + const isMatchedCell = matchedCellsMap[`${recordId}-${fieldId}`]; + + if (isMatchedCell && !isActiveCell) { + background = colors.itemMatchBgColor; + } else if (isCheckedRow || isSelected || isSiblingActiveCell) { background = colors.itemActiveBgColor; } else if (isHoverRow && !isActiveCell) { background = colors.gray80; diff --git a/packages/grid/src/services/index.ts b/packages/grid/src/services/index.ts index 6a76618d..d2764b76 100644 --- a/packages/grid/src/services/index.ts +++ b/packages/grid/src/services/index.ts @@ -1,3 +1,4 @@ export * from './event.service'; export * from './field.service'; export * from './selection.service'; +export * from './match-cell.service'; diff --git a/packages/grid/src/services/match-cell.service.ts b/packages/grid/src/services/match-cell.service.ts new file mode 100644 index 00000000..33cbec5c --- /dev/null +++ b/packages/grid/src/services/match-cell.service.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@angular/core'; +import { AITable, AITableField, AITableFieldType, AITableQueries } from '../core'; +import { AITableReferences, AITableSelectField } from '../types'; +import { transformCellValue } from '../utils'; + +@Injectable() +export class AITableGridMatchCellService { + aiTable!: AITable; + + initialize(aiTable: AITable) { + this.aiTable = aiTable; + } + + findMatchedCells(keywords: string, references: AITableReferences) { + let matchedCells: string[] = []; + this.aiTable.records().forEach((record) => { + this.aiTable.fields().forEach((field) => { + if (this.isCellMatchKeywords(this.aiTable, field, record._id, keywords, references)) { + matchedCells.push(`${record._id}-${field._id}`); + } + }); + }); + this.aiTable.matchedCells.set([...matchedCells]); + } + + private isCellMatchKeywords(aiTable: AITable, field: AITableField, recordId: string, keywords: string, references: AITableReferences) { + const cellValue = AITableQueries.getFieldValue(aiTable, [recordId, field._id]); + const transformValue = transformCellValue(aiTable, field, cellValue); + const fieldType = field.type; + + let cellText: string[] = []; + + switch (fieldType) { + case AITableFieldType.link: + if (transformValue?.text) { + cellText.push(transformValue.text); + } + break; + case AITableFieldType.select: + if (transformValue && Array.isArray(transformValue) && transformValue.length) { + transformValue.forEach((optionId) => { + const item = (field as AITableSelectField).settings.options?.find((option) => option._id === optionId); + if (item?.text) { + cellText.push(item.text); + } + }); + } + break; + case AITableFieldType.progress: + cellText.push(`${transformValue}%`); + break; + case AITableFieldType.number: + case AITableFieldType.rate: + cellText.push(String(transformValue)); + break; + case AITableFieldType.member: + case AITableFieldType.createdBy: + case AITableFieldType.updatedBy: + if (cellValue?.length && references) { + for (let index = 0; index < cellValue.length; index++) { + const userInfo = references?.members[cellValue[index]]; + if (!userInfo) { + continue; + } + if (userInfo.display_name) { + cellText.push(userInfo.display_name); + } + } + } + break; + default: + if (transformValue) { + cellText.push(transformValue); + } + } + try { + return keywords && cellText.length && cellText.some((item) => item.toLowerCase().includes(keywords.toLowerCase())); + } catch (error) { + return false; + } + } +} diff --git a/packages/state/src/types/view.ts b/packages/state/src/types/view.ts index fd759dd8..2a702247 100644 --- a/packages/state/src/types/view.ts +++ b/packages/state/src/types/view.ts @@ -49,7 +49,7 @@ export enum AITableFilterOperation { notContain = 'not_contain' } -export type ViewSettings = AITableFilterConditions & AITableSortOptions; +export type ViewSettings = AITableSearchOptions & AITableFilterConditions & AITableSortOptions; export interface AITableView { _id: string; @@ -78,4 +78,8 @@ export interface AITableSortOptions { }[]; } +export interface AITableSearchOptions { + keywords?: string; +} + export type AITableViews = AITableView[]; diff --git a/src/app/component/common/content/content.component.html b/src/app/component/common/content/content.component.html index 4a7aa5c7..d78cd75a 100644 --- a/src/app/component/common/content/content.component.html +++ b/src/app/component/common/content/content.component.html @@ -33,6 +33,7 @@ [(aiRecords)]="tableService.records" [(aiFields)]="tableService.fields" [aiFieldConfig]="aiFieldConfig()" + [aiKeywords]="tableService.keywords()" [aiPlugins]="plugins" [aiReferences]="references()" (aiAddRecord)="addRecord($event)"