@carlonicora/nextjs-jsonapi 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{BlockNoteEditor-PNAVMGHB.js → BlockNoteEditor-7WGCH2KB.js} +19 -19
  2. package/dist/{BlockNoteEditor-PNAVMGHB.js.map → BlockNoteEditor-7WGCH2KB.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-3PS3HYVI.mjs → BlockNoteEditor-GWAV5ESO.mjs} +4 -4
  4. package/dist/billing/index.d.mts +40 -1
  5. package/dist/billing/index.d.ts +40 -1
  6. package/dist/billing/index.js +519 -462
  7. package/dist/billing/index.js.map +1 -1
  8. package/dist/billing/index.mjs +298 -241
  9. package/dist/billing/index.mjs.map +1 -1
  10. package/dist/{chunk-MN2BUSEU.mjs → chunk-3LVSA7IM.mjs} +2 -2
  11. package/dist/{chunk-QYUYQO2M.js → chunk-GYGMEDVS.js} +1132 -950
  12. package/dist/chunk-GYGMEDVS.js.map +1 -0
  13. package/dist/{chunk-EQEW64CH.mjs → chunk-LCCRUWWY.mjs} +2 -1
  14. package/dist/{chunk-EQEW64CH.mjs.map → chunk-LCCRUWWY.mjs.map} +1 -1
  15. package/dist/{chunk-FSMXD3N7.mjs → chunk-M2EGUO2F.mjs} +2535 -2353
  16. package/dist/chunk-M2EGUO2F.mjs.map +1 -0
  17. package/dist/{chunk-OHJQYVSI.js → chunk-TOKHHZSP.js} +7 -7
  18. package/dist/{chunk-OHJQYVSI.js.map → chunk-TOKHHZSP.js.map} +1 -1
  19. package/dist/{chunk-X5TPKMBS.js → chunk-V66AZWPG.js} +2 -1
  20. package/dist/chunk-V66AZWPG.js.map +1 -0
  21. package/dist/client/index.d.mts +1 -1
  22. package/dist/client/index.d.ts +1 -1
  23. package/dist/client/index.js +4 -4
  24. package/dist/client/index.mjs +3 -3
  25. package/dist/components/index.d.mts +121 -3
  26. package/dist/components/index.d.ts +121 -3
  27. package/dist/components/index.js +16 -4
  28. package/dist/components/index.js.map +1 -1
  29. package/dist/components/index.mjs +15 -3
  30. package/dist/{content.fields-xH3TGvVk.d.mts → content.fields-1AlHDtDb.d.mts} +1 -0
  31. package/dist/{content.fields-xH3TGvVk.d.ts → content.fields-1AlHDtDb.d.ts} +1 -0
  32. package/dist/contexts/index.d.mts +22 -1
  33. package/dist/contexts/index.d.ts +22 -1
  34. package/dist/contexts/index.js +8 -4
  35. package/dist/contexts/index.js.map +1 -1
  36. package/dist/contexts/index.mjs +7 -3
  37. package/dist/core/index.d.mts +1 -1
  38. package/dist/core/index.d.ts +1 -1
  39. package/dist/core/index.js +2 -2
  40. package/dist/core/index.mjs +1 -1
  41. package/dist/features/help/index.js +37 -37
  42. package/dist/features/help/index.mjs +3 -3
  43. package/dist/features/tokenusage/index.js +71 -71
  44. package/dist/features/tokenusage/index.mjs +3 -3
  45. package/dist/index.d.mts +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +3 -3
  48. package/dist/index.mjs +2 -2
  49. package/dist/server/index.js +3 -3
  50. package/dist/server/index.mjs +1 -1
  51. package/package.json +1 -1
  52. package/src/billing/index.ts +1 -0
  53. package/src/components/index.ts +1 -0
  54. package/src/contexts/index.ts +1 -0
  55. package/src/features/administration/components/AdminIndexContainer.tsx +150 -0
  56. package/src/features/administration/components/__tests__/AdminIndexContainer.spec.tsx +96 -0
  57. package/src/features/administration/components/index.ts +1 -0
  58. package/src/features/administration/contexts/AdministrationContext.tsx +47 -0
  59. package/src/features/administration/contexts/index.ts +1 -0
  60. package/src/features/administration/data/admin-section.types.ts +30 -0
  61. package/src/features/administration/i18n-keys.ts +26 -0
  62. package/src/features/administration/index.ts +3 -0
  63. package/src/features/billing/contexts/BillingContext.tsx +95 -0
  64. package/src/features/billing/contexts/index.ts +1 -0
  65. package/src/features/billing/index.ts +1 -0
  66. package/src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx +39 -27
  67. package/src/features/billing/stripe-product/components/containers/ProductsAdminPageContainer.tsx +27 -0
  68. package/src/features/billing/stripe-product/components/containers/index.ts +1 -0
  69. package/src/features/company/components/containers/CompaniesListContainer.tsx +25 -0
  70. package/src/features/company/components/containers/index.ts +1 -0
  71. package/src/features/company/components/lists/CompaniesList.tsx +12 -1
  72. package/src/features/company/contexts/CompanyContext.tsx +13 -1
  73. package/src/features/user/components/containers/PlatformUsersContainer.tsx +23 -0
  74. package/src/features/user/components/containers/index.ts +1 -0
  75. package/src/features/user/components/lists/PlatformUsersList.tsx +45 -0
  76. package/src/features/user/components/lists/index.ts +1 -0
  77. package/src/features/user/data/user.fields.ts +1 -0
  78. package/src/features/user/hooks/useUserTableStructure.tsx +17 -0
  79. package/dist/chunk-FSMXD3N7.mjs.map +0 -1
  80. package/dist/chunk-QYUYQO2M.js.map +0 -1
  81. package/dist/chunk-X5TPKMBS.js.map +0 -1
  82. /package/dist/{BlockNoteEditor-3PS3HYVI.mjs.map → BlockNoteEditor-GWAV5ESO.mjs.map} +0 -0
  83. /package/dist/{chunk-MN2BUSEU.mjs.map → chunk-3LVSA7IM.mjs.map} +0 -0
