@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.
- package/ai/skills/stacks-database/SKILL.md +1 -3
- package/ai/skills/stacks-native/CATALOG.md +4 -4
- package/ide/vscode/package.json +1 -1
- package/package.json +3 -3
- package/project/bun-version +1 -1
- package/resources/components/Dashboard/Analytics/CampaignAnalytics.stx +28 -30
- package/resources/components/Dashboard/Commerce/CommerceCategoriesDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/CommerceCategoriesTable.stx +72 -29
- package/resources/components/Dashboard/Commerce/CommerceCouponsDashboard.stx +8 -29
- package/resources/components/Dashboard/Commerce/CommerceCouponsTable.stx +69 -49
- package/resources/components/Dashboard/Commerce/CommerceCustomersDashboard.stx +8 -27
- package/resources/components/Dashboard/Commerce/CommerceCustomersTable.stx +70 -48
- package/resources/components/Dashboard/Commerce/CommerceGiftCardsDashboard.stx +8 -29
- package/resources/components/Dashboard/Commerce/CommerceGiftCardsTable.stx +72 -82
- package/resources/components/Dashboard/Commerce/CommerceManufacturersDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceOrdersDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/CommerceOrdersTable.stx +76 -49
- package/resources/components/Dashboard/Commerce/CommercePaymentsDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommercePaymentsTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceProductsDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/CommerceProductsTable.stx +81 -34
- package/resources/components/Dashboard/Commerce/CommerceReviewsDashboard.stx +11 -37
- package/resources/components/Dashboard/Commerce/CommerceReviewsTable.stx +106 -77
- package/resources/components/Dashboard/Commerce/CommerceTaxesDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceUnitsDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/CommerceVariantsDashboard.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +3 -0
- package/resources/components/Dashboard/Commerce/Delivery/CouriersDashboard.stx +1 -11
- package/resources/components/Dashboard/Commerce/Delivery/CouriersTable.stx +55 -82
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +57 -86
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveriesDashboard.stx +1 -24
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +62 -80
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +58 -99
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsDashboard.stx +1 -23
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +51 -86
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +58 -71
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesDashboard.stx +1 -12
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +85 -110
- package/resources/components/Dashboard/Commerce/PrintDevicesDashboard.stx +8 -28
- package/resources/components/Dashboard/Commerce/PrintDevicesTable.stx +62 -57
- package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +7 -24
- package/resources/components/Dashboard/Marketing/AbandonedCartsTable.stx +63 -59
- package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +7 -25
- package/resources/components/Dashboard/Marketing/CampaignsTable.stx +80 -57
- package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +7 -27
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +66 -71
- package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +7 -25
- package/resources/components/Dashboard/Marketing/SocialPostsTable.stx +75 -62
- package/resources/components/Dashboard/Monitoring/ErrorDashboard.stx +9 -50
- package/resources/components/Dashboard/Monitoring/ErrorTable.stx +68 -97
- package/resources/components/Dashboard/Notifications/NotificationDeliveries.stx +22 -44
- package/resources/components/Dashboard/Notifications/NotificationDeliveryHistory.stx +13 -4
- package/resources/components/Dashboard/Notifications/NotificationDeliveryTable.stx +74 -86
- 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) |
|
|
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: `
|
|
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/
|
|
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: [
|
|
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
|
|
package/ide/vscode/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"publisher": "Stacks",
|
|
3
3
|
"name": "vscode-stacks",
|
|
4
4
|
"displayName": "Stacks",
|
|
5
|
-
"version": "0.75.
|
|
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.
|
|
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.
|
|
48
|
+
"@stacksjs/mobile": "0.75.281",
|
|
49
49
|
"@stacksjs/sanitizer": "0.2.669",
|
|
50
|
-
"@stacksjs/stx": "0.2.
|
|
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"
|
package/project/bun-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.4.
|
|
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
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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="
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
sort()
|
|
130
|
-
|
|
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="
|
|
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
|
|