@carlonicora/nextjs-jsonapi 6.32.1 → 6.33.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 (233) hide show
  1. package/dist/{BlockNoteEditor-XIEMUQCO.mjs → BlockNoteEditor-DCVT7YHE.mjs} +4 -4
  2. package/dist/{BlockNoteEditor-RD3JNEV5.js → BlockNoteEditor-ZVK5MD34.js} +24 -24
  3. package/dist/{BlockNoteEditor-RD3JNEV5.js.map → BlockNoteEditor-ZVK5MD34.js.map} +1 -1
  4. package/dist/{BlockNoteViewer-RQBYHBQL.js → BlockNoteViewer-2KPGZSTJ.js} +3 -3
  5. package/dist/{BlockNoteViewer-RQBYHBQL.js.map → BlockNoteViewer-2KPGZSTJ.js.map} +1 -1
  6. package/dist/{BlockNoteViewer-NVSR67X5.mjs → BlockNoteViewer-XDAC6AL7.mjs} +2 -2
  7. package/dist/billing/index.js +477 -469
  8. package/dist/billing/index.js.map +1 -1
  9. package/dist/billing/index.mjs +15 -7
  10. package/dist/billing/index.mjs.map +1 -1
  11. package/dist/{chunk-5H25LM2Z.js → chunk-576XHOYH.js} +5 -5
  12. package/dist/{chunk-5H25LM2Z.js.map → chunk-576XHOYH.js.map} +1 -1
  13. package/dist/{chunk-NPGX2GJD.js → chunk-CI64OTRX.js} +98 -75
  14. package/dist/chunk-CI64OTRX.js.map +1 -0
  15. package/dist/{chunk-AEBZ67JJ.mjs → chunk-ETU3AW2X.mjs} +2 -2
  16. package/dist/{chunk-4JVLLBRS.mjs → chunk-GUNH3THR.mjs} +24 -1
  17. package/dist/chunk-GUNH3THR.mjs.map +1 -0
  18. package/dist/{chunk-SQNFYMYE.js → chunk-KH4Y4FU4.js} +1532 -1288
  19. package/dist/chunk-KH4Y4FU4.js.map +1 -0
  20. package/dist/{chunk-PNCO3HMJ.js → chunk-TCAOU6NH.js} +3 -3
  21. package/dist/{chunk-PNCO3HMJ.js.map → chunk-TCAOU6NH.js.map} +1 -1
  22. package/dist/{chunk-F25BVBEH.mjs → chunk-V4NPMUZ2.mjs} +2663 -2419
  23. package/dist/chunk-V4NPMUZ2.mjs.map +1 -0
  24. package/dist/{chunk-AMQFKCAX.mjs → chunk-WPFEPIKP.mjs} +2 -2
  25. package/dist/client/index.js +6 -4
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/client/index.mjs +5 -3
  28. package/dist/components/index.js +12 -4
  29. package/dist/components/index.js.map +1 -1
  30. package/dist/components/index.mjs +11 -3
  31. package/dist/components/tables/ContentListTable.d.ts +14 -0
  32. package/dist/components/tables/ContentListTable.d.ts.map +1 -1
  33. package/dist/components/tables/SortableHeader.d.ts +17 -0
  34. package/dist/components/tables/SortableHeader.d.ts.map +1 -0
  35. package/dist/components/tables/cells/cell.date.d.ts +4 -0
  36. package/dist/components/tables/cells/cell.date.d.ts.map +1 -1
  37. package/dist/components/tables/cells/cell.dateTime.d.ts +4 -0
  38. package/dist/components/tables/cells/cell.dateTime.d.ts.map +1 -1
  39. package/dist/components/tables/cells/cell.link.d.ts +4 -0
  40. package/dist/components/tables/cells/cell.link.d.ts.map +1 -1
  41. package/dist/components/tables/cells/cell.text.d.ts +4 -0
  42. package/dist/components/tables/cells/cell.text.d.ts.map +1 -1
  43. package/dist/components/tables/index.d.ts +2 -0
  44. package/dist/components/tables/index.d.ts.map +1 -1
  45. package/dist/components/tables/sorting.d.ts +24 -0
  46. package/dist/components/tables/sorting.d.ts.map +1 -0
  47. package/dist/contexts/index.js +4 -4
  48. package/dist/contexts/index.mjs +3 -3
  49. package/dist/core/index.js +2 -2
  50. package/dist/core/index.mjs +1 -1
  51. package/dist/email-automations/index.js +120 -115
  52. package/dist/email-automations/index.js.map +1 -1
  53. package/dist/email-automations/index.mjs +18 -13
  54. package/dist/email-automations/index.mjs.map +1 -1
  55. package/dist/features/analytics/index.js +114 -114
  56. package/dist/features/analytics/index.mjs +3 -3
  57. package/dist/features/analytics/server.js +4 -4
  58. package/dist/features/analytics/server.mjs +2 -2
  59. package/dist/features/billing/stripe-coupon/components/lists/CouponCodesList.d.ts.map +1 -1
  60. package/dist/features/billing/stripe-coupon/components/lists/CouponsList.d.ts.map +1 -1
  61. package/dist/features/billing/stripe-coupon/data/stripe-coupon-code.service.d.ts +2 -0
  62. package/dist/features/billing/stripe-coupon/data/stripe-coupon-code.service.d.ts.map +1 -1
  63. package/dist/features/billing/stripe-coupon/data/stripe-coupon.service.d.ts +1 -0
  64. package/dist/features/billing/stripe-coupon/data/stripe-coupon.service.d.ts.map +1 -1
  65. package/dist/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.d.ts.map +1 -1
  66. package/dist/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.d.ts.map +1 -1
  67. package/dist/features/billing/stripe-price/components/lists/PricesList.d.ts.map +1 -1
  68. package/dist/features/billing/stripe-price/data/stripe-price.service.d.ts +2 -0
  69. package/dist/features/billing/stripe-price/data/stripe-price.service.d.ts.map +1 -1
  70. package/dist/features/billing/stripe-price/hooks/useStripePriceTableStructure.d.ts.map +1 -1
  71. package/dist/features/billing/stripe-product/components/lists/ProductsList.d.ts.map +1 -1
  72. package/dist/features/billing/stripe-product/data/stripe-product.service.d.ts +2 -0
  73. package/dist/features/billing/stripe-product/data/stripe-product.service.d.ts.map +1 -1
  74. package/dist/features/billing/stripe-product/hooks/useStripeProductTableStructure.d.ts.map +1 -1
  75. package/dist/features/blog/components/admin/BlogPostsList.d.ts.map +1 -1
  76. package/dist/features/blog/data/BlogPostService.d.ts +1 -0
  77. package/dist/features/blog/data/BlogPostService.d.ts.map +1 -1
  78. package/dist/features/blog/index.js +134 -132
  79. package/dist/features/blog/index.js.map +1 -1
  80. package/dist/features/blog/index.mjs +8 -6
  81. package/dist/features/blog/index.mjs.map +1 -1
  82. package/dist/features/blog/server.js +7 -7
  83. package/dist/features/blog/server.mjs +1 -1
  84. package/dist/features/company/components/lists/CompaniesList.d.ts.map +1 -1
  85. package/dist/features/company/data/company.service.d.ts +1 -0
  86. package/dist/features/company/data/company.service.d.ts.map +1 -1
  87. package/dist/features/content/data/content.service.d.ts +3 -0
  88. package/dist/features/content/data/content.service.d.ts.map +1 -1
  89. package/dist/features/email-automation/components/lists/EmailAutomationRecipientsList.d.ts.map +1 -1
  90. package/dist/features/email-automation/components/lists/EmailAutomationsList.d.ts.map +1 -1
  91. package/dist/features/email-automation/data/EmailAutomationService.d.ts +3 -0
  92. package/dist/features/email-automation/data/EmailAutomationService.d.ts.map +1 -1
  93. package/dist/features/email-automation/hooks/useEmailAutomationTableStructure.d.ts.map +1 -1
  94. package/dist/features/email-blast/components/lists/EmailBlastsList.d.ts.map +1 -1
  95. package/dist/features/email-blast/data/EmailBlastService.d.ts +1 -0
  96. package/dist/features/email-blast/data/EmailBlastService.d.ts.map +1 -1
  97. package/dist/features/email-blast/hooks/useEmailBlastTableStructure.d.ts.map +1 -1
  98. package/dist/features/help/index.js +37 -37
  99. package/dist/features/help/index.mjs +3 -3
  100. package/dist/features/how-to/components/lists/HowToList.d.ts.map +1 -1
  101. package/dist/features/how-to/data/HowToService.d.ts +2 -0
  102. package/dist/features/how-to/data/HowToService.d.ts.map +1 -1
  103. package/dist/features/notification/components/lists/NotificationsList.d.ts.map +1 -1
  104. package/dist/features/notification/data/notification.service.d.ts +2 -0
  105. package/dist/features/notification/data/notification.service.d.ts.map +1 -1
  106. package/dist/features/referral/components/containers/ReferralsAdminContainer.d.ts.map +1 -1
  107. package/dist/features/referral/data/ReferralAdminService.d.ts +1 -0
  108. package/dist/features/referral/data/ReferralAdminService.d.ts.map +1 -1
  109. package/dist/features/referral/hooks/useReferralAdminReferralTableStructure.d.ts.map +1 -1
  110. package/dist/features/role/components/lists/RolesList.d.ts.map +1 -1
  111. package/dist/features/role/components/lists/UserRolesList.d.ts.map +1 -1
  112. package/dist/features/role/data/role.service.d.ts +3 -0
  113. package/dist/features/role/data/role.service.d.ts.map +1 -1
  114. package/dist/features/tokenusage/index.js +114 -114
  115. package/dist/features/tokenusage/index.mjs +3 -3
  116. package/dist/features/user/components/lists/AdminUsersList.d.ts.map +1 -1
  117. package/dist/features/user/components/lists/CompanyUsersList.d.ts.map +1 -1
  118. package/dist/features/user/components/lists/PlatformUsersList.d.ts.map +1 -1
  119. package/dist/features/user/components/lists/RoleUsersList.d.ts.map +1 -1
  120. package/dist/features/user/components/lists/UsersListByContentIds.d.ts.map +1 -1
  121. package/dist/features/user/data/user.service.d.ts +11 -0
  122. package/dist/features/user/data/user.service.d.ts.map +1 -1
  123. package/dist/features/waitlist/components/lists/WaitlistList.d.ts.map +1 -1
  124. package/dist/features/waitlist/data/WaitlistService.d.ts +2 -0
  125. package/dist/features/waitlist/data/WaitlistService.d.ts.map +1 -1
  126. package/dist/hooks/index.d.ts +1 -0
  127. package/dist/hooks/index.d.ts.map +1 -1
  128. package/dist/hooks/useDataListRetriever.d.ts +13 -0
  129. package/dist/hooks/useDataListRetriever.d.ts.map +1 -1
  130. package/dist/hooks/useListSortParam.d.ts +23 -0
  131. package/dist/hooks/useListSortParam.d.ts.map +1 -0
  132. package/dist/index.js +3 -3
  133. package/dist/index.mjs +2 -2
  134. package/dist/server/index.js +3 -3
  135. package/dist/server/index.mjs +2 -2
  136. package/dist/shadcnui/ui/calendar.d.ts +1 -1
  137. package/dist/shadcnui/ui/calendar.d.ts.map +1 -1
  138. package/messages/en.json +11 -0
  139. package/messages/it.json +11 -0
  140. package/package.json +1 -1
  141. package/src/components/tables/ContentListTable.tsx +108 -10
  142. package/src/components/tables/SortableHeader.tsx +43 -0
  143. package/src/components/tables/__tests__/ContentListTable.rendering.spec.tsx +1 -0
  144. package/src/components/tables/__tests__/ContentListTable.sort.spec.tsx +273 -0
  145. package/src/components/tables/__tests__/sorting.spec.ts +61 -0
  146. package/src/components/tables/cells/cell.date.tsx +10 -2
  147. package/src/components/tables/cells/cell.dateTime.tsx +10 -2
  148. package/src/components/tables/cells/cell.link.tsx +6 -1
  149. package/src/components/tables/cells/cell.text.tsx +11 -2
  150. package/src/components/tables/index.ts +2 -0
  151. package/src/components/tables/sorting.ts +41 -0
  152. package/src/features/billing/stripe-coupon/components/lists/CouponCodesList.tsx +2 -0
  153. package/src/features/billing/stripe-coupon/components/lists/CouponsList.tsx +2 -0
  154. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.service.ts +4 -0
  155. package/src/features/billing/stripe-coupon/data/stripe-coupon.service.ts +7 -1
  156. package/src/features/billing/stripe-coupon/hooks/__tests__/useStripeCouponCodeTableStructure.sort.spec.tsx +30 -0
  157. package/src/features/billing/stripe-coupon/hooks/__tests__/useStripeCouponTableStructure.sort.spec.tsx +33 -0
  158. package/src/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.tsx +5 -4
  159. package/src/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.tsx +7 -4
  160. package/src/features/billing/stripe-price/components/lists/PricesList.tsx +2 -0
  161. package/src/features/billing/stripe-price/data/stripe-price.service.ts +4 -0
  162. package/src/features/billing/stripe-price/hooks/__tests__/useStripePriceTableStructure.sort.spec.tsx +29 -0
  163. package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +8 -5
  164. package/src/features/billing/stripe-product/components/lists/ProductsList.tsx +2 -0
  165. package/src/features/billing/stripe-product/data/stripe-product.service.ts +4 -0
  166. package/src/features/billing/stripe-product/hooks/__tests__/useStripeProductTableStructure.sort.spec.tsx +29 -0
  167. package/src/features/billing/stripe-product/hooks/useStripeProductTableStructure.tsx +3 -2
  168. package/src/features/blog/components/admin/BlogPostsList.tsx +2 -0
  169. package/src/features/blog/data/BlogPostService.ts +3 -1
  170. package/src/features/blog/hooks/__tests__/useBlogPostTableStructure.sort.spec.tsx +19 -0
  171. package/src/features/blog/hooks/useBlogPostTableStructure.tsx +2 -2
  172. package/src/features/company/components/lists/CompaniesList.tsx +2 -0
  173. package/src/features/company/data/company.service.ts +3 -1
  174. package/src/features/company/hooks/__tests__/useCompanyTableStructure.sort.spec.tsx +19 -0
  175. package/src/features/company/hooks/useCompanyTableStructure.tsx +1 -1
  176. package/src/features/content/data/content.service.ts +12 -1
  177. package/src/features/content/hooks/__tests__/useContentTableStructure.sort.spec.tsx +21 -0
  178. package/src/features/content/hooks/useContentTableStructure.tsx +1 -1
  179. package/src/features/email-automation/components/lists/EmailAutomationRecipientsList.tsx +2 -0
  180. package/src/features/email-automation/components/lists/EmailAutomationsList.tsx +2 -0
  181. package/src/features/email-automation/data/EmailAutomationService.ts +9 -1
  182. package/src/features/email-automation/hooks/__tests__/useEmailAutomationEnrollmentTableStructure.sort.spec.tsx +21 -0
  183. package/src/features/email-automation/hooks/__tests__/useEmailAutomationTableStructure.sort.spec.tsx +29 -0
  184. package/src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx +4 -4
  185. package/src/features/email-automation/hooks/useEmailAutomationTableStructure.tsx +5 -4
  186. package/src/features/email-blast/components/lists/EmailBlastsList.tsx +2 -0
  187. package/src/features/email-blast/data/EmailBlastService.ts +5 -1
  188. package/src/features/email-blast/hooks/__tests__/useEmailBlastTableStructure.sort.spec.tsx +29 -0
  189. package/src/features/email-blast/hooks/useEmailBlastTableStructure.tsx +16 -8
  190. package/src/features/how-to/components/lists/HowToList.tsx +2 -0
  191. package/src/features/how-to/data/HowToService.ts +4 -0
  192. package/src/features/how-to/hooks/__tests__/useHowToTableStructure.sort.spec.tsx +18 -0
  193. package/src/features/how-to/hooks/useHowToTableStructure.tsx +1 -1
  194. package/src/features/notification/components/lists/NotificationsList.tsx +2 -0
  195. package/src/features/notification/data/notification.service.ts +4 -0
  196. package/src/features/notification/hooks/__tests__/useNotificationTableStructure.sort.spec.tsx +19 -0
  197. package/src/features/notification/hooks/useNotificationTableStructure.tsx +1 -1
  198. package/src/features/referral/components/containers/ReferralsAdminContainer.tsx +4 -2
  199. package/src/features/referral/data/ReferralAdminService.ts +6 -1
  200. package/src/features/referral/hooks/__tests__/useReferralAdminReferralTableStructure.sort.spec.tsx +35 -0
  201. package/src/features/referral/hooks/useReferralAdminReferralTableStructure.tsx +10 -5
  202. package/src/features/role/components/lists/RolesList.tsx +2 -0
  203. package/src/features/role/components/lists/UserRolesList.tsx +2 -0
  204. package/src/features/role/data/role.service.ts +7 -1
  205. package/src/features/role/hooks/__tests__/useRoleTableStructure.sort.spec.tsx +16 -0
  206. package/src/features/role/hooks/useRoleTableStructure.tsx +2 -2
  207. package/src/features/user/components/lists/AdminUsersList.tsx +2 -0
  208. package/src/features/user/components/lists/CompanyUsersList.tsx +4 -0
  209. package/src/features/user/components/lists/PlatformUsersList.tsx +2 -0
  210. package/src/features/user/components/lists/RoleUsersList.tsx +2 -0
  211. package/src/features/user/components/lists/UsersListByContentIds.tsx +2 -0
  212. package/src/features/user/data/user.service.ts +28 -1
  213. package/src/features/user/hooks/__tests__/useUserTableStructure.sort.spec.tsx +16 -0
  214. package/src/features/user/hooks/useUserTableStructure.tsx +2 -2
  215. package/src/features/waitlist/components/lists/WaitlistList.tsx +2 -0
  216. package/src/features/waitlist/data/WaitlistService.ts +4 -0
  217. package/src/features/waitlist/hooks/__tests__/useWaitlistTableStructure.sort.spec.tsx +17 -0
  218. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +3 -3
  219. package/src/hooks/__tests__/useDataListRetriever.sort.spec.tsx +114 -0
  220. package/src/hooks/__tests__/useListSortParam.spec.tsx +91 -0
  221. package/src/hooks/index.ts +1 -0
  222. package/src/hooks/useDataListRetriever.ts +44 -4
  223. package/src/hooks/useListSortParam.ts +53 -0
  224. package/src/shadcnui/ui/__tests__/calendar.spec.tsx +222 -0
  225. package/src/shadcnui/ui/calendar.tsx +28 -1
  226. package/dist/chunk-4JVLLBRS.mjs.map +0 -1
  227. package/dist/chunk-F25BVBEH.mjs.map +0 -1
  228. package/dist/chunk-NPGX2GJD.js.map +0 -1
  229. package/dist/chunk-SQNFYMYE.js.map +0 -1
  230. /package/dist/{BlockNoteEditor-XIEMUQCO.mjs.map → BlockNoteEditor-DCVT7YHE.mjs.map} +0 -0
  231. /package/dist/{BlockNoteViewer-NVSR67X5.mjs.map → BlockNoteViewer-XDAC6AL7.mjs.map} +0 -0
  232. /package/dist/{chunk-AEBZ67JJ.mjs.map → chunk-ETU3AW2X.mjs.map} +0 -0
  233. /package/dist/{chunk-AMQFKCAX.mjs.map → chunk-WPFEPIKP.mjs.map} +0 -0