@@ -0,0 +1,95 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { createContext, ReactNode, useCallback, useContext, useState } from "react";
5
+ import { SharedProvider } from "../../../contexts/SharedContext";
6
+ import { BreadcrumbItemData } from "../../../interfaces";
7
+ import { getRoleId } from "../../../roles";
8
+ import { Button } from "../../../shadcnui";
9
+ import { useCurrentUserContext } from "../../user/contexts";
10
+ import { ProductEditor } from "../stripe-product/components/forms/ProductEditor";
11
+
12
+ interface BillingContextType {
13
+ /**
14
+ * Bumped every time the provider's own actions change the product catalogue.
15
+ * ProductsAdminContainer reloads on it — the create action lives up here in
16
+ * the page title bar, so it cannot call the container's loader directly.
17
+ */
18
+ refreshToken: number;
19
+ refresh: () => void;
20
+ }
21
+
22
+ const BillingContext = createContext<BillingContextType | undefined>(undefined);
23
+
24
+ type BillingProviderProps = {
25
+ children: ReactNode;
26
+ };
27
+
28
+ /**
29
+ * Page chrome for the administrative billing surface (Stripe products, prices
30
+ * and their credit allowances).
31
+ *
32
+ * Billing gets its own provider rather than borrowing AdministrationProvider so
33
+ * the page carries its own name — and, like CompanyProvider with CompanyEditor,
34
+ * it owns the page's ACTIONS: the create-product control is published through
35
+ * `title.functions`, which RoundPageContainerTitle renders in the header bar.
36
+ * That is where every other page in this codebase puts its primary action.
37
+ */
38
+ export const BillingProvider = ({ children }: BillingProviderProps) => {
39
+ const t = useTranslations();
40
+ const { hasRole } = useCurrentUserContext();
41
+ const [showCreateProduct, setShowCreateProduct] = useState<boolean>(false);
42
+ const [refreshToken, setRefreshToken] = useState<number>(0);
43
+
44
+ const refresh = useCallback(() => setRefreshToken((value) => value + 1), []);
45
+
46
+ // No href: this crumb IS the current page. Linking it would also point at the
47
+ // StripeProduct module's `pageUrl` (/stripe-products), which no consuming app
48
+ // routes — the page lives under the administration tree.
49
+ const breadcrumbs: BreadcrumbItemData[] = [{ name: t(`billing.admin.products.title`) }];
50
+
51
+ const title = () => {
52
+ const response: any = { type: t(`billing.admin.products.title`) };
53
+
54
+ if (hasRole(getRoleId().Administrator)) {
55
+ response.functions = [
56
+ <Button key="productEditorTrigger" onClick={() => setShowCreateProduct(true)}>
57
+ {t(`billing.admin.products.create`)}
58
+ </Button>,
59
+ showCreateProduct ? (
60
+ <ProductEditor
61
+ key="productEditor"
62
+ open={showCreateProduct}
63
+ onOpenChange={setShowCreateProduct}
64
+ onSuccess={() => {
65
+ setShowCreateProduct(false);
66
+ refresh();
67
+ }}
68
+ />
69
+ ) : null,
70
+ ];
71
+ }
72
+
73
+ return response;
74
+ };
75
+
76
+ return (
77
+ <SharedProvider value={{ breadcrumbs, title: title() }}>
78
+ <BillingContext.Provider value={{ refreshToken, refresh }}>{children}</BillingContext.Provider>
79
+ </SharedProvider>
80
+ );
81
+ };
82
+
83
+ export const useBillingContext = (): BillingContextType => {
84
+ const context = useContext(BillingContext);
85
+ if (context === undefined) {
86
+ throw new Error("useBillingContext must be used within a BillingProvider");
87
+ }
88
+ return context;
89
+ };
90
+
91
+ /**
92
+ * Non-throwing variant. ProductsAdminContainer predates this provider and is
93
+ * still mounted bare by other consumers, so it must keep working without one.
94
+ */
95
+ export const useOptionalBillingContext = (): BillingContextType | undefined => useContext(BillingContext);
@@ -0,0 +1 @@
1
+ export * from "./BillingContext";
@@ -1,3 +1,4 @@
1
1
  export * from "./components";
