浏览代码

fix(dashboard): Improved facet value editing

Michael Bromley 5 月之前
父节点
当前提交
16c2e5ea59

+ 0 - 146
packages/dashboard/src/app/routes/_authenticated/_facets/components/add-facet-value-dialog.tsx

@@ -1,146 +0,0 @@
-import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
-import { Button } from '@/vdb/components/ui/button.js';
-import {
-    Dialog,
-    DialogContent,
-    DialogFooter,
-    DialogHeader,
-    DialogTitle,
-    DialogTrigger,
-} from '@/vdb/components/ui/dialog.js';
-import { Form } from '@/vdb/components/ui/form.js';
-import { Input } from '@/vdb/components/ui/input.js';
-import { api } from '@/vdb/graphql/api.js';
-import { graphql } from '@/vdb/graphql/graphql.js';
-import { Trans, useLingui } from '@/vdb/lib/trans.js';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { useMutation } from '@tanstack/react-query';
-import { Plus } from 'lucide-react';
-import { useCallback, useState } from 'react';
-import { useForm } from 'react-hook-form';
-import { toast } from 'sonner';
-import * as z from 'zod';
-
-const createFacetValuesDocument = graphql(`
-    mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
-        createFacetValues(input: $input) {
-            id
-            name
-            code
-        }
-    }
-`);
-
-const formSchema = z.object({
-    name: z.string().min(1, 'Name is required'),
-    code: z.string().min(1, 'Code is required'),
-});
-
-type FormValues = z.infer<typeof formSchema>;
-
-export function AddFacetValueDialog({
-    facetId,
-    onSuccess,
-}: Readonly<{
-    facetId: string;
-    onSuccess?: () => void;
-}>) {
-    const [open, setOpen] = useState(false);
-    const { i18n } = useLingui();
-
-    const form = useForm<FormValues>({
-        resolver: zodResolver(formSchema),
-        defaultValues: {
-            name: '',
-            code: '',
-        },
-    });
-
-    const createFacetValueMutation = useMutation({
-        mutationFn: api.mutate(createFacetValuesDocument),
-        onSuccess: () => {
-            toast.success(i18n.t('Successfully created facet value'));
-            setOpen(false);
-            form.reset();
-            onSuccess?.();
-        },
-        onError: error => {
-            toast.error(i18n.t('Failed to create facet value'), {
-                description: error instanceof Error ? error.message : i18n.t('Unknown error'),
-            });
-        },
-    });
-
-    const onSubmit = useCallback(
-        (values: FormValues) => {
-            createFacetValueMutation.mutate({
-                input: [
-                    {
-                        facetId,
-                        code: values.code,
-                        translations: [
-                            {
-                                languageCode: 'en',
-                                name: values.name,
-                            },
-                        ],
-                    },
-                ],
-            });
-        },
-        [createFacetValueMutation, facetId],
-    );
-
-    return (
-        <Dialog open={open} onOpenChange={setOpen}>
-            <DialogTrigger asChild>
-                <Button variant="outline">
-                    <Plus className="mr-2 h-4 w-4" />
-                    <Trans>Add facet value</Trans>
-                </Button>
-            </DialogTrigger>
-            <DialogContent>
-                <DialogHeader>
-                    <DialogTitle>
-                        <Trans>Add facet value</Trans>
-                    </DialogTitle>
-                </DialogHeader>
-                <Form {...form}>
-                    <form
-                        onSubmit={e => {
-                            e.stopPropagation();
-                            form.handleSubmit(onSubmit)(e);
-                        }}
-                        className="space-y-4"
-                    >
-                        <FormFieldWrapper
-                            control={form.control}
-                            name="name"
-                            label={<Trans>Name</Trans>}
-                            render={({ field }) => <Input {...field} />}
-                        />
-                        <FormFieldWrapper
-                            control={form.control}
-                            name="code"
-                            label={<Trans>Code</Trans>}
-                            render={({ field }) => <Input {...field} />}
-                        />
-                        <DialogFooter>
-                            <Button 
-                                type="submit" 
-                                disabled={
-                                    createFacetValueMutation.isPending || 
-                                    !form.formState.isValid || 
-                                    !form.watch('name').trim() || 
-                                    !form.watch('code').trim()
-                                }
-                            >
-                                <Trans>Create facet value</Trans>
-                            </Button>
-                        </DialogFooter>
-                    </form>
-                </Form>
-            </DialogContent>
-        </Dialog>
-    );
-}

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

@@ -1,13 +1,13 @@
+import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
 import { PaginatedListDataTable } from '@/vdb/components/shared/paginated-list-data-table.js';
-import { Button } from '@/vdb/components/ui/button.js';
-import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
 import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
 import { graphql } from '@/vdb/graphql/graphql.js';
 import { Trans } from '@/vdb/lib/trans.js';