@@ -1,3 +1,5 @@
1
1
  export * from "./cells";
2
2
  export * from "./ContentListTable";
3
3
  export * from "./ContentTableSearch";
4
+ export * from "./SortableHeader";
5
+ export * from "./sorting";
@@ -0,0 +1,41 @@
1
+ /** Direction of a column sort. */
2
+ export type SortDirection = "asc" | "desc";
3
+
4
+ /**
5
+ * The fields a column sorts by: its `meta.sortFields` when declared (a column
6
+ * built from several stored fields, or whose stored property differs from its
7
+ * id), otherwise its id.
8
+ */
9
+ export function sortFieldsOf(column: { id?: string; columnDef: { meta?: unknown } }): string[] {
10
+ const meta = column.columnDef.meta as { sortFields?: unknown } | undefined;
11
+ if (Array.isArray(meta?.sortFields) && meta.sortFields.length > 0) {
12
+ return meta.sortFields.filter((field): field is string => typeof field === "string");
13
+ }
14
+ return column.id ? [column.id] : [];
15
+ }
16
+
17
+ /**
18
+ * The direction `sort` (JSON:API wire format) gives a column sorting by `fields`,
19
+ * or `undefined` when `sort` is about another column or absent.
20
+ */
21
+ export function sortStateFor(sort: string | undefined, fields: string[]): SortDirection | undefined {
22
+ if (!sort || fields.length === 0) return undefined;
23
+ if (sort === fields.join(",")) return "asc";
24
+ if (sort === fields.map((field) => `-${field}`).join(",")) return "desc";
25
+ return undefined;
26
+ }
27
+
28
+ /**
29
+ * The sort a header click sets: none → ascending → descending → none.
30
+ * Returns `null` to clear the sort.
31
+ */
32
+ export function nextSortValue(current: string | undefined, fields: string[]): string | null {
33
+ switch (sortStateFor(current, fields)) {
34
+ case undefined:
35
+ return fields.join(",");
36
+ case "asc":
37
+ return fields.map((field) => `-${field}`).join(",");
38
+ case "desc":
39
+ return null;
40
+ }
41
+ }
@@ -25,6 +25,8 @@ export function CouponCodesList({ couponId }: CouponCodesListProps) {
25
25
  retriever: (params) => StripeCouponCodeService.listByCoupon({ ...params, couponId }),
26
26
  retrieverParams: {},
27
27
  module: Modules.StripeCouponCode,
28
+ // Display only (call C8): the server's own order when no sort is chosen.
29
+ defaultSort: "createdAt",
28
30
  });