2
+ export * from "./contexts";
2
3
  export * from "./data";
3
4
  export * from "./modules";
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
5
5
  import { getRoleId } from "../../../../../roles";
6
6
  import { Button } from "../../../../../shadcnui";
7
7
  import { useCurrentUserContext } from "../../../../user/contexts";
8
+ import { useOptionalBillingContext } from "../../../contexts/BillingContext";
8
9
  import { StripeProductInterface } from "../../data/stripe-product.interface";
9
10
  import { StripeProductService } from "../../data/stripe-product.service";
10
11
  import { ProductEditor } from "../forms/ProductEditor";
@@ -12,6 +13,10 @@ import { ProductsList } from "../lists/ProductsList";
12
13
 
13
14
  export function ProductsAdminContainer() {
14
15
  const { hasRole } = useCurrentUserContext();
16
+ // Optional on purpose: the create action lives in BillingProvider's title
17
+ // functions when this container is a page, but the container is still mounted
18
+ // bare elsewhere, where it keeps its own inline create button.
19
+ const billing = useOptionalBillingContext();
15
20
  const [products, setProducts] = useState<StripeProductInterface[]>([]);
16
21
  const [loading, setLoading] = useState<boolean>(true);
17
22
  const [showCreateProduct, setShowCreateProduct] = useState<boolean>(false);
@@ -37,9 +42,10 @@ export function ProductsAdminContainer() {
37
42
  }
38
43
  };
39
44
 
45
+ // Reloads on mount and whenever the provider's create action succeeds.
40
46
  useEffect(() => {
41
47
  loadProducts();
42
- }, []);
48
+ }, [billing?.refreshToken]);
43
49
 
