@burdenoff/microfe-ggnomad 2026.1029.3 → 2026.1030.2

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 (37) hide show
  1. package/dist/generated/wspace-types.d.ts +102099 -35827
  2. package/dist/generated/wspace-types.d.ts.map +1 -1
  3. package/dist/ggnomad/components/AuditTrailDialog.d.ts +6 -0
  4. package/dist/ggnomad/components/AuditTrailDialog.d.ts.map +1 -1
  5. package/dist/ggnomad/components/AuditTrailDialog.js +47 -42
  6. package/dist/ggnomad/components/AuditTrailDialog.js.map +1 -1
  7. package/dist/ggnomad/components/EntityCard.d.ts +9 -1
  8. package/dist/ggnomad/components/EntityCard.d.ts.map +1 -1
  9. package/dist/ggnomad/components/EntityCard.js +14 -7
  10. package/dist/ggnomad/components/EntityCard.js.map +1 -1
  11. package/dist/ggnomad/graphql.d.ts +6 -0
  12. package/dist/ggnomad/graphql.d.ts.map +1 -1
  13. package/dist/ggnomad/graphql.js +39 -18
  14. package/dist/ggnomad/graphql.js.map +1 -1
  15. package/dist/ggnomad/hooks/useFocusedRecordParam.d.ts +23 -0
  16. package/dist/ggnomad/hooks/useFocusedRecordParam.d.ts.map +1 -0
  17. package/dist/ggnomad/hooks/useFocusedRecordParam.js +15 -0
  18. package/dist/ggnomad/hooks/useFocusedRecordParam.js.map +1 -0
  19. package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
  20. package/dist/ggnomad/pages/BookingsPage.js +149 -142
  21. package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
  22. package/dist/ggnomad/pages/ProviderAnalyticsPage.d.ts.map +1 -1
  23. package/dist/ggnomad/pages/ProviderAnalyticsPage.js +135 -134
  24. package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
  25. package/dist/services/assistantQuota.d.ts +69 -0
  26. package/dist/services/assistantQuota.d.ts.map +1 -0
  27. package/dist/services/assistantQuota.js +52 -0
  28. package/dist/services/assistantQuota.js.map +1 -0
  29. package/dist/services/createSandboxAssistantTransport.d.ts +5 -3
  30. package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -1
  31. package/dist/services/createSandboxAssistantTransport.js +26 -6
  32. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  33. package/dist/utils/pageContext.d.ts +62 -8
  34. package/dist/utils/pageContext.d.ts.map +1 -1
  35. package/dist/utils/pageContext.js +114 -39
  36. package/dist/utils/pageContext.js.map +1 -1
  37. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderAnalyticsPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/ProviderAnalyticsPage.tsx"],"names":[],"mappings":"AAwDA;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,gCA6SpC"}
1
+ {"version":3,"file":"ProviderAnalyticsPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/ProviderAnalyticsPage.tsx"],"names":[],"mappings":"AAyDA;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,gCAkTpC"}
@@ -3,249 +3,250 @@ import { PageShell as n } from "./PageShell.js";
3
3
  import { StatCard as r } from "../components/StatCard.js";
4
4
  import { SectionCard as i } from "../components/SectionCard.js";
5
5
  import "../components/index.js";
6
- import { useMemo as a, useState as o } from "react";
7
- import { BarChart3 as s, CalendarCheck as c, DollarSign as l, Gauge as u, TrendingUp as d, XCircle as f } from "lucide-react";
8
- import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
- import { useQuery as m } from "@apollo/client/react";
10
- import { IllustratedEmptyState as h, PagePurpose as g, Spinner as _ } from "@burdenoff/fe-libs/ui";
11
- import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
6
+ import { useFocusedRecordParam as a } from "../hooks/useFocusedRecordParam.js";
7
+ import { useMemo as o, useState as s } from "react";
8
+ import { BarChart3 as c, CalendarCheck as l, DollarSign as u, Gauge as d, TrendingUp as f, XCircle as p } from "lucide-react";
9
+ import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
+ import { useQuery as h } from "@apollo/client/react";
11
+ import { IllustratedEmptyState as g, PagePurpose as _, Spinner as v } from "@burdenoff/fe-libs/ui";
12
+ import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
12
13
  //#region src/ggnomad/pages/ProviderAnalyticsPage.tsx
