Merge branch 'main' into chore/fix-publish-relation

This commit is contained in:
Nathan
2025-10-31 07:53:35 +08:00
15 changed files with 119 additions and 64 deletions

View File

@@ -107,9 +107,7 @@ export function getCellDataText(cell: YDatabaseCell, field: YDatabaseField, curr
return (
data
.split(',')
.map((item) => {
return options?.find((option) => option.id === item)?.name;
})
.map((item) => options?.find((option) => option?.id === item)?.name)
.filter((item) => item)
.join(',') || ''
);

View File

@@ -187,13 +187,19 @@ function generateGroupByField(field: YDatabaseField) {
case FieldType.MultiSelect: {
group.set(YjsDatabaseKey.content, '');
const typeOption = parseSelectOptionTypeOptions(field);
const options = typeOption?.options || [];
const options = (typeOption?.options || []).filter((option) => Boolean(option && option.id));
columns.push([{ id: fieldId, visible: true }]);
// Add a column for each option
options.forEach((option) => {
columns.push([{ id: option.id, visible: true }]);
const optionId = option?.id;
if (!optionId) {
return;
}
columns.push([{ id: optionId, visible: true }]);
});
break;
}
@@ -2678,13 +2684,20 @@ export function useSwitchPropertyType() {
// 1. to RichText
if ([FieldType.RichText, FieldType.URL].includes(fieldType)) {
const cellType = Number(cell.get(YjsDatabaseKey.field_type));
const typeOption = field.get(YjsDatabaseKey.type_option)?.get(String(cellType));
const existingTypeOption = field
.get(YjsDatabaseKey.type_option)
?.get(String(cellType)) as YMapFieldTypeOption | undefined;
switch (cellType) {
// From Number to RichText, keep the number format value
case FieldType.Number: {
const formatRaw = existingTypeOption?.get(YjsDatabaseKey.format);
const parsedFormat =
formatRaw === undefined || formatRaw === null ? undefined : Number(formatRaw);
const format =
(Number(typeOption.get(YjsDatabaseKey.format)) as NumberFormat) ?? NumberFormat.Num;
parsedFormat === undefined || Number.isNaN(parsedFormat)
? NumberFormat.Num
: (parsedFormat as NumberFormat);
if (data) {
newData = EnhancedBigStats.parse(data.toString(), format) || '';
@@ -2696,8 +2709,13 @@ export function useSwitchPropertyType() {
case FieldType.SingleSelect:
case FieldType.MultiSelect: {
const selectedIds = (data as string).split(',');
const typeOption = typeOptionMap.get(String(cellType));
const content = typeOption.get(YjsDatabaseKey.content);
const optionSource = typeOptionMap?.get(String(cellType)) as YMapFieldTypeOption | undefined;
const content = optionSource?.get(YjsDatabaseKey.content);
if (typeof content !== 'string') {
newData = '';
break;
}
try {
const parsedContent = JSON.parse(content) as SelectTypeOption;
@@ -2766,9 +2784,12 @@ export function useSwitchPropertyType() {
// 3. to SingleSelect or MultiSelect
if ([FieldType.SingleSelect, FieldType.MultiSelect].includes(fieldType)) {
const typeOption = typeOptionMap.get(String(fieldType));
const content = typeOption.get(YjsDatabaseKey.content);
const targetTypeOption = typeOptionMap?.get(String(fieldType)) as
| YMapFieldTypeOption
| undefined;
const content = targetTypeOption?.get(YjsDatabaseKey.content);
if (typeof content === 'string') {
try {
const parsedContent = JSON.parse(content) as SelectTypeOption;
const options = parsedContent.options;
@@ -2790,6 +2811,9 @@ export function useSwitchPropertyType() {
} catch (e) {
// do nothing
}
} else {
newData = '';
}
}
// 4. to DateTime
@@ -2996,12 +3020,14 @@ export function useAddSelectOption(fieldId: string) {
if (group) {
const columns = group.get(YjsDatabaseKey.groups);
const column = columns.toArray().find((col) => col.id === option.id);
const optionId = option.id;
const column = columns.toArray().find((col) => col.id === optionId);
if (!column) {
columns.push([
{
id: option.id,
id: optionId,
visible: true,
},
]);

View File

@@ -7,6 +7,10 @@ export interface ChecklistCellData {
percentage: number;
}
function normalizeChecklistOptions(options: SelectOption[] = []) {
return options.filter((option): option is SelectOption => Boolean(option && option.id));
}
export function parseChecklistData(data: string): ChecklistCellData | null {
try {
const { options, selected_option_ids } = JSON.parse(data);
@@ -39,13 +43,14 @@ export function addTask(data: string, taskName: string): string {
}
const { options = [], selectedOptionIds } = parsedData;
const normalizedOptions = normalizeChecklistOptions(options);
if (options.find((option) => option.id === task.id)) {
if (normalizedOptions.find((option) => option.id === task.id)) {
return data;
}
return JSON.stringify({
options: [...options, task],
options: [...normalizedOptions, task],
selected_option_ids: selectedOptionIds,
});
}
@@ -58,6 +63,7 @@ export function toggleSelectedTask(data: string, taskId: string): string {
}
const { options, selectedOptionIds = [] } = parsedData;
const normalizedOptions = normalizeChecklistOptions(options);
const isSelected = selectedOptionIds.includes(taskId);
const newSelectedOptionIds = isSelected
@@ -65,7 +71,7 @@ export function toggleSelectedTask(data: string, taskId: string): string {
: [...selectedOptionIds, taskId];
return JSON.stringify({
options,
options: normalizedOptions,
selected_option_ids: newSelectedOptionIds,
});
}
@@ -78,8 +84,9 @@ export function updateTask(data: string, taskId: string, taskName: string): stri
}
const { options = [], selectedOptionIds } = parsedData;
const normalizedOptions = normalizeChecklistOptions(options);
const newOptions = options.map((option) => {
const newOptions = normalizedOptions.map((option) => {
if (option.id === taskId) {
return {
...option,
@@ -104,8 +111,9 @@ export function removeTask(data: string, taskId: string): string {
}
const { options = [], selectedOptionIds = [] } = parsedData;
const normalizedOptions = normalizeChecklistOptions(options);
const newOptions = options.filter((option) => option.id !== taskId);
const newOptions = normalizedOptions.filter((option) => option.id !== taskId);
const newSelectedOptionIds = selectedOptionIds.filter((id) => id !== taskId);
return JSON.stringify({
@@ -122,15 +130,16 @@ export function reorderTasks(data: string, { beforeId, taskId }: { beforeId?: st
}
const { selectedOptionIds, options = [] } = parsedData;
const normalizedOptions = normalizeChecklistOptions(options);
const index = options.findIndex((opt) => opt.id === taskId);
const option = options[index];
const index = normalizedOptions.findIndex((opt) => opt.id === taskId);
const option = normalizedOptions[index];
if (index === -1) {
return data;
}
const newOptions = [...options];
const newOptions = [...normalizedOptions];
const beforeIndex = newOptions.findIndex((opt) => opt.id === beforeId);
if (beforeIndex === index) {

View File

@@ -27,7 +27,7 @@ export function parseSelectOptionCellData(field: YDatabaseField, data: string) {
return selectedIds
.map((id) => {
const option = typeOption?.options?.find((option) => option.id === id);
const option = typeOption?.options?.find((option) => option?.id === id);
return option?.name ?? '';
})

View File

@@ -1,7 +1,7 @@
import { YDatabaseField, YjsDatabaseKey } from '@/application/types';
import { YDatabaseField, YMapFieldTypeOption, YjsDatabaseKey } from '@/application/types';
import { FieldType } from '@/application/database-yjs';
export function getTypeOptions (field: YDatabaseField) {
export function getTypeOptions(field?: YDatabaseField): YMapFieldTypeOption | undefined {
const fieldType = Number(field?.get(YjsDatabaseKey.type)) as FieldType;
return field?.get(YjsDatabaseKey.type_option)?.get(String(fieldType));

View File

@@ -41,9 +41,11 @@ export function getGroupColumns(field: YDatabaseField) {
return [{ id: field.get(YjsDatabaseKey.id) }];
}
const options = typeOption.options.map((option) => ({
id: option.id,
}));
const options = typeOption.options
.map((option) => ({
id: option?.id,
}))
.filter((option): option is { id: string } => Boolean(option.id));
return [{ id: field.get(YjsDatabaseKey.id) }, ...options];
}
@@ -114,7 +116,11 @@ export function groupBySelectOption(
}
typeOption.options.forEach((option) => {
const groupName = option.id;
const groupName = option?.id;
if (!groupName) {
return;
}
if (filter) {
const condition = Number(filter?.get(YjsDatabaseKey.condition)) as SelectOptionFilterCondition;
@@ -156,7 +162,7 @@ export function groupBySelectOption(
}
selectedIds.forEach((id) => {
const option = typeOption.options.find((option) => option.id === id);
const option = typeOption.options.find((option) => option?.id === id);
const groupName = option?.id ?? fieldId;
if (!result.has(groupName)) {

View File

@@ -1294,7 +1294,11 @@ export const useSelectFieldOptions = (fieldId: string, searchValue?: string) =>
if (!typeOption) return [] as SelectOption[];
return typeOption.options.filter((option) => {
const normalizedOptions = typeOption.options.filter((option): option is SelectOption => {
return Boolean(option && option.id && option.name);
});
return normalizedOptions.filter((option) => {
if (!searchValue) return true;
return option.name.toLowerCase().includes(searchValue.toLowerCase());
});

View File

@@ -30,7 +30,7 @@ function ColumnRename({
const option = useMemo(() => {
if (!field || ![FieldType.SingleSelect, FieldType.MultiSelect].includes(fieldType)) return;
return parseSelectOptionTypeOptions(field)?.options.find((option) => option.id === id);
return parseSelectOptionTypeOptions(field)?.options.find((option) => option?.id === id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [field, clock, id]);

View File

@@ -35,7 +35,7 @@ export function useRenderColumn(id: string, fieldId: string) {
</div>
);
if ([FieldType.SingleSelect, FieldType.MultiSelect].includes(fieldType)) {
const option = parseSelectOptionTypeOptions(field)?.options.find((option) => option.id === id);
const option = parseSelectOptionTypeOptions(field)?.options.find((option) => option?.id === id);
const isFieldId = fieldId === id;
const label = isFieldId ? `${t('button.no')} ${fieldName}` : option?.name || '';

View File

@@ -37,7 +37,7 @@ export function SelectOptionCell({
const renderSelectedOptions = useCallback(
(selected: string[]) =>
selected.map((id) => {
const option = typeOption?.options?.find((option) => option.id === id);
const option = typeOption?.options?.find((option) => option?.id === id);
if (!option) return null;
return (

View File

@@ -79,7 +79,7 @@ function SelectOptionCellMenu ({ open, onOpenChange, fieldId, rowId, selectOptio
if (!typeOption) return [];
return selectOptionIds.map((id) => {
const option = typeOption.options?.find((option) => option.id === id);
const option = typeOption.options?.find((option) => option?.id === id);
if (!option) return null;
return {
@@ -151,6 +151,7 @@ function SelectOptionCellMenu ({ open, onOpenChange, fieldId, rowId, selectOptio
const lastOption = options[options.length - 1];
if (hoveredId === 'create') {
if (!lastOption) return;
setHoveredId(lastOption.id);
return;
}
@@ -167,7 +168,11 @@ function SelectOptionCellMenu ({ open, onOpenChange, fieldId, rowId, selectOptio
return;
}
const nextHoveredId = options[hoveredIndex - 1].id;
const previousOption = options[hoveredIndex - 1];
if (!previousOption) return;
const nextHoveredId = previousOption.id;
setHoveredId(nextHoveredId);
@@ -181,6 +186,7 @@ function SelectOptionCellMenu ({ open, onOpenChange, fieldId, rowId, selectOptio
const firstOption = options[0];
if (hoveredId === 'create') {
if (!firstOption) return;
setHoveredId(firstOption.id);
return;
}
@@ -197,7 +203,11 @@ function SelectOptionCellMenu ({ open, onOpenChange, fieldId, rowId, selectOptio
return;
}
const nextHoveredId = options[hoveredIndex + 1].id;
const nextOption = options[hoveredIndex + 1];
if (!nextOption) return;
const nextHoveredId = nextOption.id;
setHoveredId(nextHoveredId);
}, [createdShow, options]);

View File

@@ -48,5 +48,9 @@ export function SelectOptionList({
);
if (!field || !typeOption) return null;
return <div className={'flex flex-col'}>{typeOption.options.map(renderOption)}</div>;
const normalizedOptions = typeOption.options.filter((option): option is SelectOption => {
return Boolean(option && option.id && option.name);
});
return <div className={'flex flex-col'}>{normalizedOptions.map(renderOption)}</div>;
}

View File

@@ -16,7 +16,7 @@ function SelectFilterContentOverview({ filter, field }: { filter: SelectOptionFi
const options = filter.optionIds
.map((optionId) => {
const option = typeOption?.options?.find((option) => option.id === optionId);
const option = typeOption?.options?.find((option) => option?.id === optionId);
return option?.name;
})

View File

@@ -22,7 +22,8 @@ function DataTimePropertyMenuContent({
const { t } = useTranslation();
const typeOption = useFieldTypeOption(fieldId);
const includeTime = Boolean(typeOption.get(YjsDatabaseKey.include_time));
const includeTimeRaw = typeOption?.get(YjsDatabaseKey.include_time);
const includeTime = typeof includeTimeRaw === 'boolean' ? includeTimeRaw : Boolean(includeTimeRaw);
const updateFormat = useUpdateDateTimeFieldFormat(fieldId);

View File

@@ -1,5 +1,6 @@
import { getFieldDateTimeFormats } from '@/application/database-yjs';
import { useUpdateDateTimeFieldFormat } from '@/application/database-yjs/dispatch';
import { DateFormat, TimeFormat, YjsDatabaseKey } from '@/application/types';
import { DateFormat, TimeFormat } from '@/application/types';
import { useFieldTypeOption } from '@/components/database/components/cell/Cell.hooks';
import {
DropdownMenuGroup, DropdownMenuItem, DropdownMenuItemTick,
@@ -17,11 +18,7 @@ function DateTimeFormatGroup ({
}) {
const { t } = useTranslation();
const typeOption = useFieldTypeOption(fieldId);
const typeOptionDateFormat = typeOption.get(YjsDatabaseKey.date_format);
const typeOptionTimeFormat = typeOption.get(YjsDatabaseKey.time_format);
const selectedDateFormat = typeOptionDateFormat !== undefined ? Number(typeOptionDateFormat) : undefined;
const selectedTimeFormat = typeOptionTimeFormat !== undefined ? Number(typeOptionTimeFormat) : undefined;
const { dateFormat: selectedDateFormat, timeFormat: selectedTimeFormat } = getFieldDateTimeFormats(typeOption, undefined);
const updateFormat = useUpdateDateTimeFieldFormat(fieldId);
const dateFormats = useMemo(() => [{