44
50
  if (loading) {
45
51
  return (
@@ -50,37 +56,43 @@ export function ProductsAdminContainer() {
50
56
  }
51
57
 
52
58
  return (
53
- <div className="flex w-full flex-col gap-y-6">
54
- {/* Header */}
55
- <div className="flex items-center justify-between">
56
- <div className="flex items-center gap-x-3">
57
- <Package className="h-8 w-8" />
58
- <h1 className="text-primary text-3xl font-semibold">Product & Price Management</h1>
59
+ <div className="flex w-full flex-col">
60
+ {/* No header here. The page name and the create action belong to
61
+ BillingProvider, which publishes them through `title.functions` for
62
+ RoundPageContainerTitle to render — the same way CompanyProvider
63
+ publishes CompanyEditor. A header drawn inside the content would
64
+ duplicate the title bar and strand the button above the list. */}
65
+ {/* Fallback for consumers mounting this container without BillingProvider:
66
+ they have no title bar to host the action, so it stays inline. */}
67
+ {!billing && (
68
+ <div className="flex w-full items-center justify-end px-4 pt-4">
69
+ <Button onClick={() => setShowCreateProduct(true)}>Create Product</Button>
59
70
  </div>
60
- <Button onClick={() => setShowCreateProduct(true)}>Create Product</Button>
61
- </div>
71
+ )}
62
72
 
63
- {/* Empty State */}
64
- {products.length === 0 && (
65
- <div className="bg-muted/50 flex flex-col items-center justify-center gap-y-4 rounded-lg border-2 border-dashed p-12">
66
- <Package className="text-muted-foreground h-16 w-16" />
67
- <div className="text-center">
68
- <h3 className="mb-2 text-sm font-medium">No products yet</h3>
69
- <p className="text-muted-foreground mb-4">
70
- Create your first product to start offering subscriptions to your customers.
71
- </p>
72
- <Button onClick={() => setShowCreateProduct(true)}>Create Your First Product</Button>
73
+ <div className="flex w-full flex-col gap-y-6 px-4 pb-4">
74
+ {/* Empty State */}
75
+ {products.length === 0 && (
76
+ <div className="bg-muted/50 flex flex-col items-center justify-center gap-y-4 rounded-lg border-2 border-dashed p-12">
77
+ <Package className="text-muted-foreground h-16 w-16" />
78
+ <div className="text-center">
79
+ <h3 className="mb-2 text-sm font-medium">No products yet</h3>
80
+ <p className="text-muted-foreground mb-4">
81
+ Create your first product to start offering subscriptions to your customers.
82
+ </p>
83
+ <Button onClick={() => setShowCreateProduct(true)}>Create Your First Product</Button>
84
+ </div>
73
85
  </div>
74
- </div>
75
- )}
86
+ )}
76
87
 
77
- {/* Products List */}
78
- {products.length > 0 && <ProductsList products={products} onProductsChange={loadProducts} />}
88
+ {/* Products List */}
89
+ {products.length > 0 && <ProductsList products={products} onProductsChange={loadProducts} />}
79
90
 
80
- {/* Create Product Modal */}
81
- {showCreateProduct && (
82
- <ProductEditor open={showCreateProduct} onOpenChange={setShowCreateProduct} onSuccess={loadProducts} />
83
- )}
91
+ {/* Create Product Modal */}
92
+ {showCreateProduct && (
93
+ <ProductEditor open={showCreateProduct} onOpenChange={setShowCreateProduct} onSuccess={loadProducts} />
94
+ )}
95
+ </div>
84
96
  </div>
85
97
  );
86
98
  }
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { RoundPageContainer } from "../../../../../components";
4
+ import { Modules } from "../../../../../core";
5
+ import { BillingProvider } from "../../../contexts/BillingContext";
6
+ import { ProductsAdminContainer } from "./ProductsAdminContainer";
7
+
8
+ /**
9
+ * Page shell around the Stripe product/price administration surface.
10
+ *
11
+ * Client component by necessity: `module={Modules.StripeProduct}` is a registry
12
+ * entry with an icon component and methods, which a Server Component cannot pass
13
+ * across the boundary.
14
+ */
15
+ export function ProductsAdminPageContainer() {
16
+ return (
17
+ <BillingProvider>
18
+ {/* forceHeader, unlike the sibling list pages: those get their header row
19
+ (title + actions) from ContentListTable, and this page has no table.
20
+ The strip is what renders BillingProvider's title and its create-product
21
+ action — the same arrangement TokenUsageAdminContainer uses. */}
22
+ <RoundPageContainer module={Modules.StripeProduct} fullWidth forceHeader>
23
+ <ProductsAdminContainer />
24
+ </RoundPageContainer>
25
+ </BillingProvider>
26
+ );
27
+ }
@@ -1 +1,2 @@
1
1
  export * from "./ProductsAdminContainer";
2
+ export * from "./ProductsAdminPageContainer";
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import { RoundPageContainer } from "../../../../components";
4
+ import { Modules } from "../../../../core";
5
+ import { CompanyProvider } from "../../contexts/CompanyContext";
6
+ import { CompaniesList } from "../lists/CompaniesList";
7
+
8
+ /**
9
+ * Page container for the administrative company list.
10
+ *
11
+ * This has to be a client component: `module={Modules.Company}` is a registry
12
+ * entry carrying an icon component and methods, and a Server Component cannot
13
+ * pass it across the boundary ("Only plain objects can be passed to Client
14
+ * Components"). Routes mount this container instead — the same shape as every
15
+ * other list page.
16
+ */
17
+ export function CompaniesListContainer() {
18
+ return (
19
+ <CompanyProvider>
20
+ <RoundPageContainer module={Modules.Company} fullWidth>
21
+ <CompaniesList fullWidth />
22
+ </RoundPageContainer>
23
+ </CompanyProvider>
24
+ );
25
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./AdminCompanyContainer";
2
+ export * from "./CompaniesListContainer";
2
3
  export * from "./CompanyContainer";
@@ -9,7 +9,17 @@ import { CompanyFields, CompanyInterface } from "../../data";
9
9
  import { CompanyService } from "../../data/company.service";
10
10
  import { CompanyEditor } from "../forms/CompanyEditor";
11
11
 
12
- export function CompaniesList() {
12
+ type CompaniesListProps = {
13
+ /**
14
+ * Pass this whenever the list is the page's own content inside a
15
+ * `RoundPageContainer fullWidth`. Without it ContentListTable wraps itself in
16
+ * `rounded-md border`, drawing a second bordered card inside the page's
17
+ * rounded shell — two borders fighting each other at every corner.
18
+ */
19
+ fullWidth?: boolean;
20
+ };
21
+
22
+ export function CompaniesList({ fullWidth }: CompaniesListProps = {}) {
13
23
  const t = useTranslations();
14
24
 
15
25
  const data: DataListRetriever<CompanyInterface> = useDataListRetriever({
@@ -26,6 +36,7 @@ export function CompaniesList() {
26
36
  fields={[CompanyFields.name]}
27
37
  tableGeneratorType={Modules.Company}
28
38
  functions={functions}
39
+ fullWidth={fullWidth}
29
40
  title={t(`entities.companies`, { count: 2 })}
30
41
  />
31
42
  );
@@ -43,10 +43,22 @@ export const CompanyProvider = ({ children, dehydratedCompany, configurationEdit
43
43
  const breadcrumb = () => {
44
44
  const response: BreadcrumbItemData[] = [];
45
45
 
46
+ // The list root always comes first, exactly as UserProvider does it —
47
+ // without it the company list rendered a breadcrumb of just "Home", while
48
+ // every sibling administration page showed its own name.
49
+ //
50
+ // Linked ONLY when it is not the current page: the Company module's
51
+ // `pageUrl` is /companies, which the administration tree does not route, so
52
+ // a link from the list itself would land on a 404.
53
+ response.push({
54
+ name: t(`entities.companies`, { count: 2 }),
55
+ ...(company ? { href: generateUrl({ page: Modules.Company }) } : {}),
56
+ });
57
+
46
58
  if (company)
47
59
  response.push({
48
60
  name: company.name,
49
- href: generateUrl({ page: Modules.Company }),
61
+ href: generateUrl({ page: Modules.Company, id: company.id }),
50
62
  });
51
63
 
52
64
  return response;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import { RoundPageContainer } from "../../../../components";
4
+ import { Modules } from "../../../../core";
5
+ import { UserProvider } from "../../contexts/UserContext";
6
+ import { PlatformUsersList } from "../lists/PlatformUsersList";
7
+
8
+ /**
9
+ * Page container for the platform-wide (cross-company) user list.
10
+ *
11
+ * Client component by necessity: `module={Modules.User}` is a registry entry
12
+ * with an icon component and methods, which a Server Component cannot pass
13
+ * across the boundary.
14
+ */
15
+ export function PlatformUsersContainer() {
16
+ return (
17
+ <UserProvider>
18
+ <RoundPageContainer module={Modules.User} fullWidth>
19
+ <PlatformUsersList fullWidth />
20
+ </RoundPageContainer>
21
+ </UserProvider>
22
+ );
23
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./AllUsersListContainer";
2
+ export * from "./PlatformUsersContainer";
2
3
  export * from "./UserContainer";
3
4
  export * from "./UserIndexContainer";
4
5
  export * from "./UsersListContainer";
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { ContentListTable } from "../../../../components";
5
+ import { Modules } from "../../../../core";
6
+ import { DataListRetriever, useDataListRetriever } from "../../../../hooks";
7
+ import { UserFields, UserInterface } from "../../data";
8
+ import { UserService } from "../../data/user.service";
9
+
10
+ type PlatformUsersListProps = {
11
+ includeDeleted?: boolean;
12
+ fullWidth?: boolean;
13
+ };
14
+
15
+ /**
16
+ * Every user on the platform, across all companies.
17
+ *
18
+ * Deliberately NOT company-scoped: `CompanyUsersList` and `AdminUsersList` both
19
+ * build `/companies/{id}/users` and need a company in context, so they render
20
+ * nothing for a system administrator — who has no Company. This one calls
21
+ * `UserService.findMany`, which hits `GET /users`; that controller widens the
22
+ * result set itself when the caller holds Administrator or CompanyAdministrator
23
+ * (see the backend user controller's `isAdmin` flag), so the same component is
24
+ * safe for both.
25
+ */
26
+ export function PlatformUsersList({ includeDeleted, fullWidth }: PlatformUsersListProps) {
27
+ const t = useTranslations();
28
+
29
+ const data: DataListRetriever<UserInterface> = useDataListRetriever({
30
+ retriever: (params) => UserService.findMany(params),
31
+ retrieverParams: { includeDeleted: includeDeleted },
32
+ module: Modules.User,
33
+ }) as DataListRetriever<UserInterface>;
34
+
35
+ return (
36
+ <ContentListTable
37
+ data={data}
38
+ fields={[UserFields.name, UserFields.email, UserFields.company]}
39
+ tableGeneratorType={Modules.User}
40
+ fullWidth={fullWidth}
41
+ allowSearch
42
+ title={t(`entities.users`, { count: 2 })}
43
+ />
44
+ );
45
+ }
@@ -1,5 +1,6 @@
1
1
  export * from "./AdminUsersList";
2
2
  export * from "./CompanyUsersList";
3
+ export * from "./PlatformUsersList";
3
4
  export * from "./ContributorsList";
4
5
  export * from "./RelevantUsersList";
5
6
  export * from "./RoleUsersList";
@@ -2,6 +2,7 @@ export enum UserFields {
2
2
  userId = "userId",
3
3
  name = "name",
4
4
  email = "email",
5
+ company = "company",
5
6
  relevance = "relevance",
6
7
  createdAt = "createdAt",
7
8
  updatedAt = "updatedAt",
@@ -68,6 +68,23 @@ export const useUserTableStructure: UseTableStructureHook<UserInterface, UserFie
68
68
  enableSorting: false,
69
69
  enableHiding: false,
70
70
  }),
71
+ // Only meaningful on a platform-wide list (PlatformUsersList), where users
72
+ // from every company sit in one table; a company-scoped list would repeat
73
+ // the same value on every row.
74
+ [UserFields.company]: () => ({
75
+ id: "company",
76
+ accessorKey: "company",
77
+ header: t(`entities.companies`, { count: 1 }),
78
+ cell: ({ row }: { row: Row<TableContent<UserInterface>> }) => {
79
+ const company = (row.original.jsonApiData as UserInterface).company;
80
+
81
+ if (!company) return <span className="text-muted-foreground text-xs">—</span>;
82
+
83
+ return <Link href={generateUrl({ page: Modules.Company, id: company.id })}>{company.name}</Link>;
84
+ },
85
+ enableSorting: false,
86
+ enableHiding: false,
87
+ }),
71
88
  [UserFields.relevance]: () => ({
72
89
  id: "relevance",
73
90
  accessorKey: "relevance",