@stacksjs/defaults 0.75.279 → 0.75.281

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 (60) hide show
  1. package/ai/skills/stacks-database/SKILL.md +1 -3
  2. package/ai/skills/stacks-native/CATALOG.md +4 -4
  3. package/ide/vscode/package.json +1 -1
  4. package/package.json +3 -3
  5. package/project/bun-version +1 -1
  6. package/resources/components/Dashboard/Analytics/CampaignAnalytics.stx +28 -30
  7. package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +8 -28
  8. package/resources/components/Dashboard/Commerce/CommerceCategoriesTable.stx +72 -29
  9. package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +8 -29
  10. package/resources/components/Dashboard/Commerce/CommerceCouponsTable.stx +69 -49
  11. package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +8 -27
  12. package/resources/components/Dashboard/Commerce/CommerceCustomersTable.stx +70 -48
  13. package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +8 -29
  14. package/resources/components/Dashboard/Commerce/CommerceGiftCardsTable.stx +72 -82
  15. package/resources/components/Dashboard/Commerce/CommerceManufacturersDashboard.stx +1 -1
  16. package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +3 -0
  17. package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +8 -28
  18. package/resources/components/Dashboard/Commerce/CommerceOrdersTable.stx +76 -49
  19. package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +1 -1
  20. package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +3 -0
  21. package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +8 -28
  22. package/resources/components/Dashboard/Commerce/CommerceProductsTable.stx +81 -34
  23. package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +11 -37
  24. package/resources/components/Dashboard/Commerce/CommerceReviewsTable.stx +106 -77
  25. package/resources/components/Dashboard/Commerce/CommerceTaxesDashboard.stx +1 -1
  26. package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +3 -0
  27. package/resources/components/Dashboard/Commerce/CommerceUnitsDashboard.stx +1 -1
  28. package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +3 -0
  29. package/resources/components/Dashboard/Commerce/CommerceVariantsDashboard.stx +1 -1
  30. package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +3 -0
  31. package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +1 -11
  32. package/resources/components/Dashboard/Commerce/Delivery/CouriersTable.stx +55 -82
  33. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +1 -12
  34. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +57 -86
  35. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveriesDashboard.stx +1 -24
  36. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +62 -80
  37. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +1 -12
  38. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +58 -99
  39. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsDashboard.stx +1 -23
  40. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +51 -86
  41. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +1 -12
  42. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +58 -71
  43. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +1 -12
  44. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +85 -110
  45. package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +8 -28
  46. package/resources/components/Dashboard/Commerce/PrintDevicesTable.stx +62 -57
  47. package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +7 -24
  48. package/resources/components/Dashboard/Marketing/AbandonedCartsTable.stx +63 -59
  49. package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +7 -25
  50. package/resources/components/Dashboard/Marketing/CampaignsTable.stx +80 -57
  51. package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +7 -27
  52. package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +66 -71
  53. package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +7 -25
  54. package/resources/components/Dashboard/Marketing/SocialPostsTable.stx +75 -62
  55. package/resources/components/Dashboard/Monitoring/ErrorDashboard.stx +9 -50
  56. package/resources/components/Dashboard/Monitoring/ErrorTable.stx +68 -97
  57. package/resources/components/Dashboard/Notifications/NotificationDeliveries.stx +22 -44
  58. package/resources/components/Dashboard/Notifications/NotificationDeliveryHistory.stx +13 -4
  59. package/resources/components/Dashboard/Notifications/NotificationDeliveryTable.stx +74 -86
  60. package/resources/components/Dashboard/UI/Table.stx +41 -2
@@ -287,9 +287,7 @@ SQLite's SQL on a Turso or `sqld` server. Configure `TURSO_DATABASE_URL`
287
287
  migrationLocks: 'migration_locks',
