Browse Source

feat(dashboard): Customer group list

Michael Bromley 10 months ago
parent
commit
0809c46895

+ 44 - 0
packages/dashboard/src/routes/_authenticated/_customer-groups/components/customer-group-members-sheet.tsx

@@ -0,0 +1,44 @@
+import {
+    Sheet,
+    SheetContent,
+    SheetDescription,
+    SheetHeader,
+    SheetTitle,
+    SheetTrigger,
+} from '@/components/ui/sheet.js';
+import { Trans } from '@lingui/react/macro';
+import { PanelLeftOpen } from 'lucide-react';
+import { CustomerGroupMembersTable } from './customer-group-members-table.js';
+import { Button } from '@/components/ui/button.js';
+
+export interface CustomerGroupMembersSheetProps {
+    customerGroupName: string;
+    customerGroupId: string;
+    children?: React.ReactNode;
+}
+
+export function CustomerGroupMembersSheet({ customerGroupName, customerGroupId, children }: CustomerGroupMembersSheetProps) {
+    return (
+        <Sheet>
+            <SheetTrigger asChild>
+                <Button variant="outline" size="sm" className="flex items-center gap-2">
+                    {children}
+                    <PanelLeftOpen className="w-4 h-4" />
+                </Button>
+            </SheetTrigger>
+            <SheetContent className="min-w-[90vw] lg:min-w-[800px]">
+                <SheetHeader>
+                    <SheetTitle>
+                        <Trans>Customer group members for {customerGroupName}</Trans>
+                    </SheetTitle>
+                    <SheetDescription>
+                        <Trans>These are the customers in the <strong>{customerGroupName}</strong> customer group.</Trans>
+                    </SheetDescription>
+                </SheetHeader>
+                <div className="px-4">
+                    <CustomerGroupMembersTable customerGroupId={customerGroupId} />
+                </div>
+            </SheetContent>
+        </Sheet>
+    );
+}

+ 96 - 0
packages/dashboard/src/routes/_authenticated/_customer-groups/components/customer-group-members-table.tsx

@@ -0,0 +1,96 @@
+import { PaginatedListDataTable } from '@/components/shared/paginated-list-data-table.js';
+import { Button } from '@/components/ui/button.js';
+import { addCustomFields } from '@/framework/document-introspection/add-custom-fields.js';
+import { graphql } from '@/graphql/graphql.js';
+import { Link } from '@tanstack/react-router';
+import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
+import { useState } from 'react';
+
+export const customerGroupMemberListDocument = graphql(`
+    query CustomerGroupMemberList($id: ID!, $options: CustomerListOptions) {
+        customerGroup(id: $id) {
+            customers(options: $options) {
+                items {
+                    id
+                    createdAt
+                    updatedAt
+                    firstName
+                    lastName
+                    emailAddress
+                }
+                totalItems
+            }
+        }
+    }
+`);
+
+export interface CustomerGroupMembersTableProps {
+    customerGroupId: string;
+}
+
+export function CustomerGroupMembersTable({ customerGroupId }: CustomerGroupMembersTableProps) {
+    const [sorting, setSorting] = useState<SortingState>([]);
+    const [page, setPage] = useState(1);
+    const [pageSize, setPageSize] = useState(10);
+    const [filters, setFilters] = useState<ColumnFiltersState>([]);
+
+    return (
+        <PaginatedListDataTable
+            listQuery={addCustomFields(customerGroupMemberListDocument)}
+            transformVariables={variables => ({
+                ...variables,
+                id: customerGroupId
+            })}
+            page={page}
+            itemsPerPage={pageSize}
+            sorting={sorting}
+            columnFilters={filters}
+            onPageChange={(_, page, perPage) => {
+                setPage(page);
+                setPageSize(perPage);
+            }}
+            onSortChange={(_, sorting) => {
+                setSorting(sorting);
+            }}
+            onFilterChange={(_, filters) => {
+                setFilters(filters);
+            }}
+            onSearchTermChange={searchTerm => {
+                return {
+                    lastName: {
+                        contains: searchTerm,
+                    },
+                    emailAddress: {
+                        contains: searchTerm,
+                    },
+                };
+            }}
+            additionalColumns={{
+                name: {
+                    header: 'Name',
+                    cell: ({ row }) => {
+                        const value = `${row.original.firstName} ${row.original.lastName}`;
+                        return (
+                            <Button asChild variant="ghost">
+                                <Link
+                                    to="/customers/$id"
+                                    params={{ id: row.original.id }}
+                                >
+                                    {value}
+                                </Link>
+                            </Button>
+                        );
+                    },
+                },
+            }}
+            defaultColumnOrder={['name', 'emailAddress']}
+            defaultVisibility={{
+                id: false,
+                createdAt: false,
+                updatedAt: false,
+                firstName: false,
+                lastName: false,
+            }}
+        />
+    );
+}

