- default status

- type fix
- error helper
This commit is contained in:
Philipinho
2026-06-14 11:28:39 +01:00
parent 4e5bff6d55
commit d4bcc43ec9
18 changed files with 104 additions and 56 deletions
@@ -76,6 +76,24 @@
"Failed to import pages": "Failed to import pages", "Failed to import pages": "Failed to import pages",
"Failed to load page. An error occurred.": "Failed to load page. An error occurred.", "Failed to load page. An error occurred.": "Failed to load page. An error occurred.",
"Failed to update data": "Failed to update data", "Failed to update data": "Failed to update data",
"Failed to create base": "Failed to create base",
"Failed to update base": "Failed to update base",
"Failed to delete base": "Failed to delete base",
"Failed to create property": "Failed to create property",
"Failed to update property": "Failed to update property",
"Failed to delete property": "Failed to delete property",
"Failed to reorder property": "Failed to reorder property",
"Failed to create view": "Failed to create view",
"Failed to update view": "Failed to update view",
"Failed to delete view": "Failed to delete view",
"Failed to create row": "Failed to create row",
"Failed to update row": "Failed to update row",
"Failed to delete row": "Failed to delete row",
"Failed to delete rows": "Failed to delete rows",
"Failed to reorder row": "Failed to reorder row",
"Failed to move card": "Failed to move card",
"Failed to add card": "Failed to add card",
"Failed to export CSV": "Failed to export CSV",
"Favorite spaces": "Favorite spaces", "Favorite spaces": "Favorite spaces",
"Favorite spaces appear here": "Favorite spaces appear here", "Favorite spaces appear here": "Favorite spaces appear here",
"Favorites": "Favorites", "Favorites": "Favorites",
@@ -20,6 +20,7 @@ import {
FilterGroup, FilterGroup,
} from "@/ee/base/types/base.types"; } from "@/ee/base/types/base.types";
import { exportBaseToCsv } from "@/ee/base/services/base-service"; import { exportBaseToCsv } from "@/ee/base/services/base-service";
import { getApiErrorMessage } from "@/lib/api-error";
import { useBaseEditable } from "@/ee/base/context/base-editable"; import { useBaseEditable } from "@/ee/base/context/base-editable";
import { ViewTabs } from "@/ee/base/components/views/view-tabs"; import { ViewTabs } from "@/ee/base/components/views/view-tabs";
import { ViewSortConfigPopover } from "@/ee/base/components/views/view-sort-config"; import { ViewSortConfigPopover } from "@/ee/base/components/views/view-sort-config";
@@ -78,7 +79,7 @@ export function BaseToolbar({
} catch (err) { } catch (err) {
notifications.show({ notifications.show({
color: "red", color: "red",
message: t("Failed to export CSV"), message: getApiErrorMessage(err, t("Failed to export CSV")),
}); });
} finally { } finally {
setExporting(false); setExporting(false);
@@ -4,7 +4,7 @@ import { IconGripVertical, type IconLetterT } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { IBase, IBaseProperty, IBaseView } from "@/ee/base/types/base.types"; import { IBase, IBaseProperty, IBaseView } from "@/ee/base/types/base.types";
import { useUpdateViewMutation } from "@/ee/base/queries/base-view-query"; import { useUpdateViewMutation } from "@/ee/base/queries/base-view-query";
import { propertyTypes } from "@/ee/base/components/property/property-type-picker"; import { propertyTypes } from "@/ee/base/property-types/property-type.registry";
import { BaseDropEdgeIndicator } from "@/ee/base/components/grid/base-drop-edge-indicator"; import { BaseDropEdgeIndicator } from "@/ee/base/components/grid/base-drop-edge-indicator";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import { import {
@@ -18,11 +18,12 @@ import {
TypeOptions, TypeOptions,
} from "@/ee/base/types/base.types"; } from "@/ee/base/types/base.types";
import { useCreatePropertyMutation } from "@/ee/base/queries/base-property-query"; import { useCreatePropertyMutation } from "@/ee/base/queries/base-property-query";
import { PropertyTypePicker, propertyTypes } from "./property-type-picker"; import { PropertyTypePicker } from "./property-type-picker";
import { PropertyOptions } from "./property-options"; import { PropertyOptions } from "./property-options";
import { import {
getDescriptor, getDescriptor,
defaultTypeOptionsFor, defaultTypeOptionsFor,
propertyTypes,
} from "@/ee/base/property-types/property-type.registry"; } from "@/ee/base/property-types/property-type.registry";
import { FormulaEditor } from "../formula/formula-editor"; import { FormulaEditor } from "../formula/formula-editor";
import classes from "@/ee/base/styles/grid.module.css"; import classes from "@/ee/base/styles/grid.module.css";
@@ -28,7 +28,7 @@ import {
useUpdatePropertyMutation, useUpdatePropertyMutation,
useDeletePropertyMutation, useDeletePropertyMutation,
} from "@/ee/base/queries/base-property-query"; } from "@/ee/base/queries/base-property-query";
import { PropertyTypePicker, propertyTypes } from "./property-type-picker"; import { PropertyTypePicker } from "./property-type-picker";
import { PropertyOptions } from "./property-options"; import { PropertyOptions } from "./property-options";
import { import {
conversionWarning, conversionWarning,
@@ -36,7 +36,7 @@ import {
NON_USER_TARGET_TYPES, NON_USER_TARGET_TYPES,
} from "./conversion-warning"; } from "./conversion-warning";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { isSystemPropertyType } from "@/ee/base/property-types/property-type.registry"; import { isSystemPropertyType, propertyTypes } from "@/ee/base/property-types/property-type.registry";
import cellClasses from "@/ee/base/styles/cells.module.css"; import cellClasses from "@/ee/base/styles/cells.module.css";
import classes from "@/ee/base/styles/property.module.css"; import classes from "@/ee/base/styles/property.module.css";
@@ -1,19 +1,11 @@
import { UnstyledButton, Group, Text, TextInput } from "@mantine/core"; import { UnstyledButton, Group, Text, TextInput } from "@mantine/core";
import { IconCheck, IconSearch } from "@tabler/icons-react"; import { IconCheck, IconSearch } from "@tabler/icons-react";
import { BasePropertyType } from "@/ee/base/types/base.types"; import { BasePropertyType } from "@/ee/base/types/base.types";
import { import { propertyTypes } from "@/ee/base/property-types/property-type.registry";
PROPERTY_PICKER_ORDER,
getDescriptor,
} from "@/ee/base/property-types/property-type.registry";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import classes from "@/ee/base/styles/cells.module.css"; import classes from "@/ee/base/styles/cells.module.css";
const propertyTypes = PROPERTY_PICKER_ORDER.map((type) => {
const d = getDescriptor(type)!;
return { type, icon: d.icon, labelKey: d.labelKey };
});
type PropertyTypePickerProps = { type PropertyTypePickerProps = {
onSelect: (type: BasePropertyType) => void; onSelect: (type: BasePropertyType) => void;
currentType?: BasePropertyType; currentType?: BasePropertyType;
@@ -77,5 +69,3 @@ export function PropertyTypePicker({
</> </>
); );
} }
export { propertyTypes };
@@ -292,6 +292,7 @@ export function RowDetailModal({
row={row} row={row}
primaryProperty={primaryProperty} primaryProperty={primaryProperty}
canEdit={canEdit} canEdit={canEdit}
onClose={onClose}
onCommit={(value) => { onCommit={(value) => {
if (!primaryProperty) return; if (!primaryProperty) return;
updateRowMutation.mutate({ updateRowMutation.mutate({
@@ -9,6 +9,7 @@ type RowDetailTitleProps = {
primaryProperty: IBaseProperty | undefined; primaryProperty: IBaseProperty | undefined;
canEdit: boolean; canEdit: boolean;
onCommit: (value: string) => void; onCommit: (value: string) => void;
onClose: () => void;
}; };
export function RowDetailTitle({ export function RowDetailTitle({
@@ -16,6 +17,7 @@ export function RowDetailTitle({
primaryProperty, primaryProperty,
canEdit, canEdit,
onCommit, onCommit,
onClose,
}: RowDetailTitleProps) { }: RowDetailTitleProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const initial = primaryProperty const initial = primaryProperty
@@ -57,6 +59,10 @@ export function RowDetailTitle({
if (e.key === "Enter") { if (e.key === "Enter") {
e.preventDefault(); e.preventDefault();
(e.currentTarget as HTMLInputElement).blur(); (e.currentTarget as HTMLInputElement).blur();
} else if (e.key === "Escape") {
e.preventDefault();
(e.currentTarget as HTMLInputElement).blur();
onClose();
} }
}} }}
/> />
@@ -2,7 +2,7 @@ import { useMemo, useCallback } from "react";
import { Popover, Switch, Stack, Text, Group, Divider, UnstyledButton } from "@mantine/core"; import { Popover, Switch, Stack, Text, Group, Divider, UnstyledButton } from "@mantine/core";
import { Table } from "@tanstack/react-table"; import { Table } from "@tanstack/react-table";
import { IBaseRow, IBaseProperty } from "@/ee/base/types/base.types"; import { IBaseRow, IBaseProperty } from "@/ee/base/types/base.types";
import { propertyTypes } from "@/ee/base/components/property/property-type-picker"; import { propertyTypes } from "@/ee/base/property-types/property-type.registry";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import cellClasses from "@/ee/base/styles/cells.module.css"; import cellClasses from "@/ee/base/styles/cells.module.css";
import viewClasses from "@/ee/base/styles/views.module.css"; import viewClasses from "@/ee/base/styles/views.module.css";
@@ -96,7 +96,11 @@ export const PROPERTY_TYPE_REGISTRY: Record<
hasOptions: true, hasOptions: true,
defaultTypeOptions: () => { defaultTypeOptions: () => {
const choices = defaultStatusChoices(); const choices = defaultStatusChoices();
return { choices, choiceOrder: choices.map((c) => c.id) }; return {
choices,
choiceOrder: choices.map((c) => c.id),
defaultValue: choices[0].id,
};
}, },
}, },
multiSelect: { multiSelect: {
@@ -244,6 +248,11 @@ export const PROPERTY_PICKER_ORDER: BasePropertyType[] = [
"createdAt", "lastEditedAt", "lastEditedBy", "createdAt", "lastEditedAt", "lastEditedBy",
]; ];
export const propertyTypes = PROPERTY_PICKER_ORDER.map((type) => {
const d = getDescriptor(type)!;
return { type, icon: d.icon, labelKey: d.labelKey };
});
export function systemAccessorFor(type: string) { export function systemAccessorFor(type: string) {
return getDescriptor(type)?.systemAccessor; return getDescriptor(type)?.systemAccessor;
} }
@@ -18,6 +18,7 @@ import {
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { queryClient } from "@/main"; import { queryClient } from "@/main";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getApiErrorMessage } from "@/lib/api-error";
import { IPagination } from "@/lib/types"; import { IPagination } from "@/lib/types";
export function useCreatePropertyMutation() { export function useCreatePropertyMutation() {
@@ -36,9 +37,9 @@ export function useCreatePropertyMutation() {
}, },
); );
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to create property"), message: getApiErrorMessage(error, t("Failed to create property")),
color: "red", color: "red",
}); });
}, },
@@ -69,9 +70,9 @@ export function useUpdatePropertyMutation() {
}); });
} }
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to update property"), message: getApiErrorMessage(error, t("Failed to update property")),
color: "red", color: "red",
}); });
}, },
@@ -114,9 +115,9 @@ export function useDeletePropertyMutation() {
}, },
); );
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to delete property"), message: getApiErrorMessage(error, t("Failed to delete property")),
color: "red", color: "red",
}); });
}, },
@@ -154,7 +155,7 @@ export function useReorderPropertyMutation() {
return { previous }; return { previous };
}, },
onError: (_, variables, context) => { onError: (error, variables, context) => {
if (context?.previous) { if (context?.previous) {
queryClient.setQueryData( queryClient.setQueryData(
["bases", variables.pageId], ["bases", variables.pageId],
@@ -162,7 +163,7 @@ export function useReorderPropertyMutation() {
); );
} }
notifications.show({ notifications.show({
message: t("Failed to reorder property"), message: getApiErrorMessage(error, t("Failed to reorder property")),
color: "red", color: "red",
}); });
}, },
@@ -18,6 +18,7 @@ import {
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { queryClient } from "@/main"; import { queryClient } from "@/main";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getApiErrorMessage } from "@/lib/api-error";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom";
import { treeModel } from "@/features/page/tree/model/tree-model"; import { treeModel } from "@/features/page/tree/model/tree-model";
@@ -44,9 +45,9 @@ export function useCreateBaseMutation() {
queryKey: ["bases", "list", data.spaceId], queryKey: ["bases", "list", data.spaceId],
}); });
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to create base"), message: getApiErrorMessage(error, t("Failed to create base")),
color: "red", color: "red",
}); });
}, },
@@ -77,9 +78,9 @@ export function useConvertPageToBaseMutation() {
payload: { isBase: true }, payload: { isBase: true },
}); });
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to create base"), message: getApiErrorMessage(error, t("Failed to create base")),
color: "red", color: "red",
}); });
}, },
@@ -96,9 +97,9 @@ export function useUpdateBaseMutation() {
return { ...old, ...data }; return { ...old, ...data };
}); });
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to update base"), message: getApiErrorMessage(error, t("Failed to update base")),
color: "red", color: "red",
}); });
}, },
@@ -116,9 +117,9 @@ export function useDeleteBaseMutation() {
}); });
notifications.show({ message: t("Base deleted") }); notifications.show({ message: t("Base deleted") });
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to delete base"), message: getApiErrorMessage(error, t("Failed to delete base")),
color: "red", color: "red",
}); });
}, },
@@ -31,6 +31,7 @@ import {
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { queryClient } from "@/main"; import { queryClient } from "@/main";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getApiErrorMessage } from "@/lib/api-error";
import { useHydrateReferences } from "@/ee/base/reference/reference-store"; import { useHydrateReferences } from "@/ee/base/reference/reference-store";
import { markRequestIdOutbound } from "@/ee/base/hooks/use-base-socket"; import { markRequestIdOutbound } from "@/ee/base/hooks/use-base-socket";
import { v7 as uuid7 } from "uuid"; import { v7 as uuid7 } from "uuid";
@@ -149,9 +150,9 @@ export function useCreateRowMutation() {
invalidateBaseRows(newRow.pageId); invalidateBaseRows(newRow.pageId);
} }
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to create row"), message: getApiErrorMessage(error, t("Failed to create row")),
color: "red", color: "red",
}); });
}, },
@@ -221,7 +222,7 @@ export function useUpdateRowMutation() {
return { snapshots }; return { snapshots };
}, },
onError: (_, variables, context) => { onError: (error, variables, context) => {
if (context?.snapshots) { if (context?.snapshots) {
for (const [key, data] of context.snapshots) { for (const [key, data] of context.snapshots) {
queryClient.setQueryData(key, data); queryClient.setQueryData(key, data);
@@ -231,7 +232,7 @@ export function useUpdateRowMutation() {
queryKey: ["base-row", variables.pageId, variables.rowId], queryKey: ["base-row", variables.pageId, variables.rowId],
}); });
notifications.show({ notifications.show({
message: t("Failed to update row"), message: getApiErrorMessage(error, t("Failed to update row")),
color: "red", color: "red",
}); });
}, },
@@ -305,14 +306,14 @@ export function useDeleteRowMutation() {
return { snapshots }; return { snapshots };
}, },
onError: (_, variables, context) => { onError: (error, variables, context) => {
if (context?.snapshots) { if (context?.snapshots) {
for (const [key, data] of context.snapshots) { for (const [key, data] of context.snapshots) {
queryClient.setQueryData(key, data); queryClient.setQueryData(key, data);
} }
} }
notifications.show({ notifications.show({
message: t("Failed to delete row"), message: getApiErrorMessage(error, t("Failed to delete row")),
color: "red", color: "red",
}); });
}, },
@@ -349,14 +350,14 @@ export function useDeleteRowsMutation() {
return { snapshots }; return { snapshots };
}, },
onError: (_, __, context) => { onError: (error, __, context) => {
if (context?.snapshots) { if (context?.snapshots) {
for (const [key, data] of context.snapshots) { for (const [key, data] of context.snapshots) {
queryClient.setQueryData(key, data); queryClient.setQueryData(key, data);
} }
} }
notifications.show({ notifications.show({
message: t("Failed to delete rows"), message: getApiErrorMessage(error, t("Failed to delete rows")),
color: "red", color: "red",
}); });
}, },
@@ -399,14 +400,14 @@ export function useReorderRowMutation() {
return { snapshots }; return { snapshots };
}, },
onError: (_, variables, context) => { onError: (error, variables, context) => {
if (context?.snapshots) { if (context?.snapshots) {
for (const [key, data] of context.snapshots) { for (const [key, data] of context.snapshots) {
queryClient.setQueryData(key, data); queryClient.setQueryData(key, data);
} }
} }
notifications.show({ notifications.show({
message: t("Failed to reorder row"), message: getApiErrorMessage(error, t("Failed to reorder row")),
color: "red", color: "red",
}); });
}, },
@@ -504,14 +505,14 @@ export function useKanbanMoveCardMutation() {
return { snapshots }; return { snapshots };
}, },
onError: (_, __, context) => { onError: (error, __, context) => {
if (context?.snapshots) { if (context?.snapshots) {
for (const [key, data] of context.snapshots) { for (const [key, data] of context.snapshots) {
queryClient.setQueryData(key, data); queryClient.setQueryData(key, data);
} }
} }
notifications.show({ notifications.show({
message: t("Failed to move card"), message: getApiErrorMessage(error, t("Failed to move card")),
color: "red", color: "red",
}); });
}, },
@@ -552,9 +553,9 @@ export function useKanbanCreateCardMutation() {
}; };
}); });
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to add card"), message: getApiErrorMessage(error, t("Failed to add card")),
color: "red", color: "red",
}); });
}, },
@@ -28,6 +28,7 @@ function applyConfigPatch(
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { queryClient } from "@/main"; import { queryClient } from "@/main";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getApiErrorMessage } from "@/lib/api-error";
export function useCreateViewMutation() { export function useCreateViewMutation() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -45,9 +46,9 @@ export function useCreateViewMutation() {
}, },
); );
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to create view"), message: getApiErrorMessage(error, t("Failed to create view")),
color: "red", color: "red",
}); });
}, },
@@ -99,7 +100,7 @@ export function useUpdateViewMutation() {
return { previous }; return { previous };
}, },
onError: (_, variables, context) => { onError: (error, variables, context) => {
if (context?.previous) { if (context?.previous) {
queryClient.setQueryData( queryClient.setQueryData(
["bases", variables.pageId], ["bases", variables.pageId],
@@ -107,7 +108,7 @@ export function useUpdateViewMutation() {
); );
} }
notifications.show({ notifications.show({
message: t("Failed to update view"), message: getApiErrorMessage(error, t("Failed to update view")),
color: "red", color: "red",
}); });
}, },
@@ -144,9 +145,9 @@ export function useDeleteViewMutation() {
}, },
); );
}, },
onError: () => { onError: (error) => {
notifications.show({ notifications.show({
message: t("Failed to delete view"), message: getApiErrorMessage(error, t("Failed to delete view")),
color: "red", color: "red",
}); });
}, },
@@ -447,7 +447,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
{ {
title: "Kanban", title: "Kanban",
description: "Insert a kanban board on this page", description: "Insert a kanban board on this page",
searchTerms: ["kanban", "board", "cards", "status", "task"], searchTerms: ["kanban", "board", "cards", "status", "task", "database"],
icon: IconLayoutKanban, icon: IconLayoutKanban,
command: async ({ editor, range }: CommandProps) => { command: async ({ editor, range }: CommandProps) => {
// @ts-ignore // @ts-ignore
+17
View File
@@ -0,0 +1,17 @@
import { isAxiosError } from "axios";
export function getApiErrorMessage(
error: unknown,
fallback = "An error occurred",
): string {
if (isAxiosError(error)) {
const message = error.response?.data?.message;
if (Array.isArray(message)) {
const joined = message.filter(Boolean).join(", ");
if (joined) return joined;
} else if (typeof message === "string" && message.trim()) {
return message;
}
}
return fallback;
}
+1
View File
@@ -1 +1,2 @@
export * from "./utils"; export * from "./utils";
export * from "./api-error";