288
288
  queryLogging: {
289
289
  // Defaults on outside production and off in production. Production also
290
- // skips query hooks unless persistent history is explicitly enabled,
291
- // except one onQueryError hook on PostgreSQL and MySQL that reports a
292
- // pool broken by oven-sh/bun#42804.
290
+ // skips query hooks unless persistent history is explicitly enabled.
293
291
  enabled: env.DB_QUERY_LOGGING_ENABLED ?? !['production', 'prod'].includes(env.APP_ENV || ''),
294
292
  captureAllTraces: false, // slow and failed queries always keep traces
295
293
  // Bound values in query_logs.bindings, credentials stored as `<redacted>`
@@ -75,7 +75,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
75
75
  | `@stacksjs/realtime` | [`stacks-realtime`](../stacks-realtime/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/realtime/src/index.ts) | 12 |
76
76
  | `@stacksjs/registry` | [`stacks-registry`](../stacks-registry/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/registry/src/index.ts) | 1 |
77
77
  | `@stacksjs/repl` | [`stacks-repl`](../stacks-repl/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/repl/src/index.ts) | 2 |
78
- | `@stacksjs/router` | [`stacks-router`](../stacks-router/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts) | 96 |
78
+ | `@stacksjs/router` | [`stacks-router`](../stacks-router/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/index.ts) | 95 |
79
79
  | `@stacksjs/scheduler` | [`stacks-scheduler`](../stacks-scheduler/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/scheduler/src/index.ts) | 14 |
80
80
  | `@stacksjs/search-engine` | [`stacks-search-engine`](../stacks-search-engine/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/search-engine/src/index.ts) | 10 |
81
81
  | `@stacksjs/security` | [`stacks-security`](../stacks-security/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/security/src/index.ts) | 11 |
@@ -347,9 +347,9 @@ Evidence directory: [4 retained tests](https://github.com/stacksjs/stacks/blob/m
347
347
 
348
348
  Guidance: [`stacks-database`](../stacks-database/SKILL.md). Source: [storage/framework/core/database/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts).
349
349
 
350
- Declared runtime exports: `BUN_BROKEN_MYSQL_POOL_MESSAGE`, `BUN_BROKEN_POSTGRES_POOL_MESSAGE`, `Database`, `EntityQueryBuilder`, `__flushAfterCommitNow`, `__pendingAfterCommitCount`, `addColumnSafely`, `auditForeignKeys`, `auditSchemaDrift`, `auditUniqueIndexes`, `backfillInBatches`, `buildKey`, `classifyDeclaredFKs`, `comparableIndexKey`, `createDatabase`, `createDynamo`, `createMysqlDatabase`, `createPostgresDatabase`, `createQueryBuilder`, `createSqliteDatabase`, `createTursoDatabase`, `describeIgnoreRule`, `detectDriver`, `driverDefaults`, `dynamo`, `enqueueAfterCommit`, `ensureNotificationForeignKeys`, `findFkOrphans`, `findIgnoredMigrations`, `fkKey`, `formatIgnoredMigrations`, `formatSchemaDrift`, `frameworkRouteAdmission`, `generateKeyPattern`, `getBrokenDatabasePools`, `getConfigFromEnv`, `getConnectionString`, `getDeclaredFKs`, `getDeclaredUniques`, `getLiveFKs`, `getLiveTables`, `getLiveUniqueIndexes`, `isBrokenBunPoolError`, `isInTransaction`, `logQuery`, `marshall`, `matchedRows`, `mergeWithDefaults`, `migrateNotificationTables`, `migrateRbacTables`, `mutationCount`, `parseKeyPattern`, `postgresIndexStatement`, `renameColumnSafely`, `runInTransactionScope`, `setConfig`, `setQueryTracker`, `trackIgnoredGeneratedMigrations`, `unmarshall`, `validateDriverConfig`.
350
+ Declared runtime exports: `Database`, `EntityQueryBuilder`, `__flushAfterCommitNow`, `__pendingAfterCommitCount`, `addColumnSafely`, `auditForeignKeys`, `auditSchemaDrift`, `auditUniqueIndexes`, `backfillInBatches`, `buildKey`, `classifyDeclaredFKs`, `comparableIndexKey`, `createDatabase`, `createDynamo`, `createMysqlDatabase`, `createPostgresDatabase`, `createQueryBuilder`, `createSqliteDatabase`, `createTursoDatabase`, `describeIgnoreRule`, `detectDriver`, `driverDefaults`, `dynamo`, `enqueueAfterCommit`, `ensureNotificationForeignKeys`, `findFkOrphans`, `findIgnoredMigrations`, `fkKey`, `formatIgnoredMigrations`, `formatSchemaDrift`, `frameworkRouteAdmission`, `generateKeyPattern`, `getConfigFromEnv`, `getConnectionString`, `getDeclaredFKs`, `getDeclaredUniques`, `getLiveFKs`, `getLiveTables`, `getLiveUniqueIndexes`, `isInTransaction`, `logQuery`, `marshall`, `matchedRows`, `mergeWithDefaults`, `migrateNotificationTables`, `migrateRbacTables`, `mutationCount`, `parseKeyPattern`, `postgresIndexStatement`, `renameColumnSafely`, `runInTransactionScope`, `setConfig`, `setQueryTracker`, `trackIgnoredGeneratedMigrations`, `unmarshall`, `validateDriverConfig`.
351
351
 
352
- Local source modules: [src/affected-rows.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/affected-rows.ts), [src/auth-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/auth-tables.ts), [src/broken-pool.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/broken-pool.ts), [src/custom/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/custom/index.ts), [src/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/database.ts), [src/datetime-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/datetime-columns.ts), [src/ddl-constraints.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ddl-constraints.ts), [src/defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/defaults.ts), [src/dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/dialect.ts), [src/driver-config.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/driver-config.ts), [src/drivers/dynamodb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/dynamodb.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/index.ts), [src/ensure-database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ensure-database.ts), [src/erd.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/erd.ts), [src/fk-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/fk-audit.ts), [src/framework-renames.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-renames.ts), [src/framework-route-admission.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-route-admission.ts), [src/hand-written-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/hand-written-migrations.ts), [src/ignored-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ignored-migrations.ts), [src/index-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index-shape.ts), [src/managed-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/managed-columns.ts), [src/migration-dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-dialect.ts), [src/migration-ledger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-ledger.ts), [src/migration-names.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-names.ts), [src/migration-path.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-path.ts), [src/migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migrations.ts), [src/model-sources.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/model-sources.ts), [src/notification-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/notification-tables.ts), [src/package-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-migrations.ts), [src/package-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-models.ts), [src/query-logger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/query-logger.ts), [src/rbac-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/rbac-tables.ts), [src/relation-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/relation-columns.ts), [src/replicas.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/replicas.ts), [src/row-lock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/row-lock.ts), [src/safe-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/safe-migrations.ts), [src/schema-align.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-align.ts), [src/schema-drift.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-drift.ts), [src/seeder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/seeder.ts), [src/shadowed-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/shadowed-models.ts), [src/sql-helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/sql-helpers.ts), [src/trait-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/trait-tables.ts), [src/transaction-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/transaction-context.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/types.ts), [src/unique-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/unique-audit.ts), [src/utc-defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utc-defaults.ts), [src/utils.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utils.ts), [src/uuid-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/uuid-columns.ts), [src/vschema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/vschema.ts).
352
+ Local source modules: [src/affected-rows.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/affected-rows.ts), [src/auth-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/auth-tables.ts), [src/custom/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/custom/index.ts), [src/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/database.ts), [src/datetime-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/datetime-columns.ts), [src/ddl-constraints.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ddl-constraints.ts), [src/defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/defaults.ts), [src/dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/dialect.ts), [src/driver-config.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/driver-config.ts), [src/drivers/dynamodb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/dynamodb.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/index.ts), [src/ensure-database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ensure-database.ts), [src/erd.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/erd.ts), [src/fk-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/fk-audit.ts), [src/framework-renames.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-renames.ts), [src/framework-route-admission.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-route-admission.ts), [src/hand-written-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/hand-written-migrations.ts), [src/ignored-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ignored-migrations.ts), [src/index-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index-shape.ts), [src/managed-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/managed-columns.ts), [src/migration-dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-dialect.ts), [src/migration-ledger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-ledger.ts), [src/migration-names.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-names.ts), [src/migration-path.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-path.ts), [src/migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migrations.ts), [src/model-sources.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/model-sources.ts), [src/notification-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/notification-tables.ts), [src/package-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-migrations.ts), [src/package-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-models.ts), [src/query-logger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/query-logger.ts), [src/rbac-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/rbac-tables.ts), [src/relation-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/relation-columns.ts), [src/replicas.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/replicas.ts), [src/row-lock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/row-lock.ts), [src/safe-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/safe-migrations.ts), [src/schema-align.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-align.ts), [src/schema-drift.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-drift.ts), [src/seeder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/seeder.ts), [src/shadowed-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/shadowed-models.ts), [src/sql-helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/sql-helpers.ts), [src/trait-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/trait-tables.ts), [src/transaction-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/transaction-context.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/types.ts), [src/unique-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/unique-audit.ts), [src/utc-defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utc-defaults.ts), [src/utils.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utils.ts), [src/uuid-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/uuid-columns.ts), [src/vschema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/vschema.ts).
353
353
 
354
354
  Evidence directory: [130 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
355
355
 
@@ -723,7 +723,7 @@ Declared runtime exports: `DASHBOARD_ROUTE_GROUPS`, `DEFAULT_ROUTE_BUNDLE_FEATUR
723
723
 
724
724
  Local source modules: [src/api-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/api-shape.ts), [src/body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/body.ts), [src/dashboard-access.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/dashboard-access.ts), [src/encrypted-session-store.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/encrypted-session-store.ts), [src/error-handler.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/error-handler.ts), [src/health.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/health.ts), [src/json.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/json.ts), [src/middleware.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/middleware.ts), [src/path-sanitize.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/path-sanitize.ts), [src/rate-limit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/rate-limit.ts), [src/request-augmentation.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-augmentation.ts), [src/request-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/request-context.ts), [src/route-loader.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-loader.ts), [src/route-model-binding.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/route-model-binding.ts), [src/security-settings.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/security-settings.ts), [src/session-factory.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/session-factory.ts), [src/signed-url.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/signed-url.ts), [src/stacks-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/stacks-router.ts), [src/typed-router.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/src/typed-router.ts).
725
725
 
726
- Evidence directory: [96 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
726
+ Evidence directory: [95 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/router/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
727
727
 
728
728
  ### @stacksjs/scheduler
729
729
 
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.279",
5
+ "version": "0.75.281",
6
6
  "description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.279",
5
+ "version": "0.75.281",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -45,9 +45,9 @@
45
45
  "dependencies": {
46
46
  "@iconify-json/f7": "^1.2.2",
47
47
  "@iconify-json/hugeicons": "^1.2.35",
48
- "@stacksjs/mobile": "0.75.279",
48
+ "@stacksjs/mobile": "0.75.281",
49
49
  "@stacksjs/sanitizer": "0.2.669",
50
- "@stacksjs/stx": "0.2.671",
50
+ "@stacksjs/stx": "0.2.676",
51
51
  "@xterm/addon-fit": "^0.11.0",
52
52
  "@xterm/xterm": "^6.0.0",
53
53
  "ts-qr-codes": "^0.1.8"
@@ -1 +1 @@
1
- 1.4.2
1
+ 1.4.4
@@ -1,3 +1,5 @@
1
+ @import('../UI/Table')
2
+
1
3
  <script client>
2
4
  import type { AnalyticsRange, CampaignAnalyticsResponse } from '../../../../functions/analytics'
3
5
  import { fetchCampaignAnalytics } from '../../../../functions/analytics'
@@ -34,6 +36,23 @@ function campaigns(): CampaignAnalyticsResponse['campaigns'] {
34
36
  return analytics()?.campaigns || []
35
37
  }
36
38
 
39
+ /**
40
+ * Every campaign in the range, so this is the one table on the page that
41
+ * grows without bound: it pages. Counts the campaign did not record read
42
+ * "Not recorded" rather than 0, and spend is formatted in each campaign's own
43
+ * currency, never summed across them.
44
+ */
45
+ const CAMPAIGN_COLUMNS = [
46
+ { key: 'name', label: 'Campaign', sortable: true, class: 'font-medium text-gray-900 dark:text-white' },
47
+ { key: 'type', label: 'Channel', sortable: true },
48
+ { key: 'status', label: 'Status', sortable: true },
49
+ { key: 'sentCount', label: 'Sent', type: 'number' as const, sortable: true, align: 'right' as const, emptyText: 'Not recorded' },
50
+ { key: 'opens', label: 'Opens', type: 'number' as const, sortable: true, align: 'right' as const, emptyText: 'Not recorded' },
51
+ { key: 'clicks', label: 'Clicks', type: 'number' as const, sortable: true, align: 'right' as const, emptyText: 'Not recorded' },
52
+ { key: 'conversions', label: 'Conversions', type: 'number' as const, sortable: true, align: 'right' as const, emptyText: 'Not recorded' },
53
+ { key: 'spent', label: 'Spent', type: 'currency' as const, currencyKey: 'currency', sortable: true, align: 'right' as const, emptyText: 'Not recorded' },
54
+ ]
55
+
37
56
  function channels(): CampaignAnalyticsResponse['channels'] {
38
57
  return analytics()?.channels || []
39
58
  }
@@ -141,36 +160,15 @@ onMount(() => {
141
160
  <h2 class="font-semibold text-gray-900 dark:text-white">Campaign performance</h2>
142
161
  <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Engagement counts are derived from each campaign's recorded sent count and rates.</p>
143
162
  </div>
144
- <div class="overflow-x-auto">
145
- <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
146
- <thead class="bg-gray-50 dark:bg-neutral-700/50">
147
- <tr>
148
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Campaign</th>
149
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Channel</th>
150
- <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Status</th>
151
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Sent</th>
152
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Opens</th>
153
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Clicks</th>
154
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Conversions</th>
155
- <th class="px-4 py-3 font-medium text-gray-500 text-right text-xs uppercase dark:text-neutral-300">Spent</th>
156
- </tr>
157
- </thead>
158
- <tbody class="divide-gray-200 divide-y dark:divide-neutral-700">
159
- <template :for="campaign in campaigns()">
160
- <tr>
161
- <td class="px-4 py-3 font-medium text-gray-900 text-sm dark:text-white">{{ campaign.name }}</td>
162
- <td class="px-4 py-3 text-gray-500 text-sm dark:text-neutral-300">{{ campaign.type }}</td>
163
- <td class="px-4 py-3 text-gray-500 text-sm dark:text-neutral-300">{{ campaign.status }}</td>
164
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatNumber(campaign.sentCount) }}</td>
165
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatNumber(campaign.opens) }}</td>
166
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatNumber(campaign.clicks) }}</td>
167
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatNumber(campaign.conversions) }}</td>
168
- <td class="px-4 py-3 text-gray-500 text-right text-sm dark:text-neutral-300">{{ formatMoney(campaign.spent, campaign.currency) }}</td>
169
- </tr>
170
- </template>
171
- </tbody>
172
- </table>
173
- </div>
163
+ <Table
164
+ :columns="CAMPAIGN_COLUMNS"
165
+ :data="campaigns()"
166
+ :loading="isLoading()"
167
+ paginated
168
+ :page-size="25"
169
+ empty-title="No campaigns in this period"
170
+ empty-description="Create and send campaigns from Marketing to populate this table."
171
+ />
174
172
  </section>
175
173
 
176
174
  <section class="grid gap-6 grid-cols-1 xl:grid-cols-2">
@@ -5,7 +5,6 @@ import type {
5
5
  } from '../../../../app/Actions/Dashboard/Commerce/commerce-category-records'
6
6
  import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
7
7
  import { pushToast } from '../../../../functions/toasts'
8
- import { pageControls } from '../../../../functions/dashboard-list'
9
8
 
10
9
  interface CategoryIndexResponse {
11
10
  records: CommerceCategoryRecord[]
@@ -29,8 +28,6 @@ const search = state('')
29
28
  const activeFilter = state('all')
30
29
  const hierarchyFilter = state('all')
31
30
  const sort = state('order')
32
- const page = state(1)
33
- const pageSize = 20
34
31
  const loading = state(true)
35
32
  const loadError = state('')
36
33
  const dialogOpen = state(false)
@@ -72,15 +69,6 @@ function filteredRecords(): CommerceCategoryRecord[] {
72
69
  })
73
70
  }
74
71
 
75
- function pageCount(): number {
76
- return Math.max(1, Math.ceil(filteredRecords().length / pageSize))
77
- }
78
-
79
- function visibleRecords(): CommerceCategoryRecord[] {
80
- const selectedPage = Math.min(page(), pageCount())
81
- return filteredRecords().slice((selectedPage - 1) * pageSize, selectedPage * pageSize)
82
- }
83
-
84
72
  function matchingLabel(): string {
85
73
  const count = filteredRecords().length
86
74
  return `${count} matching ${count === 1 ? 'category' : 'categories'}`
@@ -114,13 +102,13 @@ function exportHref(): string {
114
102
  return `data:text/csv;charset=utf-8,${encodeURIComponent(`\uFEFF${csv}`)}`
115
103
  }
116
104
 
117
- effect(() => {
118
- search()
119
- activeFilter()
120
- hierarchyFilter()
121
- sort()
122
- page.set(1)
123
- })
105
+ /**
106
+ * What the table is showing: a change returns it to the first page, as this
107
+ * page's own pager did before the table took over paging.
108
+ */
109
+ function viewKey(): string {
110
+ return JSON.stringify([search(), activeFilter(), hierarchyFilter(), sort()])
111
+ }
124
112
 
125
113
  async function loadCategories(): Promise<void> {
126
114
  loading.set(true)
@@ -129,7 +117,6 @@ async function loadCategories(): Promise<void> {
129
117
  const data = await dashboardApi<CategoryIndexResponse>('/api/dashboard/commerce/categories')
130
118
  records.set(Array.isArray(data.records) ? data.records : [])
131
119
  summary.set(data.summary ?? { ...emptySummary })
132
- page.set(Math.min(page(), Math.max(1, Math.ceil((data.records?.length || 0) / pageSize))))
133
120
  }
134
121
  catch (error) {
135
122
  loadError.set('Product categories could not be loaded.')
@@ -231,8 +218,6 @@ async function deleteCategory(): Promise<void> {
231
218
  }
232
219
  }
233
220
 
234
- const { previousPage, nextPage } = pageControls(page, pageCount)
235
-
236
221
  onMount(() => {
237
222
  void loadCategories()
238
223
  })
@@ -267,14 +252,9 @@ onMount(() => {
267
252
 
268
253
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
269
254
  <p class="text-gray-500 text-sm dark:text-neutral-400">{{ matchingLabel() }}</p>
270
- <div class="flex gap-2 items-center">
271
- <Button variant="secondary" size="sm" :disabled="page() <= 1" @click="previousPage()">Previous</Button>
272
- <span class="text-gray-500 text-sm dark:text-neutral-400">Page {{ Math.min(page(), pageCount()) }} of {{ pageCount() }}</span>
273
- <Button variant="secondary" size="sm" :disabled="page() >= pageCount()" @click="nextPage()">Next</Button>
274
- </div>
275
255
  </div>
276
256
 
277
- <CommerceCategoriesTable :records="visibleRecords()" :all-records="records()" :loading="loading()" @view="openDetails" @edit="openEdit" @delete="openDelete" />
257
+ <CommerceCategoriesTable :sort-reset-key="sort()" :page-reset-key="viewKey()" :records="filteredRecords()" :all-records="records()" :loading="loading()" @view="openDetails" @edit="openEdit" @delete="openDelete" />
278
258
  </section>
279
259
  </div>
280
260
 
@@ -1,43 +1,86 @@
1
+ @import('../UI/Table')
2
+
1
3
  <script client>
2
4
  import type { CommerceCategoryRecord } from '../../../../app/Actions/Dashboard/Commerce/commerce-category-records'
3
5
 
4
6
  const records = useReactiveProp('records', [] as CommerceCategoryRecord[])
5
7
  const allRecords = useReactiveProp('allRecords', [] as CommerceCategoryRecord[])
6
8
  const loading = useReactiveProp('loading', false)
7
- const emit = defineEmits<{
8
- view: [record: CommerceCategoryRecord]
9
- edit: [record: CommerceCategoryRecord]
10
- delete: [record: CommerceCategoryRecord]
11
- }>()
9
+ /** The parent's sort select: a change hands the order back from a header sort. */
10
+ const sortResetKey = useReactiveProp<unknown>('sortResetKey', '')
11
+ /** The parent's search and filters: a change returns to the first page. */
12
+ const pageResetKey = useReactiveProp<unknown>('pageResetKey', '')
13
+ const emit = defineEmits()
12
14
 
13
15
  function parentName(record: CommerceCategoryRecord): string {
14
16
  if (!record.parentCategoryId)
15
17
  return 'Root category'
16
18
  return allRecords().find(parent => parent.id === record.parentCategoryId)?.name || 'Unknown parent'
17
19
  }
20
+
21
+ /**
22
+ * Only the category cell is slotted, for its initial. The parent comes from
23
+ * `allRecords`, which can hold categories the current filter hides, so it is
24
+ * resolved into the row rather than looked up per cell.
25
+ */
26
+ const COLUMNS = [
27
+ { key: 'name', label: 'Category', sortable: true, slotted: true },
28
+ { key: 'parentLabel', label: 'Hierarchy', sortable: true, subtitleKey: 'slug', class: 'text-gray-700 dark:text-neutral-200' },
29
+ { key: 'displayOrder', label: 'Display order', type: 'number' as const, sortable: true, class: 'font-mono' },
30
+ {
31
+ key: 'availability',
32
+ label: 'Availability',
33
+ type: 'badge' as const,
34
+ sortable: true,
35
+ badgeClasses: { active: 'bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300' },
36
+ badgeLabels: { active: 'Active', inactive: 'Inactive' },
37
+ },
38
+ ]
39
+
40
+ const ROW_ACTIONS = [
41
+ { action: 'view', label: 'View', icon: 'i-hugeicons-view' },
42
+ { action: 'edit', label: 'Edit', icon: 'i-hugeicons-edit-02' },
43
+ { action: 'delete', label: 'Delete', icon: 'i-hugeicons-delete-02', class: 'text-red-500' },
44
+ ]
45
+
46
+ const rows = derived(() => records().map(record => ({
47
+ ...record,
48
+ initial: record.name.slice(0, 1).toUpperCase(),
49
+ summary: record.description || record.slug,
50
+ parentLabel: parentName(record),
51
+ availability: record.isActive ? 'active' : 'inactive',
52
+ })))
53
+
54
+ function handleRowAction(payload: { action: string, row: CommerceCategoryRecord }): void {
55
+ emit(payload.action, payload.row)
56
+ }
18
57
  </script>
19
58
 
20
- <div class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
21
- <div :if="loading()" class="flex items-center justify-center py-16 text-gray-500 text-sm dark:text-neutral-400"><span class="mr-2 h-4 w-4 animate-spin i-hugeicons-loading-03"></span>Loading categories</div>
22
- <div :if="!loading() && records().length === 0" class="py-16 text-center">
23
- <div class="mb-3 mx-auto h-8 w-8 text-gray-400 i-hugeicons-tags"></div>
24
- <p class="font-medium text-gray-900 text-sm dark:text-white">No product categories found</p>
25
- <p class="mt-1 text-gray-500 text-xs dark:text-neutral-400">Adjust the filters or add a native Category record.</p>
26
- </div>
27
- <div :if="!loading() && records().length > 0" class="overflow-x-auto">
28
- <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
29
- <thead class="bg-gray-50 dark:bg-neutral-900/60"><tr><th class="px-4 py-3 font-semibold text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Category</th><th class="px-4 py-3 font-semibold text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Hierarchy</th><th class="px-4 py-3 font-semibold text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Display order</th><th class="px-4 py-3 font-semibold text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Availability</th><th class="px-4 py-3"><span class="sr-only">Actions</span></th></tr></thead>
30
- <tbody class="divide-gray-100 divide-y dark:divide-neutral-700">
31
- <template :for="record in records()" :key="record.id">
32
- <tr class="hover:bg-gray-50/70 dark:hover:bg-neutral-900/40">
33
- <td class="px-4 py-3"><div class="flex gap-3 items-center"><span class="flex items-center justify-center shrink-0 h-9 w-9 font-semibold text-blue-700 text-sm dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-md">{{ record.name.slice(0, 1).toUpperCase() }}</span><div class="min-w-0"><Button variant="ghost" size="xs" @click="emit('view', record)">{{ record.name }}</Button><p class="mt-1 max-w-96 text-gray-400 text-xs truncate dark:text-neutral-500">{{ record.description || record.slug }}</p></div></div></td>
34
- <td class="px-4 py-3"><p class="text-gray-700 text-sm dark:text-neutral-200">{{ parentName(record) }}</p><p class="mt-1 font-mono text-gray-400 text-xs dark:text-neutral-500">{{ record.slug }}</p></td>
35
- <td class="px-4 py-3 font-mono text-gray-700 text-sm dark:text-neutral-200">{{ record.displayOrder }}</td>
36
- <td class="px-4 py-3"><span :class="'inline-flex px-2 py-1 font-medium text-xs rounded-full ' + (record.isActive ? 'bg-green-50 text-green-700 dark:bg-green-950/40 dark:text-green-300' : 'bg-gray-100 text-gray-600 dark:bg-neutral-700 dark:text-neutral-300')">{{ record.isActive ? 'Active' : 'Inactive' }}</span></td>
37
- <td class="px-4 py-3"><div class="flex gap-1 justify-end"><Button variant="ghost" size="sm" iconOnly :ariaLabel="'View ' + record.name" @click="emit('view', record)"><span class="block h-4 w-4 text-blue-500 i-hugeicons-view"></span></Button><Button variant="ghost" size="sm" iconOnly :ariaLabel="'Edit ' + record.name" @click="emit('edit', record)"><span class="block h-4 w-4 text-blue-500 i-hugeicons-edit-02"></span></Button><Button variant="ghost" size="sm" iconOnly :ariaLabel="'Delete ' + record.name" @click="emit('delete', record)"><span class="block h-4 w-4 text-red-500 i-hugeicons-delete-02"></span></Button></div></td>
38
- </tr>
39
- </template>
40
- </tbody>
41
- </table>
42
- </div>
43
- </div>
59
+ {{--
60
+ No `searchable` or `filterable`: the parent dashboard owns the search box
61
+ and the filter selects. Table pages the whole filtered list the parent passes.
62
+ --}}
63
+ <Table
64
+ :sort-reset-key="sortResetKey()"
65
+ :page-reset-key="pageResetKey()"
66
+ :columns="COLUMNS"
67
+ :data="rows()"
68
+ :loading="loading()"
69
+ :row-actions="ROW_ACTIONS"
70
+ action-label-key="name"
71
+ paginated
72
+ :page-size="20"
73
+ empty-title="No product categories found"
74
+ empty-description="Adjust the filters or add a native Category record."
75
+ @row-action="handleRowAction"
76
+ >
77
+ <template #cell="{ row }">
78
+ <div class="flex gap-3 items-center">
79
+ <span class="flex items-center justify-center shrink-0 h-9 w-9 font-semibold text-blue-700 text-sm dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-md" x-text="row.initial"></span>
80
+ <div class="min-w-0">
81
+ <p class="font-medium text-gray-900 text-sm dark:text-white" x-text="row.name"></p>
82
+ <p class="mt-1 max-w-96 text-gray-400 text-xs truncate dark:text-neutral-500" x-text="row.summary"></p>
83
+ </div>
84
+ </div>
85
+ </template>
86
+ </Table>
@@ -8,7 +8,6 @@ import type {
8
8
  import { DashboardApiError, dashboardApi } from '../../../../functions/dashboard-api'
9
9
  import { pushToast } from '../../../../functions/toasts'
10
10
  import { minorToInput } from '@stacksjs/commerce/money'
11
- import { pageControls } from '../../../../functions/dashboard-list'
12
11
 
13
12
  interface CouponIndexResponse {
14
13
  records: CouponRecord[]
@@ -38,8 +37,6 @@ const statusFilter = state('all')
38
37
  const enabledFilter = state('all')
39
38
  const typeFilter = state('all')
40
39
  const sort = state('newest')
41
- const page = state(1)
42
- const pageSize = 20
43
40
  const loading = state(true)
44
41
  const loadError = state('')
45
42
  const dialogOpen = state(false)
@@ -80,15 +77,6 @@ function filteredRecords(): CouponRecord[] {
80
77
  })
81
78
  }
82
79
 
83
- function pageCount(): number {
84
- return Math.max(1, Math.ceil(filteredRecords().length / pageSize))
85
- }
86
-
87
- function visibleRecords(): CouponRecord[] {
88
- const selectedPage = Math.min(page(), pageCount())
89
- return filteredRecords().slice((selectedPage - 1) * pageSize, selectedPage * pageSize)
90
- }
91
-
92
80
  function metricCards(): Array<{ label: string, value: string, detail: string }> {
93
81
  const current = summary()
94
82
  return [
@@ -121,14 +109,13 @@ function exportHref(): string {
121
109
  return `data:text/csv;charset=utf-8,${encodeURIComponent(`\uFEFF${csv}`)}`
122
110
  }
123
111
 
124
- effect(() => {
125
- search()
126
- statusFilter()
127
- enabledFilter()
128
- typeFilter()
129
- sort()
130
- page.set(1)
131
- })
112
+ /**
113
+ * What the table is showing: a change returns it to the first page, as this
114
+ * page's own pager did before the table took over paging.
115
+ */
116
+ function viewKey(): string {
117
+ return JSON.stringify([search(), statusFilter(), enabledFilter(), typeFilter(), sort()])
118
+ }
132
119
 
133
120
  async function loadCoupons(): Promise<void> {
134
121
  loading.set(true)
@@ -137,7 +124,6 @@ async function loadCoupons(): Promise<void> {
137
124
  const data = await dashboardApi<CouponIndexResponse>('/api/dashboard/commerce/coupons')
138
125
  records.set(Array.isArray(data.records) ? data.records : [])
139
126
  summary.set(data.summary ?? { ...emptySummary })
140
- page.set(Math.min(page(), Math.max(1, Math.ceil((data.records?.length || 0) / pageSize))))
141
127
  }
142
128
  catch (error) {
143
129
  loadError.set('Coupons could not be loaded.')
@@ -242,8 +228,6 @@ async function deleteCoupon(): Promise<void> {
242
228
  }
243
229
  }
244
230
 
245
- const { previousPage, nextPage } = pageControls(page, pageCount)
246
-
247
231
  onMount(() => {
248
232
  void loadCoupons()
249
233
  })
@@ -279,14 +263,9 @@ onMount(() => {
279
263
 
280
264
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
281
265
  <p class="text-gray-500 text-sm dark:text-neutral-400">{{ filteredRecords().length }} matching coupons</p>
282
- <div class="flex gap-2 items-center">
283
- <Button variant="secondary" size="sm" :disabled="page() <= 1" @click="previousPage()">Previous</Button>
284
- <span class="text-gray-500 text-sm dark:text-neutral-400">Page {{ Math.min(page(), pageCount()) }} of {{ pageCount() }}</span>
285
- <Button variant="secondary" size="sm" :disabled="page() >= pageCount()" @click="nextPage()">Next</Button>
286
- </div>
287
266
  </div>
288
267
 
289
- <CommerceCouponsTable :records="visibleRecords()" :loading="loading()" @view="openDetails" @edit="openEdit" @delete="openDelete" />
268
+ <CommerceCouponsTable :sort-reset-key="sort()" :page-reset-key="viewKey()" :records="filteredRecords()" :loading="loading()" @view="openDetails" @edit="openEdit" @delete="openDelete" />
290
269
  </section>
291
270
  </div>
292
271