29
31
 
30
32
  // Switching "Apply automatically" on one code clears it on the others in the
@@ -31,6 +31,8 @@ export function CouponsList({ fullWidth }: CouponsListProps = {}) {
31
31
  retriever: (params) => StripeCouponService.listCoupons(params),
32
32
  retrieverParams: {},
33
33
  module: Modules.StripeCoupon,
34
+ // Display only (call C8): the server's own order when no sort is chosen.
35
+ defaultSort: "-updatedAt",
34
36
  });
35
37
 
36
38
  // Renames and code changes happen on the detail page, so the list has to
@@ -18,6 +18,8 @@ export class StripeCouponCodeService extends AbstractService {
18
18
  couponId: string;
19
19
  next?: NextRef;
20
20
  prev?: PreviousRef;
21
+ /** JSON:API `sort` (`name`, `-name`), forwarded to the API. */
22
+ sort?: string;
21
23
  }): Promise<StripeCouponCodeInterface[]> {
22
24
  const endpoint = new EndpointCreator({
23
25
  endpoint: Modules.StripeCoupon,
@@ -25,6 +27,8 @@ export class StripeCouponCodeService extends AbstractService {
25
27
  childEndpoint: Modules.StripeCouponCode,
26
28
  });
27
29
 
30
+ if (params.sort) endpoint.addAdditionalParam("sort", params.sort);
31
+
28
32
  return this.callApi({
29
33
  type: Modules.StripeCouponCode,
30
34
  method: HttpMethod.GET,
@@ -10,11 +10,17 @@ export class StripeCouponService extends AbstractService {
10
10
  /**
11
11
  * List all coupons with their codes (admin)
12
12
  */
13
- static async listCoupons(params?: { next?: NextRef; prev?: PreviousRef }): Promise<StripeCouponInterface[]> {
13
+ static async listCoupons(params?: {
14
+ next?: NextRef;
15
+ prev?: PreviousRef;
16
+ sort?: string;
17
+ }): Promise<StripeCouponInterface[]> {
14
18
  const endpoint = new EndpointCreator({
15
19
  endpoint: Modules.StripeCoupon,
16
20
  });
17
21
 
22
+ if (params?.sort) endpoint.addAdditionalParam("sort", params?.sort);
23
+
18
24
  return this.callApi({
19
25
  type: Modules.StripeCoupon,
20
26
  method: HttpMethod.GET,
@@ -0,0 +1,30 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { StripeCouponCodeFields } from "../../data/stripe-coupon-code.fields";
4
+ import { useStripeCouponCodeTableStructure } from "../useStripeCouponCodeTableStructure";
5
+
6
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, useLocale: () => "en" }));
7
+ vi.mock("../../../contexts/CouponContext", () => ({ useCouponContext: () => ({}) }));
8
+
9
+ /** Spec row A21: code, expiresAt, automatic, places (timesRedeemed); status is derived. Only an explicit `enableSorting: true` opts a column in (call C3). */
10
+ describe("useStripeCouponCodeTableStructure sorting", () => {
11
+ it("opts in exactly the row's sortable columns", () => {
12
+ const { result } = renderHook(() =>
13
+ useStripeCouponCodeTableStructure({ data: [], fields: Object.values(StripeCouponCodeFields) as any }),
14
+ );
15
+ const columns = result.current.columns as any[];
16
+ const sortable = columns.filter((column) => column.enableSorting === true).map((column) => column.id);
17
+ expect(sortable.sort()).toEqual(["automatic", "code", "expiresAt", "places"]);
18
+ });
19
+
20
+ it("maps the columns whose key differs from the stored property (call C4)", () => {
21
+ const { result } = renderHook(() =>
22
+ useStripeCouponCodeTableStructure({ data: [], fields: Object.values(StripeCouponCodeFields) as any }),
23
+ );
24
+ const columns = result.current.columns as any[];
25
+ const sortFields = Object.fromEntries(
26
+ columns.filter((column) => column.meta?.sortFields).map((column) => [column.id, column.meta.sortFields]),
27
+ );
28
+ expect(sortFields).toEqual({ places: ["timesRedeemed"] });
29
+ });
30
+ });
@@ -0,0 +1,33 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { StripeCouponFields } from "../../data/stripe-coupon.fields";
4
+ import { useStripeCouponTableStructure } from "../useStripeCouponTableStructure";
5
+
6
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, useLocale: () => "en" }));
7
+
8
+ /** Spec row A20: name, status (valid), places, discount; codes (a count) is not sortable. Only an explicit `enableSorting: true` opts a column in (call C3). */
9
+ describe("useStripeCouponTableStructure sorting", () => {
10
+ it("opts in exactly the row's sortable columns", () => {
11
+ const { result } = renderHook(() =>
12
+ useStripeCouponTableStructure({ data: [], fields: Object.values(StripeCouponFields) as any }),
13
+ );
14
+ const columns = result.current.columns as any[];
15
+ const sortable = columns.filter((column) => column.enableSorting === true).map((column) => column.id);
16
+ expect(sortable.sort()).toEqual(["discount", "name", "places", "status"]);
17
+ });
18
+
19
+ it("maps the columns whose key differs from the stored property (call C4)", () => {
20
+ const { result } = renderHook(() =>
21
+ useStripeCouponTableStructure({ data: [], fields: Object.values(StripeCouponFields) as any }),
22
+ );
23
+ const columns = result.current.columns as any[];
24
+ const sortFields = Object.fromEntries(
25
+ columns.filter((column) => column.meta?.sortFields).map((column) => [column.id, column.meta.sortFields]),
26
+ );
27
+ expect(sortFields).toEqual({
28
+ discount: ["discountType", "percentOff", "amountOff"],
29
+ places: ["timesRedeemed", "maxRedemptions"],
30
+ status: ["valid"],
31
+ });
32
+ });
33
+ });
@@ -70,7 +70,7 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
70
70
  const code: StripeCouponCodeInterface = row.original.jsonApiData;
71
71
  return <span className="text-xs">{code.code}</span>;
72
72
  },
73
- enableSorting: false,
73
+ enableSorting: true,
74
74
  enableHiding: false,
75
75
  }),
