Explorar el Código

fix(dashboard): Fix facet values sheet in facet list (#4064)

Co-authored-by: Michael Bromley <michael@michaelbromley.co.uk>
Martin Grolmus hace 4 semanas
padre
commit
883a09ed6a

+ 4 - 1
packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx

@@ -7,6 +7,7 @@ import {
     SheetTitle,
     SheetTrigger,
 } from '@/vdb/components/ui/sheet.js';
+import { FullWidthPageBlock } from '@/vdb/framework/layout-engine/page-layout.js';
 import { Trans } from '@lingui/react/macro';
 import { PanelLeftOpen } from 'lucide-react';
 import { FacetValuesTable } from './facet-values-table.js';
@@ -38,7 +39,9 @@ export function FacetValuesSheet({ facetName, facetId, children }: Readonly<Face
                     </SheetDescription>
                 </SheetHeader>
                 <div className="px-4">
-                    <FacetValuesTable facetId={facetId} />
+                    <FullWidthPageBlock blockId="facet-values-sheet-table">
+                        <FacetValuesTable facetId={facetId} />
+                    </FullWidthPageBlock>
                 </div>
             </SheetContent>
         </Sheet>

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx

@@ -128,7 +128,7 @@ export function FacetValuesTable({ facetId, registerRefresher }: Readonly<FacetV
             />
             <div className="mt-4">
                 <Button asChild variant="outline">
-                    <Link to="./values/new">
+                    <Link to={`/facets/${facetId}/values/new`}>
                         <PlusIcon />
                         <Trans>Add facet value</Trans>
                     </Link>

+ 22 - 38
packages/dashboard/src/app/routes/_authenticated/_facets/facets.tsx

@@ -1,4 +1,3 @@
-import { DataTableCellComponent } from '@/vdb/components/data-table/types.js';
 import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
 import { FacetValueChip } from '@/vdb/components/shared/facet-value-chip.js';
 import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
@@ -8,7 +7,6 @@ import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js
 import { ListPage } from '@/vdb/framework/page/list-page.js';
 import { Trans } from '@lingui/react/macro';
 import { createFileRoute, Link } from '@tanstack/react-router';
-import { ResultOf } from 'gql.tada';
 import { PlusIcon } from 'lucide-react';
 import {
     AssignFacetsToChannelBulkAction,
@@ -24,39 +22,6 @@ export const Route = createFileRoute('/_authenticated/_facets/facets')({
     loader: () => ({ breadcrumb: () => <Trans>Facets</Trans> }),
 });
 
-const FacetValuesCell: DataTableCellComponent<ResultOf<typeof facetListDocument>['facets']['items'][0]> = ({
-    row,
-}) => {
-    const value = row.original.valueList;
-    if (!value) {
-        return null;
-    }
-    const list = value;
-    return (
-        <div className="flex flex-wrap gap-2 items-center">
-            {list.items.map(item => {
-                return (
-                    <FacetValueChip
-                        key={item.id}
-                        facetValue={item}
-                        removable={false}
-                        displayFacetName={false}
-                    />
-                );
-            })}
-            <FacetValuesSheet facetId={row.original.id} facetName={row.original.name}>
-                {list.totalItems > 3 ? (
-                    <div>
-                        <Trans>+ {list.totalItems - 3} more</Trans>
-                    </div>
-                ) : (
-                    <Trans>View values</Trans>
-                )}
-            </FacetValuesSheet>
-        </div>
-    );
-};
-
 function FacetListPage() {
     return (
         <ListPage
@@ -66,11 +31,9 @@ function FacetListPage() {
             defaultVisibility={{
                 name: true,
                 isPrivate: true,
-                valueList: true,
             }}
             customizeColumns={{
                 name: {
-                    id: 'name',
                     cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
                 },
                 isPrivate: {
@@ -86,7 +49,28 @@ function FacetListPage() {
                 },
                 valueList: {
                     header: () => <Trans>Values</Trans>,
-                    cell: FacetValuesCell,
+                    cell: ({ row }) => {
+                        const list = row.original.valueList;
+                        return (
+                            <div className="flex flex-wrap gap-2 items-center">
+                                {list?.items.map(item => (
+                                    <FacetValueChip
+                                        key={item.id}
+                                        facetValue={item}
+                                        removable={false}
+                                        displayFacetName={false}
+                                    />
+                                ))}
+                                <FacetValuesSheet facetId={row.original.id} facetName={row.original.name}>
+                                    {list && list.totalItems > 3 ? (
+                                        <Trans>+ {list.totalItems - 3} more</Trans>
+                                    ) : (
+                                        <Trans>View values</Trans>
+                                    )}
+                                </FacetValuesSheet>
+                            </div>
+                        );
+                    },
                 },
             }}
             onSearchTermChange={searchTerm => {

+ 56 - 24
packages/dashboard/src/lib/components/data-table/use-generated-columns.tsx

@@ -16,9 +16,15 @@ import { usePage } from '@/vdb/hooks/use-page.js';
 import { TypedDocumentNode } from '@graphql-typed-document-node/core';
 import { Trans, useLingui } from '@lingui/react/macro';
 import { useMutation } from '@tanstack/react-query';
-import { AccessorFnColumnDef, AccessorKeyColumnDef, createColumnHelper, Row } from '@tanstack/react-table';
+import {
+    AccessorFnColumnDef,
+    AccessorKeyColumnDef,
+    CellContext,
+    createColumnHelper,
+    Row,
+} from '@tanstack/react-table';
 import { EllipsisIcon, TrashIcon } from 'lucide-react';
-import { useMemo } from 'react';
+import { memo, useMemo } from 'react';
 import { toast } from 'sonner';
 import {
     AdditionalColumns,
@@ -116,6 +122,25 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
             const customConfig = customizeColumns?.[fieldInfo.name as unknown as AllItemFieldKeys<T>] ?? {};
             const { header, meta, cell: customCell, ...customConfigRest } = customConfig;
             const enableColumnFilter = fieldInfo.isScalar && !facetedFilters?.[fieldInfo.name];
+            const displayComponentId =
+                pageId && pageBlock?.blockId
+                    ? generateDisplayComponentKey(pageId, pageBlock.blockId, fieldInfo.name)
+                    : undefined;
+
+            // If a custom cell function is provided, use it directly (like additionalColumns does).
+            // This preserves the same behavior and prevents cell unmounting issues.
+            // Only use CellWrapper for columns without custom cells.
+            const cellFn =
+                typeof customCell === 'function'
+                    ? customCell
+                    : (cellContext: CellContext<any, any>) => (
+                          <CellWrapper
+                              cellContext={cellContext}
+                              fieldInfo={fieldInfo}
+                              isCustomField={isCustomField}
+                              displayComponentId={displayComponentId}
+                          />
+                      );
 
             return columnHelper.accessor(fieldInfo.name as any, {
                 id: fieldInfo.name,
@@ -126,28 +151,7 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
                 // otherwise the TanStack Table with apply an "auto" function which somehow
                 // prevents certain filters from working.
                 filterFn: 'equalsString',
-                cell: cellContext => {
-                    const { cell, row } = cellContext;
-                    const cellValue = cell.getValue();
-                    const value =
-                        cellValue ??
-                        (isCustomField ? row.original?.customFields?.[fieldInfo.name] : undefined);
-                    const displayComponentId =
-                        pageId && pageBlock?.blockId
-                            ? generateDisplayComponentKey(pageId, pageBlock.blockId, fieldInfo.name)
-                            : undefined;
-
-                    const CustomDisplayComponent =
-                        displayComponentId && getDisplayComponent(displayComponentId);
-
-                    if (CustomDisplayComponent) {
-                        return <CustomDisplayComponent value={value} {...cellContext} />;
-                    }
-                    if (typeof customCell === 'function') {
-                        return customCell(cellContext);
-                    }
-                    return <DefaultDisplayComponent value={value} fieldInfo={fieldInfo} />;
-                },
+                cell: cellFn,
                 header: headerContext => {
                     return (
                         <DataTableColumnHeader headerContext={headerContext} customConfig={customConfig} />
@@ -291,6 +295,34 @@ function DefaultDisplayComponent({ value, fieldInfo }: { value: any; fieldInfo:
     return value;
 }
 
+/**
+ * A cell wrapper component for columns without custom cell functions.
+ * Handles default display logic including custom display components and field-type-based rendering.
+ */
+const CellWrapper = memo(function CellWrapper({
+    cellContext,
+    fieldInfo,
+    isCustomField,
+    displayComponentId,
+}: {
+    cellContext: CellContext<any, any>;
+    fieldInfo: FieldInfo;
+    isCustomField: boolean;
+    displayComponentId?: string;
+}) {
+    const { cell, row } = cellContext;
+    const cellValue = cell.getValue();
+    const value =
+        cellValue ?? (isCustomField ? (row.original as any)?.customFields?.[fieldInfo.name] : undefined);
+
+    const CustomDisplayComponent = displayComponentId && getDisplayComponent(displayComponentId);
+
+    if (CustomDisplayComponent) {
+        return <CustomDisplayComponent value={value} {...cellContext} />;
+    }
+    return <DefaultDisplayComponent value={value} fieldInfo={fieldInfo} />;
+});
+
 function DeleteMutationRowAction({
     deleteMutation,
     row,