+import { Link } from '@tanstack/react-router';
+import { Button } from '@/vdb/components/ui/button.js';
 import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
+import { PlusIcon } from 'lucide-react';
 import { useRef, useState } from 'react';
-import { AddFacetValueDialog } from './add-facet-value-dialog.js';
-import { EditFacetValue } from './edit-facet-value.js';
 import { deleteFacetValuesDocument } from '../facets.graphql.js';
 
 export const facetValueListDocument = graphql(`
@@ -82,41 +82,26 @@ export function FacetValuesTable({ facetId, registerRefresher }: Readonly<FacetV
                         },
                     };
                 }}
-                additionalColumns={{
-                    actions: {
-                        header: 'Actions',
-                        cell: ({ row }) => {
-                            const [open, setOpen] = useState(false);
-                            const facetValue = row.original;
-                            return (
-                                <Popover open={open} onOpenChange={setOpen}>
-                                    <PopoverTrigger asChild>
-                                        <Button type="button" variant="outline" size="sm">
-                                            <Trans>Edit</Trans>
-                                        </Button>
-                                    </PopoverTrigger>
-                                    <PopoverContent className="w-80">
-                                        <EditFacetValue
-                                            facetValueId={facetValue.id}
-                                            onSuccess={() => {
-                                                setOpen(false);
-                                                refreshRef.current?.();
-                                            }}
-                                        />
-                                    </PopoverContent>
-                                </Popover>
-                            );
-                        },
+                customizeColumns={{
+                    name: {
+                        header: 'Name',
+                        cell: ({ row }) => (
+                            <DetailPageButton
+                                id={row.original.id}
+                                label={row.original.name}
+                                href={`/facets/${facetId}/values/${row.original.id}`}
+                            />
+                        ),
                     },
                 }}
             />
             <div className="mt-4">
-                <AddFacetValueDialog
-                    facetId={facetId}
-                    onSuccess={() => {
-                        refreshRef.current?.();
-                    }}
-                />
+                <Button asChild variant="outline">
+                    <Link to="./values/new">
+                        <PlusIcon />
+                        <Trans>Add facet value</Trans>
+                    </Link>
+                </Button>
             </div>
         </>
     );

+ 40 - 0
packages/dashboard/src/app/routes/_authenticated/_facets/facets.graphql.ts

@@ -141,3 +141,43 @@ export const deleteFacetValuesDocument = graphql(`
         }
     }
 `);
+
+export const facetValueDetailDocument = graphql(`
+    query FacetValueDetail($id: ID!) {
+        facetValue(id: $id) {
+            id
+            createdAt
+            updatedAt
+            name
+            code
+            languageCode
+            translations {
+                id
+                languageCode
+                name
+            }
+            facet {
+                id
+                name
+                code
+            }
+            customFields
+        }
+    }
+`);
+
+export const createFacetValueDocument = graphql(`
+    mutation CreateFacetValue($input: CreateFacetValueInput!) {
+        createFacetValue(input: $input) {
+            id
+        }
+    }
+`);
+
+export const updateFacetValueDocument = graphql(`
+    mutation UpdateFacetValue($input: UpdateFacetValueInput!) {
+        updateFacetValue(input: $input) {
+            id
+        }
+    }
+`);

+ 147 - 0
packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx

@@ -0,0 +1,147 @@
+import { ErrorPage } from '@/vdb/components/shared/error-page.js';
+import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
+import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
+import { TranslatableFormFieldWrapper } from '@/vdb/components/shared/translatable-form-field.js';
+import { Button } from '@/vdb/components/ui/button.js';
+import { Input } from '@/vdb/components/ui/input.js';
+import { NEW_ENTITY_PATH } from '@/vdb/constants.js';
+import {
+    CustomFieldsPageBlock,
+    DetailFormGrid,
+    Page,
+    PageActionBar,
+    PageActionBarRight,
+    PageBlock,
+    PageLayout,
+    PageTitle,
+} from '@/vdb/framework/layout-engine/page-layout.js';
+import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
+import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
+import { Trans, useLingui } from '@/vdb/lib/trans.js';
+import { createFileRoute, useNavigate } from '@tanstack/react-router';
+import { toast } from 'sonner';
+import {
+    createFacetValueDocument,
+    facetValueDetailDocument,
+    updateFacetValueDocument,
+} from './facets.graphql.js';
+import { PageBreadcrumb } from '@/vdb/components/layout/generated-breadcrumbs.js';
+
+const pageId = 'facet-value-detail';
+
+export const Route = createFileRoute('/_authenticated/_facets/facets_/$facetId/values_/$id')({
+    component: FacetValueDetailPage,
+    loader: detailPageRouteLoader({
+        pageId,
+        queryDocument: facetValueDetailDocument,
+        breadcrumb(isNew, entity) {
+            const facetName = entity?.facet.name ?? 'Facet Value';
+            const breadcrumb: PageBreadcrumb[] = [{ path: '/facets', label: 'Facets' }];
+            if (isNew) {
+                breadcrumb.push(<Trans>New facet value</Trans>);
+            } else if (entity) {
+                breadcrumb.push({ path: `/facets/${entity?.facet.id}`, label: facetName }, entity.name);
+            }
+            return breadcrumb;
+        },
+    }),
+    errorComponent: ({ error }) => <ErrorPage message={error.message} />,
+});
+
+function FacetValueDetailPage() {
+    const params = Route.useParams();
+    const navigate = useNavigate();
+    const creatingNewEntity = params.id === NEW_ENTITY_PATH;
+    const { i18n } = useLingui();
+
+    const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
+        pageId,
+        queryDocument: facetValueDetailDocument,
+        createDocument: createFacetValueDocument,
+        updateDocument: updateFacetValueDocument,
+        setValuesForUpdate: entity => {
+            return {
+                id: entity.id,
+                code: entity.code,
+                name: entity.name,
+                translations: entity.translations.map(translation => ({
+                    id: translation.id,
+                    languageCode: translation.languageCode,
+                    name: translation.name,
+                    customFields: (translation as any).customFields,
+                })),
+                customFields: entity.customFields as any,
+            };
+        },
+        transformCreateInput: (value): any => {
+            return {
+                ...value,
+                facetId: params.facetId,
+            };
+        },
+        params: { id: params.id },
+        onSuccess: async data => {
+            toast(i18n.t('Successfully updated facet value'));
+            resetForm();
+            const created = Array.isArray(data) ? data[0] : data;
+            if (creatingNewEntity && created) {
+                await navigate({ to: `../$id`, params: { id: (created as any).id } });
+            }
+        },
+        onError: err => {
+            toast(i18n.t('Failed to update facet value'), {
+                description: err instanceof Error ? err.message : 'Unknown error',
+            });
+        },
+    });
+
+    return (
+        <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
+            <PageTitle>
+                {creatingNewEntity ? <Trans>New facet value</Trans> : ((entity as any)?.name ?? '')}
+            </PageTitle>
+            <PageActionBar>
+                <PageActionBarRight>
+                    <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
+                        <Button
+                            type="submit"
+                            disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
+                        >
+                            <Trans>Update</Trans>
+                        </Button>
+                    </PermissionGuard>
+                </PageActionBarRight>
+            </PageActionBar>
+            <PageLayout>
+                <PageBlock column="side" blockId="facet-info">
+                    {entity?.facet && (
+                        <div className="space-y-2">
+                            <div className="text-sm font-medium">
+                                <Trans>Facet</Trans>
+                            </div>
+                            <div className="text-sm text-muted-foreground">{entity?.facet.name}</div>
+                            <div className="text-xs text-muted-foreground">{entity?.facet.code}</div>
+                        </div>
+                    )}
+                </PageBlock>
+                <PageBlock column="main" blockId="main-form">
+                    <DetailFormGrid>
+                        <TranslatableFormFieldWrapper
+                            control={form.control}
+                            name="name"
+                            label={<Trans>Name</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                        <FormFieldWrapper
+                            control={form.control}
+                            name="code"
+                            label={<Trans>Code</Trans>}
+                            render={({ field }) => <Input {...field} />}
+                        />
+                    </DetailFormGrid>
+                </PageBlock>
+                <CustomFieldsPageBlock column="main" entityType="FacetValue" control={form.control} />
+            </PageLayout>
+        </Page>
+    );
+}

+ 0 - 2
packages/dashboard/src/lib/components/layout/channel-switcher.tsx

@@ -65,8 +65,6 @@ export function ChannelSwitcher() {
         ? [displayChannel, ...channels.filter(ch => ch.id !== displayChannel.id)]
         : channels;
 
-    console.log(displayChannel);
-
     return (
         <>
             <SidebarMenu>

文件差异内容过多而无法显示
+ 4 - 3
packages/dashboard/src/lib/graphql/graphql-env.d.ts


+ 1 - 1
packages/dashboard/vite.config.mts

@@ -36,7 +36,7 @@ export default ({ mode }: { mode: string }) => {
             vendureDashboardPlugin({
                 vendureConfigPath: pathToFileURL(vendureConfigPath),
                 adminUiConfig: { apiHost: adminApiHost, apiPort: adminApiPort },
-                // gqlOutputPath: path.resolve(__dirname, './graphql/'),
+                gqlOutputPath: path.resolve(__dirname, './src/lib/graphql/'),
                 tempCompilationDir: path.resolve(__dirname, './.temp'),
             }) as any,
         ],

部分文件因为文件数量过多而无法显示