76
76
  [StripeCouponCodeFields.places]: () => ({
@@ -91,7 +91,8 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
91
91
  </span>
92
92
  );
93
93
  },
94
- enableSorting: false,
94
+ meta: { sortFields: ["timesRedeemed"] },
95
+ enableSorting: true,
95
96
  enableHiding: false,
96
97
  }),
97
98
  [StripeCouponCodeFields.expiresAt]: () => ({
@@ -106,7 +107,7 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
106
107
  </span>
107
108
  );
108
109
  },
109
- enableSorting: false,
110
+ enableSorting: true,
110
111
  enableHiding: false,
111
112
  }),
112
113
  [StripeCouponCodeFields.automatic]: () => ({
@@ -121,7 +122,7 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
121
122
  <span className="text-muted-foreground text-xs">—</span>
122
123
  );
123
124
  },
124
- enableSorting: false,
125
+ enableSorting: true,
125
126
  enableHiding: false,
126
127
  }),
127
128
  [StripeCouponCodeFields.status]: () => ({
@@ -37,7 +37,7 @@ export const useStripeCouponTableStructure: UseTableStructureHook<StripeCouponIn
37
37
  const coupon: StripeCouponInterface = row.original.jsonApiData;
38
38
  return <Link href={generateUrl({ page: STRIPE_COUPON_DETAIL_PAGE_URL, id: coupon.id })}>{coupon.name}</Link>;
39
39
  },
40
- enableSorting: false,
40
+ enableSorting: true,
41
41
  enableHiding: false,
42
42
  }),
