Browse Source

feat(dashboard): Add entity info dropdown to page action bar (#3819)

David Höck 3 months ago
parent
commit
98fd82dbc9

+ 2 - 0
packages/dashboard/src/app/routes/_authenticated/_countries/countries.graphql.ts

@@ -38,6 +38,8 @@ export const countryDetailDocument = graphql(`
                 languageCode
                 name
             }
+            createdAt
+            updatedAt
             customFields
         }
     }

+ 96 - 9
packages/dashboard/src/lib/framework/layout-engine/page-layout.tsx

@@ -6,17 +6,24 @@ import { Form } from '@/vdb/components/ui/form.js';
 import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
 import { usePage } from '@/vdb/hooks/use-page.js';
 import { cn } from '@/vdb/lib/utils.js';
-import { useMediaQuery } from '@uidotdev/usehooks';
-import { EllipsisVerticalIcon } from 'lucide-react';
-import React, { ComponentProps, useMemo } from 'react';
+import { useCopyToClipboard, useMediaQuery } from '@uidotdev/usehooks';
+import { CheckIcon, CopyIcon, EllipsisVerticalIcon, InfoIcon } from 'lucide-react';
+import React, { ComponentProps, useMemo, useState } from 'react';
 import { Control, UseFormReturn } from 'react-hook-form';
 
 import { DashboardActionBarItem } from '../extension-api/types/layout.js';
 
 import { Button } from '@/vdb/components/ui/button.js';
-import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/vdb/components/ui/dropdown-menu.js';
+import {
+    DropdownMenu,
+    DropdownMenuContent,
+    DropdownMenuLabel,
+    DropdownMenuSeparator,
+    DropdownMenuTrigger,
+} from '@/vdb/components/ui/dropdown-menu.js';
 import { PageBlockContext } from '@/vdb/framework/layout-engine/page-block-provider.js';
 import { PageContext, PageContextValue } from '@/vdb/framework/layout-engine/page-provider.js';
+import { Trans } from '@/vdb/lib/trans.js';
 import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
 import { LocationWrapper } from './location-wrapper.js';
 
@@ -50,13 +57,13 @@ export interface PageProps extends ComponentProps<'div'> {
  *  - {@link PageTitle}
  *  - {@link PageActionBar}
  *  - {@link PageLayout}
- * 
+ *
  * @example
  * ```tsx
  * import { Page, PageTitle, PageActionBar, PageLayout, PageBlock, Button } from '\@vendure/dashboard';
- * 
+ *
  * const pageId = 'my-page';
- * 
+ *
  * export function MyPage() {
  *  return (
  *    <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
@@ -319,7 +326,7 @@ export function PageActionBar({ children }: Readonly<{ children: React.ReactNode
 /**
  * @description
  * The PageActionBarLeft component should be used to display the left content of the action bar.
- * 
+ *
  * @docsCategory page-layout
  * @docsPage PageActionBar
  * @since 3.3.0
@@ -330,6 +337,85 @@ export function PageActionBarLeft({ children }: Readonly<{ children: React.React
 
 type InlineDropdownItem = Omit<DashboardActionBarItem, 'type' | 'pageId'>;
 
+function EntityInfoDropdown({ entity }: Readonly<{ entity: any }>) {
+    const [copiedField, setCopiedField] = useState<string | null>(null);
+    const [, copy] = useCopyToClipboard();
+
+    const handleCopy = async (text: string, field: string) => {
+        await copy(text);
+        setCopiedField(field);
+        setTimeout(() => setCopiedField(null), 2000);
+    };
+
+    const formatDate = (dateString: string) => {
+        return new Date(dateString).toLocaleString();
+    };
+
+    if (!entity || !entity.id) {
+        return null;
+    }
+
+    return (
+        <DropdownMenu>
+            <DropdownMenuTrigger asChild>
+                <Button variant="ghost" size="icon" className="text-muted-foreground">
+                    <InfoIcon className="w-4 h-4" />
+                </Button>
+            </DropdownMenuTrigger>
+            <DropdownMenuContent align="end" className="w-64">
+                <DropdownMenuLabel>
+                    <Trans>Entity Information</Trans>
+                </DropdownMenuLabel>
+                <DropdownMenuSeparator />
+                <div className="px-3 py-2">
+                    <div className="flex items-center justify-between">
+                        <span className="text-sm font-medium">ID</span>
+                        <div className="flex items-center gap-1">
+                            <span className="text-sm font-mono">{entity.id}</span>
+                            <button
+                                onClick={() => handleCopy(entity.id, 'id')}
+                                className="p-1 hover:bg-muted rounded-sm transition-colors"
+                            >
+                                {copiedField === 'id' ? (
+                                    <CheckIcon className="h-3 w-3 text-green-500" />
+                                ) : (
+                                    <CopyIcon className="h-3 w-3" />
+                                )}
+                            </button>
+                        </div>
+                    </div>
+                </div>
+                {entity.createdAt && (
+                    <>
+                        <DropdownMenuSeparator />
+                        <div className="px-3 py-2">
+                            <div className="text-sm">
+                                <div className="font-medium text-muted-foreground">
+                                    <Trans>Created</Trans>
+                                </div>
+                                <div className="text-xs">{formatDate(entity.createdAt)}</div>
+                            </div>
+                        </div>
+                    </>
+                )}
+                {entity.updatedAt && (
+                    <>
+                        <DropdownMenuSeparator />
+                        <div className="px-3 py-2">
+                            <div className="text-sm">
+                                <div className="font-medium text-muted-foreground">
+                                    <Trans>Updated</Trans>
+                                </div>
+                                <div className="text-xs">{formatDate(entity.updatedAt)}</div>
+                            </div>
+                        </div>
+                    </>
+                )}
+            </DropdownMenuContent>
+        </DropdownMenu>
+    );
+}
+
 /**
  * @description
  * The PageActionBarRight component should be used to display the right content of the action bar.
@@ -366,6 +452,7 @@ export function PageActionBarRight({
             {actionBarDropdownItems.length > 0 && (
                 <PageActionBarDropdown items={actionBarDropdownItems} page={page} />
             )}
+            <EntityInfoDropdown entity={page.entity} />
         </div>
     );
 }
@@ -449,7 +536,7 @@ export type PageBlockProps = {
  * A component for displaying a block of content on a page. This should be used inside the {@link PageLayout} component.
  * It should be provided with a `column` prop to determine which column it should appear in, and a `blockId` prop
  * to identify the block.
- * 
+ *
  * @example
  * ```tsx
  * <PageBlock column="main" blockId="my-block">

File diff suppressed because it is too large
+ 1 - 2
packages/dev-server/graphql/graphql-env.d.ts


Some files were not shown because too many files changed in this diff