@mohasinac/appkit 4.1.1 → 4.2.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.
- package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
- package/dist/_internal/client/features/maintenance/index.js +1 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
- package/dist/_internal/server/features/auctions/index.d.ts +1 -1
- package/dist/_internal/server/features/auctions/index.js +1 -1
- package/dist/_internal/server/features/auctions/service.js +2 -2
- package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
- package/dist/_internal/server/features/checkout/actions.js +245 -182
- package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
- package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +9 -1
- package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
- package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
- package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
- package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
- package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
- package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
- package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
- package/dist/_internal/shared/features/auctions/config.js +1 -1
- package/dist/_internal/shared/features/auctions/schema.js +1 -1
- package/dist/_internal/shared/features/cart/schema.js +2 -2
- package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
- package/dist/_internal/shared/features/checkout/config.js +7 -0
- package/dist/_internal/shared/features/events/schema.js +1 -1
- package/dist/_internal/shared/features/orders/schema.js +1 -1
- package/dist/_internal/shared/features/products/schema.js +3 -3
- package/dist/_internal/shared/features/promotions/schema.js +3 -3
- package/dist/_internal/shared/fees/calculator.d.ts +32 -7
- package/dist/_internal/shared/fees/calculator.js +25 -0
- package/dist/client.d.ts +4 -4
- package/dist/client.js +4 -2
- package/dist/constants/api-endpoints.d.ts +9 -6
- package/dist/constants/api-endpoints.js +3 -2
- package/dist/contracts/client-payment-gateway.d.ts +1 -2
- package/dist/features/about/components/AboutView.d.ts +6 -16
- package/dist/features/about/components/AboutView.js +3 -3
- package/dist/features/about/components/FeesView.js +18 -0
- package/dist/features/about/index.d.ts +2 -1
- package/dist/features/about/schemas/firestore.d.ts +47 -0
- package/dist/features/about/schemas/firestore.js +6 -0
- package/dist/features/about/schemas/index.d.ts +210 -0
- package/dist/features/about/schemas/index.js +45 -0
- package/dist/features/account/hooks/useProfile.js +3 -0
- package/dist/features/admin/actions/notification-actions.d.ts +4 -1
- package/dist/features/admin/actions/notification-actions.js +56 -10
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
- package/dist/features/admin/components/AdminCartsView.js +16 -3
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminCouponsView.js +14 -4
- package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
- package/dist/features/admin/components/AdminMediaView.js +1 -1
- package/dist/features/admin/components/AdminNewsletterView.js +42 -11
- package/dist/features/admin/components/AdminNotificationsView.js +31 -19
- package/dist/features/admin/components/AdminOrdersView.js +7 -0
- package/dist/features/admin/components/AdminPayoutsView.js +18 -3
- package/dist/features/admin/components/AdminProductsView.js +17 -2
- package/dist/features/admin/components/AdminReviewsView.js +16 -2
- package/dist/features/admin/components/AdminScammersView.js +7 -0
- package/dist/features/admin/components/AdminSectionsView.js +4 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
- package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
- package/dist/features/admin/components/AdminStoresView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
- package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
- package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
- package/dist/features/admin/constants/filter-tabs.js +55 -27
- package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
- package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
- package/dist/features/admin/repository/site-settings.repository.js +8 -1
- package/dist/features/admin/schemas/firestore.d.ts +46 -0
- package/dist/features/admin/schemas/firestore.js +62 -24
- package/dist/features/auctions/actions/bid-actions.js +1 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/AuctionsListView.js +8 -1
- package/dist/features/auth/components/LoginForm.js +2 -2
- package/dist/features/auth/components/RegisterForm.js +2 -2
- package/dist/features/auth/permissions/constants.d.ts +16 -0
- package/dist/features/auth/permissions/constants.js +53 -0
- package/dist/features/auth/schemas/firestore.js +1 -0
- package/dist/features/blog/components/BlogFilters.js +1 -1
- package/dist/features/cart/schemas/firestore.d.ts +1 -1
- package/dist/features/categories/schemas/firestore.d.ts +1 -1
- package/dist/features/consultation/components/ConsultationForm.js +1 -1
- package/dist/features/events/components/EventFilters.js +2 -0
- package/dist/features/homepage/components/WelcomeSection.js +2 -2
- package/dist/features/layout/BackToTop.d.ts +9 -5
- package/dist/features/layout/BackToTop.js +17 -21
- package/dist/features/layout/TitleBarLayout.d.ts +2 -2
- package/dist/features/layout/TitleBarLayout.js +4 -4
- package/dist/features/media/AvatarUpload.js +7 -3
- package/dist/features/media/MediaVideo.js +1 -1
- package/dist/features/media/finalize.d.ts +9 -13
- package/dist/features/media/finalize.js +23 -74
- package/dist/features/media/server.d.ts +1 -1
- package/dist/features/media/server.js +1 -1
- package/dist/features/orders/actions/refund-actions.js +1 -0
- package/dist/features/orders/hooks/useOrders.js +16 -4
- package/dist/features/orders/schemas/firestore.d.ts +19 -5
- package/dist/features/payments/schemas/firestore.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
- package/dist/features/products/components/ArtStickersListView.js +5 -0
- package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
- package/dist/features/products/components/ProductForm.js +14 -1
- package/dist/features/products/components/ProductsIndexPageView.js +7 -0
- package/dist/features/products/constants/action-defs.d.ts +29 -3
- package/dist/features/products/constants/action-defs.js +77 -1
- package/dist/features/products/schemas/firestore.d.ts +1 -1
- package/dist/features/products/schemas/product-templates.d.ts +1 -1
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/scams/schemas/index.js +2 -2
- package/dist/features/search/components/Search.js +3 -3
- package/dist/features/seller/components/SellerAuctionsView.js +27 -2
- package/dist/features/seller/components/SellerOrdersView.js +2 -2
- package/dist/features/seller/components/SellerPayoutsView.js +24 -10
- package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +1 -1
- package/dist/features/seller/components/SellerProductsView.js +8 -6
- package/dist/features/seller/components/SellerReviewsView.js +27 -6
- package/dist/features/seller/components/index.d.ts +0 -2
- package/dist/features/seller/components/index.js +0 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
- package/dist/features/stores/components/StoreProductsView.js +1 -1
- package/dist/features/stores/schemas/firestore.d.ts +11 -0
- package/dist/features/stores/schemas/firestore.js +5 -0
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
- package/dist/features/tester/actions/checklist-item-actions.js +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
- package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
- package/dist/features/tester/components/TesterHubView.js +34 -21
- package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
- package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
- package/dist/features/tester/schemas/firestore.d.ts +8 -3
- package/dist/features/tester/schemas/firestore.js +6 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
- package/dist/features/tester/utils/phases.d.ts +31 -0
- package/dist/features/tester/utils/phases.js +46 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
- package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
- package/dist/features/wishlist/types/index.d.ts +3 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.js +19 -9
- package/dist/next/components/NotFoundView.js +2 -1
- package/dist/next/routing/route-map.d.ts +6 -12
- package/dist/next/routing/route-map.js +3 -4
- package/dist/providers/db-firebase/admin.d.ts +1 -0
- package/dist/providers/db-firebase/admin.js +1 -1
- package/dist/repositories/index.d.ts +2 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/seed/addresses-seed-data.js +18 -18
- package/dist/seed/bids-seed-data.js +4 -4
- package/dist/seed/blog-posts-seed-data.js +82 -82
- package/dist/seed/carousel-slides-seed-data.js +14 -10
- package/dist/seed/carousels-seed-data.js +3 -3
- package/dist/seed/cart-seed-data.js +92 -92
- package/dist/seed/claimed-coupons-seed-data.js +10 -10
- package/dist/seed/conversations-seed-data.js +75 -75
- package/dist/seed/coupon-usage-seed-data.js +12 -12
- package/dist/seed/coupons-seed-data.js +41 -41
- package/dist/seed/events-seed-data.js +105 -105
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
- package/dist/seed/grouped-listings-seed-data.js +79 -143
- package/dist/seed/history-seed-data.js +38 -38
- package/dist/seed/homepage-sections-seed-data.js +9 -9
- package/dist/seed/notifications-seed-data.js +56 -56
- package/dist/seed/offers-seed-data.js +91 -91
- package/dist/seed/orders-seed-data.js +121 -106
- package/dist/seed/payouts-seed-data.js +42 -42
- package/dist/seed/products-auctions-seed-data.js +10 -2
- package/dist/seed/products-standard-seed-data.js +40 -8
- package/dist/seed/reviews-seed-data.js +42 -23
- package/dist/seed/scammers-seed-data.js +22 -22
- package/dist/seed/sessions-seed-data.js +7 -7
- package/dist/seed/shipments-seed-data.js +10 -10
- package/dist/seed/site-settings-seed-data.js +89 -0
- package/dist/seed/store-addresses-seed-data.js +2 -2
- package/dist/seed/store-extensions-seed-data.js +93 -93
- package/dist/seed/stores-seed-data.js +2 -0
- package/dist/seed/support-tickets-seed-data.js +47 -47
- package/dist/seed/users-seed-data.js +55 -89
- package/dist/seed/wishlists-seed-data.js +22 -22
- package/dist/server.d.ts +4 -3
- package/dist/server.js +7 -7
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PageLoader.js +8 -3
- package/dist/ui/components/Select.d.ts +8 -1
- package/dist/ui/components/Select.js +2 -2
- package/dist/ui/components/SiteMark.d.ts +23 -0
- package/dist/ui/components/SiteMark.js +76 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/validation/schemas.d.ts +0 -2
- package/dist/validation/schemas.js +0 -2
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +17 -15
|
@@ -75,6 +75,12 @@ export interface StoreDocument extends BaseDocument {
|
|
|
75
75
|
isFeatured?: boolean;
|
|
76
76
|
isVacationMode?: boolean;
|
|
77
77
|
vacationMessage?: string;
|
|
78
|
+
/** Internal admin note, never shown to the store owner or the public. */
|
|
79
|
+
adminNotes?: string;
|
|
80
|
+
/** Admin "verified store" badge — distinct from `status`/`isPublic`. */
|
|
81
|
+
isVerified?: boolean;
|
|
82
|
+
/** Reason recorded when `status` is set to "suspended". */
|
|
83
|
+
suspensionReason?: string;
|
|
78
84
|
stats?: {
|
|
79
85
|
totalProducts: number;
|
|
80
86
|
itemsSold: number;
|
|
@@ -158,6 +164,11 @@ export declare const STORE_FIELDS: {
|
|
|
158
164
|
readonly STATS: "stats";
|
|
159
165
|
readonly CREATED_AT: "createdAt";
|
|
160
166
|
readonly UPDATED_AT: "updatedAt";
|
|
167
|
+
readonly ADMIN_NOTES: "adminNotes";
|
|
168
|
+
readonly IS_FEATURED: "isFeatured";
|
|
169
|
+
readonly IS_VERIFIED: "isVerified";
|
|
170
|
+
readonly SUSPENSION_REASON: "suspensionReason";
|
|
171
|
+
readonly CAPABILITIES: "capabilities";
|
|
161
172
|
readonly STATUS_VALUES: {
|
|
162
173
|
readonly PENDING: "pending";
|
|
163
174
|
readonly ACTIVE: "active";
|
|
@@ -35,6 +35,11 @@ export const STORE_FIELDS = {
|
|
|
35
35
|
STATS: "stats",
|
|
36
36
|
CREATED_AT: "createdAt",
|
|
37
37
|
UPDATED_AT: "updatedAt",
|
|
38
|
+
ADMIN_NOTES: "adminNotes",
|
|
39
|
+
IS_FEATURED: "isFeatured",
|
|
40
|
+
IS_VERIFIED: "isVerified",
|
|
41
|
+
SUSPENSION_REASON: "suspensionReason",
|
|
42
|
+
CAPABILITIES: "capabilities",
|
|
38
43
|
STATUS_VALUES: {
|
|
39
44
|
PENDING: "pending",
|
|
40
45
|
ACTIVE: "active",
|
|
@@ -458,10 +458,10 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
458
458
|
shippingPolicy?: string | undefined;
|
|
459
459
|
isVacationMode?: boolean | undefined;
|
|
460
460
|
vacationMessage?: string | undefined;
|
|
461
|
+
capabilities?: string[] | undefined;
|
|
461
462
|
vacationReturnDate?: string | Date | z.objectOutputType<{
|
|
462
463
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
463
464
|
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
464
|
-
capabilities?: string[] | undefined;
|
|
465
465
|
customCommissionRate?: number | undefined;
|
|
466
466
|
googleReviews?: {
|
|
467
467
|
enabled: boolean;
|
|
@@ -543,10 +543,10 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
543
543
|
shippingPolicy?: string | undefined;
|
|
544
544
|
isVacationMode?: boolean | undefined;
|
|
545
545
|
vacationMessage?: string | undefined;
|
|
546
|
+
capabilities?: string[] | undefined;
|
|
546
547
|
vacationReturnDate?: string | Date | z.objectInputType<{
|
|
547
548
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
548
549
|
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
549
|
-
capabilities?: string[] | undefined;
|
|
550
550
|
customCommissionRate?: number | undefined;
|
|
551
551
|
googleReviews?: {
|
|
552
552
|
enabled: boolean;
|
|
@@ -9,6 +9,7 @@ export declare const checklistItemCreateSchema: z.ZodObject<{
|
|
|
9
9
|
description: z.ZodOptional<z.ZodString>;
|
|
10
10
|
href: z.ZodOptional<z.ZodString>;
|
|
11
11
|
order: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
phase: z.ZodDefault<z.ZodNumber>;
|
|
12
13
|
isActive: z.ZodDefault<z.ZodBoolean>;
|
|
13
14
|
adminOnly: z.ZodDefault<z.ZodBoolean>;
|
|
14
15
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -17,6 +18,7 @@ export declare const checklistItemCreateSchema: z.ZodObject<{
|
|
|
17
18
|
isActive: boolean;
|
|
18
19
|
groupKey: string;
|
|
19
20
|
pageKey: string;
|
|
21
|
+
phase: number;
|
|
20
22
|
groupLabel: string;
|
|
21
23
|
pageLabel: string;
|
|
22
24
|
adminOnly: boolean;
|
|
@@ -32,6 +34,7 @@ export declare const checklistItemCreateSchema: z.ZodObject<{
|
|
|
32
34
|
description?: string | undefined;
|
|
33
35
|
order?: number | undefined;
|
|
34
36
|
isActive?: boolean | undefined;
|
|
37
|
+
phase?: number | undefined;
|
|
35
38
|
adminOnly?: boolean | undefined;
|
|
36
39
|
}>;
|
|
37
40
|
export declare const checklistItemUpdateSchema: z.ZodObject<{
|
|
@@ -43,6 +46,7 @@ export declare const checklistItemUpdateSchema: z.ZodObject<{
|
|
|
43
46
|
description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
44
47
|
href: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
45
48
|
order: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
49
|
+
phase: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
46
50
|
isActive: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
47
51
|
adminOnly: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
48
52
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -53,6 +57,7 @@ export declare const checklistItemUpdateSchema: z.ZodObject<{
|
|
|
53
57
|
isActive?: boolean | undefined;
|
|
54
58
|
groupKey?: string | undefined;
|
|
55
59
|
pageKey?: string | undefined;
|
|
60
|
+
phase?: number | undefined;
|
|
56
61
|
groupLabel?: string | undefined;
|
|
57
62
|
pageLabel?: string | undefined;
|
|
58
63
|
adminOnly?: boolean | undefined;
|
|
@@ -64,6 +69,7 @@ export declare const checklistItemUpdateSchema: z.ZodObject<{
|
|
|
64
69
|
isActive?: boolean | undefined;
|
|
65
70
|
groupKey?: string | undefined;
|
|
66
71
|
pageKey?: string | undefined;
|
|
72
|
+
phase?: number | undefined;
|
|
67
73
|
groupLabel?: string | undefined;
|
|
68
74
|
pageLabel?: string | undefined;
|
|
69
75
|
adminOnly?: boolean | undefined;
|
|
@@ -11,6 +11,7 @@ const checklistItemBaseSchema = z.object({
|
|
|
11
11
|
description: z.string().max(2000).optional(),
|
|
12
12
|
href: z.string().max(300).optional(),
|
|
13
13
|
order: z.number().int().min(0).default(0),
|
|
14
|
+
phase: z.number().int().min(1).default(1),
|
|
14
15
|
isActive: z.boolean().default(true),
|
|
15
16
|
adminOnly: z.boolean().default(false),
|
|
16
17
|
});
|
|
@@ -27,6 +28,7 @@ export async function createChecklistItem(input) {
|
|
|
27
28
|
description: input.description,
|
|
28
29
|
href: input.href,
|
|
29
30
|
order: input.order ?? 0,
|
|
31
|
+
phase: input.phase ?? 1,
|
|
30
32
|
isActive: input.isActive ?? true,
|
|
31
33
|
adminOnly: input.adminOnly ?? false,
|
|
32
34
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useQuery } from "@tanstack/react-query";
|
|
4
|
-
import { Card, Stack, Text } from "../../../ui";
|
|
4
|
+
import { Card, Details, Div, Stack, Summary, Text } from "../../../ui";
|
|
5
5
|
import { MediaImage } from "../../media/MediaImage";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
@@ -18,5 +18,16 @@ export function AdminTesterFeedbackIssuesView() {
|
|
|
18
18
|
return _jsx(Text, { color: "muted", children: "Loading issues\u2026" });
|
|
19
19
|
if (issues.length === 0)
|
|
20
20
|
return _jsx(Text, { color: "muted", children: "No \"No\" answers recorded yet." });
|
|
21
|
-
|
|
21
|
+
const byPhase = new Map();
|
|
22
|
+
for (const issue of issues) {
|
|
23
|
+
const phase = issue.phase ?? 0;
|
|
24
|
+
if (!byPhase.has(phase))
|
|
25
|
+
byPhase.set(phase, []);
|
|
26
|
+
byPhase.get(phase).push(issue);
|
|
27
|
+
}
|
|
28
|
+
const sortedPhases = Array.from(byPhase.keys()).sort((a, b) => a - b);
|
|
29
|
+
return (_jsx(Stack, { gap: "md", children: sortedPhases.map((phase) => {
|
|
30
|
+
const phaseIssues = byPhase.get(phase);
|
|
31
|
+
return (_jsxs(Details, { tone: "card", defaultOpen: sortedPhases.length === 1, children: [_jsxs(Summary, { size: "lg", weight: "bold", children: ["Phase ", phase, " (", phaseIssues.length, " issue", phaseIssues.length === 1 ? "" : "s", ")"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "sm", children: phaseIssues.map((issue) => (_jsx(Card, { padding: "md", variant: "default", children: _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { weight: "semibold", children: issue.label }), _jsxs(Text, { size: "sm", color: "muted", children: [issue.groupLabel, " \u203A ", issue.pageLabel] }), _jsxs(Text, { size: "sm", color: "muted", children: ["Reported by ", issue.testerDisplayName] }), issue.comment && _jsx(Text, { size: "sm", children: issue.comment }), issue.screenshotUrl && (_jsx(MediaImage, { src: issue.screenshotUrl, alt: "Tester screenshot", size: "card" }))] }) }, issue.id))) }) })] }, phase));
|
|
32
|
+
}) }));
|
|
22
33
|
}
|
|
@@ -59,12 +59,13 @@ export function AdminTesterFeedbackListView(_props) {
|
|
|
59
59
|
sortOptions: [
|
|
60
60
|
{ value: sortBy(TESTER_CHECKLIST_RESPONSE_FIELDS.CREATED_AT, "DESC"), label: "Newest" },
|
|
61
61
|
{ value: sortBy(TESTER_CHECKLIST_RESPONSE_FIELDS.CREATED_AT, "ASC"), label: "Oldest" },
|
|
62
|
+
{ value: sortBy(TESTER_CHECKLIST_RESPONSE_FIELDS.PHASE, "ASC"), label: "Phase" },
|
|
62
63
|
],
|
|
63
64
|
columns: COLUMNS,
|
|
64
65
|
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
65
66
|
id: toStringValue(item.id, `feedback-${index}`),
|
|
66
67
|
primary: toStringValue(item.testerDisplayName, "Unknown tester"),
|
|
67
|
-
secondary:
|
|
68
|
+
secondary: `Phase ${toStringValue(item.phase, "?")} — ${toStringValue(item.groupKey, "")} / ${toStringValue(item.pageKey, "")}${item.comment ? " — " + toStringValue(item.comment, "") : ""}`,
|
|
68
69
|
answer: toStringValue(item.answer, ""),
|
|
69
70
|
status: toStringValue(item.status, "new"),
|
|
70
71
|
updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
|
|
@@ -17,14 +17,18 @@ export function AdminTesterFeedbackReportView() {
|
|
|
17
17
|
return _jsx(Text, { color: "muted", children: "Loading report\u2026" });
|
|
18
18
|
const itemCoverage = query.data?.itemCoverage ?? [];
|
|
19
19
|
const totals = query.data?.totals ?? { totalAnswered: 0, totalYes: 0, totalNo: 0 };
|
|
20
|
-
const
|
|
20
|
+
const byPhase = new Map();
|
|
21
21
|
for (const item of itemCoverage) {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const phase = item.phase ?? 0;
|
|
23
|
+
if (!byPhase.has(phase)) {
|
|
24
|
+
byPhase.set(phase, { groupLabel: `Phase ${phase}`, yes: 0, no: 0 });
|
|
24
25
|
}
|
|
25
|
-
const entry =
|
|
26
|
+
const entry = byPhase.get(phase);
|
|
26
27
|
entry.yes += item.yesCount;
|
|
27
28
|
entry.no += item.noCount;
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
const sortedPhaseData = Array.from(byPhase.entries())
|
|
31
|
+
.sort(([a], [b]) => a - b)
|
|
32
|
+
.map(([, datum]) => datum);
|
|
33
|
+
return (_jsx(Stack, { gap: "lg", children: _jsx(TesterFeedbackChart, { data: sortedPhaseData, totalCases: itemCoverage.length, totalAnswered: totals.totalAnswered, totalYes: totals.totalYes, totalNo: totals.totalNo }) }));
|
|
30
34
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
5
|
import { Alert, Details, Div, FieldInput, Heading, Stack, Summary, Text } from "../../../ui";
|
|
@@ -15,6 +15,21 @@ function matchesQuery(item, query) {
|
|
|
15
15
|
const haystack = `${item.label} ${item.description ?? ""} ${item.groupLabel} ${item.pageLabel} ${item.href ?? ""}`.toLowerCase();
|
|
16
16
|
return haystack.includes(query.toLowerCase());
|
|
17
17
|
}
|
|
18
|
+
function TesterChecklistPageSection({ page, testerDisplayName, onAnswer, onSaveNote }) {
|
|
19
|
+
const answeredCount = page.items.filter((i) => i.answer).length;
|
|
20
|
+
return (_jsxs(Details, { tone: "card", defaultOpen: false, children: [_jsxs(Summary, { children: [page.pageLabel, " (", answeredCount, "/", page.items.length, " answered)"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "sm", children: page.items
|
|
21
|
+
.slice()
|
|
22
|
+
.sort((a, b) => a.order - b.order)
|
|
23
|
+
.map((item) => (_jsx(TesterChecklistStepRow, { testerDisplayName: testerDisplayName, item: {
|
|
24
|
+
checklistItemId: item.id,
|
|
25
|
+
label: item.label,
|
|
26
|
+
description: item.description,
|
|
27
|
+
href: item.href,
|
|
28
|
+
answer: item.answer,
|
|
29
|
+
comment: item.comment,
|
|
30
|
+
screenshotUrl: item.screenshotUrl,
|
|
31
|
+
}, onAnswer: onAnswer, onSaveNote: onSaveNote }, item.id))) }) })] }));
|
|
32
|
+
}
|
|
18
33
|
export function TesterHubView({ sandboxExpiresAt }) {
|
|
19
34
|
const { user } = useSession();
|
|
20
35
|
const queryClient = useQueryClient();
|
|
@@ -35,38 +50,36 @@ export function TesterHubView({ sandboxExpiresAt }) {
|
|
|
35
50
|
if (!user?.isTester && !isAdminUser(user)) {
|
|
36
51
|
return (_jsx(Alert, { variant: "warning", title: "Testers only", children: "This page is only available to accounts flagged as testers (and admins). Contact an admin if you believe this is a mistake." }));
|
|
37
52
|
}
|
|
53
|
+
// Items arrive pre-sorted by catalog `order` (listActive() orders by it).
|
|
38
54
|
const items = query.data?.items ?? [];
|
|
39
55
|
const filtered = items.filter((item) => matchesQuery(item, search));
|
|
40
|
-
const
|
|
56
|
+
const phases = new Map();
|
|
41
57
|
for (const item of filtered) {
|
|
42
|
-
if (!
|
|
58
|
+
if (!phases.has(item.phase))
|
|
59
|
+
phases.set(item.phase, new Map());
|
|
60
|
+
const groups = phases.get(item.phase);
|
|
61
|
+
if (!groups.has(item.groupKey))
|
|
43
62
|
groups.set(item.groupKey, { groupLabel: item.groupLabel, pages: new Map() });
|
|
44
|
-
}
|
|
45
63
|
const group = groups.get(item.groupKey);
|
|
46
|
-
if (!group.pages.has(item.pageKey))
|
|
64
|
+
if (!group.pages.has(item.pageKey))
|
|
47
65
|
group.pages.set(item.pageKey, { pageLabel: item.pageLabel, items: [] });
|
|
48
|
-
}
|
|
49
66
|
group.pages.get(item.pageKey).items.push(item);
|
|
50
67
|
}
|
|
68
|
+
const sortedPhaseNumbers = Array.from(phases.keys()).sort((a, b) => a - b);
|
|
51
69
|
const handleAnswer = (checklistItemId, answer) => {
|
|
52
70
|
upsertMutation.mutate({ checklistItemId, patch: { answer } });
|
|
53
71
|
};
|
|
54
72
|
const handleSaveNote = async (checklistItemId, comment, screenshotUrl) => {
|
|
55
73
|
await upsertMutation.mutateAsync({ checklistItemId, patch: { comment, screenshotUrl } });
|
|
56
74
|
};
|
|
57
|
-
return (_jsxs(Stack, { gap: "lg", children: [_jsx(Heading, { level: 1, children: "Tester Hub" }), _jsx(Text, { color: "muted", children: "Work through the checklist below and answer Yes/No for each test case. Add a comment and screenshot wherever something looks off \u2014 colors, styles, readability, bugs. Your answers save automatically." }), sandboxExpiresAt && (_jsxs(Alert, { variant: "info", title: "Shared test sandbox", children: ["The shared test store/products/categories expire on ", new Date(sandboxExpiresAt).toLocaleDateString(), "."] })), _jsx(FieldInput, { name: "checklist-search", label: "Search test cases", value: search, onChange: setSearch, placeholder: "Search by title or route, e.g. checkout, /store/payouts..." }), query.isLoading && _jsx(Text, { color: "muted", children: "Loading checklist\u2026" }),
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
answer: item.answer,
|
|
68
|
-
comment: item.comment,
|
|
69
|
-
screenshotUrl: item.screenshotUrl,
|
|
70
|
-
}, onAnswer: handleAnswer, onSaveNote: handleSaveNote }, item.id))) }) })] }, pageKey));
|
|
71
|
-
})] }, groupKey)))] }));
|
|
75
|
+
return (_jsxs(Stack, { gap: "lg", children: [_jsx(Heading, { level: 1, children: "Tester Hub" }), _jsx(Text, { color: "muted", children: "Work through the checklist below and answer Yes/No for each test case. Add a comment and screenshot wherever something looks off \u2014 colors, styles, readability, bugs. Your answers save automatically." }), sandboxExpiresAt && (_jsxs(Alert, { variant: "info", title: "Shared test sandbox", children: ["The shared test store/products/categories expire on ", new Date(sandboxExpiresAt).toLocaleDateString(), "."] })), _jsx(FieldInput, { name: "checklist-search", label: "Search test cases", value: search, onChange: setSearch, placeholder: "Search by title or route, e.g. checkout, /store/payouts..." }), query.isLoading && _jsx(Text, { color: "muted", children: "Loading checklist\u2026" }), sortedPhaseNumbers.map((phaseNumber) => {
|
|
76
|
+
const groups = phases.get(phaseNumber);
|
|
77
|
+
const phaseItemCount = Array.from(groups.values())
|
|
78
|
+
.flatMap((g) => Array.from(g.pages.values()))
|
|
79
|
+
.reduce((sum, page) => sum + page.items.length, 0);
|
|
80
|
+
const phaseAnswered = Array.from(groups.values())
|
|
81
|
+
.flatMap((g) => Array.from(g.pages.values()))
|
|
82
|
+
.reduce((sum, page) => sum + page.items.filter((i) => i.answer).length, 0);
|
|
83
|
+
return (_jsxs(Details, { tone: "card", defaultOpen: false, children: [_jsxs(Summary, { size: "lg", weight: "bold", children: ["Phase ", phaseNumber, " (", phaseAnswered, "/", phaseItemCount, " answered)"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "md", children: Array.from(groups.entries()).map(([groupKey, group]) => (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 4, children: group.groupLabel }), Array.from(group.pages.entries()).map(([pageKey, page]) => (_jsx(TesterChecklistPageSection, { page: page, testerDisplayName: user?.displayName ?? user?.email ?? "tester", onAnswer: handleAnswer, onSaveNote: handleSaveNote }, pageKey)))] }, groupKey))) }) })] }, phaseNumber));
|
|
84
|
+
})] }));
|
|
72
85
|
}
|
|
@@ -48,6 +48,7 @@ TesterChecklistItemRepository.SIEVE_FIELDS = {
|
|
|
48
48
|
pageKey: { canFilter: true, canSort: true },
|
|
49
49
|
label: { canFilter: true, canSort: false },
|
|
50
50
|
order: { canFilter: true, canSort: true },
|
|
51
|
+
phase: { canFilter: true, canSort: true },
|
|
51
52
|
isActive: { canFilter: true, canSort: false },
|
|
52
53
|
searchTokens: { canFilter: true, canSort: false },
|
|
53
54
|
createdAt: { canFilter: true, canSort: true },
|
|
@@ -7,6 +7,7 @@ export interface UpsertResponseInput {
|
|
|
7
7
|
checklistItemId: string;
|
|
8
8
|
groupKey: string;
|
|
9
9
|
pageKey: string;
|
|
10
|
+
phase: number;
|
|
10
11
|
answer?: TesterAnswer | null;
|
|
11
12
|
comment?: string;
|
|
12
13
|
screenshotUrl?: string;
|
|
@@ -14,14 +15,22 @@ export interface UpsertResponseInput {
|
|
|
14
15
|
export interface ChecklistItemCoverage {
|
|
15
16
|
checklistItemId: string;
|
|
16
17
|
groupKey: string;
|
|
18
|
+
groupLabel: string;
|
|
17
19
|
pageKey: string;
|
|
20
|
+
pageLabel: string;
|
|
21
|
+
phase: number;
|
|
18
22
|
yesCount: number;
|
|
19
23
|
noCount: number;
|
|
20
24
|
totalAnswered: number;
|
|
21
25
|
}
|
|
26
|
+
export type CoverageIssue = TesterChecklistResponseDocument & {
|
|
27
|
+
groupLabel: string;
|
|
28
|
+
pageLabel: string;
|
|
29
|
+
label: string;
|
|
30
|
+
};
|
|
22
31
|
export interface CoverageReport {
|
|
23
32
|
itemCoverage: ChecklistItemCoverage[];
|
|
24
|
-
issues:
|
|
33
|
+
issues: CoverageIssue[];
|
|
25
34
|
totals: {
|
|
26
35
|
totalAnswered: number;
|
|
27
36
|
totalYes: number;
|
|
@@ -36,14 +45,19 @@ export declare class TesterChecklistResponseRepository extends BaseRepository<Te
|
|
|
36
45
|
listForTester(testerId: string): Promise<TesterChecklistResponseDocument[]>;
|
|
37
46
|
list(model: SieveModel): Promise<FirebaseSieveResult<TesterChecklistResponseDocument>>;
|
|
38
47
|
markReviewed(id: string): Promise<TesterChecklistResponseDocument>;
|
|
39
|
-
/**
|
|
48
|
+
/** Joins responses against the checklist catalog for human labels
|
|
49
|
+
* (groupLabel/pageLabel) — small tables (testers x cases, and the
|
|
50
|
+
* ~300-item catalog), so the extra read is cheap. `phase` itself is
|
|
51
|
+
* already denormalized onto the response doc, no join needed for it. */
|
|
40
52
|
getCoverageReport(): Promise<CoverageReport>;
|
|
41
53
|
/**
|
|
42
54
|
* Markdown dump of every answered case, joined against the checklist item
|
|
43
55
|
* catalog for the human-readable label/href — optimized for a future dev
|
|
44
56
|
* (or Claude session) to read directly and go fix the reported issues.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
57
|
+
* Grouped by phase (ascending), then group, then page — mirrors how the
|
|
58
|
+
* Tester Hub itself is organized. Mirrors
|
|
59
|
+
* appkit/scripts/export-tester-feedback.mjs's CLI output exactly; keep
|
|
60
|
+
* the two in sync.
|
|
47
61
|
*/
|
|
48
62
|
getMarkdownReport(siteOrigin: string): Promise<string>;
|
|
49
63
|
}
|
|
@@ -24,6 +24,7 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
24
24
|
checklistItemId: input.checklistItemId,
|
|
25
25
|
groupKey: input.groupKey,
|
|
26
26
|
pageKey: input.pageKey,
|
|
27
|
+
phase: input.phase,
|
|
27
28
|
status: "new",
|
|
28
29
|
};
|
|
29
30
|
if (input.answer !== undefined)
|
|
@@ -55,9 +56,16 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
55
56
|
status: "reviewed",
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
|
-
/**
|
|
59
|
+
/** Joins responses against the checklist catalog for human labels
|
|
60
|
+
* (groupLabel/pageLabel) — small tables (testers x cases, and the
|
|
61
|
+
* ~300-item catalog), so the extra read is cheap. `phase` itself is
|
|
62
|
+
* already denormalized onto the response doc, no join needed for it. */
|
|
59
63
|
async getCoverageReport() {
|
|
60
|
-
const snapshot = await
|
|
64
|
+
const [{ items }, snapshot] = await Promise.all([
|
|
65
|
+
testerChecklistItemRepository.list({ page: "1", pageSize: "1000" }),
|
|
66
|
+
this.db.collection(this.collection).get(),
|
|
67
|
+
]);
|
|
68
|
+
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
61
69
|
const docs = snapshot.docs.map((doc) => this.mapDoc(doc));
|
|
62
70
|
const byItem = new Map();
|
|
63
71
|
const issues = [];
|
|
@@ -66,12 +74,18 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
66
74
|
for (const doc of docs) {
|
|
67
75
|
if (!doc.answer)
|
|
68
76
|
continue;
|
|
77
|
+
const catalogItem = itemById.get(doc.checklistItemId);
|
|
78
|
+
const groupLabel = catalogItem?.groupLabel ?? doc.groupKey;
|
|
79
|
+
const pageLabel = catalogItem?.pageLabel ?? doc.pageKey;
|
|
69
80
|
let entry = byItem.get(doc.checklistItemId);
|
|
70
81
|
if (!entry) {
|
|
71
82
|
entry = {
|
|
72
83
|
checklistItemId: doc.checklistItemId,
|
|
73
84
|
groupKey: doc.groupKey,
|
|
85
|
+
groupLabel,
|
|
74
86
|
pageKey: doc.pageKey,
|
|
87
|
+
pageLabel,
|
|
88
|
+
phase: doc.phase,
|
|
75
89
|
yesCount: 0,
|
|
76
90
|
noCount: 0,
|
|
77
91
|
totalAnswered: 0,
|
|
@@ -86,7 +100,12 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
86
100
|
else {
|
|
87
101
|
entry.noCount += 1;
|
|
88
102
|
totalNo += 1;
|
|
89
|
-
issues.push(
|
|
103
|
+
issues.push({
|
|
104
|
+
...doc,
|
|
105
|
+
groupLabel,
|
|
106
|
+
pageLabel,
|
|
107
|
+
label: catalogItem?.label ?? doc.checklistItemId,
|
|
108
|
+
});
|
|
90
109
|
}
|
|
91
110
|
}
|
|
92
111
|
return {
|
|
@@ -99,8 +118,10 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
99
118
|
* Markdown dump of every answered case, joined against the checklist item
|
|
100
119
|
* catalog for the human-readable label/href — optimized for a future dev
|
|
101
120
|
* (or Claude session) to read directly and go fix the reported issues.
|
|
102
|
-
*
|
|
103
|
-
*
|
|
121
|
+
* Grouped by phase (ascending), then group, then page — mirrors how the
|
|
122
|
+
* Tester Hub itself is organized. Mirrors
|
|
123
|
+
* appkit/scripts/export-tester-feedback.mjs's CLI output exactly; keep
|
|
124
|
+
* the two in sync.
|
|
104
125
|
*/
|
|
105
126
|
async getMarkdownReport(siteOrigin) {
|
|
106
127
|
const [items, snapshot] = await Promise.all([
|
|
@@ -114,14 +135,15 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
114
135
|
const grouped = new Map();
|
|
115
136
|
for (const r of responses) {
|
|
116
137
|
const item = itemById.get(r.checklistItemId);
|
|
138
|
+
const phase = item?.phase ?? r.phase ?? 0;
|
|
117
139
|
const groupLabel = item?.groupLabel ?? r.groupKey ?? "Unknown group";
|
|
118
140
|
const pageLabel = item?.pageLabel ?? r.pageKey ?? "Unknown page";
|
|
119
|
-
const key = `${groupLabel}␟${pageLabel}`;
|
|
141
|
+
const key = `${phase}␟${groupLabel}␟${pageLabel}`;
|
|
120
142
|
if (!grouped.has(key))
|
|
121
|
-
grouped.set(key, { groupLabel, pageLabel, items: [] });
|
|
143
|
+
grouped.set(key, { phase, groupLabel, pageLabel, items: [] });
|
|
122
144
|
grouped.get(key).items.push({ ...r, label: item?.label ?? r.checklistItemId, href: item?.href });
|
|
123
145
|
}
|
|
124
|
-
const sortedGroups = Array.from(grouped.values()).sort((a, b) => a.groupLabel.localeCompare(b.groupLabel) || a.pageLabel.localeCompare(b.pageLabel));
|
|
146
|
+
const sortedGroups = Array.from(grouped.values()).sort((a, b) => a.phase - b.phase || a.groupLabel.localeCompare(b.groupLabel) || a.pageLabel.localeCompare(b.pageLabel));
|
|
125
147
|
const issues = responses.filter((r) => r.answer === "no");
|
|
126
148
|
const passingWithNotes = responses.filter((r) => r.answer === "yes" && r.comment?.trim());
|
|
127
149
|
const lines = [];
|
|
@@ -138,10 +160,16 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
138
160
|
lines.push("");
|
|
139
161
|
}
|
|
140
162
|
else {
|
|
163
|
+
let lastPhase = null;
|
|
141
164
|
for (const group of sortedGroups) {
|
|
142
165
|
const groupIssues = group.items.filter((r) => r.answer === "no");
|
|
143
166
|
if (groupIssues.length === 0)
|
|
144
167
|
continue;
|
|
168
|
+
if (group.phase !== lastPhase) {
|
|
169
|
+
lines.push(`## Phase ${group.phase}`);
|
|
170
|
+
lines.push("");
|
|
171
|
+
lastPhase = group.phase;
|
|
172
|
+
}
|
|
145
173
|
lines.push(`### ${group.groupLabel} › ${group.pageLabel}`);
|
|
146
174
|
lines.push("");
|
|
147
175
|
for (const r of groupIssues) {
|
|
@@ -166,10 +194,16 @@ export class TesterChecklistResponseRepository extends BaseRepository {
|
|
|
166
194
|
lines.push("");
|
|
167
195
|
}
|
|
168
196
|
else {
|
|
197
|
+
let lastPhase = null;
|
|
169
198
|
for (const group of sortedGroups) {
|
|
170
199
|
const groupNotes = group.items.filter((r) => r.answer === "yes" && r.comment?.trim());
|
|
171
200
|
if (groupNotes.length === 0)
|
|
172
201
|
continue;
|
|
202
|
+
if (group.phase !== lastPhase) {
|
|
203
|
+
lines.push(`## Phase ${group.phase}`);
|
|
204
|
+
lines.push("");
|
|
205
|
+
lastPhase = group.phase;
|
|
206
|
+
}
|
|
173
207
|
lines.push(`### ${group.groupLabel} › ${group.pageLabel}`);
|
|
174
208
|
lines.push("");
|
|
175
209
|
for (const r of groupNotes) {
|
|
@@ -189,6 +223,7 @@ TesterChecklistResponseRepository.SIEVE_FIELDS = {
|
|
|
189
223
|
checklistItemId: { canFilter: true, canSort: false },
|
|
190
224
|
groupKey: { canFilter: true, canSort: true },
|
|
191
225
|
pageKey: { canFilter: true, canSort: true },
|
|
226
|
+
phase: { canFilter: true, canSort: true },
|
|
192
227
|
answer: { canFilter: true, canSort: false },
|
|
193
228
|
status: { canFilter: true, canSort: false },
|
|
194
229
|
createdAt: { canFilter: true, canSort: true },
|
|
@@ -15,16 +15,17 @@ export interface TesterChecklistItemDocument extends BaseDocument {
|
|
|
15
15
|
description?: string;
|
|
16
16
|
href?: string;
|
|
17
17
|
order: number;
|
|
18
|
+
phase: number;
|
|
18
19
|
isActive: boolean;
|
|
19
20
|
searchTokens: string[];
|
|
20
21
|
adminOnly?: boolean;
|
|
21
22
|
}
|
|
22
23
|
export declare const TESTER_CHECKLIST_ITEM_COLLECTION: "testerChecklistItems";
|
|
23
24
|
export declare const TESTER_CHECKLIST_ITEM_ID_PREFIX: "checklist-";
|
|
24
|
-
export declare const TESTER_CHECKLIST_ITEM_INDEXED_FIELDS: readonly ["groupKey", "pageKey", "order", "isActive", "searchTokens", "createdAt"];
|
|
25
|
+
export declare const TESTER_CHECKLIST_ITEM_INDEXED_FIELDS: readonly ["groupKey", "pageKey", "phase", "order", "isActive", "searchTokens", "createdAt"];
|
|
25
26
|
export declare const DEFAULT_TESTER_CHECKLIST_ITEM_DATA: Partial<TesterChecklistItemDocument>;
|
|
26
27
|
export type TesterChecklistItemCreateInput = Omit<TesterChecklistItemDocument, "id" | "createdAt" | "updatedAt" | "searchTokens">;
|
|
27
|
-
export type TesterChecklistItemUpdateInput = Partial<Pick<TesterChecklistItemDocument, "groupKey" | "groupLabel" | "pageKey" | "pageLabel" | "label" | "description" | "href" | "order" | "isActive" | "adminOnly">>;
|
|
28
|
+
export type TesterChecklistItemUpdateInput = Partial<Pick<TesterChecklistItemDocument, "groupKey" | "groupLabel" | "pageKey" | "pageLabel" | "label" | "description" | "href" | "order" | "phase" | "isActive" | "adminOnly">>;
|
|
28
29
|
export declare const testerChecklistItemQueryHelpers: {
|
|
29
30
|
readonly byGroup: (groupKey: string) => readonly ["groupKey", "==", string];
|
|
30
31
|
readonly byPage: (pageKey: string) => readonly ["pageKey", "==", string];
|
|
@@ -40,18 +41,20 @@ export interface TesterChecklistResponseDocument extends BaseDocument {
|
|
|
40
41
|
checklistItemId: string;
|
|
41
42
|
groupKey: string;
|
|
42
43
|
pageKey: string;
|
|
44
|
+
phase: number;
|
|
43
45
|
answer: TesterAnswer | null;
|
|
44
46
|
comment?: string;
|
|
45
47
|
screenshotUrl?: string;
|
|
46
48
|
status: TesterFeedbackStatus;
|
|
47
49
|
}
|
|
48
50
|
export declare const TESTER_CHECKLIST_RESPONSE_COLLECTION: "testerChecklistResponses";
|
|
49
|
-
export declare const TESTER_CHECKLIST_RESPONSE_INDEXED_FIELDS: readonly ["testerId", "checklistItemId", "groupKey", "pageKey", "answer", "status", "createdAt"];
|
|
51
|
+
export declare const TESTER_CHECKLIST_RESPONSE_INDEXED_FIELDS: readonly ["testerId", "checklistItemId", "groupKey", "pageKey", "phase", "answer", "status", "createdAt"];
|
|
50
52
|
export declare const DEFAULT_TESTER_CHECKLIST_RESPONSE_DATA: Partial<TesterChecklistResponseDocument>;
|
|
51
53
|
export declare function createChecklistResponseId(testerId: string, checklistItemId: string): string;
|
|
52
54
|
export declare const testerChecklistResponseQueryHelpers: {
|
|
53
55
|
readonly byTester: (testerId: string) => readonly ["testerId", "==", string];
|
|
54
56
|
readonly byItem: (checklistItemId: string) => readonly ["checklistItemId", "==", string];
|
|
57
|
+
readonly byPhase: (phase: number) => readonly ["phase", "==", number];
|
|
55
58
|
readonly byAnswer: (answer: TesterAnswer) => readonly ["answer", "==", TesterAnswer];
|
|
56
59
|
readonly byStatus: (status: TesterFeedbackStatus) => readonly ["status", "==", TesterFeedbackStatus];
|
|
57
60
|
};
|
|
@@ -65,6 +68,7 @@ export declare const TESTER_CHECKLIST_ITEM_FIELDS: {
|
|
|
65
68
|
readonly DESCRIPTION: "description";
|
|
66
69
|
readonly HREF: "href";
|
|
67
70
|
readonly ORDER: "order";
|
|
71
|
+
readonly PHASE: "phase";
|
|
68
72
|
readonly IS_ACTIVE: "isActive";
|
|
69
73
|
readonly SEARCH_TOKENS: "searchTokens";
|
|
70
74
|
readonly ADMIN_ONLY: "adminOnly";
|
|
@@ -78,6 +82,7 @@ export declare const TESTER_CHECKLIST_RESPONSE_FIELDS: {
|
|
|
78
82
|
readonly CHECKLIST_ITEM_ID: "checklistItemId";
|
|
79
83
|
readonly GROUP_KEY: "groupKey";
|
|
80
84
|
readonly PAGE_KEY: "pageKey";
|
|
85
|
+
readonly PHASE: "phase";
|
|
81
86
|
readonly ANSWER: "answer";
|
|
82
87
|
readonly COMMENT: "comment";
|
|
83
88
|
readonly SCREENSHOT_URL: "screenshotUrl";
|
|
@@ -11,6 +11,7 @@ export const TESTER_CHECKLIST_ITEM_ID_PREFIX = "checklist-";
|
|
|
11
11
|
export const TESTER_CHECKLIST_ITEM_INDEXED_FIELDS = [
|
|
12
12
|
"groupKey",
|
|
13
13
|
"pageKey",
|
|
14
|
+
"phase",
|
|
14
15
|
"order",
|
|
15
16
|
"isActive",
|
|
16
17
|
"searchTokens",
|
|
@@ -18,6 +19,7 @@ export const TESTER_CHECKLIST_ITEM_INDEXED_FIELDS = [
|
|
|
18
19
|
];
|
|
19
20
|
export const DEFAULT_TESTER_CHECKLIST_ITEM_DATA = {
|
|
20
21
|
order: 0,
|
|
22
|
+
phase: 1,
|
|
21
23
|
isActive: true,
|
|
22
24
|
searchTokens: [],
|
|
23
25
|
adminOnly: false,
|
|
@@ -45,6 +47,7 @@ export const TESTER_CHECKLIST_RESPONSE_INDEXED_FIELDS = [
|
|
|
45
47
|
"checklistItemId",
|
|
46
48
|
"groupKey",
|
|
47
49
|
"pageKey",
|
|
50
|
+
"phase",
|
|
48
51
|
"answer",
|
|
49
52
|
"status",
|
|
50
53
|
"createdAt",
|
|
@@ -59,6 +62,7 @@ export function createChecklistResponseId(testerId, checklistItemId) {
|
|
|
59
62
|
export const testerChecklistResponseQueryHelpers = {
|
|
60
63
|
byTester: (testerId) => ["testerId", "==", testerId],
|
|
61
64
|
byItem: (checklistItemId) => ["checklistItemId", "==", checklistItemId],
|
|
65
|
+
byPhase: (phase) => ["phase", "==", phase],
|
|
62
66
|
byAnswer: (answer) => ["answer", "==", answer],
|
|
63
67
|
byStatus: (status) => ["status", "==", status],
|
|
64
68
|
};
|
|
@@ -73,6 +77,7 @@ export const TESTER_CHECKLIST_ITEM_FIELDS = {
|
|
|
73
77
|
DESCRIPTION: "description",
|
|
74
78
|
HREF: "href",
|
|
75
79
|
ORDER: "order",
|
|
80
|
+
PHASE: "phase",
|
|
76
81
|
IS_ACTIVE: "isActive",
|
|
77
82
|
SEARCH_TOKENS: "searchTokens",
|
|
78
83
|
ADMIN_ONLY: "adminOnly",
|
|
@@ -86,6 +91,7 @@ export const TESTER_CHECKLIST_RESPONSE_FIELDS = {
|
|
|
86
91
|
CHECKLIST_ITEM_ID: "checklistItemId",
|
|
87
92
|
GROUP_KEY: "groupKey",
|
|
88
93
|
PAGE_KEY: "pageKey",
|
|
94
|
+
PHASE: "phase",
|
|
89
95
|
ANSWER: "answer",
|
|
90
96
|
COMMENT: "comment",
|
|
91
97
|
SCREENSHOT_URL: "screenshotUrl",
|