mirror of
https://github.com/docmost/docmost.git
synced 2026-08-28 17:27:06 +08:00
feat(bases): render editable property rows inside detail modal
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
import { IBaseProperty } from "@/features/base/types/base.types";
|
||||||
|
import { CellText } from "./cell-text";
|
||||||
|
import { CellNumber } from "./cell-number";
|
||||||
|
import { CellSelect } from "./cell-select";
|
||||||
|
import { CellStatus } from "./cell-status";
|
||||||
|
import { CellMultiSelect } from "./cell-multi-select";
|
||||||
|
import { CellDate } from "./cell-date";
|
||||||
|
import { CellCheckbox } from "./cell-checkbox";
|
||||||
|
import { CellUrl } from "./cell-url";
|
||||||
|
import { CellEmail } from "./cell-email";
|
||||||
|
import { CellPerson } from "./cell-person";
|
||||||
|
import { CellFile } from "./cell-file";
|
||||||
|
import { CellPage } from "./cell-page";
|
||||||
|
import { CellCreatedAt } from "./cell-created-at";
|
||||||
|
import { CellLastEditedAt } from "./cell-last-edited-at";
|
||||||
|
import { CellLastEditedBy } from "./cell-last-edited-by";
|
||||||
|
import { CellFormula } from "./cell-formula";
|
||||||
|
|
||||||
|
export type CellComponentProps = {
|
||||||
|
value: unknown;
|
||||||
|
property: IBaseProperty;
|
||||||
|
rowId: string;
|
||||||
|
isEditing: boolean;
|
||||||
|
onCommit: (value: unknown) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cellComponents: Record<
|
||||||
|
string,
|
||||||
|
React.ComponentType<CellComponentProps>
|
||||||
|
> = {
|
||||||
|
text: CellText,
|
||||||
|
number: CellNumber,
|
||||||
|
select: CellSelect,
|
||||||
|
status: CellStatus,
|
||||||
|
multiSelect: CellMultiSelect,
|
||||||
|
date: CellDate,
|
||||||
|
checkbox: CellCheckbox,
|
||||||
|
url: CellUrl,
|
||||||
|
email: CellEmail,
|
||||||
|
person: CellPerson,
|
||||||
|
file: CellFile,
|
||||||
|
page: CellPage,
|
||||||
|
createdAt: CellCreatedAt,
|
||||||
|
lastEditedAt: CellLastEditedAt,
|
||||||
|
lastEditedBy: CellLastEditedBy,
|
||||||
|
formula: CellFormula,
|
||||||
|
};
|
||||||
|
|
||||||
|
type CellRendererProps = CellComponentProps;
|
||||||
|
|
||||||
|
export function CellRenderer(props: CellRendererProps) {
|
||||||
|
const Component = cellComponents[props.property.type];
|
||||||
|
if (!Component) return null;
|
||||||
|
return <Component {...props} />;
|
||||||
|
}
|
||||||
@@ -1,59 +1,13 @@
|
|||||||
import { memo, useCallback } from "react";
|
import { memo, useCallback } from "react";
|
||||||
import { Cell } from "@tanstack/react-table";
|
import { Cell } from "@tanstack/react-table";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { IBaseRow, IBaseProperty, EditingCell } from "@/features/base/types/base.types";
|
import { IBaseRow, EditingCell } from "@/features/base/types/base.types";
|
||||||
import { editingCellAtomFamily } from "@/features/base/atoms/base-atoms";
|
import { editingCellAtomFamily } from "@/features/base/atoms/base-atoms";
|
||||||
import { isSystemPropertyType } from "@/features/base/hooks/use-base-table";
|
import { isSystemPropertyType } from "@/features/base/hooks/use-base-table";
|
||||||
import { CellText } from "@/features/base/components/cells/cell-text";
|
import { cellComponents } from "@/features/base/components/cells/cell-renderer";
|
||||||
import { CellNumber } from "@/features/base/components/cells/cell-number";
|
|
||||||
import { CellSelect } from "@/features/base/components/cells/cell-select";
|
|
||||||
import { CellStatus } from "@/features/base/components/cells/cell-status";
|
|
||||||
import { CellMultiSelect } from "@/features/base/components/cells/cell-multi-select";
|
|
||||||
import { CellDate } from "@/features/base/components/cells/cell-date";
|
|
||||||
import { CellCheckbox } from "@/features/base/components/cells/cell-checkbox";
|
|
||||||
import { CellUrl } from "@/features/base/components/cells/cell-url";
|
|
||||||
import { CellEmail } from "@/features/base/components/cells/cell-email";
|
|
||||||
import { CellPerson } from "@/features/base/components/cells/cell-person";
|
|
||||||
import { CellFile } from "@/features/base/components/cells/cell-file";
|
|
||||||
import { CellPage } from "@/features/base/components/cells/cell-page";
|
|
||||||
import { CellCreatedAt } from "@/features/base/components/cells/cell-created-at";
|
|
||||||
import { CellLastEditedAt } from "@/features/base/components/cells/cell-last-edited-at";
|
|
||||||
import { CellLastEditedBy } from "@/features/base/components/cells/cell-last-edited-by";
|
|
||||||
import { CellFormula } from "@/features/base/components/cells/cell-formula";
|
|
||||||
import { RowNumberCell } from "./row-number-cell";
|
import { RowNumberCell } from "./row-number-cell";
|
||||||
import classes from "@/features/base/styles/grid.module.css";
|
import classes from "@/features/base/styles/grid.module.css";
|
||||||
|
|
||||||
type CellComponentProps = {
|
|
||||||
value: unknown;
|
|
||||||
property: IBaseProperty;
|
|
||||||
rowId: string;
|
|
||||||
isEditing: boolean;
|
|
||||||
onCommit: (value: unknown) => void;
|
|
||||||
onCancel: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cellComponents: Record<
|
|
||||||
string,
|
|
||||||
React.ComponentType<CellComponentProps>
|
|
||||||
> = {
|
|
||||||
text: CellText,
|
|
||||||
number: CellNumber,
|
|
||||||
select: CellSelect,
|
|
||||||
status: CellStatus,
|
|
||||||
multiSelect: CellMultiSelect,
|
|
||||||
date: CellDate,
|
|
||||||
checkbox: CellCheckbox,
|
|
||||||
url: CellUrl,
|
|
||||||
email: CellEmail,
|
|
||||||
person: CellPerson,
|
|
||||||
file: CellFile,
|
|
||||||
page: CellPage,
|
|
||||||
createdAt: CellCreatedAt,
|
|
||||||
lastEditedAt: CellLastEditedAt,
|
|
||||||
lastEditedBy: CellLastEditedBy,
|
|
||||||
formula: CellFormula,
|
|
||||||
};
|
|
||||||
|
|
||||||
type RowDragProps = {
|
type RowDragProps = {
|
||||||
draggable: boolean;
|
draggable: boolean;
|
||||||
onDragStart: (e: React.DragEvent) => void;
|
onDragStart: (e: React.DragEvent) => void;
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { useState, useCallback } from "react";
|
||||||
|
import { Group, Text } from "@mantine/core";
|
||||||
|
import { IBaseProperty, IBaseRow } from "@/features/base/types/base.types";
|
||||||
|
import { CellRenderer } from "@/features/base/components/cells/cell-renderer";
|
||||||
|
|
||||||
|
type PropertyRowProps = {
|
||||||
|
property: IBaseProperty;
|
||||||
|
row: IBaseRow;
|
||||||
|
onUpdate: (propertyId: string, value: unknown) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PropertyRow({ property, row, onUpdate }: PropertyRowProps) {
|
||||||
|
const value = (row.cells ?? {})[property.id];
|
||||||
|
// The cell components key their edit state off `isEditing`. In the
|
||||||
|
// modal we treat the cell as always-active: click to commit a new
|
||||||
|
// value, blur/escape to commit-or-cancel the same way the grid does.
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
|
||||||
|
const handleCommit = useCallback(
|
||||||
|
(next: unknown) => {
|
||||||
|
setEditing(false);
|
||||||
|
onUpdate(property.id, next);
|
||||||
|
},
|
||||||
|
[onUpdate, property.id],
|
||||||
|
);
|
||||||
|
const handleCancel = useCallback(() => setEditing(false), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
align="flex-start"
|
||||||
|
wrap="nowrap"
|
||||||
|
gap="md"
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
style={{ cursor: "text" }}
|
||||||
|
>
|
||||||
|
<div style={{ width: 140, flex: "0 0 140px", paddingTop: 6 }}>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{property.name}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<CellRenderer
|
||||||
|
property={property}
|
||||||
|
rowId={row.id}
|
||||||
|
value={value}
|
||||||
|
isEditing={editing}
|
||||||
|
onCommit={handleCommit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from "@/features/base/types/base.types";
|
} from "@/features/base/types/base.types";
|
||||||
import { useUpdateRowMutation } from "@/features/base/queries/base-row-query";
|
import { useUpdateRowMutation } from "@/features/base/queries/base-row-query";
|
||||||
import { RowDetailTitle } from "./row-detail-title";
|
import { RowDetailTitle } from "./row-detail-title";
|
||||||
|
import { PropertyRow } from "./property-row";
|
||||||
|
|
||||||
type RowDetailModalProps = {
|
type RowDetailModalProps = {
|
||||||
base: IBase;
|
base: IBase;
|
||||||
@@ -63,7 +64,22 @@ export function RowDetailModal({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Property list goes here in Task 19 */}
|
{base.properties
|
||||||
|
.filter((p) => !p.isPrimary)
|
||||||
|
.map((property) => (
|
||||||
|
<PropertyRow
|
||||||
|
key={property.id}
|
||||||
|
property={property}
|
||||||
|
row={row}
|
||||||
|
onUpdate={(propertyId, value) => {
|
||||||
|
updateRowMutation.mutate({
|
||||||
|
rowId: row.id,
|
||||||
|
pageId: base.id,
|
||||||
|
cells: { [propertyId]: value },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{/* Add-property button goes here in Task 20 */}
|
{/* Add-property button goes here in Task 20 */}
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user