43
43
  [StripeCouponFields.discount]: () => ({
@@ -48,7 +48,8 @@ export const useStripeCouponTableStructure: UseTableStructureHook<StripeCouponIn
48
48
  const coupon: StripeCouponInterface = row.original.jsonApiData;
49
49
  return <span className="text-xs">{formatCouponDiscount(coupon, t, fmt)}</span>;
50
50
  },
51
- enableSorting: false,
51
+ meta: { sortFields: ["discountType", "percentOff", "amountOff"] },
52
+ enableSorting: true,
52
53
  enableHiding: false,
53
54
  }),
54
55
  [StripeCouponFields.places]: () => ({
@@ -70,7 +71,8 @@ export const useStripeCouponTableStructure: UseTableStructureHook<StripeCouponIn
70
71
  </span>
71
72
  );
72
73
  },
73
- enableSorting: false,
74
+ meta: { sortFields: ["timesRedeemed", "maxRedemptions"] },
75
+ enableSorting: true,
74
76
  enableHiding: false,
75
77
  }),
76
78
  [StripeCouponFields.codes]: () => ({
@@ -96,7 +98,8 @@ export const useStripeCouponTableStructure: UseTableStructureHook<StripeCouponIn
96
98
  <Badge variant="softGray">{t("billing.admin.coupons.status.ended")}</Badge>
97
99
  );
98
100
  },