+ 17 - 0
packages/dashboard/src/routes/_authenticated/_customer-groups/customer-groups.graphql.ts

@@ -0,0 +1,17 @@
+import { graphql } from '@/graphql/graphql.js';
+
+export const customerGroupListDocument = graphql(`
+    query CustomerGroupList($options: CustomerGroupListOptions) {
+        customerGroups(options: $options) {
+            items {
+                id
+                createdAt
+                updatedAt
+                name
+                customers {
+                    totalItems
+                }
+            }
+        }
+    }
+`);

+ 65 - 0
packages/dashboard/src/routes/_authenticated/_customer-groups/customer-groups.tsx

@@ -0,0 +1,65 @@
+import { DetailPageButton } from '@/components/shared/detail-page-button.js';
+import { PermissionGuard } from '@/components/shared/permission-guard.js';
+import { Button } from '@/components/ui/button.js';
+import { addCustomFields } from '@/framework/document-introspection/add-custom-fields.js';
+import { PageActionBar } from '@/framework/layout-engine/page-layout.js';
+import { ListPage } from '@/framework/page/list-page.js';
+import { Trans } from '@lingui/react/macro';
+import { Link, createFileRoute } from '@tanstack/react-router';
+import { PlusIcon } from 'lucide-react';
+import { CustomerGroupMembersSheet } from './components/customer-group-members-sheet.js';
+import { customerGroupListDocument } from './customer-groups.graphql.js';
+
+export const Route = createFileRoute('/_authenticated/_customer-groups/customer-groups')({
+    component: CustomerGroupListPage,
+    loader: () => ({ breadcrumb: () => <Trans>Customer Groups</Trans> }),
+});
+
+function CustomerGroupListPage() {
+    return (
+        <ListPage
+            title="Customer Groups"
+            listQuery={addCustomFields(customerGroupListDocument)}
+            route={Route}
+            customizeColumns={{
+                name: {
+                    header: 'Name',
+                    cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
+                },
+                customers: {
+                  header: () => <Trans>Values</Trans>,
+                  cell: ({ cell }) => {
+                      const value = cell.getValue();
+                      if (!value) return null;
+                      return (
+                          <div className="flex flex-wrap gap-2 items-center">
+                              <CustomerGroupMembersSheet
+                                  customerGroupId={cell.row.original.id}
+                                  customerGroupName={cell.row.original.name}
+                              >
+                                  <Trans>{value.totalItems} customers</Trans>
+                              </CustomerGroupMembersSheet>
+                          </div>
+                      );
+                  },
+              },
+            }}
+            onSearchTermChange={searchTerm => {
+                return {
+                    name: { contains: searchTerm },
+                };
+            }}
+        >
+            <PageActionBar>
+                <PermissionGuard requires={['CreateCustomer']}>
+                    <Button asChild>
+                        <Link to="./new">
+                            <PlusIcon className="mr-2 h-4 w-4" />
+                            <Trans>New Customer Group</Trans>
+                        </Link>
+                    </Button>
+                </PermissionGuard>
+            </PageActionBar>
+        </ListPage>
+    );
+}