From f5602e9bcffe7506bc52ed9d1dd7e150e0d99684 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sun, 24 May 2026 16:10:31 +0100 Subject: [PATCH] feat(bases): render editable property rows inside detail modal --- .../base/components/cells/cell-renderer.tsx | 56 +++++++++++++++++++ .../base/components/grid/grid-cell.tsx | 50 +---------------- .../row-detail-modal/property-row.tsx | 53 ++++++++++++++++++ .../row-detail-modal/row-detail-modal.tsx | 18 +++++- 4 files changed, 128 insertions(+), 49 deletions(-) create mode 100644 apps/client/src/features/base/components/cells/cell-renderer.tsx create mode 100644 apps/client/src/features/base/components/row-detail-modal/property-row.tsx diff --git a/apps/client/src/features/base/components/cells/cell-renderer.tsx b/apps/client/src/features/base/components/cells/cell-renderer.tsx new file mode 100644 index 000000000..ce7e8400d --- /dev/null +++ b/apps/client/src/features/base/components/cells/cell-renderer.tsx @@ -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 +> = { + 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 ; +} diff --git a/apps/client/src/features/base/components/grid/grid-cell.tsx b/apps/client/src/features/base/components/grid/grid-cell.tsx index 0a4feca33..5b2858318 100644 --- a/apps/client/src/features/base/components/grid/grid-cell.tsx +++ b/apps/client/src/features/base/components/grid/grid-cell.tsx @@ -1,59 +1,13 @@ import { memo, useCallback } from "react"; import { Cell } from "@tanstack/react-table"; 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 { isSystemPropertyType } from "@/features/base/hooks/use-base-table"; -import { CellText } from "@/features/base/components/cells/cell-text"; -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 { cellComponents } from "@/features/base/components/cells/cell-renderer"; import { RowNumberCell } from "./row-number-cell"; 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 -> = { - 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 = { draggable: boolean; onDragStart: (e: React.DragEvent) => void; diff --git a/apps/client/src/features/base/components/row-detail-modal/property-row.tsx b/apps/client/src/features/base/components/row-detail-modal/property-row.tsx new file mode 100644 index 000000000..0acf10bd8 --- /dev/null +++ b/apps/client/src/features/base/components/row-detail-modal/property-row.tsx @@ -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 ( + setEditing(true)} + style={{ cursor: "text" }} + > +
+ + {property.name} + +
+
+ +
+
+ ); +} diff --git a/apps/client/src/features/base/components/row-detail-modal/row-detail-modal.tsx b/apps/client/src/features/base/components/row-detail-modal/row-detail-modal.tsx index 272f247fa..6df3dbb6e 100644 --- a/apps/client/src/features/base/components/row-detail-modal/row-detail-modal.tsx +++ b/apps/client/src/features/base/components/row-detail-modal/row-detail-modal.tsx @@ -7,6 +7,7 @@ import { } from "@/features/base/types/base.types"; import { useUpdateRowMutation } from "@/features/base/queries/base-row-query"; import { RowDetailTitle } from "./row-detail-title"; +import { PropertyRow } from "./property-row"; type RowDetailModalProps = { base: IBase; @@ -63,7 +64,22 @@ export function RowDetailModal({ }); }} /> - {/* Property list goes here in Task 19 */} + {base.properties + .filter((p) => !p.isPrimary) + .map((property) => ( + { + updateRowMutation.mutate({ + rowId: row.id, + pageId: base.id, + cells: { [propertyId]: value }, + }); + }} + /> + ))} {/* Add-property button goes here in Task 20 */} ) : (