99
- enableSorting: false,
101
+ meta: { sortFields: ["valid"] },
102
+ enableSorting: true,
100
103
  enableHiding: false,
101
104
  }),
102
105
  };
@@ -50,6 +50,8 @@ export function PricesList({ productId, fullWidth }: PricesListProps) {
50
50
  retriever: (params) => StripePriceService.listPrices({ ...params, productId }),
51
51
  retrieverParams: {},
52
52
  module: Modules.StripePrice,
53
+ // Display only (call C8): the server's own order when no sort is chosen.
54
+ defaultSort: "-createdAt",
53
55
  });
54
56
 
55
57
  useEffect(() => {
@@ -13,6 +13,8 @@ export class StripePriceService extends AbstractService {
13
13
  active?: boolean;
14
14
  next?: NextRef;
15
15
  prev?: PreviousRef;
16
+ /** JSON:API `sort` (`name`, `-name`), forwarded to the API. */
17
+ sort?: string;
16
18
  }): Promise<StripePriceInterface[]> {
17
19
  const endpoint = new EndpointCreator({
18
20
  endpoint: Modules.StripePrice,
@@ -25,6 +27,8 @@ export class StripePriceService extends AbstractService {
25
27
  endpoint.addAdditionalParam("active", params.active.toString());
26
28
  }
27
29
 
30
+ if (params?.sort) endpoint.addAdditionalParam("sort", params?.sort);
31
+
28
32
  return this.callApi({
29
33
  type: Modules.StripePrice,
30
34
  method: HttpMethod.GET,
@@ -0,0 +1,29 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { StripePriceFields } from "../../data/stripe-price.fields";
4
+ import { useStripePriceTableStructure } from "../useStripePriceTableStructure";
5
+
6
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, useLocale: () => "en" }));
7
+
8
+ /** Spec row A22: nickname, amount, interval, token, status. Only an explicit `enableSorting: true` opts a column in (call C3). */
9
+ describe("useStripePriceTableStructure sorting", () => {
10
+ it("opts in exactly the row's sortable columns", () => {
11
+ const { result } = renderHook(() =>
12
+ useStripePriceTableStructure({ data: [], fields: Object.values(StripePriceFields) as any }),
13
+ );
14
+ const columns = result.current.columns as any[];
15
+ const sortable = columns.filter((column) => column.enableSorting === true).map((column) => column.id);
16
+ expect(sortable.sort()).toEqual(["amount", "interval", "nickname", "status", "token"]);
17
+ });
18
+
19
+ it("maps the columns whose key differs from the stored property (call C4)", () => {
20
+ const { result } = renderHook(() =>
21
+ useStripePriceTableStructure({ data: [], fields: Object.values(StripePriceFields) as any }),
22
+ );
23
+ const columns = result.current.columns as any[];
24
+ const sortFields = Object.fromEntries(
25
+ columns.filter((column) => column.meta?.sortFields).map((column) => [column.id, column.meta.sortFields]),
26
+ );
27
+ expect(sortFields).toEqual({ amount: ["unitAmount"], interval: ["recurringInterval"], status: ["active"] });
28
+ });
29
+ });
@@ -61,7 +61,7 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
61
61
  </Link>
62
62
  );
63
63
  },
64
- enableSorting: false,
64
+ enableSorting: true,
65
65
  enableHiding: false,
66
66
  }),
67
67
  [StripePriceFields.amount]: () => ({
@@ -77,7 +77,8 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
77
77
  </span>
78
78
  );
79
79
  },
80
- enableSorting: false,
80
+ meta: { sortFields: ["unitAmount"] },
81
+ enableSorting: true,
81
82
  enableHiding: false,
82
83
  }),
83
84
  [StripePriceFields.interval]: () => ({
@@ -89,7 +90,8 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
89
90
  // Typography role 8 (UI chrome): single-line, plain text-xs.
90
91
  return <span className="text-xs">{formatInterval(price, fmt)}</span>;
91
92
  },
92
- enableSorting: false,
93
+ meta: { sortFields: ["recurringInterval"] },
94
+ enableSorting: true,
93
95
  enableHiding: false,
94
96
  }),
95
97
  [StripePriceFields.token]: () => ({
@@ -100,7 +102,7 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
100
102
  const price: StripePriceInterface = row.original.jsonApiData;
101
103
  return <span className="block text-xs tabular-nums text-end">{price.token ?? ""}</span>;
102
104
  },
103
- enableSorting: false,
105
+ enableSorting: true,
104
106
  enableHiding: false,
105
107
  }),
106
108
  [StripePriceFields.status]: () => ({
@@ -123,7 +125,8 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
123
125
  </span>
124
126
  );
125
127
  },
126
- enableSorting: false,
128
+ meta: { sortFields: ["active"] },
129
+ enableSorting: true,
127
130
  enableHiding: false,
128
131
  }),
