* feat(charts-v2): init * chore(charts-v2): init chart renderer * feat(chart-v2): add chart grid and initializer * feat(chart-v2): improve ui * feat(chart-v2): ui * feat(charts-v2): query sort ui * feat(charts-v2): field select component * feat(charts-v2): improve ui && add query action * feat(charts-v2): imporve ui, work in progress * fix(charts-v2): chart renderer request api twice * feat(charts-v2): add dimension formatter * feat(charts-v2): filter, sort, limit * feat(charts-v2): sql mode ui * feat(charts-v2): support duplicate & sql mode * fix(charts-v2): wrong defaultValue of json config * feat(charts-v2): transformer ui * feat(charts-v2): transformer * chore(charts-v2): rename transfromer to transform * feat(charts-v2): support cache * feat(charts-v2): add acl provider * chore(charts-v2): hide sql mode * refactor(charts-v2): add renderer provider * feat: collection permission check * feat(charts-v2): add antd statistic * test(charts-v2): backend * chore: improve code * test(charts-v2): add test * chore: add Chinese translation * fix(charts-v2): locale switch bug * chore: add dependency * feat(charts-v2): init chart config from query * feat: change layout * test: fix frontend test * feat: improve auto infer * fix: ui issues * chore: translation * fix: sql error * fix: some issues * feat: support table * fix: bug * chore: improve code and fix query * feat: add config reference * chore: add translation * fix: process data due to pg issue * test: fix parseBuilder * chore: upgrade formily to 2.2.25 * fix: some issues and import style * fix: bug when query with sort * feat: parse enum data * fix: yarn.lock * fix: type error * fix: infer bug and frontend test * test: fix frontend * fix: test * feat: improve preview * chore: downgrade formily * feat: support associations, draft, in testing * fix: typo * test: frontend & backend * fix: infer bug * feat: measure selection of statistics * fix: bug of group by alias * fix: some issues * fix: order issues * fix: yarn.lock * chore: fix filter include & 'data-visualization' * style: improve style * docs: add readme * chore: add translation --------- Co-authored-by: chenos <chenlinxh@gmail.com>
240 lines
7.1 KiB
TypeScript
240 lines
7.1 KiB
TypeScript
import { ArrayField } from '@formily/core';
|
|
import { ISchema, Schema } from '@formily/react';
|
|
import { useACLRoleContext, useCollectionManager } from '@nocobase/client';
|
|
import { useContext } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { ChartConfigContext } from './block/ChartConfigure';
|
|
import formatters from './block/formatters';
|
|
import transformers from './block/transformers';
|
|
import { lang } from './locale';
|
|
import { ChartRendererProps } from './renderer';
|
|
import { getField, getSelectedFields, parseField } from './utils';
|
|
|
|
export type FieldOption = {
|
|
value: string;
|
|
label: string;
|
|
key: string;
|
|
alias?: string;
|
|
name?: string;
|
|
type?: string;
|
|
interface?: string;
|
|
uiSchema?: ISchema;
|
|
target?: string;
|
|
targetFields?: FieldOption[];
|
|
};
|
|
|
|
export const useFields = (collection?: string) => {
|
|
const { current } = useContext(ChartConfigContext);
|
|
if (!collection) {
|
|
collection = current?.collection || '';
|
|
}
|
|
const { getCollectionFields } = useCollectionManager();
|
|
const fields = (getCollectionFields(collection) || [])
|
|
.filter((field) => {
|
|
return field.interface;
|
|
})
|
|
.map((field) => ({
|
|
...field,
|
|
key: field.name,
|
|
label: field.uiSchema?.title || field.name,
|
|
value: field.name,
|
|
}));
|
|
return fields;
|
|
};
|
|
|
|
export const useFieldsWithAssociation = (collection?: string) => {
|
|
const { getCollectionFields } = useCollectionManager();
|
|
const { t } = useTranslation();
|
|
const fields = useFields(collection);
|
|
return fields.map((field) => {
|
|
const label = Schema.compile(field.uiSchema?.title || field.name, { t });
|
|
if (!field.target) {
|
|
return { ...field, label };
|
|
}
|
|
const targetFields = (getCollectionFields(field.target) || [])
|
|
.filter((targetField) => {
|
|
return targetField.interface;
|
|
})
|
|
.map((targetField) => ({
|
|
...targetField,
|
|
key: `${field.name}.${targetField.name}`,
|
|
label: `${label} / ${Schema.compile(targetField.uiSchema?.title || targetField.name, { t })}`,
|
|
value: `${field.name}.${targetField.name}`,
|
|
}));
|
|
return {
|
|
...field,
|
|
label,
|
|
targetFields,
|
|
};
|
|
});
|
|
};
|
|
|
|
export const useChartFields = (fields: FieldOption[]) => (field: any) => {
|
|
const query = field.query('query').get('value') || {};
|
|
const selectedFields = getSelectedFields(fields, query);
|
|
field.dataSource = selectedFields;
|
|
};
|
|
|
|
export const useFormatters = (fields: FieldOption[]) => (field: any) => {
|
|
const selectedField = field.query('.field').get('value');
|
|
if (!selectedField) {
|
|
field.dataSource = [];
|
|
return;
|
|
}
|
|
let options = [];
|
|
const fieldInterface = getField(fields, selectedField)?.interface;
|
|
switch (fieldInterface) {
|
|
case 'datetime':
|
|
case 'createdAt':
|
|
case 'updatedAt':
|
|
options = formatters.datetime;
|
|
break;
|
|
case 'date':
|
|
options = formatters.date;
|
|
break;
|
|
case 'time':
|
|
options = formatters.time;
|
|
break;
|
|
default:
|
|
options = [];
|
|
}
|
|
field.dataSource = options;
|
|
};
|
|
|
|
export const useCollectionOptions = () => {
|
|
const { t } = useTranslation();
|
|
const { collections } = useCollectionManager();
|
|
const { allowAll, parseAction } = useACLRoleContext();
|
|
const options = collections
|
|
.filter((collection: { name: string }) => {
|
|
if (allowAll) {
|
|
return true;
|
|
}
|
|
const params = parseAction(`${collection.name}:list`);
|
|
return params;
|
|
})
|
|
.map((collection: { name: string; title: string }) => ({
|
|
label: collection.title,
|
|
value: collection.name,
|
|
key: collection.name,
|
|
}));
|
|
return Schema.compile(options, { t });
|
|
};
|
|
|
|
/**
|
|
* useFieldTypes
|
|
* Get field types for using transformers
|
|
* Only supported types will be displayed
|
|
* Some interfaces and types will be mapped to supported types
|
|
*/
|
|
export const useFieldTypes = (fields: FieldOption[]) => (field: any) => {
|
|
const selectedField = field.query('.field').get('value');
|
|
const query = field.query('query').get('value') || {};
|
|
const selectedFields = getSelectedFields(fields, query);
|
|
const fieldProps = selectedFields.find((field) => field.value === selectedField);
|
|
const supports = Object.keys(transformers);
|
|
field.dataSource = supports.map((key) => ({
|
|
label: lang(key),
|
|
value: key,
|
|
}));
|
|
const map = {
|
|
createdAt: 'datetime',
|
|
updatedAt: 'datetime',
|
|
double: 'number',
|
|
integer: 'number',
|
|
percent: 'number',
|
|
};
|
|
const fieldInterface = fieldProps?.interface;
|
|
const fieldType = fieldProps?.type;
|
|
const key = map[fieldInterface] || map[fieldType] || fieldType;
|
|
if (supports.includes(key)) {
|
|
field.setState({
|
|
value: key,
|
|
disabled: true,
|
|
});
|
|
return;
|
|
}
|
|
field.setState({
|
|
value: null,
|
|
disabled: false,
|
|
});
|
|
};
|
|
|
|
export const useTransformers = (field: any) => {
|
|
const selectedType = field.query('.type').get('value');
|
|
if (!selectedType) {
|
|
field.dataSource = [];
|
|
return;
|
|
}
|
|
const options = Object.keys(transformers[selectedType] || {}).map((key) => ({
|
|
label: lang(key),
|
|
value: key,
|
|
}));
|
|
field.dataSource = options;
|
|
};
|
|
|
|
export const useFieldTransformer = (transform: ChartRendererProps['transform'], locale = 'en-US') => {
|
|
return (transform || [])
|
|
.filter((item) => item.field && item.type && item.format)
|
|
.reduce((mp, item) => {
|
|
const transformer = transformers[item.type][item.format];
|
|
if (!transformer) {
|
|
return mp;
|
|
}
|
|
mp[item.field] = (val: any) => transformer(val, locale);
|
|
return mp;
|
|
}, {});
|
|
};
|
|
|
|
export const useOrderFieldsOptions = (defaultOptions: any[], fields: FieldOption[]) => (field: any) => {
|
|
const query = field.query('query').get('value') || {};
|
|
const { measures = [] } = query;
|
|
const hasAgg = measures.some((measure: { aggregation?: string }) => measure.aggregation);
|
|
if (!hasAgg) {
|
|
field.componentProps.fieldNames = {
|
|
label: 'title',
|
|
value: 'name',
|
|
children: 'children',
|
|
};
|
|
field.dataSource = defaultOptions;
|
|
return;
|
|
}
|
|
const selectedFields = getSelectedFields(fields, query);
|
|
field.componentProps.fieldNames = {};
|
|
field.dataSource = selectedFields;
|
|
};
|
|
|
|
export const useOrderReaction = (defaultOptions: any[], fields: FieldOption[]) => (field: ArrayField) => {
|
|
const query = field.query('query').get('value') || {};
|
|
const { measures = [] } = query;
|
|
const hasAgg = measures.some((measure: { aggregation?: string }) => measure.aggregation);
|
|
let dataSource = defaultOptions;
|
|
const allValues = [];
|
|
if (hasAgg) {
|
|
dataSource = getSelectedFields(fields, query);
|
|
dataSource.forEach((field) => {
|
|
allValues.push(field.value);
|
|
});
|
|
} else {
|
|
dataSource.forEach((field) => {
|
|
const children = field.children || [];
|
|
if (!children.length) {
|
|
allValues.push(field.value || field.name);
|
|
}
|
|
children.forEach((child: any) => {
|
|
allValues.push(`${field.name}.${child.name}`);
|
|
});
|
|
});
|
|
}
|
|
|
|
const orders = field.value || [];
|
|
const newOrders = orders.reduce((newOrders: any[], item: any) => {
|
|
const { alias } = parseField(item.field);
|
|
if (!item.field || allValues.includes(alias)) {
|
|
newOrders.push(item);
|
|
}
|
|
return newOrders;
|
|
}, []);
|
|
field.setValue(newOrders);
|
|
};
|