13
- var x = [
14
+ var S = [
14
15
  "var(--color-chart-1)",
15
16
  "var(--color-chart-2)",
16
17
  "var(--color-chart-3)",
17
18
  "var(--color-chart-4)",
18
19
  "var(--color-chart-5)"
19
- ], S = "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg";
20
- function C(e) {
20
+ ], C = "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg";
21
+ function w(e) {
21
22
  return e.toISOString().slice(0, 10);
22
23
  }
23
- function w(e) {
24
+ function T(e) {
24
25
  return /* @__PURE__ */ new Date(Date.now() - e * 24 * 60 * 60 * 1e3);
25
26
  }
26
- function T() {
27
- let { t: T } = p(), { data: E, loading: D } = m(e), O = E?.myGgnomadProviders ?? [], [k, A] = o(""), [j, M] = o(() => C(w(30))), [N, P] = o(() => C(/* @__PURE__ */ new Date())), [F, I] = o("day"), L = k || O[0]?.id || "", { data: R, loading: z } = m(t, {
27
+ function E() {
28
+ let { t: E } = m(), { data: D, loading: O } = h(e), k = D?.myGgnomadProviders ?? [], [A, j] = a(), M = k.some((e) => e.id === A) ? A ?? "" : "", [N, P] = s(() => w(T(30))), [F, I] = s(() => w(/* @__PURE__ */ new Date())), [L, R] = s("day"), z = M || k[0]?.id || "", { data: B, loading: V } = h(t, {
28
29
  variables: {
29
- providerId: L,
30
- dateFrom: `${j}T00:00:00.000Z`,
31
- dateTo: `${N}T23:59:59.999Z`,
32
- granularity: F
30
+ providerId: z,
31
+ dateFrom: `${N}T00:00:00.000Z`,
32
+ dateTo: `${F}T23:59:59.999Z`,
33
+ granularity: L
33
34
  },
34
- skip: !L
35
- }), B = R?.myGgnomadProviderAnalytics, V = a(() => {
36
- let e = B?.bookingsOverTime ?? [];
35
+ skip: !z
36
+ }), H = B?.myGgnomadProviderAnalytics, U = o(() => {
37
+ let e = H?.bookingsOverTime ?? [];
37
38
  return e.length > 0 ? Math.max(...e.map((e) => e.bookings)) : 0;
38
- }, [B?.bookingsOverTime]);
39
- return /* @__PURE__ */ b(n, {
40
- title: T("ggnomad.providerAnalytics.title", "Provider Analytics"),
41
- titleAccent: T("ggnomad.providerAnalytics.title.accent", "Analytics"),
42
- subtitle: T("ggnomad.providerAnalytics.subtitle", "Your bookings, revenue and listing performance"),
43
- children: [/* @__PURE__ */ y(g, {
44
- icon: /* @__PURE__ */ y(d, { className: "size-4" }),
45
- children: T("ggnomad.providerAnalytics.purpose", "A self-service dashboard for providers: bookings and revenue over time, cancellation rate, capacity utilization, and a per-listing breakdown — scoped to only the listings you own. Pick a date range and a listing granularity to drill in.")
46
- }), !D && O.length === 0 ? /* @__PURE__ */ y(h, {
39
+ }, [H?.bookingsOverTime]);
40
+ return /* @__PURE__ */ x(n, {
41
+ title: E("ggnomad.providerAnalytics.title", "Provider Analytics"),
42
+ titleAccent: E("ggnomad.providerAnalytics.title.accent", "Analytics"),
43
+ subtitle: E("ggnomad.providerAnalytics.subtitle", "Your bookings, revenue and listing performance"),
44
+ children: [/* @__PURE__ */ b(_, {
45
+ icon: /* @__PURE__ */ b(f, { className: "size-4" }),
46
+ children: E("ggnomad.providerAnalytics.purpose", "A self-service dashboard for providers: bookings and revenue over time, cancellation rate, capacity utilization, and a per-listing breakdown — scoped to only the listings you own. Pick a date range and a listing granularity to drill in.")
47
+ }), !O && k.length === 0 ? /* @__PURE__ */ b(g, {
47
48
  illustration: "empty-people",
48
- title: T("ggnomad.providerAnalytics.emptyTitle", "No provider profile yet"),
49
- description: T("ggnomad.providerAnalytics.emptyDescription", "Once you create a GGNomad provider profile, your bookings and revenue analytics will appear here.")
50
- }) : /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ y(i, {
51
- title: T("ggnomad.providerAnalytics.filters", "Filters"),
52
- children: /* @__PURE__ */ b("div", {
49
+ title: E("ggnomad.providerAnalytics.emptyTitle", "No provider profile yet"),
50
+ description: E("ggnomad.providerAnalytics.emptyDescription", "Once you create a GGNomad provider profile, your bookings and revenue analytics will appear here.")
51
+ }) : /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b(i, {
52
+ title: E("ggnomad.providerAnalytics.filters", "Filters"),
53
+ children: /* @__PURE__ */ x("div", {
53
54
  className: "grid gap-3 sm:grid-cols-4",
54
55
  children: [
55
- /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
56
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
56
57
  htmlFor: "provider-analytics-provider",
57
58
  className: "block text-sm font-medium text-text-primary",
58
- children: T("ggnomad.providerAnalytics.provider", "Provider")
59
- }), /* @__PURE__ */ y("select", {
59
+ children: E("ggnomad.providerAnalytics.provider", "Provider")
60
+ }), /* @__PURE__ */ b("select", {
60
61
  id: "provider-analytics-provider",
61
- value: L,
62
- onChange: (e) => A(e.target.value),
63
- className: S,
64
- children: O.map((e) => /* @__PURE__ */ y("option", {
62
+ value: z,
63
+ onChange: (e) => j(e.target.value),
64
+ className: C,
65
+ children: k.map((e) => /* @__PURE__ */ b("option", {
65
66
  value: e.id,
66
67
  children: e.name
67
68
  }, e.id))
68
69
  })] }),
69
- /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
70
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
70
71
  htmlFor: "provider-analytics-from",
71
72
  className: "block text-sm font-medium text-text-primary",
72
- children: T("ggnomad.providerAnalytics.from", "From")
73
- }), /* @__PURE__ */ y("input", {
73
+ children: E("ggnomad.providerAnalytics.from", "From")
74
+ }), /* @__PURE__ */ b("input", {
74
75
  id: "provider-analytics-from",
75
76
  type: "date",
76
- value: j,
77
- max: N,
78
- onChange: (e) => M(e.target.value),
79
- className: S
77
+ value: N,
78
+ max: F,
79
+ onChange: (e) => P(e.target.value),
80
+ className: C
80
81
  })] }),
81
- /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
82
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
82
83
  htmlFor: "provider-analytics-to",
83
84
  className: "block text-sm font-medium text-text-primary",
84
- children: T("ggnomad.providerAnalytics.to", "To")
85
- }), /* @__PURE__ */ y("input", {
85
+ children: E("ggnomad.providerAnalytics.to", "To")
86
+ }), /* @__PURE__ */ b("input", {
86
87
  id: "provider-analytics-to",
87
88
  type: "date",
88
- value: N,
89
- min: j,
90
- max: C(/* @__PURE__ */ new Date()),
91
- onChange: (e) => P(e.target.value),
92
- className: S
89
+ value: F,
90
+ min: N,
91
+ max: w(/* @__PURE__ */ new Date()),
92
+ onChange: (e) => I(e.target.value),
93
+ className: C
93
94
  })] }),
94
- /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
95
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
95
96
  htmlFor: "provider-analytics-granularity",
96
97
  className: "block text-sm font-medium text-text-primary",
97
- children: T("ggnomad.providerAnalytics.granularity", "Granularity")
98
- }), /* @__PURE__ */ b("select", {
98
+ children: E("ggnomad.providerAnalytics.granularity", "Granularity")
99
+ }), /* @__PURE__ */ x("select", {
99
100
  id: "provider-analytics-granularity",
100
- value: F,
101
- onChange: (e) => I(e.target.value),
102
- className: S,
101
+ value: L,
102
+ onChange: (e) => R(e.target.value),
103
+ className: C,
103
104
  children: [
104
- /* @__PURE__ */ y("option", {
105
+ /* @__PURE__ */ b("option", {
105
106
  value: "day",
106
- children: T("ggnomad.providerAnalytics.day", "Day")
107
+ children: E("ggnomad.providerAnalytics.day", "Day")
107
108
  }),
108
- /* @__PURE__ */ y("option", {
109
+ /* @__PURE__ */ b("option", {
109
110
  value: "week",
110
- children: T("ggnomad.providerAnalytics.week", "Week")
111
+ children: E("ggnomad.providerAnalytics.week", "Week")
111
112
  }),
112
- /* @__PURE__ */ y("option", {
113
+ /* @__PURE__ */ b("option", {
113
114
  value: "month",
114
- children: T("ggnomad.providerAnalytics.month", "Month")
115
+ children: E("ggnomad.providerAnalytics.month", "Month")
115
116
  })
116
117
  ]
117
118
  })] })
118
119
  ]
119
120
  })
120
- }), z ? /* @__PURE__ */ y("div", {
121
+ }), V ? /* @__PURE__ */ b("div", {
121
122
  className: "flex justify-center py-12",
122
- children: /* @__PURE__ */ y(_, { size: "lg" })
123
- }) : B ? /* @__PURE__ */ b(v, { children: [
124
- /* @__PURE__ */ b("section", {
123
+ children: /* @__PURE__ */ b(v, { size: "lg" })
124
+ }) : H ? /* @__PURE__ */ x(y, { children: [
125
+ /* @__PURE__ */ x("section", {
125
126
  className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-4",
126
127
  children: [
127
- /* @__PURE__ */ y(r, {
128
+ /* @__PURE__ */ b(r, {
128
129
  hero: !0,
129
- label: T("ggnomad.providerAnalytics.totalBookings", "Bookings"),
130
- value: B.totalBookings.toLocaleString(),
131
- icon: /* @__PURE__ */ y(c, {})
130
+ label: E("ggnomad.providerAnalytics.totalBookings", "Bookings"),
131
+ value: H.totalBookings.toLocaleString(),
132
+ icon: /* @__PURE__ */ b(l, {})
132
133
  }),
133
- /* @__PURE__ */ y(r, {
134
- label: T("ggnomad.providerAnalytics.totalRevenue", "Revenue"),
135
- value: `$${Number(B.totalRevenue).toLocaleString()}`,
136
- icon: /* @__PURE__ */ y(l, {})
134
+ /* @__PURE__ */ b(r, {
135
+ label: E("ggnomad.providerAnalytics.totalRevenue", "Revenue"),
136
+ value: `$${Number(H.totalRevenue).toLocaleString()}`,
137
+ icon: /* @__PURE__ */ b(u, {})
137
138
  }),
138
- /* @__PURE__ */ y(r, {
139
- label: T("ggnomad.providerAnalytics.cancellationRate", "Cancellation rate"),
140
- value: `${(B.cancellationRate * 100).toFixed(1)}%`,
141
- icon: /* @__PURE__ */ y(f, {})
139
+ /* @__PURE__ */ b(r, {
140
+ label: E("ggnomad.providerAnalytics.cancellationRate", "Cancellation rate"),
141
+ value: `${(H.cancellationRate * 100).toFixed(1)}%`,
142
+ icon: /* @__PURE__ */ b(p, {})
142
143
  }),
143
- /* @__PURE__ */ y(r, {
144
- label: T("ggnomad.providerAnalytics.capacityUtilization", "Capacity utilization"),
145
- value: `${(B.capacityUtilizationRate * 100).toFixed(1)}%`,
146
- icon: /* @__PURE__ */ y(u, {})
144
+ /* @__PURE__ */ b(r, {
145
+ label: E("ggnomad.providerAnalytics.capacityUtilization", "Capacity utilization"),
146
+ value: `${(H.capacityUtilizationRate * 100).toFixed(1)}%`,
147
+ icon: /* @__PURE__ */ b(d, {})
147
148
  })
148
149
  ]
149
150
  }),
150
- /* @__PURE__ */ y(i, {
151
- title: T("ggnomad.providerAnalytics.bookingsOverTime", "Bookings over time"),
152
- children: B.bookingsOverTime.length === 0 ? /* @__PURE__ */ b("div", {
151
+ /* @__PURE__ */ b(i, {
152
+ title: E("ggnomad.providerAnalytics.bookingsOverTime", "Bookings over time"),
153
+ children: H.bookingsOverTime.length === 0 ? /* @__PURE__ */ x("div", {
153
154
  className: "flex flex-col items-center gap-2 py-8 text-center",
154
- children: [/* @__PURE__ */ y("span", {
155
+ children: [/* @__PURE__ */ b("span", {
155
156
  className: "flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary",
156
- children: /* @__PURE__ */ y(s, { className: "size-5" })
157
- }), /* @__PURE__ */ y("p", {
157
+ children: /* @__PURE__ */ b(c, { className: "size-5" })
158
+ }), /* @__PURE__ */ b("p", {
158
159
  className: "text-sm font-medium text-text-primary",
159
- children: T("ggnomad.providerAnalytics.trendEmpty", "No bookings in this window yet")
160
+ children: E("ggnomad.providerAnalytics.trendEmpty", "No bookings in this window yet")
160
161
  })]
161
- }) : /* @__PURE__ */ b("div", {
162
+ }) : /* @__PURE__ */ x("div", {
162
163
  className: "flex flex-col gap-4",
163
- children: [/* @__PURE__ */ y("div", {
164
+ children: [/* @__PURE__ */ b("div", {
164
165
  className: "flex items-end gap-2 overflow-x-auto pb-2",
165
- children: B.bookingsOverTime.map((e, t) => /* @__PURE__ */ b("div", {
166
+ children: H.bookingsOverTime.map((e, t) => /* @__PURE__ */ x("div", {
166
167
  className: "flex flex-col items-center gap-1",
167
- children: [/* @__PURE__ */ y("div", {
168
+ children: [/* @__PURE__ */ b("div", {
168
169
  className: "flex h-24 items-end",
169
- children: /* @__PURE__ */ y("div", {
170
+ children: /* @__PURE__ */ b("div", {
170
171
  className: "w-8 rounded-t transition-all duration-500 ease-out",
171
172
  style: {
172
- height: `${V > 0 ? Math.max(e.bookings / V * 100, 5) : 5}%`,
173
- backgroundColor: x[t % x.length]
173
+ height: `${U > 0 ? Math.max(e.bookings / U * 100, 5) : 5}%`,
174
+ backgroundColor: S[t % S.length]
174
175
  },
175
176
  title: `${e.period}: ${e.bookings} bookings, $${e.revenue.toLocaleString()} revenue`
176
177
  })
177
- }), /* @__PURE__ */ y("p", {
178
+ }), /* @__PURE__ */ b("p", {
178
179
  className: "truncate text-xs text-text-secondary",
179
180
  children: e.period
180
181
  })]
181
182
  }, e.period))
182
- }), /* @__PURE__ */ y("p", {
183
+ }), /* @__PURE__ */ b("p", {
183
184
  className: "text-xs text-text-secondary",
184
- children: T("ggnomad.providerAnalytics.trendNote", "Bar height is booking volume per period; hover a bar for its exact bookings and revenue.")
185
+ children: E("ggnomad.providerAnalytics.trendNote", "Bar height is booking volume per period; hover a bar for its exact bookings and revenue.")
185
186
  })]
186
187
  })
187
188
  }),
188
- /* @__PURE__ */ y(i, {
189
- title: T("ggnomad.providerAnalytics.topListings", "Top listings"),
190
- children: B.topListings.length === 0 ? /* @__PURE__ */ y("p", {
189
+ /* @__PURE__ */ b(i, {
190
+ title: E("ggnomad.providerAnalytics.topListings", "Top listings"),
191
+ children: H.topListings.length === 0 ? /* @__PURE__ */ b("p", {
191
192
  className: "py-6 text-center text-sm text-text-secondary",
192
- children: T("ggnomad.providerAnalytics.topListingsEmpty", "No listing bookings in this range yet.")
193
- }) : /* @__PURE__ */ y("div", {
193
+ children: E("ggnomad.providerAnalytics.topListingsEmpty", "No listing bookings in this range yet.")
194
+ }) : /* @__PURE__ */ b("div", {
194
195
  className: "overflow-x-auto",
195
- children: /* @__PURE__ */ b("table", {
196
+ children: /* @__PURE__ */ x("table", {
196
197
  className: "w-full min-w-[560px] text-sm",
197
- children: [/* @__PURE__ */ y("thead", { children: /* @__PURE__ */ b("tr", {
198
+ children: [/* @__PURE__ */ b("thead", { children: /* @__PURE__ */ x("tr", {
198
199
  className: "border-b border-border-seam text-left text-text-secondary",
199
200
  children: [
200
- /* @__PURE__ */ y("th", {
201
+ /* @__PURE__ */ b("th", {
201
202
  className: "py-2 pr-3 font-medium",
202
- children: T("ggnomad.providerAnalytics.listing", "Listing")
203
+ children: E("ggnomad.providerAnalytics.listing", "Listing")
203
204
  }),
204
- /* @__PURE__ */ y("th", {
205
+ /* @__PURE__ */ b("th", {
205
206
  className: "py-2 pr-3 font-medium",
206
- children: T("ggnomad.providerAnalytics.type", "Type")
207
+ children: E("ggnomad.providerAnalytics.type", "Type")
207
208
  }),
208
- /* @__PURE__ */ y("th", {
209
+ /* @__PURE__ */ b("th", {
209
210
  className: "py-2 pr-3 text-right font-medium",
210
- children: T("ggnomad.providerAnalytics.bookings", "Bookings")
211
+ children: E("ggnomad.providerAnalytics.bookings", "Bookings")
211
212
  }),
212
- /* @__PURE__ */ y("th", {
213
+ /* @__PURE__ */ b("th", {
213
214
  className: "py-2 pr-3 text-right font-medium",
214
- children: T("ggnomad.providerAnalytics.revenue", "Revenue")
215
+ children: E("ggnomad.providerAnalytics.revenue", "Revenue")
215
216
  }),
216
- /* @__PURE__ */ y("th", {
217
+ /* @__PURE__ */ b("th", {
217
218
  className: "py-2 pr-3 text-right font-medium",
218
- children: T("ggnomad.providerAnalytics.cancellation", "Cancellation")
219
+ children: E("ggnomad.providerAnalytics.cancellation", "Cancellation")
219
220
  }),
220
- /* @__PURE__ */ y("th", {
221
+ /* @__PURE__ */ b("th", {
221
222
  className: "py-2 text-right font-medium",
222
- children: T("ggnomad.providerAnalytics.utilization", "Utilization")
223
+ children: E("ggnomad.providerAnalytics.utilization", "Utilization")
223
224
  })
224
225
  ]
225
- }) }), /* @__PURE__ */ y("tbody", { children: B.topListings.map((e) => /* @__PURE__ */ b("tr", {
226
+ }) }), /* @__PURE__ */ b("tbody", { children: H.topListings.map((e) => /* @__PURE__ */ x("tr", {
226
227
  className: "border-b border-border-seam/50 last:border-0",
227
228
  children: [
228
- /* @__PURE__ */ y("td", {
229
+ /* @__PURE__ */ b("td", {
229
230
  className: "py-2 pr-3 font-medium text-text-primary",
230
231
  children: e.listingName
231
232
  }),
232
- /* @__PURE__ */ y("td", {
233
+ /* @__PURE__ */ b("td", {
233
234
  className: "py-2 pr-3 text-text-secondary",
234
235
  children: e.listingType
235
236
  }),
236
- /* @__PURE__ */ y("td", {
237
+ /* @__PURE__ */ b("td", {
237
238
  className: "py-2 pr-3 text-right tabular-nums text-text-primary",
238
239
  children: e.bookings
239
240
  }),
240
- /* @__PURE__ */ b("td", {
241
+ /* @__PURE__ */ x("td", {
241
242
  className: "py-2 pr-3 text-right tabular-nums text-text-primary",
242
243
  children: ["$", e.revenue.toLocaleString()]
243
244
  }),
244
- /* @__PURE__ */ b("td", {
245
+ /* @__PURE__ */ x("td", {
245
246
  className: "py-2 pr-3 text-right tabular-nums text-text-primary",
246
247
  children: [(e.cancellationRate * 100).toFixed(0), "%"]
247
248
  }),
248
- /* @__PURE__ */ b("td", {
249
+ /* @__PURE__ */ x("td", {
249
250
  className: "py-2 text-right tabular-nums text-text-primary",
250
251
  children: [(e.capacityUtilizationRate * 100).toFixed(0), "%"]
251
252
  })
@@ -254,14 +255,14 @@ function T() {
254
255
  })
255
256
  })
256
257
  })
257
- ] }) : /* @__PURE__ */ y(h, {
258
+ ] }) : /* @__PURE__ */ b(g, {
258
259
  illustration: "empty-people",
259
- title: T("ggnomad.providerAnalytics.noDataTitle", "No bookings in this range"),
260
- description: T("ggnomad.providerAnalytics.noDataDescription", "Try widening the date range — analytics appear here once bookings come in for your listings.")
260
+ title: E("ggnomad.providerAnalytics.noDataTitle", "No bookings in this range"),
261
+ description: E("ggnomad.providerAnalytics.noDataDescription", "Try widening the date range — analytics appear here once bookings come in for your listings.")
261
262
  })] })]
262
263
  });
263
264
  }
264
265
  //#endregion
265
- export { T as ProviderAnalyticsPage };
266
+ export { E as ProviderAnalyticsPage };
266
267
 
267
268
  //# sourceMappingURL=ProviderAnalyticsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderAnalyticsPage.js","names":[],"sources":["../../../src/ggnomad/pages/ProviderAnalyticsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { BarChart3, CalendarCheck, DollarSign, Gauge, TrendingUp, XCircle } from 'lucide-react';\nimport { IllustratedEmptyState, PagePurpose, Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_MY_PROVIDERS_QUERY, GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatCard } from '../components';\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\n\nconst INPUT_CLASSES =\n 'mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg';\n\ntype MyProvider = { id: string; name: string; type: string; status: string };\n\ntype AnalyticsPoint = { period: string; bookings: number; revenue: number };\n\ntype ListingAnalytics = {\n listingId: string;\n listingType: string;\n listingName: string;\n bookings: number;\n revenue: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n};\n\ntype ProviderAnalytics = {\n providerId: string;\n providerName: string;\n totalBookings: number;\n totalRevenue: number;\n cancelledBookings: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n bookingsOverTime: AnalyticsPoint[];\n topListings: ListingAnalytics[];\n};\n\nfunction toISODate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\nfunction daysAgo(days: number): Date {\n return new Date(Date.now() - days * 24 * 60 * 60 * 1000);\n}\n\n/**\n * BOFF-4390 — provider-facing analytics dashboard: bookings-over-time,\n * revenue, cancellation rate and capacity utilization, plus a per-listing\n * drill-down table. Self-scoped: only providers the calling actor created\n * are listed, and the analytics call is owner-verified server-side — a\n * provider can never see another provider's numbers. Forecasting and\n * custom-report building are explicitly out of scope for this core; see the\n * `myGgnomadProviderAnalytics` schema doc comment.\n */\nexport function ProviderAnalyticsPage() {\n const { t } = useI18n();\n\n const { data: providersData, loading: providersLoading } = useQuery(\n GGNOMAD_MY_PROVIDERS_QUERY as TypedDocumentNode<\n { myGgnomadProviders: MyProvider[] },\n Record<string, never>\n >\n );\n const providers = providersData?.myGgnomadProviders ?? [];\n\n const [selectedProviderId, setSelectedProviderId] = useState('');\n // `dateFrom`/`dateTo` are the plain YYYY-MM-DD strings bound to the <input\n // type=\"date\"> controls. They are NOT sent to GraphQL as-is: a bare date\n // string parses to UTC midnight, so using \"today\" as dateTo would exclude\n // every booking made later that same day. The query variables below\n // convert dateFrom to the start of that UTC day and dateTo to the end of\n // it, so \"today\" always includes today's activity.\n const [dateFrom, setDateFrom] = useState(() => toISODate(daysAgo(30)));\n const [dateTo, setDateTo] = useState(() => toISODate(new Date()));\n const [granularity, setGranularity] = useState<'day' | 'week' | 'month'>('day');\n\n const activeProviderId = selectedProviderId || providers[0]?.id || '';\n\n const { data: analyticsData, loading: analyticsLoading } = useQuery(\n GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY as TypedDocumentNode<\n { myGgnomadProviderAnalytics: ProviderAnalytics },\n { providerId: string; dateFrom?: string; dateTo?: string; granularity?: string }\n >,\n {\n variables: {\n providerId: activeProviderId,\n dateFrom: `${dateFrom}T00:00:00.000Z`,\n dateTo: `${dateTo}T23:59:59.999Z`,\n granularity,\n },\n skip: !activeProviderId,\n }\n );\n const analytics = analyticsData?.myGgnomadProviderAnalytics;\n\n const maxTrend = useMemo(() => {\n const points = analytics?.bookingsOverTime ?? [];\n return points.length > 0 ? Math.max(...points.map((p) => p.bookings)) : 0;\n }, [analytics?.bookingsOverTime]);\n\n return (\n <PageShell\n title={t('ggnomad.providerAnalytics.title', 'Provider Analytics')}\n titleAccent={t('ggnomad.providerAnalytics.title.accent', 'Analytics')}\n subtitle={t(\n 'ggnomad.providerAnalytics.subtitle',\n 'Your bookings, revenue and listing performance'\n )}\n >\n <PagePurpose icon={<TrendingUp className=\"size-4\" />}>\n {t(\n 'ggnomad.providerAnalytics.purpose',\n 'A self-service dashboard for providers: bookings and revenue over time, cancellation rate, capacity utilization, and a per-listing breakdown — scoped to only the listings you own. Pick a date range and a listing granularity to drill in.'\n )}\n </PagePurpose>\n\n {!providersLoading && providers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.emptyTitle', 'No provider profile yet')}\n description={t(\n 'ggnomad.providerAnalytics.emptyDescription',\n 'Once you create a GGNomad provider profile, your bookings and revenue analytics will appear here.'\n )}\n />\n ) : (\n <>\n <SectionCard title={t('ggnomad.providerAnalytics.filters', 'Filters')}>\n <div className=\"grid gap-3 sm:grid-cols-4\">\n <div>\n <label\n htmlFor=\"provider-analytics-provider\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.provider', 'Provider')}\n </label>\n <select\n id=\"provider-analytics-provider\"\n value={activeProviderId}\n onChange={(e) => setSelectedProviderId(e.target.value)}\n className={INPUT_CLASSES}\n >\n {providers.map((provider) => (\n <option key={provider.id} value={provider.id}>\n {provider.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-from\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.from', 'From')}\n </label>\n <input\n id=\"provider-analytics-from\"\n type=\"date\"\n value={dateFrom}\n max={dateTo}\n onChange={(e) => setDateFrom(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-to\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.to', 'To')}\n </label>\n <input\n id=\"provider-analytics-to\"\n type=\"date\"\n value={dateTo}\n min={dateFrom}\n max={toISODate(new Date())}\n onChange={(e) => setDateTo(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-granularity\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.granularity', 'Granularity')}\n </label>\n <select\n id=\"provider-analytics-granularity\"\n value={granularity}\n onChange={(e) => setGranularity(e.target.value as 'day' | 'week' | 'month')}\n className={INPUT_CLASSES}\n >\n <option value=\"day\">{t('ggnomad.providerAnalytics.day', 'Day')}</option>\n <option value=\"week\">{t('ggnomad.providerAnalytics.week', 'Week')}</option>\n <option value=\"month\">{t('ggnomad.providerAnalytics.month', 'Month')}</option>\n </select>\n </div>\n </div>\n </SectionCard>\n\n {analyticsLoading ? (\n <div className=\"flex justify-center py-12\">\n <Spinner size=\"lg\" />\n </div>\n ) : !analytics ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.noDataTitle', 'No bookings in this range')}\n description={t(\n 'ggnomad.providerAnalytics.noDataDescription',\n 'Try widening the date range — analytics appear here once bookings come in for your listings.'\n )}\n />\n ) : (\n <>\n <section className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n hero\n label={t('ggnomad.providerAnalytics.totalBookings', 'Bookings')}\n value={analytics.totalBookings.toLocaleString()}\n icon={<CalendarCheck />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.totalRevenue', 'Revenue')}\n value={`$${Number(analytics.totalRevenue).toLocaleString()}`}\n icon={<DollarSign />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.cancellationRate', 'Cancellation rate')}\n value={`${(analytics.cancellationRate * 100).toFixed(1)}%`}\n icon={<XCircle />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.capacityUtilization', 'Capacity utilization')}\n value={`${(analytics.capacityUtilizationRate * 100).toFixed(1)}%`}\n icon={<Gauge />}\n />\n </section>\n\n <SectionCard\n title={t('ggnomad.providerAnalytics.bookingsOverTime', 'Bookings over time')}\n >\n {analytics.bookingsOverTime.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <span className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary\">\n <BarChart3 className=\"size-5\" />\n </span>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.providerAnalytics.trendEmpty', 'No bookings in this window yet')}\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-end gap-2 overflow-x-auto pb-2\">\n {analytics.bookingsOverTime.map((point, index) => {\n const pct =\n maxTrend > 0 ? Math.max((point.bookings / maxTrend) * 100, 5) : 5;\n return (\n <div key={point.period} className=\"flex flex-col items-center gap-1\">\n <div className=\"flex h-24 items-end\">\n <div\n className=\"w-8 rounded-t transition-all duration-500 ease-out\"\n style={{\n height: `${pct}%`,\n backgroundColor: CHART_COLORS[index % CHART_COLORS.length],\n }}\n title={`${point.period}: ${point.bookings} bookings, $${point.revenue.toLocaleString()} revenue`}\n />\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{point.period}</p>\n </div>\n );\n })}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.trendNote',\n 'Bar height is booking volume per period; hover a bar for its exact bookings and revenue.'\n )}\n </p>\n </div>\n )}\n </SectionCard>\n\n <SectionCard title={t('ggnomad.providerAnalytics.topListings', 'Top listings')}>\n {analytics.topListings.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.topListingsEmpty',\n 'No listing bookings in this range yet.'\n )}\n </p>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[560px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.listing', 'Listing')}\n </th>\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.type', 'Type')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.bookings', 'Bookings')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.revenue', 'Revenue')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.cancellation', 'Cancellation')}\n </th>\n <th className=\"py-2 text-right font-medium\">\n {t('ggnomad.providerAnalytics.utilization', 'Utilization')}\n </th>\n </tr>\n </thead>\n <tbody>\n {analytics.topListings.map((listing) => (\n <tr\n key={listing.listingId}\n className=\"border-b border-border-seam/50 last:border-0\"\n >\n <td className=\"py-2 pr-3 font-medium text-text-primary\">\n {listing.listingName}\n </td>\n <td className=\"py-2 pr-3 text-text-secondary\">{listing.listingType}</td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {listing.bookings}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n ${listing.revenue.toLocaleString()}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {(listing.cancellationRate * 100).toFixed(0)}%\n </td>\n <td className=\"py-2 text-right tabular-nums text-text-primary\">\n {(listing.capacityUtilizationRate * 100).toFixed(0)}%\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </SectionCard>\n </>\n )}\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAM,IAAe;CACnB;CACA;CACA;CACA;CACA;AACF,GAEM,IACJ;AA4BF,SAAS,EAAU,GAAoB;CACrC,OAAO,EAAK,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;AACvC;AAEA,SAAS,EAAQ,GAAoB;CACnC,uBAAO,IAAI,KAAK,KAAK,IAAI,IAAI,IAAO,KAAK,KAAK,KAAK,GAAI;AACzD;AAWA,SAAgB,IAAwB;CACtC,IAAM,EAAE,SAAM,EAAQ,GAEhB,EAAE,MAAM,GAAe,SAAS,MAAqB,EACzD,CAIF,GACM,IAAY,GAAe,sBAAsB,CAAC,GAElD,CAAC,GAAoB,KAAyB,EAAS,EAAE,GAOzD,CAAC,GAAU,KAAe,QAAe,EAAU,EAAQ,EAAE,CAAC,CAAC,GAC/D,CAAC,GAAQ,KAAa,QAAe,kBAAU,IAAI,KAAK,CAAC,CAAC,GAC1D,CAAC,GAAa,KAAkB,EAAmC,KAAK,GAExE,IAAmB,KAAsB,EAAU,EAAE,EAAE,MAAM,IAE7D,EAAE,MAAM,GAAe,SAAS,MAAqB,EACzD,GAIA;EACE,WAAW;GACT,YAAY;GACZ,UAAU,GAAG,EAAS;GACtB,QAAQ,GAAG,EAAO;GAClB;EACF;EACA,MAAM,CAAC;CACT,CACF,GACM,IAAY,GAAe,4BAE3B,IAAW,QAAc;EAC7B,IAAM,IAAS,GAAW,oBAAoB,CAAC;EAC/C,OAAO,EAAO,SAAS,IAAI,KAAK,IAAI,GAAG,EAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,IAAI;CAC1E,GAAG,CAAC,GAAW,gBAAgB,CAAC;CAEhC,OACE,kBAAC,GAAD;EACE,OAAO,EAAE,mCAAmC,oBAAoB;EAChE,aAAa,EAAE,0CAA0C,WAAW;EACpE,UAAU,EACR,sCACA,gDACF;YANF,CAQE,kBAAC,GAAD;GAAa,MAAM,kBAAC,GAAD,EAAY,WAAU,SAAU,CAAA;aAChD,EACC,qCACA,8OACF;EACW,CAAA,GAEZ,CAAC,KAAoB,EAAU,WAAW,IACzC,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,wCAAwC,yBAAyB;GAC1E,aAAa,EACX,8CACA,mGACF;EACD,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAa,OAAO,EAAE,qCAAqC,SAAS;aAClE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MACE,SAAQ;MACR,WAAU;gBAET,EAAE,sCAAsC,UAAU;KAC9C,CAAA,GACP,kBAAC,UAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MAAM,EAAsB,EAAE,OAAO,KAAK;MACrD,WAAW;gBAEV,EAAU,KAAK,MACd,kBAAC,UAAD;OAA0B,OAAO,EAAS;iBACvC,EAAS;MACJ,GAFK,EAAS,EAEd,CACT;KACK,CAAA,CACL,EAAA,CAAA;KACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MACE,SAAQ;MACR,WAAU;gBAET,EAAE,kCAAkC,MAAM;KACtC,CAAA,GACP,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,OAAO;MACP,KAAK;MACL,WAAW,MAAM,EAAY,EAAE,OAAO,KAAK;MAC3C,WAAW;KACZ,CAAA,CACE,EAAA,CAAA;KACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MACE,SAAQ;MACR,WAAU;gBAET,EAAE,gCAAgC,IAAI;KAClC,CAAA,GACP,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,OAAO;MACP,KAAK;MACL,KAAK,kBAAU,IAAI,KAAK,CAAC;MACzB,WAAW,MAAM,EAAU,EAAE,OAAO,KAAK;MACzC,WAAW;KACZ,CAAA,CACE,EAAA,CAAA;KACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MACE,SAAQ;MACR,WAAU;gBAET,EAAE,yCAAyC,aAAa;KACpD,CAAA,GACP,kBAAC,UAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAiC;MAC1E,WAAW;gBAJb;OAME,kBAAC,UAAD;QAAQ,OAAM;kBAAO,EAAE,iCAAiC,KAAK;OAAU,CAAA;OACvE,kBAAC,UAAD;QAAQ,OAAM;kBAAQ,EAAE,kCAAkC,MAAM;OAAU,CAAA;OAC1E,kBAAC,UAAD;QAAQ,OAAM;kBAAS,EAAE,mCAAmC,OAAO;OAAU,CAAA;MACvE;OACL,EAAA,CAAA;IACF;;EACM,CAAA,GAEZ,IACC,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA;EACjB,CAAA,IACF,IAUH,kBAAA,GAAA,EAAA,UAAA;GACE,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,GAAD;MACE,MAAA;MACA,OAAO,EAAE,2CAA2C,UAAU;MAC9D,OAAO,EAAU,cAAc,eAAe;MAC9C,MAAM,kBAAC,GAAD,CAAgB,CAAA;KACvB,CAAA;KACD,kBAAC,GAAD;MACE,OAAO,EAAE,0CAA0C,SAAS;MAC5D,OAAO,IAAI,OAAO,EAAU,YAAY,CAAC,CAAC,eAAe;MACzD,MAAM,kBAAC,GAAD,CAAa,CAAA;KACpB,CAAA;KACD,kBAAC,GAAD;MACE,OAAO,EAAE,8CAA8C,mBAAmB;MAC1E,OAAO,IAAI,EAAU,mBAAmB,IAAA,CAAK,QAAQ,CAAC,EAAE;MACxD,MAAM,kBAAC,GAAD,CAAU,CAAA;KACjB,CAAA;KACD,kBAAC,GAAD;MACE,OAAO,EAAE,iDAAiD,sBAAsB;MAChF,OAAO,IAAI,EAAU,0BAA0B,IAAA,CAAK,QAAQ,CAAC,EAAE;MAC/D,MAAM,kBAAC,GAAD,CAAQ,CAAA;KACf,CAAA;IACM;;GAET,kBAAC,GAAD;IACE,OAAO,EAAE,8CAA8C,oBAAoB;cAE1E,EAAU,iBAAiB,WAAW,IACrC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBACd,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA;KAC3B,CAAA,GACN,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAE,wCAAwC,gCAAgC;KAC1E,CAAA,CACA;SAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAU,iBAAiB,KAAK,GAAO,MAIpC,kBAAC,OAAD;OAAwB,WAAU;iBAAlC,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,OAAD;SACE,WAAU;SACV,OAAO;UACL,QAAQ,GAPhB,IAAW,IAAI,KAAK,IAAK,EAAM,WAAW,IAAY,KAAK,CAAC,IAAI,EAOzC;UACf,iBAAiB,EAAa,IAAQ,EAAa;SACrD;SACA,OAAO,GAAG,EAAM,OAAO,IAAI,EAAM,SAAS,cAAc,EAAM,QAAQ,eAAe,EAAE;QACxF,CAAA;OACE,CAAA,GACL,kBAAC,KAAD;QAAG,WAAU;kBAAwC,EAAM;OAAU,CAAA,CAClE;SAZK,EAAM,MAYX,CAER;KACE,CAAA,GACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,uCACA,0FACF;KACC,CAAA,CACA;;GAEI,CAAA;GAEb,kBAAC,GAAD;IAAa,OAAO,EAAE,yCAAyC,cAAc;cAC1E,EAAU,YAAY,WAAW,IAChC,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,8CACA,wCACF;IACC,CAAA,IAEH,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,qCAAqC,SAAS;QAC/C,CAAA;QACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,kCAAkC,MAAM;QACzC,CAAA;QACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,sCAAsC,UAAU;QACjD,CAAA;QACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,qCAAqC,SAAS;QAC/C,CAAA;QACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,0CAA0C,cAAc;QACzD,CAAA;QACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,yCAAyC,aAAa;QACvD,CAAA;OACF;SACC,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAU,YAAY,KAAK,MAC1B,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAQ;QACP,CAAA;QACJ,kBAAC,MAAD;SAAI,WAAU;mBAAiC,EAAQ;QAAgB,CAAA;QACvE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAQ;QACP,CAAA;QACJ,kBAAC,MAAD;SAAI,WAAU;mBAAd,CAAoE,KAChE,EAAQ,QAAQ,eAAe,CAC/B;;QACJ,kBAAC,MAAD;SAAI,WAAU;mBAAd,EACI,EAAQ,mBAAmB,IAAA,CAAK,QAAQ,CAAC,GAAE,GAC3C;;QACJ,kBAAC,MAAD;SAAI,WAAU;mBAAd,EACI,EAAQ,0BAA0B,IAAA,CAAK,QAAQ,CAAC,GAAE,GAClD;;OACF;SAnBG,EAAQ,SAmBX,CACL,EACI,CAAA,CACF;;IACJ,CAAA;GAEI,CAAA;EACb,EAAA,CAAA,IA7IF,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,yCAAyC,2BAA2B;GAC7E,aAAa,EACX,+CACA,8FACF;EACD,CAAA,CAwIH,EAAA,CAAA,CAEK;;AAEf"}
1
+ {"version":3,"file":"ProviderAnalyticsPage.js","names":[],"sources":["../../../src/ggnomad/pages/ProviderAnalyticsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { BarChart3, CalendarCheck, DollarSign, Gauge, TrendingUp, XCircle } from 'lucide-react';\nimport { IllustratedEmptyState, PagePurpose, Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_MY_PROVIDERS_QUERY, GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatCard } from '../components';\nimport { useFocusedRecordParam } from '../hooks/useFocusedRecordParam';\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\n\nconst INPUT_CLASSES =\n 'mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg';\n\ntype MyProvider = { id: string; name: string; type: string; status: string };\n\ntype AnalyticsPoint = { period: string; bookings: number; revenue: number };\n\ntype ListingAnalytics = {\n listingId: string;\n listingType: string;\n listingName: string;\n bookings: number;\n revenue: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n};\n\ntype ProviderAnalytics = {\n providerId: string;\n providerName: string;\n totalBookings: number;\n totalRevenue: number;\n cancelledBookings: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n bookingsOverTime: AnalyticsPoint[];\n topListings: ListingAnalytics[];\n};\n\nfunction toISODate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\nfunction daysAgo(days: number): Date {\n return new Date(Date.now() - days * 24 * 60 * 60 * 1000);\n}\n\n/**\n * BOFF-4390 — provider-facing analytics dashboard: bookings-over-time,\n * revenue, cancellation rate and capacity utilization, plus a per-listing\n * drill-down table. Self-scoped: only providers the calling actor created\n * are listed, and the analytics call is owner-verified server-side — a\n * provider can never see another provider's numbers. Forecasting and\n * custom-report building are explicitly out of scope for this core; see the\n * `myGgnomadProviderAnalytics` schema doc comment.\n */\nexport function ProviderAnalyticsPage() {\n const { t } = useI18n();\n\n const { data: providersData, loading: providersLoading } = useQuery(\n GGNOMAD_MY_PROVIDERS_QUERY as TypedDocumentNode<\n { myGgnomadProviders: MyProvider[] },\n Record<string, never>\n >\n );\n const providers = providersData?.myGgnomadProviders ?? [];\n\n // The chosen provider lives in `?id=` so the assistant's page context resolves\n // it (GGNOMAD_PROVIDER) and the view survives a reload or a shared link.\n const [focusedProviderId, setSelectedProviderId] = useFocusedRecordParam();\n const selectedProviderId = providers.some((provider) => provider.id === focusedProviderId)\n ? (focusedProviderId ?? '')\n : '';\n // `dateFrom`/`dateTo` are the plain YYYY-MM-DD strings bound to the <input\n // type=\"date\"> controls. They are NOT sent to GraphQL as-is: a bare date\n // string parses to UTC midnight, so using \"today\" as dateTo would exclude\n // every booking made later that same day. The query variables below\n // convert dateFrom to the start of that UTC day and dateTo to the end of\n // it, so \"today\" always includes today's activity.\n const [dateFrom, setDateFrom] = useState(() => toISODate(daysAgo(30)));\n const [dateTo, setDateTo] = useState(() => toISODate(new Date()));\n const [granularity, setGranularity] = useState<'day' | 'week' | 'month'>('day');\n\n const activeProviderId = selectedProviderId || providers[0]?.id || '';\n\n const { data: analyticsData, loading: analyticsLoading } = useQuery(\n GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY as TypedDocumentNode<\n { myGgnomadProviderAnalytics: ProviderAnalytics },\n { providerId: string; dateFrom?: string; dateTo?: string; granularity?: string }\n >,\n {\n variables: {\n providerId: activeProviderId,\n dateFrom: `${dateFrom}T00:00:00.000Z`,\n dateTo: `${dateTo}T23:59:59.999Z`,\n granularity,\n },\n skip: !activeProviderId,\n }\n );\n const analytics = analyticsData?.myGgnomadProviderAnalytics;\n\n const maxTrend = useMemo(() => {\n const points = analytics?.bookingsOverTime ?? [];\n return points.length > 0 ? Math.max(...points.map((p) => p.bookings)) : 0;\n }, [analytics?.bookingsOverTime]);\n\n return (\n <PageShell\n title={t('ggnomad.providerAnalytics.title', 'Provider Analytics')}\n titleAccent={t('ggnomad.providerAnalytics.title.accent', 'Analytics')}\n subtitle={t(\n 'ggnomad.providerAnalytics.subtitle',\n 'Your bookings, revenue and listing performance'\n )}\n >\n <PagePurpose icon={<TrendingUp className=\"size-4\" />}>\n {t(\n 'ggnomad.providerAnalytics.purpose',\n 'A self-service dashboard for providers: bookings and revenue over time, cancellation rate, capacity utilization, and a per-listing breakdown — scoped to only the listings you own. Pick a date range and a listing granularity to drill in.'\n )}\n </PagePurpose>\n\n {!providersLoading && providers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.emptyTitle', 'No provider profile yet')}\n description={t(\n 'ggnomad.providerAnalytics.emptyDescription',\n 'Once you create a GGNomad provider profile, your bookings and revenue analytics will appear here.'\n )}\n />\n ) : (\n <>\n <SectionCard title={t('ggnomad.providerAnalytics.filters', 'Filters')}>\n <div className=\"grid gap-3 sm:grid-cols-4\">\n <div>\n <label\n htmlFor=\"provider-analytics-provider\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.provider', 'Provider')}\n </label>\n <select\n id=\"provider-analytics-provider\"\n value={activeProviderId}\n onChange={(e) => setSelectedProviderId(e.target.value)}\n className={INPUT_CLASSES}\n >\n {providers.map((provider) => (\n <option key={provider.id} value={provider.id}>\n {provider.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-from\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.from', 'From')}\n </label>\n <input\n id=\"provider-analytics-from\"\n type=\"date\"\n value={dateFrom}\n max={dateTo}\n onChange={(e) => setDateFrom(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-to\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.to', 'To')}\n </label>\n <input\n id=\"provider-analytics-to\"\n type=\"date\"\n value={dateTo}\n min={dateFrom}\n max={toISODate(new Date())}\n onChange={(e) => setDateTo(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-granularity\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.granularity', 'Granularity')}\n </label>\n <select\n id=\"provider-analytics-granularity\"\n value={granularity}\n onChange={(e) => setGranularity(e.target.value as 'day' | 'week' | 'month')}\n className={INPUT_CLASSES}\n >\n <option value=\"day\">{t('ggnomad.providerAnalytics.day', 'Day')}</option>\n <option value=\"week\">{t('ggnomad.providerAnalytics.week', 'Week')}</option>\n <option value=\"month\">{t('ggnomad.providerAnalytics.month', 'Month')}</option>\n </select>\n </div>\n </div>\n </SectionCard>\n\n {analyticsLoading ? (\n <div className=\"flex justify-center py-12\">\n <Spinner size=\"lg\" />\n </div>\n ) : !analytics ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.noDataTitle', 'No bookings in this range')}\n description={t(\n 'ggnomad.providerAnalytics.noDataDescription',\n 'Try widening the date range — analytics appear here once bookings come in for your listings.'\n )}\n />\n ) : (\n <>\n <section className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n hero\n label={t('ggnomad.providerAnalytics.totalBookings', 'Bookings')}\n value={analytics.totalBookings.toLocaleString()}\n icon={<CalendarCheck />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.totalRevenue', 'Revenue')}\n value={`$${Number(analytics.totalRevenue).toLocaleString()}`}\n icon={<DollarSign />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.cancellationRate', 'Cancellation rate')}\n value={`${(analytics.cancellationRate * 100).toFixed(1)}%`}\n icon={<XCircle />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.capacityUtilization', 'Capacity utilization')}\n value={`${(analytics.capacityUtilizationRate * 100).toFixed(1)}%`}\n icon={<Gauge />}\n />\n </section>\n\n <SectionCard\n title={t('ggnomad.providerAnalytics.bookingsOverTime', 'Bookings over time')}\n >\n {analytics.bookingsOverTime.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <span className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary\">\n <BarChart3 className=\"size-5\" />\n </span>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.providerAnalytics.trendEmpty', 'No bookings in this window yet')}\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-end gap-2 overflow-x-auto pb-2\">\n {analytics.bookingsOverTime.map((point, index) => {\n const pct =\n maxTrend > 0 ? Math.max((point.bookings / maxTrend) * 100, 5) : 5;\n return (\n <div key={point.period} className=\"flex flex-col items-center gap-1\">\n <div className=\"flex h-24 items-end\">\n <div\n className=\"w-8 rounded-t transition-all duration-500 ease-out\"\n style={{\n height: `${pct}%`,\n backgroundColor: CHART_COLORS[index % CHART_COLORS.length],\n }}\n title={`${point.period}: ${point.bookings} bookings, $${point.revenue.toLocaleString()} revenue`}\n />\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{point.period}</p>\n </div>\n );\n })}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.trendNote',\n 'Bar height is booking volume per period; hover a bar for its exact bookings and revenue.'\n )}\n </p>\n </div>\n )}\n </SectionCard>\n\n <SectionCard title={t('ggnomad.providerAnalytics.topListings', 'Top listings')}>\n {analytics.topListings.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.topListingsEmpty',\n 'No listing bookings in this range yet.'\n )}\n </p>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[560px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.listing', 'Listing')}\n </th>\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.type', 'Type')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.bookings', 'Bookings')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.revenue', 'Revenue')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.cancellation', 'Cancellation')}\n </th>\n <th className=\"py-2 text-right font-medium\">\n {t('ggnomad.providerAnalytics.utilization', 'Utilization')}\n </th>\n </tr>\n </thead>\n <tbody>\n {analytics.topListings.map((listing) => (\n <tr\n key={listing.listingId}\n className=\"border-b border-border-seam/50 last:border-0\"\n >\n <td className=\"py-2 pr-3 font-medium text-text-primary\">\n {listing.listingName}\n </td>\n <td className=\"py-2 pr-3 text-text-secondary\">{listing.listingType}</td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {listing.bookings}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n ${listing.revenue.toLocaleString()}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {(listing.cancellationRate * 100).toFixed(0)}%\n </td>\n <td className=\"py-2 text-right tabular-nums text-text-primary\">\n {(listing.capacityUtilizationRate * 100).toFixed(0)}%\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </SectionCard>\n </>\n )}\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * What the GGNomad assistant does when a quota refuses it.
3
+ *
4
+ * Two allowances gate the assistant, both provisioned per plan in
5
+ * global-billing-svc: `ggnomad.ai.credits` (the per-token AI credit budget,
6
+ * charged after each answer) and `ggnomad.sandbox.environments` (the sandbox
7
+ * the assistant runs in). When either is used up the user must be told plainly
8
+ * and shown the way to more, never left with a generic failure:
9
+ *
10
+ * - the shell's `QuotaUpgradeBridge` (ggnomad-app) listens for
11
+ * `ggnomad:quota-exhausted` and opens ONE upgrade dialog with GGNomad's copy;
12
+ * - the chat itself shows an explicit "AI credits used up" answer instead of
13
+ * the gateway's raw refusal text.
14
+ *
15
+ * The transport can see the same wall twice in one send (the advisory credits
16
+ * preflight and the turn itself), so the dialog is announced at most once per
17
+ * {@link ANNOUNCE_WINDOW_MS}: one refusal, one dialog, no stacked prompts.
18
+ */
19
+ /** The product id the shared assistant and the shell's bridge use for GGNomad. */
20
+ export declare const GGNOMAD_ASSISTANT_PRODUCT = "ggnomad";
21
+ /** Quota names in global-billing-svc that gate the GGNomad assistant. */
22
+ export declare const GGNOMAD_AI_CREDITS_QUOTA = "ggnomad.ai.credits";
23
+ export declare const GGNOMAD_SANDBOX_QUOTA = "ggnomad.sandbox.environments";
24
+ /**
25
+ * The shared assistant's product-namespaced window events (fe-libs
26
+ * `quotaExhaustedEventName` / `aiCreditsUpdatedEventName`: the shared code owns
27
+ * the mechanism, the product owns the `<product>:` namespace). The shell's
28
+ * `QuotaUpgradeBridge` listens for the first; a credits meter for the second.
29
+ */
30
+ export declare const QUOTA_EXHAUSTED_EVENT = "ggnomad:quota-exhausted";
31
+ export declare const AI_CREDITS_UPDATED_EVENT = "ggnomad:ai-credits-updated";
32
+ /** Repeated refusals inside this window open no further dialog. */
33
+ export declare const ANNOUNCE_WINDOW_MS = 10000;
34
+ export type AssistantQuotaWall = 'ai-credits' | 'sandbox';
35
+ /** English fallbacks; the i18n keys are looked up first. */
36
+ export declare const QUOTA_MESSAGES: Record<AssistantQuotaWall, {
37
+ key: string;
38
+ fallback: string;
39
+ }>;
40
+ /**
41
+ * Which assistant wall an error is, or null when it is not an exhausted quota.
42
+ *
43
+ * A quota that was never provisioned (`QUOTA_NOT_PROVISIONED`) is not
44
+ * exhaustion: buying more would not unblock it, so it is left to the generic
45
+ * error path rather than shown as "credits used up".
46
+ */
47
+ export declare function assistantQuotaWallOf(error: unknown): AssistantQuotaWall | null;
48
+ /**
49
+ * Every `extensions.quotaName` the gateway attached to a refusal, wherever the
50
+ * raw-fetch transport put the GraphQL errors (`errors`, `result.errors`,
51
+ * `graphQLErrors`, or the error's `cause`).
52
+ */
53
+ export declare function quotaNamesIn(error: unknown): string[];
54
+ /** Opens the shell's upgrade dialog for this wall, at most once per window. */
55
+ export declare function announceAssistantQuotaWall(wall: AssistantQuotaWall, now?: number): boolean;
56
+ /** Test seam: forget the last announcement. */
57
+ export declare function resetAssistantQuotaAnnouncements(): void;
58
+ /** `onQuotaExhausted` for the shared transport (the advisory credits preflight). */
59
+ export declare function handleAssistantQuotaExhausted(error: unknown): void;
60
+ /** `onCreditsUpdated` for the shared transport: lets a credits meter refetch. */
61
+ export declare function handleAssistantCreditsUpdated(): void;
62
+ type Translate = (key: string, fallback: string) => string;
63
+ /**
64
+ * The error a failed turn rethrows. A quota wall becomes an explicit message
65
+ * (and announces the dialog); anything else is returned unchanged.
66
+ */
67
+ export declare function toAssistantTurnError(error: unknown, translate?: Translate): unknown;
68
+ export {};
69
+ //# sourceMappingURL=assistantQuota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistantQuota.d.ts","sourceRoot":"","sources":["../../src/services/assistantQuota.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,4BAAiD,CAAC;AACpF,eAAO,MAAM,wBAAwB,+BAAoD,CAAC;AAE1F,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,SAAS,CAAC;AAE1D,4DAA4D;AAC5D,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,kBAAkB,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAWxF,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAI9E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAErD;AA+BD,+EAA+E;AAC/E,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,kBAAkB,EACxB,GAAG,GAAE,MAAmB,GACvB,OAAO,CAOT;AAED,+CAA+C;AAC/C,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED,oFAAoF;AACpF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAElE;AAED,iFAAiF;AACjF,wBAAgB,6BAA6B,IAAI,IAAI,CAGpD;AAED,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;AAE3D;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,OAAO,CAQnF"}