129
132
  [StripePriceFields.actions]: () => ({
@@ -31,6 +31,8 @@ export function ProductsList({ fullWidth }: ProductsListProps = {}) {
31
31
  retriever: (params) => StripeProductService.listProducts(params),
32
32
  retrieverParams: {},
33
33
  module: Modules.StripeProduct,
34
+ // Display only (call C8): the server's own order when no sort is chosen.
35
+ defaultSort: "name",
34
36
  });
35
37
 
36
38
  // Archiving and restoring happen on the detail page, so the list has to learn
@@ -9,6 +9,8 @@ export class StripeProductService extends AbstractService {
9
9
  active?: boolean;
10
10
  next?: NextRef;
11
11
  prev?: PreviousRef;
12
+ /** JSON:API `sort` (`name`, `-name`), forwarded to the API. */
13
+ sort?: string;
12
14
  }): Promise<StripeProductInterface[]> {
13
15
  const endpoint = new EndpointCreator({
14
16
  endpoint: Modules.StripeProduct,
@@ -18,6 +20,8 @@ export class StripeProductService extends AbstractService {
18
20
  endpoint.addAdditionalParam("active", params.active.toString());
19
21
  }
20
22
 
23
+ if (params?.sort) endpoint.addAdditionalParam("sort", params?.sort);
24
+
21
25
  return this.callApi({
22
26
  type: Modules.StripeProduct,
23
27
  method: HttpMethod.GET,
@@ -0,0 +1,29 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { StripeProductFields } from "../../data/stripe-product.fields";
4
+ import { useStripeProductTableStructure } from "../useStripeProductTableStructure";
5
+
6
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, useLocale: () => "en" }));
7
+
8
+ /** Spec row A23: name, status (active); prices (a count) is not sortable. Only an explicit `enableSorting: true` opts a column in (call C3). */
9
+ describe("useStripeProductTableStructure sorting", () => {
10
+ it("opts in exactly the row's sortable columns", () => {
11
+ const { result } = renderHook(() =>
12
+ useStripeProductTableStructure({ data: [], fields: Object.values(StripeProductFields) as any }),
13
+ );
14
+ const columns = result.current.columns as any[];
15
+ const sortable = columns.filter((column) => column.enableSorting === true).map((column) => column.id);
16
+ expect(sortable.sort()).toEqual(["name", "status"]);
17
+ });
18
+
19
+ it("maps the columns whose key differs from the stored property (call C4)", () => {
20
+ const { result } = renderHook(() =>
21
+ useStripeProductTableStructure({ data: [], fields: Object.values(StripeProductFields) as any }),
22
+ );
23
+ const columns = result.current.columns as any[];
24
+ const sortFields = Object.fromEntries(
25
+ columns.filter((column) => column.meta?.sortFields).map((column) => [column.id, column.meta.sortFields]),
26
+ );
27
+ expect(sortFields).toEqual({ status: ["active"] });
28
+ });
29
+ });
@@ -35,7 +35,7 @@ export const useStripeProductTableStructure: UseTableStructureHook<StripeProduct
35
35
  const product: StripeProductInterface = row.original.jsonApiData;
36
36
  return <Link href={generateUrl({ page: Modules.StripeProduct, id: product.id })}>{product.name}</Link>;
37
37
  },
38
- enableSorting: false,
38
+ enableSorting: true,
39
39
  enableHiding: false,
40
40
  }),
41
41
  [StripeProductFields.status]: () => ({
@@ -50,7 +50,8 @@ export const useStripeProductTableStructure: UseTableStructureHook<StripeProduct
50
50
  <Badge variant="softGray">{t("billing.admin.products.status.archived")}</Badge>
51
51
  );
52
52
  },
53
- enableSorting: false,
53
+ meta: { sortFields: ["active"] },
54
+ enableSorting: true,
54
55
  enableHiding: false,
55
56
  }),
56
57
  [StripeProductFields.prices]: () => ({
@@ -24,6 +24,8 @@ export function BlogPostsList({ fullWidth }: BlogPostsListProps = {}) {
24
24
  retriever: (params) => BlogPostService.findMany(params),
25
25
  retrieverParams: {},
26
26
  module: Modules.BlogPost,
27
+ // Display only (call C8): the server's own order when no sort is chosen.
28
+ defaultSort: "-updatedAt",
27
29
  });
28
30
 
29
31
  // Creating navigates to the new post's page (EditorSheet's default without onSuccess).
@@ -3,11 +3,13 @@ import { BLOG_ACTION } from "../types/blog.constants";
3
3
  import { BlogPostInput, BlogPostInterface } from "./BlogPostInterface";
4
4
 
5
5
  export class BlogPostService extends AbstractService {
6
- static async findMany(params: { search?: string; next?: NextRef } = {}): Promise<BlogPostInterface[]> {
6
+ static async findMany(params: { search?: string; next?: NextRef; sort?: string } = {}): Promise<BlogPostInterface[]> {
7
7
  const endpoint = new EndpointCreator({ endpoint: Modules.BlogPost });
8
8
 
9
9
  if (params.search) endpoint.addAdditionalParam("search", params.search);
10
10
 
11
+ if (params.sort) endpoint.addAdditionalParam("sort", params.sort);
12
+
11
13
  return this.callApi<BlogPostInterface[]>({
12
14
  type: Modules.BlogPost,
13
15
  method: HttpMethod.GET,
@@ -0,0 +1,19 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { BlogPostFields } from "../../data";
4
+ import { useBlogPostTableStructure } from "../useBlogPostTableStructure";
5
+
6
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, useLocale: () => "en" }));
7
+ vi.mock("../useBlogCategoryNames", () => ({ useBlogCategoryNames: () => () => "" }));
8
+
9
+ /** Spec row A14: status, publishedAt. Only an explicit `enableSorting: true` opts a column in (call C3). */
10
+ describe("useBlogPostTableStructure sorting", () => {
11
+ it("opts in exactly the row's sortable columns", () => {
12
+ const { result } = renderHook(() =>
13
+ useBlogPostTableStructure({ data: [], fields: Object.values(BlogPostFields) as any }),
14
+ );
15
+ const columns = result.current.columns as any[];
16
+ const sortable = columns.filter((column) => column.enableSorting === true).map((column) => column.id);
17
+ expect(sortable.sort()).toEqual(["publishedAt", "status"]);
18
+ });
19
+ });
@@ -62,7 +62,7 @@ export const useBlogPostTableStructure: UseTableStructureHook<BlogPostInterface,
62
62
  }[post.status];
63
63
  return <Badge variant={BLOG_STATUS_BADGE[post.status] ?? "softGray"}>{label ?? post.status}</Badge>;
64
64
  },
65
- enableSorting: false,
65
+ enableSorting: true,
66
66
  enableHiding: false,
67
67
  }),
68
68
  [BlogPostFields.categories]: () => ({
@@ -87,7 +87,7 @@ export const useBlogPostTableStructure: UseTableStructureHook<BlogPostInterface,
87
87
  if (!post.publishedAt) return <span className="text-muted-foreground text-xs">—</span>;
88
88
  return <span className="text-xs tabular-nums">{dates.format(post.publishedAt, "dateMedium")}</span>;
89
89
  },
90
- enableSorting: false,
90
+ enableSorting: true,
91
91
  enableHiding: false,
92
92
  }),
93
93
  [BlogPostFields.languages]: () => ({
@@ -26,6 +26,8 @@ export function CompaniesList({ fullWidth }: CompaniesListProps = {}) {
26
26
  retriever: (params) => CompanyService.findMany(params),
27
27
  retrieverParams: {},
28
28
  module: Modules.Company,
29
+ // Display only (call C8): the server's own order when no sort is chosen.
30
+ defaultSort: "name",
29
31
  });
30
32
 
31
33
  const functions: ReactNode[] = [<CompanyEditor key="create-account" />];
@@ -11,11 +11,13 @@ export class CompanyService extends AbstractService {
11
11
  });
