mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat: support group by status in kanban mode, resolved #40
This commit is contained in:
@@ -41,6 +41,7 @@ const getKanbanColor = (
|
|||||||
return DEFAULT_COLOR;
|
return DEFAULT_COLOR;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
group.type === PropertyType.Status ||
|
||||||
group.type === PropertyType.Select ||
|
group.type === PropertyType.Select ||
|
||||||
group.type === PropertyType.MultiSelect ||
|
group.type === PropertyType.MultiSelect ||
|
||||||
group.type === DEFAULT_GROUP_ID
|
group.type === DEFAULT_GROUP_ID
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ModifyPanelContentProps } from './types';
|
|||||||
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
||||||
import { BasicSelect } from './Select';
|
import { BasicSelect } from './Select';
|
||||||
import { InformationProperty, InformationValue } from '../../recast-block';
|
import { InformationProperty, InformationValue } from '../../recast-block';
|
||||||
import { genInitialOptions, getPendantIconsConfigByName } from '../utils';
|
import { generateInitialOptions, getPendantIconsConfigByName } from '../utils';
|
||||||
|
|
||||||
export default (props: ModifyPanelContentProps) => {
|
export default (props: ModifyPanelContentProps) => {
|
||||||
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
||||||
@@ -38,7 +38,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.emailOptions ||
|
propProperty?.emailOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Email')
|
getPendantIconsConfigByName('Email')
|
||||||
)
|
)
|
||||||
@@ -66,7 +66,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.phoneOptions ||
|
propProperty?.phoneOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Phone')
|
getPendantIconsConfigByName('Phone')
|
||||||
)
|
)
|
||||||
@@ -94,7 +94,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.locationOptions ||
|
propProperty?.locationOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Location')
|
getPendantIconsConfigByName('Location')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import { HighLightIconInput } from './IconInput';
|
import { HighLightIconInput } from './IconInput';
|
||||||
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
||||||
import { genBasicOption } from '../utils';
|
import { generateBasicOption } from '../utils';
|
||||||
|
|
||||||
type OptionItemType = {
|
type OptionItemType = {
|
||||||
option: OptionType;
|
option: OptionType;
|
||||||
@@ -66,7 +66,7 @@ export const BasicSelect = ({
|
|||||||
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
||||||
|
|
||||||
const insertOption = (insertId: OptionIdType) => {
|
const insertOption = (insertId: OptionIdType) => {
|
||||||
const newOption = genBasicOption({
|
const newOption = generateBasicOption({
|
||||||
index: options.length + 1,
|
index: options.length + 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
||||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||||
import { AsyncBlock } from '../../editor';
|
import { AsyncBlock } from '../../editor';
|
||||||
@@ -15,13 +14,13 @@ import {
|
|||||||
StyledPopoverSubTitle,
|
StyledPopoverSubTitle,
|
||||||
StyledPopoverWrapper,
|
StyledPopoverWrapper,
|
||||||
} from '../StyledComponent';
|
} from '../StyledComponent';
|
||||||
import { genInitialOptions, getPendantConfigByType } from '../utils';
|
import {
|
||||||
|
generateRandomFieldName,
|
||||||
|
generateInitialOptions,
|
||||||
|
getPendantConfigByType,
|
||||||
|
} from '../utils';
|
||||||
import { useOnCreateSure } from './hooks';
|
import { useOnCreateSure } from './hooks';
|
||||||
|
|
||||||
const upperFirst = (str: string) => {
|
|
||||||
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CreatePendantPanel = ({
|
export const CreatePendantPanel = ({
|
||||||
block,
|
block,
|
||||||
onSure,
|
onSure,
|
||||||
@@ -35,7 +34,7 @@ export const CreatePendantPanel = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedOption &&
|
selectedOption &&
|
||||||
setFieldName(upperFirst(`${selectedOption.type}#${nanoid(4)}`));
|
setFieldName(generateRandomFieldName(selectedOption.type));
|
||||||
}, [selectedOption]);
|
}, [selectedOption]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,7 +92,7 @@ export const CreatePendantPanel = ({
|
|||||||
<PendantModifyPanel
|
<PendantModifyPanel
|
||||||
type={selectedOption.type}
|
type={selectedOption.type}
|
||||||
// Select, MultiSelect, Status use this props as initial property
|
// Select, MultiSelect, Status use this props as initial property
|
||||||
initialOptions={genInitialOptions(
|
initialOptions={generateInitialOptions(
|
||||||
selectedOption.type,
|
selectedOption.type,
|
||||||
getPendantConfigByType(selectedOption.type)
|
getPendantConfigByType(selectedOption.type)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import { OptionIdType, OptionType } from './types';
|
import { OptionIdType, OptionType } from './types';
|
||||||
import { pendantConfig } from './config';
|
import { pendantConfig } from './config';
|
||||||
import { PendantConfig, PendantTypes } from './types';
|
import { PendantConfig, PendantTypes } from './types';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
type Props = {
|
type Props = {
|
||||||
recastBlockId: string;
|
recastBlockId: string;
|
||||||
blockId: string;
|
blockId: string;
|
||||||
@@ -60,7 +61,7 @@ export const getPendantHistory = ({
|
|||||||
return data[recastBlockId] ?? {};
|
return data[recastBlockId] ?? {};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removePropertyValueRecord = ({
|
export const removePendantHistory = ({
|
||||||
recastBlockId,
|
recastBlockId,
|
||||||
propertyId,
|
propertyId,
|
||||||
}: {
|
}: {
|
||||||
@@ -107,7 +108,7 @@ export const getOfficialSelected = ({
|
|||||||
.map(id => {
|
.map(id => {
|
||||||
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
||||||
})
|
})
|
||||||
.filter(index => index != -1);
|
.filter(index => index !== -1);
|
||||||
selectedId = selectedIndex.map((index: number) => {
|
selectedId = selectedIndex.map((index: number) => {
|
||||||
return options[index].id;
|
return options[index].id;
|
||||||
});
|
});
|
||||||
@@ -130,7 +131,7 @@ export const getPendantIconsConfigByName = (
|
|||||||
return pendantConfig[pendantName];
|
return pendantConfig[pendantName];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genBasicOption = ({
|
export const generateBasicOption = ({
|
||||||
index,
|
index,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name = '',
|
name = '',
|
||||||
@@ -159,22 +160,22 @@ export const genBasicOption = ({
|
|||||||
/**
|
/**
|
||||||
* Status Pendant is a Select Pendant built-in some options
|
* Status Pendant is a Select Pendant built-in some options
|
||||||
* **/
|
* **/
|
||||||
export const genInitialOptions = (
|
export const generateInitialOptions = (
|
||||||
type: PendantTypes,
|
type: PendantTypes,
|
||||||
iconConfig: PendantConfig
|
iconConfig: PendantConfig
|
||||||
) => {
|
) => {
|
||||||
if (type === PendantTypes.Status) {
|
if (type === PendantTypes.Status) {
|
||||||
return [
|
return [
|
||||||
genBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
generateBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
||||||
genBasicOption({
|
generateBasicOption({
|
||||||
index: 1,
|
index: 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name: 'In Progress',
|
name: 'In Progress',
|
||||||
}),
|
}),
|
||||||
genBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
generateBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [genBasicOption({ index: 0, iconConfig })];
|
return [generateBasicOption({ index: 0, iconConfig })];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkPendantForm = (
|
export const checkPendantForm = (
|
||||||
@@ -222,3 +223,10 @@ export const checkPendantForm = (
|
|||||||
|
|
||||||
return { passed: true, message: 'Check passed !' };
|
return { passed: true, message: 'Check passed !' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const upperFirst = (str: string) => {
|
||||||
|
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateRandomFieldName = (type: PendantTypes) =>
|
||||||
|
upperFirst(`${type}#${nanoid(4)}`);
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ import {
|
|||||||
} from '../recast-block/types';
|
} from '../recast-block/types';
|
||||||
import type { DefaultGroup, KanbanGroup } from './types';
|
import type { DefaultGroup, KanbanGroup } from './types';
|
||||||
import { DEFAULT_GROUP_ID } from './types';
|
import { DEFAULT_GROUP_ID } from './types';
|
||||||
|
import {
|
||||||
|
generateInitialOptions,
|
||||||
|
generateRandomFieldName,
|
||||||
|
getPendantIconsConfigByName,
|
||||||
|
} from '../block-pendant/utils';
|
||||||
|
import { SelectOption } from '../recast-block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - If the `groupBy` is `SelectProperty` or `MultiSelectProperty`, return `(Multi)SelectProperty.options`.
|
* - If the `groupBy` is `SelectProperty` or `MultiSelectProperty`, return `(Multi)SelectProperty.options`.
|
||||||
@@ -23,6 +29,7 @@ export const getGroupOptions = async (
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
switch (groupBy.type) {
|
switch (groupBy.type) {
|
||||||
|
case PropertyType.Status:
|
||||||
case PropertyType.Select:
|
case PropertyType.Select:
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
return groupBy.options.map(option => ({
|
return groupBy.options.map(option => ({
|
||||||
@@ -57,6 +64,9 @@ const isValueBelongOption = (
|
|||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
return propertyValue.value.some(i => i === option.id);
|
return propertyValue.value.some(i => i === option.id);
|
||||||
}
|
}
|
||||||
|
case PropertyType.Status: {
|
||||||
|
return propertyValue.value === option.id;
|
||||||
|
}
|
||||||
// case PropertyType.Text: {
|
// case PropertyType.Text: {
|
||||||
// TOTODO:DO support this type
|
// TOTODO:DO support this type
|
||||||
// }
|
// }
|
||||||
@@ -107,6 +117,7 @@ export const moveCardToGroup = async (
|
|||||||
success = await removeValue(groupById);
|
success = await removeValue(groupById);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (group.type) {
|
switch (group.type) {
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select: {
|
||||||
success = await setValue({
|
success = await setValue({
|
||||||
@@ -116,6 +127,14 @@ export const moveCardToGroup = async (
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case PropertyType.Status: {
|
||||||
|
success = await setValue({
|
||||||
|
id: groupById,
|
||||||
|
type: group.type,
|
||||||
|
value: group.id,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
success = await setValue({
|
success = await setValue({
|
||||||
id: groupById,
|
id: groupById,
|
||||||
@@ -194,14 +213,18 @@ export const genDefaultGroup = (groupBy: RecastMetaProperty): DefaultGroup => ({
|
|||||||
items: [],
|
items: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DEFAULT_GROUP_BY_PROPERTY = {
|
export const generateDefaultGroupByProperty = (): {
|
||||||
name: 'Status',
|
name: string;
|
||||||
options: [
|
options: Omit<SelectOption, 'id'>[];
|
||||||
{ name: 'No Started', color: '#E53535', background: '#FFCECE' },
|
type: PropertyType.Status;
|
||||||
{ name: 'In Progress', color: '#A77F1A', background: '#FFF5AB' },
|
} => ({
|
||||||
{ name: 'Complete', color: '#3C8867', background: '#C5FBE0' },
|
name: generateRandomFieldName(PropertyType.Status),
|
||||||
],
|
type: PropertyType.Status,
|
||||||
};
|
options: generateInitialOptions(
|
||||||
|
PropertyType.Status,
|
||||||
|
getPendantIconsConfigByName(PropertyType.Status)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unwrap blocks from the grid recursively.
|
* Unwrap blocks from the grid recursively.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const useKanbanGroup = (groupBy: RecastMetaProperty) => {
|
|||||||
const { updateSelect } = useSelectProperty();
|
const { updateSelect } = useSelectProperty();
|
||||||
|
|
||||||
switch (groupBy.type) {
|
switch (groupBy.type) {
|
||||||
|
case PropertyType.Status:
|
||||||
case PropertyType.MultiSelect:
|
case PropertyType.MultiSelect:
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select: {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import {
|
|||||||
import { supportChildren } from '../utils';
|
import { supportChildren } from '../utils';
|
||||||
import {
|
import {
|
||||||
calcCardGroup,
|
calcCardGroup,
|
||||||
DEFAULT_GROUP_BY_PROPERTY,
|
|
||||||
genDefaultGroup,
|
genDefaultGroup,
|
||||||
|
generateDefaultGroupByProperty,
|
||||||
getCardGroup,
|
getCardGroup,
|
||||||
getGroupOptions,
|
getGroupOptions,
|
||||||
moveCardToAfter,
|
moveCardToAfter,
|
||||||
@@ -48,6 +48,7 @@ export const useRecastKanbanGroupBy = () => {
|
|||||||
// Add other type groupBy support
|
// Add other type groupBy support
|
||||||
const supportedGroupBy = getProperties().filter(
|
const supportedGroupBy = getProperties().filter(
|
||||||
prop =>
|
prop =>
|
||||||
|
prop.type === PropertyType.Status ||
|
||||||
prop.type === PropertyType.Select ||
|
prop.type === PropertyType.Select ||
|
||||||
prop.type === PropertyType.MultiSelect
|
prop.type === PropertyType.MultiSelect
|
||||||
);
|
);
|
||||||
@@ -88,7 +89,8 @@ export const useRecastKanbanGroupBy = () => {
|
|||||||
// TODO: support other property type
|
// TODO: support other property type
|
||||||
if (
|
if (
|
||||||
groupByProperty.type !== PropertyType.Select &&
|
groupByProperty.type !== PropertyType.Select &&
|
||||||
groupByProperty.type !== PropertyType.MultiSelect
|
groupByProperty.type !== PropertyType.MultiSelect &&
|
||||||
|
groupByProperty.type !== PropertyType.Status
|
||||||
) {
|
) {
|
||||||
console.warn('Not support groupBy type', groupByProperty);
|
console.warn('Not support groupBy type', groupByProperty);
|
||||||
|
|
||||||
@@ -134,7 +136,7 @@ export const useInitKanbanEffect = ():
|
|||||||
}
|
}
|
||||||
// 3. no group by, no properties
|
// 3. no group by, no properties
|
||||||
// create a new property and set it as group by
|
// create a new property and set it as group by
|
||||||
const prop = await createSelect(DEFAULT_GROUP_BY_PROPERTY);
|
const prop = await createSelect(generateDefaultGroupByProperty());
|
||||||
await setGroupBy(prop.id);
|
await setGroupBy(prop.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ export type DefaultGroup = KanbanGroupBase & {
|
|||||||
|
|
||||||
type SelectGroup = KanbanGroupBase &
|
type SelectGroup = KanbanGroupBase &
|
||||||
SelectOption & {
|
SelectOption & {
|
||||||
type: PropertyType.Select | PropertyType.MultiSelect;
|
type:
|
||||||
|
| PropertyType.Select
|
||||||
|
| PropertyType.MultiSelect
|
||||||
|
| PropertyType.Status;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TextGroup = KanbanGroupBase & {
|
type TextGroup = KanbanGroupBase & {
|
||||||
|
|||||||
@@ -257,14 +257,12 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
|
|||||||
const isSelectLikeProperty = (
|
const isSelectLikeProperty = (
|
||||||
metaProperty?: RecastMetaProperty
|
metaProperty?: RecastMetaProperty
|
||||||
): metaProperty is SelectProperty | MultiSelectProperty => {
|
): metaProperty is SelectProperty | MultiSelectProperty => {
|
||||||
if (
|
return !(
|
||||||
!metaProperty ||
|
!metaProperty ||
|
||||||
(metaProperty.type !== PropertyType.Select &&
|
(metaProperty.type !== PropertyType.Status &&
|
||||||
|
metaProperty.type !== PropertyType.Select &&
|
||||||
metaProperty.type !== PropertyType.MultiSelect)
|
metaProperty.type !== PropertyType.MultiSelect)
|
||||||
) {
|
);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -312,7 +310,7 @@ export const useSelectProperty = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateSelect = (
|
const updateSelect = (
|
||||||
selectProperty: SelectProperty | MultiSelectProperty
|
selectProperty: StatusProperty | SelectProperty | MultiSelectProperty
|
||||||
) => {
|
) => {
|
||||||
// if (typeof selectProperty === 'string') {
|
// if (typeof selectProperty === 'string') {
|
||||||
// const maybeSelectProperty = getProperty(selectProperty);
|
// const maybeSelectProperty = getProperty(selectProperty);
|
||||||
|
|||||||
Reference in New Issue
Block a user