12
12
  }
13
13
 
14
- static async findMany(params: { search?: string; next?: NextRef }): Promise<CompanyInterface[]> {
14
+ static async findMany(params: { search?: string; next?: NextRef; sort?: string }): Promise<CompanyInterface[]> {
15
15
  const endpoint = new EndpointCreator({ endpoint: Modules.Company });
16
16
 
17
17
  if (params.search) endpoint.addAdditionalParam("search", params.search);
18
18
 
19
+ if (params.sort) endpoint.addAdditionalParam("sort", params.sort);
20
+
19
21
  return this.callApi<CompanyInterface[]>({
20
22
  type: Modules.Company,
21
23
  method: HttpMethod.GET,
@@ -0,0 +1,19 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { CompanyFields } from "../../data";
4
+ import { useCompanyTableStructure } from "../useCompanyTableStructure";
5
+
6
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, useLocale: () => "en" }));
7
+ vi.mock("../../../user/contexts", () => ({ useCurrentUserContext: () => ({ currentUser: undefined }) }));
8
+
9
+ /** Spec row A24: name. Only an explicit `enableSorting: true` opts a column in (call C3). */
10
+ describe("useCompanyTableStructure sorting", () => {
11
+ it("opts in exactly the row's sortable columns", () => {
12
+ const { result } = renderHook(() =>
13
+ useCompanyTableStructure({ data: [], fields: Object.values(CompanyFields) as any }),
14
+ );
15
+ const columns = result.current.columns as any[];
16
+ const sortable = columns.filter((column) => column.enableSorting === true).map((column) => column.id);
17
+ expect(sortable.sort()).toEqual(["name"]);
18
+ });
19
+ });
@@ -60,7 +60,7 @@ export const useCompanyTableStructure: UseTableStructureHook<CompanyInterface, C
60
60
  </Link>
61
61
  );
62
62
  },
63
- enableSorting: false,
63
+ enableSorting: true,
64
64
  enableHiding: false,
65
65
  }),
66
66
  [CompanyFields.createdAt]: () =>
@@ -8,6 +8,8 @@ export class ContentService extends AbstractService {
8
8
  fetchAll?: boolean;
9
9
  next?: NextRef;
10
10
  prev?: PreviousRef;
11
+ /** JSON:API `sort` (`name`, `-name`), forwarded to the API. */
12
+ sort?: string;
11
13
  }): Promise<ContentInterface[]> {
12
14
  const endpoint = new EndpointCreator({ endpoint: Modules.Content });
13
15
 
@@ -21,6 +23,8 @@ export class ContentService extends AbstractService {
21
23
  if (Modules.Content.inclusions?.lists?.fields) endpoint.limitToFields(Modules.Content.inclusions.lists.fields);
22
24
  if (Modules.Content.inclusions?.lists?.types) endpoint.limitToType(Modules.Content.inclusions.lists.types);
23
25
 
26
+ if (params.sort) endpoint.addAdditionalParam("sort", params.sort);
27
+
24
28
  return this.callApi({
25
29
  type: Modules.Content,
26
30
  method: HttpMethod.GET,
@@ -29,7 +33,12 @@ export class ContentService extends AbstractService {
29
33
  });
30
34
  }
31
35
 
32
- static async findRelevant(params: { id: string; next?: NextRef; prev?: PreviousRef }): Promise<ContentInterface[]> {
36
+ static async findRelevant(params: {
37
+ id: string;
38
+ next?: NextRef;
39
+ prev?: PreviousRef;
40
+ sort?: string;
41
+ }): Promise<ContentInterface[]> {
33
42
  const endpoint: EndpointCreator = new EndpointCreator({
34
43
  endpoint: Modules.Content,
35
44
  id: params.id,
@@ -39,6 +48,8 @@ export class ContentService extends AbstractService {
39
48
  if (Modules.Content.inclusions?.lists?.fields) endpoint.limitToFields(Modules.Content.inclusions.lists.fields);
40
49
  if (Modules.Content.inclusions?.lists?.types) endpoint.limitToType(Modules.Content.inclusions.lists.types);
41
50
 
51
+ if (params.sort) endpoint.addAdditionalParam("sort", params.sort);
52
+
42
53
  return this.callApi<ContentInterface[]>({
43
54
  type: Modules.Content,
44
55
  method: HttpMethod.GET,
@@ -0,0 +1,21 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ // Loads the core barrel first, as the app does: the hook reaches it through the components barrel,
4
+ // which on its own resolves a model module before its base class (circular import).
5
+ import "../../../../core";
6
+ import { ContentFields } from "../../data";
7
+ import { useContentTableStructure } from "../useContentTableStructure";
8
+
9
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key, useLocale: () => "en" }));
10
+
11
+ /** Spec rows A7, A10: name. Only an explicit `enableSorting: true` opts a column in (call C3). */
12
+ describe("useContentTableStructure sorting", () => {
13
+ it("opts in exactly the row's sortable columns", () => {
14
+ const { result } = renderHook(() =>
15
+ useContentTableStructure({ data: [], fields: Object.values(ContentFields) as any }),
16
+ );
17
+ const columns = result.current.columns as any[];
18
+ const sortable = columns.filter((column) => column.enableSorting === true).map((column) => column.id);
19
+ expect(sortable.sort()).toEqual(["name"]);
20
+ });
21
+ });
@@ -58,7 +58,7 @@ export const useContentTableStructure = <U extends string = ContentFields>(
58
58
  </Tooltip>
59
59
  );
60
60
  },
61
- enableSorting: false,
61
+ enableSorting: true,
62
62
  enableHiding: false,
63
63
  }),
64
64
  [ContentFields.relevance]: () => ({
@@ -28,6 +28,8 @@ export function EmailAutomationRecipientsList() {
28
28
  retriever: (params) => EmailAutomationService.findEnrollments(params),
29
29
  retrieverParams: { automationId: automation?.id },
30
30
  module: Modules.EmailAutomationEnrollment,
31
+ // Display only (call C8): the server's own order when no sort is chosen.
32
+ defaultSort: "-enrolledAt",
31
33
  });
32
34
 
33
35
  const handleStatusChange = (value: string | null) => {